PluginProbe
TablePress – Tables in WordPress made easy / 3.4
TablePress – Tables in WordPress made easy v3.4
3.4 3.3.4 3.3.3 3.3.2 3.3.1 trunk 1.12 1.14 1.9.2 2.0.4 2.1.7 2.1.8 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3 2.3.1 2.3.2 2.4 2.4.1 2.4.2 2.4.3 All 45 releases
← All changes | controllers/controller-admin_ajax.php +140 -94 1.9.2 → 3.4 View file →
@@ -7,13 +7,16 @@
7 7 * @author Tobias Bäthge
8 8 * @since 1.0.0
9 9 */
10 10
11 +declare(strict_types=1);
12 +
11 13 // Prohibit direct script loading.
12 14 defined( 'ABSPATH' ) || die( 'No direct script access allowed!' );
13 15
14 16 /**
15 17 * Admin AJAX Controller class, extends Base Controller Class
18 + *
16 19 * @package TablePress
17 20 * @subpackage Controllers
18 21 * @author Tobias Bäthge
19 22 * @since 1.0.0
@@ -20,9 +23,9 @@
20 23 */
21 24 class TablePress_Admin_AJAX_Controller extends TablePress_Controller {
22 25
23 26 /**
24 - * Initiate Admin AJAX functionality.
27 + * Initiates the Admin AJAX functionality.
25 28 *
26 29 * @since 1.0.0
27 30 */
28 31 public function __construct() {
@@ -30,9 +33,9 @@
30 33 ob_start();
31 34
32 35 parent::__construct();
33 36
34 - $ajax_actions = array( 'hide_message', 'save_table', 'preview_table' );
37 + $ajax_actions = array( 'hide_message', 'save_table', 'preview_table', 'save_screen_options' );
35 38 foreach ( $ajax_actions as $action ) {
36 39 add_action( "wp_ajax_tablepress_{$action}", array( $this, "ajax_action_{$action}" ) );
37 40 }
38 41 }
@@ -37,19 +40,19 @@
37 40 }
38 41 }
39 42
40 43 /**
41 - * Hide a header message on an admin screen.
44 + * Hides a header message on an admin screen.
42 45 *
43 46 * @since 1.0.0
44 47 */
45 - public function ajax_action_hide_message() {
48 + public function ajax_action_hide_message(): void {
46 49 if ( empty( $_GET['item'] ) ) {
47 50 wp_die( '0' );
48 - } else {
49 - $message_item = $_GET['item'];
50 51 }
51 52
53 + $message_item = $_GET['item'];
54 +
52 55 TablePress::check_nonce( 'hide_message', $message_item, '_wpnonce', true );
53 56
54 57 if ( ! current_user_can( 'tablepress_list_tables' ) ) {
55 58 wp_die( '-1' );
@@ -60,19 +63,19 @@
60 63 wp_die( '1' );
61 64 }
62 65
63 66 /**
64 - * Save the table after the "Save Changes" button on the "Edit" screen has been clicked.
67 + * Saves the table after the "Save Changes" button on the "Edit" screen has been clicked.
65 68 *
66 69 * @since 1.0.0
67 70 */
68 - public function ajax_action_save_table() {
69 - if ( empty( $_POST['tablepress'] ) || empty( $_POST['tablepress']['id'] ) ) {
71 + public function ajax_action_save_table(): void {
72 + if ( empty( $_POST['tablepress']['id'] ) ) {
70 73 wp_die( '-1' );
71 - } else {
72 - $edit_table = wp_unslash( $_POST['tablepress'] );
73 74 }
74 75
76 + $edit_table = wp_unslash( $_POST['tablepress'] );
77 +
75 78 // Check if the submitted nonce matches the generated nonce we created earlier, dies -1 on failure.
76 79 TablePress::check_nonce( 'edit', $edit_table['id'], '_ajax_nonce', true );
77 80
78 81 // Ignore the request if the current user doesn't have sufficient permissions.
@@ -83,41 +86,45 @@
83 86 // Default response data.
84 87 $success = false;
85 88 $message = 'error_save';
86 89 $error_details = '';
87 - do { // to be able to "break;" (allows for better readable code)
90 + do { // To be able to "break;" (allows for better readable code).
88 91 // Load table, without table data, but with options and visibility settings.
89 92 $existing_table = TablePress::$model_table->load( $edit_table['id'], false, true );
90 - if ( is_wp_error( $existing_table ) ) { // maybe somehow load a new table here? (TablePress::$model_table->get_table_template())?
91 - // Add an error code to the existing WP_Error.
92 - $existing_table->add( 'ajax_save_table_load', '', $edit_table['id'] );
93 - $error_details = $this->get_wp_error_string( $existing_table );
93 + if ( is_wp_error( $existing_table ) ) {
94 + $error = new WP_Error( 'ajax_save_table_load', '', $edit_table['id'] );
95 + $error->merge_from( $existing_table );
96 + $error_details = TablePress::get_wp_error_string( $error );
94 97 break;
95 98 }
96 99
97 - // Check and convert data that was transmitted as JSON.
98 - if ( empty( $edit_table['data'] )
99 - || empty( $edit_table['options'] )
100 - || empty( $edit_table['visibility'] ) ) {
101 - // Create a new WP_Error.
102 - $empty_data_error = new WP_Error( 'ajax_save_table_data_empty', '', $edit_table['id'] );
103 - $error_details = $this->get_wp_error_string( $empty_data_error );
104 - break;
100 + // Check and convert all data that was transmitted as valid JSON.
101 + $keys = array( 'data', 'options', 'visibility' );
102 + foreach ( $keys as $key ) {
103 + if ( empty( $edit_table[ $key ] ) ) {
104 + $error = new WP_Error( "ajax_save_table_{$key}_empty", '', $edit_table['id'] );
105 + $error_details = TablePress::get_wp_error_string( $error );
106 + break 2;
107 + }
108 + $edit_table[ $key ] = json_decode( $edit_table[ $key ], true );
109 + if ( is_null( $edit_table[ $key ] ) ) {
110 + $error = new WP_Error( "ajax_save_table_{$key}_invalid_json", '', $edit_table['id'] );
111 + $error_details = TablePress::get_wp_error_string( $error );
112 + break 2;
113 + }
114 + $edit_table[ $key ] = (array) $edit_table[ $key ]; // Cast to array again, to catch strings, etc.
105 115 }
106 - $edit_table['data'] = (array) json_decode( $edit_table['data'], true );
107 - $edit_table['options'] = (array) json_decode( $edit_table['options'], true );
108 - $edit_table['visibility'] = (array) json_decode( $edit_table['visibility'], true );
109 116
110 117 // Check consistency of new table, and then merge with existing table.
111 - $table = TablePress::$model_table->prepare_table( $existing_table, $edit_table, true, true );
118 + $table = TablePress::$model_table->prepare_table( $existing_table, $edit_table, true );
112 119 if ( is_wp_error( $table ) ) {
113 - // Add an error code to the existing WP_Error.
114 - $table->add( 'ajax_save_table_prepare', '', $edit_table['id'] );
115 - $error_details = $this->get_wp_error_string( $table );
120 + $error = new WP_Error( 'ajax_save_table_prepare', '', $edit_table['id'] );
121 + $error->merge_from( $table );
122 + $error_details = TablePress::get_wp_error_string( $error );
116 123 break;
117 124 }
118 125
119 - // DataTables Custom Commands can only be edit by trusted users.
126 + // DataTables Custom Commands can only be edited by trusted users.
120 127 if ( ! current_user_can( 'unfiltered_html' ) ) {
121 128 $table['options']['datatables_custom_commands'] = $existing_table['options']['datatables_custom_commands'];
122 129 }
123 130
@@ -123,11 +130,11 @@
123 130
124 131 // Save updated table.
125 132 $saved = TablePress::$model_table->save( $table );
126 133 if ( is_wp_error( $saved ) ) {
127 - // Add an error code to the existing WP_Error.
128 - $saved->add( 'ajax_save_table_save', '', $table['id'] );
129 - $error_details = $this->get_wp_error_string( $saved );
134 + $error = new WP_Error( 'ajax_save_table_save', '', $table['id'] );
135 + $error->merge_from( $saved );
136 + $error_details = TablePress::get_wp_error_string( $error );
130 137 break;
131 138 }
132 139
133 140 // At this point, the table was saved successfully, possible ID change remains.
@@ -147,11 +154,11 @@
147 154 $message = 'success_save_success_id_change';
148 155 $table['id'] = $table['new_id'];
149 156 } else {
150 157 $message = 'success_save_error_id_change';
151 - // Add an error code to the existing WP_Error.
152 - $id_changed->add( 'ajax_save_table_id_change', '', $table['new_id'] );
153 - $error_details = $this->get_wp_error_string( $id_changed );
158 + $error = new WP_Error( 'ajax_save_table_id_change', '', $table['new_id'] );
159 + $error->merge_from( $id_changed );
160 + $error_details = TablePress::get_wp_error_string( $error );
154 161 }
155 162 } else {
156 163 $message = 'success_save_error_id_change';
157 164 $error_details = 'table_id_could_not_be_changed: capability_check_failed';
@@ -165,13 +172,16 @@
165 172 'success' => $success,
166 173 'message' => $message,
167 174 );
168 175 if ( $success ) {
169 - $response['table_id'] = $table['id'];
170 - $response['new_edit_nonce'] = wp_create_nonce( TablePress::nonce( 'edit', $table['id'] ) );
171 - $response['new_preview_nonce'] = wp_create_nonce( TablePress::nonce( 'preview_table', $table['id'] ) );
172 - $response['last_modified'] = TablePress::format_datetime( $table['last_modified'] );
173 - $response['last_editor'] = TablePress::get_user_display_name( $table['options']['last_editor'] );
176 + // For the phpstan ignores in the next lines: If this is reached, $table is guaranteed to exist and is a valid array.
177 + $response['table_id'] = $table['id']; // @phpstan-ignore offsetAccess.nonOffsetAccessible, variable.undefined
178 + $response['new_edit_nonce'] = wp_create_nonce( TablePress::nonce( 'edit', $table['id'] ) ); // @phpstan-ignore offsetAccess.nonOffsetAccessible, variable.undefined
179 + $response['new_preview_nonce'] = wp_create_nonce( TablePress::nonce( 'preview_table', $table['id'] ) ); // @phpstan-ignore offsetAccess.nonOffsetAccessible, variable.undefined
180 + $response['new_copy_nonce'] = wp_create_nonce( TablePress::nonce( 'copy_table', $table['id'] ) ); // @phpstan-ignore offsetAccess.nonOffsetAccessible, variable.undefined
181 + $response['new_delete_nonce'] = wp_create_nonce( TablePress::nonce( 'delete_table', $table['id'] ) ); // @phpstan-ignore offsetAccess.nonOffsetAccessible, variable.undefined
182 + $response['last_modified'] = TablePress::format_datetime( $table['last_modified'] ); // @phpstan-ignore offsetAccess.nonOffsetAccessible, variable.undefined
183 + $response['last_editor'] = TablePress::get_user_display_name( $table['options']['last_editor'] ); // @phpstan-ignore offsetAccess.nonOffsetAccessible, variable.undefined
174 184 }
175 185 if ( ! empty( $error_details ) ) {
176 186 $response['error_details'] = esc_html( $error_details );
177 187 }
@@ -185,19 +195,19 @@
185 195 wp_send_json( $response );
186 196 }
187 197
188 198 /**
189 - * Return the live preview data of table that has non-saved changes.
199 + * Returns the live preview data of table that has non-saved changes.
190 200 *
191 201 * @since 1.0.0
192 202 */
193 - public function ajax_action_preview_table() {
194 - if ( empty( $_POST['tablepress'] ) || empty( $_POST['tablepress']['id'] ) ) {
203 + public function ajax_action_preview_table(): void {
204 + if ( empty( $_POST['tablepress']['id'] ) ) {
195 205 wp_die( '-1' );
196 - } else {
197 - $preview_table = wp_unslash( $_POST['tablepress'] );
198 206 }
199 207
208 + $preview_table = wp_unslash( $_POST['tablepress'] );
209 +
200 210 // Check if the submitted nonce matches the generated nonce we created earlier, dies -1 on failure.
201 211 TablePress::check_nonce( 'preview_table', $preview_table['id'], '_ajax_nonce', true );
202 212
203 213 // Ignore the request if the current user doesn't have sufficient permissions.
@@ -206,32 +216,35 @@
206 216 }
207 217
208 218 // Default response data.
209 219 $success = false;
210 - do { // to be able to "break;" (allows for better readable code)
220 + do { // To be able to "break;" (allows for better readable code).
211 221 // Load table, without table data, but with options and visibility settings.
212 222 $existing_table = TablePress::$model_table->load( $preview_table['id'], false, true );
213 - if ( is_wp_error( $existing_table ) ) { // maybe somehow load a new table here? (TablePress::$model_table->get_table_template())?
223 + if ( is_wp_error( $existing_table ) ) {
214 224 break;
215 225 }
216 226
217 - // Check and convert data that was transmitted as JSON.
218 - if ( empty( $preview_table['data'] )
219 - || empty( $preview_table['options'] )
220 - || empty( $preview_table['visibility'] ) ) {
221 - break;
227 + // Check and convert all data that was transmitted as valid JSON.
228 + $keys = array( 'data', 'options', 'visibility' );
229 + foreach ( $keys as $key ) {
230 + if ( empty( $preview_table[ $key ] ) ) {
231 + break 2;
232 + }
233 + $preview_table[ $key ] = json_decode( $preview_table[ $key ], true );
234 + if ( is_null( $preview_table[ $key ] ) ) {
235 + break 2;
236 + }
237 + $preview_table[ $key ] = (array) $preview_table[ $key ]; // Cast to array again, to catch strings, etc.
222 238 }
223 - $preview_table['data'] = (array) json_decode( $preview_table['data'], true );
224 - $preview_table['options'] = (array) json_decode( $preview_table['options'], true );
225 - $preview_table['visibility'] = (array) json_decode( $preview_table['visibility'], true );
226 239
227 240 // Check consistency of new table, and then merge with existing table.
228 - $table = TablePress::$model_table->prepare_table( $existing_table, $preview_table, true, true );
241 + $table = TablePress::$model_table->prepare_table( $existing_table, $preview_table, true );
229 242 if ( is_wp_error( $table ) ) {
230 243 break;
231 244 }
232 245
233 - // DataTables Custom Commands can only be edit by trusted users.
246 + // DataTables Custom Commands can only be edited by trusted users.
234 247 if ( ! current_user_can( 'unfiltered_html' ) ) {
235 248 $table['options']['datatables_custom_commands'] = $existing_table['options']['datatables_custom_commands'];
236 249 }
237 250
@@ -239,18 +252,17 @@
239 252 if ( $table['id'] !== $table['new_id'] && 0 === preg_match( '/[^a-zA-Z0-9_-]/', $table['new_id'] ) ) {
240 253 $table['id'] = $table['new_id'];
241 254 }
242 255
243 - // Sanitize all table data to remove unsafe HTML from the preview output.
244 - $table = TablePress::$model_table->sanitize( $table );
256 + // Sanitize all table data to remove unsafe HTML from the preview output, if the user is not allowed to work with unfiltered HTML.
257 + if ( ! current_user_can( 'unfiltered_html' ) ) {
258 + $table = TablePress::$model_table->sanitize( $table );
259 + }
245 260
246 261 // At this point, the table data is valid and sanitized and can be rendered.
247 262 $success = true;
248 263 } while ( false ); // Do-while-loop through this exactly once, to be able to "break;" early.
249 264
250 - // Initialize i18n support, load plugin's textdomain, to retrieve correct translations for the description of the preview.
251 - load_plugin_textdomain( 'tablepress', false, dirname( TABLEPRESS_BASENAME ) . '/i18n' );
252 -
253 265 if ( $success ) {
254 266 // Create a render class instance.
255 267 $_render = TablePress::load_class( 'TablePress_Render', 'class-render.php', 'classes' );
256 268 // Merge desired options with default render options (see TablePress_Controller_Frontend::shortcode_table()).
@@ -256,24 +268,38 @@
256 268 // Merge desired options with default render options (see TablePress_Controller_Frontend::shortcode_table()).
257 269 $default_render_options = $_render->get_default_render_options();
258 270 /** This filter is documented in controllers/controller-frontend.php */
259 271 $default_render_options = apply_filters( 'tablepress_shortcode_table_default_shortcode_atts', $default_render_options );
260 - $render_options = shortcode_atts( $default_render_options, $table['options'] );
272 + // For the phpstan ignores in the next lines: If this is reached, $table is guaranteed to exist and is a valid array.
273 + $render_options = shortcode_atts( $default_render_options, $table['options'] ); // @phpstan-ignore offsetAccess.nonOffsetAccessible, variable.undefined
261 274 /** This filter is documented in controllers/controller-frontend.php */
262 275 $render_options = apply_filters( 'tablepress_shortcode_table_shortcode_atts', $render_options );
263 - $_render->set_input( $table, $render_options );
276 + $render_options['html_id'] = "tablepress-{$table['id']}"; // @phpstan-ignore offsetAccess.nonOffsetAccessible, variable.undefined
277 + $render_options['block_preview'] = true;
278 + $_render->set_input( $table, $render_options ); // @phpstan-ignore variable.undefined
264 279 $head_html = $_render->get_preview_css();
265 280 $custom_css = TablePress::$model_options->get( 'custom_css' );
266 - if ( ! empty( $custom_css ) ) {
267 - $head_html .= "<style type=\"text/css\">\n{$custom_css}\n</style>\n";
281 + $use_custom_css = ( TablePress::$model_options->get( 'use_custom_css' ) && '' !== $custom_css );
282 + if ( $use_custom_css ) {
283 + $head_html .= "<style>\n{$custom_css}\n</style>\n";
268 284 }
269 285
270 286 $body_html = '<div id="tablepress-page"><p>'
271 287 . __( 'This is a preview of your table.', 'tablepress' ) . ' '
272 288 . __( 'Because of CSS styling in your theme, the table might look different on your page!', 'tablepress' ) . ' '
273 - . __( 'The features of the DataTables JavaScript library are also not available or visible in this preview!', 'tablepress' ) . '<br />'
274 - . sprintf( __( 'To insert the table into a page, post, or text widget, copy the Shortcode %s and paste it into the editor.', 'tablepress' ), '<input type="text" class="table-shortcode table-shortcode-inline" value="' . esc_attr( '[' . TablePress::$shortcode . " id={$table['id']} /]" ) . '" readonly="readonly" />' )
275 - . '</p>' . $_render->get_output() . '</div>';
289 + . __( 'The Table Features for Site Visitors, like sorting, filtering, and pagination, are also not available in this preview!', 'tablepress' ) . '<br>';
290 + // Show the instructions string depending on whether the Block Editor is used on the site or not.
291 + if ( 'block' === TablePress::site_used_editor() ) {
292 + /* translators: %1$s: Block name */
293 + $body_html .= sprintf( __( 'To insert a table into a post or page, add a “%1$s” block in the block editor and select the desired table.', 'tablepress' ), __( 'TablePress table', 'tablepress' ) );
294 + } elseif ( 'elementor' === TablePress::site_used_editor() ) {
295 + /* translators: %1$s: Widget name */
296 + $body_html .= sprintf( __( 'To insert a table into a post or page, add a “%1$s” widget in the Elementor editor and select the desired table.', 'tablepress' ), __( 'TablePress table', 'tablepress' ) );
297 + } else {
298 + $body_html .= __( 'To insert a table into a post or page, paste its Shortcode at the desired place in the editor.', 'tablepress' ) . ' '
299 + . __( 'Each table has a unique ID that needs to be adjusted in that Shortcode.', 'tablepress' );
300 + }
301 + $body_html .= '</p><div class="preview">' . $_render->get_output( 'html' ) . '</div></div>';
276 302 } else {
277 303 $head_html = '';
278 304 $body_html = __( 'The preview could not be loaded.', 'tablepress' );
279 305 }
@@ -294,32 +320,52 @@
294 320 wp_send_json( $response );
295 321 }
296 322
297 323 /**
298 - * Retrieve all information of a WP_Error object as a string.
324 + * Saves the screen options on the "Edit" screen when they are changed.
299 325 *
300 - * @since 1.4.0
301 - *
302 - * @param WP_Error $wp_error A WP_Error object.
303 - * @return string All error codes, messages, and data of the WP_Error.
326 + * @since 2.1.0
304 327 */
305 - protected function get_wp_error_string( $wp_error ) {
306 - $error_strings = array();
307 - $error_codes = $wp_error->get_error_codes();
308 - // Reverse order to get latest errors first.
309 - $error_codes = array_reverse( $error_codes );
310 - foreach ( $error_codes as $error_code ) {
311 - $error_strings[ $error_code ] = $error_code;
312 - $error_messages = $wp_error->get_error_messages( $error_code );
313 - $error_messages = implode( ', ', $error_messages );
314 - if ( ! empty( $error_messages ) ) {
315 - $error_strings[ $error_code ] .= " ({$error_messages})";
316 - }
317 - $error_data = $wp_error->get_error_data( $error_code );
318 - if ( ! is_null( $error_data ) ) {
319 - $error_strings[ $error_code ] .= " [{$error_data}]";
320 - }
328 + public function ajax_action_save_screen_options(): void {
329 + // Check if the submitted nonce matches the generated nonce we created earlier, dies -1 on failure.
330 + TablePress::check_nonce( 'screen_options', false, '_ajax_nonce', true );
331 +
332 + if ( empty( $_POST['tablepress'] ) ) {
333 + wp_die( '-1' );
321 334 }
322 - return implode( ";\n", $error_strings );
335 + $screen_options = wp_unslash( $_POST['tablepress'] );
336 +
337 + // Sanitize and limit values to a minimum and a maximum.
338 + $new_screen_options = array();
339 +
340 + if ( isset( $screen_options['table_editor_column_width'] ) ) {
341 + $new_screen_options['table_editor_column_width'] = absint( $screen_options['table_editor_column_width'] );
342 + $new_screen_options['table_editor_column_width'] = max( $new_screen_options['table_editor_column_width'], 30 ); // Minimum width: 30 pixels.
343 + $new_screen_options['table_editor_column_width'] = min( $new_screen_options['table_editor_column_width'], 9999 ); // Maximum width: 9999 pixels.
344 + }
345 +
346 + if ( isset( $screen_options['table_editor_line_clamp'] ) ) {
347 + $new_screen_options['table_editor_line_clamp'] = absint( $screen_options['table_editor_line_clamp'] );
348 + $new_screen_options['table_editor_line_clamp'] = min( $new_screen_options['table_editor_line_clamp'], 999 ); // Maximum lines: 999. Minimum of 0 (for all lines) is ensured by absint().
349 + }
350 +
351 + if ( empty( $new_screen_options ) ) {
352 + wp_die( '-1' );
353 + }
354 + TablePress::$model_options->update( $new_screen_options );
355 +
356 + // Generate the response.
357 + $response = array(
358 + 'success' => true,
359 + );
360 +
361 + // Buffer all outputs, to prevent errors/warnings being printed that make the JSON invalid.
362 + $output_buffer = ob_get_clean();
363 + if ( ! empty( $output_buffer ) ) {
364 + $response['output_buffer'] = $output_buffer;
365 + }
366 +
367 + // Send the response.
368 + wp_send_json( $response );
323 369 }
324 370
325 371 } // class TablePress_Admin_AJAX_Controller