PluginProbe
TablePress – Tables in WordPress made easy / 3.4
TablePress – Tables in WordPress made easy v3.4
3.4 3.3.4 3.3.3 3.3.2 3.3.1 trunk 1.12 1.14 1.9.2 2.0.4 2.1.7 2.1.8 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3 2.3.1 2.3.2 2.4 2.4.1 2.4.2 2.4.3 All 45 releases
← All changes | classes/class-render.php +424 -338 1.9.2 → 3.4 View file →
@@ -7,13 +7,16 @@
7 7 * @author Tobias Bäthge
8 8 * @since 1.0.0
9 9 */
10 10
11 +declare(strict_types=1);
12 +
11 13 // Prohibit direct script loading.
12 14 defined( 'ABSPATH' ) || die( 'No direct script access allowed!' );
13 15
14 16 /**
15 17 * TablePress Rendering Class
18 + *
16 19 * @package TablePress
17 20 * @subpackage Rendering
18 21 * @author Tobias Bäthge
19 22 * @since 1.0.0
@@ -23,25 +26,25 @@
23 26 /**
24 27 * Table data that is rendered.
25 28 *
26 29 * @since 1.0.0
27 - * @var array
30 + * @var array<string, mixed>
28 31 */
29 - protected $table = array();
32 + protected array $table = array();
30 33
31 34 /**
32 35 * Table options that influence the output result.
33 36 *
34 37 * @since 1.0.0
35 - * @var array
38 + * @var array<string, mixed>
36 39 */
37 - protected $render_options = array();
40 + protected array $render_options = array();
38 41
39 42 /**
40 - * Rendered HTML of the table.
43 + * Rendered HTML code of the table or PHP array.
41 44 *
42 45 * @since 1.0.0
43 - * @var string
46 + * @var string|array<int, array<int, string>>
44 47 */
45 48 protected $output;
46 49
47 50 /**
@@ -47,11 +50,11 @@
47 50 /**
48 51 * Trigger words for colspan, rowspan, or the combination of both.
49 52 *
50 53 * @since 1.0.0
51 - * @var array
54 + * @var array<string, string>
52 55 */
53 - protected $span_trigger = array(
56 + protected array $span_trigger = array(
54 57 'colspan' => '#colspan#',
55 58 'rowspan' => '#rowspan#',
56 59 'span' => '#span#',
57 60 );
@@ -59,35 +62,40 @@
59 62 /**
60 63 * Buffer to store the counts of rowspan per column, initialized in _render_table().
61 64 *
62 65 * @since 1.0.0
63 - * @var array
66 + * @var int[]
64 67 */
65 - protected $rowspan = array();
68 + protected array $rowspan = array();
66 69
67 70 /**
68 71 * Buffer to store the counts of colspan per row, initialized in _render_table().
69 72 *
70 73 * @since 1.0.0
71 - * @var array
74 + * @var int[]
72 75 */
73 - protected $colspan = array();
76 + protected array $colspan = array();
74 77
75 78 /**
79 + * Whether the table has connected cells (colspan or rowspan), set in _render_table().
80 + *
81 + * @since 3.0.0
82 + */
83 + protected bool $tbody_has_connected_cells = false;
84 +
85 + /**
76 86 * Index of the last row of the visible data in the table, set in _render_table().
77 87 *
78 88 * @since 1.0.0
79 - * @var int
80 89 */
81 - protected $last_row_idx;
90 + protected int $last_row_idx;
82 91
83 92 /**
84 93 * Index of the last column of the visible data in the table, set in _render_table().
85 94 *
86 95 * @since 1.0.0
87 - * @var int
88 96 */
89 - protected $last_column_idx;
97 + protected int $last_column_idx;
90 98
91 99 /**
92 100 * Class constructor.
93 101 *
@@ -101,21 +109,21 @@
101 109 * Set the table (data, options, visibility, ...) that is to be rendered.
102 110 *
103 111 * @since 1.0.0
104 112 *
105 - * @param array $table Table to be rendered.
106 - * @param array $render_options Options for rendering, from both "Edit" screen and Shortcode.
113 + * @param array<string, mixed> $table Table to be rendered.
114 + * @param array<string, mixed> $render_options Options for rendering, from both "Edit" screen and Shortcode.
107 115 */
108 - public function set_input( array $table, array $render_options ) {
116 + public function set_input( array $table, array $render_options ): void {
109 117 $this->table = $table;
110 118 $this->render_options = $render_options;
111 119 /**
112 - * Filter the table before the render process.
120 + * Filters the table before the render process.
113 121 *
114 122 * @since 1.0.0
115 123 *
116 - * @param array $table The table.
117 - * @param array $render_options The render options for the table.
124 + * @param array<string, mixed> $table The table.
125 + * @param array<string, mixed> $render_options The render options for the table.
118 126 */
119 127 $this->table = apply_filters( 'tablepress_table_raw_render_data', $this->table, $this->render_options );
120 128 }
121 129
@@ -122,18 +130,39 @@
122 130 /**
123 131 * Process the table rendering and return the HTML output.
124 132 *
125 133 * @since 1.0.0
134 + * @since 2.0.0 Add the $format parameter.
126 135 *
127 - * @return string HTML of the rendered table (or an error message).
136 + * @param string $format Optional. Output format, 'html' (default) or 'array'.
137 + * @return string|array<int, array<int, string>> HTML code of the rendered table, or a PHP array, or an error message.
128 138 */
129 - public function get_output() {
139 + public function get_output( string $format = 'html' ) /* : string|array */ {
130 140 // Evaluate math expressions/formulas.
131 141 $this->_evaluate_table_data();
132 142 // Remove hidden rows and columns.
133 143 $this->_prepare_render_data();
134 - // Generate HTML output.
135 - $this->_render_table();
144 +
145 + if ( 'html' !== $format ) {
146 + add_filter( 'tablepress_cell_content', 'wptexturize' );
147 + }
148 +
149 + // Evaluate Shortcodes and escape cell content.
150 + $this->_process_render_data();
151 +
152 + if ( 'html' !== $format ) {
153 + remove_filter( 'tablepress_cell_content', 'wptexturize' );
154 + }
155 +
156 + switch ( $format ) {
157 + case 'html':
158 + $this->_render_table();
159 + break;
160 + case 'array':
161 + $this->output = $this->table['data'];
162 + break;
163 + }
164 +
136 165 return $this->output;
137 166 }
138 167
139 168 /**
@@ -140,21 +169,24 @@
140 169 * Loop through the table to evaluate math expressions/formulas.
141 170 *
142 171 * @since 1.0.0
143 172 */
144 - protected function _evaluate_table_data() {
173 + protected function _evaluate_table_data(): void {
145 174 $orig_table = $this->table;
146 175
147 - $formula_evaluator = TablePress::load_class( 'TablePress_Evaluate', 'class-evaluate.php', 'classes' );
148 - $this->table['data'] = $formula_evaluator->evaluate_table_data( $this->table['data'] );
176 + if ( $this->render_options['evaluate_formulas'] ) {
177 + $formula_evaluator = TablePress::load_class( 'TablePress_Evaluate', 'class-evaluate.php', 'classes' );
178 + $this->table['data'] = $formula_evaluator->evaluate_table_data( $this->table['data'], $this->table['id'] );
179 + }
180 +
149 181 /**
150 - * Filter the table after evaluating formulas in the table.
182 + * Filters the table after evaluating formulas in the table.
151 183 *
152 184 * @since 1.0.0
153 185 *
154 - * @param array $table The table with evaluated formulas.
155 - * @param array $orig_table The table with unevaluated formulas.
156 - * @param array $render_options The render options for the table.
186 + * @param array<string, mixed> $table The table with evaluated formulas.
187 + * @param array<string, mixed> $orig_table The table with unevaluated formulas.
188 + * @param array<string, mixed> $render_options The render options for the table.
157 189 */
158 190 $this->table = apply_filters( 'tablepress_table_evaluate_data', $this->table, $orig_table, $this->render_options );
159 191 }
160 192
@@ -162,13 +194,15 @@
162 194 * Remove all cells from the data set that shall not be rendered, because they are hidden.
163 195 *
164 196 * @since 1.0.0
165 197 */
166 - protected function _prepare_render_data() {
198 + protected function _prepare_render_data(): void {
167 199 $orig_table = $this->table;
168 200
169 - $num_rows = count( $this->table['data'] );
170 - $num_columns = ( $num_rows > 0 ) ? count( $this->table['data'][0] ) : 0;
201 + $counts = array(
202 + 'rows' => count( $this->table['data'] ),
203 + );
204 + $counts['columns'] = ( $counts['rows'] > 0 ) ? count( $this->table['data'][0] ) : 0;
171 205
172 206 // Evaluate show/hide_rows/columns parameters.
173 207 $actions = array( 'show', 'hide' );
174 208 $elements = array( 'rows', 'columns' );
@@ -173,64 +207,33 @@
173 207 $actions = array( 'show', 'hide' );
174 208 $elements = array( 'rows', 'columns' );
175 209 foreach ( $actions as $action ) {
176 210 foreach ( $elements as $element ) {
177 - if ( empty( $this->render_options[ "{$action}_{$element}" ] ) ) {
178 - $this->render_options[ "{$action}_{$element}" ] = array();
211 + $action_element = "{$action}_{$element}";
212 +
213 + if ( empty( $this->render_options[ $action_element ] ) ) {
214 + $this->render_options[ $action_element ] = array();
179 215 continue;
180 216 }
181 217
182 - // Add all rows/columns to array if "all" value set for one of the four parameters.
183 - if ( 'all' === $this->render_options[ "{$action}_{$element}" ] ) {
184 - $this->render_options[ "{$action}_{$element}" ] = range( 0, ${'num_' . $element} - 1 );
218 + // Add all rows/columns to array if a parameter is set to "all".
219 + if ( 'all' === $this->render_options[ $action_element ] ) {
220 + $this->render_options[ $action_element ] = range( 0, $counts[ $element ] - 1 );
185 221 continue;
186 222 }
187 223
188 - // We have a list of rows/columns (possibly with ranges in it).
189 - $this->render_options[ "{$action}_{$element}" ] = explode( ',', $this->render_options[ "{$action}_{$element}" ] );
190 - // Support for ranges like 3-6 or A-BA.
191 - $range_cells = array();
192 - foreach ( $this->render_options[ "{$action}_{$element}" ] as $key => $value ) {
193 - $range_dash = strpos( $value, '-' );
194 - if ( false !== $range_dash ) {
195 - unset( $this->render_options[ "{$action}_{$element}" ][ $key ] );
196 - $start = substr( $value, 0, $range_dash );
197 - if ( ! is_numeric( $start ) ) {
198 - $start = TablePress::letter_to_number( $start );
199 - }
200 - $end = substr( $value, $range_dash + 1 );
201 - if ( ! is_numeric( $end ) ) {
202 - $end = TablePress::letter_to_number( $end );
203 - }
204 - $current_range = range( $start, $end );
205 - $range_cells = array_merge( $range_cells, $current_range );
206 - }
207 - }
208 - $this->render_options[ "{$action}_{$element}" ] = array_merge( $this->render_options[ "{$action}_{$element}" ], $range_cells );
209 - /*
210 - * Parse single letters and change from regular numbering to zero-based numbering,
211 - * as rows/columns are indexed from 0 internally, but from 1 externally
212 - */
213 - foreach ( $this->render_options[ "{$action}_{$element}" ] as $key => $value ) {
214 - if ( ! is_numeric( $value ) ) {
215 - $value = TablePress::letter_to_number( $value );
216 - }
217 - $this->render_options[ "{$action}_{$element}" ][ $key ] = (int) $value - 1;
218 - }
219 -
220 - // Remove duplicate entries and sort the array.
221 - $this->render_options[ "{$action}_{$element}" ] = array_unique( $this->render_options[ "{$action}_{$element}" ] );
222 - sort( $this->render_options[ "{$action}_{$element}" ], SORT_NUMERIC );
224 + // Convert the list to a unique, sorted, and zero-based array of integers.
225 + $this->render_options[ $action_element ] = TablePress::convert_row_column_list_to_array( $this->render_options[ $action_element ], 1, $counts[ $element ], array( 'zero-based' => true ) );
223 226 }
224 227 }
225 228
226 229 // Load information about hidden rows and columns.
227 230 // Get indexes of hidden rows (array value of 0).
228 - $hidden_rows = array_keys( $this->table['visibility']['rows'], 0 );
231 + $hidden_rows = array_keys( $this->table['visibility']['rows'], 0, true );
229 232 $hidden_rows = array_merge( $hidden_rows, $this->render_options['hide_rows'] );
230 233 $hidden_rows = array_diff( $hidden_rows, $this->render_options['show_rows'] );
231 234 // Get indexes of hidden columns (array value of 0).
232 - $hidden_columns = array_keys( $this->table['visibility']['columns'], 0 );
235 + $hidden_columns = array_keys( $this->table['visibility']['columns'], 0, true );
233 236 $hidden_columns = array_merge( $hidden_columns, $this->render_options['hide_columns'] );
234 237 $hidden_columns = array_merge( array_diff( $hidden_columns, $this->render_options['show_columns'] ) );
235 238
236 239 // Remove hidden rows and re-index.
@@ -246,30 +249,86 @@
246 249 $this->table['data'][ $row_idx ] = array_merge( $row );
247 250 }
248 251
249 252 /**
250 - * Filter the table after processing the table visibility information.
253 + * Filters the table after processing the table visibility information.
251 254 *
252 255 * @since 1.0.0
253 256 *
254 - * @param array $table The processed table.
255 - * @param array $orig_table The unprocessed table.
256 - * @param array $render_options The render options for the table.
257 + * @param array<string, mixed> $table The processed table.
258 + * @param array<string, mixed> $orig_table The unprocessed table.
259 + * @param array<string, mixed> $render_options The render options for the table.
257 260 */
258 261 $this->table = apply_filters( 'tablepress_table_render_data', $this->table, $orig_table, $this->render_options );
259 262 }
260 263
261 264 /**
265 + * Generate the data that is to be rendered.
266 + *
267 + * @since 2.0.0
268 + */
269 + protected function _process_render_data(): void {
270 + $orig_table = $this->table;
271 +
272 + // Deactivate nl2br() for this render process, if "convert_line_breaks" Shortcode parameter is set to false.
273 + if ( ! $this->render_options['convert_line_breaks'] ) {
274 + add_filter( 'tablepress_apply_nl2br', '__return_false', 9 ); // Priority 9, so that this filter can easily be overwritten at the default priority.
275 + }
276 +
277 + foreach ( $this->table['data'] as $row_idx => $row ) {
278 + foreach ( $row as $col_idx => $cell_content ) {
279 + // Print formulas that are escaped with '= (like in Excel) as text.
280 + if ( str_starts_with( $cell_content, "'=" ) ) {
281 + $cell_content = substr( $cell_content, 1 );
282 + }
283 + $cell_content = $this->safe_output( $cell_content );
284 + if ( str_contains( $cell_content, '[' ) ) {
285 + $cell_content = do_shortcode( $cell_content );
286 + }
287 + /**
288 + * Filters the content of a single cell, after formulas have been evaluated, the output has been sanitized, and Shortcodes have been evaluated.
289 + *
290 + * @since 1.0.0
291 + *
292 + * @param string $cell_content The cell content.
293 + * @param string $table_id The current table ID.
294 + * @param int $row_idx The row number of the cell.
295 + * @param int $col_idx The column number of the cell.
296 + */
297 + $cell_content = apply_filters( 'tablepress_cell_content', $cell_content, $this->table['id'], $row_idx + 1, $col_idx + 1 );
298 + $this->table['data'][ $row_idx ][ $col_idx ] = $cell_content;
299 + }
300 + }
301 +
302 + // Re-instate nl2br() behavior after this render process, if "convert_line_breaks" Shortcode parameter is set to false.
303 + if ( ! $this->render_options['convert_line_breaks'] ) {
304 + remove_filter( 'tablepress_apply_nl2br', '__return_false', 9 ); // Priority 9, so that this filter can easily be overwritten at the default priority.
305 + }
306 +
307 + /**
308 + * Filters the table after processing the table content handling.
309 + *
310 + * @since 2.0.0
311 + *
312 + * @param array<string, mixed> $table The processed table.
313 + * @param array<string, mixed> $orig_table The unprocessed table.
314 + * @param array<string, mixed> $render_options The render options for the table.
315 + */
316 + $this->table = apply_filters( 'tablepress_table_content_render_data', $this->table, $orig_table, $this->render_options );
317 + }
318 +
319 + /**
262 320 * Generate the HTML output of the table.
263 321 *
264 322 * @since 1.0.0
265 323 */
266 - protected function _render_table() {
324 + protected function _render_table(): void {
267 325 $num_rows = count( $this->table['data'] );
268 326 $num_columns = ( $num_rows > 0 ) ? count( $this->table['data'][0] ) : 0;
269 327
270 328 // Check if there are rows and columns in the table (might not be the case after removing hidden rows/columns!).
271 329 if ( 0 === $num_rows || 0 === $num_columns ) {
330 + /* translators: %s: Table ID */
272 331 $this->output = sprintf( __( '<!-- The table with the ID %s is empty! -->', 'tablepress' ), $this->table['id'] );
273 332 return;
274 333 }
275 334
@@ -277,14 +336,14 @@
277 336 $this->rowspan = array_fill( 0, $num_columns, 1 );
278 337 $this->colspan = array_fill( 0, $num_rows, 1 );
279 338
280 339 /**
281 - * Filter the trigger keywords for "colspan" and "rowspan"
340 + * Filters the trigger keywords for "colspan" and "rowspan"
282 341 *
283 342 * @since 1.0.0
284 343 *
285 - * @param array $span_trigger The trigger keywords for combining table cells.
286 - * @param string $table_id The current table ID.
344 + * @param array<string, string> $span_trigger The trigger keywords for combining table cells.
345 + * @param string $table_id The current table ID.
287 346 */
288 347 $this->span_trigger = apply_filters( 'tablepress_span_trigger_keywords', $this->span_trigger, $this->table['id'] );
289 348
290 349 // Explode from string to array.
@@ -295,9 +354,9 @@
295 354 $output = '';
296 355
297 356 if ( $this->render_options['print_name'] ) {
298 357 /**
299 - * Filter the HTML tag that wraps the printed table name.
358 + * Filters the HTML tag that wraps the printed table name.
300 359 *
301 360 * @since 1.0.0
302 361 *
303 362 * @param string $tag The HTML tag around the table name. Default h2.
@@ -302,23 +361,41 @@
302 361 *
303 362 * @param string $tag The HTML tag around the table name. Default h2.
304 363 * @param string $table_id The current table ID.
305 364 */
306 - $print_name_html_tag = apply_filters( 'tablepress_print_name_html_tag', 'h2', $this->table['id'] );
365 + $name_html_tag = apply_filters( 'tablepress_print_name_html_tag', 'h2', $this->table['id'] );
366 +
367 + $name_attributes = array();
368 + if ( ! empty( $this->render_options['html_id'] ) ) {
369 + $name_attributes['id'] = "{$this->render_options['html_id']}-name";
370 + }
307 371 /**
308 - * Filter the class attribute for the printed table name.
372 + * Filters the class attribute for the printed table name.
309 373 *
310 374 * @since 1.0.0
375 + * @deprecated 1.13.0 Use {@see 'tablepress_table_name_tag_attributes'} instead.
311 376 *
312 377 * @param string $class The class attribute for the table name that can be used in CSS code.
313 378 * @param string $table_id The current table ID.
314 379 */
315 - $print_name_css_class = apply_filters( 'tablepress_print_name_css_class', "tablepress-table-name tablepress-table-name-id-{$this->table['id']}", $this->table['id'] );
316 - $print_name_html = "<{$print_name_html_tag} class=\"{$print_name_css_class}\">" . $this->safe_output( $this->table['name'] ) . "</{$print_name_html_tag}>\n";
380 + $name_attributes['class'] = apply_filters_deprecated( 'tablepress_print_name_css_class', array( "tablepress-table-name tablepress-table-name-id-{$this->table['id']}", $this->table['id'] ), 'TablePress 1.13.0', 'tablepress_table_name_tag_attributes' );
381 + /**
382 + * Filters the attributes for the table name (HTML h2 element, by default).
383 + *
384 + * @since 1.13.0
385 + *
386 + * @param array<string, string> $name_attributes The attributes for the table name element.
387 + * @param array<string, mixed> $table The current table.
388 + * @param array<string, mixed> $render_options The render options for the table.
389 + */
390 + $name_attributes = apply_filters( 'tablepress_table_name_tag_attributes', $name_attributes, $this->table, $this->render_options );
391 + $name_attributes = $this->_attributes_array_to_string( $name_attributes );
392 +
393 + $print_name_html = "<{$name_html_tag}{$name_attributes}>" . $this->safe_output( $this->table['name'] ) . "</{$name_html_tag}>\n";
317 394 }
318 395 if ( $this->render_options['print_description'] ) {
319 396 /**
320 - * Filter the HTML tag that wraps the printed table description.
397 + * Filters the HTML tag that wraps the printed table description.
321 398 *
322 399 * @since 1.0.0
323 400 *
324 401 * @param string $tag The HTML tag around the table description. Default span.
@@ -323,19 +400,37 @@
323 400 *
324 401 * @param string $tag The HTML tag around the table description. Default span.
325 402 * @param string $table_id The current table ID.
326 403 */
327 - $print_description_html_tag = apply_filters( 'tablepress_print_description_html_tag', 'span', $this->table['id'] );
404 + $description_html_tag = apply_filters( 'tablepress_print_description_html_tag', 'span', $this->table['id'] );
405 +
406 + $description_attributes = array();
407 + if ( ! empty( $this->render_options['html_id'] ) ) {
408 + $description_attributes['id'] = "{$this->render_options['html_id']}-description";
409 + }
328 410 /**
329 - * Filter the class attribute for the printed table description.
411 + * Filters the class attribute for the printed table description.
330 412 *
331 413 * @since 1.0.0
414 + * @deprecated 1.13.0 Use {@see 'tablepress_table_description_tag_attributes'} instead.
332 415 *
333 416 * @param string $class The class attribute for the table description that can be used in CSS code.
334 417 * @param string $table_id The current table ID.
335 418 */
336 - $print_description_css_class = apply_filters( 'tablepress_print_description_css_class', "tablepress-table-description tablepress-table-description-id-{$this->table['id']}", $this->table['id'] );
337 - $print_description_html = "<{$print_description_html_tag} class=\"{$print_description_css_class}\">" . $this->safe_output( $this->table['description'] ) . "</{$print_description_html_tag}>\n";
419 + $description_attributes['class'] = apply_filters_deprecated( 'tablepress_print_description_css_class', array( "tablepress-table-description tablepress-table-description-id-{$this->table['id']}", $this->table['id'] ), 'TablePress 1.13.0', 'tablepress_table_description_tag_attributes' );
420 + /**
421 + * Filters the attributes for the table description (HTML span element, by default).
422 + *
423 + * @since 1.13.0
424 + *
425 + * @param array<string, string> $description_attributes The attributes for the table description element.
426 + * @param array<string, mixed> $table The current table.
427 + * @param array<string, mixed> $render_options The render options for the table.
428 + */
429 + $description_attributes = apply_filters( 'tablepress_table_description_tag_attributes', $description_attributes, $this->table, $this->render_options );
430 + $description_attributes = $this->_attributes_array_to_string( $description_attributes );
431 +
432 + $print_description_html = "<{$description_html_tag}{$description_attributes}>" . $this->safe_output( $this->table['description'] ) . "</{$description_html_tag}>\n";
338 433 }
339 434
340 435 if ( $this->render_options['print_name'] && 'above' === $this->render_options['print_name_position'] ) {
341 436 $output .= $print_name_html;
@@ -343,56 +438,63 @@
343 438 if ( $this->render_options['print_description'] && 'above' === $this->render_options['print_description_position'] ) {
344 439 $output .= $print_description_html;
345 440 }
346 441
347 - // Deactivate nl2br() for this render process, if "convert_line_breaks" Shortcode parameter is set to false.
348 - if ( ! $this->render_options['convert_line_breaks'] ) {
349 - add_filter( 'tablepress_apply_nl2br', '__return_false', 9 ); // Priority 9, so that this filter can easily be overwritten at the default priority.
350 - }
351 -
352 - $thead = '';
353 - $tfoot = '';
442 + $thead = array();
443 + $tfoot = array();
354 444 $tbody = array();
355 445
356 446 $this->last_row_idx = $num_rows - 1;
357 447 $this->last_column_idx = $num_columns - 1;
448 +
358 449 // Loop through rows in reversed order, to search for rowspan trigger keyword.
359 - for ( $row_idx = $this->last_row_idx; $row_idx >= 0; $row_idx-- ) {
360 - // Last row, need to check for footer (but only if at least two rows).
361 - if ( $this->last_row_idx === $row_idx && $this->render_options['table_foot'] && $num_rows > 1 ) {
362 - $tfoot = $this->_render_row( $row_idx, 'th' );
363 - continue;
450 + $row_idx = $this->last_row_idx;
451 +
452 + // Render the table footer rows, if there is at least one extra row.
453 + if ( $this->render_options['table_foot'] > 0 && $num_rows >= $this->render_options['table_head'] + $this->render_options['table_foot'] ) {
454 + $last_tbody_idx = $this->last_row_idx - $this->render_options['table_foot'];
455 + while ( $row_idx > $last_tbody_idx ) {
456 + $tfoot[] = $this->_render_row( $row_idx, 'th' );
457 + --$row_idx;
364 458 }
365 - // First row, need to check for head (but only if at least two rows).
366 - if ( 0 === $row_idx && $this->render_options['table_head'] && $num_rows > 1 ) {
367 - $thead = $this->_render_row( $row_idx, 'th' );
368 - continue;
369 - }
370 - // Neither first nor last row (with respective head/foot enabled), so render as body row.
459 + // Reverse rows because we looped through the rows in reverse order.
460 + $tfoot = array_reverse( $tfoot );
461 + }
462 +
463 + // Render the table body rows.
464 + $last_thead_idx = $this->render_options['table_head'] - 1;
465 + while ( $row_idx > $last_thead_idx ) {
371 466 $tbody[] = $this->_render_row( $row_idx, 'td' );
467 + --$row_idx;
372 468 }
469 + // Reverse rows because we looped through the rows in reverse order.
470 + $tbody = array_reverse( $tbody );
373 471
374 - // Re-instate nl2br() behavior after this render process, if "convert_line_breaks" Shortcode parameter is set to false.
375 - if ( ! $this->render_options['convert_line_breaks'] ) {
376 - remove_filter( 'tablepress_apply_nl2br', '__return_false', 9 ); // Priority 9, so that this filter can easily be overwritten at the default priority.
472 + // Render the table header rows, if rows are left.
473 + while ( $row_idx > -1 ) {
474 + $thead[] = $this->_render_row( $row_idx, 'th' );
475 + --$row_idx;
377 476 }
477 + // Reverse rows because we looped through the rows in reverse order.
478 + $thead = array_reverse( $thead );
378 479
379 480 // <caption> tag.
380 481 /**
381 - * Filter the content for the HTML caption element of the table.
482 + * Filters the content for the HTML caption element of the table.
382 483 *
383 484 * If the "Edit" link for a table is shown, it is also added to the caption element.
384 485 *
385 486 * @since 1.0.0
386 487 *
387 - * @param string $caption The content for the HTML caption element of the table. Default empty.
388 - * @param array $table The current table.
488 + * @param string $caption The content for the HTML caption element of the table. Default empty.
489 + * @param array<string, mixed> $table The current table.
389 490 */
390 491 $caption = apply_filters( 'tablepress_print_caption_text', '', $this->table );
391 - $caption_style = $caption_class = '';
492 + $caption_style = '';
493 + $caption_class = '';
392 494 if ( ! empty( $caption ) ) {
393 495 /**
394 - * Filter the class attribute for the HTML caption element of the table.
496 + * Filters the class attribute for the HTML caption element of the table.
395 497 *
396 498 * @since 1.0.0
397 499 *
398 500 * @param string $class The class attribute for the HTML caption element of the table.
@@ -406,9 +508,9 @@
406 508 $caption_style = ' style="caption-side:bottom;text-align:left;border:none;background:none;margin:0;padding:0;"';
407 509 } else {
408 510 $caption .= '<br />';
409 511 }
410 - $caption .= "<a href=\"{$this->render_options['edit_table_url']}\">" . __( 'Edit', 'default' ) . '</a>';
512 + $caption .= '<a href="' . esc_url( $this->render_options['edit_table_url'] ) . '" rel="nofollow">' . __( 'Edit', 'default' ) . '</a>';
411 513 }
412 514 if ( ! empty( $caption ) ) {
413 515 $caption = "<caption{$caption_class}{$caption_style}>{$caption}</caption>\n";
414 516 }
@@ -415,9 +517,9 @@
415 517
416 518 // <colgroup> tag.
417 519 $colgroup = '';
418 520 /**
419 - * Filter whether the HTML colgroup tag shall be added to the table output.
521 + * Filters whether the HTML colgroup tag shall be added to the table output.
420 522 *
421 523 * @since 1.0.0
422 524 *
423 525 * @param bool $print Whether the colgroup element shall be printed.
@@ -426,9 +528,9 @@
426 528 if ( apply_filters( 'tablepress_print_colgroup_tag', false, $this->table['id'] ) ) {
427 529 for ( $col_idx = 0; $col_idx < $num_columns; $col_idx++ ) {
428 530 $attributes = ' class="colgroup-column-' . ( $col_idx + 1 ) . ' "';
429 531 /**
430 - * Filter the attributes of the HTML col tags in the HTML colgroup tag.
532 + * Filters the attributes of the HTML col tags in the HTML colgroup tag.
431 533 *
432 534 * @since 1.0.0
433 535 *
434 536 * @param string $attributes The attributes in the col element.
@@ -435,9 +537,9 @@
435 537 * @param string $table_id The current table ID.
436 538 * @param int $col_idx The number of the column.
437 539 */
438 540 $attributes = apply_filters( 'tablepress_colgroup_tag_attributes', $attributes, $this->table['id'], $col_idx + 1 );
439 - $colgroup .= "\t<col{$attributes}/>\n";
541 + $colgroup .= "\t<col{$attributes} />\n";
440 542 }
441 543 }
442 544 if ( ! empty( $colgroup ) ) {
443 545 $colgroup = "<colgroup>\n{$colgroup}</colgroup>\n";
@@ -442,18 +544,36 @@
442 544 if ( ! empty( $colgroup ) ) {
443 545 $colgroup = "<colgroup>\n{$colgroup}</colgroup>\n";
444 546 }
445 547
446 - // <thead>, <tfoot>, and <tbody> tags.
548 + /*
549 + * <thead>, <tfoot>, and <tbody> tags.
550 + */
551 +
447 552 if ( ! empty( $thead ) ) {
448 - $thead = "<thead>\n{$thead}</thead>\n";
553 + $thead = "<thead>\n" . implode( '', $thead ) . "</thead>\n";
554 + } else {
555 + $thead = '';
449 556 }
557 +
450 558 if ( ! empty( $tfoot ) ) {
451 - $tfoot = "<tfoot>\n{$tfoot}</tfoot>\n";
559 + $tfoot = "<tfoot>\n" . implode( '', $tfoot ) . "</tfoot>\n";
560 + } else {
561 + $tfoot = '';
452 562 }
453 - $tbody_class = ( $this->render_options['row_hover'] ) ? ' class="row-hover"' : '';
454 - // Reverse rows because we looped through the rows in reverse order.
455 - $tbody = array_reverse( $tbody );
563 +
564 + $tbody_classes = array();
565 + if ( $this->render_options['alternating_row_colors'] ) {
566 + $tbody_classes[] = 'row-striping';
567 + }
568 + if ( $this->render_options['row_hover'] ) {
569 + $tbody_classes[] = 'row-hover';
570 + }
571 + $tbody_class = implode( ' ', $tbody_classes );
572 + if ( '' !== $tbody_class ) {
573 + $tbody_class = ' class="' . esc_attr( $tbody_class ) . '"';
574 + }
575 +
456 576 $tbody = "<tbody{$tbody_class}>\n" . implode( '', $tbody ) . "</tbody>\n";
457 577
458 578 // Attributes for the table (HTML table element).
459 579 $table_attributes = array();
@@ -463,38 +583,54 @@
463 583 $table_attributes['id'] = $this->render_options['html_id'];
464 584 }
465 585
466 586 // "class" attribute.
467 - $css_classes = array( 'tablepress', "tablepress-id-{$this->table['id']}", $this->render_options['extra_css_classes'] );
587 + $css_classes = array(
588 + 'tablepress',
589 + "tablepress-id-{$this->table['id']}",
590 + $this->render_options['extra_css_classes'],
591 + );
592 + if ( $this->tbody_has_connected_cells ) {
593 + $css_classes[] = 'tbody-has-connected-cells';
594 + }
468 595 /**
469 - * Filter the CSS classes that are given to the HTML table element.
596 + * Filters the CSS classes that are given to the HTML table element.
470 597 *
471 598 * @since 1.0.0
472 599 *
473 - * @param array $css_classes The CSS classes for the table element.
474 - * @param string $table_id The current table ID.
600 + * @param string[] $css_classes The CSS classes for the table element.
601 + * @param string $table_id The current table ID.
475 602 */
476 603 $css_classes = apply_filters( 'tablepress_table_css_classes', $css_classes, $this->table['id'] );
477 604 // $css_classes might contain several classes in one array entry.
478 605 $css_classes = explode( ' ', implode( ' ', $css_classes ) );
479 - $css_classes = array_map( 'sanitize_html_class', $css_classes );
606 + $css_classes = array_map( array( 'TablePress', 'sanitize_css_class' ), $css_classes );
480 607 $css_classes = array_unique( $css_classes );
481 - $css_classes = trim( implode( ' ', $css_classes ) );
482 - if ( ! empty( $css_classes ) ) {
608 + $css_classes = array_filter( $css_classes ); // Remove empty entries.
609 + $css_classes = implode( ' ', $css_classes );
610 + if ( '' !== $css_classes ) {
483 611 $table_attributes['class'] = $css_classes;
484 612 }
485 613
614 + // ARIA label attributes.
615 + if ( $this->render_options['print_name'] && ! empty( $this->render_options['html_id'] ) ) {
616 + $table_attributes['aria-labelledby'] = "{$this->render_options['html_id']}-name";
617 + }
618 + if ( $this->render_options['print_description'] && ! empty( $this->render_options['html_id'] ) ) {
619 + $table_attributes['aria-describedby'] = "{$this->render_options['html_id']}-description";
620 + }
621 +
486 622 // "summary" attribute.
487 623 $summary = '';
488 624 /**
489 - * Filter the content for the summary attribute of the HTML table element.
625 + * Filters the content for the summary attribute of the HTML table element.
490 626 *
491 627 * The attribute is only added if it is not empty.
492 628 *
493 629 * @since 1.0.0
494 630 *
495 - * @param string $summary The content for the summary attribute of the table. Default empty.
496 - * @param array $table The current table.
631 + * @param string $summary The content for the summary attribute of the table. Default empty.
632 + * @param array<string, mixed> $table The current table.
497 633 */
498 634 $summary = apply_filters( 'tablepress_print_summary_attr', $summary, $this->table );
499 635 if ( ! empty( $summary ) ) {
500 636 $table_attributes['summary'] = esc_attr( $summary );
@@ -502,28 +638,42 @@
502 638
503 639 // Legacy support for attributes that are not encouraged in HTML5.
504 640 foreach ( array( 'cellspacing', 'cellpadding', 'border' ) as $attribute ) {
505 641 if ( false !== $this->render_options[ $attribute ] ) {
506 - $table_attributes[ $attribute ] = intval( $this->render_options[ $attribute ] );
642 + $table_attributes[ $attribute ] = (string) absint( $this->render_options[ $attribute ] );
507 643 }
508 644 }
509 645
510 646 /**
511 - * Filter the attributes for the table (HTML table element).
647 + * Filters the attributes for the table (HTML table element).
512 648 *
513 649 * @since 1.4.0
514 650 *
515 - * @param array $table_attributes The attributes for the table element.
516 - * @param array $table The current table.
517 - * @param array $render_options The render options for the table.
651 + * @param array<string, string> $table_attributes The attributes for the table element.
652 + * @param array<string, mixed> $table The current table.
653 + * @param array<string, mixed> $render_options The render options for the table.
518 654 */
519 655 $table_attributes = apply_filters( 'tablepress_table_tag_attributes', $table_attributes, $this->table, $this->render_options );
520 656 $table_attributes = $this->_attributes_array_to_string( $table_attributes );
521 657
522 - $output .= "\n<table{$table_attributes}>\n";
523 - $output .= $caption . $colgroup . $thead . $tfoot . $tbody;
524 - $output .= "</table>\n";
658 + $table_html = "<table{$table_attributes}>\n";
659 + $table_html .= $caption . $colgroup . $thead . $tbody . $tfoot;
660 + $table_html .= '</table>';
525 661
662 + /**
663 + * Filters the generated HTML code for the table, without HTML elements around it.
664 + *
665 + * @since 2.4.0
666 + *
667 + * @param string $output The generated HTML for the table, without HTML elements around it.
668 + * @param array<string, mixed> $table The current table.
669 + * @param array<string, mixed> $render_options The render options for the table, without HTML elements around it.
670 + */
671 + $table_html = apply_filters( 'tablepress_table_html', $table_html, $this->table, $this->render_options );
672 +
673 + $output .= "\n{$table_html}\n";
674 + unset( $table_html ); // Unset the potentially large variable to free up memory.
675 +
526 676 // name/description below table (HTML already generated above).
527 677 if ( $this->render_options['print_name'] && 'below' === $this->render_options['print_name_position'] ) {
528 678 $output .= $print_name_html;
529 679 }
@@ -531,15 +681,15 @@
531 681 $output .= $print_description_html;
532 682 }
533 683
534 684 /**
535 - * Filter the generated HTML code for table.
685 + * Filters the generated HTML code for the table and HTML elements around it.
536 686 *
537 687 * @since 1.0.0
538 688 *
539 - * @param string $output The generated HTML for the table.
540 - * @param array $table The current table.
541 - * @param array $render_options The render options for the table.
689 + * @param string $output The generated HTML for the table and HTML elements around it.
690 + * @param array<string, mixed> $table The current table.
691 + * @param array<string, mixed> $render_options The render options for the table and HTML elements around it.
542 692 */
543 693 $this->output = apply_filters( 'tablepress_table_output', $output, $this->table, $this->render_options );
544 694 }
545 695
@@ -551,40 +701,22 @@
551 701 * @param int $row_idx Index of the row to be rendered.
552 702 * @param string $tag HTML tag to use for the cells (td or th).
553 703 * @return string HTML for the row.
554 704 */
555 - protected function _render_row( $row_idx, $tag ) {
705 + protected function _render_row( int $row_idx, string $tag ): string {
556 706 $row_cells = array();
557 707 // Loop through cells in reversed order, to search for colspan or rowspan trigger words.
558 708 for ( $col_idx = $this->last_column_idx; $col_idx >= 0; $col_idx-- ) {
559 709 $cell_content = $this->table['data'][ $row_idx ][ $col_idx ];
560 710
561 - // Print formulas that are escaped with '= (like in Excel) as text.
562 - if ( "'=" === substr( $cell_content, 0, 2 ) ) {
563 - $cell_content = substr( $cell_content, 1 );
564 - }
565 - $cell_content = do_shortcode( $this->safe_output( $cell_content ) );
566 - /**
567 - * Filter the content of a single cell.
568 - *
569 - * Filter the content of a single cell, after formulas have been evaluated, the output has been sanitized, and Shortcodes have been evaluated.
570 - *
571 - * @since 1.0.0
572 - *
573 - * @param string $cell_content The cell content.
574 - * @param string $table_id The current table ID.
575 - * @param int $row_idx The row number of the cell.
576 - * @param int $col_idx The column number of the cell.
577 - */
578 - $cell_content = apply_filters( 'tablepress_cell_content', $cell_content, $this->table['id'], $row_idx + 1, $col_idx + 1 );
579 -
580 711 if ( $this->span_trigger['rowspan'] === $cell_content ) { // There will be a rowspan.
581 - // Check for #rowspan# in first row, which doesn't make sense.
582 - if ( ( $row_idx > 1 && $row_idx < $this->last_row_idx )
583 - || ( 1 === $row_idx && ! $this->render_options['table_head'] ) // No rowspan into table head.
584 - || ( $this->last_row_idx === $row_idx && ! $this->render_options['table_foot'] ) ) { // No rowspan out of table foot.
712 + if ( ! (
713 + ( 0 === $row_idx ) // No rowspan inside first row.
714 + || ( $this->render_options['table_head'] === $row_idx ) // No rowspan into table head.
715 + || ( $this->last_row_idx - $this->render_options['table_foot'] + 1 === $row_idx ) // No rowspan out of table foot.
716 + ) ) {
585 717 // Increase counter for rowspan in this column.
586 - $this->rowspan[ $col_idx ]++;
718 + ++$this->rowspan[ $col_idx ];
587 719 // Reset counter for colspan in this row, combined col- and rowspan might be happening.
588 720 $this->colspan[ $row_idx ] = 1;
589 721 continue;
590 722 }
@@ -590,13 +722,14 @@
590 722 }
591 723 // Invalid rowspan, so we set cell content from #rowspan# to empty.
592 724 $cell_content = '';
593 725 } elseif ( $this->span_trigger['colspan'] === $cell_content ) { // There will be a colspan.
594 - // Check for #colspan# in first column, which doesn't make sense.
595 - if ( $col_idx > 1
596 - || ( 1 === $col_idx && ! $this->render_options['first_column_th'] ) ) { // No colspan into first column head.
726 + if ( ! (
727 + ( 0 === $col_idx ) // No colspan inside first column.
728 + || ( 1 === $col_idx && $this->render_options['first_column_th'] ) // No colspan into first column head.
729 + ) ) {
597 730 // Increase counter for colspan in this row.
598 - $this->colspan[ $row_idx ]++;
731 + ++$this->colspan[ $row_idx ];
599 732 // Reset counter for rowspan in this column, combined col- and rowspan might be happening.
600 733 $this->rowspan[ $col_idx ] = 1;
601 734 continue;
602 735 }
@@ -602,22 +735,20 @@
602 735 }
603 736 // Invalid colspan, so we set cell content from #colspan# to empty.
604 737 $cell_content = '';
605 738 } elseif ( $this->span_trigger['span'] === $cell_content ) { // There will be a combined col- and rowspan.
606 - // Check for #span# in first column or first or last row, which is not always possible.
607 - if ( ( $row_idx > 1 && $row_idx < $this->last_row_idx && $col_idx > 1 )
608 - // We are in first, second, or last row or in the first or second column, so more checks are necessary.
609 - || ( ( 1 === $row_idx && ! $this->render_options['table_head'] ) // No rowspan into table head.
610 - && ( $col_idx > 1 || ( 1 === $col_idx && ! $this->render_options['first_column_th'] ) ) ) // And no colspan into first column head.
611 - || ( ( $this->last_row_idx === $row_idx && ! $this->render_options['table_foot'] ) // No rowspan out of table foot.
612 - && ( $col_idx > 1 || ( 1 === $col_idx && ! $this->render_options['first_column_th'] ) ) ) ) { // And no colspan into first column head.
739 + if ( ! (
740 + ( 0 === $row_idx ) // No rowspan inside first row.
741 + || ( $this->render_options['table_head'] === $row_idx ) // No rowspan into table head.
742 + || ( $this->last_row_idx - $this->render_options['table_foot'] + 1 === $row_idx ) // No rowspan out of table foot.
743 + ) && ! (
744 + ( 0 === $col_idx ) // No colspan inside first column.
745 + || ( 1 === $col_idx && $this->render_options['first_column_th'] ) // No colspan into first column head.
746 + ) ) {
613 747 continue;
614 748 }
615 749 // Invalid span, so we set cell content from #span# to empty.
616 750 $cell_content = '';
617 - } elseif ( '' === $cell_content && 0 === $row_idx && $this->render_options['table_head'] ) {
618 - // Make empty cells have a space in the table head, to give sorting arrows the correct position in IE9.
619 - $cell_content = '&nbsp;';
620 751 }
621 752
622 753 // Attributes for the table cell (HTML td or th element).
623 754 $tag_attributes = array();
@@ -623,18 +754,26 @@
623 754 $tag_attributes = array();
624 755
625 756 // "colspan" and "rowspan" attributes.
626 757 if ( $this->colspan[ $row_idx ] > 1 ) { // We have colspaned cells.
627 - $tag_attributes['colspan'] = $this->colspan[ $row_idx ];
758 + $tag_attributes['colspan'] = (string) $this->colspan[ $row_idx ];
759 + if ( ! $this->tbody_has_connected_cells && $row_idx > $this->render_options['table_head'] - 1 && $row_idx < $this->last_row_idx - $this->render_options['table_foot'] + 1 ) {
760 + // Set flag that there are connected cells in the tbody.
761 + $this->tbody_has_connected_cells = true;
762 + }
628 763 }
629 764 if ( $this->rowspan[ $col_idx ] > 1 ) { // We have rowspaned cells.
630 - $tag_attributes['rowspan'] = $this->rowspan[ $col_idx ];
765 + $tag_attributes['rowspan'] = (string) $this->rowspan[ $col_idx ];
766 + if ( ! $this->tbody_has_connected_cells && $row_idx > $this->render_options['table_head'] - 1 && $row_idx < $this->last_row_idx - $this->render_options['table_foot'] + 1 ) {
767 + // Set flag that there are connected cells in the tbody.
768 + $this->tbody_has_connected_cells = true;
769 + }
631 770 }
632 771
633 772 // "class" attribute.
634 773 $cell_class = 'column-' . ( $col_idx + 1 );
635 774 /**
636 - * Filter the CSS classes that are given to a single cell (HTML td element) of a table.
775 + * Filters the CSS classes that are given to a single cell (HTML td element) of a table.
637 776 *
638 777 * @since 1.0.0
639 778 *
640 779 * @param string $cell_class The CSS classes for the cell.
@@ -655,28 +794,32 @@
655 794 $tag_attributes['style'] = 'width:' . preg_replace( '#[^0-9a-z.%]#', '', $this->render_options['column_widths'][ $col_idx ] ) . ';';
656 795 }
657 796
658 797 /**
659 - * Filter the attributes for the table cell (HTML td or th element).
798 + * Filters the attributes for the table cell (HTML td or th element).
660 799 *
661 800 * @since 1.4.0
662 801 *
663 - * @param array $tag_attributes The attributes for the td or th element.
664 - * @param string $table_id The current table ID.
665 - * @param string $cell_content The cell content.
666 - * @param int $row_idx The row number of the cell.
667 - * @param int $col_idx The column number of the cell.
668 - * @param int $colspan_row The number of combined columns for this cell.
669 - * @param int $rowspan_col The number of combined rows for this cell.
802 + * @param array<string, string> $tag_attributes The attributes for the td or th element.
803 + * @param string $table_id The current table ID.
804 + * @param string $cell_content The cell content.
805 + * @param int $row_idx The row number of the cell.
806 + * @param int $col_idx The column number of the cell.
807 + * @param int $colspan_row The number of combined columns for this cell.
808 + * @param int $rowspan_col The number of combined rows for this cell.
670 809 */
671 810 $tag_attributes = apply_filters( 'tablepress_cell_tag_attributes', $tag_attributes, $this->table['id'], $cell_content, $row_idx + 1, $col_idx + 1, $this->colspan[ $row_idx ], $this->rowspan[ $col_idx ] );
672 811 $tag_attributes = $this->_attributes_array_to_string( $tag_attributes );
673 812
674 - if ( $this->render_options['first_column_th'] && 0 === $col_idx ) {
675 - $tag = 'th';
813 + if ( '' === $cell_content ) {
814 + $cell_tag = 'td'; // For accessibility, empty cells should use `td` and not `th` tags.
815 + } elseif ( $this->render_options['first_column_th'] && 0 === $col_idx ) {
816 + $cell_tag = 'th'; // Non-empty cells in the first column should use `th` tags, if enabled.
817 + } else {
818 + $cell_tag = $tag; // Otherwise, use the tag that was passed in as the default for the row.
676 819 }
677 820
678 - $row_cells[] = "<{$tag}{$tag_attributes}>{$cell_content}</{$tag}>";
821 + $row_cells[] = "<{$cell_tag}{$tag_attributes}>{$cell_content}</{$cell_tag}>";
679 822 $this->colspan[ $row_idx ] = 1; // Reset.
680 823 $this->rowspan[ $col_idx ] = 1; // Reset.
681 824 }
682 825
@@ -684,21 +827,18 @@
684 827 $tr_attributes = array();
685 828
686 829 // "class" attribute.
687 830 $row_classes = 'row-' . ( $row_idx + 1 );
688 - if ( $this->render_options['alternating_row_colors'] ) {
689 - $row_classes .= ( 1 === ( $row_idx % 2 ) ) ? ' even' : ' odd';
690 - }
691 831 /**
692 - * Filter the CSS classes that are given to a row (HTML tr element) of a table.
832 + * Filters the CSS classes that are given to a row (HTML tr element) of a table.
693 833 *
694 834 * @since 1.0.0
695 835 *
696 - * @param string $row_classes The CSS classes for the row.
697 - * @param string $table_id The current table ID.
698 - * @param array $row_cells The HTML code for the cells of the row.
699 - * @param int $row_idx The row number.
700 - * @param array $row_data The content of the cells of the row.
836 + * @param string $row_classes The CSS classes for the row.
837 + * @param string $table_id The current table ID.
838 + * @param string[] $row_cells The HTML code for the cells of the row.
839 + * @param int $row_idx The row number.
840 + * @param string[] $row_data The content of the cells of the row.
701 841 */
702 842 $row_classes = apply_filters( 'tablepress_row_css_class', $row_classes, $this->table['id'], $row_cells, $row_idx + 1, $this->table['data'][ $row_idx ] );
703 843 if ( ! empty( $row_classes ) ) {
704 844 $tr_attributes['class'] = $row_classes;
@@ -704,16 +844,16 @@
704 844 $tr_attributes['class'] = $row_classes;
705 845 }
706 846
707 847 /**
708 - * Filter the attributes for the table row (HTML tr element).
848 + * Filters the attributes for the table row (HTML tr element).
709 849 *
710 850 * @since 1.4.0
711 851 *
712 - * @param array $tr_attributes The attributes for the tr element.
713 - * @param string $table_id The current table ID.
714 - * @param int $row_idx The row number.
715 - * @param array $row_data The content of the cells of the row.
852 + * @param array<string, mixed> $tr_attributes The attributes for the tr element.
853 + * @param string $table_id The current table ID.
854 + * @param int $row_idx The row number.
855 + * @param string[] $row_data The content of the cells of the row.
716 856 */
717 857 $tr_attributes = apply_filters( 'tablepress_row_tag_attributes', $tr_attributes, $this->table['id'], $row_idx + 1, $this->table['data'][ $row_idx ] );
718 858 $tr_attributes = $this->_attributes_array_to_string( $tr_attributes );
719 859
@@ -726,12 +866,12 @@
726 866 * Convert an array of HTML tag attributes to a string.
727 867 *
728 868 * @since 1.4.0
729 869 *
730 - * @param array $attributes Attributes for the HTML tag in the array keys, and their values in the array values.
870 + * @param array<string, string> $attributes Attributes for the HTML tag in the array keys, and their values in the array values.
731 871 * @return string The attributes as a string for usage in a HTML element.
732 872 */
733 - protected function _attributes_array_to_string( array $attributes ) {
873 + protected function _attributes_array_to_string( array $attributes ): string {
734 874 $attributes_string = '';
735 875 foreach ( $attributes as $attribute => $value ) {
736 876 $attributes_string .= " {$attribute}=\"{$value}\"";
737 877 }
@@ -740,23 +880,21 @@
740 880
741 881 /**
742 882 * Possibly replace certain HTML entities and replace line breaks with HTML.
743 883 *
744 - * @TODO: Find a better solution than this function, e.g. something like wpautop().
745 - *
746 884 * @since 1.0.0
747 885 *
748 - * @param string $string The string to process.
886 + * @param string $text The string to process.
749 887 * @return string Processed string for output.
750 888 */
751 - protected function safe_output( $string ) {
889 + protected function safe_output( string $text ): string {
752 890 /*
753 891 * Replace any & with &amp; that is not already an encoded entity (from function htmlentities2 in WP 2.8).
754 892 * A complete htmlentities2() or htmlspecialchars() would encode <HTML> tags, which we don't want.
755 893 */
756 - $string = preg_replace( '/&(?![A-Za-z]{0,4}\w{2,3};|#[0-9]{2,4};)/', '&amp;', $string );
894 + $text = (string) preg_replace( '/&(?![A-Za-z]{0,4}\w{2,3};|#[0-9]{2,4};)/', '&amp;', $text );
757 895 /**
758 - * Filter whether line breaks in the cell content shall be replaced with HTML br tags.
896 + * Filters whether line breaks in the cell content shall be replaced with HTML br tags.
759 897 *
760 898 * @since 1.0.0
761 899 *
762 900 * @param bool $replace Whether to replace line breaks with HTML br tags. Default true.
@@ -762,11 +900,11 @@
762 900 * @param bool $replace Whether to replace line breaks with HTML br tags. Default true.
763 901 * @param string $table_id The current table ID.
764 902 */
765 903 if ( apply_filters( 'tablepress_apply_nl2br', true, $this->table['id'] ) ) {
766 - $string = nl2br( $string );
904 + $text = nl2br( $text );
767 905 }
768 - return $string;
906 + return $text;
769 907 }
770 908
771 909 /**
772 910 * Get the default render options, null means: Use option from "Edit" screen.
@@ -772,46 +910,49 @@
772 910 * Get the default render options, null means: Use option from "Edit" screen.
773 911 *
774 912 * @since 1.0.0
775 913 *
776 - * @return array Default render options.
914 + * @return array<string, mixed> Default render options.
777 915 */
778 - public function get_default_render_options() {
916 + public function get_default_render_options(): array {
779 917 // Attention: Array keys have to be lowercase, otherwise they won't match the Shortcode attributes, which will be passed in lowercase by WP.
780 918 return array(
781 - 'id' => '',
782 - 'column_widths' => '',
783 919 'alternating_row_colors' => null,
784 - 'row_hover' => null,
785 - 'table_head' => null,
786 - 'table_foot' => null,
787 - 'first_column_th' => false,
788 - 'print_name' => null,
789 - 'print_name_position' => null,
790 - 'print_description' => null,
791 - 'print_description_position' => null,
920 + 'block_preview' => false,
921 + 'border' => false,
792 922 'cache_table_output' => true,
923 + 'cellpadding' => false,
924 + 'cellspacing' => false,
925 + 'column_widths' => '',
793 926 'convert_line_breaks' => true,
794 - 'extra_css_classes' => null,
795 - 'use_datatables' => null,
796 - 'datatables_sort' => null,
927 + 'datatables_custom_commands' => null,
928 + 'datatables_datetime' => '',
929 + 'datatables_filter' => null,
930 + 'datatables_info' => null,
931 + 'datatables_lengthchange' => null,
932 + 'datatables_locale' => get_locale(),
797 933 'datatables_paginate' => null,
798 934 'datatables_paginate_entries' => null,
799 - 'datatables_lengthchange' => null,
800 - 'datatables_filter' => null,
801 - 'datatables_info' => null,
802 935 'datatables_scrollx' => null,
803 936 'datatables_scrolly' => false,
804 - 'datatables_custom_commands' => null,
805 - 'datatables_locale' => get_locale(),
806 - 'show_rows' => '',
807 - 'show_columns' => '',
937 + 'datatables_sort' => null,
938 + 'evaluate_formulas' => true,
939 + 'extra_css_classes' => null,
940 + 'first_column_th' => false,
941 + 'hide_columns' => '',
808 942 'hide_rows' => '',
809 - 'hide_columns' => '',
810 - 'cellspacing' => false,
811 - 'cellpadding' => false,
812 - 'border' => false,
943 + 'id' => '',
944 + 'print_description' => null,
945 + 'print_description_position' => null,
946 + 'print_name' => null,
947 + 'print_name_position' => null,
948 + 'row_hover' => null,
813 949 'shortcode_debug' => false,
950 + 'show_columns' => '',
951 + 'show_rows' => '',
952 + 'table_foot' => null,
953 + 'table_head' => null,
954 + 'use_datatables' => null,
814 955 );
815 956 }
816 957
817 958 /**
@@ -820,90 +961,35 @@
820 961 * @since 1.0.0
821 962 *
822 963 * @return string CSS for the Preview iframe.
823 964 */
824 - public function get_preview_css() {
825 - if ( is_rtl() ) {
826 - $rtl = "\ndirection: rtl;";
827 - $rtl_align = 'right';
828 - } else {
829 - $rtl = '';
830 - $rtl_align = 'left';
965 + public function get_preview_css(): string {
966 + $is_rtl = is_rtl();
967 + $tablepress_css = TablePress::load_class( 'TablePress_CSS', 'class-css.php', 'classes' );
968 + $default_css_minified = $tablepress_css->load_default_css_from_file( $is_rtl );
969 + if ( false === $default_css_minified ) {
970 + $default_css_minified = '';
831 971 }
972 +
973 + $rtl_direction = $is_rtl ? "\ndirection: rtl;" : '';
974 +
832 975 return <<<CSS
833 -<style type="text/css">
834 -/* iframe */
835 -body {
836 - margin: 10px;
837 - font-family: sans-serif;{$rtl}
838 -}
839 -/* Inline Shortcodes, in texts */
840 -.table-shortcode-inline {
841 - background: transparent;
842 - border: none;
843 - color: #333333;
844 - width: 110px;
845 - margin: 0;
846 - padding: 0;
847 - -webkit-box-shadow: none;
848 - box-shadow: none;
849 - text-align: center;
850 - font-weight: bold;
851 - font-size: 100%;
852 -}
853 -.table-shortcode {
854 - cursor: text;
855 -}
856 -/* Default table styling */
857 -.tablepress {
858 - border-collapse: collapse;
859 - border-spacing: 0;
860 - width: 100%;
861 - margin-bottom: 1em;
862 - border: none;
863 -}
864 -.tablepress td,
865 -.tablepress th {
866 - padding: 8px;
867 - border: none;
868 - background: none;
869 - text-align: {$rtl_align};
870 -}
871 -.tablepress tbody td {
872 - vertical-align: top;
873 -}
874 -.tablepress tbody tr td,
875 -.tablepress tfoot tr th {
876 - border-top: 1px solid #dddddd;
877 -}
878 -.tablepress tbody tr:first-child td {
879 - border-top: 0;
880 -}
881 -.tablepress thead tr th {
882 - border-bottom: 1px solid #dddddd;
883 -}
884 -.tablepress thead th,
885 -.tablepress tfoot th {
886 - background-color: #d9edf7;
887 - font-weight: bold;
888 - vertical-align: middle;
889 -}
890 -.tablepress tbody tr.odd td {
891 - background-color: #f9f9f9;
892 -}
893 -.tablepress tbody tr.even td {
894 - background-color: #ffffff;
895 -}
896 -.tablepress .row-hover tr:hover td {
897 - background-color: #f3f3f3;
898 -}
899 -.tablepress img {
900 - margin: 0;
901 - padding: 0;
902 - border: none;
903 - max-width: none;
904 -}
905 -</style>
906 -CSS;
976 + <style>
977 + /* iframe */
978 + body {
979 + margin: 10px;
980 + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;{$rtl_direction}
981 + }
982 + p {
983 + font-size: 13px;
984 + }
985 + .preview {
986 + overflow-x: auto;
987 + overflow-y: hidden;
988 + scroll-behavior: smooth;
989 + }
990 + {$default_css_minified}
991 + </style>
992 + CSS;
907 993 }
908 994
909 995 } // class TablePress_Render