Drupal investigation

Table.php 28KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975
  1. <?php
  2. /**
  3. * Utility for printing tables from commandline scripts.
  4. *
  5. * PHP versions 5 and 7
  6. *
  7. * All rights reserved.
  8. *
  9. * Redistribution and use in source and binary forms, with or without
  10. * modification, are permitted provided that the following conditions are met:
  11. *
  12. * o Redistributions of source code must retain the above copyright notice,
  13. * this list of conditions and the following disclaimer.
  14. * o Redistributions in binary form must reproduce the above copyright notice,
  15. * this list of conditions and the following disclaimer in the documentation
  16. * and/or other materials provided with the distribution.
  17. * o The names of the authors may not be used to endorse or promote products
  18. * derived from this software without specific prior written permission.
  19. *
  20. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  21. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  22. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  23. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  24. * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  25. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  26. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  27. * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  28. * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  29. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  30. * POSSIBILITY OF SUCH DAMAGE.
  31. *
  32. * @category Console
  33. * @package Console_Table
  34. * @author Richard Heyes <richard@phpguru.org>
  35. * @author Jan Schneider <jan@horde.org>
  36. * @copyright 2002-2005 Richard Heyes
  37. * @copyright 2006-2008 Jan Schneider
  38. * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause)
  39. * @version CVS: $Id$
  40. * @link http://pear.php.net/package/Console_Table
  41. */
  42. define('CONSOLE_TABLE_HORIZONTAL_RULE', 1);
  43. define('CONSOLE_TABLE_ALIGN_LEFT', -1);
  44. define('CONSOLE_TABLE_ALIGN_CENTER', 0);
  45. define('CONSOLE_TABLE_ALIGN_RIGHT', 1);
  46. define('CONSOLE_TABLE_BORDER_ASCII', -1);
  47. /**
  48. * The main class.
  49. *
  50. * @category Console
  51. * @package Console_Table
  52. * @author Jan Schneider <jan@horde.org>
  53. * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause)
  54. * @link http://pear.php.net/package/Console_Table
  55. */
  56. class Console_Table
  57. {
  58. /**
  59. * The table headers.
  60. *
  61. * @var array
  62. */
  63. var $_headers = array();
  64. /**
  65. * The data of the table.
  66. *
  67. * @var array
  68. */
  69. var $_data = array();
  70. /**
  71. * The maximum number of columns in a row.
  72. *
  73. * @var integer
  74. */
  75. var $_max_cols = 0;
  76. /**
  77. * The maximum number of rows in the table.
  78. *
  79. * @var integer
  80. */
  81. var $_max_rows = 0;
  82. /**
  83. * Lengths of the columns, calculated when rows are added to the table.
  84. *
  85. * @var array
  86. */
  87. var $_cell_lengths = array();
  88. /**
  89. * Heights of the rows.
  90. *
  91. * @var array
  92. */
  93. var $_row_heights = array();
  94. /**
  95. * How many spaces to use to pad the table.
  96. *
  97. * @var integer
  98. */
  99. var $_padding = 1;
  100. /**
  101. * Column filters.
  102. *
  103. * @var array
  104. */
  105. var $_filters = array();
  106. /**
  107. * Columns to calculate totals for.
  108. *
  109. * @var array
  110. */
  111. var $_calculateTotals;
  112. /**
  113. * Alignment of the columns.
  114. *
  115. * @var array
  116. */
  117. var $_col_align = array();
  118. /**
  119. * Default alignment of columns.
  120. *
  121. * @var integer
  122. */
  123. var $_defaultAlign;
  124. /**
  125. * Character set of the data.
  126. *
  127. * @var string
  128. */
  129. var $_charset = 'utf-8';
  130. /**
  131. * Border characters.
  132. * Allowed keys:
  133. * - intersection - intersection ("+")
  134. * - horizontal - horizontal rule character ("-")
  135. * - vertical - vertical rule character ("|")
  136. *
  137. * @var array
  138. */
  139. var $_border = array(
  140. 'intersection' => '+',
  141. 'horizontal' => '-',
  142. 'vertical' => '|',
  143. );
  144. /**
  145. * If borders are shown or not
  146. * Allowed keys: top, right, bottom, left, inner: true and false
  147. *
  148. * @var array
  149. */
  150. var $_borderVisibility = array(
  151. 'top' => true,
  152. 'right' => true,
  153. 'bottom' => true,
  154. 'left' => true,
  155. 'inner' => true
  156. );
  157. /**
  158. * Whether the data has ANSI colors.
  159. *
  160. * @var Console_Color2
  161. */
  162. var $_ansiColor = false;
  163. /**
  164. * Constructor.
  165. *
  166. * @param integer $align Default alignment. One of
  167. * CONSOLE_TABLE_ALIGN_LEFT,
  168. * CONSOLE_TABLE_ALIGN_CENTER or
  169. * CONSOLE_TABLE_ALIGN_RIGHT.
  170. * @param string $border The character used for table borders or
  171. * CONSOLE_TABLE_BORDER_ASCII.
  172. * @param integer $padding How many spaces to use to pad the table.
  173. * @param string $charset A charset supported by the mbstring PHP
  174. * extension.
  175. * @param boolean $color Whether the data contains ansi color codes.
  176. */
  177. function __construct($align = CONSOLE_TABLE_ALIGN_LEFT,
  178. $border = CONSOLE_TABLE_BORDER_ASCII, $padding = 1,
  179. $charset = null, $color = false)
  180. {
  181. $this->_defaultAlign = $align;
  182. $this->setBorder($border);
  183. $this->_padding = $padding;
  184. if ($color) {
  185. if (!class_exists('Console_Color2')) {
  186. include_once 'Console/Color2.php';
  187. }
  188. $this->_ansiColor = new Console_Color2();
  189. }
  190. if (!empty($charset)) {
  191. $this->setCharset($charset);
  192. }
  193. }
  194. /**
  195. * Converts an array to a table.
  196. *
  197. * @param array $headers Headers for the table.
  198. * @param array $data A two dimensional array with the table
  199. * data.
  200. * @param boolean $returnObject Whether to return the Console_Table object
  201. * instead of the rendered table.
  202. *
  203. * @static
  204. *
  205. * @return Console_Table|string A Console_Table object or the generated
  206. * table.
  207. */
  208. function fromArray($headers, $data, $returnObject = false)
  209. {
  210. if (!is_array($headers) || !is_array($data)) {
  211. return false;
  212. }
  213. $table = new Console_Table();
  214. $table->setHeaders($headers);
  215. foreach ($data as $row) {
  216. $table->addRow($row);
  217. }
  218. return $returnObject ? $table : $table->getTable();
  219. }
  220. /**
  221. * Adds a filter to a column.
  222. *
  223. * Filters are standard PHP callbacks which are run on the data before
  224. * table generation is performed. Filters are applied in the order they
  225. * are added. The callback function must accept a single argument, which
  226. * is a single table cell.
  227. *
  228. * @param integer $col Column to apply filter to.
  229. * @param mixed &$callback PHP callback to apply.
  230. *
  231. * @return void
  232. */
  233. function addFilter($col, &$callback)
  234. {
  235. $this->_filters[] = array($col, &$callback);
  236. }
  237. /**
  238. * Sets the charset of the provided table data.
  239. *
  240. * @param string $charset A charset supported by the mbstring PHP
  241. * extension.
  242. *
  243. * @return void
  244. */
  245. function setCharset($charset)
  246. {
  247. $locale = setlocale(LC_CTYPE, 0);
  248. setlocale(LC_CTYPE, 'en_US');
  249. $this->_charset = strtolower($charset);
  250. setlocale(LC_CTYPE, $locale);
  251. }
  252. /**
  253. * Set the table border settings
  254. *
  255. * Border definition modes:
  256. * - CONSOLE_TABLE_BORDER_ASCII: Default border with +, - and |
  257. * - array with keys "intersection", "horizontal" and "vertical"
  258. * - single character string that sets all three of the array keys
  259. *
  260. * @param mixed $border Border definition
  261. *
  262. * @return void
  263. * @see $_border
  264. */
  265. function setBorder($border)
  266. {
  267. if ($border === CONSOLE_TABLE_BORDER_ASCII) {
  268. $intersection = '+';
  269. $horizontal = '-';
  270. $vertical = '|';
  271. } else if (is_string($border)) {
  272. $intersection = $horizontal = $vertical = $border;
  273. } else if ($border == '') {
  274. $intersection = $horizontal = $vertical = '';
  275. } else {
  276. extract($border);
  277. }
  278. $this->_border = array(
  279. 'intersection' => $intersection,
  280. 'horizontal' => $horizontal,
  281. 'vertical' => $vertical,
  282. );
  283. }
  284. /**
  285. * Set which borders shall be shown.
  286. *
  287. * @param array $visibility Visibility settings.
  288. * Allowed keys: left, right, top, bottom, inner
  289. *
  290. * @return void
  291. * @see $_borderVisibility
  292. */
  293. function setBorderVisibility($visibility)
  294. {
  295. $this->_borderVisibility = array_merge(
  296. $this->_borderVisibility,
  297. array_intersect_key(
  298. $visibility,
  299. $this->_borderVisibility
  300. )
  301. );
  302. }
  303. /**
  304. * Sets the alignment for the columns.
  305. *
  306. * @param integer $col_id The column number.
  307. * @param integer $align Alignment to set for this column. One of
  308. * CONSOLE_TABLE_ALIGN_LEFT
  309. * CONSOLE_TABLE_ALIGN_CENTER
  310. * CONSOLE_TABLE_ALIGN_RIGHT.
  311. *
  312. * @return void
  313. */
  314. function setAlign($col_id, $align = CONSOLE_TABLE_ALIGN_LEFT)
  315. {
  316. switch ($align) {
  317. case CONSOLE_TABLE_ALIGN_CENTER:
  318. $pad = STR_PAD_BOTH;
  319. break;
  320. case CONSOLE_TABLE_ALIGN_RIGHT:
  321. $pad = STR_PAD_LEFT;
  322. break;
  323. default:
  324. $pad = STR_PAD_RIGHT;
  325. break;
  326. }
  327. $this->_col_align[$col_id] = $pad;
  328. }
  329. /**
  330. * Specifies which columns are to have totals calculated for them and
  331. * added as a new row at the bottom.
  332. *
  333. * @param array $cols Array of column numbers (starting with 0).
  334. *
  335. * @return void
  336. */
  337. function calculateTotalsFor($cols)
  338. {
  339. $this->_calculateTotals = $cols;
  340. }
  341. /**
  342. * Sets the headers for the columns.
  343. *
  344. * @param array $headers The column headers.
  345. *
  346. * @return void
  347. */
  348. function setHeaders($headers)
  349. {
  350. $this->_headers = array(array_values($headers));
  351. $this->_updateRowsCols($headers);
  352. }
  353. /**
  354. * Adds a row to the table.
  355. *
  356. * @param array $row The row data to add.
  357. * @param boolean $append Whether to append or prepend the row.
  358. *
  359. * @return void
  360. */
  361. function addRow($row, $append = true)
  362. {
  363. if ($append) {
  364. $this->_data[] = array_values($row);
  365. } else {
  366. array_unshift($this->_data, array_values($row));
  367. }
  368. $this->_updateRowsCols($row);
  369. }
  370. /**
  371. * Inserts a row after a given row number in the table.
  372. *
  373. * If $row_id is not given it will prepend the row.
  374. *
  375. * @param array $row The data to insert.
  376. * @param integer $row_id Row number to insert before.
  377. *
  378. * @return void
  379. */
  380. function insertRow($row, $row_id = 0)
  381. {
  382. array_splice($this->_data, $row_id, 0, array($row));
  383. $this->_updateRowsCols($row);
  384. }
  385. /**
  386. * Adds a column to the table.
  387. *
  388. * @param array $col_data The data of the column.
  389. * @param integer $col_id The column index to populate.
  390. * @param integer $row_id If starting row is not zero, specify it here.
  391. *
  392. * @return void
  393. */
  394. function addCol($col_data, $col_id = 0, $row_id = 0)
  395. {
  396. foreach ($col_data as $col_cell) {
  397. $this->_data[$row_id++][$col_id] = $col_cell;
  398. }
  399. $this->_updateRowsCols();
  400. $this->_max_cols = max($this->_max_cols, $col_id + 1);
  401. }
  402. /**
  403. * Adds data to the table.
  404. *
  405. * @param array $data A two dimensional array with the table data.
  406. * @param integer $col_id Starting column number.
  407. * @param integer $row_id Starting row number.
  408. *
  409. * @return void
  410. */
  411. function addData($data, $col_id = 0, $row_id = 0)
  412. {
  413. foreach ($data as $row) {
  414. if ($row === CONSOLE_TABLE_HORIZONTAL_RULE) {
  415. $this->_data[$row_id] = CONSOLE_TABLE_HORIZONTAL_RULE;
  416. $row_id++;
  417. continue;
  418. }
  419. $starting_col = $col_id;
  420. foreach ($row as $cell) {
  421. $this->_data[$row_id][$starting_col++] = $cell;
  422. }
  423. $this->_updateRowsCols();
  424. $this->_max_cols = max($this->_max_cols, $starting_col);
  425. $row_id++;
  426. }
  427. }
  428. /**
  429. * Adds a horizontal seperator to the table.
  430. *
  431. * @return void
  432. */
  433. function addSeparator()
  434. {
  435. $this->_data[] = CONSOLE_TABLE_HORIZONTAL_RULE;
  436. }
  437. /**
  438. * Returns the generated table.
  439. *
  440. * @return string The generated table.
  441. */
  442. function getTable()
  443. {
  444. $this->_applyFilters();
  445. $this->_calculateTotals();
  446. $this->_validateTable();
  447. return $this->_buildTable();
  448. }
  449. /**
  450. * Calculates totals for columns.
  451. *
  452. * @return void
  453. */
  454. function _calculateTotals()
  455. {
  456. if (empty($this->_calculateTotals)) {
  457. return;
  458. }
  459. $this->addSeparator();
  460. $totals = array();
  461. foreach ($this->_data as $row) {
  462. if (is_array($row)) {
  463. foreach ($this->_calculateTotals as $columnID) {
  464. $totals[$columnID] += $row[$columnID];
  465. }
  466. }
  467. }
  468. $this->_data[] = $totals;
  469. $this->_updateRowsCols();
  470. }
  471. /**
  472. * Applies any column filters to the data.
  473. *
  474. * @return void
  475. */
  476. function _applyFilters()
  477. {
  478. if (empty($this->_filters)) {
  479. return;
  480. }
  481. foreach ($this->_filters as $filter) {
  482. $column = $filter[0];
  483. $callback = $filter[1];
  484. foreach ($this->_data as $row_id => $row_data) {
  485. if ($row_data !== CONSOLE_TABLE_HORIZONTAL_RULE) {
  486. $this->_data[$row_id][$column] =
  487. call_user_func($callback, $row_data[$column]);
  488. }
  489. }
  490. }
  491. }
  492. /**
  493. * Ensures that column and row counts are correct.
  494. *
  495. * @return void
  496. */
  497. function _validateTable()
  498. {
  499. if (!empty($this->_headers)) {
  500. $this->_calculateRowHeight(-1, $this->_headers[0]);
  501. }
  502. for ($i = 0; $i < $this->_max_rows; $i++) {
  503. for ($j = 0; $j < $this->_max_cols; $j++) {
  504. if (!isset($this->_data[$i][$j]) &&
  505. (!isset($this->_data[$i]) ||
  506. $this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE)) {
  507. $this->_data[$i][$j] = '';
  508. }
  509. }
  510. $this->_calculateRowHeight($i, $this->_data[$i]);
  511. if ($this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE) {
  512. ksort($this->_data[$i]);
  513. }
  514. }
  515. $this->_splitMultilineRows();
  516. // Update cell lengths.
  517. for ($i = 0; $i < count($this->_headers); $i++) {
  518. $this->_calculateCellLengths($this->_headers[$i]);
  519. }
  520. for ($i = 0; $i < $this->_max_rows; $i++) {
  521. $this->_calculateCellLengths($this->_data[$i]);
  522. }
  523. ksort($this->_data);
  524. }
  525. /**
  526. * Splits multiline rows into many smaller one-line rows.
  527. *
  528. * @return void
  529. */
  530. function _splitMultilineRows()
  531. {
  532. ksort($this->_data);
  533. $sections = array(&$this->_headers, &$this->_data);
  534. $max_rows = array(count($this->_headers), $this->_max_rows);
  535. $row_height_offset = array(-1, 0);
  536. for ($s = 0; $s <= 1; $s++) {
  537. $inserted = 0;
  538. $new_data = $sections[$s];
  539. for ($i = 0; $i < $max_rows[$s]; $i++) {
  540. // Process only rows that have many lines.
  541. $height = $this->_row_heights[$i + $row_height_offset[$s]];
  542. if ($height > 1) {
  543. // Split column data into one-liners.
  544. $split = array();
  545. for ($j = 0; $j < $this->_max_cols; $j++) {
  546. $split[$j] = preg_split('/\r?\n|\r/',
  547. $sections[$s][$i][$j]);
  548. }
  549. $new_rows = array();
  550. // Construct new 'virtual' rows - insert empty strings for
  551. // columns that have less lines that the highest one.
  552. for ($i2 = 0; $i2 < $height; $i2++) {
  553. for ($j = 0; $j < $this->_max_cols; $j++) {
  554. $new_rows[$i2][$j] = !isset($split[$j][$i2])
  555. ? ''
  556. : $split[$j][$i2];
  557. }
  558. }
  559. // Replace current row with smaller rows. $inserted is
  560. // used to take account of bigger array because of already
  561. // inserted rows.
  562. array_splice($new_data, $i + $inserted, 1, $new_rows);
  563. $inserted += count($new_rows) - 1;
  564. }
  565. }
  566. // Has the data been modified?
  567. if ($inserted > 0) {
  568. $sections[$s] = $new_data;
  569. $this->_updateRowsCols();
  570. }
  571. }
  572. }
  573. /**
  574. * Builds the table.
  575. *
  576. * @return string The generated table string.
  577. */
  578. function _buildTable()
  579. {
  580. if (!count($this->_data)) {
  581. return '';
  582. }
  583. $vertical = $this->_border['vertical'];
  584. $separator = $this->_getSeparator();
  585. $return = array();
  586. for ($i = 0; $i < count($this->_data); $i++) {
  587. for ($j = 0; $j < count($this->_data[$i]); $j++) {
  588. if ($this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE &&
  589. $this->_strlen($this->_data[$i][$j]) <
  590. $this->_cell_lengths[$j]) {
  591. $this->_data[$i][$j] = $this->_strpad($this->_data[$i][$j],
  592. $this->_cell_lengths[$j],
  593. ' ',
  594. $this->_col_align[$j]);
  595. }
  596. }
  597. if ($this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE) {
  598. $row_begin = $this->_borderVisibility['left']
  599. ? $vertical . str_repeat(' ', $this->_padding)
  600. : '';
  601. $row_end = $this->_borderVisibility['right']
  602. ? str_repeat(' ', $this->_padding) . $vertical
  603. : '';
  604. $implode_char = str_repeat(' ', $this->_padding) . $vertical
  605. . str_repeat(' ', $this->_padding);
  606. $return[] = $row_begin
  607. . implode($implode_char, $this->_data[$i]) . $row_end;
  608. } elseif (!empty($separator)) {
  609. $return[] = $separator;
  610. }
  611. }
  612. $return = implode(PHP_EOL, $return);
  613. if (!empty($separator)) {
  614. if ($this->_borderVisibility['inner']) {
  615. $return = $separator . PHP_EOL . $return;
  616. }
  617. if ($this->_borderVisibility['bottom']) {
  618. $return .= PHP_EOL . $separator;
  619. }
  620. }
  621. $return .= PHP_EOL;
  622. if (!empty($this->_headers)) {
  623. $return = $this->_getHeaderLine() . PHP_EOL . $return;
  624. }
  625. return $return;
  626. }
  627. /**
  628. * Creates a horizontal separator for header separation and table
  629. * start/end etc.
  630. *
  631. * @return string The horizontal separator.
  632. */
  633. function _getSeparator()
  634. {
  635. if (!$this->_border) {
  636. return;
  637. }
  638. $horizontal = $this->_border['horizontal'];
  639. $intersection = $this->_border['intersection'];
  640. $return = array();
  641. foreach ($this->_cell_lengths as $cl) {
  642. $return[] = str_repeat($horizontal, $cl);
  643. }
  644. $row_begin = $this->_borderVisibility['left']
  645. ? $intersection . str_repeat($horizontal, $this->_padding)
  646. : '';
  647. $row_end = $this->_borderVisibility['right']
  648. ? str_repeat($horizontal, $this->_padding) . $intersection
  649. : '';
  650. $implode_char = str_repeat($horizontal, $this->_padding) . $intersection
  651. . str_repeat($horizontal, $this->_padding);
  652. return $row_begin . implode($implode_char, $return) . $row_end;
  653. }
  654. /**
  655. * Returns the header line for the table.
  656. *
  657. * @return string The header line of the table.
  658. */
  659. function _getHeaderLine()
  660. {
  661. // Make sure column count is correct
  662. for ($j = 0; $j < count($this->_headers); $j++) {
  663. for ($i = 0; $i < $this->_max_cols; $i++) {
  664. if (!isset($this->_headers[$j][$i])) {
  665. $this->_headers[$j][$i] = '';
  666. }
  667. }
  668. }
  669. for ($j = 0; $j < count($this->_headers); $j++) {
  670. for ($i = 0; $i < count($this->_headers[$j]); $i++) {
  671. if ($this->_strlen($this->_headers[$j][$i]) <
  672. $this->_cell_lengths[$i]) {
  673. $this->_headers[$j][$i] =
  674. $this->_strpad($this->_headers[$j][$i],
  675. $this->_cell_lengths[$i],
  676. ' ',
  677. $this->_col_align[$i]);
  678. }
  679. }
  680. }
  681. $vertical = $this->_border['vertical'];
  682. $row_begin = $this->_borderVisibility['left']
  683. ? $vertical . str_repeat(' ', $this->_padding)
  684. : '';
  685. $row_end = $this->_borderVisibility['right']
  686. ? str_repeat(' ', $this->_padding) . $vertical
  687. : '';
  688. $implode_char = str_repeat(' ', $this->_padding) . $vertical
  689. . str_repeat(' ', $this->_padding);
  690. $separator = $this->_getSeparator();
  691. if (!empty($separator) && $this->_borderVisibility['top']) {
  692. $return[] = $separator;
  693. }
  694. for ($j = 0; $j < count($this->_headers); $j++) {
  695. $return[] = $row_begin
  696. . implode($implode_char, $this->_headers[$j]) . $row_end;
  697. }
  698. return implode(PHP_EOL, $return);
  699. }
  700. /**
  701. * Updates values for maximum columns and rows.
  702. *
  703. * @param array $rowdata Data array of a single row.
  704. *
  705. * @return void
  706. */
  707. function _updateRowsCols($rowdata = null)
  708. {
  709. // Update maximum columns.
  710. $this->_max_cols = max($this->_max_cols, count($rowdata));
  711. // Update maximum rows.
  712. ksort($this->_data);
  713. $keys = array_keys($this->_data);
  714. $this->_max_rows = end($keys) + 1;
  715. switch ($this->_defaultAlign) {
  716. case CONSOLE_TABLE_ALIGN_CENTER:
  717. $pad = STR_PAD_BOTH;
  718. break;
  719. case CONSOLE_TABLE_ALIGN_RIGHT:
  720. $pad = STR_PAD_LEFT;
  721. break;
  722. default:
  723. $pad = STR_PAD_RIGHT;
  724. break;
  725. }
  726. // Set default column alignments
  727. for ($i = 0; $i < $this->_max_cols; $i++) {
  728. if (!isset($this->_col_align[$i])) {
  729. $this->_col_align[$i] = $pad;
  730. }
  731. }
  732. }
  733. /**
  734. * Calculates the maximum length for each column of a row.
  735. *
  736. * @param array $row The row data.
  737. *
  738. * @return void
  739. */
  740. function _calculateCellLengths($row)
  741. {
  742. for ($i = 0; $i < count($row); $i++) {
  743. if (!isset($this->_cell_lengths[$i])) {
  744. $this->_cell_lengths[$i] = 0;
  745. }
  746. $this->_cell_lengths[$i] = max($this->_cell_lengths[$i],
  747. $this->_strlen($row[$i]));
  748. }
  749. }
  750. /**
  751. * Calculates the maximum height for all columns of a row.
  752. *
  753. * @param integer $row_number The row number.
  754. * @param array $row The row data.
  755. *
  756. * @return void
  757. */
  758. function _calculateRowHeight($row_number, $row)
  759. {
  760. if (!isset($this->_row_heights[$row_number])) {
  761. $this->_row_heights[$row_number] = 1;
  762. }
  763. // Do not process horizontal rule rows.
  764. if ($row === CONSOLE_TABLE_HORIZONTAL_RULE) {
  765. return;
  766. }
  767. for ($i = 0, $c = count($row); $i < $c; ++$i) {
  768. $lines = preg_split('/\r?\n|\r/', $row[$i]);
  769. $this->_row_heights[$row_number] = max($this->_row_heights[$row_number],
  770. count($lines));
  771. }
  772. }
  773. /**
  774. * Returns the character length of a string.
  775. *
  776. * @param string $str A multibyte or singlebyte string.
  777. *
  778. * @return integer The string length.
  779. */
  780. function _strlen($str)
  781. {
  782. static $mbstring;
  783. // Strip ANSI color codes if requested.
  784. if ($this->_ansiColor) {
  785. $str = $this->_ansiColor->strip($str);
  786. }
  787. // Cache expensive function_exists() calls.
  788. if (!isset($mbstring)) {
  789. $mbstring = function_exists('mb_strwidth');
  790. }
  791. if ($mbstring) {
  792. return mb_strwidth($str, $this->_charset);
  793. }
  794. return strlen($str);
  795. }
  796. /**
  797. * Returns part of a string.
  798. *
  799. * @param string $string The string to be converted.
  800. * @param integer $start The part's start position, zero based.
  801. * @param integer $length The part's length.
  802. *
  803. * @return string The string's part.
  804. */
  805. function _substr($string, $start, $length = null)
  806. {
  807. static $mbstring;
  808. // Cache expensive function_exists() calls.
  809. if (!isset($mbstring)) {
  810. $mbstring = function_exists('mb_substr');
  811. }
  812. if (is_null($length)) {
  813. $length = $this->_strlen($string);
  814. }
  815. if ($mbstring) {
  816. $ret = @mb_substr($string, $start, $length, $this->_charset);
  817. if (!empty($ret)) {
  818. return $ret;
  819. }
  820. }
  821. return substr($string, $start, $length);
  822. }
  823. /**
  824. * Returns a string padded to a certain length with another string.
  825. *
  826. * This method behaves exactly like str_pad but is multibyte safe.
  827. *
  828. * @param string $input The string to be padded.
  829. * @param integer $length The length of the resulting string.
  830. * @param string $pad The string to pad the input string with. Must
  831. * be in the same charset like the input string.
  832. * @param const $type The padding type. One of STR_PAD_LEFT,
  833. * STR_PAD_RIGHT, or STR_PAD_BOTH.
  834. *
  835. * @return string The padded string.
  836. */
  837. function _strpad($input, $length, $pad = ' ', $type = STR_PAD_RIGHT)
  838. {
  839. $mb_length = $this->_strlen($input);
  840. $sb_length = strlen($input);
  841. $pad_length = $this->_strlen($pad);
  842. /* Return if we already have the length. */
  843. if ($mb_length >= $length) {
  844. return $input;
  845. }
  846. /* Shortcut for single byte strings. */
  847. if ($mb_length == $sb_length && $pad_length == strlen($pad)) {
  848. return str_pad($input, $length, $pad, $type);
  849. }
  850. switch ($type) {
  851. case STR_PAD_LEFT:
  852. $left = $length - $mb_length;
  853. $output = $this->_substr(str_repeat($pad, ceil($left / $pad_length)),
  854. 0, $left, $this->_charset) . $input;
  855. break;
  856. case STR_PAD_BOTH:
  857. $left = floor(($length - $mb_length) / 2);
  858. $right = ceil(($length - $mb_length) / 2);
  859. $output = $this->_substr(str_repeat($pad, ceil($left / $pad_length)),
  860. 0, $left, $this->_charset) .
  861. $input .
  862. $this->_substr(str_repeat($pad, ceil($right / $pad_length)),
  863. 0, $right, $this->_charset);
  864. break;
  865. case STR_PAD_RIGHT:
  866. $right = $length - $mb_length;
  867. $output = $input .
  868. $this->_substr(str_repeat($pad, ceil($right / $pad_length)),
  869. 0, $right, $this->_charset);
  870. break;
  871. }
  872. return $output;
  873. }
  874. }