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 +301 -155 2.1.8 → 3.4 View file →
@@ -7,8 +7,10 @@
7 7 * @author Tobias Bäthge
8 8 * @since 1.0.0
9 9 */
10 10
11 +declare(strict_types=1);
12 +
11 13 // Prohibit direct script loading.
12 14 defined( 'ABSPATH' ) || die( 'No direct script access allowed!' );
13 15
14 16 /**
@@ -25,27 +27,26 @@
25 27 * Page hooks (i.e. names) WordPress uses for the TablePress admin screens,
26 28 * populated in add_admin_menu_entry().
27 29 *
28 30 * @since 1.0.0
29 - * @var array
31 + * @var string[]
30 32 */
31 - protected $page_hooks = array();
33 + protected array $page_hooks = array();
32 34
33 35 /**
34 36 * Actions that have a view and admin menu or nav tab menu entry.
35 37 *
36 38 * @since 1.0.0
37 - * @var array
39 + * @var array<string, array<string, bool|string>>
38 40 */
39 - protected $view_actions = array();
41 + protected array $view_actions = array();
40 42
41 43 /**
42 44 * Instance of the TablePress Admin View that is rendered.
43 45 *
44 46 * @since 1.0.0
45 - * @var TablePress_View
46 47 */
47 - protected $view;
48 + protected \TablePress_View $view;
48 49
49 50 /**
50 51 * Initialize the Admin Controller, determine location the admin menu, set up actions.
51 52 *
@@ -59,9 +60,10 @@
59 60
60 61 add_action( 'admin_menu', array( $this, 'add_admin_menu_entry' ) );
61 62 add_action( 'admin_init', array( $this, 'add_admin_actions' ) );
62 63
63 - add_action( 'enqueue_block_editor_assets', array( $this, 'add_block_editor_js' ) );
64 + add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_block_editor_assets' ) );
65 + add_action( 'enqueue_block_assets', array( $this, 'enqueue_block_assets' ) );
64 66 }
65 67
66 68 /**
67 69 * Handler for changing the number of shown tables in the list of tables (via WP List Table class).
@@ -70,11 +72,11 @@
70 72 *
71 73 * @param mixed $screen_option Current value of the filter (probably bool false).
72 74 * @param string $option Option in which the setting is stored.
73 75 * @param int $value Current value of the setting.
74 - * @return bool|int False to not save the changed setting, or the int value to be saved.
76 + * @return int Changed value of the setting
75 77 */
76 - public function save_list_tables_screen_option( $screen_option, $option, $value ) {
78 + public function save_list_tables_screen_option( /* mixed */ $screen_option, string $option, int $value ): int {
77 79 return $value;
78 80 }
79 81
80 82 /**
@@ -81,9 +83,9 @@
81 83 * Add admin screens to the correct place in the admin menu.
82 84 *
83 85 * @since 1.0.0
84 86 */
85 - public function add_admin_menu_entry() {
87 + public function add_admin_menu_entry(): void {
86 88 // Callback for all menu entries.
87 89 $callback = array( $this, 'show_admin_page' );
88 90 /**
89 91 * Filters the TablePress admin menu entry name.
@@ -93,26 +95,34 @@
93 95 * @param string $entry_name The admin menu entry name. Default "TablePress".
94 96 */
95 97 $admin_menu_entry_name = apply_filters( 'tablepress_admin_menu_entry_name', 'TablePress' );
96 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 +
97 103 $this->init_view_actions();
98 104 $min_access_cap = $this->view_actions['list']['required_cap'];
99 105
100 - if ( $this->is_top_level_page ) {
101 - $icon_url = 'dashicons-list-view';
102 - 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 ) {
103 109 case 'top':
104 110 $position = 3; // Position of Dashboard + 1.
105 111 break;
106 112 case 'bottom':
107 - $position = ( ++$GLOBALS['_wp_last_utility_menu'] );
113 + $position = isset( $GLOBALS['_wp_last_utility_menu'] ) ? ++$GLOBALS['_wp_last_utility_menu'] : 80;
108 114 break;
109 115 case 'middle':
110 116 default:
111 - $position = ( ++$GLOBALS['_wp_last_object_menu'] );
117 + $position = isset( $GLOBALS['_wp_last_object_menu'] ) ? ++$GLOBALS['_wp_last_object_menu'] : 25;
112 118 break;
113 119 }
114 - 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
115 125 foreach ( $this->view_actions as $action => $entry ) {
116 126 if ( ! $entry['show_entry'] ) {
117 127 continue;
118 128 }
@@ -119,12 +129,20 @@
119 129 $slug = 'tablepress';
120 130 if ( 'list' !== $action ) {
121 131 $slug .= '_' . $action;
122 132 }
123 - $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 + }
124 138 }
125 139 } else {
126 - $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 + }
127 145 }
128 146 }
129 147
130 148 /**
@@ -131,9 +149,9 @@
131 149 * Set up handlers for user actions in the backend that exceed plain viewing.
132 150 *
133 151 * @since 1.0.0
134 152 */
135 - public function add_admin_actions() {
153 + public function add_admin_actions(): void {
136 154 // Register the callbacks for processing action requests.
137 155 $post_actions = array( 'list', 'add', 'options', 'export', 'import' );
138 156 $get_actions = array( 'hide_message', 'delete_table', 'copy_table', 'preview_table', 'editor_button_thickbox', 'uninstall_tablepress' );
139 157 foreach ( $post_actions as $action ) {
@@ -166,22 +184,16 @@
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' ) );
170 -
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 );
175 188 }
176 189
177 190 /**
178 - * Loads additional JavaScript code for the TablePress table block.
191 + * Loads additional JavaScript code for the TablePress table block (in the block editor context).
179 192 *
180 - * @since 2.0.0
193 + * @since 2.2.0
181 194 */
182 - public function add_block_editor_js() {
183 - // Add table information for the Block Editor to the page.
195 + public function enqueue_block_editor_assets(): void {
184 196 $handle = generate_block_asset_handle( 'tablepress/table', 'editorScript' );
185 197 $data = $this->get_block_editor_data();
186 198 wp_add_inline_script( $handle, $data, 'before' );
187 199 }
@@ -186,8 +198,20 @@
186 198 wp_add_inline_script( $handle, $data, 'before' );
187 199 }
188 200
189 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 + /**
190 214 * Gets the inline data that is referenced by the Block Editor JavaScript code for the TablePress blocks.
191 215 *
192 216 * @since 2.0.0
193 217 *
@@ -192,9 +216,9 @@
192 216 * @since 2.0.0
193 217 *
194 218 * @return string JavaScript code for the Block Editor.
195 219 */
196 - protected function get_block_editor_data() {
220 + protected function get_block_editor_data(): string {
197 221 $tables = array();
198 222 // Load all table IDs without priming the post meta cache, as table options/visibility are not needed.
199 223 $table_ids = TablePress::$model_table->load_all( false );
200 224 foreach ( $table_ids as $table_id ) {
@@ -199,8 +223,14 @@
199 223 $table_ids = TablePress::$model_table->load_all( false );
200 224 foreach ( $table_ids as $table_id ) {
201 225 // Load table, without table data, options, and visibility settings.
202 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 +
203 233 if ( '' === trim( $table['name'] ) ) {
204 234 $table['name'] = __( '(no name)', 'tablepress' );
205 235 }
206 236 $tables[ $table_id ] = esc_html( $table['name'] );
@@ -210,22 +240,30 @@
210 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.
211 241 *
212 242 * @since 2.0.0
213 243 *
214 - * @param array $tables List of table names, the table ID is the array key.
244 + * @param array<string, string> $tables List of table names, the table ID is the array key.
215 245 */
216 246 $tables = apply_filters( 'tablepress_block_editor_tables_list', $tables );
217 247
218 - $tables = wp_json_encode( $tables, TABLEPRESS_JSON_OPTIONS );
219 - // Print them inside a `JSON.parse()` call in JS for speed gains, with necessary escaping of `</script>`, `'`, and `\`.
220 - $tables = str_replace( array( '</script>', '\\', "'" ), array( '<\/script>', '\\\\', "\'" ), $tables );
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 );
221 255
222 256 $shortcode = esc_js( TablePress::$shortcode );
223 257
224 258 $template = TablePress::$model_table->get_table_template();
225 - $template = wp_json_encode( $template['options'], TABLEPRESS_JSON_OPTIONS );
226 - // Print them inside a `JSON.parse()` call in JS for speed gains, with necessary escaping of `</script>`, `'`, and `\`.
227 - $template = str_replace( array( '</script>', '\\', "'" ), array( '<\/script>', '\\\\', "\'" ), $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 );
228 266
229 267 /**
230 268 * Filters whether the table block preview should be loaded via a <ServerSideRender> in the block editor.
231 269 *
@@ -241,17 +279,17 @@
241 279 $url = TablePress::url( array( 'action' => 'list' ) );
242 280 }
243 281
244 282 return <<<JS
245 -// Ensure the global `tp` object exists.
246 -window.tp = window.tp || {};
247 -tp.url = '{$url}';
248 -tp.load_block_preview = {$load_block_preview};
249 -tp.table = {};
250 -tp.table.shortcode = '{$shortcode}';
251 -tp.table.template = JSON.parse( '{$template}' );
252 -tp.tables = JSON.parse( '{$tables}' );
253 -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;
254 292 }
255 293
256 294 /**
257 295 * Register actions to add "Table" button to "HTML editor" and "Visual editor" toolbars.
@@ -257,21 +295,20 @@
257 295 * Register actions to add "Table" button to "HTML editor" and "Visual editor" toolbars.
258 296 *
259 297 * @since 1.0.0
260 298 */
261 - public function add_editor_buttons() {
299 + public function add_editor_buttons(): void {
262 300 if ( ! current_user_can( 'tablepress_list_tables' ) ) {
263 301 return;
264 302 }
265 303
266 304 // Only load the toolbar integration if the Block Editor is not used.
267 - if ( TablePress::site_uses_block_editor() ) {
305 + if ( 'block' === TablePress::site_used_editor() ) {
268 306 return;
269 307 }
270 308
271 309 add_thickbox(); // The files are usually already loaded by media upload functions.
272 - $admin_page = TablePress::load_class( 'TablePress_Admin_Page', 'class-admin-page-helper.php', 'classes' );
273 - $admin_page->enqueue_script(
310 + TablePress::enqueue_script(
274 311 'quicktags-button',
275 312 array( 'quicktags', 'media-upload' ),
276 313 array(
277 314 'editor_button' => array(
@@ -279,9 +316,9 @@
279 316 'title' => __( 'Insert a TablePress table', 'tablepress' ),
280 317 'thickbox_title' => __( 'Insert a TablePress table', 'tablepress' ),
281 318 'thickbox_url' => TablePress::url( array( 'action' => 'editor_button_thickbox' ), true, 'admin-post.php' ),
282 319 ),
283 - )
320 + ),
284 321 );
285 322
286 323 // TinyMCE integration.
287 324 if ( user_can_richedit() ) {
@@ -294,12 +331,12 @@
294 331 * Adds the "Table" button to the TinyMCE toolbar.
295 332 *
296 333 * @since 1.0.0
297 334 *
298 - * @param array $buttons Current set of buttons in the TinyMCE toolbar.
299 - * @return array Extended set of buttons in the TinyMCE toolbar, including the "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.
300 337 */
301 - public function add_tinymce_button( array $buttons ) {
338 + public function add_tinymce_button( array $buttons ): array {
302 339 $buttons[] = 'tablepress_insert_table';
303 340 return $buttons;
304 341 }
305 342
@@ -307,12 +344,12 @@
307 344 * Registers the "Table" button plugin for the TinyMCE editor.
308 345 *
309 346 * @since 1.0.0
310 347 *
311 - * @param array $plugins Current set of registered TinyMCE plugins.
312 - * @return array Extended set of registered TinyMCE plugins, including the "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.
313 350 */
314 - public function add_tinymce_plugin( array $plugins ) {
351 + public function add_tinymce_plugin( array $plugins ): array {
315 352 $plugins['tablepress_tinymce'] = plugins_url( 'admin/js/build/tinymce-button.js', TABLEPRESS__FILE__ );
316 353 return $plugins;
317 354 }
318 355
@@ -322,9 +359,9 @@
322 359 * @since 1.0.0
323 360 *
324 361 * @param WP_Admin_Bar $wp_admin_bar The current WP Admin Bar object.
325 362 */
326 - 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 {
327 364 if ( ! current_user_can( 'tablepress_add_tables' ) ) {
328 365 return;
329 366 }
330 367
@@ -335,9 +372,9 @@
335 372
336 373 $wp_admin_bar->add_menu( array(
337 374 'parent' => 'new-content',
338 375 'id' => 'new-tablepress-table',
339 - 'title' => __( 'TablePress Table', 'tablepress' ),
376 + 'title' => __( 'TablePress table', 'tablepress' ),
340 377 'href' => TablePress::url( array( 'action' => 'add' ) ),
341 378 ) );
342 379 }
343 380
@@ -345,12 +382,25 @@
345 382 * Handle actions for loading of Plugins page.
346 383 *
347 384 * @since 1.0.0
348 385 */
349 - public function plugins_page() {
386 + public function plugins_page(): void {
350 387 // Add additional links on Plugins page.
351 388 add_filter( 'plugin_action_links_' . TABLEPRESS_BASENAME, array( $this, 'add_plugin_action_links' ) );
352 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 + }
353 403 }
354 404
355 405 /**
356 406 * Add links to the TablePress entry in the "Plugin" column on the Plugins page.
@@ -356,14 +406,14 @@
356 406 * Add links to the TablePress entry in the "Plugin" column on the Plugins page.
357 407 *
358 408 * @since 1.0.0
359 409 *
360 - * @param array $links List of links to print in the "Plugin" column on the Plugins page.
361 - * @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.
362 412 */
363 - public function add_plugin_action_links( array $links ) {
413 + public function add_plugin_action_links( array $links ): array {
364 414 if ( current_user_can( 'tablepress_list_tables' ) ) {
365 - $links[] = '<a href="' . TablePress::url() . '">' . __( 'Plugin page', 'tablepress' ) . '</a>';
415 + $links[] = '<a href="' . esc_url( TablePress::url() ) . '">' . __( 'Plugin page', 'tablepress' ) . '</a>';
366 416 }
367 417 return $links;
368 418 }
369 419
@@ -371,19 +421,19 @@
371 421 * Add links to the TablePress entry in the "Description" column on the Plugins page.
372 422 *
373 423 * @since 1.0.0
374 424 *
375 - * @param array $links List of links to print in the "Description" column on the Plugins page.
376 - * @param string $file Name of the plugin.
377 - * @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.
378 428 */
379 - public function add_plugin_row_meta( array $links, $file ) {
429 + public function add_plugin_row_meta( array $links, string $file ): array {
380 430 if ( TABLEPRESS_BASENAME === $file ) {
381 431 $links[] = '<a href="https://tablepress.org/faq/" title="' . esc_attr__( 'Frequently Asked Questions', 'tablepress' ) . '">' . __( 'FAQ', 'tablepress' ) . '</a>';
382 432 $links[] = '<a href="https://tablepress.org/documentation/">' . __( 'Documentation', 'tablepress' ) . '</a>';
383 433 $links[] = '<a href="https://tablepress.org/support/">' . __( 'Support', 'tablepress' ) . '</a>';
384 - if ( tb_tp_fs()->is_free_plan() ) {
385 - $links[] = '<a href="' . 'https://tablepress.org/premium/' . '" title="' . esc_attr__( 'Check out the Premium version of TablePress!', 'tablepress' ) . '"><strong>' . __( 'Go Premium', '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>';
386 436 }
387 437 }
388 438 return $links;
389 439 }
@@ -388,16 +438,62 @@
388 438 return $links;
389 439 }
390 440
391 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 + /**
392 488 * Prepare the rendering of an admin screen, by determining the current action, loading necessary data and initializing the view.
393 489 *
394 490 * @since 1.0.0
395 491 */
396 - public function load_admin_page() {
492 + public function load_admin_page(): void {
397 493 // Determine the action from either the GET parameter (for sub-menu entries, and the main admin menu entry).
398 494 $action = ( ! empty( $_GET['action'] ) ) ? $_GET['action'] : 'list'; // Default action is list.
399 - if ( $this->is_top_level_page ) {
495 + if ( TablePress::$controller->is_top_level_page ) {
400 496 // Or, for sub-menu entry of an admin menu "TablePress" entry, get it from the "page" GET parameter.
401 497 if ( 'tablepress' !== $_GET['page'] ) {
402 498 // Actions that are top-level entries, but don't have an action GET parameter (action is after last _ in string).
403 499 $action = substr( $_GET['page'], 11 ); // $_GET['page'] has the format 'tablepress_{$action}'
@@ -404,9 +500,9 @@
404 500 }
405 501 }
406 502
407 503 // Check if action is a supported action, and whether the user is allowed to access this screen.
408 - 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.)
409 505 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
410 506 }
411 507
412 508 // Don't load TablePress assets on the Freemius opt-in/activation screen.
@@ -421,34 +517,33 @@
421 517 * Set the `$typenow` global to the current CPT ourselves, as `WP_Screen::get()` does not determine the CPT correctly.
422 518 * This is necessary as the WP Admin Menu can otherwise highlight wrong entries, see https://github.com/TablePress/TablePress/issues/24.
423 519 */
424 520 if ( isset( $_GET['post_type'] ) && post_type_exists( $_GET['post_type'] ) ) {
425 - $GLOBALS['typenow'] = $_GET['post_type'];
521 + $GLOBALS['typenow'] = $_GET['post_type']; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
426 522 }
427 523
428 524 // Pre-define some view data.
429 525 $data = array(
430 - 'view_actions' => $this->view_actions,
431 - 'message' => ( ! empty( $_GET['message'] ) ) ? $_GET['message'] : false,
432 - 'error_details' => ( ! empty( $_GET['error_details'] ) ) ? $_GET['error_details'] : '',
433 - 'site_uses_block_editor' => TablePress::site_uses_block_editor(),
526 + 'view_actions' => $this->view_actions,
527 + 'message' => ( ! empty( $_GET['message'] ) ) ? $_GET['message'] : false,
528 + 'error_details' => ( ! empty( $_GET['error_details'] ) ) ? rawurldecode( wp_unslash( $_GET['error_details'] ) ) : '',
529 + 'site_used_editor' => TablePress::site_used_editor(),
434 530 );
435 531
436 532 // Depending on the action, load more necessary data for the corresponding view.
437 533 switch ( $action ) {
438 534 case 'list':
439 - $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;
440 536 // Prime the post meta cache for cached loading of last_editor.
441 537 $data['table_ids'] = TablePress::$model_table->load_all( true );
442 - $data['messages']['first_visit'] = TablePress::$model_options->get( 'message_first_visit' );
443 - $data['messages']['plugin_update_message'] = TablePress::$model_options->get( 'message_plugin_update' );
444 - $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' );
445 542 $data['table_count'] = count( $data['table_ids'] );
446 543 break;
447 544 case 'about':
448 545 $data['first_activation'] = TablePress::$model_options->get( 'first_activation' );
449 - $exporter = TablePress::load_class( 'TablePress_Export', 'class-export.php', 'classes' );
450 - $data['zip_support_available'] = $exporter->zip_support_available;
451 546 break;
452 547 case 'options':
453 548 /*
454 549 * Maybe try saving "Custom CSS" to a file:
@@ -455,9 +550,9 @@
455 550 * (called here, as the credentials form posts to this handler again, due to how `request_filesystem_credentials()` works)
456 551 */
457 552 if ( isset( $_GET['item'] ) && 'save_custom_css' === $_GET['item'] ) {
458 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.
459 - $action = 'options_custom_css'; // to load a different view
554 + $action = 'options_custom_css'; // To load a different view.
460 555 // Try saving "Custom CSS" to a file, otherwise this gets the HTML for the credentials form.
461 556 $tablepress_css = TablePress::load_class( 'TablePress_CSS', 'class-css.php', 'classes' );
462 557 $result = $tablepress_css->save_custom_css_to_file_plugin_options( TablePress::$model_options->get( 'custom_css' ), TablePress::$model_options->get( 'custom_css_minified' ) );
463 558 if ( is_string( $result ) ) {
@@ -478,12 +573,12 @@
478 573 break;
479 574 }
480 575 $data['frontend_options']['use_custom_css'] = TablePress::$model_options->get( 'use_custom_css' );
481 576 $data['frontend_options']['custom_css'] = TablePress::$model_options->get( 'custom_css' );
482 - $data['user_options']['parent_page'] = $this->parent_page;
577 + $data['user_options']['parent_page'] = TablePress::$controller->parent_page;
483 578 break;
484 579 case 'edit':
485 - if ( empty( $_GET['table_id'] ) ) {
580 + if ( ! isset( $_GET['table_id'] ) || ! preg_match( '/^[a-zA-Z0-9_-]+$/', $_GET['table_id'] ) ) {
486 581 TablePress::redirect( array( 'action' => 'list', 'message' => 'error_no_table' ) );
487 582 }
488 583 // Load table, with table data, options, and visibility settings.
489 584 $data['table'] = TablePress::$model_table->load( $_GET['table_id'], true, true );
@@ -495,35 +590,60 @@
495 590 }
496 591 break;
497 592 case 'export':
498 593 // Load all table IDs without priming the post meta cache, as table options/visibility are not needed.
499 - $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 + }
500 610 $data['tables_count'] = TablePress::$model_table->count_tables();
501 - if ( ! empty( $_GET['table_id'] ) ) {
502 - $data['export_ids'] = explode( ',', $_GET['table_id'] );
503 - } else {
504 - // Just show empty export form.
505 - $data['export_ids'] = array();
506 - }
611 + $data['export_ids'] = ( isset( $_GET['table_id'] ) && preg_match( '/^[,a-zA-Z0-9_-]+$/', $_GET['table_id'] ) ) ? explode( ',', $_GET['table_id'] ) : array();
507 612 $exporter = TablePress::load_class( 'TablePress_Export', 'class-export.php', 'classes' );
508 613 $data['zip_support_available'] = $exporter->zip_support_available;
509 614 $data['export_formats'] = $exporter->export_formats;
510 615 $data['csv_delimiters'] = $exporter->csv_delimiters;
511 - $data['export_format'] = ( ! empty( $_GET['export_format'] ) ) ? $_GET['export_format'] : false;
616 + $data['export_format'] = ( ! empty( $_GET['export_format'] ) ) ? $_GET['export_format'] : 'csv';
512 617 $data['csv_delimiter'] = ( ! empty( $_GET['csv_delimiter'] ) ) ? $_GET['csv_delimiter'] : _x( ',', 'Default CSV delimiter in the translated language (";", ",", or "tab")', 'tablepress' );
513 618 break;
514 619 case 'import':
515 620 // Load all table IDs without priming the post meta cache, as table options/visibility are not needed.
516 - $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.
517 638 $data['tables_count'] = TablePress::$model_table->count_tables();
518 639 $importer = TablePress::load_class( 'TablePress_Import', 'class-import.php', 'classes' );
519 - $data['zip_support_available'] = $importer->zip_support_available;
520 640 $data['import_type'] = ( ! empty( $_GET['import_type'] ) ) ? $_GET['import_type'] : 'add';
521 - $data['import_existing_table'] = ( ! empty( $_GET['import_existing_table'] ) ) ? $_GET['import_existing_table'] : '';
641 + $data['import_existing_table'] = $_GET['import_existing_table'] ?? '';
522 642 $data['import_source'] = ( ! empty( $_GET['import_source'] ) ) ? $_GET['import_source'] : 'file-upload';
523 - $data['import_url'] = ( ! empty( $_GET['import_url'] ) ) ? wp_unslash( $_GET['import_url'] ) : 'https://';
524 - $data['import_server'] = ( ! empty( $_GET['import_server'] ) ) ? wp_unslash( $_GET['import_server'] ) : ABSPATH;
525 - $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'] ) ) : '';
526 646 $data['legacy_import'] = ( ! empty( $_GET['legacy_import'] ) ) ? $_GET['legacy_import'] : 'false';
527 647 break;
528 648 }
529 649
@@ -531,10 +651,10 @@
531 651 * Filters the data that is passed to the current TablePress View.
532 652 *
533 653 * @since 1.0.0
534 654 *
535 - * @param array $data Data for the view.
536 - * @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.
537 657 */
538 658 $data = apply_filters( 'tablepress_view_data', $data, $action );
539 659
540 660 // Prepare and initialize the view.
@@ -545,9 +665,9 @@
545 665 * Render the view that has been initialized in load_admin_page() (called by WordPress when the actual page content is needed).
546 666 *
547 667 * @since 1.0.0
548 668 */
549 - public function show_admin_page() {
669 + public function show_admin_page(): void {
550 670 $this->view->render();
551 671 }
552 672
553 673 /**
@@ -556,9 +676,9 @@
556 676 * @since 1.0.0
557 677 *
558 678 * @return bool Whether the message shall be shown on the "All Tables" screen.
559 679 */
560 - protected function maybe_show_donation_message() {
680 + protected function maybe_show_donation_message(): bool {
561 681 // Only show the message to plugin admins.
562 682 if ( ! current_user_can( 'tablepress_edit_options' ) ) {
563 683 return false;
564 684 }
@@ -576,9 +696,9 @@
576 696 * Init list of actions that have a view with their titles/names/caps.
577 697 *
578 698 * @since 1.0.0
579 699 */
580 - protected function init_view_actions() {
700 + protected function init_view_actions(): void {
581 701 $this->view_actions = array(
582 702 'list' => array(
583 703 'show_entry' => true,
584 704 'page_title' => __( 'All Tables', 'tablepress' ),
@@ -634,9 +754,9 @@
634 754 * Filters the available TablePres Views/Actions and their parameters.
635 755 *
636 756 * @since 1.0.0
637 757 *
638 - * @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.
639 759 */
640 760 $this->view_actions = apply_filters( 'tablepress_admin_view_actions', $this->view_actions );
641 761 }
642 762
@@ -648,9 +768,9 @@
648 768 * Handle Bulk Actions (Copy, Export, Delete) on "All Tables" list screen.
649 769 *
650 770 * @since 1.0.0
651 771 */
652 - public function handle_post_action_list() {
772 + public function handle_post_action_list(): void {
653 773 TablePress::check_nonce( 'list' );
654 774
655 775 if ( isset( $_POST['bulk-action-selector-top'] ) && '-1' !== $_POST['bulk-action-selector-top'] ) {
656 776 $bulk_action = $_POST['bulk-action-selector-top'];
@@ -691,9 +811,9 @@
691 811 * To export, redirect to "Export" screen, with selected table IDs.
692 812 */
693 813 $table_ids = implode( ',', $tables );
694 814 TablePress::redirect( array( 'action' => 'export', 'table_id' => $table_ids ) );
695 - break;
815 + // break; // unreachable.
696 816 case 'delete':
697 817 foreach ( $tables as $table_id ) {
698 818 if ( current_user_can( 'tablepress_delete_table', $table_id ) ) {
699 819 $deleted = TablePress::$model_table->delete( $table_id );
@@ -706,9 +826,9 @@
706 826 }
707 827 break;
708 828 }
709 829
710 - 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?
711 831 $message = "error_{$bulk_action}_not_all_tables";
712 832 } else {
713 833 $plural = ( count( $tables ) > 1 ) ? '_plural' : '';
714 834 $message = "success_{$bulk_action}{$plural}";
@@ -733,9 +853,9 @@
733 853 * Add a table, according to the parameters on the "Add new Table" screen.
734 854 *
735 855 * @since 1.0.0
736 856 */
737 - public function handle_post_action_add() {
857 + public function handle_post_action_add(): void {
738 858 TablePress::check_nonce( 'add' );
739 859
740 860 if ( ! current_user_can( 'tablepress_add_tables' ) ) {
741 861 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
@@ -746,11 +866,11 @@
746 866 }
747 867
748 868 $add_table = wp_unslash( $_POST['table'] );
749 869
750 - // Perform sanity checks of posted data.
751 - $name = ( isset( $add_table['name'] ) ) ? $add_table['name'] : '';
752 - $description = ( isset( $add_table['description'] ) ) ? $add_table['description'] : '';
870 + // Perform confidence checks of posted data.
871 + $name = $add_table['name'] ?? '';
872 + $description = $add_table['description'] ?? '';
753 873 if ( ! isset( $add_table['rows'], $add_table['columns'] ) ) {
754 874 TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add', 'error_details' => 'The HTTP POST data does not contain the table size.' ) );
755 875 }
756 876
@@ -789,9 +909,9 @@
789 909 * Save changed "Plugin Options".
790 910 *
791 911 * @since 1.0.0
792 912 */
793 - public function handle_post_action_options() {
913 + public function handle_post_action_options(): void {
794 914 TablePress::check_nonce( 'options' );
795 915
796 916 if ( ! current_user_can( 'tablepress_access_options_screen' ) ) {
797 917 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
@@ -810,10 +930,10 @@
810 930 if ( ! empty( $posted_options['admin_menu_parent_page'] ) && '-' !== $posted_options['admin_menu_parent_page'] ) {
811 931 $new_options['admin_menu_parent_page'] = $posted_options['admin_menu_parent_page'];
812 932 // Re-init parent information, as `TablePress::redirect()` URL might be wrong otherwise.
813 933 /** This filter is documented in classes/class-controller.php */
814 - $this->parent_page = apply_filters( 'tablepress_admin_menu_parent_page', $posted_options['admin_menu_parent_page'] );
815 - $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 );
816 936 }
817 937
818 938 // Custom CSS can only be saved if the user is allowed to do so.
819 939 $update_custom_css_files = false;
@@ -824,13 +944,20 @@
824 944 if ( isset( $posted_options['custom_css'] ) ) {
825 945 $new_options['custom_css'] = $posted_options['custom_css'];
826 946
827 947 $tablepress_css = TablePress::load_class( 'TablePress_CSS', 'class-css.php', 'classes' );
828 - // Sanitize and tidy up Custom CSS.
829 - $new_options['custom_css'] = $tablepress_css->sanitize_css( $new_options['custom_css'] );
830 - // Minify Custom CSS.
831 - $new_options['custom_css_minified'] = $tablepress_css->minify_css( $new_options['custom_css'] );
832 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 +
833 960 // Maybe update CSS files as well.
834 961 $custom_css_file_contents = $tablepress_css->load_custom_css_from_file( 'normal' );
835 962 if ( false === $custom_css_file_contents ) {
836 963 $custom_css_file_contents = '';
@@ -861,9 +988,9 @@
861 988 * Export selected tables.
862 989 *
863 990 * @since 1.0.0
864 991 */
865 - public function handle_post_action_export() {
992 + public function handle_post_action_export(): void {
866 993 TablePress::check_nonce( 'export' );
867 994
868 995 if ( ! current_user_can( 'tablepress_export_tables' ) ) {
869 996 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
@@ -874,19 +1001,19 @@
874 1001 }
875 1002
876 1003 $export = wp_unslash( $_POST['export'] );
877 1004
878 - if ( empty( $export['tables'] ) ) {
1005 + if ( empty( $export['tables_list'] ) ) {
879 1006 TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export', 'error_details' => 'The HTTP POST data does not contain tables.' ) );
880 1007 }
881 1008
1009 + /** @var TablePress_Export $exporter */ // phpcs:ignore Generic.Commenting.DocComment.MissingShort
882 1010 $exporter = TablePress::load_class( 'TablePress_Export', 'class-export.php', 'classes' );
883 1011
884 1012 if ( empty( $export['format'] ) || ! isset( $exporter->export_formats[ $export['format'] ] ) ) {
885 1013 TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export', 'error_details' => 'The export format is invalid.' ) );
886 1014 }
887 - if ( empty( $export['csv_delimiter'] ) ) {
888 - // Set a value, so that the variable exists.
1015 + if ( ! isset( $export['csv_delimiter'] ) ) {
889 1016 $export['csv_delimiter'] = '';
890 1017 }
891 1018 if ( 'csv' === $export['format'] && ! isset( $exporter->csv_delimiters[ $export['csv_delimiter'] ] ) ) {
892 1019 TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export', 'error_details' => 'The CSV delimiter is invalid.' ) );
@@ -891,10 +1018,9 @@
891 1018 if ( 'csv' === $export['format'] && ! isset( $exporter->csv_delimiters[ $export['csv_delimiter'] ] ) ) {
892 1019 TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export', 'error_details' => 'The CSV delimiter is invalid.' ) );
893 1020 }
894 1021
895 - // Use list of tables from concatenated field if available (as that's hopefully not truncated by Suhosin, which is possible for $export['tables']).
896 - $tables = ( ! empty( $export['tables_list'] ) ) ? explode( ',', $export['tables_list'] ) : $export['tables'];
1022 + $tables = explode( ',', $export['tables_list'] );
897 1023
898 1024 // Determine if ZIP file support is available.
899 1025 if ( $exporter->zip_support_available
900 1026 && ( ( isset( $export['zip_file'] ) && 'true' === $export['zip_file'] ) || count( $tables ) > 1 ) ) {
@@ -904,9 +1030,9 @@
904 1030 $export_to_zip = false;
905 1031 }
906 1032
907 1033 if ( ! $export_to_zip ) {
908 - // 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.
909 1035 if ( ! current_user_can( 'tablepress_export_table', $tables[0] ) ) {
910 1036 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
911 1037 }
912 1038 // Load table, with table data, options, and visibility settings.
@@ -931,18 +1057,22 @@
931 1057 */
932 1058 $download_filename = apply_filters( 'tablepress_export_filename', $download_filename, $table['id'], $table['name'], $export['format'], $export_to_zip );
933 1059 $download_filename = sanitize_file_name( $download_filename );
934 1060 // Export the table.
935 - $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 );
936 1066 /**
937 1067 * Filters the exported table data.
938 1068 *
939 1069 * @since 1.6.0
940 1070 *
941 - * @param string $export_data The exported table data.
942 - * @param array $table Table to be exported.
943 - * @param string $export_format Format for the export ('csv', 'html', 'json').
944 - * @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.
945 1075 */
946 1076 $export_data = apply_filters( 'tablepress_export_data', $export_data, $table, $export['format'], $export['csv_delimiter'] );
947 1077 $download_data = $export_data;
948 1078 } else {
@@ -957,10 +1087,10 @@
957 1087 /** This filter is documented in controllers/controller-admin.php */
958 1088 $download_filename = apply_filters( 'tablepress_export_filename', $download_filename, '', '', $export['format'], $export_to_zip );
959 1089 $download_filename = sanitize_file_name( $download_filename );
960 1090 $full_filename = wp_tempnam( $download_filename );
961 - if ( true !== $zip_file->open( $full_filename, ZIPARCHIVE::OVERWRITE ) ) {
962 - @unlink( $full_filename );
1091 + if ( true !== $zip_file->open( $full_filename, ZipArchive::OVERWRITE ) ) {
1092 + @unlink( $full_filename ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
963 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.' ) );
964 1094 }
965 1095
966 1096 foreach ( $tables as $table_id ) {
@@ -977,9 +1107,13 @@
977 1107 // Don't export if the table is corrupted.
978 1108 if ( isset( $table['is_corrupted'] ) && $table['is_corrupted'] ) {
979 1109 continue;
980 1110 }
981 - $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 );
982 1116 /** This filter is documented in controllers/controller-admin.php */
983 1117 $export_data = apply_filters( 'tablepress_export_data', $export_data, $table, $export['format'], $export['csv_delimiter'] );
984 1118 $export_filename = sprintf( '%1$s-%2$s-%3$s.%4$s', $table['id'], $table['name'], wp_date( 'Y-m-d' ), $export['format'] );
985 1119 /** This filter is documented in controllers/controller-admin.php */
@@ -989,11 +1123,11 @@
989 1123 }
990 1124
991 1125 // If something went wrong, or no files were added to the ZIP file, bail out.
992 1126 // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
993 - if ( ! ZIPARCHIVE::ER_OK === $zip_file->status || 0 === $zip_file->numFiles ) {
1127 + if ( ZipArchive::ER_OK !== $zip_file->status || 0 === $zip_file->numFiles ) {
994 1128 $zip_file->close();
995 - @unlink( $full_filename );
1129 + @unlink( $full_filename ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
996 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.' ) );
997 1131 }
998 1132 $zip_file->close();
999 1133
@@ -998,9 +1132,13 @@
998 1132 $zip_file->close();
999 1133
1000 1134 // Load contents of the ZIP file, to send it as a download.
1001 1135 $download_data = file_get_contents( $full_filename );
1002 - @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
1003 1141 }
1004 1142
1005 1143 // Send download headers for export file.
1006 1144 header( 'Content-Description: File Transfer' );
@@ -1023,9 +1161,9 @@
1023 1161 * Import data from existing source (Upload, URL, Server, Direct input).
1024 1162 *
1025 1163 * @since 1.0.0
1026 1164 */
1027 - public function handle_post_action_import() {
1165 + public function handle_post_action_import(): void {
1028 1166 TablePress::check_nonce( 'import' );
1029 1167
1030 1168 if ( ! current_user_can( 'tablepress_import_tables' ) ) {
1031 1169 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
@@ -1047,10 +1185,17 @@
1047 1185 TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import', 'error_details' => 'You do not have the required access rights.' ) );
1048 1186 }
1049 1187 }
1050 1188
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.' ) );
1193 + }
1194 + }
1195 +
1051 1196 // Move file upload data to the main import configuration.
1052 - $import_config['file-upload'] = isset( $_FILES['import_file_upload'] ) ? $_FILES['import_file_upload'] : null;
1197 + $import_config['file-upload'] = $_FILES['import_file_upload'] ?? null;
1053 1198
1054 1199 // Check if the source data for the chosen import source is defined.
1055 1200 if ( empty( $import_config[ $import_config['source'] ] ) ) {
1056 1201 TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import', 'error_details' => 'The HTTP POST data does not contain an import source.' ) );
@@ -1078,9 +1223,9 @@
1078 1223 'import_source' => $import_config['source'],
1079 1224 'legacy_import' => $import_config['legacy_import'],
1080 1225 );
1081 1226 if ( in_array( $import_config['source'], array( 'url', 'server' ), true ) ) {
1082 - $redirect_parameters[ "import_{$import_config['source']}" ] = $import_config[ $import_config['source'] ];
1227 + $redirect_parameters[ "import_{$import_config['source']}" ] = rawurlencode( $import_config[ $import_config['source'] ] );
1083 1228 }
1084 1229 if ( is_wp_error( $import ) ) {
1085 1230 $redirect_parameters['error_details'] = TablePress::get_wp_error_string( $import );
1086 1231 } elseif ( 0 < count( $import['errors'] ) ) {
@@ -1085,9 +1230,9 @@
1085 1230 $redirect_parameters['error_details'] = TablePress::get_wp_error_string( $import );
1086 1231 } elseif ( 0 < count( $import['errors'] ) ) {
1087 1232 $wp_error_strings = array();
1088 1233 foreach ( $import['errors'] as $file ) {
1089 - $wp_error_strings[] = TablePress::get_wp_error_string( $file['error'] );
1234 + $wp_error_strings[] = TablePress::get_wp_error_string( $file->error );
1090 1235 }
1091 1236 $redirect_parameters['error_details'] = implode( ', ', $wp_error_strings );
1092 1237 }
1093 1238 TablePress::redirect( $redirect_parameters );
@@ -1111,10 +1256,10 @@
1111 1256 * Hide a header message on an admin screen.
1112 1257 *
1113 1258 * @since 1.0.0
1114 1259 */
1115 - public function handle_get_action_hide_message() {
1116 - $message_item = ! empty( $_GET['item'] ) ? $_GET['item'] : '';
1260 + public function handle_get_action_hide_message(): void {
1261 + $message_item = $_GET['item'] ?? '';
1117 1262 TablePress::check_nonce( 'hide_message', $message_item );
1118 1263
1119 1264 if ( ! current_user_can( 'tablepress_list_tables' ) ) {
1120 1265 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
@@ -1130,9 +1275,9 @@
1130 1275 * Delete a table.
1131 1276 *
1132 1277 * @since 1.0.0
1133 1278 */
1134 - public function handle_get_action_delete_table() {
1279 + public function handle_get_action_delete_table(): void {
1135 1280 $table_id = ( ! empty( $_GET['item'] ) ) ? $_GET['item'] : false;
1136 1281 TablePress::check_nonce( 'delete_table', $table_id );
1137 1282
1138 1283 $return = ! empty( $_GET['return'] ) ? $_GET['return'] : 'list';
@@ -1171,9 +1316,9 @@
1171 1316 * Copy a table.
1172 1317 *
1173 1318 * @since 1.0.0
1174 1319 */
1175 - public function handle_get_action_copy_table() {
1320 + public function handle_get_action_copy_table(): void {
1176 1321 $table_id = ( ! empty( $_GET['item'] ) ) ? $_GET['item'] : false;
1177 1322 TablePress::check_nonce( 'copy_table', $table_id );
1178 1323
1179 1324 $return = ! empty( $_GET['return'] ) ? $_GET['return'] : 'list';
@@ -1213,9 +1358,9 @@
1213 1358 * Preview a table.
1214 1359 *
1215 1360 * @since 1.0.0
1216 1361 */
1217 - public function handle_get_action_preview_table() {
1362 + public function handle_get_action_preview_table(): void {
1218 1363 $table_id = ( ! empty( $_GET['item'] ) ) ? $_GET['item'] : false;
1219 1364 TablePress::check_nonce( 'preview_table', $table_id );
1220 1365
1221 1366 // Nonce check should actually catch this already.
@@ -1247,14 +1392,15 @@
1247 1392 $render_options = shortcode_atts( $default_render_options, $table['options'] );
1248 1393 /** This filter is documented in controllers/controller-frontend.php */
1249 1394 $render_options = apply_filters( 'tablepress_shortcode_table_shortcode_atts', $render_options );
1250 1395 $render_options['html_id'] = "tablepress-{$table['id']}";
1396 + $render_options['block_preview'] = true;
1251 1397 $_render->set_input( $table, $render_options );
1252 1398 $view_data = array(
1253 - 'table_id' => $table_id,
1254 - 'head_html' => $_render->get_preview_css(),
1255 - 'body_html' => $_render->get_output(),
1256 - 'site_uses_block_editor' => TablePress::site_uses_block_editor(),
1399 + 'table_id' => $table_id,
1400 + 'head_html' => $_render->get_preview_css(),
1401 + 'body_html' => $_render->get_output( 'html' ),
1402 + 'site_used_editor' => TablePress::site_used_editor(),
1257 1403 );
1258 1404
1259 1405 $custom_css = TablePress::$model_options->get( 'custom_css' );
1260 1406 $use_custom_css = ( TablePress::$model_options->get( 'use_custom_css' ) && '' !== $custom_css );
@@ -1271,9 +1417,9 @@
1271 1417 * Shows a list of tables in the Editor toolbar Thickbox (opened by TinyMCE or Quicktags button).
1272 1418 *
1273 1419 * @since 1.0.0
1274 1420 */
1275 - public function handle_get_action_editor_button_thickbox() {
1421 + public function handle_get_action_editor_button_thickbox(): void {
1276 1422 TablePress::check_nonce( 'editor_button_thickbox' );
1277 1423
1278 1424 if ( ! current_user_can( 'tablepress_list_tables' ) ) {
1279 1425 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
@@ -1295,9 +1441,9 @@
1295 1441 * Uninstall TablePress, and delete all tables and options.
1296 1442 *
1297 1443 * @since 1.0.0
1298 1444 */
1299 - public function handle_get_action_uninstall_tablepress() {
1445 + public function handle_get_action_uninstall_tablepress(): void {
1300 1446 TablePress::check_nonce( 'uninstall_tablepress' );
1301 1447
1302 1448 $plugin = TABLEPRESS_BASENAME;
1303 1449
@@ -1317,9 +1463,9 @@
1317 1463
1318 1464 TablePress::$model_table->destroy();
1319 1465 TablePress::$model_options->destroy();
1320 1466
1321 - $output = '<strong>' . __( 'TablePress was uninstalled successfully.', 'tablepress' ) . '</strong><br /><br />';
1467 + $output = '<strong>' . __( 'TablePress was uninstalled successfully.', 'tablepress' ) . '</strong><br><br>';
1322 1468 $output .= __( 'All tables, data, and options were deleted.', 'tablepress' );
1323 1469 if ( is_multisite() ) {
1324 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' );
1325 1471 } else {
@@ -1326,9 +1472,9 @@
1326 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' );
1327 1473 }
1328 1474 if ( $css_files_deleted ) {
1329 1475 $output .= ' ' . __( 'Your TablePress &#8220;Custom CSS&#8221; files have been deleted automatically.', 'tablepress' );
1330 - } else {
1476 + } else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found
1331 1477 if ( is_multisite() ) {
1332 1478 $output .= ' ' . __( 'Please also ask him to delete your TablePress &#8220;Custom CSS&#8221; files from the server.', 'tablepress' );
1333 1479 } else {
1334 1480 $output .= ' ' . __( 'You may now also delete your TablePress &#8220;Custom CSS&#8221; files in the <code>wp-content</code> folder.', 'tablepress' );