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
mainwp / includes / rest-api / controller / version2 / class-mainwp-rest-updates-controller.php

class-mainwp-rest-updates-controller.php in MainWP Dashboard: Self-hosted WordPress Management for Agencies 6.2.2, at includes/rest-api/controller/version2/class-mainwp-rest-updates-controller.php

1,635 lines 65.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * MainWP REST Controller
4 *
5 * This class handles the REST API
6 *
7 * @package MainWP\Dashboard
8 */
9
10 use MainWP\Dashboard\MainWP_DB;
11 use MainWP\Dashboard\MainWP_Updates_Helper;
12 use MainWP\Dashboard\MainWP_DB_Common;
13 use MainWP\Dashboard\MainWP_Updates_Handler;
14 use MainWP\Dashboard\MainWP_Connect;
15 use MainWP\Dashboard\Rest_Api_V1_Helper;
16 use MainWP\Dashboard\MainWP_Utility;
17 use MainWP\Dashboard\MainWP_Common_Functions;
18 use MainWP\Dashboard\MainWP_Cron_Jobs_Batch;
19 use MainWP\Dashboard\MainWP_Logger;
20 use MainWP\Dashboard\MainWP_Auto_Updates_DB;
21
22 // Exit if accessed directly.
23 if ( ! defined( 'ABSPATH' ) ) {
24 exit;
25 }
26
27 /**
28 * Class MainWP_Rest_Updates_Controller
29 *
30 * @package MainWP\Dashboard
31 */
32 class MainWP_Rest_Updates_Controller extends MainWP_REST_Controller{ //phpcs:ignore -- NOSONAR - multi methods.
33
34 /**
35 * Protected static variable to hold the single instance of the class.
36 *
37 * @var mixed Default null
38 */
39 private static $instance = null;
40
41 /**
42 * Endpoint namespace.
43 *
44 * @var string
45 */
46 protected $namespace = 'mainwp/v2';
47
48 /**
49 * Route base.
50 *
51 * @var string
52 */
53 protected $rest_base = 'updates';
54
55
56 /**
57 * Method instance()
58 *
59 * Create public static instance.
60 *
61 * @static
62 * @return static::$instance
63 */
64 public static function instance() {
65 if ( null === static::$instance ) {
66 static::$instance = new self();
67 }
68 return static::$instance;
69 }
70
71 /**
72 * Method register_routes()
73 *
74 * Creates the necessary endpoints for the api.
75 * Note, for a request to be successful the URL query parameters consumer_key and consumer_secret need to be set and correct.
76 */
77 public function register_routes() { // phpcs:ignore -- NOSONAR - complex.
78
79 register_rest_route(
80 $this->namespace,
81 '/' . $this->rest_base,
82 array(
83 array(
84 'methods' => WP_REST_Server::READABLE,
85 'callback' => array( $this, 'get_items' ),
86 'permission_callback' => array( $this, 'get_rest_permissions_check' ),
87 ),
88 )
89 );
90
91 register_rest_route(
92 $this->namespace,
93 '/' . $this->rest_base . '/(?P<id_domain>[\d]+)',
94 array(
95 array(
96 'methods' => WP_REST_Server::READABLE,
97 'callback' => array( $this, 'get_updates_of_site' ),
98 'permission_callback' => array( $this, 'get_rest_permissions_check' ),
99 'args' => array(
100 'id_domain' => array(
101 'description' => __( 'Site ID or domain.', 'mainwp' ),
102 'type' => 'string',
103 ),
104 ),
105 ),
106 'schema' => array( $this, 'get_public_item_schema' ),
107 )
108 );
109
110 register_rest_route(
111 $this->namespace,
112 '/' . $this->rest_base . '/(?P<id_domain>[A-Za-z0-9-\.]*[A-Za-z0-9-]{1,63}\.[A-Za-z]{2,6}$)',
113 array(
114 array(
115 'methods' => WP_REST_Server::READABLE,
116 'callback' => array( $this, 'get_updates_of_site' ),
117 'permission_callback' => array( $this, 'get_rest_permissions_check' ),
118 'args' => array(
119 'id_domain' => array(
120 'description' => __( 'Site ID or domain.', 'mainwp' ),
121 'type' => 'string',
122 ),
123 ),
124 ),
125 'schema' => array( $this, 'get_public_item_schema' ),
126 )
127 );
128
129 // retrieves all globally ignored updates.
130 register_rest_route(
131 $this->namespace,
132 '/' . $this->rest_base . '/ignored',
133 array(
134 array(
135 'methods' => WP_REST_Server::READABLE,
136 'callback' => array( $this, 'get_all_global_ignored_updates' ),
137 'permission_callback' => array( $this, 'get_rest_permissions_check' ),
138 ),
139 )
140 );
141
142 // retrieves ignored updates for the site by ID or domain.
143 register_rest_route(
144 $this->namespace,
145 '/' . $this->rest_base . '/(?P<id_domain>[A-Za-z0-9-\.]+)/ignored',
146 array(
147 array(
148 'methods' => WP_REST_Server::READABLE,
149 'callback' => array( $this, 'get_ignored_updates_of_site' ),
150 'permission_callback' => array( $this, 'get_rest_permissions_check' ),
151 ),
152 )
153 );
154
155 // triggers the Update Everything process (all items on all sites).
156 register_rest_route(
157 $this->namespace,
158 '/' . $this->rest_base . '/update',
159 array(
160 array(
161 'methods' => WP_REST_Server::EDITABLE,
162 'callback' => array( $this, 'update_all_items_start' ),
163 'permission_callback' => array( $this, 'get_rest_permissions_check' ),
164 ),
165 )
166 );
167
168 // Triggers the update process on the site by site ID or domain.
169 register_rest_route(
170 $this->namespace,
171 '/' . $this->rest_base . '/(?P<id_domain>[A-Za-z0-9-\.]+)/update',
172 array(
173 array(
174 'methods' => WP_REST_Server::EDITABLE,
175 'callback' => array( $this, 'update_all_items_inidividual_site_start' ),
176 'permission_callback' => array( $this, 'get_rest_permissions_check' ),
177 ),
178 )
179 );
180
181 // Triggers the WP core update process on site by site ID or domain.
182 register_rest_route(
183 $this->namespace,
184 '/' . $this->rest_base . '/(?P<id_domain>[A-Za-z0-9-\.]+)/update/wp',
185 array(
186 array(
187 'methods' => WP_REST_Server::EDITABLE,
188 'callback' => array( $this, 'update_wp_site' ),
189 'permission_callback' => array( $this, 'get_rest_permissions_check' ),
190 ),
191 )
192 );
193
194 register_rest_route(
195 $this->namespace,
196 '/' . $this->rest_base . '/(?P<id_domain>[A-Za-z0-9-\.]+)/update/plugins',
197 array(
198 array(
199 'methods' => WP_REST_Server::EDITABLE,
200 'callback' => array( $this, 'update_plugins_site' ),
201 'permission_callback' => array( $this, 'get_rest_permissions_check' ),
202 ),
203 )
204 );
205
206 register_rest_route(
207 $this->namespace,
208 '/' . $this->rest_base . '/(?P<id_domain>[A-Za-z0-9-\.]+)/update/themes',
209 array(
210 array(
211 'methods' => WP_REST_Server::EDITABLE,
212 'callback' => array( $this, 'update_themes_site' ),
213 'permission_callback' => array( $this, 'get_rest_permissions_check' ),
214 ),
215 )
216 );
217
218 register_rest_route(
219 $this->namespace,
220 '/' . $this->rest_base . '/(?P<id_domain>[A-Za-z0-9-\.]+)/update/translations',
221 array(
222 array(
223 'methods' => WP_REST_Server::EDITABLE,
224 'callback' => array( $this, 'update_translations_site' ),
225 'permission_callback' => array( $this, 'get_rest_permissions_check' ),
226 ),
227 )
228 );
229
230 register_rest_route(
231 $this->namespace,
232 '/' . $this->rest_base . '/(?P<id_domain>[A-Za-z0-9-\.]+)/ignore/wp',
233 array(
234 array(
235 'methods' => WP_REST_Server::EDITABLE,
236 'callback' => array( $this, 'ignore_update_core_of_site' ),
237 'permission_callback' => array( $this, 'get_rest_permissions_check' ),
238 ),
239 )
240 );
241
242 register_rest_route(
243 $this->namespace,
244 '/' . $this->rest_base . '/(?P<id_domain>[A-Za-z0-9-\.]+)/ignore/plugins',
245 array(
246 array(
247 'methods' => WP_REST_Server::EDITABLE,
248 'callback' => array( $this, 'ignore_update_plugins_of_site' ),
249 'permission_callback' => array( $this, 'get_rest_permissions_check' ),
250 ),
251 )
252 );
253
254 register_rest_route(
255 $this->namespace,
256 '/' . $this->rest_base . '/(?P<id_domain>[A-Za-z0-9-\.]+)/ignore/themes',
257 array(
258 array(
259 'methods' => WP_REST_Server::EDITABLE,
260 'callback' => array( $this, 'ignore_update_themes_of_site' ),
261 'permission_callback' => array( $this, 'get_rest_permissions_check' ),
262 ),
263 )
264 );
265 }
266
267 /**
268 * Get all Clients.
269 *
270 * @param WP_REST_Request $request Full details about the request.
271 * @return WP_Error|WP_REST_Response
272 */
273 public function get_items( $request ) { //phpcs:ignore -- NOSONAR complex function.
274
275 $args = $this->prepare_objects_query( $request );
276 $args = $this->validate_rest_args( $args, $this->get_validate_args_params( 'get_sites' ) );
277 if ( is_wp_error( $args ) ) {
278 return $args;
279 }
280
281 $type = isset( $args['type'] ) && ! empty( $args['type'] ) ? wp_parse_list( $args['type'] ) : array();
282 $type = array_filter( array_map( 'trim', $type ) );
283 $s = isset( $args['s'] ) ? trim( $args['s'] ) : '';
284 $exclud = isset( $args['exclude'] ) ? wp_parse_id_list( $args['exclude'] ) : array();
285 $includ = isset( $args['include'] ) ? wp_parse_id_list( $args['include'] ) : array();
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
344 if ( empty( $type ) ) {
345 $type[] = 'all';
346 }
347
348 $all = in_array( 'all', $type ) ? true : false;
349
350 $userExtension = MainWP_DB_Common::instance()->get_user_extension_by_user_id();
351 $decodedIgnoredCores = ! empty( $userExtension->ignored_wp_upgrades ) ? json_decode( $userExtension->ignored_wp_upgrades, true ) : array();
352 $decodedIgnoredThemes = json_decode( $userExtension->ignored_themes, true );
353 $decodedIgnoredPlugins = json_decode( $userExtension->ignored_plugins, true );
354
355 if ( ! is_array( $decodedIgnoredCores ) ) {
356 $decodedIgnoredCores = array();
357 }
358
359 if ( ! is_array( $decodedIgnoredPlugins ) ) {
360 $decodedIgnoredPlugins = array();
361 }
362
363 if ( ! is_array( $decodedIgnoredThemes ) ) {
364 $decodedIgnoredThemes = array();
365 }
366
367 while ( $websites && ( $website = MainWP_DB::fetch_object( $websites ) ) ) {
368
369 if ( ! empty( $exclud ) && in_array( $website->id, $exclud ) ) {
370 continue;
371 }
372
373 if ( ! empty( $includ ) && ! in_array( $website->id, $includ ) ) {
374 continue;
375 }
376
377 $wp_upgrades = MainWP_DB::instance()->get_json_website_option( $website, 'wp_upgrades' );
378 $plugin_upgrades = json_decode( $website->plugin_upgrades, true );
379 $theme_upgrades = json_decode( $website->theme_upgrades, true );
380 $translation_upgrades = json_decode( $website->translation_upgrades, true );
381
382 if ( $website->is_ignoreCoreUpdates ) {
383 $wp_upgrades = array();
384 }
385
386 if ( $website->is_ignorePluginUpdates ) {
387 $plugin_upgrades = array();
388 }
389
390 if ( $website->is_ignoreThemeUpdates ) {
391 $theme_upgrades = array();
392 }
393
394 if ( ! is_array( $wp_upgrades ) ) {
395 $wp_upgrades = true;
396 }
397
398 if ( ! empty( $plugin_upgrades ) ) {
399 $ignored_plugins = ! empty( $website->ignored_plugins ) ? json_decode( $website->ignored_plugins, true ) : false;
400 if ( is_array( $ignored_plugins ) && ! empty( $ignored_plugins ) ) {
401 $plugin_upgrades = MainWP_Common_Functions::instance()->get_not_ignored_updates_themesplugins( $plugin_upgrades, $ignored_plugins );
402
403 }
404 if ( ! empty( $decodedIgnoredPlugins ) ) {
405 $plugin_upgrades = MainWP_Common_Functions::instance()->get_not_ignored_updates_themesplugins( $plugin_upgrades, $decodedIgnoredPlugins );
406 }
407 }
408
409 if ( ! empty( $theme_upgrades ) ) {
410 $ignored_themes = ! empty( $website->ignored_themes ) ? json_decode( $website->ignored_themes, true ) : false;
411 if ( is_array( $ignored_themes ) && ! empty( $ignored_themes ) ) {
412 $theme_upgrades = MainWP_Common_Functions::instance()->get_not_ignored_updates_themesplugins( $theme_upgrades, $ignored_themes );
413 }
414 if ( ! empty( $decodedIgnoredThemes ) ) {
415 $theme_upgrades = MainWP_Common_Functions::instance()->get_not_ignored_updates_themesplugins( $theme_upgrades, $decodedIgnoredThemes );
416 }
417 }
418
419 if ( ! empty( $wp_upgrades ) ) {
420 $ignored_wp = ! empty( $website->ignored_wp_upgrades ) ? json_decode( $website->ignored_wp_upgrades, true ) : false;
421 if ( is_array( $ignored_wp ) && ! empty( $ignored_wp ) && MainWP_Common_Functions::instance()->is_ignored_updates( $wp_upgrades, $ignored_wp, 'core' ) ) {
422 $wp_upgrades = array();
423 }
424 if ( ! empty( $decodedIgnoredCores ) && ! empty( $wp_upgrades ) && MainWP_Common_Functions::instance()->is_ignored_updates( $wp_upgrades, $decodedIgnoredCores, 'core' ) ) {
425 $wp_upgrades = array();
426 }
427 }
428
429 $roll_data = MainWP_Updates_Helper::instance()->get_roll_items_updates_of_site( $website );
430 $rollback_plugins = ! empty( $roll_data['plugins'] ) ? $roll_data['plugins'] : array();
431 $rollback_themes = ! empty( $roll_data['themes'] ) ? $roll_data['themes'] : array();
432
433 if ( ! empty( $s ) ) {
434 $wp_upgrades = mainwp_search_in_array( $wp_upgrades, $s );
435 $plugin_upgrades = mainwp_search_in_array( $plugin_upgrades, $s, array( 'in_sub_fields' => 'Name' ) );
436 $theme_upgrades = mainwp_search_in_array( $theme_upgrades, $s, array( 'in_sub_fields' => 'Name' ) );
437 $translation_upgrades = mainwp_search_in_array( $translation_upgrades, $s, array( 'in_sub_fields' => 'Name' ) );
438 $rollback_plugins = mainwp_search_in_array( $rollback_plugins, $s, array( 'in_sub_fields' => 'Name' ) );
439 $rollback_themes = mainwp_search_in_array( $rollback_themes, $s, array( 'in_sub_fields' => 'Name' ) );
440 }
441
442 if ( $all || in_array( 'wp', $type ) ) {
443 $all_updates[ $website->id ]['wp'] = $wp_upgrades;
444 }
445
446 if ( $all || in_array( 'plugins', $type ) ) {
447 $all_updates[ $website->id ]['plugins'] = $plugin_upgrades;
448 if ( ! empty( $rollback_plugins ) ) {
449 $all_updates[ $website->id ]['rollback_plugins'] = $rollback_plugins;
450 }
451 }
452
453 if ( $all || in_array( 'themes', $type ) ) {
454 $all_updates[ $website->id ]['themes'] = $theme_upgrades;
455 if ( ! empty( $rollback_themes ) ) {
456 $all_updates[ $website->id ]['rollback_themes'] = $rollback_themes;
457 }
458 }
459
460 if ( $all || in_array( 'translations', $type ) ) {
461 $all_updates[ $website->id ]['translations'] = $translation_upgrades;
462 }
463 }
464
465 MainWP_DB::free_result( $websites );
466
467 // get data.
468 $data = $all_updates;
469
470 $resp_data = array(
471 'success' => 1,
472 'data' => $data,
473 );
474 return rest_ensure_response( $resp_data );
475 }
476
477
478 /**
479 * Get site by.
480 *
481 * @param WP_REST_Request $request Full details about the request.
482 * @return WP_Error|Object Item.
483 */
484 public function get_request_item( $request ) {
485 $route = $request->get_route();
486 if ( MainWP_Utility::string_ends_by( $route, '/batch' ) ) {
487 $by = 'id';
488 $value = $request['id'];
489 } else {
490 $value = $request['id_domain'];
491 $by = 'domain';
492 if ( is_numeric( $value ) ) {
493 $by = 'id';
494 } else {
495 $value = urldecode( $value );
496 }
497 }
498 return $this->get_site_by( $by, $value );
499 }
500
501 /**
502 * Get updates of site.
503 *
504 * @param WP_REST_Request $request Full details about the request.
505 * @return WP_Error|WP_REST_Response
506 */
507 public function get_updates_of_site( $request ) { //phpcs:ignore -- NOSONAR complex function.
508
509 $item = $this->get_request_item( $request );
510
511 if ( is_wp_error( $item ) ) {
512 return $item;
513 }
514
515 $userExtension = MainWP_DB_Common::instance()->get_user_extension_by_user_id();
516 $decodedIgnoredCores = ! empty( $userExtension->ignored_wp_upgrades ) ? json_decode( $userExtension->ignored_wp_upgrades, true ) : array();
517 $decodedIgnoredThemes = json_decode( $userExtension->ignored_themes, true );
518 $decodedIgnoredPlugins = json_decode( $userExtension->ignored_plugins, true );
519
520 if ( ! is_array( $decodedIgnoredCores ) ) {
521 $decodedIgnoredCores = array();
522 }
523
524 if ( ! is_array( $decodedIgnoredPlugins ) ) {
525 $decodedIgnoredPlugins = array();
526 }
527
528 if ( ! is_array( $decodedIgnoredThemes ) ) {
529 $decodedIgnoredThemes = array();
530 }
531
532 $rollback_plugins = array();
533 $rollback_themes = array();
534
535 // get data.
536 $website = MainWP_DB::instance()->get_website_by_id( $item->id, false, array( 'ignored_wp_upgrades', 'wp_upgrades', 'rollback_updates_data' ) );
537
538 $wp_upgrades = ! empty( $website->wp_upgrades ) ? json_decode( $website->wp_upgrades, true ) : array();
539 $plugin_upgrades = ! empty( $website->plugin_upgrades ) ? json_decode( $website->plugin_upgrades, true ) : array();
540 $theme_upgrades = ! empty( $website->theme_upgrades ) ? json_decode( $website->theme_upgrades, true ) : array();
541 $translation_upgrades = ! empty( $website->translation_upgrades ) ? json_decode( $website->translation_upgrades, true ) : array();
542
543 if ( ! is_array( $wp_upgrades ) ) {
544 $wp_upgrades = array();
545 }
546
547 if ( ! is_array( $plugin_upgrades ) ) {
548 $plugin_upgrades = array();
549 }
550
551 if ( ! is_array( $theme_upgrades ) ) {
552 $theme_upgrades = array();
553 }
554
555 if ( ! is_array( $translation_upgrades ) ) {
556 $translation_upgrades = array();
557 }
558
559 if ( $website->is_ignoreCoreUpdates ) {
560 $wp_upgrades = array();
561 }
562
563 if ( $website->is_ignorePluginUpdates ) {
564 $plugin_upgrades = array();
565 }
566
567 if ( $website->is_ignoreThemeUpdates ) {
568 $theme_upgrades = array();
569 }
570
571 if ( ! empty( $wp_upgrades ) ) {
572 $ignored_wp = ! empty( $website->ignored_wp_upgrades ) ? json_decode( $website->ignored_wp_upgrades, true ) : false;
573 if ( is_array( $ignored_wp ) && ! empty( $ignored_wp ) && MainWP_Common_Functions::instance()->is_ignored_updates( $wp_upgrades, $ignored_wp, 'core' ) ) {
574 $wp_upgrades = array();
575 }
576 if ( ! empty( $decodedIgnoredCores ) && ! empty( $wp_upgrades ) && MainWP_Common_Functions::instance()->is_ignored_updates( $wp_upgrades, $decodedIgnoredCores, 'core' ) ) {
577 $wp_upgrades = array();
578 }
579 }
580
581 if ( ! empty( $plugin_upgrades ) ) {
582 $ignored_plugins = ! empty( $website->ignored_plugins ) ? json_decode( $website->ignored_plugins, true ) : false;
583 if ( is_array( $ignored_plugins ) && ! empty( $ignored_plugins ) ) {
584 $plugin_upgrades = MainWP_Common_Functions::instance()->get_not_ignored_updates_themesplugins( $plugin_upgrades, $ignored_plugins );
585
586 }
587 if ( ! empty( $decodedIgnoredPlugins ) ) {
588 $plugin_upgrades = MainWP_Common_Functions::instance()->get_not_ignored_updates_themesplugins( $plugin_upgrades, $decodedIgnoredPlugins );
589 }
590 }
591
592 if ( ! empty( $theme_upgrades ) ) {
593 $ignored_themes = ! empty( $website->ignored_themes ) ? json_decode( $website->ignored_themes, true ) : false;
594 if ( is_array( $ignored_themes ) && ! empty( $ignored_themes ) ) {
595 $theme_upgrades = MainWP_Common_Functions::instance()->get_not_ignored_updates_themesplugins( $theme_upgrades, $ignored_themes );
596 }
597 if ( ! empty( $decodedIgnoredThemes ) ) {
598 $theme_upgrades = MainWP_Common_Functions::instance()->get_not_ignored_updates_themesplugins( $theme_upgrades, $decodedIgnoredThemes );
599 }
600 }
601
602 $args = $this->prepare_objects_query( $request );
603
604 $type = isset( $args['type'] ) && ! empty( $args['type'] ) ? wp_parse_list( $args['type'] ) : array();
605 $type = array_filter( array_map( 'trim', $type ) );
606 $s = isset( $args['s'] ) ? trim( $args['s'] ) : '';
607
608 if ( empty( $type ) ) {
609 $type[] = 'all';
610 }
611
612 $all = in_array( 'all', $type ) ? true : false;
613
614 $roll_data = MainWP_Updates_Helper::instance()->get_roll_items_updates_of_site( $website );
615
616 if ( ( $all || in_array( 'plugins', $type ) ) && ! empty( $roll_data['plugins'] ) ) {
617 $rollback_plugins = $roll_data['plugins'];
618 }
619
620 if ( ( $all || in_array( 'themes', $type ) ) && ! empty( $roll_data['themes'] ) ) {
621 $rollback_themes = $roll_data['themes'];
622 }
623
624 if ( ! is_array( $rollback_plugins ) ) {
625 $rollback_plugins = array();
626 }
627
628 if ( ! is_array( $rollback_themes ) ) {
629 $rollback_themes = array();
630 }
631
632 if ( ! empty( $s ) ) {
633 $wp_upgrades = mainwp_search_in_array( $wp_upgrades, $s );
634 $plugin_upgrades = mainwp_search_in_array( $plugin_upgrades, $s, array( 'in_sub_fields' => 'Name' ) );
635 $theme_upgrades = mainwp_search_in_array( $theme_upgrades, $s, array( 'in_sub_fields' => 'Name' ) );
636 $translation_upgrades = mainwp_search_in_array( $translation_upgrades, $s, array( 'in_sub_fields' => 'Name' ) );
637 $rollback_plugins = mainwp_search_in_array( $rollback_plugins, $s, array( 'in_sub_fields' => 'Name' ) );
638 $rollback_themes = mainwp_search_in_array( $rollback_themes, $s, array( 'in_sub_fields' => 'Name' ) );
639 }
640
641 if ( $all || in_array( 'wp', $type ) ) {
642 $data['wp'] = $wp_upgrades;
643 }
644
645 if ( $all || in_array( 'plugins', $type ) ) {
646 $data['plugins'] = $plugin_upgrades;
647 if ( ! empty( $rollback_plugins ) ) {
648 $data['rollback_plugins'] = $rollback_plugins;
649 }
650 }
651
652 if ( $all || in_array( 'themes', $type ) ) {
653 $data['themes'] = $theme_upgrades;
654 if ( ! empty( $rollback_themes ) ) {
655 $data['rollback_themes'] = $rollback_themes;
656 }
657 }
658
659 if ( $all || in_array( 'translations', $type ) ) {
660 $data['translations'] = $translation_upgrades;
661 }
662
663 $resp_data = array(
664 'success' => 1,
665 'data' => $data,
666 );
667 return rest_ensure_response( $resp_data );
668 }
669
670
671 /**
672 * Get global ignored updates.
673 *
674 * @param WP_REST_Request $request Full details about the request.
675 * @return WP_Error|WP_REST_Response
676 */
677 public function get_all_global_ignored_updates( $request ) { //phpcs:ignore -- NOSONAR - complex.
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.
722 $userExtension = MainWP_DB_Common::instance()->get_user_extension();
723 $ignored_plugins = ! empty( $userExtension->ignored_plugins ) ? json_decode( $userExtension->ignored_plugins, true ) : array();
724 $ignored_themes = ! empty( $userExtension->ignored_themes ) ? json_decode( $userExtension->ignored_themes, true ) : array();
725
726 if ( ! is_array( $ignored_plugins ) ) {
727 $ignored_plugins = array();
728 }
729 if ( ! is_array( $ignored_themes ) ) {
730 $ignored_themes = array();
731 }
732
733 $all = in_array( 'all', $type ) ? true : false;
734
735 if ( ! empty( $s ) ) {
736 $ignored_plugins = mainwp_search_in_array( $ignored_plugins, $s, array( 'in_sub_fields' => 'Name' ) );
737 $ignored_themes = mainwp_search_in_array( $ignored_themes, $s, array( 'in_sub_fields' => 'Name' ) );
738 }
739
740 $data = array();
741
742 if ( ( $all || in_array( 'plugins', $type ) ) && ! empty( $ignored_plugins ) ) {
743 $data['plugins'] = $ignored_plugins;
744 }
745
746 if ( ( $all || in_array( 'themes', $type ) ) && ! empty( $ignored_themes ) ) {
747 $data['themes'] = $ignored_themes;
748 }
749
750 $resp_data = array(
751 'success' => 1,
752 'data' => $data,
753 );
754
755 return rest_ensure_response( $resp_data );
756 }
757
758
759 /**
760 * Get ignored updates of site.
761 *
762 * @param WP_REST_Request $request Full details about the request.
763 * @return WP_Error|WP_REST_Response
764 */
765 public function get_ignored_updates_of_site( $request ) { //phpcs:ignore -- NOSONAR complex function.
766
767 $website = $this->get_request_item( $request );
768
769 if ( is_wp_error( $website ) ) {
770 return $website;
771 }
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.
817 $ignored_plugins = ! empty( $website->ignored_plugins ) ? json_decode( $website->ignored_plugins, true ) : array();
818 $ignored_themes = ! empty( $website->ignored_themes ) ? json_decode( $website->ignored_themes, true ) : array();
819
820 if ( ! is_array( $ignored_plugins ) ) {
821 $ignored_plugins = array();
822 }
823 if ( ! is_array( $ignored_themes ) ) {
824 $ignored_themes = array();
825 }
826
827 $userExtension = MainWP_DB_Common::instance()->get_user_extension();
828 $ignored_plugins2 = ! empty( $userExtension->ignored_plugins ) ? json_decode( $userExtension->ignored_plugins, true ) : array();
829 $ignored_themes2 = ! empty( $userExtension->ignored_plugins ) ? json_decode( $userExtension->ignored_themes, true ) : array();
830
831 if ( ! is_array( $ignored_plugins2 ) ) {
832 $ignored_plugins2 = array();
833 }
834 if ( ! is_array( $ignored_themes2 ) ) {
835 $ignored_themes2 = array();
836 }
837
838 $ignored_plugins = $ignored_plugins + $ignored_plugins2;
839 $ignored_themes = $ignored_themes + $ignored_themes2;
840
841 $all = in_array( 'all', $type ) ? true : false;
842
843 if ( ! empty( $s ) ) {
844 $ignored_plugins = mainwp_search_in_array( $ignored_plugins, $s, array( 'in_sub_fields' => 'Name' ) );
845 $ignored_themes = mainwp_search_in_array( $ignored_themes, $s, array( 'in_sub_fields' => 'Name' ) );
846 }
847
848 $data = array();
849
850 if ( ( $all || in_array( 'plugins', $type ) ) && ! empty( $ignored_plugins ) ) {
851 $data['plugins'] = $ignored_plugins;
852 }
853
854 if ( ( $all || in_array( 'themes', $type ) ) && ! empty( $ignored_themes ) ) {
855 $data['themes'] = $ignored_themes;
856 }
857
858 $resp_data = array(
859 'success' => 1,
860 'data' => $data,
861 );
862
863 return rest_ensure_response( $resp_data );
864 }
865
866
867 /**
868 * Update all items.
869 *
870 * @param WP_REST_Request $request Full details about the request.
871 * @return WP_Error|WP_REST_Response
872 */
873 public function update_all_items_start( $request ) { //phpcs:ignore -- NOSONAR complex function.
874
875 $batch_updates_running = get_option( 'mainwp_batch_updates_is_running', 0 );
876 $start_time = get_option( 'mainwp_batch_updates_start_time', 0 );
877
878 if ( $batch_updates_running ) {
879 $datetime = get_date_from_gmt( gmdate( 'Y-m-d H:i:s', $start_time ) );
880 $resp_data = array(
881 'success' => 1,
882 'message' => esc_html__( 'A batch updates all job are running.', 'mainwp' ),
883 'last_time_start_batch_updates' => mainwp_rest_prepare_date_response( $datetime ),
884 );
885 return rest_ensure_response( $resp_data );
886 }
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.
932 $local_timestamp = MainWP_Utility::get_timestamp();
933 $start_time = $local_timestamp;
934 MainWP_Utility::update_option( 'mainwp_batch_updates_start_time', $start_time );
935 MainWP_Utility::update_option( 'mainwp_batch_updates_is_running', 1 ); // to start perform batch update job.
936
937 $websites = MainWP_Auto_Updates_DB::instance()->get_websites_to_start_updates( true, true );
938 while ( $websites && ( $website = MainWP_DB::fetch_object( $websites ) ) ) {
939 MainWP_Cron_Jobs_Batch::instance()->prepare_bulk_updates( $website );
940 $websiteValues = array(
941 'dtsAutomaticSyncStart' => $start_time,
942 );
943 MainWP_DB::instance()->update_website_sync_values( $website->id, $websiteValues );
944 }
945 MainWP_DB::free_result( $websites );
946
947 MainWP_Logger::instance()->log_update_check( 'Batch updates started' );
948
949 $msg = esc_html__( 'Batch updates all started successfully.', 'mainwp' );
950
951 $resp_data = array(
952 'success' => 1,
953 'message' => $msg,
954 );
955
956 $datetime = get_date_from_gmt( gmdate( 'Y-m-d H:i:s', $start_time ) );
957 $resp_data['last_time_start_batch_updates'] = mainwp_rest_prepare_date_response( $datetime );
958
959 return rest_ensure_response( $resp_data );
960 }
961
962 /**
963 * Update all items of site.
964 *
965 * @param WP_REST_Request $request Full details about the request.
966 * @return WP_Error|WP_REST_Response
967 */
968 public function update_all_items_inidividual_site_start( $request ) { //phpcs:ignore -- NOSONAR complex function.
969 $website = $this->get_request_item( $request );
970
971 if ( is_wp_error( $website ) ) {
972 return $website;
973 }
974
975 // Fetch full site data with options needed for business rule checks.
976 // This is done BEFORE checking abilities to ensure consistent behavior.
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 ) ) ) );
978 $website = $fetch_one ? MainWP_DB::fetch_object( $fetch_one ) : false;
979 MainWP_DB::free_result( $fetch_one );
980
981 if ( empty( $website ) ) {
982 return $this->get_rest_data_error( 'id', 'site' );
983 }
984
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 ) {
988 return new \WP_Error( 'mainwp_rest_updates_site_error', __( 'Website suspended. Please unsuspended the website and try again.', 'mainwp' ), array( 'status' => 400 ) );
989 }
990
991 $batch_individual_updates_running = get_option( 'mainwp_batch_individual_updates_is_running', 0 );
992 $start_individual_time = get_option( 'mainwp_batch_updates_individual_start_time', 0 );
993
994 // Check if the site is already in the batch updates queue.
995 if ( $batch_individual_updates_running && ! empty( $website->batch_individual_queue_time ) && (int) $website->batch_individual_queue_time >= (int) $start_individual_time ) {
996 $datetime = get_date_from_gmt( gmdate( 'Y-m-d H:i:s', $start_individual_time ) );
997 $resp_data = array(
998 'success' => 1,
999 'message' => esc_html__( 'A batch updates for the site are queuing to run.', 'mainwp' ),
1000 'last_time_start_batch_updates' => mainwp_rest_prepare_date_response( $datetime ),
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 }
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.
1015 );
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 );
1052 }
1053
1054 // Fallback: Legacy logic when Abilities API is not available.
1055 // reload full data.
1056 $website = MainWP_DB::instance()->get_website_by_id( $website->id );
1057 MainWP_Cron_Jobs_Batch::instance()->prepare_bulk_updates( $website );
1058
1059 $local_timestamp = MainWP_Utility::get_timestamp();
1060
1061 if ( ! $batch_individual_updates_running ) {
1062 $start_individual_time = $local_timestamp;
1063 MainWP_Utility::update_option( 'mainwp_batch_updates_individual_start_time', $start_individual_time ); // individual site batch start time.
1064 MainWP_Utility::update_option( 'mainwp_batch_individual_updates_is_running', 1 ); // individual site batch start time.
1065 MainWP_Logger::instance()->log_update_check( 'Batch individual updates start: [websiteid=' . $website->id . ']' );
1066 }
1067
1068 MainWP_DB::instance()->update_website_option( $website, 'batch_individual_queue_time', $local_timestamp ); // init queue time batch updates process.
1069
1070 $msg = esc_html__( 'Batch updates for the site started successfully. Please wait to completed.', 'mainwp' );
1071
1072 $resp_data = array(
1073 'success' => 1,
1074 'message' => $msg,
1075 );
1076
1077 $datetime = get_date_from_gmt( gmdate( 'Y-m-d H:i:s', $start_individual_time ) );
1078 $resp_data['last_time_start_batch_updates'] = mainwp_rest_prepare_date_response( $datetime );
1079 $resp_data['site'] = apply_filters( 'mainwp_rest_routes_sites_controller_filter_allowed_fields_by_context', $website, 'simple_view' );
1080 return rest_ensure_response( $resp_data );
1081 }
1082
1083 /**
1084 * Update core of site.
1085 *
1086 * @param WP_REST_Request $request Full details about the request.
1087 * @return WP_Error|WP_REST_Response
1088 */
1089 public function update_wp_site( $request ) {
1090
1091 $website = $this->get_request_item( $request );
1092
1093 if ( is_wp_error( $website ) ) {
1094 return $website;
1095 }
1096
1097 $success = false;
1098 $error = '';
1099 $data = array();
1100
1101 // get data.
1102 try {
1103 $info = MainWP_Updates_Handler::upgrade_website( $website );
1104
1105 if ( ! is_array( $info ) ) {
1106 $info = array();
1107 }
1108
1109 if ( isset( $info['upgrade'] ) && ( 'SUCCESS' === $info['upgrade'] ) ) {
1110 $success = true;
1111 } elseif ( ! empty( $info['error'] ) ) {
1112 $error = esc_html( $error );
1113 } else {
1114 $error = esc_html__( 'An undefined error occurred. Please try again.', 'mainwp' );
1115 }
1116
1117 if ( ! empty( $info['old_version'] ) ) {
1118 $data['old_version'] = $info['old_version'];
1119 }
1120
1121 if ( ! empty( $info['version'] ) ) {
1122 $data['version'] = $info['version'];
1123 }
1124 } catch ( \Exception $e ) {
1125 $error = MainWP_Error_Helper::get_console_error_message( $e );
1126 }
1127
1128 $resp_data = array(
1129 'success' => $success ? 1 : 0,
1130 );
1131
1132 if ( $success ) {
1133 $resp_data['message'] = esc_html__( 'WordPress was updated to the latest version.', 'mainwp' );
1134 }
1135
1136 if ( ! empty( $error ) ) {
1137 $resp_data['error'] = $error;
1138 }
1139
1140 if ( ! empty( $data ) ) {
1141 $resp_data['data'] = $data;
1142 }
1143
1144 return rest_ensure_response( $resp_data );
1145 }
1146
1147 /**
1148 * Update plugins of site.
1149 *
1150 * @param WP_REST_Request $request Full details about the request.
1151 * @return WP_Error|WP_REST_Response
1152 */
1153 public function update_plugins_site( $request ) { //phpcs:ignore -- NOSONAR - complex.
1154
1155 $website = $this->get_request_item( $request );
1156
1157 if ( is_wp_error( $website ) ) {
1158 return $website;
1159 }
1160
1161 $prepared_slugs = ! empty( $request['slug'] ) ? array_map( 'trim', wp_parse_list( $request['slug'] ) ) : array();
1162 if ( ! is_array( $prepared_slugs ) ) {
1163 $prepared_slugs = array();
1164 }
1165
1166 if ( $website->is_ignorePluginUpdates ) {
1167 return rest_ensure_response(
1168 array(
1169 'success' => 0,
1170 'message' => esc_html__( 'Plugins ignored updates per site.', 'mainwp' ),
1171 )
1172 );
1173 }
1174
1175 $plugin_upgrades = json_decode( $website->plugin_upgrades, true );
1176
1177 if ( is_array( $plugin_upgrades ) && ! empty( $plugin_upgrades ) ) {
1178 $ignored_plugins = ! empty( $website->ignored_plugins ) ? json_decode( $website->ignored_plugins, true ) : false;
1179 if ( is_array( $ignored_plugins ) ) {
1180 $plugin_upgrades = MainWP_Common_Functions::instance()->get_not_ignored_updates_themesplugins( $plugin_upgrades, $ignored_plugins );
1181
1182 }
1183
1184 $userExtension = MainWP_DB_Common::instance()->get_user_extension();
1185
1186 $ignored_plugins = ! empty( $userExtension->ignored_plugins ) ? json_decode( $userExtension->ignored_plugins, true ) : false;
1187 if ( is_array( $ignored_plugins ) ) {
1188 $plugin_upgrades = MainWP_Common_Functions::instance()->get_not_ignored_updates_themesplugins( $plugin_upgrades, $ignored_plugins );
1189 }
1190 }
1191
1192 $slugs = array();
1193
1194 foreach ( $plugin_upgrades as $slug => $plugin ) {
1195 $slugs[] = $slug;
1196 }
1197
1198 $slugs = array_intersect( $slugs, $prepared_slugs );
1199
1200 if ( empty( $slugs ) ) {
1201 return rest_ensure_response(
1202 array(
1203 'success' => 0,
1204 'message' => esc_html__( 'No Plugins to update.', 'mainwp' ),
1205 )
1206 );
1207 }
1208
1209 $result = array();
1210
1211 try {
1212 $information = MainWP_Connect::fetch_url_authed(
1213 $website,
1214 'upgradeplugintheme',
1215 array(
1216 'type' => 'plugin',
1217 'list' => urldecode( implode( ',', $slugs ) ),
1218 )
1219 );
1220
1221 $result = Rest_Api_V1_Helper::instance()->handle_site_update_item( $website->id, 'plugin', $information );
1222 } catch ( \Exception $e ) {
1223 return new \WP_Error( 'mainwp_rest_upgrade_plugins_of_site_error', $e->getMessage() );
1224 }
1225
1226 $resp_data = array(
1227 'success' => 1,
1228 'data' => is_array( $result ) && ! empty( $result['updates_info'] ) ? $result['updates_info'] : array(),
1229 );
1230
1231 return rest_ensure_response( $resp_data );
1232 }
1233
1234 /**
1235 * Update themes of site.
1236 *
1237 * @param WP_REST_Request $request Full details about the request.
1238 * @return WP_Error|WP_REST_Response
1239 */
1240 public function update_themes_site( $request ) { //phpcs:ignore -- NOSONAR complex function.
1241
1242 $website = $this->get_request_item( $request );
1243
1244 if ( is_wp_error( $website ) ) {
1245 return $website;
1246 }
1247
1248 if ( $website->is_ignoreThemeUpdates ) {
1249 return rest_ensure_response(
1250 array(
1251 'success' => 0,
1252 'message' => esc_html__( 'Themes ignored updates per site.', 'mainwp' ),
1253 )
1254 );
1255 }
1256
1257 $prepared_slugs = ! empty( $request['slug'] ) ? array_map( 'trim', wp_parse_list( $request['slug'] ) ) : array();
1258 if ( ! is_array( $prepared_slugs ) ) {
1259 $prepared_slugs = array();
1260 }
1261
1262 $theme_upgrades = ! empty( $website->theme_upgrades ) ? json_decode( $website->theme_upgrades, true ) : array();
1263
1264 if ( is_array( $theme_upgrades ) ) {
1265 $ignored_themes = ! empty( $website->ignored_themes ) ? json_decode( $website->ignored_themes, true ) : false;
1266 if ( is_array( $ignored_themes ) ) {
1267 $theme_upgrades = MainWP_Common_Functions::instance()->get_not_ignored_updates_themesplugins( $theme_upgrades, $ignored_themes );
1268 }
1269
1270 $userExtension = MainWP_DB_Common::instance()->get_user_extension();
1271
1272 $ignored_themes = ! empty( $userExtension->ignored_themes ) ? json_decode( $userExtension->ignored_themes, true ) : false;
1273 if ( is_array( $ignored_themes ) ) {
1274 $theme_upgrades = MainWP_Common_Functions::instance()->get_not_ignored_updates_themesplugins( $theme_upgrades, $ignored_themes );
1275 }
1276 }
1277
1278 $slugs = array();
1279 if ( is_array( $theme_upgrades ) ) {
1280 foreach ( $theme_upgrades as $slug => $theme ) {
1281 $slugs[] = $slug;
1282 }
1283 }
1284
1285 $slugs = array_intersect( $slugs, $prepared_slugs );
1286
1287 if ( empty( $slugs ) ) {
1288 return rest_ensure_response(
1289 array(
1290 'success' => 0,
1291 'message' => esc_html__( 'No Themes to update.', 'mainwp' ),
1292 )
1293 );
1294 }
1295
1296 $result = array();
1297
1298 try {
1299 $information = MainWP_Connect::fetch_url_authed(
1300 $website,
1301 'upgradeplugintheme',
1302 array(
1303 'type' => 'theme',
1304 'list' => urldecode( implode( ',', $slugs ) ),
1305 )
1306 );
1307
1308 $result = Rest_Api_V1_Helper::instance()->handle_site_update_item( $website->id, 'theme', $information );
1309 } catch ( \Exception $e ) {
1310 return new \WP_Error( 'mainwp_rest_upgrade_themes_of_site_error', $e->getMessage() );
1311 }
1312
1313 $resp_data = array(
1314 'success' => 1,
1315 'data' => is_array( $result ) && ! empty( $result['updates_info'] ) ? $result['updates_info'] : array(),
1316 );
1317
1318 return rest_ensure_response( $resp_data );
1319 }
1320
1321 /**
1322 * Update translations of site.
1323 *
1324 * @param WP_REST_Request $request Full details about the request.
1325 * @return WP_Error|WP_REST_Response
1326 */
1327 public function update_translations_site( $request ) {
1328
1329 $website = $this->get_request_item( $request );
1330
1331 if ( is_wp_error( $website ) ) {
1332 return $website;
1333 }
1334
1335 $prepared_slugs = ! empty( $request['slug'] ) ? array_map( 'trim', wp_parse_list( $request['slug'] ) ) : array();
1336 if ( ! is_array( $prepared_slugs ) ) {
1337 $prepared_slugs = array();
1338 }
1339
1340 // get data.
1341 $translation_upgrades = ! empty( $website->translation_upgrades ) ? json_decode( $website->translation_upgrades, true ) : array();
1342 $slugs = array();
1343 if ( is_array( $translation_upgrades ) ) {
1344 foreach ( $translation_upgrades as $translation_upgrade ) {
1345 $slugs[] = $translation_upgrade['slug'];
1346 }
1347 }
1348
1349 $slugs = array_intersect( $slugs, $prepared_slugs );
1350
1351 if ( empty( $slugs ) ) {
1352 return rest_ensure_response(
1353 array(
1354 'success' => 0,
1355 'message' => esc_html__( 'No Translations to update.', 'mainwp' ),
1356 )
1357 );
1358 }
1359
1360 try {
1361 $information = MainWP_Connect::fetch_url_authed(
1362 $website,
1363 'upgradetranslation',
1364 array(
1365 'type' => 'translation',
1366 'list' => urldecode( implode( ',', $slugs ) ),
1367 )
1368 );
1369 $result = Rest_Api_V1_Helper::instance()->handle_site_update_item( $website->id, 'translation', $information );
1370 } catch ( \Exception $e ) {
1371 return new \WP_Error( 'mainwp_rest_upgrade_translations_of_site_error', $e->getMessage() );
1372 }
1373
1374 $resp_data = array(
1375 'success' => 1,
1376 'data' => is_array( $result ) && ! empty( $result['updates_info'] ) ? $result['updates_info'] : array(),
1377 );
1378
1379 return rest_ensure_response( $resp_data );
1380 }
1381
1382 /**
1383 * Ignore Update plugins of site.
1384 *
1385 * @param WP_REST_Request $request Full details about the request.
1386 * @return WP_Error|WP_REST_Response
1387 */
1388 public function ignore_update_plugins_of_site( $request ) {
1389 $website = $this->get_request_item( $request );
1390
1391 if ( is_wp_error( $website ) ) {
1392 return $website;
1393 }
1394 return $this->ignore_update_item_of_site( $request, $website, 'plugin' );
1395 }
1396
1397 /**
1398 * Ignore Update plugins of site.
1399 *
1400 * @param WP_REST_Request $request Full details about the request.
1401 * @return WP_Error|WP_REST_Response
1402 */
1403 public function ignore_update_themes_of_site( $request ) {
1404
1405 $website = $this->get_request_item( $request );
1406
1407 if ( is_wp_error( $website ) ) {
1408 return $website;
1409 }
1410
1411 return $this->ignore_update_item_of_site( $request, $website, 'theme' );
1412 }
1413
1414 /**
1415 * Ignore Update plugins of site.
1416 *
1417 * @param WP_REST_Request $request Full details about the request.
1418 * @param object $website website.
1419 * @param string $type type ignore.
1420 *
1421 * @return WP_Error|WP_REST_Response
1422 */
1423 public function ignore_update_item_of_site( $request, $website, $type ) {
1424 // get parameters.
1425
1426 $slug = isset( $request['slug'] ) ? array_map( 'trim', wp_parse_list( $request['slug'] ) ) : array();
1427
1428 if ( ! is_array( $slug ) ) {
1429 $slug = array();
1430 }
1431
1432 $resp_data = array(
1433 'success' => 1,
1434 );
1435
1436 if ( ! empty( $slug ) ) {
1437 $results = $this->ignore_updates_batch_items( $type, $slug, $website );
1438 $resp_data['data'] = $results;
1439 } elseif ( 'plugin' === $type ) {
1440 $newValues = array( 'is_ignorePluginUpdates' => 1 );
1441 MainWP_DB::instance()->update_website_values( $website->id, $newValues );
1442 $website = MainWP_DB::instance()->get_website_by_id( $website->id );
1443 $succes = $website->is_ignorePluginUpdates;
1444 $resp_data['success'] = $succes ? 1 : 0;
1445 $resp_data['message'] = $succes ? esc_html__( 'Ignore update plugins per site successfully.', 'mainwp' ) : esc_html__( 'Ignore update plugins per site failed', 'mainwp' );
1446 } elseif ( 'theme' === $type ) {
1447 $newValues = array( 'is_ignoreThemeUpdates' => 1 );
1448 MainWP_DB::instance()->update_website_values( $website->id, $newValues );
1449 $website = MainWP_DB::instance()->get_website_by_id( $website->id );
1450 $succes = $website->is_ignoreThemeUpdates;
1451 $resp_data['success'] = $succes ? 1 : 0;
1452 $resp_data['message'] = $succes ? esc_html__( 'Ignore update themes per site successfully.', 'mainwp' ) : esc_html__( 'Ignore update themes per site failed', 'mainwp' );
1453 }
1454 $resp_data['url'] = $website->url;
1455 return rest_ensure_response( $resp_data );
1456 }
1457
1458 /**
1459 * Add a plugin or theme to the ignor list.
1460 *
1461 * @param mixed $type plugin|theme.
1462 * @param mixed $slugs Plugin or Theme Slug.
1463 * @param mixed $website Child Site.
1464 *
1465 * @return string success.
1466 *
1467 * @uses \MainWP\Dashboard\MainWP_DB::get_website_by_id()
1468 * @uses \MainWP\Dashboard\MainWP_DB::update_website_values()
1469 * @uses \MainWP\Dashboard\MainWP_System_Utility::can_edit_website()
1470 * @uses \MainWP\Dashboard\MainWP_Utility::ctype_digit()
1471 */
1472 public function ignore_updates_batch_items( $type, $slugs, $website ) { // phpcs:ignore -- NOSONAR - complex.
1473
1474 $data = array();
1475
1476 if ( 'plugin' === $type ) {
1477
1478 $decodedIgnoredPlugins = ! empty( $website->ignored_plugins ) ? json_decode( $website->ignored_plugins, true ) : array();
1479
1480 if ( ! is_array( $decodedIgnoredPlugins ) ) {
1481 $decodedIgnoredPlugins = array();
1482 }
1483
1484 foreach ( $slugs as $_slug => $name ) {
1485 if ( ! isset( $decodedIgnoredPlugins[ $_slug ] ) ) {
1486 $decodedIgnoredPlugins[ $_slug ] = array(
1487 'Name' => urldecode( $name ),
1488 'ignored_versions' => array( 'all_versions' ),
1489 );
1490 $data[ $_slug ] = 'success';
1491 }
1492 }
1493
1494 /**
1495 * Action: mainwp_before_plugin_ignore
1496 *
1497 * Fires before plugin ignore.
1498 *
1499 * @since 4.1
1500 */
1501 do_action( 'mainwp_before_plugin_ignore', $decodedIgnoredPlugins, $website );
1502
1503 MainWP_DB::instance()->update_website_values( $website->id, array( 'ignored_plugins' => wp_json_encode( $decodedIgnoredPlugins ) ) );
1504 /**
1505 * Action: mainwp_after_plugin_ignore
1506 *
1507 * Fires after plugin ignore.
1508 *
1509 * @since 4.1
1510 */
1511 do_action( 'mainwp_after_plugin_ignore', $decodedIgnoredPlugins, $website );
1512
1513 } elseif ( 'theme' === $type ) {
1514
1515 $decodedIgnoredThemes = json_decode( $website->ignored_themes, true );
1516
1517 if ( ! is_array( $decodedIgnoredThemes ) ) {
1518 $decodedIgnoredThemes = array();
1519 }
1520
1521 foreach ( $slugs as $_slug => $name ) {
1522 if ( ! isset( $decodedIgnoredThemes[ $_slug ] ) ) {
1523 $decodedIgnoredThemes[ $_slug ] = array(
1524 'Name' => urldecode( $name ),
1525 'ignored_versions' => array( 'all_versions' ),
1526 );
1527 $data[ $_slug ] = 'success';
1528 }
1529 }
1530
1531 /**
1532 * Action: mainwp_before_theme_ignore
1533 *
1534 * Fires before theme ignore.
1535 *
1536 * @since 4.1
1537 */
1538 do_action( 'mainwp_before_theme_ignore', $decodedIgnoredThemes, $website );
1539 MainWP_DB::instance()->update_website_values( $website->id, array( 'ignored_themes' => wp_json_encode( $decodedIgnoredThemes ) ) );
1540 /**
1541 * Action: mainwp_after_theme_ignore
1542 *
1543 * Fires after theme ignore.
1544 *
1545 * @since 4.1
1546 */
1547 do_action( 'mainwp_after_theme_ignore', $website, $decodedIgnoredThemes );
1548 }
1549
1550 return $data;
1551 }
1552
1553 /**
1554 * Ignore Update core of site.
1555 *
1556 * @param WP_REST_Request $request Full details about the request.
1557 * @return WP_Error|WP_REST_Response
1558 */
1559 public function ignore_update_core_of_site( $request ) {
1560
1561 $website = $this->get_request_item( $request );
1562
1563 if ( is_wp_error( $website ) ) {
1564 return $website;
1565 }
1566
1567 $newValues = array( 'is_ignoreCoreUpdates' => 1 );
1568
1569 MainWP_DB::instance()->update_website_values( $website->id, $newValues );
1570
1571 $website = MainWP_DB::instance()->get_website_by_id( $website->id );
1572
1573 $success = $website->is_ignoreCoreUpdates ? 1 : 0;
1574
1575 $resp_data = array(
1576 'success' => $success,
1577 'message' => $success ? esc_html__( 'Ignore core update successfully.', 'mainwp' ) : esc_html__( 'Ignore core update failed.', 'mainwp' ),
1578 );
1579
1580 return rest_ensure_response( $resp_data );
1581 }
1582
1583 /**
1584 * Get the Tags schema, conforming to JSON Schema.
1585 *
1586 * @since 5.2
1587 * @return array
1588 */
1589 public function get_item_schema() {
1590 return array(
1591 '$schema' => 'http://json-schema.org/draft-04/schema#',
1592 'title' => 'tags',
1593 'type' => 'object',
1594 'properties' => array(
1595 'wp' => array(
1596 'type' => 'array',
1597 'description' => __( 'WP Core Updates.', 'mainwp' ),
1598 'validate_callback' => 'rest_validate_request_arg',
1599 'context' => array( 'view' ),
1600 ),
1601 'plugins' => array(
1602 'type' => 'array',
1603 'description' => __( 'Plugin Updates.', 'mainwp' ),
1604 'validate_callback' => 'rest_validate_request_arg',
1605 'context' => array( 'view' ),
1606 ),
1607 'themes' => array(
1608 'type' => 'array',
1609 'description' => __( 'Theme Updates.', 'mainwp' ),
1610 'validate_callback' => 'rest_validate_request_arg',
1611 'context' => array( 'view' ),
1612 ),
1613 'translation' => array(
1614 'type' => 'array',
1615 'description' => __( 'Translation Updates.', 'mainwp' ),
1616 'validate_callback' => 'rest_validate_request_arg',
1617 'context' => array( 'view' ),
1618 ),
1619 'rollback_plugins' => array(
1620 'type' => 'array',
1621 'description' => __( 'Rollback Plugins.', 'mainwp' ),
1622 'validate_callback' => 'rest_validate_request_arg',
1623 'context' => array( 'view' ),
1624 ),
1625 'rollback_themes' => array(
1626 'type' => 'array',
1627 'description' => __( 'Rollback Themes.', 'mainwp' ),
1628 'validate_callback' => 'rest_validate_request_arg',
1629 'context' => array( 'view' ),
1630 ),
1631 ),
1632 );
1633 }
1634 }
1635