PluginProbe
TablePress – Tables in WordPress made easy / 2.1.8
TablePress – Tables in WordPress made easy v2.1.8
3.3.4 3.3.3 3.3.2 3.3.1 trunk 1.12 1.14 1.9.2 2.0.4 2.1.7 2.1.8 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3 2.3.1 2.3.2 2.4 2.4.1 2.4.2 2.4.3 2.4.4 All 44 releases
tablepress / controllers / controller-admin.php

controller-admin.php in TablePress – Tables in WordPress made easy 2.1.8, at controllers/controller-admin.php

1,347 lines 56.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Admin Controller for TablePress with the functionality for the non-AJAX backend
4 *
5 * @package TablePress
6 * @subpackage Controllers
7 * @author Tobias Bäthge
8 * @since 1.0.0
9 */
10
11 // Prohibit direct script loading.
12 defined( 'ABSPATH' ) || die( 'No direct script access allowed!' );
13
14 /**
15 * Admin Controller class, extends Base Controller Class
16 *
17 * @package TablePress
18 * @subpackage Controllers
19 * @author Tobias Bäthge
20 * @since 1.0.0
21 */
22 class TablePress_Admin_Controller extends TablePress_Controller {
23
24 /**
25 * Page hooks (i.e. names) WordPress uses for the TablePress admin screens,
26 * populated in add_admin_menu_entry().
27 *
28 * @since 1.0.0
29 * @var array
30 */
31 protected $page_hooks = array();
32
33 /**
34 * Actions that have a view and admin menu or nav tab menu entry.
35 *
36 * @since 1.0.0
37 * @var array
38 */
39 protected $view_actions = array();
40
41 /**
42 * Instance of the TablePress Admin View that is rendered.
43 *
44 * @since 1.0.0
45 * @var TablePress_View
46 */
47 protected $view;
48
49 /**
50 * Initialize the Admin Controller, determine location the admin menu, set up actions.
51 *
52 * @since 1.0.0
53 */
54 public function __construct() {
55 parent::__construct();
56
57 // Handler for changing the number of shown tables in the list of tables (via WP List Table class).
58 add_filter( 'set_screen_option_tablepress_list_per_page', array( $this, 'save_list_tables_screen_option' ), 10, 3 );
59
60 add_action( 'admin_menu', array( $this, 'add_admin_menu_entry' ) );
61 add_action( 'admin_init', array( $this, 'add_admin_actions' ) );
62
63 add_action( 'enqueue_block_editor_assets', array( $this, 'add_block_editor_js' ) );
64 }
65
66 /**
67 * Handler for changing the number of shown tables in the list of tables (via WP List Table class).
68 *
69 * @since 1.0.0
70 *
71 * @param mixed $screen_option Current value of the filter (probably bool false).
72 * @param string $option Option in which the setting is stored.
73 * @param int $value Current value of the setting.
74 * @return bool|int False to not save the changed setting, or the int value to be saved.
75 */
76 public function save_list_tables_screen_option( $screen_option, $option, $value ) {
77 return $value;
78 }
79
80 /**
81 * Add admin screens to the correct place in the admin menu.
82 *
83 * @since 1.0.0
84 */
85 public function add_admin_menu_entry() {
86 // Callback for all menu entries.
87 $callback = array( $this, 'show_admin_page' );
88 /**
89 * Filters the TablePress admin menu entry name.
90 *
91 * @since 1.0.0
92 *
93 * @param string $entry_name The admin menu entry name. Default "TablePress".
94 */
95 $admin_menu_entry_name = apply_filters( 'tablepress_admin_menu_entry_name', 'TablePress' );
96
97 $this->init_view_actions();
98 $min_access_cap = $this->view_actions['list']['required_cap'];
99
100 if ( $this->is_top_level_page ) {
101 $icon_url = 'dashicons-list-view';
102 switch ( $this->parent_page ) {
103 case 'top':
104 $position = 3; // Position of Dashboard + 1.
105 break;
106 case 'bottom':
107 $position = ( ++$GLOBALS['_wp_last_utility_menu'] );
108 break;
109 case 'middle':
110 default:
111 $position = ( ++$GLOBALS['_wp_last_object_menu'] );
112 break;
113 }
114 add_menu_page( 'TablePress', $admin_menu_entry_name, $min_access_cap, 'tablepress', $callback, $icon_url, $position );
115 foreach ( $this->view_actions as $action => $entry ) {
116 if ( ! $entry['show_entry'] ) {
117 continue;
118 }
119 $slug = 'tablepress';
120 if ( 'list' !== $action ) {
121 $slug .= '_' . $action;
122 }
123 $this->page_hooks[] = add_submenu_page( 'tablepress', sprintf( __( '%1$s &lsaquo; %2$s', 'tablepress' ), $entry['page_title'], 'TablePress' ), $entry['admin_menu_title'], $entry['required_cap'], $slug, $callback );
124 }
125 } else {
126 $this->page_hooks[] = add_submenu_page( $this->parent_page, 'TablePress', $admin_menu_entry_name, $min_access_cap, 'tablepress', $callback );
127 }
128 }
129
130 /**
131 * Set up handlers for user actions in the backend that exceed plain viewing.
132 *
133 * @since 1.0.0
134 */
135 public function add_admin_actions() {
136 // Register the callbacks for processing action requests.
137 $post_actions = array( 'list', 'add', 'options', 'export', 'import' );
138 $get_actions = array( 'hide_message', 'delete_table', 'copy_table', 'preview_table', 'editor_button_thickbox', 'uninstall_tablepress' );
139 foreach ( $post_actions as $action ) {
140 add_action( "admin_post_tablepress_{$action}", array( $this, "handle_post_action_{$action}" ) );
141 }
142 foreach ( $get_actions as $action ) {
143 add_action( "admin_post_tablepress_{$action}", array( $this, "handle_get_action_{$action}" ) );
144 }
145
146 // Register callbacks to trigger load behavior for admin pages.
147 foreach ( $this->page_hooks as $page_hook ) {
148 add_action( "load-{$page_hook}", array( $this, 'load_admin_page' ) );
149 }
150
151 /**
152 * Filters whether the legacy editor button should be loaded on the post editing screen.
153 *
154 * @since 2.1.0
155 *
156 * @param bool $load_button Whether to load the legacy editor button. Default true.
157 */
158 if ( apply_filters( 'tablepress_add_legacy_editor_button', true ) ) {
159 $pages_with_editor_button = array( 'post.php', 'post-new.php' );
160 foreach ( $pages_with_editor_button as $editor_page ) {
161 add_action( "load-{$editor_page}", array( $this, 'add_editor_buttons' ) );
162 }
163 }
164
165 if ( ! is_network_admin() && ! is_user_admin() ) {
166 add_action( 'admin_bar_menu', array( $this, 'add_wp_admin_bar_new_content_menu_entry' ), 71 );
167 }
168
169 add_action( 'load-plugins.php', array( $this, 'plugins_page' ) );
170
171 // Add filters and actions for the integration into the WP WXR exporter and importer.
172 add_action( 'wp_import_insert_post', array( TablePress::$model_table, 'add_table_id_on_wp_import' ), 10, 4 );
173 add_filter( 'wp_import_post_meta', array( TablePress::$model_table, 'prevent_table_id_post_meta_import_on_wp_import' ), 10, 3 );
174 add_filter( 'wxr_export_skip_postmeta', array( TablePress::$model_table, 'add_table_id_to_wp_export' ), 10, 3 );
175 }
176
177 /**
178 * Loads additional JavaScript code for the TablePress table block.
179 *
180 * @since 2.0.0
181 */
182 public function add_block_editor_js() {
183 // Add table information for the Block Editor to the page.
184 $handle = generate_block_asset_handle( 'tablepress/table', 'editorScript' );
185 $data = $this->get_block_editor_data();
186 wp_add_inline_script( $handle, $data, 'before' );
187 }
188
189 /**
190 * Gets the inline data that is referenced by the Block Editor JavaScript code for the TablePress blocks.
191 *
192 * @since 2.0.0
193 *
194 * @return string JavaScript code for the Block Editor.
195 */
196 protected function get_block_editor_data() {
197 $tables = array();
198 // Load all table IDs without priming the post meta cache, as table options/visibility are not needed.
199 $table_ids = TablePress::$model_table->load_all( false );
200 foreach ( $table_ids as $table_id ) {
201 // Load table, without table data, options, and visibility settings.
202 $table = TablePress::$model_table->load( $table_id, false, false );
203 if ( '' === trim( $table['name'] ) ) {
204 $table['name'] = __( '(no name)', 'tablepress' );
205 }
206 $tables[ $table_id ] = esc_html( $table['name'] );
207 }
208
209 /**
210 * Filters the list of table IDs and names that is passed to the block editor, and is then used in the dropdown of the TablePress table block.
211 *
212 * @since 2.0.0
213 *
214 * @param array $tables List of table names, the table ID is the array key.
215 */
216 $tables = apply_filters( 'tablepress_block_editor_tables_list', $tables );
217
218 $tables = wp_json_encode( $tables, TABLEPRESS_JSON_OPTIONS );
219 // Print them inside a `JSON.parse()` call in JS for speed gains, with necessary escaping of `</script>`, `'`, and `\`.
220 $tables = str_replace( array( '</script>', '\\', "'" ), array( '<\/script>', '\\\\', "\'" ), $tables );
221
222 $shortcode = esc_js( TablePress::$shortcode );
223
224 $template = TablePress::$model_table->get_table_template();
225 $template = wp_json_encode( $template['options'], TABLEPRESS_JSON_OPTIONS );
226 // Print them inside a `JSON.parse()` call in JS for speed gains, with necessary escaping of `</script>`, `'`, and `\`.
227 $template = str_replace( array( '</script>', '\\', "'" ), array( '<\/script>', '\\\\', "\'" ), $template );
228
229 /**
230 * Filters whether the table block preview should be loaded via a <ServerSideRender> in the block editor.
231 *
232 * @since 2.0.0
233 *
234 * @param bool $load_block_preview Whether the table block preview should be loaded.
235 */
236 $load_block_preview = apply_filters( 'tablepress_show_block_editor_preview', true );
237 $load_block_preview = (bool) $load_block_preview ? 'true' : 'false';
238
239 $url = '';
240 if ( current_user_can( 'tablepress_list_tables' ) ) {
241 $url = TablePress::url( array( 'action' => 'list' ) );
242 }
243
244 return <<<JS
245 // Ensure the global `tp` object exists.
246 window.tp = window.tp || {};
247 tp.url = '{$url}';
248 tp.load_block_preview = {$load_block_preview};
249 tp.table = {};
250 tp.table.shortcode = '{$shortcode}';
251 tp.table.template = JSON.parse( '{$template}' );
252 tp.tables = JSON.parse( '{$tables}' );
253 JS;
254 }
255
256 /**
257 * Register actions to add "Table" button to "HTML editor" and "Visual editor" toolbars.
258 *
259 * @since 1.0.0
260 */
261 public function add_editor_buttons() {
262 if ( ! current_user_can( 'tablepress_list_tables' ) ) {
263 return;
264 }
265
266 // Only load the toolbar integration if the Block Editor is not used.
267 if ( TablePress::site_uses_block_editor() ) {
268 return;
269 }
270
271 add_thickbox(); // The files are usually already loaded by media upload functions.
272 $admin_page = TablePress::load_class( 'TablePress_Admin_Page', 'class-admin-page-helper.php', 'classes' );
273 $admin_page->enqueue_script(
274 'quicktags-button',
275 array( 'quicktags', 'media-upload' ),
276 array(
277 'editor_button' => array(
278 'caption' => __( 'Table', 'tablepress' ),
279 'title' => __( 'Insert a TablePress table', 'tablepress' ),
280 'thickbox_title' => __( 'Insert a TablePress table', 'tablepress' ),
281 'thickbox_url' => TablePress::url( array( 'action' => 'editor_button_thickbox' ), true, 'admin-post.php' ),
282 ),
283 )
284 );
285
286 // TinyMCE integration.
287 if ( user_can_richedit() ) {
288 add_filter( 'mce_external_plugins', array( $this, 'add_tinymce_plugin' ) );
289 add_filter( 'mce_buttons', array( $this, 'add_tinymce_button' ) );
290 }
291 }
292
293 /**
294 * Adds the "Table" button to the TinyMCE toolbar.
295 *
296 * @since 1.0.0
297 *
298 * @param array $buttons Current set of buttons in the TinyMCE toolbar.
299 * @return array Extended set of buttons in the TinyMCE toolbar, including the "Table" button.
300 */
301 public function add_tinymce_button( array $buttons ) {
302 $buttons[] = 'tablepress_insert_table';
303 return $buttons;
304 }
305
306 /**
307 * Registers the "Table" button plugin for the TinyMCE editor.
308 *
309 * @since 1.0.0
310 *
311 * @param array $plugins Current set of registered TinyMCE plugins.
312 * @return array Extended set of registered TinyMCE plugins, including the "Table" button plugin.
313 */
314 public function add_tinymce_plugin( array $plugins ) {
315 $plugins['tablepress_tinymce'] = plugins_url( 'admin/js/build/tinymce-button.js', TABLEPRESS__FILE__ );
316 return $plugins;
317 }
318
319 /**
320 * Add "TablePress Table" entry to "New" dropdown menu in the WP Admin Bar.
321 *
322 * @since 1.0.0
323 *
324 * @param WP_Admin_Bar $wp_admin_bar The current WP Admin Bar object.
325 */
326 public function add_wp_admin_bar_new_content_menu_entry( $wp_admin_bar ) {
327 if ( ! current_user_can( 'tablepress_add_tables' ) ) {
328 return;
329 }
330
331 // Don't load TablePress assets on the Freemius opt-in/activation screen.
332 if ( tb_tp_fs()->is_activation_mode() && tb_tp_fs()->is_activation_page() ) {
333 return;
334 }
335
336 $wp_admin_bar->add_menu( array(
337 'parent' => 'new-content',
338 'id' => 'new-tablepress-table',
339 'title' => __( 'TablePress Table', 'tablepress' ),
340 'href' => TablePress::url( array( 'action' => 'add' ) ),
341 ) );
342 }
343
344 /**
345 * Handle actions for loading of Plugins page.
346 *
347 * @since 1.0.0
348 */
349 public function plugins_page() {
350 // Add additional links on Plugins page.
351 add_filter( 'plugin_action_links_' . TABLEPRESS_BASENAME, array( $this, 'add_plugin_action_links' ) );
352 add_filter( 'plugin_row_meta', array( $this, 'add_plugin_row_meta' ), 10, 2 );
353 }
354
355 /**
356 * Add links to the TablePress entry in the "Plugin" column on the Plugins page.
357 *
358 * @since 1.0.0
359 *
360 * @param array $links List of links to print in the "Plugin" column on the Plugins page.
361 * @return array Extended list of links to print in the "Plugin" column on the Plugins page.
362 */
363 public function add_plugin_action_links( array $links ) {
364 if ( current_user_can( 'tablepress_list_tables' ) ) {
365 $links[] = '<a href="' . TablePress::url() . '">' . __( 'Plugin page', 'tablepress' ) . '</a>';
366 }
367 return $links;
368 }
369
370 /**
371 * Add links to the TablePress entry in the "Description" column on the Plugins page.
372 *
373 * @since 1.0.0
374 *
375 * @param array $links List of links to print in the "Description" column on the Plugins page.
376 * @param string $file Name of the plugin.
377 * @return array Extended list of links to print in the "Description" column on the Plugins page.
378 */
379 public function add_plugin_row_meta( array $links, $file ) {
380 if ( TABLEPRESS_BASENAME === $file ) {
381 $links[] = '<a href="https://tablepress.org/faq/" title="' . esc_attr__( 'Frequently Asked Questions', 'tablepress' ) . '">' . __( 'FAQ', 'tablepress' ) . '</a>';
382 $links[] = '<a href="https://tablepress.org/documentation/">' . __( 'Documentation', 'tablepress' ) . '</a>';
383 $links[] = '<a href="https://tablepress.org/support/">' . __( 'Support', 'tablepress' ) . '</a>';
384 if ( tb_tp_fs()->is_free_plan() ) {
385 $links[] = '<a href="' . 'https://tablepress.org/premium/' . '" title="' . esc_attr__( 'Check out the Premium version of TablePress!', 'tablepress' ) . '"><strong>' . __( 'Go Premium', 'tablepress' ) . '</strong></a>';
386 }
387 }
388 return $links;
389 }
390
391 /**
392 * Prepare the rendering of an admin screen, by determining the current action, loading necessary data and initializing the view.
393 *
394 * @since 1.0.0
395 */
396 public function load_admin_page() {
397 // Determine the action from either the GET parameter (for sub-menu entries, and the main admin menu entry).
398 $action = ( ! empty( $_GET['action'] ) ) ? $_GET['action'] : 'list'; // Default action is list.
399 if ( $this->is_top_level_page ) {
400 // Or, for sub-menu entry of an admin menu "TablePress" entry, get it from the "page" GET parameter.
401 if ( 'tablepress' !== $_GET['page'] ) {
402 // Actions that are top-level entries, but don't have an action GET parameter (action is after last _ in string).
403 $action = substr( $_GET['page'], 11 ); // $_GET['page'] has the format 'tablepress_{$action}'
404 }
405 }
406
407 // Check if action is a supported action, and whether the user is allowed to access this screen.
408 if ( ! isset( $this->view_actions[ $action ] ) || ! current_user_can( $this->view_actions[ $action ]['required_cap'] ) ) {
409 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
410 }
411
412 // Don't load TablePress assets on the Freemius opt-in/activation screen.
413 if ( tb_tp_fs()->is_activation_mode() && tb_tp_fs()->is_activation_page() ) {
414 return;
415 }
416
417 // Changes current screen ID and pagenow variable in JS, to enable automatic meta box JS handling.
418 set_current_screen( "tablepress_{$action}" );
419
420 /*
421 * Set the `$typenow` global to the current CPT ourselves, as `WP_Screen::get()` does not determine the CPT correctly.
422 * This is necessary as the WP Admin Menu can otherwise highlight wrong entries, see https://github.com/TablePress/TablePress/issues/24.
423 */
424 if ( isset( $_GET['post_type'] ) && post_type_exists( $_GET['post_type'] ) ) {
425 $GLOBALS['typenow'] = $_GET['post_type'];
426 }
427
428 // Pre-define some view data.
429 $data = array(
430 'view_actions' => $this->view_actions,
431 'message' => ( ! empty( $_GET['message'] ) ) ? $_GET['message'] : false,
432 'error_details' => ( ! empty( $_GET['error_details'] ) ) ? $_GET['error_details'] : '',
433 'site_uses_block_editor' => TablePress::site_uses_block_editor(),
434 );
435
436 // Depending on the action, load more necessary data for the corresponding view.
437 switch ( $action ) {
438 case 'list':
439 $data['table_id'] = ( ! empty( $_GET['table_id'] ) ) ? $_GET['table_id'] : false;
440 // Prime the post meta cache for cached loading of last_editor.
441 $data['table_ids'] = TablePress::$model_table->load_all( true );
442 $data['messages']['first_visit'] = TablePress::$model_options->get( 'message_first_visit' );
443 $data['messages']['plugin_update_message'] = TablePress::$model_options->get( 'message_plugin_update' );
444 $data['messages']['donation_message'] = $this->maybe_show_donation_message();
445 $data['table_count'] = count( $data['table_ids'] );
446 break;
447 case 'about':
448 $data['first_activation'] = TablePress::$model_options->get( 'first_activation' );
449 $exporter = TablePress::load_class( 'TablePress_Export', 'class-export.php', 'classes' );
450 $data['zip_support_available'] = $exporter->zip_support_available;
451 break;
452 case 'options':
453 /*
454 * Maybe try saving "Custom CSS" to a file:
455 * (called here, as the credentials form posts to this handler again, due to how `request_filesystem_credentials()` works)
456 */
457 if ( isset( $_GET['item'] ) && 'save_custom_css' === $_GET['item'] ) {
458 TablePress::check_nonce( 'options', $_GET['item'] ); // Nonce check here, as we don't have an explicit handler, and even viewing the screen needs to be checked.
459 $action = 'options_custom_css'; // to load a different view
460 // Try saving "Custom CSS" to a file, otherwise this gets the HTML for the credentials form.
461 $tablepress_css = TablePress::load_class( 'TablePress_CSS', 'class-css.php', 'classes' );
462 $result = $tablepress_css->save_custom_css_to_file_plugin_options( TablePress::$model_options->get( 'custom_css' ), TablePress::$model_options->get( 'custom_css_minified' ) );
463 if ( is_string( $result ) ) {
464 $data['credentials_form'] = $result; // This will only be called if the save function doesn't do a redirect.
465 } elseif ( true === $result ) {
466 /*
467 * At this point, saving was successful, so enable usage of CSS in files again,
468 * and also increase the "Custom CSS" version number (for cache busting).
469 */
470 TablePress::$model_options->update( array(
471 'use_custom_css_file' => true,
472 'custom_css_version' => TablePress::$model_options->get( 'custom_css_version' ) + 1,
473 ) );
474 TablePress::redirect( array( 'action' => 'options', 'message' => 'success_save' ) );
475 } else { // Leaves only $result === false.
476 TablePress::redirect( array( 'action' => 'options', 'message' => 'success_save_error_custom_css' ) );
477 }
478 break;
479 }
480 $data['frontend_options']['use_custom_css'] = TablePress::$model_options->get( 'use_custom_css' );
481 $data['frontend_options']['custom_css'] = TablePress::$model_options->get( 'custom_css' );
482 $data['user_options']['parent_page'] = $this->parent_page;
483 break;
484 case 'edit':
485 if ( empty( $_GET['table_id'] ) ) {
486 TablePress::redirect( array( 'action' => 'list', 'message' => 'error_no_table' ) );
487 }
488 // Load table, with table data, options, and visibility settings.
489 $data['table'] = TablePress::$model_table->load( $_GET['table_id'], true, true );
490 if ( is_wp_error( $data['table'] ) ) {
491 TablePress::redirect( array( 'action' => 'list', 'message' => 'error_load_table', 'error_details' => TablePress::get_wp_error_string( $data['table'] ) ) );
492 }
493 if ( ! current_user_can( 'tablepress_edit_table', $_GET['table_id'] ) ) {
494 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
495 }
496 break;
497 case 'export':
498 // Load all table IDs without priming the post meta cache, as table options/visibility are not needed.
499 $data['table_ids'] = TablePress::$model_table->load_all( false );
500 $data['tables_count'] = TablePress::$model_table->count_tables();
501 if ( ! empty( $_GET['table_id'] ) ) {
502 $data['export_ids'] = explode( ',', $_GET['table_id'] );
503 } else {
504 // Just show empty export form.
505 $data['export_ids'] = array();
506 }
507 $exporter = TablePress::load_class( 'TablePress_Export', 'class-export.php', 'classes' );
508 $data['zip_support_available'] = $exporter->zip_support_available;
509 $data['export_formats'] = $exporter->export_formats;
510 $data['csv_delimiters'] = $exporter->csv_delimiters;
511 $data['export_format'] = ( ! empty( $_GET['export_format'] ) ) ? $_GET['export_format'] : false;
512 $data['csv_delimiter'] = ( ! empty( $_GET['csv_delimiter'] ) ) ? $_GET['csv_delimiter'] : _x( ',', 'Default CSV delimiter in the translated language (";", ",", or "tab")', 'tablepress' );
513 break;
514 case 'import':
515 // Load all table IDs without priming the post meta cache, as table options/visibility are not needed.
516 $data['table_ids'] = TablePress::$model_table->load_all( false );
517 $data['tables_count'] = TablePress::$model_table->count_tables();
518 $importer = TablePress::load_class( 'TablePress_Import', 'class-import.php', 'classes' );
519 $data['zip_support_available'] = $importer->zip_support_available;
520 $data['import_type'] = ( ! empty( $_GET['import_type'] ) ) ? $_GET['import_type'] : 'add';
521 $data['import_existing_table'] = ( ! empty( $_GET['import_existing_table'] ) ) ? $_GET['import_existing_table'] : '';
522 $data['import_source'] = ( ! empty( $_GET['import_source'] ) ) ? $_GET['import_source'] : 'file-upload';
523 $data['import_url'] = ( ! empty( $_GET['import_url'] ) ) ? wp_unslash( $_GET['import_url'] ) : 'https://';
524 $data['import_server'] = ( ! empty( $_GET['import_server'] ) ) ? wp_unslash( $_GET['import_server'] ) : ABSPATH;
525 $data['import_form-field'] = ( ! empty( $_GET['import_form-field'] ) ) ? wp_unslash( $_GET['import_form-field'] ) : '';
526 $data['legacy_import'] = ( ! empty( $_GET['legacy_import'] ) ) ? $_GET['legacy_import'] : 'false';
527 break;
528 }
529
530 /**
531 * Filters the data that is passed to the current TablePress View.
532 *
533 * @since 1.0.0
534 *
535 * @param array $data Data for the view.
536 * @param string $action The current action for the view.
537 */
538 $data = apply_filters( 'tablepress_view_data', $data, $action );
539
540 // Prepare and initialize the view.
541 $this->view = TablePress::load_view( $action, $data );
542 }
543
544 /**
545 * Render the view that has been initialized in load_admin_page() (called by WordPress when the actual page content is needed).
546 *
547 * @since 1.0.0
548 */
549 public function show_admin_page() {
550 $this->view->render();
551 }
552
553 /**
554 * 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.
555 *
556 * @since 1.0.0
557 *
558 * @return bool Whether the message shall be shown on the "All Tables" screen.
559 */
560 protected function maybe_show_donation_message() {
561 // Only show the message to plugin admins.
562 if ( ! current_user_can( 'tablepress_edit_options' ) ) {
563 return false;
564 }
565
566 if ( ! TablePress::$model_options->get( 'message_donation_nag' ) ) {
567 return false;
568 }
569
570 // Determine, how long has the plugin been installed.
571 $seconds_installed = time() - TablePress::$model_options->get( 'first_activation' );
572 return ( $seconds_installed > MONTH_IN_SECONDS / 2 );
573 }
574
575 /**
576 * Init list of actions that have a view with their titles/names/caps.
577 *
578 * @since 1.0.0
579 */
580 protected function init_view_actions() {
581 $this->view_actions = array(
582 'list' => array(
583 'show_entry' => true,
584 'page_title' => __( 'All Tables', 'tablepress' ),
585 'admin_menu_title' => __( 'All Tables', 'tablepress' ),
586 'nav_tab_title' => __( 'All Tables', 'tablepress' ),
587 'required_cap' => 'tablepress_list_tables',
588 ),
589 'add' => array(
590 'show_entry' => true,
591 'page_title' => __( 'Add New Table', 'tablepress' ),
592 'admin_menu_title' => __( 'Add New Table', 'tablepress' ),
593 'nav_tab_title' => __( 'Add New', 'tablepress' ),
594 'required_cap' => 'tablepress_add_tables',
595 ),
596 'edit' => array(
597 'show_entry' => false,
598 'page_title' => __( 'Edit Table', 'tablepress' ),
599 'admin_menu_title' => '',
600 'nav_tab_title' => '',
601 'required_cap' => 'tablepress_edit_tables',
602 ),
603 'import' => array(
604 'show_entry' => true,
605 'page_title' => __( 'Import a Table', 'tablepress' ),
606 'admin_menu_title' => __( 'Import a Table', 'tablepress' ),
607 'nav_tab_title' => _x( 'Import', 'navigation bar', 'tablepress' ),
608 'required_cap' => 'tablepress_import_tables',
609 ),
610 'export' => array(
611 'show_entry' => true,
612 'page_title' => __( 'Export a Table', 'tablepress' ),
613 'admin_menu_title' => __( 'Export a Table', 'tablepress' ),
614 'nav_tab_title' => _x( 'Export', 'navigation bar', 'tablepress' ),
615 'required_cap' => 'tablepress_export_tables',
616 ),
617 'options' => array(
618 'show_entry' => true,
619 'page_title' => __( 'Plugin Options', 'tablepress' ),
620 'admin_menu_title' => __( 'Plugin Options', 'tablepress' ),
621 'nav_tab_title' => __( 'Plugin Options', 'tablepress' ),
622 'required_cap' => 'tablepress_access_options_screen',
623 ),
624 'about' => array(
625 'show_entry' => true,
626 'page_title' => __( 'About', 'tablepress' ),
627 'admin_menu_title' => __( 'About TablePress', 'tablepress' ),
628 'nav_tab_title' => __( 'About', 'tablepress' ),
629 'required_cap' => 'tablepress_access_about_screen',
630 ),
631 );
632
633 /**
634 * Filters the available TablePres Views/Actions and their parameters.
635 *
636 * @since 1.0.0
637 *
638 * @param array $view_actions The available Views/Actions and their parameters.
639 */
640 $this->view_actions = apply_filters( 'tablepress_admin_view_actions', $this->view_actions );
641 }
642
643 /*
644 * HTTP POST actions.
645 */
646
647 /**
648 * Handle Bulk Actions (Copy, Export, Delete) on "All Tables" list screen.
649 *
650 * @since 1.0.0
651 */
652 public function handle_post_action_list() {
653 TablePress::check_nonce( 'list' );
654
655 if ( isset( $_POST['bulk-action-selector-top'] ) && '-1' !== $_POST['bulk-action-selector-top'] ) {
656 $bulk_action = $_POST['bulk-action-selector-top'];
657 } elseif ( isset( $_POST['bulk-action-selector-bottom'] ) && '-1' !== $_POST['bulk-action-selector-bottom'] ) {
658 $bulk_action = $_POST['bulk-action-selector-bottom'];
659 } else {
660 $bulk_action = false;
661 }
662
663 if ( ! in_array( $bulk_action, array( 'copy', 'export', 'delete' ), true ) ) {
664 TablePress::redirect( array( 'action' => 'list', 'message' => 'error_bulk_action_invalid' ) );
665 }
666
667 if ( empty( $_POST['table'] ) || ! is_array( $_POST['table'] ) ) {
668 TablePress::redirect( array( 'action' => 'list', 'message' => 'error_no_selection' ) );
669 }
670
671 $tables = wp_unslash( $_POST['table'] );
672
673 $no_success = array(); // To store table IDs that failed.
674
675 switch ( $bulk_action ) {
676 case 'copy':
677 foreach ( $tables as $table_id ) {
678 if ( current_user_can( 'tablepress_copy_table', $table_id ) ) {
679 $copy_table_id = TablePress::$model_table->copy( $table_id );
680 if ( is_wp_error( $copy_table_id ) ) {
681 $no_success[] = $table_id;
682 }
683 } else {
684 $no_success[] = $table_id;
685 }
686 }
687 break;
688 case 'export':
689 /*
690 * Cap check is done on redirect target page.
691 * To export, redirect to "Export" screen, with selected table IDs.
692 */
693 $table_ids = implode( ',', $tables );
694 TablePress::redirect( array( 'action' => 'export', 'table_id' => $table_ids ) );
695 break;
696 case 'delete':
697 foreach ( $tables as $table_id ) {
698 if ( current_user_can( 'tablepress_delete_table', $table_id ) ) {
699 $deleted = TablePress::$model_table->delete( $table_id );
700 if ( is_wp_error( $deleted ) ) {
701 $no_success[] = $table_id;
702 }
703 } else {
704 $no_success[] = $table_id;
705 }
706 }
707 break;
708 }
709
710 if ( 0 !== count( $no_success ) ) { // @TODO: maybe pass this information to the view?
711 $message = "error_{$bulk_action}_not_all_tables";
712 } else {
713 $plural = ( count( $tables ) > 1 ) ? '_plural' : '';
714 $message = "success_{$bulk_action}{$plural}";
715 }
716
717 /*
718 * Slightly more complex redirect method, to account for sort, search, and pagination in the WP_List_Table on the List View,
719 * but only if this action succeeds, to have everything fresh in the event of an error.
720 */
721 $sendback = wp_get_referer();
722 if ( ! $sendback ) {
723 $sendback = TablePress::url( array( 'action' => 'list', 'message' => $message ) );
724 } else {
725 $sendback = remove_query_arg( array( 'action', 'message', 'table_id' ), $sendback );
726 $sendback = add_query_arg( array( 'action' => 'list', 'message' => $message ), $sendback );
727 }
728 wp_redirect( $sendback );
729 exit;
730 }
731
732 /**
733 * Add a table, according to the parameters on the "Add new Table" screen.
734 *
735 * @since 1.0.0
736 */
737 public function handle_post_action_add() {
738 TablePress::check_nonce( 'add' );
739
740 if ( ! current_user_can( 'tablepress_add_tables' ) ) {
741 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
742 }
743
744 if ( empty( $_POST['table'] ) || ! is_array( $_POST['table'] ) ) {
745 TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add', 'error_details' => 'The HTTP POST data is empty.' ) );
746 }
747
748 $add_table = wp_unslash( $_POST['table'] );
749
750 // Perform sanity checks of posted data.
751 $name = ( isset( $add_table['name'] ) ) ? $add_table['name'] : '';
752 $description = ( isset( $add_table['description'] ) ) ? $add_table['description'] : '';
753 if ( ! isset( $add_table['rows'], $add_table['columns'] ) ) {
754 TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add', 'error_details' => 'The HTTP POST data does not contain the table size.' ) );
755 }
756
757 $num_rows = absint( $add_table['rows'] );
758 $num_columns = absint( $add_table['columns'] );
759 if ( 0 === $num_rows || 0 === $num_columns ) {
760 TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add', 'error_details' => 'The table size is invalid.' ) );
761 }
762
763 // Create a new table array with information from the posted data.
764 $new_table = array(
765 'name' => $name,
766 'description' => $description,
767 'data' => array_fill( 0, $num_rows, array_fill( 0, $num_columns, '' ) ),
768 'visibility' => array(
769 'rows' => array_fill( 0, $num_rows, 1 ),
770 'columns' => array_fill( 0, $num_columns, 1 ),
771 ),
772 );
773 // Merge this data into an empty table template.
774 $table = TablePress::$model_table->prepare_table( TablePress::$model_table->get_table_template(), $new_table, false );
775 if ( is_wp_error( $table ) ) {
776 TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add', 'error_details' => TablePress::get_wp_error_string( $table ) ) );
777 }
778
779 // Add the new table (and get its first ID).
780 $table_id = TablePress::$model_table->add( $table );
781 if ( is_wp_error( $table_id ) ) {
782 TablePress::redirect( array( 'action' => 'add', 'message' => 'error_add', 'error_details' => TablePress::get_wp_error_string( $table_id ) ) );
783 }
784
785 TablePress::redirect( array( 'action' => 'edit', 'table_id' => $table_id, 'message' => 'success_add' ) );
786 }
787
788 /**
789 * Save changed "Plugin Options".
790 *
791 * @since 1.0.0
792 */
793 public function handle_post_action_options() {
794 TablePress::check_nonce( 'options' );
795
796 if ( ! current_user_can( 'tablepress_access_options_screen' ) ) {
797 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
798 }
799
800 if ( empty( $_POST['options'] ) || ! is_array( $_POST['options'] ) ) {
801 TablePress::redirect( array( 'action' => 'options', 'message' => 'error_save' ) );
802 }
803
804 $posted_options = wp_unslash( $_POST['options'] );
805
806 // Valid new options that will be merged into existing ones.
807 $new_options = array();
808
809 // Check each posted option value, and (maybe) add it to the new options.
810 if ( ! empty( $posted_options['admin_menu_parent_page'] ) && '-' !== $posted_options['admin_menu_parent_page'] ) {
811 $new_options['admin_menu_parent_page'] = $posted_options['admin_menu_parent_page'];
812 // Re-init parent information, as `TablePress::redirect()` URL might be wrong otherwise.
813 /** This filter is documented in classes/class-controller.php */
814 $this->parent_page = apply_filters( 'tablepress_admin_menu_parent_page', $posted_options['admin_menu_parent_page'] );
815 $this->is_top_level_page = in_array( $this->parent_page, array( 'top', 'middle', 'bottom' ), true );
816 }
817
818 // Custom CSS can only be saved if the user is allowed to do so.
819 $update_custom_css_files = false;
820 if ( current_user_can( 'tablepress_edit_options' ) ) {
821 // Checkbox.
822 $new_options['use_custom_css'] = ( isset( $posted_options['use_custom_css'] ) && 'true' === $posted_options['use_custom_css'] );
823
824 if ( isset( $posted_options['custom_css'] ) ) {
825 $new_options['custom_css'] = $posted_options['custom_css'];
826
827 $tablepress_css = TablePress::load_class( 'TablePress_CSS', 'class-css.php', 'classes' );
828 // Sanitize and tidy up Custom CSS.
829 $new_options['custom_css'] = $tablepress_css->sanitize_css( $new_options['custom_css'] );
830 // Minify Custom CSS.
831 $new_options['custom_css_minified'] = $tablepress_css->minify_css( $new_options['custom_css'] );
832
833 // Maybe update CSS files as well.
834 $custom_css_file_contents = $tablepress_css->load_custom_css_from_file( 'normal' );
835 if ( false === $custom_css_file_contents ) {
836 $custom_css_file_contents = '';
837 }
838 // Don't write to file if it already has the desired content.
839 if ( $new_options['custom_css'] !== $custom_css_file_contents ) {
840 $update_custom_css_files = true;
841 // Set to false again. As it was set here, it will be set true again, if file saving succeeds.
842 $new_options['use_custom_css_file'] = false;
843 }
844 }
845 }
846
847 // Save gathered new options (will be merged into existing ones), and flush caches of caching plugins, to make sure that the new Custom CSS is used.
848 if ( ! empty( $new_options ) ) {
849 TablePress::$model_options->update( $new_options );
850 TablePress::$model_table->_flush_caching_plugins_caches();
851 }
852
853 if ( $update_custom_css_files ) { // Capability check is performed above.
854 TablePress::redirect( array( 'action' => 'options', 'item' => 'save_custom_css' ), true );
855 }
856
857 TablePress::redirect( array( 'action' => 'options', 'message' => 'success_save' ) );
858 }
859
860 /**
861 * Export selected tables.
862 *
863 * @since 1.0.0
864 */
865 public function handle_post_action_export() {
866 TablePress::check_nonce( 'export' );
867
868 if ( ! current_user_can( 'tablepress_export_tables' ) ) {
869 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
870 }
871
872 if ( empty( $_POST['export'] ) || ! is_array( $_POST['export'] ) ) {
873 TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export', 'error_details' => 'The HTTP POST data is empty.' ) );
874 }
875
876 $export = wp_unslash( $_POST['export'] );
877
878 if ( empty( $export['tables'] ) ) {
879 TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export', 'error_details' => 'The HTTP POST data does not contain tables.' ) );
880 }
881
882 $exporter = TablePress::load_class( 'TablePress_Export', 'class-export.php', 'classes' );
883
884 if ( empty( $export['format'] ) || ! isset( $exporter->export_formats[ $export['format'] ] ) ) {
885 TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export', 'error_details' => 'The export format is invalid.' ) );
886 }
887 if ( empty( $export['csv_delimiter'] ) ) {
888 // Set a value, so that the variable exists.
889 $export['csv_delimiter'] = '';
890 }
891 if ( 'csv' === $export['format'] && ! isset( $exporter->csv_delimiters[ $export['csv_delimiter'] ] ) ) {
892 TablePress::redirect( array( 'action' => 'export', 'message' => 'error_export', 'error_details' => 'The CSV delimiter is invalid.' ) );
893 }
894
895 // Use list of tables from concatenated field if available (as that's hopefully not truncated by Suhosin, which is possible for $export['tables']).
896 $tables = ( ! empty( $export['tables_list'] ) ) ? explode( ',', $export['tables_list'] ) : $export['tables'];
897
898 // Determine if ZIP file support is available.
899 if ( $exporter->zip_support_available
900 && ( ( isset( $export['zip_file'] ) && 'true' === $export['zip_file'] ) || count( $tables ) > 1 ) ) {
901 // Export to ZIP only if ZIP is desired or if more than one table were selected (mandatory then).
902 $export_to_zip = true;
903 } else {
904 $export_to_zip = false;
905 }
906
907 if ( ! $export_to_zip ) {
908 // This is only possible for one table, so take the first one.
909 if ( ! current_user_can( 'tablepress_export_table', $tables[0] ) ) {
910 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
911 }
912 // Load table, with table data, options, and visibility settings.
913 $table = TablePress::$model_table->load( $tables[0], true, true );
914 if ( is_wp_error( $table ) ) {
915 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 ) ) );
916 }
917 if ( isset( $table['is_corrupted'] ) && $table['is_corrupted'] ) {
918 TablePress::redirect( array( 'action' => 'export', 'message' => 'error_table_corrupted', 'export_format' => $export['format'], 'csv_delimiter' => $export['csv_delimiter'] ) );
919 }
920 $download_filename = sprintf( '%1$s-%2$s-%3$s.%4$s', $table['id'], $table['name'], wp_date( 'Y-m-d' ), $export['format'] );
921 /**
922 * Filters the download filename of the exported table.
923 *
924 * @since 2.0.0
925 *
926 * @param string $download_filename The download filename of exported table.
927 * @param string $table_id Table ID of the exported table.
928 * @param string $table_name Table name of the exported table.
929 * @param string $export_format Format for the export ('csv', 'html', 'json', 'zip').
930 * @param bool $export_to_zip Whether the export is to a ZIP file (of multiple export files).
931 */
932 $download_filename = apply_filters( 'tablepress_export_filename', $download_filename, $table['id'], $table['name'], $export['format'], $export_to_zip );
933 $download_filename = sanitize_file_name( $download_filename );
934 // Export the table.
935 $export_data = $exporter->export_table( $table, $export['format'], $export['csv_delimiter'] );
936 /**
937 * Filters the exported table data.
938 *
939 * @since 1.6.0
940 *
941 * @param string $export_data The exported table data.
942 * @param array $table Table to be exported.
943 * @param string $export_format Format for the export ('csv', 'html', 'json').
944 * @param string $csv_delimiter Delimiter for CSV export.
945 */
946 $export_data = apply_filters( 'tablepress_export_data', $export_data, $table, $export['format'], $export['csv_delimiter'] );
947 $download_data = $export_data;
948 } else {
949 // Zipping can use a lot of memory and execution time, but not this much hopefully.
950 wp_raise_memory_limit( 'admin' );
951 if ( function_exists( 'set_time_limit' ) ) {
952 @set_time_limit( 300 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
953 }
954
955 $zip_file = new ZipArchive();
956 $download_filename = sprintf( 'tablepress-export-%1$s-%2$s.zip', wp_date( 'Y-m-d-H-i-s' ), $export['format'] );
957 /** This filter is documented in controllers/controller-admin.php */
958 $download_filename = apply_filters( 'tablepress_export_filename', $download_filename, '', '', $export['format'], $export_to_zip );
959 $download_filename = sanitize_file_name( $download_filename );
960 $full_filename = wp_tempnam( $download_filename );
961 if ( true !== $zip_file->open( $full_filename, ZIPARCHIVE::OVERWRITE ) ) {
962 @unlink( $full_filename );
963 TablePress::redirect( array( 'action' => 'export', 'message' => 'error_create_zip_file', 'export_format' => $export['format'], 'csv_delimiter' => $export['csv_delimiter'], 'error_details' => 'The ZIP file could not be opened for writing.' ) );
964 }
965
966 foreach ( $tables as $table_id ) {
967 // Don't export tables for which the user doesn't have the necessary export rights.
968 if ( ! current_user_can( 'tablepress_export_table', $table_id ) ) {
969 continue;
970 }
971 // Load table, with table data, options, and visibility settings.
972 $table = TablePress::$model_table->load( $table_id, true, true );
973 // Don't export if the table could not be loaded.
974 if ( is_wp_error( $table ) ) {
975 continue;
976 }
977 // Don't export if the table is corrupted.
978 if ( isset( $table['is_corrupted'] ) && $table['is_corrupted'] ) {
979 continue;
980 }
981 $export_data = $exporter->export_table( $table, $export['format'], $export['csv_delimiter'] );
982 /** This filter is documented in controllers/controller-admin.php */
983 $export_data = apply_filters( 'tablepress_export_data', $export_data, $table, $export['format'], $export['csv_delimiter'] );
984 $export_filename = sprintf( '%1$s-%2$s-%3$s.%4$s', $table['id'], $table['name'], wp_date( 'Y-m-d' ), $export['format'] );
985 /** This filter is documented in controllers/controller-admin.php */
986 $export_filename = apply_filters( 'tablepress_export_filename', $export_filename, $table['id'], $table['name'], $export['format'], $export_to_zip );
987 $export_filename = sanitize_file_name( $export_filename );
988 $zip_file->addFromString( $export_filename, $export_data );
989 }
990
991 // If something went wrong, or no files were added to the ZIP file, bail out.
992 // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
993 if ( ! ZIPARCHIVE::ER_OK === $zip_file->status || 0 === $zip_file->numFiles ) {
994 $zip_file->close();
995 @unlink( $full_filename );
996 TablePress::redirect( array( 'action' => 'export', 'message' => 'error_create_zip_file', 'export_format' => $export['format'], 'csv_delimiter' => $export['csv_delimiter'], 'error_details' => 'The ZIP file could not be written or is empty.' ) );
997 }
998 $zip_file->close();
999
1000 // Load contents of the ZIP file, to send it as a download.
1001 $download_data = file_get_contents( $full_filename );
1002 @unlink( $full_filename );
1003 }
1004
1005 // Send download headers for export file.
1006 header( 'Content-Description: File Transfer' );
1007 header( 'Content-Type: application/octet-stream' );
1008 header( "Content-Disposition: attachment; filename=\"{$download_filename}\"" );
1009 header( 'Content-Transfer-Encoding: binary' );
1010 header( 'Expires: 0' );
1011 header( 'Cache-Control: must-revalidate' );
1012 header( 'Pragma: public' );
1013 header( 'Content-Length: ' . strlen( $download_data ) );
1014 // $filetype = text/csv, text/html, application/json
1015 // header( 'Content-Type: ' . $filetype. '; charset=' . get_option( 'blog_charset' ) );
1016 @ob_end_clean(); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
1017 flush();
1018 echo $download_data;
1019 exit;
1020 }
1021
1022 /**
1023 * Import data from existing source (Upload, URL, Server, Direct input).
1024 *
1025 * @since 1.0.0
1026 */
1027 public function handle_post_action_import() {
1028 TablePress::check_nonce( 'import' );
1029
1030 if ( ! current_user_can( 'tablepress_import_tables' ) ) {
1031 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
1032 }
1033
1034 if ( empty( $_POST['import'] ) || ! is_array( $_POST['import'] ) ) {
1035 TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import', 'error_details' => 'The HTTP POST data is empty.' ) );
1036 }
1037
1038 $import_config = wp_unslash( $_POST['import'] );
1039
1040 if ( empty( $import_config['source'] ) ) {
1041 TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import', 'error_details' => 'The HTTP POST does not contain an import configuration.' ) );
1042 }
1043
1044 // For security reasons, the "server" source is only available for super admins on multisite and admins on single sites.
1045 if ( 'server' === $import_config['source'] ) {
1046 if ( ! is_super_admin() && ! ( ! is_multisite() && current_user_can( 'manage_options' ) ) ) {
1047 TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import', 'error_details' => 'You do not have the required access rights.' ) );
1048 }
1049 }
1050
1051 // Move file upload data to the main import configuration.
1052 $import_config['file-upload'] = isset( $_FILES['import_file_upload'] ) ? $_FILES['import_file_upload'] : null;
1053
1054 // Check if the source data for the chosen import source is defined.
1055 if ( empty( $import_config[ $import_config['source'] ] ) ) {
1056 TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import', 'error_details' => 'The HTTP POST data does not contain an import source.' ) );
1057 }
1058
1059 // Set default values for non-essential configuration variables.
1060 if ( ! isset( $import_config['type'] ) ) {
1061 $import_config['type'] = 'add';
1062 }
1063 if ( ! isset( $import_config['existing_table'] ) ) {
1064 $import_config['existing_table'] = '';
1065 }
1066
1067 $import_config['legacy_import'] = ( isset( $import_config['legacy_import'] ) && 'true' === $import_config['legacy_import'] );
1068
1069 $importer = TablePress::load_class( 'TablePress_Import', 'class-import.php', 'classes' );
1070 $import = $importer->run( $import_config );
1071
1072 if ( is_wp_error( $import ) || 0 < count( $import['errors'] ) ) {
1073 $redirect_parameters = array(
1074 'action' => 'import',
1075 'message' => 'error_import',
1076 'import_type' => $import_config['type'],
1077 'import_existing_table' => $import_config['existing_table'],
1078 'import_source' => $import_config['source'],
1079 'legacy_import' => $import_config['legacy_import'],
1080 );
1081 if ( in_array( $import_config['source'], array( 'url', 'server' ), true ) ) {
1082 $redirect_parameters[ "import_{$import_config['source']}" ] = $import_config[ $import_config['source'] ];
1083 }
1084 if ( is_wp_error( $import ) ) {
1085 $redirect_parameters['error_details'] = TablePress::get_wp_error_string( $import );
1086 } elseif ( 0 < count( $import['errors'] ) ) {
1087 $wp_error_strings = array();
1088 foreach ( $import['errors'] as $file ) {
1089 $wp_error_strings[] = TablePress::get_wp_error_string( $file['error'] );
1090 }
1091 $redirect_parameters['error_details'] = implode( ', ', $wp_error_strings );
1092 }
1093 TablePress::redirect( $redirect_parameters );
1094 }
1095
1096 // At this point, there were no import errors.
1097 if ( count( $import['tables'] ) > 1 ) {
1098 TablePress::redirect( array( 'action' => 'list', 'message' => 'success_import' ) );
1099 } elseif ( 1 === count( $import['tables'] ) ) {
1100 TablePress::redirect( array( 'action' => 'edit', 'table_id' => $import['tables'][0]['id'], 'message' => 'success_import' ) );
1101 } else {
1102 TablePress::redirect( array( 'action' => 'import', 'message' => 'error_import', 'error_details' => 'The number of imported tables is invalid.' ) );
1103 }
1104 }
1105
1106 /*
1107 * HTTP GET actions.
1108 */
1109
1110 /**
1111 * Hide a header message on an admin screen.
1112 *
1113 * @since 1.0.0
1114 */
1115 public function handle_get_action_hide_message() {
1116 $message_item = ! empty( $_GET['item'] ) ? $_GET['item'] : '';
1117 TablePress::check_nonce( 'hide_message', $message_item );
1118
1119 if ( ! current_user_can( 'tablepress_list_tables' ) ) {
1120 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
1121 }
1122
1123 TablePress::$model_options->update( "message_{$message_item}", false );
1124
1125 $return = ! empty( $_GET['return'] ) ? $_GET['return'] : 'list';
1126 TablePress::redirect( array( 'action' => $return ) );
1127 }
1128
1129 /**
1130 * Delete a table.
1131 *
1132 * @since 1.0.0
1133 */
1134 public function handle_get_action_delete_table() {
1135 $table_id = ( ! empty( $_GET['item'] ) ) ? $_GET['item'] : false;
1136 TablePress::check_nonce( 'delete_table', $table_id );
1137
1138 $return = ! empty( $_GET['return'] ) ? $_GET['return'] : 'list';
1139 $return_item = ! empty( $_GET['return_item'] ) ? $_GET['return_item'] : false;
1140
1141 // The nonce check should actually catch this already.
1142 if ( false === $table_id ) {
1143 TablePress::redirect( array( 'action' => $return, 'message' => 'error_delete', 'table_id' => $return_item ) );
1144 }
1145
1146 if ( ! current_user_can( 'tablepress_delete_table', $table_id ) ) {
1147 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
1148 }
1149
1150 $deleted = TablePress::$model_table->delete( $table_id );
1151 if ( is_wp_error( $deleted ) ) {
1152 TablePress::redirect( array( 'action' => $return, 'message' => 'error_delete', 'table_id' => $return_item, 'error_details' => TablePress::get_wp_error_string( $deleted ) ) );
1153 }
1154
1155 /*
1156 * Slightly more complex redirect method, to account for sort, search, and pagination in the WP_List_Table on the List View,
1157 * but only if this action succeeds, to have everything fresh in the event of an error.
1158 */
1159 $sendback = wp_get_referer();
1160 if ( ! $sendback ) {
1161 $sendback = TablePress::url( array( 'action' => 'list', 'message' => 'success_delete', 'table_id' => $return_item ) );
1162 } else {
1163 $sendback = remove_query_arg( array( 'action', 'message', 'table_id' ), $sendback );
1164 $sendback = add_query_arg( array( 'action' => 'list', 'message' => 'success_delete', 'table_id' => $return_item ), $sendback );
1165 }
1166 wp_redirect( $sendback );
1167 exit;
1168 }
1169
1170 /**
1171 * Copy a table.
1172 *
1173 * @since 1.0.0
1174 */
1175 public function handle_get_action_copy_table() {
1176 $table_id = ( ! empty( $_GET['item'] ) ) ? $_GET['item'] : false;
1177 TablePress::check_nonce( 'copy_table', $table_id );
1178
1179 $return = ! empty( $_GET['return'] ) ? $_GET['return'] : 'list';
1180 $return_item = ! empty( $_GET['return_item'] ) ? $_GET['return_item'] : false;
1181
1182 // The nonce check should actually catch this already.
1183 if ( false === $table_id ) {
1184 TablePress::redirect( array( 'action' => $return, 'message' => 'error_copy', 'table_id' => $return_item ) );
1185 }
1186
1187 if ( ! current_user_can( 'tablepress_copy_table', $table_id ) ) {
1188 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
1189 }
1190
1191 $copy_table_id = TablePress::$model_table->copy( $table_id );
1192 if ( is_wp_error( $copy_table_id ) ) {
1193 TablePress::redirect( array( 'action' => $return, 'message' => 'error_copy', 'table_id' => $return_item, 'error_details' => TablePress::get_wp_error_string( $copy_table_id ) ) );
1194 }
1195 $return_item = $copy_table_id;
1196
1197 /*
1198 * Slightly more complex redirect method, to account for sort, search, and pagination in the WP_List_Table on the List View,
1199 * but only if this action succeeds, to have everything fresh in the event of an error.
1200 */
1201 $sendback = wp_get_referer();
1202 if ( ! $sendback ) {
1203 $sendback = TablePress::url( array( 'action' => $return, 'message' => 'success_copy', 'table_id' => $return_item ) );
1204 } else {
1205 $sendback = remove_query_arg( array( 'action', 'message', 'table_id' ), $sendback );
1206 $sendback = add_query_arg( array( 'action' => $return, 'message' => 'success_copy', 'table_id' => $return_item ), $sendback );
1207 }
1208 wp_redirect( $sendback );
1209 exit;
1210 }
1211
1212 /**
1213 * Preview a table.
1214 *
1215 * @since 1.0.0
1216 */
1217 public function handle_get_action_preview_table() {
1218 $table_id = ( ! empty( $_GET['item'] ) ) ? $_GET['item'] : false;
1219 TablePress::check_nonce( 'preview_table', $table_id );
1220
1221 // Nonce check should actually catch this already.
1222 if ( false === $table_id ) {
1223 wp_die( __( 'The preview could not be loaded.', 'tablepress' ), __( 'Preview', 'tablepress' ) );
1224 }
1225
1226 if ( ! current_user_can( 'tablepress_preview_table', $table_id ) ) {
1227 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
1228 }
1229
1230 // Load table, with table data, options, and visibility settings.
1231 $table = TablePress::$model_table->load( $table_id, true, true );
1232 if ( is_wp_error( $table ) ) {
1233 wp_die( __( 'The table could not be loaded.', 'tablepress' ), __( 'Preview', 'tablepress' ) );
1234 }
1235
1236 // Sanitize all table data to remove unsafe HTML from the preview output, if the user is not allowed to work with unfiltered HTML.
1237 if ( ! current_user_can( 'unfiltered_html' ) ) {
1238 $table = TablePress::$model_table->sanitize( $table );
1239 }
1240
1241 // Create a render class instance.
1242 $_render = TablePress::load_class( 'TablePress_Render', 'class-render.php', 'classes' );
1243 // Merge desired options with default render options (see TablePress_Controller_Frontend::shortcode_table()).
1244 $default_render_options = $_render->get_default_render_options();
1245 /** This filter is documented in controllers/controller-frontend.php */
1246 $default_render_options = apply_filters( 'tablepress_shortcode_table_default_shortcode_atts', $default_render_options );
1247 $render_options = shortcode_atts( $default_render_options, $table['options'] );
1248 /** This filter is documented in controllers/controller-frontend.php */
1249 $render_options = apply_filters( 'tablepress_shortcode_table_shortcode_atts', $render_options );
1250 $render_options['html_id'] = "tablepress-{$table['id']}";
1251 $_render->set_input( $table, $render_options );
1252 $view_data = array(
1253 'table_id' => $table_id,
1254 'head_html' => $_render->get_preview_css(),
1255 'body_html' => $_render->get_output(),
1256 'site_uses_block_editor' => TablePress::site_uses_block_editor(),
1257 );
1258
1259 $custom_css = TablePress::$model_options->get( 'custom_css' );
1260 $use_custom_css = ( TablePress::$model_options->get( 'use_custom_css' ) && '' !== $custom_css );
1261 if ( $use_custom_css ) {
1262 $view_data['head_html'] .= "<style>\n{$custom_css}\n</style>\n";
1263 }
1264
1265 // Prepare, initialize, and render the view.
1266 $this->view = TablePress::load_view( 'preview_table', $view_data );
1267 $this->view->render();
1268 }
1269
1270 /**
1271 * Shows a list of tables in the Editor toolbar Thickbox (opened by TinyMCE or Quicktags button).
1272 *
1273 * @since 1.0.0
1274 */
1275 public function handle_get_action_editor_button_thickbox() {
1276 TablePress::check_nonce( 'editor_button_thickbox' );
1277
1278 if ( ! current_user_can( 'tablepress_list_tables' ) ) {
1279 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
1280 }
1281
1282 $view_data = array(
1283 // Load all table IDs without priming the post meta cache, as table options/visibility are not needed.
1284 'table_ids' => TablePress::$model_table->load_all( false ),
1285 );
1286
1287 set_current_screen( 'tablepress_editor_button_thickbox' );
1288
1289 // Prepare, initialize, and render the view.
1290 $this->view = TablePress::load_view( 'editor_button_thickbox', $view_data );
1291 $this->view->render();
1292 }
1293
1294 /**
1295 * Uninstall TablePress, and delete all tables and options.
1296 *
1297 * @since 1.0.0
1298 */
1299 public function handle_get_action_uninstall_tablepress() {
1300 TablePress::check_nonce( 'uninstall_tablepress' );
1301
1302 $plugin = TABLEPRESS_BASENAME;
1303
1304 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 ) ) {
1305 wp_die( __( 'Sorry, you are not allowed to access this page.', 'default' ), 403 );
1306 }
1307
1308 // Deactivate TablePress for the site (but not for the network).
1309 deactivate_plugins( $plugin, false, false );
1310 update_option( 'recently_activated', array( $plugin => time() ) + (array) get_option( 'recently_activated', array() ) );
1311
1312 // Delete all tables, "Custom CSS" files, and options.
1313 TablePress::$model_table->delete_all();
1314 $tablepress_css = TablePress::load_class( 'TablePress_CSS', 'class-css.php', 'classes' );
1315 $css_files_deleted = $tablepress_css->delete_custom_css_files();
1316 TablePress::$model_options->remove_access_capabilities();
1317
1318 TablePress::$model_table->destroy();
1319 TablePress::$model_options->destroy();
1320
1321 $output = '<strong>' . __( 'TablePress was uninstalled successfully.', 'tablepress' ) . '</strong><br /><br />';
1322 $output .= __( 'All tables, data, and options were deleted.', 'tablepress' );
1323 if ( is_multisite() ) {
1324 $output .= ' ' . __( 'You may now ask the network admin to delete the plugin&#8217;s folder <code>tablepress</code> from the server, if no other site in the network uses it.', 'tablepress' );
1325 } else {
1326 $output .= ' ' . __( 'You may now manually delete the plugin&#8217;s folder <code>tablepress</code> from the <code>plugins</code> directory on your server or use the &#8220;Delete&#8221; link for TablePress on the WordPress &#8220;Plugins&#8221; page.', 'tablepress' );
1327 }
1328 if ( $css_files_deleted ) {
1329 $output .= ' ' . __( 'Your TablePress &#8220;Custom CSS&#8221; files have been deleted automatically.', 'tablepress' );
1330 } else {
1331 if ( is_multisite() ) {
1332 $output .= ' ' . __( 'Please also ask him to delete your TablePress &#8220;Custom CSS&#8221; files from the server.', 'tablepress' );
1333 } else {
1334 $output .= ' ' . __( 'You may now also delete your TablePress &#8220;Custom CSS&#8221; files in the <code>wp-content</code> folder.', 'tablepress' );
1335 }
1336 }
1337 $output .= "</p>\n<p>";
1338 if ( ! is_multisite() || is_super_admin() ) {
1339 $output .= '<a class="button" href="' . esc_url( admin_url( 'plugins.php' ) ) . '">' . __( 'Go to &#8220;Plugins&#8221; page', 'tablepress' ) . '</a> ';
1340 }
1341 $output .= '<a class="button" href="' . esc_url( admin_url( 'index.php' ) ) . '">' . __( 'Go to Dashboard', 'tablepress' ) . '</a>';
1342
1343 wp_die( $output, __( 'Uninstall TablePress', 'tablepress' ), array( 'response' => 200, 'back_link' => false ) );
1344 }
1345
1346 } // class TablePress_Admin_Controller
1347