Passed
Push — master ( 5fbab0...5feb54 )
by Paul
08:33
created

ReviewController::onCreatedReview()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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