ProductReviews::get_items()   F
last analyzed

Complexity

Conditions 15
Paths 2592

Size

Total Lines 113
Code Lines 53

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 15
eloc 53
nc 2592
nop 1
dl 0
loc 113
rs 1.7499
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * REST API Product Reviews Controller
4
 *
5
 * Handles requests to /products/<product_id>/reviews.
6
 *
7
 * @package Automattic/WooCommerce/RestApi
8
 */
9
10
namespace Automattic\WooCommerce\RestApi\Controllers\Version4;
11
12
defined( 'ABSPATH' ) || exit;
13
14
use Automattic\WooCommerce\RestApi\Controllers\Version4\Responses\ProductReviewResponse;
15
use Automattic\WooCommerce\RestApi\Controllers\Version4\Utilities\Permissions;
16
use Automattic\WooCommerce\RestApi\Controllers\Version4\Utilities\Pagination;
17
18
/**
19
 * REST API Product Reviews controller class.
20
 */
21
class ProductReviews extends AbstractController {
22
23
	/**
24
	 * Route base.
25
	 *
26
	 * @var string
27
	 */
28
	protected $rest_base = 'products/reviews';
29
30
	/**
31
	 * Permission to check.
32
	 *
33
	 * @var string
34
	 */
35
	protected $resource_type = 'product_reviews';
36
37
	/**
38
	 * Register the routes for product reviews.
39
	 */
40
	public function register_routes() {
41
		register_rest_route(
42
			$this->namespace,
43
			'/' . $this->rest_base,
44
			array(
45
				array(
46
					'methods'             => \WP_REST_Server::READABLE,
47
					'callback'            => array( $this, 'get_items' ),
48
					'permission_callback' => array( $this, 'get_items_permissions_check' ),
49
					'args'                => $this->get_collection_params(),
50
				),
51
				array(
52
					'methods'             => \WP_REST_Server::CREATABLE,
53
					'callback'            => array( $this, 'create_item' ),
54
					'permission_callback' => array( $this, 'create_item_permissions_check' ),
55
					'args'                => array_merge(
56
						$this->get_endpoint_args_for_item_schema( \WP_REST_Server::CREATABLE ),
57
						array(
58
							'product_id'     => array(
59
								'required'    => true,
60
								'description' => __( 'Unique identifier for the product.', 'woocommerce-rest-api' ),
61
								'type'        => 'integer',
62
							),
63
							'review'         => array(
64
								'required'    => true,
65
								'type'        => 'string',
66
								'description' => __( 'Review content.', 'woocommerce-rest-api' ),
67
							),
68
							'reviewer'       => array(
69
								'required'    => true,
70
								'type'        => 'string',
71
								'description' => __( 'Name of the reviewer.', 'woocommerce-rest-api' ),
72
							),
73
							'reviewer_email' => array(
74
								'required'    => true,
75
								'type'        => 'string',
76
								'description' => __( 'Email of the reviewer.', 'woocommerce-rest-api' ),
77
							),
78
						)
79
					),
80
				),
81
				'schema' => array( $this, 'get_public_item_schema' ),
82
			),
83
			true
84
		);
85
86
		register_rest_route(
87
			$this->namespace,
88
			'/' . $this->rest_base . '/(?P<id>[\d]+)',
89
			array(
90
				'args'   => array(
91
					'id' => array(
92
						'description' => __( 'Unique identifier for the resource.', 'woocommerce-rest-api' ),
93
						'type'        => 'integer',
94
					),
95
				),
96
				array(
97
					'methods'             => \WP_REST_Server::READABLE,
98
					'callback'            => array( $this, 'get_item' ),
99
					'permission_callback' => array( $this, 'get_item_permissions_check' ),
100
					'args'                => array(
101
						'context' => $this->get_context_param( array( 'default' => 'view' ) ),
102
					),
103
				),
104
				array(
105
					'methods'             => \WP_REST_Server::EDITABLE,
106
					'callback'            => array( $this, 'update_item' ),
107
					'permission_callback' => array( $this, 'update_item_permissions_check' ),
108
					'args'                => $this->get_endpoint_args_for_item_schema( \WP_REST_Server::EDITABLE ),
109
				),
110
				array(
111
					'methods'             => \WP_REST_Server::DELETABLE,
112
					'callback'            => array( $this, 'delete_item' ),
113
					'permission_callback' => array( $this, 'delete_item_permissions_check' ),
114
					'args'                => array(
115
						'force' => array(
116
							'default'     => false,
117
							'type'        => 'boolean',
118
							'description' => __( 'Whether to bypass trash and force deletion.', 'woocommerce-rest-api' ),
119
						),
120
					),
121
				),
122
				'schema' => array( $this, 'get_public_item_schema' ),
123
			),
124
			true
125
		);
126
127
		$this->register_batch_route();
128
	}
129
130
	/**
131
	 * Get all reviews.
132
	 *
133
	 * @param \WP_REST_Request $request Full details about the request.
134
	 * @return array|\WP_Error
135
	 */
136
	public function get_items( $request ) {
137
		// Retrieve the list of registered collection query parameters.
138
		$registered = $this->get_collection_params();
139
140
		/*
141
		 * This array defines mappings between public API query parameters whose
142
		 * values are accepted as-passed, and their internal \WP_Query parameter
143
		 * name equivalents (some are the same). Only values which are also
144
		 * present in $registered will be set.
145
		 */
146
		$parameter_mappings = array(
147
			'reviewer'         => 'author__in',
148
			'reviewer_email'   => 'author_email',
149
			'reviewer_exclude' => 'author__not_in',
150
			'exclude'          => 'comment__not_in',
151
			'include'          => 'comment__in',
152
			'offset'           => 'offset',
153
			'order'            => 'order',
154
			'per_page'         => 'number',
155
			'product'          => 'post__in',
156
			'search'           => 'search',
157
			'status'           => 'status',
158
		);
159
160
		$prepared_args = array();
161
162
		/*
163
		 * For each known parameter which is both registered and present in the request,
164
		 * set the parameter's value on the query $prepared_args.
165
		 */
166
		foreach ( $parameter_mappings as $api_param => $wp_param ) {
167
			if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) {
168
				$prepared_args[ $wp_param ] = $request[ $api_param ];
169
			}
170
		}
171
172
		// Ensure certain parameter values default to empty strings.
173
		foreach ( array( 'author_email', 'search' ) as $param ) {
174
			if ( ! isset( $prepared_args[ $param ] ) ) {
175
				$prepared_args[ $param ] = '';
176
			}
177
		}
178
179
		if ( isset( $registered['orderby'] ) ) {
180
			$prepared_args['orderby'] = $this->normalize_query_param( $request['orderby'] );
181
		}
182
183
		if ( isset( $prepared_args['status'] ) ) {
184
			$prepared_args['status'] = 'approved' === $prepared_args['status'] ? 'approve' : $prepared_args['status'];
185
		}
186
187
		$prepared_args['no_found_rows'] = false;
188
		$prepared_args['date_query']    = array();
189
190
		// Set before into date query. Date query must be specified as an array of an array.
191
		if ( isset( $registered['before'], $request['before'] ) ) {
192
			$prepared_args['date_query'][0]['before'] = $request['before'];
193
		}
194
195
		// Set after into date query. Date query must be specified as an array of an array.
196
		if ( isset( $registered['after'], $request['after'] ) ) {
197
			$prepared_args['date_query'][0]['after'] = $request['after'];
198
		}
199
200
		if ( isset( $registered['page'] ) && empty( $request['offset'] ) ) {
201
			$prepared_args['offset'] = $prepared_args['number'] * ( absint( $request['page'] ) - 1 );
202
		}
203
204
		/**
205
		 * Filters arguments, before passing to \WP_Comment_Query, when querying reviews via the REST API.
206
		 *
207
		 * @since 3.5.0
208
		 * @link https://developer.wordpress.org/reference/classes/\WP_Comment_Query/
209
		 * @param array           $prepared_args Array of arguments for \WP_Comment_Query.
210
		 * @param \WP_REST_Request $request       The current request.
211
		 */
212
		$prepared_args = apply_filters( 'woocommerce_rest_product_review_query', $prepared_args, $request );
213
214
		// Make sure that returns only reviews.
215
		$prepared_args['type'] = 'review';
216
217
		// Query reviews.
218
		$query        = new \WP_Comment_Query();
219
		$query_result = $query->query( $prepared_args );
220
		$reviews      = array();
221
222
		foreach ( $query_result as $review ) {
223
			if ( ! Permissions::user_can_read( 'product_review', $review->comment_ID ) ) {
224
				continue;
225
			}
226
227
			$data      = $this->prepare_item_for_response( $review, $request );
228
			$reviews[] = $this->prepare_response_for_collection( $data );
229
		}
230
231
		$total_reviews = (int) $query->found_comments;
232
		$max_pages     = (int) $query->max_num_pages;
233
234
		if ( $total_reviews < 1 ) {
235
			// Out-of-bounds, run the query again without LIMIT for total count.
236
			unset( $prepared_args['number'], $prepared_args['offset'] );
237
238
			$query                  = new \WP_Comment_Query();
239
			$prepared_args['count'] = true;
240
241
			$total_reviews = $query->query( $prepared_args );
242
			$max_pages     = ceil( $total_reviews / $request['per_page'] );
243
		}
244
245
		$response = rest_ensure_response( $reviews );
246
		$response = Pagination::add_pagination_headers( $response, $request, $total_reviews, $max_pages );
0 ignored issues
show
Bug introduced by
It seems like $max_pages can also be of type double; however, parameter $total_pages of Automattic\WooCommerce\R...dd_pagination_headers() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

246
		$response = Pagination::add_pagination_headers( $response, $request, $total_reviews, /** @scrutinizer ignore-type */ $max_pages );
Loading history...
Bug introduced by
It seems like $total_reviews can also be of type array and array and array and array<mixed,WP_Comment|array|null>; however, parameter $total_items of Automattic\WooCommerce\R...dd_pagination_headers() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

246
		$response = Pagination::add_pagination_headers( $response, $request, /** @scrutinizer ignore-type */ $total_reviews, $max_pages );
Loading history...
247
248
		return $response;
249
	}
250
251
	/**
252
	 * Create a single review.
253
	 *
254
	 * @param \WP_REST_Request $request Full details about the request.
255
	 * @return \WP_Error|\WP_REST_Response
256
	 */
257
	public function create_item( $request ) {
258
		try {
259
			if ( ! empty( $request['id'] ) ) {
260
				return new \WP_Error( 'woocommerce_rest_review_exists', __( 'Cannot create existing product review.', 'woocommerce-rest-api' ), array( 'status' => 400 ) );
261
			}
262
263
			$prepared_review = wp_parse_args(
264
				$this->prepare_item_for_database( $request ),
265
				array(
266
					'comment_post_ID'    => 0,
267
					'comment_parent'     => 0,
268
					'comment_author_url' => '',
269
					'comment_date_gmt'   => current_time( 'mysql', true ),
270
					'comment_author_IP'  => $this->get_comment_author_ip(),
271
					'comment_agent'      => $this->get_comment_agent( $request ),
272
				)
273
			);
274
275
			/**
276
			 * Filters a review after it is prepared for the database.
277
			 *
278
			 * Allows modification of the review right after it is prepared for the database.
279
			 *
280
			 * @since 3.5.0
281
			 * @param array           $prepared_review The prepared review data for `wp_insert_comment`.
282
			 * @param \WP_REST_Request $request         The current request.
283
			 */
284
			$prepared_review = apply_filters( 'woocommerce_rest_preprocess_product_review', $prepared_review, $request );
285
286
			if ( is_wp_error( $prepared_review ) ) {
287
				return $prepared_review;
288
			}
289
290
			$prepared_review = $this->validate_review( $prepared_review, true );
291
292
			/**
293
			 * Filters a review before it is inserted via the REST API.
294
			 *
295
			 * Allows modification of the review right before it is inserted via wp_insert_comment().
296
			 * Returning a \WP_Error value from the filter will shortcircuit insertion and allow
297
			 * skipping further processing.
298
			 *
299
			 * @since 3.5.0
300
			 * @param array|\WP_Error  $prepared_review The prepared review data for wp_insert_comment().
301
			 * @param \WP_REST_Request $request          Request used to insert the review.
302
			 */
303
			$prepared_review = apply_filters( 'woocommerce_rest_pre_insert_product_review', $prepared_review, $request );
304
305
			if ( is_wp_error( $prepared_review ) ) {
306
				return $prepared_review;
307
			}
308
309
			$review_id = wp_insert_comment( wp_filter_comment( wp_slash( $prepared_review ) ) );
0 ignored issues
show
Bug introduced by
It seems like wp_slash($prepared_review) can also be of type string; however, parameter $commentdata of wp_filter_comment() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

309
			$review_id = wp_insert_comment( wp_filter_comment( /** @scrutinizer ignore-type */ wp_slash( $prepared_review ) ) );
Loading history...
310
311
			if ( ! $review_id ) {
0 ignored issues
show
introduced by
The condition $review_id is always false.
Loading history...
312
				throw new \WC_REST_Exception( 'woocommerce_rest_review_failed_create', __( 'Creating product review failed.', 'woocommerce-rest-api' ), 500 );
313
			}
314
315
			if ( isset( $request['status'] ) ) {
316
				$this->handle_status_param( $request['status'], $review_id );
317
			}
318
319
			update_comment_meta( $review_id, 'rating', ! empty( $request['rating'] ) ? $request['rating'] : '0' );
320
		} catch ( \WC_REST_Exception $e ) {
321
			return new \WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
322
		}
323
324
		$review = get_comment( $review_id );
325
326
		/**
327
		 * Fires after a comment is created or updated via the REST API.
328
		 *
329
		 * @param \WP_Comment      $review   Inserted or updated comment object.
330
		 * @param \WP_REST_Request $request  Request object.
331
		 * @param bool            $creating True when creating a comment, false when updating.
332
		 */
333
		do_action( 'woocommerce_rest_insert_product_review', $review, $request, true );
334
335
		$fields_update = $this->update_additional_fields_for_object( $review, $request );
336
337
		if ( is_wp_error( $fields_update ) ) {
338
			return $fields_update;
339
		}
340
341
		$context = current_user_can( 'moderate_comments' ) ? 'edit' : 'view';
342
		$request->set_param( 'context', $context );
343
344
		$response = $this->prepare_item_for_response( $review, $request );
345
346
		$response->set_status( 201 );
347
		$response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $review_id ) ) );
348
349
		return $response;
350
	}
351
352
	/**
353
	 * Validate a review and throw an error if invalid.
354
	 *
355
	 * @throws \WC_REST_Exception Exception when a comment is not approved.
356
	 * @param  array $prepared_review Review content.
357
	 * @param  bool  $creating True when creating a review.
358
	 * @return array
359
	 */
360
	protected function validate_review( $prepared_review, $creating = false ) {
361
		if ( empty( $prepared_review['comment_content'] ) ) {
362
			throw new \WC_REST_Exception( 'woocommerce_rest_review_content_invalid', __( 'Invalid review content.', 'woocommerce-rest-api' ), 400 );
363
		}
364
365
		if ( ! empty( $prepared_review['comment_post_ID'] ) ) {
366
			if ( 'product' !== get_post_type( $prepared_review['comment_post_ID'] ) ) {
367
				throw new \WC_REST_Exception( 'woocommerce_rest_product_invalid_id', __( 'Invalid product ID.', 'woocommerce-rest-api' ), 404 );
368
			}
369
		}
370
371
		$check_comment_lengths = wp_check_comment_data_max_lengths( $prepared_review );
372
373
		if ( is_wp_error( $check_comment_lengths ) ) {
374
			$error_code = str_replace( array( 'comment_author', 'comment_content' ), array( 'reviewer', 'review_content' ), $check_comment_lengths->get_error_code() );
375
			throw new \WC_REST_Exception( 'woocommerce_rest_' . $error_code, __( 'Product review field exceeds maximum length allowed.', 'woocommerce-rest-api' ), 400 );
376
		}
377
378
		if ( $creating ) {
379
			$prepared_review['comment_approved'] = $this->get_comment_approved( $prepared_review );
380
		}
381
382
		return $prepared_review;
383
	}
384
385
	/**
386
	 * Get comment user agent.
387
	 *
388
	 * @return string
389
	 */
390
	protected function get_comment_author_ip() {
391
		if ( ! empty( $_SERVER['REMOTE_ADDR'] ) && rest_is_ip_address( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) ) { // WPCS: input var ok, sanitization ok.
0 ignored issues
show
Bug introduced by
It seems like wp_unslash($_SERVER['REMOTE_ADDR']) can also be of type array; however, parameter $ip of rest_is_ip_address() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

391
		if ( ! empty( $_SERVER['REMOTE_ADDR'] ) && rest_is_ip_address( /** @scrutinizer ignore-type */ wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) ) { // WPCS: input var ok, sanitization ok.
Loading history...
392
			return wc_clean( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ); // WPCS: input var ok.
393
		} else {
394
			return '127.0.0.1';
395
		}
396
	}
397
398
	/**
399
	 * Get comment user agent from request.
400
	 *
401
	 * @param \WP_REST_Request $request Full details about the request.
402
	 * @return string
403
	 */
404
	protected function get_comment_agent( $request ) {
405
		if ( ! empty( $request['author_user_agent'] ) ) {
406
			return $request['author_user_agent'];
407
		} elseif ( $request->get_header( 'user_agent' ) ) {
408
			return $request->get_header( 'user_agent' );
409
		} else {
410
			return '';
411
		}
412
	}
413
414
	/**
415
	 * Attempt to approve an unsaved comment or throw an error.
416
	 *
417
	 * @throws \WC_REST_Exception Exception when a comment is not approved.
418
	 * @param  array $prepared_review Review content.
419
	 * @return int|string
420
	 */
421
	protected function get_comment_approved( $prepared_review ) {
422
		$comment_approved = wp_allow_comment( $prepared_review, true );
423
424
		if ( is_wp_error( $comment_approved ) ) {
425
			$error_code    = $comment_approved->get_error_code();
426
			$error_message = $comment_approved->get_error_message();
427
428
			if ( 'comment_duplicate' === $error_code ) {
429
				throw new \WC_REST_Exception( 'woocommerce_rest_' . $error_code, $error_message, 409 );
430
			}
431
432
			if ( 'comment_flood' === $error_code ) {
433
				throw new \WC_REST_Exception( 'woocommerce_rest_' . $error_code, $error_message, 400 );
434
			}
435
		}
436
437
		return $comment_approved;
438
	}
439
440
	/**
441
	 * Get a single product review.
442
	 *
443
	 * @param \WP_REST_Request $request Full details about the request.
444
	 * @return \WP_Error|\WP_REST_Response
445
	 */
446
	public function get_item( $request ) {
447
		$review = $this->get_review( $request['id'] );
448
		if ( is_wp_error( $review ) ) {
449
			return $review;
450
		}
451
452
		return $this->prepare_item_for_response( $review, $request );
453
	}
454
455
	/**
456
	 * Updates a review.
457
	 *
458
	 * @param \WP_REST_Request $request Full details about the request.
459
	 * @return \WP_Error|\WP_REST_Response Response object on success, or error object on failure.
460
	 */
461
	public function update_item( $request ) {
462
		try {
463
			$review = $this->get_review( $request['id'] );
464
465
			if ( is_wp_error( $review ) ) {
466
				return $review;
467
			}
468
469
			$review_id = (int) $review->comment_ID;
0 ignored issues
show
Bug introduced by
The property comment_ID does not seem to exist on WP_Error.
Loading history...
470
471
			if ( isset( $request['type'] ) && 'review' !== get_comment_type( $review_id ) ) {
472
				return new \WP_Error( 'woocommerce_rest_review_invalid_type', __( 'Sorry, you are not allowed to change the comment type.', 'woocommerce-rest-api' ), array( 'status' => 404 ) );
473
			}
474
475
			$prepared_review = $this->prepare_item_for_database( $request );
476
477
			/**
478
			 * Filters a review after it is prepared for the database.
479
			 *
480
			 * Allows modification of the review right after it is prepared for the database.
481
			 *
482
			 * @since 3.5.0
483
			 * @param array           $prepared_review The prepared review data for `wp_insert_comment`.
484
			 * @param \WP_REST_Request $request         The current request.
485
			 */
486
			$prepared_review = apply_filters( 'woocommerce_rest_preprocess_product_review', $prepared_review, $request );
487
488
			if ( is_wp_error( $prepared_review ) ) {
489
				return $prepared_review;
490
			}
491
492
			$prepared_review = $this->validate_review( $prepared_review );
493
494
			wp_update_comment( wp_slash( $prepared_review ) );
495
496
			if ( isset( $request['status'] ) ) {
497
				$this->handle_status_param( $request['status'], $review_id );
498
			}
499
500
			if ( ! empty( $request['rating'] ) ) {
501
				update_comment_meta( $review_id, 'rating', $request['rating'] );
502
			}
503
		} catch ( \WC_REST_Exception $e ) {
504
			return new \WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
505
		}
506
507
		$review = get_comment( $review_id );
508
509
		/**
510
		 * Fires after a comment is created or updated via the REST API.
511
		 *
512
		 * @param \WP_Comment      $review   Inserted or updated comment object.
513
		 * @param \WP_REST_Request $request  Request object.
514
		 * @param bool            $creating True when creating a comment, false when updating.
515
		 */
516
		do_action( 'woocommerce_rest_insert_product_review', $review, $request, false );
517
518
		$fields_update = $this->update_additional_fields_for_object( $review, $request );
0 ignored issues
show
Bug introduced by
It seems like $review can also be of type WP_Comment; however, parameter $object of WP_REST_Controller::upda...nal_fields_for_object() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

518
		$fields_update = $this->update_additional_fields_for_object( /** @scrutinizer ignore-type */ $review, $request );
Loading history...
519
520
		if ( is_wp_error( $fields_update ) ) {
521
			return $fields_update;
522
		}
523
524
		$request->set_param( 'context', 'edit' );
525
526
		return $this->prepare_item_for_response( $review, $request );
527
	}
528
529
	/**
530
	 * Deletes a review.
531
	 *
532
	 * @param \WP_REST_Request $request Full details about the request.
533
	 * @return \WP_Error|\WP_REST_Response Response object on success, or error object on failure.
534
	 */
535
	public function delete_item( $request ) {
536
		$review = $this->get_review( $request['id'] );
537
		if ( is_wp_error( $review ) ) {
538
			return $review;
539
		}
540
541
		$force = isset( $request['force'] ) ? (bool) $request['force'] : false;
542
543
		/**
544
		 * Filters whether a review can be trashed.
545
		 *
546
		 * Return false to disable trash support for the post.
547
		 *
548
		 * @since 3.5.0
549
		 * @param bool       $supports_trash Whether the post type support trashing.
550
		 * @param WP_Comment $review         The review object being considered for trashing support.
551
		 */
552
		$supports_trash = apply_filters( 'woocommerce_rest_product_review_trashable', ( EMPTY_TRASH_DAYS > 0 ), $review );
553
554
		$request->set_param( 'context', 'edit' );
555
556
		if ( $force ) {
557
			$previous = $this->prepare_item_for_response( $review, $request );
558
			$result   = wp_delete_comment( $review->comment_ID, true );
0 ignored issues
show
Bug introduced by
The property comment_ID does not seem to exist on WP_Error.
Loading history...
559
			$response = new \WP_REST_Response();
560
			$response->set_data(
561
				array(
562
					'deleted'  => true,
563
					'previous' => $previous->get_data(),
564
				)
565
			);
566
		} else {
567
			// If this type doesn't support trashing, error out.
568
			if ( ! $supports_trash ) {
569
				/* translators: %s: force=true */
570
				return new \WP_Error( 'woocommerce_rest_trash_not_supported', sprintf( __( "The object does not support trashing. Set '%s' to delete.", 'woocommerce-rest-api' ), 'force=true' ), array( 'status' => 501 ) );
571
			}
572
573
			if ( 'trash' === $review->comment_approved ) {
0 ignored issues
show
Bug introduced by
The property comment_approved does not seem to exist on WP_Error.
Loading history...
574
				return new \WP_Error( 'woocommerce_rest_already_trashed', __( 'The object has already been trashed.', 'woocommerce-rest-api' ), array( 'status' => 410 ) );
575
			}
576
577
			$result   = wp_trash_comment( $review->comment_ID );
578
			$review   = get_comment( $review->comment_ID );
579
			$response = $this->prepare_item_for_response( $review, $request );
580
		}
581
582
		if ( ! $result ) {
583
			return new \WP_Error( 'woocommerce_rest_cannot_delete', __( 'The object cannot be deleted.', 'woocommerce-rest-api' ), array( 'status' => 500 ) );
584
		}
585
586
		/**
587
		 * Fires after a review is deleted via the REST API.
588
		 *
589
		 * @param WP_Comment       $review   The deleted review data.
590
		 * @param \WP_REST_Response $response The response returned from the API.
591
		 * @param \WP_REST_Request  $request  The request sent to the API.
592
		 */
593
		do_action( 'woocommerce_rest_delete_review', $review, $response, $request );
594
595
		return $response;
596
	}
597
598
	/**
599
	 * Get data for this object in the format of this endpoint's schema.
600
	 *
601
	 * @param \WP_Comment      $object Object to prepare.
602
	 * @param \WP_REST_Request $request Request object.
603
	 * @return array Array of data in the correct format.
604
	 */
605
	protected function get_data_for_response( $object, $request ) {
606
		$formatter = new ProductReviewResponse();
607
608
		return $formatter->prepare_response( $object, $this->get_request_context( $request ) );
609
	}
610
611
	/**
612
	 * Prepare a single product review to be inserted into the database.
613
	 *
614
	 * @param  \WP_REST_Request $request Request object.
615
	 * @return array  $prepared_review
616
	 */
617
	protected function prepare_item_for_database( $request ) {
618
		$mappings = [
619
			'comment_ID'           => 'id',
620
			'comment_content'      => 'review',
621
			'comment_post_ID'      => 'product_id',
622
			'comment_author'       => 'reviewer',
623
			'comment_author_email' => 'reviewer_email',
624
		];
625
626
		$prepared_review = [
627
			'comment_type' => 'review',
628
		];
629
630
		foreach ( $mappings as $key => $value ) {
631
			if ( ! isset( $request[ $value ] ) ) {
632
				continue;
633
			}
634
			$prepared_review[ $key ] = $request[ $value ];
635
		}
636
637
		if ( ! empty( $request['date_created'] ) ) {
638
			$date_data = rest_get_date_with_gmt( $request['date_created'] );
639
640
			if ( ! empty( $date_data ) ) {
641
				list( $prepared_review['comment_date'], $prepared_review['comment_date_gmt'] ) = $date_data;
642
			}
643
		} elseif ( ! empty( $request['date_created_gmt'] ) ) {
644
			$date_data = rest_get_date_with_gmt( $request['date_created_gmt'], true );
645
646
			if ( ! empty( $date_data ) ) {
647
				list( $prepared_review['comment_date'], $prepared_review['comment_date_gmt'] ) = $date_data;
648
			}
649
		}
650
651
		return $prepared_review;
652
	}
653
654
	/**
655
	 * Prepare links for the request.
656
	 *
657
	 * @param mixed            $item Object to prepare.
658
	 * @param \WP_REST_Request $request Request object.
659
	 * @return array
660
	 */
661
	protected function prepare_links( $item, $request ) {
662
		$links = array(
663
			'self'       => array(
664
				'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $item->comment_ID ) ),
665
			),
666
			'collection' => array(
667
				'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ),
668
			),
669
		);
670
		if ( 0 !== (int) $item->comment_post_ID ) {
671
			$links['up'] = array(
672
				'href'       => rest_url( sprintf( '/%s/products/%d', $this->namespace, $item->comment_post_ID ) ),
673
				'embeddable' => true,
674
			);
675
		}
676
		if ( 0 !== (int) $item->user_id ) {
677
			$links['reviewer'] = array(
678
				'href'       => rest_url( 'wp/v2/users/' . $item->user_id ),
679
				'embeddable' => true,
680
			);
681
		}
682
		return $links;
683
	}
684
685
	/**
686
	 * Get the Product Review's schema, conforming to JSON Schema.
687
	 *
688
	 * @return array
689
	 */
690
	public function get_item_schema() {
691
		$schema = array(
692
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
693
			'title'      => 'product_review',
694
			'type'       => 'object',
695
			'properties' => array(
696
				'id'               => array(
697
					'description' => __( 'Unique identifier for the resource.', 'woocommerce-rest-api' ),
698
					'type'        => 'integer',
699
					'context'     => array( 'view', 'edit' ),
700
					'readonly'    => true,
701
				),
702
				'date_created'     => array(
703
					'description' => __( "The date the review was created, in the site's timezone.", 'woocommerce-rest-api' ),
704
					'type'        => 'date-time',
705
					'context'     => array( 'view', 'edit' ),
706
					'readonly'    => true,
707
				),
708
				'date_created_gmt' => array(
709
					'description' => __( 'The date the review was created, as GMT.', 'woocommerce-rest-api' ),
710
					'type'        => 'date-time',
711
					'context'     => array( 'view', 'edit' ),
712
					'readonly'    => true,
713
				),
714
				'product_id'       => array(
715
					'description' => __( 'Unique identifier for the product that the review belongs to.', 'woocommerce-rest-api' ),
716
					'type'        => 'integer',
717
					'context'     => array( 'view', 'edit' ),
718
				),
719
				'status'           => array(
720
					'description' => __( 'Status of the review.', 'woocommerce-rest-api' ),
721
					'type'        => 'string',
722
					'default'     => 'approved',
723
					'enum'        => array( 'approved', 'hold', 'spam', 'unspam', 'trash', 'untrash' ),
724
					'context'     => array( 'view', 'edit' ),
725
				),
726
				'reviewer'         => array(
727
					'description' => __( 'Reviewer name.', 'woocommerce-rest-api' ),
728
					'type'        => 'string',
729
					'context'     => array( 'view', 'edit' ),
730
				),
731
				'reviewer_email'   => array(
732
					'description' => __( 'Reviewer email.', 'woocommerce-rest-api' ),
733
					'type'        => 'string',
734
					'format'      => 'email',
735
					'context'     => array( 'view', 'edit' ),
736
				),
737
				'review'           => array(
738
					'description' => __( 'The content of the review.', 'woocommerce-rest-api' ),
739
					'type'        => 'string',
740
					'context'     => array( 'view', 'edit' ),
741
					'arg_options' => array(
742
						'sanitize_callback' => 'wp_filter_post_kses',
743
					),
744
				),
745
				'rating'           => array(
746
					'description' => __( 'Review rating (0 to 5).', 'woocommerce-rest-api' ),
747
					'type'        => 'integer',
748
					'context'     => array( 'view', 'edit' ),
749
				),
750
				'verified'         => array(
751
					'description' => __( 'Shows if the reviewer bought the product or not.', 'woocommerce-rest-api' ),
752
					'type'        => 'boolean',
753
					'context'     => array( 'view', 'edit' ),
754
					'readonly'    => true,
755
				),
756
			),
757
		);
758
759
		if ( get_option( 'show_avatars' ) ) {
760
			$avatar_properties = array();
761
			$avatar_sizes      = rest_get_avatar_sizes();
762
763
			foreach ( $avatar_sizes as $size ) {
764
				$avatar_properties[ $size ] = array(
765
					/* translators: %d: avatar image size in pixels */
766
					'description' => sprintf( __( 'Avatar URL with image size of %d pixels.', 'woocommerce-rest-api' ), $size ),
767
					'type'        => 'string',
768
					'format'      => 'uri',
769
					'context'     => array( 'embed', 'view', 'edit' ),
770
				);
771
			}
772
			$schema['properties']['reviewer_avatar_urls'] = array(
773
				'description' => __( 'Avatar URLs for the object reviewer.', 'woocommerce-rest-api' ),
774
				'type'        => 'object',
775
				'context'     => array( 'view', 'edit' ),
776
				'readonly'    => true,
777
				'properties'  => $avatar_properties,
778
			);
779
		}
780
781
		return $this->add_additional_fields_schema( $schema );
782
	}
783
784
	/**
785
	 * Get the query params for collections.
786
	 *
787
	 * @return array
788
	 */
789
	public function get_collection_params() {
790
		$params = parent::get_collection_params();
791
792
		$params['context']['default'] = 'view';
793
794
		$params['after']            = array(
795
			'description' => __( 'Limit response to resources published after a given ISO8601 compliant date.', 'woocommerce-rest-api' ),
796
			'type'        => 'string',
797
			'format'      => 'date-time',
798
		);
799
		$params['before']           = array(
800
			'description' => __( 'Limit response to reviews published before a given ISO8601 compliant date.', 'woocommerce-rest-api' ),
801
			'type'        => 'string',
802
			'format'      => 'date-time',
803
		);
804
		$params['exclude']          = array(
805
			'description' => __( 'Ensure result set excludes specific IDs.', 'woocommerce-rest-api' ),
806
			'type'        => 'array',
807
			'items'       => array(
808
				'type' => 'integer',
809
			),
810
			'default'     => array(),
811
		);
812
		$params['include']          = array(
813
			'description' => __( 'Limit result set to specific IDs.', 'woocommerce-rest-api' ),
814
			'type'        => 'array',
815
			'items'       => array(
816
				'type' => 'integer',
817
			),
818
			'default'     => array(),
819
		);
820
		$params['offset']           = array(
821
			'description' => __( 'Offset the result set by a specific number of items.', 'woocommerce-rest-api' ),
822
			'type'        => 'integer',
823
		);
824
		$params['order']            = array(
825
			'description' => __( 'Order sort attribute ascending or descending.', 'woocommerce-rest-api' ),
826
			'type'        => 'string',
827
			'default'     => 'desc',
828
			'enum'        => array(
829
				'asc',
830
				'desc',
831
			),
832
		);
833
		$params['orderby']          = array(
834
			'description' => __( 'Sort collection by object attribute.', 'woocommerce-rest-api' ),
835
			'type'        => 'string',
836
			'default'     => 'date_gmt',
837
			'enum'        => array(
838
				'date',
839
				'date_gmt',
840
				'id',
841
				'include',
842
				'product',
843
			),
844
		);
845
		$params['reviewer']         = array(
846
			'description' => __( 'Limit result set to reviews assigned to specific user IDs.', 'woocommerce-rest-api' ),
847
			'type'        => 'array',
848
			'items'       => array(
849
				'type' => 'integer',
850
			),
851
		);
852
		$params['reviewer_exclude'] = array(
853
			'description' => __( 'Ensure result set excludes reviews assigned to specific user IDs.', 'woocommerce-rest-api' ),
854
			'type'        => 'array',
855
			'items'       => array(
856
				'type' => 'integer',
857
			),
858
		);
859
		$params['reviewer_email']   = array(
860
			'default'     => null,
861
			'description' => __( 'Limit result set to that from a specific author email.', 'woocommerce-rest-api' ),
862
			'format'      => 'email',
863
			'type'        => 'string',
864
		);
865
		$params['product']          = array(
866
			'default'     => array(),
867
			'description' => __( 'Limit result set to reviews assigned to specific product IDs.', 'woocommerce-rest-api' ),
868
			'type'        => 'array',
869
			'items'       => array(
870
				'type' => 'integer',
871
			),
872
		);
873
		$params['status']           = array(
874
			'default'           => 'approved',
875
			'description'       => __( 'Limit result set to reviews assigned a specific status.', 'woocommerce-rest-api' ),
876
			'sanitize_callback' => 'sanitize_key',
877
			'type'              => 'string',
878
			'enum'              => array(
879
				'all',
880
				'hold',
881
				'approved',
882
				'spam',
883
				'trash',
884
			),
885
		);
886
887
		/**
888
		 * Filter collection parameters for the reviews controller.
889
		 *
890
		 * This filter registers the collection parameter, but does not map the
891
		 * collection parameter to an internal \WP_Comment_Query parameter. Use the
892
		 * `wc_rest_review_query` filter to set \WP_Comment_Query parameters.
893
		 *
894
		 * @since 3.5.0
895
		 * @param array $params JSON Schema-formatted collection parameters.
896
		 */
897
		return apply_filters( 'woocommerce_rest_product_review_collection_params', $params );
898
	}
899
900
	/**
901
	 * Get the reivew, if the ID is valid.
902
	 *
903
	 * @since 3.5.0
904
	 * @param int $id Supplied ID.
905
	 * @return WP_Comment|\WP_Error Comment object if ID is valid, \WP_Error otherwise.
0 ignored issues
show
Bug introduced by
The type Automattic\WooCommerce\R...ers\Version4\WP_Comment was not found. Did you mean WP_Comment? If so, make sure to prefix the type with \.
Loading history...
906
	 */
907
	protected function get_review( $id ) {
908
		$id    = (int) $id;
909
		$error = new \WP_Error( 'woocommerce_rest_review_invalid_id', __( 'Invalid review ID.', 'woocommerce-rest-api' ), array( 'status' => 404 ) );
910
911
		if ( 0 >= $id ) {
912
			return $error;
913
		}
914
915
		$review = get_comment( $id );
916
		if ( empty( $review ) ) {
917
			return $error;
918
		}
919
920
		if ( ! empty( $review->comment_post_ID ) ) {
921
			$post = get_post( (int) $review->comment_post_ID );
922
923
			if ( 'product' !== get_post_type( (int) $review->comment_post_ID ) ) {
924
				return new \WP_Error( 'woocommerce_rest_product_invalid_id', __( 'Invalid product ID.', 'woocommerce-rest-api' ), array( 'status' => 404 ) );
925
			}
926
		}
927
928
		return $review;
929
	}
930
931
	/**
932
	 * Prepends internal property prefix to query parameters to match our response fields.
933
	 *
934
	 * @since 3.5.0
935
	 * @param string $query_param Query parameter.
936
	 * @return string
937
	 */
938
	protected function normalize_query_param( $query_param ) {
939
		$prefix = 'comment_';
940
941
		switch ( $query_param ) {
942
			case 'id':
943
				$normalized = $prefix . 'ID';
944
				break;
945
			case 'product':
946
				$normalized = $prefix . 'post_ID';
947
				break;
948
			case 'include':
949
				$normalized = 'comment__in';
950
				break;
951
			default:
952
				$normalized = $prefix . $query_param;
953
				break;
954
		}
955
956
		return $normalized;
957
	}
958
959
960
961
	/**
962
	 * Sets the comment_status of a given review object when creating or updating a review.
963
	 *
964
	 * @since 3.5.0
965
	 * @param string|int $new_status New review status.
966
	 * @param int        $id         Review ID.
967
	 * @return bool Whether the status was changed.
968
	 */
969
	protected function handle_status_param( $new_status, $id ) {
970
		$old_status = wp_get_comment_status( $id );
971
972
		if ( $new_status === $old_status ) {
973
			return false;
974
		}
975
976
		switch ( $new_status ) {
977
			case 'approved':
978
			case 'approve':
979
			case '1':
980
				$changed = wp_set_comment_status( $id, 'approve' );
981
				break;
982
			case 'hold':
983
			case '0':
984
				$changed = wp_set_comment_status( $id, 'hold' );
985
				break;
986
			case 'spam':
987
				$changed = wp_spam_comment( $id );
988
				break;
989
			case 'unspam':
990
				$changed = wp_unspam_comment( $id );
991
				break;
992
			case 'trash':
993
				$changed = wp_trash_comment( $id );
994
				break;
995
			case 'untrash':
996
				$changed = wp_untrash_comment( $id );
997
				break;
998
			default:
999
				$changed = false;
1000
				break;
1001
		}
1002
1003
		return $changed;
1004
	}
1005
1006
	/**
1007
	 * Check if a given request has access to read a webhook.
1008
	 *
1009
	 * @param  \WP_REST_Request $request Full details about the request.
1010
	 * @return \WP_Error|boolean
1011
	 */
1012
	public function get_item_permissions_check( $request ) {
1013
		$id          = $request->get_param( 'id' );
1014
		$check_valid = $this->get_review( $id );
1015
1016
		if ( is_wp_error( $check_valid ) ) {
1017
			return $check_valid;
1018
		}
1019
1020
		return parent::get_item_permissions_check( $request );
1021
	}
1022
1023
	/**
1024
	 * Check if a given request has access to delete an item.
1025
	 *
1026
	 * @param  \WP_REST_Request $request Full details about the request.
1027
	 * @return \WP_Error|boolean
1028
	 */
1029
	public function delete_item_permissions_check( $request ) {
1030
		$id          = $request->get_param( 'id' );
1031
		$check_valid = $this->get_review( $id );
1032
1033
		if ( is_wp_error( $check_valid ) ) {
1034
			return $check_valid;
1035
		}
1036
1037
		return parent::delete_item_permissions_check( $request );
1038
	}
1039
1040
	/**
1041
	 * Check if a given request has access to update an item.
1042
	 *
1043
	 * @param  \WP_REST_Request $request Full details about the request.
1044
	 * @return \WP_Error|boolean
1045
	 */
1046
	public function update_item_permissions_check( $request ) {
1047
		$id          = $request->get_param( 'id' );
1048
		$check_valid = $this->get_review( $id );
1049
1050
		if ( is_wp_error( $check_valid ) ) {
1051
			return $check_valid;
1052
		}
1053
1054
		return parent::update_item_permissions_check( $request );
1055
	}
1056
}
1057