1: <?php
2:
3: /**
4: * This file contains the base class cHTML for all HTML classes.
5: *
6: * @package Core
7: * @subpackage GUI_HTML
8: *
9: * @author Simon Sprankel
10: * @copyright four for business AG <www.4fb.de>
11: * @license http://www.contenido.org/license/LIZENZ.txt
12: * @link http://www.4fb.de
13: * @link http://www.contenido.org
14: */
15:
16: defined('CON_FRAMEWORK') || die('Illegal call: Missing framework initialization - request aborted.');
17:
18: /**
19: * Base class for all CONTENIDO HTML classes
20: *
21: * @package Core
22: * @subpackage GUI_HTML
23: */
24: class cHTML {
25:
26: /**
27: * Id attribute counter, used to generate unique values for id-attributes
28: *
29: * @var int
30: */
31: protected static $_idCounter = 0;
32:
33: /**
34: * Flag to generate XHTML valid elements
35: *
36: * @var bool
37: */
38: protected static $_generateXHTML;
39:
40: /**
41: * Storage of the open SGML tag template
42: *
43: * @var string
44: */
45: protected $_skeletonOpen = '<%s%s>';
46:
47: /**
48: * Storage of a single SGML tag template
49: *
50: * @var string
51: */
52: protected $_skeletonSingle;
53:
54: /**
55: * Storage of the close SGML tag
56: *
57: * @var string
58: */
59: protected $_skeletonClose = '</%s>';
60:
61: /**
62: * Defines which tag to use
63: *
64: * @var string
65: */
66: protected $_tag;
67:
68: /**
69: * Defines the style definitions
70: *
71: * @var array
72: */
73: protected $_styleDefs = array();
74:
75: /**
76: * Defines all scripts which are required by the current element
77: *
78: * @var array
79: */
80: protected $_requiredScripts = array();
81:
82: /**
83: * Defines if the current tag is a contentless tag
84: *
85: * @var bool
86: */
87: protected $_contentlessTag = true;
88:
89: /**
90: * Defines which JS events contain which scripts
91: *
92: * @var array
93: */
94: protected $_eventDefinitions = array();
95:
96: /**
97: * Style definitions
98: *
99: * @var array
100: */
101: protected $_styleDefinitions = array();
102:
103: /**
104: * Attributes
105: *
106: * @var array
107: */
108: protected $_attributes;
109:
110: /**
111: * The content itself
112: *
113: * @var string
114: */
115: protected $_content;
116:
117: /**
118: * Constructor to create an instance of this class.
119: *
120: * @param array $attributes [optional]
121: * Associative array of table tag attributes
122: */
123: public function __construct(array $attributes = NULL) {
124: if (!is_null($attributes)) {
125: $this->setAttributes($attributes);
126: }
127:
128: if (self::$_generateXHTML === NULL) {
129: if (getEffectiveSetting('generator', 'xhtml', 'false') == 'true') {
130: self::$_generateXHTML = true;
131: } else {
132: self::$_generateXHTML = false;
133: }
134: }
135:
136: if (self::$_generateXHTML === true) {
137: $this->_skeletonSingle = '<%s%s />';
138: } else {
139: $this->_skeletonSingle = '<%s%s>';
140: }
141:
142: if (isset($attributes['id']) === false) {
143: $this->advanceID();
144: }
145: }
146:
147: /**
148: * Setter for static $_generateXHTML property
149: *
150: * @param bool $value
151: */
152: public static function setGenerateXHTML($value) {
153: self::$_generateXHTML = (bool) $value;
154: }
155:
156: /**
157: * Advances to the next ID available in the system.
158: *
159: * This function is useful if you need to use HTML elements
160: * in a loop, but don't want to re-create new objects each time.
161: *
162: * @return cHTML
163: * $this for chaining
164: */
165: public function advanceID() {
166: self::$_idCounter++;
167: return $this->updateAttribute('id', 'm' . self::$_idCounter);
168: }
169:
170: /**
171: * Returns the current ID
172: *
173: * @return string
174: * current ID
175: */
176: public function getID() {
177: return $this->getAttribute('id');
178: }
179:
180: /**
181: * Sets the HTML tag to $tag
182: *
183: * @param string $tag
184: * The new tag
185: * @return cHTML
186: * $this for chaining
187: */
188: public function setTag($tag) {
189: $this->_tag = $tag;
190:
191: return $this;
192: }
193:
194: /**
195: * Sets the alt and title attributes
196: *
197: * Sets the "alt" and "title" tags. Usually, "alt" is used
198: * for accessibility and "title" for mouse overs.
199: *
200: * To set the text for all browsers for mouse over, set "alt"
201: * and "title". IE behaves incorrectly and shows "alt" on
202: * mouse over. Mozilla browsers only show "title" as mouse over.
203: *
204: * @param string $alt
205: * Text to set as the "alt" and "title" attribute
206: * @param bool $setTitle [optional]
207: * Whether title attribute should be set, too (optional, default: true)
208: * @return cHTML
209: * $this for chaining
210: */
211: public function setAlt($alt, $setTitle = true) {
212: $attributes = array('alt' => $alt, 'title' => $alt);
213:
214: if ($setTitle === false) {
215: unset($attributes['title']);
216: }
217:
218: return $this->updateAttributes($attributes);
219: }
220:
221: /**
222: * Sets the ID class
223: *
224: * @param string $id
225: * Text to set as the "id"
226: * @return cHTML
227: * $this for chaining
228: */
229: public function setID($id) {
230: if ($id != "") {
231: return $this->updateAttribute('id', $id);
232: } else {
233: return $this;
234: }
235:
236: }
237:
238: /**
239: * Sets the CSS class
240: *
241: * @param string $class
242: * Text to set as the "class" attribute
243: * @return cHTML
244: * $this for chaining
245: */
246: public function setClass($class) {
247: if ($class != "") {
248: return $this->updateAttribute('class', $class);
249: } else {
250: return $this;
251: }
252: }
253:
254: /**
255: * Sets the CSS style
256: *
257: * @param string $style
258: * Text to set as the "style" attribute
259: * @return cHTML
260: * $this for chaining
261: */
262: public function setStyle($style) {
263: return $this->updateAttribute('style', $style);
264: }
265:
266: /**
267: * Adds an "on???" javascript event handler
268: *
269: * example:
270: * $item->setEvent('change', 'document.forms[0].submit');
271: *
272: * @param string $event
273: * Type of the event, e.g. "change" for "onchange"
274: * @param string $action
275: * Function or action to call (JavaScript Code)
276: * @return cHTML
277: * $this for chaining
278: */
279: public function setEvent($event, $action) {
280: if (cString::getPartOfString($event, 0, 2) !== 'on' && $event != 'disabled') {
281: return $this->updateAttribute('on' . $event, conHtmlSpecialChars($action));
282: } else {
283: return $this->updateAttribute($event, conHtmlSpecialChars($action));
284: }
285: }
286:
287: /**
288: * Removes an event handler
289: *
290: * example:
291: * $item->unsetEvent('change');
292: *
293: * @param string $event
294: * Type of the event
295: * @return cHTML
296: * $this for chaining
297: */
298: public function unsetEvent($event) {
299: if (cString::getPartOfString($event, 0, 2) !== 'on') {
300: return $this->removeAttribute('on' . $event);
301: } else {
302: return $this->removeAttribute($event);
303: }
304: }
305:
306: /**
307: * Fills the open SGML tag skeleton
308: *
309: * fillSkeleton fills the SGML opener tag with the
310: * specified attributes. Attributes need to be passed
311: * in the stringyfied variant.
312: *
313: * @param string $attributes
314: * Attributes to set
315: * @return string
316: * filled SGML opener skeleton
317: */
318: public function fillSkeleton($attributes) {
319: if ($this->_contentlessTag == true) {
320: return sprintf($this->_skeletonSingle, $this->_tag, $attributes);
321: } else {
322: return sprintf($this->_skeletonOpen, $this->_tag, $attributes);
323: }
324: }
325:
326: /**
327: * Fills the close skeleton
328: *
329: * @return string
330: * filled SGML closer skeleton
331: */
332: public function fillCloseSkeleton() {
333: return sprintf($this->_skeletonClose, $this->_tag);
334: }
335:
336: /**
337: * Appends the given style definition to the HTML element.
338: * Example usage:
339: * $element->appendStyleDefinition('margin', '5px');
340: *
341: * @param string $property
342: * the property name, e.g. 'margin'
343: * @param string $value
344: * the value of the property, e.g. '5px'
345: * @return cHTML
346: * $this for chaining
347: */
348: public function appendStyleDefinition($property, $value) {
349: if (cString::getPartOfString($value, -1) === ';') {
350: $value = cString::getPartOfString($value, 0, cString::getStringLength($value) - 1);
351: }
352: $this->_styleDefinitions[$property] = $value;
353:
354: return $this;
355: }
356:
357: /**
358: * Appends the given style definitions to the HTML element.
359: * Example usage:
360: * $element->appendStyleDefinitions(array(
361: * 'margin' => '5px',
362: * 'padding' => '0'
363: * ));
364: *
365: * @param string $styles
366: * the styles to append
367: * @return cHTML
368: * $this for chaining
369: */
370: public function appendStyleDefinitions(array $styles) {
371: foreach ($styles as $property => $value) {
372: $this->appendStyleDefinition($property, $value);
373: }
374:
375: return $this;
376: }
377:
378: /**
379: * Adds a required script to the current element.
380: * Anyway, scripts are not included twice.
381: *
382: * @param string $script
383: * the script to include
384: * @return cHTML
385: * $this for chaining
386: */
387: public function addRequiredScript($script) {
388: if (!is_array($this->_requiredScripts)) {
389: $this->_requiredScripts = array();
390: }
391:
392: $this->_requiredScripts[] = $script;
393: $this->_requiredScripts = array_unique($this->_requiredScripts);
394:
395: return $this;
396: }
397:
398: /**
399: * Sets the content of the object
400: *
401: * @param string|object|array $content
402: * String with the content or a cHTML object to render or an array
403: * of strings / objects.
404: * @return cHTMLContentElement
405: * $this for chaining
406: */
407: protected function _setContent($content) {
408: $this->_contentlessTag = false;
409: if (is_array($content)) {
410: // reset content
411: $this->_content = '';
412: // content is an array, so iterate over it and append the elements
413: foreach ($content as $item) {
414: if (is_object($item)) {
415: if (method_exists($item, 'render')) {
416: $this->_content .= $item->render();
417: }
418: if (count($item->_requiredScripts) > 0) {
419: $this->_requiredScripts = array_merge($this->_requiredScripts, $item->_requiredScripts);
420: }
421: } else {
422: $this->_content .= $item;
423: }
424: }
425: } else {
426: // content is an object or a string, so just set the rendered
427: // content
428: if (is_object($content)) {
429: if (method_exists($content, 'render')) {
430: $this->_content = $content->render();
431: }
432: if (count($content->_requiredScripts) > 0) {
433: $this->_requiredScripts = array_merge($this->_requiredScripts, $content->_requiredScripts);
434: }
435: } else {
436: $this->_content = $content;
437: }
438: }
439:
440: return $this;
441: }
442:
443: /**
444: * Adds the given content to the already existing content of this object.
445: *
446: * @param string|object|array $content
447: * String with the content or an object to render
448: * or an array of strings/objects.
449: * @return cHTML
450: * $this for chaining
451: */
452: protected function _appendContent($content) {
453: if (!is_string($this->_content)) {
454: $this->_content = '';
455: }
456: $this->_contentlessTag = false;
457: if (is_array($content)) {
458: // content is an array, so iterate over it and append the elements
459: foreach ($content as $item) {
460: if (is_object($item)) {
461: if (method_exists($item, 'render')) {
462: $this->_content .= $item->render();
463: }
464: } else {
465: $this->_content .= $item;
466: }
467: }
468: } else {
469: // content is an object or a string, so just append the rendered
470: // content
471: if (is_object($content)) {
472: if (method_exists($content, 'render')) {
473: $this->_content .= $content->render();
474: }
475: } else {
476: $this->_content .= $content;
477: }
478: }
479:
480: return $this;
481: }
482:
483: /**
484: * Attaches the code for an event
485: *
486: * Example to attach an onClick handler:
487: * attachEventDefinition('foo', 'onClick', 'alert("foo");');
488: *
489: * @param string $name
490: * Defines the name of the event
491: * @param string $event
492: * Defines the event (e.g. onClick)
493: * @param string $code
494: * Defines the code
495: * @return cHTML
496: * $this for chaining
497: */
498: public function attachEventDefinition($name, $event, $code) {
499: $this->_eventDefinitions[cString::toLowerCase($event)][$name] = $code;
500:
501: return $this;
502: }
503:
504: /**
505: * Sets a specific attribute
506: *
507: * @param string $attributeName
508: * Name of the attribute
509: * @param string $value [optional]
510: * Value of the attribute
511: * @return cHTML
512: * $this for chaining
513: */
514: public function setAttribute($attributeName, $value = NULL) {
515: $attributeName = cString::toLowerCase($attributeName);
516: if (is_null($value)) {
517: $value = $attributeName;
518: }
519: $this->_attributes[$attributeName] = $value;
520:
521: return $this;
522: }
523:
524: /**
525: * Sets the HTML attributes
526: *
527: * @param array $attributes
528: * Associative array with attributes
529: * @return cHTML
530: * $this for chaining
531: */
532: public function setAttributes(array $attributes) {
533: $this->_attributes = $this->_parseAttributes($attributes);
534:
535: return $this;
536: }
537:
538: /**
539: * Returns a valid atrributes array.
540: *
541: * @param array $attributes
542: * Associative array with attributes
543: * @return array
544: * the parsed attributes
545: */
546: protected function _parseAttributes(array $attributes) {
547: $return = array();
548: foreach ($attributes as $key => $value) {
549: if (is_int($key)) {
550: $key = $value = cString::toLowerCase($value);
551: } else {
552: $key = cString::toLowerCase($key);
553: }
554:
555: $return[$key] = $value;
556: }
557:
558: return $return;
559: }
560:
561: /**
562: * Removes an attribute
563: *
564: * @param string $attributeName
565: * Attribute name
566: * @return cHTML
567: * $this for chaining
568: */
569: public function removeAttribute($attributeName) {
570: if (isset($this->_attributes[$attributeName])) {
571: unset($this->_attributes[$attributeName]);
572: }
573:
574: return $this;
575: }
576:
577: /**
578: * Returns the value of the given attribute.
579: *
580: * @param string $attributeName
581: * Attribute name
582: * @return string
583: * NULL value or NULL if the attribute does not exist
584: */
585: public function getAttribute($attributeName) {
586: $attributeName = cString::toLowerCase($attributeName);
587:
588: if (isset($this->_attributes[$attributeName])) {
589: return $this->_attributes[$attributeName];
590: }
591:
592: return NULL;
593: }
594:
595: /**
596: * Updates the passed attribute without changing the other existing
597: * attributes
598: *
599: * @param string $name
600: * the name of the attribute
601: * @param string $value
602: * the value of the attribute with the given name
603: * @return cHTML
604: * $this for chaining
605: */
606: public function updateAttribute($name, $value) {
607: return $this->updateAttributes(array(
608: $name => $value
609: ));
610: }
611:
612: /**
613: * Updates the passed attributes without changing the other existing
614: * attributes
615: *
616: * @param array $attributes
617: * Associative array with attributes
618: * @return cHTML
619: * $this for chaining
620: */
621: public function updateAttributes(array $attributes) {
622: $attributes = $this->_parseAttributes($attributes);
623:
624: foreach ($attributes as $key => $value) {
625: if (!is_null($value)) {
626: $this->_attributes[$key] = $value;
627: }
628: }
629:
630: return $this;
631: }
632:
633: /**
634: * Returns an HTML formatted attribute string
635: *
636: * @param array $attributes
637: * Associative array with attributes
638: * @return string
639: * Attribute string in HTML format
640: */
641: protected function _getAttrString(array $attributes) {
642: $attrString = '';
643:
644: if (!is_array($attributes)) {
645: return '';
646: }
647:
648: foreach ($attributes as $key => $value) {
649: $attrString .= ' ' . $key . '="' . $value . '"';
650: }
651:
652: return $attrString;
653: }
654:
655: /**
656: * Returns the assoc array(default) or string of attributes
657: *
658: * @param bool $returnAsString [optional]
659: * Whether to return the attributes as string
660: * @return array|string
661: */
662: public function getAttributes($returnAsString = false) {
663: if ($returnAsString) {
664: return $this->_getAttrString($this->_attributes);
665: } else {
666: return $this->_attributes;
667: }
668: }
669:
670: /**
671: * Generates the markup of the element.
672: *
673: * @return string
674: * generated markup
675: */
676: public function toHtml() {
677: // Fill style definition
678: $style = $this->getAttribute('style');
679:
680: // If the style doesn't end with a semicolon, append one
681: if (is_string($style)) {
682: $style = trim($style);
683: if (cString::getPartOfString($style, -1) !== ';') {
684: $style .= ';';
685: }
686: }
687:
688: // append the defined styles
689: foreach ($this->_styleDefinitions as $property => $value) {
690: $style .= "$property: $value;";
691: }
692:
693: if ($style != '') {
694: $this->setStyle($style);
695: }
696:
697: foreach ($this->_eventDefinitions as $eventName => $entry) {
698: $fullCode = array();
699: foreach ($entry as $code) {
700: $fullCode[] = $code;
701: }
702: $this->setAttribute($eventName, $this->getAttribute($eventName) . implode(' ', $fullCode));
703: }
704:
705: $attributes = $this->getAttributes(true);
706: if (!empty($this->_content) || $this->_contentlessTag === false) {
707: return $this->fillSkeleton($attributes) . $this->_content . $this->fillCloseSkeleton();
708: } else {
709: // This is a single style tag
710: return $this->fillSkeleton($attributes);
711: }
712: }
713:
714: /**
715: * Alias for toHtml
716: *
717: * @return string
718: * generated markup
719: */
720: public function render() {
721: return $this->toHtml();
722: }
723:
724: /**
725: * Direct call of object as string will return it's generated markup.
726: *
727: * @return string
728: * Generated markup
729: */
730: public function __toString() {
731: return $this->render();
732: }
733:
734: /**
735: * Outputs the generated markup
736: */
737: public function display() {
738: echo $this->render();
739: }
740:
741: }
742: