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