ProductReviewsController   D
last analyzed

Complexity

Total Complexity 59

Size/Duplication

Total Lines 433
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 59
eloc 214
dl 0
loc 433
ccs 0
cts 243
cp 0
rs 4.08
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
A batch_items_permissions_check() 0 3 1
A create_item() 0 24 6
B delete_item() 0 33 6
A create_item_permissions_check() 0 3 1
A get_item_permissions_check() 0 3 1
A delete_item_permissions_check() 0 3 1
A prepare_links() 0 23 3
A get_items_permissions_check() 0 3 1
A prepare_item_for_response() 0 30 4
A prepare_item_for_database() 0 17 3
A checkPermissionForProductReview() 0 20 4
A get_review() 0 13 4
A checkPermissions() 0 24 4
A prepare_status_response() 0 9 3
A update_item_permissions_check() 0 3 1
A prepare_item_for_update() 0 30 4
B get_items() 0 36 7
A update_item() 0 27 5

How to fix   Complexity   

Complex Class

Complex classes like ProductReviewsController often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ProductReviewsController, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace GeminiLabs\SiteReviews\Integrations\WooCommerce\Controllers\RestApi;
4
5
use Automattic\WooCommerce\StoreApi\Utilities\Pagination;
6
use GeminiLabs\SiteReviews\Database\ReviewManager;
7
use GeminiLabs\SiteReviews\Helpers\Arr;
8
use GeminiLabs\SiteReviews\Review;
9
10
class ProductReviewsController extends \WC_REST_Product_Reviews_Controller
11
{
12
    /**
13
     * @param \WP_REST_Request $request
14
     *
15
     * @return \WP_Error|bool
16
     */
17
    public function batch_items_permissions_check($request)
18
    {
19
        return $this->checkPermissions('batch', $request);
20
    }
21
22
    /**
23
     * @param \WP_REST_Request $request
24
     *
25
     * @return \WP_Error|\WP_REST_Response
26
     */
27
    public function create_item($request)
28
    {
29
        if ('product' !== get_post_type($request['product_id'])) {
30
            return new \WP_Error('woocommerce_rest_product_invalid_id', __('Invalid product ID.', 'woocommerce'), ['status' => 404]);
31
        }
32
        $args = $this->prepare_item_for_database($request);
33
        if (empty($args['content'])) {
34
            return new \WP_Error('woocommerce_rest_review_content_invalid', __('Invalid review content.', 'woocommerce'), ['status' => 400]);
35
        }
36
        if ($review = glsr_create_review($args)) {
37
            glsr()->action('woocommerce/rest-api/insert_product_review', $review, $request, true);
38
            $updateAdditionalFields = $this->update_additional_fields_for_object($review, $request);
39
            if (is_wp_error($updateAdditionalFields)) {
40
                return $updateAdditionalFields;
41
            }
42
            $context = glsr()->can('edit_posts') ? 'edit' : 'view';
43
            $request->set_param('context', $context);
44
            $response = $this->prepare_item_for_response($review, $request);
45
            $response = rest_ensure_response($response);
46
            $response->set_status(201);
47
            $response->header('Location', rest_url(sprintf('%s/%s/%d', $this->namespace, $this->rest_base, $review->ID)));
48
            return $response;
49
        }
50
        return new \WP_Error('woocommerce_rest_review_failed_create', __('Creating product review failed.', 'woocommerce'), ['status' => 500]);
51
    }
52
53
    /**
54
     * @param \WP_REST_Request $request
55
     *
56
     * @return \WP_Error|bool
57
     */
58
    public function create_item_permissions_check($request)
59
    {
60
        return $this->checkPermissions('create', $request);
61
    }
62
63
    /**
64
     * @param \WP_REST_Request $request
65
     *
66
     * @return \WP_Error|\WP_REST_Response
67
     */
68
    public function delete_item($request)
69
    {
70
        $review = $this->get_review($request['id']);
71
        if (is_wp_error($review)) {
72
            return $review;
73
        }
74
        $force = Arr::getAs('bool', $request, 'force', false);
75
        $supportsTrash = apply_filters('woocommerce_rest_product_review_trashable', EMPTY_TRASH_DAYS > 0, $review);
76
        $request->set_param('context', 'edit');
77
        if ($force) {
78
            $previous = $this->prepare_item_for_response($review, $request);
79
            $result = wp_delete_post($review->ID, $force);
80
            $response = new \WP_REST_Response();
81
            $response->set_data([
82
                'deleted' => true,
83
                'previous' => $previous->get_data(),
84
            ]);
85
        } else {
86
            if (!$supportsTrash) {
87
                return new \WP_Error('woocommerce_rest_trash_not_supported', sprintf(__("The object does not support trashing. Set '%s' to delete.", 'woocommerce'), 'force=true'), ['status' => 501]);
88
            }
89
            if ('trash' === $review->status) {
90
                return new \WP_Error('woocommerce_rest_already_trashed', __('The object has already been trashed.', 'woocommerce'), ['status' => 410]);
91
            }
92
            $result = wp_trash_post($review->ID);
93
            $review->refresh(); // refresh the review!
94
            $response = $this->prepare_item_for_response($review, $request);
95
        }
96
        if (!$result) {
97
            return new \WP_Error('woocommerce_rest_cannot_delete', __('The object cannot be deleted.', 'woocommerce'), ['status' => 500]);
98
        }
99
        glsr()->action('woocommerce/rest-api/delete_review', $review, $response, $request);
100
        return $response;
101
    }
102
103
    /**
104
     * @param \WP_REST_Request $request
105
     *
106
     * @return \WP_Error|bool
107
     */
108
    public function delete_item_permissions_check($request)
109
    {
110
        return $this->checkPermissions('delete', $request);
111
    }
112
113
    /**
114
     * @param \WP_REST_Request $request
115
     *
116
     * @return \WP_Error|bool
117
     */
118
    public function get_item_permissions_check($request)
119
    {
120
        return $this->checkPermissions('read', $request);
121
    }
122
123
    /**
124
     * @param \WP_REST_Request $request
125
     *
126
     * @return \WP_Error|\WP_REST_Response
127
     */
128
    public function get_items($request)
129
    {
130
        $registered = $this->get_collection_params(); // @todo support (hold, spam, trash) post_status
131
        $mappedKeys = [
132
            'after' => 'date_after', // string
133
            'before' => 'date_before', // string
134
            'exclude' => 'post__not_in', // int[]
135
            'include' => 'post__in', // int[]
136
            'product' => 'assigned_posts', // int[]
137
            'reviewer' => 'user__in', // int[]
138
            'reviewer_email' => 'email', // string
139
            'reviewer_exclude' => 'user__not_in', // int[]
140
        ];
141
        $args = [];
142
        foreach ($registered as $key => $params) {
143
            if (isset($request[$key])) {
144
                $mappedKey = Arr::get($mappedKeys, $key, $key);
145
                $args[$mappedKey] = $request[$key];
146
            }
147
        }
148
        if (empty($args['assigned_posts'])) {
149
            $args['assigned_posts_types'] = ['product'];
150
        }
151
        $results = glsr_get_reviews($args); // @todo only return product reviews!
152
        $reviews = [];
153
        foreach ($results->reviews as $review) {
154
            if ($this->checkPermissionForProductReview('read', $review->ID)) {
155
                $data = $this->prepare_item_for_response($review, $request);
156
                $reviews[] = $this->prepare_response_for_collection($data);
157
            }
158
        }
159
        $response = rest_ensure_response($reviews);
160
        if (!is_wp_error($response)) {
161
            $response = (new Pagination())->add_headers($response, $request, $results->total, $results->max_num_pages);
162
        }
163
        return $response;
164
    }
165
166
    /**
167
     * @param \WP_REST_Request $request
168
     *
169
     * @return \WP_Error|bool
170
     */
171
    public function get_items_permissions_check($request)
172
    {
173
        return $this->checkPermissions('list', $request);
174
    }
175
176
    /**
177
     * @param Review           $review
178
     * @param \WP_REST_Request $request
179
     *
180
     * @return \WP_REST_Response $response
181
     */
182
    public function prepare_item_for_response($review, $request) // @phpstan-ignore-line
183
    {
184
        $context = Arr::get($request, 'context', 'view');
185
        $fields = $this->get_fields_for_response($request);
186
        $productId = Arr::getAs('int', $review->assigned_posts, 0);
187
        $data = [
188
            'id' => $review->ID,
189
            'date_created' => wc_rest_prepare_date_response($review->date),
190
            'date_created_gmt' => wc_rest_prepare_date_response($review->date_gmt),
191
            'product_id' => $productId,
192
            'product_name' => get_the_title($productId),
193
            'product_permalink' => get_permalink($productId),
194
            'status' => $this->prepare_status_response($review->status),
195
            'reviewer' => $review->author,
196
            'reviewer_email' => $review->email,
197
            'review' => 'view' === $context ? wpautop($review->content) : $review->content,
198
            'rating' => $review->rating,
199
            'verified' => $review->hasVerifiedOwner(), // @phpstan-ignore-line
200
            'reviewer_avatar_urls' => rest_get_avatar_urls($review->email),
201
        ];
202
        foreach ($data as $key => $value) {
203
            if (!in_array($key, $fields, true)) {
204
                unset($data[$key]);
205
            }
206
        }
207
        $data = $this->add_additional_fields_to_object($data, $request);
208
        $data = $this->filter_response_by_context($data, $context);
209
        $response = rest_ensure_response($data);
210
        $response->add_links($this->prepare_links($review));
211
        return glsr()->filter('woocommerce/rest-api/prepare_product_review', $response, $review, $request);
212
    }
213
214
    /**
215
     * @param \WP_REST_Request $request
216
     *
217
     * @return \WP_Error|\WP_REST_Response
218
     */
219
    public function update_item($request)
220
    {
221
        $review = $this->get_review($request['id']);
222
        if (is_wp_error($review)) {
223
            return $review;
224
        }
225
        // update review post
226
        $postArgs = $this->prepare_item_for_update($request);
227
        if (is_wp_error($postArgs)) {
228
            return $postArgs;
229
        }
230
        $postArgs['ID'] = $review->ID;
231
        $updatePost = wp_update_post($postArgs, $wperror = true);
232
        if (is_wp_error($updatePost)) {
233
            return new \WP_Error('woocommerce_rest_comment_failed_edit', __('Updating review failed.', 'woocommerce'), ['status' => 500]);
234
        }
235
        // update rating entry
236
        glsr(ReviewManager::class)->update($review->ID, $this->prepare_item_for_database($request));
237
        $review->refresh(); // refresh the review!
238
        glsr()->action('woocommerce/rest-api/insert_product_review', $review, $request, false);
239
        $updateAdditionalFields = $this->update_additional_fields_for_object($review, $request);
240
        if (is_wp_error($updateAdditionalFields)) {
241
            return $updateAdditionalFields;
242
        }
243
        $request->set_param('context', 'edit');
244
        $response = $this->prepare_item_for_response($review, $request);
245
        return rest_ensure_response($response);
246
    }
247
248
    /**
249
     * @param \WP_REST_Request $request
250
     *
251
     * @return \WP_Error|bool
252
     */
253
    public function update_item_permissions_check($request)
254
    {
255
        return $this->checkPermissions('edit', $request);
256
    }
257
258
    /**
259
     * @param string $context
260
     * @param int    $reviewId
261
     *
262
     * @return bool
263
     */
264
    protected function checkPermissionForProductReview($context = 'read', $reviewId = 0)
265
    {
266
        $contexts = [
267
            'batch' => 'edit_post',
268
            'create' => 'create_post',
269
            'delete' => 'delete_post',
270
            'edit' => 'edit_post',
271
            'read' => 'edit_post',
272
            'list' => 'edit_post',
273
        ];
274
        if (!isset($contexts[$context])) {
275
            return false;
276
        }
277
        if ($reviewId > 0) {
278
            if (!glsr_get_review($reviewId)->isValid()) {
279
                return false;
280
            }
281
            return glsr()->can($contexts[$context], $reviewId);
282
        }
283
        return glsr()->can($contexts[$context].'s');
284
    }
285
286
    /**
287
     * @param string           $action
288
     * @param \WP_REST_Request $request
289
     *
290
     * @return bool|\WP_Error
291
     */
292
    protected function checkPermissions($action, $request)
293
    {
294
        $errors = [
295
            'batch' => ['woocommerce_rest_cannot_batch', __('Sorry, you are not allowed to batch manipulate this resource.', 'woocommerce')],
296
            'create' => ['woocommerce_rest_cannot_create', __('Sorry, you are not allowed to create resources.', 'woocommerce')],
297
            'delete' => ['woocommerce_rest_cannot_delete', __('Sorry, you cannot delete this resource.', 'woocommerce')],
298
            'edit' => ['woocommerce_rest_cannot_edit', __('Sorry, you cannot edit this resource.', 'woocommerce')],
299
            'list' => ['woocommerce_rest_cannot_view', __('Sorry, you cannot list resources.', 'woocommerce')],
300
            'read' => ['woocommerce_rest_cannot_view', __('Sorry, you cannot view this resource.', 'woocommerce')],
301
        ];
302
        if (in_array($action, ['delete', 'edit', 'read'])) {
303
            $hasPermission = $this->checkPermissionForProductReview($action, $request['id']);
304
        } else {
305
            $hasPermission = $this->checkPermissionForProductReview($action);
306
        }
307
        if ($hasPermission) {
308
            return true;
309
        }
310
        if (array_key_exists($action, $errors)) {
311
            return new \WP_Error($errors[$action][0], $errors[$action][1], [
312
                'status' => rest_authorization_required_code(),
313
            ]);
314
        }
315
        return false;
316
    }
317
318
    /**
319
     * @param int $id
320
     *
321
     * @return Review|\WP_Error
322
     */
323
    protected function get_review($id)
324
    {
325
        $review = glsr_get_review($id);
326
        $error = new \WP_Error('woocommerce_rest_review_invalid_id', __('Invalid review ID.', 'woocommerce'), ['status' => 404]);
327
        if (!$review->isValid()) {
328
            return $error;
329
        }
330
        if (!empty($review->assigned_posts)) {
331
            if ('product' !== get_post_type((int) Arr::get($review->assigned_posts, 0))) {
332
                return new \WP_Error('woocommerce_rest_product_invalid_id', __('Invalid product ID.', 'woocommerce'), ['status' => 404]);
333
            }
334
        }
335
        return $review;
336
    }
337
338
    /**
339
     * @param \WP_REST_Request $request
340
     *
341
     * @return array
342
     */
343
    protected function prepare_item_for_database($request)
344
    {
345
        $mappedKeys = [
346
            'date_created' => 'date',
347
            'product_id' => 'assigned_posts',
348
            'rating' => 'rating',
349
            'review' => 'content',
350
            'reviewer' => 'name',
351
            'reviewer_email' => 'email',
352
        ];
353
        $args = [];
354
        foreach ($mappedKeys as $requestKey => $key) {
355
            if (isset($request[$requestKey])) {
356
                $args[$key] = $request[$requestKey];
357
            }
358
        }
359
        return $args;
360
    }
361
362
    /**
363
     * @param \WP_REST_Request $request
364
     *
365
     * @return array|\WP_Error
366
     */
367
    protected function prepare_item_for_update($request)
368
    {
369
        $mappedKeys = [
370
            'content' => 'post_content',
371
            'status' => 'post_status',
372
            'title' => 'post_title',
373
        ];
374
        $status = [
375
            '0' => 'pending',
376
            '1' => 'publish',
377
            'approve' => 'publish',
378
            'approved' => 'publish',
379
            'draft' => 'draft',
380
            'hold' => 'pending',
381
            'pending' => 'pending',
382
            'publish' => 'publish',
383
            'unapprove' => 'pending',
384
            'unapproved' => 'pending',
385
        ];
386
        $args = [];
387
        foreach ($mappedKeys as $requestKey => $key) {
388
            if ('status' === $requestKey) {
389
                $args[$key] = Arr::get($status, $request[$requestKey]);
390
                continue;
391
            }
392
            if (isset($request[$requestKey])) {
393
                $args[$key] = $request[$requestKey];
394
            }
395
        }
396
        return array_filter($args);
397
    }
398
399
    /**
400
     * @param Review $review
401
     *
402
     * @return array
403
     */
404
    protected function prepare_links($review) // @phpstan-ignore-line
405
    {
406
        $links = [
407
            'self' => [
408
                'href' => rest_url("/{$this->namespace}/{$this->rest_base}/{$review->ID}"),
409
            ],
410
            'collection' => [
411
                'href' => rest_url("/{$this->namespace}/{$this->rest_base}"),
412
            ],
413
        ];
414
        if (!empty($review->assigned_posts)) {
415
            $postId = Arr::get($review->assigned_posts, 0);
416
            $links['up'] = [
417
                'href' => rest_url("/{$this->namespace}/products/{$postId}"),
418
            ];
419
        }
420
        if (0 !== $review->author_id) {
421
            $links['reviewer'] = [
422
                'href' => rest_url("wp/v2/users/{$review->author_id}"),
423
                'embeddable' => true,
424
            ];
425
        }
426
        return $links;
427
    }
428
429
    /**
430
     * @param string|int $status
431
     *
432
     * @return string
433
     */
434
    protected function prepare_status_response($status)
435
    {
436
        if (in_array($status, ['0', 'draft', 'hold', 'pending', 'unapprove', 'unapproved'])) {
437
            return 'hold';
438
        }
439
        if (in_array($status, ['1', 'approve', 'approved', 'publish'])) {
440
            return 'approved';
441
        }
442
        return $status;
443
    }
444
}
445