PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / 5.4
MainWP Dashboard: Self-hosted WordPress Management for Agencies v5.4
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 5.2.2 5.3 All 153 releases
mainwp / includes / rest-api / controller / version2 / class-mainwp-rest-controller.php

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

1,406 lines 48.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * REST Controller
4 *
5 * This class extend `WP_REST_Controller` in order to include /batch endpoint
6 * for almost all endpoints in MainWP REST API.
7 *
8 * It's required to follow "Controller Classes" guide before extending this class:
9 * <https://developer.wordpress.org/rest-api/extending-the-rest-api/controller-classes/>
10 *
11 * NOTE THAT ONLY CODE RELEVANT FOR MOST ENDPOINTS SHOULD BE INCLUDED INTO THIS CLASS.
12 *
13 * @class MainWP_REST_Controller
14 * @package MainWP\Dashboard
15 * @see https://developer.wordpress.org/rest-api/extending-the-rest-api/controller-classes/
16 */
17
18 if ( ! defined( 'ABSPATH' ) ) {
19 exit;
20 }
21
22 use MainWP\Dashboard\MainWP_DB;
23 use MainWP\Dashboard\MainWP_DB_Client;
24 use MainWP\Dashboard\MainWP_Utility;
25
26 /**
27 * Abstract Rest Controller Class
28 *
29 * @package MainWP\Dashboard
30 * @extends WP_REST_Controller
31 * @version 5.2
32 */
33 abstract class MainWP_REST_Controller extends WP_REST_Controller { //phpcs:ignore -- NOSONAR - maximumMethodThreshold.
34 // phpcs:disable Generic.Metrics.CyclomaticComplexity -- complexity.
35 /**
36 * Endpoint namespace.
37 *
38 * @var string
39 */
40 protected $namespace = 'mainwp/v2';
41
42 /**
43 * Route base.
44 *
45 * @var string
46 */
47 protected $rest_base = '';
48
49 /**
50 * Used to cache computed return fields.
51 *
52 * @var null|array
53 */
54 private $_fields = null; //phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
55
56 /**
57 * Used to verify if cached fields are for correct request object.
58 *
59 * @var null|WP_REST_Request
60 */
61 private $_request = null; //phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
62
63 /**
64 *
65 * Add the schema from additional fields to an schema array.
66 *
67 * The type of object is inferred from the passed schema.
68 *
69 * @param array $schema Schema array.
70 *
71 * @return array
72 */
73 protected function add_additional_fields_schema( $schema ) {
74 return $schema;
75 }
76
77
78
79 /**
80 * Get site item by id or domain.
81 *
82 * @param WP_REST_Request $request Full details about the request.
83 *
84 * @return WP_Error|Object Item.
85 */
86 public function get_site_item( $request ) {
87 $route = $request->get_route();
88 if ( MainWP_Utility::string_ends_by( $route, '/batch' ) ) {
89 $by = 'id';
90 $value = $request['id'];
91 } else {
92 $value = $request['id_domain'];
93 $by = 'domain';
94 if ( is_numeric( $value ) ) {
95 $by = 'id';
96 } else {
97 $value = urldecode( $value );
98 }
99 }
100 return $this->get_site_by( $by, $value );
101 }
102
103
104 /**
105 * Get site by.
106 *
107 * @param string $by Get by.
108 * @param mixed $value Site id or domain.
109 * @param array $args args.
110 *
111 * @return array|mixed Response data, ready for insertion into collection data.
112 */
113 public function get_client_by( $by, $value, $args = array() ) {
114 if ( 'id' === $by ) {
115 $_by = 'client_id';
116 } elseif ( 'email' === $by ) {
117 $_by = 'client_email';
118 }
119 $client = MainWP_DB_Client::instance()->get_wp_client_by( $_by, $value );
120 if ( empty( $client ) ) {
121 return $this->get_rest_data_error( $by, 'client' );
122 }
123 return $client;
124 }
125
126 /**
127 * Compatibility functions for WP 5.5, since custom types are not supported anymore.
128 * See @link https://core.trac.wordpress.org/changeset/48306
129 *
130 * @param string $method Optional. HTTP method of the request.
131 *
132 * @return array Endpoint arguments.
133 */
134 public function get_endpoint_args_for_item_schema( $method = WP_REST_Server::CREATABLE ) {
135
136 $endpoint_args = $this->parent_get_endpoint_args_for_item_schema( $method );
137
138 if ( false === strpos( WP_REST_Server::EDITABLE, $method ) ) {
139 return $endpoint_args;
140 }
141
142 return $endpoint_args;
143 }
144
145 /**
146 * Retrieves an array of endpoint arguments from the item schema for the controller.
147 *
148 * @uses rest_get_endpoint_args_for_schema()
149 * @param string $method Optional. HTTP method of the request.
150 * @return array Endpoint arguments.
151 */
152 public function parent_get_endpoint_args_for_item_schema( $method = \WP_REST_Server::CREATABLE ) {
153 $schema = $this->get_item_schema();
154 $endpoint_args = rest_get_endpoint_args_for_schema( $schema, $method );
155 $endpoint_args = $this->remove_arg_options( $endpoint_args );
156 return $endpoint_args;
157 }
158
159 /**
160 * Recursive removal of arg_options.
161 *
162 * @param array $properties Schema properties.
163 */
164 protected function remove_arg_options( $properties ) {
165 return array_map(
166 function ( $property ) {
167 if ( isset( $property['properties'] ) ) {
168 $property['properties'] = $this->remove_arg_options( $property['properties'] );
169 } elseif ( isset( $property['items']['properties'] ) ) {
170 $property['items']['properties'] = $this->remove_arg_options( $property['items']['properties'] );
171 }
172 unset( $property['arg_options'] );
173 return $property;
174 },
175 (array) $properties
176 );
177 }
178
179
180 /**
181 * Get normalized rest base.
182 *
183 * @return string
184 */
185 protected function get_normalized_rest_base() {
186 return preg_replace( '/\(.*\)\//i', '', $this->rest_base );
187 }
188
189 /**
190 * Check batch limit.
191 *
192 * @param array $items Request items.
193 * @return bool|WP_Error
194 */
195 protected function check_batch_limit( $items ) { //phpcs:ignore -- NOSONAR - complex.
196 $limit = apply_filters( 'mainwp_rest_batch_items_limit', 100, $this->get_normalized_rest_base() );
197 $total = 0;
198
199 if ( ! empty( $items['create'] ) && is_countable( $items['create'] ) ) {
200 $total += count( $items['create'] );
201 }
202
203 if ( ! empty( $items['update'] ) && is_countable( $items['update'] ) ) {
204 $total += count( $items['update'] );
205 }
206
207 if ( ! empty( $items['delete'] ) && is_countable( $items['delete'] ) ) {
208 $total += count( $items['delete'] );
209 }
210
211 if ( ! empty( $items['sync'] ) && is_countable( $items['sync'] ) ) {
212 $total += count( $items['sync'] );
213 }
214
215 if ( ! empty( $items['reconnect'] ) && is_countable( $items['reconnect'] ) ) {
216 $total += count( $items['reconnect'] );
217 }
218
219 if ( ! empty( $items['disconnect'] ) && is_countable( $items['disconnect'] ) ) {
220 $total += count( $items['disconnect'] );
221 }
222
223 if ( ! empty( $items['suspend'] ) && is_countable( $items['suspend'] ) ) {
224 $total += count( $items['suspend'] );
225 }
226
227 if ( ! empty( $items['check'] ) && is_countable( $items['check'] ) ) {
228 $total += count( $items['check'] );
229 }
230
231 if ( ! empty( $items['remove'] ) && is_countable( $items['remove'] ) ) {
232 $total += count( $items['remove'] );
233 }
234
235 if ( ! empty( $items['security'] ) && is_countable( $items['security'] ) ) {
236 $total += count( $items['security'] );
237 }
238
239 if ( ! empty( $items['plugins'] ) && is_countable( $items['plugins'] ) ) {
240 $total += count( $items['plugins'] );
241 }
242
243 if ( ! empty( $items['themes'] ) && is_countable( $items['themes'] ) ) {
244 $total += count( $items['themes'] );
245 }
246
247 if ( ! empty( $items['non-mainwp-changes'] ) && is_countable( $items['non-mainwp-changes'] ) ) {
248 $total += count( $items['non-mainwp-changes'] );
249 }
250
251 if ( $total > $limit ) {
252 /* translators: %s: items limit */
253 return new WP_Error( 'mainwp_rest_request_entity_too_large', sprintf( __( 'Unable to accept more than %s items for this request.', 'mainwp' ), $limit ), array( 'status' => 413 ) );
254 }
255
256 return true;
257 }
258
259
260 /**
261 * Method get_validate_args_params().
262 *
263 * @param string $slug to validate args.
264 * @return array validate args info.
265 */
266 public function get_validate_args_params( $slug ) {
267
268 switch ( $slug ) {
269 case 'get_sites':
270 return array(
271 'status' => array( 'any', 'connected', 'disconnected', 'suspended', 'available_update' ),
272 );
273 case 'site_plugins':
274 case 'site_themes':
275 return array(
276 'status' => array( 'any', 'active', 'inactive' ),
277 );
278 case 'get_updates':
279 return array(
280 'type' => array( 'wp', 'plugins', 'themes', 'translations' ),
281 );
282 case 'get_costs':
283 return array(
284 'type' => array( 'any', 'subscription', 'lifetime' ),
285 );
286 default:
287 break;
288 }
289 return false;
290 }
291
292 /**
293 * Method validate_get_items_args().
294 *
295 * @param array $args args request.
296 * @param array $valid_args Valid args array.
297 *
298 * @return WP_Error|WP_REST_Response
299 */
300 public function validate_rest_args( $args, $valid_args ) { //phpcs:ignore -- NOSONAR - complex.
301
302 if ( ! is_array( $args ) ) {
303 return $args;
304 }
305
306 if ( empty( $valid_args ) || ! is_array( $valid_args ) ) {
307 return $args;
308 }
309
310 foreach ( $valid_args as $name => $valid_values ) {
311 if ( ! empty( $args[ $name ] ) ) {
312 $list_values = wp_parse_list( $args[ $name ] );
313 if ( ! empty( $list_values ) && is_array( $valid_values ) ) {
314 $filtered_values = array_map(
315 function ( $val ) use ( $valid_values ) {
316 return in_array( $val, $valid_values ) ? $val : '';
317 },
318 $list_values
319 );
320 $filtered_values = array_filter(
321 $filtered_values,
322 function ( $val ) {
323 return '' !== $val;
324 }
325 );
326 if ( empty( $filtered_values ) && ! empty( $list_values ) ) {
327 return new WP_Error(
328 'mainwp_rest_invalid_param',
329 sprintf( __( 'The %s argument should be: %s', 'mainwp' ), $name, implode( ',', $valid_values ) ),
330 array( 'status' => 400 )
331 );
332 }
333 }
334 }
335 }
336 return $args;
337 }
338
339 /**
340 * Prepare objects query.
341 *
342 * @since 5.2
343 * @param WP_REST_Request $request Full details about the request.
344 * @param string $type Object type.
345 *
346 * @return array
347 */
348 protected function prepare_objects_query( $request, $type = 'object' ) {
349 $args = array();
350 if ( ! empty( $request['offset'] ) ) {
351 $args['offset'] = $request['offset'];
352 }
353
354 if ( ! empty( $request['order'] ) ) {
355 $args['order'] = $request['order'];
356 }
357
358 if ( ! empty( $request['orderby'] ) ) {
359 $args['orderby'] = $request['orderby'];
360 }
361
362 if ( ! empty( $request['page'] ) ) {
363 $args['paged'] = $request['page'];
364 } elseif ( ! empty( $request['paged'] ) ) {
365 $args['paged'] = $request['paged']; // compatible.
366 }
367
368 if ( ! empty( $request['per_page'] ) ) {
369 $args['items_per_page'] = $request['per_page'];
370 }
371
372 if ( ! empty( $request['slug'] ) ) {
373 $args['slug'] = $request['slug'];
374 }
375
376 if ( ! empty( $request['search'] ) ) {
377 $args['s'] = $request['search'];
378 }
379
380 if ( ! empty( $request['type'] ) ) {
381 $args['type'] = $request['type'];
382 }
383
384 if ( ! empty( $request['status'] ) ) {
385 $args['status'] = $request['status'];
386 }
387
388 if ( ! empty( $request['exclude'] ) ) {
389 $args['exclude'] = $request['exclude'];
390 }
391
392 if ( ! empty( $request['include'] ) ) {
393 $args['include'] = $request['include'];
394 }
395
396 if ( ! empty( $request['must_use'] ) ) {
397 $args['must_use'] = $request['must_use'];
398 }
399
400 if ( ! empty( $request['must_use'] ) ) {
401 $args['must_use'] = $request['must_use'];
402 }
403
404 $args['fields'] = $this->get_fields_for_response( $request );
405
406 /**
407 * Filter the query arguments for a request.
408 *
409 * Enables adding extra arguments or setting defaults for a post
410 * collection request.
411 *
412 * @param array $args Key value array of query var to query value.
413 * @param WP_REST_Request $request The request used.
414 */
415 $args = apply_filters( "mainwp_rest_{$type}_object_query", $args, $request );
416
417 return $args;
418 }
419
420
421 /**
422 * Get site by.
423 *
424 * @param string $by Get by.
425 * @param mixed $value Site id or domain.
426 * @param array $args args.
427 *
428 * @return array|mixed Response data, ready for insertion into collection data.
429 */
430 public function get_site_by( $by, $value, $args = array() ) {
431 $site = false;
432 $selectgroups = ! empty( $args['with_tags'] );
433
434 if ( 'id' === $by ) {
435 $site_id = intval( $value );
436 } elseif ( 'domain' === $by ) {
437 $site = MainWP_DB::instance()->get_websites_by_url( $value );
438 if ( empty( $site ) ) {
439 return $this->get_rest_data_error( 'domain', 'site' );
440 }
441 $site = current( $site );
442 $site_id = $site->id;
443 }
444
445 $site = MainWP_DB::instance()->get_website_by_id( $site_id, $selectgroups );
446 if ( empty( $site ) ) {
447 return $this->get_rest_data_error( 'id', 'site' );
448 }
449 return $site;
450 }
451
452
453 /**
454 * Bulk create, update and delete items.
455 *
456 * @param WP_REST_Request $request Full details about the request.
457 * @return array Of WP_Error or WP_REST_Response.
458 */
459 public function batch_items( $request ) { //phpcs:ignore -- NOSONAR complex function.
460 /**
461 * REST Server
462 *
463 * @var WP_REST_Server $wp_rest_server
464 */
465 global $wp_rest_server;
466
467 // Get the request params.
468 $items = array_filter( $request->get_params() );
469 $query = $request->get_query_params();
470 $response = array();
471
472 // Check batch limit.
473 $limit = $this->check_batch_limit( $items );
474 if ( is_wp_error( $limit ) ) {
475 return $limit;
476 }
477
478 if ( ! empty( $items['create'] ) ) {
479 foreach ( $items['create'] as $item ) {
480 $_item = new WP_REST_Request( 'POST', $request->get_route() );
481
482 // Default parameters.
483 $defaults = array();
484 $schema = $this->get_public_item_schema();
485 foreach ( $schema['properties'] as $arg => $options ) {
486 if ( isset( $options['default'] ) ) {
487 $defaults[ $arg ] = $options['default'];
488 }
489 }
490 $_item->set_default_params( $defaults );
491
492 // Set request parameters.
493 $_item->set_body_params( $item );
494
495 // Set query (GET) parameters.
496 $_item->set_query_params( $query );
497
498 $_response = $this->create_item( $_item );
499
500 if ( is_wp_error( $_response ) ) {
501 $response['create'][] = array(
502 'id' => 0,
503 'error' => array(
504 'code' => $_response->get_error_code(),
505 'message' => $_response->get_error_message(),
506 'data' => $_response->get_error_data(),
507 ),
508 );
509 } else {
510 $response['create'][] = $wp_rest_server->response_to_data( $_response, '' );
511 }
512 }
513 }
514
515 if ( ! empty( $items['update'] ) ) {
516 foreach ( $items['update'] as $item ) {
517 $_item = new WP_REST_Request( 'PUT', $request->get_route() );
518 $_item->set_body_params( $item );
519 $_response = $this->update_item( $_item );
520
521 if ( is_wp_error( $_response ) ) {
522 $response['update'][] = array(
523 'id' => $item['id'],
524 'error' => array(
525 'code' => $_response->get_error_code(),
526 'message' => $_response->get_error_message(),
527 'data' => $_response->get_error_data(),
528 ),
529 );
530 } else {
531 $response['update'][] = $wp_rest_server->response_to_data( $_response, '' );
532 }
533 }
534 }
535
536 if ( ! empty( $items['delete'] ) ) {
537 foreach ( $items['delete'] as $id ) {
538 $id = (int) $id;
539
540 if ( 0 === $id ) {
541 continue;
542 }
543
544 $_item = new WP_REST_Request( 'DELETE', $request->get_route() );
545 $_item->set_query_params(
546 array(
547 'id' => $id,
548 'force' => true,
549 )
550 );
551 $_response = $this->delete_item( $_item );
552
553 if ( is_wp_error( $_response ) ) {
554 $response['delete'][] = array(
555 'id' => $id,
556 'error' => array(
557 'code' => $_response->get_error_code(),
558 'message' => $_response->get_error_message(),
559 'data' => $_response->get_error_data(),
560 ),
561 );
562 } else {
563 $response['delete'][] = $wp_rest_server->response_to_data( $_response, '' );
564 }
565 }
566 }
567
568 $route = $request->get_route();
569 if ( MainWP_Utility::string_ends_by( $route, '/sites/batch' ) ) {
570 if ( ! empty( $items['sync'] ) ) {
571 foreach ( $items['sync'] as $id ) {
572 $id = (int) $id;
573
574 if ( 0 === $id ) {
575 continue;
576 }
577
578 $_item = new WP_REST_Request( 'DELETE', $request->get_route() );
579 $_item->set_query_params(
580 array(
581 'id' => $id,
582 )
583 );
584 $_response = $this->sync_item( $_item );
585
586 if ( is_wp_error( $_response ) ) {
587 $response['sync'][] = array(
588 'id' => $id,
589 'error' => array(
590 'code' => $_response->get_error_code(),
591 'message' => $_response->get_error_message(),
592 'data' => $_response->get_error_data(),
593 ),
594 );
595 } else {
596 $response['sync'][] = $wp_rest_server->response_to_data( $_response, '' );
597 }
598 }
599 }
600
601 if ( ! empty( $items['reconnect'] ) ) {
602 foreach ( $items['reconnect'] as $id ) {
603 $id = (int) $id;
604
605 if ( 0 === $id ) {
606 continue;
607 }
608
609 $_item = new WP_REST_Request( 'DELETE', $request->get_route() );
610 $_item->set_query_params(
611 array(
612 'id' => $id,
613 )
614 );
615 $_response = $this->reconnect_item( $_item );
616
617 if ( is_wp_error( $_response ) ) {
618 $response['reconnect'][] = array(
619 'id' => $id,
620 'error' => array(
621 'code' => $_response->get_error_code(),
622 'message' => $_response->get_error_message(),
623 'data' => $_response->get_error_data(),
624 ),
625 );
626 } else {
627 $response['reconnect'][] = $wp_rest_server->response_to_data( $_response, '' );
628 }
629 }
630 }
631
632 if ( ! empty( $items['disconnect'] ) ) {
633 foreach ( $items['disconnect'] as $id ) {
634 $id = (int) $id;
635
636 if ( 0 === $id ) {
637 continue;
638 }
639
640 $_item = new WP_REST_Request( 'DELETE', $request->get_route() );
641 $_item->set_query_params(
642 array(
643 'id' => $id,
644 )
645 );
646 $_response = $this->disconnect_site( $_item );
647
648 if ( is_wp_error( $_response ) ) {
649 $response['disconnect'][] = array(
650 'id' => $id,
651 'error' => array(
652 'code' => $_response->get_error_code(),
653 'message' => $_response->get_error_message(),
654 'data' => $_response->get_error_data(),
655 ),
656 );
657 } else {
658 $response['disconnect'][] = $wp_rest_server->response_to_data( $_response, '' );
659 }
660 }
661 }
662
663 if ( ! empty( $items['suspend'] ) ) {
664 foreach ( $items['suspend'] as $id ) {
665 $id = (int) $id;
666
667 if ( 0 === $id ) {
668 continue;
669 }
670
671 $_item = new WP_REST_Request( 'DELETE', $request->get_route() );
672 $_item->set_query_params(
673 array(
674 'id' => $id,
675 )
676 );
677 $_response = $this->suspend_item( $_item );
678
679 if ( is_wp_error( $_response ) ) {
680 $response['suspend'][] = array(
681 'id' => $id,
682 'error' => array(
683 'code' => $_response->get_error_code(),
684 'message' => $_response->get_error_message(),
685 'data' => $_response->get_error_data(),
686 ),
687 );
688 } else {
689 $response['suspend'][] = $wp_rest_server->response_to_data( $_response, '' );
690 }
691 }
692 }
693
694 if ( ! empty( $items['check'] ) ) {
695 foreach ( $items['check'] as $id ) {
696 $id = (int) $id;
697
698 if ( 0 === $id ) {
699 continue;
700 }
701
702 $_item = new WP_REST_Request( 'DELETE', $request->get_route() );
703 $_item->set_query_params(
704 array(
705 'id' => $id,
706 )
707 );
708 $_response = $this->check_item( $_item );
709
710 if ( is_wp_error( $_response ) ) {
711 $response['check'][] = array(
712 'id' => $id,
713 'error' => array(
714 'code' => $_response->get_error_code(),
715 'message' => $_response->get_error_message(),
716 'data' => $_response->get_error_data(),
717 ),
718 );
719 } else {
720 $response['check'][] = $wp_rest_server->response_to_data( $_response, '' );
721 }
722 }
723 }
724
725 if ( ! empty( $items['remove'] ) ) {
726 foreach ( $items['remove'] as $id ) {
727 $id = (int) $id;
728
729 if ( 0 === $id ) {
730 continue;
731 }
732
733 $_item = new WP_REST_Request( 'DELETE', $request->get_route() );
734 $_item->set_query_params(
735 array(
736 'id' => $id,
737 )
738 );
739 $_response = $this->delete_item( $_item );
740
741 if ( is_wp_error( $_response ) ) {
742 $response['remove'][] = array(
743 'id' => $id,
744 'error' => array(
745 'code' => $_response->get_error_code(),
746 'message' => $_response->get_error_message(),
747 'data' => $_response->get_error_data(),
748 ),
749 );
750 } else {
751 $response['remove'][] = $wp_rest_server->response_to_data( $_response, '' );
752 }
753 }
754 }
755
756 if ( ! empty( $items['security'] ) ) {
757 foreach ( $items['security'] as $id ) {
758 $id = (int) $id;
759
760 if ( 0 === $id ) {
761 continue;
762 }
763
764 $_item = new WP_REST_Request( 'DELETE', $request->get_route() );
765 $_item->set_query_params(
766 array(
767 'id' => $id,
768 )
769 );
770 $_response = $this->security_item( $_item );
771
772 if ( is_wp_error( $_response ) ) {
773 $response['security'][] = array(
774 'id' => $id,
775 'error' => array(
776 'code' => $_response->get_error_code(),
777 'message' => $_response->get_error_message(),
778 'data' => $_response->get_error_data(),
779 ),
780 );
781 } else {
782 $response['security'][] = $wp_rest_server->response_to_data( $_response, '' );
783 }
784 }
785 }
786
787 if ( ! empty( $items['plugins'] ) ) {
788 foreach ( $items['plugins'] as $id ) {
789 $id = (int) $id;
790
791 if ( 0 === $id ) {
792 continue;
793 }
794
795 $_item = new WP_REST_Request( 'DELETE', $request->get_route() );
796 $_item->set_query_params(
797 array(
798 'id' => $id,
799 )
800 );
801 $_response = $this->get_site_plugins( $_item );
802
803 if ( is_wp_error( $_response ) ) {
804 $response['plugins'][] = array(
805 'id' => $id,
806 'error' => array(
807 'code' => $_response->get_error_code(),
808 'message' => $_response->get_error_message(),
809 'data' => $_response->get_error_data(),
810 ),
811 );
812 } else {
813 $response['plugins'][] = $wp_rest_server->response_to_data( $_response, '' );
814 }
815 }
816 }
817
818 if ( ! empty( $items['themes'] ) ) {
819 foreach ( $items['themes'] as $id ) {
820 $id = (int) $id;
821
822 if ( 0 === $id ) {
823 continue;
824 }
825
826 $_item = new WP_REST_Request( 'DELETE', $request->get_route() );
827 $_item->set_query_params(
828 array(
829 'id' => $id,
830 )
831 );
832 $_response = $this->get_site_themes( $_item );
833
834 if ( is_wp_error( $_response ) ) {
835 $response['themes'][] = array(
836 'id' => $id,
837 'error' => array(
838 'code' => $_response->get_error_code(),
839 'message' => $_response->get_error_message(),
840 'data' => $_response->get_error_data(),
841 ),
842 );
843 } else {
844 $response['themes'][] = $wp_rest_server->response_to_data( $_response, '' );
845 }
846 }
847 }
848
849 if ( ! empty( $items['non-mainwp-changes'] ) ) {
850 foreach ( $items['non-mainwp-changes'] as $id ) {
851 $id = (int) $id;
852
853 if ( 0 === $id ) {
854 continue;
855 }
856
857 $_item = new WP_REST_Request( 'DELETE', $request->get_route() );
858 $_item->set_query_params(
859 array(
860 'id' => $id,
861 )
862 );
863 $_response = $this->get_non_mainwp_changes_of_site( $_item );
864
865 if ( is_wp_error( $_response ) ) {
866 $response['non-mainwp-changes'][] = array(
867 'id' => $id,
868 'error' => array(
869 'code' => $_response->get_error_code(),
870 'message' => $_response->get_error_message(),
871 'data' => $_response->get_error_data(),
872 ),
873 );
874 } else {
875 $response['non-mainwp-changes'][] = $wp_rest_server->response_to_data( $_response, '' );
876 }
877 }
878 }
879 }
880
881 return $response;
882 }
883
884 /**
885 * Validate a text value for a text based setting.
886 *
887 * @since 5.2
888 * @param string $value Value.
889 * @param array $setting Setting.
890 * @return string
891 */
892 public function validate_setting_text_field( $value, $setting ) {
893 $value = is_null( $value ) ? '' : $value;
894 return wp_kses_post( trim( stripslashes( $value ) ) );
895 }
896
897 /**
898 * Validate select based settings.
899 *
900 * @since 5.2
901 * @param string $value Value.
902 * @param array $setting Setting.
903 * @return string|WP_Error
904 */
905 public function validate_setting_select_field( $value, $setting ) {
906 if ( array_key_exists( $value, $setting['options'] ) ) {
907 return $value;
908 } else {
909 return new WP_Error( 'rest_setting_value_invalid', __( 'An invalid setting value was passed.', 'mainwp' ), array( 'status' => 400 ) );
910 }
911 }
912
913 /**
914 * Validate multiselect based settings.
915 *
916 * @since 5.2
917 * @param array $values Values.
918 * @param array $setting Setting.
919 * @return array|WP_Error
920 */
921 public function validate_setting_multiselect_field( $values, $setting ) {
922 if ( empty( $values ) ) {
923 return array();
924 }
925
926 if ( ! is_array( $values ) ) {
927 return new WP_Error( 'rest_setting_value_invalid', __( 'An invalid setting value was passed.', 'mainwp' ), array( 'status' => 400 ) );
928 }
929
930 $final_values = array();
931 foreach ( $values as $value ) {
932 if ( array_key_exists( $value, $setting['options'] ) ) {
933 $final_values[] = $value;
934 }
935 }
936
937 return $final_values;
938 }
939
940 /**
941 * Validate image_width based settings.
942 *
943 * @since 5.2
944 * @param array $values Values.
945 * @param array $setting Setting.
946 * @return string|WP_Error
947 */
948 public function validate_setting_image_width_field( $values, $setting ) {
949 if ( ! is_array( $values ) ) {
950 return new WP_Error( 'rest_setting_value_invalid', __( 'An invalid setting value was passed.', 'mainwp' ), array( 'status' => 400 ) );
951 }
952
953 $current = $setting['value'];
954 if ( isset( $values['width'] ) ) {
955 $current['width'] = intval( $values['width'] );
956 }
957 if ( isset( $values['height'] ) ) {
958 $current['height'] = intval( $values['height'] );
959 }
960 if ( isset( $values['crop'] ) ) {
961 $current['crop'] = (bool) $values['crop'];
962 }
963 return $current;
964 }
965
966 /**
967 * Validate radio based settings.
968 *
969 * @since 5.2
970 * @param string $value Value.
971 * @param array $setting Setting.
972 * @return string|WP_Error
973 */
974 public function validate_setting_radio_field( $value, $setting ) {
975 return $this->validate_setting_select_field( $value, $setting );
976 }
977
978 /**
979 * Validate checkbox based settings.
980 *
981 * @since 5.2
982 * @param string $value Value.
983 * @param array $setting Setting.
984 * @return string|WP_Error
985 */
986 public function validate_setting_checkbox_field( $value, $setting ) {
987 if ( in_array( $value, array( 'yes', 'no' ) ) ) {
988 return $value;
989 } elseif ( empty( $value ) ) {
990 return isset( $setting['default'] ) ? $setting['default'] : 'no';
991 } else {
992 return new WP_Error( 'rest_setting_value_invalid', __( 'An invalid setting value was passed.', 'mainwp' ), array( 'status' => 400 ) );
993 }
994 }
995
996 /**
997 * Validate textarea based settings.
998 *
999 * @since 5.2
1000 * @param string $value Value.
1001 * @param array $setting Setting.
1002 * @return string
1003 */
1004 public function validate_setting_textarea_field( $value, $setting ) {
1005 $value = is_null( $value ) ? '' : $value;
1006 return wp_kses(
1007 trim( stripslashes( $value ) ),
1008 array_merge(
1009 array(
1010 'iframe' => array(
1011 'src' => true,
1012 'style' => true,
1013 'id' => true,
1014 'class' => true,
1015 ),
1016 ),
1017 wp_kses_allowed_html( 'post' )
1018 )
1019 );
1020 }
1021
1022
1023 /**
1024 * Get the batch schema, conforming to JSON Schema.
1025 *
1026 * @return array
1027 */
1028 public function get_public_batch_schema() {
1029 return array(
1030 '$schema' => 'http://json-schema.org/draft-04/schema#',
1031 'title' => 'batch',
1032 'type' => 'object',
1033 'properties' => array(
1034 'create' => array(
1035 'description' => __( 'List of created resources.', 'mainwp' ),
1036 'type' => 'array',
1037 'context' => array( 'view', 'edit' ),
1038 'items' => array(
1039 'type' => 'object',
1040 ),
1041 ),
1042 'update' => array(
1043 'description' => __( 'List of updated resources.', 'mainwp' ),
1044 'type' => 'array',
1045 'context' => array( 'view', 'edit' ),
1046 'items' => array(
1047 'type' => 'object',
1048 ),
1049 ),
1050 'delete' => array(
1051 'description' => __( 'List of delete resources.', 'mainwp' ),
1052 'type' => 'array',
1053 'context' => array( 'view', 'edit' ),
1054 'items' => array(
1055 'type' => 'integer',
1056 ),
1057 ),
1058 ),
1059 );
1060 }
1061
1062
1063 /**
1064 * Get formatted item data, not including orders count nor total spent.
1065 * This method is needed because v3 API doesn't return those two fields.
1066 *
1067 * @internal This method could disappear or have its name or signature changed in future releases.
1068 *
1069 * @param object $obj data instance.
1070 * @return array
1071 */
1072 protected function get_formatted_item_data_core( $obj ) {
1073 return array();
1074 }
1075
1076 /**
1077 * Get formatted item data, not including orders count nor total spent.
1078 * This method is needed because v3 API doesn't return those two fields.
1079 *
1080 * @internal This method could disappear or have its name or signature changed in future releases.
1081 *
1082 * @param array $data data instance.
1083 * @return array
1084 */
1085 protected function get_pre_formatted_item_data( $data ) {
1086 return $data;
1087 }
1088
1089
1090 /**
1091 * Get formatted item data, not including orders count nor total spent.
1092 * This method is needed because v3 API doesn't return those two fields.
1093 *
1094 * @internal This method could disappear or have its name or signature changed in future releases.
1095 *
1096 * @param array $data data instance.
1097 * @return array
1098 */
1099 protected function get_formatted_item_data( $data ) { //phpcs:ignore -- NOSONAR - compatible.
1100 return $data;
1101 }
1102
1103 /**
1104 * Gets an array of fields to be included on the response.
1105 *
1106 * Included fields are based on item schema and `_fields=` request argument.
1107 * Updated from WordPress 5.3, included into this class to support old versions.
1108 *
1109 * @since 5.2
1110 * @param WP_REST_Request $request Full details about the request.
1111 * @return array Fields to be included in the response.
1112 */
1113 public function get_fields_for_response( $request ) { //phpcs:ignore -- NOSONAR - complex.
1114 // From xdebug profiling, this method could take upto 25% of request time in index calls.
1115 // Cache it and make sure _fields was cached on current request object!
1116 if ( isset( $this->_fields ) && is_array( $this->_fields ) && $request === $this->_request ) {
1117 return $this->_fields;
1118 }
1119 $this->_request = $request;
1120
1121 $schema = $this->get_item_schema();
1122 $properties = isset( $schema['properties'] ) ? $schema['properties'] : array();
1123
1124 // Exclude fields that specify a different context than the request context.
1125 $context = $request['context'];
1126 if ( $context ) {
1127 foreach ( $properties as $name => $options ) {
1128 if ( ! empty( $options['context'] ) && ! in_array( $context, $options['context'], true ) ) {
1129 unset( $properties[ $name ] );
1130 }
1131 }
1132 }
1133
1134 $fields = array_keys( $properties );
1135
1136 if ( ! isset( $request['_fields'] ) ) {
1137 $this->_fields = $fields;
1138 return $fields;
1139 }
1140 $requested_fields = wp_parse_list( $request['_fields'] );
1141 if ( empty( $requested_fields ) ) {
1142 $this->_fields = $fields;
1143 return $fields;
1144 }
1145 // Trim off outside whitespace from the comma delimited list.
1146 $requested_fields = array_map( 'trim', $requested_fields );
1147 // Always persist 'id', because it can be needed for add_additional_fields_to_object().
1148 if ( in_array( 'id', $fields, true ) ) {
1149 $requested_fields[] = 'id';
1150 }
1151 // Return the list of all requested fields which appear in the schema.
1152 $this->_fields = array_reduce(
1153 $requested_fields,
1154 function ( $response_fields, $field ) use ( $fields ) {
1155 if ( in_array( $field, $fields, true ) ) {
1156 $response_fields[] = $field;
1157 return $response_fields;
1158 }
1159 // Check for nested fields if $field is not a direct match.
1160 $nested_fields = explode( '.', $field );
1161 // A nested field is included so long as its top-level property.
1162 // is present in the schema.
1163 if ( in_array( $nested_fields[0], $fields, true ) ) {
1164 $response_fields[] = $field;
1165 }
1166 return $response_fields;
1167 },
1168 array()
1169 );
1170 return $this->_fields;
1171 }
1172
1173 /**
1174 * Returns the full item schema.
1175 *
1176 * @return array
1177 */
1178 public function get_item_schema() {
1179 return array(
1180 '$schema' => 'http://json-schema.org/draft-04/schema#',
1181 'title' => $this->title,
1182 'type' => 'object',
1183 'properties' => $this->get_properties(),
1184 );
1185 }
1186
1187
1188 /**
1189 * Returns the full item response.
1190 *
1191 * @param mixed $item Item to get response for.
1192 * @return array|stdClass
1193 */
1194 public function get_item_response( $item ) {
1195 return array();
1196 }
1197
1198 /**
1199 * Return schema properties.
1200 *
1201 * @return array
1202 */
1203 public function get_properties() {
1204 return array();
1205 }
1206
1207 /**
1208 * Get route error.
1209 *
1210 * @param string $type_slug Type slug.
1211 * @param string $type 'object'.
1212 *
1213 * @return WP_Error
1214 */
1215 public function get_rest_data_error( $type_slug, $type = 'object' ) {
1216 if ( empty( $type ) && is_string( $type_slug ) ) {
1217 $type = $type_slug;
1218 }
1219 switch ( $type_slug ) {
1220 case 'id':
1221 return new WP_Error( "mainwp_rest_invalid_{$type}_id", __( 'Invalid or not found ID.', 'mainwp' ), array( 'status' => 404 ) );
1222 case 'domain':
1223 return new WP_Error( "mainwp_rest_invalid_{$type}_data", __( 'Invalid or not found domain.', 'mainwp' ), array( 'status' => 404 ) );
1224 case 'email':
1225 return new WP_Error( "mainwp_rest_invalid_{$type}_data", __( 'Invalid or not found email.', 'mainwp' ), array( 'status' => 404 ) );
1226 default:
1227 return new WP_Error( "mainwp_rest_invalid_{$type}_data", __( 'Invalid data.', 'mainwp' ), array( 'status' => 500 ) );
1228 }
1229 }
1230
1231 /**
1232 * Gets an array of fields to be included on the response.
1233 *
1234 * Included fields are based on item schema and `_fields=` request argument.
1235 * Updated from WordPress 5.3, included into this class to support old versions.
1236 *
1237 * @param array $item data.
1238 * @param string $context fields to filter.
1239 * @return array Fields to be included in the response.
1240 */
1241 public function filter_response_data_by_allowed_fields( $item, $context = 'view' ) { //phpcs:ignore -- NOSONAR - complex.
1242 $data = $this->filter_response_by_context( $item, 'view' );
1243 $fields = $this->get_allowed_fields_by_context( $context );
1244 if ( is_array( $fields ) && ! empty( $fields ) ) {
1245 $_data = array();
1246 foreach ( $fields as $field ) {
1247 if ( is_array( $data ) ) {
1248 if ( isset( $data[ $field ] ) ) {
1249 $_data[ $field ] = $data[ $field ];
1250 } else {
1251 $_data[ $field ] = '';
1252 }
1253 } elseif ( is_object( $data ) ) {
1254 if ( property_exists( $data, $field ) ) {
1255 $_data[ $field ] = $data->{$field};
1256 } else {
1257 $_data[ $field ] = '';
1258 }
1259 }
1260 }
1261 $_data = $this->get_formatted_item_data( $_data );
1262 return $_data;
1263 }
1264 return $data;
1265 }
1266
1267 /**
1268 * Gets an array of fields to be included on the response.
1269 *
1270 * Included fields are based on item schema and `_fields=` request argument.
1271 * Updated from WordPress 5.3, included into this class to support old versions.
1272 *
1273 * @since 5.2
1274 * @param string $context context.
1275 * @return array Fields to be included in the response.
1276 */
1277 public function get_allowed_fields_by_context( $context ) {
1278 $schema = $this->get_item_schema();
1279 $properties = isset( $schema['properties'] ) ? $schema['properties'] : array();
1280 if ( $context ) {
1281 foreach ( $properties as $name => $options ) {
1282 if ( ! empty( $options['context'] ) && ! in_array( $context, $options['context'], true ) ) {
1283 unset( $properties[ $name ] );
1284 }
1285 }
1286 }
1287 return array_keys( $properties );
1288 }
1289
1290 /**
1291 * Check rest permissions callback.
1292 *
1293 * @param WP_REST_Request $request Full details about the request.
1294 *
1295 * @return bool
1296 */
1297 public function get_rest_permissions_check( $request ) {
1298 $valid = \MainWP_REST_Authentication::get_instance()->is_valid_permissions( $request );
1299 if ( is_wp_error( $valid ) ) {
1300 return $valid;
1301 }
1302 return true;
1303 }
1304
1305 /**
1306 * Method get_rest_api_user().
1307 *
1308 * @return mixed
1309 */
1310 public function get_rest_api_user() {
1311 return \MainWP_REST_Authentication::get_instance()->get_rest_valid_user();
1312 }
1313
1314 /**
1315 * Get the query params for collections of attachments.
1316 *
1317 * @return array
1318 */
1319 public function get_collection_params() {
1320 $params = array();
1321 $params['context'] = $this->get_context_param();
1322 $params['context']['default'] = 'view';
1323
1324 $params['page'] = array(
1325 'description' => __( 'Current page of the collection.', 'mainwp' ),
1326 'type' => 'integer',
1327 'default' => 1,
1328 'sanitize_callback' => 'absint',
1329 'validate_callback' => 'rest_validate_request_arg',
1330 'minimum' => 1,
1331 );
1332 $params['per_page'] = array(
1333 'description' => __( 'Maximum number of items to be returned in result set.', 'mainwp' ),
1334 'type' => 'integer',
1335 'default' => 10,
1336 'minimum' => 1,
1337 'maximum' => 100,
1338 'sanitize_callback' => 'absint',
1339 'validate_callback' => 'rest_validate_request_arg',
1340 );
1341 $params['search'] = array(
1342 'description' => __( 'Limit results to those matching a string.', 'mainwp' ),
1343 'type' => 'string',
1344 'sanitize_callback' => 'sanitize_text_field',
1345 'validate_callback' => 'rest_validate_request_arg',
1346 );
1347
1348 $params['slug'] = array(
1349 'default' => '',
1350 'description' => __( 'Slugs.', 'mainwp' ),
1351 'type' => 'array',
1352 'items' => array(
1353 'type' => 'string',
1354 ),
1355 'sanitize_callback' => 'wp_parse_list',
1356 'validate_callback' => 'rest_validate_request_arg',
1357 );
1358
1359 $params['status'] = array(
1360 'default' => '',
1361 'description' => __( 'Status.', 'mainwp' ),
1362 'type' => array( 'string' ),
1363 'sanitize_callback' => 'wp_parse_list',
1364 'validate_callback' => 'rest_validate_request_arg',
1365 );
1366
1367 $params['exclude'] = array(
1368 'description' => __( 'Exclude IDs.', 'mainwp' ),
1369 'type' => 'array',
1370 'items' => array(
1371 'type' => 'integer',
1372 ),
1373 'sanitize_callback' => 'wp_parse_id_list',
1374 );
1375 $params['include'] = array(
1376 'description' => __( 'Include IDs.', 'mainwp' ),
1377 'type' => 'array',
1378 'items' => array(
1379 'type' => 'integer',
1380 ),
1381 'sanitize_callback' => 'wp_parse_id_list',
1382 );
1383
1384 /**
1385 * Filter collection parameters for the controller.
1386 *
1387 * @param array $query_params JSON Schema-formatted collection parameters.
1388 * @param object This object.
1389 */
1390 return apply_filters( 'mainwp_rest_collection_params', $params, $this );
1391 }
1392
1393 /**
1394 * Method sanitize_request_slugs().
1395 *
1396 * @param array $slugs slugs.
1397 * @return array
1398 */
1399 public function sanitize_request_slugs( $slugs ) {
1400 if ( ! empty( $slugs ) ) {
1401 $slugs = array_map( 'sanitize_text_field', array_map( 'urldecode', $slugs ) );
1402 }
1403 return $slugs;
1404 }
1405 }
1406