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-controller.php +814 -185 5.2 → 6.2.2 View file →
@@ -9,8 +9,31 @@
9 9 * <https://developer.wordpress.org/rest-api/extending-the-rest-api/controller-classes/>
10 10 *
11 11 * NOTE THAT ONLY CODE RELEVANT FOR MOST ENDPOINTS SHOULD BE INCLUDED INTO THIS CLASS.
12 12 *
13 + * ## Authentication Pattern
14 + *
15 + * All MainWP REST API v2 routes MUST be protected using the shared `get_rest_permissions_check()`
16 + * method as their `permission_callback`. This method:
17 + *
18 + * 1. Calls `MainWP_REST_Authentication::is_valid_permissions()` to validate API key authentication
19 + * 2. Returns `WP_Error` with 401 status if authentication fails or user is null
20 + * 3. Returns `true` if the authenticated user has appropriate API key permissions
21 + *
22 + * Example route registration:
23 + * ```php
24 + * register_rest_route( $this->namespace, '/' . $this->rest_base, array(
25 + * array(
26 + * 'methods' => WP_REST_Server::READABLE,
27 + * 'callback' => array( $this, 'get_items' ),
28 + * 'permission_callback' => array( $this, 'get_rest_permissions_check' ), // REQUIRED
29 + * ),
30 + * ) );
31 + * ```
32 + *
33 + * IMPORTANT: Do NOT use `__return_true` or skip `permission_callback` for protected endpoints.
34 + * All endpoints intended for API key access must use `get_rest_permissions_check`.
35 + *
13 36 * @class MainWP_REST_Controller
14 37 * @package MainWP\Dashboard
15 38 * @see https://developer.wordpress.org/rest-api/extending-the-rest-api/controller-classes/
16 39 */
@@ -20,8 +43,13 @@
20 43 }
21 44
22 45 use MainWP\Dashboard\MainWP_DB;
23 46 use MainWP\Dashboard\MainWP_DB_Client;
47 +use MainWP\Dashboard\MainWP_Utility;
48 +use MainWP\Dashboard\MainWP_System_Utility;
49 +use MainWP\Dashboard\MainWP_Connect;
50 +use MainWP\Dashboard\MainWP_Exception;
51 +use MainWP\Dashboard\MainWP_Error_Helper;
24 52
25 53 /**
26 54 * Abstract Rest Controller Class
27 55 *
@@ -72,10 +100,8 @@
72 100 protected function add_additional_fields_schema( $schema ) {
73 101 return $schema;
74 102 }
75 103
76 -
77 -
78 104 /**
79 105 * Get site item by id or domain.
80 106 *
81 107 * @param WP_REST_Request $request Full details about the request.
@@ -83,9 +109,9 @@
83 109 * @return WP_Error|Object Item.
84 110 */
85 111 public function get_site_item( $request ) {
86 112 $route = $request->get_route();
87 - if ( str_ends_with( $route, '/batch' ) ) {
113 + if ( MainWP_Utility::string_ends_by( $route, '/batch' ) ) {
88 114 $by = 'id';
89 115 $value = $request['id'];
90 116 } else {
91 117 $value = $request['id_domain'];
@@ -98,9 +124,8 @@
98 124 }
99 125 return $this->get_site_by( $by, $value );
100 126 }
101 127
102 -
103 128 /**
104 129 * Get site by.
105 130 *
106 131 * @param string $by Get by.
@@ -174,9 +199,8 @@
174 199 (array) $properties
175 200 );
176 201 }
177 202
178 -
179 203 /**
180 204 * Get normalized rest base.
181 205 *
182 206 * @return string
@@ -254,9 +278,8 @@
254 278
255 279 return true;
256 280 }
257 281
258 -
259 282 /**
260 283 * Method get_validate_args_params().
261 284 *
262 285 * @param string $slug to validate args.
@@ -324,9 +347,9 @@
324 347 );
325 348 if ( empty( $filtered_values ) && ! empty( $list_values ) ) {
326 349 return new WP_Error(
327 350 'mainwp_rest_invalid_param',
328 - sprintf( __( 'The %s argument should be: %s', 'mainwp' ), $name, implode( ',', $valid_values ) ),
351 + /* translators: 1: argument name, 2: list of valid values */ sprintf( __( 'The %1$s argument should be: %2$s', 'mainwp' ), $name, implode( ',', $valid_values ) ),
329 352 array( 'status' => 400 )
330 353 );
331 354 }
332 355 }
@@ -349,8 +372,12 @@
349 372 if ( ! empty( $request['offset'] ) ) {
350 373 $args['offset'] = $request['offset'];
351 374 }
352 375
376 + if ( ! empty( $request['limit'] ) ) {
377 + $args['limit'] = $request['limit'];
378 + }
379 +
353 380 if ( ! empty( $request['order'] ) ) {
354 381 $args['order'] = $request['order'];
355 382 }
356 383
@@ -357,10 +384,12 @@
357 384 if ( ! empty( $request['orderby'] ) ) {
358 385 $args['orderby'] = $request['orderby'];
359 386 }
360 387
361 - if ( ! empty( $request['paged'] ) ) {
362 - $args['paged'] = $request['paged'];
388 + if ( ! empty( $request['page'] ) ) {
389 + $args['paged'] = $request['page'];
390 + } elseif ( ! empty( $request['paged'] ) ) {
391 + $args['paged'] = $request['paged']; // compatible.
363 392 }
364 393
365 394 if ( ! empty( $request['per_page'] ) ) {
366 395 $args['items_per_page'] = $request['per_page'];
@@ -377,9 +406,9 @@
377 406 if ( ! empty( $request['type'] ) ) {
378 407 $args['type'] = $request['type'];
379 408 }
380 409
381 - if ( ! empty( $request['status'] ) ) {
410 + if ( isset( $request['status'] ) ) {
382 411 $args['status'] = $request['status'];
383 412 }
384 413
385 414 if ( ! empty( $request['exclude'] ) ) {
@@ -413,9 +442,8 @@
413 442
414 443 return $args;
415 444 }
416 445
417 -
418 446 /**
419 447 * Get site by.
420 448 *
421 449 * @param string $by Get by.
@@ -445,10 +473,167 @@
445 473 }
446 474 return $site;
447 475 }
448 476
477 + /**
478 + * Get the args a batch create item is validated against.
479 + *
480 + * Mirrors the args the controller's own create route registers.
481 + *
482 + * @return array
483 + */
484 + public function get_batch_create_args() {
485 + return $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE );
486 + }
449 487
450 488 /**
489 + * Get the args a batch update item is validated against.
490 + *
491 + * Mirrors the args the controller's own edit route registers.
492 + *
493 + * @return array
494 + */
495 + public function get_batch_update_args() {
496 + $args = $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE );
497 + $args['id'] = $this->get_batch_item_id_arg();
498 + return $args;
499 + }
500 +
501 + /**
502 + * Get the args a batch delete item is validated against.
503 + *
504 + * @return array
505 + */
506 + public function get_batch_delete_args() {
507 + return array(
508 + 'id' => $this->get_batch_item_id_arg(),
509 + 'force' => array(
510 + 'type' => 'boolean',
511 + 'description' => __( 'Force deletion.', 'mainwp' ),
512 + 'sanitize_callback' => 'rest_parse_request_arg',
513 + 'validate_callback' => 'rest_validate_request_arg',
514 + ),
515 + );
516 + }
517 +
518 + /**
519 + * Get the id arg a batch update or delete item is validated against.
520 + *
521 + * The sites item schema registers id with a wp_parse_id_list sanitizer for the
522 + * collection filter, which would turn a batch item's scalar id into an array
523 + * before get_site_item() reads it, so batch items get their own scalar id arg.
524 + *
525 + * @return array
526 + */
527 + protected function get_batch_item_id_arg() {
528 + return array(
529 + 'type' => 'integer',
530 + 'required' => true,
531 + 'minimum' => 1,
532 + 'description' => __( 'Item ID.', 'mainwp' ),
533 + 'sanitize_callback' => 'rest_parse_request_arg',
534 + 'validate_callback' => 'rest_validate_request_arg',
535 + );
536 + }
537 +
538 + /**
539 + * Build the internal request for one batch item addressed by id.
540 + *
541 + * The raw value goes in uncast and is validated against the batch id arg: an
542 + * (int) cast first would turn true, 5.9 or ["x"] into a valid-looking id and
543 + * act on a different item.
544 + *
545 + * @param string $route Batch route the parent request came in on.
546 + * @param mixed $id Raw id from the batch payload.
547 + * @param array $query Extra query params (e.g. force for delete).
548 + * @param array $args Args to validate against; defaults to the id arg alone.
549 + * @return WP_REST_Request|WP_Error
550 + */
551 + protected function prepare_batch_id_request( $route, $id, $query = array(), $args = array() ) {
552 + if ( empty( $args ) ) {
553 + $args = array( 'id' => $this->get_batch_item_id_arg() );
554 + }
555 + $_item = new WP_REST_Request( 'DELETE', $route );
556 + $_item->set_query_params( array_merge( array( 'id' => $id ), $query ) );
557 + // Core only validates the registered args of a dispatched request, so a
558 + // request assembled here has to run those checks itself.
559 + $_item->set_attributes( array( 'args' => $args ) );
560 + $valid = $_item->has_valid_params();
561 + if ( ! is_wp_error( $valid ) ) {
562 + $valid = $_item->sanitize_params();
563 + }
564 + return is_wp_error( $valid ) ? $valid : $_item;
565 + }
566 +
567 + /**
568 + * Build the internal request for one batch update item.
569 + *
570 + * An update item has to carry its fields in an object: a scalar cannot be a body,
571 + * and core only validates the registered args of a dispatched request, so a
572 + * request assembled here has to run those checks itself.
573 + *
574 + * @param string $route Batch route the parent request came in on.
575 + * @param mixed $item Raw item from the batch payload.
576 + * @param array $args Args to validate against.
577 + * @return WP_REST_Request|WP_Error
578 + */
579 + protected function prepare_batch_update_request( $route, $item, $args ) {
580 + if ( ! is_array( $item ) ) {
581 + return new WP_Error( 'rest_invalid_param', __( 'Batch update items must be objects carrying an item ID.', 'mainwp' ), array( 'status' => 400 ) );
582 + }
583 + $_item = new WP_REST_Request( 'PUT', $route );
584 + $_item->set_body_params( $item );
585 + $_item->set_attributes( array( 'args' => $args ) );
586 + $valid = $_item->has_valid_params();
587 + if ( ! is_wp_error( $valid ) ) {
588 + $valid = $_item->sanitize_params();
589 + }
590 + return is_wp_error( $valid ) ? $valid : $_item;
591 + }
592 +
593 + /**
594 + * Build the internal request for one batch create item.
595 + *
596 + * A create item has to carry its fields in an object: no create arg is required, so a
597 + * scalar or a JSON list would pass validation and then insert a row built out of the
598 + * schema defaults alone. An empty array stays allowed because json_decode() reports
599 + * {} and [] as the same empty PHP array. Core only validates the registered args of a
600 + * dispatched request, so a request assembled here has to run those checks itself.
601 + *
602 + * @param string $route Batch route the parent request came in on.
603 + * @param mixed $item Raw item from the batch payload.
604 + * @param array $args Args to validate against.
605 + * @param array $defaults Schema defaults the item falls back on.
606 + * @param array $query Query params of the batch request.
607 + * @return WP_REST_Request|WP_Error
608 + */
609 + protected function prepare_batch_create_request( $route, $item, $args, $defaults, $query ) {
610 + if ( ! is_array( $item ) || ( ! empty( $item ) && wp_is_numeric_array( $item ) ) ) {
611 + return new WP_Error( 'rest_invalid_param', __( 'Batch create items must be objects.', 'mainwp' ), array( 'status' => 400 ) );
612 + }
613 + $_item = new WP_REST_Request( 'POST', $route );
614 + $_item->set_default_params( $defaults );
615 + $_item->set_body_params( $item );
616 + $_item->set_query_params( $query );
617 + $_item->set_attributes( array( 'args' => $args ) );
618 + $valid = $_item->has_valid_params();
619 + if ( ! is_wp_error( $valid ) ) {
620 + $valid = $_item->sanitize_params();
621 + }
622 + return is_wp_error( $valid ) ? $valid : $_item;
623 + }
624 +
625 + /**
626 + * Id to report for a batch item that failed before it could be resolved.
627 + *
628 + * @param mixed $id Raw id from the batch payload.
629 + * @return int
630 + */
631 + protected function batch_error_id( $id ) {
632 + return is_numeric( $id ) ? (int) $id : 0;
633 + }
634 +
635 + /**
451 636 * Bulk create, update and delete items.
452 637 *
453 638 * @param WP_REST_Request $request Full details about the request.
454 639 * @return array Of WP_Error or WP_REST_Response.
@@ -473,10 +658,8 @@
473 658 }
474 659
475 660 if ( ! empty( $items['create'] ) ) {
476 661 foreach ( $items['create'] as $item ) {
477 - $_item = new WP_REST_Request( 'POST', $request->get_route() );
478 -
479 662 // Default parameters.
480 663 $defaults = array();
481 664 $schema = $this->get_public_item_schema();
482 665 foreach ( $schema['properties'] as $arg => $options ) {
@@ -483,18 +666,12 @@
483 666 if ( isset( $options['default'] ) ) {
484 667 $defaults[ $arg ] = $options['default'];
485 668 }
486 669 }
487 - $_item->set_default_params( $defaults );
488 670
489 - // Set request parameters.
490 - $_item->set_body_params( $item );
671 + $_item = $this->prepare_batch_create_request( $request->get_route(), $item, $this->get_batch_create_args(), $defaults, $query );
672 + $_response = is_wp_error( $_item ) ? $_item : $this->create_item( $_item );
491 673
492 - // Set query (GET) parameters.
493 - $_item->set_query_params( $query );
494 -
495 - $_response = $this->create_item( $_item );
496 -
497 674 if ( is_wp_error( $_response ) ) {
498 675 $response['create'][] = array(
499 676 'id' => 0,
500 677 'error' => array(
@@ -510,15 +687,14 @@
510 687 }
511 688
512 689 if ( ! empty( $items['update'] ) ) {
513 690 foreach ( $items['update'] as $item ) {
514 - $_item = new WP_REST_Request( 'PUT', $request->get_route() );
515 - $_item->set_body_params( $item );
516 - $_response = $this->update_item( $_item );
691 + $_item = $this->prepare_batch_update_request( $request->get_route(), $item, $this->get_batch_update_args() );
692 + $_response = is_wp_error( $_item ) ? $_item : $this->update_item( $_item );
517 693
518 694 if ( is_wp_error( $_response ) ) {
519 695 $response['update'][] = array(
520 - 'id' => $item['id'],
696 + 'id' => is_array( $item ) ? $this->batch_error_id( $item['id'] ?? 0 ) : 0,
521 697 'error' => array(
522 698 'code' => $_response->get_error_code(),
523 699 'message' => $_response->get_error_message(),
524 700 'data' => $_response->get_error_data(),
@@ -531,26 +707,14 @@
531 707 }
532 708
533 709 if ( ! empty( $items['delete'] ) ) {
534 710 foreach ( $items['delete'] as $id ) {
535 - $id = (int) $id;
711 + $_item = $this->prepare_batch_id_request( $request->get_route(), $id, array( 'force' => true ), $this->get_batch_delete_args() );
712 + $_response = is_wp_error( $_item ) ? $_item : $this->delete_item( $_item );
536 713
537 - if ( 0 === $id ) {
538 - continue;
539 - }
540 -
541 - $_item = new WP_REST_Request( 'DELETE', $request->get_route() );
542 - $_item->set_query_params(
543 - array(
544 - 'id' => $id,
545 - 'force' => true,
546 - )
547 - );
548 - $_response = $this->delete_item( $_item );
549 -
550 714 if ( is_wp_error( $_response ) ) {
551 715 $response['delete'][] = array(
552 - 'id' => $id,
716 + 'id' => $this->batch_error_id( $id ),
553 717 'error' => array(
554 718 'code' => $_response->get_error_code(),
555 719 'message' => $_response->get_error_message(),
556 720 'data' => $_response->get_error_data(),
@@ -562,28 +726,17 @@
562 726 }
563 727 }
564 728
565 729 $route = $request->get_route();
566 - if ( str_ends_with( $route, '/sites/batch' ) ) {
730 + if ( MainWP_Utility::string_ends_by( $route, '/sites/batch' ) ) {
567 731 if ( ! empty( $items['sync'] ) ) {
568 732 foreach ( $items['sync'] as $id ) {
569 - $id = (int) $id;
733 + $_item = $this->prepare_batch_id_request( $request->get_route(), $id );
734 + $_response = is_wp_error( $_item ) ? $_item : $this->sync_item( $_item );
570 735
571 - if ( 0 === $id ) {
572 - continue;
573 - }
574 -
575 - $_item = new WP_REST_Request( 'DELETE', $request->get_route() );
576 - $_item->set_query_params(
577 - array(
578 - 'id' => $id,
579 - )
580 - );
581 - $_response = $this->sync_item( $_item );
582 -
583 736 if ( is_wp_error( $_response ) ) {
584 737 $response['sync'][] = array(
585 - 'id' => $id,
738 + 'id' => $this->batch_error_id( $id ),
586 739 'error' => array(
587 740 'code' => $_response->get_error_code(),
588 741 'message' => $_response->get_error_message(),
589 742 'data' => $_response->get_error_data(),
@@ -596,25 +749,14 @@
596 749 }
597 750
598 751 if ( ! empty( $items['reconnect'] ) ) {
599 752 foreach ( $items['reconnect'] as $id ) {
600 - $id = (int) $id;
753 + $_item = $this->prepare_batch_id_request( $request->get_route(), $id );
754 + $_response = is_wp_error( $_item ) ? $_item : $this->reconnect_item( $_item );
601 755
602 - if ( 0 === $id ) {
603 - continue;
604 - }
605 -
606 - $_item = new WP_REST_Request( 'DELETE', $request->get_route() );
607 - $_item->set_query_params(
608 - array(
609 - 'id' => $id,
610 - )
611 - );
612 - $_response = $this->reconnect_item( $_item );
613 -
614 756 if ( is_wp_error( $_response ) ) {
615 757 $response['reconnect'][] = array(
616 - 'id' => $id,
758 + 'id' => $this->batch_error_id( $id ),
617 759 'error' => array(
618 760 'code' => $_response->get_error_code(),
619 761 'message' => $_response->get_error_message(),
620 762 'data' => $_response->get_error_data(),
@@ -627,25 +769,14 @@
627 769 }
628 770
629 771 if ( ! empty( $items['disconnect'] ) ) {
630 772 foreach ( $items['disconnect'] as $id ) {
631 - $id = (int) $id;
773 + $_item = $this->prepare_batch_id_request( $request->get_route(), $id );
774 + $_response = is_wp_error( $_item ) ? $_item : $this->disconnect_site( $_item );
632 775
633 - if ( 0 === $id ) {
634 - continue;
635 - }
636 -
637 - $_item = new WP_REST_Request( 'DELETE', $request->get_route() );
638 - $_item->set_query_params(
639 - array(
640 - 'id' => $id,
641 - )
642 - );
643 - $_response = $this->disconnect_site( $_item );
644 -
645 776 if ( is_wp_error( $_response ) ) {
646 777 $response['disconnect'][] = array(
647 - 'id' => $id,
778 + 'id' => $this->batch_error_id( $id ),
648 779 'error' => array(
649 780 'code' => $_response->get_error_code(),
650 781 'message' => $_response->get_error_message(),
651 782 'data' => $_response->get_error_data(),
@@ -658,25 +789,14 @@
658 789 }
659 790
660 791 if ( ! empty( $items['suspend'] ) ) {
661 792 foreach ( $items['suspend'] as $id ) {
662 - $id = (int) $id;
793 + $_item = $this->prepare_batch_id_request( $request->get_route(), $id );
794 + $_response = is_wp_error( $_item ) ? $_item : $this->suspend_item( $_item );
663 795
664 - if ( 0 === $id ) {
665 - continue;
666 - }
667 -
668 - $_item = new WP_REST_Request( 'DELETE', $request->get_route() );
669 - $_item->set_query_params(
670 - array(
671 - 'id' => $id,
672 - )
673 - );
674 - $_response = $this->suspend_item( $_item );
675 -
676 796 if ( is_wp_error( $_response ) ) {
677 797 $response['suspend'][] = array(
678 - 'id' => $id,
798 + 'id' => $this->batch_error_id( $id ),
679 799 'error' => array(
680 800 'code' => $_response->get_error_code(),
681 801 'message' => $_response->get_error_message(),
682 802 'data' => $_response->get_error_data(),
@@ -689,25 +809,14 @@
689 809 }
690 810
691 811 if ( ! empty( $items['check'] ) ) {
692 812 foreach ( $items['check'] as $id ) {
693 - $id = (int) $id;
813 + $_item = $this->prepare_batch_id_request( $request->get_route(), $id );
814 + $_response = is_wp_error( $_item ) ? $_item : $this->check_item( $_item );
694 815
695 - if ( 0 === $id ) {
696 - continue;
697 - }
698 -
699 - $_item = new WP_REST_Request( 'DELETE', $request->get_route() );
700 - $_item->set_query_params(
701 - array(
702 - 'id' => $id,
703 - )
704 - );
705 - $_response = $this->check_item( $_item );
706 -
707 816 if ( is_wp_error( $_response ) ) {
708 817 $response['check'][] = array(
709 - 'id' => $id,
818 + 'id' => $this->batch_error_id( $id ),
710 819 'error' => array(
711 820 'code' => $_response->get_error_code(),
712 821 'message' => $_response->get_error_message(),
713 822 'data' => $_response->get_error_data(),
@@ -720,25 +829,14 @@
720 829 }
721 830
722 831 if ( ! empty( $items['remove'] ) ) {
723 832 foreach ( $items['remove'] as $id ) {
724 - $id = (int) $id;
833 + $_item = $this->prepare_batch_id_request( $request->get_route(), $id );
834 + $_response = is_wp_error( $_item ) ? $_item : $this->delete_item( $_item );
725 835
726 - if ( 0 === $id ) {
727 - continue;
728 - }
729 -
730 - $_item = new WP_REST_Request( 'DELETE', $request->get_route() );
731 - $_item->set_query_params(
732 - array(
733 - 'id' => $id,
734 - )
735 - );
736 - $_response = $this->delete_item( $_item );
737 -
738 836 if ( is_wp_error( $_response ) ) {
739 837 $response['remove'][] = array(
740 - 'id' => $id,
838 + 'id' => $this->batch_error_id( $id ),
741 839 'error' => array(
742 840 'code' => $_response->get_error_code(),
743 841 'message' => $_response->get_error_message(),
744 842 'data' => $_response->get_error_data(),
@@ -751,25 +849,14 @@
751 849 }
752 850
753 851 if ( ! empty( $items['security'] ) ) {
754 852 foreach ( $items['security'] as $id ) {
755 - $id = (int) $id;
853 + $_item = $this->prepare_batch_id_request( $request->get_route(), $id );
854 + $_response = is_wp_error( $_item ) ? $_item : $this->security_item( $_item );
756 855
757 - if ( 0 === $id ) {
758 - continue;
759 - }
760 -
761 - $_item = new WP_REST_Request( 'DELETE', $request->get_route() );
762 - $_item->set_query_params(
763 - array(
764 - 'id' => $id,
765 - )
766 - );
767 - $_response = $this->security_item( $_item );
768 -
769 856 if ( is_wp_error( $_response ) ) {
770 857 $response['security'][] = array(
771 - 'id' => $id,
858 + 'id' => $this->batch_error_id( $id ),
772 859 'error' => array(
773 860 'code' => $_response->get_error_code(),
774 861 'message' => $_response->get_error_message(),
775 862 'data' => $_response->get_error_data(),
@@ -782,25 +869,14 @@
782 869 }
783 870
784 871 if ( ! empty( $items['plugins'] ) ) {
785 872 foreach ( $items['plugins'] as $id ) {
786 - $id = (int) $id;
873 + $_item = $this->prepare_batch_id_request( $request->get_route(), $id );
874 + $_response = is_wp_error( $_item ) ? $_item : $this->get_site_plugins( $_item );
787 875
788 - if ( 0 === $id ) {
789 - continue;
790 - }
791 -
792 - $_item = new WP_REST_Request( 'DELETE', $request->get_route() );
793 - $_item->set_query_params(
794 - array(
795 - 'id' => $id,
796 - )
797 - );
798 - $_response = $this->get_site_plugins( $_item );
799 -
800 876 if ( is_wp_error( $_response ) ) {
801 877 $response['plugins'][] = array(
802 - 'id' => $id,
878 + 'id' => $this->batch_error_id( $id ),
803 879 'error' => array(
804 880 'code' => $_response->get_error_code(),
805 881 'message' => $_response->get_error_message(),
806 882 'data' => $_response->get_error_data(),
@@ -813,25 +889,14 @@
813 889 }
814 890
815 891 if ( ! empty( $items['themes'] ) ) {
816 892 foreach ( $items['themes'] as $id ) {
817 - $id = (int) $id;
893 + $_item = $this->prepare_batch_id_request( $request->get_route(), $id );
894 + $_response = is_wp_error( $_item ) ? $_item : $this->get_site_themes( $_item );
818 895
819 - if ( 0 === $id ) {
820 - continue;
821 - }
822 -
823 - $_item = new WP_REST_Request( 'DELETE', $request->get_route() );
824 - $_item->set_query_params(
825 - array(
826 - 'id' => $id,
827 - )
828 - );
829 - $_response = $this->get_site_themes( $_item );
830 -
831 896 if ( is_wp_error( $_response ) ) {
832 897 $response['themes'][] = array(
833 - 'id' => $id,
898 + 'id' => $this->batch_error_id( $id ),
834 899 'error' => array(
835 900 'code' => $_response->get_error_code(),
836 901 'message' => $_response->get_error_message(),
837 902 'data' => $_response->get_error_data(),
@@ -844,25 +909,14 @@
844 909 }
845 910
846 911 if ( ! empty( $items['non-mainwp-changes'] ) ) {
847 912 foreach ( $items['non-mainwp-changes'] as $id ) {
848 - $id = (int) $id;
913 + $_item = $this->prepare_batch_id_request( $request->get_route(), $id );
914 + $_response = is_wp_error( $_item ) ? $_item : $this->get_non_mainwp_changes_of_site( $_item );
849 915
850 - if ( 0 === $id ) {
851 - continue;
852 - }
853 -
854 - $_item = new WP_REST_Request( 'DELETE', $request->get_route() );
855 - $_item->set_query_params(
856 - array(
857 - 'id' => $id,
858 - )
859 - );
860 - $_response = $this->get_non_mainwp_changes_of_site( $_item );
861 -
862 916 if ( is_wp_error( $_response ) ) {
863 917 $response['non-mainwp-changes'][] = array(
864 - 'id' => $id,
918 + 'id' => $this->batch_error_id( $id ),
865 919 'error' => array(
866 920 'code' => $_response->get_error_code(),
867 921 'message' => $_response->get_error_message(),
868 922 'data' => $_response->get_error_data(),
@@ -1118,9 +1172,9 @@
1118 1172 $schema = $this->get_item_schema();
1119 1173 $properties = isset( $schema['properties'] ) ? $schema['properties'] : array();
1120 1174
1121 1175 // Exclude fields that specify a different context than the request context.
1122 - $context = $request['context'];
1176 + $context = isset( $request['context'] ) ? $request['context'] : 'view';
1123 1177 if ( $context ) {
1124 1178 foreach ( $properties as $name => $options ) {
1125 1179 if ( ! empty( $options['context'] ) && ! in_array( $context, $options['context'], true ) ) {
1126 1180 unset( $properties[ $name ] );
@@ -1232,13 +1286,20 @@
1232 1286 * Updated from WordPress 5.3, included into this class to support old versions.
1233 1287 *
1234 1288 * @param array $item data.
1235 1289 * @param string $context fields to filter.
1236 - * @return array Fields to be included in the response.
1290 + * @param array $addition_fields addition_fields to be included in the response.
1291 + *
1292 + * @return array addition_fields Fields to be included in the response.
1237 1293 */
1238 - public function filter_response_data_by_allowed_fields( $item, $context = 'view' ) { //phpcs:ignore -- NOSONAR - complex.
1239 - $data = $this->filter_response_by_context( $item, 'view' );
1294 + public function filter_response_data_by_allowed_fields( $item, $context = 'view', $addition_fields = array() ) { //phpcs:ignore -- NOSONAR - complex.
1295 + $data = $this->filter_response_by_context( $item, $context );
1240 1296 $fields = $this->get_allowed_fields_by_context( $context );
1297 +
1298 + if ( ! empty( $addition_fields ) && is_array( $addition_fields ) ) {
1299 + $fields = array_values( array_unique( array_merge( $fields, $addition_fields ) ) );
1300 + }
1301 +
1241 1302 if ( is_array( $fields ) && ! empty( $fields ) ) {
1242 1303 $_data = array();
1243 1304 foreach ( $fields as $field ) {
1244 1305 if ( is_array( $data ) ) {
@@ -1298,8 +1359,16 @@
1298 1359 }
1299 1360 return true;
1300 1361 }
1301 1362
1363 + /**
1364 + * Method get_rest_api_user().
1365 + *
1366 + * @return mixed
1367 + */
1368 + public function get_rest_api_user() {
1369 + return \MainWP_REST_Authentication::get_instance()->get_rest_valid_user();
1370 + }
1302 1371
1303 1372 /**
1304 1373 * Get the query params for collections of attachments.
1305 1374 *
@@ -1389,6 +1458,566 @@
1389 1458 if ( ! empty( $slugs ) ) {
1390 1459 $slugs = array_map( 'sanitize_text_field', array_map( 'urldecode', $slugs ) );
1391 1460 }
1392 1461 return $slugs;
1462 + }
1463 +
1464 + /**
1465 + * Prepare post or page data.
1466 + *
1467 + * @param array $new_post New post data.
1468 + * @param array $post_custom Post custom data.
1469 + * @param array $post_featured_image Post featured image data.
1470 + * @param array $featured_image_data Featured image data.
1471 + * @param array $post_gallery_images Post gallery images data.
1472 + * @param array $post_category Post category data.
1473 + *
1474 + * @return array
1475 + */
1476 + public function prepare_post_page_data( $new_post, $post_custom, $post_featured_image, $featured_image_data, $post_gallery_images = '', $post_category = '' ) {
1477 + return array(
1478 + 'new_post' => base64_encode( wp_json_encode( $new_post ) ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
1479 + 'post_custom' => base64_encode( wp_json_encode( $post_custom ) ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
1480 + 'post_featured_image' => ( null !== $post_featured_image ) ? base64_encode( $post_featured_image ) : null, // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
1481 + 'featured_image_data' => ( null !== $featured_image_data ) ? base64_encode( wp_json_encode( $featured_image_data ) ) : null, // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
1482 + 'mainwp_upload_dir' => base64_encode( wp_json_encode( wp_upload_dir() ) ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
1483 + 'post_gallery_images' => base64_encode( wp_json_encode( $post_gallery_images ) ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
1484 + 'post_category' => base64_encode( wp_json_encode( $post_category ) ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
1485 + );
1486 + }
1487 +
1488 + /**
1489 + * Decode post or page data.
1490 + *
1491 + * @param array $data Data.
1492 + *
1493 + * @return array
1494 + */
1495 + public function decode_post_page_data( $data ) {
1496 + // Decode base64 json.
1497 + $decode_base64_json = function ( $value, $default_value = '', $type = 'array' ) {
1498 + if ( empty( $value ) ) {
1499 + return $default_value;
1500 + }
1501 + $decoded = base64_decode( $value, true ); //phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
1502 + if ( false === $decoded ) {
1503 + return $default_value;
1504 + }
1505 +
1506 + if ( 'string' === $type ) {
1507 + return ! empty( $decoded ) ? rawurldecode( $decoded ) : $default_value;
1508 + }
1509 +
1510 + $json = json_decode( $decoded, true );
1511 + return is_array( $json ) ? $json : $default_value;
1512 + };
1513 + // Safely decode page data.
1514 + return array(
1515 + 'new_post' => $decode_base64_json( $data['new_post'] ?? '' ),
1516 + 'post_custom' => $decode_base64_json( $data['post_custom'] ?? '' ),
1517 + 'post_featured_image' => $decode_base64_json( $data['post_featured_image'] ?? '', '', 'string' ),
1518 + 'featured_image_data' => $decode_base64_json( $data['featured_image_data'] ?? '' ),
1519 + 'mainwp_upload_dir' => $decode_base64_json( $data['mainwp_upload_dir'] ?? '' ),
1520 + 'child_upload_dir' => $decode_base64_json( $data['child_upload_dir'] ?? '' ),
1521 + 'post_gallery_images' => $decode_base64_json( $data['post_gallery_images'] ?? '' ),
1522 + 'post_category' => $decode_base64_json( $data['post_category'] ?? '', '', 'string' ),
1523 + );
1524 + }
1525 +
1526 + /**
1527 + * Sanitize field.
1528 + *
1529 + * @param mixed $value Value to sanitize.
1530 + *
1531 + * @return mixed
1532 + */
1533 + public function sanitize_field( $value ) {
1534 + if ( null === $value || '' === $value ) {
1535 + return '';
1536 + }
1537 + // trim() raises a TypeError on arrays and objects under PHP 8, and a body value is free to be either.
1538 + if ( ! is_scalar( $value ) ) {
1539 + return '';
1540 + }
1541 + return sanitize_text_field( trim( $value ) );
1542 + }
1543 +
1544 + /**
1545 + * Read the request body as an associative array.
1546 + *
1547 + * The published routes accept either a JSON or a form-encoded body; clients
1548 + * send both, so JSON params are tried first and form params second.
1549 + *
1550 + * @param WP_REST_Request $request Request object.
1551 + * @return array|WP_Error Body data, or an empty_body error when nothing usable was sent.
1552 + */
1553 + protected function get_request_body_data( $request ) {
1554 + $body = $request->get_json_params();
1555 +
1556 + // Only the two parsed bags count: a raw-body decode would hand the
1557 + // handler values that never went through the registered-arg validation.
1558 + if ( ! is_array( $body ) || empty( $body ) ) {
1559 + $body = $request->get_body_params();
1560 + }
1561 +
1562 + if ( ! is_array( $body ) || empty( $body ) ) {
1563 + return new WP_Error(
1564 + 'empty_body',
1565 + __( 'Request body is empty.', 'mainwp' ),
1566 + array( 'status' => 400 )
1567 + );
1568 + }
1569 +
1570 + return $body;
1571 + }
1572 +
1573 + /**
1574 + * Make enum sanitizer.
1575 + *
1576 + * @param array $allowed Allowed values.
1577 + * @param string $type Type to coerce to.
1578 + *
1579 + * @return callable
1580 + */
1581 + public function make_enum_sanitizer( array $allowed, string $type = 'int' ) { // phpcs:ignore -- NOSONAR - complex.
1582 + $allowed_norm = array_map( fn( $v ) => $this->coerce_type( $v, $type ), $allowed );
1583 +
1584 + return function ( $value, $request, $param ) use ( $allowed_norm, $type, $allowed ) { // phpcs:ignore -- NOSONAR
1585 + if ( null === $value || '' === $value ) {
1586 + return $value;
1587 + }
1588 +
1589 + if ( 'array' === $type ) {
1590 + if ( ! is_array( $value ) ) {
1591 + $value = $this->sanitize_field( $value );
1592 + if ( is_string( $value ) && strpos( $value, ',' ) !== false ) {
1593 + $value = array_map( 'trim', explode( ',', $value ) );
1594 + } else {
1595 + $value = array( $value );
1596 + }
1597 + }
1598 +
1599 + $sanitized = array();
1600 + foreach ( $value as $item ) {
1601 + $item = $this->sanitize_field( $item );
1602 + if ( in_array( $item, $allowed, true ) ) {
1603 + $sanitized[] = $item;
1604 + } else {
1605 + return new WP_Error(
1606 + "invalid_{$param}",
1607 + sprintf(
1608 + /* translators: 1: field name, 2: allowed list */
1609 + __( 'Invalid %1$s. Allowed values: %2$s.', 'mainwp' ), // NOSONAR.
1610 + esc_html( $param ),
1611 + esc_html( implode( ', ', $allowed ) )
1612 + ),
1613 + );
1614 + }
1615 + }
1616 + return $sanitized;
1617 + }
1618 +
1619 + // Standard sanitization for non-array types.
1620 + // sanitize_field() trims, which fatals on an array or object, so treat those as a plain enum mismatch.
1621 + if ( is_scalar( $value ) ) {
1622 + $value = $this->sanitize_field( $value );
1623 + $v = $this->coerce_type( $value, $type );
1624 +
1625 + if ( in_array( $v, $allowed_norm, true ) ) {
1626 + return $v;
1627 + }
1628 + }
1629 +
1630 + return new WP_Error(
1631 + "invalid_{$param}",
1632 + sprintf(
1633 + /* translators: 1: field name, 2: allowed list */
1634 + __( 'Invalid %1$s. Allowed values: %2$s.', 'mainwp' ),
1635 + esc_html( $param ),
1636 + esc_html( implode( ', ', $allowed_norm ) )
1637 + ),
1638 + );
1639 + };
1640 + }
1641 +
1642 + /**
1643 + * Coerce value to type.
1644 + *
1645 + * @param mixed $value Value to coerce.
1646 + * @param string $type Type to coerce to.
1647 + *
1648 + * @return mixed
1649 + */
1650 + public function coerce_type( $value, string $type ) { // phpcs:ignore -- NOSONAR
1651 + switch ( $type ) {
1652 + case 'int':
1653 + return (int) $value;
1654 + case 'bool':
1655 + return (bool) $value;
1656 + case 'array':
1657 + return (array) $value;
1658 + case 'string':
1659 + default:
1660 + return (string) $value;
1661 + }
1662 + }
1663 +
1664 + /**
1665 + * Make enum validator.
1666 + *
1667 + * @param array $allowed Allowed values.
1668 + * @param string $type Type to coerce to.
1669 + *
1670 + * @return callable
1671 + */
1672 + public function make_enum_validator( array $allowed, string $type = 'int' ) { // phpcs:ignore -- NOSONAR
1673 + $allowed_norm = array_map( fn( $v ) => $this->coerce_type( $v, $type ), $allowed );
1674 +
1675 + return function ( $value, $request, $param ) use ( $allowed_norm, $type, $allowed ) { // phpcs:ignore -- NOSONAR
1676 + if ( null === $value || '' === $value ) {
1677 + return true;
1678 + }
1679 +
1680 + if ( 'array' === $type ) {
1681 + if ( ! is_array( $value ) ) {
1682 + $value = $this->sanitize_field( $value );
1683 + if ( is_string( $value ) && strpos( $value, ',' ) !== false ) {
1684 + $value = array_map( 'trim', explode( ',', $value ) );
1685 + } else {
1686 + $value = array( $value );
1687 + }
1688 + }
1689 +
1690 + // Validate each element in the array.
1691 + foreach ( $value as $item ) {
1692 + $item = $this->sanitize_field( $item );
1693 + if ( ! in_array( $item, $allowed, true ) ) {
1694 + return new WP_Error(
1695 + "invalid_{$param}",
1696 + sprintf(
1697 + /* translators: 1: field name, 2: allowed list */
1698 + __( 'Invalid %1$s. Allowed values: %2$s.', 'mainwp' ),
1699 + esc_html( $param ),
1700 + esc_html( implode( ', ', $allowed ) )
1701 + ),
1702 + );
1703 + }
1704 + }
1705 + return true;
1706 + }
1707 +
1708 + // Standard validation for non-array types.
1709 + // sanitize_field() trims, which fatals on an array or object, so treat those as a plain enum mismatch.
1710 + if ( is_scalar( $value ) ) {
1711 + $value = $this->sanitize_field( $value );
1712 + $v = $this->coerce_type( $value, $type );
1713 +
1714 + if ( in_array( $v, $allowed_norm, true ) ) {
1715 + return true;
1716 + }
1717 + }
1718 +
1719 + return new WP_Error(
1720 + "invalid_{$param}",
1721 + sprintf(
1722 + /* translators: 1: field name, 2: allowed list */
1723 + __( 'Invalid %1$s. Allowed values: %2$s.', 'mainwp' ),
1724 + esc_html( $param ),
1725 + esc_html( implode( ', ', $allowed_norm ) )
1726 + ),
1727 + );
1728 + };
1729 + }
1730 +
1731 + /**
1732 + * Pages or Posts search handler for REST API.
1733 + *
1734 + * @param mixed $data Search data from child site.
1735 + * @param object $website Child site object.
1736 + * @param mixed $output Output object to store results.
1737 + * @param array $params Request parameters.
1738 + *
1739 + * @return void
1740 + */
1741 + public static function posts_pages_search_handler( $data, $website, &$output, $params = array() ) {
1742 + if ( ! isset( $output->errors ) ) {
1743 + $output->errors = array();
1744 + }
1745 +
1746 + if ( preg_match( '/<mainwp>(.*)<\/mainwp>/', $data, $results ) > 0 ) {
1747 + $result = $results[1];
1748 + $pages = MainWP\Dashboard\MainWP_System_Utility::get_child_response( base64_decode( $result ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
1749 +
1750 + if ( is_array( $pages ) && isset( $pages['error'] ) ) {
1751 + $output->errors[ $website->id ] = esc_html( $pages['error'] );
1752 + return;
1753 + }
1754 +
1755 + $output->results[ $website->id ] = $pages;
1756 + }
1757 + }
1758 +
1759 + /**
1760 + * Get post/page id and remote edit data.
1761 + *
1762 + * @param object $website Website.
1763 + * @param WP_REST_Request $request Full details about the request.
1764 + * @param string $type Post type: 'page' or 'post' (or any CPT if child supports).
1765 + *
1766 + * @return array|WP_Error
1767 + */
1768 + protected function get_request_post_page_id( $website, $request, $type = 'page' ) { // phpcs:ignore -- NOSONAR
1769 + // Param names.
1770 + $type = ( 'post' === $type ) ? 'post' : 'page';
1771 + $type_id_param = ( 'page' === $type ) ? 'id_page' : 'id_post';
1772 + $id = $request->get_param( $type_id_param );
1773 +
1774 + if ( empty( $id ) ) {
1775 + return new WP_Error(
1776 + 'post_id_not_found',
1777 + ( 'page' === $type ) ? __( 'Page id not found.', 'mainwp' ) : __( 'Post id not found.', 'mainwp' )
1778 + );
1779 + }
1780 +
1781 + // Fetch remote edit data.
1782 + try {
1783 + $information = MainWP_Connect::fetch_url_authed(
1784 + $website,
1785 + 'post_action',
1786 + array(
1787 + 'action' => 'get_edit',
1788 + 'id' => $id,
1789 + 'post_type' => $type,
1790 + )
1791 + );
1792 + } catch ( MainWP_Exception $e ) {
1793 + return new WP_Error( 'get_post_error', MainWP_Error_Helper::get_error_message( $e ) );
1794 + }
1795 +
1796 + // Validate response.
1797 + if ( empty( $information['status'] ) || 'SUCCESS' !== $information['status'] || empty( $information['my_post'] ) ) {
1798 + return new WP_Error(
1799 + 'post_not_exist',
1800 + ( 'page' === $type ) ? __( 'Page not exist.', 'mainwp' ) : __( 'Post not exist.', 'mainwp' )
1801 + );
1802 + }
1803 +
1804 + return array(
1805 + 'data' => $information,
1806 + 'post_id' => $id,
1807 + 'post_type' => $type,
1808 + );
1809 + }
1810 +
1811 + /**
1812 + * Validate site ids.
1813 + *
1814 + * @param string $value Site id.
1815 + * @param WP_REST_Request $request Request object.
1816 + *
1817 + * @return bool|WP_Error
1818 + */
1819 + public function validate_site_ids( $value, $request ) {
1820 + if ( empty( $value ) ) {
1821 + return true;
1822 + }
1823 + $value = $this->sanitize_field( $value );
1824 + $site_ids = explode( ',', $value );
1825 + foreach ( $site_ids as $site_id ) {
1826 + $db_site = \MainWP\Dashboard\MainWP_DB::instance()->get_website_by_id( trim( $site_id ) );
1827 + if ( ! $db_site ) {
1828 + return new WP_Error(
1829 + 'invalid_site',
1830 + sprintf(
1831 + /* translators: %s: site ID */
1832 + __( 'Invalid site ID: %s.', 'mainwp' ),
1833 + esc_html( $site_id )
1834 + ),
1835 + );
1836 + }
1837 + }
1838 + return true;
1839 + }
1840 +
1841 + /**
1842 + * Sanitize text field to array.
1843 + *
1844 + * @param string $value Client id.
1845 + *
1846 + * @return string|array
1847 + */
1848 + public function sanitize_text_field_to_array( $value ) {
1849 + if ( empty( $value ) ) {
1850 + return '';
1851 + }
1852 + $value = $this->sanitize_field( $value );
1853 + return array_map( 'trim', explode( ',', $value ) );
1854 + }
1855 +
1856 + /**
1857 + * Validate clients.
1858 + *
1859 + * @param string $value Client id.
1860 + * @param WP_REST_Request $request Request object.
1861 + *
1862 + * @return bool|WP_Error
1863 + */
1864 + public function validate_clients( $value, $request ) {
1865 + if ( empty( $value ) ) {
1866 + return true;
1867 + }
1868 + $value = $this->sanitize_field( $value );
1869 + $clients = array_map( 'trim', explode( ',', $value ) );
1870 +
1871 + foreach ( $clients as $client ) {
1872 + $db_client = MainWP_DB_Client::instance()->get_wp_client_by( 'client_id', $client );
1873 + if ( ! $db_client ) {
1874 + return new WP_Error(
1875 + 'invalid_client',
1876 + sprintf(
1877 + /* translators: %s: client ID */
1878 + __( 'Invalid client ID: %s.', 'mainwp' ),
1879 + esc_html( $client )
1880 + ),
1881 + );
1882 + }
1883 + }
1884 + return true;
1885 + }
1886 + /**
1887 + * Sanitize groups text field.
1888 + *
1889 + * @param string $value Group name.
1890 + *
1891 + * @return string
1892 + */
1893 + public function sanitize_groups_text_field( $value ) {
1894 + if ( empty( $value ) ) {
1895 + return '';
1896 + }
1897 + $value = $this->sanitize_field( $value );
1898 + $group_ids = array();
1899 + $groups = array_map( 'trim', explode( ',', $value ) );
1900 + foreach ( $groups as $group ) {
1901 + $db_group = \MainWP\Dashboard\MainWP_DB_Common::instance()->get_group_by_name( $group );
1902 + if ( ! $db_group ) {
1903 + return new WP_Error(
1904 + 'invalid_group',
1905 + sprintf(
1906 + /* translators: %s: group name */
1907 + __( 'Invalid Group: %s.', 'mainwp' ),
1908 + esc_html( $group )
1909 + ),
1910 + );
1911 + }
1912 + $group_ids[] = $db_group->id;
1913 + }
1914 + return $group_ids;
1915 + }
1916 +
1917 + /**
1918 + * Get group ids from group names.
1919 + *
1920 + * @param string $value Group name.
1921 + * @param mixed $request Request object.
1922 + * @return bool|WP_Error True if valid, WP_Error otherwise.
1923 + */
1924 + public function validate_groups( $value, $request ) {
1925 + if ( empty( $value ) ) {
1926 + return true;
1927 + }
1928 + $value = $this->sanitize_field( $value );
1929 + $groups = array_map( 'trim', explode( ',', $value ) );
1930 + foreach ( $groups as $group ) {
1931 + $db_group = \MainWP\Dashboard\MainWP_DB_Common::instance()->get_group_by_name( $group );
1932 + if ( ! $db_group ) {
1933 + return new WP_Error(
1934 + 'invalid_group',
1935 + sprintf(
1936 + /* translators: %s: group name */
1937 + __( 'Invalid Group: %s.', 'mainwp' ),
1938 + esc_html( $group )
1939 + ),
1940 + );
1941 + }
1942 + }
1943 + return true;
1944 + }
1945 +
1946 + /**
1947 + * Get websites by filter.
1948 + *
1949 + * @param array $sites Sites.
1950 + * @param array $groups Groups.
1951 + * @param array $clients Clients.
1952 + *
1953 + * @return array
1954 + */
1955 + protected function get_db_websites_by_filter( $sites, $groups, $clients ) { // phpcs:ignore -- NOSONAR - complex.
1956 + $utility = MainWP_Utility::instance();
1957 + $system_utility = new MainWP_System_Utility();
1958 + $db = MainWP_DB::instance();
1959 + $data_fields = $system_utility->get_default_map_site_fields();
1960 + $data_fields[] = 'users';
1961 + $data_fields[] = 'verify_certificate'; // Add verify_certificate to the list of fields to fetch from the database.
1962 +
1963 + // Default result.
1964 + $website_url = array();
1965 + $db_websites = array();
1966 +
1967 + if ( ! empty( $sites ) && is_array( $sites ) ) {
1968 + foreach ( $sites as $v ) {
1969 + if ( $utility->ctype_digit( $v ) ) {
1970 + $website = $db->get_website_by_id( $v );
1971 + if ( $website && empty( $website->sync_errors ) && ! $system_utility->is_suspended_site( $website ) ) {
1972 + $db_websites[ $website->id ] = $utility->map_site(
1973 + $website,
1974 + $data_fields
1975 + );
1976 + $website_url[ $website->id ] = $website->url;
1977 + }
1978 + }
1979 + }
1980 + }
1981 + if ( ! empty( $groups ) && is_array( $groups ) ) {
1982 + foreach ( $groups as $v ) {
1983 + if ( $utility->ctype_digit( $v ) ) {
1984 + $websites = $db->query( $db->get_sql_websites_by_group_id( $v ) );
1985 + while ( $websites && ( $website = $db->fetch_object( $websites ) ) ) {
1986 + if ( ! empty( $website->sync_errors ) || $system_utility->is_suspended_site( $website ) ) {
1987 + continue;
1988 + }
1989 + $db_websites[ $website->id ] = $utility->map_site(
1990 + $website,
1991 + $data_fields
1992 + );
1993 + $website_url[ $website->id ] = $website->url;
1994 + }
1995 + $db->free_result( $websites );
1996 + }
1997 + }
1998 + }
1999 +
2000 + if ( ! empty( $clients ) && is_array( $clients ) ) {
2001 + $websites = MainWP_DB_Client::instance()->get_websites_by_client_ids(
2002 + $clients,
2003 + array(
2004 + 'select_data' => $data_fields,
2005 + )
2006 + );
2007 + if ( $websites ) {
2008 + foreach ( $websites as $website ) {
2009 + if ( ! empty( $website->sync_errors ) || $system_utility->is_suspended_site( $website ) ) {
2010 + continue;
2011 + }
2012 + $db_websites[ $website->id ] = $utility->map_site(
2013 + $website,
2014 + $data_fields
2015 + );
2016 + $website_url[ $website->id ] = $website->url;
2017 + }
2018 + }
2019 + }
2020 +
2021 + return compact( 'db_websites', 'website_url' );
1393 2022 }
1394 2023 }