Passed
Push — master ( 0d4ea9...6fa460 )
by Paul
18:46 queued 09:18
created

ReviewController   C

Complexity

Total Complexity 53

Size/Duplication

Total Lines 395
Duplicated Lines 0 %

Test Coverage

Coverage 30.23%

Importance

Changes 6
Bugs 3 Features 0
Metric Value
wmc 53
eloc 142
c 6
b 3
f 0
dl 0
loc 395
ccs 52
cts 172
cp 0.3023
rs 6.96

22 Methods

Rating   Name   Duplication   Size   Complexity  
A onChangeAssignedUsers() 0 5 1
A onDeleteReview() 0 3 1
A onAfterChangeAssignedTerms() 0 7 2
A onAfterChangeStatus() 0 15 5
A onChangeAssignedPosts() 0 5 1
A filterPostsToCacheReviews() 0 9 2
A filterReviewTemplate() 0 6 1
A filterSqlClauseOperator() 0 4 1
A onEditReview() 0 10 4
A isEditedReview() 0 10 4
A bulkUpdateReview() 0 10 3
A onCreatedReview() 0 4 1
A getAssignedDiffs() 0 13 2
A onDeleteUser() 0 10 2
A sendNotification() 0 4 2
A filterTemplateTags() 0 4 1
A onCreateReview() 0 24 5
A unapprove() 0 10 2
A filterReviewPostData() 0 12 6
A approve() 0 10 2
A onDeletePost() 0 14 3
A updateReview() 0 18 2

How to fix   Complexity   

Complex Class

Complex classes like ReviewController 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 ReviewController, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace GeminiLabs\SiteReviews\Controllers;
4
5
use GeminiLabs\SiteReviews\Commands\AssignPosts;
6
use GeminiLabs\SiteReviews\Commands\AssignTerms;
7
use GeminiLabs\SiteReviews\Commands\AssignUsers;
8
use GeminiLabs\SiteReviews\Commands\CreateReview;
9
use GeminiLabs\SiteReviews\Commands\ToggleStatus;
10
use GeminiLabs\SiteReviews\Commands\UnassignPosts;
11
use GeminiLabs\SiteReviews\Commands\UnassignTerms;
12
use GeminiLabs\SiteReviews\Commands\UnassignUsers;
13
use GeminiLabs\SiteReviews\Database;
14
use GeminiLabs\SiteReviews\Database\Cache;
15
use GeminiLabs\SiteReviews\Database\CountManager;
16
use GeminiLabs\SiteReviews\Database\Query;
17
use GeminiLabs\SiteReviews\Database\ReviewManager;
18
use GeminiLabs\SiteReviews\Defaults\RatingDefaults;
19
use GeminiLabs\SiteReviews\Helper;
20
use GeminiLabs\SiteReviews\Helpers\Arr;
21
use GeminiLabs\SiteReviews\Helpers\Cast;
22
use GeminiLabs\SiteReviews\Metaboxes\ResponseMetabox;
23
use GeminiLabs\SiteReviews\Modules\Html\ReviewHtml;
24
use GeminiLabs\SiteReviews\Modules\Queue;
25
use GeminiLabs\SiteReviews\Review;
26
27
class ReviewController extends Controller
28
{
29
    /**
30
     * @return void
31
     * @action admin_action_approve
32
     */
33
    public function approve()
34
    {
35
        if (glsr()->id == filter_input(INPUT_GET, 'plugin')) {
36
            check_admin_referer('approve-review_'.($postId = $this->getPostId()));
37
            $this->execute(new ToggleStatus([
38
                'id' => $postId,
39
                'status' => 'publish',
40
            ]));
41
            wp_safe_redirect(wp_get_referer());
42
            exit;
43
        }
44
    }
45
46
    /**
47
     * @param array $posts
48
     * @return array
49
     * @filter the_posts
50
     */
51
    public function filterPostsToCacheReviews($posts)
52
    {
53
        $reviews = array_filter($posts, function ($post) {
54
            return glsr()->post_type === $post->post_type;
55
        });
56
        if ($postIds = wp_list_pluck($reviews, 'ID')) {
57
            glsr(Query::class)->reviews([], $postIds); // this caches the associated Review objects
58
        }
59
        return $posts;
60
    }
61
62
    /**
63
     * @param array $data
64
     * @param array $postArray
65
     * @return array
66
     * @filter wp_insert_post_data
67
     */
68 17
    public function filterReviewPostData($data, $postArray)
69
    {
70 17
        if (empty($postArray['ID']) || empty($postArray['action']) || glsr()->post_type !== glsr_get($postArray, 'post_type')) {
71 17
            return $data;
72
        }
73
        if (empty(filter_input(INPUT_POST, 'post_author'))) {
74
            $data['post_author'] = 0; // the review has an unknown author
75
        }
76
        if (isset($_POST['post_author_override'])) {
77
            $data['post_author'] = $_POST['post_author_override']; // use the value from the author meta box
78
        }
79
        return $data;
80
    }
81
82
    /**
83
     * @param string $template
84
     * @return string
85
     * @filter site-reviews/rendered/template/review
86
     */
87 7
    public function filterReviewTemplate($template, array $data)
88
    {
89 7
        $search = 'id="review-';
90 7
        $dataType = Arr::get($data, 'review.type', 'local');
91 7
        $replace = sprintf('data-type="%s" %s', $dataType, $search);
92 7
        return str_replace($search, $replace, $template);
93
    }
94
95
    /**
96
     * @param string $operator
97
     * @return string
98
     * @filter site-reviews/query/sql/clause/operator
99
     */
100 7
    public function filterSqlClauseOperator($operator)
101
    {
102 7
        $operators = ['loose' => 'OR', 'strict' => 'AND'];
103 7
        return Arr::get($operators, glsr_get_option('reviews.assignment', 'strict', 'string'), $operator);
104
    }
105
106
    /**
107
     * @return array
108
     * @filter site-reviews/review/build/after
109
     */
110 7
    public function filterTemplateTags(array $tags, Review $review, ReviewHtml $reviewHtml)
111
    {
112 7
        $tags['assigned_links'] = $reviewHtml->buildTemplateTag($review, 'assigned_links', $review->assigned_posts);
113 7
        return $tags;
114
    }
115
116
    /**
117
     * Triggered when one or more categories are added or removed from a review.
118
     *
119
     * @param int $postId
120
     * @param array $terms
121
     * @param array $newTTIds
122
     * @param string $taxonomy
123
     * @param bool $append
124
     * @param array $oldTTIds
125
     * @return void
126
     * @action set_object_terms
127
     */
128 17
    public function onAfterChangeAssignedTerms($postId, $terms, $newTTIds, $taxonomy, $append, $oldTTIds)
129
    {
130 17
        if (Review::isReview($postId)) {
131 14
            $review = glsr(Query::class)->review($postId);
132 14
            $diff = $this->getAssignedDiffs($oldTTIds, $newTTIds);
133 14
            $this->execute(new UnassignTerms($review, $diff['old']));
134 14
            $this->execute(new AssignTerms($review, $diff['new']));
135
        }
136 17
    }
137
138
    /**
139
     * Triggered when a post status changes or when a review is approved|unapproved|trashed.
140
     *
141
     * @param string $oldStatus
142
     * @param string $newStatus
143
     * @param \WP_Post $post
144
     * @return void
145
     * @action transition_post_status
146
     */
147 17
    public function onAfterChangeStatus($newStatus, $oldStatus, $post)
148
    {
149 17
        if (in_array($oldStatus, ['new', $newStatus])) {
150 17
            return;
151
        }
152
        if ('auto-draft' === $oldStatus && 'auto-draft' !== $newStatus) { // create review
153
            glsr(ReviewManager::class)->createFromPost($post->ID);
154
        }
155
        if (Review::isReview($post)) {
156
            $isPublished = 'publish' === $newStatus;
157
            glsr(ReviewManager::class)->updateRating($post->ID, ['is_approved' => $isPublished]);
158
            glsr(Cache::class)->delete($post->ID, 'reviews');
159
            glsr(CountManager::class)->recalculate();
160
        } else {
161
            glsr(ReviewManager::class)->updateAssignedPost($post->ID);
162
        }
163
    }
164
165
    /**
166
     * Triggered when a review's assigned post IDs are updated.
167
     *
168
     * @return void
169
     * @action site-reviews/review/updated/post_ids
170
     */
171
    public function onChangeAssignedPosts(Review $review, array $postIds = [])
172
    {
173
        $diff = $this->getAssignedDiffs($review->assigned_posts, $postIds);
174
        $this->execute(new UnassignPosts($review, $diff['old']));
175
        $this->execute(new AssignPosts($review, $diff['new']));
176
    }
177
178
    /**
179
     * Triggered when a review's assigned users IDs are updated.
180
     *
181
     * @return void
182
     * @action site-reviews/review/updated/user_ids
183
     */
184
    public function onChangeAssignedUsers(Review $review, array $userIds = [])
185
    {
186
        $diff = $this->getAssignedDiffs($review->assigned_users, $userIds);
187
        $this->execute(new UnassignUsers($review, $diff['old']));
188
        $this->execute(new AssignUsers($review, $diff['new']));
189
    }
190
191
    /**
192
     * Triggered after a review is created.
193
     *
194
     * @return void
195
     * @action site-reviews/review/created
196
     */
197 14
    public function onCreatedReview(Review $review, CreateReview $command)
198
    {
199 14
        $this->execute(new AssignPosts($review, $command->assigned_posts));
200 14
        $this->execute(new AssignUsers($review, $command->assigned_users));
201 14
    }
202
203
    /**
204
     * Triggered when a review is created.
205
     *
206
     * @param int $postId
207
     * @return void
208
     * @action site-reviews/review/create
209
     */
210 14
    public function onCreateReview($postId, CreateReview $command)
211
    {
212 14
        $values = glsr()->args($command->toArray()); // this filters the values
213 14
        $data = glsr(RatingDefaults::class)->restrict($values->toArray());
214 14
        $data['review_id'] = $postId;
215 14
        $data['is_approved'] = 'publish' === get_post_status($postId);
216 14
        if (false === glsr(Database::class)->insert('ratings', $data)) {
217
            glsr_log()->error(sprintf('A review could not be created. Here are some things to try which may fix the problem: %s %s %s',
218
                PHP_EOL.'1. First, run the "Repair Review Relations" tool.',
219
                PHP_EOL.'2. Next, hold down the ALT key and run the Migrate Plugin tool.',
220
                PHP_EOL.'3. If the problem persists, please use the "Contact Support" section on the Help page.'
221
            ))->debug($data);
222
            wp_delete_post($postId, true); // remove post as review was not created
223
            return;
224
        }
225 14
        $termIds = wp_set_object_terms($postId, $values->assigned_terms, glsr()->taxonomy);
226 14
        if (is_wp_error($termIds)) {
227
            glsr_log()->error($termIds->get_error_message());
228
        }
229 14
        if (!empty($values->response)) {
230
            glsr(Database::class)->metaSet($postId, 'response', $values->response); // save the response if one is provided
231
        }
232 14
        foreach ($values->custom as $key => $value) {
233
            glsr(Database::class)->metaSet($postId, 'custom_'.$key, $value);
234
        }
235 14
    }
236
237
    /**
238
     * Triggered when a review or other post type is deleted and the posts table uses the MyISAM engine.
239
     *
240
     * @param int $postId
241
     * @param \WP_Post $post
242
     * @return void
243
     * @action deleted_post
244
     */
245
    public function onDeletePost($postId, $post)
246
    {
247
        if (glsr()->post_type === $post->post_type) {
248
            $this->onDeleteReview($postId);
249
            return;
250
        }
251
        $reviews = glsr(Query::class)->reviews([
252
            'assigned_posts' => $postId,
253
            'per_page' => -1,
254
            'status' => 'all',
255
        ]);
256
        if (glsr(Database::class)->delete('assigned_posts', ['post_id' => $postId])) {
257
            array_walk($reviews, function ($review) {
258
                glsr(Cache::class)->delete($review->ID, 'reviews');
259
            });
260
        }
261
    }
262
263
    /**
264
     * Triggered when a review is deleted and the posts table uses the MyISAM engine.
265
     *
266
     * @param int $reviewId
267
     * @return void
268
     * @see $this->onDeletePost()
269
     */
270
    public function onDeleteReview($reviewId)
271
    {
272
        glsr(ReviewManager::class)->delete($reviewId);
273
    }
274
275
    /**
276
     * Triggered when a user is deleted and the users table uses the MyISAM engine.
277
     *
278
     * @param int $userId
279
     * @return void
280
     * @action deleted_user
281
     */
282
    public function onDeleteUser($userId)
283
    {
284
        $reviews = glsr(Query::class)->reviews([
285
            'assigned_users' => $userId,
286
            'per_page' => -1,
287
            'status' => 'all',
288
        ]);
289
        if (glsr(Database::class)->delete('assigned_users', ['user_id' => $userId])) {
290
            array_walk($reviews, function ($review) {
291
                glsr(Cache::class)->delete($review->ID, 'reviews');
292
            });
293
        }
294
    }
295
296
    /**
297
     * Triggered when a review is edited or trashed.
298
     * It's unnecessary to trigger a term recount as this is done by the set_object_terms hook
299
     * We need to use "edit_post" to support revisions (vs "save_post").
300
     *
301
     * @param int $postId
302
     * @param \WP_Post $post
303
     * @param \WP_Post $oldPost
304
     * @return void
305
     * @action post_updated
306
     */
307
    public function onEditReview($postId, $post, $oldPost)
308
    {
309
        if (!glsr()->can('edit_posts') || !$this->isEditedReview($post, $oldPost)) {
310
            return;
311
        }
312
        $review = glsr(Query::class)->review($postId);
313
        if ('post' === glsr_current_screen()->base) {
314
            $this->updateReview($review);
315
        } else {
316
            $this->bulkUpdateReview($review);
317
        }
318
    }
319
320
    /**
321
     * Triggered after a review is created.
322
     *
323
     * @return void
324
     * @action site-reviews/review/created
325
     */
326 14
    public function sendNotification(Review $review)
327
    {
328 14
        if (!empty(glsr_get_option('general.notifications'))) {
329
            glsr(Queue::class)->async('queue/notification', ['review_id' => $review->ID]);
330
        }
331 14
    }
332
333
    /**
334
     * @return void
335
     * @action admin_action_unapprove
336
     */
337
    public function unapprove()
338
    {
339
        if (glsr()->id == filter_input(INPUT_GET, 'plugin')) {
340
            check_admin_referer('unapprove-review_'.($postId = $this->getPostId()));
341
            $this->execute(new ToggleStatus([
342
                'id' => $postId,
343
                'status' => 'pending',
344
            ]));
345
            wp_safe_redirect(wp_get_referer());
346
            exit;
347
        }
348
    }
349
350
    /**
351
     * @return void
352
     */
353
    protected function bulkUpdateReview(Review $review)
354
    {
355
        if ($assignedPostIds = filter_input(INPUT_GET, 'post_ids', FILTER_SANITIZE_NUMBER_INT, FILTER_FORCE_ARRAY)) {
356
            glsr()->action('review/updated/post_ids', $review, Cast::toArray($assignedPostIds)); // trigger a recount of assigned posts
357
        }
358
        if ($assignedUserIds = filter_input(INPUT_GET, 'user_ids', FILTER_SANITIZE_NUMBER_INT, FILTER_FORCE_ARRAY)) {
359
            glsr()->action('review/updated/user_ids', $review, Cast::toArray($assignedUserIds)); // trigger a recount of assigned users
360
        }
361
        $review = glsr(Query::class)->review($review->ID); // get a fresh copy of the review
362
        glsr()->action('review/saved', $review, []); // pass an empty array since review values are unchanged
363
    }
364
365
    /**
366
     * @return array
367
     */
368 14
    protected function getAssignedDiffs(array $existing, array $replacements)
369
    {
370 14
        sort($existing);
371 14
        sort($replacements);
372 14
        $new = $old = [];
373 14
        if ($existing !== $replacements) {
374 2
            $ignored = array_intersect($existing, $replacements);
375 2
            $new = array_diff($replacements, $ignored);
376 2
            $old = array_diff($existing, $ignored);
377
        }
378
        return [
379 14
            'new' => $new,
380 14
            'old' => $old,
381
        ];
382
    }
383
384
    /**
385
     * @param \WP_Post $post
386
     * @param \WP_Post $oldPost
387
     * @return bool
388
     */
389
    protected function isEditedReview($post, $oldPost)
390
    {
391
        if (glsr()->post_type !== $post->post_type) {
392
            return false;
393
        }
394
        if (in_array('trash', [$post->post_status, $oldPost->post_status])) {
395
            return false; // trashed posts cannot be edited
396
        }
397
        $input = 'edit' === glsr_current_screen()->base ? INPUT_GET : INPUT_POST;
398
        return 'glsr_action' !== filter_input($input, 'action'); // abort if not a proper post update (i.e. approve/unapprove)
399
    }
400
401
    /**
402
     * @return void
403
     */
404
    protected function updateReview(Review $review)
405
    {
406
        $assignedPostIds = filter_input(INPUT_POST, 'post_ids', FILTER_SANITIZE_NUMBER_INT, FILTER_FORCE_ARRAY);
407
        $assignedUserIds = filter_input(INPUT_POST, 'user_ids', FILTER_SANITIZE_NUMBER_INT, FILTER_FORCE_ARRAY);
408
        glsr()->action('review/updated/post_ids', $review, Cast::toArray($assignedPostIds)); // trigger a recount of assigned posts
409
        glsr()->action('review/updated/user_ids', $review, Cast::toArray($assignedUserIds)); // trigger a recount of assigned users
410
        glsr(ResponseMetabox::class)->save($review);
411
        $customDefaults = array_fill_keys(array_keys($review->custom()->toArray()), '');
412
        $submittedValues = Helper::filterInputArray(glsr()->id);
413
        $submittedValues = wp_parse_args($submittedValues, $customDefaults); // this ensures we save all empty custom values
414
        if (Arr::get($submittedValues, 'is_editing_review')) {
415
            $submittedValues['rating'] = Arr::get($submittedValues, 'rating');
416
            $submittedValues['terms'] = Arr::get($submittedValues, 'terms', 0);
417
            glsr(ReviewManager::class)->updateRating($review->ID, $submittedValues); // values are sanitized here
418
            glsr(ReviewManager::class)->updateCustom($review->ID, $submittedValues); // values are sanitized here
419
        }
420
        $review = glsr(Query::class)->review($review->ID); // get a fresh copy of the review
421
        glsr()->action('review/saved', $review, $submittedValues);
422
    }
423
}
424