comparison program/lib/Roundcube/html.php @ 0:4681f974d28b

vanilla 1.3.3 distro, I hope
author Charlie Root
date Thu, 04 Jan 2018 15:52:31 -0500
parents
children aff04b06b685
comparison
equal deleted inserted replaced
-1:000000000000 0:4681f974d28b
1 <?php
2
3 /**
4 +-----------------------------------------------------------------------+
5 | This file is part of the Roundcube Webmail client |
6 | Copyright (C) 2005-2013, The Roundcube Dev Team |
7 | |
8 | Licensed under the GNU General Public License version 3 or |
9 | any later version with exceptions for skins & plugins. |
10 | See the README file for a full license statement. |
11 | |
12 | PURPOSE: |
13 | Helper class to create valid XHTML code |
14 +-----------------------------------------------------------------------+
15 | Author: Thomas Bruederli <roundcube@gmail.com> |
16 +-----------------------------------------------------------------------+
17 */
18
19 /**
20 * Class for HTML code creation
21 *
22 * @package Framework
23 * @subpackage View
24 */
25 class html
26 {
27 protected $tagname;
28 protected $content;
29 protected $attrib = array();
30 protected $allowed = array();
31
32 public static $doctype = 'xhtml';
33 public static $lc_tags = true;
34 public static $common_attrib = array('id','class','style','title','align','unselectable','tabindex','role');
35 public static $containers = array('iframe','div','span','p','h1','h2','h3','ul','form','textarea','table','thead','tbody','tr','th','td','style','script');
36 public static $bool_attrib = array('checked','multiple','disabled','selected','autofocus','readonly');
37
38
39 /**
40 * Constructor
41 *
42 * @param array $attrib Hash array with tag attributes
43 */
44 public function __construct($attrib = array())
45 {
46 if (is_array($attrib)) {
47 $this->attrib = $attrib;
48 }
49 }
50
51 /**
52 * Return the tag code
53 *
54 * @return string The finally composed HTML tag
55 */
56 public function show()
57 {
58 return self::tag($this->tagname, $this->attrib, $this->content, array_merge(self::$common_attrib, $this->allowed));
59 }
60
61 /****** STATIC METHODS *******/
62
63 /**
64 * Generic method to create a HTML tag
65 *
66 * @param string $tagname Tag name
67 * @param array $attrib Tag attributes as key/value pairs
68 * @param string $content Optional Tag content (creates a container tag)
69 * @param array $allowed List with allowed attributes, omit to allow all
70 *
71 * @return string The XHTML tag
72 */
73 public static function tag($tagname, $attrib = array(), $content = null, $allowed = null)
74 {
75 if (is_string($attrib)) {
76 $attrib = array('class' => $attrib);
77 }
78
79 $inline_tags = array('a','span','img');
80 $suffix = $attrib['nl'] || ($content && $attrib['nl'] !== false && !in_array($tagname, $inline_tags)) ? "\n" : '';
81
82 $tagname = self::$lc_tags ? strtolower($tagname) : $tagname;
83 if (isset($content) || in_array($tagname, self::$containers)) {
84 $suffix = $attrib['noclose'] ? $suffix : '</' . $tagname . '>' . $suffix;
85 unset($attrib['noclose'], $attrib['nl']);
86 return '<' . $tagname . self::attrib_string($attrib, $allowed) . '>' . $content . $suffix;
87 }
88 else {
89 return '<' . $tagname . self::attrib_string($attrib, $allowed) . '>' . $suffix;
90 }
91 }
92
93 /**
94 * Return DOCTYPE tag of specified type
95 *
96 * @param string $type Document type (html5, xhtml, 'xhtml-trans, xhtml-strict)
97 */
98 public static function doctype($type)
99 {
100 $doctypes = array(
101 'html5' => '<!DOCTYPE html>',
102 'xhtml' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
103 'xhtml-trans' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
104 'xhtml-strict' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
105 );
106
107 if ($doctypes[$type]) {
108 self::$doctype = preg_replace('/-\w+$/', '', $type);
109 return $doctypes[$type];
110 }
111
112 return '';
113 }
114
115 /**
116 * Derrived method for <div> containers
117 *
118 * @param mixed $attr Hash array with tag attributes or string with class name
119 * @param string $cont Div content
120 *
121 * @return string HTML code
122 * @see html::tag()
123 */
124 public static function div($attr = null, $cont = null)
125 {
126 if (is_string($attr)) {
127 $attr = array('class' => $attr);
128 }
129
130 return self::tag('div', $attr, $cont, array_merge(self::$common_attrib, array('onclick')));
131 }
132
133 /**
134 * Derrived method for <p> blocks
135 *
136 * @param mixed $attr Hash array with tag attributes or string with class name
137 * @param string $cont Paragraph content
138 *
139 * @return string HTML code
140 * @see html::tag()
141 */
142 public static function p($attr = null, $cont = null)
143 {
144 if (is_string($attr)) {
145 $attr = array('class' => $attr);
146 }
147
148 return self::tag('p', $attr, $cont, self::$common_attrib);
149 }
150
151 /**
152 * Derrived method to create <img />
153 *
154 * @param mixed $attr Hash array with tag attributes or string with image source (src)
155 *
156 * @return string HTML code
157 * @see html::tag()
158 */
159 public static function img($attr = null)
160 {
161 if (is_string($attr)) {
162 $attr = array('src' => $attr);
163 }
164
165 return self::tag('img', $attr + array('alt' => ''), null, array_merge(self::$common_attrib,
166 array('src','alt','width','height','border','usemap','onclick','onerror','onload')));
167 }
168
169 /**
170 * Derrived method for link tags
171 *
172 * @param mixed $attr Hash array with tag attributes or string with link location (href)
173 * @param string $cont Link content
174 *
175 * @return string HTML code
176 * @see html::tag()
177 */
178 public static function a($attr, $cont)
179 {
180 if (is_string($attr)) {
181 $attr = array('href' => $attr);
182 }
183
184 return self::tag('a', $attr, $cont, array_merge(self::$common_attrib,
185 array('href','target','name','rel','onclick','onmouseover','onmouseout','onmousedown','onmouseup')));
186 }
187
188 /**
189 * Derrived method for inline span tags
190 *
191 * @param mixed $attr Hash array with tag attributes or string with class name
192 * @param string $cont Tag content
193 *
194 * @return string HTML code
195 * @see html::tag()
196 */
197 public static function span($attr, $cont)
198 {
199 if (is_string($attr)) {
200 $attr = array('class' => $attr);
201 }
202
203 return self::tag('span', $attr, $cont, self::$common_attrib);
204 }
205
206 /**
207 * Derrived method for form element labels
208 *
209 * @param mixed $attr Hash array with tag attributes or string with 'for' attrib
210 * @param string $cont Tag content
211 *
212 * @return string HTML code
213 * @see html::tag()
214 */
215 public static function label($attr, $cont)
216 {
217 if (is_string($attr)) {
218 $attr = array('for' => $attr);
219 }
220
221 return self::tag('label', $attr, $cont, array_merge(self::$common_attrib,
222 array('for','onkeypress')));
223 }
224
225 /**
226 * Derrived method to create <iframe></iframe>
227 *
228 * @param mixed $attr Hash array with tag attributes or string with frame source (src)
229 *
230 * @return string HTML code
231 * @see html::tag()
232 */
233 public static function iframe($attr = null, $cont = null)
234 {
235 if (is_string($attr)) {
236 $attr = array('src' => $attr);
237 }
238
239 return self::tag('iframe', $attr, $cont, array_merge(self::$common_attrib,
240 array('src','name','width','height','border','frameborder','onload','allowfullscreen')));
241 }
242
243 /**
244 * Derrived method to create <script> tags
245 *
246 * @param mixed $attr Hash array with tag attributes or string with script source (src)
247 * @param string $cont Javascript code to be placed as tag content
248 *
249 * @return string HTML code
250 * @see html::tag()
251 */
252 public static function script($attr, $cont = null)
253 {
254 if (is_string($attr)) {
255 $attr = array('src' => $attr);
256 }
257 if ($cont) {
258 if (self::$doctype == 'xhtml')
259 $cont = "\n/* <![CDATA[ */\n" . $cont . "\n/* ]]> */\n";
260 else
261 $cont = "\n" . $cont . "\n";
262 }
263
264 return self::tag('script', $attr + array('type' => 'text/javascript', 'nl' => true),
265 $cont, array_merge(self::$common_attrib, array('src','type','charset')));
266 }
267
268 /**
269 * Derrived method for line breaks
270 *
271 * @param array $attrib Associative arry with tag attributes
272 *
273 * @return string HTML code
274 * @see html::tag()
275 */
276 public static function br($attrib = array())
277 {
278 return self::tag('br', $attrib);
279 }
280
281 /**
282 * Create string with attributes
283 *
284 * @param array $attrib Associative array with tag attributes
285 * @param array $allowed List of allowed attributes
286 *
287 * @return string Valid attribute string
288 */
289 public static function attrib_string($attrib = array(), $allowed = null)
290 {
291 if (empty($attrib)) {
292 return '';
293 }
294
295 $allowed_f = array_flip((array)$allowed);
296 $attrib_arr = array();
297
298 foreach ($attrib as $key => $value) {
299 // skip size if not numeric
300 if ($key == 'size' && !is_numeric($value)) {
301 continue;
302 }
303
304 // ignore "internal" or empty attributes
305 if ($key == 'nl' || $value === null) {
306 continue;
307 }
308
309 // ignore not allowed attributes, except aria-* and data-*
310 if (!empty($allowed)) {
311 $is_data_attr = @substr_compare($key, 'data-', 0, 5) === 0;
312 $is_aria_attr = @substr_compare($key, 'aria-', 0, 5) === 0;
313 if (!$is_aria_attr && !$is_data_attr && !isset($allowed_f[$key])) {
314 continue;
315 }
316 }
317
318 // skip empty eventhandlers
319 if (preg_match('/^on[a-z]+/', $key) && !$value) {
320 continue;
321 }
322
323 // attributes with no value
324 if (in_array($key, self::$bool_attrib)) {
325 if ($value) {
326 $value = $key;
327 if (self::$doctype == 'xhtml') {
328 $value .= '="' . $value . '"';
329 }
330
331 $attrib_arr[] = $value;
332 }
333 }
334 else {
335 $attrib_arr[] = $key . '="' . self::quote($value) . '"';
336 }
337 }
338
339 return count($attrib_arr) ? ' '.implode(' ', $attrib_arr) : '';
340 }
341
342 /**
343 * Convert a HTML attribute string attributes to an associative array (name => value)
344 *
345 * @param string $str Input string
346 *
347 * @return array Key-value pairs of parsed attributes
348 */
349 public static function parse_attrib_string($str)
350 {
351 $attrib = array();
352 $html = '<html>'
353 . '<head><meta http-equiv="Content-Type" content="text/html; charset=' . RCUBE_CHARSET . '" /></head>'
354 . '<body><div ' . rtrim($str, '/ ') . ' /></body>'
355 . '</html>';
356
357 $document = new DOMDocument('1.0', RCUBE_CHARSET);
358 @$document->loadHTML($html);
359
360 if ($node = $document->getElementsByTagName('div')->item(0)) {
361 foreach ($node->attributes as $name => $attr) {
362 $attrib[strtolower($name)] = $attr->nodeValue;
363 }
364 }
365
366 return $attrib;
367 }
368
369 /**
370 * Replacing specials characters in html attribute value
371 *
372 * @param string $str Input string
373 *
374 * @return string The quoted string
375 */
376 public static function quote($str)
377 {
378 static $flags;
379
380 if (!$flags) {
381 $flags = ENT_COMPAT;
382 if (defined('ENT_SUBSTITUTE')) {
383 $flags |= ENT_SUBSTITUTE;
384 }
385 }
386
387 return @htmlspecialchars($str, $flags, RCUBE_CHARSET);
388 }
389 }
390
391
392 /**
393 * Class to create an HTML input field
394 *
395 * @package Framework
396 * @subpackage View
397 */
398 class html_inputfield extends html
399 {
400 protected $tagname = 'input';
401 protected $type = 'text';
402 protected $allowed = array(
403 'type','name','value','size','tabindex','autocapitalize','required',
404 'autocomplete','checked','onchange','onclick','disabled','readonly',
405 'spellcheck','results','maxlength','src','multiple','accept',
406 'placeholder','autofocus','pattern',
407 );
408
409 /**
410 * Object constructor
411 *
412 * @param array $attrib Associative array with tag attributes
413 */
414 public function __construct($attrib = array())
415 {
416 if (is_array($attrib)) {
417 $this->attrib = $attrib;
418 }
419
420 if ($attrib['type']) {
421 $this->type = $attrib['type'];
422 }
423 }
424
425 /**
426 * Compose input tag
427 *
428 * @param string $value Field value
429 * @param array $attrib Additional attributes to override
430 *
431 * @return string HTML output
432 */
433 public function show($value = null, $attrib = null)
434 {
435 // overwrite object attributes
436 if (is_array($attrib)) {
437 $this->attrib = array_merge($this->attrib, $attrib);
438 }
439
440 // set value attribute
441 if ($value !== null) {
442 $this->attrib['value'] = $value;
443 }
444 // set type
445 $this->attrib['type'] = $this->type;
446
447 return parent::show();
448 }
449 }
450
451 /**
452 * Class to create an HTML password field
453 *
454 * @package Framework
455 * @subpackage View
456 */
457 class html_passwordfield extends html_inputfield
458 {
459 protected $type = 'password';
460 }
461
462 /**
463 * Class to create an hidden HTML input field
464 *
465 * @package Framework
466 * @subpackage View
467 */
468 class html_hiddenfield extends html
469 {
470 protected $tagname = 'input';
471 protected $type = 'hidden';
472 protected $allowed = array('type','name','value','onchange','disabled','readonly');
473 protected $fields = array();
474
475 /**
476 * Constructor
477 *
478 * @param array $attrib Named tag attributes
479 */
480 public function __construct($attrib = null)
481 {
482 if (is_array($attrib)) {
483 $this->add($attrib);
484 }
485 }
486
487 /**
488 * Add a hidden field to this instance
489 *
490 * @param array $attrib Named tag attributes
491 */
492 public function add($attrib)
493 {
494 $this->fields[] = $attrib;
495 }
496
497 /**
498 * Create HTML code for the hidden fields
499 *
500 * @return string Final HTML code
501 */
502 public function show()
503 {
504 $out = '';
505 foreach ($this->fields as $attrib) {
506 $out .= self::tag($this->tagname, array('type' => $this->type) + $attrib);
507 }
508
509 return $out;
510 }
511 }
512
513 /**
514 * Class to create HTML radio buttons
515 *
516 * @package Framework
517 * @subpackage View
518 */
519 class html_radiobutton extends html_inputfield
520 {
521 protected $type = 'radio';
522
523 /**
524 * Get HTML code for this object
525 *
526 * @param string $value Value of the checked field
527 * @param array $attrib Additional attributes to override
528 *
529 * @return string HTML output
530 */
531 public function show($value = '', $attrib = null)
532 {
533 // overwrite object attributes
534 if (is_array($attrib)) {
535 $this->attrib = array_merge($this->attrib, $attrib);
536 }
537
538 // set value attribute
539 $this->attrib['checked'] = ((string)$value == (string)$this->attrib['value']);
540
541 return parent::show();
542 }
543 }
544
545 /**
546 * Class to create HTML checkboxes
547 *
548 * @package Framework
549 * @subpackage View
550 */
551 class html_checkbox extends html_inputfield
552 {
553 protected $type = 'checkbox';
554
555 /**
556 * Get HTML code for this object
557 *
558 * @param string $value Value of the checked field
559 * @param array $attrib Additional attributes to override
560 *
561 * @return string HTML output
562 */
563 public function show($value = '', $attrib = null)
564 {
565 // overwrite object attributes
566 if (is_array($attrib)) {
567 $this->attrib = array_merge($this->attrib, $attrib);
568 }
569
570 // set value attribute
571 $this->attrib['checked'] = ((string)$value == (string)$this->attrib['value']);
572
573 return parent::show();
574 }
575 }
576
577 /**
578 * Class to create an HTML textarea
579 *
580 * @package Framework
581 * @subpackage View
582 */
583 class html_textarea extends html
584 {
585 protected $tagname = 'textarea';
586 protected $allowed = array('name','rows','cols','wrap','tabindex',
587 'onchange','disabled','readonly','spellcheck');
588
589 /**
590 * Get HTML code for this object
591 *
592 * @param string $value Textbox value
593 * @param array $attrib Additional attributes to override
594 *
595 * @return string HTML output
596 */
597 public function show($value = '', $attrib = null)
598 {
599 // overwrite object attributes
600 if (is_array($attrib)) {
601 $this->attrib = array_merge($this->attrib, $attrib);
602 }
603
604 // take value attribute as content
605 if (empty($value) && !empty($this->attrib['value'])) {
606 $value = $this->attrib['value'];
607 }
608
609 // make shure we don't print the value attribute
610 if (isset($this->attrib['value'])) {
611 unset($this->attrib['value']);
612 }
613
614 if (!empty($value) && empty($this->attrib['is_escaped'])) {
615 $value = self::quote($value);
616 }
617
618 return self::tag($this->tagname, $this->attrib, $value,
619 array_merge(self::$common_attrib, $this->allowed));
620 }
621 }
622
623 /**
624 * Builder for HTML drop-down menus
625 * Syntax:<pre>
626 * // create instance. arguments are used to set attributes of select-tag
627 * $select = new html_select(array('name' => 'fieldname'));
628 *
629 * // add one option
630 * $select->add('Switzerland', 'CH');
631 *
632 * // add multiple options
633 * $select->add(array('Switzerland','Germany'), array('CH','DE'));
634 *
635 * // generate pulldown with selection 'Switzerland' and return html-code
636 * // as second argument the same attributes available to instantiate can be used
637 * print $select->show('CH');
638 * </pre>
639 *
640 * @package Framework
641 * @subpackage View
642 */
643 class html_select extends html
644 {
645 protected $tagname = 'select';
646 protected $options = array();
647 protected $allowed = array('name','size','tabindex','autocomplete',
648 'multiple','onchange','disabled','rel');
649
650 /**
651 * Add a new option to this drop-down
652 *
653 * @param mixed $names Option name or array with option names
654 * @param mixed $values Option value or array with option values
655 * @param array $attrib Additional attributes for the option entry
656 */
657 public function add($names, $values = null, $attrib = array())
658 {
659 if (is_array($names)) {
660 foreach ($names as $i => $text) {
661 $this->options[] = array('text' => $text, 'value' => $values[$i]) + $attrib;
662 }
663 }
664 else {
665 $this->options[] = array('text' => $names, 'value' => $values) + $attrib;
666 }
667 }
668
669 /**
670 * Get HTML code for this object
671 *
672 * @param string $select Value of the selection option
673 * @param array $attrib Additional attributes to override
674 *
675 * @return string HTML output
676 */
677 public function show($select = array(), $attrib = null)
678 {
679 // overwrite object attributes
680 if (is_array($attrib)) {
681 $this->attrib = array_merge($this->attrib, $attrib);
682 }
683
684 $this->content = "\n";
685 $select = (array)$select;
686 foreach ($this->options as $option) {
687 $attr = array(
688 'value' => $option['value'],
689 'selected' => (in_array($option['value'], $select, true) ||
690 in_array($option['text'], $select, true)) ? 1 : null);
691
692 $option_content = $option['text'];
693 if (empty($this->attrib['is_escaped'])) {
694 $option_content = self::quote($option_content);
695 }
696
697 $this->content .= self::tag('option', $attr + $option, $option_content, array('value','label','class','style','title','disabled','selected'));
698 }
699
700 return parent::show();
701 }
702 }
703
704
705 /**
706 * Class to build an HTML table
707 *
708 * @package Framework
709 * @subpackage View
710 */
711 class html_table extends html
712 {
713 protected $tagname = 'table';
714 protected $allowed = array('id','class','style','width','summary',
715 'cellpadding','cellspacing','border');
716
717 private $header = array();
718 private $rows = array();
719 private $rowindex = 0;
720 private $colindex = 0;
721
722 /**
723 * Constructor
724 *
725 * @param array $attrib Named tag attributes
726 */
727 public function __construct($attrib = array())
728 {
729 $default_attrib = self::$doctype == 'xhtml' ? array('summary' => '', 'border' => '0') : array();
730 $this->attrib = array_merge($attrib, $default_attrib);
731
732 if (!empty($attrib['tagname']) && $attrib['tagname'] != 'table') {
733 $this->tagname = $attrib['tagname'];
734 $this->allowed = self::$common_attrib;
735 }
736 }
737
738 /**
739 * Add a table cell
740 *
741 * @param array $attr Cell attributes
742 * @param string $cont Cell content
743 */
744 public function add($attr, $cont)
745 {
746 if (is_string($attr)) {
747 $attr = array('class' => $attr);
748 }
749
750 $cell = new stdClass;
751 $cell->attrib = $attr;
752 $cell->content = $cont;
753
754 $this->rows[$this->rowindex]->cells[$this->colindex] = $cell;
755 $this->colindex += max(1, intval($attr['colspan']));
756
757 if ($this->attrib['cols'] && $this->colindex >= $this->attrib['cols']) {
758 $this->add_row();
759 }
760 }
761
762 /**
763 * Add a table header cell
764 *
765 * @param array $attr Cell attributes
766 * @param string $cont Cell content
767 */
768 public function add_header($attr, $cont)
769 {
770 if (is_string($attr)) {
771 $attr = array('class' => $attr);
772 }
773
774 $cell = new stdClass;
775 $cell->attrib = $attr;
776 $cell->content = $cont;
777 $this->header[] = $cell;
778 }
779
780 /**
781 * Remove a column from a table
782 * Useful for plugins making alterations
783 *
784 * @param string $class Class name
785 */
786 public function remove_column($class)
787 {
788 // Remove the header
789 foreach ($this->header as $index => $header){
790 if ($header->attrib['class'] == $class){
791 unset($this->header[$index]);
792 break;
793 }
794 }
795
796 // Remove cells from rows
797 foreach ($this->rows as $i => $row){
798 foreach ($row->cells as $j => $cell){
799 if ($cell->attrib['class'] == $class){
800 unset($this->rows[$i]->cells[$j]);
801 break;
802 }
803 }
804 }
805 }
806
807 /**
808 * Jump to next row
809 *
810 * @param array $attr Row attributes
811 */
812 public function add_row($attr = array())
813 {
814 $this->rowindex++;
815 $this->colindex = 0;
816 $this->rows[$this->rowindex] = new stdClass;
817 $this->rows[$this->rowindex]->attrib = $attr;
818 $this->rows[$this->rowindex]->cells = array();
819 }
820
821 /**
822 * Set row attributes
823 *
824 * @param array $attr Row attributes
825 * @param int $index Optional row index (default current row index)
826 */
827 public function set_row_attribs($attr = array(), $index = null)
828 {
829 if (is_string($attr)) {
830 $attr = array('class' => $attr);
831 }
832
833 if ($index === null) {
834 $index = $this->rowindex;
835 }
836
837 // make sure row object exists (#1489094)
838 if (!$this->rows[$index]) {
839 $this->rows[$index] = new stdClass;
840 }
841
842 $this->rows[$index]->attrib = $attr;
843 }
844
845 /**
846 * Get row attributes
847 *
848 * @param int $index Row index
849 *
850 * @return array Row attributes
851 */
852 public function get_row_attribs($index = null)
853 {
854 if ($index === null) {
855 $index = $this->rowindex;
856 }
857
858 return $this->rows[$index] ? $this->rows[$index]->attrib : null;
859 }
860
861 /**
862 * Build HTML output of the table data
863 *
864 * @param array $attrib Table attributes
865 *
866 * @return string The final table HTML code
867 */
868 public function show($attrib = null)
869 {
870 if (is_array($attrib)) {
871 $this->attrib = array_merge($this->attrib, $attrib);
872 }
873
874 $thead = $tbody = "";
875
876 // include <thead>
877 if (!empty($this->header)) {
878 $rowcontent = '';
879 foreach ($this->header as $c => $col) {
880 $rowcontent .= self::tag($this->_head_tagname(), $col->attrib, $col->content);
881 }
882 $thead = $this->tagname == 'table' ? self::tag('thead', null, self::tag('tr', null, $rowcontent, parent::$common_attrib)) :
883 self::tag($this->_row_tagname(), array('class' => 'thead'), $rowcontent, parent::$common_attrib);
884 }
885
886 foreach ($this->rows as $r => $row) {
887 $rowcontent = '';
888 foreach ($row->cells as $c => $col) {
889 $rowcontent .= self::tag($this->_col_tagname(), $col->attrib, $col->content);
890 }
891
892 if ($r < $this->rowindex || count($row->cells)) {
893 $tbody .= self::tag($this->_row_tagname(), $row->attrib, $rowcontent, parent::$common_attrib);
894 }
895 }
896
897 if ($this->attrib['rowsonly']) {
898 return $tbody;
899 }
900
901 // add <tbody>
902 $this->content = $thead . ($this->tagname == 'table' ? self::tag('tbody', null, $tbody) : $tbody);
903
904 unset($this->attrib['cols'], $this->attrib['rowsonly']);
905 return parent::show();
906 }
907
908 /**
909 * Count number of rows
910 *
911 * @return The number of rows
912 */
913 public function size()
914 {
915 return count($this->rows);
916 }
917
918 /**
919 * Remove table body (all rows)
920 */
921 public function remove_body()
922 {
923 $this->rows = array();
924 $this->rowindex = 0;
925 }
926
927 /**
928 * Getter for the corresponding tag name for table row elements
929 */
930 private function _row_tagname()
931 {
932 static $row_tagnames = array('table' => 'tr', 'ul' => 'li', '*' => 'div');
933 return $row_tagnames[$this->tagname] ?: $row_tagnames['*'];
934 }
935
936 /**
937 * Getter for the corresponding tag name for table row elements
938 */
939 private function _head_tagname()
940 {
941 static $head_tagnames = array('table' => 'th', '*' => 'span');
942 return $head_tagnames[$this->tagname] ?: $head_tagnames['*'];
943 }
944
945 /**
946 * Getter for the corresponding tag name for table cell elements
947 */
948 private function _col_tagname()
949 {
950 static $col_tagnames = array('table' => 'td', '*' => 'span');
951 return $col_tagnames[$this->tagname] ?: $col_tagnames['*'];
952 }
953 }