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_API_Core.php

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

680 lines 28.7 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 WPDataAccess\Connection\WPDADB;
6 use WPDataAccess\Plugin_Table_Models\WPDA_Media_Model;
7 use WPDataAccess\Plugin_Table_Models\WPDA_Table_Settings_Model;
8 use WPDataAccess\WPDA;
9 abstract class WPDA_API_Core {
10 public abstract function register_rest_routes();
11
12 private static $user_roles = null;
13
14 private static $user_login = null;
15
16 private $params;
17
18 public function __construct() {
19 $this->params = array(
20 'dbs' => array(
21 'required' => true,
22 'type' => 'string',
23 'description' => __( 'Local database name or remote connection string', 'wp-data-access' ),
24 'sanitize_callback' => function ( $param ) {
25 return $this->sanitize_db_identifier( $param );
26 },
27 'validate_callback' => function ( $param ) {
28 return $this->validate_db_identifier( $param );
29 },
30 ),
31 'tbl' => array(
32 'required' => true,
33 'type' => 'string',
34 'description' => __( 'Table or view name', 'wp-data-access' ),
35 'sanitize_callback' => function ( $param ) {
36 return $this->sanitize_db_identifier( $param );
37 },
38 'validate_callback' => function ( $param ) {
39 return $this->validate_db_identifier( $param );
40 },
41 ),
42 'client_side' => array(
43 'required' => false,
44 'type' => 'boolean',
45 'description' => __( 'Server side processing', 'wp-data-access' ),
46 'sanitize_callback' => 'sanitize_text_field',
47 'validate_callback' => 'rest_validate_request_arg',
48 ),
49 'app_id' => array(
50 'required' => true,
51 'type' => 'integer',
52 'description' => __( 'App ID', 'wp-data-access' ),
53 'sanitize_callback' => 'absint',
54 'validate_callback' => 'rest_validate_request_arg',
55 ),
56 'cnt_id' => array(
57 'required' => true,
58 'type' => 'integer',
59 'description' => __( 'Container ID', 'wp-data-access' ),
60 'sanitize_callback' => 'absint',
61 'validate_callback' => 'rest_validate_request_arg',
62 ),
63 'app_name' => array(
64 'required' => true,
65 'type' => 'string',
66 'description' => __( 'App name', 'wp-data-access' ),
67 'sanitize_callback' => 'sanitize_text_field',
68 'validate_callback' => 'rest_validate_request_arg',
69 ),
70 'app_title' => array(
71 'required' => true,
72 'type' => 'string',
73 'description' => __( 'App title', 'wp-data-access' ),
74 'sanitize_callback' => 'sanitize_text_field',
75 'validate_callback' => 'rest_validate_request_arg',
76 ),
77 'app_type' => array(
78 'required' => true,
79 'type' => 'integer',
80 'description' => __( 'App type', 'wp-data-access' ),
81 'sanitize_callback' => 'absint',
82 'validate_callback' => 'rest_validate_request_arg',
83 ),
84 'app_settings' => array(
85 'required' => true,
86 'type' => 'string',
87 'description' => __( 'App settings', 'wp-data-access' ),
88 'sanitize_callback' => 'sanitize_text_field',
89 'validate_callback' => 'rest_validate_request_arg',
90 ),
91 'app_add_to_menu' => array(
92 'required' => true,
93 'type' => 'integer',
94 'description' => __( 'Add app to dashboard menu', 'wp-data-access' ),
95 'sanitize_callback' => 'absint',
96 'validate_callback' => 'rest_validate_request_arg',
97 ),
98 'app_cls' => array(
99 'required' => true,
100 'type' => 'array',
101 'description' => __( 'App columns', 'wp-data-access' ),
102 'sanitize_callback' => function ( $param ) {
103 return $this->sanitize_columns( $param );
104 },
105 'validate_callback' => function ( $param ) {
106 return $this->validate_columns( $param );
107 },
108 ),
109 'join_tab' => array(
110 'required' => false,
111 'type' => 'boolean',
112 'description' => __( 'Use join table', 'wp-data-access' ),
113 'sanitize_callback' => 'sanitize_text_field',
114 'validate_callback' => 'rest_validate_request_arg',
115 ),
116 'rel_tab' => array(
117 'required' => false,
118 'type' => 'boolean',
119 'description' => __( 'Use relation table', 'wp-data-access' ),
120 'sanitize_callback' => 'sanitize_text_field',
121 'validate_callback' => 'rest_validate_request_arg',
122 ),
123 'md' => array(
124 'required' => false,
125 'type' => 'mixed',
126 'description' => __( 'Master detail join conditions', 'wp-data-access' ),
127 'sanitize_callback' => function ( $param ) {
128 $columns = array();
129 foreach ( rest_sanitize_object( $param ) as $column_name => $value ) {
130 $columns[$this->sanitize_db_identifier( $column_name )] = sanitize_text_field( wp_unslash( $value ) );
131 }
132 return $columns;
133 },
134 'validate_callback' => function ( $param ) {
135 return is_array( $param );
136 },
137 ),
138 'cascade' => array(
139 'required' => false,
140 'type' => 'boolean',
141 'description' => __( 'Use search arguments if true', 'wp-data-access' ),
142 'sanitize_callback' => 'sanitize_text_field',
143 'validate_callback' => 'rest_validate_request_arg',
144 ),
145 'app_apps' => array(
146 'required' => false,
147 'type' => 'array',
148 'description' => __( 'Array of app IDs', 'wp-data-access' ),
149 'sanitize_callback' => function ( $param ) {
150 $apps = array();
151 foreach ( $param as $value ) {
152 if ( is_numeric( $value ) ) {
153 $apps[] = $value;
154 }
155 }
156 return $apps;
157 },
158 'validate_callback' => function ( $param ) {
159 return is_array( $param );
160 },
161 ),
162 'app_query' => array(
163 'required' => false,
164 'type' => 'string',
165 'description' => __( 'Custom query', 'wp-data-access' ),
166 'sanitize_callback' => 'sanitize_textarea_field',
167 'validate_callback' => 'rest_validate_request_arg',
168 ),
169 'col' => array(
170 'required' => true,
171 'type' => 'string',
172 'description' => __( 'Column name', 'wp-data-access' ),
173 'sanitize_callback' => function ( $param ) {
174 return $this->sanitize_db_identifier( $param );
175 },
176 'validate_callback' => function ( $param ) {
177 return $this->validate_db_identifier( $param );
178 },
179 ),
180 'cols' => array(
181 'required' => false,
182 'type' => 'mixed',
183 'description' => __( 'Table or view columns', 'wp-data-access' ),
184 'sanitize_callback' => function ( $param ) {
185 $columns = array();
186 foreach ( rest_sanitize_object( $param ) as $column_name => $queryable ) {
187 $columns[$this->sanitize_db_identifier( $column_name )] = $queryable === true;
188 }
189 return $columns;
190 },
191 'validate_callback' => function ( $param ) {
192 return is_array( $param );
193 },
194 ),
195 'page_index' => array(
196 'required' => false,
197 'type' => 'integer',
198 'description' => __( 'Page number', 'wp-data-access' ),
199 'default' => 1,
200 'minimum' => 0,
201 'sanitize_callback' => 'absint',
202 'validate_callback' => 'rest_validate_request_arg',
203 ),
204 'page_size' => array(
205 'required' => false,
206 'type' => 'integer',
207 'description' => __( 'Rows per page (0=all)', 'wp-data-access' ),
208 'default' => 10,
209 'minimum' => 1,
210 'sanitize_callback' => 'absint',
211 'validate_callback' => 'rest_validate_request_arg',
212 ),
213 'search' => array(
214 'required' => false,
215 'type' => 'string',
216 'description' => __( 'Global search filter', 'wp-data-access' ),
217 'sanitize_callback' => 'sanitize_text_field',
218 'validate_callback' => 'rest_validate_request_arg',
219 ),
220 'search_columns' => array(
221 'required' => false,
222 'type' => 'mixed',
223 'description' => __( 'Column search filters', 'wp-data-access' ),
224 'sanitize_callback' => function ( $param ) {
225 $search = array();
226 foreach ( rest_sanitize_array( $param ) as $value ) {
227 if ( isset( $value['id'], $value['value'] ) ) {
228 $search[] = array(
229 'id' => $this->sanitize_db_identifier( $value['id'] ),
230 'value' => ( is_array( $value['value'] ) ? map_deep( $value['value'], 'sanitize_text_field' ) : sanitize_text_field( $value['value'] ) ),
231 );
232 }
233 }
234 return $search;
235 },
236 'validate_callback' => function ( $param ) {
237 return is_array( $param );
238 },
239 ),
240 'search_column_fns' => array(
241 'required' => false,
242 'description' => __( 'Column search filter modes', 'wp-data-access' ),
243 'sanitize_callback' => function ( $param ) {
244 $search_modes = array();
245 foreach ( $param as $key => $value ) {
246 if ( in_array( $value, WPDA_Table::WPDA_SEARCH_MODES ) ) {
247 // Accepting only valid modes
248 $search_modes[$this->sanitize_db_identifier( $key )] = sanitize_text_field( $value );
249 }
250 }
251 return $search_modes;
252 },
253 'validate_callback' => function ( $param ) {
254 return is_array( $param );
255 },
256 ),
257 'search_column_lov' => array(
258 'required' => false,
259 'type' => 'mixed',
260 'description' => __( 'Search columns for lov support', 'wp-data-access' ),
261 'sanitize_callback' => function ( $param ) {
262 $lovs = array();
263 foreach ( $param as $value ) {
264 $lovs[] = $this->sanitize_db_identifier( $value );
265 }
266 return $lovs;
267 },
268 'validate_callback' => function ( $param ) {
269 return is_array( $param );
270 },
271 ),
272 'search_data_types' => array(
273 'required' => false,
274 'type' => 'mixed',
275 'description' => __( 'Search columns for lov support', 'wp-data-access' ),
276 'sanitize_callback' => function ( $param ) {
277 $date_types = array();
278 foreach ( $param as $key => $value ) {
279 $date_types[$this->sanitize_db_identifier( $key )] = sanitize_text_field( $value );
280 }
281 return $date_types;
282 },
283 'validate_callback' => function ( $param ) {
284 return is_array( $param );
285 },
286 ),
287 'search_custom' => array(
288 'required' => false,
289 'description' => __( 'Custom search filters auto generated from http parameter requirements in default where', 'wp-data-access' ),
290 'sanitize_callback' => function ( $param ) {
291 $search_custom = array();
292 foreach ( $param as $key => $value ) {
293 if ( is_array( $value ) ) {
294 foreach ( $value as $column_name => $column_value ) {
295 $search_custom[$key][$this->sanitize_db_identifier( $column_name )] = sanitize_text_field( $column_value );
296 }
297 }
298 }
299 return $search_custom;
300 },
301 'validate_callback' => function ( $param ) {
302 return is_array( $param );
303 },
304 ),
305 'search_params' => array(
306 'required' => false,
307 'description' => __( 'Shortcode parameters', 'wp-data-access' ),
308 'sanitize_callback' => function ( $param ) {
309 $search_custom = array();
310 foreach ( $param as $key => $value ) {
311 $search_custom[$this->sanitize_db_identifier( $key )] = sanitize_text_field( $value );
312 }
313 return $search_custom;
314 },
315 'validate_callback' => function ( $param ) {
316 return is_array( $param );
317 },
318 ),
319 'sorting' => array(
320 'required' => false,
321 'description' => __( 'Order by (array of { id and desc })', 'wp-data-access' ),
322 'sanitize_callback' => function ( $param ) {
323 $order_by = array();
324 foreach ( rest_sanitize_object( $param ) as $value ) {
325 if ( isset( $value['id'], $value['desc'] ) ) {
326 $order_by[] = array(
327 'id' => $this->sanitize_db_identifier( $value['id'] ),
328 'desc' => sanitize_text_field( $value['desc'] ),
329 );
330 }
331 }
332 return $order_by;
333 },
334 'validate_callback' => function ( $param ) {
335 if ( !is_array( $param ) ) {
336 return false;
337 }
338 foreach ( $param as $value ) {
339 if ( !isset( $value['id'], $value['desc'] ) ) {
340 return false;
341 }
342 }
343 return true;
344 },
345 ),
346 'row_count' => array(
347 'required' => false,
348 'type' => 'integer',
349 'description' => __( 'Row count', 'wp-data-access' ),
350 'minimum' => 0,
351 'sanitize_callback' => 'absint',
352 'validate_callback' => 'rest_validate_request_arg',
353 ),
354 'row_count_estimate' => array(
355 'required' => false,
356 'type' => 'boolean',
357 'description' => __( 'Calculate row count estimate', 'wp-data-access' ),
358 'sanitize_callback' => 'sanitize_text_field',
359 'validate_callback' => 'rest_validate_request_arg',
360 ),
361 'key' => array(
362 'required' => true,
363 'type' => 'mixed',
364 'description' => __( 'Primary key', 'wp-data-access' ),
365 'sanitize_callback' => function ( $param ) {
366 $primary_keys = array();
367 foreach ( $param as $key => $value ) {
368 $primary_keys[$this->sanitize_db_identifier( $key )] = sanitize_text_field( $value );
369 }
370 return $primary_keys;
371 },
372 'validate_callback' => function ( $param ) {
373 return is_array( $param );
374 },
375 ),
376 'val' => array(
377 'required' => true,
378 'type' => 'mixed',
379 'description' => __( 'Column values', 'wp-data-access' ),
380 'validate_callback' => function ( $param ) {
381 return is_array( $param );
382 },
383 ),
384 'typ' => array(
385 'required' => true,
386 'type' => 'integer',
387 'description' => __( 'Type = 0, view = 1', 'wp-data-access' ),
388 'minimum' => 0,
389 'maximum' => 1,
390 'sanitize_callback' => 'absint',
391 'validate_callback' => 'rest_validate_request_arg',
392 ),
393 'media' => array(
394 'required' => true,
395 'type' => 'mixed',
396 'description' => __( 'Media columns', 'wp-data-access' ),
397 'sanitize_callback' => function ( $param ) {
398 $media = array();
399 foreach ( $param as $key => $value ) {
400 $media[$this->sanitize_db_identifier( $key )] = sanitize_text_field( $value );
401 }
402 return $media;
403 },
404 'validate_callback' => function ( $param ) {
405 return is_array( $param );
406 },
407 ),
408 );
409 }
410
411 protected function get_param( $key, $description = null ) {
412 if ( isset( $this->params[$key] ) ) {
413 $param = $this->params[$key];
414 if ( null !== $description ) {
415 $param['description'] = $description;
416 }
417 return $param;
418 } else {
419 // Force REST API error
420 return false;
421 }
422 }
423
424 protected function get_user_roles() {
425 if ( null === WPDA_API_Core::$user_roles ) {
426 WPDA_API_Core::$user_roles = WPDA::get_current_user_roles();
427 if ( false === WPDA_API_Core::$user_roles ) {
428 WPDA_API_Core::$user_roles = array();
429 }
430 }
431 return WPDA_API_Core::$user_roles;
432 }
433
434 protected function get_user_login() {
435 if ( null === WPDA_API_Core::$user_login ) {
436 WPDA_API_Core::$user_login = WPDA::get_current_user_login();
437 }
438 return WPDA_API_Core::$user_login;
439 }
440
441 protected function current_user_can_access( $admins_only = false ) {
442 return WPDA::current_user_is_admin();
443 }
444
445 protected function unauthorized() {
446 return new \WP_Error('error', __( 'Unauthorized', 'wp-data-access' ), array(
447 'status' => 401,
448 ));
449 }
450
451 protected function current_user_token_valid( $request, $token_required = false ) {
452 return wp_verify_nonce( $request->get_header( 'X-WP-Nonce' ), 'wp_rest' );
453 }
454
455 protected function invalid_nonce() {
456 return new \WP_Error('rest_cookie_invalid_nonce', 'Cookie check failed', array(
457 'status' => 403,
458 ));
459 }
460
461 protected function bad_request() {
462 return new \WP_Error('error', __( 'Bad request', 'wp-data-access' ), array(
463 'status' => 400,
464 ));
465 }
466
467 protected function invalid_app_settings() {
468 return new \WP_Error('error', __( 'Invalid app settings - contact support', 'wp-data-access' ), array(
469 'status' => 403,
470 ));
471 }
472
473 protected function current_user_can_remote() {
474 return false;
475 }
476
477 public static function sanitize_db_identifier( $param ) {
478 if ( null === $param ) {
479 return null;
480 }
481 // Preserve starting and trailing spaces
482 $spaces_before = strlen( $param ) - strlen( ltrim( $param ) );
483 $spaces_after = strlen( $param ) - strlen( rtrim( $param ) );
484 return str_repeat( ' ', $spaces_before ) . WPDA::remove_backticks( sanitize_text_field( $param ) ) . str_repeat( ' ', $spaces_after );
485 }
486
487 public static function validate_db_identifier( $param ) {
488 return !empty( WPDA::remove_backticks( $param ) );
489 }
490
491 protected function sanitize_columns( $param ) {
492 $sanitized_param = array();
493 foreach ( $param as $p ) {
494 $sanitized_param[] = array(
495 'columnName' => $this->sanitize_db_identifier( $p['columnName'] ),
496 'isSelected' => $p['isSelected'],
497 );
498 }
499 return $sanitized_param;
500 }
501
502 protected function validate_columns( $param ) {
503 if ( !is_array( $param ) ) {
504 return false;
505 }
506 foreach ( $param as $p ) {
507 if ( !isset( $p['columnName'], $p['isSelected'] ) || !$this->validate_db_identifier( $p['columnName'] ) || 'boolean' !== gettype( $p['isSelected'] ) ) {
508 return false;
509 }
510 }
511 return true;
512 }
513
514 protected function get_wp_roles() {
515 $roles = array();
516 global $wp_roles;
517 foreach ( $wp_roles->roles as $role => $role_object ) {
518 if ( isset( $role_object['name'] ) ) {
519 $roles[$role] = $role_object['name'];
520 }
521 }
522 return $roles;
523 }
524
525 protected function get_wp_users() {
526 $users = array();
527 foreach ( get_users() as $user ) {
528 if ( isset( $user->user_login, $user->display_name ) ) {
529 $users[$user->user_login] = $user->display_name;
530 }
531 }
532 return $users;
533 }
534
535 protected function get_env() {
536 return array(
537 'ip' => $_SERVER['REMOTE_ADDR'],
538 'id' => WPDA::get_current_user_id(),
539 'user' => WPDA::get_current_user_login(),
540 'roles' => WPDA::get_current_user_roles(),
541 'login' => 'anonymous' !== WPDA::get_current_user_login(),
542 );
543 }
544
545 protected function get_table_info( $dbs, $tbl, $default_where = '' ) {
546 $wpdadb = WPDADB::get_db_connection( $dbs );
547 if ( $wpdadb === null ) {
548 return array(
549 'type' => null,
550 'engine' => null,
551 'count' => null,
552 );
553 }
554 $query = $wpdadb->prepare( "\n\t\t\t\t\tselect table_type,\n\t\t\t\t\t engine,\n\t\t\t\t\t table_rows\n\t\t\t\t\t from information_schema.tables\n\t\t\t\t\t where table_schema = %s\n\t\t\t\t\t and table_name = %s\n\t\t\t\t\t order by table_name\n\t\t\t\t", array($wpdadb->dbname, $tbl) );
555 $resultset = $wpdadb->get_results( $query, 'ARRAY_N' );
556 // phpcs:ignore Standard.Category.SniffName.ErrorCode
557 if ( count( $resultset ) === 1 ) {
558 if ( null !== $resultset[0][2] ) {
559 return array(
560 'type' => $resultset[0][0],
561 'engine' => $resultset[0][1],
562 'count' => ( '' === $default_where ? ( $resultset[0][2] == 0 ? null : $resultset[0][2] ) : null ),
563 );
564 } else {
565 $count = $this->get_row_count_estimate( $dbs, $tbl );
566 return array(
567 'type' => $resultset[0][0],
568 'engine' => $resultset[0][1],
569 'count' => ( $count === 0 ? null : $count ),
570 );
571 }
572 } else {
573 return array(
574 'type' => null,
575 'engine' => null,
576 'count' => null,
577 );
578 }
579 }
580
581 protected function get_row_count_estimate( $dbs, $tbl ) {
582 $wpdadb = WPDADB::get_db_connection( $dbs );
583 if ( null === $wpdadb ) {
584 return -1;
585 }
586 $explain = $wpdadb->get_results( 'explain select count(*) from `' . str_replace( '`', '', $tbl ) . '`', 'ARRAY_A' );
587 if ( isset( $explain[0]['rows'] ) ) {
588 return $explain[0]['rows'];
589 } else {
590 // This should never happen
591 return -1;
592 }
593 }
594
595 protected function get_media( $dbs, $tbl, $columns ) {
596 $media = array();
597 $wp_media = array();
598 foreach ( $columns as $column ) {
599 $media_type = WPDA_Media_Model::get_column_media( $tbl, $column['column_name'], $dbs );
600 switch ( $media_type ) {
601 case 'ImageURL':
602 $media[$column['column_name']] = $media_type;
603 break;
604 case 'Hyperlink':
605 // Get table settings.
606 $table_settings_db = WPDA_Table_Settings_Model::query( $tbl, $dbs );
607 if ( isset( $table_settings_db[0]['wpda_table_settings'] ) ) {
608 $table_settings = json_decode( $table_settings_db[0]['wpda_table_settings'], true );
609 } else {
610 $table_settings = null;
611 }
612 // Check hyperlink format.
613 if ( isset( $table_settings['table_settings']['hyperlink_definition'] ) && 'text' === $table_settings['table_settings']['hyperlink_definition'] ) {
614 $media[$column['column_name']] = 'HyperlinkURL';
615 } else {
616 $media[$column['column_name']] = 'HyperlinkObject';
617 }
618 break;
619 default:
620 if ( false !== $media_type ) {
621 // Handle WordPress Media Library integration
622 $media[$column['column_name']] = "WP-{$media_type}";
623 }
624 }
625 $wp_media[$column['column_name']] = $media_type;
626 }
627 return [
628 'media' => $media,
629 'wp_media' => $wp_media,
630 ];
631 }
632
633 /**
634 * Write standard JSON response.
635 *
636 * @param string $message Response text message.
637 * @param mixed $data Response data.
638 * @param mixed $context Context data.
639 * @param mixed $meta Meta data.
640 * @return \WP_REST_Response
641 */
642 protected static function WPDA_Rest_Response(
643 $message = '',
644 $data = null,
645 $context = null,
646 $meta = null
647 ) {
648 // Prepare response.
649 $response = new \WP_REST_Response(array(
650 'code' => 'ok',
651 'message' => $message,
652 'data' => $data,
653 'context' => $context,
654 'meta' => $meta,
655 ), 200);
656 // Disable caching.
657 $response->header( 'Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0' );
658 $response->header( 'Pragma', 'no-cache' );
659 $response->header( 'Expires', '0' );
660 return $response;
661 }
662
663 protected static function WPDA_Rest_Response_Info( $message = '' ) {
664 // Prepare response.
665 $response = new \WP_REST_Response(array(
666 'code' => 'info',
667 'message' => $message,
668 'data' => null,
669 'context' => null,
670 'meta' => null,
671 ), 200);
672 // Disable caching.
673 $response->header( 'Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0' );
674 $response->header( 'Pragma', 'no-cache' );
675 $response->header( 'Expires', '0' );
676 return $response;
677 }
678
679 }
680