PluginProbe
WP Data Access – App Builder for Tables, Forms, Charts, Maps & Dashboards / 5.5.3
WP Data Access – App Builder for Tables, Forms, Charts, Maps & Dashboards v5.5.3
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.3, at WPDataAccess/API/WPDA_Table.php

1,104 lines 43.5 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 ) {
541 $wpdadb = WPDADB::get_db_connection( $dbs );
542 if ( null === $wpdadb ) {
543 // Error connecting.
544 return new \WP_Error('error', "Error connecting to database {$dbs}", array(
545 'status' => 420,
546 ));
547 } else {
548 // Sanitize column names and values.
549 $sanitized_column_values = self::sanitize_column_values( $dbs, $tbl, $column_values );
550 if ( false === $sanitized_column_values ) {
551 return new \WP_Error('error', "Invalid arguments", array(
552 'status' => 420,
553 ));
554 }
555 // Update row.
556 $rows_inserted = $wpdadb->update( $tbl, $sanitized_column_values, $primary_key );
557 // Send response.
558 if ( 0 === $rows_inserted ) {
559 return $this->WPDA_Rest_Response_Info( 'Nothing to update' );
560 } elseif ( 1 === $rows_inserted ) {
561 return $this->WPDA_Rest_Response( __( 'Row successfully updated', 'wp-data-access' ) );
562 } else {
563 if ( '' !== $wpdadb->last_error ) {
564 return new \WP_Error('error', $wpdadb->last_error, array(
565 'status' => 420,
566 ));
567 } else {
568 return new \WP_Error('error', 'Update failed', array(
569 'status' => 420,
570 ));
571 }
572 }
573 }
574 }
575
576 public function delete( $dbs, $tbl, $primary_key ) {
577 $wpdadb = WPDADB::get_db_connection( $dbs );
578 if ( null === $wpdadb ) {
579 // Error connecting.
580 return new \WP_Error('error', "Error connecting to database {$dbs}", array(
581 'status' => 420,
582 ));
583 } else {
584 // Delete row.
585 $rows_deleted = $wpdadb->delete( $tbl, $primary_key );
586 // Send response.
587 if ( 0 === $rows_deleted ) {
588 return $this->WPDA_Rest_Response_Info( __( 'No data found', 'wp-data-access' ) );
589 } elseif ( 1 === $rows_deleted ) {
590 return $this->WPDA_Rest_Response( __( 'Row successfully deleted', 'wp-data-access' ) );
591 } else {
592 if ( '' !== $wpdadb->last_error ) {
593 return new \WP_Error('error', $wpdadb->last_error, array(
594 'status' => 420,
595 ));
596 } else {
597 return new \WP_Error('error', 'Delete failed', array(
598 'status' => 420,
599 ));
600 }
601 }
602 }
603 }
604
605 private function generate_lookup_condition(
606 $wpdadb,
607 $lookups,
608 $column_name,
609 $search_values,
610 $search_column_fns,
611 $filter_mode = null
612 ) {
613 $lookup = $lookups[$column_name];
614 $lookup_table = $lookup['tbl'];
615 $lookup_key = $lookup['key'];
616 $lookup_columns = explode( ',', $lookup['value'] );
617 $lookup_where = array();
618 foreach ( $lookup_columns as $lookup_column ) {
619 foreach ( $search_values as $search_value ) {
620 $lookup_where[] = $this->add_filter(
621 $wpdadb,
622 $lookup_column,
623 ( $filter_mode !== null ? $filter_mode : $search_column_fns[$lookup_key] ),
624 $search_value
625 );
626 }
627 }
628 if ( 0 < count( $lookup_where ) ) {
629 return $wpdadb->prepare( ' `%1s` in ( select `%1s` from `%1s` where (' . implode( ' or ', $lookup_where ) . ') ) ', array(
630 $column_name,
631 $lookup_key,
632 $lookup_table,
633 $lookup_columns[0],
634 "%{$search_values[0]}%"
635 ) );
636 } else {
637 return null;
638 }
639 }
640
641 /**
642 * Perform query and return result as JSON response.
643 *
644 * @param string $dbs Schema name (database).
645 * @param string $tbl Table Name.
646 * @param string $column_names Column Names.
647 * @param string $page_index Page number.
648 * @param string $page_size Rows per page.
649 * @param string $search Filter.
650 * @param string $search_columns Column search filters.
651 * @param string $search_column_fns Column search filter modes.
652 * @param string $Sorting Order by.
653 * @param integer $last_row_count Row count previous request.
654 * @param string $row_count_estimate Indicates if row count estimate should be used.
655 * @param string $media_columns Media columns.
656 * @param string $default_where Defaul where clause
657 * @param string $default_orderby Defaul order by clause
658 * @return \WP_Error|\WP_REST_Response
659 */
660 public function select(
661 $dbs,
662 $tbl,
663 $column_names,
664 $page_index,
665 $page_size,
666 $search,
667 $search_columns,
668 $search_column_fns,
669 $sorting,
670 $last_row_count,
671 $row_count_estimate,
672 $media_columns,
673 $default_where = '',
674 $default_orderby = '',
675 $lookups = array(),
676 $md = array()
677 ) {
678 $wpdadb = WPDADB::get_db_connection( $dbs );
679 if ( null === $wpdadb ) {
680 // Error connecting.
681 return new \WP_Error('error', "Error connecting to database {$dbs}", array(
682 'status' => 420,
683 ));
684 } else {
685 $suppress = $wpdadb->suppress_errors( true );
686 if ( '' !== trim( $default_where ) && 'where' !== strtolower( substr( trim( $default_where ), 0, 5 ) ) ) {
687 $where = "where {$default_where}";
688 } else {
689 $where = $default_where;
690 }
691 // Global search.
692 $where_global = array();
693 if ( null !== $search && "" !== $search ) {
694 foreach ( $column_names as $column_name => $queryable ) {
695 if ( $queryable ) {
696 if ( isset( $lookups[$column_name] ) ) {
697 // Perform look search.
698 $condition = $this->generate_lookup_condition(
699 $wpdadb,
700 $lookups,
701 $column_name,
702 array($search),
703 array(),
704 'contains'
705 );
706 if ( null !== $condition ) {
707 $where_global[] = $condition;
708 }
709 } else {
710 $where_global[] = $wpdadb->prepare( " `%1s` like '%s' ", array($column_name, '%' . esc_sql( $search ) . '%') );
711 }
712 }
713 }
714 }
715 if ( 0 < count( $where_global ) ) {
716 $where .= (( '' === trim( $where ) ? ' where ' : ' and ' )) . $this->add_condition( $where_global, 'or' );
717 }
718 // Order by.
719 $sqlorder = '';
720 if ( is_array( $sorting ) && 0 < count( $sorting ) ) {
721 foreach ( $sorting as $sort ) {
722 if ( '' === $sqlorder ) {
723 $sqlorder = 'order by ';
724 } else {
725 $sqlorder .= ',';
726 }
727 $sqlorder .= sanitize_sql_orderby( $sort['id'] ) . ' ' . (( $sort['desc'] ? 'desc' : 'asc' ));
728 }
729 }
730 if ( '' === $sqlorder && '' !== trim( $default_orderby ) ) {
731 $sqlorder = $default_orderby;
732 }
733 // Pagination.
734 if ( !is_numeric( $page_size ) ) {
735 $page_size = 10;
736 }
737 $offset = $page_index * $page_size;
738 // Calculate offset.
739 if ( !is_numeric( $offset ) ) {
740 $offset = 0;
741 }
742 // Prepare query.
743 $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} " : '' ));
744 // Prepare debug info.
745 if ( 'on' === WPDA::get_option( WPDA::OPTION_PLUGIN_DEBUG ) ) {
746 $debug = array(
747 'debug' => array(
748 'sql' => preg_replace( "/\\s+/", " ", $sql ),
749 'where' => $where,
750 'order by' => $sqlorder,
751 ),
752 );
753 } else {
754 $debug = null;
755 }
756 // Perform query.
757 $dataset = $wpdadb->get_results( $wpdadb->prepare( $sql, array($tbl) ), 'ARRAY_A' );
758 if ( $wpdadb->last_error ) {
759 // Handle SQL errors.
760 return new \WP_Error('error', $wpdadb->last_error, array(
761 'status' => 420,
762 'debug' => ( isset( $debug['debug'] ) ? $debug : null ),
763 ));
764 }
765 if ( is_numeric( $last_row_count ) and 0 <= $last_row_count ) {
766 // Prevents an extra query.
767 $rowcount = $last_row_count;
768 } else {
769 $estimate = false;
770 if ( '1' === $row_count_estimate && '' === $where ) {
771 // Perform row count estimate
772 $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' );
773 if ( isset( $countrows[0]['rowcount'] ) ) {
774 $estimate = true;
775 }
776 }
777 if ( !$estimate ) {
778 // (Re)Count rows.
779 $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' );
780 }
781 if ( $wpdadb->last_error ) {
782 // Handle SQL errors.
783 return new \WP_Error('error', $wpdadb->last_error, array(
784 'status' => 420,
785 ));
786 }
787 if ( isset( $countrows[0]['rowcount'] ) ) {
788 $rowcount = $countrows[0]['rowcount'];
789 } else {
790 $rowcount = 0;
791 }
792 }
793 // Add context node to response
794 $context = array();
795 if ( isset( $debug['debug'] ) && 'on' === WPDA::get_option( WPDA::OPTION_PLUGIN_DEBUG ) ) {
796 $context['debug'] = $debug['debug'];
797 }
798 if ( 0 < count( $media_columns ) ) {
799 // Handle WP media library
800 $media = array();
801 for ($i = 0; $i < count( $dataset ); $i++) {
802 $media_row = array();
803 foreach ( $media_columns as $media_column_name => $media_column_type ) {
804 if ( isset( $dataset[$i][$media_column_name] ) ) {
805 $media_row[$media_column_name] = WPDA_WP_Media::get_media_url( $dataset[$i][$media_column_name] );
806 }
807 }
808 $media[] = $media_row;
809 }
810 // Add media to context node
811 $context['media'] = $media;
812 }
813 $wpdadb->suppress_errors( $suppress );
814 // Send response.
815 $response = $this->WPDA_Rest_Response(
816 '',
817 $dataset,
818 $context,
819 array(
820 'rowCount' => $rowcount,
821 )
822 );
823 $response->header( 'X-WP-Total', $rowcount );
824 // Total rows for this query.
825 if ( 0 < $page_size ) {
826 $pagecount = floor( $rowcount / $page_size );
827 if ( $pagecount != $rowcount / $page_size ) {
828 // phpcs:ignore WordPress.PHP.StrictComparisons
829 $pagecount++;
830 }
831 } else {
832 // Prevent division by zero
833 $pagecount = 0;
834 }
835 $response->header( 'X-WP-TotalPages', $pagecount );
836 // Total pages for this query.
837 return $response;
838 }
839 }
840
841 public function add_filter(
842 $wpdadb,
843 $searchColumn,
844 $search_column_fns,
845 $searchValue
846 ) {
847 }
848
849 private function add_condition( $where_lines, $operand = 'and' ) {
850 if ( 0 < count( array_filter( $where_lines ) ) ) {
851 // Apply all searches.
852 return ' ( (' . implode( ") {$operand} (", array_filter( $where_lines ) ) . ') ) ';
853 } else {
854 return "";
855 }
856 }
857
858 /**
859 * Get table meta data.
860 *
861 * @param string $dbs Database schema name.
862 * @param string $tbl Database table name.
863 * @param string $waa With admin actions.
864 * @return array\object
865 */
866 public function get_table_meta_data( $dbs, $tbl, $waa ) {
867 $sql_create_table = '';
868 if ( current_user_can( 'manage_options' ) ) {
869 // Admin user has access to all resources
870 $access = array(
871 'select' => array('POST'),
872 'insert' => array('POST'),
873 'update' => array('POST'),
874 'delete' => array('POST'),
875 );
876 // Get create table script
877 $wpdadb = WPDADB::get_db_connection( $dbs );
878 if ( null !== $wpdadb ) {
879 $suppress_errors = $wpdadb->suppress_errors;
880 $wpdadb->suppress_errors = true;
881 $wpdadb->query( "SET sql_mode = 'NO_TABLE_OPTIONS'" );
882 $sql = $wpdadb->get_results( $wpdadb->prepare( 'show create table `%1s`', array($tbl) ), 'ARRAY_N' );
883 if ( isset( $sql[0][1] ) ) {
884 $sql_create_table = $sql[0][1];
885 }
886 $wpdadb->suppress_errors = $suppress_errors;
887 }
888 } else {
889 $access = $this->get_table_access( $dbs, $tbl );
890 }
891 $settings = new stdClass();
892 if ( null !== $access ) {
893 $columns = WPDA_List_Columns_Cache::get_list_columns( $dbs, $tbl );
894 $settings_db = WPDA_Table_Settings_Model::query( $tbl, $dbs );
895 if ( isset( $settings_db[0]['wpda_table_settings'] ) ) {
896 $settings = json_decode( $settings_db[0]['wpda_table_settings'] );
897 // Remove old settings from response.
898 unset($settings->form_labels);
899 unset($settings->list_labels);
900 unset($settings->custom_settings);
901 unset($settings->search_settings);
902 }
903 $settings->ui = WPDA_Settings::get_admin_settings( $dbs, $tbl );
904 $rest_api = get_option( WPDA_API::WPDA_REST_API_TABLE_ACCESS );
905 if ( isset( $rest_api[$dbs][$tbl] ) ) {
906 $settings->rest_api = $rest_api[$dbs][$tbl];
907 }
908 $settings->env = $this->get_env();
909 global $wpdb;
910 $settings->wp = [
911 'roles' => $this->get_wp_roles(),
912 'users' => $this->get_wp_users(),
913 'home' => admin_url( 'admin.php' ),
914 'tables' => array_values( $wpdb->tables() ),
915 ];
916 if ( true === $waa ) {
917 $settings->wp['aonce'] = implode( '-', array(
918 wp_create_nonce( 'wpda-export-' . json_encode( $tbl ) ),
919 // Table export
920 wp_create_nonce( 'wpda-rename-' . $tbl ),
921 ) );
922 }
923 $media = $this->get_media( $dbs, $tbl, $columns->get_table_columns() );
924 }
925 return array(
926 'columns' => $columns->get_table_columns(),
927 'table_labels' => $columns->get_table_header_labels(),
928 'form_labels' => $columns->get_table_column_headers(),
929 'primary_key' => $columns->get_table_primary_key(),
930 'access' => $access,
931 'settings' => $settings,
932 'media' => $media['media'],
933 'wp_media' => $media['wp_media'],
934 'table_info' => $this->get_table_info( $dbs, $tbl ),
935 'create' => $sql_create_table,
936 );
937 }
938
939 private function get_table_access( $dbs, $tbl ) {
940 if ( current_user_can( 'manage_options' ) ) {
941 // Check administrator rights
942 if ( is_admin() ) {
943 $access = WPDA_Dictionary_Access::check_table_access_backend( $dbs, $tbl, $done );
944 } else {
945 $access = WPDA_Dictionary_Access::check_table_access_frontend( $dbs, $tbl, $done );
946 }
947 if ( $access ) {
948 // Administrator access granted
949 return array(
950 'select' => array('POST'),
951 'insert' => array('POST'),
952 'update' => array('POST'),
953 'delete' => array('POST'),
954 );
955 }
956 }
957 $tables = get_option( WPDA_API::WPDA_REST_API_TABLE_ACCESS );
958 if ( false !== $tables && isset( $tables[$dbs][$tbl] ) && is_array( $tables[$dbs][$tbl] ) ) {
959 $table = $tables[$dbs][$tbl];
960 $table_access = new \stdClass();
961 $table_access->select = $this->get_table_access_action( $table, 'select' );
962 $table_access->insert = $this->get_table_access_action( $table, 'insert' );
963 $table_access->update = $this->get_table_access_action( $table, 'update' );
964 $table_access->delete = $this->get_table_access_action( $table, 'delete' );
965 return $table_access;
966 }
967 return false;
968 }
969
970 private function get_table_access_action( $table, $action ) {
971 if ( isset( $table[$action]['authorization'], $table[$action]['methods'] ) && is_array( $table[$action]['methods'] ) && 0 < count( $table[$action]['methods'] ) ) {
972 if ( 'anonymous' === $table[$action]['authorization'] ) {
973 return $table[$action]['methods'];
974 } else {
975 // Check authorized users
976 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'] ) ) {
977 return $table[$action]['methods'];
978 }
979 // Check authorized roles
980 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'] ) ) ) {
981 return $table[$action]['methods'];
982 }
983 }
984 }
985 return array();
986 }
987
988 /**
989 * Check if access is grant for requested database/table.
990 *
991 * @param string $dbs Remote or local database connection string.
992 * @param string $tbl Database table name.
993 * @param onject $request Request object.
994 * @param string $action Possible values: select, insert, update, delete.
995 * @return bool
996 */
997 private function check_table_access(
998 $dbs,
999 $tbl,
1000 $request,
1001 $action,
1002 &$msg = ''
1003 ) {
1004 if ( current_user_can( 'manage_options' ) ) {
1005 // Grant access to administrators always.
1006 return true;
1007 }
1008 $tables = get_option( WPDA_API::WPDA_REST_API_TABLE_ACCESS );
1009 if ( false === $tables ) {
1010 // No tables.
1011 $msg = __( 'Unauthorized', 'wp-data-access' );
1012 return false;
1013 }
1014 if ( !(isset( $tables[$dbs][$tbl][$action]['methods'] ) && is_array( $tables[$dbs][$tbl][$action]['methods'] )) ) {
1015 // No methods.
1016 $msg = __( 'Unauthorized', 'wp-data-access' );
1017 return false;
1018 } else {
1019 if ( !in_array( $request->get_method(), $tables[$dbs][$tbl][$action]['methods'] ) ) {
1020 //phpcs:ignore - 8.1 proof
1021 $msg = __( 'Unauthorized', 'wp-data-access' );
1022 return false;
1023 }
1024 }
1025 if ( !isset( $tables[$dbs][$tbl][$action]['authorization'] ) ) {
1026 // No authorization.
1027 $msg = __( 'Unauthorized', 'wp-data-access' );
1028 return false;
1029 } else {
1030 if ( 'anonymous' === $tables[$dbs][$tbl][$action]['authorization'] ) {
1031 // Access granted to all users.
1032 return true;
1033 }
1034 }
1035 global $wp_rest_auth_cookie;
1036 if ( true !== $wp_rest_auth_cookie ) {
1037 // No anonymous access.
1038 $msg = __( 'Unauthorized', 'wp-data-access' );
1039 return false;
1040 } else {
1041 if ( 'authorized' !== $tables[$dbs][$tbl][$action]['authorization'] ) {
1042 // Authorization check.
1043 $msg = __( 'Unauthorized', 'wp-data-access' );
1044 return false;
1045 }
1046 // Authorized access requires a valid nonce.
1047 if ( !wp_verify_nonce( $request->get_header( 'X-WP-Nonce' ), 'wp_rest' ) ) {
1048 $msg = 'rest_cookie_invalid_nonce';
1049 return false;
1050 }
1051 if ( !(isset( $tables[$dbs][$tbl][$action]['authorized_users'] ) && is_array( $tables[$dbs][$tbl][$action]['authorized_users'] )) ) {
1052 // No users.
1053 $msg = __( 'Unauthorized', 'wp-data-access' );
1054 return false;
1055 } else {
1056 $requesting_user_login = $this->get_user_login();
1057 if ( 0 < count( $tables[$dbs][$tbl][$action]['authorized_users'] ) && in_array( $requesting_user_login, $tables[$dbs][$tbl][$action]['authorized_users'] ) ) {
1058 return true;
1059 }
1060 }
1061 if ( !(isset( $tables[$dbs][$tbl][$action]['authorized_roles'] ) && is_array( $tables[$dbs][$tbl][$action]['authorized_roles'] )) ) {
1062 // No roles.
1063 $msg = __( 'Unauthorized', 'wp-data-access' );
1064 return false;
1065 } else {
1066 $requesting_user_roles = $this->get_user_roles();
1067 if ( false === $requesting_user_roles ) {
1068 $requesting_user_roles = array();
1069 }
1070 if ( 0 < count( $tables[$dbs][$tbl][$action]['authorized_roles'] ) && 0 < count( array_intersect( $requesting_user_roles, $tables[$dbs][$tbl][$action]['authorized_roles'] ) ) ) {
1071 return true;
1072 }
1073 }
1074 $msg = __( 'Unauthorized', 'wp-data-access' );
1075 return false;
1076 }
1077 }
1078
1079 private function sanitize_column_values( $dbs, $tbl, $column_values ) {
1080 $wpda_list_columns = WPDA_List_Columns_Cache::get_list_columns( $dbs, $tbl );
1081 $sanitized_column_values = [];
1082 foreach ( $column_values as $column_name => $column_value ) {
1083 $column_value = $column_values[$column_name];
1084 switch ( $wpda_list_columns->get_column_data_type( $column_name ) ) {
1085 case 'tinytext':
1086 case 'text':
1087 case 'mediumtext':
1088 case 'longtext':
1089 if ( null !== $column_value ) {
1090 $column_value = wp_kses_post( $column_value );
1091 }
1092 break;
1093 default:
1094 if ( null !== $column_value ) {
1095 $column_value = sanitize_text_field( $column_value );
1096 }
1097 }
1098 $sanitized_column_values[$this->sanitize_db_identifier( $column_name )] = $column_value;
1099 }
1100 return $sanitized_column_values;
1101 }
1102
1103 }
1104