PluginProbe
WP Data Access – App Builder for Tables, Forms, Charts, Maps & Dashboards / 5.5.84
WP Data Access – App Builder for Tables, Forms, Charts, Maps & Dashboards v5.5.84
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 5.5.41 5.5.42 All 160 releases
← All changes | WPDataAccess/API/WPDA_Apps.php +315 -112 5.5.765.5.84 View file →
@@ -27,8 +27,9 @@
27 27 $value->{$property_name} = $this->sanitize_settings( $property_value );
28 28 }
29 29 } else {
30 30 // Allow HTML and onclick for computed fields
31 + // phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
31 32 $value = apply_filters(
32 33 'wp_kses_post',
33 34 $value,
34 35 "",
@@ -33,8 +34,9 @@
33 34 $value,
34 35 "",
35 36 ["onclick"]
36 37 );
38 + // phpcs:enable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
37 39 }
38 40 return $value;
39 41 }
40 42
@@ -278,9 +280,13 @@
278 280 'map' => array(
279 281 'required' => false,
280 282 'type' => 'string',
281 283 'description' => __( 'Map settings - JSON string', 'wp-data-access' ),
282 - '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 + },
283 289 'validate_callback' => 'rest_validate_request_arg',
284 290 ),
285 291 'chart' => array(
286 292 'required' => false,
@@ -512,8 +518,15 @@
512 518 'permission_callback' => '__return_true',
513 519 'args' => array(
514 520 'dbs_source' => $this->get_param( 'dbs' ),
515 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 + ),
516 529 ),
517 530 ) );
518 531 register_rest_route( WPDA_API::WPDA_NAMESPACE, 'app/lang/get', array(
519 532 'methods' => array('POST'),
@@ -562,11 +575,112 @@
562 575 'validate_callback' => 'rest_validate_request_arg',
563 576 ),
564 577 ),
565 578 ) );
579 + // PWA
580 + register_rest_route( WPDA_API::WPDA_NAMESPACE, 'app/pwa/get', array(
581 + 'methods' => array('POST'),
582 + 'callback' => array($this, 'app_wpa_get'),
583 + 'permission_callback' => '__return_true',
584 + 'args' => array(
585 + 'app_id' => $this->get_param( 'app_id' ),
586 + ),
587 + ) );
588 + register_rest_route( WPDA_API::WPDA_NAMESPACE, 'app/pwa/activate', array(
589 + 'methods' => array('POST'),
590 + 'callback' => array($this, 'app_wpa_activate'),
591 + 'permission_callback' => '__return_true',
592 + 'args' => array(
593 + 'app_id' => $this->get_param( 'app_id' ),
594 + ),
595 + ) );
596 + register_rest_route( WPDA_API::WPDA_NAMESPACE, 'app/pwa/deactivate', array(
597 + 'methods' => array('POST'),
598 + 'callback' => array($this, 'app_wpa_deactivate'),
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/upload', array(
605 + 'methods' => array('POST'),
606 + 'callback' => array($this, 'app_upload'),
607 + 'permission_callback' => '__return_true',
608 + 'args' => array(
609 + 'app_id' => $this->get_param( 'app_id' ),
610 + 'cnt_id' => $this->get_param( 'cnt_id' ),
611 + 'pk' => array(
612 + 'required' => true,
613 + 'type' => 'string',
614 + 'description' => __( 'Primary key in JSON format', 'wp-data-access' ),
615 + 'sanitize_callback' => 'sanitize_text_field',
616 + 'validate_callback' => 'rest_validate_request_arg',
617 + ),
618 + 'col' => $this->get_param( 'col' ),
619 + ),
620 + ) );
621 + register_rest_route( WPDA_API::WPDA_NAMESPACE, 'app/download', array(
622 + 'methods' => array('POST'),
623 + 'callback' => array($this, 'app_download'),
624 + 'permission_callback' => '__return_true',
625 + 'args' => array(
626 + 'app_id' => $this->get_param( 'app_id' ),
627 + 'cnt_id' => $this->get_param( 'cnt_id' ),
628 + 'pk' => array(
629 + 'required' => true,
630 + 'type' => 'string',
631 + 'description' => __( 'Primary key in JSON format', 'wp-data-access' ),
632 + 'sanitize_callback' => 'sanitize_text_field',
633 + 'validate_callback' => 'rest_validate_request_arg',
634 + ),
635 + 'col' => $this->get_param( 'col' ),
636 + ),
637 + ) );
566 638 }
567 639
640 + public function app_download( $request ) {
641 + return $this->WPDA_Rest_Response( 'OK' );
642 + }
643 +
644 + public function app_upload( $request ) {
645 + return $this->WPDA_Rest_Response( 'OK' );
646 + }
647 +
648 + public function app_wpa_deactivate( $request ) {
649 + if ( !$this->current_user_can_access() ) {
650 + // Only admins
651 + return $this->unauthorized();
652 + }
653 + if ( !$this->current_user_token_valid( $request ) ) {
654 + return $this->invalid_nonce();
655 + }
656 + return $this->WPDA_Rest_Response( 'OK' );
657 + }
658 +
659 + public function app_wpa_activate( $request ) {
660 + if ( !$this->current_user_can_access() ) {
661 + // Only admins
662 + return $this->unauthorized();
663 + }
664 + if ( !$this->current_user_token_valid( $request ) ) {
665 + return $this->invalid_nonce();
666 + }
667 + return $this->WPDA_Rest_Response( 'OK' );
668 + }
669 +
670 + public function app_wpa_get( $request ) {
671 + if ( !$this->current_user_can_access() ) {
672 + // Only admins
673 + return $this->unauthorized();
674 + }
675 + if ( !$this->current_user_token_valid( $request ) ) {
676 + return $this->invalid_nonce();
677 + }
678 + return $this->WPDA_Rest_Response( 'NOT FOUND' );
679 + }
680 +
568 681 public function app_call( $request ) {
682 + return $this->bad_request();
569 683 }
570 684
571 685 public function app_lang_get( $request ) {
572 686 if ( !$this->current_user_can_access() ) {
@@ -826,8 +940,9 @@
826 940 $search_column_fns,
827 941 &$default_where,
828 942 &$lookups
829 943 ) {
944 + return $this->bad_request();
830 945 }
831 946
832 947 private function build_relationships(
833 948 $container,
@@ -834,8 +949,9 @@
834 949 &$m2m_relationship,
835 950 $tbl,
836 951 &$default_where
837 952 ) {
953 + return $this->bad_request();
838 954 }
839 955
840 956 public function app_select( $request ) {
841 957 $app_id = $request->get_param( 'app_id' );
@@ -858,8 +974,9 @@
858 974 $media = $request->get_param( 'media' );
859 975 $rel_tab = $request->get_param( 'rel_tab' );
860 976 $client_side = '1' === $request->get_param( 'client_side' );
861 977 $geo_radius = $request->get_param( 'geo_radius' );
978 + $docs = array();
862 979 $default_where = '';
863 980 $default_orderby = '';
864 981 $lookups = array();
865 982 $m2m_relationship = array();
@@ -907,8 +1024,27 @@
907 1024 }
908 1025 }
909 1026 }
910 1027 }
1028 + if ( isset( $settings['columns'] ) && is_array( $settings['columns'] ) && '1' !== $rel_tab ) {
1029 + $queryable_columns = array();
1030 + if ( isset( $settings['table']['columns'] ) && is_array( $settings['table']['columns'] ) ) {
1031 + foreach ( $settings['table']['columns'] as $column ) {
1032 + if ( isset( $column['queryable'] ) && $column['queryable'] ) {
1033 + $queryable_columns[] = $column['columnName'];
1034 + }
1035 + }
1036 + }
1037 + $app_columns = array();
1038 + foreach ( $settings['columns'] as $column ) {
1039 + if ( isset( $column['columnName'], $column['isSelected'] ) && $column['isSelected'] ) {
1040 + $app_columns[$column['columnName']] = in_array( $column['columnName'], $queryable_columns );
1041 + }
1042 + }
1043 + if ( 0 < count( $app_columns ) ) {
1044 + $col = $app_columns;
1045 + }
1046 + }
911 1047 $table_api = new WPDA_Table();
912 1048 return $table_api->select(
913 1049 $dbs,
914 1050 $tbl,
@@ -933,9 +1069,10 @@
933 1069 $md,
934 1070 $m2m_relationship,
935 1071 $search_data_types,
936 1072 $client_side,
937 - $geo_radius
1073 + $geo_radius,
1074 + $docs
938 1075 );
939 1076 } else {
940 1077 if ( 'rest_cookie_invalid_nonce' === $msg ) {
941 1078 return $this->invalid_nonce();
@@ -959,8 +1096,9 @@
959 1096 return array_map( function ( $value ) {
960 1097 if ( true === $value['isSelected'] ) {
961 1098 return $value['columnName'];
962 1099 }
1100 + return null;
963 1101 }, $columns );
964 1102 }
965 1103
966 1104 public function app_get( $request ) {
@@ -968,8 +1106,9 @@
968 1106 $cnt_id = $request->get_param( 'cnt_id' );
969 1107 $key = $request->get_param( 'key' );
970 1108 $media = $request->get_param( 'media' );
971 1109 $rel_tab = $request->get_param( 'rel_tab' );
1110 + $docs = array();
972 1111 if ( $this->check_app_access(
973 1112 $app_id,
974 1113 $cnt_id,
975 1114 'select',
@@ -989,9 +1128,10 @@
989 1128 $tbl,
990 1129 $key,
991 1130 $media,
992 1131 $column_names,
993 - $default_where
1132 + $default_where,
1133 + $docs
994 1134 );
995 1135 } else {
996 1136 if ( 'rest_cookie_invalid_nonce' === $msg ) {
997 1137 return $this->invalid_nonce();
@@ -1080,53 +1220,9 @@
1080 1220 }
1081 1221 }
1082 1222
1083 1223 public function app_update_inline( $request ) {
1084 - $app_id = $request->get_param( 'app_id' );
1085 - $cnt_id = $request->get_param( 'cnt_id' );
1086 - $key = $request->get_param( 'key' );
1087 - $val = $request->get_param( 'val' );
1088 - if ( $this->check_app_access(
1089 - $app_id,
1090 - $cnt_id,
1091 - 'select',
1092 - $dbs,
1093 - $tbl,
1094 - $msg,
1095 - $settings
1096 - ) ) {
1097 - foreach ( $val as $column_name => $column ) {
1098 - $found = false;
1099 - if ( isset( $settings['table']['columns'] ) ) {
1100 - foreach ( $settings['table']['columns'] as $settings_column ) {
1101 - if ( isset( $settings_column['columnName'] ) && $column_name === $settings_column['columnName'] ) {
1102 - $found = true;
1103 - }
1104 - }
1105 - if ( !$found ) {
1106 - return $this->unauthorized();
1107 - }
1108 - }
1109 - }
1110 - $column_names = $this->get_app_form_columns( $settings );
1111 - if ( false === $column_names ) {
1112 - $column_names = array();
1113 - }
1114 - $table_api = new WPDA_Table();
1115 - return $table_api->update(
1116 - $dbs,
1117 - $tbl,
1118 - $key,
1119 - $val,
1120 - $column_names
1121 - );
1122 - } else {
1123 - if ( 'rest_cookie_invalid_nonce' === $msg ) {
1124 - return $this->invalid_nonce();
1125 - } else {
1126 - return $this->unauthorized();
1127 - }
1128 - }
1224 + return $this->bad_request();
1129 1225 }
1130 1226
1131 1227 public function app_delete( $request ) {
1132 1228 $app_id = $request->get_param( 'app_id' );
@@ -1276,8 +1372,12 @@
1276 1372 }
1277 1373 }
1278 1374
1279 1375 public function app_lookup_dbs( $request ) {
1376 + // Only admins are allowed to configre lookups
1377 + if ( !$this->current_user_can_access() ) {
1378 + return $this->unauthorized();
1379 + }
1280 1380 $app_id = $request->get_param( 'app_id' );
1281 1381 $cnt_id = $request->get_param( 'cnt_id' );
1282 1382 if ( $this->check_app_access(
1283 1383 $app_id,
@@ -1300,8 +1400,12 @@
1300 1400 }
1301 1401 }
1302 1402
1303 1403 public function app_lookup_tbl( $request ) {
1404 + // Only admins are allowed to configre lookups
1405 + if ( !$this->current_user_can_access() ) {
1406 + return $this->unauthorized();
1407 + }
1304 1408 $app_id = $request->get_param( 'app_id' );
1305 1409 $cnt_id = $request->get_param( 'cnt_id' );
1306 1410 $dbs = $request->get_param( 'dbs' );
1307 1411 if ( $this->check_app_access(
@@ -1325,8 +1429,12 @@
1325 1429 }
1326 1430 }
1327 1431
1328 1432 public function app_lookup_cls( $request ) {
1433 + // Only admins are allowed to configre lookups
1434 + if ( !$this->current_user_can_access() ) {
1435 + return $this->unauthorized();
1436 + }
1329 1437 $app_id = $request->get_param( 'app_id' );
1330 1438 $cnt_id = $request->get_param( 'cnt_id' );
1331 1439 $dbs = $request->get_param( 'dbs' );
1332 1440 $tbl = $request->get_param( 'tbl' );
@@ -1375,8 +1483,68 @@
1375 1483 $renamed = 0;
1376 1484 $debug_mode = 'on' === WPDA::get_option( WPDA::OPTION_PLUGIN_DEBUG );
1377 1485 $debug = array();
1378 1486 $errors = array();
1487 + if ( $request->get_param( 'appDbId' ) ) {
1488 + // Update app only
1489 + $appDbId = $request->get_param( 'appDbId' );
1490 + // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared -- plugin table
1491 + $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) );
1492 + $result = $wpdb->query( $sql );
1493 + // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
1494 + $renamed += $result;
1495 + if ( $debug_mode ) {
1496 + $debug[] = array(
1497 + 'sql' => $sql,
1498 + 'result' => $result,
1499 + );
1500 + }
1501 + if ( '' !== $wpdb->last_error ) {
1502 + $errors[] = array(
1503 + 'sql' => $sql,
1504 + 'error' => $wpdb->last_error,
1505 + );
1506 + }
1507 + $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");
1508 + foreach ( $sql_content as $sql ) {
1509 + // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared -- plugin table
1510 + $result = $wpdb->query( $wpdb->prepare( $sql, array(
1511 + $dbs_source,
1512 + $dbs_destination,
1513 + $dbs_source,
1514 + $appDbId
1515 + ) ) );
1516 + $renamed += $result;
1517 + if ( $debug_mode ) {
1518 + $debug[] = array(
1519 + 'sql' => $sql,
1520 + 'result' => $result,
1521 + );
1522 + }
1523 + if ( '' !== $wpdb->last_error ) {
1524 + $errors[] = array(
1525 + 'sql' => $sql,
1526 + 'error' => $wpdb->last_error,
1527 + );
1528 + }
1529 + }
1530 + $context = array();
1531 + if ( $debug_mode ) {
1532 + $context['debug'] = $debug;
1533 + }
1534 + if ( 0 < count( $errors ) ) {
1535 + $context['errors'] = $errors;
1536 + return new \WP_Error('error', 'Failed renaming database', array(
1537 + 'status' => 401,
1538 + 'context' => $context,
1539 + ));
1540 + }
1541 + return $this->WPDA_Rest_Response( sprintf(
1542 + /* translators: %s = number of database substitutions */
1543 + __( 'Successfully renamed %s database occurrences', 'wp-data-access' ),
1544 + $renamed
1545 + ), null, $context );
1546 + }
1379 1547 // Rename all occurrences in repository tables and apps
1380 1548 $sqls = array(
1381 1549 "update `{$wpdb->prefix}wpda_publisher` set `pub_schema_name` = %s where `pub_schema_name` = %s",
1382 1550 "update `{$wpdb->prefix}wpda_project_page` set `page_schema_name` = %s where `page_schema_name` = %s",
@@ -1387,9 +1555,11 @@
1387 1555 "update `{$wpdb->prefix}wpda_table_settings` set `wpda_schema_name` = %s where `wpda_schema_name` = %s",
1388 1556 "update `{$wpdb->prefix}wpda_app_container` set `cnt_dbs` = %s where `cnt_dbs` = %s"
1389 1557 );
1390 1558 foreach ( $sqls as $sql ) {
1559 + // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared -- plugin table
1391 1560 $result = $wpdb->query( $wpdb->prepare( $sql, array($dbs_destination, $dbs_source) ) );
1561 + // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
1392 1562 $renamed += $result;
1393 1563 if ( $debug_mode ) {
1394 1564 $debug[] = array(
1395 1565 'sql' => $sql,
@@ -1404,8 +1574,9 @@
1404 1574 }
1405 1575 }
1406 1576 $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\"%'", "update `{$wpdb->prefix}wpda_app_container` set `cnt_form` = replace(`cnt_form`, '\"dbs\":\"%1s\"', '\"dbs\":\"%1s\"') where `cnt_form` like '%\"dbs\":\"%1s\"%'");
1407 1577 foreach ( $sql_content as $sql ) {
1578 + // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared -- plugin table
1408 1579 $result = $wpdb->query( $wpdb->prepare( $sql, array($dbs_source, $dbs_destination, $dbs_source) ) );
1409 1580 $renamed += $result;
1410 1581 if ( $debug_mode ) {
1411 1582 $debug[] = array(
@@ -1430,9 +1601,13 @@
1430 1601 'status' => 401,
1431 1602 'context' => $context,
1432 1603 ));
1433 1604 }
1434 - return $this->WPDA_Rest_Response( sprintf( __( 'Successfully renamed %s database occurrences', 'wp-data-access' ), $renamed ), null, $context );
1605 + return $this->WPDA_Rest_Response( sprintf(
1606 + /* translators: %s = number of database substitutions */
1607 + __( 'Successfully renamed %s database occurrences', 'wp-data-access' ),
1608 + $renamed
1609 + ), null, $context );
1435 1610 }
1436 1611
1437 1612 public function app_chart_data( $request ) {
1438 1613 $app_id = $request->get_param( 'app_id' );
@@ -1466,14 +1641,23 @@
1466 1641 ));
1467 1642 }
1468 1643 $suppress = $wpdadb->suppress_errors( true );
1469 1644 $chart_data = $wpdadb->get_results( $query, 'ARRAY_A' );
1645 + // Add an * before each label to support numeric labels
1646 + $chart_data_converted = array();
1647 + foreach ( $chart_data as $data ) {
1648 + $row = array();
1649 + foreach ( $data as $key => $value ) {
1650 + $row['*' . $key] = $value;
1651 + }
1652 + $chart_data_converted[] = $row;
1653 + }
1470 1654 $wpdadb->get_results( "create temporary table `wpda_chart_data_types` as {$query}", 'ARRAY_A' );
1471 1655 $explain = $wpdadb->get_results( "desc `wpda_chart_data_types`", 'ARRAY_A' );
1472 1656 $wpdadb->get_results( "drop temporary table `wpda_chart_data_types`", 'ARRAY_A' );
1473 1657 $wpdadb->suppress_errors( $suppress );
1474 1658 return array(
1475 - 'data' => $chart_data,
1659 + 'data' => $chart_data_converted,
1476 1660 'explain' => $explain,
1477 1661 );
1478 1662 } else {
1479 1663 return new \WP_Error('error', $msg, array(
@@ -1954,8 +2138,9 @@
1954 2138 $app_id_details = array_map( function ( $e ) {
1955 2139 if ( isset( $e['app_id_detail'] ) ) {
1956 2140 return $e['app_id_detail'];
1957 2141 }
2142 + return null;
1958 2143 }, $apps );
1959 2144 $app_titles = array();
1960 2145 foreach ( $app_id_details as $app_id_detail ) {
1961 2146 $app_detail = WPDA_App_Model::get_by_id( $app_id_detail );
@@ -1993,16 +2178,43 @@
1993 2178 $settings->wp = [
1994 2179 'roles' => $this->get_wp_roles(),
1995 2180 'users' => $this->get_wp_users(),
1996 2181 'home' => admin_url( 'admin.php' ),
2182 + 'siteurl' => site_url(),
1997 2183 'tables' => array_values( $wpdb->tables() ),
1998 2184 'date_format' => get_option( 'date_format' ),
1999 2185 'time_format' => get_option( 'time_format' ),
2000 2186 'scroll_offset' => WPDA::get_option( WPDA::OPTION_APPS_SCROLL_OFFSET ),
2187 + 'upload' => @ini_get( 'upload_max_filesize' ),
2188 + 'uploadBytes' => $this->uploadToBytes( @ini_get( 'upload_max_filesize' ) ),
2189 + 'locale' => get_locale(),
2001 2190 ];
2002 2191 return $settings;
2003 2192 }
2004 2193
2194 + /**
2195 + * Convert a PHP shorthand notation (e.g., "2M", "512K", "1G") to bytes.
2196 + *
2197 + * @param string $val The shorthand size string.
2198 + *
2199 + * @return int The size in bytes.
2200 + */
2201 + private function uploadToBytes( $val ) : int {
2202 + $val = trim( $val );
2203 + $lastChar = strtolower( $val[strlen( $val ) - 1] );
2204 + $num = (float) $val;
2205 + switch ( $lastChar ) {
2206 + case 'g':
2207 + $num *= 1024;
2208 + case 'm':
2209 + $num *= 1024;
2210 + case 'k':
2211 + $num *= 1024;
2212 + break;
2213 + }
2214 + return (int) round( $num );
2215 + }
2216 +
2005 2217 private function get_app_container_meta( $app_id, $container, $rel_tab = false ) {
2006 2218 $app = WPDA_App_Model::get_by_id( $app_id );
2007 2219 if ( false === $app ) {
2008 2220 return $this->bad_request();
@@ -2079,8 +2291,17 @@
2079 2291 return $this->get_app_container_meta( $app_id, $container );
2080 2292 }
2081 2293 }
2082 2294
2295 + private function escapeUnicodeForExport( $str ) {
2296 + if ( empty( $str ) ) {
2297 + return $str;
2298 + }
2299 + return preg_replace_callback( '/\\\\(u[0-9a-fA-F]{4})/', function ( $matches ) {
2300 + return '\\\\' . $matches[1];
2301 + }, $str );
2302 + }
2303 +
2083 2304 private function do_app_export_app( $app_id, $main_app_id ) {
2084 2305 global $wpdb;
2085 2306 $quotes = function ( $value ) {
2086 2307 return str_replace( array(
@@ -2090,9 +2311,10 @@
2090 2311 "\\t",
2091 2312 "\\\\n",
2092 2313 "\\n",
2093 2314 "\\r\\n",
2094 - "\\r"
2315 + "\\r",
2316 + "\\d"
2095 2317 ), array(
2096 2318 "''",
2097 2319 '\\\\"',
2098 2320 "\\\\\\t",
@@ -2099,14 +2321,16 @@
2099 2321 "\\\\t",
2100 2322 "\\\\\\n",
2101 2323 "\\\\n",
2102 2324 "\\\\r\\\\n",
2103 - "\\\\r"
2325 + "\\\\r",
2326 + "\\\\\\\\\\d"
2104 2327 ), $value );
2105 2328 };
2106 2329 $app = WPDA_App_Model::get_by_id( $app_id );
2107 2330 $app_settings = ( null === $app[0]['app_settings'] ? 'null' : "{$quotes( $app[0]['app_settings'] )}" );
2108 2331 $app_theme = ( null === $app[0]['app_theme'] ? 'null' : "{$quotes( $app[0]['app_theme'] )}" );
2332 + // phpcs:ignore PluginCheck.CodeAnalysis.Heredoc.NotAllowed
2109 2333 $app_sql = <<<SQL
2110 2334 # Import app
2111 2335 insert into `{wp_prefix}wpda_app`
2112 2336 \t(`app_name`
@@ -2147,8 +2371,12 @@
2147 2371 // Replace default WordPress database with conversion string
2148 2372 $cnt_dbs = ( $wpdb->dbname === $container['cnt_dbs'] ? '{wp_schema}' : "{$quotes( $container['cnt_dbs'] )}" );
2149 2373 $cnt_table = str_replace( "\"dbs\":\"{$wpdb->dbname}\"", "\"dbs\":\"{wp_schema}\"", $cnt_table );
2150 2374 $cnt_form = str_replace( "\"dbs\":\"{$wpdb->dbname}\"", "\"dbs\":\"{wp_schema}\"", $cnt_form );
2375 + // Replace Unicode characters
2376 + $cnt_table = $this->escapeUnicodeForExport( $cnt_table );
2377 + $cnt_form = $this->escapeUnicodeForExport( $cnt_form );
2378 + // phpcs:ignore PluginCheck.CodeAnalysis.Heredoc.NotAllowed
2151 2379 $containers_sql .= <<<SQL
2152 2380 # Import app container
2153 2381 insert into `{wp_prefix}wpda_app_container`
2154 2382 \t(`cnt_dbs`
@@ -2190,8 +2418,9 @@
2190 2418
2191 2419 SQL;
2192 2420 }
2193 2421 // Post update: update master container ids
2422 + // phpcs:ignore PluginCheck.CodeAnalysis.Heredoc.NotAllowed
2194 2423 $containers_sql .= <<<SQL
2195 2424 # Update app master container IDs
2196 2425 update `{wp_prefix}wpda_app_container` as a
2197 2426 set a.`cnt_relation` =
@@ -2214,8 +2443,9 @@
2214 2443 foreach ( $apps as $app ) {
2215 2444 $apps_sql .= $this->do_app_export_app( $app['app_id_detail'], $main_app_id );
2216 2445 }
2217 2446 foreach ( $apps as $app ) {
2447 + // phpcs:ignore PluginCheck.CodeAnalysis.Heredoc.NotAllowed
2218 2448 $apps_sql .= <<<SQL
2219 2449 # Import app relationships
2220 2450 insert into `{wp_prefix}wpda_app_apps`
2221 2451 \t(`app_id`
@@ -2236,8 +2466,9 @@
2236 2466
2237 2467 private function do_app_export( $app_id ) {
2238 2468 global $wpdb;
2239 2469 $sql = '';
2470 + // phpcs:ignore PluginCheck.CodeAnalysis.Heredoc.NotAllowed
2240 2471 $begin_sql = <<<SQL
2241 2472 /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
2242 2473 /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
2243 2474 /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
@@ -2258,8 +2489,9 @@
2258 2489
2259 2490
2260 2491 SQL;
2261 2492 $sql .= $this->do_app_export_app( $app_id, $app_id );
2493 + // phpcs:ignore PluginCheck.CodeAnalysis.Heredoc.NotAllowed
2262 2494 $end_sql = <<<SQL
2263 2495 # Drop temporary table
2264 2496 DROP TABLE `wpda_transfer_containers_{$app_id}`;
2265 2497 DROP TABLE `wpda_transfer_apps_{$app_id}`;
@@ -2451,14 +2683,16 @@
2451 2683 $dynamic_params = array()
2452 2684 ) {
2453 2685 // Process $search_custom > URL parameters
2454 2686 global $wpdb;
2687 + $replacements = array();
2688 + $nonce = bin2hex( random_bytes( 8 ) );
2455 2689 foreach ( self::METHODS as $method ) {
2456 2690 $offset = 0;
2457 2691 $search = $method . '[';
2692 + // Get filter
2458 2693 while ( ($pos_start = stripos( $where, $search, $offset )) !== false ) {
2459 2694 if ( ($pos_end = stripos( $where, ']', $pos_start )) !== false ) {
2460 - // Get filter
2461 2695 $filter = substr( $where, $pos_start, $pos_end - $pos_start + 1 );
2462 2696 // Get name
2463 2697 $arg_name = substr( $where, $pos_start + strlen( $search ), $pos_end - $pos_start - strlen( $search ) );
2464 2698 // Remove quotes from name
@@ -2464,61 +2698,33 @@
2464 2698 // Remove quotes from name
2465 2699 if ( substr( $arg_name, 0, 1 ) === "'" && substr( $arg_name, -1 ) === "'" ) {
2466 2700 $arg_name = substr( $arg_name, 1, -1 );
2467 2701 }
2468 - // Remove double quotes from name
2702 + // Remove quotes from name
2469 2703 if ( substr( $arg_name, 0, 1 ) === '"' && substr( $arg_name, -1 ) === '"' ) {
2470 2704 $arg_name = substr( $arg_name, 1, -1 );
2471 2705 }
2706 + $arg_value = null;
2472 2707 // Handle GET args
2473 - if ( $method === self::METHODS[0] ) {
2708 + if ( $method === self::METHODS[0] && isset( $search_custom['get'][$arg_name] ) ) {
2709 + $arg_value = sanitize_text_field( wp_unslash( $search_custom['get'][$arg_name] ) );
2710 + } elseif ( $method === self::METHODS[1] && isset( $search_custom['post'][$arg_name] ) ) {
2711 + $arg_value = sanitize_text_field( wp_unslash( $search_custom['post'][$arg_name] ) );
2712 + } elseif ( $method === self::METHODS[2] ) {
2474 2713 if ( isset( $search_custom['get'][$arg_name] ) ) {
2475 2714 $arg_value = sanitize_text_field( wp_unslash( $search_custom['get'][$arg_name] ) );
2476 - $where = $wpdb->prepare( substr_replace(
2477 - $where,
2478 - '%s',
2479 - $pos_start,
2480 - $pos_end - $pos_start + 1
2481 - ), $arg_value );
2482 - } else {
2483 - $where = str_replace( $filter, 'null', $where );
2715 + } elseif ( isset( $search_custom['post'][$arg_name] ) ) {
2716 + $arg_value = sanitize_text_field( wp_unslash( $search_custom['post'][$arg_name] ) );
2484 2717 }
2485 2718 }
2486 2719 // Handle POST args
2487 - if ( $method === self::METHODS[1] ) {
2488 - if ( isset( $search_custom['post'][$arg_name] ) ) {
2489 - $arg_value = sanitize_text_field( wp_unslash( $search_custom['post'][$arg_name] ) );
2490 - $where = $wpdb->prepare( substr_replace(
2491 - $where,
2492 - '%s',
2493 - $pos_start,
2494 - $pos_end - $pos_start + 1
2495 - ), $arg_value );
2496 - } else {
2497 - $where = str_replace( $filter, 'null', $where );
2498 - }
2499 - }
2720 + $placeholder = '###URL_PLACEHOLDER_' . $nonce . '_' . md5( $filter ) . '###';
2721 + $where = str_replace( $filter, $placeholder, $where );
2500 2722 // Handle REQUEST args
2501 - if ( $method === self::METHODS[2] ) {
2502 - if ( isset( $search_custom['get'][$arg_name] ) ) {
2503 - $arg_value = sanitize_text_field( wp_unslash( $search_custom['get'][$arg_name] ) );
2504 - $where = $wpdb->prepare( substr_replace(
2505 - $where,
2506 - '%s',
2507 - $pos_start,
2508 - $pos_end - $pos_start + 1
2509 - ), $arg_value );
2510 - } elseif ( isset( $search_custom['post'][$arg_name] ) ) {
2511 - $arg_value = sanitize_text_field( wp_unslash( $search_custom['post'][$arg_name] ) );
2512 - $where = $wpdb->prepare( substr_replace(
2513 - $where,
2514 - '%s',
2515 - $pos_start,
2516 - $pos_end - $pos_start + 1
2517 - ), $arg_value );
2518 - } else {
2519 - $where = str_replace( $filter, 'null', $where );
2520 - }
2723 + if ( $arg_value !== null ) {
2724 + $replacements[$placeholder] = $wpdb->prepare( '%s', $arg_value );
2725 + } else {
2726 + $replacements[$placeholder] = 'null';
2521 2727 }
2522 2728 }
2523 2729 $offset = $pos_start + 1;
2524 2730 if ( $offset > strlen( $where ) ) {
@@ -2530,21 +2736,16 @@
2530 2736 if ( is_array( $search_params ) && 1 === count( $search_params ) ) {
2531 2737 $filter_field_name = $this->sanitize_db_identifier( array_keys( $search_params )[0] );
2532 2738 $filter_field_value = sanitize_text_field( $search_params[$filter_field_name] );
2533 2739 $filter_field_name_array = array_map( 'trim', explode( ',', $filter_field_name ) );
2534 - //phpcs:ignore - 8.1 proof
2535 2740 $filter_field_value_array = array_map( 'trim', explode( ',', $filter_field_value ) );
2536 - //phpcs:ignore - 8.1 proof
2537 2741 if ( count( $filter_field_name_array ) === count( $filter_field_value_array ) ) {
2538 - //phpcs:ignore - 8.1 proof
2539 2742 // Add filter to where clause.
2540 2743 for ($i = 0; $i < count( $filter_field_name_array ); $i++) {
2541 - // phpcs:ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall, Squiz.PHP.DisallowSizeFunctionsInLoops
2542 - $where .= (( '' === $where ? '' : ' and ' )) . $wpdb->prepare(
2543 - ' `%1s` like %s ',
2544 - // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders
2545 - array($filter_field_name_array[$i], $filter_field_value_array[$i])
2546 - );
2744 + $where .= ( '' === $where ? '' : ' and ' );
2745 + $placeholder = '###SHORTCODE_PARAM_' . $nonce . '_' . md5( $filter_field_name_array[$i] ) . '###';
2746 + $where .= ' `' . $filter_field_name_array[$i] . '` like ' . $placeholder;
2747 + $replacements[$placeholder] = $wpdb->prepare( '%s', $filter_field_value_array[$i] );
2547 2748 }
2548 2749 }
2549 2750 }
2550 2751 // Substitute all shortcode parameters
@@ -2551,13 +2752,11 @@
2551 2752 if ( is_array( $shortcode_params ) ) {
2552 2753 foreach ( $shortcode_params as $column_name => $column_value ) {
2553 2754 $occurences = substr_count( strtolower( $where ), strtolower( "shortcodeParam['{$column_name}']" ) );
2554 2755 if ( 0 < $occurences ) {
2555 - $column_values = array();
2556 - for ($i = 0; $i < $occurences; $i++) {
2557 - $column_values[] = sanitize_text_field( $column_value );
2558 - }
2559 - $where = $wpdb->prepare( str_ireplace( "shortcodeParam['{$column_name}']", '%s', $where ), $column_values );
2756 + $placeholder = '###SHORTCODE_PARAM_' . $nonce . '_' . md5( $column_name ) . '###';
2757 + $where = str_ireplace( "shortcodeParam['{$column_name}']", $placeholder, $where );
2758 + $replacements[$placeholder] = $wpdb->prepare( '%s', sanitize_text_field( $column_value ) );
2560 2759 }
2561 2760 }
2562 2761 }
2563 2762 // Substitute all unused shortcode parameter calls with null
@@ -2575,11 +2774,15 @@
2575 2774 }
2576 2775 // Substitute all dynamic parameters
2577 2776 if ( is_array( $dynamic_params ) && 0 < count( $dynamic_params ) ) {
2578 2777 foreach ( $dynamic_params as $column_name => $column_value ) {
2579 - $where = $wpdb->prepare( str_ireplace( "{:{$column_name}}", '%s', $where ), $column_value );
2778 + if ( stripos( $where, "{:{$column_name}}" ) !== false ) {
2779 + $placeholder = '###DYNAMIC_PARAM_' . $nonce . '_' . md5( $column_name ) . '###';
2780 + $where = str_ireplace( "{:{$column_name}}", $placeholder, $where );
2781 + $replacements[$placeholder] = $wpdb->prepare( '%s', $column_value );
2782 + }
2580 2783 }
2581 2784 }
2582 - return $where;
2785 + return strtr( $where, $replacements );
2583 2786 }
2584 2787
2585 2788 }