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 +284 -350 1.123.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,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,17 @@
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 + // Convert CSS classes and some DataTables 1.x parameters to the DataTables 2 variants.
1010 914 if ( '' !== $table['options']['datatables_custom_commands'] ) {
1011 - $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'] );
1012 916 }
1013 917
1014 918 return $table;
1015 919 }
@@ -1018,15 +922,15 @@
1018 922 * Save the table options of a table (in a post meta field of the table's post).
1019 923 *
1020 924 * @since 1.0.0
1021 925 *
1022 - * @param int $post_id Post ID.
1023 - * @param array $options Table options.
926 + * @param int $post_id Post ID.
927 + * @param array<string, mixed> $options Table options.
1024 928 * @return bool True on success, false on error.
1025 929 */
1026 - protected function _add_table_options( $post_id, array $options ) {
930 + protected function _add_table_options( int $post_id, array $options ): bool {
1027 931 $options = wp_json_encode( $options, TABLEPRESS_JSON_OPTIONS );
1028 - 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
1029 933 }
1030 934
1031 935 /**
1032 936 * Update the table options of a table (in a post meta field in the table's post).
@@ -1032,15 +936,15 @@
1032 936 * Update the table options of a table (in a post meta field in the table's post).
1033 937 *
1034 938 * @since 1.0.0
1035 939 *
1036 - * @param int $post_id Post ID.
1037 - * @param array $options Table options.
940 + * @param int $post_id Post ID.
941 + * @param array<string, mixed> $options Table options.
1038 942 * @return bool True on success, false on error.
1039 943 */
1040 - protected function _update_table_options( $post_id, array $options ) {
944 + protected function _update_table_options( int $post_id, array $options ): bool {
1041 945 $options = wp_json_encode( $options, TABLEPRESS_JSON_OPTIONS );
1042 - 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
1043 947 }
1044 948
1045 949 /**
1046 950 * Get the table options of a table (from a post meta field of the table's post).
@@ -1047,16 +951,22 @@
1047 951 *
1048 952 * @since 1.0.0
1049 953 *
1050 954 * @param int $post_id Post ID.
1051 - * @return array Table options on success, empty array on error.
955 + * @return array<string, mixed> Table options on success, empty array on error.
1052 956 */
1053 - protected function _get_table_options( $post_id ) {
957 + protected function _get_table_options( int $post_id ): array {
1054 958 $options = $this->model_post->get_meta_field( $post_id, $this->table_options_field_name );
1055 959 if ( empty( $options ) ) {
1056 960 return array();
1057 961 }
1058 - 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;
1059 969 }
1060 970
1061 971 /**
1062 972 * Save the table visibility of a table (in a post meta field of the table's post).
@@ -1062,15 +972,15 @@
1062 972 * Save the table visibility of a table (in a post meta field of the table's post).
1063 973 *
1064 974 * @since 1.0.0
1065 975 *
1066 - * @param int $post_id Post ID.
1067 - * @param array $visibility Table visibility.
976 + * @param int $post_id Post ID.
977 + * @param array{rows: int[], columns: int[]} $visibility Table visibility.
1068 978 * @return bool True on success, false on error.
1069 979 */
1070 - protected function _add_table_visibility( $post_id, array $visibility ) {
980 + protected function _add_table_visibility( int $post_id, array $visibility ): bool {
1071 981 $visibility = wp_json_encode( $visibility, TABLEPRESS_JSON_OPTIONS );
1072 - 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
1073 983 }
1074 984
1075 985 /**
1076 986 * Update the table visibility of a table (in a post meta field in the table's post).
@@ -1076,15 +986,15 @@
1076 986 * Update the table visibility of a table (in a post meta field in the table's post).
1077 987 *
1078 988 * @since 1.0.0
1079 989 *
1080 - * @param int $post_id Post ID.
1081 - * @param array $visibility Table visibility.
990 + * @param int $post_id Post ID.
991 + * @param array{rows: int[], columns: int[]} $visibility Table visibility.
1082 992 * @return bool True on success, false on error.
1083 993 */
1084 - protected function _update_table_visibility( $post_id, array $visibility ) {
994 + protected function _update_table_visibility( int $post_id, array $visibility ): bool {
1085 995 $visibility = wp_json_encode( $visibility, TABLEPRESS_JSON_OPTIONS );
1086 - 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
1087 997 }
1088 998
1089 999 /**
1090 1000 * Get the table visibility of a table (from a post meta field of the table's post).
@@ -1091,14 +1001,17 @@
1091 1001 *
1092 1002 * @since 1.0.0
1093 1003 *
1094 1004 * @param int $post_id Post ID.
1095 - * @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.
1096 1006 */
1097 - protected function _get_table_visibility( $post_id ) {
1007 + protected function _get_table_visibility( int $post_id ): array {
1098 1008 $visibility = $this->model_post->get_meta_field( $post_id, $this->table_visibility_field_name );
1099 1009 if ( empty( $visibility ) ) {
1100 - return array();
1010 + return array(
1011 + 'rows' => array(),
1012 + 'columns' => array(),
1013 + );
1101 1014 }
1102 1015 return json_decode( $visibility, true );
1103 1016 }
1104 1017
@@ -1107,10 +1020,13 @@
1107 1020 * remove (no longer) existing options, after a table scheme change,
1108 1021 * for all tables.
1109 1022 *
1110 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.
1111 1027 */
1112 - public function merge_table_options_defaults() {
1028 + public function merge_table_options_defaults( bool $remove_old_options = true ): void {
1113 1029 $table_post = $this->tables->get( 'table_post' );
1114 1030 if ( empty( $table_post ) ) {
1115 1031 return;
1116 1032 }
@@ -1121,12 +1037,27 @@
1121 1037 // Get default Table with default Table Options.
1122 1038 $default_table = $this->get_table_template();
1123 1039
1124 1040 // Go through all tables (this loop now uses the WP cache).
1125 - foreach ( $table_post as $table_id => $post_id ) {
1041 + foreach ( $table_post as $post_id ) {
1126 1042 $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'] );
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 + }
1129 1060 // Merge current into new Table Options.
1130 1061 $table_options = array_merge( $default_table['options'], $table_options );
1131 1062 $this->_update_table_options( $post_id, $table_options );
1132 1063 }
@@ -1132,14 +1063,13 @@
1132 1063 }
1133 1064 }
1134 1065
1135 1066 /**
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.
1067 + * Updates all tables' "Custom Commands" to use DataTables 2 variants instead of old DataTables 1.x CSS classes and parameters
1138 1068 *
1139 - * @since 1.5.0
1069 + * @since 3.0.0
1140 1070 */
1141 - public function convert_datatables_parameter_names_tp15() {
1071 + public function update_custom_commands_datatables_tp30(): void {
1142 1072 $table_post = $this->tables->get( 'table_post' );
1143 1073 if ( empty( $table_post ) ) {
1144 1074 return;
1145 1075 }
@@ -1150,14 +1080,14 @@
1150 1080 foreach ( $table_post as $table_id => $post_id ) {
1151 1081 $table_options = $this->_get_table_options( $post_id );
1152 1082
1153 1083 // Nothing to do if there are no "Custom Commands".
1154 - if ( empty( $table_options['datatables_custom_commands'] ) ) {
1084 + if ( '' === $table_options['datatables_custom_commands'] ) {
1155 1085 continue;
1156 1086 }
1157 1087 // Run search/replace.
1158 1088 $old_custom_commands = $table_options['datatables_custom_commands'];
1159 - $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'] );
1160 1090 // No need to save (which runs a DB query) if nothing was replaced in the "Custom Commands".
1161 1091 if ( $old_custom_commands === $table_options['datatables_custom_commands'] ) {
1162 1092 continue;
1163 1093 }
@@ -1170,9 +1100,9 @@
1170 1100 * Invalidate all table output caches, e.g. after a plugin update.
1171 1101 *
1172 1102 * @since 1.0.0
1173 1103 */
1174 - public function invalidate_table_output_caches() {
1104 + public function invalidate_table_output_caches(): void {
1175 1105 $table_post = $this->tables->get( 'table_post' );
1176 1106 if ( empty( $table_post ) ) {
1177 1107 return;
1178 1108 }
@@ -1177,8 +1107,9 @@
1177 1107 return;
1178 1108 }
1179 1109
1180 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.
1181 1112 $this->invalidate_table_output_cache( $table_id );
1182 1113 }
1183 1114 }
1184 1115
@@ -1189,11 +1120,11 @@
1189 1120 * @since 1.5.0
1190 1121 *
1191 1122 * @global wpdb $wpdb WordPress database abstraction object.
1192 1123 */
1193 - public function add_mime_type_to_posts() {
1124 + public function add_mime_type_to_posts(): void {
1194 1125 global $wpdb;
1195 - $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
1196 1127 }
1197 1128
1198 1129 /**
1199 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.
@@ -1199,14 +1130,14 @@
1199 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 1131 *
1201 1132 * @since 1.5.0
1202 1133 *
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.
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.
1207 1138 */
1208 - 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 {
1209 1140 // Bail if the post could not be imported or if the post is not a TablePress table.
1210 1141 if ( is_wp_error( $post_id ) || $this->model_post->get_post_type() !== $postdata['post_type'] ) {
1211 1142 return;
1212 1143 }
@@ -1225,9 +1156,9 @@
1225 1156
1226 1157 // Save the post ID for each of the table IDs.
1227 1158 $post_id_saved = false;
1228 1159 foreach ( $table_ids as $table_id ) {
1229 - $table_id = preg_replace( '/[^a-zA-Z0-9_-]/', '', $table_id );
1160 + $table_id = (string) preg_replace( '/[^a-zA-Z0-9_-]/', '', $table_id );
1230 1161 if ( '' === $table_id || $this->table_exists( $table_id ) ) {
1231 1162 continue;
1232 1163 }
1233 1164 $this->_update_post_id( $table_id, $post_id );
@@ -1245,14 +1176,14 @@
1245 1176 * Remove the `_tablepress_export_table_id` post meta field from the fields that are imported during a WP WXR import.
1246 1177 *
1247 1178 * @since 1.5.0
1248 1179 *
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.
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.
1253 1184 */
1254 - 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 {
1255 1186 // Bail if the post is not a TablePress table.
1256 1187 if ( $this->model_post->get_post_type() !== $post['post_type'] ) {
1257 1188 return $postmeta;
1258 1189 }
@@ -1277,10 +1208,11 @@
1277 1208 *
1278 1209 * @param bool $skip Whether to skip the current post meta. Default false.
1279 1210 * @param string $meta_key Current meta key.
1280 1211 * @param stdClass $meta Current meta object.
1212 + * @return bool Whether to skip the current post meta (unchanged $skip parameter).
1281 1213 */
1282 - 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 {
1283 1215 // Bail if the exporter doesn't process a TablePress table right now.
1284 1216 if ( $this->table_options_field_name !== $meta_key ) {
1285 1217 return $skip;
1286 1218 }
@@ -1295,17 +1227,19 @@
1295 1227 }
1296 1228
1297 1229 // Pretend that there is a `_tablepress_export_table_id` post meta field with the list of table IDs.
1298 1230 $key = '_tablepress_export_table_id';
1299 - $value = wxr_cdata( implode( ',', $table_ids ) );
1231 + $value = wxr_cdata( implode( ',', $table_ids ) ); // @phpstan-ignore function.notFound
1300 1232
1301 1233 // Hijack the filter and print extra XML code for our faked post meta field.
1234 + // phpcs:disable WordPress.Security.EscapeOutput.HeredocOutputNotEscaped
1302 1235 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;
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
1308 1242
1309 1243 return $skip;
1310 1244 }
1311 1245
@@ -1313,9 +1247,9 @@
1313 1247 * Delete the WP_Option of the model.
1314 1248 *
1315 1249 * @since 1.0.0
1316 1250 */
1317 - public function destroy() {
1251 + public function destroy(): void {
1318 1252 $this->tables->delete();
1319 1253 }
1320 1254
1321 1255 } // class TablePress_Table_Model