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

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