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

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

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