PluginProbe
WP Data Access – App Builder for Tables, Forms, Charts, Maps & Dashboards / 5.5.23
WP Data Access – App Builder for Tables, Forms, Charts, Maps & Dashboards v5.5.23
5.5.83 5.5.82 5.5.81 5.5.80 5.5.79 5.5.77 5.5.76 5.5.75 5.5.73 5.5.72 5.5.22 5.5.23 5.5.29 5.5.3 5.5.31 5.5.32 5.5.34 5.5.35 5.5.36 5.5.37 5.5.4 5.5.40 5.5.41 5.5.42 5.5.43 All 159 releases
wp-data-access / WPDataAccess / API / WPDA_Actions.php

WPDA_Actions.php in WP Data Access – App Builder for Tables, Forms, Charts, Maps & Dashboards 5.5.23, at WPDataAccess/API/WPDA_Actions.php

514 lines 23.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace WPDataAccess\API;
4
5 use WPDataAccess\Connection\WPDADB;
6 use WPDataAccess\Plugin_Table_Models\WPDA_Media_Model;
7 use WPDataAccess\Plugin_Table_Models\WPDA_Table_Settings_Model;
8 use WPDataAccess\Plugin_Table_Models\WPDA_User_Menus_Model;
9 use WPDataAccess\WPDA;
10 class WPDA_Actions extends WPDA_API_Core {
11 protected $file_pointer;
12
13 protected $file_content;
14
15 public function register_rest_routes() {
16 register_rest_route( WPDA_API::WPDA_NAMESPACE, 'action/rename', array(
17 'methods' => array('POST'),
18 'callback' => array($this, 'action_rename'),
19 'permission_callback' => '__return_true',
20 'args' => array(
21 'dbs' => $this->get_param( 'dbs', __( 'Local database name or remote connection string (does not accept system schemas)', 'wp-data-access' ) ),
22 'from_tbl' => $this->get_param( 'tbl', __( 'Source table name (does not rename WordPress tables)', 'wp-data-access' ) ),
23 'to_tbl' => $this->get_param( 'tbl', __( 'Destination table name (cannot overwrite existing table)', 'wp-data-access' ) ),
24 ),
25 ) );
26 register_rest_route( WPDA_API::WPDA_NAMESPACE, 'action/copy', array(
27 'methods' => array('POST'),
28 'callback' => array($this, 'action_copy'),
29 'permission_callback' => '__return_true',
30 'args' => array(
31 'from_dbs' => $this->get_param( 'dbs', __( 'Source database name or remote connection string', 'wp-data-access' ) ),
32 'to_dbs' => $this->get_param( 'dbs', __( 'Destination database name or remote connection string', 'wp-data-access' ) ),
33 'from_tbl' => $this->get_param( 'tbl', __( 'Source table name', 'wp-data-access' ) ),
34 'to_tbl' => $this->get_param( 'tbl', __( 'Destination table name', 'wp-data-access' ) ),
35 'copy_data' => array(
36 'required' => true,
37 'type' => 'boolean',
38 'description' => __( 'Copy data from source to destination table', 'wp-data-access' ),
39 'sanitize_callback' => 'sanitize_text_field',
40 'validate_callback' => 'rest_validate_request_arg',
41 ),
42 ),
43 ) );
44 register_rest_route( WPDA_API::WPDA_NAMESPACE, 'action/truncate', array(
45 'methods' => array('POST'),
46 'callback' => array($this, 'action_truncate'),
47 'permission_callback' => '__return_true',
48 'args' => array(
49 'dbs' => $this->get_param( 'dbs', __( 'Local database name or remote connection string (does not accept system schemas)', 'wp-data-access' ) ),
50 'tbl' => $this->get_param( 'tbl', __( 'Source table name (does not truncate WordPress tables)', 'wp-data-access' ) ),
51 ),
52 ) );
53 register_rest_route( WPDA_API::WPDA_NAMESPACE, 'action/drop', array(
54 'methods' => array('POST'),
55 'callback' => array($this, 'action_drop'),
56 'permission_callback' => '__return_true',
57 'args' => array(
58 'dbs' => $this->get_param( 'dbs', __( 'Local database name or remote connection string (does not accept system schemas)', 'wp-data-access' ) ),
59 'tbl' => $this->get_param( 'tbl', __( 'Source table name (does not drop WordPress tables)', 'wp-data-access' ) ),
60 'typ' => $this->get_param( 'typ' ),
61 ),
62 ) );
63 register_rest_route( WPDA_API::WPDA_NAMESPACE, 'action/import', array(
64 'methods' => array('POST'),
65 'callback' => array($this, 'action_import'),
66 'permission_callback' => '__return_true',
67 ) );
68 }
69
70 public function action_import( $request ) {
71 if ( !$this->current_user_can_access( true ) ) {
72 return $this->unauthorized();
73 }
74 if ( !$this->current_user_token_valid( $request, true ) ) {
75 return $this->invalid_nonce();
76 }
77 $dbs = $this->sanitize_db_identifier( $request->get_param( 'dbs' ) );
78 $files = $request->get_file_params();
79 $response = array();
80 $errors = false;
81 if ( 0 === count( $files ) || '' === trim( $dbs ) ) {
82 return $this->bad_request();
83 } else {
84 foreach ( $files as $file ) {
85 // phpcs:disable
86 $temp_file_name = sanitize_text_field( $file['tmp_name'] );
87 // For Windows: do NOT unslash!
88 // phpcs:enable
89 $temp_file_type = sanitize_text_field( wp_unslash( $file['type'] ) );
90 $orig_file_name = sanitize_text_field( wp_unslash( $file['name'] ) );
91 if ( 0 === $file['error'] && is_uploaded_file( $temp_file_name ) ) {
92 if ( 'application/zip' === $temp_file_type || 'application/x-zip' === $temp_file_type || 'application/x-zip-compressed' === $temp_file_type ) {
93 // Process ZIP file.
94 if ( class_exists( '\\ZipArchive' ) ) {
95 $zip = new \ZipArchive();
96 if ( $zip->open( $temp_file_name ) ) {
97 for ($i = 0; $i < $zip->numFiles; $i++) {
98 $this->file_pointer = $zip->getStream( $zip->getNameIndex( $i ) );
99 $status = $this->import( $zip->getNameIndex( $i ), $dbs );
100 if ( isset( $status['status'], $status['msg'] ) ) {
101 $errors = $errors || 'error' === $status['status'];
102 $response[] = array(
103 $zip->getNameIndex( $i ) => array(
104 'status' => $status['status'],
105 'msg' => $status['msg'],
106 'errors' => $status['errors'],
107 ),
108 );
109 }
110 }
111 } else {
112 // Error reading ZIP file.
113 $errors = true;
114 $response[] = array(
115 $orig_file_name => array(
116 'status' => 'error',
117 'msg' => sprintf( __( 'Import failed [error reading ZIP file `%s`]', 'wp-data-access' ), $orig_file_name ),
118 ),
119 );
120 }
121 } else {
122 // ZipArchive not installed.
123 $errors = true;
124 $response[] = array(
125 $orig_file_name => array(
126 'status' => 'error',
127 'msg' => sprintf( __( 'Import failed - ZipArchive not installed %s', 'wp-data-access' ) ),
128 ),
129 );
130 }
131 } else {
132 // Process plain file.
133 $this->file_pointer = fopen( $temp_file_name, 'rb' );
134 $status = $this->import( $orig_file_name, $dbs );
135 if ( isset( $status['status'], $status['msg'] ) ) {
136 $errors = $errors || 'error' === $status['status'];
137 $response[] = array(
138 $orig_file_name => array(
139 'status' => $status['status'],
140 'msg' => $status['msg'],
141 'errors' => $status['errors'],
142 ),
143 );
144 }
145 }
146 }
147 }
148 }
149 if ( $errors ) {
150 $msg = __( 'File(s) imported with errors', 'wp-data-access' );
151 } else {
152 $msg = __( 'File(s) successfully imported', 'wp-data-access' );
153 }
154 return $this->WPDA_Rest_Response( $msg, null, array(
155 'imported' => $response,
156 ) );
157 }
158
159 public function action_drop( $request ) {
160 if ( !$this->current_user_can_access( true ) ) {
161 return $this->unauthorized();
162 }
163 if ( !$this->current_user_token_valid( $request, true ) ) {
164 return $this->invalid_nonce();
165 }
166 $dbs = $request->get_param( 'dbs' );
167 $tbl = $request->get_param( 'tbl' );
168 $typ = $request->get_param( 'typ' );
169 if ( '' === $dbs || '' === $tbl ) {
170 return $this->bad_request();
171 }
172 global $wpdb;
173 if ( $wpdb->dbname === $dbs && in_array( $tbl, $wpdb->tables() ) ) {
174 return $this->unauthorized();
175 }
176 $msg = $this->drop( $dbs, $tbl, $typ );
177 if ( '' === $msg ) {
178 if ( 1 === $typ ) {
179 return $this->WPDA_Rest_Response( __( 'View successfully dropped', 'wp-data-access' ) );
180 } else {
181 return $this->WPDA_Rest_Response( __( 'Table successfully dropped', 'wp-data-access' ) );
182 }
183 } else {
184 return new \WP_Error('error', $msg, array(
185 'status' => 403,
186 ));
187 }
188 }
189
190 public function action_truncate( $request ) {
191 if ( !$this->current_user_can_access( true ) ) {
192 return $this->unauthorized();
193 }
194 if ( !$this->current_user_token_valid( $request, true ) ) {
195 return $this->invalid_nonce();
196 }
197 $dbs = $request->get_param( 'dbs' );
198 $tbl = $request->get_param( 'tbl' );
199 if ( '' === $dbs || '' === $tbl ) {
200 return $this->bad_request();
201 }
202 global $wpdb;
203 if ( $wpdb->dbname === $dbs && in_array( $tbl, $wpdb->tables() ) ) {
204 return $this->unauthorized();
205 }
206 $msg = $this->truncate( $dbs, $tbl );
207 if ( '' === $msg ) {
208 return $this->WPDA_Rest_Response( __( 'Table successfully truncated', 'wp-data-access' ) );
209 } else {
210 return new \WP_Error('error', $msg, array(
211 'status' => 403,
212 ));
213 }
214 }
215
216 public function action_copy( $request ) {
217 if ( !$this->current_user_can_access( true ) ) {
218 return $this->unauthorized();
219 }
220 if ( !$this->current_user_token_valid( $request, true ) ) {
221 return $this->invalid_nonce();
222 }
223 $from_dbs = $request->get_param( 'from_dbs' );
224 $to_dbs = $request->get_param( 'to_dbs' );
225 $from_tbl = $request->get_param( 'from_tbl' );
226 $to_tbl = $request->get_param( 'to_tbl' );
227 $copy_data = $request->get_param( 'copy_data' );
228 if ( '' === $from_dbs || '' === $to_dbs || '' === $from_tbl || '' === $to_tbl ) {
229 return $this->bad_request();
230 }
231 $msg = $this->copy(
232 $from_dbs,
233 $to_dbs,
234 $from_tbl,
235 $to_tbl,
236 $copy_data
237 );
238 if ( '' === $msg ) {
239 return $this->WPDA_Rest_Response( __( 'Table successfully copied', 'wp-data-access' ) );
240 } else {
241 return new \WP_Error('error', $msg, array(
242 'status' => 403,
243 ));
244 }
245 }
246
247 public function action_rename( $request ) {
248 if ( !$this->current_user_can_access( true ) ) {
249 return $this->unauthorized();
250 }
251 if ( !$this->current_user_token_valid( $request, true ) ) {
252 return $this->invalid_nonce();
253 }
254 $dbs = $request->get_param( 'dbs' );
255 $from_tbl = $request->get_param( 'from_tbl' );
256 $to_tbl = $request->get_param( 'to_tbl' );
257 $typ = $request->get_param( 'typ' );
258 if ( '' === $dbs || '' === $from_tbl || '' === $to_tbl ) {
259 return $this->bad_request();
260 }
261 if ( 'information_schema' === $dbs || 'mysql' === $dbs || 'performance_schema' === $dbs || 'sys' === $dbs || '' === $dbs ) {
262 return $this->unauthorized();
263 }
264 global $wpdb;
265 if ( $wpdb->dbname === $dbs && in_array( $from_tbl, $wpdb->tables() ) ) {
266 return $this->unauthorized();
267 }
268 $msg = $this->rename( $dbs, $from_tbl, $to_tbl );
269 if ( '' === $msg ) {
270 if ( 1 === $typ ) {
271 return $this->WPDA_Rest_Response( __( 'View successfully renamed', 'wp-data-access' ) );
272 } else {
273 return $this->WPDA_Rest_Response( __( 'Table successfully renamed', 'wp-data-access' ) );
274 }
275 } else {
276 return new \WP_Error('error', $msg, array(
277 'status' => 403,
278 ));
279 }
280 }
281
282 private function rename( $dbs, $from_tbl, $to_tbl ) {
283 // All values have already been validated and sanitized in the rest route registration.
284 if ( !current_user_can( 'manage_options' ) ) {
285 return 'Unauthorized';
286 }
287 $wpdadb = WPDADB::get_db_connection( $dbs );
288 if ( null === $wpdadb ) {
289 return sprintf( __( 'Remote database %s not available', 'wp-data-access' ), esc_attr( $dbs ) );
290 }
291 $suppress_errors = $wpdadb->suppress_errors;
292 $wpdadb->suppress_errors = true;
293 $wpdadb->query( $wpdadb->prepare( 'rename table `%1s` to `%1s`', array($from_tbl, $to_tbl) ) );
294 $wpdadb->suppress_errors = $suppress_errors;
295 return $wpdadb->last_error;
296 }
297
298 private function copy(
299 $from_dbs,
300 $to_dbs,
301 $from_tbl,
302 $to_tbl,
303 $copy_data
304 ) {
305 // All values have already been validated and sanitized in the rest route registration.
306 if ( !current_user_can( 'manage_options' ) ) {
307 return 'Unauthorized';
308 }
309 $wpdadb_from = WPDADB::get_db_connection( $from_dbs );
310 if ( null === $wpdadb_from ) {
311 return sprintf( __( 'Remote database %s not available', 'wp-data-access' ), esc_attr( $from_dbs ) );
312 }
313 $wpdadb_to = WPDADB::get_db_connection( $to_dbs );
314 if ( null === $wpdadb_to ) {
315 return sprintf( __( 'Remote database %s not available', 'wp-data-access' ), esc_attr( $to_dbs ) );
316 }
317 $suppress_errors_from = $wpdadb_from->suppress_errors;
318 $wpdadb_from->suppress_errors = true;
319 $suppress_errors_to = $wpdadb_to->suppress_errors;
320 $wpdadb_to->suppress_errors = true;
321 // Get create table statement.
322 $wpdadb_from->query( "SET sql_mode = 'NO_TABLE_OPTIONS'" );
323 $sql_cmd = $wpdadb_from->get_results( $wpdadb_from->prepare( 'show create table `%1s`', array($from_tbl) ), 'ARRAY_A' );
324 // Check for errors.
325 if ( '' !== $wpdadb_from->last_error ) {
326 $wpdadb_from->suppress_errors = $suppress_errors_from;
327 $wpdadb_to->suppress_errors = $suppress_errors_to;
328 return $wpdadb_from->last_error;
329 }
330 if ( !isset( $sql_cmd[0]['Create Table'] ) ) {
331 $wpdadb_from->suppress_errors = $suppress_errors_from;
332 $wpdadb_to->suppress_errors = $suppress_errors_to;
333 return 'Create command table failed';
334 }
335 // Update destination table name if applicable.
336 $create_table_statement = $sql_cmd[0]['Create Table'];
337 if ( $from_tbl !== $to_tbl ) {
338 // Modify create table statement
339 $pos = strpos( $create_table_statement, $from_tbl );
340 if ( $pos !== false ) {
341 $create_table_statement = substr_replace(
342 $create_table_statement,
343 $to_tbl,
344 $pos,
345 strlen( $from_tbl )
346 );
347 }
348 }
349 // Create new table.
350 $wpdadb_to->query( $create_table_statement );
351 // Check for errors.
352 if ( '' !== $wpdadb_to->last_error ) {
353 $wpdadb_from->suppress_errors = $suppress_errors_from;
354 $wpdadb_to->suppress_errors = $suppress_errors_to;
355 return $wpdadb_to->last_error;
356 }
357 if ( '1' === $copy_data ) {
358 // Copy data from source to destination table.
359 set_time_limit( 0 );
360 // Prevent time out.
361 // Use a cursor to process all rows and prevent exhausting memory.
362 // Process 100 rows per batch to prevent exhausting memory.
363 $buffer_size = 100;
364 $index = 0;
365 $loop_done = false;
366 while ( !$loop_done ) {
367 // Get rows.
368 $rows = $wpdadb_from->get_results( $wpdadb_from->prepare( 'select * from `%1s` limit %1s offset %1s', array($from_tbl, $buffer_size, $index * $buffer_size) ), 'ARRAY_A' );
369 // Process rows.
370 foreach ( $rows as $row ) {
371 $wpdadb_to->insert( $to_tbl, $row );
372 }
373 if ( 100 > count( $rows ) ) {
374 // No more rows to process.
375 $loop_done = true;
376 }
377 $index++;
378 }
379 }
380 $wpdadb_from->suppress_errors = $suppress_errors_from;
381 $wpdadb_to->suppress_errors = $suppress_errors_to;
382 return '';
383 }
384
385 private function truncate( $dbs, $tbl ) {
386 // All values have already been validated and sanitized in the rest route registration.
387 if ( !current_user_can( 'manage_options' ) ) {
388 return 'Unauthorized';
389 }
390 $wpdadb = WPDADB::get_db_connection( $dbs );
391 if ( null === $wpdadb ) {
392 return sprintf( __( 'Remote database %s not available', 'wp-data-access' ), esc_attr( $dbs ) );
393 }
394 $suppress_errors = $wpdadb->suppress_errors;
395 $wpdadb->suppress_errors = true;
396 $wpdadb->query( $wpdadb->prepare( 'truncate table `%1s`', array($tbl) ) );
397 $wpdadb->suppress_errors = $suppress_errors;
398 return $wpdadb->last_error;
399 }
400
401 private function drop( $dbs, $tbl, $typ ) {
402 // All values have already been validated and sanitized in the rest route registration.
403 if ( !current_user_can( 'manage_options' ) ) {
404 return 'Unauthorized';
405 }
406 $wpdadb = WPDADB::get_db_connection( $dbs );
407 if ( null === $wpdadb ) {
408 return sprintf( __( 'Remote database %s not available', 'wp-data-access' ), esc_attr( $dbs ) );
409 }
410 $suppress_errors = $wpdadb->suppress_errors;
411 $wpdadb->suppress_errors = true;
412 if ( 1 === $typ ) {
413 $wpdadb->query( $wpdadb->prepare( 'drop view `%1s`', array($tbl) ) );
414 } else {
415 $wpdadb->query( $wpdadb->prepare( 'drop table `%1s`', array($tbl) ) );
416 }
417 $this->post_drop_table( $dbs, $tbl );
418 $wpdadb->suppress_errors = $suppress_errors;
419 return $wpdadb->last_error;
420 }
421
422 private function post_drop_table( $dbs, $tbl ) {
423 global $wpdb;
424 $suppress = $wpdb->suppress_errors( true );
425 // Table settings...
426 $wpdb->query( $wpdb->prepare(
427 'delete from `%1s` where wpda_schema_name = %s and wpda_table_name = %s ',
428 // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders
429 array(WPDA::remove_backticks( WPDA_Table_Settings_Model::get_base_table_name() ), $dbs, $tbl)
430 ) );
431 // WordPress media library columns...
432 $wpdb->query( $wpdb->prepare(
433 'delete from `%1s` where media_schema_name = %s and media_table_name = %s ',
434 // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders
435 array(WPDA::remove_backticks( WPDA_Media_Model::get_base_table_name() ), $dbs, $tbl)
436 ) );
437 // Data menus...
438 $wpdb->query( $wpdb->prepare(
439 'delete from `%1s` where menu_schema_name = %s and menu_table_name = %s ',
440 // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders
441 array(WPDA::remove_backticks( WPDA_User_Menus_Model::get_base_table_name() ), $dbs, $tbl)
442 ) );
443 $wpdb->suppress_errors( $suppress );
444 }
445
446 private function import( $file_name, $dbs ) {
447 if ( !current_user_can( 'manage_options' ) ) {
448 return array(
449 'status' => 'error',
450 'msg' => 'Unauthorized',
451 );
452 }
453 $errors = array();
454 global $wpdb;
455 $wpdadb = WPDADB::get_db_connection( $dbs );
456 if ( null === $wpdadb ) {
457 return array(
458 'status' => 'error',
459 'msg' => sprintf( __( 'ERROR - Remote database %s not available', 'wp-data-access' ), esc_attr( $dbs ) ),
460 );
461 }
462 $suppress = $wpdadb->suppress_errors( true );
463 if ( false !== $this->file_pointer ) {
464 while ( !feof( $this->file_pointer ) ) {
465 $this->file_content .= fread( $this->file_pointer, 4096 );
466 // Replace WP prefix and WPDA prefix.
467 $this->file_content = str_replace( '{wp_schema}', $wpdb->dbname, $this->file_content );
468 $this->file_content = str_replace( '{wp_prefix}', $wpdb->prefix, $this->file_content );
469 $this->file_content = str_replace( '{wpda_prefix}', 'wpda', $this->file_content );
470 // for backward compatibility
471 // Find and process SQL statements.
472 $sql_end_unix = strpos( $this->file_content, ";\n" );
473 $sql_end_windows = strpos( $this->file_content, ";\r\n" );
474 while ( false !== $sql_end_unix || false !== $sql_end_windows ) {
475 if ( false === $sql_end_unix ) {
476 $sql_end = $sql_end_windows;
477 } elseif ( false === $sql_end_windows ) {
478 $sql_end = $sql_end_unix;
479 } else {
480 $sql_end = min( $sql_end_unix, $sql_end_windows );
481 }
482 $sql = rtrim( substr( $this->file_content, 0, $sql_end ) );
483 $this->file_content = substr( $this->file_content, strpos( $this->file_content, $sql ) + strlen( $sql ) + 1 );
484 if ( false === $wpdadb->query( $sql ) ) {
485 if ( '' !== $wpdadb->last_error ) {
486 $errors[] = $wpdadb->last_error;
487 }
488 }
489 // Find next SQL statement.
490 $sql_end_unix = strpos( $this->file_content, ";\n" );
491 $sql_end_windows = strpos( $this->file_content, ";\r\n" );
492 }
493 }
494 }
495 $wpdadb->suppress_errors( $suppress );
496 // Process file content.
497 if ( 0 < count( $errors ) ) {
498 return array(
499 'status' => 'error',
500 'msg' => sprintf( __( 'Import `%s` failed [check import file]', 'wp-data-access' ), $file_name ),
501 'errors' => $errors,
502 );
503 } else {
504 // Import succeeded.
505 return array(
506 'status' => 'ok',
507 'msg' => sprintf( __( 'Import `%s` completed succesfully', 'wp-data-access' ), $file_name ),
508 'errors' => $errors,
509 );
510 }
511 }
512
513 }
514