magabriel /
trefoil
This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 1 | <?php |
||
| 2 | /* |
||
| 3 | * This file is part of the trefoil application. |
||
| 4 | * |
||
| 5 | * (c) Miguel Angel Gabriel <[email protected]> |
||
| 6 | * |
||
| 7 | * For the full copyright and license information, please view the LICENSE |
||
| 8 | * file that was distributed with this source code. |
||
| 9 | */ |
||
| 10 | |||
| 11 | namespace Trefoil\Helpers; |
||
| 12 | |||
| 13 | use Easybook\Parsers\ParserInterface; |
||
| 14 | |||
| 15 | /** |
||
| 16 | * This class: |
||
| 17 | * - For headless tables, transforms the <td> cells in first column |
||
| 18 | * within <strong> tags into <th> cells (making a vertical head). |
||
| 19 | * - Allows multiline cells. |
||
| 20 | * |
||
| 21 | * Multiline cells: |
||
| 22 | * --------------- |
||
| 23 | * |
||
| 24 | * If a line inside a cell ends with '+' char (plus sign) it will be joined |
||
| 25 | * with the next line. |
||
| 26 | * |
||
| 27 | * Automatic head cells for headless tables: |
||
| 28 | * ---------------------------------------- |
||
| 29 | * |
||
| 30 | * For a headless table (i.e. without headings in first row), cells in first |
||
| 31 | * column that are all bold (i.e. surrounded by "**") will be rendered |
||
| 32 | * as "<th>" tags instead of normal "<td>" tags, allowing formatting. |
||
| 33 | * |
||
| 34 | */ |
||
| 35 | class TableExtra |
||
| 36 | { |
||
| 37 | /** @var ParserInterface */ |
||
| 38 | protected $markdownParser; |
||
| 39 | |||
| 40 | /** |
||
| 41 | * @param ParserInterface $markdownParser |
||
| 42 | */ |
||
| 43 | 1 | public function setMarkdownParser($markdownParser) |
|
| 44 | { |
||
| 45 | 1 | $this->markdownParser = $markdownParser; |
|
| 46 | 1 | } |
|
| 47 | |||
| 48 | /** |
||
| 49 | * Processes all tables in the html string |
||
| 50 | * |
||
| 51 | * @param string $htmlString |
||
| 52 | * |
||
| 53 | * @return string |
||
| 54 | */ |
||
| 55 | 3 | public function processAllTables($htmlString) |
|
| 56 | { |
||
| 57 | 3 | $regExp = '/'; |
|
| 58 | 3 | $regExp .= '(?<table><table.*<\/table>)'; |
|
| 59 | 3 | $regExp .= '/Ums'; // Ungreedy, multiline, dotall |
|
| 60 | |||
| 61 | // PHP 5.3 compat |
||
| 62 | 3 | $me = $this; |
|
| 63 | |||
| 64 | 3 | $callback = function ($matches) use ($me) { |
|
| 65 | 3 | $table = new Table(); |
|
| 66 | 3 | $table->fromHtml($matches['table']); |
|
| 67 | |||
| 68 | 3 | if ($table->isEmpty()) { |
|
| 69 | return $matches[0]; |
||
| 70 | } |
||
| 71 | |||
| 72 | 3 | $table = $me->internalProcessExtraTable($table); |
|
| 73 | 3 | $html = $table->toHtml(); |
|
| 74 | |||
| 75 | 3 | return $html; |
|
| 76 | 3 | }; |
|
| 77 | |||
| 78 | 3 | $output = preg_replace_callback($regExp, $callback, $htmlString); |
|
| 79 | |||
| 80 | 3 | return $output; |
|
| 81 | } |
||
| 82 | |||
| 83 | /** |
||
| 84 | * @param Table $table |
||
| 85 | * |
||
| 86 | * @return Table |
||
| 87 | * @internal Should be protected but made public for PHP 5.3 compat |
||
| 88 | */ |
||
| 89 | 3 | public function internalProcessExtraTable(Table $table) |
|
| 90 | { |
||
| 91 | // process and adjusts table definition |
||
| 92 | |||
| 93 | 3 | $headless = true; |
|
| 94 | 3 | foreach ($table['thead'] as $row) { |
|
| 95 | 2 | foreach ($row as $cell) { |
|
| 96 | 2 | if (empty($cell['contents'])) { |
|
| 97 | 2 | $headless = true; |
|
| 98 | 2 | break 2; |
|
| 99 | } |
||
| 100 | 2 | } |
|
| 101 | 3 | } |
|
| 102 | |||
| 103 | // headless table |
||
| 104 | 3 | if ($headless && $table['tbody']) { |
|
| 105 | 3 | $table['tbody'] = $this->processFirstColumnCells($table['tbody']); |
|
| 106 | 3 | } |
|
| 107 | |||
| 108 | // table with head and body |
||
| 109 | 3 | if ($table['thead'] || $table['tbody']) { |
|
| 110 | |||
| 111 | 3 | $table['thead'] = $this->processMultilineCells($table['thead']); |
|
| 112 | 3 | $table['tbody'] = $this->processMultilineCells($table['tbody']); |
|
| 113 | |||
| 114 | 3 | return $table; |
|
| 115 | } |
||
| 116 | |||
| 117 | // table without head or body |
||
| 118 | $table['table'] = $this->processMultilineCells($table['table']); |
||
| 119 | |||
| 120 | return $table; |
||
| 121 | } |
||
| 122 | |||
| 123 | /** |
||
| 124 | * Join the cells that belong to multiline cells. |
||
| 125 | * |
||
| 126 | * @param $rows |
||
| 127 | * |
||
| 128 | * @return array Processed table rows |
||
| 129 | */ |
||
| 130 | 3 | protected function processMultilineCells(array $rows) |
|
| 131 | { |
||
| 132 | 3 | $newRows = $rows; |
|
| 133 | 3 | foreach ($newRows as $rowIndex => $row) { |
|
| 134 | |||
| 135 | 3 | foreach ($row as $colIndex => $col) { |
|
| 136 | 3 | $cell = $newRows[$rowIndex][$colIndex]; |
|
| 137 | 3 | $cellText = rtrim($cell['contents']); |
|
| 138 | |||
| 139 | 3 | if (substr($cellText, -1, 1) === '+') { |
|
| 140 | // continued cell |
||
| 141 | 1 | $newCell = array(); |
|
| 142 | 1 | $newCell[] = substr($cellText, 0, -1); |
|
| 143 | |||
| 144 | // find all the continuation cells (same col) |
||
| 145 | 1 | for ($nextRowIndex = $rowIndex + 1; $nextRowIndex < count($newRows); $nextRowIndex++) { |
|
|
0 ignored issues
–
show
|
|||
| 146 | |||
| 147 | 1 | $nextCell = $newRows[$nextRowIndex][$colIndex]; |
|
| 148 | 1 | $cellText = rtrim($nextCell['contents']); |
|
| 149 | |||
| 150 | 1 | $newRows[$nextRowIndex][$colIndex]['contents'] = ''; |
|
| 151 | |||
| 152 | // continued cell? |
||
| 153 | 1 | $continued = (substr($cellText, -1, 1) === '+'); |
|
| 154 | |||
| 155 | 1 | if ($continued) { |
|
| 156 | // clean the ending (+) |
||
| 157 | 1 | $cellText = substr($cellText, 0, -1); |
|
| 158 | 1 | } |
|
| 159 | |||
| 160 | // save cleaned text |
||
| 161 | 1 | $newCell[] = $cellText; |
|
| 162 | |||
| 163 | 1 | if (!$continued) { |
|
| 164 | // no more continuations |
||
| 165 | 1 | break; |
|
| 166 | } |
||
| 167 | 1 | } |
|
| 168 | |||
| 169 | 1 | if ($this->markdownParser) { |
|
| 170 | 1 | $parsedCell = $this->markdownParser->transform(join("\n\n", $newCell)); |
|
| 171 | 1 | } else { |
|
| 172 | // safe default |
||
| 173 | $parsedCell = join("<br/>", $newCell); |
||
| 174 | } |
||
| 175 | |||
| 176 | 1 | $newRows[$rowIndex][$colIndex]['contents'] = $parsedCell; |
|
| 177 | 1 | } |
|
| 178 | 3 | } |
|
| 179 | 3 | } |
|
| 180 | |||
| 181 | // remove empty rows left by the process |
||
| 182 | 3 | $newRows2 = array(); |
|
| 183 | 3 | foreach ($newRows as $rowIndex => $row) { |
|
| 184 | |||
| 185 | 3 | $emptyRow = true; |
|
| 186 | 3 | foreach ($row as $colIndex => $col) { |
|
| 187 | 3 | $cellText = trim($col['contents']); |
|
| 188 | 3 | if (!empty($cellText)) { |
|
| 189 | 3 | $emptyRow = false; |
|
| 190 | 3 | } |
|
| 191 | 3 | } |
|
| 192 | |||
| 193 | 3 | if (!$emptyRow) { |
|
| 194 | 3 | $newRows2[] = $row; |
|
| 195 | 3 | } |
|
| 196 | 3 | } |
|
| 197 | |||
| 198 | 3 | return $newRows2; |
|
| 199 | } |
||
| 200 | |||
| 201 | /** |
||
| 202 | * Converts <td> cells into <th> for first column cells that are fully bold. |
||
| 203 | * |
||
| 204 | * @param array $rows |
||
| 205 | * |
||
| 206 | * @return array Processed rows |
||
| 207 | */ |
||
| 208 | 3 | protected function processFirstColumnCells(array $rows) |
|
| 209 | { |
||
| 210 | 3 | $newRows = $rows; |
|
| 211 | 3 | foreach ($newRows as $rowIndex => $row) { |
|
| 212 | |||
| 213 | // examine first cell ir row |
||
| 214 | 3 | $cell = $newRows[$rowIndex][0]; |
|
| 215 | 3 | $cellText = rtrim($cell['contents']); |
|
| 216 | |||
| 217 | 3 | $regExp = '/^<strong>.*<\/strong>$/Us'; |
|
| 218 | 3 | if (preg_match($regExp, $cellText, $matches)) { |
|
| 219 | 1 | $cell['tag'] = 'th'; |
|
| 220 | |||
| 221 | // change cell to <th> |
||
| 222 | 1 | $newRows[$rowIndex][0]['tag'] = 'th'; |
|
| 223 | 1 | } |
|
| 224 | 3 | } |
|
| 225 | |||
| 226 | 3 | return $newRows; |
|
| 227 | } |
||
| 228 | |||
| 229 | } |
||
| 230 |
If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration: