PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / 6.2.2
MainWP Dashboard: Self-hosted WordPress Management for Agencies v6.2.2
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 5.2 5.2.1 All 155 releases
← All changes | includes/rest-api/controller/version2/class-mainwp-rest-updates-controller.php +256 -36 5.2.1 → 6.2.2 View file →
@@ -7,9 +7,8 @@
7 7 * @package MainWP\Dashboard
8 8 */
9 9
10 10 use MainWP\Dashboard\MainWP_DB;
11 -use MainWP\Dashboard\MainWP_Common_Handler;
12 11 use MainWP\Dashboard\MainWP_Updates_Helper;
13 12 use MainWP\Dashboard\MainWP_DB_Common;
14 13 use MainWP\Dashboard\MainWP_Updates_Handler;
15 14 use MainWP\Dashboard\MainWP_Connect;
@@ -18,8 +17,14 @@
18 17 use MainWP\Dashboard\MainWP_Common_Functions;
19 18 use MainWP\Dashboard\MainWP_Cron_Jobs_Batch;
20 19 use MainWP\Dashboard\MainWP_Logger;
21 20 use MainWP\Dashboard\MainWP_Auto_Updates_DB;
21 +
22 +// Exit if accessed directly.
23 +if ( ! defined( 'ABSPATH' ) ) {
24 + exit;
25 +}
26 +
22 27 /**
23 28 * Class MainWP_Rest_Updates_Controller
24 29 *
25 30 * @package MainWP\Dashboard
@@ -266,11 +271,8 @@
266 271 * @return WP_Error|WP_REST_Response
267 272 */
268 273 public function get_items( $request ) { //phpcs:ignore -- NOSONAR complex function.
269 274
270 - $all_updates = array();
271 - $websites = MainWP_DB::instance()->query( MainWP_DB::instance()->get_sql_websites_for_current_user( true, null, 'wp.url', false, false, null, false, array( 'ignored_wp_upgrades', 'rollback_updates_data' ) ) );
272 -
273 275 $args = $this->prepare_objects_query( $request );
274 276 $args = $this->validate_rest_args( $args, $this->get_validate_args_params( 'get_sites' ) );
275 277 if ( is_wp_error( $args ) ) {
276 278 return $args;
@@ -281,8 +283,65 @@
281 283 $s = isset( $args['s'] ) ? trim( $args['s'] ) : '';
282 284 $exclud = isset( $args['exclude'] ) ? wp_parse_id_list( $args['exclude'] ) : array();
283 285 $includ = isset( $args['include'] ) ? wp_parse_id_list( $args['include'] ) : array();
284 286
287 + // Try abilities-first approach (with fallback to legacy logic).
288 + $ability = function_exists( 'wp_get_ability' ) ? wp_get_ability( 'mainwp/list-updates-v1' ) : null;
289 +
290 + if ( null !== $ability ) {
291 + // Build ability input from REST parameters.
292 + // Note: For backward compatibility, we do NOT paginate here. The legacy path
293 + // returns ALL updates (no page/per_page constraints), so we omit pagination
294 + // to maintain consistent behavior. Callers who need paginated results should
295 + // use the ability directly with explicit page/per_page values.
296 + // See: .mwpdev/plans/abilities-api/phase-3-rest-integration-notes.md
297 +
298 + // Determine site IDs to include.
299 + // The ability uses site_ids_or_domains (array of IDs or domains).
300 + // REST API uses 'include' for site IDs to include.
301 + $site_ids = ! empty( $includ ) ? $includ : array();
302 +
303 + // Map REST 'type' parameter to ability 'types' parameter.
304 + // REST uses 'type' (singular), ability uses 'types' (plural).
305 + $ability_types = ! empty( $type ) ? $type : array();
306 +
307 + $ability_input = array(
308 + 'site_ids_or_domains' => $site_ids,
309 + 'types' => $ability_types,
310 + );
311 +
312 + $result = $ability->execute( $ability_input );
313 +
314 + if ( is_wp_error( $result ) ) {
315 + $error_data = $result->get_error_data();
316 + $status = isset( $error_data['status'] ) ? $error_data['status'] : 500;
317 + return new WP_Error( $result->get_error_code(), $result->get_error_message(), array( 'status' => $status ) );
318 + }
319 +
320 + // Re-apply exclude filter for backward compatibility.
321 + // The ability doesn't support this parameter, so we filter post-fetch.
322 + // This matches the pattern used in the Sites controller.
323 + $updates = isset( $result['updates'] ) ? $result['updates'] : array();
324 + if ( ! empty( $exclud ) && ! empty( $updates ) ) {
325 + foreach ( $exclud as $excluded_id ) {
326 + unset( $updates[ $excluded_id ] );
327 + }
328 + }
329 +
330 + // Transform ability output to REST response format.
331 + // Ability returns 'updates' keyed by site ID with nested type arrays.
332 + return rest_ensure_response(
333 + array(
334 + 'success' => 1,
335 + 'data' => $updates,
336 + )
337 + );
338 + }
339 +
340 + // Fallback: Legacy logic when Abilities API is not available.
341 + $all_updates = array();
342 + $websites = MainWP_DB::instance()->query( MainWP_DB::instance()->get_sql_websites_for_current_user( true, null, 'wp.url', false, false, null, false, array( 'ignored_wp_upgrades', 'rollback_updates_data' ) ) );
343 +
285 344 if ( empty( $type ) ) {
286 345 $type[] = 'all';
287 346 }
288 347
@@ -423,9 +482,9 @@
423 482 * @return WP_Error|Object Item.
424 483 */
425 484 public function get_request_item( $request ) {
426 485 $route = $request->get_route();
427 - if ( str_ends_with( $route, '/batch' ) ) {
486 + if ( MainWP_Utility::string_ends_by( $route, '/batch' ) ) {
428 487 $by = 'id';
429 488 $value = $request['id'];
430 489 } else {
431 490 $value = $request['id_domain'];
@@ -616,8 +675,51 @@
616 675 * @return WP_Error|WP_REST_Response
617 676 */
618 677 public function get_all_global_ignored_updates( $request ) { //phpcs:ignore -- NOSONAR - complex.
619 678
679 + $args = $this->prepare_objects_query( $request );
680 +
681 + $type = isset( $args['type'] ) && ! empty( $args['type'] ) ? wp_parse_list( $args['type'] ) : array();
682 + $type = array_filter( array_map( 'trim', $type ) );
683 + $s = isset( $args['s'] ) ? trim( $args['s'] ) : '';
684 +
685 + // Normalize type array: add 'all' if empty or missing both 'plugins' and 'themes'.
686 + // This ensures consistent behavior between ability and legacy paths.
687 + if ( empty( $type ) || ( ! in_array( 'plugins', $type ) && ! in_array( 'themes', $type ) ) ) {
688 + $type[] = 'all';
689 + }
690 +
691 + // Try abilities-first approach (with fallback to legacy logic).
692 + $ability = function_exists( 'wp_get_ability' ) ? wp_get_ability( 'mainwp/list-ignored-updates-v1' ) : null;
693 +
694 + if ( null !== $ability ) {
695 + // Build ability input using normalized type.
696 + $ability_input = array(
697 + 'scope' => 'global',
698 + 'type' => $type,
699 + 'search' => $s,
700 + );
701 +
702 + $result = $ability->execute( $ability_input );
703 +
704 + if ( is_wp_error( $result ) ) {
705 + $error_data = $result->get_error_data();
706 + $status = isset( $error_data['status'] ) ? $error_data['status'] : 500;
707 + return new WP_Error( $result->get_error_code(), $result->get_error_message(), array( 'status' => $status ) );
708 + }
709 +
710 + // Transform ability output to REST response format.
711 + // Ability returns 'ignored' object with 'plugins' and 'themes' keys.
712 + return rest_ensure_response(
713 + array(
714 + 'success' => 1,
715 + 'data' => isset( $result['ignored'] ) ? $result['ignored'] : array(),
716 + )
717 + );
718 + }
719 +
720 + // Fallback: Legacy logic when Abilities API is not available.
721 + // Note: $type is already normalized above before the ability check.
620 722 $userExtension = MainWP_DB_Common::instance()->get_user_extension();
621 723 $ignored_plugins = ! empty( $userExtension->ignored_plugins ) ? json_decode( $userExtension->ignored_plugins, true ) : array();
622 724 $ignored_themes = ! empty( $userExtension->ignored_themes ) ? json_decode( $userExtension->ignored_themes, true ) : array();
623 725
@@ -627,18 +729,8 @@
627 729 if ( ! is_array( $ignored_themes ) ) {
628 730 $ignored_themes = array();
629 731 }
630 732
631 - $args = $this->prepare_objects_query( $request );
632 -
633 - $type = isset( $args['type'] ) && ! empty( $args['type'] ) ? wp_parse_list( $args['type'] ) : array();
634 - $type = array_filter( array_map( 'trim', $type ) );
635 - $s = isset( $args['s'] ) ? trim( $args['s'] ) : '';
636 -
637 - if ( empty( $type ) || ( ! in_array( 'plugins', $type ) && ! in_array( 'themes', $type ) ) ) {
638 - $type[] = 'all';
639 - }
640 -
641 733 $all = in_array( 'all', $type ) ? true : false;
642 734
643 735 if ( ! empty( $s ) ) {
644 736 $ignored_plugins = mainwp_search_in_array( $ignored_plugins, $s, array( 'in_sub_fields' => 'Name' ) );
@@ -677,8 +769,52 @@
677 769 if ( is_wp_error( $website ) ) {
678 770 return $website;
679 771 }
680 772
773 + $args = $this->prepare_objects_query( $request );
774 +
775 + $type = isset( $args['type'] ) && ! empty( $args['type'] ) ? wp_parse_list( $args['type'] ) : array();
776 + $type = array_filter( array_map( 'trim', $type ) );
777 + $s = isset( $args['s'] ) ? trim( $args['s'] ) : '';
778 +
779 + // Normalize type array: add 'all' if empty or missing both 'plugins' and 'themes'.
780 + // This ensures consistent behavior between ability and legacy paths.
781 + if ( empty( $type ) || ( ! in_array( 'plugins', $type ) && ! in_array( 'themes', $type ) ) ) {
782 + $type[] = 'all';
783 + }
784 +
785 + // Try abilities-first approach (with fallback to legacy logic).
786 + $ability = function_exists( 'wp_get_ability' ) ? wp_get_ability( 'mainwp/list-ignored-updates-v1' ) : null;
787 +
788 + if ( null !== $ability ) {
789 + // Build ability input using normalized type.
790 + $ability_input = array(
791 + 'scope' => 'site',
792 + 'site_id_or_domain' => (int) $website->id,
793 + 'type' => $type,
794 + 'search' => $s,
795 + );
796 +
797 + $result = $ability->execute( $ability_input );
798 +
799 + if ( is_wp_error( $result ) ) {
800 + $error_data = $result->get_error_data();
801 + $status = isset( $error_data['status'] ) ? $error_data['status'] : 500;
802 + return new WP_Error( $result->get_error_code(), $result->get_error_message(), array( 'status' => $status ) );
803 + }
804 +
805 + // Transform ability output to REST response format.
806 + // Ability merges per-site and global ignores, matching REST behavior.
807 + return rest_ensure_response(
808 + array(
809 + 'success' => 1,
810 + 'data' => isset( $result['ignored'] ) ? $result['ignored'] : array(),
811 + )
812 + );
813 + }
814 +
815 + // Fallback: Legacy logic when Abilities API is not available.
816 + // Note: $type is already normalized above before the ability check.
681 817 $ignored_plugins = ! empty( $website->ignored_plugins ) ? json_decode( $website->ignored_plugins, true ) : array();
682 818 $ignored_themes = ! empty( $website->ignored_themes ) ? json_decode( $website->ignored_themes, true ) : array();
683 819
684 820 if ( ! is_array( $ignored_plugins ) ) {
@@ -701,19 +837,8 @@
701 837
702 838 $ignored_plugins = $ignored_plugins + $ignored_plugins2;
703 839 $ignored_themes = $ignored_themes + $ignored_themes2;
704 840
705 - $args = $this->prepare_objects_query( $request );
706 -
707 - $type = isset( $args['type'] ) && ! empty( $args['type'] ) ? wp_parse_list( $args['type'] ) : array();
708 - $type = array_filter( array_map( 'trim', $type ) );
709 -
710 - $s = isset( $args['s'] ) ? trim( $args['s'] ) : '';
711 -
712 - if ( empty( $type ) || ( ! in_array( 'plugins', $type ) && ! in_array( 'themes', $type ) ) ) {
713 - $type[] = 'all';
714 - }
715 -
716 841 $all = in_array( 'all', $type ) ? true : false;
717 842
718 843 if ( ! empty( $s ) ) {
719 844 $ignored_plugins = mainwp_search_in_array( $ignored_plugins, $s, array( 'in_sub_fields' => 'Name' ) );
@@ -744,9 +869,9 @@
744 869 *
745 870 * @param WP_REST_Request $request Full details about the request.
746 871 * @return WP_Error|WP_REST_Response
747 872 */
748 - public function update_all_items_start( $request ) {
873 + public function update_all_items_start( $request ) { //phpcs:ignore -- NOSONAR complex function.
749 874
750 875 $batch_updates_running = get_option( 'mainwp_batch_updates_is_running', 0 );
751 876 $start_time = get_option( 'mainwp_batch_updates_start_time', 0 );
752 877
@@ -759,8 +884,52 @@
759 884 );
760 885 return rest_ensure_response( $resp_data );
761 886 }
762 887
888 + // Try abilities-first approach (with fallback to legacy logic).
889 + $ability = function_exists( 'wp_get_ability' ) ? wp_get_ability( 'mainwp/run-updates-v1' ) : null;
890 +
891 + if ( null !== $ability ) {
892 + // Build ability input for updating all sites with all update types.
893 + // Empty arrays mean "all sites" and "all types" respectively.
894 + $ability_input = array(
895 + 'site_ids_or_domains' => array(), // All sites.
896 + 'types' => array(), // All types.
897 + );
898 +
899 + $result = $ability->execute( $ability_input );
900 +
901 + if ( is_wp_error( $result ) ) {
902 + $error_data = $result->get_error_data();
903 + $status = isset( $error_data['status'] ) ? $error_data['status'] : 500;
904 + return new WP_Error( $result->get_error_code(), $result->get_error_message(), array( 'status' => $status ) );
905 + }
906 +
907 + // Check if updates were queued for background processing.
908 + if ( isset( $result['job_id'] ) ) {
909 + return rest_ensure_response(
910 + array(
911 + 'success' => 1,
912 + 'message' => esc_html__( 'Batch updates queued.', 'mainwp' ),
913 + 'job_id' => $result['job_id'],
914 + 'queued_count' => isset( $result['queued_count'] ) ? $result['queued_count'] : 0,
915 + 'last_time_start_batch_updates' => mainwp_rest_prepare_date_response( current_time( 'mysql' ) ),
916 + )
917 + );
918 + }
919 +
920 + // Transform immediate results.
921 + return rest_ensure_response(
922 + array(
923 + 'success' => 1,
924 + 'message' => esc_html__( 'Batch updates completed.', 'mainwp' ),
925 + 'summary' => isset( $result['summary'] ) ? $result['summary'] : array(),
926 + 'last_time_start_batch_updates' => mainwp_rest_prepare_date_response( current_time( 'mysql' ) ),
927 + )
928 + );
929 + }
930 +
931 + // Fallback: Legacy logic when Abilities API is not available.
763 932 $local_timestamp = MainWP_Utility::get_timestamp();
764 933 $start_time = $local_timestamp;
765 934 MainWP_Utility::update_option( 'mainwp_batch_updates_start_time', $start_time );
766 935 MainWP_Utility::update_option( 'mainwp_batch_updates_is_running', 1 ); // to start perform batch update job.
@@ -795,9 +964,9 @@
795 964 *
796 965 * @param WP_REST_Request $request Full details about the request.
797 966 * @return WP_Error|WP_REST_Response
798 967 */
799 - public function update_all_items_inidividual_site_start( $request ) {
968 + public function update_all_items_inidividual_site_start( $request ) { //phpcs:ignore -- NOSONAR complex function.
800 969 $website = $this->get_request_item( $request );
801 970
802 971 if ( is_wp_error( $website ) ) {
803 972 return $website;
@@ -802,8 +971,10 @@
802 971 if ( is_wp_error( $website ) ) {
803 972 return $website;
804 973 }
805 974
975 + // Fetch full site data with options needed for business rule checks.
976 + // This is done BEFORE checking abilities to ensure consistent behavior.
806 977 $fetch_one = MainWP_DB::instance()->query( MainWP_DB::instance()->get_sql_websites_for_current_user( true, null, 'wp.url', false, false, null, false, array( 'ignored_wp_upgrades', 'batch_individual_queue_time', 'premium_upgrades' ), 'no', array( 'include' => array( $website->id ) ) ) );
807 978 $website = $fetch_one ? MainWP_DB::fetch_object( $fetch_one ) : false;
808 979 MainWP_DB::free_result( $fetch_one );
809 980
@@ -810,17 +981,18 @@
810 981 if ( empty( $website ) ) {
811 982 return $this->get_rest_data_error( 'id', 'site' );
812 983 }
813 984
814 - if ( 1 === $website->suspended ) {
985 + // Enforce business rules BEFORE checking abilities.
986 + // This ensures consistent behavior regardless of whether Abilities API is available.
987 + if ( 1 === (int) $website->suspended ) {
815 988 return new \WP_Error( 'mainwp_rest_updates_site_error', __( 'Website suspended. Please unsuspended the website and try again.', 'mainwp' ), array( 'status' => 400 ) );
816 -
817 989 }
818 990
819 991 $batch_individual_updates_running = get_option( 'mainwp_batch_individual_updates_is_running', 0 );
820 992 $start_individual_time = get_option( 'mainwp_batch_updates_individual_start_time', 0 );
821 993
822 - // check if the site in batch updates queue.
994 + // Check if the site is already in the batch updates queue.
823 995 if ( $batch_individual_updates_running && ! empty( $website->batch_individual_queue_time ) && (int) $website->batch_individual_queue_time >= (int) $start_individual_time ) {
824 996 $datetime = get_date_from_gmt( gmdate( 'Y-m-d H:i:s', $start_individual_time ) );
825 997 $resp_data = array(
826 998 'success' => 1,
@@ -826,13 +998,61 @@
826 998 'success' => 1,
827 999 'message' => esc_html__( 'A batch updates for the site are queuing to run.', 'mainwp' ),
828 1000 'last_time_start_batch_updates' => mainwp_rest_prepare_date_response( $datetime ),
829 1001 'site' => apply_filters( 'mainwp_rest_routes_sites_controller_filter_allowed_fields_by_context', $website, 'simple_view' ),
1002 + );
1003 + return rest_ensure_response( $resp_data );
1004 + }
830 1005
1006 + // Try abilities-first approach (with fallback to legacy logic).
1007 + $ability = function_exists( 'wp_get_ability' ) ? wp_get_ability( 'mainwp/run-updates-v1' ) : null;
1008 +
1009 + if ( null !== $ability ) {
1010 + // Build ability input for single-site update.
1011 + // Empty types array means "all types".
1012 + $ability_input = array(
1013 + 'site_ids_or_domains' => array( (int) $website->id ),
1014 + 'types' => array(), // All types.
831 1015 );
832 - return rest_ensure_response( $resp_data );
1016 +
1017 + $result = $ability->execute( $ability_input );
1018 +
1019 + if ( is_wp_error( $result ) ) {
1020 + $error_data = $result->get_error_data();
1021 + $status = isset( $error_data['status'] ) ? $error_data['status'] : 500;
1022 + return new \WP_Error( $result->get_error_code(), $result->get_error_message(), array( 'status' => $status ) );
1023 + }
1024 +
1025 + // Normalize site data through REST response filters for consistency.
1026 + $site_filtered = apply_filters( 'mainwp_rest_routes_sites_controller_filter_allowed_fields_by_context', $website, 'simple_view' );
1027 +
1028 + // Handle queued response (background job).
1029 + if ( isset( $result['job_id'] ) ) {
1030 + return rest_ensure_response(
1031 + array(
1032 + 'success' => 1,
1033 + 'message' => esc_html__( 'Batch updates for the site queued successfully.', 'mainwp' ),
1034 + 'job_id' => $result['job_id'],
1035 + 'last_time_start_batch_updates' => mainwp_rest_prepare_date_response( current_time( 'mysql' ) ),
1036 + 'site' => $site_filtered,
1037 + )
1038 + );
1039 + }
1040 +
1041 + // Transform immediate results - extract first result from results array.
1042 + $site_result = isset( $result['results'][0] ) ? $result['results'][0] : array();
1043 + return rest_ensure_response(
1044 + array(
1045 + 'success' => 1,
1046 + 'message' => esc_html__( 'Batch updates for the site completed.', 'mainwp' ),
1047 + 'summary' => isset( $site_result['summary'] ) ? $site_result['summary'] : array(),
1048 + 'last_time_start_batch_updates' => mainwp_rest_prepare_date_response( current_time( 'mysql' ) ),
1049 + 'site' => $site_filtered,
1050 + )
1051 + );
833 1052 }
834 1053
1054 + // Fallback: Legacy logic when Abilities API is not available.
835 1055 // reload full data.
836 1056 $website = MainWP_DB::instance()->get_website_by_id( $website->id );
837 1057 MainWP_Cron_Jobs_Batch::instance()->prepare_bulk_updates( $website );
838 1058
@@ -890,9 +1110,9 @@
890 1110 $success = true;
891 1111 } elseif ( ! empty( $info['error'] ) ) {
892 1112 $error = esc_html( $error );
893 1113 } else {
894 - $error = esc_html__( 'An undefined error occured. Please try again.', 'mainwp' );
1114 + $error = esc_html__( 'An undefined error occurred. Please try again.', 'mainwp' );
895 1115 }
896 1116
897 1117 if ( ! empty( $info['old_version'] ) ) {
898 1118 $data['old_version'] = $info['old_version'];
@@ -1137,9 +1357,9 @@
1137 1357 );
1138 1358 }
1139 1359
1140 1360 try {
1141 - MainWP_Connect::fetch_url_authed(
1361 + $information = MainWP_Connect::fetch_url_authed(
1142 1362 $website,
1143 1363 'upgradetranslation',
1144 1364 array(
1145 1365 'type' => 'translation',
@@ -1379,15 +1599,15 @@
1379 1599 'context' => array( 'view' ),
1380 1600 ),
1381 1601 'plugins' => array(
1382 1602 'type' => 'array',
1383 - 'description' => __( 'Plugins Updates.', 'mainwp' ),
1603 + 'description' => __( 'Plugin Updates.', 'mainwp' ),
1384 1604 'validate_callback' => 'rest_validate_request_arg',
1385 1605 'context' => array( 'view' ),
1386 1606 ),
1387 1607 'themes' => array(
1388 1608 'type' => 'array',
1389 - 'description' => __( 'Themes Updates.', 'mainwp' ),
1609 + 'description' => __( 'Theme Updates.', 'mainwp' ),
1390 1610 'validate_callback' => 'rest_validate_request_arg',
1391 1611 'context' => array( 'view' ),
1392 1612 ),
1393 1613 'translation' => array(