PluginProbe
TablePress – Tables in WordPress made easy / 3.0.4
TablePress – Tables in WordPress made easy v3.0.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 2.4.4 All 44 releases
← All changes | controllers/controller-admin.php +550 -1018 1.9.23.0.4 View file →
@@ -12,8 +12,9 @@
12 12 defined( 'ABSPATH' ) || die( 'No direct script access allowed!' );
13 13
14 14 /**
15 15 * Admin Controller class, extends Base Controller Class
16 + *
16 17 * @package TablePress
17 18 * @subpackage Controllers
18 19 * @author Tobias Bäthge
19 20 * @since 1.0.0
@@ -24,45 +25,28 @@
24 25 * Page hooks (i.e. names) WordPress uses for the TablePress admin screens,
25 26 * populated in add_admin_menu_entry().
26 27 *
27 28 * @since 1.0.0
28 - * @var array
29 + * @var string[]
29 30 */
30 - protected $page_hooks = array();
31 + protected array $page_hooks = array();
31 32
32 33 /**
33 34 * Actions that have a view and admin menu or nav tab menu entry.
34 35 *
35 36 * @since 1.0.0
36 - * @var array
37 + * @var array<string, array<string, bool|string>>
37 38 */
38 - protected $view_actions = array();
39 + protected array $view_actions = array();
39 40
40 41 /**
41 - * Whether language support has been loaded (to prevent doing it twice).
42 - *
43 - * @since 1.0.0
44 - * @var bool
45 - */
46 - protected $i18n_support_loaded = false;
47 -
48 - /**
49 42 * Instance of the TablePress Admin View that is rendered.
50 43 *
51 44 * @since 1.0.0
52 - * @var TablePress_View
53 45 */
54 - protected $view;
46 + protected \TablePress_View $view;
55 47
56 48 /**
57 - * Instance of the TablePress Importer.
58 - *
59 - * @since 1.0.0
60 - * @var TablePress_Import
61 - */
62 - protected $importer;
63 -
64 - /**
65 49 * Initialize the Admin Controller, determine location the admin menu, set up actions.
66 50 *
67 51 * @since 1.0.0
68 52 */
@@ -69,12 +53,15 @@
69 53 public function __construct() {
70 54 parent::__construct();
71 55
72 56 // Handler for changing the number of shown tables in the list of tables (via WP List Table class).
73 - add_filter( 'set-screen-option', array( $this, 'save_list_tables_screen_option' ), 10, 3 );
57 + add_filter( 'set_screen_option_tablepress_list_per_page', array( $this, 'save_list_tables_screen_option' ), 10, 3 );
74 58
75 59 add_action( 'admin_menu', array( $this, 'add_admin_menu_entry' ) );
76 60 add_action( 'admin_init', array( $this, 'add_admin_actions' ) );
61 +
62 + add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_block_editor_assets' ) );
63 + add_action( 'enqueue_block_assets', array( $this, 'enqueue_block_assets' ) );
77 64 }
78 65
79 66 /**
80 67 * Handler for changing the number of shown tables in the list of tables (via WP List Table class).
@@ -80,19 +67,15 @@
80 67 * Handler for changing the number of shown tables in the list of tables (via WP List Table class).
81 68 *
82 69 * @since 1.0.0
83 70 *
84 - * @param bool $false Current value of the filter (probably bool false).
85 - * @param string $option Option in which the setting is stored.
86 - * @param int $value Current value of the setting.
87 - * @return bool|int False to not save the changed setting, or the int value to be saved.
71 + * @param mixed $screen_option Current value of the filter (probably bool false).
72 + * @param string $option Option in which the setting is stored.
73 + * @param int $value Current value of the setting.
74 + * @return int Changed value of the setting
88 75 */
89 - public function save_list_tables_screen_option( $false, $option, $value ) {
90 - if ( 'tablepress_list_per_page' === $option ) {
91 - return $value;
92 - } else {
93 - return $false;
94 - }
76 + public function save_list_tables_screen_option( /* mixed */ $screen_option, string $option, int $value ): int {
77 + return $value;
95 78 }
96 79
97 80 /**
98 81 * Add admin screens to the correct place in the admin menu.
@@ -98,13 +81,13 @@
98 81 * Add admin screens to the correct place in the admin menu.
99 82 *
100 83 * @since 1.0.0
101 84 */
102 - public function add_admin_menu_entry() {
85 + public function add_admin_menu_entry(): void {
103 86 // Callback for all menu entries.
104 87 $callback = array( $this, 'show_admin_page' );
105 88 /**
106 - * Filter the TablePress admin menu entry name.
89 + * Filters the TablePress admin menu entry name.
107 90 *
108 91 * @since 1.0.0
109 92 *
110 93 * @param string $entry_name The admin menu entry name. Default "TablePress".
@@ -110,28 +93,30 @@
110 93 * @param string $entry_name The admin menu entry name. Default "TablePress".
111 94 */
112 95 $admin_menu_entry_name = apply_filters( 'tablepress_admin_menu_entry_name', 'TablePress' );
113 96
114 - if ( $this->is_top_level_page ) {
115 - // Init i18n support here as translated strings for admin menu are needed already.
116 - $this->init_i18n_support();
117 - $this->init_view_actions(); // after init_i18n_support(), as it requires translation
118 - $min_access_cap = $this->view_actions['list']['required_cap'];
97 + $this->init_view_actions();
98 + $min_access_cap = $this->view_actions['list']['required_cap'];
119 99
120 - $icon_url = 'dashicons-list-view';
121 - 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 ) {
122 103 case 'top':
123 - $position = 3; // position of Dashboard + 1
104 + $position = 3; // Position of Dashboard + 1.
124 105 break;
125 106 case 'bottom':
126 - $position = ( ++$GLOBALS['_wp_last_utility_menu'] );
107 + $position = isset( $GLOBALS['_wp_last_utility_menu'] ) ? ++$GLOBALS['_wp_last_utility_menu'] : 80;
127 108 break;
128 109 case 'middle':
129 110 default:
130 - $position = ( ++$GLOBALS['_wp_last_object_menu'] );
111 + $position = isset( $GLOBALS['_wp_last_object_menu'] ) ? ++$GLOBALS['_wp_last_object_menu'] : 25;
131 112 break;
132 113 }
133 - 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
134 119 foreach ( $this->view_actions as $action => $entry ) {
135 120 if ( ! $entry['show_entry'] ) {
136 121 continue;
137 122 }
@@ -138,14 +123,20 @@
138 123 $slug = 'tablepress';
139 124 if ( 'list' !== $action ) {
140 125 $slug .= '_' . $action;
141 126 }
142 - $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 + // @phpstan-ignore argument.type, argument.type
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 );
129 + if ( false !== $page_hook ) {
130 + $this->page_hooks[] = $page_hook;
131 + }
143 132 }
144 133 } else {
145 - $this->init_view_actions(); // no translation necessary here
146 - $min_access_cap = $this->view_actions['list']['required_cap'];
147 - $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 + }
148 139 }
149 140 }
150 141
151 142 /**
@@ -152,11 +143,11 @@
152 143 * Set up handlers for user actions in the backend that exceed plain viewing.
153 144 *
154 145 * @since 1.0.0
155 146 */
156 - public function add_admin_actions() {
147 + public function add_admin_actions(): void {
157 148 // Register the callbacks for processing action requests.
158 - $post_actions = array( 'list', 'add', 'edit', 'options', 'export', 'import' );
149 + $post_actions = array( 'list', 'add', 'options', 'export', 'import' );
159 150 $get_actions = array( 'hide_message', 'delete_table', 'copy_table', 'preview_table', 'editor_button_thickbox', 'uninstall_tablepress' );
160 151 foreach ( $post_actions as $action ) {
161 152 add_action( "admin_post_tablepress_{$action}", array( $this, "handle_post_action_{$action}" ) );
162 153 }
@@ -168,11 +159,20 @@
168 159 foreach ( $this->page_hooks as $page_hook ) {
169 160 add_action( "load-{$page_hook}", array( $this, 'load_admin_page' ) );
170 161 }
171 162
172 - $pages_with_editor_button = array( 'post.php', 'post-new.php' );
173 - foreach ( $pages_with_editor_button as $editor_page ) {
174 - 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 + }
175 175 }
176 176
177 177 if ( ! is_network_admin() && ! is_user_admin() ) {
178 178 add_action( 'admin_bar_menu', array( $this, 'add_wp_admin_bar_new_content_menu_entry' ), 71 );
@@ -178,99 +178,187 @@
178 178 add_action( 'admin_bar_menu', array( $this, 'add_wp_admin_bar_new_content_menu_entry' ), 71 );
179 179 }
180 180
181 181 add_action( 'load-plugins.php', array( $this, 'plugins_page' ) );
182 - add_action( 'admin_print_styles-media-upload-popup', array( $this, 'add_media_upload_thickbox_css' ) );
182 + }
183 183
184 - // Add filters and actions for the integration into the WP WXR exporter and importer.
185 - add_action( 'wp_import_insert_post', array( TablePress::$model_table, 'add_table_id_on_wp_import' ), 10, 4 );
186 - add_filter( 'wp_import_post_meta', array( TablePress::$model_table, 'prevent_table_id_post_meta_import_on_wp_import' ), 10, 3 );
187 - add_filter( 'wxr_export_skip_postmeta', array( TablePress::$model_table, 'add_table_id_to_wp_export' ), 10, 3 );
184 + /**
185 + * Loads additional JavaScript code for the TablePress table block (in the block editor context).
186 + *
187 + * @since 2.2.0
188 + */
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.
199 + $handle = generate_block_asset_handle( 'tablepress/table', 'editorScript' );
200 + $data = $this->get_block_editor_data();
201 + wp_add_inline_script( $handle, $data, 'before' );
188 202 }
189 203
190 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 + /**
217 + * Gets the inline data that is referenced by the Block Editor JavaScript code for the TablePress blocks.
218 + *
219 + * @since 2.0.0
220 + *
221 + * @return string JavaScript code for the Block Editor.
222 + */
223 + protected function get_block_editor_data(): string {
224 + $tables = array();
225 + // Load all table IDs without priming the post meta cache, as table options/visibility are not needed.
226 + $table_ids = TablePress::$model_table->load_all( false );
227 + foreach ( $table_ids as $table_id ) {
228 + // Load table, without table data, options, and visibility settings.
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 +
236 + if ( '' === trim( $table['name'] ) ) {
237 + $table['name'] = __( '(no name)', 'tablepress' );
238 + }
239 + $tables[ $table_id ] = esc_html( $table['name'] );
240 + }
241 +
242 + /**
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.
244 + *
245 + * @since 2.0.0
246 + *
247 + * @param array<string, string> $tables List of table names, the table ID is the array key.
248 + */
249 + $tables = apply_filters( 'tablepress_block_editor_tables_list', $tables );
250 +
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 );
258 +
259 + $shortcode = esc_js( TablePress::$shortcode );
260 +
261 + $template = TablePress::$model_table->get_table_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 );
269 +
270 + /**
271 + * Filters whether the table block preview should be loaded via a <ServerSideRender> in the block editor.
272 + *
273 + * @since 2.0.0
274 + *
275 + * @param bool $load_block_preview Whether the table block preview should be loaded.
276 + */
277 + $load_block_preview = apply_filters( 'tablepress_show_block_editor_preview', true );
278 + $load_block_preview = (bool) $load_block_preview ? 'true' : 'false';
279 +
280 + $url = '';
281 + if ( current_user_can( 'tablepress_list_tables' ) ) {
282 + $url = TablePress::url( array( 'action' => 'list' ) );
283 + }
284 +
285 + return <<<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;
295 + }
296 +
297 + /**
191 298 * Register actions to add "Table" button to "HTML editor" and "Visual editor" toolbars.
192 299 *
193 300 * @since 1.0.0
194 301 */
195 - public function add_editor_buttons() {
302 + public function add_editor_buttons(): void {
196 303 if ( ! current_user_can( 'tablepress_list_tables' ) ) {
197 304 return;
198 305 }
199 306
200 - $this->init_i18n_support();
201 - add_thickbox(); // usually already loaded by media upload functions
307 + // Only load the toolbar integration if the Block Editor is not used.
308 + if ( TablePress::site_uses_block_editor() ) {
309 + return;
310 + }
311 +
312 + add_thickbox(); // The files are usually already loaded by media upload functions.
202 313 $admin_page = TablePress::load_class( 'TablePress_Admin_Page', 'class-admin-page-helper.php', 'classes' );
203 - $admin_page->enqueue_script( 'quicktags-button', array( 'quicktags', 'media-upload' ), array(
204 - 'editor_button' => array(
205 - 'caption' => __( 'Table', 'tablepress' ),
206 - 'title' => __( 'Insert a Table from TablePress', 'tablepress' ),
207 - 'thickbox_title' => __( 'Insert a Table from TablePress', 'tablepress' ),
208 - 'thickbox_url' => TablePress::url( array( 'action' => 'editor_button_thickbox' ), true, 'admin-post.php' ),
314 + $admin_page->enqueue_script(
315 + 'quicktags-button',
316 + array( 'quicktags', 'media-upload' ),
317 + array(
318 + 'editor_button' => array(
319 + 'caption' => __( 'Table', 'tablepress' ),
320 + 'title' => __( 'Insert a TablePress table', 'tablepress' ),
321 + 'thickbox_title' => __( 'Insert a TablePress table', 'tablepress' ),
322 + 'thickbox_url' => TablePress::url( array( 'action' => 'editor_button_thickbox' ), true, 'admin-post.php' ),
323 + ),
209 324 ),
210 - ) );
325 + );
211 326
212 327 // TinyMCE integration.
213 328 if ( user_can_richedit() ) {
214 329 add_filter( 'mce_external_plugins', array( $this, 'add_tinymce_plugin' ) );
215 330 add_filter( 'mce_buttons', array( $this, 'add_tinymce_button' ) );
216 - add_action( 'admin_print_styles', array( $this, 'add_tablepress_hidpi_css' ), 21 );
217 331 }
218 332 }
219 333
220 334 /**
221 - * Add "Table" button and separator to the TinyMCE toolbar.
335 + * Adds the "Table" button to the TinyMCE toolbar.
222 336 *
223 337 * @since 1.0.0
224 338 *
225 - * @param array $buttons Current set of buttons in the TinyMCE toolbar.
226 - * @return array Current set of buttons in the TinyMCE toolbar, including "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.
227 341 */
228 - public function add_tinymce_button( array $buttons ) {
342 + public function add_tinymce_button( array $buttons ): array {
229 343 $buttons[] = 'tablepress_insert_table';
230 344 return $buttons;
231 345 }
232 346
233 347 /**
234 - * Register "Table" button plugin to TinyMCE.
348 + * Registers the "Table" button plugin for the TinyMCE editor.
235 349 *
236 350 * @since 1.0.0
237 351 *
238 - * @param array $plugins Current set of registered TinyMCE plugins.
239 - * @return array Current set of registered TinyMCE plugins, including "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.
240 354 */
241 - public function add_tinymce_plugin( array $plugins ) {
242 - $suffix = SCRIPT_DEBUG ? '' : '.min';
243 - $js_file = "admin/js/tinymce-button{$suffix}.js";
244 - $plugins['tablepress_tinymce'] = plugins_url( $js_file, TABLEPRESS__FILE__ );
355 + public function add_tinymce_plugin( array $plugins ): array {
356 + $plugins['tablepress_tinymce'] = plugins_url( 'admin/js/build/tinymce-button.js', TABLEPRESS__FILE__ );
245 357 return $plugins;
246 358 }
247 359
248 360 /**
249 - * Print TablePress HiDPI CSS to the <head> for TinyMCE button.
250 - *
251 - * @since 1.0.0
252 - */
253 - public function add_tablepress_hidpi_css() {
254 - echo '<style type="text/css">@media print,(-webkit-min-device-pixel-ratio:1.25),(min-resolution:120dpi){';
255 - echo '#content_tablepress_insert_table span{background:url(' . plugins_url( 'admin/img/tablepress-editor-button-2x.png', TABLEPRESS__FILE__ ) . ') no-repeat 0 0;background-size:20px 20px}';
256 - echo '#content_tablepress_insert_table img{display:none}';
257 - echo '}</style>' . "\n";
258 - }
259 -
260 - /**
261 - * Print some CSS in the Media Upload Thickbox to fix some positioning issues.
262 - *
263 - * These will most likely not be fixed in core, as the old media uploader is deprecated.
264 - * They will be removed in TablePress, once the new media uploader is used.
265 - *
266 - * @since 1.4.0
267 - */
268 - public function add_media_upload_thickbox_css() {
269 - echo '<style type="text/css">#media-items,#media-upload #filter{width:auto!important}.media-item .describe input[type="text"],.media-item .describe textarea{width:100%!important}.media-item .image-editor input[type="text"]{width:3em!important}</style>' . "\n";
270 - }
271 -
272 - /**
273 361 * Add "TablePress Table" entry to "New" dropdown menu in the WP Admin Bar.
274 362 *
275 363 * @since 1.0.0
276 364 *
@@ -275,18 +363,22 @@
275 363 * @since 1.0.0
276 364 *
277 365 * @param WP_Admin_Bar $wp_admin_bar The current WP Admin Bar object.
278 366 */
279 - 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 {
280 368 if ( ! current_user_can( 'tablepress_add_tables' ) ) {
281 369 return;
282 370 }
283 - // @TODO: Translation might not work, as textdomain might not yet be loaded here (for submenu entries).
284 - // Might need $this->init_i18n_support(); here.
371 +
372 + // Don't load TablePress assets on the Freemius opt-in/activation screen.
373 + if ( tb_tp_fs()->is_activation_mode() && tb_tp_fs()->is_activation_page() ) {
374 + return;
375 + }
376 +
285 377 $wp_admin_bar->add_menu( array(
286 378 'parent' => 'new-content',
287 379 'id' => 'new-tablepress-table',
288 - 'title' => __( 'TablePress Table', 'tablepress' ),
380 + 'title' => __( 'TablePress table', 'tablepress' ),
289 381 'href' => TablePress::url( array( 'action' => 'add' ) ),
290 382 ) );
291 383 }
292 384
@@ -294,13 +386,25 @@
294 386 * Handle actions for loading of Plugins page.
295 387 *
296 388 * @since 1.0.0
297 389 */
298 - public function plugins_page() {
299 - $this->init_i18n_support();
390 + public function plugins_page(): void {
300 391 // Add additional links on Plugins page.
301 392 add_filter( 'plugin_action_links_' . TABLEPRESS_BASENAME, array( $this, 'add_plugin_action_links' ) );
302 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 + }
303 407 }
304 408
305 409 /**
306 410 * Add links to the TablePress entry in the "Plugin" column on the Plugins page.
@@ -306,73 +410,128 @@
306 410 * Add links to the TablePress entry in the "Plugin" column on the Plugins page.
307 411 *
308 412 * @since 1.0.0
309 413 *
310 - * @param array $links List of links to print in the "Plugin" column on the Plugins page.
311 - * @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.
312 416 */
313 - public function add_plugin_action_links( array $links ) {
417 + public function add_plugin_action_links( array $links ): array {
314 418 if ( current_user_can( 'tablepress_list_tables' ) ) {
315 - $links[] = '<a href="' . TablePress::url() . '">' . __( 'Plugin page', 'tablepress' ) . '</a>';
419 + $links[] = '<a href="' . esc_url( TablePress::url() ) . '">' . __( 'Plugin page', 'tablepress' ) . '</a>';
316 420 }
317 421 return $links;
318 422 }
423 +
319 424 /**
320 425 * Add links to the TablePress entry in the "Description" column on the Plugins page.
321 426 *
322 427 * @since 1.0.0
323 428 *
324 - * @param array $links List of links to print in the "Description" column on the Plugins page.
325 - * @param string $file Name of the plugin.
326 - * @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.
327 432 */
328 - public function add_plugin_row_meta( array $links, $file ) {
433 + public function add_plugin_row_meta( array $links, string $file ): array {
329 434 if ( TABLEPRESS_BASENAME === $file ) {
330 435 $links[] = '<a href="https://tablepress.org/faq/" title="' . esc_attr__( 'Frequently Asked Questions', 'tablepress' ) . '">' . __( 'FAQ', 'tablepress' ) . '</a>';
331 436 $links[] = '<a href="https://tablepress.org/documentation/">' . __( 'Documentation', 'tablepress' ) . '</a>';
332 437 $links[] = '<a href="https://tablepress.org/support/">' . __( 'Support', 'tablepress' ) . '</a>';
333 - $links[] = '<a href="https://tablepress.org/donate/" title="' . esc_attr__( 'Support TablePress with your donation!', 'tablepress' ) . '"><strong>' . __( 'Donate', '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>';
440 + }
334 441 }
335 442 return $links;
336 443 }
337 444
338 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 + /**
339 492 * Prepare the rendering of an admin screen, by determining the current action, loading necessary data and initializing the view.
340 493 *
341 494 * @since 1.0.0
342 495 */
343 - public function load_admin_page() {
496 + public function load_admin_page(): void {
344 497 // Determine the action from either the GET parameter (for sub-menu entries, and the main admin menu entry).
345 - $action = ( ! empty( $_GET['action'] ) ) ? $_GET['action'] : 'list'; // default action is list
346 - if ( $this->is_top_level_page ) {
498 + $action = ( ! empty( $_GET['action'] ) ) ? $_GET['action'] : 'list'; // Default action is list.
499 + if ( TablePress::$controller->is_top_level_page ) {
347 500 // Or, for sub-menu entry of an admin menu "TablePress" entry, get it from the "page" GET parameter.
348 501 if ( 'tablepress' !== $_GET['page'] ) {
349 502 // Actions that are top-level entries, but don't have an action GET parameter (action is after last _ in string).
350 503 $action = substr( $_GET['page'], 11 ); // $_GET['page'] has the format 'tablepress_{$action}'
351 504 }
352 - } else {
353 - // Do this here in the else-part, instead of adding another if ( ! $this->is_top_level_page ) check.
354 - $this->init_i18n_support(); // done here, as for sub menu admin pages this is the first time translated strings are needed.
355 - $this->init_view_actions(); // for top-level menu entries, this has been done above, just like init_i18n_support().
356 505 }
357 506
358 507 // Check if action is a supported action, and whether the user is allowed to access this screen.
359 - if ( ! isset( $this->view_actions[ $action ] ) || ! current_user_can( $this->view_actions[ $action ]['required_cap'] ) ) {
360 - wp_die( __( 'You do not have sufficient permissions to access this page.', 'default' ), 403 );
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.)
509 + wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
361 510 }
362 511
512 + // Don't load TablePress assets on the Freemius opt-in/activation screen.
513 + if ( tb_tp_fs()->is_activation_mode() && tb_tp_fs()->is_activation_page() ) {
514 + return;
515 + }
516 +
363 517 // Changes current screen ID and pagenow variable in JS, to enable automatic meta box JS handling.
364 518 set_current_screen( "tablepress_{$action}" );
365 - // Set the $typenow global to the current CPT ourselves, as WP_Screen::get() does not determine the CPT correctly.
366 - // This is necessary as the WP Admin Menu can otherwise highlight wrong entries, see https://github.com/TobiasBg/TablePress/issues/24.
519 +
520 + /*
521 + * Set the `$typenow` global to the current CPT ourselves, as `WP_Screen::get()` does not determine the CPT correctly.
522 + * This is necessary as the WP Admin Menu can otherwise highlight wrong entries, see https://github.com/TablePress/TablePress/issues/24.
523 + */
367 524 if ( isset( $_GET['post_type'] ) && post_type_exists( $_GET['post_type'] ) ) {
368 - $GLOBALS['typenow'] = $_GET['post_type'];
525 + $GLOBALS['typenow'] = $_GET['post_type']; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
369 526 }
370 527
371 528 // Pre-define some view data.
372 529 $data = array(
373 - 'view_actions' => $this->view_actions,
374 - 'message' => ( ! empty( $_GET['message'] ) ) ? $_GET['message'] : false,
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_uses_block_editor' => TablePress::site_uses_block_editor(),
375 534 );
376 535
377 536 // Depending on the action, load more necessary data for the corresponding view.
378 537 switch ( $action ) {
@@ -379,30 +538,25 @@
379 538 case 'list':
380 539 $data['table_id'] = ( ! empty( $_GET['table_id'] ) ) ? $_GET['table_id'] : false;
381 540 // Prime the post meta cache for cached loading of last_editor.
382 541 $data['table_ids'] = TablePress::$model_table->load_all( true );
383 - $data['messages']['first_visit'] = TablePress::$model_options->get( 'message_first_visit' );
384 - if ( current_user_can( 'tablepress_import_tables_wptr' ) ) {
385 - // Check if WP-Table Reloaded is activated.
386 - $data['messages']['wp_table_reloaded_warning'] = is_plugin_active( 'wp-table-reloaded/wp-table-reloaded.php' );
387 - } else {
388 - $data['messages']['wp_table_reloaded_warning'] = false;
389 - }
390 - $data['messages']['plugin_update_message'] = TablePress::$model_options->get( 'message_plugin_update' );
391 - $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' );
392 546 $data['table_count'] = count( $data['table_ids'] );
393 547 break;
394 548 case 'about':
395 549 $data['first_activation'] = TablePress::$model_options->get( 'first_activation' );
396 - $exporter = TablePress::load_class( 'TablePress_Export', 'class-export.php', 'classes' );
397 - $data['zip_support_available'] = $exporter->zip_support_available;
398 550 break;
399 551 case 'options':
400 - // Maybe try saving "Custom CSS" to a file:
401 - // (called here, as the credentials form posts to this handler again, due to how request_filesystem_credentials() works)
552 + /*
553 + * Maybe try saving "Custom CSS" to a file:
554 + * (called here, as the credentials form posts to this handler again, due to how `request_filesystem_credentials()` works)
555 + */
402 556 if ( isset( $_GET['item'] ) && 'save_custom_css' === $_GET['item'] ) {
403 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.
404 - $action = 'options_custom_css'; // to load a different view
558 + $action = 'options_custom_css'; // To load a different view.
405 559 // Try saving "Custom CSS" to a file, otherwise this gets the HTML for the credentials form.
406 560 $tablepress_css = TablePress::load_class( 'TablePress_CSS', 'class-css.php', 'classes' );
407 561 $result = $tablepress_css->save_custom_css_to_file_plugin_options( TablePress::$model_options->get( 'custom_css' ), TablePress::$model_options->get( 'custom_css_minified' ) );
408 562 if ( is_string( $result ) ) {
@@ -416,9 +570,9 @@
416 570 'use_custom_css_file' => true,
417 571 'custom_css_version' => TablePress::$model_options->get( 'custom_css_version' ) + 1,
418 572 ) );
419 573 TablePress::redirect( array( 'action' => 'options', 'message' => 'success_save' ) );
420 - } else { // leaves only $result = false
574 + } else { // Leaves only $result === false.
421 575 TablePress::redirect( array( 'action' => 'options', 'message' => 'success_save_error_custom_css' ) );
422 576 }
423 577 break;
424 578 }
@@ -423,68 +577,88 @@
423 577 break;
424 578 }
425 579 $data['frontend_options']['use_custom_css'] = TablePress::$model_options->get( 'use_custom_css' );
426 580 $data['frontend_options']['custom_css'] = TablePress::$model_options->get( 'custom_css' );
427 - $data['user_options']['parent_page'] = $this->parent_page;
581 + $data['user_options']['parent_page'] = TablePress::$controller->parent_page;
428 582 break;
429 583 case 'edit':
430 - if ( ! empty( $_GET['table_id'] ) ) {
431 - // Load table, with table data, options, and visibility settings.
432 - $data['table'] = TablePress::$model_table->load( $_GET['table_id'], true, true );
433 - if ( is_wp_error( $data['table'] ) ) {
434 - TablePress::redirect( array( 'action' => 'list', 'message' => 'error_load_table' ) );
435 - }
436 - if ( ! current_user_can( 'tablepress_edit_table', $_GET['table_id'] ) ) {
437 - wp_die( __( 'You do not have sufficient permissions to access this page.', 'default' ), 403 );
438 - }
439 - } else {
584 + if ( empty( $_GET['table_id'] ) ) {
440 585 TablePress::redirect( array( 'action' => 'list', 'message' => 'error_no_table' ) );
441 586 }
587 + // Load table, with table data, options, and visibility settings.
588 + $data['table'] = TablePress::$model_table->load( $_GET['table_id'], true, true );
589 + if ( is_wp_error( $data['table'] ) ) {
590 + TablePress::redirect( array( 'action' => 'list', 'message' => 'error_load_table', 'error_details' => TablePress::get_wp_error_string( $data['table'] ) ) );
591 + }
592 + if ( ! current_user_can( 'tablepress_edit_table', $_GET['table_id'] ) ) {
593 + wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
594 + }
442 595 break;
443 596 case 'export':
444 597 // Load all table IDs without priming the post meta cache, as table options/visibility are not needed.
445 - $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 + }
446 614 $data['tables_count'] = TablePress::$model_table->count_tables();
447 - if ( ! empty( $_GET['table_id'] ) ) {
448 - $data['export_ids'] = explode( ',', $_GET['table_id'] );
449 - } else {
450 - // Just show empty export form.
451 - $data['export_ids'] = array();
452 - }
615 + $data['export_ids'] = ( ! empty( $_GET['table_id'] ) ) ? explode( ',', $_GET['table_id'] ) : array();
453 616 $exporter = TablePress::load_class( 'TablePress_Export', 'class-export.php', 'classes' );
454 617 $data['zip_support_available'] = $exporter->zip_support_available;
455 618 $data['export_formats'] = $exporter->export_formats;
456 619 $data['csv_delimiters'] = $exporter->csv_delimiters;
457 - $data['export_format'] = ( ! empty( $_GET['export_format'] ) ) ? $_GET['export_format'] : false;
620 + $data['export_format'] = ( ! empty( $_GET['export_format'] ) ) ? $_GET['export_format'] : 'csv';
458 621 $data['csv_delimiter'] = ( ! empty( $_GET['csv_delimiter'] ) ) ? $_GET['csv_delimiter'] : _x( ',', 'Default CSV delimiter in the translated language (";", ",", or "tab")', 'tablepress' );
459 622 break;
460 623 case 'import':
461 624 // Load all table IDs without priming the post meta cache, as table options/visibility are not needed.
462 - $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.
463 642 $data['tables_count'] = TablePress::$model_table->count_tables();
464 643 $importer = TablePress::load_class( 'TablePress_Import', 'class-import.php', 'classes' );
465 - $data['zip_support_available'] = $importer->zip_support_available;
466 - $data['html_import_support_available'] = $importer->html_import_support_available;
467 - $data['import_formats'] = $importer->import_formats;
468 - $data['import_format'] = ( ! empty( $_GET['import_format'] ) ) ? $_GET['import_format'] : false;
469 644 $data['import_type'] = ( ! empty( $_GET['import_type'] ) ) ? $_GET['import_type'] : 'add';
470 - $data['import_existing_table'] = ( ! empty( $_GET['import_existing_table'] ) ) ? $_GET['import_existing_table'] : false;
645 + $data['import_existing_table'] = ( ! empty( $_GET['import_existing_table'] ) ) ? $_GET['import_existing_table'] : '';
471 646 $data['import_source'] = ( ! empty( $_GET['import_source'] ) ) ? $_GET['import_source'] : 'file-upload';
472 - $data['import_url'] = ( ! empty( $_GET['import_url'] ) ) ? wp_unslash( $_GET['import_url'] ) : 'http://';
647 + $data['import_url'] = ( ! empty( $_GET['import_url'] ) ) ? wp_unslash( $_GET['import_url'] ) : 'https://';
473 648 $data['import_server'] = ( ! empty( $_GET['import_server'] ) ) ? wp_unslash( $_GET['import_server'] ) : ABSPATH;
474 - $data['import_form_field'] = ( ! empty( $_GET['import_form_field'] ) ) ? wp_unslash( $_GET['import_form_field'] ) : '';
475 - $data['wp_table_reloaded_installed'] = ( false !== get_option( 'wp_table_reloaded_options', false ) && false !== get_option( 'wp_table_reloaded_tables', false ) );
476 - $data['import_wp_table_reloaded_source'] = ( ! empty( $_GET['import_wp_table_reloaded_source'] ) ) ? $_GET['import_wp_table_reloaded_source'] : ( $data['wp_table_reloaded_installed'] ? 'db' : 'dump-file' );
649 + $data['import_form-field'] = ( ! empty( $_GET['import_form-field'] ) ) ? wp_unslash( $_GET['import_form-field'] ) : '';
650 + $data['legacy_import'] = ( ! empty( $_GET['legacy_import'] ) ) ? $_GET['legacy_import'] : 'false';
477 651 break;
478 652 }
479 653
480 654 /**
481 - * Filter the data that is passed to the current TablePress View.
655 + * Filters the data that is passed to the current TablePress View.
482 656 *
483 657 * @since 1.0.0
484 658 *
485 - * @param array $data Data for the view.
486 - * @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.
487 661 */
488 662 $data = apply_filters( 'tablepress_view_data', $data, $action );
489 663
490 664 // Prepare and initialize the view.
@@ -495,33 +669,20 @@
495 669 * Render the view that has been initialized in load_admin_page() (called by WordPress when the actual page content is needed).
496 670 *
497 671 * @since 1.0.0
498 672 */
499 - public function show_admin_page() {
673 + public function show_admin_page(): void {
500 674 $this->view->render();
501 675 }
502 676
503 677 /**
504 - * Initialize i18n support, load plugin's textdomain, to retrieve correct translations.
678 + * Decides whether a message about Premium versions (previously, about donations) shall be shown on the "All Tables" screen, depending on passed days since installation and whether it was shown before.
505 679 *
506 680 * @since 1.0.0
507 - */
508 - protected function init_i18n_support() {
509 - if ( $this->i18n_support_loaded ) {
510 - return;
511 - }
512 - load_plugin_textdomain( 'tablepress', false, dirname( TABLEPRESS_BASENAME ) . '/i18n' );
513 - $this->i18n_support_loaded = true;
514 - }
515 -
516 - /**
517 - * Decide whether a donate message shall be shown on the "All Tables" screen, depending on passed days since installation and whether it was shown before.
518 681 *
519 - * @since 1.0.0
520 - *
521 - * @return bool Whether the donate message shall be shown on the "All Tables" screen.
682 + * @return bool Whether the message shall be shown on the "All Tables" screen.
522 683 */
523 - protected function maybe_show_donation_message() {
684 + protected function maybe_show_donation_message(): bool {
524 685 // Only show the message to plugin admins.
525 686 if ( ! current_user_can( 'tablepress_edit_options' ) ) {
526 687 return false;
527 688 }
@@ -531,9 +692,9 @@
531 692 }
532 693
533 694 // Determine, how long has the plugin been installed.
534 695 $seconds_installed = time() - TablePress::$model_options->get( 'first_activation' );
535 - return ( $seconds_installed > 30 * DAY_IN_SECONDS );
696 + return ( $seconds_installed > MONTH_IN_SECONDS / 2 );
536 697 }
537 698
538 699 /**
539 700 * Init list of actions that have a view with their titles/names/caps.
@@ -539,9 +700,9 @@
539 700 * Init list of actions that have a view with their titles/names/caps.
540 701 *
541 702 * @since 1.0.0
542 703 */
543 - protected function init_view_actions() {
704 + protected function init_view_actions(): void {
544 705 $this->view_actions = array(
545 706 'list' => array(
546 707 'show_entry' => true,
547 708 'page_title' => __( 'All Tables', 'tablepress' ),
@@ -593,13 +754,13 @@
593 754 ),
594 755 );
595 756
596 757 /**
597 - * Filter the available TablePres Views/Actions and their parameters.
758 + * Filters the available TablePres Views/Actions and their parameters.
598 759 *
599 760 * @since 1.0.0
600 761 *
601 - * @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.
602 763 */
603 764 $this->view_actions = apply_filters( 'tablepress_admin_view_actions', $this->view_actions );
604 765 }
605 766
@@ -611,15 +772,15 @@
611 772 * Handle Bulk Actions (Copy, Export, Delete) on "All Tables" list screen.
612 773 *
613 774 * @since 1.0.0
614 775 */
615 - public function handle_post_action_list() {
776 + public function handle_post_action_list(): void {
616 777 TablePress::check_nonce( 'list' );
617 778
618 - if ( isset( $_POST['bulk-action-top'] ) && '-1' !== $_POST['bulk-action-top'] ) {
619 - $bulk_action = $_POST['bulk-action-top'];
620 - } elseif ( isset( $_POST['bulk-action-bottom'] ) && '-1' !== $_POST['bulk-action-bottom'] ) {
621 - $bulk_action = $_POST['bulk-action-bottom'];
779 + if ( isset( $_POST['bulk-action-selector-top'] ) && '-1' !== $_POST['bulk-action-selector-top'] ) {
780 + $bulk_action = $_POST['bulk-action-selector-top'];
781 + } elseif ( isset( $_POST['bulk-action-selector-bottom'] ) && '-1' !== $_POST['bulk-action-selector-bottom'] ) {
782 + $bulk_action = $_POST['bulk-action-selector-bottom'];
622 783 } else {
623 784 $bulk_action = false;
624 785 }
625 786
@@ -628,17 +789,16 @@
628 789 }
629 790
630 791 if ( empty( $_POST['table'] ) || ! is_array( $_POST['table'] ) ) {
631 792 TablePress::redirect( array( 'action' => 'list', 'message' => 'error_no_selection' ) );
632 - } else {
633 - $tables = wp_unslash( $_POST['table'] );
634 793 }
635 794
636 - $no_success = array(); // to store table IDs that failed
795 + $tables = wp_unslash( $_POST['table'] );
637 796
797 + $no_success = array(); // To store table IDs that failed.
798 +
638 799 switch ( $bulk_action ) {
639 800 case 'copy':
640 - $this->init_i18n_support(); // for the translation of "Copy of"
641 801 foreach ( $tables as $table_id ) {
642 802 if ( current_user_can( 'tablepress_copy_table', $table_id ) ) {
643 803 $copy_table_id = TablePress::$model_table->copy( $table_id );
644 804 if ( is_wp_error( $copy_table_id ) ) {
@@ -655,9 +815,9 @@
655 815 * To export, redirect to "Export" screen, with selected table IDs.
656 816 */
657 817 $table_ids = implode( ',', $tables );
658 818 TablePress::redirect( array( 'action' => 'export', 'table_id' => $table_ids ) );
659 - break;
819 + // break; // unreachable.
660 820 case 'delete':
661 821 foreach ( $tables as $table_id ) {
662 822 if ( current_user_can( 'tablepress_delete_table', $table_id ) ) {
663 823 $deleted = TablePress::$model_table->delete( $table_id );
@@ -670,9 +830,9 @@
670 830 }
671 831 break;
672 832 }
673 833
674 - 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?
675 835 $message = "error_{$bulk_action}_not_all_tables";
676 836 } else {
677 837 $plural = ( count( $tables ) > 1 ) ? '_plural' : '';
678 838 $message = "success_{$bulk_action}{$plural}";
@@ -693,122 +853,36 @@
693 853 exit;
694 854 }
695 855
696 856 /**
697 - * Save a table after the "Edit" screen was submitted.
698 - *
699 - * @since 1.0.0
700 - */
701 - public function handle_post_action_edit() {
702 - if ( empty( $_POST['table'] ) || empty( $_POST['table']['id'] ) ) {
703 - TablePress::redirect( array( 'action' => 'list', 'message' => 'error_save' ) );
704 - } else {
705 - $edit_table = wp_unslash( $_POST['table'] );
706 - }
707 -
708 - TablePress::check_nonce( 'edit', $edit_table['id'], 'nonce-edit-table' );
709 -
710 - if ( ! current_user_can( 'tablepress_edit_table', $edit_table['id'] ) ) {
711 - wp_die( __( 'You do not have sufficient permissions to access this page.', 'default' ), 403 );
712 - }
713 -
714 - // Options array must exist, so that checkboxes can be evaluated.
715 - if ( empty( $edit_table['options'] ) ) {
716 - TablePress::redirect( array( 'action' => 'edit', 'table_id' => $edit_table['id'], 'message' => 'error_save' ) );
717 - }
718 -
719 - // Evaluate options that have a checkbox (only necessary in Admin Controller, where they might not be set (if unchecked)).
720 - $checkbox_options = array(
721 - // Table Options.
722 - 'table_head',
723 - 'table_foot',
724 - 'alternating_row_colors',
725 - 'row_hover',
726 - 'print_name',
727 - 'print_description',
728 - // DataTables JS Features.
729 - 'use_datatables',
730 - 'datatables_sort',
731 - 'datatables_filter',
732 - 'datatables_paginate',
733 - 'datatables_lengthchange',
734 - 'datatables_info',
735 - 'datatables_scrollx',
736 - );
737 - foreach ( $checkbox_options as $option ) {
738 - $edit_table['options'][ $option ] = ( isset( $edit_table['options'][ $option ] ) && 'true' === $edit_table['options'][ $option ] );
739 - }
740 -
741 - // Load table, without table data, but with options and visibility settings.
742 - $existing_table = TablePress::$model_table->load( $edit_table['id'], false, true );
743 - if ( is_wp_error( $existing_table ) ) { // @TODO: Maybe somehow load a new table here? (TablePress::$model_table->get_table_template())?
744 - TablePress::redirect( array( 'action' => 'edit', 'table_id' => $edit_table['id'], 'message' => 'error_save' ) );
745 - }
746 -
747 - // Check consistency of new table, and then merge with existing table.
748 - $table = TablePress::$model_table->prepare_table( $existing_table, $edit_table );
749 - if ( is_wp_error( $table ) ) {
750 - TablePress::redirect( array( 'action' => 'edit', 'table_id' => $edit_table['id'], 'message' => 'error_save' ) );
751 - }
752 -
753 - // DataTables Custom Commands can only be edit by trusted users.
754 - if ( ! current_user_can( 'unfiltered_html' ) ) {
755 - $table['options']['datatables_custom_commands'] = $existing_table['options']['datatables_custom_commands'];
756 - }
757 -
758 - // Save updated table.
759 - $saved = TablePress::$model_table->save( $table );
760 - if ( is_wp_error( $saved ) ) {
761 - TablePress::redirect( array( 'action' => 'edit', 'table_id' => $table['id'], 'message' => 'error_save' ) );
762 - }
763 -
764 - // Check if ID change is desired.
765 - if ( $table['id'] === $table['new_id'] ) { // if not, we are done
766 - TablePress::redirect( array( 'action' => 'edit', 'table_id' => $table['id'], 'message' => 'success_save' ) );
767 - }
768 -
769 - // Change table ID.
770 - if ( current_user_can( 'tablepress_edit_table_id', $table['id'] ) ) {
771 - $id_changed = TablePress::$model_table->change_table_id( $table['id'], $table['new_id'] );
772 - if ( ! is_wp_error( $id_changed ) ) {
773 - TablePress::redirect( array( 'action' => 'edit', 'table_id' => $table['new_id'], 'message' => 'success_save_success_id_change' ) );
774 - } else {
775 - TablePress::redirect( array( 'action' => 'edit', 'table_id' => $table['id'], 'message' => 'success_save_error_id_change' ) );
776 - }
777 - } else {
778 - TablePress::redirect( array( 'action' => 'edit', 'table_id' => $table['id'], 'message' => 'success_save_error_id_change' ) );
779 - }
780 - }
781 -
782 - /**
783 857 * Add a table, according to the parameters on the "Add new Table" screen.
784 858 *
785 859 * @since 1.0.0
786 860 */
787 - public function handle_post_action_add() {
861 + public function handle_post_action_add(): void {
788 862 TablePress::check_nonce( 'add' );
789 863
790 864 if ( ! current_user_can( 'tablepress_add_tables' ) ) {
791 - wp_die( __( 'You do not have sufficient permissions to access this page.', 'default' ), 403 );
865 + wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
792 866 }
793 867
794 868 if ( empty( $_POST['table'] ) || ! is_array( $_POST['table'] ) ) {
795 - TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add' ) );
796 - } else {
797 - $add_table = wp_unslash( $_POST['table'] );
869 + TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add', 'error_details' => 'The HTTP POST data is empty.' ) );
798 870 }
799 871
800 - // Perform sanity checks of posted data.
801 - $name = ( isset( $add_table['name'] ) ) ? $add_table['name'] : '';
802 - $description = ( isset( $add_table['description'] ) ) ? $add_table['description'] : '';
803 - if ( ! isset( $add_table['rows'] ) || ! isset( $add_table['columns'] ) ) {
804 - TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add' ) );
872 + $add_table = wp_unslash( $_POST['table'] );
873 +
874 + // Perform confidence checks of posted data.
875 + $name = $add_table['name'] ?? '';
876 + $description = $add_table['description'] ?? '';
877 + if ( ! isset( $add_table['rows'], $add_table['columns'] ) ) {
878 + TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add', 'error_details' => 'The HTTP POST data does not contain the table size.' ) );
805 879 }
806 880
807 881 $num_rows = absint( $add_table['rows'] );
808 882 $num_columns = absint( $add_table['columns'] );
809 883 if ( 0 === $num_rows || 0 === $num_columns ) {
810 - TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add' ) );
884 + TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add', 'error_details' => 'The table size is invalid.' ) );
811 885 }
812 886
813 887 // Create a new table array with information from the posted data.
814 888 $new_table = array(
@@ -822,15 +896,15 @@
822 896 );
823 897 // Merge this data into an empty table template.
824 898 $table = TablePress::$model_table->prepare_table( TablePress::$model_table->get_table_template(), $new_table, false );
825 899 if ( is_wp_error( $table ) ) {
826 - TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add' ) );
900 + TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add', 'error_details' => TablePress::get_wp_error_string( $table ) ) );
827 901 }
828 902
829 903 // Add the new table (and get its first ID).
830 904 $table_id = TablePress::$model_table->add( $table );
831 905 if ( is_wp_error( $table_id ) ) {
832 - TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add' ) );
906 + TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add', 'error_details' => TablePress::get_wp_error_string( $table_id ) ) );
833 907 }
834 908
835 909 TablePress::redirect( array( 'action' => 'edit', 'table_id' => $table_id, 'message' => 'success_add' ) );
836 910 }
@@ -839,21 +913,21 @@
839 913 * Save changed "Plugin Options".
840 914 *
841 915 * @since 1.0.0
842 916 */
843 - public function handle_post_action_options() {
917 + public function handle_post_action_options(): void {
844 918 TablePress::check_nonce( 'options' );
845 919
846 920 if ( ! current_user_can( 'tablepress_access_options_screen' ) ) {
847 - wp_die( __( 'You do not have sufficient permissions to access this page.', 'default' ), 403 );
921 + wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
848 922 }
849 923
850 924 if ( empty( $_POST['options'] ) || ! is_array( $_POST['options'] ) ) {
851 925 TablePress::redirect( array( 'action' => 'options', 'message' => 'error_save' ) );
852 - } else {
853 - $posted_options = wp_unslash( $_POST['options'] );
854 926 }
855 927
928 + $posted_options = wp_unslash( $_POST['options'] );
929 +
856 930 // Valid new options that will be merged into existing ones.
857 931 $new_options = array();
858 932
859 933 // Check each posted option value, and (maybe) add it to the new options.
@@ -858,18 +932,18 @@
858 932
859 933 // Check each posted option value, and (maybe) add it to the new options.
860 934 if ( ! empty( $posted_options['admin_menu_parent_page'] ) && '-' !== $posted_options['admin_menu_parent_page'] ) {
861 935 $new_options['admin_menu_parent_page'] = $posted_options['admin_menu_parent_page'];
862 - // Re-init parent information, as TablePress::redirect() URL might be wrong otherwise.
936 + // Re-init parent information, as `TablePress::redirect()` URL might be wrong otherwise.
863 937 /** This filter is documented in classes/class-controller.php */
864 - $this->parent_page = apply_filters( 'tablepress_admin_menu_parent_page', $posted_options['admin_menu_parent_page'] );
865 - $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 );
866 940 }
867 941
868 942 // Custom CSS can only be saved if the user is allowed to do so.
869 943 $update_custom_css_files = false;
870 944 if ( current_user_can( 'tablepress_edit_options' ) ) {
871 - // Checkbox
945 + // Checkbox.
872 946 $new_options['use_custom_css'] = ( isset( $posted_options['use_custom_css'] ) && 'true' === $posted_options['use_custom_css'] );
873 947
874 948 if ( isset( $posted_options['custom_css'] ) ) {
875 949 $new_options['custom_css'] = $posted_options['custom_css'];
@@ -874,13 +948,20 @@
874 948 if ( isset( $posted_options['custom_css'] ) ) {
875 949 $new_options['custom_css'] = $posted_options['custom_css'];
876 950
877 951 $tablepress_css = TablePress::load_class( 'TablePress_CSS', 'class-css.php', 'classes' );
878 - // Sanitize and tidy up Custom CSS.
879 - $new_options['custom_css'] = $tablepress_css->sanitize_css( $new_options['custom_css'] );
880 - // Minify Custom CSS
881 - $new_options['custom_css_minified'] = $tablepress_css->minify_css( $new_options['custom_css'] );
882 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 +
883 964 // Maybe update CSS files as well.
884 965 $custom_css_file_contents = $tablepress_css->load_custom_css_from_file( 'normal' );
885 966 if ( false === $custom_css_file_contents ) {
886 967 $custom_css_file_contents = '';
@@ -911,28 +992,30 @@
911 992 * Export selected tables.
912 993 *
913 994 * @since 1.0.0
914 995 */
915 - public function handle_post_action_export() {
996 + public function handle_post_action_export(): void {
916 997 TablePress::check_nonce( 'export' );
917 998
918 999 if ( ! current_user_can( 'tablepress_export_tables' ) ) {
919 - wp_die( __( 'You do not have sufficient permissions to access this page.', 'default' ), 403 );
1000 + wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
920 1001 }
921 1002
922 1003 if ( empty( $_POST['export'] ) || ! is_array( $_POST['export'] ) ) {
923 - TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export' ) );
924 - } else {
925 - $export = wp_unslash( $_POST['export'] );
1004 + TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export', 'error_details' => 'The HTTP POST data is empty.' ) );
926 1005 }
927 1006
1007 + $export = wp_unslash( $_POST['export'] );
1008 +
1009 + if ( empty( $export['tables_list'] ) ) {
1010 + TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export', 'error_details' => 'The HTTP POST data does not contain tables.' ) );
1011 + }
1012 +
1013 + /** @var TablePress_Export $exporter */ // phpcs:ignore Generic.Commenting.DocComment.MissingShort
928 1014 $exporter = TablePress::load_class( 'TablePress_Export', 'class-export.php', 'classes' );
929 1015
930 - if ( empty( $export['tables'] ) ) {
931 - TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export' ) );
932 - }
933 1016 if ( empty( $export['format'] ) || ! isset( $exporter->export_formats[ $export['format'] ] ) ) {
934 - TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export' ) );
1017 + TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export', 'error_details' => 'The export format is invalid.' ) );
935 1018 }
936 1019 if ( empty( $export['csv_delimiter'] ) ) {
937 1020 // Set a value, so that the variable exists.
938 1021 $export['csv_delimiter'] = '';
@@ -937,13 +1020,12 @@
937 1020 // Set a value, so that the variable exists.
938 1021 $export['csv_delimiter'] = '';
939 1022 }
940 1023 if ( 'csv' === $export['format'] && ! isset( $exporter->csv_delimiters[ $export['csv_delimiter'] ] ) ) {
941 - TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export' ) );
1024 + TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export', 'error_details' => 'The CSV delimiter is invalid.' ) );
942 1025 }
943 1026
944 - // Use list of tables from concatenated field if available (as that's hopefully not truncated by Suhosin, which is possible for $export['tables']).
945 - $tables = ( ! empty( $export['tables_list'] ) ) ? explode( ',', $export['tables_list'] ) : $export['tables'];
1027 + $tables = explode( ',', $export['tables_list'] );
946 1028
947 1029 // Determine if ZIP file support is available.
948 1030 if ( $exporter->zip_support_available
949 1031 && ( ( isset( $export['zip_file'] ) && 'true' === $export['zip_file'] ) || count( $tables ) > 1 ) ) {
@@ -953,49 +1035,64 @@
953 1035 $export_to_zip = false;
954 1036 }
955 1037
956 1038 if ( ! $export_to_zip ) {
957 - // 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.
958 1040 if ( ! current_user_can( 'tablepress_export_table', $tables[0] ) ) {
959 - wp_die( __( 'You do not have sufficient permissions to access this page.', 'default' ), 403 );
1041 + wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
960 1042 }
961 1043 // Load table, with table data, options, and visibility settings.
962 1044 $table = TablePress::$model_table->load( $tables[0], true, true );
963 1045 if ( is_wp_error( $table ) ) {
964 - TablePress::redirect( array( 'action' => 'export', 'message' => 'error_load_table', 'export_format' => $export['format'], 'csv_delimiter' => $export['csv_delimiter'] ) );
1046 + TablePress::redirect( array( 'action' => 'export', 'message' => 'error_load_table', 'export_format' => $export['format'], 'csv_delimiter' => $export['csv_delimiter'], 'error_details' => TablePress::get_wp_error_string( $table ) ) );
965 1047 }
966 1048 if ( isset( $table['is_corrupted'] ) && $table['is_corrupted'] ) {
967 1049 TablePress::redirect( array( 'action' => 'export', 'message' => 'error_table_corrupted', 'export_format' => $export['format'], 'csv_delimiter' => $export['csv_delimiter'] ) );
968 1050 }
969 - $download_filename = sprintf( '%1$s-%2$s-%3$s.%4$s', $table['id'], $table['name'], date( 'Y-m-d' ), $export['format'] );
1051 + $download_filename = sprintf( '%1$s-%2$s-%3$s.%4$s', $table['id'], $table['name'], wp_date( 'Y-m-d' ), $export['format'] );
1052 + /**
1053 + * Filters the download filename of the exported table.
1054 + *
1055 + * @since 2.0.0
1056 + *
1057 + * @param string $download_filename The download filename of exported table.
1058 + * @param string $table_id Table ID of the exported table.
1059 + * @param string $table_name Table name of the exported table.
1060 + * @param string $export_format Format for the export ('csv', 'html', 'json', 'zip').
1061 + * @param bool $export_to_zip Whether the export is to a ZIP file (of multiple export files).
1062 + */
1063 + $download_filename = apply_filters( 'tablepress_export_filename', $download_filename, $table['id'], $table['name'], $export['format'], $export_to_zip );
970 1064 $download_filename = sanitize_file_name( $download_filename );
971 1065 // Export the table.
972 1066 $export_data = $exporter->export_table( $table, $export['format'], $export['csv_delimiter'] );
973 1067 /**
974 - * Filter the exported table data.
1068 + * Filters the exported table data.
975 1069 *
976 1070 * @since 1.6.0
977 1071 *
978 - * @param string $export_data The exported table data.
979 - * @param array $table Table to be exported.
980 - * @param string $export_format Format for the export ('csv', 'html', 'json').
981 - * @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.
982 1076 */
983 1077 $export_data = apply_filters( 'tablepress_export_data', $export_data, $table, $export['format'], $export['csv_delimiter'] );
984 1078 $download_data = $export_data;
985 1079 } else {
986 1080 // Zipping can use a lot of memory and execution time, but not this much hopefully.
987 - /** This filter is documented in the WordPress file wp-admin/admin.php */
988 - @ini_set( 'memory_limit', apply_filters( 'admin_memory_limit', WP_MAX_MEMORY_LIMIT ) );
989 - @set_time_limit( 300 );
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 + }
990 1085
991 1086 $zip_file = new ZipArchive();
992 - $download_filename = sprintf( 'tablepress-export-%1$s-%2$s.zip', date_i18n( 'Y-m-d-H-i-s' ), $export['format'] );
1087 + $download_filename = sprintf( 'tablepress-export-%1$s-%2$s.zip', wp_date( 'Y-m-d-H-i-s' ), $export['format'] );
1088 + /** This filter is documented in controllers/controller-admin.php */
1089 + $download_filename = apply_filters( 'tablepress_export_filename', $download_filename, '', '', $export['format'], $export_to_zip );
993 1090 $download_filename = sanitize_file_name( $download_filename );
994 1091 $full_filename = wp_tempnam( $download_filename );
995 - if ( true !== $zip_file->open( $full_filename, ZIPARCHIVE::OVERWRITE ) ) {
996 - @unlink( $full_filename );
997 - TablePress::redirect( array( 'action' => 'export', 'message' => 'error_create_zip_file', 'export_format' => $export['format'], 'csv_delimiter' => $export['csv_delimiter'] ) );
1092 + if ( true !== $zip_file->open( $full_filename, ZipArchive::OVERWRITE ) ) {
1093 + @unlink( $full_filename ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
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.' ) );
998 1095 }
999 1096
1000 1097 foreach ( $tables as $table_id ) {
1001 1098 // Don't export tables for which the user doesn't have the necessary export rights.
@@ -1014,24 +1111,31 @@
1014 1111 }
1015 1112 $export_data = $exporter->export_table( $table, $export['format'], $export['csv_delimiter'] );
1016 1113 /** This filter is documented in controllers/controller-admin.php */
1017 1114 $export_data = apply_filters( 'tablepress_export_data', $export_data, $table, $export['format'], $export['csv_delimiter'] );
1018 - $export_filename = sprintf( '%1$s-%2$s-%3$s.%4$s', $table['id'], $table['name'], date( 'Y-m-d' ), $export['format'] );
1115 + $export_filename = sprintf( '%1$s-%2$s-%3$s.%4$s', $table['id'], $table['name'], wp_date( 'Y-m-d' ), $export['format'] );
1116 + /** This filter is documented in controllers/controller-admin.php */
1117 + $export_filename = apply_filters( 'tablepress_export_filename', $export_filename, $table['id'], $table['name'], $export['format'], $export_to_zip );
1019 1118 $export_filename = sanitize_file_name( $export_filename );
1020 1119 $zip_file->addFromString( $export_filename, $export_data );
1021 1120 }
1022 1121
1023 1122 // If something went wrong, or no files were added to the ZIP file, bail out.
1024 - if ( ! ZIPARCHIVE::ER_OK === $zip_file->status || 0 === $zip_file->numFiles ) {
1123 + // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
1124 + if ( ZipArchive::ER_OK !== $zip_file->status || 0 === $zip_file->numFiles ) {
1025 1125 $zip_file->close();
1026 - @unlink( $full_filename );
1027 - TablePress::redirect( array( 'action' => 'export', 'message' => 'error_create_zip_file', 'export_format' => $export['format'], 'csv_delimiter' => $export['csv_delimiter'] ) );
1126 + @unlink( $full_filename ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
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.' ) );
1028 1128 }
1029 1129 $zip_file->close();
1030 1130
1031 1131 // Load contents of the ZIP file, to send it as a download.
1032 1132 $download_data = file_get_contents( $full_filename );
1033 - @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
1034 1138 }
1035 1139
1036 1140 // Send download headers for export file.
1037 1141 header( 'Content-Description: File Transfer' );
@@ -1043,9 +1147,9 @@
1043 1147 header( 'Pragma: public' );
1044 1148 header( 'Content-Length: ' . strlen( $download_data ) );
1045 1149 // $filetype = text/csv, text/html, application/json
1046 1150 // header( 'Content-Type: ' . $filetype. '; charset=' . get_option( 'blog_charset' ) );
1047 - @ob_end_clean();
1151 + @ob_end_clean(); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
1048 1152 flush();
1049 1153 echo $download_data;
1050 1154 exit;
1051 1155 }
@@ -1050,669 +1154,100 @@
1050 1154 exit;
1051 1155 }
1052 1156
1053 1157 /**
1054 - * Import data from either an existing source or WP-Table Reloaded.
1158 + * Import data from existing source (Upload, URL, Server, Direct input).
1055 1159 *
1056 1160 * @since 1.0.0
1057 1161 */
1058 - public function handle_post_action_import() {
1162 + public function handle_post_action_import(): void {
1059 1163 TablePress::check_nonce( 'import' );
1060 1164
1061 1165 if ( ! current_user_can( 'tablepress_import_tables' ) ) {
1062 - wp_die( __( 'You do not have sufficient permissions to access this page.', 'default' ), 403 );
1166 + wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
1063 1167 }
1064 1168
1065 1169 if ( empty( $_POST['import'] ) || ! is_array( $_POST['import'] ) ) {
1066 - TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import' ) );
1067 - } else {
1068 - $import = wp_unslash( $_POST['import'] );
1170 + TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import', 'error_details' => 'The HTTP POST data is empty.' ) );
1069 1171 }
1070 1172
1071 - // Determine if this is a regular import or an import from WP-Table Reloaded.
1072 - if ( isset( $_POST['submit_wp_table_reloaded_import'] ) && isset( $import['wp_table_reloaded'] ) && isset( $import['wp_table_reloaded']['source'] ) ) {
1073 - if ( ! current_user_can( 'tablepress_import_tables_wptr' ) ) {
1074 - wp_die( __( 'You do not have sufficient permissions to access this page.', 'default' ), 403 );
1075 - }
1173 + $import_config = wp_unslash( $_POST['import'] );
1076 1174
1077 - // Handle checkbox selections.
1078 - $import_tables = ( isset( $import['wp_table_reloaded']['tables'] ) && 'true' === $import['wp_table_reloaded']['tables'] );
1079 - $import_css = ( isset( $import['wp_table_reloaded']['css'] ) && 'true' === $import['wp_table_reloaded']['css'] );
1080 - if ( ! $import_tables && ! $import_css ) {
1081 - TablePress::redirect( array( 'action' => 'import', 'message' => 'error_wp_table_reloaded_nothing_selected' ) );
1082 - }
1083 -
1084 - if ( 'db' === $import['wp_table_reloaded']['source'] ) {
1085 - $this->_import_from_wp_table_reloaded_db( $import_tables, $import_css );
1086 - } else {
1087 - $this->_import_from_wp_table_reloaded_dump_file( $import_tables, $import_css );
1088 - }
1089 - } else {
1090 - $this->_import_tablepress_regular( $import );
1175 + if ( empty( $import_config['source'] ) ) {
1176 + TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import', 'error_details' => 'The HTTP POST does not contain an import configuration.' ) );
1091 1177 }
1092 - }
1093 1178
1094 - /**
1095 - * Import data from existing source (Upload, URL, Server, Direct input).
1096 - *
1097 - * @since 1.0.0
1098 - *
1099 - * @param array $import Submitted form data.
1100 - */
1101 - protected function _import_tablepress_regular( array $import ) {
1102 - if ( ! isset( $import['type'] ) ) {
1103 - $import['type'] = 'add';
1104 - }
1105 - if ( ! isset( $import['existing_table'] ) ) {
1106 - $import['existing_table'] = '';
1107 - }
1108 - if ( ! isset( $import['source'] ) ) {
1109 - $import['source'] = '';
1110 - }
1111 -
1112 - // Check if a table to replace or append to was selected.
1113 - if ( in_array( $import['type'], array( 'replace', 'append' ), true ) && empty( $import['existing_table'] ) ) {
1114 - TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import_no_existing_id', 'import_format' => $import['format'], 'import_type' => $import['type'], 'import_source' => $import['source'] ) );
1115 - }
1116 -
1117 - $import_error = true;
1118 - $unlink_file = false;
1119 - $import_data = array();
1120 - switch ( $import['source'] ) {
1121 - case 'file-upload':
1122 - if ( ! empty( $_FILES['import_file_upload'] ) && UPLOAD_ERR_OK === $_FILES['import_file_upload']['error'] ) {
1123 - $import_data['file_location'] = $_FILES['import_file_upload']['tmp_name'];
1124 - $import_data['file_name'] = $_FILES['import_file_upload']['name'];
1125 - // $_FILES['import_file_upload']['type'];
1126 - // $_FILES['import_file_upload']['size']
1127 - $import_error = false;
1128 - $unlink_file = true;
1129 - }
1130 - break;
1131 - case 'url':
1132 - if ( ! empty( $import['url'] ) && 'http://' !== $import['url'] ) {
1133 - // Check the host of the Import URL against a blacklist of hosts, which should not be accessible, e.g. for security considerations.
1134 - $host = wp_parse_url( $import['url'], PHP_URL_HOST );
1135 - $blocked_hosts = array(
1136 - '169.254.169.254' // AWS Meta-data API
1137 - );
1138 - if ( in_array( $host, $blocked_hosts, true ) ) {
1139 - $import_error = true;
1140 - break;
1141 - }
1142 -
1143 - // Download URL to local file.
1144 - $import_data['file_location'] = download_url( $import['url'] );
1145 - $import_data['file_name'] = $import['url'];
1146 - if ( ! is_wp_error( $import_data['file_location'] ) ) {
1147 - $import_error = false;
1148 - }
1149 - $unlink_file = true;
1150 - }
1151 - break;
1152 - case 'server':
1153 - if ( ! empty( $import['server'] ) && ABSPATH !== $import['server']
1154 - && ( ( ! is_multisite() && current_user_can( 'manage_options' ) ) || is_super_admin() )
1155 - ) {
1156 - // For security reasons, the `server` source is only available for administrators.
1157 - $import_data['file_location'] = $import['server'];
1158 - $import_data['file_name'] = pathinfo( $import['server'], PATHINFO_BASENAME );
1159 - if ( is_readable( $import['server'] ) ) {
1160 - $import_error = false;
1161 - }
1162 - }
1163 - break;
1164 - case 'form-field':
1165 - if ( ! empty( $import['form_field'] ) ) {
1166 - $import_data['file_location'] = '';
1167 - $import_data['file_name'] = __( 'Imported from Manual Input', 'tablepress' ); // Description of the table.
1168 - $import_data['data'] = $import['form_field'];
1169 - $import_error = false;
1170 - }
1171 - break;
1172 - }
1173 -
1174 - if ( $import_error ) {
1175 - if ( $unlink_file ) {
1176 - @unlink( $import_data['file_location'] );
1179 + // For security reasons, the "server" source is only available for super admins on multisite and admins on single sites.
1180 + if ( 'server' === $import_config['source'] ) {
1181 + if ( ! is_super_admin() && ! ( ! is_multisite() && current_user_can( 'manage_options' ) ) ) {
1182 + TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import', 'error_details' => 'You do not have the required access rights.' ) );
1177 1183 }
1178 - TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import_source_invalid', 'import_format' => $import['format'], 'import_type' => $import['type'], 'import_existing_table' => $import['existing_table'], 'import_source' => $import['source'] ) );
1179 1184 }
1180 1185
1181 - $this->importer = TablePress::load_class( 'TablePress_Import', 'class-import.php', 'classes' );
1182 -
1183 - if ( 'zip' === pathinfo( $import_data['file_name'], PATHINFO_EXTENSION ) ) {
1184 - // Determine if ZIP file support is available.
1185 - if ( ! $this->importer->zip_support_available ) {
1186 - if ( $unlink_file ) {
1187 - @unlink( $import_data['file_location'] );
1188 - }
1189 - TablePress::redirect( array( 'action' => 'import', 'message' => 'error_no_zip_import', 'import_format' => $import['format'], 'import_type' => $import['type'], 'import_existing_table' => $import['existing_table'], 'import_source' => $import['source'] ) );
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 1190 }
1191 - $import_zip = true;
1192 - } else {
1193 - $import_zip = false;
1194 1191 }
1195 1192
1196 - if ( ! $import_zip ) {
1197 - if ( ! isset( $import_data['data'] ) ) {
1198 - $import_data['data'] = file_get_contents( $import_data['file_location'] );
1199 - }
1200 - if ( false === $import_data['data'] ) {
1201 - TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import' ) );
1202 - }
1193 + // Move file upload data to the main import configuration.
1194 + $import_config['file-upload'] = $_FILES['import_file_upload'] ?? null;
1203 1195
1204 - $name = $import_data['file_name'];
1205 - $description = $import_data['file_name'];
1206 - $existing_table_id = ( in_array( $import['type'], array( 'replace', 'append' ), true ) && ! empty( $import['existing_table'] ) ) ? $import['existing_table'] : false;
1207 - $table_id = $this->_import_tablepress_table( $import['format'], $import_data['data'], $name, $description, $existing_table_id, $import['type'] );
1208 -
1209 - if ( $unlink_file ) {
1210 - @unlink( $import_data['file_location'] );
1211 - }
1212 -
1213 - if ( is_wp_error( $table_id ) ) {
1214 - TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import_data' ) );
1215 - } else {
1216 - TablePress::redirect( array( 'action' => 'edit', 'table_id' => $table_id, 'message' => 'success_import' ) );
1217 - }
1218 - } else {
1219 - // Zipping can use a lot of memory and execution time, but not this much hopefully.
1220 - /** This filter is documented in the WordPress file wp-admin/admin.php */
1221 - @ini_set( 'memory_limit', apply_filters( 'admin_memory_limit', WP_MAX_MEMORY_LIMIT ) );
1222 - @set_time_limit( 300 );
1223 -
1224 - $zip = new ZipArchive();
1225 - if ( true !== $zip->open( $import_data['file_location'], ZIPARCHIVE::CHECKCONS ) ) {
1226 - if ( $unlink_file ) {
1227 - @unlink( $import_data['file_location'] );
1228 - }
1229 - TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import_zip_open' ) );
1230 - }
1231 -
1232 - $imported_files = array();
1233 - for ( $file_idx = 0; $file_idx < $zip->numFiles; $file_idx++ ) {
1234 - $file_name = $zip->getNameIndex( $file_idx );
1235 - // Skip directories.
1236 - if ( '/' === substr( $file_name, -1 ) ) {
1237 - continue;
1238 - }
1239 - // Skip the __MACOSX directory that Mac OSX adds to archives.
1240 - if ( '__MACOSX/' === substr( $file_name, 0, 9 ) ) {
1241 - continue;
1242 - }
1243 - $data = $zip->getFromIndex( $file_idx );
1244 - if ( false === $data ) {
1245 - continue;
1246 - }
1247 -
1248 - $name = $file_name;
1249 - $description = $file_name;
1250 - $existing_table_id = ( in_array( $import['type'], array( 'replace', 'append' ), true ) ) ? false : false; // @TODO: Find a way to extract the replace/append ID from the filename, maybe?
1251 - $table_id = $this->_import_tablepress_table( $import['format'], $data, $name, $description, $existing_table_id, 'add' );
1252 - if ( is_wp_error( $table_id ) ) {
1253 - continue;
1254 - } else {
1255 - $imported_files[] = $table_id;
1256 - }
1257 - };
1258 - $zip->close();
1259 -
1260 - if ( $unlink_file ) {
1261 - @unlink( $import_data['file_location'] );
1262 - }
1263 -
1264 - if ( count( $imported_files ) > 1 ) {
1265 - TablePress::redirect( array( 'action' => 'list', 'message' => 'success_import' ) );
1266 - } elseif ( 1 === count( $imported_files ) ) {
1267 - TablePress::redirect( array( 'action' => 'edit', 'table_id' => $imported_files[0], 'message' => 'success_import' ) );
1268 - } else {
1269 - TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import_zip_content' ) );
1270 - }
1196 + // Check if the source data for the chosen import source is defined.
1197 + if ( empty( $import_config[ $import_config['source'] ] ) ) {
1198 + TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import', 'error_details' => 'The HTTP POST data does not contain an import source.' ) );
1271 1199 }
1272 1200
1273 - }
1274 -
1275 - /**
1276 - * Import a table by either replacing an existing table or adding it as a new table.
1277 - *
1278 - * @since 1.0.0
1279 - *
1280 - * @param string $format Import format.
1281 - * @param string $data Data to import.
1282 - * @param string $name Name of the table.
1283 - * @param string $description Description of the table.
1284 - * @param bool|string $existing_table_id False if table shall be added new, ID of the table to be replaced or appended to otherwise.
1285 - * @param string $import_type What to do with the imported data: "add", "replace", "append".
1286 - * @return string|WP_Error WP_Error on error, table ID on success.
1287 - */
1288 - protected function _import_tablepress_table( $format, $data, $name, $description, $existing_table_id, $import_type ) {
1289 - $imported_table = $this->importer->import_table( $format, $data );
1290 - if ( false === $imported_table ) {
1291 - return new WP_Error( 'table_import_import_failed' );
1201 + // Set default values for non-essential configuration variables.
1202 + if ( ! isset( $import_config['type'] ) ) {
1203 + $import_config['type'] = 'add';
1292 1204 }
1293 -
1294 - if ( false === $existing_table_id ) {
1295 - $import_type = 'add';
1205 + if ( ! isset( $import_config['existing_table'] ) ) {
1206 + $import_config['existing_table'] = '';
1296 1207 }
1297 1208
1298 - // To be able to replace or append to a table, editing that table must be allowed.
1299 - if ( in_array( $import_type, array( 'replace', 'append' ), true ) && ! current_user_can( 'tablepress_edit_table', $existing_table_id ) ) {
1300 - return new WP_Error( 'table_import_replace_append_capability_check_failed' );
1301 - }
1209 + $import_config['legacy_import'] = ( isset( $import_config['legacy_import'] ) && 'true' === $import_config['legacy_import'] );
1302 1210
1303 - // Full JSON format table can contain a table ID, try to keep that.
1304 - $table_id_in_import = false;
1211 + $importer = TablePress::load_class( 'TablePress_Import', 'class-import.php', 'classes' );
1212 + $import = $importer->run( $import_config );
1305 1213
1306 - switch ( $import_type ) {
1307 - case 'add':
1308 - $existing_table = TablePress::$model_table->get_table_template();
1309 - // If name and description are imported from a new table, use those.
1310 - if ( isset( $imported_table['id'] ) ) {
1311 - $table_id_in_import = $imported_table['id'];
1312 - }
1313 - if ( ! isset( $imported_table['name'] ) ) {
1314 - $imported_table['name'] = $name;
1315 - }
1316 - if ( ! isset( $imported_table['description'] ) ) {
1317 - $imported_table['description'] = $description;
1318 - }
1319 - if ( isset( $imported_table['visibility'] ) && isset( $imported_table['visibility']['rows'] ) && isset( $imported_table['visibility']['columns'] ) ) {
1320 - $existing_table['visibility']['rows'] = $imported_table['visibility']['rows'];
1321 - $existing_table['visibility']['columns'] = $imported_table['visibility']['columns'];
1322 - }
1323 - break;
1324 - case 'replace':
1325 - // Load table, without table data, but with options and visibility settings.
1326 - $existing_table = TablePress::$model_table->load( $existing_table_id, false, true );
1327 - if ( is_wp_error( $existing_table ) ) {
1328 - // Add an error code to the existing WP_Error.
1329 - $existing_table->add( 'table_import_replace_table_load', '', $existing_table_id );
1330 - return $existing_table;
1331 - }
1332 - // Don't change name and description when a table is replaced.
1333 - $imported_table['name'] = $existing_table['name'];
1334 - $imported_table['description'] = $existing_table['description'];
1335 - if ( isset( $imported_table['visibility'] ) && isset( $imported_table['visibility']['rows'] ) && isset( $imported_table['visibility']['columns'] ) ) {
1336 - $existing_table['visibility']['rows'] = $imported_table['visibility']['rows'];
1337 - $existing_table['visibility']['columns'] = $imported_table['visibility']['columns'];
1338 - }
1339 - break;
1340 - case 'append':
1341 - // Load table, with table data, options, and visibility settings.
1342 - $existing_table = TablePress::$model_table->load( $existing_table_id, true, true );
1343 - if ( is_wp_error( $existing_table ) ) {
1344 - // Add an error code to the existing WP_Error.
1345 - $existing_table->add( 'table_import_append_table_load', '', $existing_table_id );
1346 - return $existing_table;
1347 - }
1348 - if ( isset( $existing_table['is_corrupted'] ) && $existing_table['is_corrupted'] ) {
1349 - return new WP_Error( 'table_import_append_table_load_corrupted', '', $existing_table_id );
1350 - }
1351 - // Don't change name and description when a table is appended to.
1352 - $imported_table['name'] = $existing_table['name'];
1353 - $imported_table['description'] = $existing_table['description'];
1354 - // Actual appending:
1355 - $imported_table['data'] = array_merge( $existing_table['data'], $imported_table['data'] );
1356 - $imported_table['data'] = $this->importer->pad_array_to_max_cols( $imported_table['data'] );
1357 - // Append visibility information for rows.
1358 - if ( isset( $imported_table['visibility'] ) && isset( $imported_table['visibility']['rows'] ) ) {
1359 - $existing_table['visibility']['rows'] = array_merge( $existing_table['visibility']['rows'], $imported_table['visibility']['rows'] );
1360 - }
1361 - // When appending, do not overwrite options.
1362 - if ( isset( $imported_table['options'] ) ) {
1363 - unset( $imported_table['options'] );
1364 - }
1365 - break;
1366 - default:
1367 - return new WP_Error( 'table_import_import_type_invalid', '', $import_type );
1368 - }
1369 -
1370 - // Merge new or existing table with information from the imported table.
1371 - $imported_table['id'] = $existing_table['id']; // will be false for new table or the existing table ID
1372 - // Cut visibility array (if the imported table is smaller), and pad correctly if imported table is bigger than existing table (or new template).
1373 - $num_rows = count( $imported_table['data'] );
1374 - $num_columns = count( $imported_table['data'][0] );
1375 - $imported_table['visibility'] = array(
1376 - 'rows' => array_pad( array_slice( $existing_table['visibility']['rows'], 0, $num_rows ), $num_rows, 1 ),
1377 - 'columns' => array_pad( array_slice( $existing_table['visibility']['columns'], 0, $num_columns ), $num_columns, 1 ),
1378 - );
1379 -
1380 - // Check if new data is ok.
1381 - $table = TablePress::$model_table->prepare_table( $existing_table, $imported_table, false );
1382 - if ( is_wp_error( $table ) ) {
1383 - // Add an error code to the existing WP_Error.
1384 - $table->add( 'table_import_table_prepare', '' );
1385 - return $table;
1386 - }
1387 -
1388 - // DataTables Custom Commands can only be edit by trusted users.
1389 - if ( ! current_user_can( 'unfiltered_html' ) ) {
1390 - $table['options']['datatables_custom_commands'] = $existing_table['options']['datatables_custom_commands'];
1391 - }
1392 -
1393 - // Replace existing table or add new table.
1394 - if ( in_array( $import_type, array( 'replace', 'append' ), true ) ) {
1395 - // Replace existing table with imported/appended table.
1396 - $table_id = TablePress::$model_table->save( $table );
1397 - } else {
1398 - // Add the imported table (and get its first ID).
1399 - $table_id = TablePress::$model_table->add( $table );
1400 - }
1401 -
1402 - if ( is_wp_error( $table_id ) ) {
1403 - // Add an error code to the existing WP_Error.
1404 - $table_id->add( 'table_import_table_save_or_add', '' );
1405 - return $table_id;
1406 - }
1407 -
1408 - // Try to use ID from imported file (e.g. in full JSON format table).
1409 - if ( false !== $table_id_in_import && $table_id !== $table_id_in_import && current_user_can( 'tablepress_edit_table_id', $table_id ) ) {
1410 - $id_changed = TablePress::$model_table->change_table_id( $table_id, $table_id_in_import );
1411 - if ( ! is_wp_error( $id_changed ) ) {
1412 - $table_id = $table_id_in_import;
1214 + if ( is_wp_error( $import ) || 0 < count( $import['errors'] ) ) {
1215 + $redirect_parameters = array(
1216 + 'action' => 'import',
1217 + 'message' => 'error_import',
1218 + 'import_type' => $import_config['type'],
1219 + 'import_existing_table' => $import_config['existing_table'],
1220 + 'import_source' => $import_config['source'],
1221 + 'legacy_import' => $import_config['legacy_import'],
1222 + );
1223 + if ( in_array( $import_config['source'], array( 'url', 'server' ), true ) ) {
1224 + $redirect_parameters[ "import_{$import_config['source']}" ] = $import_config[ $import_config['source'] ];
1413 1225 }
1414 - }
1415 -
1416 - return $table_id;
1417 - }
1418 -
1419 - /**
1420 - * Import data from WP-Table Reloaded from the WordPress database.
1421 - *
1422 - * @since 1.0.0
1423 - *
1424 - * @param bool $import_tables Whether tables shall be imported.
1425 - * @param bool $import_css Whether Plugin Options (only CSS related right now) shall be imported.
1426 - */
1427 - protected function _import_from_wp_table_reloaded_db( $import_tables, $import_css ) {
1428 - if ( false === get_option( 'wp_table_reloaded_options', false ) || false === get_option( 'wp_table_reloaded_tables', false ) ) {
1429 - TablePress::redirect( array( 'action' => 'import', 'message' => 'error_wp_table_reloaded_not_installed' ) );
1430 - }
1431 -
1432 - $wp_table_reloaded_options = get_option( 'wp_table_reloaded_options', false );
1433 - if ( empty( $wp_table_reloaded_options ) ) {
1434 - $wp_table_reloaded_options = array();
1435 - }
1436 -
1437 - // Import WP-Table Reloaded tables.
1438 - $not_imported_tables = $imported_tables = $imported_other_id_tables = array();
1439 - if ( $import_tables ) {
1440 - $wp_table_reloaded_tables_list = get_option( 'wp_table_reloaded_tables', array() );
1441 - foreach ( $wp_table_reloaded_tables_list as $wptr_table_id => $table_option_name ) {
1442 - $wptr_table = get_option( $table_option_name, array() );
1443 - $import_status = $this->_import_wp_table_reloaded_table( $wptr_table, $wp_table_reloaded_options );
1444 - switch ( $import_status ) {
1445 - case 0:
1446 - $not_imported_tables[] = $wptr_table_id;
1447 - break;
1448 - case 1:
1449 - $imported_tables[] = $wptr_table_id;
1450 - break;
1451 - case 2:
1452 - $imported_other_id_tables[] = $wptr_table_id;
1453 - break;
1226 + if ( is_wp_error( $import ) ) {
1227 + $redirect_parameters['error_details'] = TablePress::get_wp_error_string( $import );
1228 + } elseif ( 0 < count( $import['errors'] ) ) {
1229 + $wp_error_strings = array();
1230 + foreach ( $import['errors'] as $file ) {
1231 + $wp_error_strings[] = TablePress::get_wp_error_string( $file->error );
1454 1232 }
1233 + $redirect_parameters['error_details'] = implode( ', ', $wp_error_strings );
1455 1234 }
1235 + TablePress::redirect( $redirect_parameters );
1456 1236 }
1457 1237
1458 - // Import WP-Table Reloaded Plugin Options (currently only CSS related options).
1459 - $imported_css = false;
1460 - if ( $import_css ) {
1461 - $imported_css = $this->_import_wp_table_reloaded_plugin_options( $wp_table_reloaded_options );
1462 - }
1463 -
1464 - // @TODO: Better handling of the different cases of imported/imported-without-ID-change/not-imported tables.
1465 - if ( count( $imported_tables ) > 1 ) {
1466 - TablePress::redirect( array( 'action' => 'list', 'message' => 'success_import_wp_table_reloaded' ) );
1467 - } elseif ( 1 === count( $imported_tables ) ) {
1468 - TablePress::redirect( array( 'action' => 'edit', 'table_id' => $imported_tables[0], 'message' => 'success_import_wp_table_reloaded' ) );
1469 - }
1470 - if ( count( $imported_other_id_tables ) > 0 ) {
1471 - TablePress::redirect( array( 'action' => 'list', 'message' => 'success_import_wp_table_reloaded' ) );
1472 - } elseif ( $imported_css ) {
1473 - TablePress::redirect( array( 'action' => 'options', 'message' => 'success_import_wp_table_reloaded' ) );
1238 + // At this point, there were no import errors.
1239 + if ( count( $import['tables'] ) > 1 ) {
1240 + TablePress::redirect( array( 'action' => 'list', 'message' => 'success_import' ) );
1241 + } elseif ( 1 === count( $import['tables'] ) ) {
1242 + TablePress::redirect( array( 'action' => 'edit', 'table_id' => $import['tables'][0]['id'], 'message' => 'success_import' ) );
1474 1243 } else {
1475 - TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import_wp_table_reloaded' ) );
1244 + TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import', 'error_details' => 'The number of imported tables is invalid.' ) );
1476 1245 }
1477 1246 }
1478 1247
1479 - /**
1480 - * Import data from WP-Table Reloaded from a WP-Table Reloaded Dump File.
1481 - *
1482 - * @since 1.0.0
1483 - *
1484 - * @param bool $import_tables Whether tables shall be imported.
1485 - * @param bool $import_css Whether Plugin Options (only CSS related right now) shall be imported.
1486 - */
1487 - protected function _import_from_wp_table_reloaded_dump_file( $import_tables, $import_css ) {
1488 - if ( empty( $_FILES['import_wp_table_reloaded_file_upload'] ) || empty( $_FILES['import_wp_table_reloaded_file_upload']['tmp_name'] ) || UPLOAD_ERR_OK !== $_FILES['import_wp_table_reloaded_file_upload']['error'] ) {
1489 - TablePress::redirect( array( 'action' => 'import', 'message' => 'error_wp_table_reloaded_dump_file' ) );
1490 - }
1491 -
1492 - $dump_file = file_get_contents( $_FILES['import_wp_table_reloaded_file_upload']['tmp_name'] );
1493 - $dump_file = unserialize( $dump_file );
1494 - if ( empty( $dump_file ) ) {
1495 - @unlink( $_FILES['import_wp_table_reloaded_file_upload']['tmp_name'] );
1496 - TablePress::redirect( array( 'action' => 'import', 'message' => 'error_wp_table_reloaded_dump_file' ) );
1497 - }
1498 - if ( empty( $dump_file['options'] ) || ! is_array( $dump_file['options'] ) ) {
1499 - $dump_file['options'] = array();
1500 - }
1501 -
1502 - // Import WP-Table Reloaded tables.
1503 - $not_imported_tables = $imported_tables = $imported_other_id_tables = array();
1504 - if ( $import_tables && ! empty( $dump_file['tables'] ) ) {
1505 - foreach ( $dump_file['tables'] as $wptr_table_id => $wptr_table ) {
1506 - $import_status = $this->_import_wp_table_reloaded_table( $wptr_table, $dump_file['options'] );
1507 - switch ( $import_status ) {
1508 - case 0:
1509 - $not_imported_tables[] = $wptr_table_id;
1510 - break;
1511 - case 1:
1512 - $imported_tables[] = $wptr_table_id;
1513 - break;
1514 - case 2:
1515 - $imported_other_id_tables[] = $wptr_table_id;
1516 - break;
1517 - }
1518 - }
1519 - }
1520 -
1521 - // Import WP-Table Reloaded Plugin Options (currently only CSS related options).
1522 - $imported_css = false;
1523 - if ( $import_css ) {
1524 - $imported_css = $this->_import_wp_table_reloaded_plugin_options( $dump_file['options'] );
1525 - }
1526 -
1527 - @unlink( $_FILES['import_wp_table_reloaded_file_upload']['tmp_name'] );
1528 - // @TODO: Better handling of the different cases of imported/imported-without-ID-change/not-imported tables.
1529 - if ( count( $imported_tables ) > 1 ) {
1530 - TablePress::redirect( array( 'action' => 'list', 'message' => 'success_import_wp_table_reloaded' ) );
1531 - } elseif ( 1 === count( $imported_tables ) ) {
1532 - TablePress::redirect( array( 'action' => 'edit', 'table_id' => $imported_tables[0], 'message' => 'success_import_wp_table_reloaded' ) );
1533 - }
1534 - if ( count( $imported_other_id_tables ) > 0 ) {
1535 - TablePress::redirect( array( 'action' => 'list', 'message' => 'success_import_wp_table_reloaded' ) );
1536 - } elseif ( $imported_css ) {
1537 - TablePress::redirect( array( 'action' => 'options', 'message' => 'success_import_wp_table_reloaded' ) );
1538 - } else {
1539 - TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import_wp_table_reloaded' ) );
1540 - }
1541 - }
1542 -
1543 - /**
1544 - * Import a WP-Table Reloaded table.
1545 - *
1546 - * @since 1.0.0
1547 - *
1548 - * @param array $wptr_table WP-Table Reloaded table.
1549 - * @param array $wp_table_reloaded_options WP-Table Reloaded Plugin Options.
1550 - * @return int Import status: 0=Import failed; 1=Imported with ID change; 2=Imported without ID change.
1551 - */
1552 - protected function _import_wp_table_reloaded_table( array $wptr_table, array $wp_table_reloaded_options ) {
1553 - if ( empty( $wptr_table ) ) {
1554 - return 0; // 0 means Import failed.
1555 - }
1556 -
1557 - // Perform sanity checks of imported table.
1558 - if ( ! isset( $wptr_table['name'] )
1559 - || ! isset( $wptr_table['description'] )
1560 - || empty( $wptr_table['data'] )
1561 - || empty( $wptr_table['options'] ) ) {
1562 - return 0; // 0 means Import failed.
1563 - }
1564 -
1565 - // Data is slashed in WP-Table Reloaded.
1566 - $wptr_table = wp_unslash( $wptr_table );
1567 -
1568 - // Table was loaded, import the data, table options, and visibility.
1569 - //
1570 - // Create a new table array with information from the imported table.
1571 - $new_table = array(
1572 - 'name' => $wptr_table['name'],
1573 - 'description' => $wptr_table['description'],
1574 - 'data' => $wptr_table['data'],
1575 - 'options' => array(),
1576 - 'visibility' => array(
1577 - 'rows' => array_fill( 0, count( $wptr_table['data'] ), 1 ),
1578 - 'columns' => array_fill( 0, count( $wptr_table['data'][0] ), 1 ),
1579 - ),
1580 - );
1581 - if ( isset( $wptr_table['last_modified'] ) ) {
1582 - $new_table['last_modified'] = $wptr_table['last_modified'];
1583 - }
1584 - if ( isset( $wptr_table['last_editor_id'] ) ) {
1585 - $new_table['author'] = $wptr_table['last_editor_id'];
1586 - }
1587 - if ( isset( $wptr_table['options']['last_editor_id'] ) ) {
1588 - $new_table['options']['last_editor'] = $wptr_table['last_editor_id'];
1589 - }
1590 - if ( isset( $wptr_table['options']['first_row_th'] ) ) {
1591 - $new_table['options']['table_head'] = $wptr_table['options']['first_row_th'];
1592 - }
1593 - if ( isset( $wptr_table['options']['table_footer'] ) ) {
1594 - $new_table['options']['table_foot'] = $wptr_table['options']['table_footer'];
1595 - }
1596 - if ( isset( $wptr_table['options']['custom_css_class'] ) ) {
1597 - $new_table['options']['extra_css_classes'] = $wptr_table['options']['custom_css_class'];
1598 - }
1599 - if ( isset( $wptr_table['options']['use_tablesorter'] ) && isset( $wp_table_reloaded_options['enable_tablesorter'] ) ) {
1600 - if ( $wp_table_reloaded_options['enable_tablesorter'] ) {
1601 - $new_table['options']['use_datatables'] = $wptr_table['options']['use_tablesorter'];
1602 - } else {
1603 - $new_table['options']['use_datatables'] = false;
1604 - }
1605 - }
1606 - // Array key is the same in both plugins for the following options.
1607 - foreach ( array(
1608 - 'alternating_row_colors',
1609 - 'row_hover',
1610 - 'print_name',
1611 - 'print_name_position',
1612 - 'print_description',
1613 - 'print_description_position',
1614 - 'datatables_sort',
1615 - 'datatables_filter',
1616 - 'datatables_paginate',
1617 - 'datatables_lengthchange',
1618 - 'datatables_paginate_entries',
1619 - 'datatables_info',
1620 - ) as $_option ) {
1621 - if ( isset( $wptr_table['options'][ $_option ] ) ) {
1622 - $new_table['options'][ $_option ] = $wptr_table['options'][ $_option ];
1623 - }
1624 - }
1625 - if ( isset( $wptr_table['options']['datatables_customcommands'] ) && current_user_can( 'unfiltered_html' ) ) {
1626 - $new_table['options']['datatables_custom_commands'] = $wptr_table['options']['datatables_customcommands'];
1627 - }
1628 - // Not imported: $wptr_table['options']['cache_table_output'].
1629 - // Not imported: $wptr_table['custom_fields'].
1630 -
1631 - // Fix visibility: WP-Table Reloaded uses 0 and 1 the other way around.
1632 - foreach ( array_keys( $wptr_table['visibility']['rows'], true ) as $row_idx ) {
1633 - $new_table['visibility']['rows'][ $row_idx ] = 0;
1634 - }
1635 - foreach ( array_keys( $wptr_table['visibility']['columns'], true ) as $column_idx ) {
1636 - $new_table['visibility']['columns'][ $column_idx ] = 0;
1637 - }
1638 -
1639 - // Merge this data into an empty table template.
1640 - $table = TablePress::$model_table->prepare_table( TablePress::$model_table->get_table_template(), $new_table, false );
1641 - if ( is_wp_error( $table ) ) {
1642 - return 0; // 0 means Import failed.
1643 - }
1644 -
1645 - // Add the new table (and get its first ID).
1646 - $tp_table_id = TablePress::$model_table->add( $table );
1647 - if ( is_wp_error( $tp_table_id ) ) {
1648 - return 0; // 0 means Import failed.
1649 - }
1650 -
1651 - // Change table ID to the ID the table had in WP-Table Reloaded (except if that ID is already taken).
1652 - $id_changed = TablePress::$model_table->change_table_id( $tp_table_id, $wptr_table['id'] );
1653 - if ( is_wp_error( $id_changed ) ) {
1654 - return 2; // 2 means Imported without ID change.
1655 - }
1656 -
1657 - return 1; // 1 means Imported with ID change.
1658 - }
1659 -
1660 - /**
1661 - * Import WP-Table Reloaded Plugin Options (currently just CSS related options).
1662 - *
1663 - * @since 1.0.0
1664 - *
1665 - * @param array $wp_table_reloaded_options Plugin Options of WP-Table Reloaded that shall be imported.
1666 - * @return bool Whether the import was successful or not (on at least on option).
1667 - */
1668 - protected function _import_wp_table_reloaded_plugin_options( array $wp_table_reloaded_options ) {
1669 - if ( ! current_user_can( 'tablepress_edit_options' ) ) {
1670 - return false;
1671 - }
1672 -
1673 - $imported_options = array();
1674 - $imported_options['use_custom_css'] = ( isset( $wp_table_reloaded_options['use_custom_css'] ) ) ? (bool) $wp_table_reloaded_options['use_custom_css'] : false;
1675 - if ( isset( $wp_table_reloaded_options['custom_css'] ) ) {
1676 - // Automatically convert WP-Table Reloaded Custom CSS to TablePress Custom CSS by search/replacing classes and IDs.
1677 - $imported_options['custom_css'] = wp_unslash( $wp_table_reloaded_options['custom_css'] ); // Be careful when removing this, as it might break CSS comments from old Dump Files.
1678 - $imported_options['custom_css'] = str_replace( '#wp-table-reloaded-id-', '#tablepress-', $imported_options['custom_css'] );
1679 - $imported_options['custom_css'] = str_replace( '-no-1', '', $imported_options['custom_css'] );
1680 - $imported_options['custom_css'] = str_replace( '.wp-table-reloaded-id-', '.tablepress-id-', $imported_options['custom_css'] );
1681 - $imported_options['custom_css'] = str_replace( '.wp-table-reloaded', '.tablepress', $imported_options['custom_css'] );
1682 -
1683 - $tablepress_css = TablePress::load_class( 'TablePress_CSS', 'class-css.php', 'classes' );
1684 - // Sanitize and tidy up Custom CSS.
1685 - $imported_options['custom_css'] = $tablepress_css->sanitize_css( $imported_options['custom_css'] );
1686 - // Minify Custom CSS.
1687 - $imported_options['custom_css_minified'] = $tablepress_css->minify_css( $imported_options['custom_css'] );
1688 -
1689 - // Maybe update CSS files as well.
1690 - //
1691 - // Only write files, if "Custom CSS" is to be used, and if there is "Custom CSS"
1692 - if ( $imported_options['use_custom_css'] && '' !== $imported_options['custom_css'] ) {
1693 - $result = $tablepress_css->save_custom_css_to_file( $imported_options['custom_css'], $imported_options['custom_css_minified'] );
1694 - // If saving was successful, use "Custom CSS" file.
1695 - $imported_options['use_custom_css_file'] = $result;
1696 - // If saving was successful, increase the "Custom CSS" version number for cache busting.
1697 - if ( $result ) {
1698 - $imported_options['custom_css_version'] = TablePress::$model_options->get( 'custom_css_version' ) + 1;
1699 - }
1700 - }
1701 - }
1702 -
1703 - // Save gathered imported options.
1704 - if ( empty( $imported_options ) ) {
1705 - return false;
1706 - }
1707 -
1708 - TablePress::$model_options->update( $imported_options );
1709 -
1710 - return true;
1711 - }
1712 -
1713 1248 /*
1714 - * Save GET actions.
1249 + * HTTP GET actions.
1715 1250 */
1716 1251
1717 1252 /**
1718 1253 * Hide a header message on an admin screen.
@@ -1718,14 +1253,14 @@
1718 1253 * Hide a header message on an admin screen.
1719 1254 *
1720 1255 * @since 1.0.0
1721 1256 */
1722 - public function handle_get_action_hide_message() {
1257 + public function handle_get_action_hide_message(): void {
1723 1258 $message_item = ! empty( $_GET['item'] ) ? $_GET['item'] : '';
1724 1259 TablePress::check_nonce( 'hide_message', $message_item );
1725 1260
1726 1261 if ( ! current_user_can( 'tablepress_list_tables' ) ) {
1727 - wp_die( __( 'You do not have sufficient permissions to access this page.', 'default' ), 403 );
1262 + wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
1728 1263 }
1729 1264
1730 1265 TablePress::$model_options->update( "message_{$message_item}", false );
1731 1266
@@ -1737,9 +1272,9 @@
1737 1272 * Delete a table.
1738 1273 *
1739 1274 * @since 1.0.0
1740 1275 */
1741 - public function handle_get_action_delete_table() {
1276 + public function handle_get_action_delete_table(): void {
1742 1277 $table_id = ( ! empty( $_GET['item'] ) ) ? $_GET['item'] : false;
1743 1278 TablePress::check_nonce( 'delete_table', $table_id );
1744 1279
1745 1280 $return = ! empty( $_GET['return'] ) ? $_GET['return'] : 'list';
@@ -1744,20 +1279,20 @@
1744 1279
1745 1280 $return = ! empty( $_GET['return'] ) ? $_GET['return'] : 'list';
1746 1281 $return_item = ! empty( $_GET['return_item'] ) ? $_GET['return_item'] : false;
1747 1282
1748 - // Nonce check should actually catch this already.
1283 + // The nonce check should actually catch this already.
1749 1284 if ( false === $table_id ) {
1750 1285 TablePress::redirect( array( 'action' => $return, 'message' => 'error_delete', 'table_id' => $return_item ) );
1751 1286 }
1752 1287
1753 1288 if ( ! current_user_can( 'tablepress_delete_table', $table_id ) ) {
1754 - wp_die( __( 'You do not have sufficient permissions to access this page.', 'default' ), 403 );
1289 + wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
1755 1290 }
1756 1291
1757 1292 $deleted = TablePress::$model_table->delete( $table_id );
1758 1293 if ( is_wp_error( $deleted ) ) {
1759 - TablePress::redirect( array( 'action' => $return, 'message' => 'error_delete', 'table_id' => $return_item ) );
1294 + TablePress::redirect( array( 'action' => $return, 'message' => 'error_delete', 'table_id' => $return_item, 'error_details' => TablePress::get_wp_error_string( $deleted ) ) );
1760 1295 }
1761 1296
1762 1297 /*
1763 1298 * Slightly more complex redirect method, to account for sort, search, and pagination in the WP_List_Table on the List View,
@@ -1778,9 +1313,9 @@
1778 1313 * Copy a table.
1779 1314 *
1780 1315 * @since 1.0.0
1781 1316 */
1782 - public function handle_get_action_copy_table() {
1317 + public function handle_get_action_copy_table(): void {
1783 1318 $table_id = ( ! empty( $_GET['item'] ) ) ? $_GET['item'] : false;
1784 1319 TablePress::check_nonce( 'copy_table', $table_id );
1785 1320
1786 1321 $return = ! empty( $_GET['return'] ) ? $_GET['return'] : 'list';
@@ -1785,25 +1320,22 @@
1785 1320
1786 1321 $return = ! empty( $_GET['return'] ) ? $_GET['return'] : 'list';
1787 1322 $return_item = ! empty( $_GET['return_item'] ) ? $_GET['return_item'] : false;
1788 1323
1789 - // Nonce check should actually catch this already.
1324 + // The nonce check should actually catch this already.
1790 1325 if ( false === $table_id ) {
1791 1326 TablePress::redirect( array( 'action' => $return, 'message' => 'error_copy', 'table_id' => $return_item ) );
1792 1327 }
1793 1328
1794 1329 if ( ! current_user_can( 'tablepress_copy_table', $table_id ) ) {
1795 - wp_die( __( 'You do not have sufficient permissions to access this page.', 'default' ), 403 );
1330 + wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
1796 1331 }
1797 1332
1798 - $this->init_i18n_support(); // for the translation of "Copy of".
1799 -
1800 1333 $copy_table_id = TablePress::$model_table->copy( $table_id );
1801 1334 if ( is_wp_error( $copy_table_id ) ) {
1802 - TablePress::redirect( array( 'action' => $return, 'message' => 'error_copy', 'table_id' => $return_item ) );
1803 - } else {
1804 - $return_item = $copy_table_id;
1335 + TablePress::redirect( array( 'action' => $return, 'message' => 'error_copy', 'table_id' => $return_item, 'error_details' => TablePress::get_wp_error_string( $copy_table_id ) ) );
1805 1336 }
1337 + $return_item = $copy_table_id;
1806 1338
1807 1339 /*
1808 1340 * Slightly more complex redirect method, to account for sort, search, and pagination in the WP_List_Table on the List View,
1809 1341 * but only if this action succeeds, to have everything fresh in the event of an error.
@@ -1823,14 +1355,12 @@
1823 1355 * Preview a table.
1824 1356 *
1825 1357 * @since 1.0.0
1826 1358 */
1827 - public function handle_get_action_preview_table() {
1359 + public function handle_get_action_preview_table(): void {
1828 1360 $table_id = ( ! empty( $_GET['item'] ) ) ? $_GET['item'] : false;
1829 1361 TablePress::check_nonce( 'preview_table', $table_id );
1830 1362
1831 - $this->init_i18n_support();
1832 -
1833 1363 // Nonce check should actually catch this already.
1834 1364 if ( false === $table_id ) {
1835 1365 wp_die( __( 'The preview could not be loaded.', 'tablepress' ), __( 'Preview', 'tablepress' ) );
1836 1366 }
@@ -1835,9 +1365,9 @@
1835 1365 wp_die( __( 'The preview could not be loaded.', 'tablepress' ), __( 'Preview', 'tablepress' ) );
1836 1366 }
1837 1367
1838 1368 if ( ! current_user_can( 'tablepress_preview_table', $table_id ) ) {
1839 - wp_die( __( 'You do not have sufficient permissions to access this page.', 'default' ), 403 );
1369 + wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
1840 1370 }
1841 1371
1842 1372 // Load table, with table data, options, and visibility settings.
1843 1373 $table = TablePress::$model_table->load( $table_id, true, true );
@@ -1844,10 +1374,12 @@
1844 1374 if ( is_wp_error( $table ) ) {
1845 1375 wp_die( __( 'The table could not be loaded.', 'tablepress' ), __( 'Preview', 'tablepress' ) );
1846 1376 }
1847 1377
1848 - // Sanitize all table data to remove unsafe HTML from the preview output.
1849 - $table = TablePress::$model_table->sanitize( $table );
1378 + // Sanitize all table data to remove unsafe HTML from the preview output, if the user is not allowed to work with unfiltered HTML.
1379 + if ( ! current_user_can( 'unfiltered_html' ) ) {
1380 + $table = TablePress::$model_table->sanitize( $table );
1381 + }
1850 1382
1851 1383 // Create a render class instance.
1852 1384 $_render = TablePress::load_class( 'TablePress_Render', 'class-render.php', 'classes' );
1853 1385 // Merge desired options with default render options (see TablePress_Controller_Frontend::shortcode_table()).
@@ -1856,18 +1388,22 @@
1856 1388 $default_render_options = apply_filters( 'tablepress_shortcode_table_default_shortcode_atts', $default_render_options );
1857 1389 $render_options = shortcode_atts( $default_render_options, $table['options'] );
1858 1390 /** This filter is documented in controllers/controller-frontend.php */
1859 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;
1860 1394 $_render->set_input( $table, $render_options );
1861 1395 $view_data = array(
1862 - 'table_id' => $table_id,
1863 - 'head_html' => $_render->get_preview_css(),
1864 - 'body_html' => $_render->get_output(),
1396 + 'table_id' => $table_id,
1397 + 'head_html' => $_render->get_preview_css(),
1398 + 'body_html' => $_render->get_output( 'html' ),
1399 + 'site_uses_block_editor' => TablePress::site_uses_block_editor(),
1865 1400 );
1866 1401
1867 1402 $custom_css = TablePress::$model_options->get( 'custom_css' );
1868 - if ( ! empty( $custom_css ) ) {
1869 - $view_data['head_html'] .= "<style type=\"text/css\">\n{$custom_css}\n</style>\n";
1403 + $use_custom_css = ( TablePress::$model_options->get( 'use_custom_css' ) && '' !== $custom_css );
1404 + if ( $use_custom_css ) {
1405 + $view_data['head_html'] .= "<style>\n{$custom_css}\n</style>\n";
1870 1406 }
1871 1407
1872 1408 // Prepare, initialize, and render the view.
1873 1409 $this->view = TablePress::load_view( 'preview_table', $view_data );
@@ -1874,21 +1410,19 @@
1874 1410 $this->view->render();
1875 1411 }
1876 1412
1877 1413 /**
1878 - * Show a list of tables in the Editor toolbar Thickbox (opened by TinyMCE or Quicktags button).
1414 + * Shows a list of tables in the Editor toolbar Thickbox (opened by TinyMCE or Quicktags button).
1879 1415 *
1880 1416 * @since 1.0.0
1881 1417 */
1882 - public function handle_get_action_editor_button_thickbox() {
1418 + public function handle_get_action_editor_button_thickbox(): void {
1883 1419 TablePress::check_nonce( 'editor_button_thickbox' );
1884 1420
1885 1421 if ( ! current_user_can( 'tablepress_list_tables' ) ) {
1886 - wp_die( __( 'You do not have sufficient permissions to access this page.', 'default' ), 403 );
1422 + wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
1887 1423 }
1888 1424
1889 - $this->init_i18n_support();
1890 -
1891 1425 $view_data = array(
1892 1426 // Load all table IDs without priming the post meta cache, as table options/visibility are not needed.
1893 1427 'table_ids' => TablePress::$model_table->load_all( false ),
1894 1428 );
@@ -1904,15 +1438,15 @@
1904 1438 * Uninstall TablePress, and delete all tables and options.
1905 1439 *
1906 1440 * @since 1.0.0
1907 1441 */
1908 - public function handle_get_action_uninstall_tablepress() {
1442 + public function handle_get_action_uninstall_tablepress(): void {
1909 1443 TablePress::check_nonce( 'uninstall_tablepress' );
1910 1444
1911 1445 $plugin = TABLEPRESS_BASENAME;
1912 1446
1913 1447 if ( ! current_user_can( 'deactivate_plugin', $plugin ) || ! current_user_can( 'tablepress_edit_options' ) || ! current_user_can( 'tablepress_delete_tables' ) || is_plugin_active_for_network( $plugin ) ) {
1914 - wp_die( __( 'You do not have sufficient permissions to access this page.', 'default' ), 403 );
1448 + wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
1915 1449 }
1916 1450
1917 1451 // Deactivate TablePress for the site (but not for the network).
1918 1452 deactivate_plugins( $plugin, false, false );
@@ -1926,11 +1460,9 @@
1926 1460
1927 1461 TablePress::$model_table->destroy();
1928 1462 TablePress::$model_options->destroy();
1929 1463
1930 - $this->init_i18n_support();
1931 -
1932 - $output = '<strong>' . __( 'TablePress was uninstalled successfully.', 'tablepress' ) . '</strong><br /><br />';
1464 + $output = '<strong>' . __( 'TablePress was uninstalled successfully.', 'tablepress' ) . '</strong><br><br>';
1933 1465 $output .= __( 'All tables, data, and options were deleted.', 'tablepress' );
1934 1466 if ( is_multisite() ) {
1935 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' );
1936 1468 } else {
@@ -1937,9 +1469,9 @@
1937 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' );
1938 1470 }
1939 1471 if ( $css_files_deleted ) {
1940 1472 $output .= ' ' . __( 'Your TablePress &#8220;Custom CSS&#8221; files have been deleted automatically.', 'tablepress' );
1941 - } else {
1473 + } else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found
1942 1474 if ( is_multisite() ) {
1943 1475 $output .= ' ' . __( 'Please also ask him to delete your TablePress &#8220;Custom CSS&#8221; files from the server.', 'tablepress' );
1944 1476 } else {
1945 1477 $output .= ' ' . __( 'You may now also delete your TablePress &#8220;Custom CSS&#8221; files in the <code>wp-content</code> folder.', 'tablepress' );