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 | controllers/controller-admin.php +158 -81 2.3.2 → 3.4 View file →
@@ -7,8 +7,10 @@
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 /**
@@ -27,9 +29,9 @@
27 29 *
28 30 * @since 1.0.0
29 31 * @var string[]
30 32 */
31 - protected $page_hooks = array();
33 + protected array $page_hooks = array();
32 34
33 35 /**
34 36 * Actions that have a view and admin menu or nav tab menu entry.
35 37 *
@@ -35,17 +37,16 @@
35 37 *
36 38 * @since 1.0.0
37 39 * @var array<string, array<string, bool|string>>
38 40 */
39 - protected $view_actions = array();
41 + protected array $view_actions = array();
40 42
41 43 /**
42 44 * Instance of the TablePress Admin View that is rendered.
43 45 *
44 46 * @since 1.0.0
45 - * @var TablePress_View
46 47 */
47 - protected $view;
48 + protected \TablePress_View $view;
48 49
49 50 /**
50 51 * Initialize the Admin Controller, determine location the admin menu, set up actions.
51 52 *
@@ -94,26 +95,34 @@
94 95 * @param string $entry_name The admin menu entry name. Default "TablePress".
95 96 */
96 97 $admin_menu_entry_name = apply_filters( 'tablepress_admin_menu_entry_name', 'TablePress' );
97 98
99 + if ( TablePress::$model_options->get( 'message_plugin_update' ) && strtotime( '2026-10-10' ) >= strtotime( 'today' ) ) {
100 + $admin_menu_entry_name .= ' <span class="dashicons dashicons-buddicons-community" aria-hidden="true" style="color:orange"></span>';
101 + }
102 +
98 103 $this->init_view_actions();
99 104 $min_access_cap = $this->view_actions['list']['required_cap'];
100 105
101 - if ( $this->is_top_level_page ) {
102 - $icon_url = 'dashicons-list-view';
103 - switch ( $this->parent_page ) {
106 + if ( TablePress::$controller->is_top_level_page ) {
107 + $icon_url = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgdmlld0JveD0iLTMyIC0zMiA2NCA2NCIgZmlsbD0iI2ZmZiI+PHBhdGggZD0iTTAtMjUuODU0aC0yNS44NTR2NTEuNzA4aDUxLjcwOFYwSDIxdjIxaC00MnYtNDJIMFoiLz48cGF0aCBkPSJNLTE4LTE4aDEwdjEwaC0xMHpNLTE4LTVoMTBWNWgtMTB6TS01LTVINVY1SC01ek0tMTggOGgxMHYxMGgtMTB6TS01IDhINXYxMEgtNXpNOCA4aDEwdjEwSDh6TTUtMzFoNi4xOHY2LjE4SDV6TTE5LTI1aDYuMTh2Ni4xOEgxOXpNMC0xNWgzLjgydjMuODJIMHpNMTAtMjBoMy44MnYzLjgySDEwek0yNS0xMmgzLjgydjMuODJIMjV6TTgtMTNoMTB2MTBIOHoiLz48L3N2Zz4=';
108 + switch ( TablePress::$controller->parent_page ) {
104 109 case 'top':
105 110 $position = 3; // Position of Dashboard + 1.
106 111 break;
107 112 case 'bottom':
108 - $position = ( ++$GLOBALS['_wp_last_utility_menu'] );
113 + $position = isset( $GLOBALS['_wp_last_utility_menu'] ) ? ++$GLOBALS['_wp_last_utility_menu'] : 80;
109 114 break;
110 115 case 'middle':
111 116 default:
112 - $position = ( ++$GLOBALS['_wp_last_object_menu'] );
117 + $position = isset( $GLOBALS['_wp_last_object_menu'] ) ? ++$GLOBALS['_wp_last_object_menu'] : 25;
113 118 break;
114 119 }
115 - add_menu_page( 'TablePress', $admin_menu_entry_name, $min_access_cap, 'tablepress', $callback, $icon_url, $position ); // @phpstan-ignore-line
120 + // Prevent overwriting existing menu entries.
121 + while ( isset( $GLOBALS['menu'][ $position ] ) ) {
122 + ++$position;
123 + }
124 + add_menu_page( 'TablePress', $admin_menu_entry_name, $min_access_cap, 'tablepress', $callback, $icon_url, $position ); // @phpstan-ignore argument.type
116 125 foreach ( $this->view_actions as $action => $entry ) {
117 126 if ( ! $entry['show_entry'] ) {
118 127 continue;
119 128 }
@@ -120,17 +129,17 @@
120 129 $slug = 'tablepress';
121 130 if ( 'list' !== $action ) {
122 131 $slug .= '_' . $action;
123 132 }
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 );
133 + /* translators: %1$s: Page title, %2$s: Plugin name (TablePress) */
134 + $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 135 if ( false !== $page_hook ) {
127 136 $this->page_hooks[] = $page_hook;
128 137 }
129 138 }
130 139 } 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 );
140 + // @phpstan-ignore argument.type
141 + $page_hook = add_submenu_page( TablePress::$controller->parent_page, 'TablePress', $admin_menu_entry_name, $min_access_cap, 'tablepress', $callback );
133 142 if ( false !== $page_hook ) {
134 143 $this->page_hooks[] = $page_hook;
135 144 }
136 145 }
@@ -175,13 +184,8 @@
175 184 add_action( 'admin_bar_menu', array( $this, 'add_wp_admin_bar_new_content_menu_entry' ), 71 );
176 185 }
177 186
178 187 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 188 }
185 189
186 190 /**
187 191 * Loads additional JavaScript code for the TablePress table block (in the block editor context).
@@ -188,9 +192,8 @@
188 192 *
189 193 * @since 2.2.0
190 194 */
191 195 public function enqueue_block_editor_assets(): void {
192 - // Add table information for the block editor to the page.
193 196 $handle = generate_block_asset_handle( 'tablepress/table', 'editorScript' );
194 197 $data = $this->get_block_editor_data();
195 198 wp_add_inline_script( $handle, $data, 'before' );
196 199 }
@@ -202,9 +205,9 @@
202 205 */
203 206 public function enqueue_block_assets(): void {
204 207 // Load the TablePress default CSS and the user's "Custom CSS" in the block editor iframe.
205 208 if ( is_admin() ) {
206 - TablePress::$controller->enqueue_css();
209 + TablePress::$controller->maybe_enqueue_css();
207 210 }
208 211 }
209 212
210 213 /**
@@ -241,26 +244,26 @@
241 244 * @param array<string, string> $tables List of table names, the table ID is the array key.
242 245 */
243 246 $tables = apply_filters( 'tablepress_block_editor_tables_list', $tables );
244 247
245 - $tables = wp_json_encode( $tables, TABLEPRESS_JSON_OPTIONS );
248 + $tables = wp_json_encode( $tables, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES );
246 249 if ( false === $tables ) {
247 250 // JSON encoding failed, return an error object. Use a prefixed "_error" key to avoid conflicts with intentionally added "error" keys.
248 251 $tables = '{ "_error": "The data could not be encoded to JSON!" }';
249 252 }
250 - // Print them inside a `JSON.parse()` call in JS for speed gains, with necessary escaping of `</script>`, `'`, and `\`.
251 - $tables = str_replace( array( '</script>', '\\', "'" ), array( '<\/script>', '\\\\', "\'" ), $tables );
253 + // Print the JSON data inside a `JSON.parse()` call in JS for speed gains, with necessary escaping of `\` and `'`.
254 + $tables = str_replace( array( '\\', "'" ), array( '\\\\', "\'" ), $tables );
252 255
253 256 $shortcode = esc_js( TablePress::$shortcode );
254 257
255 258 $template = TablePress::$model_table->get_table_template();
256 - $template = wp_json_encode( $template['options'], TABLEPRESS_JSON_OPTIONS );
259 + $template = wp_json_encode( $template['options'], JSON_HEX_TAG | JSON_UNESCAPED_SLASHES );
257 260 if ( false === $template ) {
258 261 // JSON encoding failed, return an error object. Use a prefixed "_error" key to avoid conflicts with intentionally added "error" keys.
259 262 $template = '{ "_error": "The data could not be encoded to JSON!" }';
260 263 }
261 - // Print them inside a `JSON.parse()` call in JS for speed gains, with necessary escaping of `</script>`, `'`, and `\`.
262 - $template = str_replace( array( '</script>', '\\', "'" ), array( '<\/script>', '\\\\', "\'" ), $template );
264 + // Print the JSON data inside a `JSON.parse()` call in JS for speed gains, with necessary escaping of `\` and `'`.
265 + $template = str_replace( array( '\\', "'" ), array( '\\\\', "\'" ), $template );
263 266
264 267 /**
265 268 * Filters whether the table block preview should be loaded via a <ServerSideRender> in the block editor.
266 269 *
@@ -276,17 +279,17 @@
276 279 $url = TablePress::url( array( 'action' => 'list' ) );
277 280 }
278 281
279 282 return <<<JS
280 -// Ensure the global `tp` object exists.
281 -window.tp = window.tp || {};
282 -tp.url = '{$url}';
283 -tp.load_block_preview = {$load_block_preview};
284 -tp.table = {};
285 -tp.table.shortcode = '{$shortcode}';
286 -tp.table.template = JSON.parse( '{$template}' );
287 -tp.tables = JSON.parse( '{$tables}' );
288 -JS;
283 + // Ensure the global `tp` object exists.
284 + window.tp = window.tp || {};
285 + tp.url = '{$url}';
286 + tp.load_block_preview = {$load_block_preview};
287 + tp.table = {};
288 + tp.table.shortcode = '{$shortcode}';
289 + tp.table.template = JSON.parse( '{$template}' );
290 + tp.tables = JSON.parse( '{$tables}' );
291 + JS;
289 292 }
290 293
291 294 /**
292 295 * Register actions to add "Table" button to "HTML editor" and "Visual editor" toolbars.
@@ -298,15 +301,14 @@
298 301 return;
299 302 }
300 303
301 304 // Only load the toolbar integration if the Block Editor is not used.
302 - if ( TablePress::site_uses_block_editor() ) {
305 + if ( 'block' === TablePress::site_used_editor() ) {
303 306 return;
304 307 }
305 308
306 309 add_thickbox(); // The files are usually already loaded by media upload functions.
307 - $admin_page = TablePress::load_class( 'TablePress_Admin_Page', 'class-admin-page-helper.php', 'classes' );
308 - $admin_page->enqueue_script(
310 + TablePress::enqueue_script(
309 311 'quicktags-button',
310 312 array( 'quicktags', 'media-upload' ),
311 313 array(
312 314 'editor_button' => array(
@@ -314,9 +316,9 @@
314 316 'title' => __( 'Insert a TablePress table', 'tablepress' ),
315 317 'thickbox_title' => __( 'Insert a TablePress table', 'tablepress' ),
316 318 'thickbox_url' => TablePress::url( array( 'action' => 'editor_button_thickbox' ), true, 'admin-post.php' ),
317 319 ),
318 - )
320 + ),
319 321 );
320 322
321 323 // TinyMCE integration.
322 324 if ( user_can_richedit() ) {
@@ -370,9 +372,9 @@
370 372
371 373 $wp_admin_bar->add_menu( array(
372 374 'parent' => 'new-content',
373 375 'id' => 'new-tablepress-table',
374 - 'title' => __( 'TablePress Table', 'tablepress' ),
376 + 'title' => __( 'TablePress table', 'tablepress' ),
375 377 'href' => TablePress::url( array( 'action' => 'add' ) ),
376 378 ) );
377 379 }
378 380
@@ -384,8 +386,21 @@
384 386 public function plugins_page(): void {
385 387 // Add additional links on Plugins page.
386 388 add_filter( 'plugin_action_links_' . TABLEPRESS_BASENAME, array( $this, 'add_plugin_action_links' ) );
387 389 add_filter( 'plugin_row_meta', array( $this, 'add_plugin_row_meta' ), 10, 2 );
390 + $incompatible_superseded_extensions = array(
391 + 'tablepress-datatables-alphabetsearch/tablepress-datatables-alphabetsearch.php',
392 + 'tablepress-datatables-column-filter-widgets/tablepress-datatables-column-filter-widgets.php',
393 + 'tablepress-datatables-columnfilter/tablepress-datatables-columnfilter.php',
394 + 'tablepress-datatables-fixedcolumns/tablepress-datatables-fixedcolumns.php',
395 + 'tablepress-datatables-inverted-filter/tablepress-datatables-inverted-filter.php',
396 + 'tablepress-datatables-row-details/tablepress-datatables-row-details.php',
397 + 'tablepress-datatables-rowgroup/tablepress-datatables-rowgroup.php',
398 + 'tablepress-responsive-tables/tablepress-responsive-tables.php',
399 + );
400 + foreach ( $incompatible_superseded_extensions as $plugin_file ) {
401 + add_action( "after_plugin_row_{$plugin_file}", array( $this, 'add_superseded_extension_meta_row' ), 10, 3 );
402 + }
388 403 }
389 404
390 405 /**
391 406 * Add links to the TablePress entry in the "Plugin" column on the Plugins page.
@@ -396,9 +411,9 @@
396 411 * @return string[] Extended list of links to print in the "Plugin" column on the Plugins page.
397 412 */
398 413 public function add_plugin_action_links( array $links ): array {
399 414 if ( current_user_can( 'tablepress_list_tables' ) ) {
400 - $links[] = '<a href="' . TablePress::url() . '">' . __( 'Plugin page', 'tablepress' ) . '</a>';
415 + $links[] = '<a href="' . esc_url( TablePress::url() ) . '">' . __( 'Plugin page', 'tablepress' ) . '</a>';
401 416 }
402 417 return $links;
403 418 }
404 419
@@ -423,8 +438,54 @@
423 438 return $links;
424 439 }
425 440
426 441 /**
442 + * Prints a superseded extension notice below certain TablePress Extension plugins' meta rows on the "Plugins" screen.
443 + *
444 + * @since 2.4.1
445 + *
446 + * @param string $plugin_file Path to the plugin file relative to the plugins directory.
447 + * @param array<int, string|string[]|bool> $plugin_data An array of plugin data.
448 + * @param string $status Status filter currently applied to the plugin list.
449 + */
450 + public function add_superseded_extension_meta_row( string $plugin_file, array $plugin_data, string $status ): void {
451 + if ( ! is_plugin_active( $plugin_file ) ) {
452 + return;
453 + }
454 + ?>
455 + <tr class="plugin-update-tr active">
456 + <td colspan="<?php echo esc_attr( $GLOBALS['wp_list_table']->get_column_count() ); ?>" class="plugin-update colspanchange">
457 + <div class="update-message notice inline notice-error notice-alt">
458 + <?php
459 + if ( tb_tp_fs()->is_free_plan() ) {
460 + echo '<p style="font-size:14px;">';
461 + _e( 'This TablePress Extension was retired.', 'tablepress' );
462 + echo ' ';
463 + _e( '<strong>The plugin does no longer work</strong> and will no longer receive updates or support!', 'tablepress' );
464 + echo '<br>';
465 + _e( 'Keeping it activated can lead to errors on your website!', 'tablepress' );
466 + 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>';
467 + echo '</p>';
468 + }
469 + ?>
470 + <style>
471 + /* Remove the separator line between the plugin's and the notice's table row. */
472 + .plugins .active[data-plugin="<?php echo $plugin_file; ?>"] th,
473 + .plugins .active[data-plugin="<?php echo $plugin_file; ?>"] td {
474 + box-shadow: none;
475 + }
476 + /* Hide the plugin update row for the Extension as those won't work anymore anyways. */
477 + .plugins .plugin-update-tr[data-plugin="<?php echo $plugin_file; ?>"] {
478 + display: none;
479 + }
480 + </style>
481 + </div>
482 + </td>
483 + </tr>
484 + <?php
485 + }
486 +
487 + /**
427 488 * Prepare the rendering of an admin screen, by determining the current action, loading necessary data and initializing the view.
428 489 *
429 490 * @since 1.0.0
430 491 */
@@ -430,9 +491,9 @@
430 491 */
431 492 public function load_admin_page(): void {
432 493 // Determine the action from either the GET parameter (for sub-menu entries, and the main admin menu entry).
433 494 $action = ( ! empty( $_GET['action'] ) ) ? $_GET['action'] : 'list'; // Default action is list.
434 - if ( $this->is_top_level_page ) {
495 + if ( TablePress::$controller->is_top_level_page ) {
435 496 // Or, for sub-menu entry of an admin menu "TablePress" entry, get it from the "page" GET parameter.
436 497 if ( 'tablepress' !== $_GET['page'] ) {
437 498 // Actions that are top-level entries, but don't have an action GET parameter (action is after last _ in string).
438 499 $action = substr( $_GET['page'], 11 ); // $_GET['page'] has the format 'tablepress_{$action}'
@@ -439,9 +500,9 @@
439 500 }
440 501 }
441 502
442 503 // Check if action is a supported action, and whether the user is allowed to access this screen.
443 - if ( ! isset( $this->view_actions[ $action ] ) || ! current_user_can( $this->view_actions[ $action ]['required_cap'] ) ) { // @phpstan-ignore-line (The array value for the capability is always a string.)
504 + 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.)
444 505 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
445 506 }
446 507
447 508 // Don't load TablePress assets on the Freemius opt-in/activation screen.
@@ -461,23 +522,24 @@
461 522 }
462 523
463 524 // Pre-define some view data.
464 525 $data = array(
465 - 'view_actions' => $this->view_actions,
466 - 'message' => ( ! empty( $_GET['message'] ) ) ? $_GET['message'] : false,
467 - 'error_details' => ( ! empty( $_GET['error_details'] ) ) ? $_GET['error_details'] : '',
468 - 'site_uses_block_editor' => TablePress::site_uses_block_editor(),
526 + 'view_actions' => $this->view_actions,
527 + 'message' => ( ! empty( $_GET['message'] ) ) ? $_GET['message'] : false,
528 + 'error_details' => ( ! empty( $_GET['error_details'] ) ) ? rawurldecode( wp_unslash( $_GET['error_details'] ) ) : '',
529 + 'site_used_editor' => TablePress::site_used_editor(),
469 530 );
470 531
471 532 // Depending on the action, load more necessary data for the corresponding view.
472 533 switch ( $action ) {
473 534 case 'list':
474 - $data['table_id'] = ( ! empty( $_GET['table_id'] ) ) ? $_GET['table_id'] : false;
535 + $data['table_id'] = ( isset( $_GET['table_id'] ) ) ? preg_replace( '/[^a-zA-Z0-9_-]/', '', $_GET['table_id'] ) : false;
475 536 // Prime the post meta cache for cached loading of last_editor.
476 537 $data['table_ids'] = TablePress::$model_table->load_all( true );
477 - $data['messages']['donation_message'] = $this->maybe_show_donation_message();
478 - $data['messages']['first_visit'] = ! $data['messages']['donation_message'] && TablePress::$model_options->get( 'message_first_visit' );
479 - $data['messages']['plugin_update_message'] = TablePress::$model_options->get( 'message_plugin_update' );
538 + $data['messages']['donation_nag'] = $this->maybe_show_donation_message();
539 + $data['messages']['first_visit'] = ! $data['messages']['donation_nag'] && TablePress::$model_options->get( 'message_first_visit' );
540 + $data['messages']['plugin_update'] = TablePress::$model_options->get( 'message_plugin_update' );
541 + $data['messages']['superseded_extensions'] = current_user_can( 'manage_options' ) && TablePress::$model_options->get( 'message_superseded_extensions' );
480 542 $data['table_count'] = count( $data['table_ids'] );
481 543 break;
482 544 case 'about':
483 545 $data['first_activation'] = TablePress::$model_options->get( 'first_activation' );
@@ -488,9 +550,9 @@
488 550 * (called here, as the credentials form posts to this handler again, due to how `request_filesystem_credentials()` works)
489 551 */
490 552 if ( isset( $_GET['item'] ) && 'save_custom_css' === $_GET['item'] ) {
491 553 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.
492 - $action = 'options_custom_css'; // to load a different view
554 + $action = 'options_custom_css'; // To load a different view.
493 555 // Try saving "Custom CSS" to a file, otherwise this gets the HTML for the credentials form.
494 556 $tablepress_css = TablePress::load_class( 'TablePress_CSS', 'class-css.php', 'classes' );
495 557 $result = $tablepress_css->save_custom_css_to_file_plugin_options( TablePress::$model_options->get( 'custom_css' ), TablePress::$model_options->get( 'custom_css_minified' ) );
496 558 if ( is_string( $result ) ) {
@@ -511,12 +573,12 @@
511 573 break;
512 574 }
513 575 $data['frontend_options']['use_custom_css'] = TablePress::$model_options->get( 'use_custom_css' );
514 576 $data['frontend_options']['custom_css'] = TablePress::$model_options->get( 'custom_css' );
515 - $data['user_options']['parent_page'] = $this->parent_page;
577 + $data['user_options']['parent_page'] = TablePress::$controller->parent_page;
516 578 break;
517 579 case 'edit':
518 - if ( empty( $_GET['table_id'] ) ) {
580 + if ( ! isset( $_GET['table_id'] ) || ! preg_match( '/^[a-zA-Z0-9_-]+$/', $_GET['table_id'] ) ) {
519 581 TablePress::redirect( array( 'action' => 'list', 'message' => 'error_no_table' ) );
520 582 }
521 583 // Load table, with table data, options, and visibility settings.
522 584 $data['table'] = TablePress::$model_table->load( $_GET['table_id'], true, true );
@@ -545,9 +607,9 @@
545 607
546 608 $data['tables'][ $table['id'] ] = $table['name'];
547 609 }
548 610 $data['tables_count'] = TablePress::$model_table->count_tables();
549 - $data['export_ids'] = ( ! empty( $_GET['table_id'] ) ) ? explode( ',', $_GET['table_id'] ) : array();
611 + $data['export_ids'] = ( isset( $_GET['table_id'] ) && preg_match( '/^[,a-zA-Z0-9_-]+$/', $_GET['table_id'] ) ) ? explode( ',', $_GET['table_id'] ) : array();
550 612 $exporter = TablePress::load_class( 'TablePress_Export', 'class-export.php', 'classes' );
551 613 $data['zip_support_available'] = $exporter->zip_support_available;
552 614 $data['export_formats'] = $exporter->export_formats;
553 615 $data['csv_delimiters'] = $exporter->csv_delimiters;
@@ -575,13 +637,13 @@
575 637 $data['table_ids'] = $table_ids; // Backward compatibility for the retired "Table Auto Update" Extension, which still relies on this variable name.
576 638 $data['tables_count'] = TablePress::$model_table->count_tables();
577 639 $importer = TablePress::load_class( 'TablePress_Import', 'class-import.php', 'classes' );
578 640 $data['import_type'] = ( ! empty( $_GET['import_type'] ) ) ? $_GET['import_type'] : 'add';
579 - $data['import_existing_table'] = ( ! empty( $_GET['import_existing_table'] ) ) ? $_GET['import_existing_table'] : '';
641 + $data['import_existing_table'] = $_GET['import_existing_table'] ?? '';
580 642 $data['import_source'] = ( ! empty( $_GET['import_source'] ) ) ? $_GET['import_source'] : 'file-upload';
581 - $data['import_url'] = ( ! empty( $_GET['import_url'] ) ) ? wp_unslash( $_GET['import_url'] ) : 'https://';
582 - $data['import_server'] = ( ! empty( $_GET['import_server'] ) ) ? wp_unslash( $_GET['import_server'] ) : ABSPATH;
583 - $data['import_form-field'] = ( ! empty( $_GET['import_form-field'] ) ) ? wp_unslash( $_GET['import_form-field'] ) : '';
643 + $data['import_url'] = ( ! empty( $_GET['import_url'] ) ) ? rawurldecode( wp_unslash( $_GET['import_url'] ) ) : 'https://';
644 + $data['import_server'] = ( ! empty( $_GET['import_server'] ) ) ? rawurldecode( wp_unslash( $_GET['import_server'] ) ) : ABSPATH;
645 + $data['import_form-field'] = ( ! empty( $_GET['import_form-field'] ) ) ? rawurldecode( wp_unslash( $_GET['import_form-field'] ) ) : '';
584 646 $data['legacy_import'] = ( ! empty( $_GET['legacy_import'] ) ) ? $_GET['legacy_import'] : 'false';
585 647 break;
586 648 }
587 649
@@ -805,10 +867,10 @@
805 867
806 868 $add_table = wp_unslash( $_POST['table'] );
807 869
808 870 // Perform confidence checks of posted data.
809 - $name = ( isset( $add_table['name'] ) ) ? $add_table['name'] : '';
810 - $description = ( isset( $add_table['description'] ) ) ? $add_table['description'] : '';
871 + $name = $add_table['name'] ?? '';
872 + $description = $add_table['description'] ?? '';
811 873 if ( ! isset( $add_table['rows'], $add_table['columns'] ) ) {
812 874 TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add', 'error_details' => 'The HTTP POST data does not contain the table size.' ) );
813 875 }
814 876
@@ -868,10 +930,10 @@
868 930 if ( ! empty( $posted_options['admin_menu_parent_page'] ) && '-' !== $posted_options['admin_menu_parent_page'] ) {
869 931 $new_options['admin_menu_parent_page'] = $posted_options['admin_menu_parent_page'];
870 932 // Re-init parent information, as `TablePress::redirect()` URL might be wrong otherwise.
871 933 /** This filter is documented in classes/class-controller.php */
872 - $this->parent_page = apply_filters( 'tablepress_admin_menu_parent_page', $posted_options['admin_menu_parent_page'] );
873 - $this->is_top_level_page = in_array( $this->parent_page, array( 'top', 'middle', 'bottom' ), true );
934 + TablePress::$controller->parent_page = apply_filters( 'tablepress_admin_menu_parent_page', $posted_options['admin_menu_parent_page'] );
935 + TablePress::$controller->is_top_level_page = in_array( TablePress::$controller->parent_page, array( 'top', 'middle', 'bottom' ), true );
874 936 }
875 937
876 938 // Custom CSS can only be saved if the user is allowed to do so.
877 939 $update_custom_css_files = false;
@@ -882,13 +944,20 @@
882 944 if ( isset( $posted_options['custom_css'] ) ) {
883 945 $new_options['custom_css'] = $posted_options['custom_css'];
884 946
885 947 $tablepress_css = TablePress::load_class( 'TablePress_CSS', 'class-css.php', 'classes' );
886 - // Sanitize and tidy up Custom CSS.
887 - $new_options['custom_css'] = $tablepress_css->sanitize_css( $new_options['custom_css'] );
888 - // Minify Custom CSS.
889 - $new_options['custom_css_minified'] = $tablepress_css->minify_css( $new_options['custom_css'] );
890 948
949 + if ( '' !== $new_options['custom_css'] ) {
950 + // Update "Custom CSS" to use DataTables 2 variants instead of old DataTables 1.x CSS classes.
951 + $new_options['custom_css'] = TablePress::convert_datatables_api_data( $new_options['custom_css'] );
952 + // Sanitize and tidy up Custom CSS.
953 + $new_options['custom_css'] = $tablepress_css->sanitize_css( $new_options['custom_css'] );
954 + // Minify Custom CSS.
955 + $new_options['custom_css_minified'] = $tablepress_css->minify_css( $new_options['custom_css'] );
956 + } else {
957 + $new_options['custom_css_minified'] = '';
958 + }
959 +
891 960 // Maybe update CSS files as well.
892 961 $custom_css_file_contents = $tablepress_css->load_custom_css_from_file( 'normal' );
893 962 if ( false === $custom_css_file_contents ) {
894 963 $custom_css_file_contents = '';
@@ -942,10 +1011,9 @@
942 1011
943 1012 if ( empty( $export['format'] ) || ! isset( $exporter->export_formats[ $export['format'] ] ) ) {
944 1013 TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export', 'error_details' => 'The export format is invalid.' ) );
945 1014 }
946 - if ( empty( $export['csv_delimiter'] ) ) {
947 - // Set a value, so that the variable exists.
1015 + if ( ! isset( $export['csv_delimiter'] ) ) {
948 1016 $export['csv_delimiter'] = '';
949 1017 }
950 1018 if ( 'csv' === $export['format'] && ! isset( $exporter->csv_delimiters[ $export['csv_delimiter'] ] ) ) {
951 1019 TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export', 'error_details' => 'The CSV delimiter is invalid.' ) );
@@ -989,9 +1057,13 @@
989 1057 */
990 1058 $download_filename = apply_filters( 'tablepress_export_filename', $download_filename, $table['id'], $table['name'], $export['format'], $export_to_zip );
991 1059 $download_filename = sanitize_file_name( $download_filename );
992 1060 // Export the table.
993 - $export_data = $exporter->export_table( $table, $export['format'], $export['csv_delimiter'] );
1061 + $options = array();
1062 + if ( 'csv' === $export['format'] ) {
1063 + $options['csv_delimiter'] = $export['csv_delimiter'];
1064 + }
1065 + $export_data = $exporter->export_table( $table, $export['format'], $options );
994 1066 /**
995 1067 * Filters the exported table data.
996 1068 *
997 1069 * @since 1.6.0
@@ -1015,9 +1087,9 @@
1015 1087 /** This filter is documented in controllers/controller-admin.php */
1016 1088 $download_filename = apply_filters( 'tablepress_export_filename', $download_filename, '', '', $export['format'], $export_to_zip );
1017 1089 $download_filename = sanitize_file_name( $download_filename );
1018 1090 $full_filename = wp_tempnam( $download_filename );
1019 - if ( true !== $zip_file->open( $full_filename, ZIPARCHIVE::OVERWRITE ) ) {
1091 + if ( true !== $zip_file->open( $full_filename, ZipArchive::OVERWRITE ) ) {
1020 1092 @unlink( $full_filename ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
1021 1093 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.' ) );
1022 1094 }
1023 1095
@@ -1035,9 +1107,13 @@
1035 1107 // Don't export if the table is corrupted.
1036 1108 if ( isset( $table['is_corrupted'] ) && $table['is_corrupted'] ) {
1037 1109 continue;
1038 1110 }
1039 - $export_data = $exporter->export_table( $table, $export['format'], $export['csv_delimiter'] );
1111 + $options = array();
1112 + if ( 'csv' === $export['format'] ) {
1113 + $options['csv_delimiter'] = $export['csv_delimiter'];
1114 + }
1115 + $export_data = $exporter->export_table( $table, $export['format'], $options );
1040 1116 /** This filter is documented in controllers/controller-admin.php */
1041 1117 $export_data = apply_filters( 'tablepress_export_data', $export_data, $table, $export['format'], $export['csv_delimiter'] );
1042 1118 $export_filename = sprintf( '%1$s-%2$s-%3$s.%4$s', $table['id'], $table['name'], wp_date( 'Y-m-d' ), $export['format'] );
1043 1119 /** This filter is documented in controllers/controller-admin.php */
@@ -1047,9 +1123,9 @@
1047 1123 }
1048 1124
1049 1125 // If something went wrong, or no files were added to the ZIP file, bail out.
1050 1126 // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
1051 - if ( ZIPARCHIVE::ER_OK !== $zip_file->status || 0 === $zip_file->numFiles ) {
1127 + if ( ZipArchive::ER_OK !== $zip_file->status || 0 === $zip_file->numFiles ) {
1052 1128 $zip_file->close();
1053 1129 @unlink( $full_filename ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
1054 1130 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.' ) );
1055 1131 }
@@ -1147,9 +1223,9 @@
1147 1223 'import_source' => $import_config['source'],
1148 1224 'legacy_import' => $import_config['legacy_import'],
1149 1225 );
1150 1226 if ( in_array( $import_config['source'], array( 'url', 'server' ), true ) ) {
1151 - $redirect_parameters[ "import_{$import_config['source']}" ] = $import_config[ $import_config['source'] ];
1227 + $redirect_parameters[ "import_{$import_config['source']}" ] = rawurlencode( $import_config[ $import_config['source'] ] );
1152 1228 }
1153 1229 if ( is_wp_error( $import ) ) {
1154 1230 $redirect_parameters['error_details'] = TablePress::get_wp_error_string( $import );
1155 1231 } elseif ( 0 < count( $import['errors'] ) ) {
@@ -1181,9 +1257,9 @@
1181 1257 *
1182 1258 * @since 1.0.0
1183 1259 */
1184 1260 public function handle_get_action_hide_message(): void {
1185 - $message_item = ! empty( $_GET['item'] ) ? $_GET['item'] : '';
1261 + $message_item = $_GET['item'] ?? '';
1186 1262 TablePress::check_nonce( 'hide_message', $message_item );
1187 1263
1188 1264 if ( ! current_user_can( 'tablepress_list_tables' ) ) {
1189 1265 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
@@ -1316,14 +1392,15 @@
1316 1392 $render_options = shortcode_atts( $default_render_options, $table['options'] );
1317 1393 /** This filter is documented in controllers/controller-frontend.php */
1318 1394 $render_options = apply_filters( 'tablepress_shortcode_table_shortcode_atts', $render_options );
1319 1395 $render_options['html_id'] = "tablepress-{$table['id']}";
1396 + $render_options['block_preview'] = true;
1320 1397 $_render->set_input( $table, $render_options );
1321 1398 $view_data = array(
1322 - 'table_id' => $table_id,
1323 - 'head_html' => $_render->get_preview_css(),
1324 - 'body_html' => $_render->get_output( 'html' ),
1325 - 'site_uses_block_editor' => TablePress::site_uses_block_editor(),
1399 + 'table_id' => $table_id,
1400 + 'head_html' => $_render->get_preview_css(),
1401 + 'body_html' => $_render->get_output( 'html' ),
1402 + 'site_used_editor' => TablePress::site_used_editor(),
1326 1403 );
1327 1404
1328 1405 $custom_css = TablePress::$model_options->get( 'custom_css' );
1329 1406 $use_custom_css = ( TablePress::$model_options->get( 'use_custom_css' ) && '' !== $custom_css );
@@ -1386,9 +1463,9 @@
1386 1463
1387 1464 TablePress::$model_table->destroy();
1388 1465 TablePress::$model_options->destroy();
1389 1466
1390 - $output = '<strong>' . __( 'TablePress was uninstalled successfully.', 'tablepress' ) . '</strong><br /><br />';
1467 + $output = '<strong>' . __( 'TablePress was uninstalled successfully.', 'tablepress' ) . '</strong><br><br>';
1391 1468 $output .= __( 'All tables, data, and options were deleted.', 'tablepress' );
1392 1469 if ( is_multisite() ) {
1393 1470 $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' );
1394 1471 } else {