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 +113 -63 2.4.23.2 View file →
@@ -26,9 +26,9 @@
26 26 *
27 27 * @since 1.0.0
28 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 *
@@ -34,9 +34,9 @@
34 34 *
35 35 * @since 1.0.0
36 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 *
@@ -50,9 +50,9 @@
50 50 *
51 51 * @since 1.0.0
52 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 );
@@ -62,9 +62,9 @@
62 62 *
63 63 * @since 1.0.0
64 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 *
@@ -70,25 +70,30 @@
70 70 *
71 71 * @since 1.0.0
72 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 *
@@ -350,8 +355,9 @@
350 355 $num_columns = ( $num_rows > 0 ) ? count( $this->table['data'][0] ) : 0;
351 356
352 357 // Check if there are rows and columns in the table (might not be the case after removing hidden rows/columns!).
353 358 if ( 0 === $num_rows || 0 === $num_columns ) {
359 + /* translators: %s: Table ID */
354 360 $this->output = sprintf( __( '<!-- The table with the ID %s is empty! -->', 'tablepress' ), $this->table['id'] );
355 361 return;
356 362 }
357 363
@@ -461,30 +467,46 @@
461 467 if ( $this->render_options['print_description'] && 'above' === $this->render_options['print_description_position'] ) {
462 468 $output .= $print_description_html;
463 469 }
464 470
465 - $thead = '';
466 - $tfoot = '';
471 + $thead = array();
472 + $tfoot = array();
467 473 $tbody = array();
468 474
469 475 $this->last_row_idx = $num_rows - 1;
470 476 $this->last_column_idx = $num_columns - 1;
477 +
471 478 // Loop through rows in reversed order, to search for rowspan trigger keyword.
472 - for ( $row_idx = $this->last_row_idx; $row_idx >= 0; $row_idx-- ) {
473 - // Last row, need to check for footer (but only if at least two rows).
474 - if ( $this->last_row_idx === $row_idx && $this->render_options['table_foot'] && $num_rows > 1 ) {
475 - $tfoot = $this->_render_row( $row_idx, 'th' );
476 - 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;
477 487 }
478 - // First row, need to check for head (but only if at least two rows).
479 - if ( 0 === $row_idx && $this->render_options['table_head'] && $num_rows > 1 ) {
480 - $thead = $this->_render_row( $row_idx, 'th' );
481 - continue;
482 - }
483 - // 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 ) {
484 495 $tbody[] = $this->_render_row( $row_idx, 'td' );
496 + --$row_idx;
485 497 }
498 + // Reverse rows because we looped through the rows in reverse order.
499 + $tbody = array_reverse( $tbody );
486 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 +
487 509 // <caption> tag.
488 510 /**
489 511 * Filters the content for the HTML caption element of the table.
490 512 *
@@ -551,18 +573,36 @@
551 573 if ( ! empty( $colgroup ) ) {
552 574 $colgroup = "<colgroup>\n{$colgroup}</colgroup>\n";
553 575 }
554 576
555 - // <thead>, <tfoot>, and <tbody> tags.
577 + /*
578 + * <thead>, <tfoot>, and <tbody> tags.
579 + */
580 +
556 581 if ( ! empty( $thead ) ) {
557 - $thead = "<thead>\n{$thead}</thead>\n";
582 + $thead = "<thead>\n" . implode( '', $thead ) . "</thead>\n";
583 + } else {
584 + $thead = '';
558 585 }
586 +
559 587 if ( ! empty( $tfoot ) ) {
560 - $tfoot = "<tfoot>\n{$tfoot}</tfoot>\n";
588 + $tfoot = "<tfoot>\n" . implode( '', $tfoot ) . "</tfoot>\n";
589 + } else {
590 + $tfoot = '';
561 591 }
562 - $tbody_class = ( $this->render_options['row_hover'] ) ? ' class="row-hover"' : '';
563 - // Reverse rows because we looped through the rows in reverse order.
564 - $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 +
565 605 $tbody = "<tbody{$tbody_class}>\n" . implode( '', $tbody ) . "</tbody>\n";
566 606
567 607 // Attributes for the table (HTML table element).
568 608 $table_attributes = array();
@@ -572,9 +612,16 @@
572 612 $table_attributes['id'] = $this->render_options['html_id'];
573 613 }
574 614
575 615 // "class" attribute.
576 - $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 + }
577 624 /**
578 625 * Filters the CSS classes that are given to the HTML table element.
579 626 *
580 627 * @since 1.0.0
@@ -586,10 +633,11 @@
586 633 // $css_classes might contain several classes in one array entry.
587 634 $css_classes = explode( ' ', implode( ' ', $css_classes ) );
588 635 $css_classes = array_map( array( 'TablePress', 'sanitize_css_class' ), $css_classes );
589 636 $css_classes = array_unique( $css_classes );
590 - $css_classes = trim( implode( ' ', $css_classes ) );
591 - if ( ! empty( $css_classes ) ) {
637 + $css_classes = array_filter( $css_classes ); // Remove empty entries.
638 + $css_classes = implode( ' ', $css_classes );
639 + if ( '' !== $css_classes ) {
592 640 $table_attributes['class'] = $css_classes;
593 641 }
594 642
595 643 // ARIA label attributes.
@@ -655,12 +703,12 @@
655 703 unset( $table_html ); // Unset the potentially large variable to free up memory.
656 704
657 705 // name/description below table (HTML already generated above).
658 706 if ( $this->render_options['print_name'] && 'below' === $this->render_options['print_name_position'] ) {
659 - $output .= $print_name_html;
707 + $output .= $print_name_html; // @phpstan-ignore variable.undefined (The variable is set above.)
660 708 }
661 709 if ( $this->render_options['print_description'] && 'below' === $this->render_options['print_description_position'] ) {
662 - $output .= $print_description_html;
710 + $output .= $print_description_html; // @phpstan-ignore variable.undefined (The variable is set above.)
663 711 }
664 712
665 713 /**
666 714 * Filters the generated HTML code for the table and HTML elements around it.
@@ -691,10 +739,10 @@
691 739
692 740 if ( $this->span_trigger['rowspan'] === $cell_content ) { // There will be a rowspan.
693 741 if ( ! (
694 742 ( 0 === $row_idx ) // No rowspan inside first row.
695 - || ( 1 === $row_idx && $this->render_options['table_head'] ) // No rowspan into table head.
696 - || ( $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.
697 745 ) ) {
698 746 // Increase counter for rowspan in this column.
699 747 ++$this->rowspan[ $col_idx ];
700 748 // Reset counter for colspan in this row, combined col- and rowspan might be happening.
@@ -718,10 +766,10 @@
718 766 $cell_content = '';
719 767 } elseif ( $this->span_trigger['span'] === $cell_content ) { // There will be a combined col- and rowspan.
720 768 if ( ! (
721 769 ( 0 === $row_idx ) // No rowspan inside first row.
722 - || ( 1 === $row_idx && $this->render_options['table_head'] ) // No rowspan into table head.
723 - || ( $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.
724 772 ) && ! (
725 773 ( 0 === $col_idx ) // No colspan inside first column.
726 774 || ( 1 === $col_idx && $this->render_options['first_column_th'] ) // No colspan into first column head.
727 775 ) ) {
@@ -736,11 +784,19 @@
736 784
737 785 // "colspan" and "rowspan" attributes.
738 786 if ( $this->colspan[ $row_idx ] > 1 ) { // We have colspaned cells.
739 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 + }
740 792 }
741 793 if ( $this->rowspan[ $col_idx ] > 1 ) { // We have rowspaned cells.
742 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 + }
743 799 }
744 800
745 801 // "class" attribute.
746 802 $cell_class = 'column-' . ( $col_idx + 1 );
@@ -782,13 +838,17 @@
782 838 */
783 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 ] );
784 840 $tag_attributes = $this->_attributes_array_to_string( $tag_attributes );
785 841
786 - if ( $this->render_options['first_column_th'] && 0 === $col_idx ) {
787 - $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.
788 848 }
789 849
790 - $row_cells[] = "<{$tag}{$tag_attributes}>{$cell_content}</{$tag}>";
850 + $row_cells[] = "<{$cell_tag}{$tag_attributes}>{$cell_content}</{$cell_tag}>";
791 851 $this->colspan[ $row_idx ] = 1; // Reset.
792 852 $this->rowspan[ $col_idx ] = 1; // Reset.
793 853 }
794 854
@@ -796,11 +856,8 @@
796 856 $tr_attributes = array();
797 857
798 858 // "class" attribute.
799 859 $row_classes = 'row-' . ( $row_idx + 1 );
800 - if ( $this->render_options['alternating_row_colors'] ) {
801 - $row_classes .= ( 1 === ( $row_idx % 2 ) ) ? ' even' : ' odd';
802 - }
803 860 /**
804 861 * Filters the CSS classes that are given to a row (HTML tr element) of a table.
805 862 *
806 863 * @since 1.0.0
@@ -852,10 +909,8 @@
852 909
853 910 /**
854 911 * Possibly replace certain HTML entities and replace line breaks with HTML.
855 912 *
856 - * @todo Find a better solution than this function, e.g. something like wpautop().
857 - *
858 913 * @since 1.0.0
859 914 *
860 915 * @param string $text The string to process.
861 916 * @return string Processed string for output.
@@ -898,8 +953,9 @@
898 953 'cellspacing' => false,
899 954 'column_widths' => '',
900 955 'convert_line_breaks' => true,
901 956 'datatables_custom_commands' => null,
957 + 'datatables_datetime' => '',
902 958 'datatables_filter' => null,
903 959 'datatables_info' => null,
904 960 'datatables_lengthchange' => null,
905 961 'datatables_locale' => get_locale(),
@@ -940,30 +996,24 @@
940 996 $tablepress_css = TablePress::load_class( 'TablePress_CSS', 'class-css.php', 'classes' );
941 997 $default_css_minified = $tablepress_css->load_default_css_from_file( $is_rtl );
942 998 if ( false === $default_css_minified ) {
943 999 $default_css_minified = '';
944 - } else {
945 - // Change relative URLs to web font files to absolute URLs, as combining the CSS files and saving to another directory breaks the relative URLs.
946 - $absolute_path = plugins_url( 'css/build/tablepress.', TABLEPRESS__FILE__ );
947 - // Make the absolute URL protocol-relative to prevent mixed content warnings.
948 - $absolute_path = str_replace( array( 'http:', 'https:' ), '', $absolute_path );
949 - $default_css_minified = str_replace( 'url(tablepress.', 'url(' . $absolute_path, $default_css_minified );
950 1000 }
951 1001
952 1002 $rtl_direction = $is_rtl ? "\ndirection: rtl;" : '';
953 1003
954 1004 return <<<CSS
955 -<style>
956 -/* iframe */
957 -body {
958 - margin: 10px;
959 - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;{$rtl_direction}
960 -}
961 -p {
962 - font-size: 13px;
963 -}
964 -{$default_css_minified}
965 -</style>
966 -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;
967 1017 }
968 1018
969 1019 } // class TablePress_Render