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-pages-controller.php

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

1,359 lines 52.2 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_DB_Client;
12 use MainWP\Dashboard\MainWP_Utility;
13 use MainWP\Dashboard\MainWP_System_Utility;
14 use MainWP\Dashboard\MainWP_Connect;
15 use MainWP\Dashboard\MainWP_Exception;
16 use MainWP\Dashboard\MainWP_Error_Helper;
17
18 // Exit if accessed directly.
19 if ( ! defined( 'ABSPATH' ) ) {
20 exit;
21 }
22
23 /**
24 * Class MainWP_Rest_Pages_Controller
25 *
26 * @package MainWP\Dashboard
27 */
28 class MainWP_Rest_Pages_Controller extends MainWP_REST_Controller { //phpcs:ignore -- NOSONAR - multi methods.
29
30 // phpcs:disable Generic.Metrics.CyclomaticComplexity -- complexity.
31
32 /**
33 * Protected static variable to hold the single instance of the class.
34 *
35 * @var mixed Default null
36 */
37 private static $instance = null;
38
39 /**
40 * Route base.
41 *
42 * @var string
43 */
44 protected $rest_base = 'pages';
45
46 /**
47 * Database instance.
48 *
49 * @var MainWP_DB
50 */
51 private $db = null;
52
53 /**
54 * Constructor.
55 */
56 public function __construct() {
57 $this->db = MainWP_DB::instance();
58 add_filter( 'mainwp_rest_pages_fields_object_query', array( $this, 'pages_fields_custom_query_args' ), 10, 2 );
59 }
60
61 /**
62 * Method instance()
63 *
64 * Create public static instance.
65 *
66 * @static
67 * @return static::$instance
68 */
69 public static function instance() {
70 if ( null === static::$instance ) {
71 static::$instance = new self();
72 }
73 return static::$instance;
74 }
75
76 /**
77 * Method register_routes()
78 *
79 * Creates the necessary endpoints for the api.
80 * Note, for a request to be successful the URL query parameters consumer_key and consumer_secret need to be set and correct.
81 */
82 public function register_routes() { // phpcs:ignore -- NOSONAR - complex.
83 register_rest_route(
84 $this->namespace,
85 '/' . $this->rest_base,
86 array(
87 array(
88 'methods' => WP_REST_Server::READABLE,
89 'callback' => array( $this, 'list_pages' ),
90 'permission_callback' => array( $this, 'get_rest_permissions_check' ),
91 'args' => $this->get_pages_fields_allowed_fields(),
92 'validate_callback' => array( $this, 'validate_filter_params' ),
93 ),
94 )
95 );
96 register_rest_route(
97 $this->namespace,
98 '/' . $this->rest_base . '/(?P<id_domain>(\d+|[A-Za-z0-9-\.]*[A-Za-z0-9-]{1,63}\.[A-Za-z]{2,6}))/(?P<id_page>[\d]+)',
99 array(
100 array(
101 'methods' => WP_REST_Server::READABLE,
102 'callback' => array( $this, 'get_page' ),
103 'permission_callback' => array( $this, 'get_rest_permissions_check' ),
104 'args' => $this->get_page_fields_allowed_fields(),
105 ),
106 )
107 );
108 register_rest_route(
109 $this->namespace,
110 '/' . $this->rest_base . '/(?P<id_domain>(\d+|[A-Za-z0-9-\.]*[A-Za-z0-9-]{1,63}\.[A-Za-z]{2,6}))/(?P<id_page>[\d]+)/update-status',
111 array(
112 array(
113 'methods' => 'PUT, PATCH',
114 'callback' => array( $this, 'update_status_page' ),
115 'permission_callback' => array( $this, 'get_rest_permissions_check' ),
116 'args' => $this->update_status_page_fields_allowed_fields(),
117 ),
118 )
119 );
120 register_rest_route(
121 $this->namespace,
122 '/' . $this->rest_base . '/(?P<id_domain>(\d+|[A-Za-z0-9-\.]*[A-Za-z0-9-]{1,63}\.[A-Za-z]{2,6}))/(?P<id_page>[\d]+)/edit',
123 array(
124 array(
125 'methods' => 'PUT, PATCH',
126 'callback' => array( $this, 'edit_page' ),
127 'permission_callback' => array( $this, 'get_rest_permissions_check' ),
128 'args' => $this->edit_page_fields_allowed_fields(),
129 ),
130 )
131 );
132 register_rest_route(
133 $this->namespace,
134 '/' . $this->rest_base . '/(?P<id_domain>(\d+|[A-Za-z0-9-\.]*[A-Za-z0-9-]{1,63}\.[A-Za-z]{2,6}))/create',
135 array(
136 array(
137 'methods' => WP_REST_Server::CREATABLE,
138 'callback' => array( $this, 'create_page' ),
139 'permission_callback' => array( $this, 'get_rest_permissions_check' ),
140 'args' => $this->create_page_fields_allowed_fields(),
141 ),
142 )
143 );
144 register_rest_route(
145 $this->namespace,
146 '/' . $this->rest_base . '/(?P<id_domain>(\d+|[A-Za-z0-9-\.]*[A-Za-z0-9-]{1,63}\.[A-Za-z]{2,6}))/(?P<id_page>[\d]+)/delete',
147 array(
148 array(
149 'methods' => WP_REST_Server::DELETABLE,
150 'callback' => array( $this, 'delete_page' ),
151 'permission_callback' => array( $this, 'get_rest_permissions_check' ),
152 'args' => $this->get_page_fields_allowed_fields(),
153 ),
154 )
155 );
156 }
157
158 /**
159 * Add custom query args for pages.
160 *
161 * @param array $args Query args.
162 * @param WP_REST_Request $request Request object.
163 *
164 * @return array
165 */
166 public function pages_fields_custom_query_args( $args, $request ) { // phpcs:ignore -- NOSONAR - complex.
167 if ( ! empty( $request['clients'] ) ) {
168 $args['clients'] = $request['clients'];
169 }
170 if ( ! empty( $request['groups'] ) ) {
171 $args['groups'] = $request['groups'];
172 }
173 if ( ! empty( $request['websites'] ) ) {
174 $args['websites'] = $request['websites'];
175 }
176 if ( ! empty( $request['dtsstart'] ) ) {
177 $args['dtsstart'] = $request['dtsstart'];
178 }
179 if ( ! empty( $request['dtsstop'] ) ) {
180 $args['dtsstop'] = $request['dtsstop'];
181 }
182 if ( ! empty( $request['search_on'] ) ) {
183 $args['search_on'] = $request['search_on'];
184 }
185 if ( ! empty( $request['maximum'] ) ) {
186 $args['maximum'] = $request['maximum'];
187 }
188 if ( ! empty( $request['post_title'] ) ) {
189 $args['post_title'] = $request['post_title'];
190 }
191 if ( ! empty( $request['post_content'] ) ) {
192 $args['post_content'] = $request['post_content'];
193 }
194 if ( ! empty( $request['post_status'] ) ) {
195 $args['post_status'] = $request['post_status'];
196 }
197 if ( ! empty( $request['post_date'] ) ) {
198 $args['post_date'] = $request['post_date'];
199 }
200 if ( ! empty( $request['post_date_gmt'] ) ) {
201 $args['post_date_gmt'] = $request['post_date_gmt'];
202 }
203 if ( ! empty( $request['post_excerpt'] ) ) {
204 $args['post_excerpt'] = $request['post_excerpt'];
205 }
206 if ( ! empty( $request['post_name'] ) ) {
207 $args['post_name'] = $request['post_name'];
208 }
209 if ( ! empty( $request['comment_status'] ) ) {
210 $args['comment_status'] = $request['comment_status'];
211 }
212 if ( ! empty( $request['ping_status'] ) ) {
213 $args['ping_status'] = $request['ping_status'];
214 }
215 if ( ! empty( $request['post_password'] ) ) {
216 $args['post_password'] = $request['post_password'];
217 }
218 if ( ! empty( $request['is_sticky'] ) ) {
219 $args['is_sticky'] = $request['is_sticky'];
220 }
221 if ( ! empty( $request['post_custom'] ) ) {
222 $args['post_custom'] = $request['post_custom'];
223 }
224 if ( ! empty( $request['post_featured_image'] ) ) {
225 $args['post_featured_image'] = $request['post_featured_image'];
226 }
227 if ( ! empty( $request['featured_image_data'] ) ) {
228 $args['featured_image_data'] = $request['featured_image_data'];
229 }
230 return $args;
231 }
232
233 /**
234 * List all pages.
235 *
236 * @param WP_REST_Request $request Full details about the request.
237 *
238 * @return WP_Error|WP_REST_Response
239 */
240 public function list_pages( $request ) { // phpcs:ignore -- NOSONAR - complex.
241 $page_data = array();
242 $utility = MainWP_Utility::instance();
243 // Get params.
244 $args = $this->prepare_objects_query( $request, 'pages_fields' );
245
246 // Map search_on values.
247 $search_on = ! empty( $args['search_on'] ) ? (string) $args['search_on'] : '';
248 $search_on = $this->map_search_on( $search_on );
249
250 $page_data = array(
251 'keyword' => ! empty( $args['s'] ) ? $args['s'] : '',
252 'search_on' => $search_on,
253 'dtsstart' => ! empty( $args['dtsstart'] ) ? $args['dtsstart'] : '',
254 'dtsstop' => ! empty( $args['dtsstop'] ) ? $args['dtsstop'] : '',
255 'status' => ! empty( $args['status'] ) ? $args['status'] : '',
256 'maxRecords' => ! empty( $args['maximum'] ) ? $args['maximum'] : 10,
257 );
258
259 if ( $utility->enabled_wp_seo() ) {
260 $page_data['WPSEOEnabled'] = 1;
261 }
262
263 // Get target websites.
264 $websites_data = $this->get_websites_for_pages_query( $args );
265 if ( is_wp_error( $websites_data ) ) {
266 return $websites_data;
267 }
268
269 // Extract websites data.
270 $website_url = $websites_data['website_url'] ?? array();
271 $db_websites = $websites_data['db_websites'] ?? array();
272
273 // Fetch pages from child sites.
274 $output = new \stdClass();
275 $output->results = array();
276 $output->errors = array();
277 MainWP_Connect::fetch_urls_authed( $db_websites, 'get_all_pages', $page_data, array( 'MainWP_REST_Controller', 'posts_pages_search_handler' ), $output );
278
279 if ( empty( $output->results ) ) {
280 // Return errors if any occurred during fetch.
281 if ( ! empty( $output->errors ) ) {
282 return new WP_Error(
283 'pages_fetch_error',
284 __( 'Error fetching pages from child sites.', 'mainwp' ),
285 array( 'errors' => $output->errors )
286 );
287 }
288 return new WP_Error(
289 'no_pages_found',
290 __( 'No pages found.', 'mainwp' ),
291 );
292 }
293
294 // Map pages data with website url.
295 $results = array();
296 foreach ( $output->results as $k => $v ) {
297 if ( ! isset( $website_url[ $k ] ) ) {
298 continue;
299 }
300 foreach ( $v as $p => $page ) {
301 $page['website_id'] = $k; // add child website id.
302 $page['dts'] = ! empty( $page['dts'] ) ? gmdate( 'Y-m-d H:i:s', $page['dts'] ) : '';
303 $results[ $website_url[ $k ] ][ $p ] = $this->filter_response_data_by_allowed_fields( $page, 'view' );
304 }
305 }
306
307 return rest_ensure_response(
308 array(
309 'success' => 1,
310 'message' => __( 'Get pages successfully.', 'mainwp' ),
311 'data' => $results,
312 )
313 );
314 }
315
316 /**
317 * Get page.
318 *
319 * @param WP_REST_Request $request Full details about the request.
320 *
321 * @return WP_Error|WP_REST_Response
322 */
323 public function get_page( $request ) { // phpcs:ignore -- NOSONAR - complex.
324 $website = $this->get_request_item( $request );
325
326 if ( empty( $website ) ) {
327 return new WP_Error(
328 'website_not_found',
329 $this->get_common_message( 'website_not_found' )
330 );
331 }
332
333 // Check page id exist.
334 $page = $this->get_request_post_page_id( $website, $request, 'page' );
335 if ( is_wp_error( $page ) ) {
336 return $page;
337 }
338
339 $page_id = $page['post_id'];
340 if ( empty( $page['data']['my_post'] ) ) {
341 return new WP_Error( 'post_not_exist', __( 'Page not exist.', 'mainwp' ) );
342 }
343
344 $data = $page['data']['my_post'];
345
346 // Safely decode page data.
347 $page_data = $this->decode_post_page_data( $data );
348 $result = array_merge(
349 array(
350 'id' => $page_id,
351 'website_id' => $website->id,
352 'post_featured_image' => $page_data['post_featured_image'],
353 'post_custom' => $page_data['post_custom'],
354 'upload_dir' => $page_data['child_upload_dir'],
355 ),
356 $page_data['new_post'],
357 );
358
359 return rest_ensure_response(
360 array(
361 'success' => 1,
362 'message' => __( 'Get page successfully.', 'mainwp' ),
363 'data' => $this->filter_response_data_by_allowed_fields( $result, 'detail' ),
364 )
365 );
366 }
367
368 /**
369 * Edit page status.
370 *
371 * @param WP_REST_Request $request Full details about the request.
372 *
373 * @return WP_Error|WP_REST_Response
374 */
375 public function update_status_page( $request ) { // phpcs:ignore -- NOSONAR - complex.
376 // Get website exist.
377 $website = $this->get_request_item( $request );
378 if ( empty( $website ) ) {
379 return new WP_Error(
380 'website_not_found',
381 $this->get_common_message( 'website_not_found' )
382 );
383 }
384
385 // Check page id exist.
386 $page = $this->get_request_post_page_id( $website, $request, 'page' );
387 if ( is_wp_error( $page ) ) {
388 return $page;
389 }
390
391 // Get status action.
392 $page_id = $page['post_id'];
393 $args = $this->prepare_objects_query( $request );
394 $status = isset( $args['status'] ) ? $args['status'] : '';
395 if ( empty( $status ) ) {
396 return new WP_Error(
397 'status_not_found',
398 __( 'Status not found.', 'mainwp' )
399 );
400 }
401
402 // Map status values.
403 $status_map = array( 'approve' => 'publish' );
404 $status = isset( $status_map[ $status ] ) ? $status_map[ $status ] : $status;
405
406 // Update page status.
407 try {
408 $information = MainWP_Connect::fetch_url_authed(
409 $website,
410 'post_action',
411 array(
412 'action' => $status,
413 'id' => $page_id,
414 )
415 );
416
417 } catch ( MainWP_Exception $e ) {
418 return new WP_Error( 'update_page_status_error', MainWP_Error_Helper::get_error_message( $e ) );
419 }
420
421 if ( ! isset( $information['status'] ) || ( 'SUCCESS' !== $information['status'] ) ) {
422 return new WP_Error(
423 'update_page_status_failed',
424 __( 'Update page status failed.', 'mainwp' )
425 );
426 }
427
428 return rest_ensure_response(
429 array(
430 'success' => 1,
431 'message' => __( 'Update page status successfully.', 'mainwp' ),
432 )
433 );
434 }
435
436 /**
437 * Edit page.
438 *
439 * @param WP_REST_Request $request Full details about the request.
440 *
441 * @return WP_Error|WP_REST_Response
442 */
443 public function edit_page( $request ) { // phpcs:ignore -- NOSONAR - complex.
444 // Get website exist.
445 $website = $this->get_request_item( $request );
446 if ( empty( $website ) ) {
447 return new WP_Error(
448 'website_not_found',
449 $this->get_common_message( 'website_not_found' )
450 );
451 }
452
453 // Check page id exist.
454 $page = $this->get_request_post_page_id( $website, $request, 'page' );
455 if ( is_wp_error( $page ) ) {
456 return $page;
457 }
458
459 $page_id = $page['post_id'];
460 $old_page = $page['data']['my_post'];
461 $old_page_data = $this->decode_post_page_data( $old_page );
462 $args = $this->prepare_objects_query( $request, 'pages_fields' );
463
464 // Get post_custom from request or use old value.
465 $new_post_custom = isset( $args['post_custom'] ) && ! empty( $args['post_custom'] ) ? $args['post_custom'] : '';
466 $post_custom = ! empty( $new_post_custom ) ? $new_post_custom : $old_page_data['post_custom'];
467
468 // Get post_featured_image from request or use old value.
469 $new_post_featured_image = isset( $args['post_featured_image'] ) && ! empty( $args['post_featured_image'] ) ? $args['post_featured_image'] : '';
470 $post_featured_image = ! empty( $new_post_featured_image ) ? $new_post_featured_image : $old_page_data['post_featured_image'];
471 $featured_image_data = isset( $args['featured_image_data'] ) && ! empty( $args['featured_image_data'] ) ? $args['featured_image_data'] : null;
472
473 // Build the page data to send to child site.
474 $new_page = array(
475 'ID' => $page_id,
476 'post_type' => 'page',
477 'post_title' => isset( $args['post_title'] ) ? $args['post_title'] : '',
478 'post_content' => isset( $args['post_content'] ) ? $args['post_content'] : '',
479 'post_status' => isset( $args['post_status'] ) ? $args['post_status'] : 'draft',
480 'post_excerpt' => isset( $args['post_excerpt'] ) ? $args['post_excerpt'] : '',
481 'post_name' => isset( $args['post_name'] ) ? $args['post_name'] : '',
482 'post_password' => isset( $args['post_password'] ) ? $args['post_password'] : '',
483 'comment_status' => isset( $args['comment_status'] ) ? $args['comment_status'] : 'open',
484 'ping_status' => isset( $args['ping_status'] ) ? $args['ping_status'] : 'closed',
485 'post_date' => isset( $args['post_date'] ) ? $args['post_date'] : '',
486 'post_date_gmt' => isset( $args['post_date_gmt'] ) ? $args['post_date_gmt'] : '',
487 'is_sticky' => isset( $args['is_sticky'] ) ? $args['is_sticky'] : '',
488 );
489 $page_data = $this->prepare_post_page_data( $new_page, $post_custom, $post_featured_image, $featured_image_data );
490
491 // Send edit request to child site.
492 try {
493 $information = MainWP_Connect::fetch_url_authed(
494 $website,
495 'newpost',
496 $page_data
497 );
498 } catch ( MainWP_Exception $e ) {
499 return new WP_Error( 'edit_page_error', MainWP_Error_Helper::get_error_message( $e ) );
500 }
501
502 if ( empty( $information['added'] ) ) {
503 return new WP_Error(
504 'edit_page_failed',
505 __( 'Edit page failed.', 'mainwp' )
506 );
507 }
508
509 return rest_ensure_response(
510 array(
511 'success' => 1,
512 'message' => __( 'Edit page successfully.', 'mainwp' ),
513 )
514 );
515 }
516
517 /**
518 * Create page.
519 *
520 * @param WP_REST_Request $request Full details about the request.
521 *
522 * @return WP_Error|WP_REST_Response
523 */
524 public function create_page( $request ) { // phpcs:ignore -- NOSONAR - complex.
525 // Get website exist.
526 $website = $this->get_request_item( $request );
527 if ( empty( $website ) ) {
528 return new WP_Error(
529 'website_not_found',
530 $this->get_common_message( 'website_not_found' )
531 );
532 }
533
534 $args = $this->prepare_objects_query( $request, 'pages_fields' );
535 $new_post = array(
536 'post_type' => 'page',
537 'post_title' => isset( $args['post_title'] ) ? $args['post_title'] : '',
538 'post_content' => isset( $args['post_content'] ) ? $args['post_content'] : '',
539 'post_status' => isset( $args['post_status'] ) ? $args['post_status'] : 'draft',
540 'post_excerpt' => isset( $args['post_excerpt'] ) ? $args['post_excerpt'] : '',
541 'post_name' => isset( $args['post_name'] ) ? $args['post_name'] : '',
542 'post_password' => isset( $args['post_password'] ) ? $args['post_password'] : '',
543 'comment_status' => isset( $args['comment_status'] ) ? $args['comment_status'] : 'open',
544 'ping_status' => isset( $args['ping_status'] ) ? $args['ping_status'] : 'closed',
545 'post_date' => isset( $args['post_date'] ) ? $args['post_date'] : '',
546 'post_date_gmt' => isset( $args['post_date_gmt'] ) ? $args['post_date_gmt'] : '',
547 'is_sticky' => isset( $args['is_sticky'] ) ? $args['is_sticky'] : '',
548 );
549
550 // Collect advanced data similar to bulk posting flow.
551 $post_custom = isset( $args['post_custom'] ) && ! empty( $args['post_custom'] ) && is_array( $args['post_custom'] ) ? $args['post_custom'] : array();
552 $post_featured_image = isset( $args['post_featured_image'] ) && ! empty( $args['post_featured_image'] ) ? $args['post_featured_image'] : null;
553 $featured_image_data = isset( $args['featured_image_data'] ) && ! empty( $args['featured_image_data'] ) ? $args['featured_image_data'] : null;
554 $post_data = $this->prepare_post_page_data( $new_post, $post_custom, $post_featured_image, $featured_image_data );
555 // Send create request to child site.
556 try {
557 $information = MainWP_Connect::fetch_url_authed(
558 $website,
559 'newpost',
560 $post_data
561 );
562
563 } catch ( MainWP_Exception $e ) {
564 return new WP_Error( 'create_page_error', MainWP_Error_Helper::get_error_message( $e ) );
565 }
566
567 if ( empty( $information['added'] ) ) {
568 return new WP_Error(
569 'create_page_failed',
570 __( 'Create page failed.', 'mainwp' )
571 );
572 }
573
574 return rest_ensure_response(
575 array(
576 'success' => 1,
577 'message' => __( 'Create page successfully.', 'mainwp' ),
578 'data' => array(
579 'post_id' => isset( $information['added_id'] ) ? $information['added_id'] : 0,
580 'link' => isset( $information['link'] ) ? $information['link'] : '',
581 ),
582 )
583 );
584 }
585
586 /**
587 * Delete page.
588 *
589 * @param WP_REST_Request $request Full details about the request.
590 *
591 * @return WP_Error|WP_REST_Response
592 */
593 public function delete_page( $request ) { // phpcs:ignore -- NOSONAR - complex.
594 // Get website exist.
595 $website = $this->get_request_item( $request );
596 if ( empty( $website ) ) {
597 return new WP_Error(
598 'website_not_found',
599 $this->get_common_message( 'website_not_found' )
600 );
601 }
602
603 // Check page id exist.
604 $page = $this->get_request_post_page_id( $website, $request, 'page' );
605 if ( is_wp_error( $page ) ) {
606 return $page;
607 }
608
609 $page_id = $page['post_id'];
610
611 try {
612 $information = MainWP_Connect::fetch_url_authed(
613 $website,
614 'post_action',
615 array(
616 'action' => 'delete',
617 'id' => $page_id,
618 'post_type' => 'page',
619 )
620 );
621
622 } catch ( MainWP_Exception $e ) {
623 return new WP_Error( 'delete_page_error', MainWP_Error_Helper::get_error_message( $e ) );
624 }
625
626 if ( ! isset( $information['status'] ) || ( 'SUCCESS' !== $information['status'] ) ) {
627 return new WP_Error(
628 'delete_page_failed',
629 __( 'Delete page failed.', 'mainwp' )
630 );
631 }
632
633 return rest_ensure_response(
634 array(
635 'success' => 1,
636 'message' => __( 'Delete page successfully.', 'mainwp' ),
637 )
638 );
639 }
640
641 /**
642 * Get site by.
643 *
644 * @param string $by Get by.
645 * @param mixed $value Site id or domain.
646 * @param array $args args.
647 *
648 * @return array|mixed Response data, ready for insertion into collection data.
649 */
650 public function get_site_by( $by, $value, $args = array() ) {
651 $site = false;
652 $select_groups = ! empty( $args['with_tags'] );
653
654 if ( 'id' === $by ) {
655 $site_id = intval( $value );
656 } elseif ( 'domain' === $by ) {
657 $site = $this->db->get_websites_by_url( $value );
658 if ( empty( $site ) ) {
659 return $this->get_rest_data_error( 'domain', 'site' );
660 }
661 $site = current( $site );
662 $site_id = $site->id;
663 }
664
665 $site = $this->db->get_website_by_id( $site_id, $select_groups );
666 if ( empty( $site ) ) {
667 return $this->get_rest_data_error( 'id', 'site' );
668 }
669 return $site;
670 }
671
672 /**
673 * Get allowed fields for pages.
674 *
675 * @return array
676 */
677 public function get_pages_fields_allowed_fields() {
678 $search_on = array( 'title', 'body', 'Title and Body' );
679 $status = array( 'publish', 'pending', 'private', 'future', 'draft', 'trash' );
680 return array(
681 'search' => array(
682 'required' => false,
683 'type' => 'string',
684 'sanitize_callback' => 'sanitize_text_field',
685 'description' => __( 'Search by keyword.', 'mainwp' ),
686 ),
687 'search_on' => array(
688 'required' => false,
689 'type' => 'string',
690 'description' => __( 'Search by title or content.', 'mainwp' ),
691 'sanitize_callback' => $this->make_enum_sanitizer( $search_on, 'string' ),
692 'validate_callback' => $this->make_enum_validator( $search_on, 'string' ),
693 ),
694 'status' => array(
695 'required' => false,
696 'type' => 'string',
697 'description' => __( 'Search by status.', 'mainwp' ),
698 'sanitize_callback' => $this->make_enum_sanitizer( $status, 'string' ),
699 'validate_callback' => $this->make_enum_validator( $status, 'string' ),
700 ),
701 'dtsstart' => array(
702 'required' => false,
703 'type' => 'string',
704 'sanitize_callback' => 'sanitize_text_field',
705 'description' => __( 'Search by start date.', 'mainwp' ),
706 'validate_callback' => array( $this, 'validate_date_format' ),
707 ),
708 'dtsstop' => array(
709 'required' => false,
710 'type' => 'string',
711 'sanitize_callback' => 'sanitize_text_field',
712 'description' => __( 'Search by stop date.', 'mainwp' ),
713 'validate_callback' => array( $this, 'validate_date_format' ),
714 ),
715 'clients' => array(
716 'required' => false,
717 'type' => 'string',
718 'description' => __( 'Search by clients.', 'mainwp' ),
719 'sanitize_callback' => array( $this, 'sanitize_text_field_to_array' ),
720 'validate_callback' => array( $this, 'validate_clients' ),
721 ),
722 'groups' => array(
723 'required' => false,
724 'type' => 'string',
725 'description' => __( 'Search by tags.', 'mainwp' ),
726 'sanitize_callback' => array( $this, 'sanitize_groups_text_field' ),
727 'validate_callback' => array( $this, 'validate_groups' ),
728 ),
729 'websites' => array(
730 'required' => false,
731 'type' => 'string',
732 'description' => __( 'Websites child site field IDs.', 'mainwp' ),
733 'sanitize_callback' => array( $this, 'sanitize_text_field_to_array' ),
734 'validate_callback' => array( $this, 'validate_site_ids' ),
735 ),
736 'maximum' => array(
737 'required' => false,
738 'type' => 'integer',
739 'sanitize_callback' => 'absint',
740 'minimum' => 1,
741 'maximum' => 200,
742 'default' => 10,
743 'description' => __( 'Maximum number of pages to return.', 'mainwp' ),
744 ),
745 'post_type' => array(
746 'required' => false,
747 'type' => 'string',
748 'sanitize_callback' => 'sanitize_text_field',
749 'description' => __( 'Search by page type.', 'mainwp' ),
750 ),
751 );
752 }
753
754 /**
755 * Get allowed fields for page.
756 *
757 * @return array
758 */
759 public function get_page_fields_allowed_fields() {
760 return array_merge(
761 $this->allowed_id_domain_field(),
762 $this->allowed_id_page_field()
763 );
764 }
765
766 /**
767 * Get allowed fields for pages.
768 *
769 * @return array
770 */
771 public function update_status_page_fields_allowed_fields() {
772 $status = array( 'unpublish', 'publish', 'approve', 'trash', 'delete', 'restore' );
773 return array_merge(
774 $this->allowed_id_domain_field(),
775 $this->allowed_id_page_field(),
776 array(
777 'status' => array(
778 'required' => true,
779 'type' => 'string',
780 'description' => __( 'New status.', 'mainwp' ),
781 'sanitize_callback' => $this->make_enum_sanitizer( $status, 'string' ),
782 'validate_callback' => $this->make_enum_validator( $status, 'string' ),
783 ),
784 )
785 );
786 }
787
788 /**
789 * Get allowed fields for editing a page.
790 *
791 * @return array
792 */
793 public function edit_page_fields_allowed_fields() {
794 $status = array( 'publish', 'pending', 'private', 'future', 'draft', 'trash' );
795 return array_merge(
796 $this->allowed_id_domain_field(),
797 $this->allowed_id_page_field(),
798 $this->allow_page_fields(),
799 array(
800 'post_title' => array(
801 'required' => false,
802 'type' => 'string',
803 'description' => $this->get_common_message( 'page_title' ),
804 'sanitize_callback' => 'sanitize_text_field',
805 ),
806 'post_content' => array(
807 'required' => false,
808 'type' => 'string',
809 'description' => $this->get_common_message( 'page_content' ),
810 'sanitize_callback' => 'wp_kses_post',
811 ),
812 'post_status' => array(
813 'required' => false,
814 'type' => 'string',
815 'description' => $this->get_common_message( 'page_status' ),
816 'sanitize_callback' => $this->make_enum_sanitizer( $status, 'string' ),
817 'validate_callback' => $this->make_enum_validator( $status, 'string' ),
818 ),
819 'post_name' => array(
820 'required' => false,
821 'type' => 'string',
822 'description' => $this->get_common_message( 'page_slug' ),
823 'sanitize_callback' => 'sanitize_title',
824 ),
825 )
826 );
827 }
828
829 /**
830 * Get allowed fields for pages.
831 *
832 * @return array
833 */
834 public function create_page_fields_allowed_fields() {
835 $status = array( 'publish', 'pending', 'private', 'future', 'draft', 'trash' );
836 return array_merge(
837 $this->allowed_id_domain_field(),
838 $this->allow_page_fields(),
839 array(
840 'post_title' => array(
841 'required' => true,
842 'type' => 'string',
843 'description' => $this->get_common_message( 'page_title' ),
844 'sanitize_callback' => 'sanitize_text_field',
845 ),
846 'post_content' => array(
847 'required' => true,
848 'type' => 'string',
849 'description' => $this->get_common_message( 'page_content' ),
850 'sanitize_callback' => 'wp_kses_post',
851 ),
852 'post_status' => array(
853 'required' => true,
854 'type' => 'string',
855 'description' => $this->get_common_message( 'page_status' ),
856 'sanitize_callback' => $this->make_enum_sanitizer( $status, 'string' ),
857 'validate_callback' => $this->make_enum_validator( $status, 'string' ),
858 ),
859 'post_name' => array(
860 'required' => true,
861 'type' => 'string',
862 'description' => $this->get_common_message( 'page_slug' ),
863 'sanitize_callback' => 'sanitize_title',
864 ),
865 )
866 );
867 }
868
869 /**
870 * Get allowed fields for pages.
871 *
872 * @return array
873 */
874 public function allowed_id_page_field() {
875 return array(
876 'id_page' => array(
877 'required' => true,
878 'description' => $this->get_common_message( 'page_id' ),
879 'type' => 'integer',
880 'sanitize_callback' => 'absint',
881 ),
882 );
883 }
884
885 /**
886 * Get allowed fields for pages.
887 *
888 * @return array
889 */
890 public function allowed_id_domain_field() {
891 return array(
892 'id_domain' => array(
893 'required' => true,
894 'description' => __( 'Site ID (number) or domain (string).', 'mainwp' ),
895 'type' => 'string',
896 'sanitize_callback' => 'sanitize_text_field',
897 ),
898 );
899 }
900
901 /**
902 * Validate filter params.
903 *
904 * @param WP_REST_Request $request Request object.
905 *
906 * @return bool|WP_Error
907 */
908 public function validate_filter_params( $request ) { // phpcs:ignore -- NOSONAR - complex.
909 // Customer, group, site authentication only one of the 3 is processed once.
910 $clients = $request->get_param( 'clients' );
911 $groups = $request->get_param( 'groups' );
912 $websites = $request->get_param( 'websites' );
913 $filled_count = count( array_filter( array( $clients, $groups, $websites ) ) );
914 if ( $filled_count > 1 ) {
915 return new WP_Error(
916 'invalid_filter_params',
917 __( 'Only one of clients, groups, or websites can be specified.', 'mainwp' ),
918 );
919 }
920
921 // Validate search and search_on.
922 $search = $request->get_param( 'search' );
923 $search_on = $request->get_param( 'search_on' );
924 $has_search = ! empty( $search );
925 $has_search_on = ! empty( $search_on );
926 if ( $has_search xor $has_search_on ) {
927 return new WP_Error(
928 'incomplete_search',
929 __( 'Both search and search_on must be provided together.', 'mainwp' ),
930 );
931 }
932
933 return true;
934 }
935
936 /**
937 * Validate date format.
938 *
939 * @param string $value Date format.
940 * @param WP_REST_Request $request Request object.
941 * @param string $param Parameter name.
942 *
943 * @return bool|WP_Error
944 */
945 public function validate_date_format( $value, $request, $param ) { // phpcs:ignore -- NOSONAR - complex.
946 if ( empty( $value ) ) {
947 return true;
948 }
949
950 $value = $this->sanitize_field( $value );
951
952 // Expect ISO date literal in YYYY-MM-DD format, e.g. YYYY-MM-DD.
953 if ( ! preg_match( '/^\d{4}-\d{2}-\d{2}$/', $value ) ) {
954 return new WP_Error(
955 'invalid_date_format',
956 __( 'Invalid date format. Use YYYY-MM-DD', 'mainwp' ),
957 );
958 }
959
960 // Validate actual calendar date.
961 list( $year, $month, $day ) = array_map( 'intval', explode( '-', $value ) );
962 if ( ! checkdate( $month, $day, $year ) ) {
963 return new WP_Error(
964 'invalid_date_value',
965 __( 'Invalid date value. Use a real date.', 'mainwp' ),
966 );
967 }
968
969 return true;
970 }
971
972 /**
973 * Validate datetime format.
974 *
975 * @param string $value Datetime value.
976 * @param WP_REST_Request $request Request object.
977 * @param string $param Parameter name.
978 *
979 * @return bool|WP_Error
980 */
981 public function validate_datetime_format( $value, $request, $param = '' ) { // phpcs:ignore -- NOSONAR - callback signature required by WordPress.
982 if ( empty( $value ) ) {
983 return true; // Allow empty values.
984 }
985
986 $value = $this->sanitize_field( $value );
987
988 // Expect datetime format: YYYY-MM-DD HH:MM:SS.
989 if ( ! preg_match( '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', $value ) ) {
990 return new WP_Error(
991 'invalid_datetime_format',
992 __( 'Invalid datetime format. Use YYYY-MM-DD HH:MM:SS (e.g., 2024-08-23 18:02:40)', 'mainwp' ),
993 );
994 }
995
996 // Validate actual calendar date and time.
997 list( $date_part, $time_part ) = explode( ' ', $value );
998 list( $year, $month, $day ) = array_map( 'intval', explode( '-', $date_part ) );
999 list( $hour, $minute, $second ) = array_map( 'intval', explode( ':', $time_part ) );
1000
1001 // Validate date.
1002 if ( ! checkdate( $month, $day, $year ) ) {
1003 return new WP_Error(
1004 'invalid_datetime_date',
1005 __( 'Invalid date value. Use a real date.', 'mainwp' ),
1006 );
1007 }
1008
1009 // Validate time.
1010 if ( $hour < 0 || $hour > 23 || $minute < 0 || $minute > 59 || $second < 0 || $second > 59 ) {
1011 return new WP_Error(
1012 'invalid_datetime_time',
1013 __( 'Invalid time value. Hour must be 0-23, minute and second must be 0-59.', 'mainwp' ),
1014 );
1015 }
1016
1017 return true;
1018 }
1019
1020 /**
1021 * Get the Tags schema, conforming to JSON Schema.
1022 *
1023 * @since 5.2
1024 * @return array
1025 */
1026 public function get_item_schema() { // phpcs:ignore -- NOSONAR - long schema.
1027 return array(
1028 '$schema' => 'http://json-schema.org/draft-04/schema#',
1029 'title' => 'api_keys',
1030 'type' => 'object',
1031 'properties' => array(
1032 'id' => array(
1033 'type' => 'integer',
1034 'description' => $this->get_common_message( 'page_id' ),
1035 'context' => array( 'view', 'simple_view', 'detail' ),
1036 'readonly' => true,
1037 ),
1038 'website_id' => array(
1039 'type' => 'integer',
1040 'description' => __( 'Website ID.', 'mainwp' ),
1041 'context' => array( 'view', 'detail' ),
1042 'readonly' => true,
1043 ),
1044 'post_type' => array(
1045 'type' => 'string',
1046 'description' => __( 'Page type.', 'mainwp' ),
1047 'context' => array( 'view', 'simple_view', 'detail' ),
1048 'readonly' => true,
1049 ),
1050 'status' => array(
1051 'type' => 'string',
1052 'description' => $this->get_common_message( 'page_status' ),
1053 'context' => array( 'view', 'simple_view' ),
1054 'readonly' => true,
1055 ),
1056 'title' => array(
1057 'type' => 'string',
1058 'description' => $this->get_common_message( 'page_title' ),
1059 'context' => array( 'view', 'simple_view' ),
1060 'readonly' => true,
1061 ),
1062 'comment_count' => array(
1063 'type' => 'integer',
1064 'description' => __( 'Number of comments.', 'mainwp' ),
1065 'context' => array( 'view', 'simple_view' ),
1066 'readonly' => true,
1067 ),
1068 'author' => array(
1069 'type' => 'string',
1070 'description' => __( 'Page author.', 'mainwp' ),
1071 'context' => array( 'view', 'simple_view' ),
1072 'readonly' => true,
1073 ),
1074 'dts' => array(
1075 'type' => 'string',
1076 'format' => 'date-time',
1077 'description' => __( 'Page date.', 'mainwp' ),
1078 'context' => array( 'view', 'simple_view' ),
1079 'readonly' => true,
1080 ),
1081 'authorEmail' => array(
1082 'type' => 'string',
1083 'description' => __( 'Page author email.', 'mainwp' ),
1084 'context' => array( 'simple_view', 'view' ),
1085 'readonly' => true,
1086 ),
1087 'post_custom' => array(
1088 'type' => 'object',
1089 'description' => __( 'Custom meta key/values.', 'mainwp' ),
1090 'context' => array( 'detail' ),
1091 'readonly' => true,
1092 ),
1093 'post_featured_image' => array(
1094 'type' => 'string',
1095 'description' => __( 'Featured image.', 'mainwp' ),
1096 'context' => array( 'detail' ),
1097 'readonly' => true,
1098 ),
1099 'post_content' => array(
1100 'type' => 'string',
1101 'description' => $this->get_common_message( 'page_content' ),
1102 'context' => array( 'detail' ),
1103 'readonly' => true,
1104 ),
1105 'post_status' => array(
1106 'type' => 'string',
1107 'description' => $this->get_common_message( 'page_status' ),
1108 'context' => array( 'detail' ),
1109 'readonly' => true,
1110 ),
1111 'post_name' => array(
1112 'type' => 'string',
1113 'description' => $this->get_common_message( 'page_slug' ),
1114 'context' => array( 'detail' ),
1115 'readonly' => true,
1116 ),
1117 'post_excerpt' => array(
1118 'type' => 'string',
1119 'description' => __( 'Page excerpt.', 'mainwp' ),
1120 'context' => array( 'detail' ),
1121 'readonly' => true,
1122 ),
1123 'post_password' => array(
1124 'type' => 'string',
1125 'description' => __( 'Page password.', 'mainwp' ),
1126 'context' => array( 'detail' ),
1127 'readonly' => true,
1128 ),
1129 'comment_status' => array(
1130 'type' => 'string',
1131 'description' => __( 'Comment status.', 'mainwp' ),
1132 'context' => array( 'detail' ),
1133 'readonly' => true,
1134 ),
1135 'ping_status' => array(
1136 'type' => 'string',
1137 'description' => __( 'Ping status.', 'mainwp' ),
1138 'context' => array( 'detail' ),
1139 'readonly' => true,
1140 ),
1141 'post_date' => array(
1142 'type' => 'string',
1143 'description' => __( 'Page date.', 'mainwp' ),
1144 'context' => array( 'detail' ),
1145 'readonly' => true,
1146 ),
1147 'post_date_gmt' => array(
1148 'type' => 'string',
1149 'description' => __( 'Page date GMT.', 'mainwp' ),
1150 'context' => array( 'detail' ),
1151 'readonly' => true,
1152 ),
1153 'upload_dir' => array(
1154 'type' => 'string',
1155 'description' => __( 'Upload directory.', 'mainwp' ),
1156 'context' => array( 'detail' ),
1157 'readonly' => true,
1158 ),
1159 ),
1160 );
1161 }
1162
1163 /**
1164 * Get allowed page fields.
1165 *
1166 * @return array
1167 */
1168 private function allow_page_fields() {
1169 $ping_status = array( 'open', 'closed' );
1170 $comment_status = array( 'open', 'closed' );
1171 $sticky = array( 1, 0 );
1172 return array(
1173 'post_excerpt' => array(
1174 'required' => false,
1175 'type' => 'string',
1176 'description' => __( 'Page excerpt.', 'mainwp' ),
1177 'sanitize_callback' => 'sanitize_text_field',
1178 ),
1179 'post_password' => array(
1180 'required' => false,
1181 'type' => 'string',
1182 'description' => __( 'Page password.', 'mainwp' ),
1183 'sanitize_callback' => 'sanitize_text_field',
1184 ),
1185 'comment_status' => array(
1186 'required' => false,
1187 'type' => 'string',
1188 'description' => __( 'Comment status.', 'mainwp' ),
1189 'sanitize_callback' => $this->make_enum_sanitizer( $comment_status, 'string' ),
1190 'validate_callback' => $this->make_enum_validator( $comment_status, 'string' ),
1191 ),
1192 'ping_status' => array(
1193 'required' => false,
1194 'type' => 'string',
1195 'description' => __( 'Ping status.', 'mainwp' ),
1196 'sanitize_callback' => $this->make_enum_sanitizer( $ping_status, 'string' ),
1197 'validate_callback' => $this->make_enum_validator( $ping_status, 'string' ),
1198 ),
1199 'post_date' => array(
1200 'required' => false,
1201 'type' => 'string',
1202 'description' => __( 'Page date (YYYY-MM-DD HH:MM:SS).', 'mainwp' ),
1203 'sanitize_callback' => 'sanitize_text_field',
1204 'validate_callback' => array( $this, 'validate_datetime_format' ),
1205 ),
1206 'post_date_gmt' => array(
1207 'required' => false,
1208 'type' => 'string',
1209 'description' => __( 'Page date GMT (YYYY-MM-DD HH:MM:SS).', 'mainwp' ),
1210 'sanitize_callback' => 'sanitize_text_field',
1211 'validate_callback' => array( $this, 'validate_datetime_format' ),
1212 ),
1213 'is_sticky' => array(
1214 'required' => false,
1215 'type' => 'integer',
1216 'description' => __( 'Sticky status.', 'mainwp' ),
1217 'sanitize_callback' => $this->make_enum_sanitizer( $sticky, 'integer' ),
1218 'validate_callback' => $this->make_enum_validator( $sticky, 'integer' ),
1219 ),
1220 'post_custom' => array(
1221 'required' => false,
1222 'type' => 'object',
1223 'description' => __( 'Custom meta key/values.', 'mainwp' ),
1224 'sanitize_callback' => function ( $value ) {
1225 return is_array( $value ) ? $value : array();
1226 },
1227 ),
1228 'post_featured_image' => array(
1229 'required' => false,
1230 'type' => 'string',
1231 'description' => __( 'Featured image URL or data.', 'mainwp' ),
1232 'sanitize_callback' => 'sanitize_text_field',
1233 ),
1234 'featured_image_data' => array(
1235 'required' => false,
1236 'type' => 'object',
1237 'description' => __( 'Featured image extra data.', 'mainwp' ),
1238 'sanitize_callback' => function ( $value ) {
1239 return is_array( $value ) ? $value : null;
1240 },
1241 ),
1242 );
1243 }
1244
1245 /**
1246 * Get monitor by.
1247 *
1248 * @param WP_REST_Request $request Full details about the request.
1249 *
1250 * @uses MainWP_DB_Uptime_Monitoring::instance()->get_monitor_by()
1251 *
1252 * @return WP_Error|Object Item.
1253 */
1254 private function get_request_item( $request ) { // phpcs:ignore -- NOSONAR - complex.
1255 // Get id or domain raw value.
1256 $raw = (string) $request->get_param( 'id_domain' );
1257 $raw = trim( $raw );
1258
1259 if ( empty( $raw ) ) {
1260 return false;
1261 }
1262
1263 // Get monitor by monitor id.
1264 if ( ctype_digit( $raw ) ) {
1265 $website_id = (int) $raw;
1266 return $this->get_site_by( 'id', $website_id );
1267 }
1268
1269 // Get monitor by domain.
1270 $domain = strtolower( rtrim( rawurldecode( $raw ), '/' ) );
1271 $website = $this->get_site_by( 'domain', $domain );
1272 if ( empty( $website ) ) {
1273 return false;
1274 }
1275
1276 return $website;
1277 }
1278
1279 /**
1280 * Get websites for users query.
1281 *
1282 * @param array $args Query arguments.
1283 * @return array|WP_Error Array with db_websites and website_url or WP_Error on failure.
1284 */
1285 private function get_websites_for_pages_query( $args ) {
1286 $clients = $args['clients'] ?? '';
1287 $groups = $args['groups'] ?? '';
1288 $sites = $args['websites'] ?? '';
1289
1290 $filter_db_websites = $this->get_db_websites_by_filter( $sites, $groups, $clients );
1291
1292 if ( empty( $filter_db_websites ) || ! is_array( $filter_db_websites ) ) {
1293 return new WP_Error(
1294 'no_website_found',
1295 __( 'No website found.', 'mainwp' ) // NOSONAR.
1296 );
1297 }
1298
1299 $db_websites = $filter_db_websites['db_websites'] ?? array();
1300 $website_url = $filter_db_websites['website_url'] ?? array();
1301
1302 if ( empty( $db_websites ) ) {
1303 return new WP_Error(
1304 'no_website_found',
1305 __( 'No website found.', 'mainwp' ) // NOSONAR.
1306 );
1307 }
1308
1309 return array(
1310 'db_websites' => $db_websites,
1311 'website_url' => $website_url,
1312 );
1313 }
1314
1315 /**
1316 * Get common message.
1317 *
1318 * @param string $key Message key.
1319 *
1320 * @return string
1321 */
1322 protected function get_common_message( $key ) {
1323 $words_map = array(
1324 'page_id' => __( 'Page ID.', 'mainwp' ),
1325 'page_status' => __( 'Page status.', 'mainwp' ),
1326 'page_title' => __( 'Page title.', 'mainwp' ),
1327 'page_content' => __( 'Page content.', 'mainwp' ),
1328 'page_slug' => __( 'Page slug.', 'mainwp' ),
1329 'website_not_found' => __( 'Website not found.', 'mainwp' ),
1330 );
1331
1332 return isset( $words_map[ $key ] ) ? $words_map[ $key ] : '';
1333 }
1334
1335 /**
1336 * Map search on.
1337 *
1338 * @param string $search_on Search on.
1339 *
1340 * @return string
1341 */
1342 protected function map_search_on( $search_on ) {
1343 $search = '';
1344 if ( empty( $search_on ) ) {
1345 return $search;
1346 }
1347
1348 // Map search_on values.
1349 if ( 'body' === $search_on ) {
1350 $search = 'content';
1351 } elseif ( 'Title and Body' === $search_on ) {
1352 $search = 'all';
1353 } elseif ( 'title' === $search_on ) {
1354 $search = $search_on;
1355 }
1356 return $search;
1357 }
1358 }
1359