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