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 +135 -70 2.3.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 *
@@ -544,9 +566,9 @@
544 566 * @param string $table_id The current table ID.
545 567 * @param int $col_idx The number of the column.
546 568 */
547 569 $attributes = apply_filters( 'tablepress_colgroup_tag_attributes', $attributes, $this->table['id'], $col_idx + 1 );
548 - $colgroup .= "\t<col{$attributes}/>\n";
570 + $colgroup .= "\t<col{$attributes} />\n";
549 571 }
550 572 }
551 573 if ( ! empty( $colgroup ) ) {
552 574 $colgroup = "<colgroup>\n{$colgroup}</colgroup>\n";
@@ -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.
@@ -635,28 +683,42 @@
635 683 */
636 684 $table_attributes = apply_filters( 'tablepress_table_tag_attributes', $table_attributes, $this->table, $this->render_options );
637 685 $table_attributes = $this->_attributes_array_to_string( $table_attributes );
638 686
639 - $output .= "\n<table{$table_attributes}>\n";
640 - $output .= $caption . $colgroup . $thead . $tbody . $tfoot;
641 - $output .= "</table>\n";
687 + $table_html = "<table{$table_attributes}>\n";
688 + $table_html .= $caption . $colgroup . $thead . $tbody . $tfoot;
689 + $table_html .= '</table>';
642 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 +
643 705 // name/description below table (HTML already generated above).
644 706 if ( $this->render_options['print_name'] && 'below' === $this->render_options['print_name_position'] ) {
645 - $output .= $print_name_html;
707 + $output .= $print_name_html; // @phpstan-ignore variable.undefined (The variable is set above.)
646 708 }
647 709 if ( $this->render_options['print_description'] && 'below' === $this->render_options['print_description_position'] ) {
648 - $output .= $print_description_html;
710 + $output .= $print_description_html; // @phpstan-ignore variable.undefined (The variable is set above.)
649 711 }
650 712
651 713 /**
652 - * Filters the generated HTML code for table.
714 + * Filters the generated HTML code for the table and HTML elements around it.
653 715 *
654 716 * @since 1.0.0
655 717 *
656 - * @param string $output The generated HTML for the table.
718 + * @param string $output The generated HTML for the table and HTML elements around it.
657 719 * @param array<string, mixed> $table The current table.
658 - * @param array<string, mixed> $render_options The render options for the table.
720 + * @param array<string, mixed> $render_options The render options for the table and HTML elements around it.
659 721 */
660 722 $this->output = apply_filters( 'tablepress_table_output', $output, $this->table, $this->render_options );
661 723 }
662 724
@@ -677,10 +739,10 @@
677 739
678 740 if ( $this->span_trigger['rowspan'] === $cell_content ) { // There will be a rowspan.
679 741 if ( ! (
680 742 ( 0 === $row_idx ) // No rowspan inside first row.
681 - || ( 1 === $row_idx && $this->render_options['table_head'] ) // No rowspan into table head.
682 - || ( $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.
683 745 ) ) {
684 746 // Increase counter for rowspan in this column.
685 747 ++$this->rowspan[ $col_idx ];
686 748 // Reset counter for colspan in this row, combined col- and rowspan might be happening.
@@ -704,10 +766,10 @@
704 766 $cell_content = '';
705 767 } elseif ( $this->span_trigger['span'] === $cell_content ) { // There will be a combined col- and rowspan.
706 768 if ( ! (
707 769 ( 0 === $row_idx ) // No rowspan inside first row.
708 - || ( 1 === $row_idx && $this->render_options['table_head'] ) // No rowspan into table head.
709 - || ( $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.
710 772 ) && ! (
711 773 ( 0 === $col_idx ) // No colspan inside first column.
712 774 || ( 1 === $col_idx && $this->render_options['first_column_th'] ) // No colspan into first column head.
713 775 ) ) {
@@ -722,11 +784,19 @@
722 784
723 785 // "colspan" and "rowspan" attributes.
724 786 if ( $this->colspan[ $row_idx ] > 1 ) { // We have colspaned cells.
725 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 + }
726 792 }
727 793 if ( $this->rowspan[ $col_idx ] > 1 ) { // We have rowspaned cells.
728 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 + }
729 799 }
730 800
731 801 // "class" attribute.
732 802 $cell_class = 'column-' . ( $col_idx + 1 );
@@ -768,13 +838,17 @@
768 838 */
769 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 ] );
770 840 $tag_attributes = $this->_attributes_array_to_string( $tag_attributes );
771 841
772 - if ( $this->render_options['first_column_th'] && 0 === $col_idx ) {
773 - $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.
774 848 }
775 849
776 - $row_cells[] = "<{$tag}{$tag_attributes}>{$cell_content}</{$tag}>";
850 + $row_cells[] = "<{$cell_tag}{$tag_attributes}>{$cell_content}</{$cell_tag}>";
777 851 $this->colspan[ $row_idx ] = 1; // Reset.
778 852 $this->rowspan[ $col_idx ] = 1; // Reset.
779 853 }
780 854
@@ -782,11 +856,8 @@
782 856 $tr_attributes = array();
783 857
784 858 // "class" attribute.
785 859 $row_classes = 'row-' . ( $row_idx + 1 );
786 - if ( $this->render_options['alternating_row_colors'] ) {
787 - $row_classes .= ( 1 === ( $row_idx % 2 ) ) ? ' even' : ' odd';
788 - }
789 860 /**
790 861 * Filters the CSS classes that are given to a row (HTML tr element) of a table.
791 862 *
792 863 * @since 1.0.0
@@ -838,10 +909,8 @@
838 909
839 910 /**
840 911 * Possibly replace certain HTML entities and replace line breaks with HTML.
841 912 *
842 - * @todo Find a better solution than this function, e.g. something like wpautop().
843 - *
844 913 * @since 1.0.0
845 914 *
846 915 * @param string $text The string to process.
847 916 * @return string Processed string for output.
@@ -876,8 +945,9 @@
876 945 public function get_default_render_options(): array {
877 946 // Attention: Array keys have to be lowercase, otherwise they won't match the Shortcode attributes, which will be passed in lowercase by WP.
878 947 return array(
879 948 'alternating_row_colors' => null,
949 + 'block_preview' => false,
880 950 'border' => false,
881 951 'cache_table_output' => true,
882 952 'cellpadding' => false,
883 953 'cellspacing' => false,
@@ -883,8 +953,9 @@
883 953 'cellspacing' => false,
884 954 'column_widths' => '',
885 955 'convert_line_breaks' => true,
886 956 'datatables_custom_commands' => null,
957 + 'datatables_datetime' => '',
887 958 'datatables_filter' => null,
888 959 'datatables_info' => null,
889 960 'datatables_lengthchange' => null,
890 961 'datatables_locale' => get_locale(),
@@ -925,30 +996,24 @@
925 996 $tablepress_css = TablePress::load_class( 'TablePress_CSS', 'class-css.php', 'classes' );
926 997 $default_css_minified = $tablepress_css->load_default_css_from_file( $is_rtl );
927 998 if ( false === $default_css_minified ) {
928 999 $default_css_minified = '';
929 - } else {
930 - // Change relative URLs to web font files to absolute URLs, as combining the CSS files and saving to another directory breaks the relative URLs.
931 - $absolute_path = plugins_url( 'css/build/tablepress.', TABLEPRESS__FILE__ );
932 - // Make the absolute URL protocol-relative to prevent mixed content warnings.
933 - $absolute_path = str_replace( array( 'http:', 'https:' ), '', $absolute_path );
934 - $default_css_minified = str_replace( 'url(tablepress.', 'url(' . $absolute_path, $default_css_minified );
935 1000 }
936 1001
937 1002 $rtl_direction = $is_rtl ? "\ndirection: rtl;" : '';
938 1003
939 1004 return <<<CSS
940 -<style>
941 -/* iframe */
942 -body {
943 - margin: 10px;
944 - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;{$rtl_direction}
945 -}
946 -p {
947 - font-size: 13px;
948 -}
949 -{$default_css_minified}
950 -</style>
951 -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;
952 1017 }
953 1018
954 1019 } // class TablePress_Render