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 | models/model-table.php +250 -181 2.0.43.2 View file →
@@ -24,38 +24,35 @@
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 *
51 48 * @since 1.0.0
52 - * @var array $args {
53 - * @type int $last_id Last table ID that was given to a new table.
54 - * @type array $table_post Connections between table ID and post ID (key: table ID, value: post ID).
49 + * @var array{last_id: int, table_post: array<string, int>} {
50 + * @type int $last_id Last table ID that was given to a new table.
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 *
@@ -87,11 +83,11 @@
87 83 * Get the tables option, which holds the connection between table ID and post ID.
88 84 *
89 85 * @since 1.0.0
90 86 *
91 - * @return array Current set of tables.
87 + * @return mixed[] Current set of tables.
92 88 */
93 - public function _debug_get_tables() {
89 + public function _debug_get_tables(): array {
94 90 return $this->tables->get();
95 91 }
96 92
97 93 /**
@@ -98,11 +94,11 @@
98 94 * Update the tables option, which holds the connection between table ID and post ID.
99 95 *
100 96 * @since 1.0.0
101 97 *
102 - * @param array $tables New set of tables.
98 + * @param mixed[] $tables New set of tables.
103 99 */
104 - public function _debug_update_tables( array $tables ) {
100 + public function _debug_update_tables( array $tables ): void {
105 101 $this->tables->update( $tables );
106 102 }
107 103
108 104 /**
@@ -109,16 +105,13 @@
109 105 * Convert a table to a post, which can be stored in the database.
110 106 *
111 107 * @since 1.0.0
112 108 *
113 - * @param array $table Table.
114 - * @param int $post_id Post ID of an existing table, or -1 for a new table.
115 - * @return array Post.
109 + * @param array<string, mixed> $table Table.
110 + * @param int $post_id Post ID of an existing table, or -1 for a new table.
111 + * @return array<string, mixed> Post.
116 112 */
117 - protected function _table_to_post( array $table, $post_id ) {
118 - // Run filters on content in each cell and other fields.
119 - $table = $this->filter_content( $table );
120 -
113 + protected function _table_to_post( array $table, int $post_id ): array {
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 }
@@ -146,11 +139,11 @@
146 139 *
147 140 * @param WP_Post $post Post.
148 141 * @param string $table_id Table ID.
149 142 * @param bool $load_data Whether the table data shall be loaded.
150 - * @return array Table.
143 + * @return array<string, mixed> Table.
151 144 */
152 - protected function _post_to_table( $post, $table_id, $load_data ) {
145 + protected function _post_to_table( WP_Post $post, string $table_id, bool $load_data ): array {
153 146 $table = array(
154 147 'id' => $table_id,
155 148 'name' => $post->post_title,
156 149 'description' => $post->post_excerpt,
@@ -186,11 +179,11 @@
186 179 *
187 180 * @param string $table_id Table ID.
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 - * @return array|WP_Error Table as an array on success, WP_Error on error.
183 + * @return array<string, mixed>|WP_Error Table as an array on success, WP_Error on error.
191 184 */
192 - public function load( $table_id, $load_data = true, $load_options_visibility = true ) {
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
@@ -218,11 +211,11 @@
218 211 * @since 1.0.0
219 212 *
220 213 * @param bool $prime_meta_cache Optional. Whether the prime the post meta cache when loading the posts.
221 214 * @param bool $run_filter Optional. Whether to run a filter on the list of table IDs.
222 - * @return array Array of table IDs.
215 + * @return string[] Array of table IDs.
223 216 */
224 - public function load_all( $prime_meta_cache = true, $run_filter = true ) {
217 + public function load_all( bool $prime_meta_cache = true, bool $run_filter = true ): array {
225 218 $table_post = $this->tables->get( 'table_post' );
226 219 if ( empty( $table_post ) ) {
227 220 return array();
228 221 }
@@ -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 }
@@ -247,9 +242,9 @@
247 242 * Filters all table IDs that are loaded.
248 243 *
249 244 * @since 1.4.0
250 245 *
251 - * @param array $table_ids The table IDs that are loaded.
246 + * @param string[] $table_ids The table IDs that are loaded.
252 247 */
253 248 $table_ids = apply_filters( 'tablepress_load_all_tables', $table_ids );
254 249 }
255 250
@@ -260,12 +255,12 @@
260 255 * Sanitize the table to remove undesired HTML code using KSES.
261 256 *
262 257 * @since 1.8.0
263 258 *
264 - * @param array $table Table.
265 - * @return array Sanitized table.
259 + * @param array<string, mixed> $table Table.
260 + * @return array<string, mixed> Sanitized table.
266 261 */
267 - public function sanitize( array $table ) {
262 + public function sanitize( array $table ): array {
268 263 // Sanitize the table name and description.
269 264 $fields = array( 'name', 'description' );
270 265 foreach ( $fields as $field ) {
271 266 $table[ $field ] = wp_kses_post( $table[ $field ] );
@@ -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,59 +276,29 @@
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 $table Table.
294 - * @return array Filtered/modified table.
295 - */
296 - public function filter_content( array $table ) {
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 *
328 - * @param array $table Table (needs to have $table['id']!).
284 + * @param array<string, mixed> $table Table (needs to have $table['id']!).
329 285 * @return string|WP_Error WP_Error on error, string table ID on success.
330 286 */
331 - public function save( array $table ) {
287 + public function save( array $table ) /* : string|WP_Error */ {
332 288 if ( empty( $table['id'] ) ) {
333 289 return new WP_Error( 'table_save_empty_table_id' );
334 290 }
335 291
292 + /**
293 + * Fires before a table is saved.
294 + *
295 + * @since 3.1.0
296 + *
297 + * @param string $table_id ID of the table to be saved.
298 + */
299 + do_action( 'tablepress_event_pre_save_table', $table['id'] );
300 +
336 301 $post_id = $this->_get_post_id( $table['id'] );
337 302 if ( false === $post_id ) {
338 303 return new WP_Error( 'table_save_no_post_id_for_table_id', '', $table['id'] );
339 304 }
@@ -340,11 +305,11 @@
340 305
341 306 $post = $this->_table_to_post( $table, $post_id );
342 307 $new_post_id = $this->model_post->update( $post );
343 308 if ( is_wp_error( $new_post_id ) ) {
344 - // Add an error code to the existing WP_Error.
345 - $new_post_id->add( 'table_save_post_update', '', $post_id );
346 - return $new_post_id;
309 + $error = new WP_Error( 'table_save_post_update', '', $post_id );
310 + $error->merge_from( $new_post_id );
311 + return $error;
347 312 }
348 313 if ( $post_id !== $new_post_id ) {
349 314 return new WP_Error( 'table_save_new_post_id_does_not_match', '', $new_post_id );
350 315 }
@@ -370,9 +335,9 @@
370 335 * Fires after a table has been saved.
371 336 *
372 337 * @since 1.5.0
373 338 *
374 - * @param string $table_id ID of the added table.
339 + * @param string $table_id ID of the saved table.
375 340 */
376 341 do_action( 'tablepress_event_saved_table', $table['id'] );
377 342
378 343 return $table['id'];
@@ -382,20 +347,20 @@
382 347 * Add a new table.
383 348 *
384 349 * @since 1.0.0
385 350 *
386 - * @param array $table Table ($table['id'] is not necessary).
387 - * @param string $copy_or_add Optional. 'copy' if the table is copied, 'add' if it is a new table. Default 'add'.
351 + * @param array<string, mixed> $table Table ($table['id'] is not necessary).
352 + * @param string $copy_or_add Optional. 'copy' if the table is copied, 'add' if it is a new table. Default 'add'.
388 353 * @return string|WP_Error WP_Error on error, string table ID of the new table on success.
389 354 */
390 - public function add( array $table, $copy_or_add = 'add' ) {
355 + public function add( array $table, string $copy_or_add = 'add' ) /* : string|WP_Error */ {
391 356 $post_id = -1; // To insert table.
392 357 $post = $this->_table_to_post( $table, $post_id );
393 358 $new_post_id = $this->model_post->insert( $post );
394 359 if ( is_wp_error( $new_post_id ) ) {
395 - // Add an error code to the existing WP_Error.
396 - $new_post_id->add( 'table_add_post_insert', '' );
397 - return $new_post_id;
360 + $error = new WP_Error( 'table_add_post_insert', '' );
361 + $error->merge_from( $new_post_id );
362 + return $error;
398 363 }
399 364
400 365 $options_saved = $this->_add_table_options( $new_post_id, $table['options'] );
401 366 if ( ! $options_saved ) {
@@ -432,14 +397,14 @@
432 397 *
433 398 * @param string $table_id ID of the table to be copied.
434 399 * @return string|WP_Error WP_Error on error, string table ID of the new table on success.
435 400 */
436 - public function copy( $table_id ) {
401 + public function copy( string $table_id ) /* : string|WP_Error */ {
437 402 $table = $this->load( $table_id, true, true );
438 403 if ( is_wp_error( $table ) ) {
439 - // Add an error code to the existing WP_Error.
440 - $table->add( 'table_copy_table_load', '', $table_id );
441 - return $table;
404 + $error = new WP_Error( 'table_copy_table_load', '', $table_id );
405 + $error->merge_from( $table );
406 + return $error;
442 407 }
443 408
444 409 // Adjust name of copied table.
445 410 if ( '' === trim( $table['name'] ) ) {
@@ -449,19 +414,19 @@
449 414
450 415 // Merge this data into an empty table template.
451 416 $table = $this->prepare_table( $this->get_table_template(), $table, false );
452 417 if ( is_wp_error( $table ) ) {
453 - // Add an error code to the existing WP_Error.
454 - $table->add( 'table_copy_table_prepare', '', $table_id );
455 - return $table;
418 + $error = new WP_Error( 'table_copy_table_prepare', '', $table_id );
419 + $error->merge_from( $table );
420 + return $error;
456 421 }
457 422
458 423 // Add the copied table.
459 424 $new_table_id = $this->add( $table, 'copy' );
460 425 if ( is_wp_error( $new_table_id ) ) {
461 - // Add an error code to the existing WP_Error.
462 - $new_table_id->add( 'table_copy_table_add', '', $table_id );
463 - return $new_table_id;
426 + $error = new WP_Error( 'table_copy_table_add', '', $table_id );
427 + $error->merge_from( $new_table_id );
428 + return $error;
464 429 }
465 430
466 431 /**
467 432 * Fires after an existing table has been copied.
@@ -483,14 +448,25 @@
483 448 *
484 449 * @param string $table_id ID of the table to be deleted.
485 450 * @return bool|WP_Error WP_Error on error, true on success.
486 451 */
487 - public function delete( $table_id ) {
452 + public function delete( string $table_id ) /* : true|WP_Error */ {
488 453 if ( ! $this->table_exists( $table_id ) ) {
489 454 return new WP_Error( 'table_delete_table_does_not_exist', '', $table_id );
490 455 }
491 456
457 + /**
458 + * Fires before a table is deleted.
459 + *
460 + * @since 3.1.0
461 + *
462 + * @param string $table_id ID of the table to be deleted.
463 + */
464 + do_action( 'tablepress_event_pre_delete_table', $table_id );
465 +
492 466 $post_id = $this->_get_post_id( $table_id ); // No ! false check necessary, as this is covered by table_exists() check above.
467 +
468 + // @phpstan-ignore argument.type
493 469 $deleted = $this->model_post->delete( $post_id ); // Post Meta fields will be deleted automatically by that function.
494 470 if ( false === $deleted ) {
495 471 return new WP_Error( 'table_delete_post_could_not_be_deleted', '', $post_id );
496 472 }
@@ -519,9 +495,9 @@
519 495 * Delete all tables.
520 496 *
521 497 * @since 1.0.0
522 498 */
523 - public function delete_all() {
499 + public function delete_all(): void {
524 500 $tables = $this->tables->get();
525 501 if ( empty( $tables['table_post'] ) ) {
526 502 return;
527 503 }
@@ -526,11 +502,13 @@
526 502 return;
527 503 }
528 504
529 505 foreach ( $tables['table_post'] as $table_id => $post_id ) {
530 - $table_id = (string) $table_id;
506 + $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.
507 +
531 508 $this->model_post->delete( $post_id ); // Post Meta fields will be deleted automatically by that function.
532 509 unset( $tables['table_post'][ $table_id ] );
510 +
533 511 // Invalidate table output caches that belong to this table.
534 512 $this->invalidate_table_output_cache( $table_id );
535 513 }
536 514
@@ -553,9 +531,9 @@
553 531 *
554 532 * @param string $table_id Table ID.
555 533 * @return bool Whether the table ID exists.
556 534 */
557 - public function table_exists( $table_id ) {
535 + public function table_exists( string $table_id ): bool {
558 536 $table_post = $this->tables->get( 'table_post' );
559 537 return isset( $table_post[ $table_id ] );
560 538 }
561 539
@@ -564,11 +542,11 @@
564 542 *
565 543 * @since 1.0.0
566 544 *
567 545 * @param bool $single_value Optional. Whether to return just the number of tables from the list, or also count in the database.
568 - * @return int|array Number of Tables (if $single_value), or array of Numbers from list/DB (if ! $single_value).
546 + * @return int|array{list: int, db: int} Number of Tables (if $single_value), or array of Numbers from list/DB (if ! $single_value).
569 547 */
570 - public function count_tables( $single_value = true ) {
548 + public function count_tables( bool $single_value = true ) /* : int|array */ {
571 549 $count_list = count( $this->tables->get( 'table_post' ) );
572 550 if ( $single_value ) {
573 551 return $count_list;
574 552 }
@@ -587,9 +565,9 @@
587 565 * @since 1.8.0 Renamed from _invalidate_table_output_cache to invalidate_table_output_cache and made public.
588 566 *
589 567 * @param string $table_id Table ID.
590 568 */
591 - public function invalidate_table_output_cache( $table_id ) {
569 + public function invalidate_table_output_cache( string $table_id ): void {
592 570 $caches_list_transient_name = 'tablepress_c_' . md5( $table_id );
593 571 $caches_list = get_transient( $caches_list_transient_name );
594 572 if ( false !== $caches_list ) {
595 573 $caches_list = (array) json_decode( $caches_list, true );
@@ -600,13 +578,13 @@
600 578 delete_transient( $caches_list_transient_name );
601 579 }
602 580
603 581 /**
604 - * Flush the caches of the plugins W3 Total Cache, WP Super Cache, Cachify, and Quick Cache.
582 + * Flush the caches of common caching plugins.
605 583 *
606 584 * @since 1.0.0
607 585 */
608 - public function _flush_caching_plugins_caches() {
586 + public function _flush_caching_plugins_caches(): void {
609 587 /**
610 588 * Filters whether the caches of common caching plugins shall be flushed.
611 589 *
612 590 * @since 1.0.0
@@ -650,13 +628,13 @@
650 628 }
651 629
652 630 // Kinsta.
653 631 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();
632 + $GLOBALS['kinsta_cache']->kinsta_cache_purge->purge_complete_caches(); // @phpstan-ignore method.nonObject
655 633 }
656 634 // LiteSpeed Cache.
657 635 if ( is_callable( array( 'LiteSpeed_Cache_Tags', 'add_purge_tag' ) ) ) {
658 - LiteSpeed_Cache_Tags::add_purge_tag( '*' );
636 + LiteSpeed_Cache_Tags::add_purge_tag( '*' ); // @phpstan-ignore class.notFound
659 637 }
660 638 // Pagely.
661 639 if ( class_exists( 'PagelyCachePurge' ) ) {
662 640 $_pagely = new PagelyCachePurge();
@@ -665,23 +643,23 @@
665 643 }
666 644 }
667 645 // Pressidum.
668 646 if ( is_callable( array( 'Ninukis_Plugin', 'get_instance' ) ) ) {
669 - $_pressidum = Ninukis_Plugin::get_instance();
647 + $_pressidum = Ninukis_Plugin::get_instance(); // @phpstan-ignore class.notFound
670 648 if ( is_callable( array( $_pressidum, 'purgeAllCaches' ) ) ) {
671 - $_pressidum->purgeAllCaches();
649 + $_pressidum->purgeAllCaches(); // @phpstan-ignore method.nonObject
672 650 }
673 651 }
674 652 // Savvii.
675 653 if ( defined( '\Savvii\CacheFlusherPlugin::NAME_DOMAINFLUSH_NOW' ) ) {
676 - $_savvii = new \Savvii\CacheFlusherPlugin();
654 + $_savvii = new \Savvii\CacheFlusherPlugin(); // @phpstan-ignore class.notFound
677 655 if ( is_callable( array( $_savvii, 'domainflush' ) ) ) {
678 - $_savvii->domainflush();
656 + $_savvii->domainflush(); // @phpstan-ignore class.notFound
679 657 }
680 658 }
681 659 // WP Fastest Cache.
682 - if ( isset( $GLOBALS['wp_fastest_cache'] ) && is_callable( $GLOBALS['wp_fastest_cache'], 'deleteCache' ) ) {
683 - $GLOBALS['wp_fastest_cache']->deleteCache();
660 + if ( isset( $GLOBALS['wp_fastest_cache'] ) && is_callable( array( $GLOBALS['wp_fastest_cache'], 'deleteCache' ) ) ) {
661 + $GLOBALS['wp_fastest_cache']->deleteCache( true ); // @phpstan-ignore method.nonObject
684 662 }
685 663 // WP-Optimize.
686 664 if ( function_exists( 'WP_Optimize' ) ) {
687 665 WP_Optimize()->get_page_cache()->purge();
@@ -695,9 +673,9 @@
695 673 *
696 674 * @param string $table_id Table ID.
697 675 * @return int|false Post ID on success, false on error.
698 676 */
699 - protected function _get_post_id( $table_id ) {
677 + protected function _get_post_id( string $table_id ) /* : int|false */ {
700 678 $table_post = $this->tables->get( 'table_post' );
701 679 if ( ! isset( $table_post[ $table_id ] ) ) {
702 680 return false;
703 681 }
@@ -711,9 +689,9 @@
711 689 *
712 690 * @param string $table_id Table ID.
713 691 * @param int $post_id Post ID.
714 692 */
715 - protected function _update_post_id( $table_id, $post_id ) {
693 + protected function _update_post_id( string $table_id, int $post_id ): void {
716 694 $tables = $this->tables->get();
717 695 $tables['table_post'][ $table_id ] = $post_id;
718 696 uksort( $tables['table_post'], 'strnatcasecmp' );
719 697 $this->tables->update( $tables );
@@ -725,9 +703,9 @@
725 703 * @since 1.0.0
726 704 *
727 705 * @param string $table_id Table ID.
728 706 */
729 - protected function _remove_post_id( $table_id ) {
707 + protected function _remove_post_id( string $table_id ): void {
730 708 $tables = $this->tables->get();
731 709 unset( $tables['table_post'][ $table_id ] );
732 710 $this->tables->update( $tables );
733 711 }
@@ -740,9 +718,9 @@
740 718 * @param string $old_id Old table ID.
741 719 * @param string $new_id New table ID.
742 720 * @return bool|WP_Error True on success, WP_Error on error.
743 721 */
744 - public function change_table_id( $old_id, $new_id ) {
722 + public function change_table_id( string $old_id, string $new_id ) /* : true|WP_Error */ {
745 723 $post_id = $this->_get_post_id( $old_id );
746 724 if ( false === $post_id ) {
747 725 return new WP_Error( 'table_change_id_no_post_id_for_table_id', '', $old_id );
748 726 }
@@ -778,9 +756,9 @@
778 756 * @since 1.0.0
779 757 *
780 758 * @return string Unused table ID (e.g. for a new table).
781 759 */
782 - protected function _get_new_table_id() {
760 + protected function _get_new_table_id(): string {
783 761 $tables = $this->tables->get();
784 762 // Need to check new ID candidate in a loop, because a higher ID might already be in use, if a table ID was changed manually.
785 763 do {
786 764 ++$tables['last_id'];
@@ -796,11 +774,11 @@
796 774 * Important: This scheme is versioned via TablePress::table_scheme_version; changes likely need a version update!
797 775 *
798 776 * @since 1.0.0
799 777 *
800 - * @return array Empty table.
778 + * @return array<string, mixed> Empty table.
801 779 */
802 - public function get_table_template() {
780 + public function get_table_template(): array {
803 781 // Attention: Array keys have to be lowercase, to make it possible to match them with Shortcode attributes!
804 782 $table = array(
805 783 'id' => false,
806 784 'name' => '',
@@ -809,10 +787,10 @@
809 787 'last_modified' => wp_date( 'Y-m-d H:i:s' ),
810 788 'author' => get_current_user_id(),
811 789 'options' => array(
812 790 'last_editor' => get_current_user_id(),
813 - 'table_head' => true,
814 - 'table_foot' => false,
791 + 'table_head' => 1,
792 + 'table_foot' => 0,
815 793 'alternating_row_colors' => true,
816 794 'row_hover' => true,
817 795 'print_name' => false,
818 796 'print_name_position' => 'above',
@@ -830,9 +808,9 @@
830 808 'datatables_scrollx' => false,
831 809 'datatables_custom_commands' => '',
832 810 ),
833 811 'visibility' => array(
834 - 'rows' => array( 1 ), // One visbile row.
812 + 'rows' => array( 1 ), // One visible row.
835 813 'columns' => array( 1 ), // One visible column.
836 814 ),
837 815 );
838 816 /**
@@ -839,9 +817,9 @@
839 817 * Filters the default template/structure of an empty table.
840 818 *
841 819 * @since 1.0.0
842 820 *
843 - * @param array $table Default template/structure of an empty table.
821 + * @param array<string, mixed> $table Default template/structure of an empty table.
844 822 */
845 823 return apply_filters( 'tablepress_table_template', $table );
846 824 }
847 825
@@ -851,14 +829,14 @@
851 829 * Performs consistency checks on data and visibility settings.
852 830 *
853 831 * @since 1.0.0
854 832 *
855 - * @param array $table Table to merge into.
856 - * @param array $new_table Table to merge.
857 - * @param bool $table_size_check Optional. Whether to check the number of rows and columns (e.g. not necessary for added or copied tables).
858 - * @return array|WP_Error Merged table on success, WP_Error on error.
833 + * @param array<string, mixed> $table Table to merge into.
834 + * @param array<string, mixed> $new_table Table to merge.
835 + * @param bool $table_size_check Optional. Whether to check the number of rows and columns (e.g. not necessary for added or copied tables).
836 + * @return array<string, mixed>|WP_Error Merged table on success, WP_Error on error.
859 837 */
860 - public function prepare_table( array $table, array $new_table, $table_size_check = true ) {
838 + public function prepare_table( array $table, array $new_table, bool $table_size_check = true ) /* : array|WP_Error */ {
861 839 // Table ID must be the same (if there was an ID already).
862 840 if ( false !== $table['id'] && $table['id'] !== $new_table['id'] ) {
863 841 return new WP_Error( 'table_prepare_no_id_match', '', $new_table['id'] );
864 842 }
@@ -899,9 +877,9 @@
899 877
900 878 // All checks were successful, replace original values with new ones.
901 879
902 880 // $table['id'] is either false (and remains false) or already equal to $new_table['id'].
903 - $table['new_id'] = isset( $new_table['new_id'] ) ? $new_table['new_id'] : $table['id'];
881 + $table['new_id'] = $new_table['new_id'] ?? $table['id'];
904 882 $table['name'] = $new_table['name'];
905 883 $table['description'] = $new_table['description'];
906 884 $table['data'] = $new_table['data'];
907 885 // Make sure that cells are stored as strings.
@@ -906,16 +884,12 @@
906 884 $table['data'] = $new_table['data'];
907 885 // Make sure that cells are stored as strings.
908 886 array_walk_recursive(
909 887 $table['data'],
910 - static function( &$cell_content, $col_idx ) {
888 + static function ( /* string|int|float|bool|null */ &$cell_content, int $col_idx ): void {
911 889 $cell_content = (string) $cell_content;
912 - }
890 + },
913 891 );
914 - // $table['author'] = get_current_user_id(); // We don't want this, as it would override the original author.
915 - // $table['created'] = wp_date( 'Y-m-d H:i:s' ); // We don't want this, as it would override the original datetime.
916 - $table['last_modified'] = wp_date( 'Y-m-d H:i:s' );
917 - $table['options']['last_editor'] = get_current_user_id();
918 892 // Table Options.
919 893 if ( isset( $new_table['options'] ) ) { // Options are for example not set for newly added tables.
920 894 // Specials check for certain options.
921 895 if ( isset( $new_table['options']['extra_css_classes'] ) ) {
@@ -929,8 +903,17 @@
929 903 if ( $new_table['options']['datatables_paginate_entries'] < 1 ) {
930 904 $new_table['options']['datatables_paginate_entries'] = 10; // Default value.
931 905 }
932 906 }
907 +
908 + // Backward compatibility: Convert boolean or numeric string "table_head" and "table_foot" options to integer.
909 + if ( isset( $new_table['options']['table_head'] ) ) {
910 + $new_table['options']['table_head'] = absint( $new_table['options']['table_head'] );
911 + }
912 + if ( isset( $new_table['options']['table_foot'] ) ) {
913 + $new_table['options']['table_foot'] = absint( $new_table['options']['table_foot'] );
914 + }
915 +
933 916 // Merge new options.
934 917 $default_table = $this->get_table_template();
935 918 $table['options'] = array_intersect_key( $table['options'], $default_table['options'] );
936 919 $new_table['options'] = array_intersect_key( $new_table['options'], $default_table['options'] );
@@ -939,8 +922,23 @@
939 922 // Table Visibility.
940 923 $table['visibility']['rows'] = $new_table['visibility']['rows'];
941 924 $table['visibility']['columns'] = $new_table['visibility']['columns'];
942 925
926 + // $table['author'] = get_current_user_id(); // We don't want this, as it would override the original author.
927 + // $table['created'] = wp_date( 'Y-m-d H:i:s' ); // We don't want this, as it would override the original datetime.
928 + $table['last_modified'] = wp_date( 'Y-m-d H:i:s' );
929 + $table['options']['last_editor'] = get_current_user_id();
930 +
931 + // Prevent issues if the "Custom Commands" field is not set, e.g. when non-admins have previously edited the table.
932 + if ( ! isset( $table['options']['datatables_custom_commands'] ) ) {
933 + $table['options']['datatables_custom_commands'] = '';
934 + }
935 +
936 + // Convert CSS classes and some DataTables 1.x parameters to the DataTables 2 variants.
937 + if ( '' !== $table['options']['datatables_custom_commands'] ) {
938 + $table['options']['datatables_custom_commands'] = TablePress::convert_datatables_api_data( $table['options']['datatables_custom_commands'] );
939 + }
940 +
943 941 return $table;
944 942 }
945 943
946 944 /**
@@ -947,15 +945,15 @@
947 945 * Save the table options of a table (in a post meta field of the table's post).
948 946 *
949 947 * @since 1.0.0
950 948 *
951 - * @param int $post_id Post ID.
952 - * @param array $options Table options.
949 + * @param int $post_id Post ID.
950 + * @param array<string, mixed> $options Table options.
953 951 * @return bool True on success, false on error.
954 952 */
955 - protected function _add_table_options( $post_id, array $options ) {
953 + protected function _add_table_options( int $post_id, array $options ): bool {
956 954 $options = wp_json_encode( $options, TABLEPRESS_JSON_OPTIONS );
957 - return $this->model_post->add_meta_field( $post_id, $this->table_options_field_name, $options );
955 + return $this->model_post->add_meta_field( $post_id, $this->table_options_field_name, $options ); // @phpstan-ignore argument.type
958 956 }
959 957
960 958 /**
961 959 * Update the table options of a table (in a post meta field in the table's post).
@@ -961,15 +959,15 @@
961 959 * Update the table options of a table (in a post meta field in the table's post).
962 960 *
963 961 * @since 1.0.0
964 962 *
965 - * @param int $post_id Post ID.
966 - * @param array $options Table options.
963 + * @param int $post_id Post ID.
964 + * @param array<string, mixed> $options Table options.
967 965 * @return bool True on success, false on error.
968 966 */
969 - protected function _update_table_options( $post_id, array $options ) {
967 + protected function _update_table_options( int $post_id, array $options ): bool {
970 968 $options = wp_json_encode( $options, TABLEPRESS_JSON_OPTIONS );
971 - return $this->model_post->update_meta_field( $post_id, $this->table_options_field_name, $options );
969 + return $this->model_post->update_meta_field( $post_id, $this->table_options_field_name, $options ); // @phpstan-ignore argument.type
972 970 }
973 971
974 972 /**
975 973 * Get the table options of a table (from a post meta field of the table's post).
@@ -976,16 +974,22 @@
976 974 *
977 975 * @since 1.0.0
978 976 *
979 977 * @param int $post_id Post ID.
980 - * @return array Table options on success, empty array on error.
978 + * @return array<string, mixed> Table options on success, empty array on error.
981 979 */
982 - protected function _get_table_options( $post_id ) {
980 + protected function _get_table_options( int $post_id ): array {
983 981 $options = $this->model_post->get_meta_field( $post_id, $this->table_options_field_name );
984 982 if ( empty( $options ) ) {
985 983 return array();
986 984 }
987 - return (array) json_decode( $options, true );
985 + $options = (array) json_decode( $options, true );
986 +
987 + // Backward compatibility: Convert boolean "table_head" and "table_foot" options to integer.
988 + $options['table_head'] = absint( $options['table_head'] );
989 + $options['table_foot'] = absint( $options['table_foot'] );
990 +
991 + return $options;
988 992 }
989 993
990 994 /**
991 995 * Save the table visibility of a table (in a post meta field of the table's post).
@@ -991,15 +995,15 @@
991 995 * Save the table visibility of a table (in a post meta field of the table's post).
992 996 *
993 997 * @since 1.0.0
994 998 *
995 - * @param int $post_id Post ID.
996 - * @param array $visibility Table visibility.
999 + * @param int $post_id Post ID.
1000 + * @param array{rows: int[], columns: int[]} $visibility Table visibility.
997 1001 * @return bool True on success, false on error.
998 1002 */
999 - protected function _add_table_visibility( $post_id, array $visibility ) {
1003 + protected function _add_table_visibility( int $post_id, array $visibility ): bool {
1000 1004 $visibility = wp_json_encode( $visibility, TABLEPRESS_JSON_OPTIONS );
1001 - return $this->model_post->add_meta_field( $post_id, $this->table_visibility_field_name, $visibility );
1005 + return $this->model_post->add_meta_field( $post_id, $this->table_visibility_field_name, $visibility ); // @phpstan-ignore argument.type
1002 1006 }
1003 1007
1004 1008 /**
1005 1009 * Update the table visibility of a table (in a post meta field in the table's post).
@@ -1005,15 +1009,15 @@
1005 1009 * Update the table visibility of a table (in a post meta field in the table's post).
1006 1010 *
1007 1011 * @since 1.0.0
1008 1012 *
1009 - * @param int $post_id Post ID.
1010 - * @param array $visibility Table visibility.
1013 + * @param int $post_id Post ID.
1014 + * @param array{rows: int[], columns: int[]} $visibility Table visibility.
1011 1015 * @return bool True on success, false on error.
1012 1016 */
1013 - protected function _update_table_visibility( $post_id, array $visibility ) {
1017 + protected function _update_table_visibility( int $post_id, array $visibility ): bool {
1014 1018 $visibility = wp_json_encode( $visibility, TABLEPRESS_JSON_OPTIONS );
1015 - return $this->model_post->update_meta_field( $post_id, $this->table_visibility_field_name, $visibility );
1019 + return $this->model_post->update_meta_field( $post_id, $this->table_visibility_field_name, $visibility ); // @phpstan-ignore argument.type
1016 1020 }
1017 1021
1018 1022 /**
1019 1023 * Get the table visibility of a table (from a post meta field of the table's post).
@@ -1020,14 +1024,17 @@
1020 1024 *
1021 1025 * @since 1.0.0
1022 1026 *
1023 1027 * @param int $post_id Post ID.
1024 - * @return array Table visibility on success, empty array on error.
1028 + * @return array{rows: int[], columns: int[]} Table visibility on success, empty array on error.
1025 1029 */
1026 - protected function _get_table_visibility( $post_id ) {
1030 + protected function _get_table_visibility( int $post_id ): array {
1027 1031 $visibility = $this->model_post->get_meta_field( $post_id, $this->table_visibility_field_name );
1028 1032 if ( empty( $visibility ) ) {
1029 - return array();
1033 + return array(
1034 + 'rows' => array(),
1035 + 'columns' => array(),
1036 + );
1030 1037 }
1031 1038 return json_decode( $visibility, true );
1032 1039 }
1033 1040
@@ -1040,9 +1047,9 @@
1040 1047 * @since 2.0.0 Add optional $remove_old_options parameter.
1041 1048 *
1042 1049 * @param bool $remove_old_options Optional. Whether old table options should be removed from the database. Default true.
1043 1050 */
1044 - public function merge_table_options_defaults( $remove_old_options = true ) {
1051 + public function merge_table_options_defaults( bool $remove_old_options = true ): void {
1045 1052 $table_post = $this->tables->get( 'table_post' );
1046 1053 if ( empty( $table_post ) ) {
1047 1054 return;
1048 1055 }
@@ -1053,10 +1060,23 @@
1053 1060 // Get default Table with default Table Options.
1054 1061 $default_table = $this->get_table_template();
1055 1062
1056 1063 // Go through all tables (this loop now uses the WP cache).
1057 - foreach ( $table_post as $table_id => $post_id ) {
1064 + foreach ( $table_post as $post_id ) {
1058 1065 $table_options = $this->_get_table_options( $post_id );
1066 +
1067 + /**
1068 + * Filters the Table Options before they are merged with the default Table Options.
1069 + *
1070 + * @since 3.0.0
1071 + *
1072 + * @param array<string, mixed> $table_options Table Options.
1073 + * @param array<string, mixed> $default_table_options Default Table Options.
1074 + * @param bool $remove_old_options Whether old table options should be removed from the database.
1075 + * @param int $post_id Post ID of the table.
1076 + */
1077 + $table_options = apply_filters( 'tablepress_table_options_before_merge', $table_options, $default_table['options'], $remove_old_options, $post_id );
1078 +
1059 1079 if ( $remove_old_options ) {
1060 1080 // Remove old (i.e. no longer existing) Table Options.
1061 1081 $table_options = array_intersect_key( $table_options, $default_table['options'] );
1062 1082 }
@@ -1066,13 +1086,53 @@
1066 1086 }
1067 1087 }
1068 1088
1069 1089 /**
1090 + * Updates all tables' "Custom Commands" to use DataTables 2 variants instead of old DataTables 1.x CSS classes and parameters
1091 + *
1092 + * @since 3.0.0
1093 + */
1094 + public function update_custom_commands_datatables_tp30(): void {
1095 + $table_post = $this->tables->get( 'table_post' );
1096 + if ( empty( $table_post ) ) {
1097 + return;
1098 + }
1099 +
1100 + // Prime the meta cache with the table options of all tables.
1101 + update_meta_cache( 'post', array_values( $table_post ) );
1102 +
1103 + foreach ( $table_post as $table_id => $post_id ) {
1104 + $table_options = $this->_get_table_options( $post_id );
1105 +
1106 + // Fix tables where the "Custom Commands" entry is missing entirely.
1107 + if ( ! isset( $table_options['datatables_custom_commands'] ) ) {
1108 + $table_options['datatables_custom_commands'] = '';
1109 + $this->_update_table_options( $post_id, $table_options );
1110 + continue;
1111 + }
1112 +
1113 + // Nothing to do if there are no "Custom Commands".
1114 + if ( '' === $table_options['datatables_custom_commands'] ) {
1115 + continue;
1116 + }
1117 + // Run search/replace.
1118 + $old_custom_commands = $table_options['datatables_custom_commands'];
1119 + $table_options['datatables_custom_commands'] = TablePress::convert_datatables_api_data( $table_options['datatables_custom_commands'] );
1120 + // No need to save (which runs a DB query) if nothing was replaced in the "Custom Commands".
1121 + if ( $old_custom_commands === $table_options['datatables_custom_commands'] ) {
1122 + continue;
1123 + }
1124 +
1125 + $this->_update_table_options( $post_id, $table_options );
1126 + }
1127 + }
1128 +
1129 + /**
1070 1130 * Invalidate all table output caches, e.g. after a plugin update.
1071 1131 *
1072 1132 * @since 1.0.0
1073 1133 */
1074 - public function invalidate_table_output_caches() {
1134 + public function invalidate_table_output_caches(): void {
1075 1135 $table_post = $this->tables->get( 'table_post' );
1076 1136 if ( empty( $table_post ) ) {
1077 1137 return;
1078 1138 }
@@ -1077,8 +1137,9 @@
1077 1137 return;
1078 1138 }
1079 1139
1080 1140 foreach ( $table_post as $table_id => $post_id ) {
1141 + $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.
1081 1142 $this->invalidate_table_output_cache( $table_id );
1082 1143 }
1083 1144 }
1084 1145
@@ -1089,11 +1150,11 @@
1089 1150 * @since 1.5.0
1090 1151 *
1091 1152 * @global wpdb $wpdb WordPress database abstraction object.
1092 1153 */
1093 - public function add_mime_type_to_posts() {
1154 + public function add_mime_type_to_posts(): void {
1094 1155 global $wpdb;
1095 - $wpdb->update( $wpdb->posts, array( 'post_mime_type' => 'application/json' ), array( 'post_type' => $this->model_post->get_post_type() ) );
1156 + $wpdb->update( $wpdb->posts, array( 'post_mime_type' => 'application/json' ), array( 'post_type' => $this->model_post->get_post_type() ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
1096 1157 }
1097 1158
1098 1159 /**
1099 1160 * Add a table ID for a post (table) that was imported through the WP WXR importer to the table ID to post ID map.
@@ -1099,14 +1160,14 @@
1099 1160 * Add a table ID for a post (table) that was imported through the WP WXR importer to the table ID to post ID map.
1100 1161 *
1101 1162 * @since 1.5.0
1102 1163 *
1103 - * @param int|WP_Error $post_id Post ID of the imported post on success. 0 or WP_Error on failure.
1104 - * @param int $original_post_id Original post ID that the post had on the site where it was exported from.
1105 - * @param array $postdata Post data that was imported into the database.
1106 - * @param array $post Original post data as it was exported.
1164 + * @param int|WP_Error $post_id Post ID of the imported post on success. 0 or WP_Error on failure.
1165 + * @param int $original_post_id Original post ID that the post had on the site where it was exported from.
1166 + * @param array<string, mixed> $postdata Post data that was imported into the database.
1167 + * @param array<string, mixed> $post Original post data as it was exported.
1107 1168 */
1108 - public function add_table_id_on_wp_import( $post_id, $original_post_id, array $postdata, array $post ) {
1169 + public function add_table_id_on_wp_import( /* int|WP_Error */ $post_id, int $original_post_id, array $postdata, array $post ): void {
1109 1170 // Bail if the post could not be imported or if the post is not a TablePress table.
1110 1171 if ( is_wp_error( $post_id ) || $this->model_post->get_post_type() !== $postdata['post_type'] ) {
1111 1172 return;
1112 1173 }
@@ -1125,9 +1186,9 @@
1125 1186
1126 1187 // Save the post ID for each of the table IDs.
1127 1188 $post_id_saved = false;
1128 1189 foreach ( $table_ids as $table_id ) {
1129 - $table_id = preg_replace( '/[^a-zA-Z0-9_-]/', '', $table_id );
1190 + $table_id = (string) preg_replace( '/[^a-zA-Z0-9_-]/', '', $table_id );
1130 1191 if ( '' === $table_id || $this->table_exists( $table_id ) ) {
1131 1192 continue;
1132 1193 }
1133 1194 $this->_update_post_id( $table_id, $post_id );
@@ -1145,14 +1206,14 @@
1145 1206 * Remove the `_tablepress_export_table_id` post meta field from the fields that are imported during a WP WXR import.
1146 1207 *
1147 1208 * @since 1.5.0
1148 1209 *
1149 - * @param array $postmeta Post meta fields for the post.
1150 - * @param int $post_id Post ID.
1151 - * @param array $post Post.
1152 - * @return array Modified post meta fields.
1210 + * @param array<string, mixed> $postmeta Post meta fields for the post.
1211 + * @param int $post_id Post ID.
1212 + * @param array<string, mixed> $post Post.
1213 + * @return array<string, mixed> Modified post meta fields.
1153 1214 */
1154 - public function prevent_table_id_post_meta_import_on_wp_import( array $postmeta, $post_id, array $post ) {
1215 + public function prevent_table_id_post_meta_import_on_wp_import( array $postmeta, int $post_id, array $post ): array {
1155 1216 // Bail if the post is not a TablePress table.
1156 1217 if ( $this->model_post->get_post_type() !== $post['post_type'] ) {
1157 1218 return $postmeta;
1158 1219 }
@@ -1177,10 +1238,11 @@
1177 1238 *
1178 1239 * @param bool $skip Whether to skip the current post meta. Default false.
1179 1240 * @param string $meta_key Current meta key.
1180 1241 * @param stdClass $meta Current meta object.
1242 + * @return bool Whether to skip the current post meta (unchanged $skip parameter).
1181 1243 */
1182 - public function add_table_id_to_wp_export( $skip, $meta_key, $meta ) {
1244 + public function add_table_id_to_wp_export( bool $skip, string $meta_key, stdClass $meta ): bool {
1183 1245 // Bail if the exporter doesn't process a TablePress table right now.
1184 1246 if ( $this->table_options_field_name !== $meta_key ) {
1185 1247 return $skip;
1186 1248 }
@@ -1195,17 +1257,24 @@
1195 1257 }
1196 1258
1197 1259 // Pretend that there is a `_tablepress_export_table_id` post meta field with the list of table IDs.
1198 1260 $key = '_tablepress_export_table_id';
1261 +
1262 + /**
1263 + * Load WP export functions.
1264 + */
1265 + require_once ABSPATH . 'wp-admin/includes/export.php'; // @phpstan-ignore requireOnce.fileNotFound (This is a WordPress core file that always exists.)
1199 1266 $value = wxr_cdata( implode( ',', $table_ids ) );
1200 1267
1201 1268 // Hijack the filter and print extra XML code for our faked post meta field.
1269 + // phpcs:disable WordPress.Security.EscapeOutput.HeredocOutputNotEscaped
1202 1270 echo <<<WXR
1203 - <wp:postmeta>
1204 - <wp:meta_key>{$key}</wp:meta_key>
1205 - <wp:meta_value>{$value}</wp:meta_value>
1206 - </wp:postmeta>\n
1207 -WXR;
1271 + <wp:postmeta>
1272 + <wp:meta_key>{$key}</wp:meta_key>
1273 + <wp:meta_value>{$value}</wp:meta_value>
1274 + </wp:postmeta>\n
1275 + WXR;
1276 + // phpcs:enable
1208 1277
1209 1278 return $skip;
1210 1279 }
1211 1280
@@ -1213,9 +1282,9 @@
1213 1282 * Delete the WP_Option of the model.
1214 1283 *
1215 1284 * @since 1.0.0
1216 1285 */
1217 - public function destroy() {
1286 + public function destroy(): void {
1218 1287 $this->tables->delete();
1219 1288 }
1220 1289
1221 1290 } // class TablePress_Table_Model