PluginProbe
TablePress – Tables in WordPress made easy / 3.0.4
TablePress – Tables in WordPress made easy v3.0.4
3.3.4 3.3.3 3.3.2 3.3.1 trunk 1.12 1.14 1.9.2 2.0.4 2.1.7 2.1.8 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3 2.3.1 2.3.2 2.4 2.4.1 2.4.2 2.4.3 2.4.4 All 44 releases
← All changes | models/model-table.php +300 -349 1.123.0.4 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,54 +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 - *
407 - * @since 1.10.0
408 - *
409 - * @param array $table Table.
410 - * @return array Filtered/modified table.
411 - */
412 - public function filter_content( array $table ) {
413 - /**
414 - * Filter whether the contents of table cells and fields should be filtered/modified.
415 - *
416 - * @since 1.10.0
417 - *
418 - * @param bool $filter Whether to filter the content of table cells and other fields. Default true.
419 - */
420 - if ( ! apply_filters( 'tablepress_filter_table_cell_content', true ) ) {
421 - return;
422 - }
423 -
424 - // Filter the table name and description.
425 - $fields = array( 'name', 'description' );
426 - foreach ( $fields as $field ) {
427 - $table[ $field ] = wp_targeted_link_rel( $table[ $field ] );
428 - }
429 -
430 - foreach ( $table['data'] as $row_idx => $row ) {
431 - foreach ( $row as $column_idx => $cell_content ) {
432 - $table['data'][ $row_idx ][ $column_idx ] = wp_targeted_link_rel( $cell_content );
433 - }
434 - }
435 -
436 - return $table;
437 - }
438 -
439 - /**
440 280 * Save a table.
441 281 *
442 282 * @since 1.0.0
443 283 *
444 - * @param array $table Table (needs to have $table['id']!).
284 + * @param array<string, mixed> $table Table (needs to have $table['id']!).
445 285 * @return string|WP_Error WP_Error on error, string table ID on success.
446 286 */
447 - public function save( array $table ) {
287 + public function save( array $table ) /* : string|WP_Error */ {
448 288 if ( empty( $table['id'] ) ) {
449 289 return new WP_Error( 'table_save_empty_table_id' );
450 290 }
451 291
@@ -456,11 +296,11 @@
456 296
457 297 $post = $this->_table_to_post( $table, $post_id );
458 298 $new_post_id = $this->model_post->update( $post );
459 299 if ( is_wp_error( $new_post_id ) ) {
460 - // Add an error code to the existing WP_Error.
461 - $new_post_id->add( 'table_save_post_update', '', $post_id );
462 - 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;
463 303 }
464 304 if ( $post_id !== $new_post_id ) {
465 305 return new WP_Error( 'table_save_new_post_id_does_not_match', '', $new_post_id );
466 306 }
@@ -498,20 +338,20 @@
498 338 * Add a new table.
499 339 *
500 340 * @since 1.0.0
501 341 *
502 - * @param array $table Table ($table['id'] is not necessary).
503 - * @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'.
504 344 * @return string|WP_Error WP_Error on error, string table ID of the new table on success.
505 345 */
506 - public function add( array $table, $copy_or_add = 'add' ) {
507 - $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.
508 348 $post = $this->_table_to_post( $table, $post_id );
509 349 $new_post_id = $this->model_post->insert( $post );
510 350 if ( is_wp_error( $new_post_id ) ) {
511 - // Add an error code to the existing WP_Error.
512 - $new_post_id->add( 'table_add_post_insert', '' );
513 - return $new_post_id;
351 + $error = new WP_Error( 'table_add_post_insert', '' );
352 + $error->merge_from( $new_post_id );
353 + return $error;
514 354 }
515 355
516 356 $options_saved = $this->_add_table_options( $new_post_id, $table['options'] );
517 357 if ( ! $options_saved ) {
@@ -548,14 +388,14 @@
548 388 *
549 389 * @param string $table_id ID of the table to be copied.
550 390 * @return string|WP_Error WP_Error on error, string table ID of the new table on success.
551 391 */
552 - public function copy( $table_id ) {
392 + public function copy( string $table_id ) /* : string|WP_Error */ {
553 393 $table = $this->load( $table_id, true, true );
554 394 if ( is_wp_error( $table ) ) {
555 - // Add an error code to the existing WP_Error.
556 - $table->add( 'table_copy_table_load', '', $table_id );
557 - return $table;
395 + $error = new WP_Error( 'table_copy_table_load', '', $table_id );
396 + $error->merge_from( $table );
397 + return $error;
558 398 }
559 399
560 400 // Adjust name of copied table.
561 401 if ( '' === trim( $table['name'] ) ) {
@@ -565,19 +405,19 @@
565 405
566 406 // Merge this data into an empty table template.
567 407 $table = $this->prepare_table( $this->get_table_template(), $table, false );
568 408 if ( is_wp_error( $table ) ) {
569 - // Add an error code to the existing WP_Error.
570 - $table->add( 'table_copy_table_prepare', '', $table_id );
571 - return $table;
409 + $error = new WP_Error( 'table_copy_table_prepare', '', $table_id );
410 + $error->merge_from( $table );
411 + return $error;
572 412 }
573 413
574 414 // Add the copied table.
575 415 $new_table_id = $this->add( $table, 'copy' );
576 416 if ( is_wp_error( $new_table_id ) ) {
577 - // Add an error code to the existing WP_Error.
578 - $new_table_id->add( 'table_copy_table_add', '', $table_id );
579 - 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;
580 420 }
581 421
582 422 /**
583 423 * Fires after an existing table has been copied.
@@ -599,14 +439,16 @@
599 439 *
600 440 * @param string $table_id ID of the table to be deleted.
601 441 * @return bool|WP_Error WP_Error on error, true on success.
602 442 */
603 - public function delete( $table_id ) {
443 + public function delete( string $table_id ) /* : true|WP_Error */ {
604 444 if ( ! $this->table_exists( $table_id ) ) {
605 445 return new WP_Error( 'table_delete_table_does_not_exist', '', $table_id );
606 446 }
607 447
608 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
609 451 $deleted = $this->model_post->delete( $post_id ); // Post Meta fields will be deleted automatically by that function.
610 452 if ( false === $deleted ) {
611 453 return new WP_Error( 'table_delete_post_could_not_be_deleted', '', $post_id );
612 454 }
@@ -635,9 +477,9 @@
635 477 * Delete all tables.
636 478 *
637 479 * @since 1.0.0
638 480 */
639 - public function delete_all() {
481 + public function delete_all(): void {
640 482 $tables = $this->tables->get();
641 483 if ( empty( $tables['table_post'] ) ) {
642 484 return;
643 485 }
@@ -642,11 +484,13 @@
642 484 return;
643 485 }
644 486
645 487 foreach ( $tables['table_post'] as $table_id => $post_id ) {
646 - $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 +
647 490 $this->model_post->delete( $post_id ); // Post Meta fields will be deleted automatically by that function.
648 491 unset( $tables['table_post'][ $table_id ] );
492 +
649 493 // Invalidate table output caches that belong to this table.
650 494 $this->invalidate_table_output_cache( $table_id );
651 495 }
652 496
@@ -669,9 +513,9 @@
669 513 *
670 514 * @param string $table_id Table ID.
671 515 * @return bool Whether the table ID exists.
672 516 */
673 - public function table_exists( $table_id ) {
517 + public function table_exists( string $table_id ): bool {
674 518 $table_post = $this->tables->get( 'table_post' );
675 519 return isset( $table_post[ $table_id ] );
676 520 }
677 521
@@ -680,11 +524,11 @@
680 524 *
681 525 * @since 1.0.0
682 526 *
683 527 * @param bool $single_value Optional. Whether to return just the number of tables from the list, or also count in the database.
684 - * @return bool 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).
685 529 */
686 - public function count_tables( $single_value = true ) {
530 + public function count_tables( bool $single_value = true ) /* : int|array */ {
687 531 $count_list = count( $this->tables->get( 'table_post' ) );
688 532 if ( $single_value ) {
689 533 return $count_list;
690 534 }
@@ -703,9 +547,9 @@
703 547 * @since 1.8.0 Renamed from _invalidate_table_output_cache to invalidate_table_output_cache and made public.
704 548 *
705 549 * @param string $table_id Table ID.
706 550 */
707 - public function invalidate_table_output_cache( $table_id ) {
551 + public function invalidate_table_output_cache( string $table_id ): void {
708 552 $caches_list_transient_name = 'tablepress_c_' . md5( $table_id );
709 553 $caches_list = get_transient( $caches_list_transient_name );
710 554 if ( false !== $caches_list ) {
711 555 $caches_list = (array) json_decode( $caches_list, true );
@@ -716,15 +560,15 @@
716 560 delete_transient( $caches_list_transient_name );
717 561 }
718 562
719 563 /**
720 - * Flush the caches of the plugins W3 Total Cache, WP Super Cache, Cachify, and Quick Cache.
564 + * Flush the caches of common caching plugins.
721 565 *
722 566 * @since 1.0.0
723 567 */
724 - public function _flush_caching_plugins_caches() {
568 + public function _flush_caching_plugins_caches(): void {
725 569 /**
726 - * Filter whether the caches of common caching plugins shall be flushed.
570 + * Filters whether the caches of common caching plugins shall be flushed.
727 571 *
728 572 * @since 1.0.0
729 573 *
730 574 * @param bool $flush Whether caches of caching plugins shall be flushed. Default true.
@@ -732,26 +576,77 @@
732 576 if ( ! apply_filters( 'tablepress_flush_caching_plugins_caches', true ) ) {
733 577 return;
734 578 }
735 579
736 - // W3 Total Cache
737 - if ( function_exists( 'w3tc_pgcache_flush' ) ) {
738 - 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 + }
739 600 }
740 - // WP Super Cache
741 - if ( function_exists( 'wp_cache_clear_cache' ) ) {
742 - 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 );
743 610 }
744 - // Cachify
745 - do_action( 'cachify_flush_cache' );
746 - // Quick Cache
747 - if ( isset( $GLOBALS['quick_cache'] ) && method_exists( $GLOBALS['quick_cache'], 'clear_cache' ) ) {
748 - $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
749 615 }
750 - // WP Fastest Cache
751 - if ( isset( $GLOBALS['wp_fastest_cache'] ) && method_exists( $GLOBALS['wp_fastest_cache'], 'deleteCache' ) ) {
752 - $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
753 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 + }
754 649 }
755 650
756 651 /**
757 652 * Get the post ID of a given table ID (if the table ID exists).
@@ -758,17 +653,16 @@
758 653 *
759 654 * @since 1.0.0
760 655 *
761 656 * @param string $table_id Table ID.
762 - * @return int|bool Post ID on success, false on error.
657 + * @return int|false Post ID on success, false on error.
763 658 */
764 - protected function _get_post_id( $table_id ) {
659 + protected function _get_post_id( string $table_id ) /* : int|false */ {
765 660 $table_post = $this->tables->get( 'table_post' );
766 - if ( isset( $table_post[ $table_id ] ) ) {
767 - return $table_post[ $table_id ];
768 - } else {
661 + if ( ! isset( $table_post[ $table_id ] ) ) {
769 662 return false;
770 663 }
664 + return $table_post[ $table_id ];
771 665 }
772 666
773 667 /**
774 668 * Update/Add a post ID for a given table ID, and sort the list of tables by their key in natural sort order.
@@ -775,11 +669,11 @@
775 669 *
776 670 * @since 1.0.0
777 671 *
778 672 * @param string $table_id Table ID.
779 - * @param int $post_id Post ID.
673 + * @param int $post_id Post ID.
780 674 */
781 - protected function _update_post_id( $table_id, $post_id ) {
675 + protected function _update_post_id( string $table_id, int $post_id ): void {
782 676 $tables = $this->tables->get();
783 677 $tables['table_post'][ $table_id ] = $post_id;
784 678 uksort( $tables['table_post'], 'strnatcasecmp' );
785 679 $this->tables->update( $tables );
@@ -791,9 +685,9 @@
791 685 * @since 1.0.0
792 686 *
793 687 * @param string $table_id Table ID.
794 688 */
795 - protected function _remove_post_id( $table_id ) {
689 + protected function _remove_post_id( string $table_id ): void {
796 690 $tables = $this->tables->get();
797 691 unset( $tables['table_post'][ $table_id ] );
798 692 $this->tables->update( $tables );
799 693 }
@@ -806,9 +700,9 @@
806 700 * @param string $old_id Old table ID.
807 701 * @param string $new_id New table ID.
808 702 * @return bool|WP_Error True on success, WP_Error on error.
809 703 */
810 - public function change_table_id( $old_id, $new_id ) {
704 + public function change_table_id( string $old_id, string $new_id ) /* : true|WP_Error */ {
811 705 $post_id = $this->_get_post_id( $old_id );
812 706 if ( false === $post_id ) {
813 707 return new WP_Error( 'table_change_id_no_post_id_for_table_id', '', $old_id );
814 708 }
@@ -844,16 +738,17 @@
844 738 * @since 1.0.0
845 739 *
846 740 * @return string Unused table ID (e.g. for a new table).
847 741 */
848 - protected function _get_new_table_id() {
742 + protected function _get_new_table_id(): string {
849 743 $tables = $this->tables->get();
850 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.
851 745 do {
852 - $tables['last_id'] ++;
853 - } 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 ) );
854 749 $this->tables->update( $tables );
855 - return (string) $tables['last_id'];
750 + return $last_id_string;
856 751 }
857 752
858 753 /**
859 754 * Get the template for an empty table.
@@ -861,24 +756,23 @@
861 756 * Important: This scheme is versioned via TablePress::table_scheme_version; changes likely need a version update!
862 757 *
863 758 * @since 1.0.0
864 759 *
865 - * @return array Empty table.
760 + * @return array<string, mixed> Empty table.
866 761 */
867 - public function get_table_template() {
762 + public function get_table_template(): array {
868 763 // Attention: Array keys have to be lowercase, to make it possible to match them with Shortcode attributes!
869 764 $table = array(
870 765 'id' => false,
871 766 'name' => '',
872 767 'description' => '',
873 - 'data' => array( array( '' ) ), // one empty cell
874 - // 'created' => current_time( 'mysql' ),
875 - 'last_modified' => current_time( 'mysql' ),
768 + 'data' => array( array( '' ) ), // One empty cell.
769 + 'last_modified' => wp_date( 'Y-m-d H:i:s' ),
876 770 'author' => get_current_user_id(),
877 771 'options' => array(
878 772 'last_editor' => get_current_user_id(),
879 - 'table_head' => true,
880 - 'table_foot' => false,
773 + 'table_head' => 1,
774 + 'table_foot' => 0,
881 775 'alternating_row_colors' => true,
882 776 'row_hover' => true,
883 777 'print_name' => false,
884 778 'print_name_position' => 'above',
@@ -884,9 +778,9 @@
884 778 'print_name_position' => 'above',
885 779 'print_description' => false,
886 780 'print_description_position' => 'below',
887 781 'extra_css_classes' => '',
888 - // DataTables JavaScript library
782 + // DataTables JavaScript library.
889 783 'use_datatables' => true,
890 784 'datatables_sort' => true,
891 785 'datatables_filter' => true,
892 786 'datatables_paginate' => true,
@@ -896,18 +790,18 @@
896 790 'datatables_scrollx' => false,
897 791 'datatables_custom_commands' => '',
898 792 ),
899 793 'visibility' => array(
900 - 'rows' => array( 1 ), // one visbile row
901 - 'columns' => array( 1 ), // one visible column
794 + 'rows' => array( 1 ), // One visbile row.
795 + 'columns' => array( 1 ), // One visible column.
902 796 ),
903 797 );
904 798 /**
905 - * Filter the default template/structure of an empty table.
799 + * Filters the default template/structure of an empty table.
906 800 *
907 801 * @since 1.0.0
908 802 *
909 - * @param array $table Default template/structure of an empty table.
803 + * @param array<string, mixed> $table Default template/structure of an empty table.
910 804 */
911 805 return apply_filters( 'tablepress_table_template', $table );
912 806 }
913 807
@@ -917,33 +811,27 @@
917 811 * Performs consistency checks on data and visibility settings.
918 812 *
919 813 * @since 1.0.0
920 814 *
921 - * @param array $table Table to merge into.
922 - * @param array $new_table Table to merge.
923 - * @param bool $table_size_check Optional. Whether to check the number of rows and columns (e.g. not necessary for added or copied tables).
924 - * @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.
925 819 */
926 - 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 */ {
927 821 // Table ID must be the same (if there was an ID already).
928 - if ( false !== $table['id'] ) {
929 - if ( $table['id'] !== $new_table['id'] ) {
930 - return new WP_Error( 'table_prepare_no_id_match', '', $new_table['id'] );
931 - }
822 + if ( false !== $table['id'] && $table['id'] !== $new_table['id'] ) {
823 + return new WP_Error( 'table_prepare_no_id_match', '', $new_table['id'] );
932 824 }
933 825
934 826 // Name, description, and data array need to exist, data must not be empty, the others could be ''.
935 - if ( ! isset( $new_table['name'] )
936 - || ! isset( $new_table['description'] )
937 - || empty( $new_table['data'] )
938 - || empty( $new_table['data'][0] ) ) {
827 + if ( ! isset( $new_table['name'], $new_table['description'] )
828 + || empty( $new_table['data'] ) || empty( $new_table['data'][0] ) ) {
939 829 return new WP_Error( 'table_prepare_name_description_or_data_not_set' );
940 830 }
941 831
942 832 // Visibility needs to exist.
943 - if ( ! isset( $new_table['visibility'] )
944 - || ! isset( $new_table['visibility']['rows'] )
945 - || ! isset( $new_table['visibility']['columns'] ) ) {
833 + if ( ! isset( $new_table['visibility']['rows'], $new_table['visibility']['columns'] ) ) {
946 834 return new WP_Error( 'table_prepare_visibility_not_set' );
947 835 }
948 836 $new_table['visibility']['rows'] = array_map( 'intval', $new_table['visibility']['rows'] );
949 837 $new_table['visibility']['columns'] = array_map( 'intval', $new_table['visibility']['columns'] );
@@ -949,16 +837,14 @@
949 837 $new_table['visibility']['columns'] = array_map( 'intval', $new_table['visibility']['columns'] );
950 838
951 839 // Check dimensions of table data array (not done for newly added, copied, or imported tables).
952 840 if ( $table_size_check ) {
953 - if ( empty( $new_table['number'] )
954 - || ! isset( $new_table['number']['rows'] )
955 - || ! isset( $new_table['number']['columns'] ) ) {
841 + if ( ! isset( $new_table['number']['rows'], $new_table['number']['columns'] ) ) {
956 842 return new WP_Error( 'table_prepare_size_check_numbers_not_set' );
957 843 }
958 844 // Table data needs to be ok, and have the correct number of rows and columns.
959 - $new_table['number']['rows'] = intval( $new_table['number']['rows'] );
960 - $new_table['number']['columns'] = intval( $new_table['number']['columns'] );
845 + $new_table['number']['rows'] = (int) $new_table['number']['rows'];
846 + $new_table['number']['columns'] = (int) $new_table['number']['columns'];
961 847 if ( 0 === $new_table['number']['rows']
962 848 || 0 === $new_table['number']['columns']
963 849 || count( $new_table['data'] ) !== $new_table['number']['rows']
964 850 || count( $new_table['data'][0] ) !== $new_table['number']['columns'] ) {
@@ -973,18 +859,21 @@
973 859
974 860 // All checks were successful, replace original values with new ones.
975 861
976 862 // $table['id'] is either false (and remains false) or already equal to $new_table['id'].
977 - $table['new_id'] = isset( $new_table['new_id'] ) ? $new_table['new_id'] : $table['id'];
863 + $table['new_id'] = $new_table['new_id'] ?? $table['id'];
978 864 $table['name'] = $new_table['name'];
979 865 $table['description'] = $new_table['description'];
980 866 $table['data'] = $new_table['data'];
981 - // $table['author'] = get_current_user_id(); // We don't want this, as it would override the original author.
982 - // $table['created'] = current_time( 'mysql' ); // We don't want this, as it would override the original datetime.
983 - $table['last_modified'] = current_time( 'mysql' );
984 - $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 + );
985 874 // Table Options.
986 - 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.
987 876 // Specials check for certain options.
988 877 if ( isset( $new_table['options']['extra_css_classes'] ) ) {
989 878 $new_table['options']['extra_css_classes'] = explode( ' ', $new_table['options']['extra_css_classes'] );
990 879 $new_table['options']['extra_css_classes'] = array_map( array( 'TablePress', 'sanitize_css_class' ), $new_table['options']['extra_css_classes'] );
@@ -991,13 +880,22 @@
991 880 $new_table['options']['extra_css_classes'] = array_unique( $new_table['options']['extra_css_classes'] );
992 881 $new_table['options']['extra_css_classes'] = trim( implode( ' ', $new_table['options']['extra_css_classes'] ) );
993 882 }
994 883 if ( isset( $new_table['options']['datatables_paginate_entries'] ) ) {
995 - $new_table['options']['datatables_paginate_entries'] = intval( $new_table['options']['datatables_paginate_entries'] );
884 + $new_table['options']['datatables_paginate_entries'] = (int) $new_table['options']['datatables_paginate_entries'];
996 885 if ( $new_table['options']['datatables_paginate_entries'] < 1 ) {
997 - $new_table['options']['datatables_paginate_entries'] = 10; // default value
886 + $new_table['options']['datatables_paginate_entries'] = 10; // Default value.
998 887 }
999 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 +
1000 898 // Merge new options.
1001 899 $default_table = $this->get_table_template();
1002 900 $table['options'] = array_intersect_key( $table['options'], $default_table['options'] );
1003 901 $new_table['options'] = array_intersect_key( $new_table['options'], $default_table['options'] );
@@ -1005,11 +903,22 @@
1005 903 }
1006 904 // Table Visibility.
1007 905 $table['visibility']['rows'] = $new_table['visibility']['rows'];
1008 906 $table['visibility']['columns'] = $new_table['visibility']['columns'];
1009 - // 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 + // Prevent issues if the "Custom Commands" field is not set, e.g. when non-admins have previously edited the table.
914 + if ( ! isset( $table['options']['datatables_custom_commands'] ) ) {
915 + $table['options']['datatables_custom_commands'] = '';
916 + }
917 +
918 + // Convert CSS classes and some DataTables 1.x parameters to the DataTables 2 variants.
1010 919 if ( '' !== $table['options']['datatables_custom_commands'] ) {
1011 - $table['options']['datatables_custom_commands'] = strtr( $table['options']['datatables_custom_commands'], $this->datatables_parameter_mappings );
920 + $table['options']['datatables_custom_commands'] = TablePress::convert_datatables_api_data( $table['options']['datatables_custom_commands'] );
1012 921 }
1013 922
1014 923 return $table;
1015 924 }
@@ -1018,15 +927,15 @@
1018 927 * Save the table options of a table (in a post meta field of the table's post).
1019 928 *
1020 929 * @since 1.0.0
1021 930 *
1022 - * @param int $post_id Post ID.
1023 - * @param array $options Table options.
931 + * @param int $post_id Post ID.
932 + * @param array<string, mixed> $options Table options.
1024 933 * @return bool True on success, false on error.
1025 934 */
1026 - protected function _add_table_options( $post_id, array $options ) {
935 + protected function _add_table_options( int $post_id, array $options ): bool {
1027 936 $options = wp_json_encode( $options, TABLEPRESS_JSON_OPTIONS );
1028 - return $this->model_post->add_meta_field( $post_id, $this->table_options_field_name, $options );
937 + return $this->model_post->add_meta_field( $post_id, $this->table_options_field_name, $options ); // @phpstan-ignore argument.type
1029 938 }
1030 939
1031 940 /**
1032 941 * Update the table options of a table (in a post meta field in the table's post).
@@ -1032,15 +941,15 @@
1032 941 * Update the table options of a table (in a post meta field in the table's post).
1033 942 *
1034 943 * @since 1.0.0
1035 944 *
1036 - * @param int $post_id Post ID.
1037 - * @param array $options Table options.
945 + * @param int $post_id Post ID.
946 + * @param array<string, mixed> $options Table options.
1038 947 * @return bool True on success, false on error.
1039 948 */
1040 - protected function _update_table_options( $post_id, array $options ) {
949 + protected function _update_table_options( int $post_id, array $options ): bool {
1041 950 $options = wp_json_encode( $options, TABLEPRESS_JSON_OPTIONS );
1042 - return $this->model_post->update_meta_field( $post_id, $this->table_options_field_name, $options );
951 + return $this->model_post->update_meta_field( $post_id, $this->table_options_field_name, $options ); // @phpstan-ignore argument.type
1043 952 }
1044 953
1045 954 /**
1046 955 * Get the table options of a table (from a post meta field of the table's post).
@@ -1047,16 +956,22 @@
1047 956 *
1048 957 * @since 1.0.0
1049 958 *
1050 959 * @param int $post_id Post ID.
1051 - * @return array Table options on success, empty array on error.
960 + * @return array<string, mixed> Table options on success, empty array on error.
1052 961 */
1053 - protected function _get_table_options( $post_id ) {
962 + protected function _get_table_options( int $post_id ): array {
1054 963 $options = $this->model_post->get_meta_field( $post_id, $this->table_options_field_name );
1055 964 if ( empty( $options ) ) {
1056 965 return array();
1057 966 }
1058 - return (array) json_decode( $options, true );
967 + $options = (array) json_decode( $options, true );
968 +
969 + // Backward compatibility: Convert boolean "table_head" and "table_foot" options to integer.
970 + $options['table_head'] = absint( $options['table_head'] );
971 + $options['table_foot'] = absint( $options['table_foot'] );
972 +
973 + return $options;
1059 974 }
1060 975
1061 976 /**
1062 977 * Save the table visibility of a table (in a post meta field of the table's post).
@@ -1062,15 +977,15 @@
1062 977 * Save the table visibility of a table (in a post meta field of the table's post).
1063 978 *
1064 979 * @since 1.0.0
1065 980 *
1066 - * @param int $post_id Post ID.
1067 - * @param array $visibility Table visibility.
981 + * @param int $post_id Post ID.
982 + * @param array{rows: int[], columns: int[]} $visibility Table visibility.
1068 983 * @return bool True on success, false on error.
1069 984 */
1070 - protected function _add_table_visibility( $post_id, array $visibility ) {
985 + protected function _add_table_visibility( int $post_id, array $visibility ): bool {
1071 986 $visibility = wp_json_encode( $visibility, TABLEPRESS_JSON_OPTIONS );
1072 - return $this->model_post->add_meta_field( $post_id, $this->table_visibility_field_name, $visibility );
987 + return $this->model_post->add_meta_field( $post_id, $this->table_visibility_field_name, $visibility ); // @phpstan-ignore argument.type
1073 988 }
1074 989
1075 990 /**
1076 991 * Update the table visibility of a table (in a post meta field in the table's post).
@@ -1076,15 +991,15 @@
1076 991 * Update the table visibility of a table (in a post meta field in the table's post).
1077 992 *
1078 993 * @since 1.0.0
1079 994 *
1080 - * @param int $post_id Post ID.
1081 - * @param array $visibility Table visibility.
995 + * @param int $post_id Post ID.
996 + * @param array{rows: int[], columns: int[]} $visibility Table visibility.
1082 997 * @return bool True on success, false on error.
1083 998 */
1084 - protected function _update_table_visibility( $post_id, array $visibility ) {
999 + protected function _update_table_visibility( int $post_id, array $visibility ): bool {
1085 1000 $visibility = wp_json_encode( $visibility, TABLEPRESS_JSON_OPTIONS );
1086 - return $this->model_post->update_meta_field( $post_id, $this->table_visibility_field_name, $visibility );
1001 + return $this->model_post->update_meta_field( $post_id, $this->table_visibility_field_name, $visibility ); // @phpstan-ignore argument.type
1087 1002 }
1088 1003
1089 1004 /**
1090 1005 * Get the table visibility of a table (from a post meta field of the table's post).
@@ -1091,14 +1006,17 @@
1091 1006 *
1092 1007 * @since 1.0.0
1093 1008 *
1094 1009 * @param int $post_id Post ID.
1095 - * @return array Table visibility on success, empty array on error.
1010 + * @return array{rows: int[], columns: int[]} Table visibility on success, empty array on error.
1096 1011 */
1097 - protected function _get_table_visibility( $post_id ) {
1012 + protected function _get_table_visibility( int $post_id ): array {
1098 1013 $visibility = $this->model_post->get_meta_field( $post_id, $this->table_visibility_field_name );
1099 1014 if ( empty( $visibility ) ) {
1100 - return array();
1015 + return array(
1016 + 'rows' => array(),
1017 + 'columns' => array(),
1018 + );
1101 1019 }
1102 1020 return json_decode( $visibility, true );
1103 1021 }
1104 1022
@@ -1107,10 +1025,13 @@
1107 1025 * remove (no longer) existing options, after a table scheme change,
1108 1026 * for all tables.
1109 1027 *
1110 1028 * @since 1.0.0
1029 + * @since 2.0.0 Add optional $remove_old_options parameter.
1030 + *
1031 + * @param bool $remove_old_options Optional. Whether old table options should be removed from the database. Default true.
1111 1032 */
1112 - public function merge_table_options_defaults() {
1033 + public function merge_table_options_defaults( bool $remove_old_options = true ): void {
1113 1034 $table_post = $this->tables->get( 'table_post' );
1114 1035 if ( empty( $table_post ) ) {
1115 1036 return;
1116 1037 }
@@ -1121,12 +1042,27 @@
1121 1042 // Get default Table with default Table Options.
1122 1043 $default_table = $this->get_table_template();
1123 1044
1124 1045 // Go through all tables (this loop now uses the WP cache).
1125 - foreach ( $table_post as $table_id => $post_id ) {
1046 + foreach ( $table_post as $post_id ) {
1126 1047 $table_options = $this->_get_table_options( $post_id );
1127 - // Remove old (i.e. no longer existing) Table Options.
1128 - $table_options = array_intersect_key( $table_options, $default_table['options'] );
1048 +
1049 + /**
1050 + * Filters the Table Options before they are merged with the default Table Options.
1051 + *
1052 + * @since 3.0.0
1053 + *
1054 + * @param array<string, mixed> $table_options Table Options.
1055 + * @param array<string, mixed> $default_table_options Default Table Options.
1056 + * @param bool $remove_old_options Whether old table options should be removed from the database.
1057 + * @param int $post_id Post ID of the table.
1058 + */
1059 + $table_options = apply_filters( 'tablepress_table_options_before_merge', $table_options, $default_table['options'], $remove_old_options, $post_id );
1060 +
1061 + if ( $remove_old_options ) {
1062 + // Remove old (i.e. no longer existing) Table Options.
1063 + $table_options = array_intersect_key( $table_options, $default_table['options'] );
1064 + }
1129 1065 // Merge current into new Table Options.
1130 1066 $table_options = array_merge( $default_table['options'], $table_options );
1131 1067 $this->_update_table_options( $post_id, $table_options );
1132 1068 }
@@ -1132,14 +1068,13 @@
1132 1068 }
1133 1069 }
1134 1070
1135 1071 /**
1136 - * Convert old parameter names to new ones in DataTables "Custom Commands".
1137 - * DataTables 1.9 used Hungarian notation, while DataTables 1.10+ (used since TablePress 1.5) uses camelCase notation.
1072 + * Updates all tables' "Custom Commands" to use DataTables 2 variants instead of old DataTables 1.x CSS classes and parameters
1138 1073 *
1139 - * @since 1.5.0
1074 + * @since 3.0.0
1140 1075 */
1141 - public function convert_datatables_parameter_names_tp15() {
1076 + public function update_custom_commands_datatables_tp30(): void {
1142 1077 $table_post = $this->tables->get( 'table_post' );
1143 1078 if ( empty( $table_post ) ) {
1144 1079 return;
1145 1080 }
@@ -1149,15 +1084,22 @@
1149 1084
1150 1085 foreach ( $table_post as $table_id => $post_id ) {
1151 1086 $table_options = $this->_get_table_options( $post_id );
1152 1087
1088 + // Fix tables where the "Custom Commands" entry is missing entirely.
1089 + if ( ! isset( $table_options['datatables_custom_commands'] ) ) {
1090 + $table_options['datatables_custom_commands'] = '';
1091 + $this->_update_table_options( $post_id, $table_options );
1092 + continue;
1093 + }
1094 +
1153 1095 // Nothing to do if there are no "Custom Commands".
1154 - if ( empty( $table_options['datatables_custom_commands'] ) ) {
1096 + if ( '' === $table_options['datatables_custom_commands'] ) {
1155 1097 continue;
1156 1098 }
1157 1099 // Run search/replace.
1158 1100 $old_custom_commands = $table_options['datatables_custom_commands'];
1159 - $table_options['datatables_custom_commands'] = strtr( $table_options['datatables_custom_commands'], $this->datatables_parameter_mappings );
1101 + $table_options['datatables_custom_commands'] = TablePress::convert_datatables_api_data( $table_options['datatables_custom_commands'] );
1160 1102 // No need to save (which runs a DB query) if nothing was replaced in the "Custom Commands".
1161 1103 if ( $old_custom_commands === $table_options['datatables_custom_commands'] ) {
1162 1104 continue;
1163 1105 }
@@ -1170,9 +1112,9 @@
1170 1112 * Invalidate all table output caches, e.g. after a plugin update.
1171 1113 *
1172 1114 * @since 1.0.0
1173 1115 */
1174 - public function invalidate_table_output_caches() {
1116 + public function invalidate_table_output_caches(): void {
1175 1117 $table_post = $this->tables->get( 'table_post' );
1176 1118 if ( empty( $table_post ) ) {
1177 1119 return;
1178 1120 }
@@ -1177,8 +1119,9 @@
1177 1119 return;
1178 1120 }
1179 1121
1180 1122 foreach ( $table_post as $table_id => $post_id ) {
1123 + $table_id = (string) $table_id; // Ensure that the table ID is a string, as it comes from an array key where numeric strings are converted to integers.
1181 1124 $this->invalidate_table_output_cache( $table_id );
1182 1125 }
1183 1126 }
1184 1127
@@ -1189,11 +1132,11 @@
1189 1132 * @since 1.5.0
1190 1133 *
1191 1134 * @global wpdb $wpdb WordPress database abstraction object.
1192 1135 */
1193 - public function add_mime_type_to_posts() {
1136 + public function add_mime_type_to_posts(): void {
1194 1137 global $wpdb;
1195 - $wpdb->update( $wpdb->posts, array( 'post_mime_type' => 'application/json' ), array( 'post_type' => $this->model_post->get_post_type() ) );
1138 + $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
1196 1139 }
1197 1140
1198 1141 /**
1199 1142 * Add a table ID for a post (table) that was imported through the WP WXR importer to the table ID to post ID map.
@@ -1199,14 +1142,14 @@
1199 1142 * 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 1143 *
1201 1144 * @since 1.5.0
1202 1145 *
1203 - * @param int $post_id Post ID of the imported post.
1204 - * @param int $original_post_ID Original post ID that the post had on the site where it was exported from.
1205 - * @param array $postdata Post data that was imported into the database.
1206 - * @param array $post Original post data as it was exported.
1146 + * @param int|WP_Error $post_id Post ID of the imported post on success. 0 or WP_Error on failure.
1147 + * @param int $original_post_id Original post ID that the post had on the site where it was exported from.
1148 + * @param array<string, mixed> $postdata Post data that was imported into the database.
1149 + * @param array<string, mixed> $post Original post data as it was exported.
1207 1150 */
1208 - public function add_table_id_on_wp_import( $post_id, $original_post_ID, array $postdata, array $post ) {
1151 + public function add_table_id_on_wp_import( /* int|WP_Error */ $post_id, int $original_post_id, array $postdata, array $post ): void {
1209 1152 // Bail if the post could not be imported or if the post is not a TablePress table.
1210 1153 if ( is_wp_error( $post_id ) || $this->model_post->get_post_type() !== $postdata['post_type'] ) {
1211 1154 return;
1212 1155 }
@@ -1225,9 +1168,9 @@
1225 1168
1226 1169 // Save the post ID for each of the table IDs.
1227 1170 $post_id_saved = false;
1228 1171 foreach ( $table_ids as $table_id ) {
1229 - $table_id = preg_replace( '/[^a-zA-Z0-9_-]/', '', $table_id );
1172 + $table_id = (string) preg_replace( '/[^a-zA-Z0-9_-]/', '', $table_id );
1230 1173 if ( '' === $table_id || $this->table_exists( $table_id ) ) {
1231 1174 continue;
1232 1175 }
1233 1176 $this->_update_post_id( $table_id, $post_id );
@@ -1245,14 +1188,14 @@
1245 1188 * Remove the `_tablepress_export_table_id` post meta field from the fields that are imported during a WP WXR import.
1246 1189 *
1247 1190 * @since 1.5.0
1248 1191 *
1249 - * @param array $postmeta Post meta fields for the post.
1250 - * @param int $post_id Post ID.
1251 - * @param array $post Post.
1252 - * @return array Modified post meta fields.
1192 + * @param array<string, mixed> $postmeta Post meta fields for the post.
1193 + * @param int $post_id Post ID.
1194 + * @param array<string, mixed> $post Post.
1195 + * @return array<string, mixed> Modified post meta fields.
1253 1196 */
1254 - public function prevent_table_id_post_meta_import_on_wp_import( array $postmeta, $post_id, array $post ) {
1197 + public function prevent_table_id_post_meta_import_on_wp_import( array $postmeta, int $post_id, array $post ): array {
1255 1198 // Bail if the post is not a TablePress table.
1256 1199 if ( $this->model_post->get_post_type() !== $post['post_type'] ) {
1257 1200 return $postmeta;
1258 1201 }
@@ -1277,10 +1220,11 @@
1277 1220 *
1278 1221 * @param bool $skip Whether to skip the current post meta. Default false.
1279 1222 * @param string $meta_key Current meta key.
1280 1223 * @param stdClass $meta Current meta object.
1224 + * @return bool Whether to skip the current post meta (unchanged $skip parameter).
1281 1225 */
1282 - public function add_table_id_to_wp_export( $skip, $meta_key, $meta ) {
1226 + public function add_table_id_to_wp_export( bool $skip, string $meta_key, stdClass $meta ): bool {
1283 1227 // Bail if the exporter doesn't process a TablePress table right now.
1284 1228 if ( $this->table_options_field_name !== $meta_key ) {
1285 1229 return $skip;
1286 1230 }
@@ -1295,17 +1239,24 @@
1295 1239 }
1296 1240
1297 1241 // Pretend that there is a `_tablepress_export_table_id` post meta field with the list of table IDs.
1298 1242 $key = '_tablepress_export_table_id';
1243 +
1244 + /**
1245 + * Load WP export functions.
1246 + */
1247 + require_once ABSPATH . 'wp-admin/includes/export.php'; // @phpstan-ignore requireOnce.fileNotFound (This is a WordPress core file that always exists.)
1299 1248 $value = wxr_cdata( implode( ',', $table_ids ) );
1300 1249
1301 1250 // Hijack the filter and print extra XML code for our faked post meta field.
1251 + // phpcs:disable WordPress.Security.EscapeOutput.HeredocOutputNotEscaped
1302 1252 echo <<<WXR
1303 - <wp:postmeta>
1304 - <wp:meta_key>{$key}</wp:meta_key>
1305 - <wp:meta_value>{$value}</wp:meta_value>
1306 - </wp:postmeta>\n
1307 -WXR;
1253 + <wp:postmeta>
1254 + <wp:meta_key>{$key}</wp:meta_key>
1255 + <wp:meta_value>{$value}</wp:meta_value>
1256 + </wp:postmeta>\n
1257 + WXR;
1258 + // phpcs:enable
1308 1259
1309 1260 return $skip;
1310 1261 }
1311 1262
@@ -1313,9 +1264,9 @@
1313 1264 * Delete the WP_Option of the model.
1314 1265 *
1315 1266 * @since 1.0.0
1316 1267 */
1317 - public function destroy() {
1268 + public function destroy(): void {
1318 1269 $this->tables->delete();
1319 1270 }
1320 1271
1321 1272 } // class TablePress_Table_Model