PluginProbe
TablePress – Tables in WordPress made easy / 2.3
TablePress – Tables in WordPress made easy v2.3
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 2.3, at controllers/controller-admin_ajax.php

363 lines 14.2 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 ) ) { // @todo Maybe somehow load a new table here? (TablePress::$model_table->get_table_template())?
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 } while ( false ); // Do-while-loop through this exactly once, to be able to "break;" early. // @phpstan-ignore-line .
165
166 // Generate the response.
167
168 // Common data for all responses.
169 $response = array(
170 'success' => $success,
171 'message' => $message,
172 );
173 if ( $success ) {
174 $response['table_id'] = $table['id']; // @phpstan-ignore-line
175 $response['new_edit_nonce'] = wp_create_nonce( TablePress::nonce( 'edit', $table['id'] ) ); // @phpstan-ignore-line
176 $response['new_preview_nonce'] = wp_create_nonce( TablePress::nonce( 'preview_table', $table['id'] ) ); // @phpstan-ignore-line
177 $response['new_copy_nonce'] = wp_create_nonce( TablePress::nonce( 'copy_table', $table['id'] ) ); // @phpstan-ignore-line
178 $response['new_delete_nonce'] = wp_create_nonce( TablePress::nonce( 'delete_table', $table['id'] ) ); // @phpstan-ignore-line
179 $response['last_modified'] = TablePress::format_datetime( $table['last_modified'] ); // @phpstan-ignore-line
180 $response['last_editor'] = TablePress::get_user_display_name( $table['options']['last_editor'] ); // @phpstan-ignore-line
181 }
182 if ( ! empty( $error_details ) ) {
183 $response['error_details'] = esc_html( $error_details );
184 }
185 // Buffer all outputs, to prevent errors/warnings being printed that make the JSON invalid.
186 $output_buffer = ob_get_clean();
187 if ( ! empty( $output_buffer ) ) {
188 $response['output_buffer'] = $output_buffer;
189 }
190
191 // Send the response.
192 wp_send_json( $response );
193 }
194
195 /**
196 * Returns the live preview data of table that has non-saved changes.
197 *
198 * @since 1.0.0
199 */
200 public function ajax_action_preview_table(): void {
201 if ( empty( $_POST['tablepress']['id'] ) ) {
202 wp_die( '-1' );
203 }
204
205 $preview_table = wp_unslash( $_POST['tablepress'] );
206
207 // Check if the submitted nonce matches the generated nonce we created earlier, dies -1 on failure.
208 TablePress::check_nonce( 'preview_table', $preview_table['id'], '_ajax_nonce', true );
209
210 // Ignore the request if the current user doesn't have sufficient permissions.
211 if ( ! current_user_can( 'tablepress_preview_table', $preview_table['id'] ) ) {
212 wp_die( '-1' );
213 }
214
215 // Default response data.
216 $success = false;
217 do { // To be able to "break;" (allows for better readable code).
218 // Load table, without table data, but with options and visibility settings.
219 $existing_table = TablePress::$model_table->load( $preview_table['id'], false, true );
220 if ( is_wp_error( $existing_table ) ) { // @todo Maybe somehow load a new table here? (TablePress::$model_table->get_table_template())?
221 break;
222 }
223
224 // Check and convert all data that was transmitted as valid JSON.
225 $keys = array( 'data', 'options', 'visibility' );
226 foreach ( $keys as $key ) {
227 if ( empty( $preview_table[ $key ] ) ) {
228 break 2;
229 }
230 $preview_table[ $key ] = json_decode( $preview_table[ $key ], true );
231 if ( is_null( $preview_table[ $key ] ) ) {
232 break 2;
233 }
234 $preview_table[ $key ] = (array) $preview_table[ $key ]; // Cast to array again, to catch strings, etc.
235 }
236
237 // Check consistency of new table, and then merge with existing table.
238 $table = TablePress::$model_table->prepare_table( $existing_table, $preview_table, true );
239 if ( is_wp_error( $table ) ) {
240 break;
241 }
242
243 // DataTables Custom Commands can only be edited by trusted users.
244 if ( ! current_user_can( 'unfiltered_html' ) ) {
245 $table['options']['datatables_custom_commands'] = $existing_table['options']['datatables_custom_commands'];
246 }
247
248 // If the ID has changed, and the new ID is valid, render with the new ID (important e.g. for CSS classes/HTML ID).
249 if ( $table['id'] !== $table['new_id'] && 0 === preg_match( '/[^a-zA-Z0-9_-]/', $table['new_id'] ) ) {
250 $table['id'] = $table['new_id'];
251 }
252
253 // Sanitize all table data to remove unsafe HTML from the preview output, if the user is not allowed to work with unfiltered HTML.
254 if ( ! current_user_can( 'unfiltered_html' ) ) {
255 $table = TablePress::$model_table->sanitize( $table );
256 }
257
258 // At this point, the table data is valid and sanitized and can be rendered.
259 $success = true;
260 } while ( false ); // Do-while-loop through this exactly once, to be able to "break;" early. // @phpstan-ignore-line .
261
262 if ( $success ) {
263 // Create a render class instance.
264 $_render = TablePress::load_class( 'TablePress_Render', 'class-render.php', 'classes' );
265 // Merge desired options with default render options (see TablePress_Controller_Frontend::shortcode_table()).
266 $default_render_options = $_render->get_default_render_options();
267 /** This filter is documented in controllers/controller-frontend.php */
268 $default_render_options = apply_filters( 'tablepress_shortcode_table_default_shortcode_atts', $default_render_options );
269 $render_options = shortcode_atts( $default_render_options, $table['options'] ); // @phpstan-ignore-line
270 /** This filter is documented in controllers/controller-frontend.php */
271 $render_options = apply_filters( 'tablepress_shortcode_table_shortcode_atts', $render_options );
272 $render_options['html_id'] = "tablepress-{$table['id']}"; // @phpstan-ignore-line
273 $_render->set_input( $table, $render_options ); // @phpstan-ignore-line
274 $head_html = $_render->get_preview_css();
275 $custom_css = TablePress::$model_options->get( 'custom_css' );
276 $use_custom_css = ( TablePress::$model_options->get( 'use_custom_css' ) && '' !== $custom_css );
277 if ( $use_custom_css ) {
278 $head_html .= "<style>\n{$custom_css}\n</style>\n";
279 }
280
281 $body_html = '<div id="tablepress-page"><p>'
282 . __( 'This is a preview of your table.', 'tablepress' ) . ' '
283 . __( 'Because of CSS styling in your theme, the table might look different on your page!', 'tablepress' ) . ' '
284 . __( 'The Table Features for Site Visitors, like sorting, filtering, and pagination, are also not available in this preview!', 'tablepress' ) . '<br />';
285 // Show the instructions string depending on whether the Block Editor is used on the site or not.
286 if ( TablePress::site_uses_block_editor() ) {
287 $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' ) );
288 } else {
289 $body_html .= __( 'To insert a table into a post or page, paste its Shortcode at the desired place in the editor.', 'tablepress' ) . ' '
290 . __( 'Each table has a unique ID that needs to be adjusted in that Shortcode.', 'tablepress' );
291 }
292 $body_html .= '</p>' . $_render->get_output( 'html' ) . '</div>';
293 } else {
294 $head_html = '';
295 $body_html = __( 'The preview could not be loaded.', 'tablepress' );
296 }
297
298 // Generate the response.
299 $response = array(
300 'success' => $success,
301 'head_html' => $head_html,
302 'body_html' => $body_html,
303 );
304 // Buffer all outputs, to prevent errors/warnings being printed that make the JSON invalid.
305 $output_buffer = ob_get_clean();
306 if ( ! empty( $output_buffer ) ) {
307 $response['output_buffer'] = $output_buffer;
308 }
309
310 // Send the response.
311 wp_send_json( $response );
312 }
313
314 /**
315 * Saves the screen options on the "Edit" screen when they are changed.
316 *
317 * @since 2.1.0
318 */
319 public function ajax_action_save_screen_options(): void {
320 // Check if the submitted nonce matches the generated nonce we created earlier, dies -1 on failure.
321 TablePress::check_nonce( 'screen_options', false, '_ajax_nonce', true );
322
323 if ( empty( $_POST['tablepress'] ) ) {
324 wp_die( '-1' );
325 }
326 $screen_options = wp_unslash( $_POST['tablepress'] );
327
328 // Sanitize and limit values to a minimum and a maximum.
329 $new_screen_options = array();
330
331 if ( isset( $screen_options['table_editor_column_width'] ) ) {
332 $new_screen_options['table_editor_column_width'] = absint( $screen_options['table_editor_column_width'] );
333 $new_screen_options['table_editor_column_width'] = max( $new_screen_options['table_editor_column_width'], 30 ); // Minimum width: 30 pixels.
334 $new_screen_options['table_editor_column_width'] = min( $new_screen_options['table_editor_column_width'], 9999 ); // Maximum width: 9999 pixels.
335 }
336
337 if ( isset( $screen_options['table_editor_line_clamp'] ) ) {
338 $new_screen_options['table_editor_line_clamp'] = absint( $screen_options['table_editor_line_clamp'] );
339 $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().
340 }
341
342 if ( empty( $new_screen_options ) ) {
343 wp_die( '-1' );
344 }
345 TablePress::$model_options->update( $new_screen_options );
346
347 // Generate the response.
348 $response = array(
349 'success' => true,
350 );
351
352 // Buffer all outputs, to prevent errors/warnings being printed that make the JSON invalid.
353 $output_buffer = ob_get_clean();
354 if ( ! empty( $output_buffer ) ) {
355 $response['output_buffer'] = $output_buffer;
356 }
357
358 // Send the response.
359 wp_send_json( $response );
360 }
361
362 } // class TablePress_Admin_AJAX_Controller
363