PluginProbe
TablePress – Tables in WordPress made easy / 3.4
TablePress – Tables in WordPress made easy v3.4
3.4 3.3.4 3.3.3 3.3.2 3.3.1 trunk 1.12 1.14 1.9.2 2.0.4 2.1.7 2.1.8 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3 2.3.1 2.3.2 2.4 2.4.1 2.4.2 2.4.3 All 45 releases
← All changes | controllers/controller-admin.php +561 -1026 1.9.2 → 3.4 View file →
@@ -7,13 +7,16 @@
7 7 * @author Tobias Bäthge
8 8 * @since 1.0.0
9 9 */
10 10
11 +declare(strict_types=1);
12 +
11 13 // Prohibit direct script loading.
12 14 defined( 'ABSPATH' ) || die( 'No direct script access allowed!' );
13 15
14 16 /**
15 17 * Admin Controller class, extends Base Controller Class
18 + *
16 19 * @package TablePress
17 20 * @subpackage Controllers
18 21 * @author Tobias Bäthge
19 22 * @since 1.0.0
@@ -24,45 +27,28 @@
24 27 * Page hooks (i.e. names) WordPress uses for the TablePress admin screens,
25 28 * populated in add_admin_menu_entry().
26 29 *
27 30 * @since 1.0.0
28 - * @var array
31 + * @var string[]
29 32 */
30 - protected $page_hooks = array();
33 + protected array $page_hooks = array();
31 34
32 35 /**
33 36 * Actions that have a view and admin menu or nav tab menu entry.
34 37 *
35 38 * @since 1.0.0
36 - * @var array
39 + * @var array<string, array<string, bool|string>>
37 40 */
38 - protected $view_actions = array();
41 + protected array $view_actions = array();
39 42
40 43 /**
41 - * 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 44 * Instance of the TablePress Admin View that is rendered.
50 45 *
51 46 * @since 1.0.0
52 - * @var TablePress_View
53 47 */
54 - protected $view;
48 + protected \TablePress_View $view;
55 49
56 50 /**
57 - * Instance of the TablePress Importer.
58 - *
59 - * @since 1.0.0
60 - * @var TablePress_Import
61 - */
62 - protected $importer;
63 -
64 - /**
65 51 * Initialize the Admin Controller, determine location the admin menu, set up actions.
66 52 *
67 53 * @since 1.0.0
68 54 */
@@ -69,12 +55,15 @@
69 55 public function __construct() {
70 56 parent::__construct();
71 57
72 58 // 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 );
59 + add_filter( 'set_screen_option_tablepress_list_per_page', array( $this, 'save_list_tables_screen_option' ), 10, 3 );
74 60
75 61 add_action( 'admin_menu', array( $this, 'add_admin_menu_entry' ) );
76 62 add_action( 'admin_init', array( $this, 'add_admin_actions' ) );
63 +
64 + add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_block_editor_assets' ) );
65 + add_action( 'enqueue_block_assets', array( $this, 'enqueue_block_assets' ) );
77 66 }
78 67
79 68 /**
80 69 * Handler for changing the number of shown tables in the list of tables (via WP List Table class).
@@ -80,19 +69,15 @@
80 69 * Handler for changing the number of shown tables in the list of tables (via WP List Table class).
81 70 *
82 71 * @since 1.0.0
83 72 *
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.
73 + * @param mixed $screen_option Current value of the filter (probably bool false).
74 + * @param string $option Option in which the setting is stored.
75 + * @param int $value Current value of the setting.
76 + * @return int Changed value of the setting
88 77 */
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 - }
78 + public function save_list_tables_screen_option( /* mixed */ $screen_option, string $option, int $value ): int {
79 + return $value;
95 80 }
96 81
97 82 /**
98 83 * Add admin screens to the correct place in the admin menu.
@@ -98,13 +83,13 @@
98 83 * Add admin screens to the correct place in the admin menu.
99 84 *
100 85 * @since 1.0.0
101 86 */
102 - public function add_admin_menu_entry() {
87 + public function add_admin_menu_entry(): void {
103 88 // Callback for all menu entries.
104 89 $callback = array( $this, 'show_admin_page' );
105 90 /**
106 - * Filter the TablePress admin menu entry name.
91 + * Filters the TablePress admin menu entry name.
107 92 *
108 93 * @since 1.0.0
109 94 *
110 95 * @param string $entry_name The admin menu entry name. Default "TablePress".
@@ -110,28 +95,34 @@
110 95 * @param string $entry_name The admin menu entry name. Default "TablePress".
111 96 */
112 97 $admin_menu_entry_name = apply_filters( 'tablepress_admin_menu_entry_name', 'TablePress' );
113 98
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'];
99 + if ( TablePress::$model_options->get( 'message_plugin_update' ) && strtotime( '2026-10-10' ) >= strtotime( 'today' ) ) {
100 + $admin_menu_entry_name .= ' <span class="dashicons dashicons-buddicons-community" aria-hidden="true" style="color:orange"></span>';
101 + }
119 102
120 - $icon_url = 'dashicons-list-view';
121 - switch ( $this->parent_page ) {
103 + $this->init_view_actions();
104 + $min_access_cap = $this->view_actions['list']['required_cap'];
105 +
106 + if ( TablePress::$controller->is_top_level_page ) {
107 + $icon_url = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgdmlld0JveD0iLTMyIC0zMiA2NCA2NCIgZmlsbD0iI2ZmZiI+PHBhdGggZD0iTTAtMjUuODU0aC0yNS44NTR2NTEuNzA4aDUxLjcwOFYwSDIxdjIxaC00MnYtNDJIMFoiLz48cGF0aCBkPSJNLTE4LTE4aDEwdjEwaC0xMHpNLTE4LTVoMTBWNWgtMTB6TS01LTVINVY1SC01ek0tMTggOGgxMHYxMGgtMTB6TS01IDhINXYxMEgtNXpNOCA4aDEwdjEwSDh6TTUtMzFoNi4xOHY2LjE4SDV6TTE5LTI1aDYuMTh2Ni4xOEgxOXpNMC0xNWgzLjgydjMuODJIMHpNMTAtMjBoMy44MnYzLjgySDEwek0yNS0xMmgzLjgydjMuODJIMjV6TTgtMTNoMTB2MTBIOHoiLz48L3N2Zz4=';
108 + switch ( TablePress::$controller->parent_page ) {
122 109 case 'top':
123 - $position = 3; // position of Dashboard + 1
110 + $position = 3; // Position of Dashboard + 1.
124 111 break;
125 112 case 'bottom':
126 - $position = ( ++$GLOBALS['_wp_last_utility_menu'] );
113 + $position = isset( $GLOBALS['_wp_last_utility_menu'] ) ? ++$GLOBALS['_wp_last_utility_menu'] : 80;
127 114 break;
128 115 case 'middle':
129 116 default:
130 - $position = ( ++$GLOBALS['_wp_last_object_menu'] );
117 + $position = isset( $GLOBALS['_wp_last_object_menu'] ) ? ++$GLOBALS['_wp_last_object_menu'] : 25;
131 118 break;
132 119 }
133 - add_menu_page( 'TablePress', $admin_menu_entry_name, $min_access_cap, 'tablepress', $callback, $icon_url, $position );
120 + // Prevent overwriting existing menu entries.
121 + while ( isset( $GLOBALS['menu'][ $position ] ) ) {
122 + ++$position;
123 + }
124 + add_menu_page( 'TablePress', $admin_menu_entry_name, $min_access_cap, 'tablepress', $callback, $icon_url, $position ); // @phpstan-ignore argument.type
134 125 foreach ( $this->view_actions as $action => $entry ) {
135 126 if ( ! $entry['show_entry'] ) {
136 127 continue;
137 128 }
@@ -138,14 +129,20 @@
138 129 $slug = 'tablepress';
139 130 if ( 'list' !== $action ) {
140 131 $slug .= '_' . $action;
141 132 }
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 );
133 + /* translators: %1$s: Page title, %2$s: Plugin name (TablePress) */
134 + $page_hook = add_submenu_page( 'tablepress', sprintf( __( '%1$s &lsaquo; %2$s', 'tablepress' ), $entry['page_title'], 'TablePress' ), $entry['admin_menu_title'], $entry['required_cap'], $slug, $callback ); // @phpstan-ignore argument.type, argument.type
135 + if ( false !== $page_hook ) {
136 + $this->page_hooks[] = $page_hook;
137 + }
143 138 }
144 139 } 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 );
140 + // @phpstan-ignore argument.type
141 + $page_hook = add_submenu_page( TablePress::$controller->parent_page, 'TablePress', $admin_menu_entry_name, $min_access_cap, 'tablepress', $callback );
142 + if ( false !== $page_hook ) {
143 + $this->page_hooks[] = $page_hook;
144 + }
148 145 }
149 146 }
150 147
151 148 /**
@@ -152,11 +149,11 @@
152 149 * Set up handlers for user actions in the backend that exceed plain viewing.
153 150 *
154 151 * @since 1.0.0
155 152 */
156 - public function add_admin_actions() {
153 + public function add_admin_actions(): void {
157 154 // Register the callbacks for processing action requests.
158 - $post_actions = array( 'list', 'add', 'edit', 'options', 'export', 'import' );
155 + $post_actions = array( 'list', 'add', 'options', 'export', 'import' );
159 156 $get_actions = array( 'hide_message', 'delete_table', 'copy_table', 'preview_table', 'editor_button_thickbox', 'uninstall_tablepress' );
160 157 foreach ( $post_actions as $action ) {
161 158 add_action( "admin_post_tablepress_{$action}", array( $this, "handle_post_action_{$action}" ) );
162 159 }
@@ -168,11 +165,20 @@
168 165 foreach ( $this->page_hooks as $page_hook ) {
169 166 add_action( "load-{$page_hook}", array( $this, 'load_admin_page' ) );
170 167 }
171 168
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' ) );
169 + /**
170 + * Filters whether the legacy editor button should be loaded on the post editing screen.
171 + *
172 + * @since 2.1.0
173 + *
174 + * @param bool $load_button Whether to load the legacy editor button. Default true.
175 + */
176 + if ( apply_filters( 'tablepress_add_legacy_editor_button', true ) ) {
177 + $pages_with_editor_button = array( 'post.php', 'post-new.php' );
178 + foreach ( $pages_with_editor_button as $editor_page ) {
179 + add_action( "load-{$editor_page}", array( $this, 'add_editor_buttons' ) );
180 + }
175 181 }
176 182
177 183 if ( ! is_network_admin() && ! is_user_admin() ) {
178 184 add_action( 'admin_bar_menu', array( $this, 'add_wp_admin_bar_new_content_menu_entry' ), 71 );
@@ -178,99 +184,177 @@
178 184 add_action( 'admin_bar_menu', array( $this, 'add_wp_admin_bar_new_content_menu_entry' ), 71 );
179 185 }
180 186
181 187 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' ) );
188 + }
183 189
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 );
190 + /**
191 + * Loads additional JavaScript code for the TablePress table block (in the block editor context).
192 + *
193 + * @since 2.2.0
194 + */
195 + public function enqueue_block_editor_assets(): void {
196 + $handle = generate_block_asset_handle( 'tablepress/table', 'editorScript' );
197 + $data = $this->get_block_editor_data();
198 + wp_add_inline_script( $handle, $data, 'before' );
188 199 }
189 200
190 201 /**
202 + * Loads additional CSS code for the TablePress table block (inside the block editor iframe).
203 + *
204 + * @since 2.2.0
205 + */
206 + public function enqueue_block_assets(): void {
207 + // Load the TablePress default CSS and the user's "Custom CSS" in the block editor iframe.
208 + if ( is_admin() ) {
209 + TablePress::$controller->maybe_enqueue_css();
210 + }
211 + }
212 +
213 + /**
214 + * Gets the inline data that is referenced by the Block Editor JavaScript code for the TablePress blocks.
215 + *
216 + * @since 2.0.0
217 + *
218 + * @return string JavaScript code for the Block Editor.
219 + */
220 + protected function get_block_editor_data(): string {
221 + $tables = array();
222 + // Load all table IDs without priming the post meta cache, as table options/visibility are not needed.
223 + $table_ids = TablePress::$model_table->load_all( false );
224 + foreach ( $table_ids as $table_id ) {
225 + // Load table, without table data, options, and visibility settings.
226 + $table = TablePress::$model_table->load( $table_id, false, false );
227 +
228 + // Skip tables that could not be loaded.
229 + if ( is_wp_error( $table ) ) {
230 + continue;
231 + }
232 +
233 + if ( '' === trim( $table['name'] ) ) {
234 + $table['name'] = __( '(no name)', 'tablepress' );
235 + }
236 + $tables[ $table_id ] = esc_html( $table['name'] );
237 + }
238 +
239 + /**
240 + * Filters the list of table IDs and names that is passed to the block editor, and is then used in the dropdown of the TablePress table block.
241 + *
242 + * @since 2.0.0
243 + *
244 + * @param array<string, string> $tables List of table names, the table ID is the array key.
245 + */
246 + $tables = apply_filters( 'tablepress_block_editor_tables_list', $tables );
247 +
248 + $tables = wp_json_encode( $tables, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES );
249 + if ( false === $tables ) {
250 + // JSON encoding failed, return an error object. Use a prefixed "_error" key to avoid conflicts with intentionally added "error" keys.
251 + $tables = '{ "_error": "The data could not be encoded to JSON!" }';
252 + }
253 + // Print the JSON data inside a `JSON.parse()` call in JS for speed gains, with necessary escaping of `\` and `'`.
254 + $tables = str_replace( array( '\\', "'" ), array( '\\\\', "\'" ), $tables );
255 +
256 + $shortcode = esc_js( TablePress::$shortcode );
257 +
258 + $template = TablePress::$model_table->get_table_template();
259 + $template = wp_json_encode( $template['options'], JSON_HEX_TAG | JSON_UNESCAPED_SLASHES );
260 + if ( false === $template ) {
261 + // JSON encoding failed, return an error object. Use a prefixed "_error" key to avoid conflicts with intentionally added "error" keys.
262 + $template = '{ "_error": "The data could not be encoded to JSON!" }';
263 + }
264 + // Print the JSON data inside a `JSON.parse()` call in JS for speed gains, with necessary escaping of `\` and `'`.
265 + $template = str_replace( array( '\\', "'" ), array( '\\\\', "\'" ), $template );
266 +
267 + /**
268 + * Filters whether the table block preview should be loaded via a <ServerSideRender> in the block editor.
269 + *
270 + * @since 2.0.0
271 + *
272 + * @param bool $load_block_preview Whether the table block preview should be loaded.
273 + */
274 + $load_block_preview = apply_filters( 'tablepress_show_block_editor_preview', true );
275 + $load_block_preview = (bool) $load_block_preview ? 'true' : 'false';
276 +
277 + $url = '';
278 + if ( current_user_can( 'tablepress_list_tables' ) ) {
279 + $url = TablePress::url( array( 'action' => 'list' ) );
280 + }
281 +
282 + return <<<JS
283 + // Ensure the global `tp` object exists.
284 + window.tp = window.tp || {};
285 + tp.url = '{$url}';
286 + tp.load_block_preview = {$load_block_preview};
287 + tp.table = {};
288 + tp.table.shortcode = '{$shortcode}';
289 + tp.table.template = JSON.parse( '{$template}' );
290 + tp.tables = JSON.parse( '{$tables}' );
291 + JS;
292 + }
293 +
294 + /**
191 295 * Register actions to add "Table" button to "HTML editor" and "Visual editor" toolbars.
192 296 *
193 297 * @since 1.0.0
194 298 */
195 - public function add_editor_buttons() {
299 + public function add_editor_buttons(): void {
196 300 if ( ! current_user_can( 'tablepress_list_tables' ) ) {
197 301 return;
198 302 }
199 303
200 - $this->init_i18n_support();
201 - add_thickbox(); // usually already loaded by media upload functions
202 - $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' ),
304 + // Only load the toolbar integration if the Block Editor is not used.
305 + if ( 'block' === TablePress::site_used_editor() ) {
306 + return;
307 + }
308 +
309 + add_thickbox(); // The files are usually already loaded by media upload functions.
310 + TablePress::enqueue_script(
311 + 'quicktags-button',
312 + array( 'quicktags', 'media-upload' ),
313 + array(
314 + 'editor_button' => array(
315 + 'caption' => __( 'Table', 'tablepress' ),
316 + 'title' => __( 'Insert a TablePress table', 'tablepress' ),
317 + 'thickbox_title' => __( 'Insert a TablePress table', 'tablepress' ),
318 + 'thickbox_url' => TablePress::url( array( 'action' => 'editor_button_thickbox' ), true, 'admin-post.php' ),
319 + ),
209 320 ),
210 - ) );
321 + );
211 322
212 323 // TinyMCE integration.
213 324 if ( user_can_richedit() ) {
214 325 add_filter( 'mce_external_plugins', array( $this, 'add_tinymce_plugin' ) );
215 326 add_filter( 'mce_buttons', array( $this, 'add_tinymce_button' ) );
216 - add_action( 'admin_print_styles', array( $this, 'add_tablepress_hidpi_css' ), 21 );
217 327 }
218 328 }
219 329
220 330 /**
221 - * Add "Table" button and separator to the TinyMCE toolbar.
331 + * Adds the "Table" button to the TinyMCE toolbar.
222 332 *
223 333 * @since 1.0.0
224 334 *
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.
335 + * @param string[] $buttons Current set of buttons in the TinyMCE toolbar.
336 + * @return string[] Extended set of buttons in the TinyMCE toolbar, including the "Table" button.
227 337 */
228 - public function add_tinymce_button( array $buttons ) {
338 + public function add_tinymce_button( array $buttons ): array {
229 339 $buttons[] = 'tablepress_insert_table';
230 340 return $buttons;
231 341 }
232 342
233 343 /**
234 - * Register "Table" button plugin to TinyMCE.
344 + * Registers the "Table" button plugin for the TinyMCE editor.
235 345 *
236 346 * @since 1.0.0
237 347 *
238 - * @param array $plugins Current set of registered TinyMCE plugins.
239 - * @return array Current set of registered TinyMCE plugins, including "Table" button plugin.
348 + * @param array<string, string> $plugins Current set of registered TinyMCE plugins.
349 + * @return array<string, string> Extended set of registered TinyMCE plugins, including the "Table" button plugin.
240 350 */
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__ );
351 + public function add_tinymce_plugin( array $plugins ): array {
352 + $plugins['tablepress_tinymce'] = plugins_url( 'admin/js/build/tinymce-button.js', TABLEPRESS__FILE__ );
245 353 return $plugins;
246 354 }
247 355
248 356 /**
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 357 * Add "TablePress Table" entry to "New" dropdown menu in the WP Admin Bar.
274 358 *
275 359 * @since 1.0.0
276 360 *
@@ -275,18 +359,22 @@
275 359 * @since 1.0.0
276 360 *
277 361 * @param WP_Admin_Bar $wp_admin_bar The current WP Admin Bar object.
278 362 */
279 - public function add_wp_admin_bar_new_content_menu_entry( $wp_admin_bar ) {
363 + public function add_wp_admin_bar_new_content_menu_entry( WP_Admin_Bar $wp_admin_bar ): void {
280 364 if ( ! current_user_can( 'tablepress_add_tables' ) ) {
281 365 return;
282 366 }
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.
367 +
368 + // Don't load TablePress assets on the Freemius opt-in/activation screen.
369 + if ( tb_tp_fs()->is_activation_mode() && tb_tp_fs()->is_activation_page() ) {
370 + return;
371 + }
372 +
285 373 $wp_admin_bar->add_menu( array(
286 374 'parent' => 'new-content',
287 375 'id' => 'new-tablepress-table',
288 - 'title' => __( 'TablePress Table', 'tablepress' ),
376 + 'title' => __( 'TablePress table', 'tablepress' ),
289 377 'href' => TablePress::url( array( 'action' => 'add' ) ),
290 378 ) );
291 379 }
292 380
@@ -294,13 +382,25 @@
294 382 * Handle actions for loading of Plugins page.
295 383 *
296 384 * @since 1.0.0
297 385 */
298 - public function plugins_page() {
299 - $this->init_i18n_support();
386 + public function plugins_page(): void {
300 387 // Add additional links on Plugins page.
301 388 add_filter( 'plugin_action_links_' . TABLEPRESS_BASENAME, array( $this, 'add_plugin_action_links' ) );
302 389 add_filter( 'plugin_row_meta', array( $this, 'add_plugin_row_meta' ), 10, 2 );
390 + $incompatible_superseded_extensions = array(
391 + 'tablepress-datatables-alphabetsearch/tablepress-datatables-alphabetsearch.php',
392 + 'tablepress-datatables-column-filter-widgets/tablepress-datatables-column-filter-widgets.php',
393 + 'tablepress-datatables-columnfilter/tablepress-datatables-columnfilter.php',
394 + 'tablepress-datatables-fixedcolumns/tablepress-datatables-fixedcolumns.php',
395 + 'tablepress-datatables-inverted-filter/tablepress-datatables-inverted-filter.php',
396 + 'tablepress-datatables-row-details/tablepress-datatables-row-details.php',
397 + 'tablepress-datatables-rowgroup/tablepress-datatables-rowgroup.php',
398 + 'tablepress-responsive-tables/tablepress-responsive-tables.php',
399 + );
400 + foreach ( $incompatible_superseded_extensions as $plugin_file ) {
401 + add_action( "after_plugin_row_{$plugin_file}", array( $this, 'add_superseded_extension_meta_row' ), 10, 3 );
402 + }
303 403 }
304 404
305 405 /**
306 406 * Add links to the TablePress entry in the "Plugin" column on the Plugins page.
@@ -306,103 +406,153 @@
306 406 * Add links to the TablePress entry in the "Plugin" column on the Plugins page.
307 407 *
308 408 * @since 1.0.0
309 409 *
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.
410 + * @param string[] $links List of links to print in the "Plugin" column on the Plugins page.
411 + * @return string[] Extended list of links to print in the "Plugin" column on the Plugins page.
312 412 */
313 - public function add_plugin_action_links( array $links ) {
413 + public function add_plugin_action_links( array $links ): array {
314 414 if ( current_user_can( 'tablepress_list_tables' ) ) {
315 - $links[] = '<a href="' . TablePress::url() . '">' . __( 'Plugin page', 'tablepress' ) . '</a>';
415 + $links[] = '<a href="' . esc_url( TablePress::url() ) . '">' . __( 'Plugin page', 'tablepress' ) . '</a>';
316 416 }
317 417 return $links;
318 418 }
419 +
319 420 /**
320 421 * Add links to the TablePress entry in the "Description" column on the Plugins page.
321 422 *
322 423 * @since 1.0.0
323 424 *
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.
425 + * @param string[] $links List of links to print in the "Description" column on the Plugins page.
426 + * @param string $file Name of the plugin.
427 + * @return string[] Extended list of links to print in the "Description" column on the Plugins page.
327 428 */
328 - public function add_plugin_row_meta( array $links, $file ) {
429 + public function add_plugin_row_meta( array $links, string $file ): array {
329 430 if ( TABLEPRESS_BASENAME === $file ) {
330 431 $links[] = '<a href="https://tablepress.org/faq/" title="' . esc_attr__( 'Frequently Asked Questions', 'tablepress' ) . '">' . __( 'FAQ', 'tablepress' ) . '</a>';
331 432 $links[] = '<a href="https://tablepress.org/documentation/">' . __( 'Documentation', 'tablepress' ) . '</a>';
332 433 $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>';
434 + if ( ! TABLEPRESS_IS_PLAYGROUND_PREVIEW && tb_tp_fs()->is_free_plan() ) {
435 + $links[] = '<a href="https://tablepress.org/premium/?utm_source=plugin&utm_medium=textlink&utm_content=plugins-screen" title="' . esc_attr__( 'Check out the Premium version of TablePress!', 'tablepress' ) . '"><strong>' . __( 'Go Premium', 'tablepress' ) . '</strong></a>';
436 + }
334 437 }
335 438 return $links;
336 439 }
337 440
338 441 /**
442 + * Prints a superseded extension notice below certain TablePress Extension plugins' meta rows on the "Plugins" screen.
443 + *
444 + * @since 2.4.1
445 + *
446 + * @param string $plugin_file Path to the plugin file relative to the plugins directory.
447 + * @param array<int, string|string[]|bool> $plugin_data An array of plugin data.
448 + * @param string $status Status filter currently applied to the plugin list.
449 + */
450 + public function add_superseded_extension_meta_row( string $plugin_file, array $plugin_data, string $status ): void {
451 + if ( ! is_plugin_active( $plugin_file ) ) {
452 + return;
453 + }
454 + ?>
455 + <tr class="plugin-update-tr active">
456 + <td colspan="<?php echo esc_attr( $GLOBALS['wp_list_table']->get_column_count() ); ?>" class="plugin-update colspanchange">
457 + <div class="update-message notice inline notice-error notice-alt">
458 + <?php
459 + if ( tb_tp_fs()->is_free_plan() ) {
460 + echo '<p style="font-size:14px;">';
461 + _e( 'This TablePress Extension was retired.', 'tablepress' );
462 + echo ' ';
463 + _e( '<strong>The plugin does no longer work</strong> and will no longer receive updates or support!', 'tablepress' );
464 + echo '<br>';
465 + _e( 'Keeping it activated can lead to errors on your website!', 'tablepress' );
466 + echo ' <strong>' . sprintf( __( '<a href="%s">Find out what you can do to continue using its features!</a>', 'tablepress' ), 'https://tablepress.org/upgrade-extensions/?utm_source=plugin&utm_medium=textlink&utm_content=plugins-list-table' ) . '</strong>';
467 + echo '</p>';
468 + }
469 + ?>
470 + <style>
471 + /* Remove the separator line between the plugin's and the notice's table row. */
472 + .plugins .active[data-plugin="<?php echo $plugin_file; ?>"] th,
473 + .plugins .active[data-plugin="<?php echo $plugin_file; ?>"] td {
474 + box-shadow: none;
475 + }
476 + /* Hide the plugin update row for the Extension as those won't work anymore anyways. */
477 + .plugins .plugin-update-tr[data-plugin="<?php echo $plugin_file; ?>"] {
478 + display: none;
479 + }
480 + </style>
481 + </div>
482 + </td>
483 + </tr>
484 + <?php
485 + }
486 +
487 + /**
339 488 * Prepare the rendering of an admin screen, by determining the current action, loading necessary data and initializing the view.
340 489 *
341 490 * @since 1.0.0
342 491 */
343 - public function load_admin_page() {
492 + public function load_admin_page(): void {
344 493 // 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 ) {
494 + $action = ( ! empty( $_GET['action'] ) ) ? $_GET['action'] : 'list'; // Default action is list.
495 + if ( TablePress::$controller->is_top_level_page ) {
347 496 // Or, for sub-menu entry of an admin menu "TablePress" entry, get it from the "page" GET parameter.
348 497 if ( 'tablepress' !== $_GET['page'] ) {
349 498 // Actions that are top-level entries, but don't have an action GET parameter (action is after last _ in string).
350 499 $action = substr( $_GET['page'], 11 ); // $_GET['page'] has the format 'tablepress_{$action}'
351 500 }
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 501 }
357 502
358 503 // 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 );
504 + if ( ! isset( $this->view_actions[ $action ] ) || ! current_user_can( $this->view_actions[ $action ]['required_cap'] ) ) { // @phpstan-ignore argument.type (The array value for the capability is always a string.)
505 + wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
361 506 }
362 507
508 + // Don't load TablePress assets on the Freemius opt-in/activation screen.
509 + if ( tb_tp_fs()->is_activation_mode() && tb_tp_fs()->is_activation_page() ) {
510 + return;
511 + }
512 +
363 513 // Changes current screen ID and pagenow variable in JS, to enable automatic meta box JS handling.
364 514 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.
515 +
516 + /*
517 + * Set the `$typenow` global to the current CPT ourselves, as `WP_Screen::get()` does not determine the CPT correctly.
518 + * This is necessary as the WP Admin Menu can otherwise highlight wrong entries, see https://github.com/TablePress/TablePress/issues/24.
519 + */
367 520 if ( isset( $_GET['post_type'] ) && post_type_exists( $_GET['post_type'] ) ) {
368 - $GLOBALS['typenow'] = $_GET['post_type'];
521 + $GLOBALS['typenow'] = $_GET['post_type']; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
369 522 }
370 523
371 524 // Pre-define some view data.
372 525 $data = array(
373 - 'view_actions' => $this->view_actions,
374 - 'message' => ( ! empty( $_GET['message'] ) ) ? $_GET['message'] : false,
526 + 'view_actions' => $this->view_actions,
527 + 'message' => ( ! empty( $_GET['message'] ) ) ? $_GET['message'] : false,
528 + 'error_details' => ( ! empty( $_GET['error_details'] ) ) ? rawurldecode( wp_unslash( $_GET['error_details'] ) ) : '',
529 + 'site_used_editor' => TablePress::site_used_editor(),
375 530 );
376 531
377 532 // Depending on the action, load more necessary data for the corresponding view.
378 533 switch ( $action ) {
379 534 case 'list':
380 - $data['table_id'] = ( ! empty( $_GET['table_id'] ) ) ? $_GET['table_id'] : false;
535 + $data['table_id'] = ( isset( $_GET['table_id'] ) ) ? preg_replace( '/[^a-zA-Z0-9_-]/', '', $_GET['table_id'] ) : false;
381 536 // Prime the post meta cache for cached loading of last_editor.
382 537 $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();
538 + $data['messages']['donation_nag'] = $this->maybe_show_donation_message();
539 + $data['messages']['first_visit'] = ! $data['messages']['donation_nag'] && TablePress::$model_options->get( 'message_first_visit' );
540 + $data['messages']['plugin_update'] = TablePress::$model_options->get( 'message_plugin_update' );
541 + $data['messages']['superseded_extensions'] = current_user_can( 'manage_options' ) && TablePress::$model_options->get( 'message_superseded_extensions' );
392 542 $data['table_count'] = count( $data['table_ids'] );
393 543 break;
394 544 case 'about':
395 545 $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 546 break;
399 547 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)
548 + /*
549 + * Maybe try saving "Custom CSS" to a file:
550 + * (called here, as the credentials form posts to this handler again, due to how `request_filesystem_credentials()` works)
551 + */
402 552 if ( isset( $_GET['item'] ) && 'save_custom_css' === $_GET['item'] ) {
403 553 TablePress::check_nonce( 'options', $_GET['item'] ); // Nonce check here, as we don't have an explicit handler, and even viewing the screen needs to be checked.
404 - $action = 'options_custom_css'; // to load a different view
554 + $action = 'options_custom_css'; // To load a different view.
405 555 // Try saving "Custom CSS" to a file, otherwise this gets the HTML for the credentials form.
406 556 $tablepress_css = TablePress::load_class( 'TablePress_CSS', 'class-css.php', 'classes' );
407 557 $result = $tablepress_css->save_custom_css_to_file_plugin_options( TablePress::$model_options->get( 'custom_css' ), TablePress::$model_options->get( 'custom_css_minified' ) );
408 558 if ( is_string( $result ) ) {
@@ -416,9 +566,9 @@
416 566 'use_custom_css_file' => true,
417 567 'custom_css_version' => TablePress::$model_options->get( 'custom_css_version' ) + 1,
418 568 ) );
419 569 TablePress::redirect( array( 'action' => 'options', 'message' => 'success_save' ) );
420 - } else { // leaves only $result = false
570 + } else { // Leaves only $result === false.
421 571 TablePress::redirect( array( 'action' => 'options', 'message' => 'success_save_error_custom_css' ) );
422 572 }
423 573 break;
424 574 }
@@ -423,68 +573,88 @@
423 573 break;
424 574 }
425 575 $data['frontend_options']['use_custom_css'] = TablePress::$model_options->get( 'use_custom_css' );
426 576 $data['frontend_options']['custom_css'] = TablePress::$model_options->get( 'custom_css' );
427 - $data['user_options']['parent_page'] = $this->parent_page;
577 + $data['user_options']['parent_page'] = TablePress::$controller->parent_page;
428 578 break;
429 579 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 {
580 + if ( ! isset( $_GET['table_id'] ) || ! preg_match( '/^[a-zA-Z0-9_-]+$/', $_GET['table_id'] ) ) {
440 581 TablePress::redirect( array( 'action' => 'list', 'message' => 'error_no_table' ) );
441 582 }
583 + // Load table, with table data, options, and visibility settings.
584 + $data['table'] = TablePress::$model_table->load( $_GET['table_id'], true, true );
585 + if ( is_wp_error( $data['table'] ) ) {
586 + TablePress::redirect( array( 'action' => 'list', 'message' => 'error_load_table', 'error_details' => TablePress::get_wp_error_string( $data['table'] ) ) );
587 + }
588 + if ( ! current_user_can( 'tablepress_edit_table', $_GET['table_id'] ) ) {
589 + wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
590 + }
442 591 break;
443 592 case 'export':
444 593 // 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 );
594 + $table_ids = TablePress::$model_table->load_all( false );
595 + $data['tables'] = array();
596 + foreach ( $table_ids as $table_id ) {
597 + if ( ! current_user_can( 'tablepress_export_table', $table_id ) ) {
598 + continue;
599 + }
600 + // Load table, without table data, options, and visibility settings.
601 + $table = TablePress::$model_table->load( $table_id, false, false );
602 +
603 + // Skip tables that could not be loaded.
604 + if ( is_wp_error( $table ) ) {
605 + continue;
606 + }
607 +
608 + $data['tables'][ $table['id'] ] = $table['name'];
609 + }
446 610 $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 - }
611 + $data['export_ids'] = ( isset( $_GET['table_id'] ) && preg_match( '/^[,a-zA-Z0-9_-]+$/', $_GET['table_id'] ) ) ? explode( ',', $_GET['table_id'] ) : array();
453 612 $exporter = TablePress::load_class( 'TablePress_Export', 'class-export.php', 'classes' );
454 613 $data['zip_support_available'] = $exporter->zip_support_available;
455 614 $data['export_formats'] = $exporter->export_formats;
456 615 $data['csv_delimiters'] = $exporter->csv_delimiters;
457 - $data['export_format'] = ( ! empty( $_GET['export_format'] ) ) ? $_GET['export_format'] : false;
616 + $data['export_format'] = ( ! empty( $_GET['export_format'] ) ) ? $_GET['export_format'] : 'csv';
458 617 $data['csv_delimiter'] = ( ! empty( $_GET['csv_delimiter'] ) ) ? $_GET['csv_delimiter'] : _x( ',', 'Default CSV delimiter in the translated language (";", ",", or "tab")', 'tablepress' );
459 618 break;
460 619 case 'import':
461 620 // 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 );
621 + $table_ids = TablePress::$model_table->load_all( false );
622 + $data['tables'] = array();
623 + foreach ( $table_ids as $table_id ) {
624 + if ( ! current_user_can( 'tablepress_edit_table', $table_id ) ) {
625 + continue;
626 + }
627 + // Load table, without table data, options, and visibility settings.
628 + $table = TablePress::$model_table->load( $table_id, false, false );
629 +
630 + // Skip tables that could not be loaded.
631 + if ( is_wp_error( $table ) ) {
632 + continue;
633 + }
634 +
635 + $data['tables'][ $table['id'] ] = $table['name'];
636 + }
637 + $data['table_ids'] = $table_ids; // Backward compatibility for the retired "Table Auto Update" Extension, which still relies on this variable name.
463 638 $data['tables_count'] = TablePress::$model_table->count_tables();
464 639 $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 640 $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;
641 + $data['import_existing_table'] = $_GET['import_existing_table'] ?? '';
471 642 $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://';
473 - $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' );
643 + $data['import_url'] = ( ! empty( $_GET['import_url'] ) ) ? rawurldecode( wp_unslash( $_GET['import_url'] ) ) : 'https://';
644 + $data['import_server'] = ( ! empty( $_GET['import_server'] ) ) ? rawurldecode( wp_unslash( $_GET['import_server'] ) ) : ABSPATH;
645 + $data['import_form-field'] = ( ! empty( $_GET['import_form-field'] ) ) ? rawurldecode( wp_unslash( $_GET['import_form-field'] ) ) : '';
646 + $data['legacy_import'] = ( ! empty( $_GET['legacy_import'] ) ) ? $_GET['legacy_import'] : 'false';
477 647 break;
478 648 }
479 649
480 650 /**
481 - * Filter the data that is passed to the current TablePress View.
651 + * Filters the data that is passed to the current TablePress View.
482 652 *
483 653 * @since 1.0.0
484 654 *
485 - * @param array $data Data for the view.
486 - * @param string $action The current action for the view.
655 + * @param array<string, mixed> $data Data for the view.
656 + * @param string $action The current action for the view.
487 657 */
488 658 $data = apply_filters( 'tablepress_view_data', $data, $action );
489 659
490 660 // Prepare and initialize the view.
@@ -495,33 +665,20 @@
495 665 * Render the view that has been initialized in load_admin_page() (called by WordPress when the actual page content is needed).
496 666 *
497 667 * @since 1.0.0
498 668 */
499 - public function show_admin_page() {
669 + public function show_admin_page(): void {
500 670 $this->view->render();
501 671 }
502 672
503 673 /**
504 - * Initialize i18n support, load plugin's textdomain, to retrieve correct translations.
674 + * Decides whether a message about Premium versions (previously, about donations) shall be shown on the "All Tables" screen, depending on passed days since installation and whether it was shown before.
505 675 *
506 676 * @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 677 *
519 - * @since 1.0.0
520 - *
521 - * @return bool Whether the donate message shall be shown on the "All Tables" screen.
678 + * @return bool Whether the message shall be shown on the "All Tables" screen.
522 679 */
523 - protected function maybe_show_donation_message() {
680 + protected function maybe_show_donation_message(): bool {
524 681 // Only show the message to plugin admins.
525 682 if ( ! current_user_can( 'tablepress_edit_options' ) ) {
526 683 return false;
527 684 }
@@ -531,9 +688,9 @@
531 688 }
532 689
533 690 // Determine, how long has the plugin been installed.
534 691 $seconds_installed = time() - TablePress::$model_options->get( 'first_activation' );
535 - return ( $seconds_installed > 30 * DAY_IN_SECONDS );
692 + return ( $seconds_installed > MONTH_IN_SECONDS / 2 );
536 693 }
537 694
538 695 /**
539 696 * Init list of actions that have a view with their titles/names/caps.
@@ -539,9 +696,9 @@
539 696 * Init list of actions that have a view with their titles/names/caps.
540 697 *
541 698 * @since 1.0.0
542 699 */
543 - protected function init_view_actions() {
700 + protected function init_view_actions(): void {
544 701 $this->view_actions = array(
545 702 'list' => array(
546 703 'show_entry' => true,
547 704 'page_title' => __( 'All Tables', 'tablepress' ),
@@ -593,13 +750,13 @@
593 750 ),
594 751 );
595 752
596 753 /**
597 - * Filter the available TablePres Views/Actions and their parameters.
754 + * Filters the available TablePres Views/Actions and their parameters.
598 755 *
599 756 * @since 1.0.0
600 757 *
601 - * @param array $view_actions The available Views/Actions and their parameters.
758 + * @param array<string, array<string, bool|string>> $view_actions The available Views/Actions and their parameters.
602 759 */
603 760 $this->view_actions = apply_filters( 'tablepress_admin_view_actions', $this->view_actions );
604 761 }
605 762
@@ -611,15 +768,15 @@
611 768 * Handle Bulk Actions (Copy, Export, Delete) on "All Tables" list screen.
612 769 *
613 770 * @since 1.0.0
614 771 */
615 - public function handle_post_action_list() {
772 + public function handle_post_action_list(): void {
616 773 TablePress::check_nonce( 'list' );
617 774
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'];
775 + if ( isset( $_POST['bulk-action-selector-top'] ) && '-1' !== $_POST['bulk-action-selector-top'] ) {
776 + $bulk_action = $_POST['bulk-action-selector-top'];
777 + } elseif ( isset( $_POST['bulk-action-selector-bottom'] ) && '-1' !== $_POST['bulk-action-selector-bottom'] ) {
778 + $bulk_action = $_POST['bulk-action-selector-bottom'];
622 779 } else {
623 780 $bulk_action = false;
624 781 }
625 782
@@ -628,17 +785,16 @@
628 785 }
629 786
630 787 if ( empty( $_POST['table'] ) || ! is_array( $_POST['table'] ) ) {
631 788 TablePress::redirect( array( 'action' => 'list', 'message' => 'error_no_selection' ) );
632 - } else {
633 - $tables = wp_unslash( $_POST['table'] );
634 789 }
635 790
636 - $no_success = array(); // to store table IDs that failed
791 + $tables = wp_unslash( $_POST['table'] );
637 792
793 + $no_success = array(); // To store table IDs that failed.
794 +
638 795 switch ( $bulk_action ) {
639 796 case 'copy':
640 - $this->init_i18n_support(); // for the translation of "Copy of"
641 797 foreach ( $tables as $table_id ) {
642 798 if ( current_user_can( 'tablepress_copy_table', $table_id ) ) {
643 799 $copy_table_id = TablePress::$model_table->copy( $table_id );
644 800 if ( is_wp_error( $copy_table_id ) ) {
@@ -655,9 +811,9 @@
655 811 * To export, redirect to "Export" screen, with selected table IDs.
656 812 */
657 813 $table_ids = implode( ',', $tables );
658 814 TablePress::redirect( array( 'action' => 'export', 'table_id' => $table_ids ) );
659 - break;
815 + // break; // unreachable.
660 816 case 'delete':
661 817 foreach ( $tables as $table_id ) {
662 818 if ( current_user_can( 'tablepress_delete_table', $table_id ) ) {
663 819 $deleted = TablePress::$model_table->delete( $table_id );
@@ -670,9 +826,9 @@
670 826 }
671 827 break;
672 828 }
673 829
674 - if ( 0 !== count( $no_success ) ) { // @TODO: maybe pass this information to the view?
830 + if ( 0 !== count( $no_success ) ) { // @todo maybe pass this information to the view?
675 831 $message = "error_{$bulk_action}_not_all_tables";
676 832 } else {
677 833 $plural = ( count( $tables ) > 1 ) ? '_plural' : '';
678 834 $message = "success_{$bulk_action}{$plural}";
@@ -693,122 +849,36 @@
693 849 exit;
694 850 }
695 851
696 852 /**
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 853 * Add a table, according to the parameters on the "Add new Table" screen.
784 854 *
785 855 * @since 1.0.0
786 856 */
787 - public function handle_post_action_add() {
857 + public function handle_post_action_add(): void {
788 858 TablePress::check_nonce( 'add' );
789 859
790 860 if ( ! current_user_can( 'tablepress_add_tables' ) ) {
791 - wp_die( __( 'You do not have sufficient permissions to access this page.', 'default' ), 403 );
861 + wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
792 862 }
793 863
794 864 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'] );
865 + TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add', 'error_details' => 'The HTTP POST data is empty.' ) );
798 866 }
799 867
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' ) );
868 + $add_table = wp_unslash( $_POST['table'] );
869 +
870 + // Perform confidence checks of posted data.
871 + $name = $add_table['name'] ?? '';
872 + $description = $add_table['description'] ?? '';
873 + if ( ! isset( $add_table['rows'], $add_table['columns'] ) ) {
874 + TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add', 'error_details' => 'The HTTP POST data does not contain the table size.' ) );
805 875 }
806 876
807 877 $num_rows = absint( $add_table['rows'] );
808 878 $num_columns = absint( $add_table['columns'] );
809 879 if ( 0 === $num_rows || 0 === $num_columns ) {
810 - TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add' ) );
880 + TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add', 'error_details' => 'The table size is invalid.' ) );
811 881 }
812 882
813 883 // Create a new table array with information from the posted data.
814 884 $new_table = array(
@@ -822,15 +892,15 @@
822 892 );
823 893 // Merge this data into an empty table template.
824 894 $table = TablePress::$model_table->prepare_table( TablePress::$model_table->get_table_template(), $new_table, false );
825 895 if ( is_wp_error( $table ) ) {
826 - TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add' ) );
896 + TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add', 'error_details' => TablePress::get_wp_error_string( $table ) ) );
827 897 }
828 898
829 899 // Add the new table (and get its first ID).
830 900 $table_id = TablePress::$model_table->add( $table );
831 901 if ( is_wp_error( $table_id ) ) {
832 - TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add' ) );
902 + TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add', 'error_details' => TablePress::get_wp_error_string( $table_id ) ) );
833 903 }
834 904
835 905 TablePress::redirect( array( 'action' => 'edit', 'table_id' => $table_id, 'message' => 'success_add' ) );
836 906 }
@@ -839,21 +909,21 @@
839 909 * Save changed "Plugin Options".
840 910 *
841 911 * @since 1.0.0
842 912 */
843 - public function handle_post_action_options() {
913 + public function handle_post_action_options(): void {
844 914 TablePress::check_nonce( 'options' );
845 915
846 916 if ( ! current_user_can( 'tablepress_access_options_screen' ) ) {
847 - wp_die( __( 'You do not have sufficient permissions to access this page.', 'default' ), 403 );
917 + wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
848 918 }
849 919
850 920 if ( empty( $_POST['options'] ) || ! is_array( $_POST['options'] ) ) {
851 921 TablePress::redirect( array( 'action' => 'options', 'message' => 'error_save' ) );
852 - } else {
853 - $posted_options = wp_unslash( $_POST['options'] );
854 922 }
855 923
924 + $posted_options = wp_unslash( $_POST['options'] );
925 +
856 926 // Valid new options that will be merged into existing ones.
857 927 $new_options = array();
858 928
859 929 // Check each posted option value, and (maybe) add it to the new options.
@@ -858,18 +928,18 @@
858 928
859 929 // Check each posted option value, and (maybe) add it to the new options.
860 930 if ( ! empty( $posted_options['admin_menu_parent_page'] ) && '-' !== $posted_options['admin_menu_parent_page'] ) {
861 931 $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.
932 + // Re-init parent information, as `TablePress::redirect()` URL might be wrong otherwise.
863 933 /** 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 );
934 + TablePress::$controller->parent_page = apply_filters( 'tablepress_admin_menu_parent_page', $posted_options['admin_menu_parent_page'] );
935 + TablePress::$controller->is_top_level_page = in_array( TablePress::$controller->parent_page, array( 'top', 'middle', 'bottom' ), true );
866 936 }
867 937
868 938 // Custom CSS can only be saved if the user is allowed to do so.
869 939 $update_custom_css_files = false;
870 940 if ( current_user_can( 'tablepress_edit_options' ) ) {
871 - // Checkbox
941 + // Checkbox.
872 942 $new_options['use_custom_css'] = ( isset( $posted_options['use_custom_css'] ) && 'true' === $posted_options['use_custom_css'] );
873 943
874 944 if ( isset( $posted_options['custom_css'] ) ) {
875 945 $new_options['custom_css'] = $posted_options['custom_css'];
@@ -874,13 +944,20 @@
874 944 if ( isset( $posted_options['custom_css'] ) ) {
875 945 $new_options['custom_css'] = $posted_options['custom_css'];
876 946
877 947 $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 948
949 + if ( '' !== $new_options['custom_css'] ) {
950 + // Update "Custom CSS" to use DataTables 2 variants instead of old DataTables 1.x CSS classes.
951 + $new_options['custom_css'] = TablePress::convert_datatables_api_data( $new_options['custom_css'] );
952 + // Sanitize and tidy up Custom CSS.
953 + $new_options['custom_css'] = $tablepress_css->sanitize_css( $new_options['custom_css'] );
954 + // Minify Custom CSS.
955 + $new_options['custom_css_minified'] = $tablepress_css->minify_css( $new_options['custom_css'] );
956 + } else {
957 + $new_options['custom_css_minified'] = '';
958 + }
959 +
883 960 // Maybe update CSS files as well.
884 961 $custom_css_file_contents = $tablepress_css->load_custom_css_from_file( 'normal' );
885 962 if ( false === $custom_css_file_contents ) {
886 963 $custom_css_file_contents = '';
@@ -911,39 +988,39 @@
911 988 * Export selected tables.
912 989 *
913 990 * @since 1.0.0
914 991 */
915 - public function handle_post_action_export() {
992 + public function handle_post_action_export(): void {
916 993 TablePress::check_nonce( 'export' );
917 994
918 995 if ( ! current_user_can( 'tablepress_export_tables' ) ) {
919 - wp_die( __( 'You do not have sufficient permissions to access this page.', 'default' ), 403 );
996 + wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
920 997 }
921 998
922 999 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'] );
1000 + TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export', 'error_details' => 'The HTTP POST data is empty.' ) );
926 1001 }
927 1002
1003 + $export = wp_unslash( $_POST['export'] );
1004 +
1005 + if ( empty( $export['tables_list'] ) ) {
1006 + TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export', 'error_details' => 'The HTTP POST data does not contain tables.' ) );
1007 + }
1008 +
1009 + /** @var TablePress_Export $exporter */ // phpcs:ignore Generic.Commenting.DocComment.MissingShort
928 1010 $exporter = TablePress::load_class( 'TablePress_Export', 'class-export.php', 'classes' );
929 1011
930 - if ( empty( $export['tables'] ) ) {
931 - TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export' ) );
932 - }
933 1012 if ( empty( $export['format'] ) || ! isset( $exporter->export_formats[ $export['format'] ] ) ) {
934 - TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export' ) );
1013 + TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export', 'error_details' => 'The export format is invalid.' ) );
935 1014 }
936 - if ( empty( $export['csv_delimiter'] ) ) {
937 - // Set a value, so that the variable exists.
1015 + if ( ! isset( $export['csv_delimiter'] ) ) {
938 1016 $export['csv_delimiter'] = '';
939 1017 }
940 1018 if ( 'csv' === $export['format'] && ! isset( $exporter->csv_delimiters[ $export['csv_delimiter'] ] ) ) {
941 - TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export' ) );
1019 + TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export', 'error_details' => 'The CSV delimiter is invalid.' ) );
942 1020 }
943 1021
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'];
1022 + $tables = explode( ',', $export['tables_list'] );
946 1023
947 1024 // Determine if ZIP file support is available.
948 1025 if ( $exporter->zip_support_available
949 1026 && ( ( isset( $export['zip_file'] ) && 'true' === $export['zip_file'] ) || count( $tables ) > 1 ) ) {
@@ -953,49 +1030,68 @@
953 1030 $export_to_zip = false;
954 1031 }
955 1032
956 1033 if ( ! $export_to_zip ) {
957 - // This is only possible for one table, so take the first one.
1034 + // Exporting without a ZIP file is only possible for one table, so take the first one.
958 1035 if ( ! current_user_can( 'tablepress_export_table', $tables[0] ) ) {
959 - wp_die( __( 'You do not have sufficient permissions to access this page.', 'default' ), 403 );
1036 + wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
960 1037 }
961 1038 // Load table, with table data, options, and visibility settings.
962 1039 $table = TablePress::$model_table->load( $tables[0], true, true );
963 1040 if ( is_wp_error( $table ) ) {
964 - TablePress::redirect( array( 'action' => 'export', 'message' => 'error_load_table', 'export_format' => $export['format'], 'csv_delimiter' => $export['csv_delimiter'] ) );
1041 + TablePress::redirect( array( 'action' => 'export', 'message' => 'error_load_table', 'export_format' => $export['format'], 'csv_delimiter' => $export['csv_delimiter'], 'error_details' => TablePress::get_wp_error_string( $table ) ) );
965 1042 }
966 1043 if ( isset( $table['is_corrupted'] ) && $table['is_corrupted'] ) {
967 1044 TablePress::redirect( array( 'action' => 'export', 'message' => 'error_table_corrupted', 'export_format' => $export['format'], 'csv_delimiter' => $export['csv_delimiter'] ) );
968 1045 }
969 - $download_filename = sprintf( '%1$s-%2$s-%3$s.%4$s', $table['id'], $table['name'], date( 'Y-m-d' ), $export['format'] );
1046 + $download_filename = sprintf( '%1$s-%2$s-%3$s.%4$s', $table['id'], $table['name'], wp_date( 'Y-m-d' ), $export['format'] );
1047 + /**
1048 + * Filters the download filename of the exported table.
1049 + *
1050 + * @since 2.0.0
1051 + *
1052 + * @param string $download_filename The download filename of exported table.
1053 + * @param string $table_id Table ID of the exported table.
1054 + * @param string $table_name Table name of the exported table.
1055 + * @param string $export_format Format for the export ('csv', 'html', 'json', 'zip').
1056 + * @param bool $export_to_zip Whether the export is to a ZIP file (of multiple export files).
1057 + */
1058 + $download_filename = apply_filters( 'tablepress_export_filename', $download_filename, $table['id'], $table['name'], $export['format'], $export_to_zip );
970 1059 $download_filename = sanitize_file_name( $download_filename );
971 1060 // Export the table.
972 - $export_data = $exporter->export_table( $table, $export['format'], $export['csv_delimiter'] );
1061 + $options = array();
1062 + if ( 'csv' === $export['format'] ) {
1063 + $options['csv_delimiter'] = $export['csv_delimiter'];
1064 + }
1065 + $export_data = $exporter->export_table( $table, $export['format'], $options );
973 1066 /**
974 - * Filter the exported table data.
1067 + * Filters the exported table data.
975 1068 *
976 1069 * @since 1.6.0
977 1070 *
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.
1071 + * @param string $export_data The exported table data.
1072 + * @param array<string, mixed> $table Table to be exported.
1073 + * @param string $export_format Format for the export ('csv', 'html', 'json').
1074 + * @param string $csv_delimiter Delimiter for CSV export.
982 1075 */
983 1076 $export_data = apply_filters( 'tablepress_export_data', $export_data, $table, $export['format'], $export['csv_delimiter'] );
984 1077 $download_data = $export_data;
985 1078 } else {
986 1079 // 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 );
1080 + wp_raise_memory_limit( 'admin' );
1081 + if ( function_exists( 'set_time_limit' ) ) {
1082 + @set_time_limit( 300 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
1083 + }
990 1084
991 1085 $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'] );
1086 + $download_filename = sprintf( 'tablepress-export-%1$s-%2$s.zip', wp_date( 'Y-m-d-H-i-s' ), $export['format'] );
1087 + /** This filter is documented in controllers/controller-admin.php */
1088 + $download_filename = apply_filters( 'tablepress_export_filename', $download_filename, '', '', $export['format'], $export_to_zip );
993 1089 $download_filename = sanitize_file_name( $download_filename );
994 1090 $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'] ) );
1091 + if ( true !== $zip_file->open( $full_filename, ZipArchive::OVERWRITE ) ) {
1092 + @unlink( $full_filename ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
1093 + TablePress::redirect( array( 'action' => 'export', 'message' => 'error_create_zip_file', 'export_format' => $export['format'], 'csv_delimiter' => $export['csv_delimiter'], 'error_details' => 'The ZIP file could not be opened for writing.' ) );
998 1094 }
999 1095
1000 1096 foreach ( $tables as $table_id ) {
1001 1097 // Don't export tables for which the user doesn't have the necessary export rights.
@@ -1011,27 +1107,38 @@
1011 1107 // Don't export if the table is corrupted.
1012 1108 if ( isset( $table['is_corrupted'] ) && $table['is_corrupted'] ) {
1013 1109 continue;
1014 1110 }
1015 - $export_data = $exporter->export_table( $table, $export['format'], $export['csv_delimiter'] );
1111 + $options = array();
1112 + if ( 'csv' === $export['format'] ) {
1113 + $options['csv_delimiter'] = $export['csv_delimiter'];
1114 + }
1115 + $export_data = $exporter->export_table( $table, $export['format'], $options );
1016 1116 /** This filter is documented in controllers/controller-admin.php */
1017 1117 $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'] );
1118 + $export_filename = sprintf( '%1$s-%2$s-%3$s.%4$s', $table['id'], $table['name'], wp_date( 'Y-m-d' ), $export['format'] );
1119 + /** This filter is documented in controllers/controller-admin.php */
1120 + $export_filename = apply_filters( 'tablepress_export_filename', $export_filename, $table['id'], $table['name'], $export['format'], $export_to_zip );
1019 1121 $export_filename = sanitize_file_name( $export_filename );
1020 1122 $zip_file->addFromString( $export_filename, $export_data );
1021 1123 }
1022 1124
1023 1125 // 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 ) {
1126 + // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
1127 + if ( ZipArchive::ER_OK !== $zip_file->status || 0 === $zip_file->numFiles ) {
1025 1128 $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'] ) );
1129 + @unlink( $full_filename ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
1130 + TablePress::redirect( array( 'action' => 'export', 'message' => 'error_create_zip_file', 'export_format' => $export['format'], 'csv_delimiter' => $export['csv_delimiter'], 'error_details' => 'The ZIP file could not be written or is empty.' ) );
1028 1131 }
1029 1132 $zip_file->close();
1030 1133
1031 1134 // Load contents of the ZIP file, to send it as a download.
1032 1135 $download_data = file_get_contents( $full_filename );
1033 - @unlink( $full_filename );
1136 + if ( false === $download_data ) {
1137 + @unlink( $full_filename ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
1138 + TablePress::redirect( array( 'action' => 'export', 'message' => 'error_create_zip_file', 'export_format' => $export['format'], 'csv_delimiter' => $export['csv_delimiter'], 'error_details' => 'The ZIP file content could not be read.' ) );
1139 + }
1140 + @unlink( $full_filename ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
1034 1141 }
1035 1142
1036 1143 // Send download headers for export file.
1037 1144 header( 'Content-Description: File Transfer' );
@@ -1043,9 +1150,9 @@
1043 1150 header( 'Pragma: public' );
1044 1151 header( 'Content-Length: ' . strlen( $download_data ) );
1045 1152 // $filetype = text/csv, text/html, application/json
1046 1153 // header( 'Content-Type: ' . $filetype. '; charset=' . get_option( 'blog_charset' ) );
1047 - @ob_end_clean();
1154 + @ob_end_clean(); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
1048 1155 flush();
1049 1156 echo $download_data;
1050 1157 exit;
1051 1158 }
@@ -1050,669 +1157,100 @@
1050 1157 exit;
1051 1158 }
1052 1159
1053 1160 /**
1054 - * Import data from either an existing source or WP-Table Reloaded.
1161 + * Import data from existing source (Upload, URL, Server, Direct input).
1055 1162 *
1056 1163 * @since 1.0.0
1057 1164 */
1058 - public function handle_post_action_import() {
1165 + public function handle_post_action_import(): void {
1059 1166 TablePress::check_nonce( 'import' );
1060 1167
1061 1168 if ( ! current_user_can( 'tablepress_import_tables' ) ) {
1062 - wp_die( __( 'You do not have sufficient permissions to access this page.', 'default' ), 403 );
1169 + wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
1063 1170 }
1064 1171
1065 1172 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'] );
1173 + TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import', 'error_details' => 'The HTTP POST data is empty.' ) );
1069 1174 }
1070 1175
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 - }
1176 + $import_config = wp_unslash( $_POST['import'] );
1076 1177
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 );
1178 + if ( empty( $import_config['source'] ) ) {
1179 + TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import', 'error_details' => 'The HTTP POST does not contain an import configuration.' ) );
1091 1180 }
1092 - }
1093 1181
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'] );
1182 + // For security reasons, the "server" source is only available for super admins on multisite and admins on single sites.
1183 + if ( 'server' === $import_config['source'] ) {
1184 + if ( ! is_super_admin() && ! ( ! is_multisite() && current_user_can( 'manage_options' ) ) ) {
1185 + TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import', 'error_details' => 'You do not have the required access rights.' ) );
1177 1186 }
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 1187 }
1180 1188
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'] ) );
1189 + // For security reasons, the "url" source is only available admins and editors via a custom capability.
1190 + if ( 'url' === $import_config['source'] ) {
1191 + if ( ! current_user_can( 'tablepress_import_tables_url' ) ) {
1192 + TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import', 'error_details' => 'You do not have the required access rights.' ) );
1190 1193 }
1191 - $import_zip = true;
1192 - } else {
1193 - $import_zip = false;
1194 1194 }
1195 1195
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 - }
1196 + // Move file upload data to the main import configuration.
1197 + $import_config['file-upload'] = $_FILES['import_file_upload'] ?? null;
1203 1198
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 - }
1199 + // Check if the source data for the chosen import source is defined.
1200 + if ( empty( $import_config[ $import_config['source'] ] ) ) {
1201 + TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import', 'error_details' => 'The HTTP POST data does not contain an import source.' ) );
1271 1202 }
1272 1203
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' );
1204 + // Set default values for non-essential configuration variables.
1205 + if ( ! isset( $import_config['type'] ) ) {
1206 + $import_config['type'] = 'add';
1292 1207 }
1293 -
1294 - if ( false === $existing_table_id ) {
1295 - $import_type = 'add';
1208 + if ( ! isset( $import_config['existing_table'] ) ) {
1209 + $import_config['existing_table'] = '';
1296 1210 }
1297 1211
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 - }
1212 + $import_config['legacy_import'] = ( isset( $import_config['legacy_import'] ) && 'true' === $import_config['legacy_import'] );
1302 1213
1303 - // Full JSON format table can contain a table ID, try to keep that.
1304 - $table_id_in_import = false;
1214 + $importer = TablePress::load_class( 'TablePress_Import', 'class-import.php', 'classes' );
1215 + $import = $importer->run( $import_config );
1305 1216
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;
1217 + if ( is_wp_error( $import ) || 0 < count( $import['errors'] ) ) {
1218 + $redirect_parameters = array(
1219 + 'action' => 'import',
1220 + 'message' => 'error_import',
1221 + 'import_type' => $import_config['type'],
1222 + 'import_existing_table' => $import_config['existing_table'],
1223 + 'import_source' => $import_config['source'],
1224 + 'legacy_import' => $import_config['legacy_import'],
1225 + );
1226 + if ( in_array( $import_config['source'], array( 'url', 'server' ), true ) ) {
1227 + $redirect_parameters[ "import_{$import_config['source']}" ] = rawurlencode( $import_config[ $import_config['source'] ] );
1413 1228 }
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;
1229 + if ( is_wp_error( $import ) ) {
1230 + $redirect_parameters['error_details'] = TablePress::get_wp_error_string( $import );
1231 + } elseif ( 0 < count( $import['errors'] ) ) {
1232 + $wp_error_strings = array();
1233 + foreach ( $import['errors'] as $file ) {
1234 + $wp_error_strings[] = TablePress::get_wp_error_string( $file->error );
1454 1235 }
1236 + $redirect_parameters['error_details'] = implode( ', ', $wp_error_strings );
1455 1237 }
1238 + TablePress::redirect( $redirect_parameters );
1456 1239 }
1457 1240
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' ) );
1241 + // At this point, there were no import errors.
1242 + if ( count( $import['tables'] ) > 1 ) {
1243 + TablePress::redirect( array( 'action' => 'list', 'message' => 'success_import' ) );
1244 + } elseif ( 1 === count( $import['tables'] ) ) {
1245 + TablePress::redirect( array( 'action' => 'edit', 'table_id' => $import['tables'][0]['id'], 'message' => 'success_import' ) );
1474 1246 } else {
1475 - TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import_wp_table_reloaded' ) );
1247 + TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import', 'error_details' => 'The number of imported tables is invalid.' ) );
1476 1248 }
1477 1249 }
1478 1250
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 1251 /*
1714 - * Save GET actions.
1252 + * HTTP GET actions.
1715 1253 */
1716 1254
1717 1255 /**
1718 1256 * Hide a header message on an admin screen.
@@ -1718,14 +1256,14 @@
1718 1256 * Hide a header message on an admin screen.
1719 1257 *
1720 1258 * @since 1.0.0
1721 1259 */
1722 - public function handle_get_action_hide_message() {
1723 - $message_item = ! empty( $_GET['item'] ) ? $_GET['item'] : '';
1260 + public function handle_get_action_hide_message(): void {
1261 + $message_item = $_GET['item'] ?? '';
1724 1262 TablePress::check_nonce( 'hide_message', $message_item );
1725 1263
1726 1264 if ( ! current_user_can( 'tablepress_list_tables' ) ) {
1727 - wp_die( __( 'You do not have sufficient permissions to access this page.', 'default' ), 403 );
1265 + wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
1728 1266 }
1729 1267
1730 1268 TablePress::$model_options->update( "message_{$message_item}", false );
1731 1269
@@ -1737,9 +1275,9 @@
1737 1275 * Delete a table.
1738 1276 *
1739 1277 * @since 1.0.0
1740 1278 */
1741 - public function handle_get_action_delete_table() {
1279 + public function handle_get_action_delete_table(): void {
1742 1280 $table_id = ( ! empty( $_GET['item'] ) ) ? $_GET['item'] : false;
1743 1281 TablePress::check_nonce( 'delete_table', $table_id );
1744 1282
1745 1283 $return = ! empty( $_GET['return'] ) ? $_GET['return'] : 'list';
@@ -1744,20 +1282,20 @@
1744 1282
1745 1283 $return = ! empty( $_GET['return'] ) ? $_GET['return'] : 'list';
1746 1284 $return_item = ! empty( $_GET['return_item'] ) ? $_GET['return_item'] : false;
1747 1285
1748 - // Nonce check should actually catch this already.
1286 + // The nonce check should actually catch this already.
1749 1287 if ( false === $table_id ) {
1750 1288 TablePress::redirect( array( 'action' => $return, 'message' => 'error_delete', 'table_id' => $return_item ) );
1751 1289 }
1752 1290
1753 1291 if ( ! current_user_can( 'tablepress_delete_table', $table_id ) ) {
1754 - wp_die( __( 'You do not have sufficient permissions to access this page.', 'default' ), 403 );
1292 + wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
1755 1293 }
1756 1294
1757 1295 $deleted = TablePress::$model_table->delete( $table_id );
1758 1296 if ( is_wp_error( $deleted ) ) {
1759 - TablePress::redirect( array( 'action' => $return, 'message' => 'error_delete', 'table_id' => $return_item ) );
1297 + TablePress::redirect( array( 'action' => $return, 'message' => 'error_delete', 'table_id' => $return_item, 'error_details' => TablePress::get_wp_error_string( $deleted ) ) );
1760 1298 }
1761 1299
1762 1300 /*
1763 1301 * Slightly more complex redirect method, to account for sort, search, and pagination in the WP_List_Table on the List View,
@@ -1778,9 +1316,9 @@
1778 1316 * Copy a table.
1779 1317 *
1780 1318 * @since 1.0.0
1781 1319 */
1782 - public function handle_get_action_copy_table() {
1320 + public function handle_get_action_copy_table(): void {
1783 1321 $table_id = ( ! empty( $_GET['item'] ) ) ? $_GET['item'] : false;
1784 1322 TablePress::check_nonce( 'copy_table', $table_id );
1785 1323
1786 1324 $return = ! empty( $_GET['return'] ) ? $_GET['return'] : 'list';
@@ -1785,25 +1323,22 @@
1785 1323
1786 1324 $return = ! empty( $_GET['return'] ) ? $_GET['return'] : 'list';
1787 1325 $return_item = ! empty( $_GET['return_item'] ) ? $_GET['return_item'] : false;
1788 1326
1789 - // Nonce check should actually catch this already.
1327 + // The nonce check should actually catch this already.
1790 1328 if ( false === $table_id ) {
1791 1329 TablePress::redirect( array( 'action' => $return, 'message' => 'error_copy', 'table_id' => $return_item ) );
1792 1330 }
1793 1331
1794 1332 if ( ! current_user_can( 'tablepress_copy_table', $table_id ) ) {
1795 - wp_die( __( 'You do not have sufficient permissions to access this page.', 'default' ), 403 );
1333 + wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
1796 1334 }
1797 1335
1798 - $this->init_i18n_support(); // for the translation of "Copy of".
1799 -
1800 1336 $copy_table_id = TablePress::$model_table->copy( $table_id );
1801 1337 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;
1338 + TablePress::redirect( array( 'action' => $return, 'message' => 'error_copy', 'table_id' => $return_item, 'error_details' => TablePress::get_wp_error_string( $copy_table_id ) ) );
1805 1339 }
1340 + $return_item = $copy_table_id;
1806 1341
1807 1342 /*
1808 1343 * Slightly more complex redirect method, to account for sort, search, and pagination in the WP_List_Table on the List View,
1809 1344 * but only if this action succeeds, to have everything fresh in the event of an error.
@@ -1823,14 +1358,12 @@
1823 1358 * Preview a table.
1824 1359 *
1825 1360 * @since 1.0.0
1826 1361 */
1827 - public function handle_get_action_preview_table() {
1362 + public function handle_get_action_preview_table(): void {
1828 1363 $table_id = ( ! empty( $_GET['item'] ) ) ? $_GET['item'] : false;
1829 1364 TablePress::check_nonce( 'preview_table', $table_id );
1830 1365
1831 - $this->init_i18n_support();
1832 -
1833 1366 // Nonce check should actually catch this already.
1834 1367 if ( false === $table_id ) {
1835 1368 wp_die( __( 'The preview could not be loaded.', 'tablepress' ), __( 'Preview', 'tablepress' ) );
1836 1369 }
@@ -1835,9 +1368,9 @@
1835 1368 wp_die( __( 'The preview could not be loaded.', 'tablepress' ), __( 'Preview', 'tablepress' ) );
1836 1369 }
1837 1370
1838 1371 if ( ! current_user_can( 'tablepress_preview_table', $table_id ) ) {
1839 - wp_die( __( 'You do not have sufficient permissions to access this page.', 'default' ), 403 );
1372 + wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
1840 1373 }
1841 1374
1842 1375 // Load table, with table data, options, and visibility settings.
1843 1376 $table = TablePress::$model_table->load( $table_id, true, true );
@@ -1844,10 +1377,12 @@
1844 1377 if ( is_wp_error( $table ) ) {
1845 1378 wp_die( __( 'The table could not be loaded.', 'tablepress' ), __( 'Preview', 'tablepress' ) );
1846 1379 }
1847 1380
1848 - // Sanitize all table data to remove unsafe HTML from the preview output.
1849 - $table = TablePress::$model_table->sanitize( $table );
1381 + // Sanitize all table data to remove unsafe HTML from the preview output, if the user is not allowed to work with unfiltered HTML.
1382 + if ( ! current_user_can( 'unfiltered_html' ) ) {
1383 + $table = TablePress::$model_table->sanitize( $table );
1384 + }
1850 1385
1851 1386 // Create a render class instance.
1852 1387 $_render = TablePress::load_class( 'TablePress_Render', 'class-render.php', 'classes' );
1853 1388 // Merge desired options with default render options (see TablePress_Controller_Frontend::shortcode_table()).
@@ -1856,18 +1391,22 @@
1856 1391 $default_render_options = apply_filters( 'tablepress_shortcode_table_default_shortcode_atts', $default_render_options );
1857 1392 $render_options = shortcode_atts( $default_render_options, $table['options'] );
1858 1393 /** This filter is documented in controllers/controller-frontend.php */
1859 1394 $render_options = apply_filters( 'tablepress_shortcode_table_shortcode_atts', $render_options );
1395 + $render_options['html_id'] = "tablepress-{$table['id']}";
1396 + $render_options['block_preview'] = true;
1860 1397 $_render->set_input( $table, $render_options );
1861 1398 $view_data = array(
1862 - 'table_id' => $table_id,
1863 - 'head_html' => $_render->get_preview_css(),
1864 - 'body_html' => $_render->get_output(),
1399 + 'table_id' => $table_id,
1400 + 'head_html' => $_render->get_preview_css(),
1401 + 'body_html' => $_render->get_output( 'html' ),
1402 + 'site_used_editor' => TablePress::site_used_editor(),
1865 1403 );
1866 1404
1867 1405 $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";
1406 + $use_custom_css = ( TablePress::$model_options->get( 'use_custom_css' ) && '' !== $custom_css );
1407 + if ( $use_custom_css ) {
1408 + $view_data['head_html'] .= "<style>\n{$custom_css}\n</style>\n";
1870 1409 }
1871 1410
1872 1411 // Prepare, initialize, and render the view.
1873 1412 $this->view = TablePress::load_view( 'preview_table', $view_data );
@@ -1874,21 +1413,19 @@
1874 1413 $this->view->render();
1875 1414 }
1876 1415
1877 1416 /**
1878 - * Show a list of tables in the Editor toolbar Thickbox (opened by TinyMCE or Quicktags button).
1417 + * Shows a list of tables in the Editor toolbar Thickbox (opened by TinyMCE or Quicktags button).
1879 1418 *
1880 1419 * @since 1.0.0
1881 1420 */
1882 - public function handle_get_action_editor_button_thickbox() {
1421 + public function handle_get_action_editor_button_thickbox(): void {
1883 1422 TablePress::check_nonce( 'editor_button_thickbox' );
1884 1423
1885 1424 if ( ! current_user_can( 'tablepress_list_tables' ) ) {
1886 - wp_die( __( 'You do not have sufficient permissions to access this page.', 'default' ), 403 );
1425 + wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
1887 1426 }
1888 1427
1889 - $this->init_i18n_support();
1890 -
1891 1428 $view_data = array(
1892 1429 // Load all table IDs without priming the post meta cache, as table options/visibility are not needed.
1893 1430 'table_ids' => TablePress::$model_table->load_all( false ),
1894 1431 );
@@ -1904,15 +1441,15 @@
1904 1441 * Uninstall TablePress, and delete all tables and options.
1905 1442 *
1906 1443 * @since 1.0.0
1907 1444 */
1908 - public function handle_get_action_uninstall_tablepress() {
1445 + public function handle_get_action_uninstall_tablepress(): void {
1909 1446 TablePress::check_nonce( 'uninstall_tablepress' );
1910 1447
1911 1448 $plugin = TABLEPRESS_BASENAME;
1912 1449
1913 1450 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 );
1451 + wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
1915 1452 }
1916 1453
1917 1454 // Deactivate TablePress for the site (but not for the network).
1918 1455 deactivate_plugins( $plugin, false, false );
@@ -1926,11 +1463,9 @@
1926 1463
1927 1464 TablePress::$model_table->destroy();
1928 1465 TablePress::$model_options->destroy();
1929 1466
1930 - $this->init_i18n_support();
1931 -
1932 - $output = '<strong>' . __( 'TablePress was uninstalled successfully.', 'tablepress' ) . '</strong><br /><br />';
1467 + $output = '<strong>' . __( 'TablePress was uninstalled successfully.', 'tablepress' ) . '</strong><br><br>';
1933 1468 $output .= __( 'All tables, data, and options were deleted.', 'tablepress' );
1934 1469 if ( is_multisite() ) {
1935 1470 $output .= ' ' . __( 'You may now ask the network admin to delete the plugin&#8217;s folder <code>tablepress</code> from the server, if no other site in the network uses it.', 'tablepress' );
1936 1471 } else {
@@ -1937,9 +1472,9 @@
1937 1472 $output .= ' ' . __( 'You may now manually delete the plugin&#8217;s folder <code>tablepress</code> from the <code>plugins</code> directory on your server or use the &#8220;Delete&#8221; link for TablePress on the WordPress &#8220;Plugins&#8221; page.', 'tablepress' );
1938 1473 }
1939 1474 if ( $css_files_deleted ) {
1940 1475 $output .= ' ' . __( 'Your TablePress &#8220;Custom CSS&#8221; files have been deleted automatically.', 'tablepress' );
1941 - } else {
1476 + } else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found
1942 1477 if ( is_multisite() ) {
1943 1478 $output .= ' ' . __( 'Please also ask him to delete your TablePress &#8220;Custom CSS&#8221; files from the server.', 'tablepress' );
1944 1479 } else {
1945 1480 $output .= ' ' . __( 'You may now also delete your TablePress &#8220;Custom CSS&#8221; files in the <code>wp-content</code> folder.', 'tablepress' );