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 | models/model-table.php +143 -77 2.3.2 → 3.4 View file →
@@ -7,8 +7,10 @@
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 /**
@@ -24,27 +26,24 @@
24 26 /**
25 27 * Instance of the Post Type Model.
26 28 *
27 29 * @since 1.0.0
28 - * @var TablePress_Post_Model
29 30 */
30 - protected $model_post;
31 + protected \TablePress_Post_Model $model_post;
31 32
32 33 /**
33 34 * Name of the Post Meta Field for table options.
34 35 *
35 36 * @since 1.0.0
36 - * @var string
37 37 */
38 - protected $table_options_field_name = '_tablepress_table_options';
38 + protected string $table_options_field_name = '_tablepress_table_options';
39 39
40 40 /**
41 41 * Name of the Post Meta Field for table visibility.
42 42 *
43 43 * @since 1.0.0
44 - * @var string
45 44 */
46 - protected $table_visibility_field_name = '_tablepress_table_visibility';
45 + protected string $table_visibility_field_name = '_tablepress_table_visibility';
47 46
48 47 /**
49 48 * Default set of tables.
50 49 *
@@ -53,9 +52,9 @@
53 52 * @type int $last_id Last table ID that was given to a new table.
54 53 * @type array<string, int> $table_post Connections between table ID and post ID (key: table ID, value: post ID).
55 54 * }
56 55 */
57 - protected $default_tables = array(
56 + protected array $default_tables = array(
58 57 'last_id' => 0,
59 58 'table_post' => array(),
60 59 );
61 60
@@ -62,11 +61,10 @@
62 61 /**
63 62 * Instance of WP_Option class for the list of tables.
64 63 *
65 64 * @since 1.0.0
66 - * @var TablePress_WP_Option
67 65 */
68 - protected $tables;
66 + protected \TablePress_WP_Option $tables;
69 67
70 68 /**
71 69 * Init the Table model by instantiating a Post model and loading the list of tables option.
72 70 *
@@ -114,11 +112,8 @@
114 112 * @param int $post_id Post ID of an existing table, or -1 for a new table.
115 113 * @return array<string, mixed> Post.
116 114 */
117 115 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 116 // Sanitize each cell, table name, and table description, if the user is not allowed to work with unfiltered HTML.
122 117 if ( ! current_user_can( 'unfiltered_html' ) ) {
123 118 $table = $this->sanitize( $table );
124 119 }
@@ -153,9 +148,9 @@
153 148 $table = array(
154 149 'id' => $table_id,
155 150 'name' => $post->post_title,
156 151 'description' => $post->post_excerpt,
157 - 'author' => $post->post_author,
152 + 'author' => (int) $post->post_author,
158 153 // 'created' => $post->post_date,
159 154 'last_modified' => $post->post_modified,
160 155 );
161 156
@@ -283,47 +278,8 @@
283 278 return $table;
284 279 }
285 280
286 281 /**
287 - * Filter/modify the content of table cells and other fields, e.g. for security hardening.
288 - *
289 - * This is similar to the `sanitize()` method, but executed for all users.
290 - * 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.
291 - * Since 1.13.0, and on WP 5.6, only `rel="noopener"` is added. See https://core.trac.wordpress.org/ticket/49558.
292 - *
293 - * @since 1.10.0
294 - *
295 - * @param array<string, mixed> $table Table.
296 - * @return array<string, mixed> Filtered/modified table.
297 - */
298 - public function filter_content( array $table ): array {
299 - /**
300 - * Filters whether the contents of table cells and fields should be filtered/modified.
301 - *
302 - * @since 1.10.0
303 - *
304 - * @param bool $filter Whether to filter the content of table cells and other fields. Default true.
305 - */
306 - if ( ! apply_filters( 'tablepress_filter_table_cell_content', true ) ) {
307 - return $table;
308 - }
309 -
310 - // Filter the table name and description.
311 - $fields = array( 'name', 'description' );
312 - foreach ( $fields as $field ) {
313 - $table[ $field ] = wp_targeted_link_rel( $table[ $field ] );
314 - }
315 -
316 - foreach ( $table['data'] as $row_idx => $row ) {
317 - foreach ( $row as $column_idx => $cell_content ) {
318 - $table['data'][ $row_idx ][ $column_idx ] = wp_targeted_link_rel( $cell_content );
319 - }
320 - }
321 -
322 - return $table;
323 - }
324 -
325 - /**
326 282 * Save a table.
327 283 *
328 284 * @since 1.0.0
329 285 *
@@ -334,8 +290,17 @@
334 290 if ( empty( $table['id'] ) ) {
335 291 return new WP_Error( 'table_save_empty_table_id' );
336 292 }
337 293
294 + /**
295 + * Fires before a table is saved.
296 + *
297 + * @since 3.1.0
298 + *
299 + * @param string $table_id ID of the table to be saved.
300 + */
301 + do_action( 'tablepress_event_pre_save_table', $table['id'] );
302 +
338 303 $post_id = $this->_get_post_id( $table['id'] );
339 304 if ( false === $post_id ) {
340 305 return new WP_Error( 'table_save_no_post_id_for_table_id', '', $table['id'] );
341 306 }
@@ -372,9 +337,9 @@
372 337 * Fires after a table has been saved.
373 338 *
374 339 * @since 1.5.0
375 340 *
376 - * @param string $table_id ID of the added table.
341 + * @param string $table_id ID of the saved table.
377 342 */
378 343 do_action( 'tablepress_event_saved_table', $table['id'] );
379 344
380 345 return $table['id'];
@@ -490,10 +455,21 @@
490 455 if ( ! $this->table_exists( $table_id ) ) {
491 456 return new WP_Error( 'table_delete_table_does_not_exist', '', $table_id );
492 457 }
493 458
459 + /**
460 + * Fires before a table is deleted.
461 + *
462 + * @since 3.1.0
463 + *
464 + * @param string $table_id ID of the table to be deleted.
465 + */
466 + do_action( 'tablepress_event_pre_delete_table', $table_id );
467 +
494 468 $post_id = $this->_get_post_id( $table_id ); // No ! false check necessary, as this is covered by table_exists() check above.
495 - $deleted = $this->model_post->delete( $post_id ); // Post Meta fields will be deleted automatically by that function. // @phpstan-ignore-line .
469 +
470 + // @phpstan-ignore argument.type
471 + $deleted = $this->model_post->delete( $post_id ); // Post Meta fields will be deleted automatically by that function.
496 472 if ( false === $deleted ) {
497 473 return new WP_Error( 'table_delete_post_could_not_be_deleted', '', $post_id );
498 474 }
499 475
@@ -652,15 +628,22 @@
652 628 foreach ( $cache_flush_hooks as $cache_flush_hook ) {
653 629 do_action( $cache_flush_hook );
654 630 }
655 631
632 + // Cloudflare APO.
633 + if ( class_exists( '\Cloudflare\APO\WordPress\Hooks' ) ) {
634 + $_cloudflare = new \Cloudflare\APO\WordPress\Hooks();
635 + if ( is_callable( array( $_cloudflare, 'purgeCacheEverything' ) ) ) {
636 + $_cloudflare->purgeCacheEverything();
637 + }
638 + }
656 639 // Kinsta.
657 640 if ( isset( $GLOBALS['kinsta_cache'] ) && ! empty( $GLOBALS['kinsta_cache']->kinsta_cache_purge ) && is_callable( array( $GLOBALS['kinsta_cache']->kinsta_cache_purge, 'purge_complete_caches' ) ) ) {
658 - $GLOBALS['kinsta_cache']->kinsta_cache_purge->purge_complete_caches(); // @phpstan-ignore-line
641 + $GLOBALS['kinsta_cache']->kinsta_cache_purge->purge_complete_caches(); // @phpstan-ignore method.nonObject
659 642 }
660 643 // LiteSpeed Cache.
661 644 if ( is_callable( array( 'LiteSpeed_Cache_Tags', 'add_purge_tag' ) ) ) {
662 - LiteSpeed_Cache_Tags::add_purge_tag( '*' ); // @phpstan-ignore-line
645 + LiteSpeed_Cache_Tags::add_purge_tag( '*' ); // @phpstan-ignore class.notFound
663 646 }
664 647 // Pagely.
665 648 if ( class_exists( 'PagelyCachePurge' ) ) {
666 649 $_pagely = new PagelyCachePurge();
@@ -667,25 +650,25 @@
667 650 if ( is_callable( array( $_pagely, 'purgeAll' ) ) ) {
668 651 $_pagely->purgeAll();
669 652 }
670 653 }
671 - // Pressidum.
654 + // Pressidium.
672 655 if ( is_callable( array( 'Ninukis_Plugin', 'get_instance' ) ) ) {
673 - $_pressidum = Ninukis_Plugin::get_instance(); // @phpstan-ignore-line
656 + $_pressidum = Ninukis_Plugin::get_instance(); // @phpstan-ignore class.notFound
674 657 if ( is_callable( array( $_pressidum, 'purgeAllCaches' ) ) ) {
675 - $_pressidum->purgeAllCaches(); // @phpstan-ignore-line
658 + $_pressidum->purgeAllCaches(); // @phpstan-ignore method.nonObject
676 659 }
677 660 }
678 661 // Savvii.
679 662 if ( defined( '\Savvii\CacheFlusherPlugin::NAME_DOMAINFLUSH_NOW' ) ) {
680 - $_savvii = new \Savvii\CacheFlusherPlugin(); // @phpstan-ignore-line
663 + $_savvii = new \Savvii\CacheFlusherPlugin(); // @phpstan-ignore class.notFound
681 664 if ( is_callable( array( $_savvii, 'domainflush' ) ) ) {
682 - $_savvii->domainflush(); // @phpstan-ignore-line
665 + $_savvii->domainflush(); // @phpstan-ignore class.notFound
683 666 }
684 667 }
685 668 // WP Fastest Cache.
686 669 if ( isset( $GLOBALS['wp_fastest_cache'] ) && is_callable( array( $GLOBALS['wp_fastest_cache'], 'deleteCache' ) ) ) {
687 - $GLOBALS['wp_fastest_cache']->deleteCache( true ); // @phpstan-ignore-line
670 + $GLOBALS['wp_fastest_cache']->deleteCache( true ); // @phpstan-ignore method.nonObject
688 671 }
689 672 // WP-Optimize.
690 673 if ( function_exists( 'WP_Optimize' ) ) {
691 674 WP_Optimize()->get_page_cache()->purge();
@@ -813,10 +796,10 @@
813 796 'last_modified' => wp_date( 'Y-m-d H:i:s' ),
814 797 'author' => get_current_user_id(),
815 798 'options' => array(
816 799 'last_editor' => get_current_user_id(),
817 - 'table_head' => true,
818 - 'table_foot' => false,
800 + 'table_head' => 1,
801 + 'table_foot' => 0,
819 802 'alternating_row_colors' => true,
820 803 'row_hover' => true,
821 804 'print_name' => false,
822 805 'print_name_position' => 'above',
@@ -834,9 +817,9 @@
834 817 'datatables_scrollx' => false,
835 818 'datatables_custom_commands' => '',
836 819 ),
837 820 'visibility' => array(
838 - 'rows' => array( 1 ), // One visbile row.
821 + 'rows' => array( 1 ), // One visible row.
839 822 'columns' => array( 1 ), // One visible column.
840 823 ),
841 824 );
842 825 /**
@@ -912,9 +895,9 @@
912 895 array_walk_recursive(
913 896 $table['data'],
914 897 static function ( /* string|int|float|bool|null */ &$cell_content, int $col_idx ): void {
915 898 $cell_content = (string) $cell_content;
916 - }
899 + },
917 900 );
918 901 // Table Options.
919 902 if ( isset( $new_table['options'] ) ) { // Options are for example not set for newly added tables.
920 903 // Specials check for certain options.
@@ -929,8 +912,17 @@
929 912 if ( $new_table['options']['datatables_paginate_entries'] < 1 ) {
930 913 $new_table['options']['datatables_paginate_entries'] = 10; // Default value.
931 914 }
932 915 }
916 +
917 + // Backward compatibility: Convert boolean or numeric string "table_head" and "table_foot" options to integer.
918 + if ( isset( $new_table['options']['table_head'] ) ) {
919 + $new_table['options']['table_head'] = absint( $new_table['options']['table_head'] );
920 + }
921 + if ( isset( $new_table['options']['table_foot'] ) ) {
922 + $new_table['options']['table_foot'] = absint( $new_table['options']['table_foot'] );
923 + }
924 +
933 925 // Merge new options.
934 926 $default_table = $this->get_table_template();
935 927 $table['options'] = array_intersect_key( $table['options'], $default_table['options'] );
936 928 $new_table['options'] = array_intersect_key( $new_table['options'], $default_table['options'] );
@@ -944,8 +936,18 @@
944 936 // $table['created'] = wp_date( 'Y-m-d H:i:s' ); // We don't want this, as it would override the original datetime.
945 937 $table['last_modified'] = wp_date( 'Y-m-d H:i:s' );
946 938 $table['options']['last_editor'] = get_current_user_id();
947 939
940 + // Prevent issues if the "Custom Commands" field is not set, e.g. when non-admins have previously edited the table.
941 + if ( ! isset( $table['options']['datatables_custom_commands'] ) ) {
942 + $table['options']['datatables_custom_commands'] = '';
943 + }
944 +
945 + // Convert CSS classes and some DataTables 1.x parameters to the DataTables 2 variants.
946 + if ( '' !== $table['options']['datatables_custom_commands'] ) {
947 + $table['options']['datatables_custom_commands'] = TablePress::convert_datatables_api_data( $table['options']['datatables_custom_commands'] );
948 + }
949 +
948 950 return $table;
949 951 }
950 952
951 953 /**
@@ -958,9 +960,9 @@
958 960 * @return bool True on success, false on error.
959 961 */
960 962 protected function _add_table_options( int $post_id, array $options ): bool {
961 963 $options = wp_json_encode( $options, TABLEPRESS_JSON_OPTIONS );
962 - return $this->model_post->add_meta_field( $post_id, $this->table_options_field_name, $options ); // @phpstan-ignore-line
964 + return $this->model_post->add_meta_field( $post_id, $this->table_options_field_name, $options ); // @phpstan-ignore argument.type
963 965 }
964 966
965 967 /**
966 968 * Update the table options of a table (in a post meta field in the table's post).
@@ -972,9 +974,9 @@
972 974 * @return bool True on success, false on error.
973 975 */
974 976 protected function _update_table_options( int $post_id, array $options ): bool {
975 977 $options = wp_json_encode( $options, TABLEPRESS_JSON_OPTIONS );
976 - return $this->model_post->update_meta_field( $post_id, $this->table_options_field_name, $options ); // @phpstan-ignore-line
978 + return $this->model_post->update_meta_field( $post_id, $this->table_options_field_name, $options ); // @phpstan-ignore argument.type
977 979 }
978 980
979 981 /**
980 982 * Get the table options of a table (from a post meta field of the table's post).
@@ -988,9 +990,15 @@
988 990 $options = $this->model_post->get_meta_field( $post_id, $this->table_options_field_name );
989 991 if ( empty( $options ) ) {
990 992 return array();
991 993 }
992 - return (array) json_decode( $options, true );
994 + $options = (array) json_decode( $options, true );
995 +
996 + // Backward compatibility: Convert boolean "table_head" and "table_foot" options to integer.
997 + $options['table_head'] = absint( $options['table_head'] );
998 + $options['table_foot'] = absint( $options['table_foot'] );
999 +
1000 + return $options;
993 1001 }
994 1002
995 1003 /**
996 1004 * Save the table visibility of a table (in a post meta field of the table's post).
@@ -1002,9 +1010,9 @@
1002 1010 * @return bool True on success, false on error.
1003 1011 */
1004 1012 protected function _add_table_visibility( int $post_id, array $visibility ): bool {
1005 1013 $visibility = wp_json_encode( $visibility, TABLEPRESS_JSON_OPTIONS );
1006 - return $this->model_post->add_meta_field( $post_id, $this->table_visibility_field_name, $visibility ); // @phpstan-ignore-line
1014 + return $this->model_post->add_meta_field( $post_id, $this->table_visibility_field_name, $visibility ); // @phpstan-ignore argument.type
1007 1015 }
1008 1016
1009 1017 /**
1010 1018 * Update the table visibility of a table (in a post meta field in the table's post).
@@ -1016,9 +1024,9 @@
1016 1024 * @return bool True on success, false on error.
1017 1025 */
1018 1026 protected function _update_table_visibility( int $post_id, array $visibility ): bool {
1019 1027 $visibility = wp_json_encode( $visibility, TABLEPRESS_JSON_OPTIONS );
1020 - return $this->model_post->update_meta_field( $post_id, $this->table_visibility_field_name, $visibility ); // @phpstan-ignore-line
1028 + return $this->model_post->update_meta_field( $post_id, $this->table_visibility_field_name, $visibility ); // @phpstan-ignore argument.type
1021 1029 }
1022 1030
1023 1031 /**
1024 1032 * Get the table visibility of a table (from a post meta field of the table's post).
@@ -1063,8 +1071,21 @@
1063 1071
1064 1072 // Go through all tables (this loop now uses the WP cache).
1065 1073 foreach ( $table_post as $post_id ) {
1066 1074 $table_options = $this->_get_table_options( $post_id );
1075 +
1076 + /**
1077 + * Filters the Table Options before they are merged with the default Table Options.
1078 + *
1079 + * @since 3.0.0
1080 + *
1081 + * @param array<string, mixed> $table_options Table Options.
1082 + * @param array<string, mixed> $default_table_options Default Table Options.
1083 + * @param bool $remove_old_options Whether old table options should be removed from the database.
1084 + * @param int $post_id Post ID of the table.
1085 + */
1086 + $table_options = apply_filters( 'tablepress_table_options_before_merge', $table_options, $default_table['options'], $remove_old_options, $post_id );
1087 +
1067 1088 if ( $remove_old_options ) {
1068 1089 // Remove old (i.e. no longer existing) Table Options.
1069 1090 $table_options = array_intersect_key( $table_options, $default_table['options'] );
1070 1091 }
@@ -1074,8 +1095,48 @@
1074 1095 }
1075 1096 }
1076 1097
1077 1098 /**
1099 + * Updates all tables' "Custom Commands" to use DataTables 2 variants instead of old DataTables 1.x CSS classes and parameters
1100 + *
1101 + * @since 3.0.0
1102 + */
1103 + public function update_custom_commands_datatables_tp30(): void {
1104 + $table_post = $this->tables->get( 'table_post' );
1105 + if ( empty( $table_post ) ) {
1106 + return;
1107 + }
1108 +
1109 + // Prime the meta cache with the table options of all tables.
1110 + update_meta_cache( 'post', array_values( $table_post ) );
1111 +
1112 + foreach ( $table_post as $table_id => $post_id ) {
1113 + $table_options = $this->_get_table_options( $post_id );
1114 +
1115 + // Fix tables where the "Custom Commands" entry is missing entirely.
1116 + if ( ! isset( $table_options['datatables_custom_commands'] ) ) {
1117 + $table_options['datatables_custom_commands'] = '';
1118 + $this->_update_table_options( $post_id, $table_options );
1119 + continue;
1120 + }
1121 +
1122 + // Nothing to do if there are no "Custom Commands".
1123 + if ( '' === $table_options['datatables_custom_commands'] ) {
1124 + continue;
1125 + }
1126 + // Run search/replace.
1127 + $old_custom_commands = $table_options['datatables_custom_commands'];
1128 + $table_options['datatables_custom_commands'] = TablePress::convert_datatables_api_data( $table_options['datatables_custom_commands'] );
1129 + // No need to save (which runs a DB query) if nothing was replaced in the "Custom Commands".
1130 + if ( $old_custom_commands === $table_options['datatables_custom_commands'] ) {
1131 + continue;
1132 + }
1133 +
1134 + $this->_update_table_options( $post_id, $table_options );
1135 + }
1136 + }
1137 +
1138 + /**
1078 1139 * Invalidate all table output caches, e.g. after a plugin update.
1079 1140 *
1080 1141 * @since 1.0.0
1081 1142 */
@@ -1205,18 +1266,23 @@
1205 1266 }
1206 1267
1207 1268 // Pretend that there is a `_tablepress_export_table_id` post meta field with the list of table IDs.
1208 1269 $key = '_tablepress_export_table_id';
1209 - $value = wxr_cdata( implode( ',', $table_ids ) ); // @phpstan-ignore-line
1210 1270
1271 + /**
1272 + * Load WP export functions.
1273 + */
1274 + require_once ABSPATH . 'wp-admin/includes/export.php';
1275 + $value = wxr_cdata( implode( ',', $table_ids ) );
1276 +
1211 1277 // Hijack the filter and print extra XML code for our faked post meta field.
1212 1278 // phpcs:disable WordPress.Security.EscapeOutput.HeredocOutputNotEscaped
1213 1279 echo <<<WXR
1214 - <wp:postmeta>
1215 - <wp:meta_key>{$key}</wp:meta_key>
1216 - <wp:meta_value>{$value}</wp:meta_value>
1217 - </wp:postmeta>\n
1218 -WXR;
1280 + <wp:postmeta>
1281 + <wp:meta_key>{$key}</wp:meta_key>
1282 + <wp:meta_value>{$value}</wp:meta_value>
1283 + </wp:postmeta>\n
1284 + WXR;
1219 1285 // phpcs:enable
1220 1286
1221 1287 return $skip;
1222 1288 }