PluginProbe
TablePress – Tables in WordPress made easy / 3.2.2
TablePress – Tables in WordPress made easy v3.2.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_ajax.php

controller-admin_ajax.php in TablePress – Tables in WordPress made easy 3.2.2, at controllers/controller-admin_ajax.php

374 lines 15.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Admin AJAX Controller for TablePress with functionality for the 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 AJAX Controller class, extends Base Controller Class
16 *
17 * @package TablePress
18 * @subpackage Controllers
19 * @author Tobias Bäthge
20 * @since 1.0.0
21 */
22 class TablePress_Admin_AJAX_Controller extends TablePress_Controller {
23
24 /**
25 * Initiates the Admin AJAX functionality.
26 *
27 * @since 1.0.0
28 */
29 public function __construct() {
30 // Buffer all outputs, to prevent errors/warnings being printed that make the JSON invalid.
31 ob_start();
32
33 parent::__construct();
34
35 $ajax_actions = array( 'hide_message', 'save_table', 'preview_table', 'save_screen_options' );
36 foreach ( $ajax_actions as $action ) {
37 add_action( "wp_ajax_tablepress_{$action}", array( $this, "ajax_action_{$action}" ) );
38 }
39 }
40
41 /**
42 * Hides a header message on an admin screen.
43 *
44 * @since 1.0.0
45 */
46 public function ajax_action_hide_message(): void {
47 if ( empty( $_GET['item'] ) ) {
48 wp_die( '0' );
49 }
50
51 $message_item = $_GET['item'];
52
53 TablePress::check_nonce( 'hide_message', $message_item, '_wpnonce', true );
54
55 if ( ! current_user_can( 'tablepress_list_tables' ) ) {
56 wp_die( '-1' );
57 }
58
59 TablePress::$model_options->update( "message_{$message_item}", false );
60
61 wp_die( '1' );
62 }
63
64 /**
65 * Saves the table after the "Save Changes" button on the "Edit" screen has been clicked.
66 *
67 * @since 1.0.0
68 */
69 public function ajax_action_save_table(): void {
70 if ( empty( $_POST['tablepress']['id'] ) ) {
71 wp_die( '-1' );
72 }
73
74 $edit_table = wp_unslash( $_POST['tablepress'] );
75
76 // Check if the submitted nonce matches the generated nonce we created earlier, dies -1 on failure.
77 TablePress::check_nonce( 'edit', $edit_table['id'], '_ajax_nonce', true );
78
79 // Ignore the request if the current user doesn't have sufficient permissions.
80 if ( ! current_user_can( 'tablepress_edit_table', $edit_table['id'] ) ) {
81 wp_die( '-1' );
82 }
83
84 // Default response data.
85 $success = false;
86 $message = 'error_save';
87 $error_details = '';
88 do { // To be able to "break;" (allows for better readable code).
89 // Load table, without table data, but with options and visibility settings.
90 $existing_table = TablePress::$model_table->load( $edit_table['id'], false, true );
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 break;
96 }
97
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.
113 }
114
115 // Check consistency of new table, and then merge with existing table.
116 $table = TablePress::$model_table->prepare_table( $existing_table, $edit_table, true );
117 if ( is_wp_error( $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 );
121 break;
122 }
123
124 // DataTables Custom Commands can only be edited by trusted users.
125 if ( ! current_user_can( 'unfiltered_html' ) ) {
126 $table['options']['datatables_custom_commands'] = $existing_table['options']['datatables_custom_commands'];
127 }
128
129 // Save updated table.
130 $saved = TablePress::$model_table->save( $table );
131 if ( is_wp_error( $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 );
135 break;
136 }
137
138 // At this point, the table was saved successfully, possible ID change remains.
139 $success = true;
140 $message = 'success_save';
141
142 // Check if ID change is desired.
143 if ( $table['id'] === $table['new_id'] ) {
144 // If not, we are done.
145 break;
146 }
147
148 // Change table ID.
149 if ( current_user_can( 'tablepress_edit_table_id', $table['id'] ) ) {
150 $id_changed = TablePress::$model_table->change_table_id( $table['id'], $table['new_id'] );
151 if ( ! is_wp_error( $id_changed ) ) {
152 $message = 'success_save_success_id_change';
153 $table['id'] = $table['new_id'];
154 } else {
155 $message = 'success_save_error_id_change';
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 );
159 }
160 } else {
161 $message = 'success_save_error_id_change';
162 $error_details = 'table_id_could_not_be_changed: capability_check_failed';
163 }
164
165 // @phpstan-ignore doWhile.alwaysFalse
166 } while ( false ); // Do-while-loop through this exactly once, to be able to "break;" early.
167
168 // Generate the response.
169
170 // Common data for all responses.
171 $response = array(
172 'success' => $success,
173 'message' => $message,
174 );
175 if ( $success ) {
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
184 }
185 if ( ! empty( $error_details ) ) {
186 $response['error_details'] = esc_html( $error_details );
187 }
188 // Buffer all outputs, to prevent errors/warnings being printed that make the JSON invalid.
189 $output_buffer = ob_get_clean();
190 if ( ! empty( $output_buffer ) ) {
191 $response['output_buffer'] = $output_buffer;
192 }
193
194 // Send the response.
195 wp_send_json( $response );
196 }
197
198 /**
199 * Returns the live preview data of table that has non-saved changes.
200 *
201 * @since 1.0.0
202 */
203 public function ajax_action_preview_table(): void {
204 if ( empty( $_POST['tablepress']['id'] ) ) {
205 wp_die( '-1' );
206 }
207
208 $preview_table = wp_unslash( $_POST['tablepress'] );
209
210 // Check if the submitted nonce matches the generated nonce we created earlier, dies -1 on failure.
211 TablePress::check_nonce( 'preview_table', $preview_table['id'], '_ajax_nonce', true );
212
213 // Ignore the request if the current user doesn't have sufficient permissions.
214 if ( ! current_user_can( 'tablepress_preview_table', $preview_table['id'] ) ) {
215 wp_die( '-1' );
216 }
217
218 // Default response data.
219 $success = false;
220 do { // To be able to "break;" (allows for better readable code).
221 // Load table, without table data, but with options and visibility settings.
222 $existing_table = TablePress::$model_table->load( $preview_table['id'], false, true );
223 if ( is_wp_error( $existing_table ) ) {
224 break;
225 }
226
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.
238 }
239
240 // Check consistency of new table, and then merge with existing table.
241 $table = TablePress::$model_table->prepare_table( $existing_table, $preview_table, true );
242 if ( is_wp_error( $table ) ) {
243 break;
244 }
245
246 // DataTables Custom Commands can only be edited by trusted users.
247 if ( ! current_user_can( 'unfiltered_html' ) ) {
248 $table['options']['datatables_custom_commands'] = $existing_table['options']['datatables_custom_commands'];
249 }
250
251 // If the ID has changed, and the new ID is valid, render with the new ID (important e.g. for CSS classes/HTML ID).
252 if ( $table['id'] !== $table['new_id'] && 0 === preg_match( '/[^a-zA-Z0-9_-]/', $table['new_id'] ) ) {
253 $table['id'] = $table['new_id'];
254 }
255
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 }
260
261 // At this point, the table data is valid and sanitized and can be rendered.
262 $success = true;
263
264 // @phpstan-ignore doWhile.alwaysFalse
265 } while ( false ); // Do-while-loop through this exactly once, to be able to "break;" early.
266
267 if ( $success ) {
268 // Create a render class instance.
269 $_render = TablePress::load_class( 'TablePress_Render', 'class-render.php', 'classes' );
270 // Merge desired options with default render options (see TablePress_Controller_Frontend::shortcode_table()).
271 $default_render_options = $_render->get_default_render_options();
272 /** This filter is documented in controllers/controller-frontend.php */
273 $default_render_options = apply_filters( 'tablepress_shortcode_table_default_shortcode_atts', $default_render_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
276 /** This filter is documented in controllers/controller-frontend.php */
277 $render_options = apply_filters( 'tablepress_shortcode_table_shortcode_atts', $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
281 $head_html = $_render->get_preview_css();
282 $custom_css = TablePress::$model_options->get( 'custom_css' );
283 $use_custom_css = ( TablePress::$model_options->get( 'use_custom_css' ) && '' !== $custom_css );
284 if ( $use_custom_css ) {
285 $head_html .= "<style>\n{$custom_css}\n</style>\n";
286 }
287
288 $body_html = '<div id="tablepress-page"><p>'
289 . __( 'This is a preview of your table.', 'tablepress' ) . ' '
290 . __( 'Because of CSS styling in your theme, the table might look different on your page!', 'tablepress' ) . ' '
291 . __( 'The Table Features for Site Visitors, like sorting, filtering, and pagination, are also not available in this preview!', 'tablepress' ) . '<br>';
292 // Show the instructions string depending on whether the Block Editor is used on the site or not.
293 if ( 'block' === TablePress::site_used_editor() ) {
294 /* translators: %1$s: Block name */
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' ) );
299 } else {
300 $body_html .= __( 'To insert a table into a post or page, paste its Shortcode at the desired place in the editor.', 'tablepress' ) . ' '
301 . __( 'Each table has a unique ID that needs to be adjusted in that Shortcode.', 'tablepress' );
302 }
303 $body_html .= '</p>' . $_render->get_output( 'html' ) . '</div>';
304 } else {
305 $head_html = '';
306 $body_html = __( 'The preview could not be loaded.', 'tablepress' );
307 }
308
309 // Generate the response.
310 $response = array(
311 'success' => $success,
312 'head_html' => $head_html,
313 'body_html' => $body_html,
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
363 // Buffer all outputs, to prevent errors/warnings being printed that make the JSON invalid.
364 $output_buffer = ob_get_clean();
365 if ( ! empty( $output_buffer ) ) {
366 $response['output_buffer'] = $output_buffer;
367 }
368
369 // Send the response.
370 wp_send_json( $response );
371 }
372
373 } // class TablePress_Admin_AJAX_Controller
374