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

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

1,311 lines 50.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace WPDataAccess\API;
4
5 use 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 ) {
866 $wpdadb = WPDADB::get_db_connection( $dbs );
867 if ( null === $wpdadb ) {
868 // Error connecting.
869 return new \WP_Error('error', "Error connecting to database {$dbs}", array(
870 'status' => 420,
871 ));
872 } else {
873 $suppress = $wpdadb->suppress_errors( true );
874 // Build where clause.
875 $where = $this->get_where(
876 $wpdadb,
877 $default_where,
878 $md,
879 $m2m_relationship,
880 $search,
881 $column_names,
882 $lookups,
883 $search_columns,
884 $search_column_fns,
885 $search_data_types
886 );
887 // Build order by.
888 $sqlorder = '';
889 if ( is_array( $sorting ) && 0 < count( $sorting ) ) {
890 foreach ( $sorting as $sort ) {
891 if ( '' === $sqlorder ) {
892 $sqlorder = 'order by ';
893 } else {
894 $sqlorder .= ',';
895 }
896 $sqlorder .= '`' . $this->convert_column_name( $m2m_relationship, $sort['id'] ) . '` ' . (( $sort['desc'] ? 'desc' : 'asc' ));
897 }
898 }
899 if ( '' === $sqlorder && '' !== trim( $default_orderby ) ) {
900 $sqlorder = $default_orderby;
901 }
902 // Add pagination.
903 if ( !is_numeric( $page_size ) ) {
904 $page_size = 10;
905 }
906 $offset = $page_index * $page_size;
907 // Calculate offset.
908 if ( !is_numeric( $offset ) ) {
909 $offset = 0;
910 }
911 // Prepare query.
912 $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";
913 $sql_tables = array($tbl);
914 // Perpare query.
915 $sql = $wpdadb->prepare( $sql . (( 0 < $page_size ? " limit {$page_size} offset {$offset} " : '' )), $sql_tables );
916 // Prepare debug info.
917 if ( 'on' === WPDA::get_option( WPDA::OPTION_PLUGIN_DEBUG ) ) {
918 $debug = array(
919 'sql' => preg_replace( "/\\s+/", " ", $sql ),
920 'where' => $where,
921 'order by' => $sqlorder,
922 );
923 } else {
924 $debug = null;
925 }
926 // Perform query.
927 $dataset = $wpdadb->get_results( $sql, 'ARRAY_A' );
928 if ( $wpdadb->last_error ) {
929 // Handle SQL errors.
930 return new \WP_Error('error', $wpdadb->last_error, array(
931 'status' => 420,
932 'debug' => $debug,
933 ));
934 }
935 if ( is_numeric( $last_row_count ) and 0 <= $last_row_count ) {
936 // Prevents additional unnecessary queries.
937 $rowcount = $last_row_count;
938 } else {
939 $estimate = false;
940 if ( '1' === $row_count_estimate && '' === $where ) {
941 // Perform row count estimate
942 $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' );
943 if ( isset( $countrows[0]['rowcount'] ) && 0 != $countrows[0]['rowcount'] ) {
944 $estimate = true;
945 }
946 }
947 if ( !$estimate ) {
948 if ( !$estimate ) {
949 // (Re)Count rows.
950 $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' );
951 }
952 }
953 if ( $wpdadb->last_error ) {
954 // Handle SQL errors.
955 return new \WP_Error('error', $wpdadb->last_error, array(
956 'status' => 420,
957 ));
958 }
959 if ( isset( $countrows[0]['rowcount'] ) ) {
960 $rowcount = $countrows[0]['rowcount'];
961 } else {
962 $rowcount = 0;
963 }
964 }
965 // Add context node to response
966 $context = array();
967 if ( 'on' === WPDA::get_option( WPDA::OPTION_PLUGIN_DEBUG ) ) {
968 $context['debug'] = $debug;
969 }
970 if ( 0 < count( $media_columns ) ) {
971 // Handle WP media library
972 $media = array();
973 for ($i = 0; $i < count( $dataset ); $i++) {
974 $media_row = array();
975 foreach ( $media_columns as $media_column_name => $media_column_type ) {
976 if ( isset( $dataset[$i][$media_column_name] ) ) {
977 $media_row[$media_column_name] = WPDA_WP_Media::get_media_url( $dataset[$i][$media_column_name] );
978 }
979 }
980 $media[] = $media_row;
981 }
982 // Add media to context node
983 $context['media'] = $media;
984 }
985 $wpdadb->suppress_errors( $suppress );
986 // Send response.
987 $response = $this->WPDA_Rest_Response(
988 '',
989 $dataset,
990 $context,
991 array(
992 'rowCount' => $rowcount,
993 )
994 );
995 $response->header( 'X-WP-Total', $rowcount );
996 // Total rows for this query.
997 if ( 0 < $page_size ) {
998 $pagecount = floor( $rowcount / $page_size );
999 if ( $pagecount != $rowcount / $page_size ) {
1000 // phpcs:ignore WordPress.PHP.StrictComparisons
1001 $pagecount++;
1002 }
1003 } else {
1004 // Prevent division by zero
1005 $pagecount = 0;
1006 }
1007 $response->header( 'X-WP-TotalPages', $pagecount );
1008 // Total pages for this query.
1009 return $response;
1010 }
1011 }
1012
1013 private function convert_column_name( $m2m_relationship, $column_name ) {
1014 // Return plain column name.
1015 return $this->sanitize_db_identifier( $column_name );
1016 }
1017
1018 private function map_columns( $prefix, $column_names ) {
1019 return implode( ",", array_map( function ( $v ) use($prefix) {
1020 $c = $this->sanitize_db_identifier( $v );
1021 $r = ( 'd' === $prefix ? static::RELATIONTABLEPREFIX . $c : $c );
1022 return "`{$prefix}`.`{$c}` as \"{$r}\"";
1023 }, array_keys( $column_names ) ) );
1024 }
1025
1026 public function add_filter(
1027 $wpdadb,
1028 $search_column,
1029 $search_column_fns,
1030 $search_value,
1031 $m2m_relationship = array(),
1032 $search_data_types = array()
1033 ) {
1034 }
1035
1036 public static function add_condition( $where_lines, $operand = 'and' ) {
1037 if ( 0 < count( array_filter( $where_lines ) ) ) {
1038 // Apply all searches.
1039 return ' ( (' . implode( ") {$operand} (", array_filter( $where_lines ) ) . ') ) ';
1040 } else {
1041 return "";
1042 }
1043 }
1044
1045 /**
1046 * Get table meta data.
1047 *
1048 * @param string $dbs Database schema name.
1049 * @param string $tbl Database table name.
1050 * @param string $waa With admin actions.
1051 * @return array\object
1052 */
1053 public function get_table_meta_data( $dbs, $tbl, $waa ) {
1054 $sql_create_table = '';
1055 if ( current_user_can( 'manage_options' ) ) {
1056 // Admin user has access to all resources
1057 $access = array(
1058 'select' => array('POST'),
1059 'insert' => array('POST'),
1060 'update' => array('POST'),
1061 'delete' => array('POST'),
1062 );
1063 // Get create table script
1064 $wpdadb = WPDADB::get_db_connection( $dbs );
1065 if ( null !== $wpdadb ) {
1066 $suppress_errors = $wpdadb->suppress_errors;
1067 $wpdadb->suppress_errors = true;
1068 $wpdadb->query( "SET sql_mode = 'NO_TABLE_OPTIONS'" );
1069 $sql = $wpdadb->get_results( $wpdadb->prepare( 'show create table `%1s`', array($tbl) ), 'ARRAY_N' );
1070 if ( isset( $sql[0][1] ) ) {
1071 $sql_create_table = $sql[0][1];
1072 }
1073 $wpdadb->suppress_errors = $suppress_errors;
1074 }
1075 } else {
1076 $access = $this->get_table_access( $dbs, $tbl );
1077 }
1078 $settings = new stdClass();
1079 if ( null !== $access ) {
1080 $columns = WPDA_List_Columns_Cache::get_list_columns( $dbs, $tbl );
1081 $settings_db = WPDA_Table_Settings_Model::query( $tbl, $dbs );
1082 if ( isset( $settings_db[0]['wpda_table_settings'] ) ) {
1083 $settings = json_decode( $settings_db[0]['wpda_table_settings'] );
1084 // Remove old settings from response.
1085 unset($settings->form_labels);
1086 unset($settings->list_labels);
1087 unset($settings->custom_settings);
1088 unset($settings->search_settings);
1089 }
1090 $settings->ui = WPDA_Settings::get_admin_settings( $dbs, $tbl );
1091 $rest_api = get_option( WPDA_API::WPDA_REST_API_TABLE_ACCESS );
1092 if ( isset( $rest_api[$dbs][$tbl] ) ) {
1093 $settings->rest_api = $rest_api[$dbs][$tbl];
1094 }
1095 $settings->env = $this->get_env();
1096 $wp_nonce_action_alter = "wpda-alter-{$tbl}";
1097 $wp_nonce_alter = wp_create_nonce( $wp_nonce_action_alter );
1098 $wp_nonce_refresh = null;
1099 $connect = null;
1100 global $wpdb;
1101 $settings->wp = [
1102 'roles' => $this->get_wp_roles(),
1103 'users' => $this->get_wp_users(),
1104 'home' => admin_url( 'admin.php' ),
1105 'homea' => admin_url( 'admin-ajax.php' ),
1106 'tables' => array_values( $wpdb->tables() ),
1107 'date_format' => get_option( 'date_format' ),
1108 'time_format' => get_option( 'time_format' ),
1109 'alter' => $wp_nonce_alter,
1110 'refresh' => $wp_nonce_refresh,
1111 'connect' => $connect,
1112 ];
1113 if ( true === $waa ) {
1114 $settings->wp['aonce'] = implode( '-', array(
1115 wp_create_nonce( 'wpda-export-' . json_encode( $tbl ) ),
1116 // Table export
1117 wp_create_nonce( 'wpda-rename-' . $tbl ),
1118 ) );
1119 }
1120 $media = $this->get_media( $dbs, $tbl, $columns->get_table_columns() );
1121 }
1122 return array(
1123 'columns' => $columns->get_table_columns(),
1124 'table_labels' => $columns->get_table_header_labels(),
1125 'form_labels' => $columns->get_table_column_headers(),
1126 'primary_key' => $columns->get_table_primary_key(),
1127 'access' => $access,
1128 'settings' => $settings,
1129 'media' => $media['media'],
1130 'wp_media' => $media['wp_media'],
1131 'table_info' => $this->get_table_info( $dbs, $tbl ),
1132 'create' => $sql_create_table,
1133 );
1134 }
1135
1136 private function get_table_access( $dbs, $tbl ) {
1137 if ( current_user_can( 'manage_options' ) ) {
1138 // Check administrator rights
1139 if ( is_admin() ) {
1140 $access = WPDA_Dictionary_Access::check_table_access_backend( $dbs, $tbl, $done );
1141 } else {
1142 $access = WPDA_Dictionary_Access::check_table_access_frontend( $dbs, $tbl, $done );
1143 }
1144 if ( $access ) {
1145 // Administrator access granted
1146 return array(
1147 'select' => array('POST'),
1148 'insert' => array('POST'),
1149 'update' => array('POST'),
1150 'delete' => array('POST'),
1151 );
1152 }
1153 }
1154 $tables = get_option( WPDA_API::WPDA_REST_API_TABLE_ACCESS );
1155 if ( false !== $tables && isset( $tables[$dbs][$tbl] ) && is_array( $tables[$dbs][$tbl] ) ) {
1156 $table = $tables[$dbs][$tbl];
1157 $table_access = new \stdClass();
1158 $table_access->select = $this->get_table_access_action( $table, 'select' );
1159 $table_access->insert = $this->get_table_access_action( $table, 'insert' );
1160 $table_access->update = $this->get_table_access_action( $table, 'update' );
1161 $table_access->delete = $this->get_table_access_action( $table, 'delete' );
1162 return $table_access;
1163 }
1164 return false;
1165 }
1166
1167 private function get_table_access_action( $table, $action ) {
1168 if ( isset( $table[$action]['authorization'], $table[$action]['methods'] ) && is_array( $table[$action]['methods'] ) && 0 < count( $table[$action]['methods'] ) ) {
1169 if ( 'anonymous' === $table[$action]['authorization'] ) {
1170 return $table[$action]['methods'];
1171 } else {
1172 // Check authorized users
1173 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'] ) ) {
1174 return $table[$action]['methods'];
1175 }
1176 // Check authorized roles
1177 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'] ) ) ) {
1178 return $table[$action]['methods'];
1179 }
1180 }
1181 }
1182 return array();
1183 }
1184
1185 /**
1186 * Check if access is grant for requested database/table.
1187 *
1188 * @param string $dbs Remote or local database connection string.
1189 * @param string $tbl Database table name.
1190 * @param onject $request Request object.
1191 * @param string $action Possible values: select, insert, update, delete.
1192 * @return bool
1193 */
1194 private function check_table_access(
1195 $dbs,
1196 $tbl,
1197 $request,
1198 $action,
1199 &$msg = ''
1200 ) {
1201 if ( current_user_can( 'manage_options' ) ) {
1202 // Grant access to administrators always.
1203 return true;
1204 }
1205 $tables = get_option( WPDA_API::WPDA_REST_API_TABLE_ACCESS );
1206 if ( false === $tables ) {
1207 // No tables.
1208 $msg = __( 'Unauthorized', 'wp-data-access' );
1209 return false;
1210 }
1211 if ( !(isset( $tables[$dbs][$tbl][$action]['methods'] ) && is_array( $tables[$dbs][$tbl][$action]['methods'] )) ) {
1212 // No methods.
1213 $msg = __( 'Unauthorized', 'wp-data-access' );
1214 return false;
1215 } else {
1216 if ( !in_array( $request->get_method(), $tables[$dbs][$tbl][$action]['methods'] ) ) {
1217 //phpcs:ignore - 8.1 proof
1218 $msg = __( 'Unauthorized', 'wp-data-access' );
1219 return false;
1220 }
1221 }
1222 if ( !isset( $tables[$dbs][$tbl][$action]['authorization'] ) ) {
1223 // No authorization.
1224 $msg = __( 'Unauthorized', 'wp-data-access' );
1225 return false;
1226 } else {
1227 if ( 'anonymous' === $tables[$dbs][$tbl][$action]['authorization'] ) {
1228 // Access granted to all users.
1229 return true;
1230 }
1231 }
1232 global $wp_rest_auth_cookie;
1233 if ( true !== $wp_rest_auth_cookie ) {
1234 // No anonymous access.
1235 $msg = __( 'Unauthorized', 'wp-data-access' );
1236 return false;
1237 } else {
1238 if ( 'authorized' !== $tables[$dbs][$tbl][$action]['authorization'] ) {
1239 // Authorization check.
1240 $msg = __( 'Unauthorized', 'wp-data-access' );
1241 return false;
1242 }
1243 // Authorized access requires a valid nonce.
1244 if ( !wp_verify_nonce( $request->get_header( 'X-WP-Nonce' ), 'wp_rest' ) ) {
1245 $msg = 'rest_cookie_invalid_nonce';
1246 return false;
1247 }
1248 if ( !(isset( $tables[$dbs][$tbl][$action]['authorized_users'] ) && is_array( $tables[$dbs][$tbl][$action]['authorized_users'] )) ) {
1249 // No users.
1250 $msg = __( 'Unauthorized', 'wp-data-access' );
1251 return false;
1252 } else {
1253 $requesting_user_login = $this->get_user_login();
1254 if ( 0 < count( $tables[$dbs][$tbl][$action]['authorized_users'] ) && in_array( $requesting_user_login, $tables[$dbs][$tbl][$action]['authorized_users'] ) ) {
1255 return true;
1256 }
1257 }
1258 if ( !(isset( $tables[$dbs][$tbl][$action]['authorized_roles'] ) && is_array( $tables[$dbs][$tbl][$action]['authorized_roles'] )) ) {
1259 // No roles.
1260 $msg = __( 'Unauthorized', 'wp-data-access' );
1261 return false;
1262 } else {
1263 $requesting_user_roles = $this->get_user_roles();
1264 if ( false === $requesting_user_roles ) {
1265 $requesting_user_roles = array();
1266 }
1267 if ( 0 < count( $tables[$dbs][$tbl][$action]['authorized_roles'] ) && 0 < count( array_intersect( $requesting_user_roles, $tables[$dbs][$tbl][$action]['authorized_roles'] ) ) ) {
1268 return true;
1269 }
1270 }
1271 $msg = __( 'Unauthorized', 'wp-data-access' );
1272 return false;
1273 }
1274 }
1275
1276 private function sanitize_column_values(
1277 $dbs,
1278 $tbl,
1279 $column_values,
1280 $code_columns = array(),
1281 $html_columns = array()
1282 ) {
1283 $wpda_list_columns = WPDA_List_Columns_Cache::get_list_columns( $dbs, $tbl );
1284 $sanitized_column_values = [];
1285 foreach ( $column_values as $column_name => $column_value ) {
1286 $column_value = $column_values[$column_name];
1287 switch ( $wpda_list_columns->get_column_data_type( $column_name ) ) {
1288 case 'tinytext':
1289 case 'text':
1290 case 'mediumtext':
1291 case 'longtext':
1292 if ( null !== $column_value ) {
1293 if ( in_array( $column_name, $html_columns ) ) {
1294 $column_value = sanitize_textarea_field( $column_value );
1295 } else {
1296 $column_value = wp_kses_post( $column_value );
1297 }
1298 }
1299 break;
1300 default:
1301 if ( null !== $column_value ) {
1302 $column_value = sanitize_text_field( $column_value );
1303 }
1304 }
1305 $sanitized_column_values[$this->sanitize_db_identifier( $column_name )] = $column_value;
1306 }
1307 return $sanitized_column_values;
1308 }
1309
1310 }
1311