PluginProbe
TablePress – Tables in WordPress made easy / 3.2
TablePress – Tables in WordPress made easy v3.2
3.3.4 3.3.3 3.3.2 3.3.1 trunk 1.12 1.14 1.9.2 2.0.4 2.1.7 2.1.8 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3 2.3.1 2.3.2 2.4 2.4.1 2.4.2 2.4.3 2.4.4 All 44 releases
← All changes | controllers/controller-admin.php +301 -147 2.0.43.2 View file →
@@ -25,27 +25,26 @@
25 25 * Page hooks (i.e. names) WordPress uses for the TablePress admin screens,
26 26 * populated in add_admin_menu_entry().
27 27 *
28 28 * @since 1.0.0
29 - * @var array
29 + * @var string[]
30 30 */
31 - protected $page_hooks = array();
31 + protected array $page_hooks = array();
32 32
33 33 /**
34 34 * Actions that have a view and admin menu or nav tab menu entry.
35 35 *
36 36 * @since 1.0.0
37 - * @var array
37 + * @var array<string, array<string, bool|string>>
38 38 */
39 - protected $view_actions = array();
39 + protected array $view_actions = array();
40 40
41 41 /**
42 42 * Instance of the TablePress Admin View that is rendered.
43 43 *
44 44 * @since 1.0.0
45 - * @var TablePress_View
46 45 */
47 - protected $view;
46 + protected \TablePress_View $view;
48 47
49 48 /**
50 49 * Initialize the Admin Controller, determine location the admin menu, set up actions.
51 50 *
@@ -59,9 +58,10 @@
59 58
60 59 add_action( 'admin_menu', array( $this, 'add_admin_menu_entry' ) );
61 60 add_action( 'admin_init', array( $this, 'add_admin_actions' ) );
62 61
63 - add_action( 'enqueue_block_editor_assets', array( $this, 'add_block_editor_js' ) );
62 + add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_block_editor_assets' ) );
63 + add_action( 'enqueue_block_assets', array( $this, 'enqueue_block_assets' ) );
64 64 }
65 65
66 66 /**
67 67 * Handler for changing the number of shown tables in the list of tables (via WP List Table class).
@@ -70,11 +70,11 @@
70 70 *
71 71 * @param mixed $screen_option Current value of the filter (probably bool false).
72 72 * @param string $option Option in which the setting is stored.
73 73 * @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.
74 + * @return int Changed value of the setting
75 75 */
76 - public function save_list_tables_screen_option( $screen_option, $option, $value ) {
76 + public function save_list_tables_screen_option( /* mixed */ $screen_option, string $option, int $value ): int {
77 77 return $value;
78 78 }
79 79
80 80 /**
@@ -81,9 +81,9 @@
81 81 * Add admin screens to the correct place in the admin menu.
82 82 *
83 83 * @since 1.0.0
84 84 */
85 - public function add_admin_menu_entry() {
85 + public function add_admin_menu_entry(): void {
86 86 // Callback for all menu entries.
87 87 $callback = array( $this, 'show_admin_page' );
88 88 /**
89 89 * Filters the TablePress admin menu entry name.
@@ -96,23 +96,27 @@
96 96
97 97 $this->init_view_actions();
98 98 $min_access_cap = $this->view_actions['list']['required_cap'];
99 99
100 - if ( $this->is_top_level_page ) {
101 - $icon_url = 'dashicons-list-view';
102 - switch ( $this->parent_page ) {
100 + if ( TablePress::$controller->is_top_level_page ) {
101 + $icon_url = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgdmlld0JveD0iLTMyIC0zMiA2NCA2NCIgZmlsbD0iI2ZmZiI+PHBhdGggZD0iTTAtMjUuODU0aC0yNS44NTR2NTEuNzA4aDUxLjcwOFYwSDIxdjIxaC00MnYtNDJIMFoiLz48cGF0aCBkPSJNLTE4LTE4aDEwdjEwaC0xMHpNLTE4LTVoMTBWNWgtMTB6TS01LTVINVY1SC01ek0tMTggOGgxMHYxMGgtMTB6TS01IDhINXYxMEgtNXpNOCA4aDEwdjEwSDh6TTUtMzFoNi4xOHY2LjE4SDV6TTE5LTI1aDYuMTh2Ni4xOEgxOXpNMC0xNWgzLjgydjMuODJIMHpNMTAtMjBoMy44MnYzLjgySDEwek0yNS0xMmgzLjgydjMuODJIMjV6TTgtMTNoMTB2MTBIOHoiLz48L3N2Zz4=';
102 + switch ( TablePress::$controller->parent_page ) {
103 103 case 'top':
104 104 $position = 3; // Position of Dashboard + 1.
105 105 break;
106 106 case 'bottom':
107 - $position = ( ++$GLOBALS['_wp_last_utility_menu'] );
107 + $position = isset( $GLOBALS['_wp_last_utility_menu'] ) ? ++$GLOBALS['_wp_last_utility_menu'] : 80;
108 108 break;
109 109 case 'middle':
110 110 default:
111 - $position = ( ++$GLOBALS['_wp_last_object_menu'] );
111 + $position = isset( $GLOBALS['_wp_last_object_menu'] ) ? ++$GLOBALS['_wp_last_object_menu'] : 25;
112 112 break;
113 113 }
114 - add_menu_page( 'TablePress', $admin_menu_entry_name, $min_access_cap, 'tablepress', $callback, $icon_url, $position );
114 + // Prevent overwriting existing menu entries.
115 + while ( isset( $GLOBALS['menu'][ $position ] ) ) {
116 + ++$position;
117 + }
118 + add_menu_page( 'TablePress', $admin_menu_entry_name, $min_access_cap, 'tablepress', $callback, $icon_url, $position ); // @phpstan-ignore argument.type
115 119 foreach ( $this->view_actions as $action => $entry ) {
116 120 if ( ! $entry['show_entry'] ) {
117 121 continue;
118 122 }
@@ -119,12 +123,20 @@
119 123 $slug = 'tablepress';
120 124 if ( 'list' !== $action ) {
121 125 $slug .= '_' . $action;
122 126 }
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 );
127 + /* translators: %1$s: Page title, %2$s: Plugin name (TablePress) */
128 + $page_hook = add_submenu_page( 'tablepress', sprintf( __( '%1$s &lsaquo; %2$s', 'tablepress' ), $entry['page_title'], 'TablePress' ), $entry['admin_menu_title'], $entry['required_cap'], $slug, $callback ); // @phpstan-ignore argument.type, argument.type
129 + if ( false !== $page_hook ) {
130 + $this->page_hooks[] = $page_hook;
131 + }
124 132 }
125 133 } else {
126 - $this->page_hooks[] = add_submenu_page( $this->parent_page, 'TablePress', $admin_menu_entry_name, $min_access_cap, 'tablepress', $callback );
134 + // @phpstan-ignore argument.type
135 + $page_hook = add_submenu_page( TablePress::$controller->parent_page, 'TablePress', $admin_menu_entry_name, $min_access_cap, 'tablepress', $callback );
136 + if ( false !== $page_hook ) {
137 + $this->page_hooks[] = $page_hook;
138 + }
127 139 }
128 140 }
129 141
130 142 /**
@@ -131,9 +143,9 @@
131 143 * Set up handlers for user actions in the backend that exceed plain viewing.
132 144 *
133 145 * @since 1.0.0
134 146 */
135 - public function add_admin_actions() {
147 + public function add_admin_actions(): void {
136 148 // Register the callbacks for processing action requests.
137 149 $post_actions = array( 'list', 'add', 'options', 'export', 'import' );
138 150 $get_actions = array( 'hide_message', 'delete_table', 'copy_table', 'preview_table', 'editor_button_thickbox', 'uninstall_tablepress' );
139 151 foreach ( $post_actions as $action ) {
@@ -147,11 +159,20 @@
147 159 foreach ( $this->page_hooks as $page_hook ) {
148 160 add_action( "load-{$page_hook}", array( $this, 'load_admin_page' ) );
149 161 }
150 162
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' ) );
163 + /**
164 + * Filters whether the legacy editor button should be loaded on the post editing screen.
165 + *
166 + * @since 2.1.0
167 + *
168 + * @param bool $load_button Whether to load the legacy editor button. Default true.
169 + */
170 + if ( apply_filters( 'tablepress_add_legacy_editor_button', true ) ) {
171 + $pages_with_editor_button = array( 'post.php', 'post-new.php' );
172 + foreach ( $pages_with_editor_button as $editor_page ) {
173 + add_action( "load-{$editor_page}", array( $this, 'add_editor_buttons' ) );
174 + }
154 175 }
155 176
156 177 if ( ! is_network_admin() && ! is_user_admin() ) {
157 178 add_action( 'admin_bar_menu', array( $this, 'add_wp_admin_bar_new_content_menu_entry' ), 71 );
@@ -157,22 +178,25 @@
157 178 add_action( 'admin_bar_menu', array( $this, 'add_wp_admin_bar_new_content_menu_entry' ), 71 );
158 179 }
159 180
160 181 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 182 }
167 183
168 184 /**
169 - * Loads additional JavaScript code for the TablePress table block.
185 + * Loads additional JavaScript code for the TablePress table block (in the block editor context).
170 186 *
171 - * @since 2.0.0
187 + * @since 2.2.0
172 188 */
173 - public function add_block_editor_js() {
174 - // Add table information for the Block Editor to the page.
189 + public function enqueue_block_editor_assets(): void {
190 + /*
191 + * Register the `react-jsx-runtime` polyfill, if it is not already registered.
192 + * This is needed as a polyfill for WP < 6.6, and can be removed once WP 6.6 is the minimum requirement for TablePress.
193 + */
194 + if ( ! wp_script_is( 'react-jsx-runtime', 'registered' ) ) {
195 + wp_register_script( 'react-jsx-runtime', plugins_url( 'admin/js/react-jsx-runtime.min.js', TABLEPRESS__FILE__ ), array( 'react' ), TablePress::version, true );
196 + }
197 +
198 + // Add table information for the block editor to the page.
175 199 $handle = generate_block_asset_handle( 'tablepress/table', 'editorScript' );
176 200 $data = $this->get_block_editor_data();
177 201 wp_add_inline_script( $handle, $data, 'before' );
178 202 }
@@ -177,8 +201,20 @@
177 201 wp_add_inline_script( $handle, $data, 'before' );
178 202 }
179 203
180 204 /**
205 + * Loads additional CSS code for the TablePress table block (inside the block editor iframe).
206 + *
207 + * @since 2.2.0
208 + */
209 + public function enqueue_block_assets(): void {
210 + // Load the TablePress default CSS and the user's "Custom CSS" in the block editor iframe.
211 + if ( is_admin() ) {
212 + TablePress::$controller->maybe_enqueue_css();
213 + }
214 + }
215 +
216 + /**
181 217 * Gets the inline data that is referenced by the Block Editor JavaScript code for the TablePress blocks.
182 218 *
183 219 * @since 2.0.0
184 220 *
@@ -183,9 +219,9 @@
183 219 * @since 2.0.0
184 220 *
185 221 * @return string JavaScript code for the Block Editor.
186 222 */
187 - protected function get_block_editor_data() {
223 + protected function get_block_editor_data(): string {
188 224 $tables = array();
189 225 // Load all table IDs without priming the post meta cache, as table options/visibility are not needed.
190 226 $table_ids = TablePress::$model_table->load_all( false );
191 227 foreach ( $table_ids as $table_id ) {
@@ -190,8 +226,14 @@
190 226 $table_ids = TablePress::$model_table->load_all( false );
191 227 foreach ( $table_ids as $table_id ) {
192 228 // Load table, without table data, options, and visibility settings.
193 229 $table = TablePress::$model_table->load( $table_id, false, false );
230 +
231 + // Skip tables that could not be loaded.
232 + if ( is_wp_error( $table ) ) {
233 + continue;
234 + }
235 +
194 236 if ( '' === trim( $table['name'] ) ) {
195 237 $table['name'] = __( '(no name)', 'tablepress' );
196 238 }
197 239 $tables[ $table_id ] = esc_html( $table['name'] );
@@ -201,22 +243,30 @@
201 243 * Filters the list of table IDs and names that is passed to the block editor, and is then used in the dropdown of the TablePress table block.
202 244 *
203 245 * @since 2.0.0
204 246 *
205 - * @param array $tables List of table names, the table ID is the array key.
247 + * @param array<string, string> $tables List of table names, the table ID is the array key.
206 248 */
207 249 $tables = apply_filters( 'tablepress_block_editor_tables_list', $tables );
208 250
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 );
251 + $tables = wp_json_encode( $tables, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES );
252 + if ( false === $tables ) {
253 + // JSON encoding failed, return an error object. Use a prefixed "_error" key to avoid conflicts with intentionally added "error" keys.
254 + $tables = '{ "_error": "The data could not be encoded to JSON!" }';
255 + }
256 + // Print the JSON data inside a `JSON.parse()` call in JS for speed gains, with necessary escaping of `\` and `'`.
257 + $tables = str_replace( array( '\\', "'" ), array( '\\\\', "\'" ), $tables );
212 258
213 259 $shortcode = esc_js( TablePress::$shortcode );
214 260
215 261 $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 );
262 + $template = wp_json_encode( $template['options'], JSON_HEX_TAG | JSON_UNESCAPED_SLASHES );
263 + if ( false === $template ) {
264 + // JSON encoding failed, return an error object. Use a prefixed "_error" key to avoid conflicts with intentionally added "error" keys.
265 + $template = '{ "_error": "The data could not be encoded to JSON!" }';
266 + }
267 + // Print the JSON data inside a `JSON.parse()` call in JS for speed gains, with necessary escaping of `\` and `'`.
268 + $template = str_replace( array( '\\', "'" ), array( '\\\\', "\'" ), $template );
219 269
220 270 /**
221 271 * Filters whether the table block preview should be loaded via a <ServerSideRender> in the block editor.
222 272 *
@@ -232,17 +282,17 @@
232 282 $url = TablePress::url( array( 'action' => 'list' ) );
233 283 }
234 284
235 285 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;
286 + // Ensure the global `tp` object exists.
287 + window.tp = window.tp || {};
288 + tp.url = '{$url}';
289 + tp.load_block_preview = {$load_block_preview};
290 + tp.table = {};
291 + tp.table.shortcode = '{$shortcode}';
292 + tp.table.template = JSON.parse( '{$template}' );
293 + tp.tables = JSON.parse( '{$tables}' );
294 + JS;
245 295 }
246 296
247 297 /**
248 298 * Register actions to add "Table" button to "HTML editor" and "Visual editor" toolbars.
@@ -248,15 +298,15 @@
248 298 * Register actions to add "Table" button to "HTML editor" and "Visual editor" toolbars.
249 299 *
250 300 * @since 1.0.0
251 301 */
252 - public function add_editor_buttons() {
302 + public function add_editor_buttons(): void {
253 303 if ( ! current_user_can( 'tablepress_list_tables' ) ) {
254 304 return;
255 305 }
256 306
257 307 // Only load the toolbar integration if the Block Editor is not used.
258 - if ( TablePress::site_uses_block_editor() ) {
308 + if ( 'block' === TablePress::site_used_editor() ) {
259 309 return;
260 310 }
261 311
262 312 add_thickbox(); // The files are usually already loaded by media upload functions.
@@ -270,9 +320,9 @@
270 320 'title' => __( 'Insert a TablePress table', 'tablepress' ),
271 321 'thickbox_title' => __( 'Insert a TablePress table', 'tablepress' ),
272 322 'thickbox_url' => TablePress::url( array( 'action' => 'editor_button_thickbox' ), true, 'admin-post.php' ),
273 323 ),
274 - )
324 + ),
275 325 );
276 326
277 327 // TinyMCE integration.
278 328 if ( user_can_richedit() ) {
@@ -285,12 +335,12 @@
285 335 * Adds the "Table" button to the TinyMCE toolbar.
286 336 *
287 337 * @since 1.0.0
288 338 *
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.
339 + * @param string[] $buttons Current set of buttons in the TinyMCE toolbar.
340 + * @return string[] Extended set of buttons in the TinyMCE toolbar, including the "Table" button.
291 341 */
292 - public function add_tinymce_button( array $buttons ) {
342 + public function add_tinymce_button( array $buttons ): array {
293 343 $buttons[] = 'tablepress_insert_table';
294 344 return $buttons;
295 345 }
296 346
@@ -298,12 +348,12 @@
298 348 * Registers the "Table" button plugin for the TinyMCE editor.
299 349 *
300 350 * @since 1.0.0
301 351 *
302 - * @param array $plugins Current set of registered TinyMCE plugins.
303 - * @return array Extended set of registered TinyMCE plugins, including the "Table" button plugin.
352 + * @param array<string, string> $plugins Current set of registered TinyMCE plugins.
353 + * @return array<string, string> Extended set of registered TinyMCE plugins, including the "Table" button plugin.
304 354 */
305 - public function add_tinymce_plugin( array $plugins ) {
355 + public function add_tinymce_plugin( array $plugins ): array {
306 356 $plugins['tablepress_tinymce'] = plugins_url( 'admin/js/build/tinymce-button.js', TABLEPRESS__FILE__ );
307 357 return $plugins;
308 358 }
309 359
@@ -313,9 +363,9 @@
313 363 * @since 1.0.0
314 364 *
315 365 * @param WP_Admin_Bar $wp_admin_bar The current WP Admin Bar object.
316 366 */
317 - public function add_wp_admin_bar_new_content_menu_entry( $wp_admin_bar ) {
367 + public function add_wp_admin_bar_new_content_menu_entry( WP_Admin_Bar $wp_admin_bar ): void {
318 368 if ( ! current_user_can( 'tablepress_add_tables' ) ) {
319 369 return;
320 370 }
321 371
@@ -326,9 +376,9 @@
326 376
327 377 $wp_admin_bar->add_menu( array(
328 378 'parent' => 'new-content',
329 379 'id' => 'new-tablepress-table',
330 - 'title' => __( 'TablePress Table', 'tablepress' ),
380 + 'title' => __( 'TablePress table', 'tablepress' ),
331 381 'href' => TablePress::url( array( 'action' => 'add' ) ),
332 382 ) );
333 383 }
334 384
@@ -336,12 +386,25 @@
336 386 * Handle actions for loading of Plugins page.
337 387 *
338 388 * @since 1.0.0
339 389 */
340 - public function plugins_page() {
390 + public function plugins_page(): void {
341 391 // Add additional links on Plugins page.
342 392 add_filter( 'plugin_action_links_' . TABLEPRESS_BASENAME, array( $this, 'add_plugin_action_links' ) );
343 393 add_filter( 'plugin_row_meta', array( $this, 'add_plugin_row_meta' ), 10, 2 );
394 + $incompatible_superseded_extensions = array(
395 + 'tablepress-datatables-alphabetsearch/tablepress-datatables-alphabetsearch.php',
396 + 'tablepress-datatables-column-filter-widgets/tablepress-datatables-column-filter-widgets.php',
397 + 'tablepress-datatables-columnfilter/tablepress-datatables-columnfilter.php',
398 + 'tablepress-datatables-fixedcolumns/tablepress-datatables-fixedcolumns.php',
399 + 'tablepress-datatables-inverted-filter/tablepress-datatables-inverted-filter.php',
400 + 'tablepress-datatables-row-details/tablepress-datatables-row-details.php',
401 + 'tablepress-datatables-rowgroup/tablepress-datatables-rowgroup.php',
402 + 'tablepress-responsive-tables/tablepress-responsive-tables.php',
403 + );
404 + foreach ( $incompatible_superseded_extensions as $plugin_file ) {
405 + add_action( "after_plugin_row_{$plugin_file}", array( $this, 'add_superseded_extension_meta_row' ), 10, 3 );
406 + }
344 407 }
345 408
346 409 /**
347 410 * Add links to the TablePress entry in the "Plugin" column on the Plugins page.
@@ -347,14 +410,14 @@
347 410 * Add links to the TablePress entry in the "Plugin" column on the Plugins page.
348 411 *
349 412 * @since 1.0.0
350 413 *
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.
414 + * @param string[] $links List of links to print in the "Plugin" column on the Plugins page.
415 + * @return string[] Extended list of links to print in the "Plugin" column on the Plugins page.
353 416 */
354 - public function add_plugin_action_links( array $links ) {
417 + public function add_plugin_action_links( array $links ): array {
355 418 if ( current_user_can( 'tablepress_list_tables' ) ) {
356 - $links[] = '<a href="' . TablePress::url() . '">' . __( 'Plugin page', 'tablepress' ) . '</a>';
419 + $links[] = '<a href="' . esc_url( TablePress::url() ) . '">' . __( 'Plugin page', 'tablepress' ) . '</a>';
357 420 }
358 421 return $links;
359 422 }
360 423
@@ -362,19 +425,19 @@
362 425 * Add links to the TablePress entry in the "Description" column on the Plugins page.
363 426 *
364 427 * @since 1.0.0
365 428 *
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.
429 + * @param string[] $links List of links to print in the "Description" column on the Plugins page.
430 + * @param string $file Name of the plugin.
431 + * @return string[] Extended list of links to print in the "Description" column on the Plugins page.
369 432 */
370 - public function add_plugin_row_meta( array $links, $file ) {
433 + public function add_plugin_row_meta( array $links, string $file ): array {
371 434 if ( TABLEPRESS_BASENAME === $file ) {
372 435 $links[] = '<a href="https://tablepress.org/faq/" title="' . esc_attr__( 'Frequently Asked Questions', 'tablepress' ) . '">' . __( 'FAQ', 'tablepress' ) . '</a>';
373 436 $links[] = '<a href="https://tablepress.org/documentation/">' . __( 'Documentation', 'tablepress' ) . '</a>';
374 437 $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>';
438 + if ( ! TABLEPRESS_IS_PLAYGROUND_PREVIEW && tb_tp_fs()->is_free_plan() ) {
439 + $links[] = '<a href="https://tablepress.org/premium/?utm_source=plugin&utm_medium=textlink&utm_content=plugins-screen" title="' . esc_attr__( 'Check out the Premium version of TablePress!', 'tablepress' ) . '"><strong>' . __( 'Go Premium', 'tablepress' ) . '</strong></a>';
377 440 }
378 441 }
379 442 return $links;
380 443 }
@@ -379,16 +442,62 @@
379 442 return $links;
380 443 }
381 444
382 445 /**
446 + * Prints a superseded extension notice below certain TablePress Extension plugins' meta rows on the "Plugins" screen.
447 + *
448 + * @since 2.4.1
449 + *
450 + * @param string $plugin_file Path to the plugin file relative to the plugins directory.
451 + * @param array<int, string|string[]|bool> $plugin_data An array of plugin data.
452 + * @param string $status Status filter currently applied to the plugin list.
453 + */
454 + public function add_superseded_extension_meta_row( string $plugin_file, array $plugin_data, string $status ): void {
455 + if ( ! is_plugin_active( $plugin_file ) ) {
456 + return;
457 + }
458 + ?>
459 + <tr class="plugin-update-tr active">
460 + <td colspan="<?php echo esc_attr( $GLOBALS['wp_list_table']->get_column_count() ); ?>" class="plugin-update colspanchange">
461 + <div class="update-message notice inline notice-error notice-alt">
462 + <?php
463 + if ( tb_tp_fs()->is_free_plan() ) {
464 + echo '<p style="font-size:14px;">';
465 + _e( 'This TablePress Extension was retired.', 'tablepress' );
466 + echo ' ';
467 + _e( '<strong>The plugin does no longer work with TablePress 3</strong> and will no longer receive updates or support!', 'tablepress' );
468 + echo '<br>';
469 + _e( 'Keeping it activated can lead to errors on your website!', 'tablepress' );
470 + echo ' <strong>' . sprintf( __( '<a href="%s">Find out what you can do to continue using its features!</a>', 'tablepress' ), 'https://tablepress.org/upgrade-extensions/?utm_source=plugin&utm_medium=textlink&utm_content=plugins-list-table' ) . '</strong>';
471 + echo '</p>';
472 + }
473 + ?>
474 + <style>
475 + /* Remove the separator line between the plugin's and the notice's table row. */
476 + .plugins .active[data-plugin="<?php echo $plugin_file; ?>"] th,
477 + .plugins .active[data-plugin="<?php echo $plugin_file; ?>"] td {
478 + box-shadow: none;
479 + }
480 + /* Hide the plugin update row for the Extension as those won't work anymore anyways. */
481 + .plugins .plugin-update-tr[data-plugin="<?php echo $plugin_file; ?>"] {
482 + display: none;
483 + }
484 + </style>
485 + </div>
486 + </td>
487 + </tr>
488 + <?php
489 + }
490 +
491 + /**
383 492 * Prepare the rendering of an admin screen, by determining the current action, loading necessary data and initializing the view.
384 493 *
385 494 * @since 1.0.0
386 495 */
387 - public function load_admin_page() {
496 + public function load_admin_page(): void {
388 497 // Determine the action from either the GET parameter (for sub-menu entries, and the main admin menu entry).
389 498 $action = ( ! empty( $_GET['action'] ) ) ? $_GET['action'] : 'list'; // Default action is list.
390 - if ( $this->is_top_level_page ) {
499 + if ( TablePress::$controller->is_top_level_page ) {
391 500 // Or, for sub-menu entry of an admin menu "TablePress" entry, get it from the "page" GET parameter.
392 501 if ( 'tablepress' !== $_GET['page'] ) {
393 502 // Actions that are top-level entries, but don't have an action GET parameter (action is after last _ in string).
394 503 $action = substr( $_GET['page'], 11 ); // $_GET['page'] has the format 'tablepress_{$action}'
@@ -395,9 +504,9 @@
395 504 }
396 505 }
397 506
398 507 // 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'] ) ) {
508 + if ( ! isset( $this->view_actions[ $action ] ) || ! current_user_can( $this->view_actions[ $action ]['required_cap'] ) ) { // @phpstan-ignore argument.type (The array value for the capability is always a string.)
400 509 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
401 510 }
402 511
403 512 // Don't load TablePress assets on the Freemius opt-in/activation screen.
@@ -412,17 +521,17 @@
412 521 * Set the `$typenow` global to the current CPT ourselves, as `WP_Screen::get()` does not determine the CPT correctly.
413 522 * This is necessary as the WP Admin Menu can otherwise highlight wrong entries, see https://github.com/TablePress/TablePress/issues/24.
414 523 */
415 524 if ( isset( $_GET['post_type'] ) && post_type_exists( $_GET['post_type'] ) ) {
416 - $GLOBALS['typenow'] = $_GET['post_type'];
525 + $GLOBALS['typenow'] = $_GET['post_type']; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
417 526 }
418 527
419 528 // Pre-define some view data.
420 529 $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(),
530 + 'view_actions' => $this->view_actions,
531 + 'message' => ( ! empty( $_GET['message'] ) ) ? $_GET['message'] : false,
532 + 'error_details' => ( ! empty( $_GET['error_details'] ) ) ? $_GET['error_details'] : '',
533 + 'site_used_editor' => TablePress::site_used_editor(),
425 534 );
426 535
427 536 // Depending on the action, load more necessary data for the corresponding view.
428 537 switch ( $action ) {
@@ -429,17 +538,16 @@
429 538 case 'list':
430 539 $data['table_id'] = ( ! empty( $_GET['table_id'] ) ) ? $_GET['table_id'] : false;
431 540 // Prime the post meta cache for cached loading of last_editor.
432 541 $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();
542 + $data['messages']['donation_nag'] = $this->maybe_show_donation_message();
543 + $data['messages']['first_visit'] = ! $data['messages']['donation_nag'] && TablePress::$model_options->get( 'message_first_visit' );
544 + $data['messages']['plugin_update'] = TablePress::$model_options->get( 'message_plugin_update' );
545 + $data['messages']['superseded_extensions'] = current_user_can( 'manage_options' ) && TablePress::$model_options->get( 'message_superseded_extensions' );
436 546 $data['table_count'] = count( $data['table_ids'] );
437 547 break;
438 548 case 'about':
439 549 $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 550 break;
443 551 case 'options':
444 552 /*
445 553 * Maybe try saving "Custom CSS" to a file:
@@ -446,9 +554,9 @@
446 554 * (called here, as the credentials form posts to this handler again, due to how `request_filesystem_credentials()` works)
447 555 */
448 556 if ( isset( $_GET['item'] ) && 'save_custom_css' === $_GET['item'] ) {
449 557 TablePress::check_nonce( 'options', $_GET['item'] ); // Nonce check here, as we don't have an explicit handler, and even viewing the screen needs to be checked.
450 - $action = 'options_custom_css'; // to load a different view
558 + $action = 'options_custom_css'; // To load a different view.
451 559 // Try saving "Custom CSS" to a file, otherwise this gets the HTML for the credentials form.
452 560 $tablepress_css = TablePress::load_class( 'TablePress_CSS', 'class-css.php', 'classes' );
453 561 $result = $tablepress_css->save_custom_css_to_file_plugin_options( TablePress::$model_options->get( 'custom_css' ), TablePress::$model_options->get( 'custom_css_minified' ) );
454 562 if ( is_string( $result ) ) {
@@ -469,9 +577,9 @@
469 577 break;
470 578 }
471 579 $data['frontend_options']['use_custom_css'] = TablePress::$model_options->get( 'use_custom_css' );
472 580 $data['frontend_options']['custom_css'] = TablePress::$model_options->get( 'custom_css' );
473 - $data['user_options']['parent_page'] = $this->parent_page;
581 + $data['user_options']['parent_page'] = TablePress::$controller->parent_page;
474 582 break;
475 583 case 'edit':
476 584 if ( empty( $_GET['table_id'] ) ) {
477 585 TablePress::redirect( array( 'action' => 'list', 'message' => 'error_no_table' ) );
@@ -486,29 +594,54 @@
486 594 }
487 595 break;
488 596 case 'export':
489 597 // 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 );
598 + $table_ids = TablePress::$model_table->load_all( false );
599 + $data['tables'] = array();
600 + foreach ( $table_ids as $table_id ) {
601 + if ( ! current_user_can( 'tablepress_export_table', $table_id ) ) {
602 + continue;
603 + }
604 + // Load table, without table data, options, and visibility settings.
605 + $table = TablePress::$model_table->load( $table_id, false, false );
606 +
607 + // Skip tables that could not be loaded.
608 + if ( is_wp_error( $table ) ) {
609 + continue;
610 + }
611 +
612 + $data['tables'][ $table['id'] ] = $table['name'];
613 + }
491 614 $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 - }
615 + $data['export_ids'] = ( ! empty( $_GET['table_id'] ) ) ? explode( ',', $_GET['table_id'] ) : array();
498 616 $exporter = TablePress::load_class( 'TablePress_Export', 'class-export.php', 'classes' );
499 617 $data['zip_support_available'] = $exporter->zip_support_available;
500 618 $data['export_formats'] = $exporter->export_formats;
501 619 $data['csv_delimiters'] = $exporter->csv_delimiters;
502 - $data['export_format'] = ( ! empty( $_GET['export_format'] ) ) ? $_GET['export_format'] : false;
620 + $data['export_format'] = ( ! empty( $_GET['export_format'] ) ) ? $_GET['export_format'] : 'csv';
503 621 $data['csv_delimiter'] = ( ! empty( $_GET['csv_delimiter'] ) ) ? $_GET['csv_delimiter'] : _x( ',', 'Default CSV delimiter in the translated language (";", ",", or "tab")', 'tablepress' );
504 622 break;
505 623 case 'import':
506 624 // 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 );
625 + $table_ids = TablePress::$model_table->load_all( false );
626 + $data['tables'] = array();
627 + foreach ( $table_ids as $table_id ) {
628 + if ( ! current_user_can( 'tablepress_edit_table', $table_id ) ) {
629 + continue;
630 + }
631 + // Load table, without table data, options, and visibility settings.
632 + $table = TablePress::$model_table->load( $table_id, false, false );
633 +
634 + // Skip tables that could not be loaded.
635 + if ( is_wp_error( $table ) ) {
636 + continue;
637 + }
638 +
639 + $data['tables'][ $table['id'] ] = $table['name'];
640 + }
641 + $data['table_ids'] = $table_ids; // Backward compatibility for the retired "Table Auto Update" Extension, which still relies on this variable name.
508 642 $data['tables_count'] = TablePress::$model_table->count_tables();
509 643 $importer = TablePress::load_class( 'TablePress_Import', 'class-import.php', 'classes' );
510 - $data['zip_support_available'] = $importer->zip_support_available;
511 644 $data['import_type'] = ( ! empty( $_GET['import_type'] ) ) ? $_GET['import_type'] : 'add';
512 645 $data['import_existing_table'] = ( ! empty( $_GET['import_existing_table'] ) ) ? $_GET['import_existing_table'] : '';
513 646 $data['import_source'] = ( ! empty( $_GET['import_source'] ) ) ? $_GET['import_source'] : 'file-upload';
514 647 $data['import_url'] = ( ! empty( $_GET['import_url'] ) ) ? wp_unslash( $_GET['import_url'] ) : 'https://';
@@ -522,10 +655,10 @@
522 655 * Filters the data that is passed to the current TablePress View.
523 656 *
524 657 * @since 1.0.0
525 658 *
526 - * @param array $data Data for the view.
527 - * @param string $action The current action for the view.
659 + * @param array<string, mixed> $data Data for the view.
660 + * @param string $action The current action for the view.
528 661 */
529 662 $data = apply_filters( 'tablepress_view_data', $data, $action );
530 663
531 664 // Prepare and initialize the view.
@@ -536,9 +669,9 @@
536 669 * Render the view that has been initialized in load_admin_page() (called by WordPress when the actual page content is needed).
537 670 *
538 671 * @since 1.0.0
539 672 */
540 - public function show_admin_page() {
673 + public function show_admin_page(): void {
541 674 $this->view->render();
542 675 }
543 676
544 677 /**
@@ -547,9 +680,9 @@
547 680 * @since 1.0.0
548 681 *
549 682 * @return bool Whether the message shall be shown on the "All Tables" screen.
550 683 */
551 - protected function maybe_show_donation_message() {
684 + protected function maybe_show_donation_message(): bool {
552 685 // Only show the message to plugin admins.
553 686 if ( ! current_user_can( 'tablepress_edit_options' ) ) {
554 687 return false;
555 688 }
@@ -567,9 +700,9 @@
567 700 * Init list of actions that have a view with their titles/names/caps.
568 701 *
569 702 * @since 1.0.0
570 703 */
571 - protected function init_view_actions() {
704 + protected function init_view_actions(): void {
572 705 $this->view_actions = array(
573 706 'list' => array(
574 707 'show_entry' => true,
575 708 'page_title' => __( 'All Tables', 'tablepress' ),
@@ -625,9 +758,9 @@
625 758 * Filters the available TablePres Views/Actions and their parameters.
626 759 *
627 760 * @since 1.0.0
628 761 *
629 - * @param array $view_actions The available Views/Actions and their parameters.
762 + * @param array<string, array<string, bool|string>> $view_actions The available Views/Actions and their parameters.
630 763 */
631 764 $this->view_actions = apply_filters( 'tablepress_admin_view_actions', $this->view_actions );
632 765 }
633 766
@@ -639,9 +772,9 @@
639 772 * Handle Bulk Actions (Copy, Export, Delete) on "All Tables" list screen.
640 773 *
641 774 * @since 1.0.0
642 775 */
643 - public function handle_post_action_list() {
776 + public function handle_post_action_list(): void {
644 777 TablePress::check_nonce( 'list' );
645 778
646 779 if ( isset( $_POST['bulk-action-selector-top'] ) && '-1' !== $_POST['bulk-action-selector-top'] ) {
647 780 $bulk_action = $_POST['bulk-action-selector-top'];
@@ -682,9 +815,9 @@
682 815 * To export, redirect to "Export" screen, with selected table IDs.
683 816 */
684 817 $table_ids = implode( ',', $tables );
685 818 TablePress::redirect( array( 'action' => 'export', 'table_id' => $table_ids ) );
686 - break;
819 + // break; // unreachable.
687 820 case 'delete':
688 821 foreach ( $tables as $table_id ) {
689 822 if ( current_user_can( 'tablepress_delete_table', $table_id ) ) {
690 823 $deleted = TablePress::$model_table->delete( $table_id );
@@ -697,9 +830,9 @@
697 830 }
698 831 break;
699 832 }
700 833
701 - if ( 0 !== count( $no_success ) ) { // @TODO: maybe pass this information to the view?
834 + if ( 0 !== count( $no_success ) ) { // @todo maybe pass this information to the view?
702 835 $message = "error_{$bulk_action}_not_all_tables";
703 836 } else {
704 837 $plural = ( count( $tables ) > 1 ) ? '_plural' : '';
705 838 $message = "success_{$bulk_action}{$plural}";
@@ -724,9 +857,9 @@
724 857 * Add a table, according to the parameters on the "Add new Table" screen.
725 858 *
726 859 * @since 1.0.0
727 860 */
728 - public function handle_post_action_add() {
861 + public function handle_post_action_add(): void {
729 862 TablePress::check_nonce( 'add' );
730 863
731 864 if ( ! current_user_can( 'tablepress_add_tables' ) ) {
732 865 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
@@ -737,11 +870,11 @@
737 870 }
738 871
739 872 $add_table = wp_unslash( $_POST['table'] );
740 873
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'] : '';
874 + // Perform confidence checks of posted data.
875 + $name = $add_table['name'] ?? '';
876 + $description = $add_table['description'] ?? '';
744 877 if ( ! isset( $add_table['rows'], $add_table['columns'] ) ) {
745 878 TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add', 'error_details' => 'The HTTP POST data does not contain the table size.' ) );
746 879 }
747 880
@@ -780,9 +913,9 @@
780 913 * Save changed "Plugin Options".
781 914 *
782 915 * @since 1.0.0
783 916 */
784 - public function handle_post_action_options() {
917 + public function handle_post_action_options(): void {
785 918 TablePress::check_nonce( 'options' );
786 919
787 920 if ( ! current_user_can( 'tablepress_access_options_screen' ) ) {
788 921 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
@@ -801,10 +934,10 @@
801 934 if ( ! empty( $posted_options['admin_menu_parent_page'] ) && '-' !== $posted_options['admin_menu_parent_page'] ) {
802 935 $new_options['admin_menu_parent_page'] = $posted_options['admin_menu_parent_page'];
803 936 // Re-init parent information, as `TablePress::redirect()` URL might be wrong otherwise.
804 937 /** 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 );
938 + TablePress::$controller->parent_page = apply_filters( 'tablepress_admin_menu_parent_page', $posted_options['admin_menu_parent_page'] );
939 + TablePress::$controller->is_top_level_page = in_array( TablePress::$controller->parent_page, array( 'top', 'middle', 'bottom' ), true );
807 940 }
808 941
809 942 // Custom CSS can only be saved if the user is allowed to do so.
810 943 $update_custom_css_files = false;
@@ -815,13 +948,20 @@
815 948 if ( isset( $posted_options['custom_css'] ) ) {
816 949 $new_options['custom_css'] = $posted_options['custom_css'];
817 950
818 951 $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 952
953 + if ( '' !== $new_options['custom_css'] ) {
954 + // Update "Custom CSS" to use DataTables 2 variants instead of old DataTables 1.x CSS classes.
955 + $new_options['custom_css'] = TablePress::convert_datatables_api_data( $new_options['custom_css'] );
956 + // Sanitize and tidy up Custom CSS.
957 + $new_options['custom_css'] = $tablepress_css->sanitize_css( $new_options['custom_css'] );
958 + // Minify Custom CSS.
959 + $new_options['custom_css_minified'] = $tablepress_css->minify_css( $new_options['custom_css'] );
960 + } else {
961 + $new_options['custom_css_minified'] = '';
962 + }
963 +
824 964 // Maybe update CSS files as well.
825 965 $custom_css_file_contents = $tablepress_css->load_custom_css_from_file( 'normal' );
826 966 if ( false === $custom_css_file_contents ) {
827 967 $custom_css_file_contents = '';
@@ -852,9 +992,9 @@
852 992 * Export selected tables.
853 993 *
854 994 * @since 1.0.0
855 995 */
856 - public function handle_post_action_export() {
996 + public function handle_post_action_export(): void {
857 997 TablePress::check_nonce( 'export' );
858 998
859 999 if ( ! current_user_can( 'tablepress_export_tables' ) ) {
860 1000 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
@@ -865,12 +1005,13 @@
865 1005 }
866 1006
867 1007 $export = wp_unslash( $_POST['export'] );
868 1008
869 - if ( empty( $export['tables'] ) ) {
1009 + if ( empty( $export['tables_list'] ) ) {
870 1010 TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export', 'error_details' => 'The HTTP POST data does not contain tables.' ) );
871 1011 }
872 1012
1013 + /** @var TablePress_Export $exporter */ // phpcs:ignore Generic.Commenting.DocComment.MissingShort
873 1014 $exporter = TablePress::load_class( 'TablePress_Export', 'class-export.php', 'classes' );
874 1015
875 1016 if ( empty( $export['format'] ) || ! isset( $exporter->export_formats[ $export['format'] ] ) ) {
876 1017 TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export', 'error_details' => 'The export format is invalid.' ) );
@@ -882,10 +1023,9 @@
882 1023 if ( 'csv' === $export['format'] && ! isset( $exporter->csv_delimiters[ $export['csv_delimiter'] ] ) ) {
883 1024 TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export', 'error_details' => 'The CSV delimiter is invalid.' ) );
884 1025 }
885 1026
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'];
1027 + $tables = explode( ',', $export['tables_list'] );
888 1028
889 1029 // Determine if ZIP file support is available.
890 1030 if ( $exporter->zip_support_available
891 1031 && ( ( isset( $export['zip_file'] ) && 'true' === $export['zip_file'] ) || count( $tables ) > 1 ) ) {
@@ -895,9 +1035,9 @@
895 1035 $export_to_zip = false;
896 1036 }
897 1037
898 1038 if ( ! $export_to_zip ) {
899 - // This is only possible for one table, so take the first one.
1039 + // Exporting without a ZIP file is only possible for one table, so take the first one.
900 1040 if ( ! current_user_can( 'tablepress_export_table', $tables[0] ) ) {
901 1041 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
902 1042 }
903 1043 // Load table, with table data, options, and visibility settings.
@@ -928,20 +1068,21 @@
928 1068 * Filters the exported table data.
929 1069 *
930 1070 * @since 1.6.0
931 1071 *
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.
1072 + * @param string $export_data The exported table data.
1073 + * @param array<string, mixed> $table Table to be exported.
1074 + * @param string $export_format Format for the export ('csv', 'html', 'json').
1075 + * @param string $csv_delimiter Delimiter for CSV export.
936 1076 */
937 1077 $export_data = apply_filters( 'tablepress_export_data', $export_data, $table, $export['format'], $export['csv_delimiter'] );
938 1078 $download_data = $export_data;
939 1079 } else {
940 1080 // 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
1081 + wp_raise_memory_limit( 'admin' );
1082 + if ( function_exists( 'set_time_limit' ) ) {
1083 + @set_time_limit( 300 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
1084 + }
944 1085
945 1086 $zip_file = new ZipArchive();
946 1087 $download_filename = sprintf( 'tablepress-export-%1$s-%2$s.zip', wp_date( 'Y-m-d-H-i-s' ), $export['format'] );
947 1088 /** This filter is documented in controllers/controller-admin.php */
@@ -947,10 +1088,10 @@
947 1088 /** This filter is documented in controllers/controller-admin.php */
948 1089 $download_filename = apply_filters( 'tablepress_export_filename', $download_filename, '', '', $export['format'], $export_to_zip );
949 1090 $download_filename = sanitize_file_name( $download_filename );
950 1091 $full_filename = wp_tempnam( $download_filename );
951 - if ( true !== $zip_file->open( $full_filename, ZIPARCHIVE::OVERWRITE ) ) {
952 - @unlink( $full_filename );
1092 + if ( true !== $zip_file->open( $full_filename, ZipArchive::OVERWRITE ) ) {
1093 + @unlink( $full_filename ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
953 1094 TablePress::redirect( array( 'action' => 'export', 'message' => 'error_create_zip_file', 'export_format' => $export['format'], 'csv_delimiter' => $export['csv_delimiter'], 'error_details' => 'The ZIP file could not be opened for writing.' ) );
954 1095 }
955 1096
956 1097 foreach ( $tables as $table_id ) {
@@ -979,11 +1120,11 @@
979 1120 }
980 1121
981 1122 // If something went wrong, or no files were added to the ZIP file, bail out.
982 1123 // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
983 - if ( ! ZIPARCHIVE::ER_OK === $zip_file->status || 0 === $zip_file->numFiles ) {
1124 + if ( ZipArchive::ER_OK !== $zip_file->status || 0 === $zip_file->numFiles ) {
984 1125 $zip_file->close();
985 - @unlink( $full_filename );
1126 + @unlink( $full_filename ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
986 1127 TablePress::redirect( array( 'action' => 'export', 'message' => 'error_create_zip_file', 'export_format' => $export['format'], 'csv_delimiter' => $export['csv_delimiter'], 'error_details' => 'The ZIP file could not be written or is empty.' ) );
987 1128 }
988 1129 $zip_file->close();
989 1130
@@ -988,9 +1129,13 @@
988 1129 $zip_file->close();
989 1130
990 1131 // Load contents of the ZIP file, to send it as a download.
991 1132 $download_data = file_get_contents( $full_filename );
992 - @unlink( $full_filename );
1133 + if ( false === $download_data ) {
1134 + @unlink( $full_filename ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
1135 + TablePress::redirect( array( 'action' => 'export', 'message' => 'error_create_zip_file', 'export_format' => $export['format'], 'csv_delimiter' => $export['csv_delimiter'], 'error_details' => 'The ZIP file content could not be read.' ) );
1136 + }
1137 + @unlink( $full_filename ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
993 1138 }
994 1139
995 1140 // Send download headers for export file.
996 1141 header( 'Content-Description: File Transfer' );
@@ -1013,9 +1158,9 @@
1013 1158 * Import data from existing source (Upload, URL, Server, Direct input).
1014 1159 *
1015 1160 * @since 1.0.0
1016 1161 */
1017 - public function handle_post_action_import() {
1162 + public function handle_post_action_import(): void {
1018 1163 TablePress::check_nonce( 'import' );
1019 1164
1020 1165 if ( ! current_user_can( 'tablepress_import_tables' ) ) {
1021 1166 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
@@ -1037,10 +1182,17 @@
1037 1182 TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import', 'error_details' => 'You do not have the required access rights.' ) );
1038 1183 }
1039 1184 }
1040 1185
1186 + // For security reasons, the "url" source is only available admins and editors via a custom capability.
1187 + if ( 'url' === $import_config['source'] ) {
1188 + if ( ! current_user_can( 'tablepress_import_tables_url' ) ) {
1189 + TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import', 'error_details' => 'You do not have the required access rights.' ) );
1190 + }
1191 + }
1192 +
1041 1193 // Move file upload data to the main import configuration.
1042 - $import_config['file-upload'] = isset( $_FILES['import_file_upload'] ) ? $_FILES['import_file_upload'] : null;
1194 + $import_config['file-upload'] = $_FILES['import_file_upload'] ?? null;
1043 1195
1044 1196 // Check if the source data for the chosen import source is defined.
1045 1197 if ( empty( $import_config[ $import_config['source'] ] ) ) {
1046 1198 TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import', 'error_details' => 'The HTTP POST data does not contain an import source.' ) );
@@ -1075,9 +1227,9 @@
1075 1227 $redirect_parameters['error_details'] = TablePress::get_wp_error_string( $import );
1076 1228 } elseif ( 0 < count( $import['errors'] ) ) {
1077 1229 $wp_error_strings = array();
1078 1230 foreach ( $import['errors'] as $file ) {
1079 - $wp_error_strings[] = TablePress::get_wp_error_string( $file['error'] );
1231 + $wp_error_strings[] = TablePress::get_wp_error_string( $file->error );
1080 1232 }
1081 1233 $redirect_parameters['error_details'] = implode( ', ', $wp_error_strings );
1082 1234 }
1083 1235 TablePress::redirect( $redirect_parameters );
@@ -1101,9 +1253,9 @@
1101 1253 * Hide a header message on an admin screen.
1102 1254 *
1103 1255 * @since 1.0.0
1104 1256 */
1105 - public function handle_get_action_hide_message() {
1257 + public function handle_get_action_hide_message(): void {
1106 1258 $message_item = ! empty( $_GET['item'] ) ? $_GET['item'] : '';
1107 1259 TablePress::check_nonce( 'hide_message', $message_item );
1108 1260
1109 1261 if ( ! current_user_can( 'tablepress_list_tables' ) ) {
@@ -1120,9 +1272,9 @@
1120 1272 * Delete a table.
1121 1273 *
1122 1274 * @since 1.0.0
1123 1275 */
1124 - public function handle_get_action_delete_table() {
1276 + public function handle_get_action_delete_table(): void {
1125 1277 $table_id = ( ! empty( $_GET['item'] ) ) ? $_GET['item'] : false;
1126 1278 TablePress::check_nonce( 'delete_table', $table_id );
1127 1279
1128 1280 $return = ! empty( $_GET['return'] ) ? $_GET['return'] : 'list';
@@ -1161,9 +1313,9 @@
1161 1313 * Copy a table.
1162 1314 *
1163 1315 * @since 1.0.0
1164 1316 */
1165 - public function handle_get_action_copy_table() {
1317 + public function handle_get_action_copy_table(): void {
1166 1318 $table_id = ( ! empty( $_GET['item'] ) ) ? $_GET['item'] : false;
1167 1319 TablePress::check_nonce( 'copy_table', $table_id );
1168 1320
1169 1321 $return = ! empty( $_GET['return'] ) ? $_GET['return'] : 'list';
@@ -1203,9 +1355,9 @@
1203 1355 * Preview a table.
1204 1356 *
1205 1357 * @since 1.0.0
1206 1358 */
1207 - public function handle_get_action_preview_table() {
1359 + public function handle_get_action_preview_table(): void {
1208 1360 $table_id = ( ! empty( $_GET['item'] ) ) ? $_GET['item'] : false;
1209 1361 TablePress::check_nonce( 'preview_table', $table_id );
1210 1362
1211 1363 // Nonce check should actually catch this already.
@@ -1236,14 +1388,16 @@
1236 1388 $default_render_options = apply_filters( 'tablepress_shortcode_table_default_shortcode_atts', $default_render_options );
1237 1389 $render_options = shortcode_atts( $default_render_options, $table['options'] );
1238 1390 /** This filter is documented in controllers/controller-frontend.php */
1239 1391 $render_options = apply_filters( 'tablepress_shortcode_table_shortcode_atts', $render_options );
1392 + $render_options['html_id'] = "tablepress-{$table['id']}";
1393 + $render_options['block_preview'] = true;
1240 1394 $_render->set_input( $table, $render_options );
1241 1395 $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(),
1396 + 'table_id' => $table_id,
1397 + 'head_html' => $_render->get_preview_css(),
1398 + 'body_html' => $_render->get_output( 'html' ),
1399 + 'site_used_editor' => TablePress::site_used_editor(),
1246 1400 );
1247 1401
1248 1402 $custom_css = TablePress::$model_options->get( 'custom_css' );
1249 1403 $use_custom_css = ( TablePress::$model_options->get( 'use_custom_css' ) && '' !== $custom_css );
@@ -1260,9 +1414,9 @@
1260 1414 * Shows a list of tables in the Editor toolbar Thickbox (opened by TinyMCE or Quicktags button).
1261 1415 *
1262 1416 * @since 1.0.0
1263 1417 */
1264 - public function handle_get_action_editor_button_thickbox() {
1418 + public function handle_get_action_editor_button_thickbox(): void {
1265 1419 TablePress::check_nonce( 'editor_button_thickbox' );
1266 1420
1267 1421 if ( ! current_user_can( 'tablepress_list_tables' ) ) {
1268 1422 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
@@ -1284,9 +1438,9 @@
1284 1438 * Uninstall TablePress, and delete all tables and options.
1285 1439 *
1286 1440 * @since 1.0.0
1287 1441 */
1288 - public function handle_get_action_uninstall_tablepress() {
1442 + public function handle_get_action_uninstall_tablepress(): void {
1289 1443 TablePress::check_nonce( 'uninstall_tablepress' );
1290 1444
1291 1445 $plugin = TABLEPRESS_BASENAME;
1292 1446
@@ -1306,9 +1460,9 @@
1306 1460
1307 1461 TablePress::$model_table->destroy();
1308 1462 TablePress::$model_options->destroy();
1309 1463
1310 - $output = '<strong>' . __( 'TablePress was uninstalled successfully.', 'tablepress' ) . '</strong><br /><br />';
1464 + $output = '<strong>' . __( 'TablePress was uninstalled successfully.', 'tablepress' ) . '</strong><br><br>';
1311 1465 $output .= __( 'All tables, data, and options were deleted.', 'tablepress' );
1312 1466 if ( is_multisite() ) {
1313 1467 $output .= ' ' . __( 'You may now ask the network admin to delete the plugin&#8217;s folder <code>tablepress</code> from the server, if no other site in the network uses it.', 'tablepress' );
1314 1468 } else {
@@ -1315,9 +1469,9 @@
1315 1469 $output .= ' ' . __( 'You may now manually delete the plugin&#8217;s folder <code>tablepress</code> from the <code>plugins</code> directory on your server or use the &#8220;Delete&#8221; link for TablePress on the WordPress &#8220;Plugins&#8221; page.', 'tablepress' );
1316 1470 }
1317 1471 if ( $css_files_deleted ) {
1318 1472 $output .= ' ' . __( 'Your TablePress &#8220;Custom CSS&#8221; files have been deleted automatically.', 'tablepress' );
1319 - } else {
1473 + } else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found
1320 1474 if ( is_multisite() ) {
1321 1475 $output .= ' ' . __( 'Please also ask him to delete your TablePress &#8220;Custom CSS&#8221; files from the server.', 'tablepress' );
1322 1476 } else {
1323 1477 $output .= ' ' . __( 'You may now also delete your TablePress &#8220;Custom CSS&#8221; files in the <code>wp-content</code> folder.', 'tablepress' );