Passed
Push — master ( 18d41c...f9af5f )
by Paul
13:52 queued 07:13
created

ReviewController   B

Complexity

Total Complexity 49

Size/Duplication

Total Lines 367
Duplicated Lines 0 %

Test Coverage

Coverage 31.41%

Importance

Changes 4
Bugs 1 Features 0
Metric Value
wmc 49
eloc 128
c 4
b 1
f 0
dl 0
loc 367
ccs 49
cts 156
cp 0.3141
rs 8.48

21 Methods

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

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