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