PluginProbe
TablePress – Tables in WordPress made easy / 3.0.4
TablePress – Tables in WordPress made easy v3.0.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 2.4.4 All 44 releases
← All changes | models/model-table.php +131 -84 2.23.0.4 View file →
@@ -24,27 +24,24 @@
24 24 /**
25 25 * Instance of the Post Type Model.
26 26 *
27 27 * @since 1.0.0
28 - * @var TablePress_Post_Model
29 28 */
30 - protected $model_post;
29 + protected \TablePress_Post_Model $model_post;
31 30
32 31 /**
33 32 * Name of the Post Meta Field for table options.
34 33 *
35 34 * @since 1.0.0
36 - * @var string
37 35 */
38 - protected $table_options_field_name = '_tablepress_table_options';
36 + protected string $table_options_field_name = '_tablepress_table_options';
39 37
40 38 /**
41 39 * Name of the Post Meta Field for table visibility.
42 40 *
43 41 * @since 1.0.0
44 - * @var string
45 42 */
46 - protected $table_visibility_field_name = '_tablepress_table_visibility';
43 + protected string $table_visibility_field_name = '_tablepress_table_visibility';
47 44
48 45 /**
49 46 * Default set of tables.
50 47 *
@@ -53,9 +50,9 @@
53 50 * @type int $last_id Last table ID that was given to a new table.
54 51 * @type array<string, int> $table_post Connections between table ID and post ID (key: table ID, value: post ID).
55 52 * }
56 53 */
57 - protected $default_tables = array(
54 + protected array $default_tables = array(
58 55 'last_id' => 0,
59 56 'table_post' => array(),
60 57 );
61 58
@@ -62,11 +59,10 @@
62 59 /**
63 60 * Instance of WP_Option class for the list of tables.
64 61 *
65 62 * @since 1.0.0
66 - * @var TablePress_WP_Option
67 63 */
68 - protected $tables;
64 + protected \TablePress_WP_Option $tables;
69 65
70 66 /**
71 67 * Init the Table model by instantiating a Post model and loading the list of tables option.
72 68 *
@@ -114,11 +110,8 @@
114 110 * @param int $post_id Post ID of an existing table, or -1 for a new table.
115 111 * @return array<string, mixed> Post.
116 112 */
117 113 protected function _table_to_post( array $table, int $post_id ): array {
118 - // Run filters on content in each cell and other fields.
119 - $table = $this->filter_content( $table );
120 -
121 114 // Sanitize each cell, table name, and table description, if the user is not allowed to work with unfiltered HTML.
122 115 if ( ! current_user_can( 'unfiltered_html' ) ) {
123 116 $table = $this->sanitize( $table );
124 117 }
@@ -188,9 +181,9 @@
188 181 * @param bool $load_data Whether the table data shall be loaded.
189 182 * @param bool $load_options_visibility Whether the table options and table visibility shall be loaded.
190 183 * @return array<string, mixed>|WP_Error Table as an array on success, WP_Error on error.
191 184 */
192 - public function load( string $table_id, bool $load_data = true, bool $load_options_visibility = true ) /* : array|WP_Post */ {
185 + public function load( string $table_id, bool $load_data = true, bool $load_options_visibility = true ) /* : array|WP_Error */ {
193 186 if ( empty( $table_id ) ) {
194 187 return new WP_Error( 'table_load_empty_table_id' );
195 188 }
196 189
@@ -232,11 +225,13 @@
232 225
233 226 // This loop now uses the WP cache.
234 227 $table_ids = array();
235 228 foreach ( $table_post as $table_id => $post_id ) {
236 - $table_id = (string) $table_id;
229 + $table_id = (string) $table_id; // Ensure that the table ID is a string, as it comes from an array key where numeric strings are converted to integers.
230 +
237 231 // Load table without data and options to save memory.
238 232 $table = $this->load( $table_id, false, false );
233 +
239 234 // Skip tables that could not be loaded properly.
240 235 if ( ! is_wp_error( $table ) ) {
241 236 $table_ids[] = $table_id;
242 237 }
@@ -273,9 +268,9 @@
273 268
274 269 // Sanitize each cell.
275 270 foreach ( $table['data'] as $row_idx => $row ) {
276 271 foreach ( $row as $column_idx => $cell_content ) {
277 - $table['data'][ $row_idx ][ $column_idx ] = wp_kses_post( $cell_content ); // Equals wp_filter_post_kses(), but without the unncessary slashes handling.
272 + $table['data'][ $row_idx ][ $column_idx ] = wp_kses_post( $cell_content ); // Equals wp_filter_post_kses(), but without the unnecessary slashes handling.
278 273 }
279 274 }
280 275
281 276 return $table;
@@ -281,47 +276,8 @@
281 276 return $table;
282 277 }
283 278
284 279 /**
285 - * Filter/modify the content of table cells and other fields, e.g. for security hardening.
286 - *
287 - * This is similar to the `sanitize()` method, but executed for all users.
288 - * In 1.10.0, adding `rel="noopener noreferrer"` to all HTML link elements like `<a target=` was added. See https://core.trac.wordpress.org/ticket/43187.
289 - * Since 1.13.0, and on WP 5.6, only `rel="noopener"` is added. See https://core.trac.wordpress.org/ticket/49558.
290 - *
291 - * @since 1.10.0
292 - *
293 - * @param array<string, mixed> $table Table.
294 - * @return array<string, mixed> Filtered/modified table.
295 - */
296 - public function filter_content( array $table ): array {
297 - /**
298 - * Filters whether the contents of table cells and fields should be filtered/modified.
299 - *
300 - * @since 1.10.0
301 - *
302 - * @param bool $filter Whether to filter the content of table cells and other fields. Default true.
303 - */
304 - if ( ! apply_filters( 'tablepress_filter_table_cell_content', true ) ) {
305 - return $table;
306 - }
307 -
308 - // Filter the table name and description.
309 - $fields = array( 'name', 'description' );
310 - foreach ( $fields as $field ) {
311 - $table[ $field ] = wp_targeted_link_rel( $table[ $field ] );
312 - }
313 -
314 - foreach ( $table['data'] as $row_idx => $row ) {
315 - foreach ( $row as $column_idx => $cell_content ) {
316 - $table['data'][ $row_idx ][ $column_idx ] = wp_targeted_link_rel( $cell_content );
317 - }
318 - }
319 -
320 - return $table;
321 - }
322 -
323 - /**
324 280 * Save a table.
325 281 *
326 282 * @since 1.0.0
327 283 *
@@ -489,9 +445,11 @@
489 445 return new WP_Error( 'table_delete_table_does_not_exist', '', $table_id );
490 446 }
491 447
492 448 $post_id = $this->_get_post_id( $table_id ); // No ! false check necessary, as this is covered by table_exists() check above.
493 - $deleted = $this->model_post->delete( $post_id ); // Post Meta fields will be deleted automatically by that function. // @phpstan-ignore-line .
449 +
450 + // @phpstan-ignore argument.type
451 + $deleted = $this->model_post->delete( $post_id ); // Post Meta fields will be deleted automatically by that function.
494 452 if ( false === $deleted ) {
495 453 return new WP_Error( 'table_delete_post_could_not_be_deleted', '', $post_id );
496 454 }
497 455
@@ -526,11 +484,13 @@
526 484 return;
527 485 }
528 486
529 487 foreach ( $tables['table_post'] as $table_id => $post_id ) {
530 - $table_id = (string) $table_id;
488 + $table_id = (string) $table_id; // Ensure that the table ID is a string, as it comes from an array key where numeric strings are converted to integers.
489 +
531 490 $this->model_post->delete( $post_id ); // Post Meta fields will be deleted automatically by that function.
532 491 unset( $tables['table_post'][ $table_id ] );
492 +
533 493 // Invalidate table output caches that belong to this table.
534 494 $this->invalidate_table_output_cache( $table_id );
535 495 }
536 496
@@ -650,13 +610,13 @@
650 610 }
651 611
652 612 // Kinsta.
653 613 if ( isset( $GLOBALS['kinsta_cache'] ) && ! empty( $GLOBALS['kinsta_cache']->kinsta_cache_purge ) && is_callable( array( $GLOBALS['kinsta_cache']->kinsta_cache_purge, 'purge_complete_caches' ) ) ) {
654 - $GLOBALS['kinsta_cache']->kinsta_cache_purge->purge_complete_caches(); // @phpstan-ignore-line
614 + $GLOBALS['kinsta_cache']->kinsta_cache_purge->purge_complete_caches(); // @phpstan-ignore method.nonObject
655 615 }
656 616 // LiteSpeed Cache.
657 617 if ( is_callable( array( 'LiteSpeed_Cache_Tags', 'add_purge_tag' ) ) ) {
658 - LiteSpeed_Cache_Tags::add_purge_tag( '*' ); // @phpstan-ignore-line
618 + LiteSpeed_Cache_Tags::add_purge_tag( '*' ); // @phpstan-ignore class.notFound
659 619 }
660 620 // Pagely.
661 621 if ( class_exists( 'PagelyCachePurge' ) ) {
662 622 $_pagely = new PagelyCachePurge();
@@ -665,23 +625,23 @@
665 625 }
666 626 }
667 627 // Pressidum.
668 628 if ( is_callable( array( 'Ninukis_Plugin', 'get_instance' ) ) ) {
669 - $_pressidum = Ninukis_Plugin::get_instance(); // @phpstan-ignore-line
629 + $_pressidum = Ninukis_Plugin::get_instance(); // @phpstan-ignore class.notFound
670 630 if ( is_callable( array( $_pressidum, 'purgeAllCaches' ) ) ) {
671 - $_pressidum->purgeAllCaches(); // @phpstan-ignore-line
631 + $_pressidum->purgeAllCaches(); // @phpstan-ignore method.nonObject
672 632 }
673 633 }
674 634 // Savvii.
675 635 if ( defined( '\Savvii\CacheFlusherPlugin::NAME_DOMAINFLUSH_NOW' ) ) {
676 - $_savvii = new \Savvii\CacheFlusherPlugin(); // @phpstan-ignore-line
636 + $_savvii = new \Savvii\CacheFlusherPlugin(); // @phpstan-ignore class.notFound
677 637 if ( is_callable( array( $_savvii, 'domainflush' ) ) ) {
678 - $_savvii->domainflush(); // @phpstan-ignore-line
638 + $_savvii->domainflush(); // @phpstan-ignore class.notFound
679 639 }
680 640 }
681 641 // WP Fastest Cache.
682 642 if ( isset( $GLOBALS['wp_fastest_cache'] ) && is_callable( array( $GLOBALS['wp_fastest_cache'], 'deleteCache' ) ) ) {
683 - $GLOBALS['wp_fastest_cache']->deleteCache( true ); // @phpstan-ignore-line
643 + $GLOBALS['wp_fastest_cache']->deleteCache( true ); // @phpstan-ignore method.nonObject
684 644 }
685 645 // WP-Optimize.
686 646 if ( function_exists( 'WP_Optimize' ) ) {
687 647 WP_Optimize()->get_page_cache()->purge();
@@ -809,10 +769,10 @@
809 769 'last_modified' => wp_date( 'Y-m-d H:i:s' ),
810 770 'author' => get_current_user_id(),
811 771 'options' => array(
812 772 'last_editor' => get_current_user_id(),
813 - 'table_head' => true,
814 - 'table_foot' => false,
773 + 'table_head' => 1,
774 + 'table_foot' => 0,
815 775 'alternating_row_colors' => true,
816 776 'row_hover' => true,
817 777 'print_name' => false,
818 778 'print_name_position' => 'above',
@@ -908,9 +868,9 @@
908 868 array_walk_recursive(
909 869 $table['data'],
910 870 static function ( /* string|int|float|bool|null */ &$cell_content, int $col_idx ): void {
911 871 $cell_content = (string) $cell_content;
912 - }
872 + },
913 873 );
914 874 // Table Options.
915 875 if ( isset( $new_table['options'] ) ) { // Options are for example not set for newly added tables.
916 876 // Specials check for certain options.
@@ -925,8 +885,17 @@
925 885 if ( $new_table['options']['datatables_paginate_entries'] < 1 ) {
926 886 $new_table['options']['datatables_paginate_entries'] = 10; // Default value.
927 887 }
928 888 }
889 +
890 + // Backward compatibility: Convert boolean or numeric string "table_head" and "table_foot" options to integer.
891 + if ( isset( $new_table['options']['table_head'] ) ) {
892 + $new_table['options']['table_head'] = absint( $new_table['options']['table_head'] );
893 + }
894 + if ( isset( $new_table['options']['table_foot'] ) ) {
895 + $new_table['options']['table_foot'] = absint( $new_table['options']['table_foot'] );
896 + }
897 +
929 898 // Merge new options.
930 899 $default_table = $this->get_table_template();
931 900 $table['options'] = array_intersect_key( $table['options'], $default_table['options'] );
932 901 $new_table['options'] = array_intersect_key( $new_table['options'], $default_table['options'] );
@@ -940,8 +909,18 @@
940 909 // $table['created'] = wp_date( 'Y-m-d H:i:s' ); // We don't want this, as it would override the original datetime.
941 910 $table['last_modified'] = wp_date( 'Y-m-d H:i:s' );
942 911 $table['options']['last_editor'] = get_current_user_id();
943 912
913 + // Prevent issues if the "Custom Commands" field is not set, e.g. when non-admins have previously edited the table.
914 + if ( ! isset( $table['options']['datatables_custom_commands'] ) ) {
915 + $table['options']['datatables_custom_commands'] = '';
916 + }
917 +
918 + // Convert CSS classes and some DataTables 1.x parameters to the DataTables 2 variants.
919 + if ( '' !== $table['options']['datatables_custom_commands'] ) {
920 + $table['options']['datatables_custom_commands'] = TablePress::convert_datatables_api_data( $table['options']['datatables_custom_commands'] );
921 + }
922 +
944 923 return $table;
945 924 }
946 925
947 926 /**
@@ -954,9 +933,9 @@
954 933 * @return bool True on success, false on error.
955 934 */
956 935 protected function _add_table_options( int $post_id, array $options ): bool {
957 936 $options = wp_json_encode( $options, TABLEPRESS_JSON_OPTIONS );
958 - return $this->model_post->add_meta_field( $post_id, $this->table_options_field_name, $options ); // @phpstan-ignore-line
937 + return $this->model_post->add_meta_field( $post_id, $this->table_options_field_name, $options ); // @phpstan-ignore argument.type
959 938 }
960 939
961 940 /**
962 941 * Update the table options of a table (in a post meta field in the table's post).
@@ -968,9 +947,9 @@
968 947 * @return bool True on success, false on error.
969 948 */
970 949 protected function _update_table_options( int $post_id, array $options ): bool {
971 950 $options = wp_json_encode( $options, TABLEPRESS_JSON_OPTIONS );
972 - return $this->model_post->update_meta_field( $post_id, $this->table_options_field_name, $options ); // @phpstan-ignore-line
951 + return $this->model_post->update_meta_field( $post_id, $this->table_options_field_name, $options ); // @phpstan-ignore argument.type
973 952 }
974 953
975 954 /**
976 955 * Get the table options of a table (from a post meta field of the table's post).
@@ -984,9 +963,15 @@
984 963 $options = $this->model_post->get_meta_field( $post_id, $this->table_options_field_name );
985 964 if ( empty( $options ) ) {
986 965 return array();
987 966 }
988 - return (array) json_decode( $options, true );
967 + $options = (array) json_decode( $options, true );
968 +
969 + // Backward compatibility: Convert boolean "table_head" and "table_foot" options to integer.
970 + $options['table_head'] = absint( $options['table_head'] );
971 + $options['table_foot'] = absint( $options['table_foot'] );
972 +
973 + return $options;
989 974 }
990 975
991 976 /**
992 977 * Save the table visibility of a table (in a post meta field of the table's post).
@@ -992,15 +977,15 @@
992 977 * Save the table visibility of a table (in a post meta field of the table's post).
993 978 *
994 979 * @since 1.0.0
995 980 *
996 - * @param int $post_id Post ID.
997 - * @param array<string, array{rows: int[], columns: int[]}> $visibility Table visibility.
981 + * @param int $post_id Post ID.
982 + * @param array{rows: int[], columns: int[]} $visibility Table visibility.
998 983 * @return bool True on success, false on error.
999 984 */
1000 985 protected function _add_table_visibility( int $post_id, array $visibility ): bool {
1001 986 $visibility = wp_json_encode( $visibility, TABLEPRESS_JSON_OPTIONS );
1002 - return $this->model_post->add_meta_field( $post_id, $this->table_visibility_field_name, $visibility ); // @phpstan-ignore-line
987 + return $this->model_post->add_meta_field( $post_id, $this->table_visibility_field_name, $visibility ); // @phpstan-ignore argument.type
1003 988 }
1004 989
1005 990 /**
1006 991 * Update the table visibility of a table (in a post meta field in the table's post).
@@ -1006,15 +991,15 @@
1006 991 * Update the table visibility of a table (in a post meta field in the table's post).
1007 992 *
1008 993 * @since 1.0.0
1009 994 *
1010 - * @param int $post_id Post ID.
1011 - * @param array<string, array{rows: int[], columns: int[]}> $visibility Table visibility.
995 + * @param int $post_id Post ID.
996 + * @param array{rows: int[], columns: int[]} $visibility Table visibility.
1012 997 * @return bool True on success, false on error.
1013 998 */
1014 999 protected function _update_table_visibility( int $post_id, array $visibility ): bool {
1015 1000 $visibility = wp_json_encode( $visibility, TABLEPRESS_JSON_OPTIONS );
1016 - return $this->model_post->update_meta_field( $post_id, $this->table_visibility_field_name, $visibility ); // @phpstan-ignore-line
1001 + return $this->model_post->update_meta_field( $post_id, $this->table_visibility_field_name, $visibility ); // @phpstan-ignore argument.type
1017 1002 }
1018 1003
1019 1004 /**
1020 1005 * Get the table visibility of a table (from a post meta field of the table's post).
@@ -1021,14 +1006,17 @@
1021 1006 *
1022 1007 * @since 1.0.0
1023 1008 *
1024 1009 * @param int $post_id Post ID.
1025 - * @return array<string, array{rows: int[], columns: int[]}> Table visibility on success, empty array on error.
1010 + * @return array{rows: int[], columns: int[]} Table visibility on success, empty array on error.
1026 1011 */
1027 1012 protected function _get_table_visibility( int $post_id ): array {
1028 1013 $visibility = $this->model_post->get_meta_field( $post_id, $this->table_visibility_field_name );
1029 1014 if ( empty( $visibility ) ) {
1030 - return array();
1015 + return array(
1016 + 'rows' => array(),
1017 + 'columns' => array(),
1018 + );
1031 1019 }
1032 1020 return json_decode( $visibility, true );
1033 1021 }
1034 1022
@@ -1054,10 +1042,23 @@
1054 1042 // Get default Table with default Table Options.
1055 1043 $default_table = $this->get_table_template();
1056 1044
1057 1045 // Go through all tables (this loop now uses the WP cache).
1058 - foreach ( $table_post as $table_id => $post_id ) {
1046 + foreach ( $table_post as $post_id ) {
1059 1047 $table_options = $this->_get_table_options( $post_id );
1048 +
1049 + /**
1050 + * Filters the Table Options before they are merged with the default Table Options.
1051 + *
1052 + * @since 3.0.0
1053 + *
1054 + * @param array<string, mixed> $table_options Table Options.
1055 + * @param array<string, mixed> $default_table_options Default Table Options.
1056 + * @param bool $remove_old_options Whether old table options should be removed from the database.
1057 + * @param int $post_id Post ID of the table.
1058 + */
1059 + $table_options = apply_filters( 'tablepress_table_options_before_merge', $table_options, $default_table['options'], $remove_old_options, $post_id );
1060 +
1060 1061 if ( $remove_old_options ) {
1061 1062 // Remove old (i.e. no longer existing) Table Options.
1062 1063 $table_options = array_intersect_key( $table_options, $default_table['options'] );
1063 1064 }
@@ -1067,8 +1068,48 @@
1067 1068 }
1068 1069 }
1069 1070
1070 1071 /**
1072 + * Updates all tables' "Custom Commands" to use DataTables 2 variants instead of old DataTables 1.x CSS classes and parameters
1073 + *
1074 + * @since 3.0.0
1075 + */
1076 + public function update_custom_commands_datatables_tp30(): void {
1077 + $table_post = $this->tables->get( 'table_post' );
1078 + if ( empty( $table_post ) ) {
1079 + return;
1080 + }
1081 +
1082 + // Prime the meta cache with the table options of all tables.
1083 + update_meta_cache( 'post', array_values( $table_post ) );
1084 +
1085 + foreach ( $table_post as $table_id => $post_id ) {
1086 + $table_options = $this->_get_table_options( $post_id );
1087 +
1088 + // Fix tables where the "Custom Commands" entry is missing entirely.
1089 + if ( ! isset( $table_options['datatables_custom_commands'] ) ) {
1090 + $table_options['datatables_custom_commands'] = '';
1091 + $this->_update_table_options( $post_id, $table_options );
1092 + continue;
1093 + }
1094 +
1095 + // Nothing to do if there are no "Custom Commands".
1096 + if ( '' === $table_options['datatables_custom_commands'] ) {
1097 + continue;
1098 + }
1099 + // Run search/replace.
1100 + $old_custom_commands = $table_options['datatables_custom_commands'];
1101 + $table_options['datatables_custom_commands'] = TablePress::convert_datatables_api_data( $table_options['datatables_custom_commands'] );
1102 + // No need to save (which runs a DB query) if nothing was replaced in the "Custom Commands".
1103 + if ( $old_custom_commands === $table_options['datatables_custom_commands'] ) {
1104 + continue;
1105 + }
1106 +
1107 + $this->_update_table_options( $post_id, $table_options );
1108 + }
1109 + }
1110 +
1111 + /**
1071 1112 * Invalidate all table output caches, e.g. after a plugin update.
1072 1113 *
1073 1114 * @since 1.0.0
1074 1115 */
@@ -1078,8 +1119,9 @@
1078 1119 return;
1079 1120 }
1080 1121
1081 1122 foreach ( $table_post as $table_id => $post_id ) {
1123 + $table_id = (string) $table_id; // Ensure that the table ID is a string, as it comes from an array key where numeric strings are converted to integers.
1082 1124 $this->invalidate_table_output_cache( $table_id );
1083 1125 }
1084 1126 }
1085 1127
@@ -1197,18 +1239,23 @@
1197 1239 }
1198 1240
1199 1241 // Pretend that there is a `_tablepress_export_table_id` post meta field with the list of table IDs.
1200 1242 $key = '_tablepress_export_table_id';
1201 - $value = wxr_cdata( implode( ',', $table_ids ) ); // @phpstan-ignore-line
1202 1243
1244 + /**
1245 + * Load WP export functions.
1246 + */
1247 + require_once ABSPATH . 'wp-admin/includes/export.php'; // @phpstan-ignore requireOnce.fileNotFound (This is a WordPress core file that always exists.)
1248 + $value = wxr_cdata( implode( ',', $table_ids ) );
1249 +
1203 1250 // Hijack the filter and print extra XML code for our faked post meta field.
1204 1251 // phpcs:disable WordPress.Security.EscapeOutput.HeredocOutputNotEscaped
1205 1252 echo <<<WXR
1206 - <wp:postmeta>
1207 - <wp:meta_key>{$key}</wp:meta_key>
1208 - <wp:meta_value>{$value}</wp:meta_value>
1209 - </wp:postmeta>\n
1210 -WXR;
1253 + <wp:postmeta>
1254 + <wp:meta_key>{$key}</wp:meta_key>
1255 + <wp:meta_value>{$value}</wp:meta_value>
1256 + </wp:postmeta>\n
1257 + WXR;
1211 1258 // phpcs:enable
1212 1259
1213 1260 return $skip;
1214 1261 }