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

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