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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945
  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 Optinal 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')));
  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, array('for')));
  197. }
  198. /**
  199. * Derrived method to create <iframe></iframe>
  200. *
  201. * @param mixed $attr Hash array with tag attributes or string with frame source (src)
  202. *
  203. * @return string HTML code
  204. * @see html::tag()
  205. */
  206. public static function iframe($attr = null, $cont = null)
  207. {
  208. if (is_string($attr)) {
  209. $attr = array('src' => $attr);
  210. }
  211. return self::tag('iframe', $attr, $cont, array_merge(self::$common_attrib,
  212. array('src','name','width','height','border','frameborder','onload','allowfullscreen')));
  213. }
  214. /**
  215. * Derrived method to create <script> tags
  216. *
  217. * @param mixed $attr Hash array with tag attributes or string with script source (src)
  218. * @param string $cont Javascript code to be placed as tag content
  219. *
  220. * @return string HTML code
  221. * @see html::tag()
  222. */
  223. public static function script($attr, $cont = null)
  224. {
  225. if (is_string($attr)) {
  226. $attr = array('src' => $attr);
  227. }
  228. if ($cont) {
  229. if (self::$doctype == 'xhtml')
  230. $cont = "\n/* <![CDATA[ */\n" . $cont . "\n/* ]]> */\n";
  231. else
  232. $cont = "\n" . $cont . "\n";
  233. }
  234. return self::tag('script', $attr + array('type' => 'text/javascript', 'nl' => true),
  235. $cont, array_merge(self::$common_attrib, array('src','type','charset')));
  236. }
  237. /**
  238. * Derrived method for line breaks
  239. *
  240. * @param array $attrib Associative arry with tag attributes
  241. *
  242. * @return string HTML code
  243. * @see html::tag()
  244. */
  245. public static function br($attrib = array())
  246. {
  247. return self::tag('br', $attrib);
  248. }
  249. /**
  250. * Create string with attributes
  251. *
  252. * @param array $attrib Associative array with tag attributes
  253. * @param array $allowed List of allowed attributes
  254. *
  255. * @return string Valid attribute string
  256. */
  257. public static function attrib_string($attrib = array(), $allowed = null)
  258. {
  259. if (empty($attrib)) {
  260. return '';
  261. }
  262. $allowed_f = array_flip((array)$allowed);
  263. $attrib_arr = array();
  264. foreach ($attrib as $key => $value) {
  265. // skip size if not numeric
  266. if ($key == 'size' && !is_numeric($value)) {
  267. continue;
  268. }
  269. // ignore "internal" or empty attributes
  270. if ($key == 'nl' || $value === null) {
  271. continue;
  272. }
  273. // ignore not allowed attributes, except aria-* and data-*
  274. if (!empty($allowed)) {
  275. $is_data_attr = @substr_compare($key, 'data-', 0, 5) === 0;
  276. $is_aria_attr = @substr_compare($key, 'aria-', 0, 5) === 0;
  277. if (!$is_aria_attr && !$is_data_attr && !isset($allowed_f[$key])) {
  278. continue;
  279. }
  280. }
  281. // skip empty eventhandlers
  282. if (preg_match('/^on[a-z]+/', $key) && !$value) {
  283. continue;
  284. }
  285. // attributes with no value
  286. if (in_array($key, self::$bool_attrib)) {
  287. if ($value) {
  288. // @TODO: minimize attribute in non-xhtml mode
  289. $attrib_arr[] = $key . '="' . $key . '"';
  290. }
  291. }
  292. else {
  293. $attrib_arr[] = $key . '="' . self::quote($value) . '"';
  294. }
  295. }
  296. return count($attrib_arr) ? ' '.implode(' ', $attrib_arr) : '';
  297. }
  298. /**
  299. * Convert a HTML attribute string attributes to an associative array (name => value)
  300. *
  301. * @param string Input string
  302. *
  303. * @return array Key-value pairs of parsed attributes
  304. */
  305. public static function parse_attrib_string($str)
  306. {
  307. $attrib = array();
  308. $regexp = '/\s*([-_a-z]+)=(["\'])??(?(2)([^\2]*)\2|(\S+?))/Ui';
  309. preg_match_all($regexp, stripslashes($str), $regs, PREG_SET_ORDER);
  310. // convert attributes to an associative array (name => value)
  311. if ($regs) {
  312. foreach ($regs as $attr) {
  313. $attrib[strtolower($attr[1])] = html_entity_decode($attr[3] . $attr[4]);
  314. }
  315. }
  316. return $attrib;
  317. }
  318. /**
  319. * Replacing specials characters in html attribute value
  320. *
  321. * @param string $str Input string
  322. *
  323. * @return string The quoted string
  324. */
  325. public static function quote($str)
  326. {
  327. static $flags;
  328. if (!$flags) {
  329. $flags = ENT_COMPAT;
  330. if (defined('ENT_SUBSTITUTE')) {
  331. $flags |= ENT_SUBSTITUTE;
  332. }
  333. }
  334. return @htmlspecialchars($str, $flags, RCUBE_CHARSET);
  335. }
  336. }
  337. /**
  338. * Class to create an HTML input field
  339. *
  340. * @package Framework
  341. * @subpackage View
  342. */
  343. class html_inputfield extends html
  344. {
  345. protected $tagname = 'input';
  346. protected $type = 'text';
  347. protected $allowed = array(
  348. 'type','name','value','size','tabindex','autocapitalize','required',
  349. 'autocomplete','checked','onchange','onclick','disabled','readonly',
  350. 'spellcheck','results','maxlength','src','multiple','accept',
  351. 'placeholder','autofocus','pattern'
  352. );
  353. /**
  354. * Object constructor
  355. *
  356. * @param array $attrib Associative array with tag attributes
  357. */
  358. public function __construct($attrib = array())
  359. {
  360. if (is_array($attrib)) {
  361. $this->attrib = $attrib;
  362. }
  363. if ($attrib['type']) {
  364. $this->type = $attrib['type'];
  365. }
  366. }
  367. /**
  368. * Compose input tag
  369. *
  370. * @param string $value Field value
  371. * @param array $attrib Additional attributes to override
  372. *
  373. * @return string HTML output
  374. */
  375. public function show($value = null, $attrib = null)
  376. {
  377. // overwrite object attributes
  378. if (is_array($attrib)) {
  379. $this->attrib = array_merge($this->attrib, $attrib);
  380. }
  381. // set value attribute
  382. if ($value !== null) {
  383. $this->attrib['value'] = $value;
  384. }
  385. // set type
  386. $this->attrib['type'] = $this->type;
  387. return parent::show();
  388. }
  389. }
  390. /**
  391. * Class to create an HTML password field
  392. *
  393. * @package Framework
  394. * @subpackage View
  395. */
  396. class html_passwordfield extends html_inputfield
  397. {
  398. protected $type = 'password';
  399. }
  400. /**
  401. * Class to create an hidden HTML input field
  402. *
  403. * @package Framework
  404. * @subpackage View
  405. */
  406. class html_hiddenfield extends html
  407. {
  408. protected $tagname = 'input';
  409. protected $type = 'hidden';
  410. protected $allowed = array('type','name','value','onchange','disabled','readonly');
  411. protected $fields = array();
  412. /**
  413. * Constructor
  414. *
  415. * @param array $attrib Named tag attributes
  416. */
  417. public function __construct($attrib = null)
  418. {
  419. if (is_array($attrib)) {
  420. $this->add($attrib);
  421. }
  422. }
  423. /**
  424. * Add a hidden field to this instance
  425. *
  426. * @param array $attrib Named tag attributes
  427. */
  428. public function add($attrib)
  429. {
  430. $this->fields[] = $attrib;
  431. }
  432. /**
  433. * Create HTML code for the hidden fields
  434. *
  435. * @return string Final HTML code
  436. */
  437. public function show()
  438. {
  439. $out = '';
  440. foreach ($this->fields as $attrib) {
  441. $out .= self::tag($this->tagname, array('type' => $this->type) + $attrib);
  442. }
  443. return $out;
  444. }
  445. }
  446. /**
  447. * Class to create HTML radio buttons
  448. *
  449. * @package Framework
  450. * @subpackage View
  451. */
  452. class html_radiobutton extends html_inputfield
  453. {
  454. protected $type = 'radio';
  455. /**
  456. * Get HTML code for this object
  457. *
  458. * @param string $value Value of the checked field
  459. * @param array $attrib Additional attributes to override
  460. *
  461. * @return string HTML output
  462. */
  463. public function show($value = '', $attrib = null)
  464. {
  465. // overwrite object attributes
  466. if (is_array($attrib)) {
  467. $this->attrib = array_merge($this->attrib, $attrib);
  468. }
  469. // set value attribute
  470. $this->attrib['checked'] = ((string)$value == (string)$this->attrib['value']);
  471. return parent::show();
  472. }
  473. }
  474. /**
  475. * Class to create HTML checkboxes
  476. *
  477. * @package Framework
  478. * @subpackage View
  479. */
  480. class html_checkbox extends html_inputfield
  481. {
  482. protected $type = 'checkbox';
  483. /**
  484. * Get HTML code for this object
  485. *
  486. * @param string $value Value of the checked field
  487. * @param array $attrib Additional attributes to override
  488. *
  489. * @return string HTML output
  490. */
  491. public function show($value = '', $attrib = null)
  492. {
  493. // overwrite object attributes
  494. if (is_array($attrib)) {
  495. $this->attrib = array_merge($this->attrib, $attrib);
  496. }
  497. // set value attribute
  498. $this->attrib['checked'] = ((string)$value == (string)$this->attrib['value']);
  499. return parent::show();
  500. }
  501. }
  502. /**
  503. * Class to create an HTML textarea
  504. *
  505. * @package Framework
  506. * @subpackage View
  507. */
  508. class html_textarea extends html
  509. {
  510. protected $tagname = 'textarea';
  511. protected $allowed = array('name','rows','cols','wrap','tabindex',
  512. 'onchange','disabled','readonly','spellcheck');
  513. /**
  514. * Get HTML code for this object
  515. *
  516. * @param string $value Textbox value
  517. * @param array $attrib Additional attributes to override
  518. *
  519. * @return string HTML output
  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. // take value attribute as content
  528. if (empty($value) && !empty($this->attrib['value'])) {
  529. $value = $this->attrib['value'];
  530. }
  531. // make shure we don't print the value attribute
  532. if (isset($this->attrib['value'])) {
  533. unset($this->attrib['value']);
  534. }
  535. if (!empty($value) && empty($this->attrib['is_escaped'])) {
  536. $value = self::quote($value);
  537. }
  538. return self::tag($this->tagname, $this->attrib, $value,
  539. array_merge(self::$common_attrib, $this->allowed));
  540. }
  541. }
  542. /**
  543. * Builder for HTML drop-down menus
  544. * Syntax:<pre>
  545. * // create instance. arguments are used to set attributes of select-tag
  546. * $select = new html_select(array('name' => 'fieldname'));
  547. *
  548. * // add one option
  549. * $select->add('Switzerland', 'CH');
  550. *
  551. * // add multiple options
  552. * $select->add(array('Switzerland','Germany'), array('CH','DE'));
  553. *
  554. * // generate pulldown with selection 'Switzerland' and return html-code
  555. * // as second argument the same attributes available to instanciate can be used
  556. * print $select->show('CH');
  557. * </pre>
  558. *
  559. * @package Framework
  560. * @subpackage View
  561. */
  562. class html_select extends html
  563. {
  564. protected $tagname = 'select';
  565. protected $options = array();
  566. protected $allowed = array('name','size','tabindex','autocomplete',
  567. 'multiple','onchange','disabled','rel');
  568. /**
  569. * Add a new option to this drop-down
  570. *
  571. * @param mixed $names Option name or array with option names
  572. * @param mixed $values Option value or array with option values
  573. * @param array $attrib Additional attributes for the option entry
  574. */
  575. public function add($names, $values = null, $attrib = array())
  576. {
  577. if (is_array($names)) {
  578. foreach ($names as $i => $text) {
  579. $this->options[] = array('text' => $text, 'value' => $values[$i]) + $attrib;
  580. }
  581. }
  582. else {
  583. $this->options[] = array('text' => $names, 'value' => $values) + $attrib;
  584. }
  585. }
  586. /**
  587. * Get HTML code for this object
  588. *
  589. * @param string $select Value of the selection option
  590. * @param array $attrib Additional attributes to override
  591. *
  592. * @return string HTML output
  593. */
  594. public function show($select = array(), $attrib = null)
  595. {
  596. // overwrite object attributes
  597. if (is_array($attrib)) {
  598. $this->attrib = array_merge($this->attrib, $attrib);
  599. }
  600. $this->content = "\n";
  601. $select = (array)$select;
  602. foreach ($this->options as $option) {
  603. $attr = array(
  604. 'value' => $option['value'],
  605. 'selected' => (in_array($option['value'], $select, true) ||
  606. in_array($option['text'], $select, true)) ? 1 : null);
  607. $option_content = $option['text'];
  608. if (empty($this->attrib['is_escaped'])) {
  609. $option_content = self::quote($option_content);
  610. }
  611. $this->content .= self::tag('option', $attr + $option, $option_content, array('value','label','class','style','title','disabled','selected'));
  612. }
  613. return parent::show();
  614. }
  615. }
  616. /**
  617. * Class to build an HTML table
  618. *
  619. * @package Framework
  620. * @subpackage View
  621. */
  622. class html_table extends html
  623. {
  624. protected $tagname = 'table';
  625. protected $allowed = array('id','class','style','width','summary',
  626. 'cellpadding','cellspacing','border');
  627. private $header = array();
  628. private $rows = array();
  629. private $rowindex = 0;
  630. private $colindex = 0;
  631. /**
  632. * Constructor
  633. *
  634. * @param array $attrib Named tag attributes
  635. */
  636. public function __construct($attrib = array())
  637. {
  638. $default_attrib = self::$doctype == 'xhtml' ? array('summary' => '', 'border' => '0') : array();
  639. $this->attrib = array_merge($attrib, $default_attrib);
  640. if (!empty($attrib['tagname']) && $attrib['tagname'] != 'table') {
  641. $this->tagname = $attrib['tagname'];
  642. $this->allowed = self::$common_attrib;
  643. }
  644. }
  645. /**
  646. * Add a table cell
  647. *
  648. * @param array $attr Cell attributes
  649. * @param string $cont Cell content
  650. */
  651. public function add($attr, $cont)
  652. {
  653. if (is_string($attr)) {
  654. $attr = array('class' => $attr);
  655. }
  656. $cell = new stdClass;
  657. $cell->attrib = $attr;
  658. $cell->content = $cont;
  659. $this->rows[$this->rowindex]->cells[$this->colindex] = $cell;
  660. $this->colindex += max(1, intval($attr['colspan']));
  661. if ($this->attrib['cols'] && $this->colindex >= $this->attrib['cols']) {
  662. $this->add_row();
  663. }
  664. }
  665. /**
  666. * Add a table header cell
  667. *
  668. * @param array $attr Cell attributes
  669. * @param string $cont Cell content
  670. */
  671. public function add_header($attr, $cont)
  672. {
  673. if (is_string($attr)) {
  674. $attr = array('class' => $attr);
  675. }
  676. $cell = new stdClass;
  677. $cell->attrib = $attr;
  678. $cell->content = $cont;
  679. $this->header[] = $cell;
  680. }
  681. /**
  682. * Remove a column from a table
  683. * Useful for plugins making alterations
  684. *
  685. * @param string $class
  686. */
  687. public function remove_column($class)
  688. {
  689. // Remove the header
  690. foreach ($this->header as $index=>$header){
  691. if ($header->attrib['class'] == $class){
  692. unset($this->header[$index]);
  693. break;
  694. }
  695. }
  696. // Remove cells from rows
  697. foreach ($this->rows as $i=>$row){
  698. foreach ($row->cells as $j=>$cell){
  699. if ($cell->attrib['class'] == $class){
  700. unset($this->rows[$i]->cells[$j]);
  701. break;
  702. }
  703. }
  704. }
  705. }
  706. /**
  707. * Jump to next row
  708. *
  709. * @param array $attr Row attributes
  710. */
  711. public function add_row($attr = array())
  712. {
  713. $this->rowindex++;
  714. $this->colindex = 0;
  715. $this->rows[$this->rowindex] = new stdClass;
  716. $this->rows[$this->rowindex]->attrib = $attr;
  717. $this->rows[$this->rowindex]->cells = array();
  718. }
  719. /**
  720. * Set row attributes
  721. *
  722. * @param array $attr Row attributes
  723. * @param int $index Optional row index (default current row index)
  724. */
  725. public function set_row_attribs($attr = array(), $index = null)
  726. {
  727. if (is_string($attr)) {
  728. $attr = array('class' => $attr);
  729. }
  730. if ($index === null) {
  731. $index = $this->rowindex;
  732. }
  733. // make sure row object exists (#1489094)
  734. if (!$this->rows[$index]) {
  735. $this->rows[$index] = new stdClass;
  736. }
  737. $this->rows[$index]->attrib = $attr;
  738. }
  739. /**
  740. * Get row attributes
  741. *
  742. * @param int $index Row index
  743. *
  744. * @return array Row attributes
  745. */
  746. public function get_row_attribs($index = null)
  747. {
  748. if ($index === null) {
  749. $index = $this->rowindex;
  750. }
  751. return $this->rows[$index] ? $this->rows[$index]->attrib : null;
  752. }
  753. /**
  754. * Build HTML output of the table data
  755. *
  756. * @param array $attrib Table attributes
  757. *
  758. * @return string The final table HTML code
  759. */
  760. public function show($attrib = null)
  761. {
  762. if (is_array($attrib)) {
  763. $this->attrib = array_merge($this->attrib, $attrib);
  764. }
  765. $thead = $tbody = "";
  766. // include <thead>
  767. if (!empty($this->header)) {
  768. $rowcontent = '';
  769. foreach ($this->header as $c => $col) {
  770. $rowcontent .= self::tag($this->_head_tagname(), $col->attrib, $col->content);
  771. }
  772. $thead = $this->tagname == 'table' ? self::tag('thead', null, self::tag('tr', null, $rowcontent, parent::$common_attrib)) :
  773. self::tag($this->_row_tagname(), array('class' => 'thead'), $rowcontent, parent::$common_attrib);
  774. }
  775. foreach ($this->rows as $r => $row) {
  776. $rowcontent = '';
  777. foreach ($row->cells as $c => $col) {
  778. $rowcontent .= self::tag($this->_col_tagname(), $col->attrib, $col->content);
  779. }
  780. if ($r < $this->rowindex || count($row->cells)) {
  781. $tbody .= self::tag($this->_row_tagname(), $row->attrib, $rowcontent, parent::$common_attrib);
  782. }
  783. }
  784. if ($this->attrib['rowsonly']) {
  785. return $tbody;
  786. }
  787. // add <tbody>
  788. $this->content = $thead . ($this->tagname == 'table' ? self::tag('tbody', null, $tbody) : $tbody);
  789. unset($this->attrib['cols'], $this->attrib['rowsonly']);
  790. return parent::show();
  791. }
  792. /**
  793. * Count number of rows
  794. *
  795. * @return The number of rows
  796. */
  797. public function size()
  798. {
  799. return count($this->rows);
  800. }
  801. /**
  802. * Remove table body (all rows)
  803. */
  804. public function remove_body()
  805. {
  806. $this->rows = array();
  807. $this->rowindex = 0;
  808. }
  809. /**
  810. * Getter for the corresponding tag name for table row elements
  811. */
  812. private function _row_tagname()
  813. {
  814. static $row_tagnames = array('table' => 'tr', 'ul' => 'li', '*' => 'div');
  815. return $row_tagnames[$this->tagname] ?: $row_tagnames['*'];
  816. }
  817. /**
  818. * Getter for the corresponding tag name for table row elements
  819. */
  820. private function _head_tagname()
  821. {
  822. static $head_tagnames = array('table' => 'th', '*' => 'span');
  823. return $head_tagnames[$this->tagname] ?: $head_tagnames['*'];
  824. }
  825. /**
  826. * Getter for the corresponding tag name for table cell elements
  827. */
  828. private function _col_tagname()
  829. {
  830. static $col_tagnames = array('table' => 'td', '*' => 'span');
  831. return $col_tagnames[$this->tagname] ?: $col_tagnames['*'];
  832. }
  833. }