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 +120 -53 2.0.4 → 3.4 View file →
@@ -7,8 +7,10 @@
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 /**
@@ -21,9 +23,9 @@
21 23 */
22 24 class TablePress_Admin_AJAX_Controller extends TablePress_Controller {
23 25
24 26 /**
25 - * Initiate Admin AJAX functionality.
27 + * Initiates the Admin AJAX functionality.
26 28 *
27 29 * @since 1.0.0
28 30 */
29 31 public function __construct() {
@@ -31,9 +33,9 @@
31 33 ob_start();
32 34
33 35 parent::__construct();
34 36
35 - $ajax_actions = array( 'hide_message', 'save_table', 'preview_table' );
37 + $ajax_actions = array( 'hide_message', 'save_table', 'preview_table', 'save_screen_options' );
36 38 foreach ( $ajax_actions as $action ) {
37 39 add_action( "wp_ajax_tablepress_{$action}", array( $this, "ajax_action_{$action}" ) );
38 40 }
39 41 }
@@ -38,13 +40,13 @@
38 40 }
39 41 }
40 42
41 43 /**
42 - * Hide a header message on an admin screen.
44 + * Hides a header message on an admin screen.
43 45 *
44 46 * @since 1.0.0
45 47 */
46 - public function ajax_action_hide_message() {
48 + public function ajax_action_hide_message(): void {
47 49 if ( empty( $_GET['item'] ) ) {
48 50 wp_die( '0' );
49 51 }
50 52
@@ -61,13 +63,13 @@
61 63 wp_die( '1' );
62 64 }
63 65
64 66 /**
65 - * 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.
66 68 *
67 69 * @since 1.0.0
68 70 */
69 - public function ajax_action_save_table() {
71 + public function ajax_action_save_table(): void {
70 72 if ( empty( $_POST['tablepress']['id'] ) ) {
71 73 wp_die( '-1' );
72 74 }
73 75
@@ -84,37 +86,41 @@
84 86 // Default response data.
85 87 $success = false;
86 88 $message = 'error_save';
87 89 $error_details = '';
88 - do { // to be able to "break;" (allows for better readable code)
90 + do { // To be able to "break;" (allows for better readable code).
89 91 // Load table, without table data, but with options and visibility settings.
90 92 $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 );
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 );
95 97 break;
96 98 }
97 99
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;
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.
106 115 }
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 116
111 117 // Check consistency of new table, and then merge with existing table.
112 118 $table = TablePress::$model_table->prepare_table( $existing_table, $edit_table, true );
113 119 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 );
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 );
117 123 break;
118 124 }
119 125
120 126 // DataTables Custom Commands can only be edited by trusted users.
@@ -124,11 +130,11 @@
124 130
125 131 // Save updated table.
126 132 $saved = TablePress::$model_table->save( $table );
127 133 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 );
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 );
131 137 break;
132 138 }
133 139
134 140 // At this point, the table was saved successfully, possible ID change remains.
@@ -148,11 +154,11 @@
148 154 $message = 'success_save_success_id_change';
149 155 $table['id'] = $table['new_id'];
150 156 } else {
151 157 $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 );
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 );
155 161 }
156 162 } else {
157 163 $message = 'success_save_error_id_change';
158 164 $error_details = 'table_id_could_not_be_changed: capability_check_failed';
@@ -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 ) ) {
@@ -256,12 +268,15 @@
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 281 $use_custom_css = ( TablePress::$model_options->get( 'use_custom_css' ) && '' !== $custom_css );
267 282 if ( $use_custom_css ) {
@@ -270,17 +285,21 @@
270 285
271 286 $body_html = '<div id="tablepress-page"><p>'
272 287 . __( 'This is a preview of your table.', 'tablepress' ) . ' '
273 288 . __( '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 />';
289 + . __( 'The Table Features for Site Visitors, like sorting, filtering, and pagination, are also not available in this preview!', 'tablepress' ) . '<br>';
275 290 // Show the instructions string depending on whether the Block Editor is used on the site or not.
276 - if ( TablePress::site_uses_block_editor() ) {
291 + if ( 'block' === TablePress::site_used_editor() ) {
292 + /* translators: %1$s: Block name */
277 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' ) );
278 297 } else {
279 298 $body_html .= __( 'To insert a table into a post or page, paste its Shortcode at the desired place in the editor.', 'tablepress' ) . ' '
280 299 . __( 'Each table has a unique ID that needs to be adjusted in that Shortcode.', 'tablepress' );
281 300 }
282 - $body_html .= '</p>' . $_render->get_output() . '</div>';
301 + $body_html .= '</p><div class="preview">' . $_render->get_output( 'html' ) . '</div></div>';
283 302 } else {
284 303 $head_html = '';
285 304 $body_html = __( 'The preview could not be loaded.', 'tablepress' );
286 305 }
@@ -290,8 +309,56 @@
290 309 'success' => $success,
291 310 'head_html' => $head_html,
292 311 'body_html' => $body_html,
293 312 );
313 + // Buffer all outputs, to prevent errors/warnings being printed that make the JSON invalid.
314 + $output_buffer = ob_get_clean();
315 + if ( ! empty( $output_buffer ) ) {
316 + $response['output_buffer'] = $output_buffer;
317 + }
318 +
319 + // Send the response.
320 + wp_send_json( $response );
321 + }
322 +
323 + /**
324 + * Saves the screen options on the "Edit" screen when they are changed.
325 + *
326 + * @since 2.1.0
327 + */
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' );
334 + }
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 +
294 361 // Buffer all outputs, to prevent errors/warnings being printed that make the JSON invalid.
295 362 $output_buffer = ob_get_clean();
296 363 if ( ! empty( $output_buffer ) ) {
297 364 $response['output_buffer'] = $output_buffer;