PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / 6.2.1
MainWP Dashboard: Self-hosted WordPress Management for Agencies v6.2.1
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-posts-controller.php

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

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