You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

html.php 27KB

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