PluginProbe
TablePress – Tables in WordPress made easy / 3.0
TablePress – Tables in WordPress made easy v3.0
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 +279 -346 1.143.0 View file →
@@ -12,8 +12,9 @@
12 12 defined( 'ABSPATH' ) || die( 'No direct script access allowed!' );
13 13
14 14 /**
15 15 * Table Model class
16 + *
16 17 * @package TablePress
17 18 * @subpackage Models
18 19 * @author Tobias Bäthge
19 20 * @since 1.0.0
@@ -23,38 +24,35 @@
23 24 /**
24 25 * Instance of the Post Type Model.
25 26 *
26 27 * @since 1.0.0
27 - * @var TablePress_Post_Model
28 28 */
29 - protected $model_post;
29 + protected \TablePress_Post_Model $model_post;
30 30
31 31 /**
32 32 * Name of the Post Meta Field for table options.
33 33 *
34 34 * @since 1.0.0
35 - * @var string
36 35 */
37 - protected $table_options_field_name = '_tablepress_table_options';
36 + protected string $table_options_field_name = '_tablepress_table_options';
38 37
39 38 /**
40 39 * Name of the Post Meta Field for table visibility.
41 40 *
42 41 * @since 1.0.0
43 - * @var string
44 42 */
45 - protected $table_visibility_field_name = '_tablepress_table_visibility';
43 + protected string $table_visibility_field_name = '_tablepress_table_visibility';
46 44
47 45 /**
48 46 * Default set of tables.
49 47 *
50 48 * @since 1.0.0
51 - * @var array $args {
52 - * @type int $last_id Last table ID that was given to a new table.
53 - * @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).
54 52 * }
55 53 */
56 - protected $default_tables = array(
54 + protected array $default_tables = array(
57 55 'last_id' => 0,
58 56 'table_post' => array(),
59 57 );
60 58
@@ -61,121 +59,12 @@
61 59 /**
62 60 * Instance of WP_Option class for the list of tables.
63 61 *
64 62 * @since 1.0.0
65 - * @var TablePress_WP_Option
66 63 */
67 - protected $tables;
64 + protected \TablePress_WP_Option $tables;
68 65
69 66 /**
70 - * Mappings for old to new parameter names for DataTables.
71 - * DataTables 1.9 used Hungarian notation, while DataTables 1.10+ uses camelCase notation.
72 - *
73 - * As this array is used in strtr(), it's pre-sorted for descending string length of the array keys.
74 - *
75 - * @since 1.5.0
76 - * @var array
77 - * @link https://www.datatables.net/upgrade/1.10-convert
78 - */
79 - protected $datatables_parameter_mappings = array(
80 - 'fnStateSaveParams' => 'stateSaveParams',
81 - 'fnStateLoadParams' => 'stateLoadParams',
82 - 'fnPreDrawCallback' => 'preDrawCallback',
83 - 'fnHeaderCallback' => 'headerCallback',
84 - 'fnFooterCallback' => 'footerCallback',
85 - 'sSortDescending' => 'sortDescending',
86 - 'sPaginationType' => 'pagingType',
87 - 'sLoadingRecords' => 'loadingRecords',
88 - 'sDefaultContent' => 'defaultContent',
89 - 'sContentPadding' => 'contentPadding',
90 - 'iCookieDuration' => 'stateDuration',
91 - 'bScrollCollapse' => 'scrollCollapse',
92 - 'asStripeClasses' => 'stripeClasses',
93 - 'sSortAscending' => 'sortAscending',
94 - 'sInfoThousands' => 'thousands',
95 - 'iDisplayLength' => 'pageLength',
96 - 'fnServerParams' => 'ajax',
97 - 'fnInitComplete' => 'initComplete',
98 - 'fnInfoCallback' => 'infoCallback',
99 - 'fnFormatNumber' => 'formatNumber',
100 - 'fnDrawCallback' => 'drawCallback',
101 - 'aaSortingFixed' => 'orderFixed',
102 - 'sSortDataType' => 'orderDataType',
103 - 'sServerMethod' => 'ajax',
104 - 'sScrollXInner' => 'scrollXInner',
105 - 'sInfoFiltered' => 'infoFiltered',
106 - 'sAjaxDataProp' => 'dataSrc',
107 - 'iDisplayStart' => 'displayStart',
108 - 'iDeferLoading' => 'deferLoading',
109 - 'fnStateLoaded' => 'stateLoaded',
110 - 'fnRowCallback' => 'rowCallback',
111 - 'fnCreatedCell' => 'createdCell',
112 - 'bSortCellsTop' => 'orderCellsTop',
113 - 'bLengthChange' => 'lengthChange',
114 - 'sZeroRecords' => 'zeroRecords',
115 - 'sInfoPostFix' => 'infoPostFix',
116 - 'fnServerData' => 'ajax',
117 - 'fnCreatedRow' => 'createdRow',
118 - 'bSortClasses' => 'orderClasses',
119 - 'bDeferRender' => 'deferRender',
120 - 'aoSearchCols' => 'searchCols',
121 - 'aoColumnDefs' => 'columnDefs',
122 - 'sProcessing' => 'processing',
123 - 'sLengthMenu' => 'lengthMenu',
124 - 'sEmptyTable' => 'emptyTable',
125 - 'sAjaxSource' => 'ajax',
126 - 'fnStateSave' => 'stateSaveCallback',
127 - 'fnStateLoad' => 'stateLoadCallback',
128 - 'bServerSide' => 'serverSide',
129 - 'bSearchable' => 'searchable',
130 - 'bProcessing' => 'processing',
131 - 'aLengthMenu' => 'lengthMenu',
132 - 'sInfoEmpty' => 'infoEmpty',
133 - 'bStateSave' => 'stateSave',
134 - 'bAutoWidth' => 'autoWidth',
135 - 'className' => 'className', // Replace "className" with itself, to avoid that the replacement for "sName" breaks it.
136 - 'sPrevious' => 'previous',
137 - 'sCellType' => 'cellType',
138 - 'oPaginate' => 'paginate',
139 - 'oLanguage' => 'language',
140 - 'iTabIndex' => 'tabIndex',
141 - 'iDataSort' => 'orderData',
142 - 'bSortable' => 'orderable',
143 - 'bRetrieve' => 'retrieve',
144 - 'bPaginate' => 'paging',
145 - 'bJQueryUI' => 'jQueryUI',
146 - 'asSorting' => 'orderSequence',
147 - 'aoColumns' => 'columns',
148 - 'aaSorting' => 'order',
149 - 'aDataSort' => 'orderData',
150 - 'sScrollY' => 'scrollY',
151 - 'sScrollX' => 'scrollX',
152 - 'bVisible' => 'visible',
153 - 'bDestroy' => 'destroy',
154 - 'aTargets' => 'targets',
155 - 'sSearch' => 'search',
156 - 'oSearch' => 'search',
157 - 'mRender' => 'render',
158 - 'bFilter' => 'searching',
159 - 'sWidth' => 'width',
160 - 'sTitle' => 'title',
161 - 'sFirst' => 'first',
162 - 'sClass' => 'className',
163 - 'aaData' => 'data',
164 - 'sType' => 'type',
165 - 'sNext' => 'next',
166 - 'sName' => 'name',
167 - 'sLast' => 'last',
168 - 'sInfo' => 'info',
169 - 'oAria' => 'aria',
170 - 'mData' => 'data',
171 - 'bSort' => 'ordering',
172 - 'bInfo' => 'info',
173 - 'sUrl' => 'url',
174 - 'sDom' => 'dom',
175 - );
176 -
177 - /**
178 67 * Init the Table model by instantiating a Post model and loading the list of tables option.
179 68 *
180 69 * @since 1.0.0
181 70 */
@@ -194,11 +83,11 @@
194 83 * Get the tables option, which holds the connection between table ID and post ID.
195 84 *
196 85 * @since 1.0.0
197 86 *
198 - * @return array Current set of tables.
87 + * @return mixed[] Current set of tables.
199 88 */
200 - public function _debug_get_tables() {
89 + public function _debug_get_tables(): array {
201 90 return $this->tables->get();
202 91 }
203 92
204 93 /**
@@ -205,11 +94,11 @@
205 94 * Update the tables option, which holds the connection between table ID and post ID.
206 95 *
207 96 * @since 1.0.0
208 97 *
209 - * @param array $tables New set of tables.
98 + * @param mixed[] $tables New set of tables.
210 99 */
211 - public function _debug_update_tables( array $tables ) {
100 + public function _debug_update_tables( array $tables ): void {
212 101 $this->tables->update( $tables );
213 102 }
214 103
215 104 /**
@@ -216,16 +105,13 @@
216 105 * Convert a table to a post, which can be stored in the database.
217 106 *
218 107 * @since 1.0.0
219 108 *
220 - * @param array $table Table.
221 - * @param int $post_id Post ID of an existing table, or -1 for a new table.
222 - * @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.
223 112 */
224 - protected function _table_to_post( array $table, $post_id ) {
225 - // Run filters on content in each cell and other fields.
226 - $table = $this->filter_content( $table );
227 -
113 + protected function _table_to_post( array $table, int $post_id ): array {
228 114 // Sanitize each cell, table name, and table description, if the user is not allowed to work with unfiltered HTML.
229 115 if ( ! current_user_can( 'unfiltered_html' ) ) {
230 116 $table = $this->sanitize( $table );
231 117 }
@@ -237,9 +123,8 @@
237 123
238 124 $post = array(
239 125 'ID' => $post_id,
240 126 'post_title' => $table['name'],
241 - // 'post_author' => $table['author'],
242 127 'post_excerpt' => $table['description'],
243 128 'post_content' => wp_json_encode( $table['data'], TABLEPRESS_JSON_OPTIONS ),
244 129 'post_mime_type' => 'application/json',
245 130 );
@@ -254,11 +139,11 @@
254 139 *
255 140 * @param WP_Post $post Post.
256 141 * @param string $table_id Table ID.
257 142 * @param bool $load_data Whether the table data shall be loaded.
258 - * @return array Table.
143 + * @return array<string, mixed> Table.
259 144 */
260 - 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 {
261 146 $table = array(
262 147 'id' => $table_id,
263 148 'name' => $post->post_title,
264 149 'description' => $post->post_excerpt,
@@ -274,20 +159,11 @@
274 159 $table['data'] = json_decode( $post->post_content, true );
275 160
276 161 // Check if JSON could be decoded.
277 162 if ( is_null( $table['data'] ) ) {
278 - // Set a single cell as the default.
279 - $table['data'] = array( array( "The internal data of table {$table_id} is corrupted." ) );
280 - // Mark table as corrupted.
163 + $table['data'] = array( array( "The internal data of table {$table_id} is corrupted." ) ); // Set a single cell as the cell content.
281 164 $table['is_corrupted'] = true;
282 -
283 - // If possible, try to find out what error prevented the JSON from being decoded.
284 - $table['json_error'] = 'The error could not be determined.';
285 - $json_error_msg = json_last_error_msg();
286 - if ( false !== $json_error_msg ) {
287 - $table['json_error'] = $json_error_msg;
288 - }
289 -
165 + $table['json_error'] = json_last_error_msg();
290 166 $table['description'] = "[ERROR] TABLE IS CORRUPTED (JSON error: {$table['json_error']})! DO NOT EDIT THIS TABLE NOW!\nInstead, please see https://tablepress.org/faq/corrupted-tables/ for instructions.\n-\n{$table['description']}";
291 167 } else {
292 168 // Specifically cast to an array again.
293 169 $table['data'] = (array) $table['data'];
@@ -303,11 +179,11 @@
303 179 *
304 180 * @param string $table_id Table ID.
305 181 * @param bool $load_data Whether the table data shall be loaded.
306 182 * @param bool $load_options_visibility Whether the table options and table visibility shall be loaded.
307 - * @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.
308 184 */
309 - 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 */ {
310 186 if ( empty( $table_id ) ) {
311 187 return new WP_Error( 'table_load_empty_table_id' );
312 188 }
313 189
@@ -335,11 +211,11 @@
335 211 * @since 1.0.0
336 212 *
337 213 * @param bool $prime_meta_cache Optional. Whether the prime the post meta cache when loading the posts.
338 214 * @param bool $run_filter Optional. Whether to run a filter on the list of table IDs.
339 - * @return array Array of table IDs.
215 + * @return string[] Array of table IDs.
340 216 */
341 - 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 {
342 218 $table_post = $this->tables->get( 'table_post' );
343 219 if ( empty( $table_post ) ) {
344 220 return array();
345 221 }
@@ -349,11 +225,13 @@
349 225
350 226 // This loop now uses the WP cache.
351 227 $table_ids = array();
352 228 foreach ( $table_post as $table_id => $post_id ) {
353 - $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 +
354 231 // Load table without data and options to save memory.
355 232 $table = $this->load( $table_id, false, false );
233 +
356 234 // Skip tables that could not be loaded properly.
357 235 if ( ! is_wp_error( $table ) ) {
358 236 $table_ids[] = $table_id;
359 237 }
@@ -360,13 +238,13 @@
360 238 }
361 239
362 240 if ( $run_filter ) {
363 241 /**
364 - * Filter all table IDs that are loaded.
242 + * Filters all table IDs that are loaded.
365 243 *
366 244 * @since 1.4.0
367 245 *
368 - * @param array $table_ids The table IDs that are loaded.
246 + * @param string[] $table_ids The table IDs that are loaded.
369 247 */
370 248 $table_ids = apply_filters( 'tablepress_load_all_tables', $table_ids );
371 249 }
372 250
@@ -377,12 +255,12 @@
377 255 * Sanitize the table to remove undesired HTML code using KSES.
378 256 *
379 257 * @since 1.8.0
380 258 *
381 - * @param array $table Table.
382 - * @return array Sanitized table.
259 + * @param array<string, mixed> $table Table.
260 + * @return array<string, mixed> Sanitized table.
383 261 */
384 - public function sanitize( array $table ) {
262 + public function sanitize( array $table ): array {
385 263 // Sanitize the table name and description.
386 264 $fields = array( 'name', 'description' );
387 265 foreach ( $fields as $field ) {
388 266 $table[ $field ] = wp_kses_post( $table[ $field ] );
@@ -390,9 +268,9 @@
390 268
391 269 // Sanitize each cell.
392 270 foreach ( $table['data'] as $row_idx => $row ) {
393 271 foreach ( $row as $column_idx => $cell_content ) {
394 - $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.
395 273 }
396 274 }
397 275
398 276 return $table;
@@ -398,55 +276,16 @@
398 276 return $table;
399 277 }
400 278
401 279 /**
402 - * Filter/modify the content of table cells and other fields, e.g. for security hardening.
403 - *
404 - * This is similar to the `sanitize()` method, but executed for all users.
405 - * 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.
406 - * Since 1.13.0, and on WP 5.6, only `rel="noopener"` is added. See https://core.trac.wordpress.org/ticket/49558.
407 - *
408 - * @since 1.10.0
409 - *
410 - * @param array $table Table.
411 - * @return array Filtered/modified table.
412 - */
413 - public function filter_content( array $table ) {
414 - /**
415 - * Filter whether the contents of table cells and fields should be filtered/modified.
416 - *
417 - * @since 1.10.0
418 - *
419 - * @param bool $filter Whether to filter the content of table cells and other fields. Default true.
420 - */
421 - if ( ! apply_filters( 'tablepress_filter_table_cell_content', true ) ) {
422 - return $table;
423 - }
424 -
425 - // Filter the table name and description.
426 - $fields = array( 'name', 'description' );
427 - foreach ( $fields as $field ) {
428 - $table[ $field ] = wp_targeted_link_rel( $table[ $field ] );
429 - }
430 -
431 - foreach ( $table['data'] as $row_idx => $row ) {
432 - foreach ( $row as $column_idx => $cell_content ) {
433 - $table['data'][ $row_idx ][ $column_idx ] = wp_targeted_link_rel( $cell_content );
434 - }
435 - }
436 -
437 - return $table;
438 - }
439 -
440 - /**
441 280 * Save a table.
442 281 *
443 282 * @since 1.0.0
444 283 *
445 - * @param array $table Table (needs to have $table['id']!).
284 + * @param array<string, mixed> $table Table (needs to have $table['id']!).
446 285 * @return string|WP_Error WP_Error on error, string table ID on success.
447 286 */
448 - public function save( array $table ) {
287 + public function save( array $table ) /* : string|WP_Error */ {
449 288 if ( empty( $table['id'] ) ) {
450 289 return new WP_Error( 'table_save_empty_table_id' );
451 290 }
452 291
@@ -457,11 +296,11 @@
457 296
458 297 $post = $this->_table_to_post( $table, $post_id );
459 298 $new_post_id = $this->model_post->update( $post );
460 299 if ( is_wp_error( $new_post_id ) ) {
461 - // Add an error code to the existing WP_Error.
462 - $new_post_id->add( 'table_save_post_update', '', $post_id );
463 - return $new_post_id;
300 + $error = new WP_Error( 'table_save_post_update', '', $post_id );
301 + $error->merge_from( $new_post_id );
302 + return $error;
464 303 }
465 304 if ( $post_id !== $new_post_id ) {
466 305 return new WP_Error( 'table_save_new_post_id_does_not_match', '', $new_post_id );
467 306 }
@@ -499,20 +338,20 @@
499 338 * Add a new table.
500 339 *
501 340 * @since 1.0.0
502 341 *
503 - * @param array $table Table ($table['id'] is not necessary).
504 - * @param string $copy_or_add Optional. 'copy' if the table is copied, 'add' if it is a new table. Default 'add'.
342 + * @param array<string, mixed> $table Table ($table['id'] is not necessary).
343 + * @param string $copy_or_add Optional. 'copy' if the table is copied, 'add' if it is a new table. Default 'add'.
505 344 * @return string|WP_Error WP_Error on error, string table ID of the new table on success.
506 345 */
507 - public function add( array $table, $copy_or_add = 'add' ) {
508 - $post_id = -1; // to insert table
346 + public function add( array $table, string $copy_or_add = 'add' ) /* : string|WP_Error */ {
347 + $post_id = -1; // To insert table.
509 348 $post = $this->_table_to_post( $table, $post_id );
510 349 $new_post_id = $this->model_post->insert( $post );
511 350 if ( is_wp_error( $new_post_id ) ) {
512 - // Add an error code to the existing WP_Error.
513 - $new_post_id->add( 'table_add_post_insert', '' );
514 - return $new_post_id;
351 + $error = new WP_Error( 'table_add_post_insert', '' );
352 + $error->merge_from( $new_post_id );
353 + return $error;
515 354 }
516 355
517 356 $options_saved = $this->_add_table_options( $new_post_id, $table['options'] );
518 357 if ( ! $options_saved ) {
@@ -549,14 +388,14 @@
549 388 *
550 389 * @param string $table_id ID of the table to be copied.
551 390 * @return string|WP_Error WP_Error on error, string table ID of the new table on success.
552 391 */
553 - public function copy( $table_id ) {
392 + public function copy( string $table_id ) /* : string|WP_Error */ {
554 393 $table = $this->load( $table_id, true, true );
555 394 if ( is_wp_error( $table ) ) {
556 - // Add an error code to the existing WP_Error.
557 - $table->add( 'table_copy_table_load', '', $table_id );
558 - return $table;
395 + $error = new WP_Error( 'table_copy_table_load', '', $table_id );
396 + $error->merge_from( $table );
397 + return $error;
559 398 }
560 399
561 400 // Adjust name of copied table.
562 401 if ( '' === trim( $table['name'] ) ) {
@@ -566,19 +405,19 @@
566 405
567 406 // Merge this data into an empty table template.
568 407 $table = $this->prepare_table( $this->get_table_template(), $table, false );
569 408 if ( is_wp_error( $table ) ) {
570 - // Add an error code to the existing WP_Error.
571 - $table->add( 'table_copy_table_prepare', '', $table_id );
572 - return $table;
409 + $error = new WP_Error( 'table_copy_table_prepare', '', $table_id );
410 + $error->merge_from( $table );
411 + return $error;
573 412 }
574 413
575 414 // Add the copied table.
576 415 $new_table_id = $this->add( $table, 'copy' );
577 416 if ( is_wp_error( $new_table_id ) ) {
578 - // Add an error code to the existing WP_Error.
579 - $new_table_id->add( 'table_copy_table_add', '', $table_id );
580 - return $new_table_id;
417 + $error = new WP_Error( 'table_copy_table_add', '', $table_id );
418 + $error->merge_from( $new_table_id );
419 + return $error;
581 420 }
582 421
583 422 /**
584 423 * Fires after an existing table has been copied.
@@ -600,14 +439,16 @@
600 439 *
601 440 * @param string $table_id ID of the table to be deleted.
602 441 * @return bool|WP_Error WP_Error on error, true on success.
603 442 */
604 - public function delete( $table_id ) {
443 + public function delete( string $table_id ) /* : true|WP_Error */ {
605 444 if ( ! $this->table_exists( $table_id ) ) {
606 445 return new WP_Error( 'table_delete_table_does_not_exist', '', $table_id );
607 446 }
608 447
609 448 $post_id = $this->_get_post_id( $table_id ); // No ! false check necessary, as this is covered by table_exists() check above.
449 +
450 + // @phpstan-ignore argument.type
610 451 $deleted = $this->model_post->delete( $post_id ); // Post Meta fields will be deleted automatically by that function.
611 452 if ( false === $deleted ) {
612 453 return new WP_Error( 'table_delete_post_could_not_be_deleted', '', $post_id );
613 454 }
@@ -636,9 +477,9 @@
636 477 * Delete all tables.
637 478 *
638 479 * @since 1.0.0
639 480 */
640 - public function delete_all() {
481 + public function delete_all(): void {
641 482 $tables = $this->tables->get();
642 483 if ( empty( $tables['table_post'] ) ) {
643 484 return;
644 485 }
@@ -643,11 +484,13 @@
643 484 return;
644 485 }
645 486
646 487 foreach ( $tables['table_post'] as $table_id => $post_id ) {
647 - $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 +
648 490 $this->model_post->delete( $post_id ); // Post Meta fields will be deleted automatically by that function.
649 491 unset( $tables['table_post'][ $table_id ] );
492 +
650 493 // Invalidate table output caches that belong to this table.
651 494 $this->invalidate_table_output_cache( $table_id );
652 495 }
653 496
@@ -670,9 +513,9 @@
670 513 *
671 514 * @param string $table_id Table ID.
672 515 * @return bool Whether the table ID exists.
673 516 */
674 - public function table_exists( $table_id ) {
517 + public function table_exists( string $table_id ): bool {
675 518 $table_post = $this->tables->get( 'table_post' );
676 519 return isset( $table_post[ $table_id ] );
677 520 }
678 521
@@ -681,11 +524,11 @@
681 524 *
682 525 * @since 1.0.0
683 526 *
684 527 * @param bool $single_value Optional. Whether to return just the number of tables from the list, or also count in the database.
685 - * @return int|array Number of Tables (if $single_value), or array of Numbers from list/DB (if ! $single_value).
528 + * @return int|array{list: int, db: int} Number of Tables (if $single_value), or array of Numbers from list/DB (if ! $single_value).
686 529 */
687 - public function count_tables( $single_value = true ) {
530 + public function count_tables( bool $single_value = true ) /* : int|array */ {
688 531 $count_list = count( $this->tables->get( 'table_post' ) );
689 532 if ( $single_value ) {
690 533 return $count_list;
691 534 }
@@ -704,9 +547,9 @@
704 547 * @since 1.8.0 Renamed from _invalidate_table_output_cache to invalidate_table_output_cache and made public.
705 548 *
706 549 * @param string $table_id Table ID.
707 550 */
708 - public function invalidate_table_output_cache( $table_id ) {
551 + public function invalidate_table_output_cache( string $table_id ): void {
709 552 $caches_list_transient_name = 'tablepress_c_' . md5( $table_id );
710 553 $caches_list = get_transient( $caches_list_transient_name );
711 554 if ( false !== $caches_list ) {
712 555 $caches_list = (array) json_decode( $caches_list, true );
@@ -717,15 +560,15 @@
717 560 delete_transient( $caches_list_transient_name );
718 561 }
719 562
720 563 /**
721 - * Flush the caches of the plugins W3 Total Cache, WP Super Cache, Cachify, and Quick Cache.
564 + * Flush the caches of common caching plugins.
722 565 *
723 566 * @since 1.0.0
724 567 */
725 - public function _flush_caching_plugins_caches() {
568 + public function _flush_caching_plugins_caches(): void {
726 569 /**
727 - * Filter whether the caches of common caching plugins shall be flushed.
570 + * Filters whether the caches of common caching plugins shall be flushed.
728 571 *
729 572 * @since 1.0.0
730 573 *
731 574 * @param bool $flush Whether caches of caching plugins shall be flushed. Default true.
@@ -733,26 +576,77 @@
733 576 if ( ! apply_filters( 'tablepress_flush_caching_plugins_caches', true ) ) {
734 577 return;
735 578 }
736 579
737 - // W3 Total Cache
738 - if ( function_exists( 'w3tc_pgcache_flush' ) ) {
739 - w3tc_pgcache_flush();
580 + // Common cache flush callback.
581 + $cache_flush_callbacks = array(
582 + array( 'Breeze_PurgeCache', 'breeze_cache_flush' ), // Breeze.
583 + array( 'comet_cache', 'clear' ), // Comet Cache.
584 + 'pantheon_wp_clear_edge_all', // Pantheon.
585 + 'sg_cachepress_purge_cache', // SG Optimizer.
586 + array( 'Swift_Performance_Cache', 'clear_all_cache' ), // Swift Performance.
587 + 'w3tc_pgcache_flush', // W3 Total Cache.
588 + array( 'WpeCommon', 'purge_memcached' ), // WP Engine.
589 + array( 'WpeCommon', 'clear_maxcdn_cache' ), // WP Engine.
590 + array( 'WpeCommon', 'purge_varnish_cache' ), // WP Engine.
591 + 'wpfc_clear_all_cache', // WP Fastest Cache.
592 + 'rocket_clean_domain', // WP Rocket.
593 + 'wp_cache_clear_cache', // WP Super Cache.
594 + array( 'zencache', 'clear' ), // Zen Cache.
595 + );
596 + foreach ( $cache_flush_callbacks as $cache_flush_callback ) {
597 + if ( is_callable( $cache_flush_callback ) ) {
598 + call_user_func( $cache_flush_callback );
599 + }
740 600 }
741 - // WP Super Cache
742 - if ( function_exists( 'wp_cache_clear_cache' ) ) {
743 - wp_cache_clear_cache();
601 +
602 + // Common cache flush hooks.
603 + $cache_flush_hooks = array(
604 + 'ce_clear_cache', // Cache Enabler.
605 + 'cachify_flush_cache', // Cachify.
606 + 'autoptimize_action_cachepurged', // Hyper Cache.
607 + );
608 + foreach ( $cache_flush_hooks as $cache_flush_hook ) {
609 + do_action( $cache_flush_hook );
744 610 }
745 - // Cachify
746 - do_action( 'cachify_flush_cache' );
747 - // Quick Cache
748 - if ( isset( $GLOBALS['quick_cache'] ) && method_exists( $GLOBALS['quick_cache'], 'clear_cache' ) ) {
749 - $GLOBALS['quick_cache']->clear_cache();
611 +
612 + // Kinsta.
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' ) ) ) {
614 + $GLOBALS['kinsta_cache']->kinsta_cache_purge->purge_complete_caches(); // @phpstan-ignore method.nonObject
750 615 }
751 - // WP Fastest Cache
752 - if ( isset( $GLOBALS['wp_fastest_cache'] ) && method_exists( $GLOBALS['wp_fastest_cache'], 'deleteCache' ) ) {
753 - $GLOBALS['wp_fastest_cache']->deleteCache();
616 + // LiteSpeed Cache.
617 + if ( is_callable( array( 'LiteSpeed_Cache_Tags', 'add_purge_tag' ) ) ) {
618 + LiteSpeed_Cache_Tags::add_purge_tag( '*' ); // @phpstan-ignore class.notFound
754 619 }
620 + // Pagely.
621 + if ( class_exists( 'PagelyCachePurge' ) ) {
622 + $_pagely = new PagelyCachePurge();
623 + if ( is_callable( array( $_pagely, 'purgeAll' ) ) ) {
624 + $_pagely->purgeAll();
625 + }
626 + }
627 + // Pressidum.
628 + if ( is_callable( array( 'Ninukis_Plugin', 'get_instance' ) ) ) {
629 + $_pressidum = Ninukis_Plugin::get_instance(); // @phpstan-ignore class.notFound
630 + if ( is_callable( array( $_pressidum, 'purgeAllCaches' ) ) ) {
631 + $_pressidum->purgeAllCaches(); // @phpstan-ignore method.nonObject
632 + }
633 + }
634 + // Savvii.
635 + if ( defined( '\Savvii\CacheFlusherPlugin::NAME_DOMAINFLUSH_NOW' ) ) {
636 + $_savvii = new \Savvii\CacheFlusherPlugin(); // @phpstan-ignore class.notFound
637 + if ( is_callable( array( $_savvii, 'domainflush' ) ) ) {
638 + $_savvii->domainflush(); // @phpstan-ignore class.notFound
639 + }
640 + }
641 + // WP Fastest Cache.
642 + if ( isset( $GLOBALS['wp_fastest_cache'] ) && is_callable( array( $GLOBALS['wp_fastest_cache'], 'deleteCache' ) ) ) {
643 + $GLOBALS['wp_fastest_cache']->deleteCache( true ); // @phpstan-ignore method.nonObject
644 + }
645 + // WP-Optimize.
646 + if ( function_exists( 'WP_Optimize' ) ) {
647 + WP_Optimize()->get_page_cache()->purge();
648 + }
755 649 }
756 650
757 651 /**
758 652 * Get the post ID of a given table ID (if the table ID exists).
@@ -761,15 +655,14 @@
761 655 *
762 656 * @param string $table_id Table ID.
763 657 * @return int|false Post ID on success, false on error.
764 658 */
765 - protected function _get_post_id( $table_id ) {
659 + protected function _get_post_id( string $table_id ) /* : int|false */ {
766 660 $table_post = $this->tables->get( 'table_post' );
767 - if ( isset( $table_post[ $table_id ] ) ) {
768 - return $table_post[ $table_id ];
769 - } else {
661 + if ( ! isset( $table_post[ $table_id ] ) ) {
770 662 return false;
771 663 }
664 + return $table_post[ $table_id ];
772 665 }
773 666
774 667 /**
775 668 * Update/Add a post ID for a given table ID, and sort the list of tables by their key in natural sort order.
@@ -776,11 +669,11 @@
776 669 *
777 670 * @since 1.0.0
778 671 *
779 672 * @param string $table_id Table ID.
780 - * @param int $post_id Post ID.
673 + * @param int $post_id Post ID.
781 674 */
782 - protected function _update_post_id( $table_id, $post_id ) {
675 + protected function _update_post_id( string $table_id, int $post_id ): void {
783 676 $tables = $this->tables->get();
784 677 $tables['table_post'][ $table_id ] = $post_id;
785 678 uksort( $tables['table_post'], 'strnatcasecmp' );
786 679 $this->tables->update( $tables );
@@ -792,9 +685,9 @@
792 685 * @since 1.0.0
793 686 *
794 687 * @param string $table_id Table ID.
795 688 */
796 - protected function _remove_post_id( $table_id ) {
689 + protected function _remove_post_id( string $table_id ): void {
797 690 $tables = $this->tables->get();
798 691 unset( $tables['table_post'][ $table_id ] );
799 692 $this->tables->update( $tables );
800 693 }
@@ -807,9 +700,9 @@
807 700 * @param string $old_id Old table ID.
808 701 * @param string $new_id New table ID.
809 702 * @return bool|WP_Error True on success, WP_Error on error.
810 703 */
811 - public function change_table_id( $old_id, $new_id ) {
704 + public function change_table_id( string $old_id, string $new_id ) /* : true|WP_Error */ {
812 705 $post_id = $this->_get_post_id( $old_id );
813 706 if ( false === $post_id ) {
814 707 return new WP_Error( 'table_change_id_no_post_id_for_table_id', '', $old_id );
815 708 }
@@ -845,16 +738,17 @@
845 738 * @since 1.0.0
846 739 *
847 740 * @return string Unused table ID (e.g. for a new table).
848 741 */
849 - protected function _get_new_table_id() {
742 + protected function _get_new_table_id(): string {
850 743 $tables = $this->tables->get();
851 744 // 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.
852 745 do {
853 - $tables['last_id'] ++;
854 - } while ( $this->table_exists( $tables['last_id'] ) );
746 + ++$tables['last_id'];
747 + $last_id_string = (string) $tables['last_id'];
748 + } while ( $this->table_exists( $last_id_string ) );
855 749 $this->tables->update( $tables );
856 - return (string) $tables['last_id'];
750 + return $last_id_string;
857 751 }
858 752
859 753 /**
860 754 * Get the template for an empty table.
@@ -862,24 +756,23 @@
862 756 * Important: This scheme is versioned via TablePress::table_scheme_version; changes likely need a version update!
863 757 *
864 758 * @since 1.0.0
865 759 *
866 - * @return array Empty table.
760 + * @return array<string, mixed> Empty table.
867 761 */
868 - public function get_table_template() {
762 + public function get_table_template(): array {
869 763 // Attention: Array keys have to be lowercase, to make it possible to match them with Shortcode attributes!
870 764 $table = array(
871 765 'id' => false,
872 766 'name' => '',
873 767 'description' => '',
874 - 'data' => array( array( '' ) ), // one empty cell
875 - // 'created' => wp_date( 'Y-m-d H:i:s' ),
768 + 'data' => array( array( '' ) ), // One empty cell.
876 769 'last_modified' => wp_date( 'Y-m-d H:i:s' ),
877 770 'author' => get_current_user_id(),
878 771 'options' => array(
879 772 'last_editor' => get_current_user_id(),
880 - 'table_head' => true,
881 - 'table_foot' => false,
773 + 'table_head' => 1,
774 + 'table_foot' => 0,
882 775 'alternating_row_colors' => true,
883 776 'row_hover' => true,
884 777 'print_name' => false,
885 778 'print_name_position' => 'above',
@@ -885,9 +778,9 @@
885 778 'print_name_position' => 'above',
886 779 'print_description' => false,
887 780 'print_description_position' => 'below',
888 781 'extra_css_classes' => '',
889 - // DataTables JavaScript library
782 + // DataTables JavaScript library.
890 783 'use_datatables' => true,
891 784 'datatables_sort' => true,
892 785 'datatables_filter' => true,
893 786 'datatables_paginate' => true,
@@ -897,18 +790,18 @@
897 790 'datatables_scrollx' => false,
898 791 'datatables_custom_commands' => '',
899 792 ),
900 793 'visibility' => array(
901 - 'rows' => array( 1 ), // one visbile row
902 - 'columns' => array( 1 ), // one visible column
794 + 'rows' => array( 1 ), // One visbile row.
795 + 'columns' => array( 1 ), // One visible column.
903 796 ),
904 797 );
905 798 /**
906 - * Filter the default template/structure of an empty table.
799 + * Filters the default template/structure of an empty table.
907 800 *
908 801 * @since 1.0.0
909 802 *
910 - * @param array $table Default template/structure of an empty table.
803 + * @param array<string, mixed> $table Default template/structure of an empty table.
911 804 */
912 805 return apply_filters( 'tablepress_table_template', $table );
913 806 }
914 807
@@ -918,33 +811,27 @@
918 811 * Performs consistency checks on data and visibility settings.
919 812 *
920 813 * @since 1.0.0
921 814 *
922 - * @param array $table Table to merge into.
923 - * @param array $new_table Table to merge.
924 - * @param bool $table_size_check Optional. Whether to check the number of rows and columns (e.g. not necessary for added or copied tables).
925 - * @return array|WP_Error Merged table on success, WP_Error on error.
815 + * @param array<string, mixed> $table Table to merge into.
816 + * @param array<string, mixed> $new_table Table to merge.
817 + * @param bool $table_size_check Optional. Whether to check the number of rows and columns (e.g. not necessary for added or copied tables).
818 + * @return array<string, mixed>|WP_Error Merged table on success, WP_Error on error.
926 819 */
927 - public function prepare_table( array $table, array $new_table, $table_size_check = true ) {
820 + public function prepare_table( array $table, array $new_table, bool $table_size_check = true ) /* : array|WP_Error */ {
928 821 // Table ID must be the same (if there was an ID already).
929 - if ( false !== $table['id'] ) {
930 - if ( $table['id'] !== $new_table['id'] ) {
931 - return new WP_Error( 'table_prepare_no_id_match', '', $new_table['id'] );
932 - }
822 + if ( false !== $table['id'] && $table['id'] !== $new_table['id'] ) {
823 + return new WP_Error( 'table_prepare_no_id_match', '', $new_table['id'] );
933 824 }
934 825
935 826 // Name, description, and data array need to exist, data must not be empty, the others could be ''.
936 - if ( ! isset( $new_table['name'] )
937 - || ! isset( $new_table['description'] )
938 - || empty( $new_table['data'] )
939 - || empty( $new_table['data'][0] ) ) {
827 + if ( ! isset( $new_table['name'], $new_table['description'] )
828 + || empty( $new_table['data'] ) || empty( $new_table['data'][0] ) ) {
940 829 return new WP_Error( 'table_prepare_name_description_or_data_not_set' );
941 830 }
942 831
943 832 // Visibility needs to exist.
944 - if ( ! isset( $new_table['visibility'] )
945 - || ! isset( $new_table['visibility']['rows'] )
946 - || ! isset( $new_table['visibility']['columns'] ) ) {
833 + if ( ! isset( $new_table['visibility']['rows'], $new_table['visibility']['columns'] ) ) {
947 834 return new WP_Error( 'table_prepare_visibility_not_set' );
948 835 }
949 836 $new_table['visibility']['rows'] = array_map( 'intval', $new_table['visibility']['rows'] );
950 837 $new_table['visibility']['columns'] = array_map( 'intval', $new_table['visibility']['columns'] );
@@ -950,11 +837,9 @@
950 837 $new_table['visibility']['columns'] = array_map( 'intval', $new_table['visibility']['columns'] );
951 838
952 839 // Check dimensions of table data array (not done for newly added, copied, or imported tables).
953 840 if ( $table_size_check ) {
954 - if ( empty( $new_table['number'] )
955 - || ! isset( $new_table['number']['rows'] )
956 - || ! isset( $new_table['number']['columns'] ) ) {
841 + if ( ! isset( $new_table['number']['rows'], $new_table['number']['columns'] ) ) {
957 842 return new WP_Error( 'table_prepare_size_check_numbers_not_set' );
958 843 }
959 844 // Table data needs to be ok, and have the correct number of rows and columns.
960 845 $new_table['number']['rows'] = (int) $new_table['number']['rows'];
@@ -974,18 +859,21 @@
974 859
975 860 // All checks were successful, replace original values with new ones.
976 861
977 862 // $table['id'] is either false (and remains false) or already equal to $new_table['id'].
978 - $table['new_id'] = isset( $new_table['new_id'] ) ? $new_table['new_id'] : $table['id'];
863 + $table['new_id'] = $new_table['new_id'] ?? $table['id'];
979 864 $table['name'] = $new_table['name'];
980 865 $table['description'] = $new_table['description'];
981 866 $table['data'] = $new_table['data'];
982 - // $table['author'] = get_current_user_id(); // We don't want this, as it would override the original author.
983 - // $table['created'] = wp_date( 'Y-m-d H:i:s' ); // We don't want this, as it would override the original datetime.
984 - $table['last_modified'] = wp_date( 'Y-m-d H:i:s' );
985 - $table['options']['last_editor'] = get_current_user_id();
867 + // Make sure that cells are stored as strings.
868 + array_walk_recursive(
869 + $table['data'],
870 + static function ( /* string|int|float|bool|null */ &$cell_content, int $col_idx ): void {
871 + $cell_content = (string) $cell_content;
872 + },
873 + );
986 874 // Table Options.
987 - if ( isset( $new_table['options'] ) ) { // is for example not set for newly added tables
875 + if ( isset( $new_table['options'] ) ) { // Options are for example not set for newly added tables.
988 876 // Specials check for certain options.
989 877 if ( isset( $new_table['options']['extra_css_classes'] ) ) {
990 878 $new_table['options']['extra_css_classes'] = explode( ' ', $new_table['options']['extra_css_classes'] );
991 879 $new_table['options']['extra_css_classes'] = array_map( array( 'TablePress', 'sanitize_css_class' ), $new_table['options']['extra_css_classes'] );
@@ -994,11 +882,20 @@
994 882 }
995 883 if ( isset( $new_table['options']['datatables_paginate_entries'] ) ) {
996 884 $new_table['options']['datatables_paginate_entries'] = (int) $new_table['options']['datatables_paginate_entries'];
997 885 if ( $new_table['options']['datatables_paginate_entries'] < 1 ) {
998 - $new_table['options']['datatables_paginate_entries'] = 10; // default value
886 + $new_table['options']['datatables_paginate_entries'] = 10; // Default value.
999 887 }
1000 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 +
1001 898 // Merge new options.
1002 899 $default_table = $this->get_table_template();
1003 900 $table['options'] = array_intersect_key( $table['options'], $default_table['options'] );
1004 901 $new_table['options'] = array_intersect_key( $new_table['options'], $default_table['options'] );
@@ -1006,11 +903,17 @@
1006 903 }
1007 904 // Table Visibility.
1008 905 $table['visibility']['rows'] = $new_table['visibility']['rows'];
1009 906 $table['visibility']['columns'] = $new_table['visibility']['columns'];
1010 - // Convert DataTables 1.9 parameters (Hungarian notation) to DataTables 1.10 parameters (camelCase notation).
907 +
908 + // $table['author'] = get_current_user_id(); // We don't want this, as it would override the original author.
909 + // $table['created'] = wp_date( 'Y-m-d H:i:s' ); // We don't want this, as it would override the original datetime.
910 + $table['last_modified'] = wp_date( 'Y-m-d H:i:s' );
911 + $table['options']['last_editor'] = get_current_user_id();
912 +
913 + // Convert CSS classes and some DataTables 1.x parameters to the DataTables 2 variants.
1011 914 if ( '' !== $table['options']['datatables_custom_commands'] ) {
1012 - $table['options']['datatables_custom_commands'] = strtr( $table['options']['datatables_custom_commands'], $this->datatables_parameter_mappings );
915 + $table['options']['datatables_custom_commands'] = TablePress::convert_datatables_api_data( $table['options']['datatables_custom_commands'] );
1013 916 }
1014 917
1015 918 return $table;
1016 919 }
@@ -1019,15 +922,15 @@
1019 922 * Save the table options of a table (in a post meta field of the table's post).
1020 923 *
1021 924 * @since 1.0.0
1022 925 *
1023 - * @param int $post_id Post ID.
1024 - * @param array $options Table options.
926 + * @param int $post_id Post ID.
927 + * @param array<string, mixed> $options Table options.
1025 928 * @return bool True on success, false on error.
1026 929 */
1027 - protected function _add_table_options( $post_id, array $options ) {
930 + protected function _add_table_options( int $post_id, array $options ): bool {
1028 931 $options = wp_json_encode( $options, TABLEPRESS_JSON_OPTIONS );
1029 - return $this->model_post->add_meta_field( $post_id, $this->table_options_field_name, $options );
932 + return $this->model_post->add_meta_field( $post_id, $this->table_options_field_name, $options ); // @phpstan-ignore argument.type
1030 933 }
1031 934
1032 935 /**
1033 936 * Update the table options of a table (in a post meta field in the table's post).
@@ -1033,15 +936,15 @@
1033 936 * Update the table options of a table (in a post meta field in the table's post).
1034 937 *
1035 938 * @since 1.0.0
1036 939 *
1037 - * @param int $post_id Post ID.
1038 - * @param array $options Table options.
940 + * @param int $post_id Post ID.
941 + * @param array<string, mixed> $options Table options.
1039 942 * @return bool True on success, false on error.
1040 943 */
1041 - protected function _update_table_options( $post_id, array $options ) {
944 + protected function _update_table_options( int $post_id, array $options ): bool {
1042 945 $options = wp_json_encode( $options, TABLEPRESS_JSON_OPTIONS );
1043 - return $this->model_post->update_meta_field( $post_id, $this->table_options_field_name, $options );
946 + return $this->model_post->update_meta_field( $post_id, $this->table_options_field_name, $options ); // @phpstan-ignore argument.type
1044 947 }
1045 948
1046 949 /**
1047 950 * Get the table options of a table (from a post meta field of the table's post).
@@ -1048,16 +951,22 @@
1048 951 *
1049 952 * @since 1.0.0
1050 953 *
1051 954 * @param int $post_id Post ID.
1052 - * @return array Table options on success, empty array on error.
955 + * @return array<string, mixed> Table options on success, empty array on error.
1053 956 */
1054 - protected function _get_table_options( $post_id ) {
957 + protected function _get_table_options( int $post_id ): array {
1055 958 $options = $this->model_post->get_meta_field( $post_id, $this->table_options_field_name );
1056 959 if ( empty( $options ) ) {
1057 960 return array();
1058 961 }
1059 - return (array) json_decode( $options, true );
962 + $options = (array) json_decode( $options, true );
963 +
964 + // Backward compatibility: Convert boolean "table_head" and "table_foot" options to integer.
965 + $options['table_head'] = absint( $options['table_head'] );
966 + $options['table_foot'] = absint( $options['table_foot'] );
967 +
968 + return $options;
1060 969 }
1061 970
1062 971 /**
1063 972 * Save the table visibility of a table (in a post meta field of the table's post).
@@ -1063,15 +972,15 @@
1063 972 * Save the table visibility of a table (in a post meta field of the table's post).
1064 973 *
1065 974 * @since 1.0.0
1066 975 *
1067 - * @param int $post_id Post ID.
1068 - * @param array $visibility Table visibility.
976 + * @param int $post_id Post ID.
977 + * @param array{rows: int[], columns: int[]} $visibility Table visibility.
1069 978 * @return bool True on success, false on error.
1070 979 */
1071 - protected function _add_table_visibility( $post_id, array $visibility ) {
980 + protected function _add_table_visibility( int $post_id, array $visibility ): bool {
1072 981 $visibility = wp_json_encode( $visibility, TABLEPRESS_JSON_OPTIONS );
1073 - return $this->model_post->add_meta_field( $post_id, $this->table_visibility_field_name, $visibility );
982 + return $this->model_post->add_meta_field( $post_id, $this->table_visibility_field_name, $visibility ); // @phpstan-ignore argument.type
1074 983 }
1075 984
1076 985 /**
1077 986 * Update the table visibility of a table (in a post meta field in the table's post).
@@ -1077,15 +986,15 @@
1077 986 * Update the table visibility of a table (in a post meta field in the table's post).
1078 987 *
1079 988 * @since 1.0.0
1080 989 *
1081 - * @param int $post_id Post ID.
1082 - * @param array $visibility Table visibility.
990 + * @param int $post_id Post ID.
991 + * @param array{rows: int[], columns: int[]} $visibility Table visibility.
1083 992 * @return bool True on success, false on error.
1084 993 */
1085 - protected function _update_table_visibility( $post_id, array $visibility ) {
994 + protected function _update_table_visibility( int $post_id, array $visibility ): bool {
1086 995 $visibility = wp_json_encode( $visibility, TABLEPRESS_JSON_OPTIONS );
1087 - return $this->model_post->update_meta_field( $post_id, $this->table_visibility_field_name, $visibility );
996 + return $this->model_post->update_meta_field( $post_id, $this->table_visibility_field_name, $visibility ); // @phpstan-ignore argument.type
1088 997 }
1089 998
1090 999 /**
1091 1000 * Get the table visibility of a table (from a post meta field of the table's post).
@@ -1092,14 +1001,17 @@
1092 1001 *
1093 1002 * @since 1.0.0
1094 1003 *
1095 1004 * @param int $post_id Post ID.
1096 - * @return array Table visibility on success, empty array on error.
1005 + * @return array{rows: int[], columns: int[]} Table visibility on success, empty array on error.
1097 1006 */
1098 - protected function _get_table_visibility( $post_id ) {
1007 + protected function _get_table_visibility( int $post_id ): array {
1099 1008 $visibility = $this->model_post->get_meta_field( $post_id, $this->table_visibility_field_name );
1100 1009 if ( empty( $visibility ) ) {
1101 - return array();
1010 + return array(
1011 + 'rows' => array(),
1012 + 'columns' => array(),
1013 + );
1102 1014 }
1103 1015 return json_decode( $visibility, true );
1104 1016 }
1105 1017
@@ -1108,10 +1020,13 @@
1108 1020 * remove (no longer) existing options, after a table scheme change,
1109 1021 * for all tables.
1110 1022 *
1111 1023 * @since 1.0.0
1024 + * @since 2.0.0 Add optional $remove_old_options parameter.
1025 + *
1026 + * @param bool $remove_old_options Optional. Whether old table options should be removed from the database. Default true.
1112 1027 */
1113 - public function merge_table_options_defaults() {
1028 + public function merge_table_options_defaults( bool $remove_old_options = true ): void {
1114 1029 $table_post = $this->tables->get( 'table_post' );
1115 1030 if ( empty( $table_post ) ) {
1116 1031 return;
1117 1032 }
@@ -1122,12 +1037,27 @@
1122 1037 // Get default Table with default Table Options.
1123 1038 $default_table = $this->get_table_template();
1124 1039
1125 1040 // Go through all tables (this loop now uses the WP cache).
1126 - foreach ( $table_post as $table_id => $post_id ) {
1041 + foreach ( $table_post as $post_id ) {
1127 1042 $table_options = $this->_get_table_options( $post_id );
1128 - // Remove old (i.e. no longer existing) Table Options.
1129 - $table_options = array_intersect_key( $table_options, $default_table['options'] );
1043 +
1044 + /**
1045 + * Filters the Table Options before they are merged with the default Table Options.
1046 + *
1047 + * @since 3.0.0
1048 + *
1049 + * @param array<string, mixed> $table_options Table Options.
1050 + * @param array<string, mixed> $default_table_options Default Table Options.
1051 + * @param bool $remove_old_options Whether old table options should be removed from the database.
1052 + * @param int $post_id Post ID of the table.
1053 + */
1054 + $table_options = apply_filters( 'tablepress_table_options_before_merge', $table_options, $default_table['options'], $remove_old_options, $post_id );
1055 +
1056 + if ( $remove_old_options ) {
1057 + // Remove old (i.e. no longer existing) Table Options.
1058 + $table_options = array_intersect_key( $table_options, $default_table['options'] );
1059 + }
1130 1060 // Merge current into new Table Options.
1131 1061 $table_options = array_merge( $default_table['options'], $table_options );
1132 1062 $this->_update_table_options( $post_id, $table_options );
1133 1063 }
@@ -1133,14 +1063,13 @@
1133 1063 }
1134 1064 }
1135 1065
1136 1066 /**
1137 - * Convert old parameter names to new ones in DataTables "Custom Commands".
1138 - * DataTables 1.9 used Hungarian notation, while DataTables 1.10+ (used since TablePress 1.5) uses camelCase notation.
1067 + * Updates all tables' "Custom Commands" to use DataTables 2 variants instead of old DataTables 1.x CSS classes and parameters
1139 1068 *
1140 - * @since 1.5.0
1069 + * @since 3.0.0
1141 1070 */
1142 - public function convert_datatables_parameter_names_tp15() {
1071 + public function update_custom_commands_datatables_tp30(): void {
1143 1072 $table_post = $this->tables->get( 'table_post' );
1144 1073 if ( empty( $table_post ) ) {
1145 1074 return;
1146 1075 }
@@ -1151,14 +1080,14 @@
1151 1080 foreach ( $table_post as $table_id => $post_id ) {
1152 1081 $table_options = $this->_get_table_options( $post_id );
1153 1082
1154 1083 // Nothing to do if there are no "Custom Commands".
1155 - if ( empty( $table_options['datatables_custom_commands'] ) ) {
1084 + if ( '' === $table_options['datatables_custom_commands'] ) {
1156 1085 continue;
1157 1086 }
1158 1087 // Run search/replace.
1159 1088 $old_custom_commands = $table_options['datatables_custom_commands'];
1160 - $table_options['datatables_custom_commands'] = strtr( $table_options['datatables_custom_commands'], $this->datatables_parameter_mappings );
1089 + $table_options['datatables_custom_commands'] = TablePress::convert_datatables_api_data( $table_options['datatables_custom_commands'] );
1161 1090 // No need to save (which runs a DB query) if nothing was replaced in the "Custom Commands".
1162 1091 if ( $old_custom_commands === $table_options['datatables_custom_commands'] ) {
1163 1092 continue;
1164 1093 }
@@ -1171,9 +1100,9 @@
1171 1100 * Invalidate all table output caches, e.g. after a plugin update.
1172 1101 *
1173 1102 * @since 1.0.0
1174 1103 */
1175 - public function invalidate_table_output_caches() {
1104 + public function invalidate_table_output_caches(): void {
1176 1105 $table_post = $this->tables->get( 'table_post' );
1177 1106 if ( empty( $table_post ) ) {
1178 1107 return;
1179 1108 }
@@ -1178,8 +1107,9 @@
1178 1107 return;
1179 1108 }
1180 1109
1181 1110 foreach ( $table_post as $table_id => $post_id ) {
1111 + $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.
1182 1112 $this->invalidate_table_output_cache( $table_id );
1183 1113 }
1184 1114 }
1185 1115
@@ -1190,11 +1120,11 @@
1190 1120 * @since 1.5.0
1191 1121 *
1192 1122 * @global wpdb $wpdb WordPress database abstraction object.
1193 1123 */
1194 - public function add_mime_type_to_posts() {
1124 + public function add_mime_type_to_posts(): void {
1195 1125 global $wpdb;
1196 - $wpdb->update( $wpdb->posts, array( 'post_mime_type' => 'application/json' ), array( 'post_type' => $this->model_post->get_post_type() ) );
1126 + $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
1197 1127 }
1198 1128
1199 1129 /**
1200 1130 * Add a table ID for a post (table) that was imported through the WP WXR importer to the table ID to post ID map.
@@ -1200,14 +1130,14 @@
1200 1130 * Add a table ID for a post (table) that was imported through the WP WXR importer to the table ID to post ID map.
1201 1131 *
1202 1132 * @since 1.5.0
1203 1133 *
1204 - * @param int $post_id Post ID of the imported post.
1205 - * @param int $original_post_id Original post ID that the post had on the site where it was exported from.
1206 - * @param array $postdata Post data that was imported into the database.
1207 - * @param array $post Original post data as it was exported.
1134 + * @param int|WP_Error $post_id Post ID of the imported post on success. 0 or WP_Error on failure.
1135 + * @param int $original_post_id Original post ID that the post had on the site where it was exported from.
1136 + * @param array<string, mixed> $postdata Post data that was imported into the database.
1137 + * @param array<string, mixed> $post Original post data as it was exported.
1208 1138 */
1209 - public function add_table_id_on_wp_import( $post_id, $original_post_id, array $postdata, array $post ) {
1139 + public function add_table_id_on_wp_import( /* int|WP_Error */ $post_id, int $original_post_id, array $postdata, array $post ): void {
1210 1140 // Bail if the post could not be imported or if the post is not a TablePress table.
1211 1141 if ( is_wp_error( $post_id ) || $this->model_post->get_post_type() !== $postdata['post_type'] ) {
1212 1142 return;
1213 1143 }
@@ -1226,9 +1156,9 @@
1226 1156
1227 1157 // Save the post ID for each of the table IDs.
1228 1158 $post_id_saved = false;
1229 1159 foreach ( $table_ids as $table_id ) {
1230 - $table_id = preg_replace( '/[^a-zA-Z0-9_-]/', '', $table_id );
1160 + $table_id = (string) preg_replace( '/[^a-zA-Z0-9_-]/', '', $table_id );
1231 1161 if ( '' === $table_id || $this->table_exists( $table_id ) ) {
1232 1162 continue;
1233 1163 }
1234 1164 $this->_update_post_id( $table_id, $post_id );
@@ -1246,14 +1176,14 @@
1246 1176 * Remove the `_tablepress_export_table_id` post meta field from the fields that are imported during a WP WXR import.
1247 1177 *
1248 1178 * @since 1.5.0
1249 1179 *
1250 - * @param array $postmeta Post meta fields for the post.
1251 - * @param int $post_id Post ID.
1252 - * @param array $post Post.
1253 - * @return array Modified post meta fields.
1180 + * @param array<string, mixed> $postmeta Post meta fields for the post.
1181 + * @param int $post_id Post ID.
1182 + * @param array<string, mixed> $post Post.
1183 + * @return array<string, mixed> Modified post meta fields.
1254 1184 */
1255 - public function prevent_table_id_post_meta_import_on_wp_import( array $postmeta, $post_id, array $post ) {
1185 + public function prevent_table_id_post_meta_import_on_wp_import( array $postmeta, int $post_id, array $post ): array {
1256 1186 // Bail if the post is not a TablePress table.
1257 1187 if ( $this->model_post->get_post_type() !== $post['post_type'] ) {
1258 1188 return $postmeta;
1259 1189 }
@@ -1278,10 +1208,11 @@
1278 1208 *
1279 1209 * @param bool $skip Whether to skip the current post meta. Default false.
1280 1210 * @param string $meta_key Current meta key.
1281 1211 * @param stdClass $meta Current meta object.
1212 + * @return bool Whether to skip the current post meta (unchanged $skip parameter).
1282 1213 */
1283 - public function add_table_id_to_wp_export( $skip, $meta_key, $meta ) {
1214 + public function add_table_id_to_wp_export( bool $skip, string $meta_key, stdClass $meta ): bool {
1284 1215 // Bail if the exporter doesn't process a TablePress table right now.
1285 1216 if ( $this->table_options_field_name !== $meta_key ) {
1286 1217 return $skip;
1287 1218 }
@@ -1296,17 +1227,19 @@
1296 1227 }
1297 1228
1298 1229 // Pretend that there is a `_tablepress_export_table_id` post meta field with the list of table IDs.
1299 1230 $key = '_tablepress_export_table_id';
1300 - $value = wxr_cdata( implode( ',', $table_ids ) );
1231 + $value = wxr_cdata( implode( ',', $table_ids ) ); // @phpstan-ignore function.notFound
1301 1232
1302 1233 // Hijack the filter and print extra XML code for our faked post meta field.
1234 + // phpcs:disable WordPress.Security.EscapeOutput.HeredocOutputNotEscaped
1303 1235 echo <<<WXR
1304 - <wp:postmeta>
1305 - <wp:meta_key>{$key}</wp:meta_key>
1306 - <wp:meta_value>{$value}</wp:meta_value>
1307 - </wp:postmeta>\n
1308 -WXR;
1236 + <wp:postmeta>
1237 + <wp:meta_key>{$key}</wp:meta_key>
1238 + <wp:meta_value>{$value}</wp:meta_value>
1239 + </wp:postmeta>\n
1240 + WXR;
1241 + // phpcs:enable
1309 1242
1310 1243 return $skip;
1311 1244 }
1312 1245
@@ -1314,9 +1247,9 @@
1314 1247 * Delete the WP_Option of the model.
1315 1248 *
1316 1249 * @since 1.0.0
1317 1250 */
1318 - public function destroy() {
1251 + public function destroy(): void {
1319 1252 $this->tables->delete();
1320 1253 }
1321 1254
1322 1255 } // class TablePress_Table_Model