| @@ -38,8 +38,15 @@ | ||
| 38 | 38 | 'validate_callback' => function ( $param ) { |
| 39 | 39 | return $this->validate_db_identifier( $param ); |
| 40 | 40 | }, |
| 41 | 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 | + ), | |
| 42 | 49 | 'app_id' => array( |
| 43 | 50 | 'required' => true, |
| 44 | 51 | 'type' => 'integer', |
| 45 | 52 | 'description' => __( 'App ID', 'wp-data-access' ), |
| @@ -80,8 +87,15 @@ | ||
| 80 | 87 | 'description' => __( 'App settings', 'wp-data-access' ), |
| 81 | 88 | 'sanitize_callback' => 'sanitize_text_field', |
| 82 | 89 | 'validate_callback' => 'rest_validate_request_arg', |
| 83 | 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 | + ), | |
| 84 | 98 | 'app_cls' => array( |
| 85 | 99 | 'required' => true, |
| 86 | 100 | 'type' => 'array', |
| 87 | 101 | 'description' => __( 'App columns', 'wp-data-access' ), |
| @@ -91,8 +105,70 @@ | ||
| 91 | 105 | 'validate_callback' => function ( $param ) { |
| 92 | 106 | return $this->validate_columns( $param ); |
| 93 | 107 | }, |
| 94 | 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' => function ( $param ) { | |
| 167 | + return html_entity_decode( wp_unslash( $param ), ENT_QUOTES ); | |
| 168 | + // Preserve SQL operators | |
| 169 | + }, | |
| 170 | + ), | |
| 95 | 171 | 'col' => array( |
| 96 | 172 | 'required' => true, |
| 97 | 173 | 'type' => 'string', |
| 98 | 174 | 'description' => __( 'Column name', 'wp-data-access' ), |
| @@ -103,9 +179,9 @@ | ||
| 103 | 179 | return $this->validate_db_identifier( $param ); |
| 104 | 180 | }, |
| 105 | 181 | ), |
| 106 | 182 | 'cols' => array( |
| 107 | - 'required' => true, | |
| 183 | + 'required' => false, | |
| 108 | 184 | 'type' => 'mixed', |
| 109 | 185 | 'description' => __( 'Table or view columns', 'wp-data-access' ), |
| 110 | 186 | 'sanitize_callback' => function ( $param ) { |
| 111 | 187 | $columns = array(); |
| @@ -121,9 +197,9 @@ | ||
| 121 | 197 | 'page_index' => array( |
| 122 | 198 | 'required' => false, |
| 123 | 199 | 'type' => 'integer', |
| 124 | 200 | 'description' => __( 'Page number', 'wp-data-access' ), |
| 125 | - 'default' => 1, | |
| 201 | + 'default' => 0, | |
| 126 | 202 | 'minimum' => 0, |
| 127 | 203 | 'sanitize_callback' => 'absint', |
| 128 | 204 | 'validate_callback' => 'rest_validate_request_arg', |
| 129 | 205 | ), |
| @@ -184,18 +260,65 @@ | ||
| 184 | 260 | 'required' => false, |
| 185 | 261 | 'type' => 'mixed', |
| 186 | 262 | 'description' => __( 'Search columns for lov support', 'wp-data-access' ), |
| 187 | 263 | 'sanitize_callback' => function ( $param ) { |
| 188 | - $columns = array(); | |
| 189 | - foreach ( rest_sanitize_object( $param ) as $column_name => $value ) { | |
| 190 | - $columns[$this->sanitize_db_identifier( $column_name )] = sanitize_text_field( wp_unslash( $value ) ); | |
| 264 | + $lovs = array(); | |
| 265 | + foreach ( $param as $value ) { | |
| 266 | + $lovs[] = $this->sanitize_db_identifier( $value ); | |
| 191 | 267 | } |
| 192 | - return $columns; | |
| 268 | + return $lovs; | |
| 193 | 269 | }, |
| 194 | 270 | 'validate_callback' => function ( $param ) { |
| 195 | 271 | return is_array( $param ); |
| 196 | 272 | }, |
| 197 | 273 | ), |
| 274 | + 'search_data_types' => array( | |
| 275 | + 'required' => false, | |
| 276 | + 'type' => 'mixed', | |
| 277 | + 'description' => __( 'Search columns for lov support', 'wp-data-access' ), | |
| 278 | + 'sanitize_callback' => function ( $param ) { | |
| 279 | + $date_types = array(); | |
| 280 | + foreach ( $param as $key => $value ) { | |
| 281 | + $date_types[$this->sanitize_db_identifier( $key )] = sanitize_text_field( $value ); | |
| 282 | + } | |
| 283 | + return $date_types; | |
| 284 | + }, | |
| 285 | + 'validate_callback' => function ( $param ) { | |
| 286 | + return is_array( $param ); | |
| 287 | + }, | |
| 288 | + ), | |
| 289 | + 'search_custom' => array( | |
| 290 | + 'required' => false, | |
| 291 | + 'description' => __( 'Custom search filters auto generated from http parameter requirements in default where', 'wp-data-access' ), | |
| 292 | + 'sanitize_callback' => function ( $param ) { | |
| 293 | + $search_custom = array(); | |
| 294 | + foreach ( $param as $key => $value ) { | |
| 295 | + if ( is_array( $value ) ) { | |
| 296 | + foreach ( $value as $column_name => $column_value ) { | |
| 297 | + $search_custom[$key][$this->sanitize_db_identifier( $column_name )] = sanitize_text_field( $column_value ); | |
| 298 | + } | |
| 299 | + } | |
| 300 | + } | |
| 301 | + return $search_custom; | |
| 302 | + }, | |
| 303 | + 'validate_callback' => function ( $param ) { | |
| 304 | + return is_array( $param ); | |
| 305 | + }, | |
| 306 | + ), | |
| 307 | + 'search_params' => array( | |
| 308 | + 'required' => false, | |
| 309 | + 'description' => __( 'Shortcode parameters', 'wp-data-access' ), | |
| 310 | + 'sanitize_callback' => function ( $param ) { | |
| 311 | + $search_custom = array(); | |
| 312 | + foreach ( $param as $key => $value ) { | |
| 313 | + $search_custom[$this->sanitize_db_identifier( $key )] = sanitize_text_field( $value ); | |
| 314 | + } | |
| 315 | + return $search_custom; | |
| 316 | + }, | |
| 317 | + 'validate_callback' => function ( $param ) { | |
| 318 | + return is_array( $param ); | |
| 319 | + }, | |
| 320 | + ), | |
| 198 | 321 | 'sorting' => array( |
| 199 | 322 | 'required' => false, |
| 200 | 323 | 'description' => __( 'Order by (array of { id and desc })', 'wp-data-access' ), |
| 201 | 324 | 'sanitize_callback' => function ( $param ) { |
| @@ -269,9 +392,9 @@ | ||
| 269 | 392 | 'sanitize_callback' => 'absint', |
| 270 | 393 | 'validate_callback' => 'rest_validate_request_arg', |
| 271 | 394 | ), |
| 272 | 395 | 'media' => array( |
| 273 | - 'required' => true, | |
| 396 | + 'required' => false, | |
| 274 | 397 | 'type' => 'mixed', |
| 275 | 398 | 'description' => __( 'Media columns', 'wp-data-access' ), |
| 276 | 399 | 'sanitize_callback' => function ( $param ) { |
| 277 | 400 | $media = array(); |
| @@ -283,8 +406,77 @@ | ||
| 283 | 406 | 'validate_callback' => function ( $param ) { |
| 284 | 407 | return is_array( $param ); |
| 285 | 408 | }, |
| 286 | 409 | ), |
| 410 | + 'copy_key' => array( | |
| 411 | + 'required' => true, | |
| 412 | + 'type' => 'string', | |
| 413 | + 'description' => __( 'Internal copy action identifier', 'wp-data-access' ), | |
| 414 | + 'sanitize_callback' => 'sanitize_text_field', | |
| 415 | + 'validate_callback' => 'rest_validate_request_arg', | |
| 416 | + ), | |
| 417 | + 'access' => array( | |
| 418 | + 'required' => true, | |
| 419 | + 'type' => 'string', | |
| 420 | + 'description' => __( 'Access (user | global) ', 'wp-data-access' ), | |
| 421 | + 'sanitize_callback' => function ( $param ) { | |
| 422 | + return ( 'global' === strtolower( $param ) ? 'global' : 'user' ); | |
| 423 | + }, | |
| 424 | + 'validate_callback' => function ( $param ) { | |
| 425 | + return 'global' === strtolower( $param ) || 'user' === strtolower( $param ); | |
| 426 | + }, | |
| 427 | + ), | |
| 428 | + 'query' => array( | |
| 429 | + 'required' => true, | |
| 430 | + 'type' => 'string', | |
| 431 | + 'description' => __( 'SQL query', 'wp-data-access' ), | |
| 432 | + 'sanitize_callback' => function ( $param ) { | |
| 433 | + return html_entity_decode( wp_unslash( $param ), ENT_QUOTES ); | |
| 434 | + // Preserve SQL operators | |
| 435 | + }, | |
| 436 | + ), | |
| 437 | + 'name' => array( | |
| 438 | + 'required' => true, | |
| 439 | + 'type' => 'string', | |
| 440 | + 'description' => __( 'Query name', 'wp-data-access' ), | |
| 441 | + 'sanitize_callback' => 'sanitize_text_field', | |
| 442 | + 'validate_callback' => 'rest_validate_request_arg', | |
| 443 | + ), | |
| 444 | + 'vqb' => array( | |
| 445 | + 'required' => false, | |
| 446 | + 'type' => 'mixed', | |
| 447 | + 'description' => __( 'Visual Query Builder', 'wp-data-access' ), | |
| 448 | + 'sanitize_callback' => function ( $param ) { | |
| 449 | + return rest_sanitize_object( $param ); | |
| 450 | + }, | |
| 451 | + 'validate_callback' => 'rest_validate_request_arg', | |
| 452 | + ), | |
| 453 | + 'params' => array( | |
| 454 | + 'required' => false, | |
| 455 | + 'type' => 'array', | |
| 456 | + 'description' => __( 'Cron job parameters', 'wp-data-access' ), | |
| 457 | + 'sanitize_callback' => function ( $param ) { | |
| 458 | + $params = array(); | |
| 459 | + if ( is_array( $param ) ) { | |
| 460 | + foreach ( $param as $key => $value ) { | |
| 461 | + if ( 'params' === $key || 'notify' === $key ) { | |
| 462 | + // Sanitize custom parameters | |
| 463 | + $custom_params = array(); | |
| 464 | + foreach ( $value as $param_key => $param_value ) { | |
| 465 | + $custom_params[sanitize_text_field( $param_key )] = sanitize_text_field( $param_value ); | |
| 466 | + } | |
| 467 | + $params[sanitize_text_field( $key )] = $custom_params; | |
| 468 | + } else { | |
| 469 | + $params[sanitize_text_field( $key )] = sanitize_text_field( $value ); | |
| 470 | + } | |
| 471 | + } | |
| 472 | + } | |
| 473 | + return $params; | |
| 474 | + }, | |
| 475 | + 'validate_callback' => function ( $param ) { | |
| 476 | + return is_array( $param ); | |
| 477 | + }, | |
| 478 | + ), | |
| 287 | 479 | ); |
| 288 | 480 | } |
| 289 | 481 | |
| 290 | 482 | protected function get_param( $key, $description = null ) { |
| @@ -316,10 +508,10 @@ | ||
| 316 | 508 | } |
| 317 | 509 | return WPDA_API_Core::$user_login; |
| 318 | 510 | } |
| 319 | 511 | |
| 320 | - protected function current_user_can_access( $admins_only = false ) { | |
| 321 | - return in_array( 'administrator', $this->get_user_roles() ); | |
| 512 | + protected function current_user_can_access() { | |
| 513 | + return WPDA::current_user_is_admin(); | |
| 322 | 514 | } |
| 323 | 515 | |
| 324 | 516 | protected function unauthorized() { |
| 325 | 517 | return new \WP_Error('error', __( 'Unauthorized', 'wp-data-access' ), array( |
| @@ -326,9 +518,9 @@ | ||
| 326 | 518 | 'status' => 401, |
| 327 | 519 | )); |
| 328 | 520 | } |
| 329 | 521 | |
| 330 | - protected function current_user_token_valid( $request, $token_required = false ) { | |
| 522 | + protected function current_user_token_valid( $request ) { | |
| 331 | 523 | return wp_verify_nonce( $request->get_header( 'X-WP-Nonce' ), 'wp_rest' ); |
| 332 | 524 | } |
| 333 | 525 | |
| 334 | 526 | protected function invalid_nonce() { |
| @@ -352,9 +544,12 @@ | ||
| 352 | 544 | protected function current_user_can_remote() { |
| 353 | 545 | return false; |
| 354 | 546 | } |
| 355 | 547 | |
| 356 | - protected function sanitize_db_identifier( $param ) { | |
| 548 | + public static function sanitize_db_identifier( $param ) { | |
| 549 | + if ( !is_string( $param ) ) { | |
| 550 | + return null; | |
| 551 | + } | |
| 357 | 552 | // Preserve starting and trailing spaces |
| 358 | 553 | $spaces_before = strlen( $param ) - strlen( ltrim( $param ) ); |
| 359 | 554 | $spaces_after = strlen( $param ) - strlen( rtrim( $param ) ); |
| 360 | 555 | return str_repeat( ' ', $spaces_before ) . WPDA::remove_backticks( sanitize_text_field( $param ) ) . str_repeat( ' ', $spaces_after ); |
| @@ -359,9 +554,9 @@ | ||
| 359 | 554 | $spaces_after = strlen( $param ) - strlen( rtrim( $param ) ); |
| 360 | 555 | return str_repeat( ' ', $spaces_before ) . WPDA::remove_backticks( sanitize_text_field( $param ) ) . str_repeat( ' ', $spaces_after ); |
| 361 | 556 | } |
| 362 | 557 | |
| 363 | - protected function validate_db_identifier( $param ) { | |
| 558 | + public static function validate_db_identifier( $param ) { | |
| 364 | 559 | return !empty( WPDA::remove_backticks( $param ) ); |
| 365 | 560 | } |
| 366 | 561 | |
| 367 | 562 | protected function sanitize_columns( $param ) { |
| @@ -408,15 +603,16 @@ | ||
| 408 | 603 | return $users; |
| 409 | 604 | } |
| 410 | 605 | |
| 411 | 606 | protected function get_env() { |
| 412 | - return array( | |
| 413 | - 'ip' => $_SERVER['REMOTE_ADDR'], | |
| 607 | + $env = array( | |
| 608 | + 'ip' => ( isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '' ), | |
| 414 | 609 | 'id' => WPDA::get_current_user_id(), |
| 415 | 610 | 'user' => WPDA::get_current_user_login(), |
| 416 | 611 | 'roles' => WPDA::get_current_user_roles(), |
| 417 | 612 | 'login' => 'anonymous' !== WPDA::get_current_user_login(), |
| 418 | 613 | ); |
| 614 | + return $env; | |
| 419 | 615 | } |
| 420 | 616 | |
| 421 | 617 | protected function get_table_info( $dbs, $tbl, $default_where = '' ) { |
| 422 | 618 | $wpdadb = WPDADB::get_db_connection( $dbs ); |
| @@ -426,17 +622,16 @@ | ||
| 426 | 622 | 'engine' => null, |
| 427 | 623 | 'count' => null, |
| 428 | 624 | ); |
| 429 | 625 | } |
| 430 | - $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) ); | |
| 626 | + $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", array($wpdadb->dbname, $tbl) ); | |
| 431 | 627 | $resultset = $wpdadb->get_results( $query, 'ARRAY_N' ); |
| 432 | - // phpcs:ignore Standard.Category.SniffName.ErrorCode | |
| 433 | 628 | if ( count( $resultset ) === 1 ) { |
| 434 | 629 | if ( null !== $resultset[0][2] ) { |
| 435 | 630 | return array( |
| 436 | 631 | 'type' => $resultset[0][0], |
| 437 | 632 | 'engine' => $resultset[0][1], |
| 438 | - 'count' => ( '' === $default_where ? ( $resultset[0][2] === 0 ? null : $resultset[0][2] ) : null ), | |
| 633 | + 'count' => ( '' === $default_where ? ( $resultset[0][2] == 0 ? null : $resultset[0][2] ) : null ), | |
| 439 | 634 | ); |
| 440 | 635 | } else { |
| 441 | 636 | $count = $this->get_row_count_estimate( $dbs, $tbl ); |
| 442 | 637 | return array( |
| @@ -467,20 +662,26 @@ | ||
| 467 | 662 | return -1; |
| 468 | 663 | } |
| 469 | 664 | } |
| 470 | 665 | |
| 471 | - protected function get_media( $dbs, $tbl, $columns ) { | |
| 666 | + protected function get_media( | |
| 667 | + $dbs, | |
| 668 | + $tbl, | |
| 669 | + $columns, | |
| 670 | + $prefix = '' | |
| 671 | + ) { | |
| 472 | 672 | $media = array(); |
| 473 | 673 | $wp_media = array(); |
| 474 | 674 | foreach ( $columns as $column ) { |
| 475 | 675 | $media_type = WPDA_Media_Model::get_column_media( $tbl, $column['column_name'], $dbs ); |
| 676 | + $column_name = $prefix . $column['column_name']; | |
| 476 | 677 | switch ( $media_type ) { |
| 477 | 678 | case 'ImageURL': |
| 478 | - $media[$column['column_name']] = $media_type; | |
| 679 | + $media[$column_name] = $media_type; | |
| 479 | 680 | break; |
| 480 | 681 | case 'Hyperlink': |
| 481 | 682 | // Get table settings. |
| 482 | - $table_settings_db = WPDA_Table_Settings_Model::query( $dbs, $tbl ); | |
| 683 | + $table_settings_db = WPDA_Table_Settings_Model::query( $tbl, $dbs ); | |
| 483 | 684 | if ( isset( $table_settings_db[0]['wpda_table_settings'] ) ) { |
| 484 | 685 | $table_settings = json_decode( $table_settings_db[0]['wpda_table_settings'], true ); |
| 485 | 686 | } else { |
| 486 | 687 | $table_settings = null; |
| @@ -486,20 +687,20 @@ | ||
| 486 | 687 | $table_settings = null; |
| 487 | 688 | } |
| 488 | 689 | // Check hyperlink format. |
| 489 | 690 | if ( isset( $table_settings['table_settings']['hyperlink_definition'] ) && 'text' === $table_settings['table_settings']['hyperlink_definition'] ) { |
| 490 | - $media[$column['column_name']] = 'HyperlinkURL'; | |
| 691 | + $media[$column_name] = 'HyperlinkURL'; | |
| 491 | 692 | } else { |
| 492 | - $media[$column['column_name']] = 'HyperlinkObject'; | |
| 693 | + $media[$column_name] = 'HyperlinkObject'; | |
| 493 | 694 | } |
| 494 | 695 | break; |
| 495 | 696 | default: |
| 496 | 697 | if ( false !== $media_type ) { |
| 497 | 698 | // Handle WordPress Media Library integration |
| 498 | - $media[$column['column_name']] = "WP-{$media_type}"; | |
| 699 | + $media[$column_name] = "WP-{$media_type}"; | |
| 499 | 700 | } |
| 500 | 701 | } |
| 501 | - $wp_media[$column['column_name']] = $media_type; | |
| 702 | + $wp_media[$column_name] = $media_type; | |
| 502 | 703 | } |
| 503 | 704 | return [ |
| 504 | 705 | 'media' => $media, |
| 505 | 706 | 'wp_media' => $wp_media, |