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

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

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