PluginProbe
TablePress – Tables in WordPress made easy / 3.4
TablePress – Tables in WordPress made easy v3.4
3.4 3.3.4 3.3.3 3.3.2 3.3.1 trunk 1.12 1.14 1.9.2 2.0.4 2.1.7 2.1.8 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3 2.3.1 2.3.2 2.4 2.4.1 2.4.2 2.4.3 All 45 releases
← All changes | models/model-table.php +330 -427 1.9.2 → 3.4 View file →
@@ -7,13 +7,16 @@
7 7 * @author Tobias Bäthge
8 8 * @since 1.0.0
9 9 */
10 10
11 +declare(strict_types=1);
12 +
11 13 // Prohibit direct script loading.
12 14 defined( 'ABSPATH' ) || die( 'No direct script access allowed!' );
13 15
14 16 /**
15 17 * Table Model class
18 + *
16 19 * @package TablePress
17 20 * @subpackage Models
18 21 * @author Tobias Bäthge
19 22 * @since 1.0.0
@@ -23,38 +26,35 @@
23 26 /**
24 27 * Instance of the Post Type Model.
25 28 *
26 29 * @since 1.0.0
27 - * @var TablePress_Post_Model
28 30 */
29 - protected $model_post;
31 + protected \TablePress_Post_Model $model_post;
30 32
31 33 /**
32 34 * Name of the Post Meta Field for table options.
33 35 *
34 36 * @since 1.0.0
35 - * @var string
36 37 */
37 - protected $table_options_field_name = '_tablepress_table_options';
38 + protected string $table_options_field_name = '_tablepress_table_options';
38 39
39 40 /**
40 41 * Name of the Post Meta Field for table visibility.
41 42 *
42 43 * @since 1.0.0
43 - * @var string
44 44 */
45 - protected $table_visibility_field_name = '_tablepress_table_visibility';
45 + protected string $table_visibility_field_name = '_tablepress_table_visibility';
46 46
47 47 /**
48 48 * Default set of tables.
49 49 *
50 50 * @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).
51 + * @var array{last_id: int, table_post: array<string, int>} {
52 + * @type int $last_id Last table ID that was given to a new table.
53 + * @type array<string, int> $table_post Connections between table ID and post ID (key: table ID, value: post ID).
54 54 * }
55 55 */
56 - protected $default_tables = array(
56 + protected array $default_tables = array(
57 57 'last_id' => 0,
58 58 'table_post' => array(),
59 59 );
60 60
@@ -61,121 +61,12 @@
61 61 /**
62 62 * Instance of WP_Option class for the list of tables.
63 63 *
64 64 * @since 1.0.0
65 - * @var TablePress_WP_Option
66 65 */
67 - protected $tables;
66 + protected \TablePress_WP_Option $tables;
68 67
69 68 /**
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 69 * Init the Table model by instantiating a Post model and loading the list of tables option.
179 70 *
180 71 * @since 1.0.0
181 72 */
@@ -194,11 +85,11 @@
194 85 * Get the tables option, which holds the connection between table ID and post ID.
195 86 *
196 87 * @since 1.0.0
197 88 *
198 - * @return array Current set of tables.
89 + * @return mixed[] Current set of tables.
199 90 */
200 - public function _debug_get_tables() {
91 + public function _debug_get_tables(): array {
201 92 return $this->tables->get();
202 93 }
203 94
204 95 /**
@@ -205,11 +96,11 @@
205 96 * Update the tables option, which holds the connection between table ID and post ID.
206 97 *
207 98 * @since 1.0.0
208 99 *
209 - * @param array $tables New set of tables.
100 + * @param mixed[] $tables New set of tables.
210 101 */
211 - public function _debug_update_tables( array $tables ) {
102 + public function _debug_update_tables( array $tables ): void {
212 103 $this->tables->update( $tables );
213 104 }
214 105
215 106 /**
@@ -216,13 +107,13 @@
216 107 * Convert a table to a post, which can be stored in the database.
217 108 *
218 109 * @since 1.0.0
219 110 *
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.
111 + * @param array<string, mixed> $table Table.
112 + * @param int $post_id Post ID of an existing table, or -1 for a new table.
113 + * @return array<string, mixed> Post.
223 114 */
224 - protected function _table_to_post( array $table, $post_id ) {
115 + protected function _table_to_post( array $table, int $post_id ): array {
225 116 // Sanitize each cell, table name, and table description, if the user is not allowed to work with unfiltered HTML.
226 117 if ( ! current_user_can( 'unfiltered_html' ) ) {
227 118 $table = $this->sanitize( $table );
228 119 }
@@ -234,9 +125,8 @@
234 125
235 126 $post = array(
236 127 'ID' => $post_id,
237 128 'post_title' => $table['name'],
238 - // 'post_author' => $table['author'],
239 129 'post_excerpt' => $table['description'],
240 130 'post_content' => wp_json_encode( $table['data'], TABLEPRESS_JSON_OPTIONS ),
241 131 'post_mime_type' => 'application/json',
242 132 );
@@ -251,16 +141,16 @@
251 141 *
252 142 * @param WP_Post $post Post.
253 143 * @param string $table_id Table ID.
254 144 * @param bool $load_data Whether the table data shall be loaded.
255 - * @return array Table.
145 + * @return array<string, mixed> Table.
256 146 */
257 - protected function _post_to_table( $post, $table_id, $load_data ) {
147 + protected function _post_to_table( WP_Post $post, string $table_id, bool $load_data ): array {
258 148 $table = array(
259 149 'id' => $table_id,
260 150 'name' => $post->post_title,
261 151 'description' => $post->post_excerpt,
262 - 'author' => $post->post_author,
152 + 'author' => (int) $post->post_author,
263 153 // 'created' => $post->post_date,
264 154 'last_modified' => $post->post_modified,
265 155 );
266 156
@@ -271,20 +161,11 @@
271 161 $table['data'] = json_decode( $post->post_content, true );
272 162
273 163 // Check if JSON could be decoded.
274 164 if ( is_null( $table['data'] ) ) {
275 - // Set a single cell as the default.
276 - $table['data'] = array( array( "The internal data of table {$table_id} is corrupted." ) );
277 - // Mark table as corrupted.
165 + $table['data'] = array( array( "The internal data of table {$table_id} is corrupted." ) ); // Set a single cell as the cell content.
278 166 $table['is_corrupted'] = true;
279 -
280 - // If possible, try to find out what error prevented the JSON from being decoded.
281 - $table['json_error'] = 'The error could not be determined.';
282 - $json_error_msg = json_last_error_msg();
283 - if ( false !== $json_error_msg ) {
284 - $table['json_error'] = $json_error_msg;
285 - }
286 -
167 + $table['json_error'] = json_last_error_msg();
287 168 $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']}";
288 169 } else {
289 170 // Specifically cast to an array again.
290 171 $table['data'] = (array) $table['data'];
@@ -300,11 +181,11 @@
300 181 *
301 182 * @param string $table_id Table ID.
302 183 * @param bool $load_data Whether the table data shall be loaded.
303 184 * @param bool $load_options_visibility Whether the table options and table visibility shall be loaded.
304 - * @return array|WP_Error Table as an array on success, WP_Error on error.
185 + * @return array<string, mixed>|WP_Error Table as an array on success, WP_Error on error.
305 186 */
306 - public function load( $table_id, $load_data = true, $load_options_visibility = true ) {
187 + public function load( string $table_id, bool $load_data = true, bool $load_options_visibility = true ) /* : array|WP_Error */ {
307 188 if ( empty( $table_id ) ) {
308 189 return new WP_Error( 'table_load_empty_table_id' );
309 190 }
310 191
@@ -332,11 +213,11 @@
332 213 * @since 1.0.0
333 214 *
334 215 * @param bool $prime_meta_cache Optional. Whether the prime the post meta cache when loading the posts.
335 216 * @param bool $run_filter Optional. Whether to run a filter on the list of table IDs.
336 - * @return array Array of table IDs.
217 + * @return string[] Array of table IDs.
337 218 */
338 - public function load_all( $prime_meta_cache = true, $run_filter = true ) {
219 + public function load_all( bool $prime_meta_cache = true, bool $run_filter = true ): array {
339 220 $table_post = $this->tables->get( 'table_post' );
340 221 if ( empty( $table_post ) ) {
341 222 return array();
342 223 }
@@ -346,11 +227,13 @@
346 227
347 228 // This loop now uses the WP cache.
348 229 $table_ids = array();
349 230 foreach ( $table_post as $table_id => $post_id ) {
350 - $table_id = (string) $table_id;
231 + $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.
232 +
351 233 // Load table without data and options to save memory.
352 234 $table = $this->load( $table_id, false, false );
235 +
353 236 // Skip tables that could not be loaded properly.
354 237 if ( ! is_wp_error( $table ) ) {
355 238 $table_ids[] = $table_id;
356 239 }
@@ -357,13 +240,13 @@
357 240 }
358 241
359 242 if ( $run_filter ) {
360 243 /**
361 - * Filter all table IDs that are loaded.
244 + * Filters all table IDs that are loaded.
362 245 *
363 246 * @since 1.4.0
364 247 *
365 - * @param array $table_ids The table IDs that are loaded.
248 + * @param string[] $table_ids The table IDs that are loaded.
366 249 */
367 250 $table_ids = apply_filters( 'tablepress_load_all_tables', $table_ids );
368 251 }
369 252
@@ -374,15 +257,15 @@
374 257 * Sanitize the table to remove undesired HTML code using KSES.
375 258 *
376 259 * @since 1.8.0
377 260 *
378 - * @param array $table Table.
379 - * @return array Sanitized table.
261 + * @param array<string, mixed> $table Table.
262 + * @return array<string, mixed> Sanitized table.
380 263 */
381 - public function sanitize( array $table ) {
264 + public function sanitize( array $table ): array {
382 265 // Sanitize the table name and description.
383 - $fields_to_sanitize = array( 'name', 'description' );
384 - foreach ( $fields_to_sanitize as $field ) {
266 + $fields = array( 'name', 'description' );
267 + foreach ( $fields as $field ) {
385 268 $table[ $field ] = wp_kses_post( $table[ $field ] );
386 269 }
387 270
388 271 // Sanitize each cell.
@@ -387,9 +270,9 @@
387 270
388 271 // Sanitize each cell.
389 272 foreach ( $table['data'] as $row_idx => $row ) {
390 273 foreach ( $row as $column_idx => $cell_content ) {
391 - $table['data'][ $row_idx ][ $column_idx ] = wp_kses_post( $cell_content ); // equals wp_filter_post_kses(), but without the unncessary slashes handling
274 + $table['data'][ $row_idx ][ $column_idx ] = wp_kses_post( $cell_content ); // Equals wp_filter_post_kses(), but without the unnecessary slashes handling.
392 275 }
393 276 }
394 277
395 278 return $table;
@@ -399,16 +282,25 @@
399 282 * Save a table.
400 283 *
401 284 * @since 1.0.0
402 285 *
403 - * @param array $table Table (needs to have $table['id']!).
286 + * @param array<string, mixed> $table Table (needs to have $table['id']!).
404 287 * @return string|WP_Error WP_Error on error, string table ID on success.
405 288 */
406 - public function save( array $table ) {
289 + public function save( array $table ) /* : string|WP_Error */ {
407 290 if ( empty( $table['id'] ) ) {
408 291 return new WP_Error( 'table_save_empty_table_id' );
409 292 }
410 293
294 + /**
295 + * Fires before a table is saved.
296 + *
297 + * @since 3.1.0
298 + *
299 + * @param string $table_id ID of the table to be saved.
300 + */
301 + do_action( 'tablepress_event_pre_save_table', $table['id'] );
302 +
411 303 $post_id = $this->_get_post_id( $table['id'] );
412 304 if ( false === $post_id ) {
413 305 return new WP_Error( 'table_save_no_post_id_for_table_id', '', $table['id'] );
414 306 }
@@ -415,11 +307,11 @@
415 307
416 308 $post = $this->_table_to_post( $table, $post_id );
417 309 $new_post_id = $this->model_post->update( $post );
418 310 if ( is_wp_error( $new_post_id ) ) {
419 - // Add an error code to the existing WP_Error.
420 - $new_post_id->add( 'table_save_post_update', '', $post_id );
421 - return $new_post_id;
311 + $error = new WP_Error( 'table_save_post_update', '', $post_id );
312 + $error->merge_from( $new_post_id );
313 + return $error;
422 314 }
423 315 if ( $post_id !== $new_post_id ) {
424 316 return new WP_Error( 'table_save_new_post_id_does_not_match', '', $new_post_id );
425 317 }
@@ -445,9 +337,9 @@
445 337 * Fires after a table has been saved.
446 338 *
447 339 * @since 1.5.0
448 340 *
449 - * @param string $table_id ID of the added table.
341 + * @param string $table_id ID of the saved table.
450 342 */
451 343 do_action( 'tablepress_event_saved_table', $table['id'] );
452 344
453 345 return $table['id'];
@@ -457,20 +349,20 @@
457 349 * Add a new table.
458 350 *
459 351 * @since 1.0.0
460 352 *
461 - * @param array $table Table ($table['id'] is not necessary).
462 - * @param string $copy_or_add Optional. 'copy' if the table is copied, 'add' if it is a new table. Default 'add'.
353 + * @param array<string, mixed> $table Table ($table['id'] is not necessary).
354 + * @param string $copy_or_add Optional. 'copy' if the table is copied, 'add' if it is a new table. Default 'add'.
463 355 * @return string|WP_Error WP_Error on error, string table ID of the new table on success.
464 356 */
465 - public function add( array $table, $copy_or_add = 'add' ) {
466 - $post_id = -1; // to insert table
357 + public function add( array $table, string $copy_or_add = 'add' ) /* : string|WP_Error */ {
358 + $post_id = -1; // To insert table.
467 359 $post = $this->_table_to_post( $table, $post_id );
468 360 $new_post_id = $this->model_post->insert( $post );
469 361 if ( is_wp_error( $new_post_id ) ) {
470 - // Add an error code to the existing WP_Error.
471 - $new_post_id->add( 'table_add_post_insert', '' );
472 - return $new_post_id;
362 + $error = new WP_Error( 'table_add_post_insert', '' );
363 + $error->merge_from( $new_post_id );
364 + return $error;
473 365 }
474 366
475 367 $options_saved = $this->_add_table_options( $new_post_id, $table['options'] );
476 368 if ( ! $options_saved ) {
@@ -507,14 +399,14 @@
507 399 *
508 400 * @param string $table_id ID of the table to be copied.
509 401 * @return string|WP_Error WP_Error on error, string table ID of the new table on success.
510 402 */
511 - public function copy( $table_id ) {
403 + public function copy( string $table_id ) /* : string|WP_Error */ {
512 404 $table = $this->load( $table_id, true, true );
513 405 if ( is_wp_error( $table ) ) {
514 - // Add an error code to the existing WP_Error.
515 - $table->add( 'table_copy_table_load', '', $table_id );
516 - return $table;
406 + $error = new WP_Error( 'table_copy_table_load', '', $table_id );
407 + $error->merge_from( $table );
408 + return $error;
517 409 }
518 410
519 411 // Adjust name of copied table.
520 412 if ( '' === trim( $table['name'] ) ) {
@@ -524,19 +416,19 @@
524 416
525 417 // Merge this data into an empty table template.
526 418 $table = $this->prepare_table( $this->get_table_template(), $table, false );
527 419 if ( is_wp_error( $table ) ) {
528 - // Add an error code to the existing WP_Error.
529 - $table->add( 'table_copy_table_prepare', '', $table_id );
530 - return $table;
420 + $error = new WP_Error( 'table_copy_table_prepare', '', $table_id );
421 + $error->merge_from( $table );
422 + return $error;
531 423 }
532 424
533 425 // Add the copied table.
534 426 $new_table_id = $this->add( $table, 'copy' );
535 427 if ( is_wp_error( $new_table_id ) ) {
536 - // Add an error code to the existing WP_Error.
537 - $new_table_id->add( 'table_copy_table_add', '', $table_id );
538 - return $new_table_id;
428 + $error = new WP_Error( 'table_copy_table_add', '', $table_id );
429 + $error->merge_from( $new_table_id );
430 + return $error;
539 431 }
540 432
541 433 /**
542 434 * Fires after an existing table has been copied.
@@ -558,14 +450,25 @@
558 450 *
559 451 * @param string $table_id ID of the table to be deleted.
560 452 * @return bool|WP_Error WP_Error on error, true on success.
561 453 */
562 - public function delete( $table_id ) {
454 + public function delete( string $table_id ) /* : true|WP_Error */ {
563 455 if ( ! $this->table_exists( $table_id ) ) {
564 456 return new WP_Error( 'table_delete_table_does_not_exist', '', $table_id );
565 457 }
566 458
459 + /**
460 + * Fires before a table is deleted.
461 + *
462 + * @since 3.1.0
463 + *
464 + * @param string $table_id ID of the table to be deleted.
465 + */
466 + do_action( 'tablepress_event_pre_delete_table', $table_id );
467 +
567 468 $post_id = $this->_get_post_id( $table_id ); // No ! false check necessary, as this is covered by table_exists() check above.
469 +
470 + // @phpstan-ignore argument.type
568 471 $deleted = $this->model_post->delete( $post_id ); // Post Meta fields will be deleted automatically by that function.
569 472 if ( false === $deleted ) {
570 473 return new WP_Error( 'table_delete_post_could_not_be_deleted', '', $post_id );
571 474 }
@@ -594,9 +497,9 @@
594 497 * Delete all tables.
595 498 *
596 499 * @since 1.0.0
597 500 */
598 - public function delete_all() {
501 + public function delete_all(): void {
599 502 $tables = $this->tables->get();
600 503 if ( empty( $tables['table_post'] ) ) {
601 504 return;
602 505 }
@@ -601,11 +504,13 @@
601 504 return;
602 505 }
603 506
604 507 foreach ( $tables['table_post'] as $table_id => $post_id ) {
605 - $table_id = (string) $table_id;
508 + $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.
509 +
606 510 $this->model_post->delete( $post_id ); // Post Meta fields will be deleted automatically by that function.
607 511 unset( $tables['table_post'][ $table_id ] );
512 +
608 513 // Invalidate table output caches that belong to this table.
609 514 $this->invalidate_table_output_cache( $table_id );
610 515 }
611 516
@@ -628,9 +533,9 @@
628 533 *
629 534 * @param string $table_id Table ID.
630 535 * @return bool Whether the table ID exists.
631 536 */
632 - public function table_exists( $table_id ) {
537 + public function table_exists( string $table_id ): bool {
633 538 $table_post = $this->tables->get( 'table_post' );
634 539 return isset( $table_post[ $table_id ] );
635 540 }
636 541
@@ -639,11 +544,11 @@
639 544 *
640 545 * @since 1.0.0
641 546 *
642 547 * @param bool $single_value Optional. Whether to return just the number of tables from the list, or also count in the database.
643 - * @return bool int|array Number of Tables (if $single_value), or array of Numbers from list/DB (if ! $single_value).
548 + * @return int|array{list: int, db: int} Number of Tables (if $single_value), or array of Numbers from list/DB (if ! $single_value).
644 549 */
645 - public function count_tables( $single_value = true ) {
550 + public function count_tables( bool $single_value = true ) /* : int|array */ {
646 551 $count_list = count( $this->tables->get( 'table_post' ) );
647 552 if ( $single_value ) {
648 553 return $count_list;
649 554 }
@@ -662,9 +567,9 @@
662 567 * @since 1.8.0 Renamed from _invalidate_table_output_cache to invalidate_table_output_cache and made public.
663 568 *
664 569 * @param string $table_id Table ID.
665 570 */
666 - public function invalidate_table_output_cache( $table_id ) {
571 + public function invalidate_table_output_cache( string $table_id ): void {
667 572 $caches_list_transient_name = 'tablepress_c_' . md5( $table_id );
668 573 $caches_list = get_transient( $caches_list_transient_name );
669 574 if ( false !== $caches_list ) {
670 575 $caches_list = (array) json_decode( $caches_list, true );
@@ -675,15 +580,15 @@
675 580 delete_transient( $caches_list_transient_name );
676 581 }
677 582
678 583 /**
679 - * Flush the caches of the plugins W3 Total Cache, WP Super Cache, Cachify, and Quick Cache.
584 + * Flush the caches of common caching plugins.
680 585 *
681 586 * @since 1.0.0
682 587 */
683 - public function _flush_caching_plugins_caches() {
588 + public function _flush_caching_plugins_caches(): void {
684 589 /**
685 - * Filter whether the caches of common caching plugins shall be flushed.
590 + * Filters whether the caches of common caching plugins shall be flushed.
686 591 *
687 592 * @since 1.0.0
688 593 *
689 594 * @param bool $flush Whether caches of caching plugins shall be flushed. Default true.
@@ -691,26 +596,84 @@
691 596 if ( ! apply_filters( 'tablepress_flush_caching_plugins_caches', true ) ) {
692 597 return;
693 598 }
694 599
695 - // W3 Total Cache
696 - if ( function_exists( 'w3tc_pgcache_flush' ) ) {
697 - w3tc_pgcache_flush();
600 + // Common cache flush callback.
601 + $cache_flush_callbacks = array(
602 + array( 'Breeze_PurgeCache', 'breeze_cache_flush' ), // Breeze.
603 + array( 'comet_cache', 'clear' ), // Comet Cache.
604 + 'pantheon_wp_clear_edge_all', // Pantheon.
605 + 'sg_cachepress_purge_cache', // SG Optimizer.
606 + array( 'Swift_Performance_Cache', 'clear_all_cache' ), // Swift Performance.
607 + 'w3tc_pgcache_flush', // W3 Total Cache.
608 + array( 'WpeCommon', 'purge_memcached' ), // WP Engine.
609 + array( 'WpeCommon', 'clear_maxcdn_cache' ), // WP Engine.
610 + array( 'WpeCommon', 'purge_varnish_cache' ), // WP Engine.
611 + 'wpfc_clear_all_cache', // WP Fastest Cache.
612 + 'rocket_clean_domain', // WP Rocket.
613 + 'wp_cache_clear_cache', // WP Super Cache.
614 + array( 'zencache', 'clear' ), // Zen Cache.
615 + );
616 + foreach ( $cache_flush_callbacks as $cache_flush_callback ) {
617 + if ( is_callable( $cache_flush_callback ) ) {
618 + call_user_func( $cache_flush_callback );
619 + }
698 620 }
699 - // WP Super Cache
700 - if ( function_exists( 'wp_cache_clear_cache' ) ) {
701 - wp_cache_clear_cache();
621 +
622 + // Common cache flush hooks.
623 + $cache_flush_hooks = array(
624 + 'ce_clear_cache', // Cache Enabler.
625 + 'cachify_flush_cache', // Cachify.
626 + 'autoptimize_action_cachepurged', // Hyper Cache.
627 + );
628 + foreach ( $cache_flush_hooks as $cache_flush_hook ) {
629 + do_action( $cache_flush_hook );
702 630 }
703 - // Cachify
704 - do_action( 'cachify_flush_cache' );
705 - // Quick Cache
706 - if ( isset( $GLOBALS['quick_cache'] ) && method_exists( $GLOBALS['quick_cache'], 'clear_cache' ) ) {
707 - $GLOBALS['quick_cache']->clear_cache();
631 +
632 + // Cloudflare APO.
633 + if ( class_exists( '\Cloudflare\APO\WordPress\Hooks' ) ) {
634 + $_cloudflare = new \Cloudflare\APO\WordPress\Hooks();
635 + if ( is_callable( array( $_cloudflare, 'purgeCacheEverything' ) ) ) {
636 + $_cloudflare->purgeCacheEverything();
637 + }
708 638 }
709 - // WP Fastest Cache
710 - if ( isset( $GLOBALS['wp_fastest_cache'] ) && method_exists( $GLOBALS['wp_fastest_cache'], 'deleteCache' ) ) {
711 - $GLOBALS['wp_fastest_cache']->deleteCache();
639 + // Kinsta.
640 + if ( isset( $GLOBALS['kinsta_cache'] ) && ! empty( $GLOBALS['kinsta_cache']->kinsta_cache_purge ) && is_callable( array( $GLOBALS['kinsta_cache']->kinsta_cache_purge, 'purge_complete_caches' ) ) ) {
641 + $GLOBALS['kinsta_cache']->kinsta_cache_purge->purge_complete_caches(); // @phpstan-ignore method.nonObject
712 642 }
643 + // LiteSpeed Cache.
644 + if ( is_callable( array( 'LiteSpeed_Cache_Tags', 'add_purge_tag' ) ) ) {
645 + LiteSpeed_Cache_Tags::add_purge_tag( '*' ); // @phpstan-ignore class.notFound
646 + }
647 + // Pagely.
648 + if ( class_exists( 'PagelyCachePurge' ) ) {
649 + $_pagely = new PagelyCachePurge();
650 + if ( is_callable( array( $_pagely, 'purgeAll' ) ) ) {
651 + $_pagely->purgeAll();
652 + }
653 + }
654 + // Pressidium.
655 + if ( is_callable( array( 'Ninukis_Plugin', 'get_instance' ) ) ) {
656 + $_pressidum = Ninukis_Plugin::get_instance(); // @phpstan-ignore class.notFound
657 + if ( is_callable( array( $_pressidum, 'purgeAllCaches' ) ) ) {
658 + $_pressidum->purgeAllCaches(); // @phpstan-ignore method.nonObject
659 + }
660 + }
661 + // Savvii.
662 + if ( defined( '\Savvii\CacheFlusherPlugin::NAME_DOMAINFLUSH_NOW' ) ) {
663 + $_savvii = new \Savvii\CacheFlusherPlugin(); // @phpstan-ignore class.notFound
664 + if ( is_callable( array( $_savvii, 'domainflush' ) ) ) {
665 + $_savvii->domainflush(); // @phpstan-ignore class.notFound
666 + }
667 + }
668 + // WP Fastest Cache.
669 + if ( isset( $GLOBALS['wp_fastest_cache'] ) && is_callable( array( $GLOBALS['wp_fastest_cache'], 'deleteCache' ) ) ) {
670 + $GLOBALS['wp_fastest_cache']->deleteCache( true ); // @phpstan-ignore method.nonObject
671 + }
672 + // WP-Optimize.
673 + if ( function_exists( 'WP_Optimize' ) ) {
674 + WP_Optimize()->get_page_cache()->purge();
675 + }
713 676 }
714 677
715 678 /**
716 679 * Get the post ID of a given table ID (if the table ID exists).
@@ -717,17 +680,16 @@
717 680 *
718 681 * @since 1.0.0
719 682 *
720 683 * @param string $table_id Table ID.
721 - * @return int|bool Post ID on success, false on error.
684 + * @return int|false Post ID on success, false on error.
722 685 */
723 - protected function _get_post_id( $table_id ) {
686 + protected function _get_post_id( string $table_id ) /* : int|false */ {
724 687 $table_post = $this->tables->get( 'table_post' );
725 - if ( isset( $table_post[ $table_id ] ) ) {
726 - return $table_post[ $table_id ];
727 - } else {
688 + if ( ! isset( $table_post[ $table_id ] ) ) {
728 689 return false;
729 690 }
691 + return $table_post[ $table_id ];
730 692 }
731 693
732 694 /**
733 695 * Update/Add a post ID for a given table ID, and sort the list of tables by their key in natural sort order.
@@ -734,11 +696,11 @@
734 696 *
735 697 * @since 1.0.0
736 698 *
737 699 * @param string $table_id Table ID.
738 - * @param int $post_id Post ID.
700 + * @param int $post_id Post ID.
739 701 */
740 - protected function _update_post_id( $table_id, $post_id ) {
702 + protected function _update_post_id( string $table_id, int $post_id ): void {
741 703 $tables = $this->tables->get();
742 704 $tables['table_post'][ $table_id ] = $post_id;
743 705 uksort( $tables['table_post'], 'strnatcasecmp' );
744 706 $this->tables->update( $tables );
@@ -750,9 +712,9 @@
750 712 * @since 1.0.0
751 713 *
752 714 * @param string $table_id Table ID.
753 715 */
754 - protected function _remove_post_id( $table_id ) {
716 + protected function _remove_post_id( string $table_id ): void {
755 717 $tables = $this->tables->get();
756 718 unset( $tables['table_post'][ $table_id ] );
757 719 $this->tables->update( $tables );
758 720 }
@@ -765,9 +727,9 @@
765 727 * @param string $old_id Old table ID.
766 728 * @param string $new_id New table ID.
767 729 * @return bool|WP_Error True on success, WP_Error on error.
768 730 */
769 - public function change_table_id( $old_id, $new_id ) {
731 + public function change_table_id( string $old_id, string $new_id ) /* : true|WP_Error */ {
770 732 $post_id = $this->_get_post_id( $old_id );
771 733 if ( false === $post_id ) {
772 734 return new WP_Error( 'table_change_id_no_post_id_for_table_id', '', $old_id );
773 735 }
@@ -803,16 +765,17 @@
803 765 * @since 1.0.0
804 766 *
805 767 * @return string Unused table ID (e.g. for a new table).
806 768 */
807 - protected function _get_new_table_id() {
769 + protected function _get_new_table_id(): string {
808 770 $tables = $this->tables->get();
809 771 // 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.
810 772 do {
811 - $tables['last_id'] ++;
812 - } while ( $this->table_exists( $tables['last_id'] ) );
773 + ++$tables['last_id'];
774 + $last_id_string = (string) $tables['last_id'];
775 + } while ( $this->table_exists( $last_id_string ) );
813 776 $this->tables->update( $tables );
814 - return (string) $tables['last_id'];
777 + return $last_id_string;
815 778 }
816 779
817 780 /**
818 781 * Get the template for an empty table.
@@ -820,24 +783,23 @@
820 783 * Important: This scheme is versioned via TablePress::table_scheme_version; changes likely need a version update!
821 784 *
822 785 * @since 1.0.0
823 786 *
824 - * @return array Empty table.
787 + * @return array<string, mixed> Empty table.
825 788 */
826 - public function get_table_template() {
789 + public function get_table_template(): array {
827 790 // Attention: Array keys have to be lowercase, to make it possible to match them with Shortcode attributes!
828 791 $table = array(
829 792 'id' => false,
830 793 'name' => '',
831 794 'description' => '',
832 - 'data' => array( array( '' ) ), // one empty cell
833 - // 'created' => current_time( 'mysql' ),
834 - 'last_modified' => current_time( 'mysql' ),
795 + 'data' => array( array( '' ) ), // One empty cell.
796 + 'last_modified' => wp_date( 'Y-m-d H:i:s' ),
835 797 'author' => get_current_user_id(),
836 798 'options' => array(
837 799 'last_editor' => get_current_user_id(),
838 - 'table_head' => true,
839 - 'table_foot' => false,
800 + 'table_head' => 1,
801 + 'table_foot' => 0,
840 802 'alternating_row_colors' => true,
841 803 'row_hover' => true,
842 804 'print_name' => false,
843 805 'print_name_position' => 'above',
@@ -843,9 +805,9 @@
843 805 'print_name_position' => 'above',
844 806 'print_description' => false,
845 807 'print_description_position' => 'below',
846 808 'extra_css_classes' => '',
847 - // DataTables JavaScript library
809 + // DataTables JavaScript library.
848 810 'use_datatables' => true,
849 811 'datatables_sort' => true,
850 812 'datatables_filter' => true,
851 813 'datatables_paginate' => true,
@@ -855,18 +817,18 @@
855 817 'datatables_scrollx' => false,
856 818 'datatables_custom_commands' => '',
857 819 ),
858 820 'visibility' => array(
859 - 'rows' => array( 1 ), // one visbile row
860 - 'columns' => array( 1 ), // one visible column
821 + 'rows' => array( 1 ), // One visible row.
822 + 'columns' => array( 1 ), // One visible column.
861 823 ),
862 824 );
863 825 /**
864 - * Filter the default template/structure of an empty table.
826 + * Filters the default template/structure of an empty table.
865 827 *
866 828 * @since 1.0.0
867 829 *
868 - * @param array $table Default template/structure of an empty table.
830 + * @param array<string, mixed> $table Default template/structure of an empty table.
869 831 */
870 832 return apply_filters( 'tablepress_table_template', $table );
871 833 }
872 834
@@ -876,34 +838,27 @@
876 838 * Performs consistency checks on data and visibility settings.
877 839 *
878 840 * @since 1.0.0
879 841 *
880 - * @param array $table Table to merge into.
881 - * @param array $new_table Table to merge.
882 - * @param bool $table_size_check Optional. Whether to check the number of rows and columns (e.g. not necessary for added or copied tables).
883 - * @param bool $extended_visibility_check Optional. Whether to check the counts of hidden rows and columns (only possible for Admin_AJAX controller as of now).
884 - * @return array|WP_Error Merged table on success, WP_Error on error.
842 + * @param array<string, mixed> $table Table to merge into.
843 + * @param array<string, mixed> $new_table Table to merge.
844 + * @param bool $table_size_check Optional. Whether to check the number of rows and columns (e.g. not necessary for added or copied tables).
845 + * @return array<string, mixed>|WP_Error Merged table on success, WP_Error on error.
885 846 */
886 - public function prepare_table( array $table, array $new_table, $table_size_check = true, $extended_visibility_check = false ) {
847 + public function prepare_table( array $table, array $new_table, bool $table_size_check = true ) /* : array|WP_Error */ {
887 848 // Table ID must be the same (if there was an ID already).
888 - if ( false !== $table['id'] ) {
889 - if ( $table['id'] !== $new_table['id'] ) {
890 - return new WP_Error( 'table_prepare_no_id_match', '', $new_table['id'] );
891 - }
849 + if ( false !== $table['id'] && $table['id'] !== $new_table['id'] ) {
850 + return new WP_Error( 'table_prepare_no_id_match', '', $new_table['id'] );
892 851 }
893 852
894 853 // Name, description, and data array need to exist, data must not be empty, the others could be ''.
895 - if ( ! isset( $new_table['name'] )
896 - || ! isset( $new_table['description'] )
897 - || empty( $new_table['data'] )
898 - || empty( $new_table['data'][0] ) ) {
854 + if ( ! isset( $new_table['name'], $new_table['description'] )
855 + || empty( $new_table['data'] ) || empty( $new_table['data'][0] ) ) {
899 856 return new WP_Error( 'table_prepare_name_description_or_data_not_set' );
900 857 }
901 858
902 859 // Visibility needs to exist.
903 - if ( ! isset( $new_table['visibility'] )
904 - || ! isset( $new_table['visibility']['rows'] )
905 - || ! isset( $new_table['visibility']['columns'] ) ) {
860 + if ( ! isset( $new_table['visibility']['rows'], $new_table['visibility']['columns'] ) ) {
906 861 return new WP_Error( 'table_prepare_visibility_not_set' );
907 862 }
908 863 $new_table['visibility']['rows'] = array_map( 'intval', $new_table['visibility']['rows'] );
909 864 $new_table['visibility']['columns'] = array_map( 'intval', $new_table['visibility']['columns'] );
@@ -909,16 +864,14 @@
909 864 $new_table['visibility']['columns'] = array_map( 'intval', $new_table['visibility']['columns'] );
910 865
911 866 // Check dimensions of table data array (not done for newly added, copied, or imported tables).
912 867 if ( $table_size_check ) {
913 - if ( empty( $new_table['number'] )
914 - || ! isset( $new_table['number']['rows'] )
915 - || ! isset( $new_table['number']['columns'] ) ) {
868 + if ( ! isset( $new_table['number']['rows'], $new_table['number']['columns'] ) ) {
916 869 return new WP_Error( 'table_prepare_size_check_numbers_not_set' );
917 870 }
918 871 // Table data needs to be ok, and have the correct number of rows and columns.
919 - $new_table['number']['rows'] = intval( $new_table['number']['rows'] );
920 - $new_table['number']['columns'] = intval( $new_table['number']['columns'] );
872 + $new_table['number']['rows'] = (int) $new_table['number']['rows'];
873 + $new_table['number']['columns'] = (int) $new_table['number']['columns'];
921 874 if ( 0 === $new_table['number']['rows']
922 875 || 0 === $new_table['number']['columns']
923 876 || count( $new_table['data'] ) !== $new_table['number']['rows']
924 877 || count( $new_table['data'][0] ) !== $new_table['number']['columns'] ) {
@@ -928,61 +881,48 @@
928 881 if ( count( $new_table['visibility']['rows'] ) !== $new_table['number']['rows']
929 882 || count( $new_table['visibility']['columns'] ) !== $new_table['number']['columns'] ) {
930 883 return new WP_Error( 'table_prepare_size_check_visibility_doesnt_match' );
931 884 }
932 -
933 - if ( $extended_visibility_check ) { // only for Admin_AJAX controller
934 - if ( ! isset( $new_table['number']['hidden_rows'] )
935 - || ! isset( $new_table['number']['hidden_columns'] ) ) {
936 - return new WP_Error( 'table_prepare_extended_visibility_check_numbers_not_set' );
937 - }
938 - $new_table['number']['hidden_rows'] = intval( $new_table['number']['hidden_rows'] );
939 - $new_table['number']['hidden_columns'] = intval( $new_table['number']['hidden_columns'] );
940 - // Count hidden and visible rows.
941 - $num_visible_rows = count( array_keys( $new_table['visibility']['rows'], 1 ) );
942 - $num_hidden_rows = count( array_keys( $new_table['visibility']['rows'], 0 ) );
943 - // Check number of hidden and visible rows.
944 - if ( $new_table['number']['hidden_rows'] !== $num_hidden_rows
945 - || ( $new_table['number']['rows'] - $new_table['number']['hidden_rows'] ) !== $num_visible_rows ) {
946 - return new WP_Error( 'table_prepare_extended_visibility_check_rows_dont_match' );
947 - }
948 - // Count hidden and visible columns.
949 - $num_visible_columns = count( array_keys( $new_table['visibility']['columns'], 1 ) );
950 - $num_hidden_columns = count( array_keys( $new_table['visibility']['columns'], 0 ) );
951 - // Check number of hidden and visible columns.
952 - if ( $new_table['number']['hidden_columns'] !== $num_hidden_columns
953 - || ( $new_table['number']['columns'] - $new_table['number']['hidden_columns'] ) !== $num_visible_columns ) {
954 - return new WP_Error( 'table_prepare_extended_visibility_check_columns_dont_match' );
955 - }
956 - }
957 885 }
958 886
959 887 // All checks were successful, replace original values with new ones.
960 888
961 889 // $table['id'] is either false (and remains false) or already equal to $new_table['id'].
962 - $table['new_id'] = isset( $new_table['new_id'] ) ? $new_table['new_id'] : $table['id'];
890 + $table['new_id'] = $new_table['new_id'] ?? $table['id'];
963 891 $table['name'] = $new_table['name'];
964 892 $table['description'] = $new_table['description'];
965 893 $table['data'] = $new_table['data'];
966 - // $table['author'] = get_current_user_id(); // We don't want this, as it would override the original author.
967 - // $table['created'] = current_time( 'mysql' ); // We don't want this, as it would override the original datetime.
968 - $table['last_modified'] = current_time( 'mysql' );
969 - $table['options']['last_editor'] = get_current_user_id();
894 + // Make sure that cells are stored as strings.
895 + array_walk_recursive(
896 + $table['data'],
897 + static function ( /* string|int|float|bool|null */ &$cell_content, int $col_idx ): void {
898 + $cell_content = (string) $cell_content;
899 + },
900 + );
970 901 // Table Options.
971 - if ( isset( $new_table['options'] ) ) { // is for example not set for newly added tables
902 + if ( isset( $new_table['options'] ) ) { // Options are for example not set for newly added tables.
972 903 // Specials check for certain options.
973 904 if ( isset( $new_table['options']['extra_css_classes'] ) ) {
974 905 $new_table['options']['extra_css_classes'] = explode( ' ', $new_table['options']['extra_css_classes'] );
975 - $new_table['options']['extra_css_classes'] = array_map( 'sanitize_html_class', $new_table['options']['extra_css_classes'] );
906 + $new_table['options']['extra_css_classes'] = array_map( array( 'TablePress', 'sanitize_css_class' ), $new_table['options']['extra_css_classes'] );
976 907 $new_table['options']['extra_css_classes'] = array_unique( $new_table['options']['extra_css_classes'] );
977 908 $new_table['options']['extra_css_classes'] = trim( implode( ' ', $new_table['options']['extra_css_classes'] ) );
978 909 }
979 910 if ( isset( $new_table['options']['datatables_paginate_entries'] ) ) {
980 - $new_table['options']['datatables_paginate_entries'] = intval( $new_table['options']['datatables_paginate_entries'] );
911 + $new_table['options']['datatables_paginate_entries'] = (int) $new_table['options']['datatables_paginate_entries'];
981 912 if ( $new_table['options']['datatables_paginate_entries'] < 1 ) {
982 - $new_table['options']['datatables_paginate_entries'] = 10; // default value
913 + $new_table['options']['datatables_paginate_entries'] = 10; // Default value.
983 914 }
984 915 }
916 +
917 + // Backward compatibility: Convert boolean or numeric string "table_head" and "table_foot" options to integer.
918 + if ( isset( $new_table['options']['table_head'] ) ) {
919 + $new_table['options']['table_head'] = absint( $new_table['options']['table_head'] );
920 + }
921 + if ( isset( $new_table['options']['table_foot'] ) ) {
922 + $new_table['options']['table_foot'] = absint( $new_table['options']['table_foot'] );
923 + }
924 +
985 925 // Merge new options.
986 926 $default_table = $this->get_table_template();
987 927 $table['options'] = array_intersect_key( $table['options'], $default_table['options'] );
988 928 $new_table['options'] = array_intersect_key( $new_table['options'], $default_table['options'] );
@@ -990,11 +930,22 @@
990 930 }
991 931 // Table Visibility.
992 932 $table['visibility']['rows'] = $new_table['visibility']['rows'];
993 933 $table['visibility']['columns'] = $new_table['visibility']['columns'];
994 - // Convert DataTables 1.9 parameters (Hungarian notation) to DataTables 1.10 parameters (camelCase notation).
934 +
935 + // $table['author'] = get_current_user_id(); // We don't want this, as it would override the original author.
936 + // $table['created'] = wp_date( 'Y-m-d H:i:s' ); // We don't want this, as it would override the original datetime.
937 + $table['last_modified'] = wp_date( 'Y-m-d H:i:s' );
938 + $table['options']['last_editor'] = get_current_user_id();
939 +
940 + // Prevent issues if the "Custom Commands" field is not set, e.g. when non-admins have previously edited the table.
941 + if ( ! isset( $table['options']['datatables_custom_commands'] ) ) {
942 + $table['options']['datatables_custom_commands'] = '';
943 + }
944 +
945 + // Convert CSS classes and some DataTables 1.x parameters to the DataTables 2 variants.
995 946 if ( '' !== $table['options']['datatables_custom_commands'] ) {
996 - $table['options']['datatables_custom_commands'] = strtr( $table['options']['datatables_custom_commands'], $this->datatables_parameter_mappings );
947 + $table['options']['datatables_custom_commands'] = TablePress::convert_datatables_api_data( $table['options']['datatables_custom_commands'] );
997 948 }
998 949
999 950 return $table;
1000 951 }
@@ -1003,15 +954,15 @@
1003 954 * Save the table options of a table (in a post meta field of the table's post).
1004 955 *
1005 956 * @since 1.0.0
1006 957 *
1007 - * @param int $post_id Post ID.
1008 - * @param array $options Table options.
958 + * @param int $post_id Post ID.
959 + * @param array<string, mixed> $options Table options.
1009 960 * @return bool True on success, false on error.
1010 961 */
1011 - protected function _add_table_options( $post_id, array $options ) {
962 + protected function _add_table_options( int $post_id, array $options ): bool {
1012 963 $options = wp_json_encode( $options, TABLEPRESS_JSON_OPTIONS );
1013 - return $this->model_post->add_meta_field( $post_id, $this->table_options_field_name, $options );
964 + return $this->model_post->add_meta_field( $post_id, $this->table_options_field_name, $options ); // @phpstan-ignore argument.type
1014 965 }
1015 966
1016 967 /**
1017 968 * Update the table options of a table (in a post meta field in the table's post).
@@ -1017,15 +968,15 @@
1017 968 * Update the table options of a table (in a post meta field in the table's post).
1018 969 *
1019 970 * @since 1.0.0
1020 971 *
1021 - * @param int $post_id Post ID.
1022 - * @param array $options Table options.
972 + * @param int $post_id Post ID.
973 + * @param array<string, mixed> $options Table options.
1023 974 * @return bool True on success, false on error.
1024 975 */
1025 - protected function _update_table_options( $post_id, array $options ) {
976 + protected function _update_table_options( int $post_id, array $options ): bool {
1026 977 $options = wp_json_encode( $options, TABLEPRESS_JSON_OPTIONS );
1027 - return $this->model_post->update_meta_field( $post_id, $this->table_options_field_name, $options );
978 + return $this->model_post->update_meta_field( $post_id, $this->table_options_field_name, $options ); // @phpstan-ignore argument.type
1028 979 }
1029 980
1030 981 /**
1031 982 * Get the table options of a table (from a post meta field of the table's post).
@@ -1032,16 +983,22 @@
1032 983 *
1033 984 * @since 1.0.0
1034 985 *
1035 986 * @param int $post_id Post ID.
1036 - * @return array Table options on success, empty array on error.
987 + * @return array<string, mixed> Table options on success, empty array on error.
1037 988 */
1038 - protected function _get_table_options( $post_id ) {
989 + protected function _get_table_options( int $post_id ): array {
1039 990 $options = $this->model_post->get_meta_field( $post_id, $this->table_options_field_name );
1040 991 if ( empty( $options ) ) {
1041 992 return array();
1042 993 }
1043 - return (array) json_decode( $options, true );
994 + $options = (array) json_decode( $options, true );
995 +
996 + // Backward compatibility: Convert boolean "table_head" and "table_foot" options to integer.
997 + $options['table_head'] = absint( $options['table_head'] );
998 + $options['table_foot'] = absint( $options['table_foot'] );
999 +
1000 + return $options;
1044 1001 }
1045 1002
1046 1003 /**
1047 1004 * Save the table visibility of a table (in a post meta field of the table's post).
@@ -1047,15 +1004,15 @@
1047 1004 * Save the table visibility of a table (in a post meta field of the table's post).
1048 1005 *
1049 1006 * @since 1.0.0
1050 1007 *
1051 - * @param int $post_id Post ID.
1052 - * @param array $visibility Table visibility.
1008 + * @param int $post_id Post ID.
1009 + * @param array{rows: int[], columns: int[]} $visibility Table visibility.
1053 1010 * @return bool True on success, false on error.
1054 1011 */
1055 - protected function _add_table_visibility( $post_id, array $visibility ) {
1012 + protected function _add_table_visibility( int $post_id, array $visibility ): bool {
1056 1013 $visibility = wp_json_encode( $visibility, TABLEPRESS_JSON_OPTIONS );
1057 - return $this->model_post->add_meta_field( $post_id, $this->table_visibility_field_name, $visibility );
1014 + return $this->model_post->add_meta_field( $post_id, $this->table_visibility_field_name, $visibility ); // @phpstan-ignore argument.type
1058 1015 }
1059 1016
1060 1017 /**
1061 1018 * Update the table visibility of a table (in a post meta field in the table's post).
@@ -1061,15 +1018,15 @@
1061 1018 * Update the table visibility of a table (in a post meta field in the table's post).
1062 1019 *
1063 1020 * @since 1.0.0
1064 1021 *
1065 - * @param int $post_id Post ID.
1066 - * @param array $visibility Table visibility.
1022 + * @param int $post_id Post ID.
1023 + * @param array{rows: int[], columns: int[]} $visibility Table visibility.
1067 1024 * @return bool True on success, false on error.
1068 1025 */
1069 - protected function _update_table_visibility( $post_id, array $visibility ) {
1026 + protected function _update_table_visibility( int $post_id, array $visibility ): bool {
1070 1027 $visibility = wp_json_encode( $visibility, TABLEPRESS_JSON_OPTIONS );
1071 - return $this->model_post->update_meta_field( $post_id, $this->table_visibility_field_name, $visibility );
1028 + return $this->model_post->update_meta_field( $post_id, $this->table_visibility_field_name, $visibility ); // @phpstan-ignore argument.type
1072 1029 }
1073 1030
1074 1031 /**
1075 1032 * Get the table visibility of a table (from a post meta field of the table's post).
@@ -1076,14 +1033,17 @@
1076 1033 *
1077 1034 * @since 1.0.0
1078 1035 *
1079 1036 * @param int $post_id Post ID.
1080 - * @return array Table visibility on success, empty array on error.
1037 + * @return array{rows: int[], columns: int[]} Table visibility on success, empty array on error.
1081 1038 */
1082 - protected function _get_table_visibility( $post_id ) {
1039 + protected function _get_table_visibility( int $post_id ): array {
1083 1040 $visibility = $this->model_post->get_meta_field( $post_id, $this->table_visibility_field_name );
1084 1041 if ( empty( $visibility ) ) {
1085 - return array();
1042 + return array(
1043 + 'rows' => array(),
1044 + 'columns' => array(),
1045 + );
1086 1046 }
1087 1047 return json_decode( $visibility, true );
1088 1048 }
1089 1049
@@ -1092,10 +1052,13 @@
1092 1052 * remove (no longer) existing options, after a table scheme change,
1093 1053 * for all tables.
1094 1054 *
1095 1055 * @since 1.0.0
1056 + * @since 2.0.0 Add optional $remove_old_options parameter.
1057 + *
1058 + * @param bool $remove_old_options Optional. Whether old table options should be removed from the database. Default true.
1096 1059 */
1097 - public function merge_table_options_defaults() {
1060 + public function merge_table_options_defaults( bool $remove_old_options = true ): void {
1098 1061 $table_post = $this->tables->get( 'table_post' );
1099 1062 if ( empty( $table_post ) ) {
1100 1063 return;
1101 1064 }
@@ -1106,62 +1069,39 @@
1106 1069 // Get default Table with default Table Options.
1107 1070 $default_table = $this->get_table_template();
1108 1071
1109 1072 // Go through all tables (this loop now uses the WP cache).
1110 - foreach ( $table_post as $table_id => $post_id ) {
1073 + foreach ( $table_post as $post_id ) {
1111 1074 $table_options = $this->_get_table_options( $post_id );
1112 - // Remove old (i.e. no longer existing) Table Options.
1113 - $table_options = array_intersect_key( $table_options, $default_table['options'] );
1114 - // Merge current into new Table Options.
1115 - $table_options = array_merge( $default_table['options'], $table_options );
1116 - $this->_update_table_options( $post_id, $table_options );
1117 - }
1118 - }
1119 1075
1120 - /**
1121 - * Merge changes made for TablePress 0.6-beta:
1122 - * Table Name/Table Description.
1123 - *
1124 - * @since 0.6-beta
1125 - */
1126 - public function merge_table_options_tp06() {
1127 - $table_post = $this->tables->get( 'table_post' );
1128 - if ( empty( $table_post ) ) {
1129 - return;
1130 - }
1076 + /**
1077 + * Filters the Table Options before they are merged with the default Table Options.
1078 + *
1079 + * @since 3.0.0
1080 + *
1081 + * @param array<string, mixed> $table_options Table Options.
1082 + * @param array<string, mixed> $default_table_options Default Table Options.
1083 + * @param bool $remove_old_options Whether old table options should be removed from the database.
1084 + * @param int $post_id Post ID of the table.
1085 + */
1086 + $table_options = apply_filters( 'tablepress_table_options_before_merge', $table_options, $default_table['options'], $remove_old_options, $post_id );
1131 1087
1132 - // Prime the meta cache with the table options of all tables.
1133 - update_meta_cache( 'post', array_values( $table_post ) );
1134 -
1135 - // Go through all tables.
1136 - foreach ( $table_post as $table_id => $post_id ) {
1137 - $table_options = $this->_get_table_options( $post_id );
1138 -
1139 - // Move "Print Name" to new format.
1140 - $print_name = in_array( $table_options['print_name'], array( 'above', 'below' ), true );
1141 - if ( $print_name ) {
1142 - $table_options['print_name_position'] = $table_options['print_name'];
1088 + if ( $remove_old_options ) {
1089 + // Remove old (i.e. no longer existing) Table Options.
1090 + $table_options = array_intersect_key( $table_options, $default_table['options'] );
1143 1091 }
1144 - $table_options['print_name'] = $print_name;
1145 - // Move "Print Description" to new format.
1146 - $print_description = in_array( $table_options['print_description'], array( 'above', 'below' ), true );
1147 - if ( $print_description ) {
1148 - $table_options['print_description_position'] = $table_options['print_description'];
1149 - }
1150 - $table_options['print_description'] = $print_description;
1151 -
1092 + // Merge current into new Table Options.
1093 + $table_options = array_merge( $default_table['options'], $table_options );
1152 1094 $this->_update_table_options( $post_id, $table_options );
1153 1095 }
1154 1096 }
1155 1097
1156 1098 /**
1157 - * Merge changes made for TablePress 0.8-beta:
1158 - * Conversion of parameter "datatables_scrollX" to "datatables_scrollx".
1159 - * Fixes a bug that affects about the first 600 downloaders of 0.8-beta.
1099 + * Updates all tables' "Custom Commands" to use DataTables 2 variants instead of old DataTables 1.x CSS classes and parameters
1160 1100 *
1161 - * @since 0.8-beta
1101 + * @since 3.0.0
1162 1102 */
1163 - public function merge_table_options_tp08() {
1103 + public function update_custom_commands_datatables_tp30(): void {
1164 1104 $table_post = $this->tables->get( 'table_post' );
1165 1105 if ( empty( $table_post ) ) {
1166 1106 return;
1167 1107 }
@@ -1171,42 +1111,22 @@
1171 1111
1172 1112 foreach ( $table_post as $table_id => $post_id ) {
1173 1113 $table_options = $this->_get_table_options( $post_id );
1174 1114
1175 - // Convert parameter "datatables_scrollX" to "datatables_scrollx".
1176 - if ( isset( $table_options['datatables_scrollX'] ) && ! isset( $table_options['datatables_scrollx'] ) ) {
1177 - $table_options['datatables_scrollx'] = $table_options['datatables_scrollX'];
1115 + // Fix tables where the "Custom Commands" entry is missing entirely.
1116 + if ( ! isset( $table_options['datatables_custom_commands'] ) ) {
1117 + $table_options['datatables_custom_commands'] = '';
1118 + $this->_update_table_options( $post_id, $table_options );
1119 + continue;
1178 1120 }
1179 1121
1180 - $this->_update_table_options( $post_id, $table_options );
1181 - }
1182 - }
1183 -
1184 - /**
1185 - * Convert old parameter names to new ones in DataTables "Custom Commands".
1186 - * DataTables 1.9 used Hungarian notation, while DataTables 1.10+ (used since TablePress 1.5) uses camelCase notation.
1187 - *
1188 - * @since 1.5.0
1189 - */
1190 - public function convert_datatables_parameter_names_tp15() {
1191 - $table_post = $this->tables->get( 'table_post' );
1192 - if ( empty( $table_post ) ) {
1193 - return;
1194 - }
1195 -
1196 - // Prime the meta cache with the table options of all tables.
1197 - update_meta_cache( 'post', array_values( $table_post ) );
1198 -
1199 - foreach ( $table_post as $table_id => $post_id ) {
1200 - $table_options = $this->_get_table_options( $post_id );
1201 -
1202 1122 // Nothing to do if there are no "Custom Commands".
1203 - if ( empty( $table_options['datatables_custom_commands'] ) ) {
1123 + if ( '' === $table_options['datatables_custom_commands'] ) {
1204 1124 continue;
1205 1125 }
1206 1126 // Run search/replace.
1207 1127 $old_custom_commands = $table_options['datatables_custom_commands'];
1208 - $table_options['datatables_custom_commands'] = strtr( $table_options['datatables_custom_commands'], $this->datatables_parameter_mappings );
1128 + $table_options['datatables_custom_commands'] = TablePress::convert_datatables_api_data( $table_options['datatables_custom_commands'] );
1209 1129 // No need to save (which runs a DB query) if nothing was replaced in the "Custom Commands".
1210 1130 if ( $old_custom_commands === $table_options['datatables_custom_commands'] ) {
1211 1131 continue;
1212 1132 }
@@ -1217,13 +1137,11 @@
1217 1137
1218 1138 /**
1219 1139 * Invalidate all table output caches, e.g. after a plugin update.
1220 1140 *
1221 - * For TablePress 0.9-RC and onwards.
1222 - *
1223 - * @since 0.9-RC
1141 + * @since 1.0.0
1224 1142 */
1225 - public function invalidate_table_output_caches() {
1143 + public function invalidate_table_output_caches(): void {
1226 1144 $table_post = $this->tables->get( 'table_post' );
1227 1145 if ( empty( $table_post ) ) {
1228 1146 return;
1229 1147 }
@@ -1228,37 +1146,14 @@
1228 1146 return;
1229 1147 }
1230 1148
1231 1149 foreach ( $table_post as $table_id => $post_id ) {
1150 + $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.
1232 1151 $this->invalidate_table_output_cache( $table_id );
1233 1152 }
1234 1153 }
1235 1154
1236 1155 /**
1237 - * Invalidate all table output caches, e.g. after a plugin update.
1238 - * For TablePress pre-0.9-RC updates.
1239 - *
1240 - * @since 0.9-RC
1241 - */
1242 - public function invalidate_table_output_caches_tp09() {
1243 - $table_post = $this->tables->get( 'table_post' );
1244 - if ( empty( $table_post ) ) {
1245 - return;
1246 - }
1247 -
1248 - foreach ( $table_post as $table_id => $post_id ) {
1249 - $caches_list_transient_name = 'tablepress_c_' . md5( $table_id );
1250 - $caches_list = get_transient( $caches_list_transient_name );
1251 - if ( is_array( $caches_list ) ) {
1252 - foreach ( $caches_list as $cache_transient_name => $dummy_value ) {
1253 - delete_transient( $cache_transient_name );
1254 - }
1255 - }
1256 - delete_transient( $caches_list_transient_name );
1257 - }
1258 - }
1259 -
1260 - /**
1261 1156 * Add mime type field to existing posts with the TablePress Custom Post Type,
1262 1157 * so that other plugins know that they are not dealing with plain text.
1263 1158 *
1264 1159 * @since 1.5.0
@@ -1264,11 +1159,11 @@
1264 1159 * @since 1.5.0
1265 1160 *
1266 1161 * @global wpdb $wpdb WordPress database abstraction object.
1267 1162 */
1268 - public function add_mime_type_to_posts() {
1163 + public function add_mime_type_to_posts(): void {
1269 1164 global $wpdb;
1270 - $wpdb->update( $wpdb->posts, array( 'post_mime_type' => 'application/json' ), array( 'post_type' => $this->model_post->get_post_type() ) );
1165 + $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
1271 1166 }
1272 1167
1273 1168 /**
1274 1169 * Add a table ID for a post (table) that was imported through the WP WXR importer to the table ID to post ID map.
@@ -1274,14 +1169,14 @@
1274 1169 * Add a table ID for a post (table) that was imported through the WP WXR importer to the table ID to post ID map.
1275 1170 *
1276 1171 * @since 1.5.0
1277 1172 *
1278 - * @param int $post_id Post ID of the imported post.
1279 - * @param int $original_post_ID Original post ID that the post had on the site where it was exported from.
1280 - * @param array $postdata Post data that was imported into the database.
1281 - * @param array $post Original post data as it was exported.
1173 + * @param int|WP_Error $post_id Post ID of the imported post on success. 0 or WP_Error on failure.
1174 + * @param int $original_post_id Original post ID that the post had on the site where it was exported from.
1175 + * @param array<string, mixed> $postdata Post data that was imported into the database.
1176 + * @param array<string, mixed> $post Original post data as it was exported.
1282 1177 */
1283 - public function add_table_id_on_wp_import( $post_id, $original_post_ID, array $postdata, array $post ) {
1178 + public function add_table_id_on_wp_import( /* int|WP_Error */ $post_id, int $original_post_id, array $postdata, array $post ): void {
1284 1179 // Bail if the post could not be imported or if the post is not a TablePress table.
1285 1180 if ( is_wp_error( $post_id ) || $this->model_post->get_post_type() !== $postdata['post_type'] ) {
1286 1181 return;
1287 1182 }
@@ -1300,9 +1195,9 @@
1300 1195
1301 1196 // Save the post ID for each of the table IDs.
1302 1197 $post_id_saved = false;
1303 1198 foreach ( $table_ids as $table_id ) {
1304 - $table_id = preg_replace( '/[^a-zA-Z0-9_-]/', '', $table_id );
1199 + $table_id = (string) preg_replace( '/[^a-zA-Z0-9_-]/', '', $table_id );
1305 1200 if ( '' === $table_id || $this->table_exists( $table_id ) ) {
1306 1201 continue;
1307 1202 }
1308 1203 $this->_update_post_id( $table_id, $post_id );
@@ -1320,14 +1215,14 @@
1320 1215 * Remove the `_tablepress_export_table_id` post meta field from the fields that are imported during a WP WXR import.
1321 1216 *
1322 1217 * @since 1.5.0
1323 1218 *
1324 - * @param array $postmeta Post meta fields for the post.
1325 - * @param int $post_id Post ID.
1326 - * @param array $post Post.
1327 - * @return array Modified post meta fields.
1219 + * @param array<string, mixed> $postmeta Post meta fields for the post.
1220 + * @param int $post_id Post ID.
1221 + * @param array<string, mixed> $post Post.
1222 + * @return array<string, mixed> Modified post meta fields.
1328 1223 */
1329 - public function prevent_table_id_post_meta_import_on_wp_import( array $postmeta, $post_id, array $post ) {
1224 + public function prevent_table_id_post_meta_import_on_wp_import( array $postmeta, int $post_id, array $post ): array {
1330 1225 // Bail if the post is not a TablePress table.
1331 1226 if ( $this->model_post->get_post_type() !== $post['post_type'] ) {
1332 1227 return $postmeta;
1333 1228 }
@@ -1352,10 +1247,11 @@
1352 1247 *
1353 1248 * @param bool $skip Whether to skip the current post meta. Default false.
1354 1249 * @param string $meta_key Current meta key.
1355 1250 * @param stdClass $meta Current meta object.
1251 + * @return bool Whether to skip the current post meta (unchanged $skip parameter).
1356 1252 */
1357 - public function add_table_id_to_wp_export( $skip, $meta_key, $meta ) {
1253 + public function add_table_id_to_wp_export( bool $skip, string $meta_key, stdClass $meta ): bool {
1358 1254 // Bail if the exporter doesn't process a TablePress table right now.
1359 1255 if ( $this->table_options_field_name !== $meta_key ) {
1360 1256 return $skip;
1361 1257 }
@@ -1370,17 +1266,24 @@
1370 1266 }
1371 1267
1372 1268 // Pretend that there is a `_tablepress_export_table_id` post meta field with the list of table IDs.
1373 1269 $key = '_tablepress_export_table_id';
1270 +
1271 + /**
1272 + * Load WP export functions.
1273 + */
1274 + require_once ABSPATH . 'wp-admin/includes/export.php';
1374 1275 $value = wxr_cdata( implode( ',', $table_ids ) );
1375 1276
1376 1277 // Hijack the filter and print extra XML code for our faked post meta field.
1278 + // phpcs:disable WordPress.Security.EscapeOutput.HeredocOutputNotEscaped
1377 1279 echo <<<WXR
1378 - <wp:postmeta>
1379 - <wp:meta_key>{$key}</wp:meta_key>
1380 - <wp:meta_value>{$value}</wp:meta_value>
1381 - </wp:postmeta>\n
1382 -WXR;
1280 + <wp:postmeta>
1281 + <wp:meta_key>{$key}</wp:meta_key>
1282 + <wp:meta_value>{$value}</wp:meta_value>
1283 + </wp:postmeta>\n
1284 + WXR;
1285 + // phpcs:enable
1383 1286
1384 1287 return $skip;
1385 1288 }
1386 1289
@@ -1388,9 +1291,9 @@
1388 1291 * Delete the WP_Option of the model.
1389 1292 *
1390 1293 * @since 1.0.0
1391 1294 */
1392 - public function destroy() {
1295 + public function destroy(): void {
1393 1296 $this->tables->delete();
1394 1297 }
1395 1298
1396 1299 } // class TablePress_Table_Model