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 +318 -161 2.0.4 → 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 ) {
@@ -147,11 +165,20 @@
147 165 foreach ( $this->page_hooks as $page_hook ) {
148 166 add_action( "load-{$page_hook}", array( $this, 'load_admin_page' ) );
149 167 }
150 168
151 - $pages_with_editor_button = array( 'post.php', 'post-new.php' );
152 - foreach ( $pages_with_editor_button as $editor_page ) {
153 - 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 + }
154 181 }
155 182
156 183 if ( ! is_network_admin() && ! is_user_admin() ) {
157 184 add_action( 'admin_bar_menu', array( $this, 'add_wp_admin_bar_new_content_menu_entry' ), 71 );
@@ -157,22 +184,16 @@
157 184 add_action( 'admin_bar_menu', array( $this, 'add_wp_admin_bar_new_content_menu_entry' ), 71 );
158 185 }
159 186
160 187 add_action( 'load-plugins.php', array( $this, 'plugins_page' ) );
161 -
162 - // Add filters and actions for the integration into the WP WXR exporter and importer.
163 - add_action( 'wp_import_insert_post', array( TablePress::$model_table, 'add_table_id_on_wp_import' ), 10, 4 );
164 - add_filter( 'wp_import_post_meta', array( TablePress::$model_table, 'prevent_table_id_post_meta_import_on_wp_import' ), 10, 3 );
165 - add_filter( 'wxr_export_skip_postmeta', array( TablePress::$model_table, 'add_table_id_to_wp_export' ), 10, 3 );
166 188 }
167 189
168 190 /**
169 - * Loads additional JavaScript code for the TablePress table block.
191 + * Loads additional JavaScript code for the TablePress table block (in the block editor context).
170 192 *
171 - * @since 2.0.0
193 + * @since 2.2.0
172 194 */
173 - public function add_block_editor_js() {
174 - // Add table information for the Block Editor to the page.
195 + public function enqueue_block_editor_assets(): void {
175 196 $handle = generate_block_asset_handle( 'tablepress/table', 'editorScript' );
176 197 $data = $this->get_block_editor_data();
177 198 wp_add_inline_script( $handle, $data, 'before' );
178 199 }
@@ -177,8 +198,20 @@
177 198 wp_add_inline_script( $handle, $data, 'before' );
178 199 }
179 200
180 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 + /**
181 214 * Gets the inline data that is referenced by the Block Editor JavaScript code for the TablePress blocks.
182 215 *
183 216 * @since 2.0.0
184 217 *
@@ -183,9 +216,9 @@
183 216 * @since 2.0.0
184 217 *
185 218 * @return string JavaScript code for the Block Editor.
186 219 */
187 - protected function get_block_editor_data() {
220 + protected function get_block_editor_data(): string {
188 221 $tables = array();
189 222 // Load all table IDs without priming the post meta cache, as table options/visibility are not needed.
190 223 $table_ids = TablePress::$model_table->load_all( false );
191 224 foreach ( $table_ids as $table_id ) {
@@ -190,8 +223,14 @@
190 223 $table_ids = TablePress::$model_table->load_all( false );
191 224 foreach ( $table_ids as $table_id ) {
192 225 // Load table, without table data, options, and visibility settings.
193 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 +
194 233 if ( '' === trim( $table['name'] ) ) {
195 234 $table['name'] = __( '(no name)', 'tablepress' );
196 235 }
197 236 $tables[ $table_id ] = esc_html( $table['name'] );
@@ -201,22 +240,30 @@
201 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.
202 241 *
203 242 * @since 2.0.0
204 243 *
205 - * @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.
206 245 */
207 246 $tables = apply_filters( 'tablepress_block_editor_tables_list', $tables );
208 247
209 - $tables = wp_json_encode( $tables, TABLEPRESS_JSON_OPTIONS );
210 - // Print them inside a `JSON.parse()` call in JS for speed gains, with necessary escaping of `</script>`, `'`, and `\`.
211 - $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 );
212 255
213 256 $shortcode = esc_js( TablePress::$shortcode );
214 257
215 258 $template = TablePress::$model_table->get_table_template();
216 - $template = wp_json_encode( $template['options'], TABLEPRESS_JSON_OPTIONS );
217 - // Print them inside a `JSON.parse()` call in JS for speed gains, with necessary escaping of `</script>`, `'`, and `\`.
218 - $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 );
219 266
220 267 /**
221 268 * Filters whether the table block preview should be loaded via a <ServerSideRender> in the block editor.
222 269 *
@@ -232,17 +279,17 @@
232 279 $url = TablePress::url( array( 'action' => 'list' ) );
233 280 }
234 281
235 282 return <<<JS
236 -// Ensure the global `tp` object exists.
237 -window.tp = window.tp || {};
238 -tp.url = '{$url}';
239 -tp.load_block_preview = {$load_block_preview};
240 -tp.table = {};
241 -tp.table.shortcode = '{$shortcode}';
242 -tp.table.template = JSON.parse( '{$template}' );
243 -tp.tables = JSON.parse( '{$tables}' );
244 -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;
245 292 }
246 293
247 294 /**
248 295 * Register actions to add "Table" button to "HTML editor" and "Visual editor" toolbars.
@@ -248,21 +295,20 @@
248 295 * Register actions to add "Table" button to "HTML editor" and "Visual editor" toolbars.
249 296 *
250 297 * @since 1.0.0
251 298 */
252 - public function add_editor_buttons() {
299 + public function add_editor_buttons(): void {
253 300 if ( ! current_user_can( 'tablepress_list_tables' ) ) {
254 301 return;
255 302 }
256 303
257 304 // Only load the toolbar integration if the Block Editor is not used.
258 - if ( TablePress::site_uses_block_editor() ) {
305 + if ( 'block' === TablePress::site_used_editor() ) {
259 306 return;
260 307 }
261 308
262 309 add_thickbox(); // The files are usually already loaded by media upload functions.
263 - $admin_page = TablePress::load_class( 'TablePress_Admin_Page', 'class-admin-page-helper.php', 'classes' );
264 - $admin_page->enqueue_script(
310 + TablePress::enqueue_script(
265 311 'quicktags-button',
266 312 array( 'quicktags', 'media-upload' ),
267 313 array(
268 314 'editor_button' => array(
@@ -270,9 +316,9 @@
270 316 'title' => __( 'Insert a TablePress table', 'tablepress' ),
271 317 'thickbox_title' => __( 'Insert a TablePress table', 'tablepress' ),
272 318 'thickbox_url' => TablePress::url( array( 'action' => 'editor_button_thickbox' ), true, 'admin-post.php' ),
273 319 ),
274 - )
320 + ),
275 321 );
276 322
277 323 // TinyMCE integration.
278 324 if ( user_can_richedit() ) {
@@ -285,12 +331,12 @@
285 331 * Adds the "Table" button to the TinyMCE toolbar.
286 332 *
287 333 * @since 1.0.0
288 334 *
289 - * @param array $buttons Current set of buttons in the TinyMCE toolbar.
290 - * @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.
291 337 */
292 - public function add_tinymce_button( array $buttons ) {
338 + public function add_tinymce_button( array $buttons ): array {
293 339 $buttons[] = 'tablepress_insert_table';
294 340 return $buttons;
295 341 }
296 342
@@ -298,12 +344,12 @@
298 344 * Registers the "Table" button plugin for the TinyMCE editor.
299 345 *
300 346 * @since 1.0.0
301 347 *
302 - * @param array $plugins Current set of registered TinyMCE plugins.
303 - * @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.
304 350 */
305 - public function add_tinymce_plugin( array $plugins ) {
351 + public function add_tinymce_plugin( array $plugins ): array {
306 352 $plugins['tablepress_tinymce'] = plugins_url( 'admin/js/build/tinymce-button.js', TABLEPRESS__FILE__ );
307 353 return $plugins;
308 354 }
309 355
@@ -313,9 +359,9 @@
313 359 * @since 1.0.0
314 360 *
315 361 * @param WP_Admin_Bar $wp_admin_bar The current WP Admin Bar object.
316 362 */
317 - 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 {
318 364 if ( ! current_user_can( 'tablepress_add_tables' ) ) {
319 365 return;
320 366 }
321 367
@@ -326,9 +372,9 @@
326 372
327 373 $wp_admin_bar->add_menu( array(
328 374 'parent' => 'new-content',
329 375 'id' => 'new-tablepress-table',
330 - 'title' => __( 'TablePress Table', 'tablepress' ),
376 + 'title' => __( 'TablePress table', 'tablepress' ),
331 377 'href' => TablePress::url( array( 'action' => 'add' ) ),
332 378 ) );
333 379 }
334 380
@@ -336,12 +382,25 @@
336 382 * Handle actions for loading of Plugins page.
337 383 *
338 384 * @since 1.0.0
339 385 */
340 - public function plugins_page() {
386 + public function plugins_page(): void {
341 387 // Add additional links on Plugins page.
342 388 add_filter( 'plugin_action_links_' . TABLEPRESS_BASENAME, array( $this, 'add_plugin_action_links' ) );
343 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 + }
344 403 }
345 404
346 405 /**
347 406 * Add links to the TablePress entry in the "Plugin" column on the Plugins page.
@@ -347,14 +406,14 @@
347 406 * Add links to the TablePress entry in the "Plugin" column on the Plugins page.
348 407 *
349 408 * @since 1.0.0
350 409 *
351 - * @param array $links List of links to print in the "Plugin" column on the Plugins page.
352 - * @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.
353 412 */
354 - public function add_plugin_action_links( array $links ) {
413 + public function add_plugin_action_links( array $links ): array {
355 414 if ( current_user_can( 'tablepress_list_tables' ) ) {
356 - $links[] = '<a href="' . TablePress::url() . '">' . __( 'Plugin page', 'tablepress' ) . '</a>';
415 + $links[] = '<a href="' . esc_url( TablePress::url() ) . '">' . __( 'Plugin page', 'tablepress' ) . '</a>';
357 416 }
358 417 return $links;
359 418 }
360 419
@@ -362,19 +421,19 @@
362 421 * Add links to the TablePress entry in the "Description" column on the Plugins page.
363 422 *
364 423 * @since 1.0.0
365 424 *
366 - * @param array $links List of links to print in the "Description" column on the Plugins page.
367 - * @param string $file Name of the plugin.
368 - * @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.
369 428 */
370 - public function add_plugin_row_meta( array $links, $file ) {
429 + public function add_plugin_row_meta( array $links, string $file ): array {
371 430 if ( TABLEPRESS_BASENAME === $file ) {
372 431 $links[] = '<a href="https://tablepress.org/faq/" title="' . esc_attr__( 'Frequently Asked Questions', 'tablepress' ) . '">' . __( 'FAQ', 'tablepress' ) . '</a>';
373 432 $links[] = '<a href="https://tablepress.org/documentation/">' . __( 'Documentation', 'tablepress' ) . '</a>';
374 433 $links[] = '<a href="https://tablepress.org/support/">' . __( 'Support', 'tablepress' ) . '</a>';
375 - if ( tb_tp_fs()->is_free_plan() ) {
376 - $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>';
377 436 }
378 437 }
379 438 return $links;
380 439 }
@@ -379,16 +438,62 @@
379 438 return $links;
380 439 }
381 440
382 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 + /**
383 488 * Prepare the rendering of an admin screen, by determining the current action, loading necessary data and initializing the view.
384 489 *
385 490 * @since 1.0.0
386 491 */
387 - public function load_admin_page() {
492 + public function load_admin_page(): void {
388 493 // Determine the action from either the GET parameter (for sub-menu entries, and the main admin menu entry).
389 494 $action = ( ! empty( $_GET['action'] ) ) ? $_GET['action'] : 'list'; // Default action is list.
390 - if ( $this->is_top_level_page ) {
495 + if ( TablePress::$controller->is_top_level_page ) {
391 496 // Or, for sub-menu entry of an admin menu "TablePress" entry, get it from the "page" GET parameter.
392 497 if ( 'tablepress' !== $_GET['page'] ) {
393 498 // Actions that are top-level entries, but don't have an action GET parameter (action is after last _ in string).
394 499 $action = substr( $_GET['page'], 11 ); // $_GET['page'] has the format 'tablepress_{$action}'
@@ -395,9 +500,9 @@
395 500 }
396 501 }
397 502
398 503 // Check if action is a supported action, and whether the user is allowed to access this screen.
399 - 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.)
400 505 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
401 506 }
402 507
403 508 // Don't load TablePress assets on the Freemius opt-in/activation screen.
@@ -412,34 +517,33 @@
412 517 * Set the `$typenow` global to the current CPT ourselves, as `WP_Screen::get()` does not determine the CPT correctly.
413 518 * This is necessary as the WP Admin Menu can otherwise highlight wrong entries, see https://github.com/TablePress/TablePress/issues/24.
414 519 */
415 520 if ( isset( $_GET['post_type'] ) && post_type_exists( $_GET['post_type'] ) ) {
416 - $GLOBALS['typenow'] = $_GET['post_type'];
521 + $GLOBALS['typenow'] = $_GET['post_type']; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
417 522 }
418 523
419 524 // Pre-define some view data.
420 525 $data = array(
421 - 'view_actions' => $this->view_actions,
422 - 'message' => ( ! empty( $_GET['message'] ) ) ? $_GET['message'] : false,
423 - 'error_details' => ( ! empty( $_GET['error_details'] ) ) ? $_GET['error_details'] : '',
424 - '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(),
425 530 );
426 531
427 532 // Depending on the action, load more necessary data for the corresponding view.
428 533 switch ( $action ) {
429 534 case 'list':
430 - $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;
431 536 // Prime the post meta cache for cached loading of last_editor.
432 537 $data['table_ids'] = TablePress::$model_table->load_all( true );
433 - $data['messages']['first_visit'] = TablePress::$model_options->get( 'message_first_visit' );
434 - $data['messages']['plugin_update_message'] = TablePress::$model_options->get( 'message_plugin_update' );
435 - $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' );
436 542 $data['table_count'] = count( $data['table_ids'] );
437 543 break;
438 544 case 'about':
439 545 $data['first_activation'] = TablePress::$model_options->get( 'first_activation' );
440 - $exporter = TablePress::load_class( 'TablePress_Export', 'class-export.php', 'classes' );
441 - $data['zip_support_available'] = $exporter->zip_support_available;
442 546 break;
443 547 case 'options':
444 548 /*
445 549 * Maybe try saving "Custom CSS" to a file:
@@ -446,9 +550,9 @@
446 550 * (called here, as the credentials form posts to this handler again, due to how `request_filesystem_credentials()` works)
447 551 */
448 552 if ( isset( $_GET['item'] ) && 'save_custom_css' === $_GET['item'] ) {
449 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.
450 - $action = 'options_custom_css'; // to load a different view
554 + $action = 'options_custom_css'; // To load a different view.
451 555 // Try saving "Custom CSS" to a file, otherwise this gets the HTML for the credentials form.
452 556 $tablepress_css = TablePress::load_class( 'TablePress_CSS', 'class-css.php', 'classes' );
453 557 $result = $tablepress_css->save_custom_css_to_file_plugin_options( TablePress::$model_options->get( 'custom_css' ), TablePress::$model_options->get( 'custom_css_minified' ) );
454 558 if ( is_string( $result ) ) {
@@ -469,12 +573,12 @@
469 573 break;
470 574 }
471 575 $data['frontend_options']['use_custom_css'] = TablePress::$model_options->get( 'use_custom_css' );
472 576 $data['frontend_options']['custom_css'] = TablePress::$model_options->get( 'custom_css' );
473 - $data['user_options']['parent_page'] = $this->parent_page;
577 + $data['user_options']['parent_page'] = TablePress::$controller->parent_page;
474 578 break;
475 579 case 'edit':
476 - if ( empty( $_GET['table_id'] ) ) {
580 + if ( ! isset( $_GET['table_id'] ) || ! preg_match( '/^[a-zA-Z0-9_-]+$/', $_GET['table_id'] ) ) {
477 581 TablePress::redirect( array( 'action' => 'list', 'message' => 'error_no_table' ) );
478 582 }
479 583 // Load table, with table data, options, and visibility settings.
480 584 $data['table'] = TablePress::$model_table->load( $_GET['table_id'], true, true );
@@ -486,35 +590,60 @@
486 590 }
487 591 break;
488 592 case 'export':
489 593 // Load all table IDs without priming the post meta cache, as table options/visibility are not needed.
490 - $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 + }
491 610 $data['tables_count'] = TablePress::$model_table->count_tables();
492 - if ( ! empty( $_GET['table_id'] ) ) {
493 - $data['export_ids'] = explode( ',', $_GET['table_id'] );
494 - } else {
495 - // Just show empty export form.
496 - $data['export_ids'] = array();
497 - }
611 + $data['export_ids'] = ( isset( $_GET['table_id'] ) && preg_match( '/^[,a-zA-Z0-9_-]+$/', $_GET['table_id'] ) ) ? explode( ',', $_GET['table_id'] ) : array();
498 612 $exporter = TablePress::load_class( 'TablePress_Export', 'class-export.php', 'classes' );
499 613 $data['zip_support_available'] = $exporter->zip_support_available;
500 614 $data['export_formats'] = $exporter->export_formats;
501 615 $data['csv_delimiters'] = $exporter->csv_delimiters;
502 - $data['export_format'] = ( ! empty( $_GET['export_format'] ) ) ? $_GET['export_format'] : false;
616 + $data['export_format'] = ( ! empty( $_GET['export_format'] ) ) ? $_GET['export_format'] : 'csv';
503 617 $data['csv_delimiter'] = ( ! empty( $_GET['csv_delimiter'] ) ) ? $_GET['csv_delimiter'] : _x( ',', 'Default CSV delimiter in the translated language (";", ",", or "tab")', 'tablepress' );
504 618 break;
505 619 case 'import':
506 620 // Load all table IDs without priming the post meta cache, as table options/visibility are not needed.
507 - $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.
508 638 $data['tables_count'] = TablePress::$model_table->count_tables();
509 639 $importer = TablePress::load_class( 'TablePress_Import', 'class-import.php', 'classes' );
510 - $data['zip_support_available'] = $importer->zip_support_available;
511 640 $data['import_type'] = ( ! empty( $_GET['import_type'] ) ) ? $_GET['import_type'] : 'add';
512 - $data['import_existing_table'] = ( ! empty( $_GET['import_existing_table'] ) ) ? $_GET['import_existing_table'] : '';
641 + $data['import_existing_table'] = $_GET['import_existing_table'] ?? '';
513 642 $data['import_source'] = ( ! empty( $_GET['import_source'] ) ) ? $_GET['import_source'] : 'file-upload';
514 - $data['import_url'] = ( ! empty( $_GET['import_url'] ) ) ? wp_unslash( $_GET['import_url'] ) : 'https://';
515 - $data['import_server'] = ( ! empty( $_GET['import_server'] ) ) ? wp_unslash( $_GET['import_server'] ) : ABSPATH;
516 - $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'] ) ) : '';
517 646 $data['legacy_import'] = ( ! empty( $_GET['legacy_import'] ) ) ? $_GET['legacy_import'] : 'false';
518 647 break;
519 648 }
520 649
@@ -522,10 +651,10 @@
522 651 * Filters the data that is passed to the current TablePress View.
523 652 *
524 653 * @since 1.0.0
525 654 *
526 - * @param array $data Data for the view.
527 - * @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.
528 657 */
529 658 $data = apply_filters( 'tablepress_view_data', $data, $action );
530 659
531 660 // Prepare and initialize the view.
@@ -536,9 +665,9 @@
536 665 * Render the view that has been initialized in load_admin_page() (called by WordPress when the actual page content is needed).
537 666 *
538 667 * @since 1.0.0
539 668 */
540 - public function show_admin_page() {
669 + public function show_admin_page(): void {
541 670 $this->view->render();
542 671 }
543 672
544 673 /**
@@ -547,9 +676,9 @@
547 676 * @since 1.0.0
548 677 *
549 678 * @return bool Whether the message shall be shown on the "All Tables" screen.
550 679 */
551 - protected function maybe_show_donation_message() {
680 + protected function maybe_show_donation_message(): bool {
552 681 // Only show the message to plugin admins.
553 682 if ( ! current_user_can( 'tablepress_edit_options' ) ) {
554 683 return false;
555 684 }
@@ -567,9 +696,9 @@
567 696 * Init list of actions that have a view with their titles/names/caps.
568 697 *
569 698 * @since 1.0.0
570 699 */
571 - protected function init_view_actions() {
700 + protected function init_view_actions(): void {
572 701 $this->view_actions = array(
573 702 'list' => array(
574 703 'show_entry' => true,
575 704 'page_title' => __( 'All Tables', 'tablepress' ),
@@ -625,9 +754,9 @@
625 754 * Filters the available TablePres Views/Actions and their parameters.
626 755 *
627 756 * @since 1.0.0
628 757 *
629 - * @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.
630 759 */
631 760 $this->view_actions = apply_filters( 'tablepress_admin_view_actions', $this->view_actions );
632 761 }
633 762
@@ -639,9 +768,9 @@
639 768 * Handle Bulk Actions (Copy, Export, Delete) on "All Tables" list screen.
640 769 *
641 770 * @since 1.0.0
642 771 */
643 - public function handle_post_action_list() {
772 + public function handle_post_action_list(): void {
644 773 TablePress::check_nonce( 'list' );
645 774
646 775 if ( isset( $_POST['bulk-action-selector-top'] ) && '-1' !== $_POST['bulk-action-selector-top'] ) {
647 776 $bulk_action = $_POST['bulk-action-selector-top'];
@@ -682,9 +811,9 @@
682 811 * To export, redirect to "Export" screen, with selected table IDs.
683 812 */
684 813 $table_ids = implode( ',', $tables );
685 814 TablePress::redirect( array( 'action' => 'export', 'table_id' => $table_ids ) );
686 - break;
815 + // break; // unreachable.
687 816 case 'delete':
688 817 foreach ( $tables as $table_id ) {
689 818 if ( current_user_can( 'tablepress_delete_table', $table_id ) ) {
690 819 $deleted = TablePress::$model_table->delete( $table_id );
@@ -697,9 +826,9 @@
697 826 }
698 827 break;
699 828 }
700 829
701 - 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?
702 831 $message = "error_{$bulk_action}_not_all_tables";
703 832 } else {
704 833 $plural = ( count( $tables ) > 1 ) ? '_plural' : '';
705 834 $message = "success_{$bulk_action}{$plural}";
@@ -724,9 +853,9 @@
724 853 * Add a table, according to the parameters on the "Add new Table" screen.
725 854 *
726 855 * @since 1.0.0
727 856 */
728 - public function handle_post_action_add() {
857 + public function handle_post_action_add(): void {
729 858 TablePress::check_nonce( 'add' );
730 859
731 860 if ( ! current_user_can( 'tablepress_add_tables' ) ) {
732 861 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
@@ -737,11 +866,11 @@
737 866 }
738 867
739 868 $add_table = wp_unslash( $_POST['table'] );
740 869
741 - // Perform sanity checks of posted data.
742 - $name = ( isset( $add_table['name'] ) ) ? $add_table['name'] : '';
743 - $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'] ?? '';
744 873 if ( ! isset( $add_table['rows'], $add_table['columns'] ) ) {
745 874 TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add', 'error_details' => 'The HTTP POST data does not contain the table size.' ) );
746 875 }
747 876
@@ -780,9 +909,9 @@
780 909 * Save changed "Plugin Options".
781 910 *
782 911 * @since 1.0.0
783 912 */
784 - public function handle_post_action_options() {
913 + public function handle_post_action_options(): void {
785 914 TablePress::check_nonce( 'options' );
786 915
787 916 if ( ! current_user_can( 'tablepress_access_options_screen' ) ) {
788 917 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
@@ -801,10 +930,10 @@
801 930 if ( ! empty( $posted_options['admin_menu_parent_page'] ) && '-' !== $posted_options['admin_menu_parent_page'] ) {
802 931 $new_options['admin_menu_parent_page'] = $posted_options['admin_menu_parent_page'];
803 932 // Re-init parent information, as `TablePress::redirect()` URL might be wrong otherwise.
804 933 /** This filter is documented in classes/class-controller.php */
805 - $this->parent_page = apply_filters( 'tablepress_admin_menu_parent_page', $posted_options['admin_menu_parent_page'] );
806 - $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 );
807 936 }
808 937
809 938 // Custom CSS can only be saved if the user is allowed to do so.
810 939 $update_custom_css_files = false;
@@ -815,13 +944,20 @@
815 944 if ( isset( $posted_options['custom_css'] ) ) {
816 945 $new_options['custom_css'] = $posted_options['custom_css'];
817 946
818 947 $tablepress_css = TablePress::load_class( 'TablePress_CSS', 'class-css.php', 'classes' );
819 - // Sanitize and tidy up Custom CSS.
820 - $new_options['custom_css'] = $tablepress_css->sanitize_css( $new_options['custom_css'] );
821 - // Minify Custom CSS.
822 - $new_options['custom_css_minified'] = $tablepress_css->minify_css( $new_options['custom_css'] );
823 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 +
824 960 // Maybe update CSS files as well.
825 961 $custom_css_file_contents = $tablepress_css->load_custom_css_from_file( 'normal' );
826 962 if ( false === $custom_css_file_contents ) {
827 963 $custom_css_file_contents = '';
@@ -852,9 +988,9 @@
852 988 * Export selected tables.
853 989 *
854 990 * @since 1.0.0
855 991 */
856 - public function handle_post_action_export() {
992 + public function handle_post_action_export(): void {
857 993 TablePress::check_nonce( 'export' );
858 994
859 995 if ( ! current_user_can( 'tablepress_export_tables' ) ) {
860 996 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
@@ -865,19 +1001,19 @@
865 1001 }
866 1002
867 1003 $export = wp_unslash( $_POST['export'] );
868 1004
869 - if ( empty( $export['tables'] ) ) {
1005 + if ( empty( $export['tables_list'] ) ) {
870 1006 TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export', 'error_details' => 'The HTTP POST data does not contain tables.' ) );
871 1007 }
872 1008
1009 + /** @var TablePress_Export $exporter */ // phpcs:ignore Generic.Commenting.DocComment.MissingShort
873 1010 $exporter = TablePress::load_class( 'TablePress_Export', 'class-export.php', 'classes' );
874 1011
875 1012 if ( empty( $export['format'] ) || ! isset( $exporter->export_formats[ $export['format'] ] ) ) {
876 1013 TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export', 'error_details' => 'The export format is invalid.' ) );
877 1014 }
878 - if ( empty( $export['csv_delimiter'] ) ) {
879 - // Set a value, so that the variable exists.
1015 + if ( ! isset( $export['csv_delimiter'] ) ) {
880 1016 $export['csv_delimiter'] = '';
881 1017 }
882 1018 if ( 'csv' === $export['format'] && ! isset( $exporter->csv_delimiters[ $export['csv_delimiter'] ] ) ) {
883 1019 TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export', 'error_details' => 'The CSV delimiter is invalid.' ) );
@@ -882,10 +1018,9 @@
882 1018 if ( 'csv' === $export['format'] && ! isset( $exporter->csv_delimiters[ $export['csv_delimiter'] ] ) ) {
883 1019 TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export', 'error_details' => 'The CSV delimiter is invalid.' ) );
884 1020 }
885 1021
886 - // Use list of tables from concatenated field if available (as that's hopefully not truncated by Suhosin, which is possible for $export['tables']).
887 - $tables = ( ! empty( $export['tables_list'] ) ) ? explode( ',', $export['tables_list'] ) : $export['tables'];
1022 + $tables = explode( ',', $export['tables_list'] );
888 1023
889 1024 // Determine if ZIP file support is available.
890 1025 if ( $exporter->zip_support_available
891 1026 && ( ( isset( $export['zip_file'] ) && 'true' === $export['zip_file'] ) || count( $tables ) > 1 ) ) {
@@ -895,9 +1030,9 @@
895 1030 $export_to_zip = false;
896 1031 }
897 1032
898 1033 if ( ! $export_to_zip ) {
899 - // 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.
900 1035 if ( ! current_user_can( 'tablepress_export_table', $tables[0] ) ) {
901 1036 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
902 1037 }
903 1038 // Load table, with table data, options, and visibility settings.
@@ -922,26 +1057,31 @@
922 1057 */
923 1058 $download_filename = apply_filters( 'tablepress_export_filename', $download_filename, $table['id'], $table['name'], $export['format'], $export_to_zip );
924 1059 $download_filename = sanitize_file_name( $download_filename );
925 1060 // Export the table.
926 - $export_data = $exporter->export_table( $table, $export['format'], $export['csv_delimiter'] );
1061 + $options = array();
1062 + if ( 'csv' === $export['format'] ) {
1063 + $options['csv_delimiter'] = $export['csv_delimiter'];
1064 + }
1065 + $export_data = $exporter->export_table( $table, $export['format'], $options );
927 1066 /**
928 1067 * Filters the exported table data.
929 1068 *
930 1069 * @since 1.6.0
931 1070 *
932 - * @param string $export_data The exported table data.
933 - * @param array $table Table to be exported.
934 - * @param string $export_format Format for the export ('csv', 'html', 'json').
935 - * @param string $csv_delimiter Delimiter for CSV export.
1071 + * @param string $export_data The exported table data.
1072 + * @param array<string, mixed> $table Table to be exported.
1073 + * @param string $export_format Format for the export ('csv', 'html', 'json').
1074 + * @param string $csv_delimiter Delimiter for CSV export.
936 1075 */
937 1076 $export_data = apply_filters( 'tablepress_export_data', $export_data, $table, $export['format'], $export['csv_delimiter'] );
938 1077 $download_data = $export_data;
939 1078 } else {
940 1079 // Zipping can use a lot of memory and execution time, but not this much hopefully.
941 - /** This filter is documented in the WordPress file wp-admin/admin.php */
942 - @ini_set( 'memory_limit', apply_filters( 'admin_memory_limit', WP_MAX_MEMORY_LIMIT ) ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
943 - @set_time_limit( 300 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
1080 + wp_raise_memory_limit( 'admin' );
1081 + if ( function_exists( 'set_time_limit' ) ) {
1082 + @set_time_limit( 300 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
1083 + }
944 1084
945 1085 $zip_file = new ZipArchive();
946 1086 $download_filename = sprintf( 'tablepress-export-%1$s-%2$s.zip', wp_date( 'Y-m-d-H-i-s' ), $export['format'] );
947 1087 /** This filter is documented in controllers/controller-admin.php */
@@ -947,10 +1087,10 @@
947 1087 /** This filter is documented in controllers/controller-admin.php */
948 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 );
1091 + if ( true !== $zip_file->open( $full_filename, ZipArchive::OVERWRITE ) ) {
1092 + @unlink( $full_filename ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
953 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 ) {
@@ -967,9 +1107,13 @@
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 1118 $export_filename = sprintf( '%1$s-%2$s-%3$s.%4$s', $table['id'], $table['name'], wp_date( 'Y-m-d' ), $export['format'] );
975 1119 /** This filter is documented in controllers/controller-admin.php */
@@ -979,11 +1123,11 @@
979 1123 }
980 1124
981 1125 // If something went wrong, or no files were added to the ZIP file, bail out.
982 1126 // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
983 - if ( ! ZIPARCHIVE::ER_OK === $zip_file->status || 0 === $zip_file->numFiles ) {
1127 + if ( ZipArchive::ER_OK !== $zip_file->status || 0 === $zip_file->numFiles ) {
984 1128 $zip_file->close();
985 - @unlink( $full_filename );
1129 + @unlink( $full_filename ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
986 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.' ) );
987 1131 }
988 1132 $zip_file->close();
989 1133
@@ -988,9 +1132,13 @@
988 1132 $zip_file->close();
989 1133
990 1134 // Load contents of the ZIP file, to send it as a download.
991 1135 $download_data = file_get_contents( $full_filename );
992 - @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
993 1141 }
994 1142
995 1143 // Send download headers for export file.
996 1144 header( 'Content-Description: File Transfer' );
@@ -1013,9 +1161,9 @@
1013 1161 * Import data from existing source (Upload, URL, Server, Direct input).
1014 1162 *
1015 1163 * @since 1.0.0
1016 1164 */
1017 - public function handle_post_action_import() {
1165 + public function handle_post_action_import(): void {
1018 1166 TablePress::check_nonce( 'import' );
1019 1167
1020 1168 if ( ! current_user_can( 'tablepress_import_tables' ) ) {
1021 1169 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
@@ -1037,10 +1185,17 @@
1037 1185 TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import', 'error_details' => 'You do not have the required access rights.' ) );
1038 1186 }
1039 1187 }
1040 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 +
1041 1196 // Move file upload data to the main import configuration.
1042 - $import_config['file-upload'] = isset( $_FILES['import_file_upload'] ) ? $_FILES['import_file_upload'] : null;
1197 + $import_config['file-upload'] = $_FILES['import_file_upload'] ?? null;
1043 1198
1044 1199 // Check if the source data for the chosen import source is defined.
1045 1200 if ( empty( $import_config[ $import_config['source'] ] ) ) {
1046 1201 TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import', 'error_details' => 'The HTTP POST data does not contain an import source.' ) );
@@ -1068,9 +1223,9 @@
1068 1223 'import_source' => $import_config['source'],
1069 1224 'legacy_import' => $import_config['legacy_import'],
1070 1225 );
1071 1226 if ( in_array( $import_config['source'], array( 'url', 'server' ), true ) ) {
1072 - $redirect_parameters[ "import_{$import_config['source']}" ] = $import_config[ $import_config['source'] ];
1227 + $redirect_parameters[ "import_{$import_config['source']}" ] = rawurlencode( $import_config[ $import_config['source'] ] );
1073 1228 }
1074 1229 if ( is_wp_error( $import ) ) {
1075 1230 $redirect_parameters['error_details'] = TablePress::get_wp_error_string( $import );
1076 1231 } elseif ( 0 < count( $import['errors'] ) ) {
@@ -1075,9 +1230,9 @@
1075 1230 $redirect_parameters['error_details'] = TablePress::get_wp_error_string( $import );
1076 1231 } elseif ( 0 < count( $import['errors'] ) ) {
1077 1232 $wp_error_strings = array();
1078 1233 foreach ( $import['errors'] as $file ) {
1079 - $wp_error_strings[] = TablePress::get_wp_error_string( $file['error'] );
1234 + $wp_error_strings[] = TablePress::get_wp_error_string( $file->error );
1080 1235 }
1081 1236 $redirect_parameters['error_details'] = implode( ', ', $wp_error_strings );
1082 1237 }
1083 1238 TablePress::redirect( $redirect_parameters );
@@ -1101,10 +1256,10 @@
1101 1256 * Hide a header message on an admin screen.
1102 1257 *
1103 1258 * @since 1.0.0
1104 1259 */
1105 - public function handle_get_action_hide_message() {
1106 - $message_item = ! empty( $_GET['item'] ) ? $_GET['item'] : '';
1260 + public function handle_get_action_hide_message(): void {
1261 + $message_item = $_GET['item'] ?? '';
1107 1262 TablePress::check_nonce( 'hide_message', $message_item );
1108 1263
1109 1264 if ( ! current_user_can( 'tablepress_list_tables' ) ) {
1110 1265 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
@@ -1120,9 +1275,9 @@
1120 1275 * Delete a table.
1121 1276 *
1122 1277 * @since 1.0.0
1123 1278 */
1124 - public function handle_get_action_delete_table() {
1279 + public function handle_get_action_delete_table(): void {
1125 1280 $table_id = ( ! empty( $_GET['item'] ) ) ? $_GET['item'] : false;
1126 1281 TablePress::check_nonce( 'delete_table', $table_id );
1127 1282
1128 1283 $return = ! empty( $_GET['return'] ) ? $_GET['return'] : 'list';
@@ -1161,9 +1316,9 @@
1161 1316 * Copy a table.
1162 1317 *
1163 1318 * @since 1.0.0
1164 1319 */
1165 - public function handle_get_action_copy_table() {
1320 + public function handle_get_action_copy_table(): void {
1166 1321 $table_id = ( ! empty( $_GET['item'] ) ) ? $_GET['item'] : false;
1167 1322 TablePress::check_nonce( 'copy_table', $table_id );
1168 1323
1169 1324 $return = ! empty( $_GET['return'] ) ? $_GET['return'] : 'list';
@@ -1203,9 +1358,9 @@
1203 1358 * Preview a table.
1204 1359 *
1205 1360 * @since 1.0.0
1206 1361 */
1207 - public function handle_get_action_preview_table() {
1362 + public function handle_get_action_preview_table(): void {
1208 1363 $table_id = ( ! empty( $_GET['item'] ) ) ? $_GET['item'] : false;
1209 1364 TablePress::check_nonce( 'preview_table', $table_id );
1210 1365
1211 1366 // Nonce check should actually catch this already.
@@ -1236,14 +1391,16 @@
1236 1391 $default_render_options = apply_filters( 'tablepress_shortcode_table_default_shortcode_atts', $default_render_options );
1237 1392 $render_options = shortcode_atts( $default_render_options, $table['options'] );
1238 1393 /** This filter is documented in controllers/controller-frontend.php */
1239 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;
1240 1397 $_render->set_input( $table, $render_options );
1241 1398 $view_data = array(
1242 - 'table_id' => $table_id,
1243 - 'head_html' => $_render->get_preview_css(),
1244 - 'body_html' => $_render->get_output(),
1245 - '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(),
1246 1403 );
1247 1404
1248 1405 $custom_css = TablePress::$model_options->get( 'custom_css' );
1249 1406 $use_custom_css = ( TablePress::$model_options->get( 'use_custom_css' ) && '' !== $custom_css );
@@ -1260,9 +1417,9 @@
1260 1417 * Shows a list of tables in the Editor toolbar Thickbox (opened by TinyMCE or Quicktags button).
1261 1418 *
1262 1419 * @since 1.0.0
1263 1420 */
1264 - public function handle_get_action_editor_button_thickbox() {
1421 + public function handle_get_action_editor_button_thickbox(): void {
1265 1422 TablePress::check_nonce( 'editor_button_thickbox' );
1266 1423
1267 1424 if ( ! current_user_can( 'tablepress_list_tables' ) ) {
1268 1425 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
@@ -1284,9 +1441,9 @@
1284 1441 * Uninstall TablePress, and delete all tables and options.
1285 1442 *
1286 1443 * @since 1.0.0
1287 1444 */
1288 - public function handle_get_action_uninstall_tablepress() {
1445 + public function handle_get_action_uninstall_tablepress(): void {
1289 1446 TablePress::check_nonce( 'uninstall_tablepress' );
1290 1447
1291 1448 $plugin = TABLEPRESS_BASENAME;
1292 1449
@@ -1306,9 +1463,9 @@
1306 1463
1307 1464 TablePress::$model_table->destroy();
1308 1465 TablePress::$model_options->destroy();
1309 1466
1310 - $output = '<strong>' . __( 'TablePress was uninstalled successfully.', 'tablepress' ) . '</strong><br /><br />';
1467 + $output = '<strong>' . __( 'TablePress was uninstalled successfully.', 'tablepress' ) . '</strong><br><br>';
1311 1468 $output .= __( 'All tables, data, and options were deleted.', 'tablepress' );
1312 1469 if ( is_multisite() ) {
1313 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' );
1314 1471 } else {
@@ -1315,9 +1472,9 @@
1315 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' );
1316 1473 }
1317 1474 if ( $css_files_deleted ) {
1318 1475 $output .= ' ' . __( 'Your TablePress &#8220;Custom CSS&#8221; files have been deleted automatically.', 'tablepress' );
1319 - } else {
1476 + } else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found
1320 1477 if ( is_multisite() ) {
1321 1478 $output .= ' ' . __( 'Please also ask him to delete your TablePress &#8220;Custom CSS&#8221; files from the server.', 'tablepress' );
1322 1479 } else {
1323 1480 $output .= ' ' . __( 'You may now also delete your TablePress &#8220;Custom CSS&#8221; files in the <code>wp-content</code> folder.', 'tablepress' );