PluginProbe
TablePress – Tables in WordPress made easy / 3.2
TablePress – Tables in WordPress made easy v3.2
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 2.4.4 All 44 releases
← All changes | classes/class-render.php +217 -143 2.0.43.2 View file →
@@ -24,25 +24,25 @@
24 24 /**
25 25 * Table data that is rendered.
26 26 *
27 27 * @since 1.0.0
28 - * @var array
28 + * @var array<string, mixed>
29 29 */
30 - protected $table = array();
30 + protected array $table = array();
31 31
32 32 /**
33 33 * Table options that influence the output result.
34 34 *
35 35 * @since 1.0.0
36 - * @var array
36 + * @var array<string, mixed>
37 37 */
38 - protected $render_options = array();
38 + protected array $render_options = array();
39 39
40 40 /**
41 41 * Rendered HTML code of the table or PHP array.
42 42 *
43 43 * @since 1.0.0
44 - * @var string|array
44 + * @var string|array<int, array<int, string>>
45 45 */
46 46 protected $output;
47 47
48 48 /**
@@ -48,11 +48,11 @@
48 48 /**
49 49 * Trigger words for colspan, rowspan, or the combination of both.
50 50 *
51 51 * @since 1.0.0
52 - * @var array
52 + * @var array<string, string>
53 53 */
54 - protected $span_trigger = array(
54 + protected array $span_trigger = array(
55 55 'colspan' => '#colspan#',
56 56 'rowspan' => '#rowspan#',
57 57 'span' => '#span#',
58 58 );
@@ -60,35 +60,40 @@
60 60 /**
61 61 * Buffer to store the counts of rowspan per column, initialized in _render_table().
62 62 *
63 63 * @since 1.0.0
64 - * @var array
64 + * @var int[]
65 65 */
66 - protected $rowspan = array();
66 + protected array $rowspan = array();
67 67
68 68 /**
69 69 * Buffer to store the counts of colspan per row, initialized in _render_table().
70 70 *
71 71 * @since 1.0.0
72 - * @var array
72 + * @var int[]
73 73 */
74 - protected $colspan = array();
74 + protected array $colspan = array();
75 75
76 76 /**
77 + * Whether the table has connected cells (colspan or rowspan), set in _render_table().
78 + *
79 + * @since 3.0.0
80 + */
81 + protected bool $tbody_has_connected_cells = false;
82 +
83 + /**
77 84 * Index of the last row of the visible data in the table, set in _render_table().
78 85 *
79 86 * @since 1.0.0
80 - * @var int
81 87 */
82 - protected $last_row_idx;
88 + protected int $last_row_idx;
83 89
84 90 /**
85 91 * Index of the last column of the visible data in the table, set in _render_table().
86 92 *
87 93 * @since 1.0.0
88 - * @var int
89 94 */
90 - protected $last_column_idx;
95 + protected int $last_column_idx;
91 96
92 97 /**
93 98 * Class constructor.
94 99 *
@@ -102,12 +107,12 @@
102 107 * Set the table (data, options, visibility, ...) that is to be rendered.
103 108 *
104 109 * @since 1.0.0
105 110 *
106 - * @param array $table Table to be rendered.
107 - * @param array $render_options Options for rendering, from both "Edit" screen and Shortcode.
111 + * @param array<string, mixed> $table Table to be rendered.
112 + * @param array<string, mixed> $render_options Options for rendering, from both "Edit" screen and Shortcode.
108 113 */
109 - public function set_input( array $table, array $render_options ) {
114 + public function set_input( array $table, array $render_options ): void {
110 115 $this->table = $table;
111 116 $this->render_options = $render_options;
112 117 /**
113 118 * Filters the table before the render process.
@@ -113,10 +118,10 @@
113 118 * Filters the table before the render process.
114 119 *
115 120 * @since 1.0.0
116 121 *
117 - * @param array $table The table.
118 - * @param array $render_options The render options for the table.
122 + * @param array<string, mixed> $table The table.
123 + * @param array<string, mixed> $render_options The render options for the table.
119 124 */
120 125 $this->table = apply_filters( 'tablepress_table_raw_render_data', $this->table, $this->render_options );
121 126 }
122 127
@@ -126,11 +131,11 @@
126 131 * @since 1.0.0
127 132 * @since 2.0.0 Add the $format parameter.
128 133 *
129 134 * @param string $format Optional. Output format, 'html' (default) or 'array'.
130 - * @return string|array[] HTML code of the rendered table, or a PHP array, or an error message.
135 + * @return string|array<int, array<int, string>> HTML code of the rendered table, or a PHP array, or an error message.
131 136 */
132 - public function get_output( $format = 'html' ) {
137 + public function get_output( string $format = 'html' ) /* : string|array */ {
133 138 // Evaluate math expressions/formulas.
134 139 $this->_evaluate_table_data();
135 140 // Remove hidden rows and columns.
136 141 $this->_prepare_render_data();
@@ -162,9 +167,9 @@
162 167 * Loop through the table to evaluate math expressions/formulas.
163 168 *
164 169 * @since 1.0.0
165 170 */
166 - protected function _evaluate_table_data() {
171 + protected function _evaluate_table_data(): void {
167 172 $orig_table = $this->table;
168 173
169 174 if ( $this->render_options['evaluate_formulas'] ) {
170 175 $formula_evaluator = TablePress::load_class( 'TablePress_Evaluate', 'class-evaluate.php', 'classes' );
@@ -175,11 +180,11 @@
175 180 * Filters the table after evaluating formulas in the table.
176 181 *
177 182 * @since 1.0.0
178 183 *
179 - * @param array $table The table with evaluated formulas.
180 - * @param array $orig_table The table with unevaluated formulas.
181 - * @param array $render_options The render options for the table.
184 + * @param array<string, mixed> $table The table with evaluated formulas.
185 + * @param array<string, mixed> $orig_table The table with unevaluated formulas.
186 + * @param array<string, mixed> $render_options The render options for the table.
182 187 */
183 188 $this->table = apply_filters( 'tablepress_table_evaluate_data', $this->table, $orig_table, $this->render_options );
184 189 }
185 190
@@ -187,9 +192,9 @@
187 192 * Remove all cells from the data set that shall not be rendered, because they are hidden.
188 193 *
189 194 * @since 1.0.0
190 195 */
191 - protected function _prepare_render_data() {
196 + protected function _prepare_render_data(): void {
192 197 $orig_table = $this->table;
193 198
194 199 $num_rows = count( $this->table['data'] );
195 200 $num_columns = ( $num_rows > 0 ) ? count( $this->table['data'][0] ) : 0;
@@ -277,11 +282,11 @@
277 282 * Filters the table after processing the table visibility information.
278 283 *
279 284 * @since 1.0.0
280 285 *
281 - * @param array $table The processed table.
282 - * @param array $orig_table The unprocessed table.
283 - * @param array $render_options The render options for the table.
286 + * @param array<string, mixed> $table The processed table.
287 + * @param array<string, mixed> $orig_table The unprocessed table.
288 + * @param array<string, mixed> $render_options The render options for the table.
284 289 */
285 290 $this->table = apply_filters( 'tablepress_table_render_data', $this->table, $orig_table, $this->render_options );
286 291 }
287 292
@@ -289,9 +294,9 @@
289 294 * Generate the data that is to be rendered.
290 295 *
291 296 * @since 2.0.0
292 297 */
293 - protected function _process_render_data() {
298 + protected function _process_render_data(): void {
294 299 $orig_table = $this->table;
295 300
296 301 // Deactivate nl2br() for this render process, if "convert_line_breaks" Shortcode parameter is set to false.
297 302 if ( ! $this->render_options['convert_line_breaks'] ) {
@@ -300,16 +305,25 @@
300 305
301 306 foreach ( $this->table['data'] as $row_idx => $row ) {
302 307 foreach ( $row as $col_idx => $cell_content ) {
303 308 // Print formulas that are escaped with '= (like in Excel) as text.
304 - if ( "'=" === substr( $cell_content, 0, 2 ) ) {
309 + if ( str_starts_with( $cell_content, "'=" ) ) {
305 310 $cell_content = substr( $cell_content, 1 );
306 311 }
307 312 $cell_content = $this->safe_output( $cell_content );
308 - if ( false !== strpos( $cell_content, '[' ) ) {
313 + if ( str_contains( $cell_content, '[' ) ) {
309 314 $cell_content = do_shortcode( $cell_content );
310 315 }
311 - /** This filter is documented in classes/class-render.php */
316 + /**
317 + * Filters the content of a single cell, after formulas have been evaluated, the output has been sanitized, and Shortcodes have been evaluated.
318 + *
319 + * @since 1.0.0
320 + *
321 + * @param string $cell_content The cell content.
322 + * @param string $table_id The current table ID.
323 + * @param int $row_idx The row number of the cell.
324 + * @param int $col_idx The column number of the cell.
325 + */
312 326 $cell_content = apply_filters( 'tablepress_cell_content', $cell_content, $this->table['id'], $row_idx + 1, $col_idx + 1 );
313 327 $this->table['data'][ $row_idx ][ $col_idx ] = $cell_content;
314 328 }
315 329 }
@@ -323,11 +337,11 @@
323 337 * Filters the table after processing the table content handling.
324 338 *
325 339 * @since 2.0.0
326 340 *
327 - * @param array $table The processed table.
328 - * @param array $orig_table The unprocessed table.
329 - * @param array $render_options The render options for the table.
341 + * @param array<string, mixed> $table The processed table.
342 + * @param array<string, mixed> $orig_table The unprocessed table.
343 + * @param array<string, mixed> $render_options The render options for the table.
330 344 */
331 345 $this->table = apply_filters( 'tablepress_table_content_render_data', $this->table, $orig_table, $this->render_options );
332 346 }
333 347
@@ -335,14 +349,15 @@
335 349 * Generate the HTML output of the table.
336 350 *
337 351 * @since 1.0.0
338 352 */
339 - protected function _render_table() {
353 + protected function _render_table(): void {
340 354 $num_rows = count( $this->table['data'] );
341 355 $num_columns = ( $num_rows > 0 ) ? count( $this->table['data'][0] ) : 0;
342 356
343 357 // Check if there are rows and columns in the table (might not be the case after removing hidden rows/columns!).
344 358 if ( 0 === $num_rows || 0 === $num_columns ) {
359 + /* translators: %s: Table ID */
345 360 $this->output = sprintf( __( '<!-- The table with the ID %s is empty! -->', 'tablepress' ), $this->table['id'] );
346 361 return;
347 362 }
348 363
@@ -354,10 +369,10 @@
354 369 * Filters the trigger keywords for "colspan" and "rowspan"
355 370 *
356 371 * @since 1.0.0
357 372 *
358 - * @param array $span_trigger The trigger keywords for combining table cells.
359 - * @param string $table_id The current table ID.
373 + * @param array<string, string> $span_trigger The trigger keywords for combining table cells.
374 + * @param string $table_id The current table ID.
360 375 */
361 376 $this->span_trigger = apply_filters( 'tablepress_span_trigger_keywords', $this->span_trigger, $this->table['id'] );
362 377
363 378 // Explode from string to array.
@@ -396,11 +411,11 @@
396 411 * Filters the attributes for the table name (HTML h2 element, by default).
397 412 *
398 413 * @since 1.13.0
399 414 *
400 - * @param array $name_attributes The attributes for the table name element.
401 - * @param array $table The current table.
402 - * @param array $render_options The render options for the table.
415 + * @param array<string, string> $name_attributes The attributes for the table name element.
416 + * @param array<string, mixed> $table The current table.
417 + * @param array<string, mixed> $render_options The render options for the table.
403 418 */
404 419 $name_attributes = apply_filters( 'tablepress_table_name_tag_attributes', $name_attributes, $this->table, $this->render_options );
405 420 $name_attributes = $this->_attributes_array_to_string( $name_attributes );
406 421
@@ -435,11 +450,11 @@
435 450 * Filters the attributes for the table description (HTML span element, by default).
436 451 *
437 452 * @since 1.13.0
438 453 *
439 - * @param array $description_attributes The attributes for the table description element.
440 - * @param array $table The current table.
441 - * @param array $render_options The render options for the table.
454 + * @param array<string, string> $description_attributes The attributes for the table description element.
455 + * @param array<string, mixed> $table The current table.
456 + * @param array<string, mixed> $render_options The render options for the table.
442 457 */
443 458 $description_attributes = apply_filters( 'tablepress_table_description_tag_attributes', $description_attributes, $this->table, $this->render_options );
444 459 $description_attributes = $this->_attributes_array_to_string( $description_attributes );
445 460
@@ -452,30 +467,46 @@
452 467 if ( $this->render_options['print_description'] && 'above' === $this->render_options['print_description_position'] ) {
453 468 $output .= $print_description_html;
454 469 }
455 470
456 - $thead = '';
457 - $tfoot = '';
471 + $thead = array();
472 + $tfoot = array();
458 473 $tbody = array();
459 474
460 475 $this->last_row_idx = $num_rows - 1;
461 476 $this->last_column_idx = $num_columns - 1;
477 +
462 478 // Loop through rows in reversed order, to search for rowspan trigger keyword.
463 - for ( $row_idx = $this->last_row_idx; $row_idx >= 0; $row_idx-- ) {
464 - // Last row, need to check for footer (but only if at least two rows).
465 - if ( $this->last_row_idx === $row_idx && $this->render_options['table_foot'] && $num_rows > 1 ) {
466 - $tfoot = $this->_render_row( $row_idx, 'th' );
467 - continue;
479 + $row_idx = $this->last_row_idx;
480 +
481 + // Render the table footer rows, if there is at least one extra row.
482 + if ( $this->render_options['table_foot'] > 0 && $num_rows >= $this->render_options['table_head'] + $this->render_options['table_foot'] ) { // @phpstan-ignore greaterOrEqual.invalid (`table_head` and `table_foot` are integers.)
483 + $last_tbody_idx = $this->last_row_idx - $this->render_options['table_foot'];
484 + while ( $row_idx > $last_tbody_idx ) {
485 + $tfoot[] = $this->_render_row( $row_idx, 'th' );
486 + --$row_idx;
468 487 }
469 - // First row, need to check for head (but only if at least two rows).
470 - if ( 0 === $row_idx && $this->render_options['table_head'] && $num_rows > 1 ) {
471 - $thead = $this->_render_row( $row_idx, 'th' );
472 - continue;
473 - }
474 - // Neither first nor last row (with respective head/foot enabled), so render as body row.
488 + // Reverse rows because we looped through the rows in reverse order.
489 + $tfoot = array_reverse( $tfoot );
490 + }
491 +
492 + // Render the table body rows.
493 + $last_thead_idx = $this->render_options['table_head'] - 1;
494 + while ( $row_idx > $last_thead_idx ) {
475 495 $tbody[] = $this->_render_row( $row_idx, 'td' );
496 + --$row_idx;
476 497 }
498 + // Reverse rows because we looped through the rows in reverse order.
499 + $tbody = array_reverse( $tbody );
477 500
501 + // Render the table header rows, if rows are left.
502 + while ( $row_idx > -1 ) {
503 + $thead[] = $this->_render_row( $row_idx, 'th' );
504 + --$row_idx;
505 + }
506 + // Reverse rows because we looped through the rows in reverse order.
507 + $thead = array_reverse( $thead );
508 +
478 509 // <caption> tag.
479 510 /**
480 511 * Filters the content for the HTML caption element of the table.
481 512 *
@@ -482,10 +513,10 @@
482 513 * If the "Edit" link for a table is shown, it is also added to the caption element.
483 514 *
484 515 * @since 1.0.0
485 516 *
486 - * @param string $caption The content for the HTML caption element of the table. Default empty.
487 - * @param array $table The current table.
517 + * @param string $caption The content for the HTML caption element of the table. Default empty.
518 + * @param array<string, mixed> $table The current table.
488 519 */
489 520 $caption = apply_filters( 'tablepress_print_caption_text', '', $this->table );
490 521 $caption_style = '';
491 522 $caption_class = '';
@@ -535,9 +566,9 @@
535 566 * @param string $table_id The current table ID.
536 567 * @param int $col_idx The number of the column.
537 568 */
538 569 $attributes = apply_filters( 'tablepress_colgroup_tag_attributes', $attributes, $this->table['id'], $col_idx + 1 );
539 - $colgroup .= "\t<col{$attributes}/>\n";
570 + $colgroup .= "\t<col{$attributes} />\n";
540 571 }
541 572 }
542 573 if ( ! empty( $colgroup ) ) {
543 574 $colgroup = "<colgroup>\n{$colgroup}</colgroup>\n";
@@ -542,18 +573,36 @@
542 573 if ( ! empty( $colgroup ) ) {
543 574 $colgroup = "<colgroup>\n{$colgroup}</colgroup>\n";
544 575 }
545 576
546 - // <thead>, <tfoot>, and <tbody> tags.
577 + /*
578 + * <thead>, <tfoot>, and <tbody> tags.
579 + */
580 +
547 581 if ( ! empty( $thead ) ) {
548 - $thead = "<thead>\n{$thead}</thead>\n";
582 + $thead = "<thead>\n" . implode( '', $thead ) . "</thead>\n";
583 + } else {
584 + $thead = '';
549 585 }
586 +
550 587 if ( ! empty( $tfoot ) ) {
551 - $tfoot = "<tfoot>\n{$tfoot}</tfoot>\n";
588 + $tfoot = "<tfoot>\n" . implode( '', $tfoot ) . "</tfoot>\n";
589 + } else {
590 + $tfoot = '';
552 591 }
553 - $tbody_class = ( $this->render_options['row_hover'] ) ? ' class="row-hover"' : '';
554 - // Reverse rows because we looped through the rows in reverse order.
555 - $tbody = array_reverse( $tbody );
592 +
593 + $tbody_classes = array();
594 + if ( $this->render_options['alternating_row_colors'] ) {
595 + $tbody_classes[] = 'row-striping';
596 + }
597 + if ( $this->render_options['row_hover'] ) {
598 + $tbody_classes[] = 'row-hover';
599 + }
600 + $tbody_class = implode( ' ', $tbody_classes );
601 + if ( '' !== $tbody_class ) {
602 + $tbody_class = ' class="' . esc_attr( $tbody_class ) . '"';
603 + }
604 +
556 605 $tbody = "<tbody{$tbody_class}>\n" . implode( '', $tbody ) . "</tbody>\n";
557 606
558 607 // Attributes for the table (HTML table element).
559 608 $table_attributes = array();
@@ -563,16 +612,23 @@
563 612 $table_attributes['id'] = $this->render_options['html_id'];
564 613 }
565 614
566 615 // "class" attribute.
567 - $css_classes = array( 'tablepress', "tablepress-id-{$this->table['id']}", $this->render_options['extra_css_classes'] );
616 + $css_classes = array(
617 + 'tablepress',
618 + "tablepress-id-{$this->table['id']}",
619 + $this->render_options['extra_css_classes'],
620 + );
621 + if ( $this->tbody_has_connected_cells ) {
622 + $css_classes[] = 'tbody-has-connected-cells';
623 + }
568 624 /**
569 625 * Filters the CSS classes that are given to the HTML table element.
570 626 *
571 627 * @since 1.0.0
572 628 *
573 - * @param array $css_classes The CSS classes for the table element.
574 - * @param string $table_id The current table ID.
629 + * @param string[] $css_classes The CSS classes for the table element.
630 + * @param string $table_id The current table ID.
575 631 */
576 632 $css_classes = apply_filters( 'tablepress_table_css_classes', $css_classes, $this->table['id'] );
577 633 // $css_classes might contain several classes in one array entry.
578 634 $css_classes = explode( ' ', implode( ' ', $css_classes ) );
@@ -577,10 +633,11 @@
577 633 // $css_classes might contain several classes in one array entry.
578 634 $css_classes = explode( ' ', implode( ' ', $css_classes ) );
579 635 $css_classes = array_map( array( 'TablePress', 'sanitize_css_class' ), $css_classes );
580 636 $css_classes = array_unique( $css_classes );
581 - $css_classes = trim( implode( ' ', $css_classes ) );
582 - if ( ! empty( $css_classes ) ) {
637 + $css_classes = array_filter( $css_classes ); // Remove empty entries.
638 + $css_classes = implode( ' ', $css_classes );
639 + if ( '' !== $css_classes ) {
583 640 $table_attributes['class'] = $css_classes;
584 641 }
585 642
586 643 // ARIA label attributes.
@@ -599,10 +656,10 @@
599 656 * The attribute is only added if it is not empty.
600 657 *
601 658 * @since 1.0.0
602 659 *
603 - * @param string $summary The content for the summary attribute of the table. Default empty.
604 - * @param array $table The current table.
660 + * @param string $summary The content for the summary attribute of the table. Default empty.
661 + * @param array<string, mixed> $table The current table.
605 662 */
606 663 $summary = apply_filters( 'tablepress_print_summary_attr', $summary, $this->table );
607 664 if ( ! empty( $summary ) ) {
608 665 $table_attributes['summary'] = esc_attr( $summary );
@@ -619,35 +676,49 @@
619 676 * Filters the attributes for the table (HTML table element).
620 677 *
621 678 * @since 1.4.0
622 679 *
623 - * @param array $table_attributes The attributes for the table element.
624 - * @param array $table The current table.
625 - * @param array $render_options The render options for the table.
680 + * @param array<string, string> $table_attributes The attributes for the table element.
681 + * @param array<string, mixed> $table The current table.
682 + * @param array<string, mixed> $render_options The render options for the table.
626 683 */
627 684 $table_attributes = apply_filters( 'tablepress_table_tag_attributes', $table_attributes, $this->table, $this->render_options );
628 685 $table_attributes = $this->_attributes_array_to_string( $table_attributes );
629 686
630 - $output .= "\n<table{$table_attributes}>\n";
631 - $output .= $caption . $colgroup . $thead . $tbody . $tfoot;
632 - $output .= "</table>\n";
687 + $table_html = "<table{$table_attributes}>\n";
688 + $table_html .= $caption . $colgroup . $thead . $tbody . $tfoot;
689 + $table_html .= '</table>';
633 690
691 + /**
692 + * Filters the generated HTML code for the table, without HTML elements around it.
693 + *
694 + * @since 2.4.0
695 + *
696 + * @param string $output The generated HTML for the table, without HTML elements around it.
697 + * @param array<string, mixed> $table The current table.
698 + * @param array<string, mixed> $render_options The render options for the table, without HTML elements around it.
699 + */
700 + $table_html = apply_filters( 'tablepress_table_html', $table_html, $this->table, $this->render_options );
701 +
702 + $output .= "\n{$table_html}\n";
703 + unset( $table_html ); // Unset the potentially large variable to free up memory.
704 +
634 705 // name/description below table (HTML already generated above).
635 706 if ( $this->render_options['print_name'] && 'below' === $this->render_options['print_name_position'] ) {
636 - $output .= $print_name_html;
707 + $output .= $print_name_html; // @phpstan-ignore variable.undefined (The variable is set above.)
637 708 }
638 709 if ( $this->render_options['print_description'] && 'below' === $this->render_options['print_description_position'] ) {
639 - $output .= $print_description_html;
710 + $output .= $print_description_html; // @phpstan-ignore variable.undefined (The variable is set above.)
640 711 }
641 712
642 713 /**
643 - * Filters the generated HTML code for table.
714 + * Filters the generated HTML code for the table and HTML elements around it.
644 715 *
645 716 * @since 1.0.0
646 717 *
647 - * @param string $output The generated HTML for the table.
648 - * @param array $table The current table.
649 - * @param array $render_options The render options for the table.
718 + * @param string $output The generated HTML for the table and HTML elements around it.
719 + * @param array<string, mixed> $table The current table.
720 + * @param array<string, mixed> $render_options The render options for the table and HTML elements around it.
650 721 */
651 722 $this->output = apply_filters( 'tablepress_table_output', $output, $this->table, $this->render_options );
652 723 }
653 724
@@ -659,9 +730,9 @@
659 730 * @param int $row_idx Index of the row to be rendered.
660 731 * @param string $tag HTML tag to use for the cells (td or th).
661 732 * @return string HTML for the row.
662 733 */
663 - protected function _render_row( $row_idx, $tag ) {
734 + protected function _render_row( int $row_idx, string $tag ): string {
664 735 $row_cells = array();
665 736 // Loop through cells in reversed order, to search for colspan or rowspan trigger words.
666 737 for ( $col_idx = $this->last_column_idx; $col_idx >= 0; $col_idx-- ) {
667 738 $cell_content = $this->table['data'][ $row_idx ][ $col_idx ];
@@ -668,10 +739,10 @@
668 739
669 740 if ( $this->span_trigger['rowspan'] === $cell_content ) { // There will be a rowspan.
670 741 if ( ! (
671 742 ( 0 === $row_idx ) // No rowspan inside first row.
672 - || ( 1 === $row_idx && $this->render_options['table_head'] ) // No rowspan into table head.
673 - || ( $this->last_row_idx === $row_idx && $this->render_options['table_foot'] ) // No rowspan out of table foot.
743 + || ( $this->render_options['table_head'] === $row_idx ) // No rowspan into table head.
744 + || ( $this->last_row_idx - $this->render_options['table_foot'] + 1 === $row_idx ) // No rowspan out of table foot.
674 745 ) ) {
675 746 // Increase counter for rowspan in this column.
676 747 ++$this->rowspan[ $col_idx ];
677 748 // Reset counter for colspan in this row, combined col- and rowspan might be happening.
@@ -695,10 +766,10 @@
695 766 $cell_content = '';
696 767 } elseif ( $this->span_trigger['span'] === $cell_content ) { // There will be a combined col- and rowspan.
697 768 if ( ! (
698 769 ( 0 === $row_idx ) // No rowspan inside first row.
699 - || ( 1 === $row_idx && $this->render_options['table_head'] ) // No rowspan into table head.
700 - || ( $this->last_row_idx === $row_idx && $this->render_options['table_foot'] ) // No rowspan out of table foot.
770 + || ( $this->render_options['table_head'] === $row_idx ) // No rowspan into table head.
771 + || ( $this->last_row_idx - $this->render_options['table_foot'] + 1 === $row_idx ) // No rowspan out of table foot.
701 772 ) && ! (
702 773 ( 0 === $col_idx ) // No colspan inside first column.
703 774 || ( 1 === $col_idx && $this->render_options['first_column_th'] ) // No colspan into first column head.
704 775 ) ) {
@@ -712,12 +783,20 @@
712 783 $tag_attributes = array();
713 784
714 785 // "colspan" and "rowspan" attributes.
715 786 if ( $this->colspan[ $row_idx ] > 1 ) { // We have colspaned cells.
716 - $tag_attributes['colspan'] = $this->colspan[ $row_idx ];
787 + $tag_attributes['colspan'] = (string) $this->colspan[ $row_idx ];
788 + 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 ) {
789 + // Set flag that there are connected cells in the tbody.
790 + $this->tbody_has_connected_cells = true;
791 + }
717 792 }
718 793 if ( $this->rowspan[ $col_idx ] > 1 ) { // We have rowspaned cells.
719 - $tag_attributes['rowspan'] = $this->rowspan[ $col_idx ];
794 + $tag_attributes['rowspan'] = (string) $this->rowspan[ $col_idx ];
795 + 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 ) {
796 + // Set flag that there are connected cells in the tbody.
797 + $this->tbody_has_connected_cells = true;
798 + }
720 799 }
721 800
722 801 // "class" attribute.
723 802 $cell_class = 'column-' . ( $col_idx + 1 );
@@ -748,24 +827,28 @@
748 827 * Filters the attributes for the table cell (HTML td or th element).
749 828 *
750 829 * @since 1.4.0
751 830 *
752 - * @param array $tag_attributes The attributes for the td or th element.
753 - * @param string $table_id The current table ID.
754 - * @param string $cell_content The cell content.
755 - * @param int $row_idx The row number of the cell.
756 - * @param int $col_idx The column number of the cell.
757 - * @param int $colspan_row The number of combined columns for this cell.
758 - * @param int $rowspan_col The number of combined rows for this cell.
831 + * @param array<string, string> $tag_attributes The attributes for the td or th element.
832 + * @param string $table_id The current table ID.
833 + * @param string $cell_content The cell content.
834 + * @param int $row_idx The row number of the cell.
835 + * @param int $col_idx The column number of the cell.
836 + * @param int $colspan_row The number of combined columns for this cell.
837 + * @param int $rowspan_col The number of combined rows for this cell.
759 838 */
760 839 $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 ] );
761 840 $tag_attributes = $this->_attributes_array_to_string( $tag_attributes );
762 841
763 - if ( $this->render_options['first_column_th'] && 0 === $col_idx ) {
764 - $tag = 'th';
842 + if ( '' === $cell_content ) {
843 + $cell_tag = 'td'; // For accessibility, empty cells should use `td` and not `th` tags.
844 + } elseif ( $this->render_options['first_column_th'] && 0 === $col_idx ) {
845 + $cell_tag = 'th'; // Non-empty cells in the first column should use `th` tags, if enabled.
846 + } else {
847 + $cell_tag = $tag; // Otherwise, use the tag that was passed in as the default for the row.
765 848 }
766 849
767 - $row_cells[] = "<{$tag}{$tag_attributes}>{$cell_content}</{$tag}>";
850 + $row_cells[] = "<{$cell_tag}{$tag_attributes}>{$cell_content}</{$cell_tag}>";
768 851 $this->colspan[ $row_idx ] = 1; // Reset.
769 852 $this->rowspan[ $col_idx ] = 1; // Reset.
770 853 }
771 854
@@ -773,21 +856,18 @@
773 856 $tr_attributes = array();
774 857
775 858 // "class" attribute.
776 859 $row_classes = 'row-' . ( $row_idx + 1 );
777 - if ( $this->render_options['alternating_row_colors'] ) {
778 - $row_classes .= ( 1 === ( $row_idx % 2 ) ) ? ' even' : ' odd';
779 - }
780 860 /**
781 861 * Filters the CSS classes that are given to a row (HTML tr element) of a table.
782 862 *
783 863 * @since 1.0.0
784 864 *
785 - * @param string $row_classes The CSS classes for the row.
786 - * @param string $table_id The current table ID.
787 - * @param array $row_cells The HTML code for the cells of the row.
788 - * @param int $row_idx The row number.
789 - * @param array $row_data The content of the cells of the row.
865 + * @param string $row_classes The CSS classes for the row.
866 + * @param string $table_id The current table ID.
867 + * @param string[] $row_cells The HTML code for the cells of the row.
868 + * @param int $row_idx The row number.
869 + * @param string[] $row_data The content of the cells of the row.
790 870 */
791 871 $row_classes = apply_filters( 'tablepress_row_css_class', $row_classes, $this->table['id'], $row_cells, $row_idx + 1, $this->table['data'][ $row_idx ] );
792 872 if ( ! empty( $row_classes ) ) {
793 873 $tr_attributes['class'] = $row_classes;
@@ -797,12 +877,12 @@
797 877 * Filters the attributes for the table row (HTML tr element).
798 878 *
799 879 * @since 1.4.0
800 880 *
801 - * @param array $tr_attributes The attributes for the tr element.
802 - * @param string $table_id The current table ID.
803 - * @param int $row_idx The row number.
804 - * @param array $row_data The content of the cells of the row.
881 + * @param array<string, mixed> $tr_attributes The attributes for the tr element.
882 + * @param string $table_id The current table ID.
883 + * @param int $row_idx The row number.
884 + * @param string[] $row_data The content of the cells of the row.
805 885 */
806 886 $tr_attributes = apply_filters( 'tablepress_row_tag_attributes', $tr_attributes, $this->table['id'], $row_idx + 1, $this->table['data'][ $row_idx ] );
807 887 $tr_attributes = $this->_attributes_array_to_string( $tr_attributes );
808 888
@@ -815,12 +895,12 @@
815 895 * Convert an array of HTML tag attributes to a string.
816 896 *
817 897 * @since 1.4.0
818 898 *
819 - * @param array $attributes Attributes for the HTML tag in the array keys, and their values in the array values.
899 + * @param array<string, string> $attributes Attributes for the HTML tag in the array keys, and their values in the array values.
820 900 * @return string The attributes as a string for usage in a HTML element.
821 901 */
822 - protected function _attributes_array_to_string( array $attributes ) {
902 + protected function _attributes_array_to_string( array $attributes ): string {
823 903 $attributes_string = '';
824 904 foreach ( $attributes as $attribute => $value ) {
825 905 $attributes_string .= " {$attribute}=\"{$value}\"";
826 906 }
@@ -829,21 +909,19 @@
829 909
830 910 /**
831 911 * Possibly replace certain HTML entities and replace line breaks with HTML.
832 912 *
833 - * @TODO: Find a better solution than this function, e.g. something like wpautop().
834 - *
835 913 * @since 1.0.0
836 914 *
837 915 * @param string $text The string to process.
838 916 * @return string Processed string for output.
839 917 */
840 - protected function safe_output( $text ) {
918 + protected function safe_output( string $text ): string {
841 919 /*
842 920 * Replace any & with &amp; that is not already an encoded entity (from function htmlentities2 in WP 2.8).
843 921 * A complete htmlentities2() or htmlspecialchars() would encode <HTML> tags, which we don't want.
844 922 */
845 - $text = preg_replace( '/&(?![A-Za-z]{0,4}\w{2,3};|#[0-9]{2,4};)/', '&amp;', $text );
923 + $text = (string) preg_replace( '/&(?![A-Za-z]{0,4}\w{2,3};|#[0-9]{2,4};)/', '&amp;', $text );
846 924 /**
847 925 * Filters whether line breaks in the cell content shall be replaced with HTML br tags.
848 926 *
849 927 * @since 1.0.0
@@ -861,14 +939,15 @@
861 939 * Get the default render options, null means: Use option from "Edit" screen.
862 940 *
863 941 * @since 1.0.0
864 942 *
865 - * @return array Default render options.
943 + * @return array<string, mixed> Default render options.
866 944 */
867 - public function get_default_render_options() {
945 + public function get_default_render_options(): array {
868 946 // Attention: Array keys have to be lowercase, otherwise they won't match the Shortcode attributes, which will be passed in lowercase by WP.
869 947 return array(
870 948 'alternating_row_colors' => null,
949 + 'block_preview' => false,
871 950 'border' => false,
872 951 'cache_table_output' => true,
873 952 'cellpadding' => false,
874 953 'cellspacing' => false,
@@ -874,8 +953,9 @@
874 953 'cellspacing' => false,
875 954 'column_widths' => '',
876 955 'convert_line_breaks' => true,
877 956 'datatables_custom_commands' => null,
957 + 'datatables_datetime' => '',
878 958 'datatables_filter' => null,
879 959 'datatables_info' => null,
880 960 'datatables_lengthchange' => null,
881 961 'datatables_locale' => get_locale(),
@@ -910,36 +990,30 @@
910 990 * @since 1.0.0
911 991 *
912 992 * @return string CSS for the Preview iframe.
913 993 */
914 - public function get_preview_css() {
994 + public function get_preview_css(): string {
915 995 $is_rtl = is_rtl();
916 996 $tablepress_css = TablePress::load_class( 'TablePress_CSS', 'class-css.php', 'classes' );
917 997 $default_css_minified = $tablepress_css->load_default_css_from_file( $is_rtl );
918 998 if ( false === $default_css_minified ) {
919 999 $default_css_minified = '';
920 - } else {
921 - // Change relative URLs to web font files to absolute URLs, as combining the CSS files and saving to another directory breaks the relative URLs.
922 - $absolute_path = plugins_url( 'css/build/tablepress.', TABLEPRESS__FILE__ );
923 - // Make the absolute URL protocol-relative to prevent mixed content warnings.
924 - $absolute_path = str_replace( array( 'http:', 'https:' ), '', $absolute_path );
925 - $default_css_minified = str_replace( 'url(tablepress.', 'url(' . $absolute_path, $default_css_minified );
926 1000 }
927 1001
928 1002 $rtl_direction = $is_rtl ? "\ndirection: rtl;" : '';
929 1003
930 1004 return <<<CSS
931 -<style>
932 -/* iframe */
933 -body {
934 - margin: 10px;
935 - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;{$rtl_direction}
936 -}
937 -p {
938 - font-size: 13px;
939 -}
940 -{$default_css_minified}
941 -</style>
942 -CSS;
1005 + <style>
1006 + /* iframe */
1007 + body {
1008 + margin: 10px;
1009 + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;{$rtl_direction}
1010 + }
1011 + p {
1012 + font-size: 13px;
1013 + }
1014 + {$default_css_minified}
1015 + </style>
1016 + CSS;
943 1017 }
944 1018
945 1019 } // class TablePress_Render