PluginProbe
TablePress – Tables in WordPress made easy / 3.0
TablePress – Tables in WordPress made easy v3.0
3.3.4 3.3.3 3.3.2 3.3.1 trunk 1.12 1.14 1.9.2 2.0.4 2.1.7 2.1.8 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3 2.3.1 2.3.2 2.4 2.4.1 2.4.2 2.4.3 2.4.4 All 44 releases
← All changes | controllers/controller-admin.php +491 -600 1.143.0 View file →
@@ -12,8 +12,9 @@
12 12 defined( 'ABSPATH' ) || die( 'No direct script access allowed!' );
13 13
14 14 /**
15 15 * Admin Controller class, extends Base Controller Class
16 + *
16 17 * @package TablePress
17 18 * @subpackage Controllers
18 19 * @author Tobias Bäthge
19 20 * @since 1.0.0
@@ -24,37 +25,28 @@
24 25 * Page hooks (i.e. names) WordPress uses for the TablePress admin screens,
25 26 * populated in add_admin_menu_entry().
26 27 *
27 28 * @since 1.0.0
28 - * @var array
29 + * @var string[]
29 30 */
30 - protected $page_hooks = array();
31 + protected array $page_hooks = array();
31 32
32 33 /**
33 34 * Actions that have a view and admin menu or nav tab menu entry.
34 35 *
35 36 * @since 1.0.0
36 - * @var array
37 + * @var array<string, array<string, bool|string>>
37 38 */
38 - protected $view_actions = array();
39 + protected array $view_actions = array();
39 40
40 41 /**
41 42 * Instance of the TablePress Admin View that is rendered.
42 43 *
43 44 * @since 1.0.0
44 - * @var TablePress_View
45 45 */
46 - protected $view;
46 + protected \TablePress_View $view;
47 47
48 48 /**
49 - * Instance of the TablePress Importer.
50 - *
51 - * @since 1.0.0
52 - * @var TablePress_Import
53 - */
54 - protected $importer;
55 -
56 - /**
57 49 * Initialize the Admin Controller, determine location the admin menu, set up actions.
58 50 *
59 51 * @since 1.0.0
60 52 */
@@ -61,12 +53,15 @@
61 53 public function __construct() {
62 54 parent::__construct();
63 55
64 56 // Handler for changing the number of shown tables in the list of tables (via WP List Table class).
65 - add_filter( 'set-screen-option', array( $this, 'save_list_tables_screen_option' ), 10, 3 );
57 + add_filter( 'set_screen_option_tablepress_list_per_page', array( $this, 'save_list_tables_screen_option' ), 10, 3 );
66 58
67 59 add_action( 'admin_menu', array( $this, 'add_admin_menu_entry' ) );
68 60 add_action( 'admin_init', array( $this, 'add_admin_actions' ) );
61 +
62 + add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_block_editor_assets' ) );
63 + add_action( 'enqueue_block_assets', array( $this, 'enqueue_block_assets' ) );
69 64 }
70 65
71 66 /**
72 67 * Handler for changing the number of shown tables in the list of tables (via WP List Table class).
@@ -72,15 +67,15 @@
72 67 * Handler for changing the number of shown tables in the list of tables (via WP List Table class).
73 68 *
74 69 * @since 1.0.0
75 70 *
76 - * @param bool $false Current value of the filter (probably bool false).
77 - * @param string $option Option in which the setting is stored.
78 - * @param int $value Current value of the setting.
79 - * @return bool|int False to not save the changed setting, or the int value to be saved.
71 + * @param mixed $screen_option Current value of the filter (probably bool false).
72 + * @param string $option Option in which the setting is stored.
73 + * @param int $value Current value of the setting.
74 + * @return int Changed value of the setting
80 75 */
81 - public function save_list_tables_screen_option( $false, $option, $value ) {
82 - return ( 'tablepress_list_per_page' === $option ) ? $value : $false;
76 + public function save_list_tables_screen_option( /* mixed */ $screen_option, string $option, int $value ): int {
77 + return $value;
83 78 }
84 79
85 80 /**
86 81 * Add admin screens to the correct place in the admin menu.
@@ -86,13 +81,13 @@
86 81 * Add admin screens to the correct place in the admin menu.
87 82 *
88 83 * @since 1.0.0
89 84 */
90 - public function add_admin_menu_entry() {
85 + public function add_admin_menu_entry(): void {
91 86 // Callback for all menu entries.
92 87 $callback = array( $this, 'show_admin_page' );
93 88 /**
94 - * Filter the TablePress admin menu entry name.
89 + * Filters the TablePress admin menu entry name.
95 90 *
96 91 * @since 1.0.0
97 92 *
98 93 * @param string $entry_name The admin menu entry name. Default "TablePress".
@@ -101,23 +96,27 @@
101 96
102 97 $this->init_view_actions();
103 98 $min_access_cap = $this->view_actions['list']['required_cap'];
104 99
105 - if ( $this->is_top_level_page ) {
106 - $icon_url = 'dashicons-list-view';
107 - 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 ) {
108 103 case 'top':
109 - $position = 3; // position of Dashboard + 1
104 + $position = 3; // Position of Dashboard + 1.
110 105 break;
111 106 case 'bottom':
112 - $position = ( ++$GLOBALS['_wp_last_utility_menu'] );
107 + $position = isset( $GLOBALS['_wp_last_utility_menu'] ) ? ++$GLOBALS['_wp_last_utility_menu'] : 80;
113 108 break;
114 109 case 'middle':
115 110 default:
116 - $position = ( ++$GLOBALS['_wp_last_object_menu'] );
111 + $position = isset( $GLOBALS['_wp_last_object_menu'] ) ? ++$GLOBALS['_wp_last_object_menu'] : 25;
117 112 break;
118 113 }
119 - add_menu_page( 'TablePress', $admin_menu_entry_name, $min_access_cap, 'tablepress', $callback, $icon_url, $position );
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
120 119 foreach ( $this->view_actions as $action => $entry ) {
121 120 if ( ! $entry['show_entry'] ) {
122 121 continue;
123 122 }
@@ -124,12 +123,20 @@
124 123 $slug = 'tablepress';
125 124 if ( 'list' !== $action ) {
126 125 $slug .= '_' . $action;
127 126 }
128 - $this->page_hooks[] = 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 + // @phpstan-ignore argument.type, argument.type
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 );
129 + if ( false !== $page_hook ) {
130 + $this->page_hooks[] = $page_hook;
131 + }
129 132 }
130 133 } else {
131 - $this->page_hooks[] = 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 );
136 + if ( false !== $page_hook ) {
137 + $this->page_hooks[] = $page_hook;
138 + }
132 139 }
133 140 }
134 141
135 142 /**
@@ -136,11 +143,11 @@
136 143 * Set up handlers for user actions in the backend that exceed plain viewing.
137 144 *
138 145 * @since 1.0.0
139 146 */
140 - public function add_admin_actions() {
147 + public function add_admin_actions(): void {
141 148 // Register the callbacks for processing action requests.
142 - $post_actions = array( 'list', 'add', 'edit', 'options', 'export', 'import' );
149 + $post_actions = array( 'list', 'add', 'options', 'export', 'import' );
143 150 $get_actions = array( 'hide_message', 'delete_table', 'copy_table', 'preview_table', 'editor_button_thickbox', 'uninstall_tablepress' );
144 151 foreach ( $post_actions as $action ) {
145 152 add_action( "admin_post_tablepress_{$action}", array( $this, "handle_post_action_{$action}" ) );
146 153 }
@@ -152,11 +159,20 @@
152 159 foreach ( $this->page_hooks as $page_hook ) {
153 160 add_action( "load-{$page_hook}", array( $this, 'load_admin_page' ) );
154 161 }
155 162
156 - $pages_with_editor_button = array( 'post.php', 'post-new.php' );
157 - foreach ( $pages_with_editor_button as $editor_page ) {
158 - add_action( "load-{$editor_page}", array( $this, 'add_editor_buttons' ) );
163 + /**
164 + * Filters whether the legacy editor button should be loaded on the post editing screen.
165 + *
166 + * @since 2.1.0
167 + *
168 + * @param bool $load_button Whether to load the legacy editor button. Default true.
169 + */
170 + if ( apply_filters( 'tablepress_add_legacy_editor_button', true ) ) {
171 + $pages_with_editor_button = array( 'post.php', 'post-new.php' );
172 + foreach ( $pages_with_editor_button as $editor_page ) {
173 + add_action( "load-{$editor_page}", array( $this, 'add_editor_buttons' ) );
174 + }
159 175 }
160 176
161 177 if ( ! is_network_admin() && ! is_user_admin() ) {
162 178 add_action( 'admin_bar_menu', array( $this, 'add_wp_admin_bar_new_content_menu_entry' ), 71 );
@@ -162,93 +178,187 @@
162 178 add_action( 'admin_bar_menu', array( $this, 'add_wp_admin_bar_new_content_menu_entry' ), 71 );
163 179 }
164 180
165 181 add_action( 'load-plugins.php', array( $this, 'plugins_page' ) );
182 + }
166 183
167 - // Add filters and actions for the integration into the WP WXR exporter and importer.
168 - add_action( 'wp_import_insert_post', array( TablePress::$model_table, 'add_table_id_on_wp_import' ), 10, 4 );
169 - add_filter( 'wp_import_post_meta', array( TablePress::$model_table, 'prevent_table_id_post_meta_import_on_wp_import' ), 10, 3 );
170 - add_filter( 'wxr_export_skip_postmeta', array( TablePress::$model_table, 'add_table_id_to_wp_export' ), 10, 3 );
184 + /**
185 + * Loads additional JavaScript code for the TablePress table block (in the block editor context).
186 + *
187 + * @since 2.2.0
188 + */
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 +
198 + // Add table information for the block editor to the page.
199 + $handle = generate_block_asset_handle( 'tablepress/table', 'editorScript' );
200 + $data = $this->get_block_editor_data();
201 + wp_add_inline_script( $handle, $data, 'before' );
171 202 }
172 203
173 204 /**
205 + * Loads additional CSS code for the TablePress table block (inside the block editor iframe).
206 + *
207 + * @since 2.2.0
208 + */
209 + public function enqueue_block_assets(): void {
210 + // Load the TablePress default CSS and the user's "Custom CSS" in the block editor iframe.
211 + if ( is_admin() ) {
212 + TablePress::$controller->enqueue_css();
213 + }
214 + }
215 +
216 + /**
217 + * Gets the inline data that is referenced by the Block Editor JavaScript code for the TablePress blocks.
218 + *
219 + * @since 2.0.0
220 + *
221 + * @return string JavaScript code for the Block Editor.
222 + */
223 + protected function get_block_editor_data(): string {
224 + $tables = array();
225 + // Load all table IDs without priming the post meta cache, as table options/visibility are not needed.
226 + $table_ids = TablePress::$model_table->load_all( false );
227 + foreach ( $table_ids as $table_id ) {
228 + // Load table, without table data, options, and visibility settings.
229 + $table = TablePress::$model_table->load( $table_id, false, false );
230 +
231 + // Skip tables that could not be loaded.
232 + if ( is_wp_error( $table ) ) {
233 + continue;
234 + }
235 +
236 + if ( '' === trim( $table['name'] ) ) {
237 + $table['name'] = __( '(no name)', 'tablepress' );
238 + }
239 + $tables[ $table_id ] = esc_html( $table['name'] );
240 + }
241 +
242 + /**
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.
244 + *
245 + * @since 2.0.0
246 + *
247 + * @param array<string, string> $tables List of table names, the table ID is the array key.
248 + */
249 + $tables = apply_filters( 'tablepress_block_editor_tables_list', $tables );
250 +
251 + $tables = wp_json_encode( $tables, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES );
252 + if ( false === $tables ) {
253 + // JSON encoding failed, return an error object. Use a prefixed "_error" key to avoid conflicts with intentionally added "error" keys.
254 + $tables = '{ "_error": "The data could not be encoded to JSON!" }';
255 + }
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 );
258 +
259 + $shortcode = esc_js( TablePress::$shortcode );
260 +
261 + $template = TablePress::$model_table->get_table_template();
262 + $template = wp_json_encode( $template['options'], JSON_HEX_TAG | JSON_UNESCAPED_SLASHES );
263 + if ( false === $template ) {
264 + // JSON encoding failed, return an error object. Use a prefixed "_error" key to avoid conflicts with intentionally added "error" keys.
265 + $template = '{ "_error": "The data could not be encoded to JSON!" }';
266 + }
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 );
269 +
270 + /**
271 + * Filters whether the table block preview should be loaded via a <ServerSideRender> in the block editor.
272 + *
273 + * @since 2.0.0
274 + *
275 + * @param bool $load_block_preview Whether the table block preview should be loaded.
276 + */
277 + $load_block_preview = apply_filters( 'tablepress_show_block_editor_preview', true );
278 + $load_block_preview = (bool) $load_block_preview ? 'true' : 'false';
279 +
280 + $url = '';
281 + if ( current_user_can( 'tablepress_list_tables' ) ) {
282 + $url = TablePress::url( array( 'action' => 'list' ) );
283 + }
284 +
285 + return <<<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;
295 + }
296 +
297 + /**
174 298 * Register actions to add "Table" button to "HTML editor" and "Visual editor" toolbars.
175 299 *
176 300 * @since 1.0.0
177 301 */
178 - public function add_editor_buttons() {
302 + public function add_editor_buttons(): void {
179 303 if ( ! current_user_can( 'tablepress_list_tables' ) ) {
180 304 return;
181 305 }
182 306
183 - /*
184 - * Only load the toolbar integration when the Classic Editor plugin (https://wordpress.org/plugins/classic-editor/) is activated.
185 - * Without it, the Block Editor user interface is used, which can not directly use these buttons.
186 - */
187 - if ( ! class_exists( 'Classic_Editor' ) ) {
307 + // Only load the toolbar integration if the Block Editor is not used.
308 + if ( TablePress::site_uses_block_editor() ) {
188 309 return;
189 310 }
190 311
191 - add_thickbox(); // usually already loaded by media upload functions
312 + add_thickbox(); // The files are usually already loaded by media upload functions.
192 313 $admin_page = TablePress::load_class( 'TablePress_Admin_Page', 'class-admin-page-helper.php', 'classes' );
193 - $admin_page->enqueue_script( 'quicktags-button', array( 'quicktags', 'media-upload' ), array( // phpcs:ignore PEAR.Functions.FunctionCallSignature.MultipleArguments
194 - 'editor_button' => array(
195 - 'caption' => __( 'Table', 'tablepress' ),
196 - 'title' => __( 'Insert a Table from TablePress', 'tablepress' ),
197 - 'thickbox_title' => __( 'Insert a Table from TablePress', 'tablepress' ),
198 - 'thickbox_url' => TablePress::url( array( 'action' => 'editor_button_thickbox' ), true, 'admin-post.php' ),
314 + $admin_page->enqueue_script(
315 + 'quicktags-button',
316 + array( 'quicktags', 'media-upload' ),
317 + array(
318 + 'editor_button' => array(
319 + 'caption' => __( 'Table', 'tablepress' ),
320 + 'title' => __( 'Insert a TablePress table', 'tablepress' ),
321 + 'thickbox_title' => __( 'Insert a TablePress table', 'tablepress' ),
322 + 'thickbox_url' => TablePress::url( array( 'action' => 'editor_button_thickbox' ), true, 'admin-post.php' ),
323 + ),
199 324 ),
200 - ) );
325 + );
201 326
202 327 // TinyMCE integration.
203 328 if ( user_can_richedit() ) {
204 329 add_filter( 'mce_external_plugins', array( $this, 'add_tinymce_plugin' ) );
205 330 add_filter( 'mce_buttons', array( $this, 'add_tinymce_button' ) );
206 - add_action( 'admin_print_styles', array( $this, 'add_tablepress_hidpi_css' ), 21 );
207 331 }
208 332 }
209 333
210 334 /**
211 - * Add "Table" button and separator to the TinyMCE toolbar.
335 + * Adds the "Table" button to the TinyMCE toolbar.
212 336 *
213 337 * @since 1.0.0
214 338 *
215 - * @param array $buttons Current set of buttons in the TinyMCE toolbar.
216 - * @return array Current set of buttons in the TinyMCE toolbar, including "Table" button.
339 + * @param string[] $buttons Current set of buttons in the TinyMCE toolbar.
340 + * @return string[] Extended set of buttons in the TinyMCE toolbar, including the "Table" button.
217 341 */
218 - public function add_tinymce_button( array $buttons ) {
342 + public function add_tinymce_button( array $buttons ): array {
219 343 $buttons[] = 'tablepress_insert_table';
220 344 return $buttons;
221 345 }
222 346
223 347 /**
224 - * Register "Table" button plugin to TinyMCE.
348 + * Registers the "Table" button plugin for the TinyMCE editor.
225 349 *
226 350 * @since 1.0.0
227 351 *
228 - * @param array $plugins Current set of registered TinyMCE plugins.
229 - * @return array Current set of registered TinyMCE plugins, including "Table" button plugin.
352 + * @param array<string, string> $plugins Current set of registered TinyMCE plugins.
353 + * @return array<string, string> Extended set of registered TinyMCE plugins, including the "Table" button plugin.
230 354 */
231 - public function add_tinymce_plugin( array $plugins ) {
232 - $suffix = SCRIPT_DEBUG ? '' : '.min';
233 - $js_file = "admin/js/tinymce-button{$suffix}.js";
234 - $plugins['tablepress_tinymce'] = plugins_url( $js_file, TABLEPRESS__FILE__ );
355 + public function add_tinymce_plugin( array $plugins ): array {
356 + $plugins['tablepress_tinymce'] = plugins_url( 'admin/js/build/tinymce-button.js', TABLEPRESS__FILE__ );
235 357 return $plugins;
236 358 }
237 359
238 360 /**
239 - * Print TablePress HiDPI CSS to the <head> for TinyMCE button.
240 - *
241 - * @since 1.0.0
242 - */
243 - public function add_tablepress_hidpi_css() {
244 - echo '<style type="text/css">@media print,(-webkit-min-device-pixel-ratio:1.25),(min-resolution:120dpi){';
245 - echo '#content_tablepress_insert_table span{background:url(' . plugins_url( 'admin/img/tablepress-editor-button-2x.png', TABLEPRESS__FILE__ ) . ') no-repeat 0 0;background-size:20px 20px}';
246 - echo '#content_tablepress_insert_table img{display:none}';
247 - echo '}</style>' . "\n";
248 - }
249 -
250 - /**
251 361 * Add "TablePress Table" entry to "New" dropdown menu in the WP Admin Bar.
252 362 *
253 363 * @since 1.0.0
254 364 *
@@ -253,13 +363,18 @@
253 363 * @since 1.0.0
254 364 *
255 365 * @param WP_Admin_Bar $wp_admin_bar The current WP Admin Bar object.
256 366 */
257 - public function add_wp_admin_bar_new_content_menu_entry( $wp_admin_bar ) {
367 + public function add_wp_admin_bar_new_content_menu_entry( WP_Admin_Bar $wp_admin_bar ): void {
258 368 if ( ! current_user_can( 'tablepress_add_tables' ) ) {
259 369 return;
260 370 }
261 371
372 + // Don't load TablePress assets on the Freemius opt-in/activation screen.
373 + if ( tb_tp_fs()->is_activation_mode() && tb_tp_fs()->is_activation_page() ) {
374 + return;
375 + }
376 +
262 377 $wp_admin_bar->add_menu( array(
263 378 'parent' => 'new-content',
264 379 'id' => 'new-tablepress-table',
265 380 'title' => __( 'TablePress Table', 'tablepress' ),
@@ -271,12 +386,24 @@
271 386 * Handle actions for loading of Plugins page.
272 387 *
273 388 * @since 1.0.0
274 389 */
275 - public function plugins_page() {
390 + public function plugins_page(): void {
276 391 // Add additional links on Plugins page.
277 392 add_filter( 'plugin_action_links_' . TABLEPRESS_BASENAME, array( $this, 'add_plugin_action_links' ) );
278 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-row-details/tablepress-datatables-row-details.php',
400 + 'tablepress-datatables-rowgroup/tablepress-datatables-rowgroup.php',
401 + 'tablepress-responsive-tables/tablepress-responsive-tables.php',
402 + );
403 + foreach ( $incompatible_superseded_extensions as $plugin_file ) {
404 + add_action( "after_plugin_row_{$plugin_file}", array( $this, 'add_superseded_extension_meta_row' ), 10, 3 );
405 + }
279 406 }
280 407
281 408 /**
282 409 * Add links to the TablePress entry in the "Plugin" column on the Plugins page.
@@ -282,14 +409,14 @@
282 409 * Add links to the TablePress entry in the "Plugin" column on the Plugins page.
283 410 *
284 411 * @since 1.0.0
285 412 *
286 - * @param array $links List of links to print in the "Plugin" column on the Plugins page.
287 - * @return array Extended list of links to print in the "Plugin" column on the Plugins page.
413 + * @param string[] $links List of links to print in the "Plugin" column on the Plugins page.
414 + * @return string[] Extended list of links to print in the "Plugin" column on the Plugins page.
288 415 */
289 - public function add_plugin_action_links( array $links ) {
416 + public function add_plugin_action_links( array $links ): array {
290 417 if ( current_user_can( 'tablepress_list_tables' ) ) {
291 - $links[] = '<a href="' . TablePress::url() . '">' . __( 'Plugin page', 'tablepress' ) . '</a>';
418 + $links[] = '<a href="' . esc_url( TablePress::url() ) . '">' . __( 'Plugin page', 'tablepress' ) . '</a>';
292 419 }
293 420 return $links;
294 421 }
295 422
@@ -297,31 +424,79 @@
297 424 * Add links to the TablePress entry in the "Description" column on the Plugins page.
298 425 *
299 426 * @since 1.0.0
300 427 *
301 - * @param array $links List of links to print in the "Description" column on the Plugins page.
302 - * @param string $file Name of the plugin.
303 - * @return array Extended list of links to print in the "Description" column on the Plugins page.
428 + * @param string[] $links List of links to print in the "Description" column on the Plugins page.
429 + * @param string $file Name of the plugin.
430 + * @return string[] Extended list of links to print in the "Description" column on the Plugins page.
304 431 */
305 - public function add_plugin_row_meta( array $links, $file ) {
432 + public function add_plugin_row_meta( array $links, string $file ): array {
306 433 if ( TABLEPRESS_BASENAME === $file ) {
307 434 $links[] = '<a href="https://tablepress.org/faq/" title="' . esc_attr__( 'Frequently Asked Questions', 'tablepress' ) . '">' . __( 'FAQ', 'tablepress' ) . '</a>';
308 435 $links[] = '<a href="https://tablepress.org/documentation/">' . __( 'Documentation', 'tablepress' ) . '</a>';
309 436 $links[] = '<a href="https://tablepress.org/support/">' . __( 'Support', 'tablepress' ) . '</a>';
310 - $links[] = '<a href="https://tablepress.org/donate/" title="' . esc_attr__( 'Support TablePress with your donation!', 'tablepress' ) . '"><strong>' . __( 'Donate', 'tablepress' ) . '</strong></a>';
437 + if ( ! TABLEPRESS_IS_PLAYGROUND_PREVIEW && tb_tp_fs()->is_free_plan() ) {
438 + $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>';
439 + }
311 440 }
312 441 return $links;
313 442 }
314 443
315 444 /**
445 + * Prints a superseded extension notice below certain TablePress Extension plugins' meta rows on the "Plugins" screen.
446 + *
447 + * @since 2.4.1
448 + *
449 + * @param string $plugin_file Path to the plugin file relative to the plugins directory.
450 + * @param array<int, string|string[]|bool> $plugin_data An array of plugin data.
451 + * @param string $status Status filter currently applied to the plugin list.
452 + */
453 + public function add_superseded_extension_meta_row( string $plugin_file, array $plugin_data, string $status ): void {
454 + if ( ! is_plugin_active( $plugin_file ) ) {
455 + return;
456 + }
457 + ?>
458 + <tr class="plugin-update-tr active">
459 + <td colspan="<?php echo esc_attr( $GLOBALS['wp_list_table']->get_column_count() ); ?>" class="plugin-update colspanchange">
460 + <div class="update-message notice inline notice-error notice-alt">
461 + <?php
462 + if ( tb_tp_fs()->is_free_plan() ) {
463 + echo '<p style="font-size:14px;">';
464 + _e( 'This TablePress Extension was retired.', 'tablepress' );
465 + echo ' ';
466 + _e( '<strong>The plugin does no longer work with TablePress 3</strong> and will no longer receive updates or support!', 'tablepress' );
467 + echo '<br>';
468 + _e( 'Keeping it activated can lead to errors on your website!', 'tablepress' );
469 + 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>';
470 + echo '</p>';
471 + }
472 + ?>
473 + <style>
474 + /* Remove the separator line between the plugin's and the notice's table row. */
475 + .plugins .active[data-plugin="<?php echo $plugin_file; ?>"] th,
476 + .plugins .active[data-plugin="<?php echo $plugin_file; ?>"] td {
477 + box-shadow: none;
478 + }
479 + /* Hide the plugin update row for the Extension as those won't work anymore anyways. */
480 + .plugins .plugin-update-tr[data-plugin="<?php echo $plugin_file; ?>"] {
481 + display: none;
482 + }
483 + </style>
484 + </div>
485 + </td>
486 + </tr>
487 + <?php
488 + }
489 +
490 + /**
316 491 * Prepare the rendering of an admin screen, by determining the current action, loading necessary data and initializing the view.
317 492 *
318 493 * @since 1.0.0
319 494 */
320 - public function load_admin_page() {
495 + public function load_admin_page(): void {
321 496 // Determine the action from either the GET parameter (for sub-menu entries, and the main admin menu entry).
322 - $action = ( ! empty( $_GET['action'] ) ) ? $_GET['action'] : 'list'; // default action is list
323 - if ( $this->is_top_level_page ) {
497 + $action = ( ! empty( $_GET['action'] ) ) ? $_GET['action'] : 'list'; // Default action is list.
498 + if ( TablePress::$controller->is_top_level_page ) {
324 499 // Or, for sub-menu entry of an admin menu "TablePress" entry, get it from the "page" GET parameter.
325 500 if ( 'tablepress' !== $_GET['page'] ) {
326 501 // Actions that are top-level entries, but don't have an action GET parameter (action is after last _ in string).
327 502 $action = substr( $_GET['page'], 11 ); // $_GET['page'] has the format 'tablepress_{$action}'
@@ -328,26 +503,34 @@
328 503 }
329 504 }
330 505
331 506 // Check if action is a supported action, and whether the user is allowed to access this screen.
332 - if ( ! isset( $this->view_actions[ $action ] ) || ! current_user_can( $this->view_actions[ $action ]['required_cap'] ) ) {
507 + 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.)
333 508 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
334 509 }
335 510
511 + // Don't load TablePress assets on the Freemius opt-in/activation screen.
512 + if ( tb_tp_fs()->is_activation_mode() && tb_tp_fs()->is_activation_page() ) {
513 + return;
514 + }
515 +
336 516 // Changes current screen ID and pagenow variable in JS, to enable automatic meta box JS handling.
337 517 set_current_screen( "tablepress_{$action}" );
518 +
338 519 /*
339 520 * Set the `$typenow` global to the current CPT ourselves, as `WP_Screen::get()` does not determine the CPT correctly.
340 - * This is necessary as the WP Admin Menu can otherwise highlight wrong entries, see https://github.com/TobiasBg/TablePress/issues/24.
521 + * This is necessary as the WP Admin Menu can otherwise highlight wrong entries, see https://github.com/TablePress/TablePress/issues/24.
341 522 */
342 523 if ( isset( $_GET['post_type'] ) && post_type_exists( $_GET['post_type'] ) ) {
343 - $GLOBALS['typenow'] = $_GET['post_type'];
524 + $GLOBALS['typenow'] = $_GET['post_type']; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
344 525 }
345 526
346 527 // Pre-define some view data.
347 528 $data = array(
348 - 'view_actions' => $this->view_actions,
349 - 'message' => ( ! empty( $_GET['message'] ) ) ? $_GET['message'] : false,
529 + 'view_actions' => $this->view_actions,
530 + 'message' => ( ! empty( $_GET['message'] ) ) ? $_GET['message'] : false,
531 + 'error_details' => ( ! empty( $_GET['error_details'] ) ) ? $_GET['error_details'] : '',
532 + 'site_uses_block_editor' => TablePress::site_uses_block_editor(),
350 533 );
351 534
352 535 // Depending on the action, load more necessary data for the corresponding view.
353 536 switch ( $action ) {
@@ -354,19 +537,16 @@
354 537 case 'list':
355 538 $data['table_id'] = ( ! empty( $_GET['table_id'] ) ) ? $_GET['table_id'] : false;
356 539 // Prime the post meta cache for cached loading of last_editor.
357 540 $data['table_ids'] = TablePress::$model_table->load_all( true );
358 - $data['messages']['first_visit'] = TablePress::$model_options->get( 'message_first_visit' );
359 - // Check if WP-Table Reloaded is activated and show a warning.
360 - $data['messages']['wp_table_reloaded_warning'] = is_plugin_active( 'wp-table-reloaded/wp-table-reloaded.php' );
361 - $data['messages']['plugin_update_message'] = TablePress::$model_options->get( 'message_plugin_update' );
362 - $data['messages']['donation_message'] = $this->maybe_show_donation_message();
541 + $data['messages']['donation_nag'] = $this->maybe_show_donation_message();
542 + $data['messages']['first_visit'] = ! $data['messages']['donation_nag'] && TablePress::$model_options->get( 'message_first_visit' );
543 + $data['messages']['plugin_update'] = TablePress::$model_options->get( 'message_plugin_update' );
544 + $data['messages']['superseded_extensions'] = current_user_can( 'manage_options' ) && TablePress::$model_options->get( 'message_superseded_extensions' );
363 545 $data['table_count'] = count( $data['table_ids'] );
364 546 break;
365 547 case 'about':
366 548 $data['first_activation'] = TablePress::$model_options->get( 'first_activation' );
367 - $exporter = TablePress::load_class( 'TablePress_Export', 'class-export.php', 'classes' );
368 - $data['zip_support_available'] = $exporter->zip_support_available;
369 549 break;
370 550 case 'options':
371 551 /*
372 552 * Maybe try saving "Custom CSS" to a file:
@@ -373,9 +553,9 @@
373 553 * (called here, as the credentials form posts to this handler again, due to how `request_filesystem_credentials()` works)
374 554 */
375 555 if ( isset( $_GET['item'] ) && 'save_custom_css' === $_GET['item'] ) {
376 556 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.
377 - $action = 'options_custom_css'; // to load a different view
557 + $action = 'options_custom_css'; // To load a different view.
378 558 // Try saving "Custom CSS" to a file, otherwise this gets the HTML for the credentials form.
379 559 $tablepress_css = TablePress::load_class( 'TablePress_CSS', 'class-css.php', 'classes' );
380 560 $result = $tablepress_css->save_custom_css_to_file_plugin_options( TablePress::$model_options->get( 'custom_css' ), TablePress::$model_options->get( 'custom_css_minified' ) );
381 561 if ( is_string( $result ) ) {
@@ -389,9 +569,9 @@
389 569 'use_custom_css_file' => true,
390 570 'custom_css_version' => TablePress::$model_options->get( 'custom_css_version' ) + 1,
391 571 ) );
392 572 TablePress::redirect( array( 'action' => 'options', 'message' => 'success_save' ) );
393 - } else { // leaves only $result === false
573 + } else { // Leaves only $result === false.
394 574 TablePress::redirect( array( 'action' => 'options', 'message' => 'success_save_error_custom_css' ) );
395 575 }
396 576 break;
397 577 }
@@ -396,9 +576,9 @@
396 576 break;
397 577 }
398 578 $data['frontend_options']['use_custom_css'] = TablePress::$model_options->get( 'use_custom_css' );
399 579 $data['frontend_options']['custom_css'] = TablePress::$model_options->get( 'custom_css' );
400 - $data['user_options']['parent_page'] = $this->parent_page;
580 + $data['user_options']['parent_page'] = TablePress::$controller->parent_page;
401 581 break;
402 582 case 'edit':
403 583 if ( empty( $_GET['table_id'] ) ) {
404 584 TablePress::redirect( array( 'action' => 'list', 'message' => 'error_no_table' ) );
@@ -405,9 +585,9 @@
405 585 }
406 586 // Load table, with table data, options, and visibility settings.
407 587 $data['table'] = TablePress::$model_table->load( $_GET['table_id'], true, true );
408 588 if ( is_wp_error( $data['table'] ) ) {
409 - TablePress::redirect( array( 'action' => 'list', 'message' => 'error_load_table' ) );
589 + TablePress::redirect( array( 'action' => 'list', 'message' => 'error_load_table', 'error_details' => TablePress::get_wp_error_string( $data['table'] ) ) );
410 590 }
411 591 if ( ! current_user_can( 'tablepress_edit_table', $_GET['table_id'] ) ) {
412 592 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
413 593 }
@@ -413,48 +593,71 @@
413 593 }
414 594 break;
415 595 case 'export':
416 596 // Load all table IDs without priming the post meta cache, as table options/visibility are not needed.
417 - $data['table_ids'] = TablePress::$model_table->load_all( false );
597 + $table_ids = TablePress::$model_table->load_all( false );
598 + $data['tables'] = array();
599 + foreach ( $table_ids as $table_id ) {
600 + if ( ! current_user_can( 'tablepress_export_table', $table_id ) ) {
601 + continue;
602 + }
603 + // Load table, without table data, options, and visibility settings.
604 + $table = TablePress::$model_table->load( $table_id, false, false );
605 +
606 + // Skip tables that could not be loaded.
607 + if ( is_wp_error( $table ) ) {
608 + continue;
609 + }
610 +
611 + $data['tables'][ $table['id'] ] = $table['name'];
612 + }
418 613 $data['tables_count'] = TablePress::$model_table->count_tables();
419 - if ( ! empty( $_GET['table_id'] ) ) {
420 - $data['export_ids'] = explode( ',', $_GET['table_id'] );
421 - } else {
422 - // Just show empty export form.
423 - $data['export_ids'] = array();
424 - }
614 + $data['export_ids'] = ( ! empty( $_GET['table_id'] ) ) ? explode( ',', $_GET['table_id'] ) : array();
425 615 $exporter = TablePress::load_class( 'TablePress_Export', 'class-export.php', 'classes' );
426 616 $data['zip_support_available'] = $exporter->zip_support_available;
427 617 $data['export_formats'] = $exporter->export_formats;
428 618 $data['csv_delimiters'] = $exporter->csv_delimiters;
429 - $data['export_format'] = ( ! empty( $_GET['export_format'] ) ) ? $_GET['export_format'] : false;
619 + $data['export_format'] = ( ! empty( $_GET['export_format'] ) ) ? $_GET['export_format'] : 'csv';
430 620 $data['csv_delimiter'] = ( ! empty( $_GET['csv_delimiter'] ) ) ? $_GET['csv_delimiter'] : _x( ',', 'Default CSV delimiter in the translated language (";", ",", or "tab")', 'tablepress' );
431 621 break;
432 622 case 'import':
433 623 // Load all table IDs without priming the post meta cache, as table options/visibility are not needed.
434 - $data['table_ids'] = TablePress::$model_table->load_all( false );
624 + $table_ids = TablePress::$model_table->load_all( false );
625 + $data['tables'] = array();
626 + foreach ( $table_ids as $table_id ) {
627 + if ( ! current_user_can( 'tablepress_edit_table', $table_id ) ) {
628 + continue;
629 + }
630 + // Load table, without table data, options, and visibility settings.
631 + $table = TablePress::$model_table->load( $table_id, false, false );
632 +
633 + // Skip tables that could not be loaded.
634 + if ( is_wp_error( $table ) ) {
635 + continue;
636 + }
637 +
638 + $data['tables'][ $table['id'] ] = $table['name'];
639 + }
640 + $data['table_ids'] = $table_ids; // Backward compatibility for the retired "Table Auto Update" Extension, which still relies on this variable name.
435 641 $data['tables_count'] = TablePress::$model_table->count_tables();
436 642 $importer = TablePress::load_class( 'TablePress_Import', 'class-import.php', 'classes' );
437 - $data['zip_support_available'] = $importer->zip_support_available;
438 - $data['html_import_support_available'] = $importer->html_import_support_available;
439 - $data['import_formats'] = $importer->import_formats;
440 - $data['import_format'] = ( ! empty( $_GET['import_format'] ) ) ? $_GET['import_format'] : false;
441 643 $data['import_type'] = ( ! empty( $_GET['import_type'] ) ) ? $_GET['import_type'] : 'add';
442 - $data['import_existing_table'] = ( ! empty( $_GET['import_existing_table'] ) ) ? $_GET['import_existing_table'] : false;
644 + $data['import_existing_table'] = ( ! empty( $_GET['import_existing_table'] ) ) ? $_GET['import_existing_table'] : '';
443 645 $data['import_source'] = ( ! empty( $_GET['import_source'] ) ) ? $_GET['import_source'] : 'file-upload';
444 646 $data['import_url'] = ( ! empty( $_GET['import_url'] ) ) ? wp_unslash( $_GET['import_url'] ) : 'https://';
445 647 $data['import_server'] = ( ! empty( $_GET['import_server'] ) ) ? wp_unslash( $_GET['import_server'] ) : ABSPATH;
446 - $data['import_form_field'] = ( ! empty( $_GET['import_form_field'] ) ) ? wp_unslash( $_GET['import_form_field'] ) : '';
648 + $data['import_form-field'] = ( ! empty( $_GET['import_form-field'] ) ) ? wp_unslash( $_GET['import_form-field'] ) : '';
649 + $data['legacy_import'] = ( ! empty( $_GET['legacy_import'] ) ) ? $_GET['legacy_import'] : 'false';
447 650 break;
448 651 }
449 652
450 653 /**
451 - * Filter the data that is passed to the current TablePress View.
654 + * Filters the data that is passed to the current TablePress View.
452 655 *
453 656 * @since 1.0.0
454 657 *
455 - * @param array $data Data for the view.
456 - * @param string $action The current action for the view.
658 + * @param array<string, mixed> $data Data for the view.
659 + * @param string $action The current action for the view.
457 660 */
458 661 $data = apply_filters( 'tablepress_view_data', $data, $action );
459 662
460 663 // Prepare and initialize the view.
@@ -465,20 +668,20 @@
465 668 * Render the view that has been initialized in load_admin_page() (called by WordPress when the actual page content is needed).
466 669 *
467 670 * @since 1.0.0
468 671 */
469 - public function show_admin_page() {
672 + public function show_admin_page(): void {
470 673 $this->view->render();
471 674 }
472 675
473 676 /**
474 - * Decide whether a donate message shall be shown on the "All Tables" screen, depending on passed days since installation and whether it was shown before.
677 + * Decides whether a message about Premium versions (previously, about donations) shall be shown on the "All Tables" screen, depending on passed days since installation and whether it was shown before.
475 678 *
476 679 * @since 1.0.0
477 680 *
478 - * @return bool Whether the donate message shall be shown on the "All Tables" screen.
681 + * @return bool Whether the message shall be shown on the "All Tables" screen.
479 682 */
480 - protected function maybe_show_donation_message() {
683 + protected function maybe_show_donation_message(): bool {
481 684 // Only show the message to plugin admins.
482 685 if ( ! current_user_can( 'tablepress_edit_options' ) ) {
483 686 return false;
484 687 }
@@ -488,9 +691,9 @@
488 691 }
489 692
490 693 // Determine, how long has the plugin been installed.
491 694 $seconds_installed = time() - TablePress::$model_options->get( 'first_activation' );
492 - return ( $seconds_installed > MONTH_IN_SECONDS );
695 + return ( $seconds_installed > MONTH_IN_SECONDS / 2 );
493 696 }
494 697
495 698 /**
496 699 * Init list of actions that have a view with their titles/names/caps.
@@ -496,9 +699,9 @@
496 699 * Init list of actions that have a view with their titles/names/caps.
497 700 *
498 701 * @since 1.0.0
499 702 */
500 - protected function init_view_actions() {
703 + protected function init_view_actions(): void {
501 704 $this->view_actions = array(
502 705 'list' => array(
503 706 'show_entry' => true,
504 707 'page_title' => __( 'All Tables', 'tablepress' ),
@@ -550,13 +753,13 @@
550 753 ),
551 754 );
552 755
553 756 /**
554 - * Filter the available TablePres Views/Actions and their parameters.
757 + * Filters the available TablePres Views/Actions and their parameters.
555 758 *
556 759 * @since 1.0.0
557 760 *
558 - * @param array $view_actions The available Views/Actions and their parameters.
761 + * @param array<string, array<string, bool|string>> $view_actions The available Views/Actions and their parameters.
559 762 */
560 763 $this->view_actions = apply_filters( 'tablepress_admin_view_actions', $this->view_actions );
561 764 }
562 765
@@ -568,9 +771,9 @@
568 771 * Handle Bulk Actions (Copy, Export, Delete) on "All Tables" list screen.
569 772 *
570 773 * @since 1.0.0
571 774 */
572 - public function handle_post_action_list() {
775 + public function handle_post_action_list(): void {
573 776 TablePress::check_nonce( 'list' );
574 777
575 778 if ( isset( $_POST['bulk-action-selector-top'] ) && '-1' !== $_POST['bulk-action-selector-top'] ) {
576 779 $bulk_action = $_POST['bulk-action-selector-top'];
@@ -589,9 +792,9 @@
589 792 }
590 793
591 794 $tables = wp_unslash( $_POST['table'] );
592 795
593 - $no_success = array(); // to store table IDs that failed
796 + $no_success = array(); // To store table IDs that failed.
594 797
595 798 switch ( $bulk_action ) {
596 799 case 'copy':
597 800 foreach ( $tables as $table_id ) {
@@ -611,9 +814,9 @@
611 814 * To export, redirect to "Export" screen, with selected table IDs.
612 815 */
613 816 $table_ids = implode( ',', $tables );
614 817 TablePress::redirect( array( 'action' => 'export', 'table_id' => $table_ids ) );
615 - break;
818 + // break; // unreachable.
616 819 case 'delete':
617 820 foreach ( $tables as $table_id ) {
618 821 if ( current_user_can( 'tablepress_delete_table', $table_id ) ) {
619 822 $deleted = TablePress::$model_table->delete( $table_id );
@@ -626,9 +829,9 @@
626 829 }
627 830 break;
628 831 }
629 832
630 - if ( 0 !== count( $no_success ) ) { // @TODO: maybe pass this information to the view?
833 + if ( 0 !== count( $no_success ) ) { // @todo maybe pass this information to the view?
631 834 $message = "error_{$bulk_action}_not_all_tables";
632 835 } else {
633 836 $plural = ( count( $tables ) > 1 ) ? '_plural' : '';
634 837 $message = "success_{$bulk_action}{$plural}";
@@ -649,97 +852,13 @@
649 852 exit;
650 853 }
651 854
652 855 /**
653 - * Save a table after the "Edit" screen was submitted.
654 - *
655 - * @since 1.0.0
656 - */
657 - public function handle_post_action_edit() {
658 - if ( empty( $_POST['table']['id'] ) ) {
659 - TablePress::redirect( array( 'action' => 'list', 'message' => 'error_save' ) );
660 - }
661 -
662 - $edit_table = wp_unslash( $_POST['table'] );
663 -
664 - TablePress::check_nonce( 'edit', $edit_table['id'], 'nonce-edit-table' );
665 -
666 - if ( ! current_user_can( 'tablepress_edit_table', $edit_table['id'] ) ) {
667 - wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
668 - }
669 -
670 - // Options array must exist, so that checkboxes can be evaluated.
671 - if ( empty( $edit_table['options'] ) ) {
672 - TablePress::redirect( array( 'action' => 'edit', 'table_id' => $edit_table['id'], 'message' => 'error_save' ) );
673 - }
674 -
675 - // Evaluate options that have a checkbox (only necessary in Admin Controller, where they might not be set (if unchecked)).
676 - $checkbox_options = array(
677 - // Table Options.
678 - 'table_head',
679 - 'table_foot',
680 - 'alternating_row_colors',
681 - 'row_hover',
682 - 'print_name',
683 - 'print_description',
684 - // DataTables JS Features.
685 - 'use_datatables',
686 - 'datatables_sort',
687 - 'datatables_filter',
688 - 'datatables_paginate',
689 - 'datatables_lengthchange',
690 - 'datatables_info',
691 - 'datatables_scrollx',
692 - );
693 - foreach ( $checkbox_options as $option ) {
694 - $edit_table['options'][ $option ] = ( isset( $edit_table['options'][ $option ] ) && 'true' === $edit_table['options'][ $option ] );
695 - }
696 -
697 - // Load table, without table data, but with options and visibility settings.
698 - $existing_table = TablePress::$model_table->load( $edit_table['id'], false, true );
699 - if ( is_wp_error( $existing_table ) ) { // @TODO: Maybe somehow load a new table here? (TablePress::$model_table->get_table_template())?
700 - TablePress::redirect( array( 'action' => 'edit', 'table_id' => $edit_table['id'], 'message' => 'error_save' ) );
701 - }
702 -
703 - // Check consistency of new table, and then merge with existing table.
704 - $table = TablePress::$model_table->prepare_table( $existing_table, $edit_table );
705 - if ( is_wp_error( $table ) ) {
706 - TablePress::redirect( array( 'action' => 'edit', 'table_id' => $edit_table['id'], 'message' => 'error_save' ) );
707 - }
708 -
709 - // DataTables Custom Commands can only be edit by trusted users.
710 - if ( ! current_user_can( 'unfiltered_html' ) ) {
711 - $table['options']['datatables_custom_commands'] = $existing_table['options']['datatables_custom_commands'];
712 - }
713 -
714 - // Save updated table.
715 - $saved = TablePress::$model_table->save( $table );
716 - if ( is_wp_error( $saved ) ) {
717 - TablePress::redirect( array( 'action' => 'edit', 'table_id' => $table['id'], 'message' => 'error_save' ) );
718 - }
719 -
720 - // Check if ID change is desired.
721 - if ( $table['id'] === $table['new_id'] ) { // if not, we are done
722 - TablePress::redirect( array( 'action' => 'edit', 'table_id' => $table['id'], 'message' => 'success_save' ) );
723 - }
724 -
725 - // Change table ID.
726 - if ( ! current_user_can( 'tablepress_edit_table_id', $table['id'] ) ) {
727 - TablePress::redirect( array( 'action' => 'edit', 'table_id' => $table['id'], 'message' => 'success_save_error_id_change' ) );
728 - }
729 - $id_changed = TablePress::$model_table->change_table_id( $table['id'], $table['new_id'] );
730 - if ( is_wp_error( $id_changed ) ) {
731 - TablePress::redirect( array( 'action' => 'edit', 'table_id' => $table['id'], 'message' => 'success_save_error_id_change' ) );
732 - }
733 - TablePress::redirect( array( 'action' => 'edit', 'table_id' => $table['new_id'], 'message' => 'success_save_success_id_change' ) );
734 - }
735 -
736 - /**
737 856 * Add a table, according to the parameters on the "Add new Table" screen.
738 857 *
739 858 * @since 1.0.0
740 859 */
741 - public function handle_post_action_add() {
860 + public function handle_post_action_add(): void {
742 861 TablePress::check_nonce( 'add' );
743 862
744 863 if ( ! current_user_can( 'tablepress_add_tables' ) ) {
745 864 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
@@ -745,24 +864,24 @@
745 864 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
746 865 }
747 866
748 867 if ( empty( $_POST['table'] ) || ! is_array( $_POST['table'] ) ) {
749 - TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add' ) );
868 + TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add', 'error_details' => 'The HTTP POST data is empty.' ) );
750 869 }
751 870
752 871 $add_table = wp_unslash( $_POST['table'] );
753 872
754 - // Perform sanity checks of posted data.
755 - $name = ( isset( $add_table['name'] ) ) ? $add_table['name'] : '';
756 - $description = ( isset( $add_table['description'] ) ) ? $add_table['description'] : '';
873 + // Perform confidence checks of posted data.
874 + $name = $add_table['name'] ?? '';
875 + $description = $add_table['description'] ?? '';
757 876 if ( ! isset( $add_table['rows'], $add_table['columns'] ) ) {
758 - TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add' ) );
877 + TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add', 'error_details' => 'The HTTP POST data does not contain the table size.' ) );
759 878 }
760 879
761 880 $num_rows = absint( $add_table['rows'] );
762 881 $num_columns = absint( $add_table['columns'] );
763 882 if ( 0 === $num_rows || 0 === $num_columns ) {
764 - TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add' ) );
883 + TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add', 'error_details' => 'The table size is invalid.' ) );
765 884 }
766 885
767 886 // Create a new table array with information from the posted data.
768 887 $new_table = array(
@@ -776,15 +895,15 @@
776 895 );
777 896 // Merge this data into an empty table template.
778 897 $table = TablePress::$model_table->prepare_table( TablePress::$model_table->get_table_template(), $new_table, false );
779 898 if ( is_wp_error( $table ) ) {
780 - TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add' ) );
899 + TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add', 'error_details' => TablePress::get_wp_error_string( $table ) ) );
781 900 }
782 901
783 902 // Add the new table (and get its first ID).
784 903 $table_id = TablePress::$model_table->add( $table );
785 904 if ( is_wp_error( $table_id ) ) {
786 - TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add' ) );
905 + TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add', 'error_details' => TablePress::get_wp_error_string( $table_id ) ) );
787 906 }
788 907
789 908 TablePress::redirect( array( 'action' => 'edit', 'table_id' => $table_id, 'message' => 'success_add' ) );
790 909 }
@@ -793,9 +912,9 @@
793 912 * Save changed "Plugin Options".
794 913 *
795 914 * @since 1.0.0
796 915 */
797 - public function handle_post_action_options() {
916 + public function handle_post_action_options(): void {
798 917 TablePress::check_nonce( 'options' );
799 918
800 919 if ( ! current_user_can( 'tablepress_access_options_screen' ) ) {
801 920 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
@@ -814,16 +933,16 @@
814 933 if ( ! empty( $posted_options['admin_menu_parent_page'] ) && '-' !== $posted_options['admin_menu_parent_page'] ) {
815 934 $new_options['admin_menu_parent_page'] = $posted_options['admin_menu_parent_page'];
816 935 // Re-init parent information, as `TablePress::redirect()` URL might be wrong otherwise.
817 936 /** This filter is documented in classes/class-controller.php */
818 - $this->parent_page = apply_filters( 'tablepress_admin_menu_parent_page', $posted_options['admin_menu_parent_page'] );
819 - $this->is_top_level_page = in_array( $this->parent_page, array( 'top', 'middle', 'bottom' ), true );
937 + TablePress::$controller->parent_page = apply_filters( 'tablepress_admin_menu_parent_page', $posted_options['admin_menu_parent_page'] );
938 + TablePress::$controller->is_top_level_page = in_array( TablePress::$controller->parent_page, array( 'top', 'middle', 'bottom' ), true );
820 939 }
821 940
822 941 // Custom CSS can only be saved if the user is allowed to do so.
823 942 $update_custom_css_files = false;
824 943 if ( current_user_can( 'tablepress_edit_options' ) ) {
825 - // Checkbox
944 + // Checkbox.
826 945 $new_options['use_custom_css'] = ( isset( $posted_options['use_custom_css'] ) && 'true' === $posted_options['use_custom_css'] );
827 946
828 947 if ( isset( $posted_options['custom_css'] ) ) {
829 948 $new_options['custom_css'] = $posted_options['custom_css'];
@@ -828,13 +947,20 @@
828 947 if ( isset( $posted_options['custom_css'] ) ) {
829 948 $new_options['custom_css'] = $posted_options['custom_css'];
830 949
831 950 $tablepress_css = TablePress::load_class( 'TablePress_CSS', 'class-css.php', 'classes' );
832 - // Sanitize and tidy up Custom CSS.
833 - $new_options['custom_css'] = $tablepress_css->sanitize_css( $new_options['custom_css'] );
834 - // Minify Custom CSS
835 - $new_options['custom_css_minified'] = $tablepress_css->minify_css( $new_options['custom_css'] );
836 951
952 + if ( '' !== $new_options['custom_css'] ) {
953 + // Update "Custom CSS" to use DataTables 2 variants instead of old DataTables 1.x CSS classes.
954 + $new_options['custom_css'] = TablePress::convert_datatables_api_data( $new_options['custom_css'] );
955 + // Sanitize and tidy up Custom CSS.
956 + $new_options['custom_css'] = $tablepress_css->sanitize_css( $new_options['custom_css'] );
957 + // Minify Custom CSS.
958 + $new_options['custom_css_minified'] = $tablepress_css->minify_css( $new_options['custom_css'] );
959 + } else {
960 + $new_options['custom_css_minified'] = '';
961 + }
962 +
837 963 // Maybe update CSS files as well.
838 964 $custom_css_file_contents = $tablepress_css->load_custom_css_from_file( 'normal' );
839 965 if ( false === $custom_css_file_contents ) {
840 966 $custom_css_file_contents = '';
@@ -865,9 +991,9 @@
865 991 * Export selected tables.
866 992 *
867 993 * @since 1.0.0
868 994 */
869 - public function handle_post_action_export() {
995 + public function handle_post_action_export(): void {
870 996 TablePress::check_nonce( 'export' );
871 997
872 998 if ( ! current_user_can( 'tablepress_export_tables' ) ) {
873 999 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
@@ -873,20 +999,22 @@
873 999 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
874 1000 }
875 1001
876 1002 if ( empty( $_POST['export'] ) || ! is_array( $_POST['export'] ) ) {
877 - TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export' ) );
1003 + TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export', 'error_details' => 'The HTTP POST data is empty.' ) );
878 1004 }
879 1005
880 1006 $export = wp_unslash( $_POST['export'] );
881 1007
1008 + if ( empty( $export['tables_list'] ) ) {
1009 + TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export', 'error_details' => 'The HTTP POST data does not contain tables.' ) );
1010 + }
1011 +
1012 + /** @var TablePress_Export $exporter */ // phpcs:ignore Generic.Commenting.DocComment.MissingShort
882 1013 $exporter = TablePress::load_class( 'TablePress_Export', 'class-export.php', 'classes' );
883 1014
884 - if ( empty( $export['tables'] ) ) {
885 - TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export' ) );
886 - }
887 1015 if ( empty( $export['format'] ) || ! isset( $exporter->export_formats[ $export['format'] ] ) ) {
888 - TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export' ) );
1016 + TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export', 'error_details' => 'The export format is invalid.' ) );
889 1017 }
890 1018 if ( empty( $export['csv_delimiter'] ) ) {
891 1019 // Set a value, so that the variable exists.
892 1020 $export['csv_delimiter'] = '';
@@ -891,13 +1019,12 @@
891 1019 // Set a value, so that the variable exists.
892 1020 $export['csv_delimiter'] = '';
893 1021 }
894 1022 if ( 'csv' === $export['format'] && ! isset( $exporter->csv_delimiters[ $export['csv_delimiter'] ] ) ) {
895 - TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export' ) );
1023 + TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export', 'error_details' => 'The CSV delimiter is invalid.' ) );
896 1024 }
897 1025
898 - // Use list of tables from concatenated field if available (as that's hopefully not truncated by Suhosin, which is possible for $export['tables']).
899 - $tables = ( ! empty( $export['tables_list'] ) ) ? explode( ',', $export['tables_list'] ) : $export['tables'];
1026 + $tables = explode( ',', $export['tables_list'] );
900 1027
901 1028 // Determine if ZIP file support is available.
902 1029 if ( $exporter->zip_support_available
903 1030 && ( ( isset( $export['zip_file'] ) && 'true' === $export['zip_file'] ) || count( $tables ) > 1 ) ) {
@@ -907,9 +1034,9 @@
907 1034 $export_to_zip = false;
908 1035 }
909 1036
910 1037 if ( ! $export_to_zip ) {
911 - // This is only possible for one table, so take the first one.
1038 + // Exporting without a ZIP file is only possible for one table, so take the first one.
912 1039 if ( ! current_user_can( 'tablepress_export_table', $tables[0] ) ) {
913 1040 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
914 1041 }
915 1042 // Load table, with table data, options, and visibility settings.
@@ -914,42 +1041,57 @@
914 1041 }
915 1042 // Load table, with table data, options, and visibility settings.
916 1043 $table = TablePress::$model_table->load( $tables[0], true, true );
917 1044 if ( is_wp_error( $table ) ) {
918 - TablePress::redirect( array( 'action' => 'export', 'message' => 'error_load_table', 'export_format' => $export['format'], 'csv_delimiter' => $export['csv_delimiter'] ) );
1045 + TablePress::redirect( array( 'action' => 'export', 'message' => 'error_load_table', 'export_format' => $export['format'], 'csv_delimiter' => $export['csv_delimiter'], 'error_details' => TablePress::get_wp_error_string( $table ) ) );
919 1046 }
920 1047 if ( isset( $table['is_corrupted'] ) && $table['is_corrupted'] ) {
921 1048 TablePress::redirect( array( 'action' => 'export', 'message' => 'error_table_corrupted', 'export_format' => $export['format'], 'csv_delimiter' => $export['csv_delimiter'] ) );
922 1049 }
923 1050 $download_filename = sprintf( '%1$s-%2$s-%3$s.%4$s', $table['id'], $table['name'], wp_date( 'Y-m-d' ), $export['format'] );
1051 + /**
1052 + * Filters the download filename of the exported table.
1053 + *
1054 + * @since 2.0.0
1055 + *
1056 + * @param string $download_filename The download filename of exported table.
1057 + * @param string $table_id Table ID of the exported table.
1058 + * @param string $table_name Table name of the exported table.
1059 + * @param string $export_format Format for the export ('csv', 'html', 'json', 'zip').
1060 + * @param bool $export_to_zip Whether the export is to a ZIP file (of multiple export files).
1061 + */
1062 + $download_filename = apply_filters( 'tablepress_export_filename', $download_filename, $table['id'], $table['name'], $export['format'], $export_to_zip );
924 1063 $download_filename = sanitize_file_name( $download_filename );
925 1064 // Export the table.
926 1065 $export_data = $exporter->export_table( $table, $export['format'], $export['csv_delimiter'] );
927 1066 /**
928 - * Filter the exported table data.
1067 + * Filters the exported table data.
929 1068 *
930 1069 * @since 1.6.0
931 1070 *
932 - * @param string $export_data The exported table data.
933 - * @param array $table Table to be exported.
934 - * @param string $export_format Format for the export ('csv', 'html', 'json').
935 - * @param string $csv_delimiter Delimiter for CSV export.
1071 + * @param string $export_data The exported table data.
1072 + * @param array<string, mixed> $table Table to be exported.
1073 + * @param string $export_format Format for the export ('csv', 'html', 'json').
1074 + * @param string $csv_delimiter Delimiter for CSV export.
936 1075 */
937 1076 $export_data = apply_filters( 'tablepress_export_data', $export_data, $table, $export['format'], $export['csv_delimiter'] );
938 1077 $download_data = $export_data;
939 1078 } else {
940 1079 // Zipping can use a lot of memory and execution time, but not this much hopefully.
941 - /** This filter is documented in the WordPress file wp-admin/admin.php */
942 - @ini_set( 'memory_limit', apply_filters( 'admin_memory_limit', WP_MAX_MEMORY_LIMIT ) ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
943 - @set_time_limit( 300 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
1080 + wp_raise_memory_limit( 'admin' );
1081 + if ( function_exists( 'set_time_limit' ) ) {
1082 + @set_time_limit( 300 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
1083 + }
944 1084
945 1085 $zip_file = new ZipArchive();
946 1086 $download_filename = sprintf( 'tablepress-export-%1$s-%2$s.zip', wp_date( 'Y-m-d-H-i-s' ), $export['format'] );
1087 + /** This filter is documented in controllers/controller-admin.php */
1088 + $download_filename = apply_filters( 'tablepress_export_filename', $download_filename, '', '', $export['format'], $export_to_zip );
947 1089 $download_filename = sanitize_file_name( $download_filename );
948 1090 $full_filename = wp_tempnam( $download_filename );
949 - if ( true !== $zip_file->open( $full_filename, ZIPARCHIVE::OVERWRITE ) ) {
950 - @unlink( $full_filename );
951 - TablePress::redirect( array( 'action' => 'export', 'message' => 'error_create_zip_file', 'export_format' => $export['format'], 'csv_delimiter' => $export['csv_delimiter'] ) );
1091 + if ( true !== $zip_file->open( $full_filename, ZipArchive::OVERWRITE ) ) {
1092 + @unlink( $full_filename ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
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.' ) );
952 1094 }
953 1095
954 1096 foreach ( $tables as $table_id ) {
955 1097 // Don't export tables for which the user doesn't have the necessary export rights.
@@ -969,8 +1111,10 @@
969 1111 $export_data = $exporter->export_table( $table, $export['format'], $export['csv_delimiter'] );
970 1112 /** This filter is documented in controllers/controller-admin.php */
971 1113 $export_data = apply_filters( 'tablepress_export_data', $export_data, $table, $export['format'], $export['csv_delimiter'] );
972 1114 $export_filename = sprintf( '%1$s-%2$s-%3$s.%4$s', $table['id'], $table['name'], wp_date( 'Y-m-d' ), $export['format'] );
1115 + /** This filter is documented in controllers/controller-admin.php */
1116 + $export_filename = apply_filters( 'tablepress_export_filename', $export_filename, $table['id'], $table['name'], $export['format'], $export_to_zip );
973 1117 $export_filename = sanitize_file_name( $export_filename );
974 1118 $zip_file->addFromString( $export_filename, $export_data );
975 1119 }
976 1120
@@ -975,18 +1119,22 @@
975 1119 }
976 1120
977 1121 // If something went wrong, or no files were added to the ZIP file, bail out.
978 1122 // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
979 - if ( ! ZIPARCHIVE::ER_OK === $zip_file->status || 0 === $zip_file->numFiles ) {
1123 + if ( ZipArchive::ER_OK !== $zip_file->status || 0 === $zip_file->numFiles ) {
980 1124 $zip_file->close();
981 - @unlink( $full_filename );
982 - TablePress::redirect( array( 'action' => 'export', 'message' => 'error_create_zip_file', 'export_format' => $export['format'], 'csv_delimiter' => $export['csv_delimiter'] ) );
1125 + @unlink( $full_filename ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
1126 + 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.' ) );
983 1127 }
984 1128 $zip_file->close();
985 1129
986 1130 // Load contents of the ZIP file, to send it as a download.
987 1131 $download_data = file_get_contents( $full_filename );
988 - @unlink( $full_filename );
1132 + if ( false === $download_data ) {
1133 + @unlink( $full_filename ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
1134 + TablePress::redirect( array( 'action' => 'export', 'message' => 'error_create_zip_file', 'export_format' => $export['format'], 'csv_delimiter' => $export['csv_delimiter'], 'error_details' => 'The ZIP file content could not be read.' ) );
1135 + }
1136 + @unlink( $full_filename ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
989 1137 }
990 1138
991 1139 // Send download headers for export file.
992 1140 header( 'Content-Description: File Transfer' );
@@ -1009,9 +1157,9 @@
1009 1157 * Import data from existing source (Upload, URL, Server, Direct input).
1010 1158 *
1011 1159 * @since 1.0.0
1012 1160 */
1013 - public function handle_post_action_import() {
1161 + public function handle_post_action_import(): void {
1014 1162 TablePress::check_nonce( 'import' );
1015 1163
1016 1164 if ( ! current_user_can( 'tablepress_import_tables' ) ) {
1017 1165 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
@@ -1017,349 +1165,88 @@
1017 1165 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
1018 1166 }
1019 1167
1020 1168 if ( empty( $_POST['import'] ) || ! is_array( $_POST['import'] ) ) {
1021 - TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import' ) );
1169 + TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import', 'error_details' => 'The HTTP POST data is empty.' ) );
1022 1170 }
1023 1171
1024 - $import = wp_unslash( $_POST['import'] );
1172 + $import_config = wp_unslash( $_POST['import'] );
1025 1173
1026 - if ( ! isset( $import['type'] ) ) {
1027 - $import['type'] = 'add';
1174 + if ( empty( $import_config['source'] ) ) {
1175 + TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import', 'error_details' => 'The HTTP POST does not contain an import configuration.' ) );
1028 1176 }
1029 - if ( ! isset( $import['existing_table'] ) ) {
1030 - $import['existing_table'] = '';
1031 - }
1032 - if ( ! isset( $import['source'] ) ) {
1033 - $import['source'] = '';
1034 - }
1035 1177
1036 - $import_error = true;
1037 - $unlink_file = false;
1038 - $import_data = array();
1039 - switch ( $import['source'] ) {
1040 - case 'file-upload':
1041 - if ( ! empty( $_FILES['import_file_upload'] ) && UPLOAD_ERR_OK === $_FILES['import_file_upload']['error'] ) {
1042 - $import_data['file_location'] = $_FILES['import_file_upload']['tmp_name'];
1043 - $import_data['file_name'] = $_FILES['import_file_upload']['name'];
1044 - // $_FILES['import_file_upload']['type'];
1045 - // $_FILES['import_file_upload']['size']
1046 - $import_error = false;
1047 - $unlink_file = true;
1048 - }
1049 - break;
1050 - case 'url':
1051 - if ( ! empty( $import['url'] ) && 'https://' !== $import['url'] ) {
1052 - // Check the host of the Import URL against a blacklist of hosts, which should not be accessible, e.g. for security considerations.
1053 - $host = wp_parse_url( $import['url'], PHP_URL_HOST );
1054 - $blocked_hosts = array(
1055 - '169.254.169.254', // AWS Meta-data API
1056 - );
1057 - if ( empty( $host ) || in_array( $host, $blocked_hosts, true ) ) {
1058 - $import_error = true;
1059 - break;
1060 - }
1061 -
1062 - // Download URL to local file.
1063 - $import_data['file_location'] = download_url( $import['url'] );
1064 - $import_data['file_name'] = $import['url'];
1065 - if ( ! is_wp_error( $import_data['file_location'] ) ) {
1066 - $import_error = false;
1067 - $unlink_file = true;
1068 - }
1069 - }
1070 - break;
1071 - case 'server':
1072 - if ( ! empty( $import['server'] ) && ABSPATH !== $import['server']
1073 - && ( ( ! is_multisite() && current_user_can( 'manage_options' ) ) || is_super_admin() )
1074 - ) {
1075 - // For security reasons, the `server` source is only available for administrators.
1076 - $import_data['file_location'] = $import['server'];
1077 - $import_data['file_name'] = pathinfo( $import['server'], PATHINFO_BASENAME );
1078 - if ( is_readable( $import['server'] ) ) {
1079 - $import_error = false;
1080 - }
1081 - }
1082 - break;
1083 - case 'form-field':
1084 - if ( ! empty( $import['form_field'] ) ) {
1085 - $import_data['file_location'] = '';
1086 - $import_data['file_name'] = __( 'Imported from Manual Input', 'tablepress' ); // Description of the table.
1087 - $import_data['data'] = $import['form_field'];
1088 - $import_error = false;
1089 - }
1090 - break;
1178 + // For security reasons, the "server" source is only available for super admins on multisite and admins on single sites.
1179 + if ( 'server' === $import_config['source'] ) {
1180 + if ( ! is_super_admin() && ! ( ! is_multisite() && current_user_can( 'manage_options' ) ) ) {
1181 + TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import', 'error_details' => 'You do not have the required access rights.' ) );
1182 + }
1091 1183 }
1092 1184
1093 - if ( $import_error ) {
1094 - if ( $unlink_file ) {
1095 - @unlink( $import_data['file_location'] );
1185 + // For security reasons, the "url" source is only available admins and editors via a custom capability.
1186 + if ( 'url' === $import_config['source'] ) {
1187 + if ( ! current_user_can( 'tablepress_import_tables_url' ) ) {
1188 + TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import', 'error_details' => 'You do not have the required access rights.' ) );
1096 1189 }
1097 - TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import_source_invalid', 'import_format' => $import['format'], 'import_type' => $import['type'], 'import_existing_table' => $import['existing_table'], 'import_source' => $import['source'] ) );
1098 1190 }
1099 1191
1100 - $this->importer = TablePress::load_class( 'TablePress_Import', 'class-import.php', 'classes' );
1192 + // Move file upload data to the main import configuration.
1193 + $import_config['file-upload'] = $_FILES['import_file_upload'] ?? null;
1101 1194
1102 - $import_zip = ( 'zip' === pathinfo( $import_data['file_name'], PATHINFO_EXTENSION ) );
1195 + // Check if the source data for the chosen import source is defined.
1196 + if ( empty( $import_config[ $import_config['source'] ] ) ) {
1197 + TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import', 'error_details' => 'The HTTP POST data does not contain an import source.' ) );
1198 + }
1103 1199
1104 - // Determine if ZIP file support is available.
1105 - if ( $import_zip && ! $this->importer->zip_support_available ) {
1106 - if ( $unlink_file ) {
1107 - @unlink( $import_data['file_location'] );
1108 - }
1109 - TablePress::redirect( array( 'action' => 'import', 'message' => 'error_no_zip_import', 'import_format' => $import['format'], 'import_type' => $import['type'], 'import_existing_table' => $import['existing_table'], 'import_source' => $import['source'] ) );
1200 + // Set default values for non-essential configuration variables.
1201 + if ( ! isset( $import_config['type'] ) ) {
1202 + $import_config['type'] = 'add';
1110 1203 }
1204 + if ( ! isset( $import_config['existing_table'] ) ) {
1205 + $import_config['existing_table'] = '';
1206 + }
1111 1207
1112 - if ( ! $import_zip ) {
1113 - // Check if a table to replace or append to was selected (which is only necessary for import from non-ZIP files).
1114 - if ( in_array( $import['type'], array( 'replace', 'append' ), true ) && empty( $import['existing_table'] ) ) {
1115 - if ( $unlink_file ) {
1116 - @unlink( $import_data['file_location'] );
1117 - }
1118 - TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import_no_existing_id', 'import_format' => $import['format'], 'import_type' => $import['type'], 'import_source' => $import['source'] ) );
1119 - }
1208 + $import_config['legacy_import'] = ( isset( $import_config['legacy_import'] ) && 'true' === $import_config['legacy_import'] );
1120 1209
1121 - if ( ! isset( $import_data['data'] ) ) {
1122 - $import_data['data'] = file_get_contents( $import_data['file_location'] );
1123 - }
1124 - if ( false === $import_data['data'] ) {
1125 - TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import' ) );
1126 - }
1210 + $importer = TablePress::load_class( 'TablePress_Import', 'class-import.php', 'classes' );
1211 + $import = $importer->run( $import_config );
1127 1212
1128 - $name = $import_data['file_name'];
1129 - $description = $import_data['file_name'];
1130 - $existing_table_id = ( in_array( $import['type'], array( 'replace', 'append' ), true ) && ! empty( $import['existing_table'] ) ) ? $import['existing_table'] : false;
1131 - $table_id = $this->_import_tablepress_table( $import['format'], $import_data['data'], $name, $description, $existing_table_id, $import['type'] );
1132 -
1133 - if ( $unlink_file ) {
1134 - @unlink( $import_data['file_location'] );
1213 + if ( is_wp_error( $import ) || 0 < count( $import['errors'] ) ) {
1214 + $redirect_parameters = array(
1215 + 'action' => 'import',
1216 + 'message' => 'error_import',
1217 + 'import_type' => $import_config['type'],
1218 + 'import_existing_table' => $import_config['existing_table'],
1219 + 'import_source' => $import_config['source'],
1220 + 'legacy_import' => $import_config['legacy_import'],
1221 + );
1222 + if ( in_array( $import_config['source'], array( 'url', 'server' ), true ) ) {
1223 + $redirect_parameters[ "import_{$import_config['source']}" ] = $import_config[ $import_config['source'] ];
1135 1224 }
1136 -
1137 - if ( is_wp_error( $table_id ) ) {
1138 - TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import_data' ) );
1139 - }
1140 -
1141 - TablePress::redirect( array( 'action' => 'edit', 'table_id' => $table_id, 'message' => 'success_import' ) );
1142 - } else {
1143 - // Zipping can use a lot of memory and execution time, but not this much hopefully.
1144 - /** This filter is documented in the WordPress file wp-admin/admin.php */
1145 - @ini_set( 'memory_limit', apply_filters( 'admin_memory_limit', WP_MAX_MEMORY_LIMIT ) ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
1146 - @set_time_limit( 300 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
1147 -
1148 - $zip = new ZipArchive();
1149 - if ( true !== $zip->open( $import_data['file_location'], ZIPARCHIVE::CHECKCONS ) ) {
1150 - if ( $unlink_file ) {
1151 - @unlink( $import_data['file_location'] );
1225 + if ( is_wp_error( $import ) ) {
1226 + $redirect_parameters['error_details'] = TablePress::get_wp_error_string( $import );
1227 + } elseif ( 0 < count( $import['errors'] ) ) {
1228 + $wp_error_strings = array();
1229 + foreach ( $import['errors'] as $file ) {
1230 + $wp_error_strings[] = TablePress::get_wp_error_string( $file->error );
1152 1231 }
1153 - TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import_zip_open' ) );
1232 + $redirect_parameters['error_details'] = implode( ', ', $wp_error_strings );
1154 1233 }
1155 -
1156 - // Prepare a list of table names/IDs when importing a ZIP archive and replacing/appending existing tables (except for the JSON format).
1157 - $existing_tables = array();
1158 - if ( in_array( $import['type'], array( 'replace', 'append' ), true ) && 'json' !== $import['format'] ) {
1159 - // Load all table IDs and names for a comparison with the file name.
1160 - $table_ids = TablePress::$model_table->load_all( false );
1161 - foreach ( $table_ids as $table_id ) {
1162 - // Load table, without table data, options, and visibility settings.
1163 - $table = TablePress::$model_table->load( $table_id, false, false );
1164 - $existing_tables[ $table['name'] ][] = $table['id']; // Attention: The table name is not unique!
1165 - }
1166 - }
1167 -
1168 - $imported_files = array();
1169 - // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
1170 - for ( $file_idx = 0; $file_idx < $zip->numFiles; $file_idx++ ) {
1171 - $file_name = $zip->getNameIndex( $file_idx );
1172 - // Skip directories.
1173 - if ( '/' === substr( $file_name, -1 ) ) {
1174 - continue;
1175 - }
1176 - // Skip the __MACOSX directory that Mac OSX adds to archives.
1177 - if ( '__MACOSX/' === substr( $file_name, 0, 9 ) ) {
1178 - continue;
1179 - }
1180 - $data = $zip->getFromIndex( $file_idx );
1181 - if ( false === $data ) {
1182 - continue;
1183 - }
1184 -
1185 - $name = $file_name;
1186 - $description = $file_name;
1187 - // Use the replace/append ID of tables where the table name matches the file name, except for JSON imports, and only if there was exactly one file name match.
1188 - $existing_table_id = ( isset( $existing_tables[ $file_name ] ) && 1 === count( $existing_tables[ $file_name ] ) ) ? $existing_tables[ $file_name ][0] : false;
1189 - $table_id = $this->_import_tablepress_table( $import['format'], $data, $name, $description, $existing_table_id, $import['type'] );
1190 - if ( is_wp_error( $table_id ) ) {
1191 - continue;
1192 - } else {
1193 - $imported_files[] = $table_id;
1194 - }
1195 - };
1196 - $zip->close();
1197 -
1198 - if ( $unlink_file ) {
1199 - @unlink( $import_data['file_location'] );
1200 - }
1201 -
1202 - if ( count( $imported_files ) > 1 ) {
1203 - TablePress::redirect( array( 'action' => 'list', 'message' => 'success_import' ) );
1204 - } elseif ( 1 === count( $imported_files ) ) {
1205 - TablePress::redirect( array( 'action' => 'edit', 'table_id' => $imported_files[0], 'message' => 'success_import' ) );
1206 - } else {
1207 - TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import_zip_content' ) );
1208 - }
1234 + TablePress::redirect( $redirect_parameters );
1209 1235 }
1210 1236
1211 - }
1212 -
1213 - /**
1214 - * Import a table by either replacing an existing table or adding it as a new table.
1215 - *
1216 - * @since 1.0.0
1217 - *
1218 - * @param string $format Import format.
1219 - * @param string $data Data to import.
1220 - * @param string $name Name of the table.
1221 - * @param string $description Description of the table.
1222 - * @param bool|string $existing_table_id False if table shall be added new, ID of the table to be replaced or appended to otherwise.
1223 - * @param string $import_type What to do with the imported data: "add", "replace", "append".
1224 - * @return string|WP_Error WP_Error on error, table ID on success.
1225 - */
1226 - protected function _import_tablepress_table( $format, $data, $name, $description, $existing_table_id, $import_type ) {
1227 - $imported_table = $this->importer->import_table( $format, $data );
1228 - if ( false === $imported_table ) {
1229 - return new WP_Error( 'table_import_import_failed' );
1230 - }
1231 -
1232 - // Full JSON format table can contain a table ID, try to keep that.
1233 - $table_id_in_import = isset( $imported_table['id'] ) ? $imported_table['id'] : false;
1234 -
1235 - // If no ID for an existing table was specified in the import form, we add the imported table,
1236 - // except for replacing and appending of JSON files in ZIP archives, where we try to use the imported table ID.
1237 - if ( false === $existing_table_id ) {
1238 - if ( false !== $table_id_in_import && TablePress::$model_table->table_exists( $table_id_in_import ) ) {
1239 - $existing_table_id = $table_id_in_import;
1240 - } else {
1241 - $import_type = 'add';
1242 - }
1243 - }
1244 -
1245 - // To be able to replace or append to a table, editing that table must be allowed.
1246 - if ( in_array( $import_type, array( 'replace', 'append' ), true ) && ! current_user_can( 'tablepress_edit_table', $existing_table_id ) ) {
1247 - return new WP_Error( 'table_import_replace_append_capability_check_failed' );
1248 - }
1249 -
1250 - switch ( $import_type ) {
1251 - case 'add':
1252 - $existing_table = TablePress::$model_table->get_table_template();
1253 - // If name and description are imported from a new table, use those.
1254 - if ( ! isset( $imported_table['name'] ) ) {
1255 - $imported_table['name'] = $name;
1256 - }
1257 - if ( ! isset( $imported_table['description'] ) ) {
1258 - $imported_table['description'] = $description;
1259 - }
1260 - if ( isset( $imported_table['visibility']['rows'], $imported_table['visibility']['columns'] ) ) {
1261 - $existing_table['visibility']['rows'] = $imported_table['visibility']['rows'];
1262 - $existing_table['visibility']['columns'] = $imported_table['visibility']['columns'];
1263 - }
1264 - break;
1265 - case 'replace':
1266 - // Load table, without table data, but with options and visibility settings.
1267 - $existing_table = TablePress::$model_table->load( $existing_table_id, false, true );
1268 - if ( is_wp_error( $existing_table ) ) {
1269 - // Add an error code to the existing WP_Error.
1270 - $existing_table->add( 'table_import_replace_table_load', '', $existing_table_id );
1271 - return $existing_table;
1272 - }
1273 - // Don't change name and description when a table is replaced.
1274 - $imported_table['name'] = $existing_table['name'];
1275 - $imported_table['description'] = $existing_table['description'];
1276 - if ( isset( $imported_table['visibility']['rows'], $imported_table['visibility']['columns'] ) ) {
1277 - $existing_table['visibility']['rows'] = $imported_table['visibility']['rows'];
1278 - $existing_table['visibility']['columns'] = $imported_table['visibility']['columns'];
1279 - }
1280 - break;
1281 - case 'append':
1282 - // Load table, with table data, options, and visibility settings.
1283 - $existing_table = TablePress::$model_table->load( $existing_table_id, true, true );
1284 - if ( is_wp_error( $existing_table ) ) {
1285 - // Add an error code to the existing WP_Error.
1286 - $existing_table->add( 'table_import_append_table_load', '', $existing_table_id );
1287 - return $existing_table;
1288 - }
1289 - if ( isset( $existing_table['is_corrupted'] ) && $existing_table['is_corrupted'] ) {
1290 - return new WP_Error( 'table_import_append_table_load_corrupted', '', $existing_table_id );
1291 - }
1292 - // Don't change name and description when a table is appended to.
1293 - $imported_table['name'] = $existing_table['name'];
1294 - $imported_table['description'] = $existing_table['description'];
1295 - // Actual appending:
1296 - $imported_table['data'] = array_merge( $existing_table['data'], $imported_table['data'] );
1297 - $imported_table['data'] = $this->importer->pad_array_to_max_cols( $imported_table['data'] );
1298 - // Append visibility information for rows.
1299 - if ( isset( $imported_table['visibility']['rows'] ) ) {
1300 - $existing_table['visibility']['rows'] = array_merge( $existing_table['visibility']['rows'], $imported_table['visibility']['rows'] );
1301 - }
1302 - // When appending, do not overwrite options.
1303 - if ( isset( $imported_table['options'] ) ) {
1304 - unset( $imported_table['options'] );
1305 - }
1306 - break;
1307 - default:
1308 - return new WP_Error( 'table_import_import_type_invalid', '', $import_type );
1309 - }
1310 -
1311 - // Merge new or existing table with information from the imported table.
1312 - $imported_table['id'] = $existing_table['id']; // will be false for new table or the existing table ID
1313 - // Cut visibility array (if the imported table is smaller), and pad correctly if imported table is bigger than existing table (or new template).
1314 - $num_rows = count( $imported_table['data'] );
1315 - $num_columns = count( $imported_table['data'][0] );
1316 - $imported_table['visibility'] = array(
1317 - 'rows' => array_pad( array_slice( $existing_table['visibility']['rows'], 0, $num_rows ), $num_rows, 1 ),
1318 - 'columns' => array_pad( array_slice( $existing_table['visibility']['columns'], 0, $num_columns ), $num_columns, 1 ),
1319 - );
1320 -
1321 - // Check if new data is ok.
1322 - $table = TablePress::$model_table->prepare_table( $existing_table, $imported_table, false );
1323 - if ( is_wp_error( $table ) ) {
1324 - // Add an error code to the existing WP_Error.
1325 - $table->add( 'table_import_table_prepare', '' );
1326 - return $table;
1327 - }
1328 -
1329 - // DataTables Custom Commands can only be edit by trusted users.
1330 - if ( ! current_user_can( 'unfiltered_html' ) ) {
1331 - $table['options']['datatables_custom_commands'] = $existing_table['options']['datatables_custom_commands'];
1332 - }
1333 -
1334 - // Replace existing table or add new table.
1335 - if ( in_array( $import_type, array( 'replace', 'append' ), true ) ) {
1336 - // Replace existing table with imported/appended table.
1337 - $table_id = TablePress::$model_table->save( $table );
1237 + // At this point, there were no import errors.
1238 + if ( count( $import['tables'] ) > 1 ) {
1239 + TablePress::redirect( array( 'action' => 'list', 'message' => 'success_import' ) );
1240 + } elseif ( 1 === count( $import['tables'] ) ) {
1241 + TablePress::redirect( array( 'action' => 'edit', 'table_id' => $import['tables'][0]['id'], 'message' => 'success_import' ) );
1338 1242 } else {
1339 - // Add the imported table (and get its first ID).
1340 - $table_id = TablePress::$model_table->add( $table );
1243 + TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import', 'error_details' => 'The number of imported tables is invalid.' ) );
1341 1244 }
1342 -
1343 - if ( is_wp_error( $table_id ) ) {
1344 - // Add an error code to the existing WP_Error.
1345 - $table_id->add( 'table_import_table_save_or_add', '' );
1346 - return $table_id;
1347 - }
1348 -
1349 - // Try to use ID from imported file (e.g. in full JSON format table).
1350 - if ( false !== $table_id_in_import && $table_id !== $table_id_in_import && current_user_can( 'tablepress_edit_table_id', $table_id ) ) {
1351 - $id_changed = TablePress::$model_table->change_table_id( $table_id, $table_id_in_import );
1352 - if ( ! is_wp_error( $id_changed ) ) {
1353 - $table_id = $table_id_in_import;
1354 - }
1355 - }
1356 -
1357 - return $table_id;
1358 1245 }
1359 1246
1360 1247 /*
1361 - * Save GET actions.
1248 + * HTTP GET actions.
1362 1249 */
1363 1250
1364 1251 /**
1365 1252 * Hide a header message on an admin screen.
@@ -1365,9 +1252,9 @@
1365 1252 * Hide a header message on an admin screen.
1366 1253 *
1367 1254 * @since 1.0.0
1368 1255 */
1369 - public function handle_get_action_hide_message() {
1256 + public function handle_get_action_hide_message(): void {
1370 1257 $message_item = ! empty( $_GET['item'] ) ? $_GET['item'] : '';
1371 1258 TablePress::check_nonce( 'hide_message', $message_item );
1372 1259
1373 1260 if ( ! current_user_can( 'tablepress_list_tables' ) ) {
@@ -1384,9 +1271,9 @@
1384 1271 * Delete a table.
1385 1272 *
1386 1273 * @since 1.0.0
1387 1274 */
1388 - public function handle_get_action_delete_table() {
1275 + public function handle_get_action_delete_table(): void {
1389 1276 $table_id = ( ! empty( $_GET['item'] ) ) ? $_GET['item'] : false;
1390 1277 TablePress::check_nonce( 'delete_table', $table_id );
1391 1278
1392 1279 $return = ! empty( $_GET['return'] ) ? $_GET['return'] : 'list';
@@ -1391,9 +1278,9 @@
1391 1278
1392 1279 $return = ! empty( $_GET['return'] ) ? $_GET['return'] : 'list';
1393 1280 $return_item = ! empty( $_GET['return_item'] ) ? $_GET['return_item'] : false;
1394 1281
1395 - // Nonce check should actually catch this already.
1282 + // The nonce check should actually catch this already.
1396 1283 if ( false === $table_id ) {
1397 1284 TablePress::redirect( array( 'action' => $return, 'message' => 'error_delete', 'table_id' => $return_item ) );
1398 1285 }
1399 1286
@@ -1402,9 +1289,9 @@
1402 1289 }
1403 1290
1404 1291 $deleted = TablePress::$model_table->delete( $table_id );
1405 1292 if ( is_wp_error( $deleted ) ) {
1406 - TablePress::redirect( array( 'action' => $return, 'message' => 'error_delete', 'table_id' => $return_item ) );
1293 + TablePress::redirect( array( 'action' => $return, 'message' => 'error_delete', 'table_id' => $return_item, 'error_details' => TablePress::get_wp_error_string( $deleted ) ) );
1407 1294 }
1408 1295
1409 1296 /*
1410 1297 * Slightly more complex redirect method, to account for sort, search, and pagination in the WP_List_Table on the List View,
@@ -1425,9 +1312,9 @@
1425 1312 * Copy a table.
1426 1313 *
1427 1314 * @since 1.0.0
1428 1315 */
1429 - public function handle_get_action_copy_table() {
1316 + public function handle_get_action_copy_table(): void {
1430 1317 $table_id = ( ! empty( $_GET['item'] ) ) ? $_GET['item'] : false;
1431 1318 TablePress::check_nonce( 'copy_table', $table_id );
1432 1319
1433 1320 $return = ! empty( $_GET['return'] ) ? $_GET['return'] : 'list';
@@ -1432,9 +1319,9 @@
1432 1319
1433 1320 $return = ! empty( $_GET['return'] ) ? $_GET['return'] : 'list';
1434 1321 $return_item = ! empty( $_GET['return_item'] ) ? $_GET['return_item'] : false;
1435 1322
1436 - // Nonce check should actually catch this already.
1323 + // The nonce check should actually catch this already.
1437 1324 if ( false === $table_id ) {
1438 1325 TablePress::redirect( array( 'action' => $return, 'message' => 'error_copy', 'table_id' => $return_item ) );
1439 1326 }
1440 1327
@@ -1443,9 +1330,9 @@
1443 1330 }
1444 1331
1445 1332 $copy_table_id = TablePress::$model_table->copy( $table_id );
1446 1333 if ( is_wp_error( $copy_table_id ) ) {
1447 - TablePress::redirect( array( 'action' => $return, 'message' => 'error_copy', 'table_id' => $return_item ) );
1334 + TablePress::redirect( array( 'action' => $return, 'message' => 'error_copy', 'table_id' => $return_item, 'error_details' => TablePress::get_wp_error_string( $copy_table_id ) ) );
1448 1335 }
1449 1336 $return_item = $copy_table_id;
1450 1337
1451 1338 /*
@@ -1467,9 +1354,9 @@
1467 1354 * Preview a table.
1468 1355 *
1469 1356 * @since 1.0.0
1470 1357 */
1471 - public function handle_get_action_preview_table() {
1358 + public function handle_get_action_preview_table(): void {
1472 1359 $table_id = ( ! empty( $_GET['item'] ) ) ? $_GET['item'] : false;
1473 1360 TablePress::check_nonce( 'preview_table', $table_id );
1474 1361
1475 1362 // Nonce check should actually catch this already.
@@ -1500,18 +1387,22 @@
1500 1387 $default_render_options = apply_filters( 'tablepress_shortcode_table_default_shortcode_atts', $default_render_options );
1501 1388 $render_options = shortcode_atts( $default_render_options, $table['options'] );
1502 1389 /** This filter is documented in controllers/controller-frontend.php */
1503 1390 $render_options = apply_filters( 'tablepress_shortcode_table_shortcode_atts', $render_options );
1391 + $render_options['html_id'] = "tablepress-{$table['id']}";
1392 + $render_options['block_preview'] = true;
1504 1393 $_render->set_input( $table, $render_options );
1505 1394 $view_data = array(
1506 - 'table_id' => $table_id,
1507 - 'head_html' => $_render->get_preview_css(),
1508 - 'body_html' => $_render->get_output(),
1395 + 'table_id' => $table_id,
1396 + 'head_html' => $_render->get_preview_css(),
1397 + 'body_html' => $_render->get_output( 'html' ),
1398 + 'site_uses_block_editor' => TablePress::site_uses_block_editor(),
1509 1399 );
1510 1400
1511 1401 $custom_css = TablePress::$model_options->get( 'custom_css' );
1512 - if ( ! empty( $custom_css ) ) {
1513 - $view_data['head_html'] .= "<style type=\"text/css\">\n{$custom_css}\n</style>\n";
1402 + $use_custom_css = ( TablePress::$model_options->get( 'use_custom_css' ) && '' !== $custom_css );
1403 + if ( $use_custom_css ) {
1404 + $view_data['head_html'] .= "<style>\n{$custom_css}\n</style>\n";
1514 1405 }
1515 1406
1516 1407 // Prepare, initialize, and render the view.
1517 1408 $this->view = TablePress::load_view( 'preview_table', $view_data );
@@ -1518,13 +1409,13 @@
1518 1409 $this->view->render();
1519 1410 }
1520 1411
1521 1412 /**
1522 - * Show a list of tables in the Editor toolbar Thickbox (opened by TinyMCE or Quicktags button).
1413 + * Shows a list of tables in the Editor toolbar Thickbox (opened by TinyMCE or Quicktags button).
1523 1414 *
1524 1415 * @since 1.0.0
1525 1416 */
1526 - public function handle_get_action_editor_button_thickbox() {
1417 + public function handle_get_action_editor_button_thickbox(): void {
1527 1418 TablePress::check_nonce( 'editor_button_thickbox' );
1528 1419
1529 1420 if ( ! current_user_can( 'tablepress_list_tables' ) ) {
1530 1421 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
@@ -1546,9 +1437,9 @@
1546 1437 * Uninstall TablePress, and delete all tables and options.
1547 1438 *
1548 1439 * @since 1.0.0
1549 1440 */
1550 - public function handle_get_action_uninstall_tablepress() {
1441 + public function handle_get_action_uninstall_tablepress(): void {
1551 1442 TablePress::check_nonce( 'uninstall_tablepress' );
1552 1443
1553 1444 $plugin = TABLEPRESS_BASENAME;
1554 1445
@@ -1568,9 +1459,9 @@
1568 1459
1569 1460 TablePress::$model_table->destroy();
1570 1461 TablePress::$model_options->destroy();
1571 1462
1572 - $output = '<strong>' . __( 'TablePress was uninstalled successfully.', 'tablepress' ) . '</strong><br /><br />';
1463 + $output = '<strong>' . __( 'TablePress was uninstalled successfully.', 'tablepress' ) . '</strong><br><br>';
1573 1464 $output .= __( 'All tables, data, and options were deleted.', 'tablepress' );
1574 1465 if ( is_multisite() ) {
1575 1466 $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' );
1576 1467 } else {
@@ -1577,9 +1468,9 @@
1577 1468 $output .= ' ' . __( 'You may now manually delete the plugin&#8217;s folder <code>tablepress</code> from the <code>plugins</code> directory on your server or use the &#8220;Delete&#8221; link for TablePress on the WordPress &#8220;Plugins&#8221; page.', 'tablepress' );
1578 1469 }
1579 1470 if ( $css_files_deleted ) {
1580 1471 $output .= ' ' . __( 'Your TablePress &#8220;Custom CSS&#8221; files have been deleted automatically.', 'tablepress' );
1581 - } else {
1472 + } else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found
1582 1473 if ( is_multisite() ) {
1583 1474 $output .= ' ' . __( 'Please also ask him to delete your TablePress &#8220;Custom CSS&#8221; files from the server.', 'tablepress' );
1584 1475 } else {
1585 1476 $output .= ' ' . __( 'You may now also delete your TablePress &#8220;Custom CSS&#8221; files in the <code>wp-content</code> folder.', 'tablepress' );