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

1,317 lines 50.6 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 $media = array();
509 if ( 0 < count( $media_columns ) ) {
510 foreach ( $media_columns as $media_column_name => $media_column_type ) {
511 if ( isset( $dataset[0][$media_column_name] ) ) {
512 if ( in_array( $media_column_type, [
513 'WP-Image',
514 'WP-Attachment',
515 'WP-Audio',
516 'WP-Video'
517 ] ) ) {
518 $media[$media_column_name] = WPDA_WP_Media::get_media_url( $dataset[0][$media_column_name] );
519 }
520 }
521 }
522 }
523 $context = array();
524 $context['media'] = $media;
525 if ( isset( $debug['debug'] ) && 'on' === WPDA::get_option( WPDA::OPTION_PLUGIN_DEBUG ) ) {
526 $context['debug'] = $debug['debug'];
527 }
528 if ( 0 === count( $dataset ) ) {
529 return $this->WPDA_Rest_Response( 'No data found', $dataset, array(
530 'debug' => $debug['debug'],
531 ) );
532 } else {
533 if ( 1 === count( $dataset ) ) {
534 return $this->WPDA_Rest_Response( '', $dataset, $context );
535 } else {
536 return $this->WPDA_Rest_Response( 'Query returned more than one row', $dataset, array(
537 'debug' => $debug['debug'],
538 ) );
539 }
540 }
541 }
542 }
543
544 public function insert( $dbs, $tbl, $column_values ) {
545 $wpdadb = WPDADB::get_db_connection( $dbs );
546 if ( null === $wpdadb ) {
547 // Error connecting.
548 return new \WP_Error('error', "Error connecting to database {$dbs}", array(
549 'status' => 420,
550 ));
551 } else {
552 // Get column default values
553 $column_list = WPDA_List_Columns_Cache::get_list_columns( $dbs, $tbl );
554 $table_columns = $column_list->get_table_columns();
555 foreach ( $table_columns as $table_column_type ) {
556 if ( isset( $column_values[$table_column_type['column_name']] ) && $column_values[$table_column_type['column_name']] === $table_column_type['column_default'] ) {
557 // Remove default values if send values equals column default to support defaults using functions
558 unset($column_values[$table_column_type['column_name']]);
559 }
560 }
561 // Sanitize column names and values.
562 $sanitized_column_values = self::sanitize_column_values( $dbs, $tbl, $column_values );
563 if ( false === $sanitized_column_values ) {
564 return new \WP_Error('error', "Invalid arguments", array(
565 'status' => 420,
566 ));
567 }
568 // Insert row.
569 $rows_inserted = $wpdadb->insert( $tbl, $sanitized_column_values );
570 // Send response.
571 if ( 1 === $rows_inserted ) {
572 return $this->WPDA_Rest_Response( __( 'Row successfully inserted', 'wp-data-access' ), null, array(
573 'insert_id' => $wpdadb->insert_id,
574 ) );
575 } else {
576 if ( '' !== $wpdadb->last_error ) {
577 return new \WP_Error('error', $wpdadb->last_error, array(
578 'status' => 420,
579 ));
580 } else {
581 return new \WP_Error('error', 'Insert failed', array(
582 'status' => 420,
583 ));
584 }
585 }
586 }
587 }
588
589 public function update(
590 $dbs,
591 $tbl,
592 $primary_key,
593 $column_values,
594 $column_names = array(),
595 $code_columns = array(),
596 $html_columns = array()
597 ) {
598 $wpdadb = WPDADB::get_db_connection( $dbs );
599 if ( null === $wpdadb ) {
600 // Error connecting.
601 return new \WP_Error('error', "Error connecting to database {$dbs}", array(
602 'status' => 420,
603 ));
604 } else {
605 // Sanitize column names and values.
606 $sanitized_column_values = self::sanitize_column_values(
607 $dbs,
608 $tbl,
609 $column_values,
610 $code_columns,
611 $html_columns
612 );
613 if ( false === $sanitized_column_values ) {
614 return new \WP_Error('error', "Invalid arguments", array(
615 'status' => 420,
616 ));
617 }
618 // Update row.
619 $rows_inserted = $wpdadb->update( $tbl, $sanitized_column_values, $primary_key );
620 // Send response.
621 if ( 0 === $rows_inserted ) {
622 return $this->WPDA_Rest_Response_Info( 'Nothing to update' );
623 } elseif ( 1 === $rows_inserted ) {
624 $context = null;
625 if ( 0 < count( $column_names ) ) {
626 // Return updated values
627 $updated_row = $this->get(
628 $dbs,
629 $tbl,
630 $primary_key,
631 $column_names
632 );
633 if ( isset( $updated_row->data['data'][0] ) ) {
634 $updated_values = $updated_row->data['data'][0];
635 $updated_context = array();
636 foreach ( $updated_values as $key => $value ) {
637 if ( !isset( $column_values[$key] ) ) {
638 $updated_context[$key] = $value;
639 }
640 }
641 if ( 0 < count( $updated_context ) ) {
642 $context = array(
643 'updated' => $updated_context,
644 );
645 }
646 }
647 }
648 return $this->WPDA_Rest_Response( __( 'Row successfully updated', 'wp-data-access' ), null, $context );
649 } else {
650 if ( '' !== $wpdadb->last_error ) {
651 return new \WP_Error('error', $wpdadb->last_error, array(
652 'status' => 420,
653 ));
654 } else {
655 return new \WP_Error('error', 'Update failed', array(
656 'status' => 420,
657 ));
658 }
659 }
660 }
661 }
662
663 public function delete( $dbs, $tbl, $primary_key ) {
664 $wpdadb = WPDADB::get_db_connection( $dbs );
665 if ( null === $wpdadb ) {
666 // Error connecting.
667 return new \WP_Error('error', "Error connecting to database {$dbs}", array(
668 'status' => 420,
669 ));
670 } else {
671 // Delete row.
672 $rows_deleted = $wpdadb->delete( $tbl, $primary_key );
673 // Send response.
674 if ( 0 === $rows_deleted ) {
675 return $this->WPDA_Rest_Response_Info( __( 'No data found', 'wp-data-access' ) );
676 } elseif ( 1 === $rows_deleted ) {
677 return $this->WPDA_Rest_Response( __( 'Row successfully deleted', 'wp-data-access' ) );
678 } else {
679 if ( '' !== $wpdadb->last_error ) {
680 return new \WP_Error('error', $wpdadb->last_error, array(
681 'status' => 420,
682 ));
683 } else {
684 return new \WP_Error('error', 'Delete failed', array(
685 'status' => 420,
686 ));
687 }
688 }
689 }
690 }
691
692 private function generate_lookup_condition(
693 $wpdadb,
694 $lookups,
695 $column_name,
696 $search_values,
697 $search_column_fns,
698 $filter_mode = null,
699 $filter_key = false
700 ) {
701 $lookup = $lookups[$column_name];
702 $lookup_table = $lookup['tbl'];
703 $lookup_key = $lookup['key'];
704 $lookup_columns = explode( ',', $lookup['value'] );
705 $lookup_where = array();
706 if ( $filter_key ) {
707 $filter_columns = array($lookup_key);
708 } else {
709 $filter_columns = $lookup_columns;
710 }
711 foreach ( $filter_columns as $lookup_column ) {
712 foreach ( $search_values as $search_value ) {
713 $lookup_where[] = $this->add_filter(
714 $wpdadb,
715 $lookup_column,
716 ( $filter_mode !== null ? $filter_mode : $search_column_fns[$column_name] ),
717 $search_value
718 );
719 }
720 }
721 if ( 0 < count( $lookup_where ) ) {
722 return $wpdadb->prepare( ' `%1s` in ( select `%1s` from `%1s` where (' . implode( ' or ', $lookup_where ) . ') ) ', array(
723 $column_name,
724 $lookup_key,
725 $lookup_table,
726 $lookup_columns[0],
727 "%{$search_values[0]}%"
728 ) );
729 } else {
730 return null;
731 }
732 }
733
734 public static function remove_where_from_sql( $sql ) {
735 if ( 'where' === substr( trim( $sql ), 0, 5 ) ) {
736 $pos = strpos( $sql, 'where' );
737 if ( false !== $pos ) {
738 $sql = substr_replace(
739 $sql,
740 '',
741 $pos,
742 5
743 );
744 }
745 }
746 return $sql;
747 }
748
749 private function get_md( $md, $wpdadb, $m2m_relationship ) {
750 }
751
752 private function get_global_filter(
753 $wpdadb,
754 $search,
755 $column_names,
756 $lookups,
757 $m2m_relationship
758 ) {
759 $where_global = array();
760 if ( null !== $search && "" !== $search ) {
761 foreach ( $column_names as $column_name => $queryable ) {
762 if ( $queryable ) {
763 if ( isset( $lookups[$column_name] ) ) {
764 // Perform look search.
765 $condition = $this->generate_lookup_condition(
766 $wpdadb,
767 $lookups,
768 $column_name,
769 array($search),
770 array(),
771 'contains'
772 );
773 if ( null !== $condition ) {
774 $where_global[] = $condition;
775 }
776 } else {
777 $where_global[] = $wpdadb->prepare( " `%1s` like '%s' ", array($this->convert_column_name( $m2m_relationship, $column_name ), '%' . esc_sql( $search ) . '%') );
778 }
779 }
780 }
781 }
782 return $where_global;
783 }
784
785 private function get_column_filters(
786 $wpdadb,
787 $search_columns,
788 $search_column_fns,
789 $lookups,
790 $m2m_relationship,
791 $search_data_types
792 ) {
793 }
794
795 private function get_where(
796 $wpdadb,
797 $default_where,
798 $md,
799 $m2m_relationship,
800 $search,
801 $column_names,
802 $lookups,
803 $search_columns,
804 $search_column_fns,
805 $search_data_types
806 ) {
807 // Default where.
808 if ( '' !== trim( $default_where ) && 'where' !== strtolower( substr( trim( $default_where ), 0, 5 ) ) ) {
809 $where = "where {$default_where}";
810 } else {
811 $where = $default_where;
812 }
813 // Global filter.
814 $where_global = $this->get_global_filter(
815 $wpdadb,
816 $search,
817 $column_names,
818 $lookups,
819 $m2m_relationship
820 );
821 if ( 0 < count( $where_global ) ) {
822 $where .= (( '' === trim( $where ) ? ' where ' : ' and ' )) . $this->add_condition( $where_global, 'or' );
823 }
824 return $where;
825 }
826
827 /**
828 * Perform query and return result as JSON response.
829 *
830 * @param string $dbs Schema name (database).
831 * @param string $tbl Table Name.
832 * @param string $column_names Column Names.
833 * @param string $page_index Page number.
834 * @param string $page_size Rows per page.
835 * @param string $search Filter.
836 * @param string $search_columns Column search filters.
837 * @param string $search_column_fns Column search filter modes.
838 * @param string $Sorting Order by.
839 * @param integer $last_row_count Row count previous request.
840 * @param string $row_count_estimate Indicates if row count estimate should be used.
841 * @param string $media_columns Media columns.
842 * @param string $default_where Defaul where clause
843 * @param string $default_orderby Defaul order by clause
844 * @return \WP_Error|\WP_REST_Response
845 */
846 public function select(
847 $dbs,
848 $tbl,
849 $column_names,
850 $page_index,
851 $page_size,
852 $search,
853 $search_columns,
854 $search_column_fns,
855 $sorting,
856 $last_row_count,
857 $row_count_estimate,
858 $media_columns,
859 $default_where = '',
860 $default_orderby = '',
861 $lookups = array(),
862 $md = array(),
863 $m2m_relationship = array(),
864 $search_data_types = array(),
865 $client_side = false
866 ) {
867 $wpdadb = WPDADB::get_db_connection( $dbs );
868 if ( null === $wpdadb ) {
869 // Error connecting.
870 return new \WP_Error('error', "Error connecting to database {$dbs}", array(
871 'status' => 420,
872 ));
873 } else {
874 $suppress = $wpdadb->suppress_errors( true );
875 // Build where clause.
876 $where = $this->get_where(
877 $wpdadb,
878 $default_where,
879 $md,
880 $m2m_relationship,
881 $search,
882 $column_names,
883 $lookups,
884 $search_columns,
885 $search_column_fns,
886 $search_data_types
887 );
888 // Build order by.
889 $sqlorder = '';
890 if ( is_array( $sorting ) && 0 < count( $sorting ) ) {
891 foreach ( $sorting as $sort ) {
892 if ( '' === $sqlorder ) {
893 $sqlorder = 'order by ';
894 } else {
895 $sqlorder .= ',';
896 }
897 $sqlorder .= '`' . $this->convert_column_name( $m2m_relationship, $sort['id'] ) . '` ' . (( $sort['desc'] ? 'desc' : 'asc' ));
898 }
899 }
900 if ( '' === $sqlorder && '' !== trim( $default_orderby ) ) {
901 $sqlorder = $default_orderby;
902 }
903 // Add pagination.
904 if ( !is_numeric( $page_size ) ) {
905 $page_size = 10;
906 }
907 $offset = $page_index * $page_size;
908 // Calculate offset.
909 if ( !is_numeric( $offset ) ) {
910 $offset = 0;
911 }
912 // Prepare query.
913 $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";
914 $sql_tables = array($tbl);
915 // Perpare query.
916 $sql = $wpdadb->prepare( ( true === $client_side ? $sql : $sql . (( 0 < $page_size ? " limit {$page_size} offset {$offset} " : '' )) ), $sql_tables );
917 // Prepare debug info.
918 if ( 'on' === WPDA::get_option( WPDA::OPTION_PLUGIN_DEBUG ) ) {
919 $debug = array(
920 'sql' => preg_replace( "/\\s+/", " ", $sql ),
921 'where' => $where,
922 'order by' => $sqlorder,
923 );
924 } else {
925 $debug = null;
926 }
927 // Perform query.
928 $dataset = $wpdadb->get_results( $sql, 'ARRAY_A' );
929 if ( $wpdadb->last_error ) {
930 // Handle SQL errors.
931 return new \WP_Error('error', $wpdadb->last_error, array(
932 'status' => 420,
933 'debug' => $debug,
934 ));
935 }
936 if ( is_numeric( $last_row_count ) and 0 <= $last_row_count ) {
937 // Prevents additional unnecessary queries.
938 $rowcount = $last_row_count;
939 } else {
940 if ( true === $client_side ) {
941 $rowcount = 0;
942 } else {
943 $estimate = false;
944 if ( '1' === $row_count_estimate && '' === $where ) {
945 // Perform row count estimate
946 $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' );
947 if ( isset( $countrows[0]['rowcount'] ) && 0 != $countrows[0]['rowcount'] ) {
948 $estimate = true;
949 }
950 }
951 if ( !$estimate ) {
952 if ( !$estimate ) {
953 // (Re)Count rows.
954 $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' );
955 }
956 }
957 if ( $wpdadb->last_error ) {
958 // Handle SQL errors.
959 return new \WP_Error('error', $wpdadb->last_error, array(
960 'status' => 420,
961 ));
962 }
963 if ( isset( $countrows[0]['rowcount'] ) ) {
964 $rowcount = $countrows[0]['rowcount'];
965 } else {
966 $rowcount = 0;
967 }
968 }
969 }
970 // Add context node to response
971 $context = array();
972 if ( 'on' === WPDA::get_option( WPDA::OPTION_PLUGIN_DEBUG ) ) {
973 $context['debug'] = $debug;
974 }
975 if ( 0 < count( $media_columns ) ) {
976 // Handle WP media library
977 $media = array();
978 for ($i = 0; $i < count( $dataset ); $i++) {
979 $media_row = array();
980 foreach ( $media_columns as $media_column_name => $media_column_type ) {
981 if ( isset( $dataset[$i][$media_column_name] ) ) {
982 $media_row[$media_column_name] = WPDA_WP_Media::get_media_url( $dataset[$i][$media_column_name] );
983 }
984 }
985 $media[] = $media_row;
986 }
987 // Add media to context node
988 $context['media'] = $media;
989 }
990 $wpdadb->suppress_errors( $suppress );
991 // Send response.
992 $response = $this->WPDA_Rest_Response(
993 '',
994 $dataset,
995 $context,
996 array(
997 'rowCount' => $rowcount,
998 )
999 );
1000 $response->header( 'X-WP-Total', $rowcount );
1001 // Total rows for this query.
1002 if ( 0 < $page_size ) {
1003 $pagecount = floor( $rowcount / $page_size );
1004 if ( $pagecount != $rowcount / $page_size ) {
1005 // phpcs:ignore WordPress.PHP.StrictComparisons
1006 $pagecount++;
1007 }
1008 } else {
1009 // Prevent division by zero
1010 $pagecount = 0;
1011 }
1012 $response->header( 'X-WP-TotalPages', $pagecount );
1013 // Total pages for this query.
1014 return $response;
1015 }
1016 }
1017
1018 private function convert_column_name( $m2m_relationship, $column_name ) {
1019 // Return plain column name.
1020 return $this->sanitize_db_identifier( $column_name );
1021 }
1022
1023 private function map_columns( $prefix, $column_names ) {
1024 return implode( ",", array_map( function ( $v ) use($prefix) {
1025 $c = $this->sanitize_db_identifier( $v );
1026 $r = ( 'd' === $prefix ? static::RELATIONTABLEPREFIX . $c : $c );
1027 return "`{$prefix}`.`{$c}` as \"{$r}\"";
1028 }, array_keys( $column_names ) ) );
1029 }
1030
1031 public function add_filter(
1032 $wpdadb,
1033 $search_column,
1034 $search_column_fns,
1035 $search_value,
1036 $m2m_relationship = array(),
1037 $search_data_types = array()
1038 ) {
1039 }
1040
1041 public static function add_condition( $where_lines, $operand = 'and' ) {
1042 if ( 0 < count( array_filter( $where_lines ) ) ) {
1043 // Apply all searches.
1044 return ' ( (' . implode( ") {$operand} (", array_filter( $where_lines ) ) . ') ) ';
1045 } else {
1046 return "";
1047 }
1048 }
1049
1050 /**
1051 * Get table meta data.
1052 *
1053 * @param string $dbs Database schema name.
1054 * @param string $tbl Database table name.
1055 * @param string $waa With admin actions.
1056 * @return array\object
1057 */
1058 public function get_table_meta_data( $dbs, $tbl, $waa ) {
1059 $sql_create_table = '';
1060 if ( current_user_can( 'manage_options' ) ) {
1061 // Admin user has access to all resources
1062 $access = array(
1063 'select' => array('POST'),
1064 'insert' => array('POST'),
1065 'update' => array('POST'),
1066 'delete' => array('POST'),
1067 );
1068 // Get create table script
1069 $wpdadb = WPDADB::get_db_connection( $dbs );
1070 if ( null !== $wpdadb ) {
1071 $suppress_errors = $wpdadb->suppress_errors;
1072 $wpdadb->suppress_errors = true;
1073 // NO_TABLE_OPTIONS is deprecated in V8
1074 // $wpdadb->query( "SET sql_mode = 'NO_TABLE_OPTIONS'" );
1075 $sql = $wpdadb->get_results( $wpdadb->prepare( 'show create table `%1s`', array($tbl) ), 'ARRAY_N' );
1076 if ( isset( $sql[0][1] ) ) {
1077 $sql_create_table = $sql[0][1];
1078 }
1079 $wpdadb->suppress_errors = $suppress_errors;
1080 }
1081 } else {
1082 $access = $this->get_table_access( $dbs, $tbl );
1083 }
1084 $settings = new stdClass();
1085 if ( null !== $access ) {
1086 $columns = WPDA_List_Columns_Cache::get_list_columns( $dbs, $tbl );
1087 $settings_db = WPDA_Table_Settings_Model::query( $tbl, $dbs );
1088 if ( isset( $settings_db[0]['wpda_table_settings'] ) ) {
1089 $settings = json_decode( $settings_db[0]['wpda_table_settings'] );
1090 // Remove old settings from response.
1091 unset($settings->form_labels);
1092 unset($settings->list_labels);
1093 unset($settings->custom_settings);
1094 unset($settings->search_settings);
1095 }
1096 $settings->ui = WPDA_Settings::get_admin_settings( $dbs, $tbl );
1097 $rest_api = get_option( WPDA_API::WPDA_REST_API_TABLE_ACCESS );
1098 if ( isset( $rest_api[$dbs][$tbl] ) ) {
1099 $settings->rest_api = $rest_api[$dbs][$tbl];
1100 }
1101 $settings->env = $this->get_env();
1102 $wp_nonce_action_alter = "wpda-alter-{$tbl}";
1103 $wp_nonce_alter = wp_create_nonce( $wp_nonce_action_alter );
1104 $wp_nonce_refresh = null;
1105 $connect = null;
1106 global $wpdb;
1107 $settings->wp = [
1108 'roles' => $this->get_wp_roles(),
1109 'users' => $this->get_wp_users(),
1110 'home' => admin_url( 'admin.php' ),
1111 'homea' => admin_url( 'admin-ajax.php' ),
1112 'tables' => array_values( $wpdb->tables() ),
1113 'date_format' => get_option( 'date_format' ),
1114 'time_format' => get_option( 'time_format' ),
1115 'alter' => $wp_nonce_alter,
1116 'refresh' => $wp_nonce_refresh,
1117 'connect' => $connect,
1118 ];
1119 if ( true === $waa ) {
1120 $settings->wp['aonce'] = implode( '-', array(
1121 wp_create_nonce( 'wpda-export-' . json_encode( $tbl ) ),
1122 // Table export
1123 wp_create_nonce( 'wpda-rename-' . $tbl ),
1124 ) );
1125 }
1126 $media = $this->get_media( $dbs, $tbl, $columns->get_table_columns() );
1127 }
1128 return array(
1129 'columns' => $columns->get_table_columns(),
1130 'table_labels' => $columns->get_table_header_labels(),
1131 'form_labels' => $columns->get_table_column_headers(),
1132 'primary_key' => $columns->get_table_primary_key(),
1133 'access' => $access,
1134 'settings' => $settings,
1135 'media' => $media['media'],
1136 'wp_media' => $media['wp_media'],
1137 'table_info' => $this->get_table_info( $dbs, $tbl ),
1138 'create' => $sql_create_table,
1139 );
1140 }
1141
1142 private function get_table_access( $dbs, $tbl ) {
1143 if ( current_user_can( 'manage_options' ) ) {
1144 // Check administrator rights
1145 if ( is_admin() ) {
1146 $access = WPDA_Dictionary_Access::check_table_access_backend( $dbs, $tbl, $done );
1147 } else {
1148 $access = WPDA_Dictionary_Access::check_table_access_frontend( $dbs, $tbl, $done );
1149 }
1150 if ( $access ) {
1151 // Administrator access granted
1152 return array(
1153 'select' => array('POST'),
1154 'insert' => array('POST'),
1155 'update' => array('POST'),
1156 'delete' => array('POST'),
1157 );
1158 }
1159 }
1160 $tables = get_option( WPDA_API::WPDA_REST_API_TABLE_ACCESS );
1161 if ( false !== $tables && isset( $tables[$dbs][$tbl] ) && is_array( $tables[$dbs][$tbl] ) ) {
1162 $table = $tables[$dbs][$tbl];
1163 $table_access = new \stdClass();
1164 $table_access->select = $this->get_table_access_action( $table, 'select' );
1165 $table_access->insert = $this->get_table_access_action( $table, 'insert' );
1166 $table_access->update = $this->get_table_access_action( $table, 'update' );
1167 $table_access->delete = $this->get_table_access_action( $table, 'delete' );
1168 return $table_access;
1169 }
1170 return false;
1171 }
1172
1173 private function get_table_access_action( $table, $action ) {
1174 if ( isset( $table[$action]['authorization'], $table[$action]['methods'] ) && is_array( $table[$action]['methods'] ) && 0 < count( $table[$action]['methods'] ) ) {
1175 if ( 'anonymous' === $table[$action]['authorization'] ) {
1176 return $table[$action]['methods'];
1177 } else {
1178 // Check authorized users
1179 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'] ) ) {
1180 return $table[$action]['methods'];
1181 }
1182 // Check authorized roles
1183 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'] ) ) ) {
1184 return $table[$action]['methods'];
1185 }
1186 }
1187 }
1188 return array();
1189 }
1190
1191 /**
1192 * Check if access is grant for requested database/table.
1193 *
1194 * @param string $dbs Remote or local database connection string.
1195 * @param string $tbl Database table name.
1196 * @param onject $request Request object.
1197 * @param string $action Possible values: select, insert, update, delete.
1198 * @return bool
1199 */
1200 private function check_table_access(
1201 $dbs,
1202 $tbl,
1203 $request,
1204 $action,
1205 &$msg = ''
1206 ) {
1207 if ( current_user_can( 'manage_options' ) ) {
1208 // Grant access to administrators always.
1209 return true;
1210 }
1211 $tables = get_option( WPDA_API::WPDA_REST_API_TABLE_ACCESS );
1212 if ( false === $tables ) {
1213 // No tables.
1214 $msg = __( 'Unauthorized', 'wp-data-access' );
1215 return false;
1216 }
1217 if ( !(isset( $tables[$dbs][$tbl][$action]['methods'] ) && is_array( $tables[$dbs][$tbl][$action]['methods'] )) ) {
1218 // No methods.
1219 $msg = __( 'Unauthorized', 'wp-data-access' );
1220 return false;
1221 } else {
1222 if ( !in_array( $request->get_method(), $tables[$dbs][$tbl][$action]['methods'] ) ) {
1223 //phpcs:ignore - 8.1 proof
1224 $msg = __( 'Unauthorized', 'wp-data-access' );
1225 return false;
1226 }
1227 }
1228 if ( !isset( $tables[$dbs][$tbl][$action]['authorization'] ) ) {
1229 // No authorization.
1230 $msg = __( 'Unauthorized', 'wp-data-access' );
1231 return false;
1232 } else {
1233 if ( 'anonymous' === $tables[$dbs][$tbl][$action]['authorization'] ) {
1234 // Access granted to all users.
1235 return true;
1236 }
1237 }
1238 global $wp_rest_auth_cookie;
1239 if ( true !== $wp_rest_auth_cookie ) {
1240 // No anonymous access.
1241 $msg = __( 'Unauthorized', 'wp-data-access' );
1242 return false;
1243 } else {
1244 if ( 'authorized' !== $tables[$dbs][$tbl][$action]['authorization'] ) {
1245 // Authorization check.
1246 $msg = __( 'Unauthorized', 'wp-data-access' );
1247 return false;
1248 }
1249 // Authorized access requires a valid nonce.
1250 if ( !wp_verify_nonce( $request->get_header( 'X-WP-Nonce' ), 'wp_rest' ) ) {
1251 $msg = 'rest_cookie_invalid_nonce';
1252 return false;
1253 }
1254 if ( !(isset( $tables[$dbs][$tbl][$action]['authorized_users'] ) && is_array( $tables[$dbs][$tbl][$action]['authorized_users'] )) ) {
1255 // No users.
1256 $msg = __( 'Unauthorized', 'wp-data-access' );
1257 return false;
1258 } else {
1259 $requesting_user_login = $this->get_user_login();
1260 if ( 0 < count( $tables[$dbs][$tbl][$action]['authorized_users'] ) && in_array( $requesting_user_login, $tables[$dbs][$tbl][$action]['authorized_users'] ) ) {
1261 return true;
1262 }
1263 }
1264 if ( !(isset( $tables[$dbs][$tbl][$action]['authorized_roles'] ) && is_array( $tables[$dbs][$tbl][$action]['authorized_roles'] )) ) {
1265 // No roles.
1266 $msg = __( 'Unauthorized', 'wp-data-access' );
1267 return false;
1268 } else {
1269 $requesting_user_roles = $this->get_user_roles();
1270 if ( false === $requesting_user_roles ) {
1271 $requesting_user_roles = array();
1272 }
1273 if ( 0 < count( $tables[$dbs][$tbl][$action]['authorized_roles'] ) && 0 < count( array_intersect( $requesting_user_roles, $tables[$dbs][$tbl][$action]['authorized_roles'] ) ) ) {
1274 return true;
1275 }
1276 }
1277 $msg = __( 'Unauthorized', 'wp-data-access' );
1278 return false;
1279 }
1280 }
1281
1282 private function sanitize_column_values(
1283 $dbs,
1284 $tbl,
1285 $column_values,
1286 $code_columns = array(),
1287 $html_columns = array()
1288 ) {
1289 $wpda_list_columns = WPDA_List_Columns_Cache::get_list_columns( $dbs, $tbl );
1290 $sanitized_column_values = [];
1291 foreach ( $column_values as $column_name => $column_value ) {
1292 $column_value = $column_values[$column_name];
1293 switch ( $wpda_list_columns->get_column_data_type( $column_name ) ) {
1294 case 'tinytext':
1295 case 'text':
1296 case 'mediumtext':
1297 case 'longtext':
1298 if ( null !== $column_value ) {
1299 if ( in_array( $column_name, $html_columns ) ) {
1300 $column_value = sanitize_textarea_field( $column_value );
1301 } else {
1302 $column_value = wp_kses_post( $column_value );
1303 }
1304 }
1305 break;
1306 default:
1307 if ( null !== $column_value ) {
1308 $column_value = sanitize_text_field( $column_value );
1309 }
1310 }
1311 $sanitized_column_values[$this->sanitize_db_identifier( $column_name )] = $column_value;
1312 }
1313 return $sanitized_column_values;
1314 }
1315
1316 }
1317