PluginProbe
TablePress – Tables in WordPress made easy / 3.2
TablePress – Tables in WordPress made easy v3.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
← All changes | controllers/controller-admin_ajax.php +122 -53 2.0.43.2 View file →
@@ -21,9 +21,9 @@
21 21 */
22 22 class TablePress_Admin_AJAX_Controller extends TablePress_Controller {
23 23
24 24 /**
25 - * Initiate Admin AJAX functionality.
25 + * Initiates the Admin AJAX functionality.
26 26 *
27 27 * @since 1.0.0
28 28 */
29 29 public function __construct() {
@@ -31,9 +31,9 @@
31 31 ob_start();
32 32
33 33 parent::__construct();
34 34
35 - $ajax_actions = array( 'hide_message', 'save_table', 'preview_table' );
35 + $ajax_actions = array( 'hide_message', 'save_table', 'preview_table', 'save_screen_options' );
36 36 foreach ( $ajax_actions as $action ) {
37 37 add_action( "wp_ajax_tablepress_{$action}", array( $this, "ajax_action_{$action}" ) );
38 38 }
39 39 }
@@ -38,13 +38,13 @@
38 38 }
39 39 }
40 40
41 41 /**
42 - * Hide a header message on an admin screen.
42 + * Hides a header message on an admin screen.
43 43 *
44 44 * @since 1.0.0
45 45 */
46 - public function ajax_action_hide_message() {
46 + public function ajax_action_hide_message(): void {
47 47 if ( empty( $_GET['item'] ) ) {
48 48 wp_die( '0' );
49 49 }
50 50
@@ -61,13 +61,13 @@
61 61 wp_die( '1' );
62 62 }
63 63
64 64 /**
65 - * Save the table after the "Save Changes" button on the "Edit" screen has been clicked.
65 + * Saves the table after the "Save Changes" button on the "Edit" screen has been clicked.
66 66 *
67 67 * @since 1.0.0
68 68 */
69 - public function ajax_action_save_table() {
69 + public function ajax_action_save_table(): void {
70 70 if ( empty( $_POST['tablepress']['id'] ) ) {
71 71 wp_die( '-1' );
72 72 }
73 73
@@ -84,37 +84,41 @@
84 84 // Default response data.
85 85 $success = false;
86 86 $message = 'error_save';
87 87 $error_details = '';
88 - do { // to be able to "break;" (allows for better readable code)
88 + do { // To be able to "break;" (allows for better readable code).
89 89 // Load table, without table data, but with options and visibility settings.
90 90 $existing_table = TablePress::$model_table->load( $edit_table['id'], false, true );
91 - if ( is_wp_error( $existing_table ) ) { // maybe somehow load a new table here? (TablePress::$model_table->get_table_template())?
92 - // Add an error code to the existing WP_Error.
93 - $existing_table->add( 'ajax_save_table_load', '', $edit_table['id'] );
94 - $error_details = TablePress::get_wp_error_string( $existing_table );
91 + if ( is_wp_error( $existing_table ) ) {
92 + $error = new WP_Error( 'ajax_save_table_load', '', $edit_table['id'] );
93 + $error->merge_from( $existing_table );
94 + $error_details = TablePress::get_wp_error_string( $error );
95 95 break;
96 96 }
97 97
98 - // Check and convert data that was transmitted as JSON.
99 - if ( empty( $edit_table['data'] )
100 - || empty( $edit_table['options'] )
101 - || empty( $edit_table['visibility'] ) ) {
102 - // Create a new WP_Error.
103 - $empty_data_error = new WP_Error( 'ajax_save_table_data_empty', '', $edit_table['id'] );
104 - $error_details = TablePress::get_wp_error_string( $empty_data_error );
105 - break;
98 + // Check and convert all data that was transmitted as valid JSON.
99 + $keys = array( 'data', 'options', 'visibility' );
100 + foreach ( $keys as $key ) {
101 + if ( empty( $edit_table[ $key ] ) ) {
102 + $error = new WP_Error( "ajax_save_table_{$key}_empty", '', $edit_table['id'] );
103 + $error_details = TablePress::get_wp_error_string( $error );
104 + break 2;
105 + }
106 + $edit_table[ $key ] = json_decode( $edit_table[ $key ], true );
107 + if ( is_null( $edit_table[ $key ] ) ) {
108 + $error = new WP_Error( "ajax_save_table_{$key}_invalid_json", '', $edit_table['id'] );
109 + $error_details = TablePress::get_wp_error_string( $error );
110 + break 2;
111 + }
112 + $edit_table[ $key ] = (array) $edit_table[ $key ]; // Cast to array again, to catch strings, etc.
106 113 }
107 - $edit_table['data'] = (array) json_decode( $edit_table['data'], true );
108 - $edit_table['options'] = (array) json_decode( $edit_table['options'], true );
109 - $edit_table['visibility'] = (array) json_decode( $edit_table['visibility'], true );
110 114
111 115 // Check consistency of new table, and then merge with existing table.
112 116 $table = TablePress::$model_table->prepare_table( $existing_table, $edit_table, true );
113 117 if ( is_wp_error( $table ) ) {
114 - // Add an error code to the existing WP_Error.
115 - $table->add( 'ajax_save_table_prepare', '', $edit_table['id'] );
116 - $error_details = TablePress::get_wp_error_string( $table );
118 + $error = new WP_Error( 'ajax_save_table_prepare', '', $edit_table['id'] );
119 + $error->merge_from( $table );
120 + $error_details = TablePress::get_wp_error_string( $error );
117 121 break;
118 122 }
119 123
120 124 // DataTables Custom Commands can only be edited by trusted users.
@@ -124,11 +128,11 @@
124 128
125 129 // Save updated table.
126 130 $saved = TablePress::$model_table->save( $table );
127 131 if ( is_wp_error( $saved ) ) {
128 - // Add an error code to the existing WP_Error.
129 - $saved->add( 'ajax_save_table_save', '', $table['id'] );
130 - $error_details = TablePress::get_wp_error_string( $saved );
132 + $error = new WP_Error( 'ajax_save_table_save', '', $table['id'] );
133 + $error->merge_from( $saved );
134 + $error_details = TablePress::get_wp_error_string( $error );
131 135 break;
132 136 }
133 137
134 138 // At this point, the table was saved successfully, possible ID change remains.
@@ -148,16 +152,18 @@
148 152 $message = 'success_save_success_id_change';
149 153 $table['id'] = $table['new_id'];
150 154 } else {
151 155 $message = 'success_save_error_id_change';
152 - // Add an error code to the existing WP_Error.
153 - $id_changed->add( 'ajax_save_table_id_change', '', $table['new_id'] );
154 - $error_details = TablePress::get_wp_error_string( $id_changed );
156 + $error = new WP_Error( 'ajax_save_table_id_change', '', $table['new_id'] );
157 + $error->merge_from( $id_changed );
158 + $error_details = TablePress::get_wp_error_string( $error );
155 159 }
156 160 } else {
157 161 $message = 'success_save_error_id_change';
158 162 $error_details = 'table_id_could_not_be_changed: capability_check_failed';
159 163 }
164 +
165 + // @phpstan-ignore doWhile.alwaysFalse
160 166 } while ( false ); // Do-while-loop through this exactly once, to be able to "break;" early.
161 167
162 168 // Generate the response.
163 169
@@ -166,13 +172,16 @@
166 172 'success' => $success,
167 173 'message' => $message,
168 174 );
169 175 if ( $success ) {
170 - $response['table_id'] = $table['id'];
171 - $response['new_edit_nonce'] = wp_create_nonce( TablePress::nonce( 'edit', $table['id'] ) );
172 - $response['new_preview_nonce'] = wp_create_nonce( TablePress::nonce( 'preview_table', $table['id'] ) );
173 - $response['last_modified'] = TablePress::format_datetime( $table['last_modified'] );
174 - $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
175 184 }
176 185 if ( ! empty( $error_details ) ) {
177 186 $response['error_details'] = esc_html( $error_details );
178 187 }
@@ -186,13 +195,13 @@
186 195 wp_send_json( $response );
187 196 }
188 197
189 198 /**
190 - * 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.
191 200 *
192 201 * @since 1.0.0
193 202 */
194 - public function ajax_action_preview_table() {
203 + public function ajax_action_preview_table(): void {
195 204 if ( empty( $_POST['tablepress']['id'] ) ) {
196 205 wp_die( '-1' );
197 206 }
198 207
@@ -207,24 +216,27 @@
207 216 }
208 217
209 218 // Default response data.
210 219 $success = false;
211 - do { // to be able to "break;" (allows for better readable code)
220 + do { // To be able to "break;" (allows for better readable code).
212 221 // Load table, without table data, but with options and visibility settings.
213 222 $existing_table = TablePress::$model_table->load( $preview_table['id'], false, true );
214 - 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 ) ) {
215 224 break;
216 225 }
217 226
218 - // Check and convert data that was transmitted as JSON.
219 - if ( empty( $preview_table['data'] )
220 - || empty( $preview_table['options'] )
221 - || empty( $preview_table['visibility'] ) ) {
222 - 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.
223 238 }
224 - $preview_table['data'] = (array) json_decode( $preview_table['data'], true );
225 - $preview_table['options'] = (array) json_decode( $preview_table['options'], true );
226 - $preview_table['visibility'] = (array) json_decode( $preview_table['visibility'], true );
227 239
228 240 // Check consistency of new table, and then merge with existing table.
229 241 $table = TablePress::$model_table->prepare_table( $existing_table, $preview_table, true );
230 242 if ( is_wp_error( $table ) ) {
@@ -247,8 +259,10 @@
247 259 }
248 260
249 261 // At this point, the table data is valid and sanitized and can be rendered.
250 262 $success = true;
263 +
264 + // @phpstan-ignore doWhile.alwaysFalse
251 265 } while ( false ); // Do-while-loop through this exactly once, to be able to "break;" early.
252 266
253 267 if ( $success ) {
254 268 // Create a render class instance.
@@ -256,12 +270,15 @@
256 270 // Merge desired options with default render options (see TablePress_Controller_Frontend::shortcode_table()).
257 271 $default_render_options = $_render->get_default_render_options();
258 272 /** This filter is documented in controllers/controller-frontend.php */
259 273 $default_render_options = apply_filters( 'tablepress_shortcode_table_default_shortcode_atts', $default_render_options );
260 - $render_options = shortcode_atts( $default_render_options, $table['options'] );
274 + // For the phpstan ignores in the next lines: If this is reached, $table is guaranteed to exist and is a valid array.
275 + $render_options = shortcode_atts( $default_render_options, $table['options'] ); // @phpstan-ignore offsetAccess.nonOffsetAccessible, variable.undefined
261 276 /** This filter is documented in controllers/controller-frontend.php */
262 277 $render_options = apply_filters( 'tablepress_shortcode_table_shortcode_atts', $render_options );
263 - $_render->set_input( $table, $render_options );
278 + $render_options['html_id'] = "tablepress-{$table['id']}"; // @phpstan-ignore offsetAccess.nonOffsetAccessible, variable.undefined
279 + $render_options['block_preview'] = true;
280 + $_render->set_input( $table, $render_options ); // @phpstan-ignore variable.undefined
264 281 $head_html = $_render->get_preview_css();
265 282 $custom_css = TablePress::$model_options->get( 'custom_css' );
266 283 $use_custom_css = ( TablePress::$model_options->get( 'use_custom_css' ) && '' !== $custom_css );
267 284 if ( $use_custom_css ) {
@@ -270,17 +287,21 @@
270 287
271 288 $body_html = '<div id="tablepress-page"><p>'
272 289 . __( 'This is a preview of your table.', 'tablepress' ) . ' '
273 290 . __( 'Because of CSS styling in your theme, the table might look different on your page!', 'tablepress' ) . ' '
274 - . __( 'The Table Features for Site Visitors, like sorting, filtering, and pagination, are also not available in this preview!', 'tablepress' ) . '<br />';
291 + . __( 'The Table Features for Site Visitors, like sorting, filtering, and pagination, are also not available in this preview!', 'tablepress' ) . '<br>';
275 292 // Show the instructions string depending on whether the Block Editor is used on the site or not.
276 - if ( TablePress::site_uses_block_editor() ) {
293 + if ( 'block' === TablePress::site_used_editor() ) {
294 + /* translators: %1$s: Block name */
277 295 $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' ) );
296 + } elseif ( 'elementor' === TablePress::site_used_editor() ) {
297 + /* translators: %1$s: Widget name */
298 + $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' ) );
278 299 } else {
279 300 $body_html .= __( 'To insert a table into a post or page, paste its Shortcode at the desired place in the editor.', 'tablepress' ) . ' '
280 301 . __( 'Each table has a unique ID that needs to be adjusted in that Shortcode.', 'tablepress' );
281 302 }
282 - $body_html .= '</p>' . $_render->get_output() . '</div>';
303 + $body_html .= '</p>' . $_render->get_output( 'html' ) . '</div>';
283 304 } else {
284 305 $head_html = '';
285 306 $body_html = __( 'The preview could not be loaded.', 'tablepress' );
286 307 }
@@ -290,8 +311,56 @@
290 311 'success' => $success,
291 312 'head_html' => $head_html,
292 313 'body_html' => $body_html,
293 314 );
315 + // Buffer all outputs, to prevent errors/warnings being printed that make the JSON invalid.
316 + $output_buffer = ob_get_clean();
317 + if ( ! empty( $output_buffer ) ) {
318 + $response['output_buffer'] = $output_buffer;
319 + }
320 +
321 + // Send the response.
322 + wp_send_json( $response );
323 + }
324 +
325 + /**
326 + * Saves the screen options on the "Edit" screen when they are changed.
327 + *
328 + * @since 2.1.0
329 + */
330 + public function ajax_action_save_screen_options(): void {
331 + // Check if the submitted nonce matches the generated nonce we created earlier, dies -1 on failure.
332 + TablePress::check_nonce( 'screen_options', false, '_ajax_nonce', true );
333 +
334 + if ( empty( $_POST['tablepress'] ) ) {
335 + wp_die( '-1' );
336 + }
337 + $screen_options = wp_unslash( $_POST['tablepress'] );
338 +
339 + // Sanitize and limit values to a minimum and a maximum.
340 + $new_screen_options = array();
341 +
342 + if ( isset( $screen_options['table_editor_column_width'] ) ) {
343 + $new_screen_options['table_editor_column_width'] = absint( $screen_options['table_editor_column_width'] );
344 + $new_screen_options['table_editor_column_width'] = max( $new_screen_options['table_editor_column_width'], 30 ); // Minimum width: 30 pixels.
345 + $new_screen_options['table_editor_column_width'] = min( $new_screen_options['table_editor_column_width'], 9999 ); // Maximum width: 9999 pixels.
346 + }
347 +
348 + if ( isset( $screen_options['table_editor_line_clamp'] ) ) {
349 + $new_screen_options['table_editor_line_clamp'] = absint( $screen_options['table_editor_line_clamp'] );
350 + $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().
351 + }
352 +
353 + if ( empty( $new_screen_options ) ) {
354 + wp_die( '-1' );
355 + }
356 + TablePress::$model_options->update( $new_screen_options );
357 +
358 + // Generate the response.
359 + $response = array(
360 + 'success' => true,
361 + );
362 +
294 363 // Buffer all outputs, to prevent errors/warnings being printed that make the JSON invalid.
295 364 $output_buffer = ob_get_clean();
296 365 if ( ! empty( $output_buffer ) ) {
297 366 $response['output_buffer'] = $output_buffer;