PluginProbe
TablePress – Tables in WordPress made easy / 3.2
TablePress – Tables in WordPress made easy v3.2
3.3.4 3.3.3 3.3.2 3.3.1 trunk 1.12 1.14 1.9.2 2.0.4 2.1.7 2.1.8 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3 2.3.1 2.3.2 2.4 2.4.1 2.4.2 2.4.3 2.4.4 All 44 releases
← All changes | controllers/controller-admin.php +173 -77 2.2.33.2 View file →
@@ -27,9 +27,9 @@
27 27 *
28 28 * @since 1.0.0
29 29 * @var string[]
30 30 */
31 - protected $page_hooks = array();
31 + protected array $page_hooks = array();
32 32
33 33 /**
34 34 * Actions that have a view and admin menu or nav tab menu entry.
35 35 *
@@ -35,17 +35,16 @@
35 35 *
36 36 * @since 1.0.0
37 37 * @var array<string, array<string, bool|string>>
38 38 */
39 - protected $view_actions = array();
39 + protected array $view_actions = array();
40 40
41 41 /**
42 42 * Instance of the TablePress Admin View that is rendered.
43 43 *
44 44 * @since 1.0.0
45 - * @var TablePress_View
46 45 */
47 - protected $view;
46 + protected \TablePress_View $view;
48 47
49 48 /**
50 49 * Initialize the Admin Controller, determine location the admin menu, set up actions.
51 50 *
@@ -97,23 +96,27 @@
97 96
98 97 $this->init_view_actions();
99 98 $min_access_cap = $this->view_actions['list']['required_cap'];
100 99
101 - if ( $this->is_top_level_page ) {
102 - $icon_url = 'dashicons-list-view';
103 - switch ( $this->parent_page ) {
100 + if ( TablePress::$controller->is_top_level_page ) {
101 + $icon_url = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgdmlld0JveD0iLTMyIC0zMiA2NCA2NCIgZmlsbD0iI2ZmZiI+PHBhdGggZD0iTTAtMjUuODU0aC0yNS44NTR2NTEuNzA4aDUxLjcwOFYwSDIxdjIxaC00MnYtNDJIMFoiLz48cGF0aCBkPSJNLTE4LTE4aDEwdjEwaC0xMHpNLTE4LTVoMTBWNWgtMTB6TS01LTVINVY1SC01ek0tMTggOGgxMHYxMGgtMTB6TS01IDhINXYxMEgtNXpNOCA4aDEwdjEwSDh6TTUtMzFoNi4xOHY2LjE4SDV6TTE5LTI1aDYuMTh2Ni4xOEgxOXpNMC0xNWgzLjgydjMuODJIMHpNMTAtMjBoMy44MnYzLjgySDEwek0yNS0xMmgzLjgydjMuODJIMjV6TTgtMTNoMTB2MTBIOHoiLz48L3N2Zz4=';
102 + switch ( TablePress::$controller->parent_page ) {
104 103 case 'top':
105 104 $position = 3; // Position of Dashboard + 1.
106 105 break;
107 106 case 'bottom':
108 - $position = ( ++$GLOBALS['_wp_last_utility_menu'] );
107 + $position = isset( $GLOBALS['_wp_last_utility_menu'] ) ? ++$GLOBALS['_wp_last_utility_menu'] : 80;
109 108 break;
110 109 case 'middle':
111 110 default:
112 - $position = ( ++$GLOBALS['_wp_last_object_menu'] );
111 + $position = isset( $GLOBALS['_wp_last_object_menu'] ) ? ++$GLOBALS['_wp_last_object_menu'] : 25;
113 112 break;
114 113 }
115 - add_menu_page( 'TablePress', $admin_menu_entry_name, $min_access_cap, 'tablepress', $callback, $icon_url, $position ); // @phpstan-ignore-line
114 + // Prevent overwriting existing menu entries.
115 + while ( isset( $GLOBALS['menu'][ $position ] ) ) {
116 + ++$position;
117 + }
118 + add_menu_page( 'TablePress', $admin_menu_entry_name, $min_access_cap, 'tablepress', $callback, $icon_url, $position ); // @phpstan-ignore argument.type
116 119 foreach ( $this->view_actions as $action => $entry ) {
117 120 if ( ! $entry['show_entry'] ) {
118 121 continue;
119 122 }
@@ -120,17 +123,17 @@
120 123 $slug = 'tablepress';
121 124 if ( 'list' !== $action ) {
122 125 $slug .= '_' . $action;
123 126 }
124 - // @phpstan-ignore-next-line
125 - $page_hook = add_submenu_page( 'tablepress', sprintf( __( '%1$s &lsaquo; %2$s', 'tablepress' ), $entry['page_title'], 'TablePress' ), $entry['admin_menu_title'], $entry['required_cap'], $slug, $callback );
127 + /* translators: %1$s: Page title, %2$s: Plugin name (TablePress) */
128 + $page_hook = add_submenu_page( 'tablepress', sprintf( __( '%1$s &lsaquo; %2$s', 'tablepress' ), $entry['page_title'], 'TablePress' ), $entry['admin_menu_title'], $entry['required_cap'], $slug, $callback ); // @phpstan-ignore argument.type, argument.type
126 129 if ( false !== $page_hook ) {
127 130 $this->page_hooks[] = $page_hook;
128 131 }
129 132 }
130 133 } else {
131 - // @phpstan-ignore-next-line
132 - $page_hook = add_submenu_page( $this->parent_page, 'TablePress', $admin_menu_entry_name, $min_access_cap, 'tablepress', $callback );
134 + // @phpstan-ignore argument.type
135 + $page_hook = add_submenu_page( TablePress::$controller->parent_page, 'TablePress', $admin_menu_entry_name, $min_access_cap, 'tablepress', $callback );
133 136 if ( false !== $page_hook ) {
134 137 $this->page_hooks[] = $page_hook;
135 138 }
136 139 }
@@ -175,13 +178,8 @@
175 178 add_action( 'admin_bar_menu', array( $this, 'add_wp_admin_bar_new_content_menu_entry' ), 71 );
176 179 }
177 180
178 181 add_action( 'load-plugins.php', array( $this, 'plugins_page' ) );
179 -
180 - // Add filters and actions for the integration into the WP WXR exporter and importer.
181 - add_action( 'wp_import_insert_post', array( TablePress::$model_table, 'add_table_id_on_wp_import' ), 10, 4 );
182 - add_filter( 'wp_import_post_meta', array( TablePress::$model_table, 'prevent_table_id_post_meta_import_on_wp_import' ), 10, 3 );
183 - add_filter( 'wxr_export_skip_postmeta', array( TablePress::$model_table, 'add_table_id_to_wp_export' ), 10, 3 );
184 182 }
185 183
186 184 /**
187 185 * Loads additional JavaScript code for the TablePress table block (in the block editor context).
@@ -188,8 +186,16 @@
188 186 *
189 187 * @since 2.2.0
190 188 */
191 189 public function enqueue_block_editor_assets(): void {
190 + /*
191 + * Register the `react-jsx-runtime` polyfill, if it is not already registered.
192 + * This is needed as a polyfill for WP < 6.6, and can be removed once WP 6.6 is the minimum requirement for TablePress.
193 + */
194 + if ( ! wp_script_is( 'react-jsx-runtime', 'registered' ) ) {
195 + wp_register_script( 'react-jsx-runtime', plugins_url( 'admin/js/react-jsx-runtime.min.js', TABLEPRESS__FILE__ ), array( 'react' ), TablePress::version, true );
196 + }
197 +
192 198 // Add table information for the block editor to the page.
193 199 $handle = generate_block_asset_handle( 'tablepress/table', 'editorScript' );
194 200 $data = $this->get_block_editor_data();
195 201 wp_add_inline_script( $handle, $data, 'before' );
@@ -202,9 +208,9 @@
202 208 */
203 209 public function enqueue_block_assets(): void {
204 210 // Load the TablePress default CSS and the user's "Custom CSS" in the block editor iframe.
205 211 if ( is_admin() ) {
206 - TablePress::$controller->enqueue_css();
212 + TablePress::$controller->maybe_enqueue_css();
207 213 }
208 214 }
209 215
210 216 /**
@@ -220,12 +226,18 @@
220 226 $table_ids = TablePress::$model_table->load_all( false );
221 227 foreach ( $table_ids as $table_id ) {
222 228 // Load table, without table data, options, and visibility settings.
223 229 $table = TablePress::$model_table->load( $table_id, false, false );
224 - if ( '' === trim( $table['name'] ) ) { // @phpstan-ignore-line
225 - $table['name'] = __( '(no name)', 'tablepress' ); // @phpstan-ignore-line
230 +
231 + // Skip tables that could not be loaded.
232 + if ( is_wp_error( $table ) ) {
233 + continue;
226 234 }
227 - $tables[ $table_id ] = esc_html( $table['name'] ); // @phpstan-ignore-line
235 +
236 + if ( '' === trim( $table['name'] ) ) {
237 + $table['name'] = __( '(no name)', 'tablepress' );
238 + }
239 + $tables[ $table_id ] = esc_html( $table['name'] );
228 240 }
229 241
230 242 /**
231 243 * Filters the list of table IDs and names that is passed to the block editor, and is then used in the dropdown of the TablePress table block.
@@ -235,26 +247,26 @@
235 247 * @param array<string, string> $tables List of table names, the table ID is the array key.
236 248 */
237 249 $tables = apply_filters( 'tablepress_block_editor_tables_list', $tables );
238 250
239 - $tables = wp_json_encode( $tables, TABLEPRESS_JSON_OPTIONS );
251 + $tables = wp_json_encode( $tables, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES );
240 252 if ( false === $tables ) {
241 253 // JSON encoding failed, return an error object. Use a prefixed "_error" key to avoid conflicts with intentionally added "error" keys.
242 254 $tables = '{ "_error": "The data could not be encoded to JSON!" }';
243 255 }
244 - // Print them inside a `JSON.parse()` call in JS for speed gains, with necessary escaping of `</script>`, `'`, and `\`.
245 - $tables = str_replace( array( '</script>', '\\', "'" ), array( '<\/script>', '\\\\', "\'" ), $tables );
256 + // Print the JSON data inside a `JSON.parse()` call in JS for speed gains, with necessary escaping of `\` and `'`.
257 + $tables = str_replace( array( '\\', "'" ), array( '\\\\', "\'" ), $tables );
246 258
247 259 $shortcode = esc_js( TablePress::$shortcode );
248 260
249 261 $template = TablePress::$model_table->get_table_template();
250 - $template = wp_json_encode( $template['options'], TABLEPRESS_JSON_OPTIONS );
262 + $template = wp_json_encode( $template['options'], JSON_HEX_TAG | JSON_UNESCAPED_SLASHES );
251 263 if ( false === $template ) {
252 264 // JSON encoding failed, return an error object. Use a prefixed "_error" key to avoid conflicts with intentionally added "error" keys.
253 265 $template = '{ "_error": "The data could not be encoded to JSON!" }';
254 266 }
255 - // Print them inside a `JSON.parse()` call in JS for speed gains, with necessary escaping of `</script>`, `'`, and `\`.
256 - $template = str_replace( array( '</script>', '\\', "'" ), array( '<\/script>', '\\\\', "\'" ), $template );
267 + // Print the JSON data inside a `JSON.parse()` call in JS for speed gains, with necessary escaping of `\` and `'`.
268 + $template = str_replace( array( '\\', "'" ), array( '\\\\', "\'" ), $template );
257 269
258 270 /**
259 271 * Filters whether the table block preview should be loaded via a <ServerSideRender> in the block editor.
260 272 *
@@ -270,17 +282,17 @@
270 282 $url = TablePress::url( array( 'action' => 'list' ) );
271 283 }
272 284
273 285 return <<<JS
274 -// Ensure the global `tp` object exists.
275 -window.tp = window.tp || {};
276 -tp.url = '{$url}';
277 -tp.load_block_preview = {$load_block_preview};
278 -tp.table = {};
279 -tp.table.shortcode = '{$shortcode}';
280 -tp.table.template = JSON.parse( '{$template}' );
281 -tp.tables = JSON.parse( '{$tables}' );
282 -JS;
286 + // Ensure the global `tp` object exists.
287 + window.tp = window.tp || {};
288 + tp.url = '{$url}';
289 + tp.load_block_preview = {$load_block_preview};
290 + tp.table = {};
291 + tp.table.shortcode = '{$shortcode}';
292 + tp.table.template = JSON.parse( '{$template}' );
293 + tp.tables = JSON.parse( '{$tables}' );
294 + JS;
283 295 }
284 296
285 297 /**
286 298 * Register actions to add "Table" button to "HTML editor" and "Visual editor" toolbars.
@@ -292,9 +304,9 @@
292 304 return;
293 305 }
294 306
295 307 // Only load the toolbar integration if the Block Editor is not used.
296 - if ( TablePress::site_uses_block_editor() ) {
308 + if ( 'block' === TablePress::site_used_editor() ) {
297 309 return;
298 310 }
299 311
300 312 add_thickbox(); // The files are usually already loaded by media upload functions.
@@ -308,9 +320,9 @@
308 320 'title' => __( 'Insert a TablePress table', 'tablepress' ),
309 321 'thickbox_title' => __( 'Insert a TablePress table', 'tablepress' ),
310 322 'thickbox_url' => TablePress::url( array( 'action' => 'editor_button_thickbox' ), true, 'admin-post.php' ),
311 323 ),
312 - )
324 + ),
313 325 );
314 326
315 327 // TinyMCE integration.
316 328 if ( user_can_richedit() ) {
@@ -364,9 +376,9 @@
364 376
365 377 $wp_admin_bar->add_menu( array(
366 378 'parent' => 'new-content',
367 379 'id' => 'new-tablepress-table',
368 - 'title' => __( 'TablePress Table', 'tablepress' ),
380 + 'title' => __( 'TablePress table', 'tablepress' ),
369 381 'href' => TablePress::url( array( 'action' => 'add' ) ),
370 382 ) );
371 383 }
372 384
@@ -378,8 +390,21 @@
378 390 public function plugins_page(): void {
379 391 // Add additional links on Plugins page.
380 392 add_filter( 'plugin_action_links_' . TABLEPRESS_BASENAME, array( $this, 'add_plugin_action_links' ) );
381 393 add_filter( 'plugin_row_meta', array( $this, 'add_plugin_row_meta' ), 10, 2 );
394 + $incompatible_superseded_extensions = array(
395 + 'tablepress-datatables-alphabetsearch/tablepress-datatables-alphabetsearch.php',
396 + 'tablepress-datatables-column-filter-widgets/tablepress-datatables-column-filter-widgets.php',
397 + 'tablepress-datatables-columnfilter/tablepress-datatables-columnfilter.php',
398 + 'tablepress-datatables-fixedcolumns/tablepress-datatables-fixedcolumns.php',
399 + 'tablepress-datatables-inverted-filter/tablepress-datatables-inverted-filter.php',
400 + 'tablepress-datatables-row-details/tablepress-datatables-row-details.php',
401 + 'tablepress-datatables-rowgroup/tablepress-datatables-rowgroup.php',
402 + 'tablepress-responsive-tables/tablepress-responsive-tables.php',
403 + );
404 + foreach ( $incompatible_superseded_extensions as $plugin_file ) {
405 + add_action( "after_plugin_row_{$plugin_file}", array( $this, 'add_superseded_extension_meta_row' ), 10, 3 );
406 + }
382 407 }
383 408
384 409 /**
385 410 * Add links to the TablePress entry in the "Plugin" column on the Plugins page.
@@ -390,9 +415,9 @@
390 415 * @return string[] Extended list of links to print in the "Plugin" column on the Plugins page.
391 416 */
392 417 public function add_plugin_action_links( array $links ): array {
393 418 if ( current_user_can( 'tablepress_list_tables' ) ) {
394 - $links[] = '<a href="' . TablePress::url() . '">' . __( 'Plugin page', 'tablepress' ) . '</a>';
419 + $links[] = '<a href="' . esc_url( TablePress::url() ) . '">' . __( 'Plugin page', 'tablepress' ) . '</a>';
395 420 }
396 421 return $links;
397 422 }
398 423
@@ -409,9 +434,9 @@
409 434 if ( TABLEPRESS_BASENAME === $file ) {
410 435 $links[] = '<a href="https://tablepress.org/faq/" title="' . esc_attr__( 'Frequently Asked Questions', 'tablepress' ) . '">' . __( 'FAQ', 'tablepress' ) . '</a>';
411 436 $links[] = '<a href="https://tablepress.org/documentation/">' . __( 'Documentation', 'tablepress' ) . '</a>';
412 437 $links[] = '<a href="https://tablepress.org/support/">' . __( 'Support', 'tablepress' ) . '</a>';
413 - if ( tb_tp_fs()->is_free_plan() ) {
438 + if ( ! TABLEPRESS_IS_PLAYGROUND_PREVIEW && tb_tp_fs()->is_free_plan() ) {
414 439 $links[] = '<a href="https://tablepress.org/premium/?utm_source=plugin&utm_medium=textlink&utm_content=plugins-screen" title="' . esc_attr__( 'Check out the Premium version of TablePress!', 'tablepress' ) . '"><strong>' . __( 'Go Premium', 'tablepress' ) . '</strong></a>';
415 440 }
416 441 }
417 442 return $links;
@@ -417,8 +442,54 @@
417 442 return $links;
418 443 }
419 444
420 445 /**
446 + * Prints a superseded extension notice below certain TablePress Extension plugins' meta rows on the "Plugins" screen.
447 + *
448 + * @since 2.4.1
449 + *
450 + * @param string $plugin_file Path to the plugin file relative to the plugins directory.
451 + * @param array<int, string|string[]|bool> $plugin_data An array of plugin data.
452 + * @param string $status Status filter currently applied to the plugin list.
453 + */
454 + public function add_superseded_extension_meta_row( string $plugin_file, array $plugin_data, string $status ): void {
455 + if ( ! is_plugin_active( $plugin_file ) ) {
456 + return;
457 + }
458 + ?>
459 + <tr class="plugin-update-tr active">
460 + <td colspan="<?php echo esc_attr( $GLOBALS['wp_list_table']->get_column_count() ); ?>" class="plugin-update colspanchange">
461 + <div class="update-message notice inline notice-error notice-alt">
462 + <?php
463 + if ( tb_tp_fs()->is_free_plan() ) {
464 + echo '<p style="font-size:14px;">';
465 + _e( 'This TablePress Extension was retired.', 'tablepress' );
466 + echo ' ';
467 + _e( '<strong>The plugin does no longer work with TablePress 3</strong> and will no longer receive updates or support!', 'tablepress' );
468 + echo '<br>';
469 + _e( 'Keeping it activated can lead to errors on your website!', 'tablepress' );
470 + echo ' <strong>' . sprintf( __( '<a href="%s">Find out what you can do to continue using its features!</a>', 'tablepress' ), 'https://tablepress.org/upgrade-extensions/?utm_source=plugin&utm_medium=textlink&utm_content=plugins-list-table' ) . '</strong>';
471 + echo '</p>';
472 + }
473 + ?>
474 + <style>
475 + /* Remove the separator line between the plugin's and the notice's table row. */
476 + .plugins .active[data-plugin="<?php echo $plugin_file; ?>"] th,
477 + .plugins .active[data-plugin="<?php echo $plugin_file; ?>"] td {
478 + box-shadow: none;
479 + }
480 + /* Hide the plugin update row for the Extension as those won't work anymore anyways. */
481 + .plugins .plugin-update-tr[data-plugin="<?php echo $plugin_file; ?>"] {
482 + display: none;
483 + }
484 + </style>
485 + </div>
486 + </td>
487 + </tr>
488 + <?php
489 + }
490 +
491 + /**
421 492 * Prepare the rendering of an admin screen, by determining the current action, loading necessary data and initializing the view.
422 493 *
423 494 * @since 1.0.0
424 495 */
@@ -424,9 +495,9 @@
424 495 */
425 496 public function load_admin_page(): void {
426 497 // Determine the action from either the GET parameter (for sub-menu entries, and the main admin menu entry).
427 498 $action = ( ! empty( $_GET['action'] ) ) ? $_GET['action'] : 'list'; // Default action is list.
428 - if ( $this->is_top_level_page ) {
499 + if ( TablePress::$controller->is_top_level_page ) {
429 500 // Or, for sub-menu entry of an admin menu "TablePress" entry, get it from the "page" GET parameter.
430 501 if ( 'tablepress' !== $_GET['page'] ) {
431 502 // Actions that are top-level entries, but don't have an action GET parameter (action is after last _ in string).
432 503 $action = substr( $_GET['page'], 11 ); // $_GET['page'] has the format 'tablepress_{$action}'
@@ -433,9 +504,9 @@
433 504 }
434 505 }
435 506
436 507 // Check if action is a supported action, and whether the user is allowed to access this screen.
437 - if ( ! isset( $this->view_actions[ $action ] ) || ! current_user_can( $this->view_actions[ $action ]['required_cap'] ) ) { // @phpstan-ignore-line
508 + if ( ! isset( $this->view_actions[ $action ] ) || ! current_user_can( $this->view_actions[ $action ]['required_cap'] ) ) { // @phpstan-ignore argument.type (The array value for the capability is always a string.)
438 509 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
439 510 }
440 511
441 512 // Don't load TablePress assets on the Freemius opt-in/activation screen.
@@ -455,12 +526,12 @@
455 526 }
456 527
457 528 // Pre-define some view data.
458 529 $data = array(
459 - 'view_actions' => $this->view_actions,
460 - 'message' => ( ! empty( $_GET['message'] ) ) ? $_GET['message'] : false,
461 - 'error_details' => ( ! empty( $_GET['error_details'] ) ) ? $_GET['error_details'] : '',
462 - 'site_uses_block_editor' => TablePress::site_uses_block_editor(),
530 + 'view_actions' => $this->view_actions,
531 + 'message' => ( ! empty( $_GET['message'] ) ) ? $_GET['message'] : false,
532 + 'error_details' => ( ! empty( $_GET['error_details'] ) ) ? $_GET['error_details'] : '',
533 + 'site_used_editor' => TablePress::site_used_editor(),
463 534 );
464 535
465 536 // Depending on the action, load more necessary data for the corresponding view.
466 537 switch ( $action ) {
@@ -467,17 +538,16 @@
467 538 case 'list':
468 539 $data['table_id'] = ( ! empty( $_GET['table_id'] ) ) ? $_GET['table_id'] : false;
469 540 // Prime the post meta cache for cached loading of last_editor.
470 541 $data['table_ids'] = TablePress::$model_table->load_all( true );
471 - $data['messages']['donation_message'] = $this->maybe_show_donation_message();
472 - $data['messages']['first_visit'] = ! $data['messages']['donation_message'] && TablePress::$model_options->get( 'message_first_visit' );
473 - $data['messages']['plugin_update_message'] = TablePress::$model_options->get( 'message_plugin_update' );
542 + $data['messages']['donation_nag'] = $this->maybe_show_donation_message();
543 + $data['messages']['first_visit'] = ! $data['messages']['donation_nag'] && TablePress::$model_options->get( 'message_first_visit' );
544 + $data['messages']['plugin_update'] = TablePress::$model_options->get( 'message_plugin_update' );
545 + $data['messages']['superseded_extensions'] = current_user_can( 'manage_options' ) && TablePress::$model_options->get( 'message_superseded_extensions' );
474 546 $data['table_count'] = count( $data['table_ids'] );
475 547 break;
476 548 case 'about':
477 549 $data['first_activation'] = TablePress::$model_options->get( 'first_activation' );
478 - $exporter = TablePress::load_class( 'TablePress_Export', 'class-export.php', 'classes' );
479 - $data['zip_support_available'] = $exporter->zip_support_available;
480 550 break;
481 551 case 'options':
482 552 /*
483 553 * Maybe try saving "Custom CSS" to a file:
@@ -484,9 +554,9 @@
484 554 * (called here, as the credentials form posts to this handler again, due to how `request_filesystem_credentials()` works)
485 555 */
486 556 if ( isset( $_GET['item'] ) && 'save_custom_css' === $_GET['item'] ) {
487 557 TablePress::check_nonce( 'options', $_GET['item'] ); // Nonce check here, as we don't have an explicit handler, and even viewing the screen needs to be checked.
488 - $action = 'options_custom_css'; // to load a different view
558 + $action = 'options_custom_css'; // To load a different view.
489 559 // Try saving "Custom CSS" to a file, otherwise this gets the HTML for the credentials form.
490 560 $tablepress_css = TablePress::load_class( 'TablePress_CSS', 'class-css.php', 'classes' );
491 561 $result = $tablepress_css->save_custom_css_to_file_plugin_options( TablePress::$model_options->get( 'custom_css' ), TablePress::$model_options->get( 'custom_css_minified' ) );
492 562 if ( is_string( $result ) ) {
@@ -507,9 +577,9 @@
507 577 break;
508 578 }
509 579 $data['frontend_options']['use_custom_css'] = TablePress::$model_options->get( 'use_custom_css' );
510 580 $data['frontend_options']['custom_css'] = TablePress::$model_options->get( 'custom_css' );
511 - $data['user_options']['parent_page'] = $this->parent_page;
581 + $data['user_options']['parent_page'] = TablePress::$controller->parent_page;
512 582 break;
513 583 case 'edit':
514 584 if ( empty( $_GET['table_id'] ) ) {
515 585 TablePress::redirect( array( 'action' => 'list', 'message' => 'error_no_table' ) );
@@ -532,9 +602,15 @@
532 602 continue;
533 603 }
534 604 // Load table, without table data, options, and visibility settings.
535 605 $table = TablePress::$model_table->load( $table_id, false, false );
536 - $data['tables'][ $table['id'] ] = $table['name']; // @phpstan-ignore-line
606 +
607 + // Skip tables that could not be loaded.
608 + if ( is_wp_error( $table ) ) {
609 + continue;
610 + }
611 +
612 + $data['tables'][ $table['id'] ] = $table['name'];
537 613 }
538 614 $data['tables_count'] = TablePress::$model_table->count_tables();
539 615 $data['export_ids'] = ( ! empty( $_GET['table_id'] ) ) ? explode( ',', $_GET['table_id'] ) : array();
540 616 $exporter = TablePress::load_class( 'TablePress_Export', 'class-export.php', 'classes' );
@@ -553,14 +629,19 @@
553 629 continue;
554 630 }
555 631 // Load table, without table data, options, and visibility settings.
556 632 $table = TablePress::$model_table->load( $table_id, false, false );
557 - $data['tables'][ $table['id'] ] = $table['name']; // @phpstan-ignore-line
633 +
634 + // Skip tables that could not be loaded.
635 + if ( is_wp_error( $table ) ) {
636 + continue;
637 + }
638 +
639 + $data['tables'][ $table['id'] ] = $table['name'];
558 640 }
559 - $data['table_ids'] = $table_ids; // Backwards compatibility for the retired "Table Auto Update" Extension, which still relies on this variable name.
641 + $data['table_ids'] = $table_ids; // Backward compatibility for the retired "Table Auto Update" Extension, which still relies on this variable name.
560 642 $data['tables_count'] = TablePress::$model_table->count_tables();
561 643 $importer = TablePress::load_class( 'TablePress_Import', 'class-import.php', 'classes' );
562 - $data['zip_support_available'] = $importer->zip_support_available;
563 644 $data['import_type'] = ( ! empty( $_GET['import_type'] ) ) ? $_GET['import_type'] : 'add';
564 645 $data['import_existing_table'] = ( ! empty( $_GET['import_existing_table'] ) ) ? $_GET['import_existing_table'] : '';
565 646 $data['import_source'] = ( ! empty( $_GET['import_source'] ) ) ? $_GET['import_source'] : 'file-upload';
566 647 $data['import_url'] = ( ! empty( $_GET['import_url'] ) ) ? wp_unslash( $_GET['import_url'] ) : 'https://';
@@ -789,11 +870,11 @@
789 870 }
790 871
791 872 $add_table = wp_unslash( $_POST['table'] );
792 873
793 - // Perform sanity checks of posted data.
794 - $name = ( isset( $add_table['name'] ) ) ? $add_table['name'] : '';
795 - $description = ( isset( $add_table['description'] ) ) ? $add_table['description'] : '';
874 + // Perform confidence checks of posted data.
875 + $name = $add_table['name'] ?? '';
876 + $description = $add_table['description'] ?? '';
796 877 if ( ! isset( $add_table['rows'], $add_table['columns'] ) ) {
797 878 TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add', 'error_details' => 'The HTTP POST data does not contain the table size.' ) );
798 879 }
799 880
@@ -853,10 +934,10 @@
853 934 if ( ! empty( $posted_options['admin_menu_parent_page'] ) && '-' !== $posted_options['admin_menu_parent_page'] ) {
854 935 $new_options['admin_menu_parent_page'] = $posted_options['admin_menu_parent_page'];
855 936 // Re-init parent information, as `TablePress::redirect()` URL might be wrong otherwise.
856 937 /** This filter is documented in classes/class-controller.php */
857 - $this->parent_page = apply_filters( 'tablepress_admin_menu_parent_page', $posted_options['admin_menu_parent_page'] );
858 - $this->is_top_level_page = in_array( $this->parent_page, array( 'top', 'middle', 'bottom' ), true );
938 + TablePress::$controller->parent_page = apply_filters( 'tablepress_admin_menu_parent_page', $posted_options['admin_menu_parent_page'] );
939 + TablePress::$controller->is_top_level_page = in_array( TablePress::$controller->parent_page, array( 'top', 'middle', 'bottom' ), true );
859 940 }
860 941
861 942 // Custom CSS can only be saved if the user is allowed to do so.
862 943 $update_custom_css_files = false;
@@ -867,13 +948,20 @@
867 948 if ( isset( $posted_options['custom_css'] ) ) {
868 949 $new_options['custom_css'] = $posted_options['custom_css'];
869 950
870 951 $tablepress_css = TablePress::load_class( 'TablePress_CSS', 'class-css.php', 'classes' );
871 - // Sanitize and tidy up Custom CSS.
872 - $new_options['custom_css'] = $tablepress_css->sanitize_css( $new_options['custom_css'] );
873 - // Minify Custom CSS.
874 - $new_options['custom_css_minified'] = $tablepress_css->minify_css( $new_options['custom_css'] );
875 952
953 + if ( '' !== $new_options['custom_css'] ) {
954 + // Update "Custom CSS" to use DataTables 2 variants instead of old DataTables 1.x CSS classes.
955 + $new_options['custom_css'] = TablePress::convert_datatables_api_data( $new_options['custom_css'] );
956 + // Sanitize and tidy up Custom CSS.
957 + $new_options['custom_css'] = $tablepress_css->sanitize_css( $new_options['custom_css'] );
958 + // Minify Custom CSS.
959 + $new_options['custom_css_minified'] = $tablepress_css->minify_css( $new_options['custom_css'] );
960 + } else {
961 + $new_options['custom_css_minified'] = '';
962 + }
963 +
876 964 // Maybe update CSS files as well.
877 965 $custom_css_file_contents = $tablepress_css->load_custom_css_from_file( 'normal' );
878 966 if ( false === $custom_css_file_contents ) {
879 967 $custom_css_file_contents = '';
@@ -1000,9 +1088,9 @@
1000 1088 /** This filter is documented in controllers/controller-admin.php */
1001 1089 $download_filename = apply_filters( 'tablepress_export_filename', $download_filename, '', '', $export['format'], $export_to_zip );
1002 1090 $download_filename = sanitize_file_name( $download_filename );
1003 1091 $full_filename = wp_tempnam( $download_filename );
1004 - if ( true !== $zip_file->open( $full_filename, ZIPARCHIVE::OVERWRITE ) ) {
1092 + if ( true !== $zip_file->open( $full_filename, ZipArchive::OVERWRITE ) ) {
1005 1093 @unlink( $full_filename ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
1006 1094 TablePress::redirect( array( 'action' => 'export', 'message' => 'error_create_zip_file', 'export_format' => $export['format'], 'csv_delimiter' => $export['csv_delimiter'], 'error_details' => 'The ZIP file could not be opened for writing.' ) );
1007 1095 }
1008 1096
@@ -1032,9 +1120,9 @@
1032 1120 }
1033 1121
1034 1122 // If something went wrong, or no files were added to the ZIP file, bail out.
1035 1123 // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
1036 - if ( ZIPARCHIVE::ER_OK !== $zip_file->status || 0 === $zip_file->numFiles ) {
1124 + if ( ZipArchive::ER_OK !== $zip_file->status || 0 === $zip_file->numFiles ) {
1037 1125 $zip_file->close();
1038 1126 @unlink( $full_filename ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
1039 1127 TablePress::redirect( array( 'action' => 'export', 'message' => 'error_create_zip_file', 'export_format' => $export['format'], 'csv_delimiter' => $export['csv_delimiter'], 'error_details' => 'The ZIP file could not be written or is empty.' ) );
1040 1128 }
@@ -1094,8 +1182,15 @@
1094 1182 TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import', 'error_details' => 'You do not have the required access rights.' ) );
1095 1183 }
1096 1184 }
1097 1185
1186 + // For security reasons, the "url" source is only available admins and editors via a custom capability.
1187 + if ( 'url' === $import_config['source'] ) {
1188 + if ( ! current_user_can( 'tablepress_import_tables_url' ) ) {
1189 + TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import', 'error_details' => 'You do not have the required access rights.' ) );
1190 + }
1191 + }
1192 +
1098 1193 // Move file upload data to the main import configuration.
1099 1194 $import_config['file-upload'] = $_FILES['import_file_upload'] ?? null;
1100 1195
1101 1196 // Check if the source data for the chosen import source is defined.
@@ -1132,9 +1227,9 @@
1132 1227 $redirect_parameters['error_details'] = TablePress::get_wp_error_string( $import );
1133 1228 } elseif ( 0 < count( $import['errors'] ) ) {
1134 1229 $wp_error_strings = array();
1135 1230 foreach ( $import['errors'] as $file ) {
1136 - $wp_error_strings[] = TablePress::get_wp_error_string( $file['error'] );
1231 + $wp_error_strings[] = TablePress::get_wp_error_string( $file->error );
1137 1232 }
1138 1233 $redirect_parameters['error_details'] = implode( ', ', $wp_error_strings );
1139 1234 }
1140 1235 TablePress::redirect( $redirect_parameters );
@@ -1294,14 +1389,15 @@
1294 1389 $render_options = shortcode_atts( $default_render_options, $table['options'] );
1295 1390 /** This filter is documented in controllers/controller-frontend.php */
1296 1391 $render_options = apply_filters( 'tablepress_shortcode_table_shortcode_atts', $render_options );
1297 1392 $render_options['html_id'] = "tablepress-{$table['id']}";
1393 + $render_options['block_preview'] = true;
1298 1394 $_render->set_input( $table, $render_options );
1299 1395 $view_data = array(
1300 - 'table_id' => $table_id,
1301 - 'head_html' => $_render->get_preview_css(),
1302 - 'body_html' => $_render->get_output(),
1303 - 'site_uses_block_editor' => TablePress::site_uses_block_editor(),
1396 + 'table_id' => $table_id,
1397 + 'head_html' => $_render->get_preview_css(),
1398 + 'body_html' => $_render->get_output( 'html' ),
1399 + 'site_used_editor' => TablePress::site_used_editor(),
1304 1400 );
1305 1401
1306 1402 $custom_css = TablePress::$model_options->get( 'custom_css' );
1307 1403 $use_custom_css = ( TablePress::$model_options->get( 'use_custom_css' ) && '' !== $custom_css );
@@ -1364,9 +1460,9 @@
1364 1460
1365 1461 TablePress::$model_table->destroy();
1366 1462 TablePress::$model_options->destroy();
1367 1463
1368 - $output = '<strong>' . __( 'TablePress was uninstalled successfully.', 'tablepress' ) . '</strong><br /><br />';
1464 + $output = '<strong>' . __( 'TablePress was uninstalled successfully.', 'tablepress' ) . '</strong><br><br>';
1369 1465 $output .= __( 'All tables, data, and options were deleted.', 'tablepress' );
1370 1466 if ( is_multisite() ) {
1371 1467 $output .= ' ' . __( 'You may now ask the network admin to delete the plugin&#8217;s folder <code>tablepress</code> from the server, if no other site in the network uses it.', 'tablepress' );
1372 1468 } else {