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

326 lines 12.4 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 * @package TablePress
17 * @subpackage Controllers
18 * @author Tobias Bäthge
19 * @since 1.0.0
20 */
21 class TablePress_Admin_AJAX_Controller extends TablePress_Controller {
22
23 /**
24 * Initiate Admin AJAX functionality.
25 *
26 * @since 1.0.0
27 */
28 public function __construct() {
29 // Buffer all outputs, to prevent errors/warnings being printed that make the JSON invalid.
30 ob_start();
31
32 parent::__construct();
33
34 $ajax_actions = array( 'hide_message', 'save_table', 'preview_table' );
35 foreach ( $ajax_actions as $action ) {
36 add_action( "wp_ajax_tablepress_{$action}", array( $this, "ajax_action_{$action}" ) );
37 }
38 }
39
40 /**
41 * Hide a header message on an admin screen.
42 *
43 * @since 1.0.0
44 */
45 public function ajax_action_hide_message() {
46 if ( empty( $_GET['item'] ) ) {
47 wp_die( '0' );
48 } else {
49 $message_item = $_GET['item'];
50 }
51
52 TablePress::check_nonce( 'hide_message', $message_item, '_wpnonce', true );
53
54 if ( ! current_user_can( 'tablepress_list_tables' ) ) {
55 wp_die( '-1' );
56 }
57
58 TablePress::$model_options->update( "message_{$message_item}", false );
59
60 wp_die( '1' );
61 }
62
63 /**
64 * Save the table after the "Save Changes" button on the "Edit" screen has been clicked.
65 *
66 * @since 1.0.0
67 */
68 public function ajax_action_save_table() {
69 if ( empty( $_POST['tablepress'] ) || empty( $_POST['tablepress']['id'] ) ) {
70 wp_die( '-1' );
71 } else {
72 $edit_table = wp_unslash( $_POST['tablepress'] );
73 }
74
75 // Check if the submitted nonce matches the generated nonce we created earlier, dies -1 on failure.
76 TablePress::check_nonce( 'edit', $edit_table['id'], '_ajax_nonce', true );
77
78 // Ignore the request if the current user doesn't have sufficient permissions.
79 if ( ! current_user_can( 'tablepress_edit_table', $edit_table['id'] ) ) {
80 wp_die( '-1' );
81 }
82
83 // Default response data.
84 $success = false;
85 $message = 'error_save';
86 $error_details = '';
87 do { // to be able to "break;" (allows for better readable code)
88 // Load table, without table data, but with options and visibility settings.
89 $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 );
94 break;
95 }
96
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;
105 }
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
110 // Check consistency of new table, and then merge with existing table.
111 $table = TablePress::$model_table->prepare_table( $existing_table, $edit_table, true, true );
112 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 );
116 break;
117 }
118
119 // DataTables Custom Commands can only be edit by trusted users.
120 if ( ! current_user_can( 'unfiltered_html' ) ) {
121 $table['options']['datatables_custom_commands'] = $existing_table['options']['datatables_custom_commands'];
122 }
123
124 // Save updated table.
125 $saved = TablePress::$model_table->save( $table );
126 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 );
130 break;
131 }
132
133 // At this point, the table was saved successfully, possible ID change remains.
134 $success = true;
135 $message = 'success_save';
136
137 // Check if ID change is desired.
138 if ( $table['id'] === $table['new_id'] ) {
139 // If not, we are done.
140 break;
141 }
142
143 // Change table ID.
144 if ( current_user_can( 'tablepress_edit_table_id', $table['id'] ) ) {
145 $id_changed = TablePress::$model_table->change_table_id( $table['id'], $table['new_id'] );
146 if ( ! is_wp_error( $id_changed ) ) {
147 $message = 'success_save_success_id_change';
148 $table['id'] = $table['new_id'];
149 } else {
150 $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 );
154 }
155 } else {
156 $message = 'success_save_error_id_change';
157 $error_details = 'table_id_could_not_be_changed: capability_check_failed';
158 }
159 } while ( false ); // Do-while-loop through this exactly once, to be able to "break;" early.
160
161 // Generate the response.
162
163 // Common data for all responses.
164 $response = array(
165 'success' => $success,
166 'message' => $message,
167 );
168 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'] );
174 }
175 if ( ! empty( $error_details ) ) {
176 $response['error_details'] = esc_html( $error_details );
177 }
178 // Buffer all outputs, to prevent errors/warnings being printed that make the JSON invalid.
179 $output_buffer = ob_get_clean();
180 if ( ! empty( $output_buffer ) ) {
181 $response['output_buffer'] = $output_buffer;
182 }
183
184 // Send the response.
185 wp_send_json( $response );
186 }
187
188 /**
189 * Return the live preview data of table that has non-saved changes.
190 *
191 * @since 1.0.0
192 */
193 public function ajax_action_preview_table() {
194 if ( empty( $_POST['tablepress'] ) || empty( $_POST['tablepress']['id'] ) ) {
195 wp_die( '-1' );
196 } else {
197 $preview_table = wp_unslash( $_POST['tablepress'] );
198 }
199
200 // Check if the submitted nonce matches the generated nonce we created earlier, dies -1 on failure.
201 TablePress::check_nonce( 'preview_table', $preview_table['id'], '_ajax_nonce', true );
202
203 // Ignore the request if the current user doesn't have sufficient permissions.
204 if ( ! current_user_can( 'tablepress_preview_table', $preview_table['id'] ) ) {
205 wp_die( '-1' );
206 }
207
208 // Default response data.
209 $success = false;
210 do { // to be able to "break;" (allows for better readable code)
211 // Load table, without table data, but with options and visibility settings.
212 $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())?
214 break;
215 }
216
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;
222 }
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
227 // Check consistency of new table, and then merge with existing table.
228 $table = TablePress::$model_table->prepare_table( $existing_table, $preview_table, true, true );
229 if ( is_wp_error( $table ) ) {
230 break;
231 }
232
233 // DataTables Custom Commands can only be edit by trusted users.
234 if ( ! current_user_can( 'unfiltered_html' ) ) {
235 $table['options']['datatables_custom_commands'] = $existing_table['options']['datatables_custom_commands'];
236 }
237
238 // If the ID has changed, and the new ID is valid, render with the new ID (important e.g. for CSS classes/HTML ID).
239 if ( $table['id'] !== $table['new_id'] && 0 === preg_match( '/[^a-zA-Z0-9_-]/', $table['new_id'] ) ) {
240 $table['id'] = $table['new_id'];
241 }
242
243 // Sanitize all table data to remove unsafe HTML from the preview output.
244 $table = TablePress::$model_table->sanitize( $table );
245
246 // At this point, the table data is valid and sanitized and can be rendered.
247 $success = true;
248 } while ( false ); // Do-while-loop through this exactly once, to be able to "break;" early.
249
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 if ( $success ) {
254 // Create a render class instance.
255 $_render = TablePress::load_class( 'TablePress_Render', 'class-render.php', 'classes' );
256 // Merge desired options with default render options (see TablePress_Controller_Frontend::shortcode_table()).
257 $default_render_options = $_render->get_default_render_options();
258 /** This filter is documented in controllers/controller-frontend.php */
259 $default_render_options = apply_filters( 'tablepress_shortcode_table_default_shortcode_atts', $default_render_options );
260 $render_options = shortcode_atts( $default_render_options, $table['options'] );
261 /** This filter is documented in controllers/controller-frontend.php */
262 $render_options = apply_filters( 'tablepress_shortcode_table_shortcode_atts', $render_options );
263 $_render->set_input( $table, $render_options );
264 $head_html = $_render->get_preview_css();
265 $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";
268 }
269
270 $body_html = '<div id="tablepress-page"><p>'
271 . __( 'This is a preview of your table.', 'tablepress' ) . ' '
272 . __( '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>';
276 } else {
277 $head_html = '';
278 $body_html = __( 'The preview could not be loaded.', 'tablepress' );
279 }
280
281 // Generate the response.
282 $response = array(
283 'success' => $success,
284 'head_html' => $head_html,
285 'body_html' => $body_html,
286 );
287 // Buffer all outputs, to prevent errors/warnings being printed that make the JSON invalid.
288 $output_buffer = ob_get_clean();
289 if ( ! empty( $output_buffer ) ) {
290 $response['output_buffer'] = $output_buffer;
291 }
292
293 // Send the response.
294 wp_send_json( $response );
295 }
296
297 /**
298 * Retrieve all information of a WP_Error object as a string.
299 *
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.
304 */
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 }
321 }
322 return implode( ";\n", $error_strings );
323 }
324
325 } // class TablePress_Admin_AJAX_Controller
326