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