PluginProbe
WP Data Access – App Builder for Tables, Forms, Charts, Maps & Dashboards / 5.5.4
WP Data Access – App Builder for Tables, Forms, Charts, Maps & Dashboards v5.5.4
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_Table.php

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

1,139 lines 45.1 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 stdClass;
6 use WPDataAccess\Connection\WPDADB;
7 use WPDataAccess\Data_Dictionary\WPDA_Dictionary_Access;
8 use WPDataAccess\Data_Dictionary\WPDA_List_Columns_Cache;
9 use WPDataAccess\Plugin_Table_Models\WPDA_Table_Settings_Model;
10 use WPDataAccess\Utilities\WPDA_WP_Media;
11 use WPDataAccess\WPDA;
12 class WPDA_Table extends WPDA_API_Core {
13 const WPDA_SEARCH_MODES = array(
14 'contains',
15 'startsWith',
16 'endsWith',
17 'equals',
18 'notEquals',
19 'empty',
20 'notEmpty',
21 'between',
22 'betweenInclusive',
23 'greaterThan',
24 'greaterThanOrEqualTo',
25 'lessThan',
26 'lessThanOrEqualTo'
27 );
28
29 public function register_rest_routes() {
30 register_rest_route( WPDA_API::WPDA_NAMESPACE, 'table/meta', array(
31 'methods' => array('POST'),
32 'callback' => array($this, 'table_meta'),
33 'permission_callback' => '__return_true',
34 'args' => array(
35 'dbs' => $this->get_param( 'dbs' ),
36 'tbl' => $this->get_param( 'tbl' ),
37 'waa' => array(
38 'required' => false,
39 'type' => 'boolean',
40 'description' => __( 'With admin actions (to support table exports)', 'wp-data-access' ),
41 ),
42 ),
43 ) );
44 register_rest_route( WPDA_API::WPDA_NAMESPACE, 'table/select', array(
45 'methods' => array('GET', 'POST'),
46 'callback' => array($this, 'table_select'),
47 'permission_callback' => '__return_true',
48 'args' => array(
49 'dbs' => $this->get_param( 'dbs' ),
50 'tbl' => $this->get_param( 'tbl' ),
51 'col' => $this->get_param( 'cols' ),
52 'page_index' => $this->get_param( 'page_index' ),
53 'page_size' => $this->get_param( 'page_size' ),
54 'search' => $this->get_param( 'search' ),
55 'search_columns' => $this->get_param( 'search_columns' ),
56 'search_column_fns' => $this->get_param( 'search_column_fns' ),
57 'sorting' => $this->get_param( 'sorting' ),
58 'row_count' => $this->get_param( 'row_count' ),
59 'row_count_estimate' => $this->get_param( 'row_count_estimate' ),
60 'media' => $this->get_param( 'media' ),
61 ),
62 ) );
63 register_rest_route( WPDA_API::WPDA_NAMESPACE, 'table/get', array(
64 'methods' => array('GET', 'POST'),
65 'callback' => array($this, 'table_get'),
66 'permission_callback' => '__return_true',
67 'args' => array(
68 'dbs' => $this->get_param( 'dbs' ),
69 'tbl' => $this->get_param( 'tbl' ),
70 'key' => $this->get_param( 'key' ),
71 'media' => $this->get_param( 'media' ),
72 ),
73 ) );
74 register_rest_route( WPDA_API::WPDA_NAMESPACE, 'table/insert', array(
75 'methods' => array('GET', 'POST'),
76 'callback' => array($this, 'table_insert'),
77 'permission_callback' => '__return_true',
78 'args' => array(
79 'dbs' => $this->get_param( 'dbs' ),
80 'tbl' => $this->get_param( 'tbl' ),
81 'val' => $this->get_param( 'val' ),
82 ),
83 ) );
84 register_rest_route( WPDA_API::WPDA_NAMESPACE, 'table/update', array(
85 'methods' => array('GET', 'POST'),
86 'callback' => array($this, 'table_update'),
87 'permission_callback' => '__return_true',
88 'args' => array(
89 'dbs' => $this->get_param( 'dbs' ),
90 'tbl' => $this->get_param( 'tbl' ),
91 'key' => $this->get_param( 'key' ),
92 'val' => $this->get_param( 'val' ),
93 ),
94 ) );
95 register_rest_route( WPDA_API::WPDA_NAMESPACE, 'table/delete', array(
96 'methods' => array('GET', 'POST'),
97 'callback' => array($this, 'table_delete'),
98 'permission_callback' => '__return_true',
99 'args' => array(
100 'dbs' => $this->get_param( 'dbs' ),
101 'tbl' => $this->get_param( 'tbl' ),
102 'key' => $this->get_param( 'key' ),
103 ),
104 ) );
105 register_rest_route( WPDA_API::WPDA_NAMESPACE, 'table/lov', array(
106 'methods' => array('GET', 'POST'),
107 'callback' => array($this, 'table_lov'),
108 'permission_callback' => '__return_true',
109 'args' => array(
110 'dbs' => $this->get_param( 'dbs' ),
111 'tbl' => $this->get_param( 'tbl' ),
112 'col' => $this->get_param( 'col' ),
113 ),
114 ) );
115 }
116
117 /**
118 * Get table meta info.
119 *
120 * @param WP_REST_Request $request Rest API request.
121 * @return \WP_Error|\WP_REST_Response
122 */
123 public function table_meta( $request ) {
124 $dbs = $request->get_param( 'dbs' );
125 $tbl = $request->get_param( 'tbl' );
126 $waa = $request->get_param( 'waa' );
127 if ( $this->check_table_access(
128 $dbs,
129 $tbl,
130 $request,
131 'select',
132 $msg
133 ) ) {
134 return $this->WPDA_Rest_Response( '', $this->get_table_meta_data( $dbs, $tbl, $waa ) );
135 } else {
136 if ( 'rest_cookie_invalid_nonce' === $msg ) {
137 return $this->invalid_nonce();
138 } else {
139 return new \WP_Error('error', $msg, array(
140 'status' => 401,
141 ));
142 }
143 }
144 }
145
146 /**
147 * Database table query using the full primary key. Must return exactly one row.
148 *
149 * @param WP_REST_Request $request Rest API request.
150 * @return \WP_Error|\WP_REST_Response
151 */
152 public function table_get( $request ) {
153 $dbs = $request->get_param( 'dbs' );
154 $tbl = $request->get_param( 'tbl' );
155 $key = $request->get_param( 'key' );
156 $media = $request->get_param( 'media' );
157 if ( $this->check_table_access(
158 $dbs,
159 $tbl,
160 $request,
161 'select',
162 $msg
163 ) ) {
164 return $this->get(
165 $dbs,
166 $tbl,
167 $key,
168 $media
169 );
170 } else {
171 if ( 'rest_cookie_invalid_nonce' === $msg ) {
172 return $this->invalid_nonce();
173 } else {
174 return new \WP_Error('error', $msg, array(
175 'status' => 401,
176 ));
177 }
178 }
179 }
180
181 /**
182 * Insert one row.
183 *
184 * @param WP_REST_Request $request Rest API request.
185 * @return \WP_Error|\WP_REST_Response
186 */
187 public function table_insert( $request ) {
188 $dbs = $request->get_param( 'dbs' );
189 $tbl = $request->get_param( 'tbl' );
190 $val = $request->get_param( 'val' );
191 if ( $this->check_table_access(
192 $dbs,
193 $tbl,
194 $request,
195 'insert',
196 $msg
197 ) ) {
198 return $this->insert( $dbs, $tbl, $val );
199 } else {
200 if ( 'rest_cookie_invalid_nonce' === $msg ) {
201 return $this->invalid_nonce();
202 } else {
203 return new \WP_Error('error', $msg, array(
204 'status' => 401,
205 ));
206 }
207 }
208 }
209
210 /**
211 * Update uses primary key. Must return exactly one row.
212 *
213 * @param WP_REST_Request $request Rest API request.
214 * @return \WP_Error|\WP_REST_Response
215 */
216 public function table_update( $request ) {
217 $dbs = $request->get_param( 'dbs' );
218 $tbl = $request->get_param( 'tbl' );
219 $key = $request->get_param( 'key' );
220 $val = $request->get_param( 'val' );
221 if ( $this->check_table_access(
222 $dbs,
223 $tbl,
224 $request,
225 'update',
226 $msg
227 ) ) {
228 return $this->update(
229 $dbs,
230 $tbl,
231 $key,
232 $val
233 );
234 } else {
235 if ( 'rest_cookie_invalid_nonce' === $msg ) {
236 return $this->invalid_nonce();
237 } else {
238 return new \WP_Error('error', $msg, array(
239 'status' => 401,
240 ));
241 }
242 }
243 }
244
245 /**
246 * Delete uses primary key. Must return exactly one row.
247 *
248 * @param WP_REST_Request $request Rest API request.
249 * @return \WP_Error|\WP_REST_Response
250 */
251 public function table_delete( $request ) {
252 $dbs = $request->get_param( 'dbs' );
253 $tbl = $request->get_param( 'tbl' );
254 $key = $request->get_param( 'key' );
255 if ( $this->check_table_access(
256 $dbs,
257 $tbl,
258 $request,
259 'delete',
260 $msg
261 ) ) {
262 return $this->delete( $dbs, $tbl, $key );
263 } else {
264 if ( 'rest_cookie_invalid_nonce' === $msg ) {
265 return $this->invalid_nonce();
266 } else {
267 return new \WP_Error('error', $msg, array(
268 'status' => 401,
269 ));
270 }
271 }
272 }
273
274 /**
275 * Database table query to populate a list of values for a specific table/column.
276 *
277 * @param WP_REST_Request $request Rest API request.
278 * @return \WP_Error|\WP_REST_Response
279 */
280 public function table_lov( $request ) {
281 }
282
283 /**
284 * Database table query.
285 *
286 * Supports: searching, ordering and pagination.
287 *
288 * @param WP_REST_Request $request Rest API request.
289 * @return \WP_Error|\WP_REST_Response
290 */
291 public function table_select( $request ) {
292 $dbs = $request->get_param( 'dbs' );
293 $tbl = $request->get_param( 'tbl' );
294 $col = $request->get_param( 'col' );
295 $page_index = $request->get_param( 'page_index' );
296 $page_size = $request->get_param( 'page_size' );
297 $search = $request->get_param( 'search' );
298 $search_columns = $request->get_param( 'search_columns' );
299 $search_column_fns = $request->get_param( 'search_column_fns' );
300 $sorting = $request->get_param( 'sorting' );
301 $row_count = $request->get_param( 'row_count' );
302 $row_count_estimate = $request->get_param( 'row_count_estimate' );
303 $media = $request->get_param( 'media' );
304 if ( $this->check_table_access(
305 $dbs,
306 $tbl,
307 $request,
308 'select',
309 $msg
310 ) ) {
311 return $this->select(
312 $dbs,
313 $tbl,
314 $col,
315 $page_index,
316 $page_size,
317 $search,
318 $search_columns,
319 $search_column_fns,
320 $sorting,
321 $row_count,
322 $row_count_estimate,
323 $media
324 );
325 } else {
326 if ( 'rest_cookie_invalid_nonce' === $msg ) {
327 return $this->invalid_nonce();
328 } else {
329 return new \WP_Error('error', $msg, array(
330 'status' => 401,
331 ));
332 }
333 }
334 }
335
336 /**
337 * Perform query and return result as JSON response.
338 *
339 * @param string $dbs Schema name (database).
340 * @param string $tbl Table Name.
341 * @param array $column_name Column name.
342 * @param array $search Global search.
343 * @param array $search_columns Column filters.
344 * @param array $search_column_fns Column filter fns.
345 * @return \WP_Error|\WP_REST_Response
346 */
347 public function lov(
348 $dbs,
349 $tbl,
350 $column_name,
351 $search,
352 $search_columns,
353 $search_column_fns
354 ) {
355 }
356
357 public function lookup(
358 $dbs,
359 $tbl,
360 $column_key,
361 $column_value,
362 $column_dynamic_values,
363 $default_where
364 ) {
365 $wpdadb = WPDADB::get_db_connection( $dbs );
366 if ( null === $wpdadb ) {
367 // Error connecting.
368 return new \WP_Error('error', "Error connecting to database {$dbs}", array(
369 'status' => 420,
370 ));
371 } else {
372 // Connected, perform queries.
373 $suppress = $wpdadb->suppress_errors( true );
374 $where = '';
375 if ( '' !== trim( $default_where ) ) {
376 if ( 'where' !== strtolower( substr( trim( $default_where ), 0, 5 ) ) ) {
377 $where = "where {$default_where}";
378 } else {
379 $where = $default_where;
380 }
381 }
382 $dynamic_where = array();
383 if ( is_array( $column_dynamic_values ) && 0 < count( $column_dynamic_values ) ) {
384 foreach ( $column_dynamic_values as $key => $value ) {
385 $dynamic_where[] = $wpdadb->prepare( " `{$key}` = %s ", $value );
386 }
387 $where .= (( '' === $where ? ' where ' : ' and ' )) . ' (' . implode( ' and ', $dynamic_where ) . ') ';
388 }
389 if ( strpos( $column_value, ',' ) !== false ) {
390 $columns = explode( ',', $column_value );
391 $dataset = $wpdadb->get_results( $wpdadb->prepare( "\n\t\t\t\t\t\t\tselect distinct `%1s` as 'key'\n\t\t\t\t\t\t\t, `%1s`\n\t\t\t\t\t\t\tfrom `%1s`\n\t\t\t\t\t\t\t{$where}\n\t\t\t\t\t\t\torder by 2\n\t\t\t\t\t\t", array($column_key, implode( '`,`', $columns ), $tbl) ), 'OBJECT' );
392 } else {
393 $dataset = $wpdadb->get_results( $wpdadb->prepare( "\n\t\t\t\t\t\t\tselect distinct `%1s` as 'key'\n\t\t\t\t\t\t\t, `%1s` as 'value' \n\t\t\t\t\t\t\tfrom `%1s` \n\t\t\t\t\t\t\t{$where}\n\t\t\t\t\t\t\torder by 2\n\t\t\t\t\t\t", array($column_key, $column_value, $tbl) ), 'OBJECT' );
394 }
395 $wpdadb->suppress_errors( $suppress );
396 // Send response.
397 if ( '' === $wpdadb->last_error ) {
398 return $this->WPDA_Rest_Response( '', $dataset );
399 } else {
400 return new \WP_Error('error', $wpdadb->last_error, array(
401 'status' => 420,
402 ));
403 }
404 }
405 }
406
407 /**
408 * Perform query and return result as JSON response.
409 *
410 * @param string $dbs Schema name (database).
411 * @param string $tbl Table Name.
412 * @param array $primary Primary (key|value pairs.
413 * @param array $media_columns Media columns.
414 * @param array $column_names Just a plain array containing the column names.
415 * @return \WP_Error|\WP_REST_Response
416 */
417 public function get(
418 $dbs,
419 $tbl,
420 $primary_key,
421 $media_columns,
422 $column_names = array(),
423 $default_where = ''
424 ) {
425 $wpdadb = WPDADB::get_db_connection( $dbs );
426 if ( null === $wpdadb ) {
427 // Error connecting.
428 return new \WP_Error('error', "Error connecting to database {$dbs}", array(
429 'status' => 420,
430 ));
431 } else {
432 // Connected, perform queries.
433 $suppress = $wpdadb->suppress_errors( true );
434 $where = '';
435 foreach ( $primary_key as $primary_key_column => $primary_key_value ) {
436 $where = ( '' === $where ? ' where ' : $where . ' and ' );
437 $where .= $wpdadb->prepare( " `%1s` = %s ", array($primary_key_column, $primary_key_value) );
438 }
439 if ( '' !== $default_where ) {
440 if ( '' === $where ) {
441 $where = $default_where;
442 } else {
443 $where .= " and {$default_where} ";
444 }
445 }
446 $selected_columns = '*';
447 if ( 0 < count( $column_names ) ) {
448 $selected_columns = '`' . implode( '`,`', array_map( function ( $column_name ) {
449 return WPDA::remove_backticks( $column_name );
450 }, $column_names ) ) . '`';
451 }
452 $dataset = $wpdadb->get_results( $wpdadb->prepare( "\n\t\t\t\t\t\t\tselect {$selected_columns}\n\t\t\t\t\t\t\tfrom `%1s`\n\t\t\t\t\t\t\t{$where}\n\t\t\t\t\t\t", array($tbl) ), 'ARRAY_A' );
453 // Prepare debug info.
454 if ( 'on' === WPDA::get_option( WPDA::OPTION_PLUGIN_DEBUG ) ) {
455 $debug = array(
456 'debug' => array(
457 'where' => $where,
458 ),
459 );
460 } else {
461 $debug = null;
462 }
463 $wpdadb->suppress_errors( $suppress );
464 // Send response.
465 if ( 0 === count( $dataset ) ) {
466 return new \WP_Error('error', "No data found", array(
467 'status' => 420,
468 ));
469 } elseif ( 1 === count( $dataset ) ) {
470 $media = array();
471 if ( 0 < count( $media_columns ) ) {
472 foreach ( $media_columns as $media_column_name => $media_column_type ) {
473 if ( isset( $dataset[0][$media_column_name] ) ) {
474 if ( in_array( $media_column_type, [
475 'WP-Image',
476 'WP-Attachment',
477 'WP-Audio',
478 'WP-Video'
479 ] ) ) {
480 $media[$media_column_name] = WPDA_WP_Media::get_media_url( $dataset[0][$media_column_name] );
481 }
482 }
483 }
484 }
485 $context = array();
486 $context['media'] = $media;
487 if ( isset( $debug['debug'] ) && 'on' === WPDA::get_option( WPDA::OPTION_PLUGIN_DEBUG ) ) {
488 $context['debug'] = $debug['debug'];
489 }
490 return $this->WPDA_Rest_Response( '', $dataset, $context );
491 } else {
492 return new \WP_Error('error', "Invalid arguments", array(
493 'status' => 420,
494 ));
495 }
496 }
497 }
498
499 public function insert( $dbs, $tbl, $column_values ) {
500 $wpdadb = WPDADB::get_db_connection( $dbs );
501 if ( null === $wpdadb ) {
502 // Error connecting.
503 return new \WP_Error('error', "Error connecting to database {$dbs}", array(
504 'status' => 420,
505 ));
506 } else {
507 // Sanitize column names and values.
508 $sanitized_column_values = self::sanitize_column_values( $dbs, $tbl, $column_values );
509 if ( false === $sanitized_column_values ) {
510 return new \WP_Error('error', "Invalid arguments", array(
511 'status' => 420,
512 ));
513 }
514 // Insert row.
515 $rows_inserted = $wpdadb->insert( $tbl, $sanitized_column_values );
516 // Send response.
517 if ( 1 === $rows_inserted ) {
518 return $this->WPDA_Rest_Response( __( 'Row successfully inserted', 'wp-data-access' ), null, array(
519 'insert_id' => $wpdadb->insert_id,
520 ) );
521 } else {
522 if ( '' !== $wpdadb->last_error ) {
523 return new \WP_Error('error', $wpdadb->last_error, array(
524 'status' => 420,
525 ));
526 } else {
527 return new \WP_Error('error', 'Insert failed', array(
528 'status' => 420,
529 ));
530 }
531 }
532 }
533 }
534
535 public function update(
536 $dbs,
537 $tbl,
538 $primary_key,
539 $column_values,
540 $column_names = array()
541 ) {
542 $wpdadb = WPDADB::get_db_connection( $dbs );
543 if ( null === $wpdadb ) {
544 // Error connecting.
545 return new \WP_Error('error', "Error connecting to database {$dbs}", array(
546 'status' => 420,
547 ));
548 } else {
549 // Sanitize column names and values.
550 $sanitized_column_values = self::sanitize_column_values( $dbs, $tbl, $column_values );
551 if ( false === $sanitized_column_values ) {
552 return new \WP_Error('error', "Invalid arguments", array(
553 'status' => 420,
554 ));
555 }
556 // Update row.
557 $rows_inserted = $wpdadb->update( $tbl, $sanitized_column_values, $primary_key );
558 // Send response.
559 if ( 0 === $rows_inserted ) {
560 return $this->WPDA_Rest_Response_Info( 'Nothing to update' );
561 } elseif ( 1 === $rows_inserted ) {
562 $context = null;
563 if ( 0 < count( $column_names ) ) {
564 // Return updated values
565 $updated_row = $this->get(
566 $dbs,
567 $tbl,
568 $primary_key,
569 $column_names
570 );
571 if ( isset( $updated_row->data['data'][0] ) ) {
572 $updated_values = $updated_row->data['data'][0];
573 $updated_context = array();
574 foreach ( $updated_values as $key => $value ) {
575 if ( !isset( $column_values[$key] ) ) {
576 $updated_context[$key] = $value;
577 }
578 }
579 if ( 0 < count( $updated_context ) ) {
580 $context = array(
581 'updated' => $updated_context,
582 );
583 }
584 }
585 }
586 return $this->WPDA_Rest_Response( __( 'Row successfully updated', 'wp-data-access' ), null, $context );
587 } else {
588 if ( '' !== $wpdadb->last_error ) {
589 return new \WP_Error('error', $wpdadb->last_error, array(
590 'status' => 420,
591 ));
592 } else {
593 return new \WP_Error('error', 'Update failed', array(
594 'status' => 420,
595 ));
596 }
597 }
598 }
599 }
600
601 public function delete( $dbs, $tbl, $primary_key ) {
602 $wpdadb = WPDADB::get_db_connection( $dbs );
603 if ( null === $wpdadb ) {
604 // Error connecting.
605 return new \WP_Error('error', "Error connecting to database {$dbs}", array(
606 'status' => 420,
607 ));
608 } else {
609 // Delete row.
610 $rows_deleted = $wpdadb->delete( $tbl, $primary_key );
611 // Send response.
612 if ( 0 === $rows_deleted ) {
613 return $this->WPDA_Rest_Response_Info( __( 'No data found', 'wp-data-access' ) );
614 } elseif ( 1 === $rows_deleted ) {
615 return $this->WPDA_Rest_Response( __( 'Row successfully deleted', 'wp-data-access' ) );
616 } else {
617 if ( '' !== $wpdadb->last_error ) {
618 return new \WP_Error('error', $wpdadb->last_error, array(
619 'status' => 420,
620 ));
621 } else {
622 return new \WP_Error('error', 'Delete failed', array(
623 'status' => 420,
624 ));
625 }
626 }
627 }
628 }
629
630 private function generate_lookup_condition(
631 $wpdadb,
632 $lookups,
633 $column_name,
634 $search_values,
635 $search_column_fns,
636 $filter_mode = null
637 ) {
638 $lookup = $lookups[$column_name];
639 $lookup_table = $lookup['tbl'];
640 $lookup_key = $lookup['key'];
641 $lookup_columns = explode( ',', $lookup['value'] );
642 $lookup_where = array();
643 foreach ( $lookup_columns as $lookup_column ) {
644 foreach ( $search_values as $search_value ) {
645 $lookup_where[] = $this->add_filter(
646 $wpdadb,
647 $lookup_column,
648 ( $filter_mode !== null ? $filter_mode : $search_column_fns[$column_name] ),
649 $search_value
650 );
651 }
652 }
653 if ( 0 < count( $lookup_where ) ) {
654 return $wpdadb->prepare( ' `%1s` in ( select `%1s` from `%1s` where (' . implode( ' or ', $lookup_where ) . ') ) ', array(
655 $column_name,
656 $lookup_key,
657 $lookup_table,
658 $lookup_columns[0],
659 "%{$search_values[0]}%"
660 ) );
661 } else {
662 return null;
663 }
664 }
665
666 /**
667 * Perform query and return result as JSON response.
668 *
669 * @param string $dbs Schema name (database).
670 * @param string $tbl Table Name.
671 * @param string $column_names Column Names.
672 * @param string $page_index Page number.
673 * @param string $page_size Rows per page.
674 * @param string $search Filter.
675 * @param string $search_columns Column search filters.
676 * @param string $search_column_fns Column search filter modes.
677 * @param string $Sorting Order by.
678 * @param integer $last_row_count Row count previous request.
679 * @param string $row_count_estimate Indicates if row count estimate should be used.
680 * @param string $media_columns Media columns.
681 * @param string $default_where Defaul where clause
682 * @param string $default_orderby Defaul order by clause
683 * @return \WP_Error|\WP_REST_Response
684 */
685 public function select(
686 $dbs,
687 $tbl,
688 $column_names,
689 $page_index,
690 $page_size,
691 $search,
692 $search_columns,
693 $search_column_fns,
694 $sorting,
695 $last_row_count,
696 $row_count_estimate,
697 $media_columns,
698 $default_where = '',
699 $default_orderby = '',
700 $lookups = array(),
701 $md = array()
702 ) {
703 $wpdadb = WPDADB::get_db_connection( $dbs );
704 if ( null === $wpdadb ) {
705 // Error connecting.
706 return new \WP_Error('error', "Error connecting to database {$dbs}", array(
707 'status' => 420,
708 ));
709 } else {
710 $suppress = $wpdadb->suppress_errors( true );
711 if ( '' !== trim( $default_where ) && 'where' !== strtolower( substr( trim( $default_where ), 0, 5 ) ) ) {
712 $where = "where {$default_where}";
713 } else {
714 $where = $default_where;
715 }
716 // Global search.
717 $where_global = array();
718 if ( null !== $search && "" !== $search ) {
719 foreach ( $column_names as $column_name => $queryable ) {
720 if ( $queryable ) {
721 if ( isset( $lookups[$column_name] ) ) {
722 // Perform look search.
723 $condition = $this->generate_lookup_condition(
724 $wpdadb,
725 $lookups,
726 $column_name,
727 array($search),
728 array(),
729 'contains'
730 );
731 if ( null !== $condition ) {
732 $where_global[] = $condition;
733 }
734 } else {
735 $where_global[] = $wpdadb->prepare( " `%1s` like '%s' ", array($column_name, '%' . esc_sql( $search ) . '%') );
736 }
737 }
738 }
739 }
740 if ( 0 < count( $where_global ) ) {
741 $where .= (( '' === trim( $where ) ? ' where ' : ' and ' )) . $this->add_condition( $where_global, 'or' );
742 }
743 // Order by.
744 $sqlorder = '';
745 if ( is_array( $sorting ) && 0 < count( $sorting ) ) {
746 foreach ( $sorting as $sort ) {
747 if ( '' === $sqlorder ) {
748 $sqlorder = 'order by ';
749 } else {
750 $sqlorder .= ',';
751 }
752 $sqlorder .= sanitize_sql_orderby( $sort['id'] ) . ' ' . (( $sort['desc'] ? 'desc' : 'asc' ));
753 }
754 }
755 if ( '' === $sqlorder && '' !== trim( $default_orderby ) ) {
756 $sqlorder = $default_orderby;
757 }
758 // Pagination.
759 if ( !is_numeric( $page_size ) ) {
760 $page_size = 10;
761 }
762 $offset = $page_index * $page_size;
763 // Calculate offset.
764 if ( !is_numeric( $offset ) ) {
765 $offset = 0;
766 }
767 // Prepare query.
768 $sql = "\n\t\t\t\t\tselect `" . implode( "`,`", array_keys( $column_names ) ) . "`\n\t\t\t\t\tfrom `%1s`\n\t\t\t\t\t{$where}\n\t\t\t\t\t{$sqlorder}\n\t\t\t\t" . (( 0 < $page_size ? " limit {$page_size} offset {$offset} " : '' ));
769 // Prepare debug info.
770 if ( 'on' === WPDA::get_option( WPDA::OPTION_PLUGIN_DEBUG ) ) {
771 $debug = array(
772 'debug' => array(
773 'sql' => preg_replace( "/\\s+/", " ", $sql ),
774 'where' => $where,
775 'order by' => $sqlorder,
776 ),
777 );
778 } else {
779 $debug = null;
780 }
781 // Perform query.
782 $dataset = $wpdadb->get_results( $wpdadb->prepare( $sql, array($tbl) ), 'ARRAY_A' );
783 if ( $wpdadb->last_error ) {
784 // Handle SQL errors.
785 return new \WP_Error('error', $wpdadb->last_error, array(
786 'status' => 420,
787 'debug' => ( isset( $debug['debug'] ) ? $debug : null ),
788 ));
789 }
790 if ( is_numeric( $last_row_count ) and 0 <= $last_row_count ) {
791 // Prevents an extra query.
792 $rowcount = $last_row_count;
793 } else {
794 $estimate = false;
795 if ( '1' === $row_count_estimate && '' === $where ) {
796 // Perform row count estimate
797 $countrows = $wpdadb->get_results( $wpdadb->prepare( "\n\t\t\t\t\t\t\t\t\tselect table_rows as rowcount\n\t\t\t\t\t\t\t\t\t from information_schema.tables\n\t\t\t\t\t\t\t\t\twhere table_schema = %s\n\t\t\t\t\t\t\t\t\t and table_name = %s\n\t\t\t\t\t\t\t\t", [$wpdadb->dbname, $tbl] ), 'ARRAY_A' );
798 if ( isset( $countrows[0]['rowcount'] ) ) {
799 $estimate = true;
800 }
801 }
802 if ( !$estimate ) {
803 // (Re)Count rows.
804 $countrows = $wpdadb->get_results( $wpdadb->prepare( "\n\t\t\t\t\t\t\t\t\tselect count(1) as rowcount\n\t\t\t\t\t\t\t\t\tfrom `%1s`\n\t\t\t\t\t\t\t\t\t{$where}\n\t\t\t\t\t\t\t\t", array($tbl) ), 'ARRAY_A' );
805 }
806 if ( $wpdadb->last_error ) {
807 // Handle SQL errors.
808 return new \WP_Error('error', $wpdadb->last_error, array(
809 'status' => 420,
810 ));
811 }
812 if ( isset( $countrows[0]['rowcount'] ) ) {
813 $rowcount = $countrows[0]['rowcount'];
814 } else {
815 $rowcount = 0;
816 }
817 }
818 // Add context node to response
819 $context = array();
820 if ( isset( $debug['debug'] ) && 'on' === WPDA::get_option( WPDA::OPTION_PLUGIN_DEBUG ) ) {
821 $context['debug'] = $debug['debug'];
822 }
823 if ( 0 < count( $media_columns ) ) {
824 // Handle WP media library
825 $media = array();
826 for ($i = 0; $i < count( $dataset ); $i++) {
827 $media_row = array();
828 foreach ( $media_columns as $media_column_name => $media_column_type ) {
829 if ( isset( $dataset[$i][$media_column_name] ) ) {
830 $media_row[$media_column_name] = WPDA_WP_Media::get_media_url( $dataset[$i][$media_column_name] );
831 }
832 }
833 $media[] = $media_row;
834 }
835 // Add media to context node
836 $context['media'] = $media;
837 }
838 $wpdadb->suppress_errors( $suppress );
839 // Send response.
840 $response = $this->WPDA_Rest_Response(
841 '',
842 $dataset,
843 $context,
844 array(
845 'rowCount' => $rowcount,
846 )
847 );
848 $response->header( 'X-WP-Total', $rowcount );
849 // Total rows for this query.
850 if ( 0 < $page_size ) {
851 $pagecount = floor( $rowcount / $page_size );
852 if ( $pagecount != $rowcount / $page_size ) {
853 // phpcs:ignore WordPress.PHP.StrictComparisons
854 $pagecount++;
855 }
856 } else {
857 // Prevent division by zero
858 $pagecount = 0;
859 }
860 $response->header( 'X-WP-TotalPages', $pagecount );
861 // Total pages for this query.
862 return $response;
863 }
864 }
865
866 public function add_filter(
867 $wpdadb,
868 $searchColumn,
869 $search_column_fns,
870 $searchValue
871 ) {
872 }
873
874 private function add_condition( $where_lines, $operand = 'and' ) {
875 if ( 0 < count( array_filter( $where_lines ) ) ) {
876 // Apply all searches.
877 return ' ( (' . implode( ") {$operand} (", array_filter( $where_lines ) ) . ') ) ';
878 } else {
879 return "";
880 }
881 }
882
883 /**
884 * Get table meta data.
885 *
886 * @param string $dbs Database schema name.
887 * @param string $tbl Database table name.
888 * @param string $waa With admin actions.
889 * @return array\object
890 */
891 public function get_table_meta_data( $dbs, $tbl, $waa ) {
892 $sql_create_table = '';
893 if ( current_user_can( 'manage_options' ) ) {
894 // Admin user has access to all resources
895 $access = array(
896 'select' => array('POST'),
897 'insert' => array('POST'),
898 'update' => array('POST'),
899 'delete' => array('POST'),
900 );
901 // Get create table script
902 $wpdadb = WPDADB::get_db_connection( $dbs );
903 if ( null !== $wpdadb ) {
904 $suppress_errors = $wpdadb->suppress_errors;
905 $wpdadb->suppress_errors = true;
906 $wpdadb->query( "SET sql_mode = 'NO_TABLE_OPTIONS'" );
907 $sql = $wpdadb->get_results( $wpdadb->prepare( 'show create table `%1s`', array($tbl) ), 'ARRAY_N' );
908 if ( isset( $sql[0][1] ) ) {
909 $sql_create_table = $sql[0][1];
910 }
911 $wpdadb->suppress_errors = $suppress_errors;
912 }
913 } else {
914 $access = $this->get_table_access( $dbs, $tbl );
915 }
916 $settings = new stdClass();
917 if ( null !== $access ) {
918 $columns = WPDA_List_Columns_Cache::get_list_columns( $dbs, $tbl );
919 $settings_db = WPDA_Table_Settings_Model::query( $tbl, $dbs );
920 if ( isset( $settings_db[0]['wpda_table_settings'] ) ) {
921 $settings = json_decode( $settings_db[0]['wpda_table_settings'] );
922 // Remove old settings from response.
923 unset($settings->form_labels);
924 unset($settings->list_labels);
925 unset($settings->custom_settings);
926 unset($settings->search_settings);
927 }
928 $settings->ui = WPDA_Settings::get_admin_settings( $dbs, $tbl );
929 $rest_api = get_option( WPDA_API::WPDA_REST_API_TABLE_ACCESS );
930 if ( isset( $rest_api[$dbs][$tbl] ) ) {
931 $settings->rest_api = $rest_api[$dbs][$tbl];
932 }
933 $settings->env = $this->get_env();
934 $wp_nonce_action_alter = "wpda-alter-{$tbl}";
935 $wp_nonce_alter = wp_create_nonce( $wp_nonce_action_alter );
936 $wp_nonce_refresh = null;
937 $connect = null;
938 global $wpdb;
939 $settings->wp = [
940 'roles' => $this->get_wp_roles(),
941 'users' => $this->get_wp_users(),
942 'home' => admin_url( 'admin.php' ),
943 'homea' => admin_url( 'admin-ajax.php' ),
944 'tables' => array_values( $wpdb->tables() ),
945 'date_format' => get_option( 'date_format' ),
946 'time_format' => get_option( 'time_format' ),
947 'alter' => $wp_nonce_alter,
948 'refresh' => $wp_nonce_refresh,
949 'connect' => $connect,
950 ];
951 if ( true === $waa ) {
952 $settings->wp['aonce'] = implode( '-', array(
953 wp_create_nonce( 'wpda-export-' . json_encode( $tbl ) ),
954 // Table export
955 wp_create_nonce( 'wpda-rename-' . $tbl ),
956 ) );
957 }
958 $media = $this->get_media( $dbs, $tbl, $columns->get_table_columns() );
959 }
960 return array(
961 'columns' => $columns->get_table_columns(),
962 'table_labels' => $columns->get_table_header_labels(),
963 'form_labels' => $columns->get_table_column_headers(),
964 'primary_key' => $columns->get_table_primary_key(),
965 'access' => $access,
966 'settings' => $settings,
967 'media' => $media['media'],
968 'wp_media' => $media['wp_media'],
969 'table_info' => $this->get_table_info( $dbs, $tbl ),
970 'create' => $sql_create_table,
971 );
972 }
973
974 private function get_table_access( $dbs, $tbl ) {
975 if ( current_user_can( 'manage_options' ) ) {
976 // Check administrator rights
977 if ( is_admin() ) {
978 $access = WPDA_Dictionary_Access::check_table_access_backend( $dbs, $tbl, $done );
979 } else {
980 $access = WPDA_Dictionary_Access::check_table_access_frontend( $dbs, $tbl, $done );
981 }
982 if ( $access ) {
983 // Administrator access granted
984 return array(
985 'select' => array('POST'),
986 'insert' => array('POST'),
987 'update' => array('POST'),
988 'delete' => array('POST'),
989 );
990 }
991 }
992 $tables = get_option( WPDA_API::WPDA_REST_API_TABLE_ACCESS );
993 if ( false !== $tables && isset( $tables[$dbs][$tbl] ) && is_array( $tables[$dbs][$tbl] ) ) {
994 $table = $tables[$dbs][$tbl];
995 $table_access = new \stdClass();
996 $table_access->select = $this->get_table_access_action( $table, 'select' );
997 $table_access->insert = $this->get_table_access_action( $table, 'insert' );
998 $table_access->update = $this->get_table_access_action( $table, 'update' );
999 $table_access->delete = $this->get_table_access_action( $table, 'delete' );
1000 return $table_access;
1001 }
1002 return false;
1003 }
1004
1005 private function get_table_access_action( $table, $action ) {
1006 if ( isset( $table[$action]['authorization'], $table[$action]['methods'] ) && is_array( $table[$action]['methods'] ) && 0 < count( $table[$action]['methods'] ) ) {
1007 if ( 'anonymous' === $table[$action]['authorization'] ) {
1008 return $table[$action]['methods'];
1009 } else {
1010 // Check authorized users
1011 if ( isset( $table[$action]['authorized_users'] ) && is_array( $table[$action]['authorized_users'] ) && 0 < count( $table[$action]['authorized_users'] ) && in_array( (string) $this->get_user_login(), $table[$action]['authorized_users'] ) ) {
1012 return $table[$action]['methods'];
1013 }
1014 // Check authorized roles
1015 if ( isset( $table[$action]['authorized_roles'] ) && is_array( $table[$action]['authorized_roles'] ) && 0 < count( $table[$action]['authorized_roles'] ) && 0 < count( array_intersect( $this->get_user_roles(), $table[$action]['authorized_roles'] ) ) ) {
1016 return $table[$action]['methods'];
1017 }
1018 }
1019 }
1020 return array();
1021 }
1022
1023 /**
1024 * Check if access is grant for requested database/table.
1025 *
1026 * @param string $dbs Remote or local database connection string.
1027 * @param string $tbl Database table name.
1028 * @param onject $request Request object.
1029 * @param string $action Possible values: select, insert, update, delete.
1030 * @return bool
1031 */
1032 private function check_table_access(
1033 $dbs,
1034 $tbl,
1035 $request,
1036 $action,
1037 &$msg = ''
1038 ) {
1039 if ( current_user_can( 'manage_options' ) ) {
1040 // Grant access to administrators always.
1041 return true;
1042 }
1043 $tables = get_option( WPDA_API::WPDA_REST_API_TABLE_ACCESS );
1044 if ( false === $tables ) {
1045 // No tables.
1046 $msg = __( 'Unauthorized', 'wp-data-access' );
1047 return false;
1048 }
1049 if ( !(isset( $tables[$dbs][$tbl][$action]['methods'] ) && is_array( $tables[$dbs][$tbl][$action]['methods'] )) ) {
1050 // No methods.
1051 $msg = __( 'Unauthorized', 'wp-data-access' );
1052 return false;
1053 } else {
1054 if ( !in_array( $request->get_method(), $tables[$dbs][$tbl][$action]['methods'] ) ) {
1055 //phpcs:ignore - 8.1 proof
1056 $msg = __( 'Unauthorized', 'wp-data-access' );
1057 return false;
1058 }
1059 }
1060 if ( !isset( $tables[$dbs][$tbl][$action]['authorization'] ) ) {
1061 // No authorization.
1062 $msg = __( 'Unauthorized', 'wp-data-access' );
1063 return false;
1064 } else {
1065 if ( 'anonymous' === $tables[$dbs][$tbl][$action]['authorization'] ) {
1066 // Access granted to all users.
1067 return true;
1068 }
1069 }
1070 global $wp_rest_auth_cookie;
1071 if ( true !== $wp_rest_auth_cookie ) {
1072 // No anonymous access.
1073 $msg = __( 'Unauthorized', 'wp-data-access' );
1074 return false;
1075 } else {
1076 if ( 'authorized' !== $tables[$dbs][$tbl][$action]['authorization'] ) {
1077 // Authorization check.
1078 $msg = __( 'Unauthorized', 'wp-data-access' );
1079 return false;
1080 }
1081 // Authorized access requires a valid nonce.
1082 if ( !wp_verify_nonce( $request->get_header( 'X-WP-Nonce' ), 'wp_rest' ) ) {
1083 $msg = 'rest_cookie_invalid_nonce';
1084 return false;
1085 }
1086 if ( !(isset( $tables[$dbs][$tbl][$action]['authorized_users'] ) && is_array( $tables[$dbs][$tbl][$action]['authorized_users'] )) ) {
1087 // No users.
1088 $msg = __( 'Unauthorized', 'wp-data-access' );
1089 return false;
1090 } else {
1091 $requesting_user_login = $this->get_user_login();
1092 if ( 0 < count( $tables[$dbs][$tbl][$action]['authorized_users'] ) && in_array( $requesting_user_login, $tables[$dbs][$tbl][$action]['authorized_users'] ) ) {
1093 return true;
1094 }
1095 }
1096 if ( !(isset( $tables[$dbs][$tbl][$action]['authorized_roles'] ) && is_array( $tables[$dbs][$tbl][$action]['authorized_roles'] )) ) {
1097 // No roles.
1098 $msg = __( 'Unauthorized', 'wp-data-access' );
1099 return false;
1100 } else {
1101 $requesting_user_roles = $this->get_user_roles();
1102 if ( false === $requesting_user_roles ) {
1103 $requesting_user_roles = array();
1104 }
1105 if ( 0 < count( $tables[$dbs][$tbl][$action]['authorized_roles'] ) && 0 < count( array_intersect( $requesting_user_roles, $tables[$dbs][$tbl][$action]['authorized_roles'] ) ) ) {
1106 return true;
1107 }
1108 }
1109 $msg = __( 'Unauthorized', 'wp-data-access' );
1110 return false;
1111 }
1112 }
1113
1114 private function sanitize_column_values( $dbs, $tbl, $column_values ) {
1115 $wpda_list_columns = WPDA_List_Columns_Cache::get_list_columns( $dbs, $tbl );
1116 $sanitized_column_values = [];
1117 foreach ( $column_values as $column_name => $column_value ) {
1118 $column_value = $column_values[$column_name];
1119 switch ( $wpda_list_columns->get_column_data_type( $column_name ) ) {
1120 case 'tinytext':
1121 case 'text':
1122 case 'mediumtext':
1123 case 'longtext':
1124 if ( null !== $column_value ) {
1125 $column_value = wp_kses_post( $column_value );
1126 }
1127 break;
1128 default:
1129 if ( null !== $column_value ) {
1130 $column_value = sanitize_text_field( $column_value );
1131 }
1132 }
1133 $sanitized_column_values[$this->sanitize_db_identifier( $column_name )] = $column_value;
1134 }
1135 return $sanitized_column_values;
1136 }
1137
1138 }
1139