PluginProbe
WP Data Access – App Builder for Tables, Forms, Charts, Maps & Dashboards / 5.5.22
WP Data Access – App Builder for Tables, Forms, Charts, Maps & Dashboards v5.5.22
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.22, at WPDataAccess/API/WPDA_API_Core.php

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