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