PluginProbe
WP Data Access – App Builder for Tables, Forms, Dashboards, Charts & Maps / 5.5.87
WP Data Access – App Builder for Tables, Forms, Dashboards, Charts & Maps v5.5.87
5.5.87 5.5.85 5.5.84 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 All 162 releases
← All changes | WPDataAccess/API/WPDA_Apps.php +327 -69 5.5.77 → 5.5.87 View file →
@@ -280,9 +280,13 @@
280 280 'map' => array(
281 281 'required' => false,
282 282 'type' => 'string',
283 283 'description' => __( 'Map settings - JSON string', 'wp-data-access' ),
284 - 'sanitize_callback' => 'wp_kses_post',
284 + 'sanitize_callback' => function ( $param ) {
285 + $sanitized_settings = $this->sanitize_settings( json_decode( (string) $param, true ) );
286 + // Save sanitized JSON as string
287 + return json_encode( $sanitized_settings );
288 + },
285 289 'validate_callback' => 'rest_validate_request_arg',
286 290 ),
287 291 'chart' => array(
288 292 'required' => false,
@@ -514,8 +518,15 @@
514 518 'permission_callback' => '__return_true',
515 519 'args' => array(
516 520 'dbs_source' => $this->get_param( 'dbs' ),
517 521 'dbs_destination' => $this->get_param( 'dbs' ),
522 + 'appDbId' => array(
523 + 'required' => false,
524 + 'type' => 'integer',
525 + 'description' => __( 'App Id', 'wp-data-access' ),
526 + 'sanitize_callback' => 'absint',
527 + 'validate_callback' => 'rest_validate_request_arg',
528 + ),
518 529 ),
519 530 ) );
520 531 register_rest_route( WPDA_API::WPDA_NAMESPACE, 'app/lang/get', array(
521 532 'methods' => array('POST'),
@@ -564,10 +575,142 @@
564 575 'validate_callback' => 'rest_validate_request_arg',
565 576 ),
566 577 ),
567 578 ) );
579 + register_rest_route( WPDA_API::WPDA_NAMESPACE, 'app/proxy', array(
580 + 'methods' => array('GET'),
581 + 'callback' => array($this, 'app_proxy'),
582 + 'permission_callback' => '__return_true',
583 + 'args' => array(
584 + 'app_id' => $this->get_param( 'app_id' ),
585 + 'cnt_id' => $this->get_param( 'cnt_id' ),
586 + 'url' => array(
587 + 'required' => true,
588 + 'type' => 'string',
589 + 'description' => __( 'URL', 'wp-data-access' ),
590 + 'sanitize_callback' => 'sanitize_text_field',
591 + 'validate_callback' => 'rest_validate_request_arg',
592 + ),
593 + ),
594 + ) );
595 + // PWA
596 + register_rest_route( WPDA_API::WPDA_NAMESPACE, 'app/pwa/get', array(
597 + 'methods' => array('POST'),
598 + 'callback' => array($this, 'app_wpa_get'),
599 + 'permission_callback' => '__return_true',
600 + 'args' => array(
601 + 'app_id' => $this->get_param( 'app_id' ),
602 + ),
603 + ) );
604 + register_rest_route( WPDA_API::WPDA_NAMESPACE, 'app/pwa/activate', array(
605 + 'methods' => array('POST'),
606 + 'callback' => array($this, 'app_wpa_activate'),
607 + 'permission_callback' => '__return_true',
608 + 'args' => array(
609 + 'app_id' => $this->get_param( 'app_id' ),
610 + ),
611 + ) );
612 + register_rest_route( WPDA_API::WPDA_NAMESPACE, 'app/pwa/deactivate', array(
613 + 'methods' => array('POST'),
614 + 'callback' => array($this, 'app_wpa_deactivate'),
615 + 'permission_callback' => '__return_true',
616 + 'args' => array(
617 + 'app_id' => $this->get_param( 'app_id' ),
618 + ),
619 + ) );
620 + register_rest_route( WPDA_API::WPDA_NAMESPACE, 'app/upload', array(
621 + 'methods' => array('POST'),
622 + 'callback' => array($this, 'app_upload'),
623 + 'permission_callback' => '__return_true',
624 + 'args' => array(
625 + 'app_id' => $this->get_param( 'app_id' ),
626 + 'cnt_id' => $this->get_param( 'cnt_id' ),
627 + 'pk' => array(
628 + 'required' => true,
629 + 'type' => 'string',
630 + 'description' => __( 'Primary key in JSON format', 'wp-data-access' ),
631 + 'sanitize_callback' => 'sanitize_text_field',
632 + 'validate_callback' => 'rest_validate_request_arg',
633 + ),
634 + 'col' => $this->get_param( 'col' ),
635 + ),
636 + ) );
637 + register_rest_route( WPDA_API::WPDA_NAMESPACE, 'app/download', array(
638 + 'methods' => array('POST'),
639 + 'callback' => array($this, 'app_download'),
640 + 'permission_callback' => '__return_true',
641 + 'args' => array(
642 + 'app_id' => $this->get_param( 'app_id' ),
643 + 'cnt_id' => $this->get_param( 'cnt_id' ),
644 + 'pk' => array(
645 + 'required' => true,
646 + 'type' => 'string',
647 + 'description' => __( 'Primary key in JSON format', 'wp-data-access' ),
648 + 'sanitize_callback' => 'sanitize_text_field',
649 + 'validate_callback' => 'rest_validate_request_arg',
650 + ),
651 + 'col' => $this->get_param( 'col' ),
652 + ),
653 + ) );
568 654 }
569 655
656 + public function app_proxy( $request ) {
657 + wp_die();
658 + }
659 +
660 + private function detect_image_mime( $body ) {
661 + return null;
662 + }
663 +
664 + private function is_safe_remote_url( $url, $allowed_hosts ) {
665 + return false;
666 + }
667 +
668 + private function is_public_ip( $ip ) {
669 + return false;
670 + }
671 +
672 + public function app_download( $request ) {
673 + return $this->WPDA_Rest_Response( 'OK' );
674 + }
675 +
676 + public function app_upload( $request ) {
677 + return $this->WPDA_Rest_Response( 'OK' );
678 + }
679 +
680 + public function app_wpa_deactivate( $request ) {
681 + if ( !$this->current_user_can_access() ) {
682 + // Only admins
683 + return $this->unauthorized();
684 + }
685 + if ( !$this->current_user_token_valid( $request ) ) {
686 + return $this->invalid_nonce();
687 + }
688 + return $this->WPDA_Rest_Response( 'OK' );
689 + }
690 +
691 + public function app_wpa_activate( $request ) {
692 + if ( !$this->current_user_can_access() ) {
693 + // Only admins
694 + return $this->unauthorized();
695 + }
696 + if ( !$this->current_user_token_valid( $request ) ) {
697 + return $this->invalid_nonce();
698 + }
699 + return $this->WPDA_Rest_Response( 'OK' );
700 + }
701 +
702 + public function app_wpa_get( $request ) {
703 + if ( !$this->current_user_can_access() ) {
704 + // Only admins
705 + return $this->unauthorized();
706 + }
707 + if ( !$this->current_user_token_valid( $request ) ) {
708 + return $this->invalid_nonce();
709 + }
710 + return $this->WPDA_Rest_Response( 'NOT FOUND' );
711 + }
712 +
570 713 public function app_call( $request ) {
571 714 return $this->bad_request();
572 715 }
573 716
@@ -863,8 +1006,9 @@
863 1006 $media = $request->get_param( 'media' );
864 1007 $rel_tab = $request->get_param( 'rel_tab' );
865 1008 $client_side = '1' === $request->get_param( 'client_side' );
866 1009 $geo_radius = $request->get_param( 'geo_radius' );
1010 + $docs = array();
867 1011 $default_where = '';
868 1012 $default_orderby = '';
869 1013 $lookups = array();
870 1014 $m2m_relationship = array();
@@ -912,8 +1056,27 @@
912 1056 }
913 1057 }
914 1058 }
915 1059 }
1060 + if ( isset( $settings['columns'] ) && is_array( $settings['columns'] ) && '1' !== $rel_tab ) {
1061 + $queryable_columns = array();
1062 + if ( isset( $settings['table']['columns'] ) && is_array( $settings['table']['columns'] ) ) {
1063 + foreach ( $settings['table']['columns'] as $column ) {
1064 + if ( isset( $column['queryable'] ) && $column['queryable'] ) {
1065 + $queryable_columns[] = $column['columnName'];
1066 + }
1067 + }
1068 + }
1069 + $app_columns = array();
1070 + foreach ( $settings['columns'] as $column ) {
1071 + if ( isset( $column['columnName'], $column['isSelected'] ) && $column['isSelected'] ) {
1072 + $app_columns[$column['columnName']] = in_array( $column['columnName'], $queryable_columns );
1073 + }
1074 + }
1075 + if ( 0 < count( $app_columns ) ) {
1076 + $col = $app_columns;
1077 + }
1078 + }
916 1079 $table_api = new WPDA_Table();
917 1080 return $table_api->select(
918 1081 $dbs,
919 1082 $tbl,
@@ -938,9 +1101,10 @@
938 1101 $md,
939 1102 $m2m_relationship,
940 1103 $search_data_types,
941 1104 $client_side,
942 - $geo_radius
1105 + $geo_radius,
1106 + $docs
943 1107 );
944 1108 } else {
945 1109 if ( 'rest_cookie_invalid_nonce' === $msg ) {
946 1110 return $this->invalid_nonce();
@@ -964,8 +1128,9 @@
964 1128 return array_map( function ( $value ) {
965 1129 if ( true === $value['isSelected'] ) {
966 1130 return $value['columnName'];
967 1131 }
1132 + return null;
968 1133 }, $columns );
969 1134 }
970 1135
971 1136 public function app_get( $request ) {
@@ -973,8 +1138,9 @@
973 1138 $cnt_id = $request->get_param( 'cnt_id' );
974 1139 $key = $request->get_param( 'key' );
975 1140 $media = $request->get_param( 'media' );
976 1141 $rel_tab = $request->get_param( 'rel_tab' );
1142 + $docs = array();
977 1143 if ( $this->check_app_access(
978 1144 $app_id,
979 1145 $cnt_id,
980 1146 'select',
@@ -994,9 +1160,10 @@
994 1160 $tbl,
995 1161 $key,
996 1162 $media,
997 1163 $column_names,
998 - $default_where
1164 + $default_where,
1165 + $docs
999 1166 );
1000 1167 } else {
1001 1168 if ( 'rest_cookie_invalid_nonce' === $msg ) {
1002 1169 return $this->invalid_nonce();
@@ -1237,8 +1404,12 @@
1237 1404 }
1238 1405 }
1239 1406
1240 1407 public function app_lookup_dbs( $request ) {
1408 + // Only admins are allowed to configre lookups
1409 + if ( !$this->current_user_can_access() ) {
1410 + return $this->unauthorized();
1411 + }
1241 1412 $app_id = $request->get_param( 'app_id' );
1242 1413 $cnt_id = $request->get_param( 'cnt_id' );
1243 1414 if ( $this->check_app_access(
1244 1415 $app_id,
@@ -1261,8 +1432,12 @@
1261 1432 }
1262 1433 }
1263 1434
1264 1435 public function app_lookup_tbl( $request ) {
1436 + // Only admins are allowed to configre lookups
1437 + if ( !$this->current_user_can_access() ) {
1438 + return $this->unauthorized();
1439 + }
1265 1440 $app_id = $request->get_param( 'app_id' );
1266 1441 $cnt_id = $request->get_param( 'cnt_id' );
1267 1442 $dbs = $request->get_param( 'dbs' );
1268 1443 if ( $this->check_app_access(
@@ -1286,8 +1461,12 @@
1286 1461 }
1287 1462 }
1288 1463
1289 1464 public function app_lookup_cls( $request ) {
1465 + // Only admins are allowed to configre lookups
1466 + if ( !$this->current_user_can_access() ) {
1467 + return $this->unauthorized();
1468 + }
1290 1469 $app_id = $request->get_param( 'app_id' );
1291 1470 $cnt_id = $request->get_param( 'cnt_id' );
1292 1471 $dbs = $request->get_param( 'dbs' );
1293 1472 $tbl = $request->get_param( 'tbl' );
@@ -1336,8 +1515,68 @@
1336 1515 $renamed = 0;
1337 1516 $debug_mode = 'on' === WPDA::get_option( WPDA::OPTION_PLUGIN_DEBUG );
1338 1517 $debug = array();
1339 1518 $errors = array();
1519 + if ( $request->get_param( 'appDbId' ) ) {
1520 + // Update app only
1521 + $appDbId = $request->get_param( 'appDbId' );
1522 + // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared -- plugin table
1523 + $sql = $wpdb->prepare( "update `{$wpdb->prefix}wpda_app_container` set `cnt_dbs` = %s where `cnt_dbs` = %s and app_id = %d", array($dbs_destination, $dbs_source, $appDbId) );
1524 + $result = $wpdb->query( $sql );
1525 + // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
1526 + $renamed += $result;
1527 + if ( $debug_mode ) {
1528 + $debug[] = array(
1529 + 'sql' => $sql,
1530 + 'result' => $result,
1531 + );
1532 + }
1533 + if ( '' !== $wpdb->last_error ) {
1534 + $errors[] = array(
1535 + 'sql' => $sql,
1536 + 'error' => $wpdb->last_error,
1537 + );
1538 + }
1539 + $sql_content = array("update `{$wpdb->prefix}wpda_app_container` set `cnt_table` = replace(`cnt_table`, '\"dbs\":\"%1s\"', '\"dbs\":\"%1s\"') where `cnt_table` like '%\"dbs\":\"%1s\"%' and app_id = %d", "update `{$wpdb->prefix}wpda_app_container` set `cnt_form` = replace(`cnt_form`, '\"dbs\":\"%1s\"', '\"dbs\":\"%1s\"') where `cnt_form` like '%\"dbs\":\"%1s\"%' and app_id = %d");
1540 + foreach ( $sql_content as $sql ) {
1541 + // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared -- plugin table
1542 + $result = $wpdb->query( $wpdb->prepare( $sql, array(
1543 + $dbs_source,
1544 + $dbs_destination,
1545 + $dbs_source,
1546 + $appDbId
1547 + ) ) );
1548 + $renamed += $result;
1549 + if ( $debug_mode ) {
1550 + $debug[] = array(
1551 + 'sql' => $sql,
1552 + 'result' => $result,
1553 + );
1554 + }
1555 + if ( '' !== $wpdb->last_error ) {
1556 + $errors[] = array(
1557 + 'sql' => $sql,
1558 + 'error' => $wpdb->last_error,
1559 + );
1560 + }
1561 + }
1562 + $context = array();
1563 + if ( $debug_mode ) {
1564 + $context['debug'] = $debug;
1565 + }
1566 + if ( 0 < count( $errors ) ) {
1567 + $context['errors'] = $errors;
1568 + return new \WP_Error('error', 'Failed renaming database', array(
1569 + 'status' => 401,
1570 + 'context' => $context,
1571 + ));
1572 + }
1573 + return $this->WPDA_Rest_Response( sprintf(
1574 + /* translators: %s = number of database substitutions */
1575 + __( 'Successfully renamed %s database occurrences', 'wp-data-access' ),
1576 + $renamed
1577 + ), null, $context );
1578 + }
1340 1579 // Rename all occurrences in repository tables and apps
1341 1580 $sqls = array(
1342 1581 "update `{$wpdb->prefix}wpda_publisher` set `pub_schema_name` = %s where `pub_schema_name` = %s",
1343 1582 "update `{$wpdb->prefix}wpda_project_page` set `page_schema_name` = %s where `page_schema_name` = %s",
@@ -1434,14 +1673,23 @@
1434 1673 ));
1435 1674 }
1436 1675 $suppress = $wpdadb->suppress_errors( true );
1437 1676 $chart_data = $wpdadb->get_results( $query, 'ARRAY_A' );
1677 + // Add an * before each label to support numeric labels
1678 + $chart_data_converted = array();
1679 + foreach ( $chart_data as $data ) {
1680 + $row = array();
1681 + foreach ( $data as $key => $value ) {
1682 + $row['*' . $key] = $value;
1683 + }
1684 + $chart_data_converted[] = $row;
1685 + }
1438 1686 $wpdadb->get_results( "create temporary table `wpda_chart_data_types` as {$query}", 'ARRAY_A' );
1439 1687 $explain = $wpdadb->get_results( "desc `wpda_chart_data_types`", 'ARRAY_A' );
1440 1688 $wpdadb->get_results( "drop temporary table `wpda_chart_data_types`", 'ARRAY_A' );
1441 1689 $wpdadb->suppress_errors( $suppress );
1442 1690 return array(
1443 - 'data' => $chart_data,
1691 + 'data' => $chart_data_converted,
1444 1692 'explain' => $explain,
1445 1693 );
1446 1694 } else {
1447 1695 return new \WP_Error('error', $msg, array(
@@ -1922,8 +2170,9 @@
1922 2170 $app_id_details = array_map( function ( $e ) {
1923 2171 if ( isset( $e['app_id_detail'] ) ) {
1924 2172 return $e['app_id_detail'];
1925 2173 }
2174 + return null;
1926 2175 }, $apps );
1927 2176 $app_titles = array();
1928 2177 foreach ( $app_id_details as $app_id_detail ) {
1929 2178 $app_detail = WPDA_App_Model::get_by_id( $app_id_detail );
@@ -1961,16 +2210,43 @@
1961 2210 $settings->wp = [
1962 2211 'roles' => $this->get_wp_roles(),
1963 2212 'users' => $this->get_wp_users(),
1964 2213 'home' => admin_url( 'admin.php' ),
2214 + 'siteurl' => site_url(),
1965 2215 'tables' => array_values( $wpdb->tables() ),
1966 2216 'date_format' => get_option( 'date_format' ),
1967 2217 'time_format' => get_option( 'time_format' ),
1968 2218 'scroll_offset' => WPDA::get_option( WPDA::OPTION_APPS_SCROLL_OFFSET ),
2219 + 'upload' => @ini_get( 'upload_max_filesize' ),
2220 + 'uploadBytes' => $this->uploadToBytes( @ini_get( 'upload_max_filesize' ) ),
2221 + 'locale' => get_locale(),
1969 2222 ];
1970 2223 return $settings;
1971 2224 }
1972 2225
2226 + /**
2227 + * Convert a PHP shorthand notation (e.g., "2M", "512K", "1G") to bytes.
2228 + *
2229 + * @param string $val The shorthand size string.
2230 + *
2231 + * @return int The size in bytes.
2232 + */
2233 + private function uploadToBytes( $val ) : int {
2234 + $val = trim( $val );
2235 + $lastChar = strtolower( $val[strlen( $val ) - 1] );
2236 + $num = (float) $val;
2237 + switch ( $lastChar ) {
2238 + case 'g':
2239 + $num *= 1024;
2240 + case 'm':
2241 + $num *= 1024;
2242 + case 'k':
2243 + $num *= 1024;
2244 + break;
2245 + }
2246 + return (int) round( $num );
2247 + }
2248 +
1973 2249 private function get_app_container_meta( $app_id, $container, $rel_tab = false ) {
1974 2250 $app = WPDA_App_Model::get_by_id( $app_id );
1975 2251 if ( false === $app ) {
1976 2252 return $this->bad_request();
@@ -2047,8 +2323,17 @@
2047 2323 return $this->get_app_container_meta( $app_id, $container );
2048 2324 }
2049 2325 }
2050 2326
2327 + private function escapeUnicodeForExport( $str ) {
2328 + if ( empty( $str ) ) {
2329 + return $str;
2330 + }
2331 + return preg_replace_callback( '/\\\\(u[0-9a-fA-F]{4})/', function ( $matches ) {
2332 + return '\\\\' . $matches[1];
2333 + }, $str );
2334 + }
2335 +
2051 2336 private function do_app_export_app( $app_id, $main_app_id ) {
2052 2337 global $wpdb;
2053 2338 $quotes = function ( $value ) {
2054 2339 return str_replace( array(
@@ -2058,9 +2343,10 @@
2058 2343 "\\t",
2059 2344 "\\\\n",
2060 2345 "\\n",
2061 2346 "\\r\\n",
2062 - "\\r"
2347 + "\\r",
2348 + "\\d"
2063 2349 ), array(
2064 2350 "''",
2065 2351 '\\\\"',
2066 2352 "\\\\\\t",
@@ -2067,9 +2353,10 @@
2067 2353 "\\\\t",
2068 2354 "\\\\\\n",
2069 2355 "\\\\n",
2070 2356 "\\\\r\\\\n",
2071 - "\\\\r"
2357 + "\\\\r",
2358 + "\\\\\\\\\\d"
2072 2359 ), $value );
2073 2360 };
2074 2361 $app = WPDA_App_Model::get_by_id( $app_id );
2075 2362 $app_settings = ( null === $app[0]['app_settings'] ? 'null' : "{$quotes( $app[0]['app_settings'] )}" );
@@ -2116,8 +2403,11 @@
2116 2403 // Replace default WordPress database with conversion string
2117 2404 $cnt_dbs = ( $wpdb->dbname === $container['cnt_dbs'] ? '{wp_schema}' : "{$quotes( $container['cnt_dbs'] )}" );
2118 2405 $cnt_table = str_replace( "\"dbs\":\"{$wpdb->dbname}\"", "\"dbs\":\"{wp_schema}\"", $cnt_table );
2119 2406 $cnt_form = str_replace( "\"dbs\":\"{$wpdb->dbname}\"", "\"dbs\":\"{wp_schema}\"", $cnt_form );
2407 + // Replace Unicode characters
2408 + $cnt_table = $this->escapeUnicodeForExport( $cnt_table );
2409 + $cnt_form = $this->escapeUnicodeForExport( $cnt_form );
2120 2410 // phpcs:ignore PluginCheck.CodeAnalysis.Heredoc.NotAllowed
2121 2411 $containers_sql .= <<<SQL
2122 2412 # Import app container
2123 2413 insert into `{wp_prefix}wpda_app_container`
@@ -2425,14 +2715,16 @@
2425 2715 $dynamic_params = array()
2426 2716 ) {
2427 2717 // Process $search_custom > URL parameters
2428 2718 global $wpdb;
2719 + $replacements = array();
2720 + $nonce = bin2hex( random_bytes( 8 ) );
2429 2721 foreach ( self::METHODS as $method ) {
2430 2722 $offset = 0;
2431 2723 $search = $method . '[';
2724 + // Get filter
2432 2725 while ( ($pos_start = stripos( $where, $search, $offset )) !== false ) {
2433 2726 if ( ($pos_end = stripos( $where, ']', $pos_start )) !== false ) {
2434 - // Get filter
2435 2727 $filter = substr( $where, $pos_start, $pos_end - $pos_start + 1 );
2436 2728 // Get name
2437 2729 $arg_name = substr( $where, $pos_start + strlen( $search ), $pos_end - $pos_start - strlen( $search ) );
2438 2730 // Remove quotes from name
@@ -2438,64 +2730,34 @@
2438 2730 // Remove quotes from name
2439 2731 if ( substr( $arg_name, 0, 1 ) === "'" && substr( $arg_name, -1 ) === "'" ) {
2440 2732 $arg_name = substr( $arg_name, 1, -1 );
2441 2733 }
2442 - // Remove double quotes from name
2734 + // Remove quotes from name
2443 2735 if ( substr( $arg_name, 0, 1 ) === '"' && substr( $arg_name, -1 ) === '"' ) {
2444 2736 $arg_name = substr( $arg_name, 1, -1 );
2445 2737 }
2738 + $arg_value = null;
2446 2739 // Handle GET args
2447 - // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
2448 - if ( $method === self::METHODS[0] ) {
2740 + if ( $method === self::METHODS[0] && isset( $search_custom['get'][$arg_name] ) ) {
2741 + $arg_value = sanitize_text_field( wp_unslash( $search_custom['get'][$arg_name] ) );
2742 + } elseif ( $method === self::METHODS[1] && isset( $search_custom['post'][$arg_name] ) ) {
2743 + $arg_value = sanitize_text_field( wp_unslash( $search_custom['post'][$arg_name] ) );
2744 + } elseif ( $method === self::METHODS[2] ) {
2449 2745 if ( isset( $search_custom['get'][$arg_name] ) ) {
2450 2746 $arg_value = sanitize_text_field( wp_unslash( $search_custom['get'][$arg_name] ) );
2451 - $where = $wpdb->prepare( substr_replace(
2452 - $where,
2453 - '%s',
2454 - $pos_start,
2455 - $pos_end - $pos_start + 1
2456 - ), $arg_value );
2457 - } else {
2458 - $where = str_replace( $filter, 'null', $where );
2747 + } elseif ( isset( $search_custom['post'][$arg_name] ) ) {
2748 + $arg_value = sanitize_text_field( wp_unslash( $search_custom['post'][$arg_name] ) );
2459 2749 }
2460 2750 }
2461 2751 // Handle POST args
2462 - if ( $method === self::METHODS[1] ) {
2463 - if ( isset( $search_custom['post'][$arg_name] ) ) {
2464 - $arg_value = sanitize_text_field( wp_unslash( $search_custom['post'][$arg_name] ) );
2465 - $where = $wpdb->prepare( substr_replace(
2466 - $where,
2467 - '%s',
2468 - $pos_start,
2469 - $pos_end - $pos_start + 1
2470 - ), $arg_value );
2471 - } else {
2472 - $where = str_replace( $filter, 'null', $where );
2473 - }
2474 - }
2752 + $placeholder = '###URL_PLACEHOLDER_' . $nonce . '_' . md5( $filter ) . '###';
2753 + $where = str_replace( $filter, $placeholder, $where );
2475 2754 // Handle REQUEST args
2476 - if ( $method === self::METHODS[2] ) {
2477 - if ( isset( $search_custom['get'][$arg_name] ) ) {
2478 - $arg_value = sanitize_text_field( wp_unslash( $search_custom['get'][$arg_name] ) );
2479 - $where = $wpdb->prepare( substr_replace(
2480 - $where,
2481 - '%s',
2482 - $pos_start,
2483 - $pos_end - $pos_start + 1
2484 - ), $arg_value );
2485 - } elseif ( isset( $search_custom['post'][$arg_name] ) ) {
2486 - $arg_value = sanitize_text_field( wp_unslash( $search_custom['post'][$arg_name] ) );
2487 - $where = $wpdb->prepare( substr_replace(
2488 - $where,
2489 - '%s',
2490 - $pos_start,
2491 - $pos_end - $pos_start + 1
2492 - ), $arg_value );
2493 - } else {
2494 - $where = str_replace( $filter, 'null', $where );
2495 - }
2755 + if ( $arg_value !== null ) {
2756 + $replacements[$placeholder] = $wpdb->prepare( '%s', $arg_value );
2757 + } else {
2758 + $replacements[$placeholder] = 'null';
2496 2759 }
2497 - // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared
2498 2760 }
2499 2761 $offset = $pos_start + 1;
2500 2762 if ( $offset > strlen( $where ) ) {
2501 2763 $offset = strlen( $where ) - 1;
@@ -2506,19 +2768,17 @@
2506 2768 if ( is_array( $search_params ) && 1 === count( $search_params ) ) {
2507 2769 $filter_field_name = $this->sanitize_db_identifier( array_keys( $search_params )[0] );
2508 2770 $filter_field_value = sanitize_text_field( $search_params[$filter_field_name] );
2509 2771 $filter_field_name_array = array_map( 'trim', explode( ',', $filter_field_name ) );
2510 - // phpcs:ignore -- 8.1 proof
2511 2772 $filter_field_value_array = array_map( 'trim', explode( ',', $filter_field_value ) );
2512 - // phpcs:ignore -- 8.1 proof
2513 2773 if ( count( $filter_field_name_array ) === count( $filter_field_value_array ) ) {
2514 - // phpcs:ignore -- 8.1 proof
2515 2774 // Add filter to where clause.
2516 - // phpcs:disable Generic.CodeAnalysis.ForLoopWithTestFunctionCall, Squiz.PHP.DisallowSizeFunctionsInLoops, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnquotedComplexPlaceholder
2517 2775 for ($i = 0; $i < count( $filter_field_name_array ); $i++) {
2518 - $where .= (( '' === $where ? '' : ' and ' )) . $wpdb->prepare( ' `%1s` like %s ', array($filter_field_name_array[$i], $filter_field_value_array[$i]) );
2776 + $where .= ( '' === $where ? '' : ' and ' );
2777 + $placeholder = '###SHORTCODE_PARAM_' . $nonce . '_' . md5( $filter_field_name_array[$i] ) . '###';
2778 + $where .= ' `' . $filter_field_name_array[$i] . '` like ' . $placeholder;
2779 + $replacements[$placeholder] = $wpdb->prepare( '%s', $filter_field_value_array[$i] );
2519 2780 }
2520 - // phpcs:enable Generic.CodeAnalysis.ForLoopWithTestFunctionCall, Squiz.PHP.DisallowSizeFunctionsInLoops, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnquotedComplexPlaceholder
2521 2781 }
2522 2782 }
2523 2783 // Substitute all shortcode parameters
2524 2784 if ( is_array( $shortcode_params ) ) {
@@ -2524,15 +2784,11 @@
2524 2784 if ( is_array( $shortcode_params ) ) {
2525 2785 foreach ( $shortcode_params as $column_name => $column_value ) {
2526 2786 $occurences = substr_count( strtolower( $where ), strtolower( "shortcodeParam['{$column_name}']" ) );
2527 2787 if ( 0 < $occurences ) {
2528 - $column_values = array();
2529 - for ($i = 0; $i < $occurences; $i++) {
2530 - $column_values[] = sanitize_text_field( $column_value );
2531 - }
2532 - // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
2533 - $where = $wpdb->prepare( str_ireplace( "shortcodeParam['{$column_name}']", '%s', $where ), $column_values );
2534 - // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
2788 + $placeholder = '###SHORTCODE_PARAM_' . $nonce . '_' . md5( $column_name ) . '###';
2789 + $where = str_ireplace( "shortcodeParam['{$column_name}']", $placeholder, $where );
2790 + $replacements[$placeholder] = $wpdb->prepare( '%s', sanitize_text_field( $column_value ) );
2535 2791 }
2536 2792 }
2537 2793 }
2538 2794 // Substitute all unused shortcode parameter calls with null
@@ -2550,13 +2806,15 @@
2550 2806 }
2551 2807 // Substitute all dynamic parameters
2552 2808 if ( is_array( $dynamic_params ) && 0 < count( $dynamic_params ) ) {
2553 2809 foreach ( $dynamic_params as $column_name => $column_value ) {
2554 - // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
2555 - $where = $wpdb->prepare( str_ireplace( "{:{$column_name}}", '%s', $where ), $column_value );
2556 - // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
2810 + if ( stripos( $where, "{:{$column_name}}" ) !== false ) {
2811 + $placeholder = '###DYNAMIC_PARAM_' . $nonce . '_' . md5( $column_name ) . '###';
2812 + $where = str_ireplace( "{:{$column_name}}", $placeholder, $where );
2813 + $replacements[$placeholder] = $wpdb->prepare( '%s', $column_value );
2814 + }
2557 2815 }
2558 2816 }
2559 - return $where;
2817 + return strtr( $where, $replacements );
2560 2818 }
2561 2819
2562 2820 }