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