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

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