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