← All changes
|
includes/rest-api/controller/version2/class-mainwp-rest-monitors-controller.php
+131
-16
6.1.7
→
6.3
View file →
| @@ -300,9 +300,9 @@ | ||
| 300 | 300 | array( |
| 301 | 301 | array( |
| 302 | 302 | 'methods' => 'PUT, PATCH', |
| 303 | 303 | 'callback' => array( $this, 'update_global_monitoring_settings' ), |
| 304 | - 'permission_callback' => array( $this, 'get_rest_permissions_check' ), | |
| 304 | + 'permission_callback' => array( $this, 'get_unrestricted_permissions_check' ), | |
| 305 | 305 | 'args' => $this->get_global_monitor_settings_allowed_fields(), |
| 306 | 306 | ), |
| 307 | 307 | ) |
| 308 | 308 | ); |
| @@ -337,10 +337,9 @@ | ||
| 337 | 337 | $one_week_ago = gmdate( 'Y-m-d', $now - 7 * DAY_IN_SECONDS ); |
| 338 | 338 | // Get last 24h hourly key. |
| 339 | 339 | $last24_key = MainWP_Uptime_Monitoring_Handle::get_hourly_key_by_timestamp( $now - DAY_IN_SECONDS ); |
| 340 | 340 | |
| 341 | - // Get data from uptime monitoring DB. | |
| 342 | - $monitors = $this->db->get_monitors( $params ); // get monitors. | |
| 341 | + $monitors = $this->get_scoped_monitors_page( $params ); | |
| 343 | 342 | if ( empty( $monitors ) || ! is_array( $monitors ) ) { |
| 344 | 343 | return rest_ensure_response( |
| 345 | 344 | array( |
| 346 | 345 | 'success' => 1, |
| @@ -366,10 +365,11 @@ | ||
| 366 | 365 | $incidents_7d = (int) $this->db->count_site_incidents_stats( $monitor_id, $one_week_ago, $today ); |
| 367 | 366 | $incidents_30d = (int) $this->db->count_site_incidents_stats( $monitor_id, $one_month_ago, $today ); |
| 368 | 367 | |
| 369 | 368 | // Get monitor type & check frequency. |
| 370 | - $type = $this->get_apply_setting( 'type', $monitor->type ?? '', 'useglobal', 'http' ); | |
| 371 | - $interval_min = (int) $this->get_apply_setting( 'interval', (int) ( $monitor->interval ?? 0 ), -1, 60 ); | |
| 369 | + $type = $this->get_apply_setting( 'type', $monitor->type ?? '', 'useglobal', 'http' ); | |
| 370 | + $interval_min = (int) $this->get_apply_setting( 'interval', (int) ( $monitor->interval ?? 0 ), -1, 60 ); | |
| 371 | + $bypass_cache_opt = (int) $this->get_apply_setting( 'bypass_cache', (int) ( $monitor->bypass_cache ?? -1 ), -1, 0 ); | |
| 372 | 372 | |
| 373 | 373 | $record = array( |
| 374 | 374 | 'id' => $monitor_id, |
| 375 | 375 | 'url' => $monitor->url ?? '', |
| @@ -385,8 +385,9 @@ | ||
| 385 | 385 | 'response_time_max_ms' => (int) ( $reports_data['max_time_ms'] ?? 0 ), |
| 386 | 386 | 'last_check_at' => ! empty( $monitor->lasttime_check ) ? gmdate( 'Y-m-d H:i:s', (int) $monitor->lasttime_check ) : '', |
| 387 | 387 | 'last_status_code' => ! empty( $monitor->last_http_code ) ? (int) $monitor->last_http_code : '', |
| 388 | 388 | 'last_status' => $this->uptime_status( $monitor->last_status ?? null ), |
| 389 | + 'bypass_cache' => $bypass_cache_opt, | |
| 389 | 390 | ); |
| 390 | 391 | |
| 391 | 392 | // Filter data by allowed fields. |
| 392 | 393 | $data[] = $this->filter_response_data_by_allowed_fields( $record, 'view' ); |
| @@ -419,10 +420,9 @@ | ||
| 419 | 420 | 'page' => ! empty( $args['paged'] ) ? (int) $args['paged'] : 1, |
| 420 | 421 | 'per_page' => ! empty( $args['items_per_page'] ) ? (int) $args['items_per_page'] : 20, |
| 421 | 422 | ); |
| 422 | 423 | |
| 423 | - // Get data from uptime monitoring DB. | |
| 424 | - $monitors = $this->db->get_monitors( $params ); // get monitors. | |
| 424 | + $monitors = $this->get_scoped_monitors_page( $params ); | |
| 425 | 425 | if ( empty( $monitors ) || ! is_array( $monitors ) ) { |
| 426 | 426 | return rest_ensure_response( |
| 427 | 427 | array( |
| 428 | 428 | 'success' => 1, |
| @@ -454,8 +454,26 @@ | ||
| 454 | 454 | ); |
| 455 | 455 | } |
| 456 | 456 | |
| 457 | 457 | /** |
| 458 | + * A restricted key scopes the monitor query to its allowed sites before the SQL | |
| 459 | + * LIMIT, so hidden monitors neither consume a page nor get loaded at all. | |
| 460 | + * | |
| 461 | + * @param array $params Monitor query parameters including page and per_page. | |
| 462 | + * @return array|false Monitors for the requested page. | |
| 463 | + */ | |
| 464 | + private function get_scoped_monitors_page( array $params ) { | |
| 465 | + $allowed = \MainWP\Dashboard\MainWP_Abilities_Util::get_scoped_site_ids_or_all(); | |
| 466 | + if ( 'all' !== $allowed ) { | |
| 467 | + if ( empty( $allowed ) ) { | |
| 468 | + return array(); | |
| 469 | + } | |
| 470 | + $params['custom_where'] = ( $params['custom_where'] ?? '' ) . ' AND mo.wpid IN (' . implode( ',', array_map( 'intval', $allowed ) ) . ')'; | |
| 471 | + } | |
| 472 | + return $this->db->get_monitors( $params ); | |
| 473 | + } | |
| 474 | + | |
| 475 | + /** | |
| 458 | 476 | * Count all Monitors. |
| 459 | 477 | * |
| 460 | 478 | * @param WP_REST_Request $request Full details about the request. |
| 461 | 479 | * |
| @@ -472,9 +490,9 @@ | ||
| 472 | 490 | 'search' => ! empty( $args['s'] ) ? $args['s'] : '', |
| 473 | 491 | ); |
| 474 | 492 | |
| 475 | 493 | // Get data from uptime monitoring DB. |
| 476 | - $monitors = $this->db->get_monitors( $params ); | |
| 494 | + $monitors = $this->filter_scoped_sites( $this->db->get_monitors( $params ) ); | |
| 477 | 495 | $total = is_array( $monitors ) ? count( $monitors ) : 0; |
| 478 | 496 | |
| 479 | 497 | return rest_ensure_response( |
| 480 | 498 | array( |
| @@ -530,10 +548,11 @@ | ||
| 530 | 548 | // Get full heartbeat data. |
| 531 | 549 | $heartbeat_data = $this->db->get_uptime_monitoring_stats( $monitor_id, $one_month_ago, $today ); |
| 532 | 550 | |
| 533 | 551 | // Get monitor type & check frequency. |
| 534 | - $type = $this->get_apply_setting( 'type', $monitor->type ?? '', 'useglobal', 'http' ); | |
| 535 | - $interval_min = (int) $this->get_apply_setting( 'interval', (int) ( $monitor->interval ?? 0 ), -1, 60 ); | |
| 552 | + $type = $this->get_apply_setting( 'type', $monitor->type ?? '', 'useglobal', 'http' ); | |
| 553 | + $interval_min = (int) $this->get_apply_setting( 'interval', (int) ( $monitor->interval ?? 0 ), -1, 60 ); | |
| 554 | + $bypass_cache_opt = (int) $this->get_apply_setting( 'bypass_cache', (int) ( $monitor->bypass_cache ?? -1 ), -1, 0 ); | |
| 536 | 555 | |
| 537 | 556 | $record = array( |
| 538 | 557 | 'id' => $monitor_id, |
| 539 | 558 | 'name' => $monitor->name ?? '', |
| @@ -551,8 +570,9 @@ | ||
| 551 | 570 | 'heartbeats' => $heartbeat_data ?? array(), |
| 552 | 571 | 'last_check_at' => ! empty( $monitor->lasttime_check ) ? gmdate( 'Y-m-d H:i:s', (int) $monitor->lasttime_check ) : '', |
| 553 | 572 | 'last_status_code' => ! empty( $monitor->last_http_code ) ? (int) $monitor->last_http_code : '', |
| 554 | 573 | 'last_status' => $this->uptime_status( $monitor->last_status ?? null ), |
| 574 | + 'bypass_cache' => $bypass_cache_opt, | |
| 555 | 575 | ); |
| 556 | 576 | |
| 557 | 577 | return rest_ensure_response( |
| 558 | 578 | array( |
| @@ -879,8 +899,16 @@ | ||
| 879 | 899 | */ |
| 880 | 900 | public function update_global_monitoring_settings( $request ) { |
| 881 | 901 | // Get request body. |
| 882 | 902 | $body = $request->get_json_params(); |
| 903 | + | |
| 904 | + // A JSON scalar or list body carries none of the settings keys: array_intersect_key() below | |
| 905 | + // fatals on the scalar, and the list would intersect down to nothing and answer as a | |
| 906 | + // successful no-op, so both are treated as no body at all. | |
| 907 | + if ( ! is_array( $body ) || ( array() !== $body && array_keys( $body ) === range( 0, count( $body ) - 1 ) ) ) { | |
| 908 | + $body = array(); | |
| 909 | + } | |
| 910 | + | |
| 883 | 911 | if ( empty( $body ) ) { |
| 884 | 912 | return new WP_Error( |
| 885 | 913 | 'empty_body', |
| 886 | 914 | __( 'Request body is empty.', 'mainwp' ), |
| @@ -896,10 +924,27 @@ | ||
| 896 | 924 | __( 'Invalid content type. Expected application/json.', 'mainwp' ), |
| 897 | 925 | ); |
| 898 | 926 | } |
| 899 | 927 | |
| 928 | + // Reject a key the route does not declare. get_sanitized_settings_params() drops it, so a | |
| 929 | + // misspelled setting would otherwise reach the processor as an empty settings array and | |
| 930 | + // answer as a successful no-op. The individual route reports the same mistake through its | |
| 931 | + // schema, so it gets the same error code here. | |
| 932 | + $unknown_keys = array_diff( array_keys( $body ), array_keys( $this->get_global_monitor_settings_allowed_fields() ) ); | |
| 933 | + if ( ! empty( $unknown_keys ) ) { | |
| 934 | + return new WP_Error( | |
| 935 | + 'rest_additional_properties_forbidden', | |
| 936 | + sprintf( | |
| 937 | + /* translators: %s: comma separated list of request body keys the route does not accept. */ | |
| 938 | + __( 'Unknown settings: %s.', 'mainwp' ), | |
| 939 | + implode( ', ', $unknown_keys ) | |
| 940 | + ), | |
| 941 | + array( 'status' => 400 ) | |
| 942 | + ); | |
| 943 | + } | |
| 944 | + | |
| 900 | 945 | // Process global monitoring settings update. |
| 901 | - $result = $this->process_global_monitoring_settings_update( $body ); | |
| 946 | + $result = $this->process_global_monitoring_settings_update( $this->get_sanitized_settings_params( $request, $body ) ); | |
| 902 | 947 | if ( is_wp_error( $result ) ) { |
| 903 | 948 | return $result; |
| 904 | 949 | } |
| 905 | 950 | |
| @@ -934,12 +979,20 @@ | ||
| 934 | 979 | |
| 935 | 980 | // Get request body. |
| 936 | 981 | $body = $request->get_json_params(); |
| 937 | 982 | |
| 983 | + // A JSON scalar or list body carries none of the settings keys: array_intersect_key() below | |
| 984 | + // fatals on the scalar, and the list would intersect down to nothing and answer as a | |
| 985 | + // successful no-op, so both are treated as no body at all. | |
| 986 | + if ( ! is_array( $body ) || ( array() !== $body && array_keys( $body ) === range( 0, count( $body ) - 1 ) ) ) { | |
| 987 | + $body = array(); | |
| 988 | + } | |
| 989 | + | |
| 938 | 990 | if ( empty( $body ) ) { |
| 939 | 991 | return new WP_Error( |
| 940 | 992 | 'empty_body', |
| 941 | 993 | __( 'Request body is empty.', 'mainwp' ), |
| 994 | + array( 'status' => 400 ) | |
| 942 | 995 | ); |
| 943 | 996 | } |
| 944 | 997 | |
| 945 | 998 | // Check content type. |
| @@ -950,17 +1003,22 @@ | ||
| 950 | 1003 | __( 'Invalid content type. Expected application/json.', 'mainwp' ), |
| 951 | 1004 | ); |
| 952 | 1005 | } |
| 953 | 1006 | |
| 954 | - // Validate request body against schema. | |
| 1007 | + // Validate request body against schema. The schema has to see the raw body: the sanitized | |
| 1008 | + // params keep only the keys the route registered, so a misspelled key would be filtered out | |
| 1009 | + // before additionalProperties could reject it. | |
| 955 | 1010 | $schema = $this->get_monitor_settings_schema(); // Get default schema. |
| 956 | 1011 | $valid = rest_validate_value_from_schema( $body, $schema, 'body' ); |
| 957 | 1012 | if ( is_wp_error( $valid ) ) { |
| 1013 | + // A schema error carries no status of its own and REST falls back to 500 for one that | |
| 1014 | + // has none, which is the wrong answer for a body the caller got wrong. | |
| 1015 | + $valid->add_data( array( 'status' => 400 ) ); | |
| 958 | 1016 | return $valid; |
| 959 | 1017 | } |
| 960 | 1018 | |
| 961 | 1019 | // Sanitize request body. |
| 962 | - $data = rest_sanitize_value_from_schema( $body, $schema ); | |
| 1020 | + $data = rest_sanitize_value_from_schema( $this->get_sanitized_settings_params( $request, $body ), $schema ); | |
| 963 | 1021 | |
| 964 | 1022 | // Process individual monitor settings update. |
| 965 | 1023 | $result = $this->process_individual_monitor_settings_update( $monitor, $data ); |
| 966 | 1024 | |
| @@ -1013,9 +1071,18 @@ | ||
| 1013 | 1071 | // Check website. |
| 1014 | 1072 | $error_msg = ''; |
| 1015 | 1073 | $result = ''; |
| 1016 | 1074 | try { |
| 1017 | - $result = MainWP_Monitoring_Handler::handle_check_website( $website ); | |
| 1075 | + if ( ! empty( $monitor->issub ) ) { | |
| 1076 | + $result = MainWP_Uptime_Monitoring_Handle::check_website_uptime_monitoring_status( | |
| 1077 | + $monitor, | |
| 1078 | + array( | |
| 1079 | + 'ignore_compatible_save' => 1, // compatible. | |
| 1080 | + ) | |
| 1081 | + ); | |
| 1082 | + } else { | |
| 1083 | + $result = MainWP_Monitoring_Handler::handle_check_website( $website ); | |
| 1084 | + } | |
| 1018 | 1085 | } catch ( \Exception $e ) { |
| 1019 | 1086 | $error_msg = $e->getMessage(); |
| 1020 | 1087 | } |
| 1021 | 1088 | |
| @@ -1044,8 +1111,30 @@ | ||
| 1044 | 1111 | ); |
| 1045 | 1112 | } |
| 1046 | 1113 | |
| 1047 | 1114 | /** |
| 1115 | + * Get the settings the request carries, as the registered callbacks sanitized them. | |
| 1116 | + * | |
| 1117 | + * @param WP_REST_Request $request Full details about the request. | |
| 1118 | + * @param array $body Raw request body. | |
| 1119 | + * | |
| 1120 | + * @return array Sanitized settings, keyed by the keys the body carried. | |
| 1121 | + */ | |
| 1122 | + private function get_sanitized_settings_params( $request, $body ) { | |
| 1123 | + // The registered sanitize_callbacks produce the request params, so the persisted value is | |
| 1124 | + // read from there rather than from the raw body; WordPress currently writes the sanitized | |
| 1125 | + // value back into the JSON body too, but that is internal to sanitize_params(), not a | |
| 1126 | + // contract. get_params() also carries auth, query and route params, so keep only the | |
| 1127 | + // settings keys the route declares and only the ones this body actually sent. It merges in | |
| 1128 | + // the reverse of get_parameter_order(), so a JSON body value beats a query parameter of the | |
| 1129 | + // same name rather than the other way round. | |
| 1130 | + $allowed = $this->get_global_monitor_settings_allowed_fields(); | |
| 1131 | + $params = array_intersect_key( $request->get_params(), $allowed ); | |
| 1132 | + | |
| 1133 | + return array_intersect_key( $params, $body ); | |
| 1134 | + } | |
| 1135 | + | |
| 1136 | + /** | |
| 1048 | 1137 | * Process global monitoring settings update with all business logic. |
| 1049 | 1138 | * |
| 1050 | 1139 | * @param array $settings Settings to update. |
| 1051 | 1140 | * |
| @@ -1109,8 +1198,12 @@ | ||
| 1109 | 1198 | if ( isset( $settings['keyword'] ) ) { |
| 1110 | 1199 | $updated_settings['keyword'] = $settings['keyword']; |
| 1111 | 1200 | } |
| 1112 | 1201 | |
| 1202 | + if ( isset( $settings['bypass_cache'] ) && in_array( (int) $settings['bypass_cache'], array( 1, 0 ), true ) ) { | |
| 1203 | + $updated_settings['bypass_cache'] = $settings['bypass_cache']; | |
| 1204 | + } | |
| 1205 | + | |
| 1113 | 1206 | // Apply filters to allow customization. |
| 1114 | 1207 | $updated_settings = apply_filters( 'mainwp_uptime_monitoring_update_global_settings', $updated_settings ); |
| 1115 | 1208 | |
| 1116 | 1209 | // Save the updated settings. |
| @@ -1189,8 +1282,16 @@ | ||
| 1189 | 1282 | |
| 1190 | 1283 | // Update the monitor. |
| 1191 | 1284 | $result = $this->db->update_wp_monitor( $update_data ); |
| 1192 | 1285 | |
| 1286 | + if ( isset( $settings['bypass_cache'] ) ) { | |
| 1287 | + $bypass_cache = (int) $settings['bypass_cache']; | |
| 1288 | + if ( in_array( $bypass_cache, array( -1, 1, 0 ), true ) ) { | |
| 1289 | + MainWP_DB_Uptime_Monitoring::instance()->update_website_option( $monitor->wpid, 'bypass_cache', $bypass_cache ); | |
| 1290 | + $update_data['bypass_cache'] = $bypass_cache; | |
| 1291 | + } | |
| 1292 | + } | |
| 1293 | + | |
| 1193 | 1294 | if ( false === $result ) { |
| 1194 | 1295 | return new WP_Error( |
| 1195 | 1296 | 'update_failed', |
| 1196 | 1297 | __( 'Failed to update monitor settings.', 'mainwp' ), |
| @@ -1366,15 +1467,19 @@ | ||
| 1366 | 1467 | |
| 1367 | 1468 | // Get monitor by monitor id. |
| 1368 | 1469 | if ( ctype_digit( $raw ) ) { |
| 1369 | 1470 | $monitor_id = (int) $raw; |
| 1370 | - return $this->db->get_monitor_by( false, 'monitor_id', $monitor_id ); | |
| 1471 | + $monitor = $this->db->get_monitor_by( false, 'monitor_id', $monitor_id ); | |
| 1472 | + if ( empty( $monitor ) || empty( $this->filter_scoped_sites( array( $monitor ) ) ) ) { | |
| 1473 | + return false; | |
| 1474 | + } | |
| 1475 | + return $monitor; | |
| 1371 | 1476 | } |
| 1372 | 1477 | |
| 1373 | 1478 | // Get monitor by domain. |
| 1374 | 1479 | $domain = strtolower( rtrim( rawurldecode( $raw ), '/' ) ); |
| 1375 | 1480 | $website = $this->get_site_by( 'domain', $domain ); |
| 1376 | - if ( empty( $website ) ) { | |
| 1481 | + if ( empty( $website ) || is_wp_error( $website ) ) { | |
| 1377 | 1482 | return false; |
| 1378 | 1483 | } |
| 1379 | 1484 | |
| 1380 | 1485 | $site_id = (int) $website->id; |
| @@ -2207,8 +2312,13 @@ | ||
| 2207 | 2312 | 'timeout' => array( 'type' => 'integer' ), |
| 2208 | 2313 | 'interval' => array( 'type' => 'string' ), |
| 2209 | 2314 | 'expected_status' => array( 'type' => array( 'string' ) ), |
| 2210 | 2315 | 'keyword' => array( 'type' => 'string' ), |
| 2316 | + 'bypass_cache' => array( | |
| 2317 | + 'type' => 'integer', | |
| 2318 | + 'enum' => array( -1, 0, 1 ), | |
| 2319 | + 'description' => __( 'Cache bypass setting. -1 uses the global setting, 0 disables it, and 1 enables it.', 'mainwp' ), | |
| 2320 | + ), | |
| 2211 | 2321 | ), |
| 2212 | 2322 | ); |
| 2213 | 2323 | } |
| 2214 | 2324 | |
| @@ -2255,8 +2365,13 @@ | ||
| 2255 | 2365 | ), |
| 2256 | 2366 | 'type' => array( |
| 2257 | 2367 | 'type' => 'string', |
| 2258 | 2368 | 'description' => __( 'Monitor type.', 'mainwp' ), |
| 2369 | + 'context' => array( 'view', 'monitor_view' ), | |
| 2370 | + ), | |
| 2371 | + 'bypass_cache' => array( | |
| 2372 | + 'type' => 'integer', | |
| 2373 | + 'description' => __( 'Attempt to bypass page cache.', 'mainwp' ), | |
| 2259 | 2374 | 'context' => array( 'view', 'monitor_view' ), |
| 2260 | 2375 | ), |
| 2261 | 2376 | 'last_status' => array( |
| 2262 | 2377 | 'type' => 'string', |