Test Failed
Push — master ( a5e4c3...7495fa )
by Mike
43:00
created

ProductReviews::update_item()   D

Complexity

Conditions 19
Paths 46

Size

Total Lines 78
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 19
eloc 41
nc 46
nop 1
dl 0
loc 78
rs 4.5166
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 WooCommerce/RestApi
8
 */
9
10
namespace WooCommerce\RestApi\Controllers\Version4;
11
12
defined( 'ABSPATH' ) || exit;
13
14
/**
15
 * REST API Product Reviews controller class.
16
 */
17
class ProductReviews extends AbstractController {
18
19
	/**
20
	 * Route base.
21
	 *
22
	 * @var string
23
	 */
24
	protected $rest_base = 'products/reviews';
25
26
	/**
27
	 * Register the routes for product reviews.
28
	 */
29
	public function register_routes() {
30
		register_rest_route(
31
			$this->namespace,
32
			'/' . $this->rest_base,
33
			array(
34
				array(
35
					'methods'             => \WP_REST_Server::READABLE,
0 ignored issues
show
Bug introduced by
The type WP_REST_Server was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
36
					'callback'            => array( $this, 'get_items' ),
37
					'permission_callback' => array( $this, 'get_items_permissions_check' ),
38
					'args'                => $this->get_collection_params(),
39
				),
40
				array(
41
					'methods'             => \WP_REST_Server::CREATABLE,
42
					'callback'            => array( $this, 'create_item' ),
43
					'permission_callback' => array( $this, 'create_item_permissions_check' ),
44
					'args'                => array_merge(
45
						$this->get_endpoint_args_for_item_schema( \WP_REST_Server::CREATABLE ),
46
						array(
47
							'product_id'     => array(
48
								'required'    => true,
49
								'description' => __( 'Unique identifier for the product.', 'woocommerce' ),
50
								'type'        => 'integer',
51
							),
52
							'review'         => array(
53
								'required'    => true,
54
								'type'        => 'string',
55
								'description' => __( 'Review content.', 'woocommerce' ),
56
							),
57
							'reviewer'       => array(
58
								'required'    => true,
59
								'type'        => 'string',
60
								'description' => __( 'Name of the reviewer.', 'woocommerce' ),
61
							),
62
							'reviewer_email' => array(
63
								'required'    => true,
64
								'type'        => 'string',
65
								'description' => __( 'Email of the reviewer.', 'woocommerce' ),
66
							),
67
						)
68
					),
69
				),
70
				'schema' => array( $this, 'get_public_item_schema' ),
71
			),
72
			true
73
		);
74
75
		register_rest_route(
76
			$this->namespace,
77
			'/' . $this->rest_base . '/(?P<id>[\d]+)',
78
			array(
79
				'args'   => array(
80
					'id' => array(
81
						'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
82
						'type'        => 'integer',
83
					),
84
				),
85
				array(
86
					'methods'             => \WP_REST_Server::READABLE,
87
					'callback'            => array( $this, 'get_item' ),
88
					'permission_callback' => array( $this, 'get_item_permissions_check' ),
89
					'args'                => array(
90
						'context' => $this->get_context_param( array( 'default' => 'view' ) ),
91
					),
92
				),
93
				array(
94
					'methods'             => \WP_REST_Server::EDITABLE,
95
					'callback'            => array( $this, 'update_item' ),
96
					'permission_callback' => array( $this, 'update_item_permissions_check' ),
97
					'args'                => $this->get_endpoint_args_for_item_schema( \WP_REST_Server::EDITABLE ),
98
				),
99
				array(
100
					'methods'             => \WP_REST_Server::DELETABLE,
101
					'callback'            => array( $this, 'delete_item' ),
102
					'permission_callback' => array( $this, 'delete_item_permissions_check' ),
103
					'args'                => array(
104
						'force' => array(
105
							'default'     => false,
106
							'type'        => 'boolean',
107
							'description' => __( 'Whether to bypass trash and force deletion.', 'woocommerce' ),
108
						),
109
					),
110
				),
111
				'schema' => array( $this, 'get_public_item_schema' ),
112
			),
113
			true
114
		);
115
116
		register_rest_route(
117
			$this->namespace,
118
			'/' . $this->rest_base . '/batch',
119
			array(
120
				array(
121
					'methods'             => \WP_REST_Server::EDITABLE,
122
					'callback'            => array( $this, 'batch_items' ),
123
					'permission_callback' => array( $this, 'batch_items_permissions_check' ),
124
					'args'                => $this->get_endpoint_args_for_item_schema( \WP_REST_Server::EDITABLE ),
125
				),
126
				'schema' => array( $this, 'get_public_batch_schema' ),
127
			),
128
			true
129
		);
130
	}
131
132
	/**
133
	 * Check whether a given request has permission to read webhook deliveries.
134
	 *
135
	 * @param  \WP_REST_Request $request Full details about the request.
0 ignored issues
show
Bug introduced by
The type WP_REST_Request was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
136
	 * @return \WP_Error|boolean
137
	 */
138
	public function get_items_permissions_check( $request ) {
139
		if ( ! wc_rest_check_product_reviews_permissions( 'read' ) ) {
140
			return new \WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
0 ignored issues
show
Bug introduced by
The type WP_Error was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
141
		}
142
143
		return true;
144
	}
145
146
	/**
147
	 * Check if a given request has access to read a product review.
148
	 *
149
	 * @param  \WP_REST_Request $request Full details about the request.
150
	 * @return \WP_Error|boolean
151
	 */
152
	public function get_item_permissions_check( $request ) {
153
		$id     = (int) $request['id'];
154
		$review = get_comment( $id );
155
156
		if ( $review && ! wc_rest_check_product_reviews_permissions( 'read', $review->comment_ID ) ) {
157
			return new \WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot view this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
158
		}
159
160
		return true;
161
	}
162
163
	/**
164
	 * Check if a given request has access to create a new product review.
165
	 *
166
	 * @param  \WP_REST_Request $request Full details about the request.
167
	 * @return \WP_Error|boolean
168
	 */
169
	public function create_item_permissions_check( $request ) {
170
		if ( ! wc_rest_check_product_reviews_permissions( 'create' ) ) {
171
			return new \WP_Error( 'woocommerce_rest_cannot_create', __( 'Sorry, you are not allowed to create resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
172
		}
173
174
		return true;
175
	}
176
177
	/**
178
	 * Check if a given request has access to update a product review.
179
	 *
180
	 * @param  \WP_REST_Request $request Full details about the request.
181
	 * @return \WP_Error|boolean
182
	 */
183
	public function update_item_permissions_check( $request ) {
184
		$id     = (int) $request['id'];
185
		$review = get_comment( $id );
186
187
		if ( $review && ! wc_rest_check_product_reviews_permissions( 'edit', $review->comment_ID ) ) {
188
			return new \WP_Error( 'woocommerce_rest_cannot_edit', __( 'Sorry, you cannot edit this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
189
		}
190
191
		return true;
192
	}
193
194
	/**
195
	 * Check if a given request has access to delete a product review.
196
	 *
197
	 * @param  \WP_REST_Request $request Full details about the request.
198
	 * @return \WP_Error|boolean
199
	 */
200
	public function delete_item_permissions_check( $request ) {
201
		$id     = (int) $request['id'];
202
		$review = get_comment( $id );
203
204
		if ( $review && ! wc_rest_check_product_reviews_permissions( 'delete', $review->comment_ID ) ) {
205
			return new \WP_Error( 'woocommerce_rest_cannot_edit', __( 'Sorry, you cannot delete this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
206
		}
207
208
		return true;
209
	}
210
211
	/**
212
	 * Check if a given request has access batch create, update and delete items.
213
	 *
214
	 * @param  \WP_REST_Request $request Full details about the request.
215
	 * @return boolean|\WP_Error
216
	 */
217
	public function batch_items_permissions_check( $request ) {
218
		if ( ! wc_rest_check_product_reviews_permissions( 'create' ) ) {
219
			return new \WP_Error( 'woocommerce_rest_cannot_batch', __( 'Sorry, you are not allowed to batch manipulate this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
220
		}
221
222
		return true;
223
	}
224
225
	/**
226
	 * Get all reviews.
227
	 *
228
	 * @param \WP_REST_Request $request Full details about the request.
229
	 * @return array|\WP_Error
230
	 */
231
	public function get_items( $request ) {
232
		// Retrieve the list of registered collection query parameters.
233
		$registered = $this->get_collection_params();
234
235
		/*
236
		 * This array defines mappings between public API query parameters whose
237
		 * values are accepted as-passed, and their internal \WP_Query parameter
238
		 * name equivalents (some are the same). Only values which are also
239
		 * present in $registered will be set.
240
		 */
241
		$parameter_mappings = array(
242
			'reviewer'         => 'author__in',
243
			'reviewer_email'   => 'author_email',
244
			'reviewer_exclude' => 'author__not_in',
245
			'exclude'          => 'comment__not_in',
246
			'include'          => 'comment__in',
247
			'offset'           => 'offset',
248
			'order'            => 'order',
249
			'per_page'         => 'number',
250
			'product'          => 'post__in',
251
			'search'           => 'search',
252
			'status'           => 'status',
253
		);
254
255
		$prepared_args = array();
256
257
		/*
258
		 * For each known parameter which is both registered and present in the request,
259
		 * set the parameter's value on the query $prepared_args.
260
		 */
261
		foreach ( $parameter_mappings as $api_param => $wp_param ) {
262
			if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) {
263
				$prepared_args[ $wp_param ] = $request[ $api_param ];
264
			}
265
		}
266
267
		// Ensure certain parameter values default to empty strings.
268
		foreach ( array( 'author_email', 'search' ) as $param ) {
269
			if ( ! isset( $prepared_args[ $param ] ) ) {
270
				$prepared_args[ $param ] = '';
271
			}
272
		}
273
274
		if ( isset( $registered['orderby'] ) ) {
275
			$prepared_args['orderby'] = $this->normalize_query_param( $request['orderby'] );
276
		}
277
278
		if ( isset( $prepared_args['status'] ) ) {
279
			$prepared_args['status'] = 'approved' === $prepared_args['status'] ? 'approve' : $prepared_args['status'];
280
		}
281
282
		$prepared_args['no_found_rows'] = false;
283
		$prepared_args['date_query']    = array();
284
285
		// Set before into date query. Date query must be specified as an array of an array.
286
		if ( isset( $registered['before'], $request['before'] ) ) {
287
			$prepared_args['date_query'][0]['before'] = $request['before'];
288
		}
289
290
		// Set after into date query. Date query must be specified as an array of an array.
291
		if ( isset( $registered['after'], $request['after'] ) ) {
292
			$prepared_args['date_query'][0]['after'] = $request['after'];
293
		}
294
295
		if ( isset( $registered['page'] ) && empty( $request['offset'] ) ) {
296
			$prepared_args['offset'] = $prepared_args['number'] * ( absint( $request['page'] ) - 1 );
297
		}
298
299
		/**
300
		 * Filters arguments, before passing to \WP_Comment_Query, when querying reviews via the REST API.
301
		 *
302
		 * @since 3.5.0
303
		 * @link https://developer.wordpress.org/reference/classes/\WP_Comment_Query/
304
		 * @param array           $prepared_args Array of arguments for \WP_Comment_Query.
305
		 * @param \WP_REST_Request $request       The current request.
306
		 */
307
		$prepared_args = apply_filters( 'woocommerce_rest_product_review_query', $prepared_args, $request );
308
309
		// Make sure that returns only reviews.
310
		$prepared_args['type'] = 'review';
311
312
		// Query reviews.
313
		$query        = new \WP_Comment_Query();
0 ignored issues
show
Bug introduced by
The type WP_Comment_Query was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
314
		$query_result = $query->query( $prepared_args );
315
		$reviews      = array();
316
317
		foreach ( $query_result as $review ) {
318
			if ( ! wc_rest_check_product_reviews_permissions( 'read', $review->comment_ID ) ) {
319
				continue;
320
			}
321
322
			$data      = $this->prepare_item_for_response( $review, $request );
323
			$reviews[] = $this->prepare_response_for_collection( $data );
324
		}
325
326
		$total_reviews = (int) $query->found_comments;
327
		$max_pages     = (int) $query->max_num_pages;
328
329
		if ( $total_reviews < 1 ) {
330
			// Out-of-bounds, run the query again without LIMIT for total count.
331
			unset( $prepared_args['number'], $prepared_args['offset'] );
332
333
			$query                  = new \WP_Comment_Query();
334
			$prepared_args['count'] = true;
335
336
			$total_reviews = $query->query( $prepared_args );
337
			$max_pages     = ceil( $total_reviews / $request['per_page'] );
338
		}
339
340
		$response = rest_ensure_response( $reviews );
341
		$response->header( 'X-WP-Total', $total_reviews );
342
		$response->header( 'X-WP-TotalPages', $max_pages );
343
344
		$base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ) );
345
346
		if ( $request['page'] > 1 ) {
347
			$prev_page = $request['page'] - 1;
348
349
			if ( $prev_page > $max_pages ) {
350
				$prev_page = $max_pages;
351
			}
352
353
			$prev_link = add_query_arg( 'page', $prev_page, $base );
354
			$response->link_header( 'prev', $prev_link );
355
		}
356
357
		if ( $max_pages > $request['page'] ) {
358
			$next_page = $request['page'] + 1;
359
			$next_link = add_query_arg( 'page', $next_page, $base );
360
361
			$response->link_header( 'next', $next_link );
362
		}
363
364
		return $response;
365
	}
366
367
	/**
368
	 * Create a single review.
369
	 *
370
	 * @param \WP_REST_Request $request Full details about the request.
371
	 * @return \WP_Error|\WP_REST_Response
0 ignored issues
show
Bug introduced by
The type WP_REST_Response was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
372
	 */
373
	public function create_item( $request ) {
374
		if ( ! empty( $request['id'] ) ) {
375
			return new \WP_Error( 'woocommerce_rest_review_exists', __( 'Cannot create existing product review.', 'woocommerce' ), array( 'status' => 400 ) );
376
		}
377
378
		$product_id = (int) $request['product_id'];
379
380
		if ( 'product' !== get_post_type( $product_id ) ) {
381
			return new \WP_Error( 'woocommerce_rest_product_invalid_id', __( 'Invalid product ID.', 'woocommerce' ), array( 'status' => 404 ) );
382
		}
383
384
		$prepared_review = $this->prepare_item_for_database( $request );
385
		if ( is_wp_error( $prepared_review ) ) {
386
			return $prepared_review;
387
		}
388
389
		$prepared_review['comment_type'] = 'review';
390
391
		/*
392
		 * Do not allow a comment to be created with missing or empty comment_content. See wp_handle_comment_submission().
393
		 */
394
		if ( empty( $prepared_review['comment_content'] ) ) {
395
			return new \WP_Error( 'woocommerce_rest_review_content_invalid', __( 'Invalid review content.', 'woocommerce' ), array( 'status' => 400 ) );
396
		}
397
398
		// Setting remaining values before wp_insert_comment so we can use wp_allow_comment().
399
		if ( ! isset( $prepared_review['comment_date_gmt'] ) ) {
400
			$prepared_review['comment_date_gmt'] = current_time( 'mysql', true );
401
		}
402
403
		if ( ! empty( $_SERVER['REMOTE_ADDR'] ) && rest_is_ip_address( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) ) { // WPCS: input var ok, sanitization ok.
404
			$prepared_review['comment_author_IP'] = wc_clean( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ); // WPCS: input var ok.
405
		} else {
406
			$prepared_review['comment_author_IP'] = '127.0.0.1';
407
		}
408
409
		if ( ! empty( $request['author_user_agent'] ) ) {
410
			$prepared_review['comment_agent'] = $request['author_user_agent'];
411
		} elseif ( $request->get_header( 'user_agent' ) ) {
412
			$prepared_review['comment_agent'] = $request->get_header( 'user_agent' );
413
		} else {
414
			$prepared_review['comment_agent'] = '';
415
		}
416
417
		$check_comment_lengths = wp_check_comment_data_max_lengths( $prepared_review );
418
		if ( is_wp_error( $check_comment_lengths ) ) {
419
			$error_code = str_replace( array( 'comment_author', 'comment_content' ), array( 'reviewer', 'review_content' ), $check_comment_lengths->get_error_code() );
420
			return new \WP_Error( 'woocommerce_rest_' . $error_code, __( 'Product review field exceeds maximum length allowed.', 'woocommerce' ), array( 'status' => 400 ) );
421
		}
422
423
		$prepared_review['comment_parent']     = 0;
424
		$prepared_review['comment_author_url'] = '';
425
		$prepared_review['comment_approved']   = wp_allow_comment( $prepared_review, true );
426
427
		if ( is_wp_error( $prepared_review['comment_approved'] ) ) {
428
			$error_code    = $prepared_review['comment_approved']->get_error_code();
429
			$error_message = $prepared_review['comment_approved']->get_error_message();
430
431
			if ( 'comment_duplicate' === $error_code ) {
432
				return new \WP_Error( 'woocommerce_rest_' . $error_code, $error_message, array( 'status' => 409 ) );
433
			}
434
435
			if ( 'comment_flood' === $error_code ) {
436
				return new \WP_Error( 'woocommerce_rest_' . $error_code, $error_message, array( 'status' => 400 ) );
437
			}
438
439
			return $prepared_review['comment_approved'];
440
		}
441
442
		/**
443
		 * Filters a review before it is inserted via the REST API.
444
		 *
445
		 * Allows modification of the review right before it is inserted via wp_insert_comment().
446
		 * Returning a \WP_Error value from the filter will shortcircuit insertion and allow
447
		 * skipping further processing.
448
		 *
449
		 * @since 3.5.0
450
		 * @param array|\WP_Error  $prepared_review The prepared review data for wp_insert_comment().
451
		 * @param \WP_REST_Request $request          Request used to insert the review.
452
		 */
453
		$prepared_review = apply_filters( 'woocommerce_rest_pre_insert_product_review', $prepared_review, $request );
454
		if ( is_wp_error( $prepared_review ) ) {
455
			return $prepared_review;
456
		}
457
458
		$review_id = wp_insert_comment( wp_filter_comment( wp_slash( (array) $prepared_review ) ) );
459
460
		if ( ! $review_id ) {
461
			return new \WP_Error( 'woocommerce_rest_review_failed_create', __( 'Creating product review failed.', 'woocommerce' ), array( 'status' => 500 ) );
462
		}
463
464
		if ( isset( $request['status'] ) ) {
465
			$this->handle_status_param( $request['status'], $review_id );
466
		}
467
468
		update_comment_meta( $review_id, 'rating', ! empty( $request['rating'] ) ? $request['rating'] : '0' );
469
470
		$review = get_comment( $review_id );
471
472
		/**
473
		 * Fires after a comment is created or updated via the REST API.
474
		 *
475
		 * @param WP_Comment      $review   Inserted or updated comment object.
476
		 * @param \WP_REST_Request $request  Request object.
477
		 * @param bool            $creating True when creating a comment, false when updating.
478
		 */
479
		do_action( 'woocommerce_rest_insert_product_review', $review, $request, true );
480
481
		$fields_update = $this->update_additional_fields_for_object( $review, $request );
482
		if ( is_wp_error( $fields_update ) ) {
483
			return $fields_update;
484
		}
485
486
		$context = current_user_can( 'moderate_comments' ) ? 'edit' : 'view';
487
		$request->set_param( 'context', $context );
488
489
		$response = $this->prepare_item_for_response( $review, $request );
490
		$response = rest_ensure_response( $response );
491
492
		$response->set_status( 201 );
493
		$response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $review_id ) ) );
494
495
		return $response;
496
	}
497
498
	/**
499
	 * Get a single product review.
500
	 *
501
	 * @param \WP_REST_Request $request Full details about the request.
502
	 * @return \WP_Error|\WP_REST_Response
503
	 */
504
	public function get_item( $request ) {
505
		$review = $this->get_review( $request['id'] );
506
		if ( is_wp_error( $review ) ) {
507
			return $review;
508
		}
509
510
		$data     = $this->prepare_item_for_response( $review, $request );
511
		$response = rest_ensure_response( $data );
512
513
		return $response;
514
	}
515
516
	/**
517
	 * Updates a review.
518
	 *
519
	 * @param \WP_REST_Request $request Full details about the request.
520
	 * @return \WP_Error|\WP_REST_Response Response object on success, or error object on failure.
521
	 */
522
	public function update_item( $request ) {
523
		$review = $this->get_review( $request['id'] );
524
		if ( is_wp_error( $review ) ) {
525
			return $review;
526
		}
527
528
		$id = (int) $review->comment_ID;
529
530
		if ( isset( $request['type'] ) && 'review' !== get_comment_type( $id ) ) {
531
			return new \WP_Error( 'woocommerce_rest_review_invalid_type', __( 'Sorry, you are not allowed to change the comment type.', 'woocommerce' ), array( 'status' => 404 ) );
532
		}
533
534
		$prepared_args = $this->prepare_item_for_database( $request );
535
		if ( is_wp_error( $prepared_args ) ) {
536
			return $prepared_args;
537
		}
538
539
		if ( ! empty( $prepared_args['comment_post_ID'] ) ) {
540
			if ( 'product' !== get_post_type( (int) $prepared_args['comment_post_ID'] ) ) {
541
				return new \WP_Error( 'woocommerce_rest_product_invalid_id', __( 'Invalid product ID.', 'woocommerce' ), array( 'status' => 404 ) );
542
			}
543
		}
544
545
		if ( empty( $prepared_args ) && isset( $request['status'] ) ) {
546
			// Only the comment status is being changed.
547
			$change = $this->handle_status_param( $request['status'], $id );
548
549
			if ( ! $change ) {
550
				return new \WP_Error( 'woocommerce_rest_review_failed_edit', __( 'Updating review status failed.', 'woocommerce' ), array( 'status' => 500 ) );
551
			}
552
		} elseif ( ! empty( $prepared_args ) ) {
553
			if ( is_wp_error( $prepared_args ) ) {
554
				return $prepared_args;
555
			}
556
557
			if ( isset( $prepared_args['comment_content'] ) && empty( $prepared_args['comment_content'] ) ) {
558
				return new \WP_Error( 'woocommerce_rest_review_content_invalid', __( 'Invalid review content.', 'woocommerce' ), array( 'status' => 400 ) );
559
			}
560
561
			$prepared_args['comment_ID'] = $id;
562
563
			$check_comment_lengths = wp_check_comment_data_max_lengths( $prepared_args );
564
			if ( is_wp_error( $check_comment_lengths ) ) {
565
				$error_code = str_replace( array( 'comment_author', 'comment_content' ), array( 'reviewer', 'review_content' ), $check_comment_lengths->get_error_code() );
566
				return new \WP_Error( 'woocommerce_rest_' . $error_code, __( 'Product review field exceeds maximum length allowed.', 'woocommerce' ), array( 'status' => 400 ) );
567
			}
568
569
			$updated = wp_update_comment( wp_slash( (array) $prepared_args ) );
570
571
			if ( false === $updated ) {
572
				return new \WP_Error( 'woocommerce_rest_comment_failed_edit', __( 'Updating review failed.', 'woocommerce' ), array( 'status' => 500 ) );
573
			}
574
575
			if ( isset( $request['status'] ) ) {
576
				$this->handle_status_param( $request['status'], $id );
577
			}
578
		}
579
580
		if ( ! empty( $request['rating'] ) ) {
581
			update_comment_meta( $id, 'rating', $request['rating'] );
582
		}
583
584
		$review = get_comment( $id );
585
586
		/** This action is documented in includes/api/class-wc-rest-product-reviews-controller.php */
587
		do_action( 'woocommerce_rest_insert_product_review', $review, $request, false );
588
589
		$fields_update = $this->update_additional_fields_for_object( $review, $request );
590
591
		if ( is_wp_error( $fields_update ) ) {
592
			return $fields_update;
593
		}
594
595
		$request->set_param( 'context', 'edit' );
596
597
		$response = $this->prepare_item_for_response( $review, $request );
598
599
		return rest_ensure_response( $response );
600
	}
601
602
	/**
603
	 * Deletes a review.
604
	 *
605
	 * @param \WP_REST_Request $request Full details about the request.
606
	 * @return \WP_Error|\WP_REST_Response Response object on success, or error object on failure.
607
	 */
608
	public function delete_item( $request ) {
609
		$review = $this->get_review( $request['id'] );
610
		if ( is_wp_error( $review ) ) {
611
			return $review;
612
		}
613
614
		$force = isset( $request['force'] ) ? (bool) $request['force'] : false;
615
616
		/**
617
		 * Filters whether a review can be trashed.
618
		 *
619
		 * Return false to disable trash support for the post.
620
		 *
621
		 * @since 3.5.0
622
		 * @param bool       $supports_trash Whether the post type support trashing.
623
		 * @param WP_Comment $review         The review object being considered for trashing support.
624
		 */
625
		$supports_trash = apply_filters( 'woocommerce_rest_product_review_trashable', ( EMPTY_TRASH_DAYS > 0 ), $review );
0 ignored issues
show
Bug introduced by
The constant WooCommerce\RestApi\Cont...rsion4\EMPTY_TRASH_DAYS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
626
627
		$request->set_param( 'context', 'edit' );
628
629
		if ( $force ) {
630
			$previous = $this->prepare_item_for_response( $review, $request );
631
			$result   = wp_delete_comment( $review->comment_ID, true );
632
			$response = new \WP_REST_Response();
633
			$response->set_data(
634
				array(
635
					'deleted'  => true,
636
					'previous' => $previous->get_data(),
637
				)
638
			);
639
		} else {
640
			// If this type doesn't support trashing, error out.
641
			if ( ! $supports_trash ) {
642
				/* translators: %s: force=true */
643
				return new \WP_Error( 'woocommerce_rest_trash_not_supported', sprintf( __( "The object does not support trashing. Set '%s' to delete.", 'woocommerce' ), 'force=true' ), array( 'status' => 501 ) );
644
			}
645
646
			if ( 'trash' === $review->comment_approved ) {
647
				return new \WP_Error( 'woocommerce_rest_already_trashed', __( 'The object has already been trashed.', 'woocommerce' ), array( 'status' => 410 ) );
648
			}
649
650
			$result   = wp_trash_comment( $review->comment_ID );
651
			$review   = get_comment( $review->comment_ID );
652
			$response = $this->prepare_item_for_response( $review, $request );
653
		}
654
655
		if ( ! $result ) {
656
			return new \WP_Error( 'woocommerce_rest_cannot_delete', __( 'The object cannot be deleted.', 'woocommerce' ), array( 'status' => 500 ) );
657
		}
658
659
		/**
660
		 * Fires after a review is deleted via the REST API.
661
		 *
662
		 * @param WP_Comment       $review   The deleted review data.
663
		 * @param \WP_REST_Response $response The response returned from the API.
664
		 * @param \WP_REST_Request  $request  The request sent to the API.
665
		 */
666
		do_action( 'woocommerce_rest_delete_review', $review, $response, $request );
667
668
		return $response;
669
	}
670
671
	/**
672
	 * Prepare a single product review output for response.
673
	 *
674
	 * @param WP_Comment      $review Product review object.
0 ignored issues
show
Bug introduced by
The type WooCommerce\RestApi\Cont...ers\Version4\WP_Comment was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
675
	 * @param \WP_REST_Request $request Request object.
676
	 * @return \WP_REST_Response $response Response data.
677
	 */
678
	public function prepare_item_for_response( $review, $request ) {
679
		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
680
		$fields  = $this->get_fields_for_response( $request );
681
		$data    = array();
682
683
		if ( in_array( 'id', $fields, true ) ) {
684
			$data['id'] = (int) $review->comment_ID;
685
		}
686
		if ( in_array( 'date_created', $fields, true ) ) {
687
			$data['date_created'] = wc_rest_prepare_date_response( $review->comment_date );
688
		}
689
		if ( in_array( 'date_created_gmt', $fields, true ) ) {
690
			$data['date_created_gmt'] = wc_rest_prepare_date_response( $review->comment_date_gmt );
691
		}
692
		if ( in_array( 'product_id', $fields, true ) ) {
693
			$data['product_id'] = (int) $review->comment_post_ID;
694
		}
695
		if ( in_array( 'status', $fields, true ) ) {
696
			$data['status'] = $this->prepare_status_response( (string) $review->comment_approved );
697
		}
698
		if ( in_array( 'reviewer', $fields, true ) ) {
699
			$data['reviewer'] = $review->comment_author;
700
		}
701
		if ( in_array( 'reviewer_email', $fields, true ) ) {
702
			$data['reviewer_email'] = $review->comment_author_email;
703
		}
704
		if ( in_array( 'review', $fields, true ) ) {
705
			$data['review'] = 'view' === $context ? wpautop( $review->comment_content ) : $review->comment_content;
706
		}
707
		if ( in_array( 'rating', $fields, true ) ) {
708
			$data['rating'] = (int) get_comment_meta( $review->comment_ID, 'rating', true );
709
		}
710
		if ( in_array( 'verified', $fields, true ) ) {
711
			$data['verified'] = wc_review_is_from_verified_owner( $review->comment_ID );
712
		}
713
		if ( in_array( 'reviewer_avatar_urls', $fields, true ) ) {
714
			$data['reviewer_avatar_urls'] = rest_get_avatar_urls( $review->comment_author_email );
715
		}
716
717
		$data = $this->add_additional_fields_to_object( $data, $request );
718
		$data = $this->filter_response_by_context( $data, $context );
719
720
		// Wrap the data in a response object.
721
		$response = rest_ensure_response( $data );
722
723
		$response->add_links( $this->prepare_links( $review ) );
724
725
		/**
726
		 * Filter product reviews object returned from the REST API.
727
		 *
728
		 * @param \WP_REST_Response $response The response object.
729
		 * @param \WP_Comment       $review   Product review object used to create response.
730
		 * @param \WP_REST_Request  $request  Request object.
731
		 */
732
		return apply_filters( 'woocommerce_rest_prepare_product_review', $response, $review, $request );
733
	}
734
735
	/**
736
	 * Prepare a single product review to be inserted into the database.
737
	 *
738
	 * @param  \WP_REST_Request $request Request object.
739
	 * @return array|\WP_Error  $prepared_review
740
	 */
741
	protected function prepare_item_for_database( $request ) {
742
		if ( isset( $request['id'] ) ) {
743
			$prepared_review['comment_ID'] = (int) $request['id'];
744
		}
745
746
		if ( isset( $request['review'] ) ) {
747
			$prepared_review['comment_content'] = $request['review'];
748
		}
749
750
		if ( isset( $request['product_id'] ) ) {
751
			$prepared_review['comment_post_ID'] = (int) $request['product_id'];
752
		}
753
754
		if ( isset( $request['reviewer'] ) ) {
755
			$prepared_review['comment_author'] = $request['reviewer'];
756
		}
757
758
		if ( isset( $request['reviewer_email'] ) ) {
759
			$prepared_review['comment_author_email'] = $request['reviewer_email'];
760
		}
761
762
		if ( ! empty( $request['date_created'] ) ) {
763
			$date_data = rest_get_date_with_gmt( $request['date_created'] );
764
765
			if ( ! empty( $date_data ) ) {
766
				list( $prepared_review['comment_date'], $prepared_review['comment_date_gmt'] ) = $date_data;
767
			}
768
		} elseif ( ! empty( $request['date_created_gmt'] ) ) {
769
			$date_data = rest_get_date_with_gmt( $request['date_created_gmt'], true );
770
771
			if ( ! empty( $date_data ) ) {
772
				list( $prepared_review['comment_date'], $prepared_review['comment_date_gmt'] ) = $date_data;
773
			}
774
		}
775
776
		/**
777
		 * Filters a review after it is prepared for the database.
778
		 *
779
		 * Allows modification of the review right after it is prepared for the database.
780
		 *
781
		 * @since 3.5.0
782
		 * @param array           $prepared_review The prepared review data for `wp_insert_comment`.
783
		 * @param \WP_REST_Request $request         The current request.
784
		 */
785
		return apply_filters( 'woocommerce_rest_preprocess_product_review', $prepared_review, $request );
786
	}
787
788
	/**
789
	 * Prepare links for the request.
790
	 *
791
	 * @param WP_Comment $review Product review object.
792
	 * @return array Links for the given product review.
793
	 */
794
	protected function prepare_links( $review ) {
795
		$links = array(
796
			'self'       => array(
797
				'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $review->comment_ID ) ),
798
			),
799
			'collection' => array(
800
				'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ),
801
			),
802
		);
803
		if ( 0 !== (int) $review->comment_post_ID ) {
804
			$links['up'] = array(
805
				'href'       => rest_url( sprintf( '/%s/products/%d', $this->namespace, $review->comment_post_ID ) ),
806
				'embeddable' => true,
807
			);
808
		}
809
		if ( 0 !== (int) $review->user_id ) {
810
			$links['reviewer'] = array(
811
				'href'       => rest_url( 'wp/v2/users/' . $review->user_id ),
812
				'embeddable' => true,
813
			);
814
		}
815
		return $links;
816
	}
817
818
	/**
819
	 * Get the Product Review's schema, conforming to JSON Schema.
820
	 *
821
	 * @return array
822
	 */
823
	public function get_item_schema() {
824
		$schema = array(
825
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
826
			'title'      => 'product_review',
827
			'type'       => 'object',
828
			'properties' => array(
829
				'id'               => array(
830
					'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
831
					'type'        => 'integer',
832
					'context'     => array( 'view', 'edit' ),
833
					'readonly'    => true,
834
				),
835
				'date_created'     => array(
836
					'description' => __( "The date the review was created, in the site's timezone.", 'woocommerce' ),
837
					'type'        => 'date-time',
838
					'context'     => array( 'view', 'edit' ),
839
					'readonly'    => true,
840
				),
841
				'date_created_gmt' => array(
842
					'description' => __( 'The date the review was created, as GMT.', 'woocommerce' ),
843
					'type'        => 'date-time',
844
					'context'     => array( 'view', 'edit' ),
845
					'readonly'    => true,
846
				),
847
				'product_id'       => array(
848
					'description' => __( 'Unique identifier for the product that the review belongs to.', 'woocommerce' ),
849
					'type'        => 'integer',
850
					'context'     => array( 'view', 'edit' ),
851
				),
852
				'status'           => array(
853
					'description' => __( 'Status of the review.', 'woocommerce' ),
854
					'type'        => 'string',
855
					'default'     => 'approved',
856
					'enum'        => array( 'approved', 'hold', 'spam', 'unspam', 'trash', 'untrash' ),
857
					'context'     => array( 'view', 'edit' ),
858
				),
859
				'reviewer'         => array(
860
					'description' => __( 'Reviewer name.', 'woocommerce' ),
861
					'type'        => 'string',
862
					'context'     => array( 'view', 'edit' ),
863
				),
864
				'reviewer_email'   => array(
865
					'description' => __( 'Reviewer email.', 'woocommerce' ),
866
					'type'        => 'string',
867
					'format'      => 'email',
868
					'context'     => array( 'view', 'edit' ),
869
				),
870
				'review'           => array(
871
					'description' => __( 'The content of the review.', 'woocommerce' ),
872
					'type'        => 'string',
873
					'context'     => array( 'view', 'edit' ),
874
					'arg_options' => array(
875
						'sanitize_callback' => 'wp_filter_post_kses',
876
					),
877
				),
878
				'rating'           => array(
879
					'description' => __( 'Review rating (0 to 5).', 'woocommerce' ),
880
					'type'        => 'integer',
881
					'context'     => array( 'view', 'edit' ),
882
				),
883
				'verified'         => array(
884
					'description' => __( 'Shows if the reviewer bought the product or not.', 'woocommerce' ),
885
					'type'        => 'boolean',
886
					'context'     => array( 'view', 'edit' ),
887
					'readonly'    => true,
888
				),
889
			),
890
		);
891
892
		if ( get_option( 'show_avatars' ) ) {
893
			$avatar_properties = array();
894
			$avatar_sizes      = rest_get_avatar_sizes();
895
896
			foreach ( $avatar_sizes as $size ) {
897
				$avatar_properties[ $size ] = array(
898
					/* translators: %d: avatar image size in pixels */
899
					'description' => sprintf( __( 'Avatar URL with image size of %d pixels.', 'woocommerce' ), $size ),
900
					'type'        => 'string',
901
					'format'      => 'uri',
902
					'context'     => array( 'embed', 'view', 'edit' ),
903
				);
904
			}
905
			$schema['properties']['reviewer_avatar_urls'] = array(
906
				'description' => __( 'Avatar URLs for the object reviewer.', 'woocommerce' ),
907
				'type'        => 'object',
908
				'context'     => array( 'view', 'edit' ),
909
				'readonly'    => true,
910
				'properties'  => $avatar_properties,
911
			);
912
		}
913
914
		return $this->add_additional_fields_schema( $schema );
915
	}
916
917
	/**
918
	 * Get the query params for collections.
919
	 *
920
	 * @return array
921
	 */
922
	public function get_collection_params() {
923
		$params = parent::get_collection_params();
924
925
		$params['context']['default'] = 'view';
926
927
		$params['after']            = array(
928
			'description' => __( 'Limit response to resources published after a given ISO8601 compliant date.', 'woocommerce' ),
929
			'type'        => 'string',
930
			'format'      => 'date-time',
931
		);
932
		$params['before']           = array(
933
			'description' => __( 'Limit response to reviews published before a given ISO8601 compliant date.', 'woocommerce' ),
934
			'type'        => 'string',
935
			'format'      => 'date-time',
936
		);
937
		$params['exclude']          = array(
938
			'description' => __( 'Ensure result set excludes specific IDs.', 'woocommerce' ),
939
			'type'        => 'array',
940
			'items'       => array(
941
				'type' => 'integer',
942
			),
943
			'default'     => array(),
944
		);
945
		$params['include']          = array(
946
			'description' => __( 'Limit result set to specific IDs.', 'woocommerce' ),
947
			'type'        => 'array',
948
			'items'       => array(
949
				'type' => 'integer',
950
			),
951
			'default'     => array(),
952
		);
953
		$params['offset']           = array(
954
			'description' => __( 'Offset the result set by a specific number of items.', 'woocommerce' ),
955
			'type'        => 'integer',
956
		);
957
		$params['order']            = array(
958
			'description' => __( 'Order sort attribute ascending or descending.', 'woocommerce' ),
959
			'type'        => 'string',
960
			'default'     => 'desc',
961
			'enum'        => array(
962
				'asc',
963
				'desc',
964
			),
965
		);
966
		$params['orderby']          = array(
967
			'description' => __( 'Sort collection by object attribute.', 'woocommerce' ),
968
			'type'        => 'string',
969
			'default'     => 'date_gmt',
970
			'enum'        => array(
971
				'date',
972
				'date_gmt',
973
				'id',
974
				'include',
975
				'product',
976
			),
977
		);
978
		$params['reviewer']         = array(
979
			'description' => __( 'Limit result set to reviews assigned to specific user IDs.', 'woocommerce' ),
980
			'type'        => 'array',
981
			'items'       => array(
982
				'type' => 'integer',
983
			),
984
		);
985
		$params['reviewer_exclude'] = array(
986
			'description' => __( 'Ensure result set excludes reviews assigned to specific user IDs.', 'woocommerce' ),
987
			'type'        => 'array',
988
			'items'       => array(
989
				'type' => 'integer',
990
			),
991
		);
992
		$params['reviewer_email']   = array(
993
			'default'     => null,
994
			'description' => __( 'Limit result set to that from a specific author email.', 'woocommerce' ),
995
			'format'      => 'email',
996
			'type'        => 'string',
997
		);
998
		$params['product']          = array(
999
			'default'     => array(),
1000
			'description' => __( 'Limit result set to reviews assigned to specific product IDs.', 'woocommerce' ),
1001
			'type'        => 'array',
1002
			'items'       => array(
1003
				'type' => 'integer',
1004
			),
1005
		);
1006
		$params['status']           = array(
1007
			'default'           => 'approved',
1008
			'description'       => __( 'Limit result set to reviews assigned a specific status.', 'woocommerce' ),
1009
			'sanitize_callback' => 'sanitize_key',
1010
			'type'              => 'string',
1011
			'enum'              => array(
1012
				'all',
1013
				'hold',
1014
				'approved',
1015
				'spam',
1016
				'trash',
1017
			),
1018
		);
1019
1020
		/**
1021
		 * Filter collection parameters for the reviews controller.
1022
		 *
1023
		 * This filter registers the collection parameter, but does not map the
1024
		 * collection parameter to an internal \WP_Comment_Query parameter. Use the
1025
		 * `wc_rest_review_query` filter to set \WP_Comment_Query parameters.
1026
		 *
1027
		 * @since 3.5.0
1028
		 * @param array $params JSON Schema-formatted collection parameters.
1029
		 */
1030
		return apply_filters( 'woocommerce_rest_product_review_collection_params', $params );
1031
	}
1032
1033
	/**
1034
	 * Get the reivew, if the ID is valid.
1035
	 *
1036
	 * @since 3.5.0
1037
	 * @param int $id Supplied ID.
1038
	 * @return WP_Comment|\WP_Error Comment object if ID is valid, \WP_Error otherwise.
1039
	 */
1040
	protected function get_review( $id ) {
1041
		$id    = (int) $id;
1042
		$error = new \WP_Error( 'woocommerce_rest_review_invalid_id', __( 'Invalid review ID.', 'woocommerce' ), array( 'status' => 404 ) );
1043
1044
		if ( 0 >= $id ) {
1045
			return $error;
1046
		}
1047
1048
		$review = get_comment( $id );
1049
		if ( empty( $review ) ) {
1050
			return $error;
1051
		}
1052
1053
		if ( ! empty( $review->comment_post_ID ) ) {
1054
			$post = get_post( (int) $review->comment_post_ID );
1055
1056
			if ( 'product' !== get_post_type( (int) $review->comment_post_ID ) ) {
1057
				return new \WP_Error( 'woocommerce_rest_product_invalid_id', __( 'Invalid product ID.', 'woocommerce' ), array( 'status' => 404 ) );
1058
			}
1059
		}
1060
1061
		return $review;
1062
	}
1063
1064
	/**
1065
	 * Prepends internal property prefix to query parameters to match our response fields.
1066
	 *
1067
	 * @since 3.5.0
1068
	 * @param string $query_param Query parameter.
1069
	 * @return string
1070
	 */
1071
	protected function normalize_query_param( $query_param ) {
1072
		$prefix = 'comment_';
1073
1074
		switch ( $query_param ) {
1075
			case 'id':
1076
				$normalized = $prefix . 'ID';
1077
				break;
1078
			case 'product':
1079
				$normalized = $prefix . 'post_ID';
1080
				break;
1081
			case 'include':
1082
				$normalized = 'comment__in';
1083
				break;
1084
			default:
1085
				$normalized = $prefix . $query_param;
1086
				break;
1087
		}
1088
1089
		return $normalized;
1090
	}
1091
1092
	/**
1093
	 * Checks comment_approved to set comment status for single comment output.
1094
	 *
1095
	 * @since 3.5.0
1096
	 * @param string|int $comment_approved comment status.
1097
	 * @return string Comment status.
1098
	 */
1099
	protected function prepare_status_response( $comment_approved ) {
1100
		switch ( $comment_approved ) {
1101
			case 'hold':
1102
			case '0':
1103
				$status = 'hold';
1104
				break;
1105
			case 'approve':
1106
			case '1':
1107
				$status = 'approved';
1108
				break;
1109
			case 'spam':
1110
			case 'trash':
1111
			default:
1112
				$status = $comment_approved;
1113
				break;
1114
		}
1115
1116
		return $status;
1117
	}
1118
1119
	/**
1120
	 * Sets the comment_status of a given review object when creating or updating a review.
1121
	 *
1122
	 * @since 3.5.0
1123
	 * @param string|int $new_status New review status.
1124
	 * @param int        $id         Review ID.
1125
	 * @return bool Whether the status was changed.
1126
	 */
1127
	protected function handle_status_param( $new_status, $id ) {
1128
		$old_status = wp_get_comment_status( $id );
1129
1130
		if ( $new_status === $old_status ) {
1131
			return false;
1132
		}
1133
1134
		switch ( $new_status ) {
1135
			case 'approved':
1136
			case 'approve':
1137
			case '1':
1138
				$changed = wp_set_comment_status( $id, 'approve' );
1139
				break;
1140
			case 'hold':
1141
			case '0':
1142
				$changed = wp_set_comment_status( $id, 'hold' );
1143
				break;
1144
			case 'spam':
1145
				$changed = wp_spam_comment( $id );
1146
				break;
1147
			case 'unspam':
1148
				$changed = wp_unspam_comment( $id );
1149
				break;
1150
			case 'trash':
1151
				$changed = wp_trash_comment( $id );
1152
				break;
1153
			case 'untrash':
1154
				$changed = wp_untrash_comment( $id );
1155
				break;
1156
			default:
1157
				$changed = false;
1158
				break;
1159
		}
1160
1161
		return $changed;
1162
	}
1163
}
1164