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