PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / 6.3
MainWP Dashboard: Self-hosted WordPress Management for Agencies v6.3
6.3 6.2.3 6.2.2 6.2.1 6.2 6.1.8 6.1.7 6.1.6 6.1.5 6.1.4 6.1.3 6.1.2 6.1.1 6.1 6.0.12 6.0.11 4.6.0.1 5.0 5.0.1 5.0.2 5.0.3 5.0.3.1 5.0.3.2 5.1 5.1.1 All 157 releases
← 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',