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 +324 -347 1.14 → 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,16 +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 ) {
225 - // Run filters on content in each cell and other fields.
226 - $table = $this->filter_content( $table );
227 -
115 + protected function _table_to_post( array $table, int $post_id ): array {
228 116 // Sanitize each cell, table name, and table description, if the user is not allowed to work with unfiltered HTML.
229 117 if ( ! current_user_can( 'unfiltered_html' ) ) {
230 118 $table = $this->sanitize( $table );
231 119 }
@@ -237,9 +125,8 @@
237 125
238 126 $post = array(
239 127 'ID' => $post_id,
240 128 'post_title' => $table['name'],
241 - // 'post_author' => $table['author'],
242 129 'post_excerpt' => $table['description'],
243 130 'post_content' => wp_json_encode( $table['data'], TABLEPRESS_JSON_OPTIONS ),
244 131 'post_mime_type' => 'application/json',
245 132 );
@@ -254,16 +141,16 @@
254 141 *
255 142 * @param WP_Post $post Post.
256 143 * @param string $table_id Table ID.
257 144 * @param bool $load_data Whether the table data shall be loaded.
258 - * @return array Table.
145 + * @return array<string, mixed> Table.
259 146 */
260 - 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 {
261 148 $table = array(
262 149 'id' => $table_id,
263 150 'name' => $post->post_title,
264 151 'description' => $post->post_excerpt,
265 - 'author' => $post->post_author,
152 + 'author' => (int) $post->post_author,
266 153 // 'created' => $post->post_date,
267 154 'last_modified' => $post->post_modified,
268 155 );
269 156
@@ -274,20 +161,11 @@
274 161 $table['data'] = json_decode( $post->post_content, true );
275 162
276 163 // Check if JSON could be decoded.
277 164 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.
165 + $table['data'] = array( array( "The internal data of table {$table_id} is corrupted." ) ); // Set a single cell as the cell content.
281 166 $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 -
167 + $table['json_error'] = json_last_error_msg();
290 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']}";
291 169 } else {
292 170 // Specifically cast to an array again.
293 171 $table['data'] = (array) $table['data'];
@@ -303,11 +181,11 @@
303 181 *
304 182 * @param string $table_id Table ID.
305 183 * @param bool $load_data Whether the table data shall be loaded.
306 184 * @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.
185 + * @return array<string, mixed>|WP_Error Table as an array on success, WP_Error on error.
308 186 */
309 - 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 */ {
310 188 if ( empty( $table_id ) ) {
311 189 return new WP_Error( 'table_load_empty_table_id' );
312 190 }
313 191
@@ -335,11 +213,11 @@
335 213 * @since 1.0.0
336 214 *
337 215 * @param bool $prime_meta_cache Optional. Whether the prime the post meta cache when loading the posts.
338 216 * @param bool $run_filter Optional. Whether to run a filter on the list of table IDs.
339 - * @return array Array of table IDs.
217 + * @return string[] Array of table IDs.
340 218 */
341 - 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 {
342 220 $table_post = $this->tables->get( 'table_post' );
343 221 if ( empty( $table_post ) ) {
344 222 return array();
345 223 }
@@ -349,11 +227,13 @@
349 227
350 228 // This loop now uses the WP cache.
351 229 $table_ids = array();
352 230 foreach ( $table_post as $table_id => $post_id ) {
353 - $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 +
354 233 // Load table without data and options to save memory.
355 234 $table = $this->load( $table_id, false, false );
235 +
356 236 // Skip tables that could not be loaded properly.
357 237 if ( ! is_wp_error( $table ) ) {
358 238 $table_ids[] = $table_id;
359 239 }
@@ -360,13 +240,13 @@
360 240 }
361 241
362 242 if ( $run_filter ) {
363 243 /**
364 - * Filter all table IDs that are loaded.
244 + * Filters all table IDs that are loaded.
365 245 *
366 246 * @since 1.4.0
367 247 *
368 - * @param array $table_ids The table IDs that are loaded.
248 + * @param string[] $table_ids The table IDs that are loaded.
369 249 */
370 250 $table_ids = apply_filters( 'tablepress_load_all_tables', $table_ids );
371 251 }
372 252
@@ -377,12 +257,12 @@
377 257 * Sanitize the table to remove undesired HTML code using KSES.
378 258 *
379 259 * @since 1.8.0
380 260 *
381 - * @param array $table Table.
382 - * @return array Sanitized table.
261 + * @param array<string, mixed> $table Table.
262 + * @return array<string, mixed> Sanitized table.
383 263 */
384 - public function sanitize( array $table ) {
264 + public function sanitize( array $table ): array {
385 265 // Sanitize the table name and description.
386 266 $fields = array( 'name', 'description' );
387 267 foreach ( $fields as $field ) {
388 268 $table[ $field ] = wp_kses_post( $table[ $field ] );
@@ -390,9 +270,9 @@
390 270
391 271 // Sanitize each cell.
392 272 foreach ( $table['data'] as $row_idx => $row ) {
393 273 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
274 + $table['data'][ $row_idx ][ $column_idx ] = wp_kses_post( $cell_content ); // Equals wp_filter_post_kses(), but without the unnecessary slashes handling.
395 275 }
396 276 }
397 277
398 278 return $table;
@@ -398,59 +278,29 @@
398 278 return $table;
399 279 }
400 280
401 281 /**
402 - * Filter/modify the content of table cells and other fields, e.g. for security hardening.
403 - *
404 - * This is similar to the `sanitize()` method, but executed for all users.
405 - * In 1.10.0, adding `rel="noopener noreferrer"` to all HTML link elements like `<a target=` was added. See https://core.trac.wordpress.org/ticket/43187.
406 - * Since 1.13.0, and on WP 5.6, only `rel="noopener"` is added. See https://core.trac.wordpress.org/ticket/49558.
407 - *
408 - * @since 1.10.0
409 - *
410 - * @param array $table Table.
411 - * @return array Filtered/modified table.
412 - */
413 - public function filter_content( array $table ) {
414 - /**
415 - * Filter whether the contents of table cells and fields should be filtered/modified.
416 - *
417 - * @since 1.10.0
418 - *
419 - * @param bool $filter Whether to filter the content of table cells and other fields. Default true.
420 - */
421 - if ( ! apply_filters( 'tablepress_filter_table_cell_content', true ) ) {
422 - return $table;
423 - }
424 -
425 - // Filter the table name and description.
426 - $fields = array( 'name', 'description' );
427 - foreach ( $fields as $field ) {
428 - $table[ $field ] = wp_targeted_link_rel( $table[ $field ] );
429 - }
430 -
431 - foreach ( $table['data'] as $row_idx => $row ) {
432 - foreach ( $row as $column_idx => $cell_content ) {
433 - $table['data'][ $row_idx ][ $column_idx ] = wp_targeted_link_rel( $cell_content );
434 - }
435 - }
436 -
437 - return $table;
438 - }
439 -
440 - /**
441 282 * Save a table.
442 283 *
443 284 * @since 1.0.0
444 285 *
445 - * @param array $table Table (needs to have $table['id']!).
286 + * @param array<string, mixed> $table Table (needs to have $table['id']!).
446 287 * @return string|WP_Error WP_Error on error, string table ID on success.
447 288 */
448 - public function save( array $table ) {
289 + public function save( array $table ) /* : string|WP_Error */ {
449 290 if ( empty( $table['id'] ) ) {
450 291 return new WP_Error( 'table_save_empty_table_id' );
451 292 }
452 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 +
453 303 $post_id = $this->_get_post_id( $table['id'] );
454 304 if ( false === $post_id ) {
455 305 return new WP_Error( 'table_save_no_post_id_for_table_id', '', $table['id'] );
456 306 }
@@ -457,11 +307,11 @@
457 307
458 308 $post = $this->_table_to_post( $table, $post_id );
459 309 $new_post_id = $this->model_post->update( $post );
460 310 if ( is_wp_error( $new_post_id ) ) {
461 - // Add an error code to the existing WP_Error.
462 - $new_post_id->add( 'table_save_post_update', '', $post_id );
463 - return $new_post_id;
311 + $error = new WP_Error( 'table_save_post_update', '', $post_id );
312 + $error->merge_from( $new_post_id );
313 + return $error;
464 314 }
465 315 if ( $post_id !== $new_post_id ) {
466 316 return new WP_Error( 'table_save_new_post_id_does_not_match', '', $new_post_id );
467 317 }
@@ -487,9 +337,9 @@
487 337 * Fires after a table has been saved.
488 338 *
489 339 * @since 1.5.0
490 340 *
491 - * @param string $table_id ID of the added table.
341 + * @param string $table_id ID of the saved table.
492 342 */
493 343 do_action( 'tablepress_event_saved_table', $table['id'] );
494 344
495 345 return $table['id'];
@@ -499,20 +349,20 @@
499 349 * Add a new table.
500 350 *
501 351 * @since 1.0.0
502 352 *
503 - * @param array $table Table ($table['id'] is not necessary).
504 - * @param string $copy_or_add Optional. 'copy' if the table is copied, 'add' if it is a new table. Default 'add'.
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'.
505 355 * @return string|WP_Error WP_Error on error, string table ID of the new table on success.
506 356 */
507 - public function add( array $table, $copy_or_add = 'add' ) {
508 - $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.
509 359 $post = $this->_table_to_post( $table, $post_id );
510 360 $new_post_id = $this->model_post->insert( $post );
511 361 if ( is_wp_error( $new_post_id ) ) {
512 - // Add an error code to the existing WP_Error.
513 - $new_post_id->add( 'table_add_post_insert', '' );
514 - return $new_post_id;
362 + $error = new WP_Error( 'table_add_post_insert', '' );
363 + $error->merge_from( $new_post_id );
364 + return $error;
515 365 }
516 366
517 367 $options_saved = $this->_add_table_options( $new_post_id, $table['options'] );
518 368 if ( ! $options_saved ) {
@@ -549,14 +399,14 @@
549 399 *
550 400 * @param string $table_id ID of the table to be copied.
551 401 * @return string|WP_Error WP_Error on error, string table ID of the new table on success.
552 402 */
553 - public function copy( $table_id ) {
403 + public function copy( string $table_id ) /* : string|WP_Error */ {
554 404 $table = $this->load( $table_id, true, true );
555 405 if ( is_wp_error( $table ) ) {
556 - // Add an error code to the existing WP_Error.
557 - $table->add( 'table_copy_table_load', '', $table_id );
558 - return $table;
406 + $error = new WP_Error( 'table_copy_table_load', '', $table_id );
407 + $error->merge_from( $table );
408 + return $error;
559 409 }
560 410
561 411 // Adjust name of copied table.
562 412 if ( '' === trim( $table['name'] ) ) {
@@ -566,19 +416,19 @@
566 416
567 417 // Merge this data into an empty table template.
568 418 $table = $this->prepare_table( $this->get_table_template(), $table, false );
569 419 if ( is_wp_error( $table ) ) {
570 - // Add an error code to the existing WP_Error.
571 - $table->add( 'table_copy_table_prepare', '', $table_id );
572 - return $table;
420 + $error = new WP_Error( 'table_copy_table_prepare', '', $table_id );
421 + $error->merge_from( $table );
422 + return $error;
573 423 }
574 424
575 425 // Add the copied table.
576 426 $new_table_id = $this->add( $table, 'copy' );
577 427 if ( is_wp_error( $new_table_id ) ) {
578 - // Add an error code to the existing WP_Error.
579 - $new_table_id->add( 'table_copy_table_add', '', $table_id );
580 - return $new_table_id;
428 + $error = new WP_Error( 'table_copy_table_add', '', $table_id );
429 + $error->merge_from( $new_table_id );
430 + return $error;
581 431 }
582 432
583 433 /**
584 434 * Fires after an existing table has been copied.
@@ -600,14 +450,25 @@
600 450 *
601 451 * @param string $table_id ID of the table to be deleted.
602 452 * @return bool|WP_Error WP_Error on error, true on success.
603 453 */
604 - public function delete( $table_id ) {
454 + public function delete( string $table_id ) /* : true|WP_Error */ {
605 455 if ( ! $this->table_exists( $table_id ) ) {
606 456 return new WP_Error( 'table_delete_table_does_not_exist', '', $table_id );
607 457 }
608 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 +
609 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
610 471 $deleted = $this->model_post->delete( $post_id ); // Post Meta fields will be deleted automatically by that function.
611 472 if ( false === $deleted ) {
612 473 return new WP_Error( 'table_delete_post_could_not_be_deleted', '', $post_id );
613 474 }
@@ -636,9 +497,9 @@
636 497 * Delete all tables.
637 498 *
638 499 * @since 1.0.0
639 500 */
640 - public function delete_all() {
501 + public function delete_all(): void {
641 502 $tables = $this->tables->get();
642 503 if ( empty( $tables['table_post'] ) ) {
643 504 return;
644 505 }
@@ -643,11 +504,13 @@
643 504 return;
644 505 }
645 506
646 507 foreach ( $tables['table_post'] as $table_id => $post_id ) {
647 - $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 +
648 510 $this->model_post->delete( $post_id ); // Post Meta fields will be deleted automatically by that function.
649 511 unset( $tables['table_post'][ $table_id ] );
512 +
650 513 // Invalidate table output caches that belong to this table.
651 514 $this->invalidate_table_output_cache( $table_id );
652 515 }
653 516
@@ -670,9 +533,9 @@
670 533 *
671 534 * @param string $table_id Table ID.
672 535 * @return bool Whether the table ID exists.
673 536 */
674 - public function table_exists( $table_id ) {
537 + public function table_exists( string $table_id ): bool {
675 538 $table_post = $this->tables->get( 'table_post' );
676 539 return isset( $table_post[ $table_id ] );
677 540 }
678 541
@@ -681,11 +544,11 @@
681 544 *
682 545 * @since 1.0.0
683 546 *
684 547 * @param bool $single_value Optional. Whether to return just the number of tables from the list, or also count in the database.
685 - * @return int|array Number of Tables (if $single_value), or array of Numbers from list/DB (if ! $single_value).
548 + * @return int|array{list: int, db: int} Number of Tables (if $single_value), or array of Numbers from list/DB (if ! $single_value).
686 549 */
687 - public function count_tables( $single_value = true ) {
550 + public function count_tables( bool $single_value = true ) /* : int|array */ {
688 551 $count_list = count( $this->tables->get( 'table_post' ) );
689 552 if ( $single_value ) {
690 553 return $count_list;
691 554 }
@@ -704,9 +567,9 @@
704 567 * @since 1.8.0 Renamed from _invalidate_table_output_cache to invalidate_table_output_cache and made public.
705 568 *
706 569 * @param string $table_id Table ID.
707 570 */
708 - public function invalidate_table_output_cache( $table_id ) {
571 + public function invalidate_table_output_cache( string $table_id ): void {
709 572 $caches_list_transient_name = 'tablepress_c_' . md5( $table_id );
710 573 $caches_list = get_transient( $caches_list_transient_name );
711 574 if ( false !== $caches_list ) {
712 575 $caches_list = (array) json_decode( $caches_list, true );
@@ -717,15 +580,15 @@
717 580 delete_transient( $caches_list_transient_name );
718 581 }
719 582
720 583 /**
721 - * Flush the caches of the plugins W3 Total Cache, WP Super Cache, Cachify, and Quick Cache.
584 + * Flush the caches of common caching plugins.
722 585 *
723 586 * @since 1.0.0
724 587 */
725 - public function _flush_caching_plugins_caches() {
588 + public function _flush_caching_plugins_caches(): void {
726 589 /**
727 - * Filter whether the caches of common caching plugins shall be flushed.
590 + * Filters whether the caches of common caching plugins shall be flushed.
728 591 *
729 592 * @since 1.0.0
730 593 *
731 594 * @param bool $flush Whether caches of caching plugins shall be flushed. Default true.
@@ -733,26 +596,84 @@
733 596 if ( ! apply_filters( 'tablepress_flush_caching_plugins_caches', true ) ) {
734 597 return;
735 598 }
736 599
737 - // W3 Total Cache
738 - if ( function_exists( 'w3tc_pgcache_flush' ) ) {
739 - 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 + }
740 620 }
741 - // WP Super Cache
742 - if ( function_exists( 'wp_cache_clear_cache' ) ) {
743 - 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 );
744 630 }
745 - // Cachify
746 - do_action( 'cachify_flush_cache' );
747 - // Quick Cache
748 - if ( isset( $GLOBALS['quick_cache'] ) && method_exists( $GLOBALS['quick_cache'], 'clear_cache' ) ) {
749 - $GLOBALS['quick_cache']->clear_cache();
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 + }
750 638 }
751 - // WP Fastest Cache
752 - if ( isset( $GLOBALS['wp_fastest_cache'] ) && method_exists( $GLOBALS['wp_fastest_cache'], 'deleteCache' ) ) {
753 - $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
754 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 + }
755 676 }
756 677
757 678 /**
758 679 * Get the post ID of a given table ID (if the table ID exists).
@@ -761,15 +682,14 @@
761 682 *
762 683 * @param string $table_id Table ID.
763 684 * @return int|false Post ID on success, false on error.
764 685 */
765 - protected function _get_post_id( $table_id ) {
686 + protected function _get_post_id( string $table_id ) /* : int|false */ {
766 687 $table_post = $this->tables->get( 'table_post' );
767 - if ( isset( $table_post[ $table_id ] ) ) {
768 - return $table_post[ $table_id ];
769 - } else {
688 + if ( ! isset( $table_post[ $table_id ] ) ) {
770 689 return false;
771 690 }
691 + return $table_post[ $table_id ];
772 692 }
773 693
774 694 /**
775 695 * Update/Add a post ID for a given table ID, and sort the list of tables by their key in natural sort order.
@@ -776,11 +696,11 @@
776 696 *
777 697 * @since 1.0.0
778 698 *
779 699 * @param string $table_id Table ID.
780 - * @param int $post_id Post ID.
700 + * @param int $post_id Post ID.
781 701 */
782 - protected function _update_post_id( $table_id, $post_id ) {
702 + protected function _update_post_id( string $table_id, int $post_id ): void {
783 703 $tables = $this->tables->get();
784 704 $tables['table_post'][ $table_id ] = $post_id;
785 705 uksort( $tables['table_post'], 'strnatcasecmp' );
786 706 $this->tables->update( $tables );
@@ -792,9 +712,9 @@
792 712 * @since 1.0.0
793 713 *
794 714 * @param string $table_id Table ID.
795 715 */
796 - protected function _remove_post_id( $table_id ) {
716 + protected function _remove_post_id( string $table_id ): void {
797 717 $tables = $this->tables->get();
798 718 unset( $tables['table_post'][ $table_id ] );
799 719 $this->tables->update( $tables );
800 720 }
@@ -807,9 +727,9 @@
807 727 * @param string $old_id Old table ID.
808 728 * @param string $new_id New table ID.
809 729 * @return bool|WP_Error True on success, WP_Error on error.
810 730 */
811 - public function change_table_id( $old_id, $new_id ) {
731 + public function change_table_id( string $old_id, string $new_id ) /* : true|WP_Error */ {
812 732 $post_id = $this->_get_post_id( $old_id );
813 733 if ( false === $post_id ) {
814 734 return new WP_Error( 'table_change_id_no_post_id_for_table_id', '', $old_id );
815 735 }
@@ -845,16 +765,17 @@
845 765 * @since 1.0.0
846 766 *
847 767 * @return string Unused table ID (e.g. for a new table).
848 768 */
849 - protected function _get_new_table_id() {
769 + protected function _get_new_table_id(): string {
850 770 $tables = $this->tables->get();
851 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.
852 772 do {
853 - $tables['last_id'] ++;
854 - } 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 ) );
855 776 $this->tables->update( $tables );
856 - return (string) $tables['last_id'];
777 + return $last_id_string;
857 778 }
858 779
859 780 /**
860 781 * Get the template for an empty table.
@@ -862,24 +783,23 @@
862 783 * Important: This scheme is versioned via TablePress::table_scheme_version; changes likely need a version update!
863 784 *
864 785 * @since 1.0.0
865 786 *
866 - * @return array Empty table.
787 + * @return array<string, mixed> Empty table.
867 788 */
868 - public function get_table_template() {
789 + public function get_table_template(): array {
869 790 // Attention: Array keys have to be lowercase, to make it possible to match them with Shortcode attributes!
870 791 $table = array(
871 792 'id' => false,
872 793 'name' => '',
873 794 'description' => '',
874 - 'data' => array( array( '' ) ), // one empty cell
875 - // 'created' => wp_date( 'Y-m-d H:i:s' ),
795 + 'data' => array( array( '' ) ), // One empty cell.
876 796 'last_modified' => wp_date( 'Y-m-d H:i:s' ),
877 797 'author' => get_current_user_id(),
878 798 'options' => array(
879 799 'last_editor' => get_current_user_id(),
880 - 'table_head' => true,
881 - 'table_foot' => false,
800 + 'table_head' => 1,
801 + 'table_foot' => 0,
882 802 'alternating_row_colors' => true,
883 803 'row_hover' => true,
884 804 'print_name' => false,
885 805 'print_name_position' => 'above',
@@ -885,9 +805,9 @@
885 805 'print_name_position' => 'above',
886 806 'print_description' => false,
887 807 'print_description_position' => 'below',
888 808 'extra_css_classes' => '',
889 - // DataTables JavaScript library
809 + // DataTables JavaScript library.
890 810 'use_datatables' => true,
891 811 'datatables_sort' => true,
892 812 'datatables_filter' => true,
893 813 'datatables_paginate' => true,
@@ -897,18 +817,18 @@
897 817 'datatables_scrollx' => false,
898 818 'datatables_custom_commands' => '',
899 819 ),
900 820 'visibility' => array(
901 - 'rows' => array( 1 ), // one visbile row
902 - 'columns' => array( 1 ), // one visible column
821 + 'rows' => array( 1 ), // One visible row.
822 + 'columns' => array( 1 ), // One visible column.
903 823 ),
904 824 );
905 825 /**
906 - * Filter the default template/structure of an empty table.
826 + * Filters the default template/structure of an empty table.
907 827 *
908 828 * @since 1.0.0
909 829 *
910 - * @param array $table Default template/structure of an empty table.
830 + * @param array<string, mixed> $table Default template/structure of an empty table.
911 831 */
912 832 return apply_filters( 'tablepress_table_template', $table );
913 833 }
914 834
@@ -918,33 +838,27 @@
918 838 * Performs consistency checks on data and visibility settings.
919 839 *
920 840 * @since 1.0.0
921 841 *
922 - * @param array $table Table to merge into.
923 - * @param array $new_table Table to merge.
924 - * @param bool $table_size_check Optional. Whether to check the number of rows and columns (e.g. not necessary for added or copied tables).
925 - * @return array|WP_Error Merged table on success, WP_Error on error.
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.
926 846 */
927 - public function prepare_table( array $table, array $new_table, $table_size_check = true ) {
847 + public function prepare_table( array $table, array $new_table, bool $table_size_check = true ) /* : array|WP_Error */ {
928 848 // Table ID must be the same (if there was an ID already).
929 - if ( false !== $table['id'] ) {
930 - if ( $table['id'] !== $new_table['id'] ) {
931 - return new WP_Error( 'table_prepare_no_id_match', '', $new_table['id'] );
932 - }
849 + if ( false !== $table['id'] && $table['id'] !== $new_table['id'] ) {
850 + return new WP_Error( 'table_prepare_no_id_match', '', $new_table['id'] );
933 851 }
934 852
935 853 // Name, description, and data array need to exist, data must not be empty, the others could be ''.
936 - if ( ! isset( $new_table['name'] )
937 - || ! isset( $new_table['description'] )
938 - || empty( $new_table['data'] )
939 - || empty( $new_table['data'][0] ) ) {
854 + if ( ! isset( $new_table['name'], $new_table['description'] )
855 + || empty( $new_table['data'] ) || empty( $new_table['data'][0] ) ) {
940 856 return new WP_Error( 'table_prepare_name_description_or_data_not_set' );
941 857 }
942 858
943 859 // Visibility needs to exist.
944 - if ( ! isset( $new_table['visibility'] )
945 - || ! isset( $new_table['visibility']['rows'] )
946 - || ! isset( $new_table['visibility']['columns'] ) ) {
860 + if ( ! isset( $new_table['visibility']['rows'], $new_table['visibility']['columns'] ) ) {
947 861 return new WP_Error( 'table_prepare_visibility_not_set' );
948 862 }
949 863 $new_table['visibility']['rows'] = array_map( 'intval', $new_table['visibility']['rows'] );
950 864 $new_table['visibility']['columns'] = array_map( 'intval', $new_table['visibility']['columns'] );
@@ -950,11 +864,9 @@
950 864 $new_table['visibility']['columns'] = array_map( 'intval', $new_table['visibility']['columns'] );
951 865
952 866 // Check dimensions of table data array (not done for newly added, copied, or imported tables).
953 867 if ( $table_size_check ) {
954 - if ( empty( $new_table['number'] )
955 - || ! isset( $new_table['number']['rows'] )
956 - || ! isset( $new_table['number']['columns'] ) ) {
868 + if ( ! isset( $new_table['number']['rows'], $new_table['number']['columns'] ) ) {
957 869 return new WP_Error( 'table_prepare_size_check_numbers_not_set' );
958 870 }
959 871 // Table data needs to be ok, and have the correct number of rows and columns.
960 872 $new_table['number']['rows'] = (int) $new_table['number']['rows'];
@@ -974,18 +886,21 @@
974 886
975 887 // All checks were successful, replace original values with new ones.
976 888
977 889 // $table['id'] is either false (and remains false) or already equal to $new_table['id'].
978 - $table['new_id'] = isset( $new_table['new_id'] ) ? $new_table['new_id'] : $table['id'];
890 + $table['new_id'] = $new_table['new_id'] ?? $table['id'];
979 891 $table['name'] = $new_table['name'];
980 892 $table['description'] = $new_table['description'];
981 893 $table['data'] = $new_table['data'];
982 - // $table['author'] = get_current_user_id(); // We don't want this, as it would override the original author.
983 - // $table['created'] = wp_date( 'Y-m-d H:i:s' ); // We don't want this, as it would override the original datetime.
984 - $table['last_modified'] = wp_date( 'Y-m-d H:i:s' );
985 - $table['options']['last_editor'] = get_current_user_id();
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 + );
986 901 // Table Options.
987 - 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.
988 903 // Specials check for certain options.
989 904 if ( isset( $new_table['options']['extra_css_classes'] ) ) {
990 905 $new_table['options']['extra_css_classes'] = explode( ' ', $new_table['options']['extra_css_classes'] );
991 906 $new_table['options']['extra_css_classes'] = array_map( array( 'TablePress', 'sanitize_css_class' ), $new_table['options']['extra_css_classes'] );
@@ -994,11 +909,20 @@
994 909 }
995 910 if ( isset( $new_table['options']['datatables_paginate_entries'] ) ) {
996 911 $new_table['options']['datatables_paginate_entries'] = (int) $new_table['options']['datatables_paginate_entries'];
997 912 if ( $new_table['options']['datatables_paginate_entries'] < 1 ) {
998 - $new_table['options']['datatables_paginate_entries'] = 10; // default value
913 + $new_table['options']['datatables_paginate_entries'] = 10; // Default value.
999 914 }
1000 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 +
1001 925 // Merge new options.
1002 926 $default_table = $this->get_table_template();
1003 927 $table['options'] = array_intersect_key( $table['options'], $default_table['options'] );
1004 928 $new_table['options'] = array_intersect_key( $new_table['options'], $default_table['options'] );
@@ -1006,11 +930,22 @@
1006 930 }
1007 931 // Table Visibility.
1008 932 $table['visibility']['rows'] = $new_table['visibility']['rows'];
1009 933 $table['visibility']['columns'] = $new_table['visibility']['columns'];
1010 - // 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.
1011 946 if ( '' !== $table['options']['datatables_custom_commands'] ) {
1012 - $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'] );
1013 948 }
1014 949
1015 950 return $table;
1016 951 }
@@ -1019,15 +954,15 @@
1019 954 * Save the table options of a table (in a post meta field of the table's post).
1020 955 *
1021 956 * @since 1.0.0
1022 957 *
1023 - * @param int $post_id Post ID.
1024 - * @param array $options Table options.
958 + * @param int $post_id Post ID.
959 + * @param array<string, mixed> $options Table options.
1025 960 * @return bool True on success, false on error.
1026 961 */
1027 - protected function _add_table_options( $post_id, array $options ) {
962 + protected function _add_table_options( int $post_id, array $options ): bool {
1028 963 $options = wp_json_encode( $options, TABLEPRESS_JSON_OPTIONS );
1029 - 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
1030 965 }
1031 966
1032 967 /**
1033 968 * Update the table options of a table (in a post meta field in the table's post).
@@ -1033,15 +968,15 @@
1033 968 * Update the table options of a table (in a post meta field in the table's post).
1034 969 *
1035 970 * @since 1.0.0
1036 971 *
1037 - * @param int $post_id Post ID.
1038 - * @param array $options Table options.
972 + * @param int $post_id Post ID.
973 + * @param array<string, mixed> $options Table options.
1039 974 * @return bool True on success, false on error.
1040 975 */
1041 - protected function _update_table_options( $post_id, array $options ) {
976 + protected function _update_table_options( int $post_id, array $options ): bool {
1042 977 $options = wp_json_encode( $options, TABLEPRESS_JSON_OPTIONS );
1043 - 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
1044 979 }
1045 980
1046 981 /**
1047 982 * Get the table options of a table (from a post meta field of the table's post).
@@ -1048,16 +983,22 @@
1048 983 *
1049 984 * @since 1.0.0
1050 985 *
1051 986 * @param int $post_id Post ID.
1052 - * @return array Table options on success, empty array on error.
987 + * @return array<string, mixed> Table options on success, empty array on error.
1053 988 */
1054 - protected function _get_table_options( $post_id ) {
989 + protected function _get_table_options( int $post_id ): array {
1055 990 $options = $this->model_post->get_meta_field( $post_id, $this->table_options_field_name );
1056 991 if ( empty( $options ) ) {
1057 992 return array();
1058 993 }
1059 - 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;
1060 1001 }
1061 1002
1062 1003 /**
1063 1004 * Save the table visibility of a table (in a post meta field of the table's post).
@@ -1063,15 +1004,15 @@
1063 1004 * Save the table visibility of a table (in a post meta field of the table's post).
1064 1005 *
1065 1006 * @since 1.0.0
1066 1007 *
1067 - * @param int $post_id Post ID.
1068 - * @param array $visibility Table visibility.
1008 + * @param int $post_id Post ID.
1009 + * @param array{rows: int[], columns: int[]} $visibility Table visibility.
1069 1010 * @return bool True on success, false on error.
1070 1011 */
1071 - protected function _add_table_visibility( $post_id, array $visibility ) {
1012 + protected function _add_table_visibility( int $post_id, array $visibility ): bool {
1072 1013 $visibility = wp_json_encode( $visibility, TABLEPRESS_JSON_OPTIONS );
1073 - 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
1074 1015 }
1075 1016
1076 1017 /**
1077 1018 * Update the table visibility of a table (in a post meta field in the table's post).
@@ -1077,15 +1018,15 @@
1077 1018 * Update the table visibility of a table (in a post meta field in the table's post).
1078 1019 *
1079 1020 * @since 1.0.0
1080 1021 *
1081 - * @param int $post_id Post ID.
1082 - * @param array $visibility Table visibility.
1022 + * @param int $post_id Post ID.
1023 + * @param array{rows: int[], columns: int[]} $visibility Table visibility.
1083 1024 * @return bool True on success, false on error.
1084 1025 */
1085 - protected function _update_table_visibility( $post_id, array $visibility ) {
1026 + protected function _update_table_visibility( int $post_id, array $visibility ): bool {
1086 1027 $visibility = wp_json_encode( $visibility, TABLEPRESS_JSON_OPTIONS );
1087 - 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
1088 1029 }
1089 1030
1090 1031 /**
1091 1032 * Get the table visibility of a table (from a post meta field of the table's post).
@@ -1092,14 +1033,17 @@
1092 1033 *
1093 1034 * @since 1.0.0
1094 1035 *
1095 1036 * @param int $post_id Post ID.
1096 - * @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.
1097 1038 */
1098 - protected function _get_table_visibility( $post_id ) {
1039 + protected function _get_table_visibility( int $post_id ): array {
1099 1040 $visibility = $this->model_post->get_meta_field( $post_id, $this->table_visibility_field_name );
1100 1041 if ( empty( $visibility ) ) {
1101 - return array();
1042 + return array(
1043 + 'rows' => array(),
1044 + 'columns' => array(),
1045 + );
1102 1046 }
1103 1047 return json_decode( $visibility, true );
1104 1048 }
1105 1049
@@ -1108,10 +1052,13 @@
1108 1052 * remove (no longer) existing options, after a table scheme change,
1109 1053 * for all tables.
1110 1054 *
1111 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.
1112 1059 */
1113 - public function merge_table_options_defaults() {
1060 + public function merge_table_options_defaults( bool $remove_old_options = true ): void {
1114 1061 $table_post = $this->tables->get( 'table_post' );
1115 1062 if ( empty( $table_post ) ) {
1116 1063 return;
1117 1064 }
@@ -1122,12 +1069,27 @@
1122 1069 // Get default Table with default Table Options.
1123 1070 $default_table = $this->get_table_template();
1124 1071
1125 1072 // Go through all tables (this loop now uses the WP cache).
1126 - foreach ( $table_post as $table_id => $post_id ) {
1073 + foreach ( $table_post as $post_id ) {
1127 1074 $table_options = $this->_get_table_options( $post_id );
1128 - // Remove old (i.e. no longer existing) Table Options.
1129 - $table_options = array_intersect_key( $table_options, $default_table['options'] );
1075 +
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 );
1087 +
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'] );
1091 + }
1130 1092 // Merge current into new Table Options.
1131 1093 $table_options = array_merge( $default_table['options'], $table_options );
1132 1094 $this->_update_table_options( $post_id, $table_options );
1133 1095 }
@@ -1133,14 +1095,13 @@
1133 1095 }
1134 1096 }
1135 1097
1136 1098 /**
1137 - * Convert old parameter names to new ones in DataTables "Custom Commands".
1138 - * DataTables 1.9 used Hungarian notation, while DataTables 1.10+ (used since TablePress 1.5) uses camelCase notation.
1099 + * Updates all tables' "Custom Commands" to use DataTables 2 variants instead of old DataTables 1.x CSS classes and parameters
1139 1100 *
1140 - * @since 1.5.0
1101 + * @since 3.0.0
1141 1102 */
1142 - public function convert_datatables_parameter_names_tp15() {
1103 + public function update_custom_commands_datatables_tp30(): void {
1143 1104 $table_post = $this->tables->get( 'table_post' );
1144 1105 if ( empty( $table_post ) ) {
1145 1106 return;
1146 1107 }
@@ -1150,15 +1111,22 @@
1150 1111
1151 1112 foreach ( $table_post as $table_id => $post_id ) {
1152 1113 $table_options = $this->_get_table_options( $post_id );
1153 1114
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;
1120 + }
1121 +
1154 1122 // Nothing to do if there are no "Custom Commands".
1155 - if ( empty( $table_options['datatables_custom_commands'] ) ) {
1123 + if ( '' === $table_options['datatables_custom_commands'] ) {
1156 1124 continue;
1157 1125 }
1158 1126 // Run search/replace.
1159 1127 $old_custom_commands = $table_options['datatables_custom_commands'];
1160 - $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'] );
1161 1129 // No need to save (which runs a DB query) if nothing was replaced in the "Custom Commands".
1162 1130 if ( $old_custom_commands === $table_options['datatables_custom_commands'] ) {
1163 1131 continue;
1164 1132 }
@@ -1171,9 +1139,9 @@
1171 1139 * Invalidate all table output caches, e.g. after a plugin update.
1172 1140 *
1173 1141 * @since 1.0.0
1174 1142 */
1175 - public function invalidate_table_output_caches() {
1143 + public function invalidate_table_output_caches(): void {
1176 1144 $table_post = $this->tables->get( 'table_post' );
1177 1145 if ( empty( $table_post ) ) {
1178 1146 return;
1179 1147 }
@@ -1178,8 +1146,9 @@
1178 1146 return;
1179 1147 }
1180 1148
1181 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.
1182 1151 $this->invalidate_table_output_cache( $table_id );
1183 1152 }
1184 1153 }
1185 1154
@@ -1190,11 +1159,11 @@
1190 1159 * @since 1.5.0
1191 1160 *
1192 1161 * @global wpdb $wpdb WordPress database abstraction object.
1193 1162 */
1194 - public function add_mime_type_to_posts() {
1163 + public function add_mime_type_to_posts(): void {
1195 1164 global $wpdb;
1196 - $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
1197 1166 }
1198 1167
1199 1168 /**
1200 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.
@@ -1200,14 +1169,14 @@
1200 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.
1201 1170 *
1202 1171 * @since 1.5.0
1203 1172 *
1204 - * @param int $post_id Post ID of the imported post.
1205 - * @param int $original_post_id Original post ID that the post had on the site where it was exported from.
1206 - * @param array $postdata Post data that was imported into the database.
1207 - * @param array $post Original post data as it was exported.
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.
1208 1177 */
1209 - 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 {
1210 1179 // Bail if the post could not be imported or if the post is not a TablePress table.
1211 1180 if ( is_wp_error( $post_id ) || $this->model_post->get_post_type() !== $postdata['post_type'] ) {
1212 1181 return;
1213 1182 }
@@ -1226,9 +1195,9 @@
1226 1195
1227 1196 // Save the post ID for each of the table IDs.
1228 1197 $post_id_saved = false;
1229 1198 foreach ( $table_ids as $table_id ) {
1230 - $table_id = preg_replace( '/[^a-zA-Z0-9_-]/', '', $table_id );
1199 + $table_id = (string) preg_replace( '/[^a-zA-Z0-9_-]/', '', $table_id );
1231 1200 if ( '' === $table_id || $this->table_exists( $table_id ) ) {
1232 1201 continue;
1233 1202 }
1234 1203 $this->_update_post_id( $table_id, $post_id );
@@ -1246,14 +1215,14 @@
1246 1215 * Remove the `_tablepress_export_table_id` post meta field from the fields that are imported during a WP WXR import.
1247 1216 *
1248 1217 * @since 1.5.0
1249 1218 *
1250 - * @param array $postmeta Post meta fields for the post.
1251 - * @param int $post_id Post ID.
1252 - * @param array $post Post.
1253 - * @return array Modified post meta fields.
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.
1254 1223 */
1255 - 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 {
1256 1225 // Bail if the post is not a TablePress table.
1257 1226 if ( $this->model_post->get_post_type() !== $post['post_type'] ) {
1258 1227 return $postmeta;
1259 1228 }
@@ -1278,10 +1247,11 @@
1278 1247 *
1279 1248 * @param bool $skip Whether to skip the current post meta. Default false.
1280 1249 * @param string $meta_key Current meta key.
1281 1250 * @param stdClass $meta Current meta object.
1251 + * @return bool Whether to skip the current post meta (unchanged $skip parameter).
1282 1252 */
1283 - 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 {
1284 1254 // Bail if the exporter doesn't process a TablePress table right now.
1285 1255 if ( $this->table_options_field_name !== $meta_key ) {
1286 1256 return $skip;
1287 1257 }
@@ -1296,17 +1266,24 @@
1296 1266 }
1297 1267
1298 1268 // Pretend that there is a `_tablepress_export_table_id` post meta field with the list of table IDs.
1299 1269 $key = '_tablepress_export_table_id';
1270 +
1271 + /**
1272 + * Load WP export functions.
1273 + */
1274 + require_once ABSPATH . 'wp-admin/includes/export.php';
1300 1275 $value = wxr_cdata( implode( ',', $table_ids ) );
1301 1276
1302 1277 // Hijack the filter and print extra XML code for our faked post meta field.
1278 + // phpcs:disable WordPress.Security.EscapeOutput.HeredocOutputNotEscaped
1303 1279 echo <<<WXR
1304 - <wp:postmeta>
1305 - <wp:meta_key>{$key}</wp:meta_key>
1306 - <wp:meta_value>{$value}</wp:meta_value>
1307 - </wp:postmeta>\n
1308 -WXR;
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
1309 1286
1310 1287 return $skip;
1311 1288 }
1312 1289
@@ -1314,9 +1291,9 @@
1314 1291 * Delete the WP_Option of the model.
1315 1292 *
1316 1293 * @since 1.0.0
1317 1294 */
1318 - public function destroy() {
1295 + public function destroy(): void {
1319 1296 $this->tables->delete();
1320 1297 }
1321 1298
1322 1299 } // class TablePress_Table_Model