ReviewManager::assignPost()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 4

Importance

Changes 0
Metric Value
eloc 11
dl 0
loc 17
ccs 13
cts 13
cp 1
rs 9.9
c 0
b 0
f 0
cc 4
nc 4
nop 2
crap 4
1
<?php
2
3
namespace GeminiLabs\SiteReviews\Database;
4
5
use GeminiLabs\SiteReviews\Commands\CreateReview;
6
use GeminiLabs\SiteReviews\Database;
7
use GeminiLabs\SiteReviews\Database\PostMeta;
8
use GeminiLabs\SiteReviews\Defaults\CustomFieldsDefaults;
9
use GeminiLabs\SiteReviews\Defaults\RatingDefaults;
10
use GeminiLabs\SiteReviews\Defaults\UpdateReviewDefaults;
11
use GeminiLabs\SiteReviews\Helper;
12
use GeminiLabs\SiteReviews\Helpers\Arr;
13
use GeminiLabs\SiteReviews\Helpers\Cast;
14
use GeminiLabs\SiteReviews\Modules\Sanitizer;
15
use GeminiLabs\SiteReviews\Request;
16
use GeminiLabs\SiteReviews\Review;
17
use GeminiLabs\SiteReviews\Reviews;
18
19
class ReviewManager
20
{
21
    /**
22
     * Foreign indexes ensure that $postId is a valid Post ID.
23
     */
24 2
    public function assignPost(Review $review, int $postId): bool
25
    {
26 2
        if (!glsr()->can('assign_post', $postId)) {
27 1
            return false;
28
        }
29 2
        $data = [
30 2
            'is_published' => $this->isPublishedPost($postId),
31 2
            'post_id' => $postId,
32 2
            'rating_id' => $review->rating_id,
33 2
        ];
34 2
        if ($result = glsr(Database::class)->insert('assigned_posts', $data)) {
35 2
            glsr(Cache::class)->delete($review->ID, 'reviews');
36 2
            if (!defined('WP_IMPORTING')) {
37 2
                glsr(CountManager::class)->posts($postId);
38
            }
39
        }
40 2
        return Cast::toInt($result) > 0;
41
    }
42
43
    /**
44
     * Foreign indexes ensure that $termId is a valid Term ID.
45
     */
46 2
    public function assignTerm(Review $review, int $termId): bool
47
    {
48 2
        $data = [
49 2
            'rating_id' => $review->rating_id,
50 2
            'term_id' => $termId,
51 2
        ];
52 2
        if ($result = glsr(Database::class)->insert('assigned_terms', $data)) {
53 2
            glsr(Cache::class)->delete($review->ID, 'reviews');
54 2
            if (!defined('WP_IMPORTING')) {
55 2
                glsr(CountManager::class)->terms($termId);
56
            }
57
        }
58 2
        return Cast::toInt($result) > 0;
59
    }
60
61
    /**
62
     * Foreign indexes ensure that $userId is a valid User ID.
63
     */
64 2
    public function assignUser(Review $review, int $userId): bool
65
    {
66 2
        $data = [
67 2
            'rating_id' => $review->rating_id,
68 2
            'user_id' => $userId,
69 2
        ];
70 2
        if ($result = glsr(Database::class)->insert('assigned_users', $data)) {
71 2
            glsr(Cache::class)->delete($review->ID, 'reviews');
72 2
            if (!defined('WP_IMPORTING')) {
73 2
                glsr(CountManager::class)->users($userId);
74
            }
75
        }
76 2
        return Cast::toInt($result) > 0;
77
    }
78
79
    /**
80
     * @return Review|false
81
     */
82 17
    public function create(CreateReview $command, ?int $postId = null)
83
    {
84 17
        if (empty($postId)) {
85 17
            $postId = $this->createRaw($command);
86
        }
87 17
        if (empty($postId)) {
88
            return false;
89
        }
90 17
        $review = $this->get($postId);
91 17
        if ($review->isValid()) {
92 17
            glsr()->action('review/created', $review, $command);
93 17
            return $review->refresh();
94
        }
95
        return false;
96
    }
97
98
    /**
99
     * @return Review|false
100
     */
101
    public function createFromPost(int $postId, array $data = [])
102
    {
103
        if (!Review::isReview($postId)) {
104
            return false;
105
        }
106
        $command = new CreateReview(new Request($data));
107
        glsr()->action('review/create', $postId, $command);
108
        return $this->create($command, $postId);
109
    }
110
111
    /**
112
     * @return int|false
113
     */
114 17
    public function createRaw(CreateReview $command)
115
    {
116 17
        $values = glsr()->args($command->toArray()); // this filters the values
117 17
        $submitted = $this->submittedMeta($command->request);
118 17
        $metaInput = [
119 17
            '_submitted' => $submitted, // save the original submitted request in metadata
120 17
            '_submitted_hash' => md5(maybe_serialize($submitted)),
121 17
        ];
122 17
        $values = [
123 17
            'comment_status' => 'closed',
124 17
            'meta_input' => $metaInput,
125 17
            'ping_status' => 'closed',
126 17
            'post_author' => $values->author_id,
127 17
            'post_content' => $values->content,
128 17
            'post_date' => $values->date,
129 17
            'post_date_gmt' => $values->date_gmt,
130 17
            'post_modified' => $values->date,
131 17
            'post_modified_gmt' => $values->date_gmt,
132 17
            'post_name' => uniqid($values->type),
133 17
            'post_status' => $this->postStatus($command),
134 17
            'post_title' => $values->title,
135 17
            'post_type' => glsr()->post_type,
136 17
        ];
137 17
        $values = glsr()->filterArray('review/create/post_data', $values, $command);
138 17
        $postId = wp_insert_post($values, true);
139 17
        if (is_wp_error($postId)) {
140
            glsr_log()->error($postId->get_error_message())->debug($values);
141
            return false;
142
        }
143 17
        glsr()->action('review/create', $postId, $command);
144 17
        return $postId;
145
    }
146
147
    public function deleteRating(int $reviewId): bool
148
    {
149
        $result = glsr(Database::class)->delete('ratings', ['review_id' => $reviewId]);
150
        if ($result) {
151
            glsr(Cache::class)->delete($reviewId, 'reviews');
152
        }
153
        return Cast::toInt($result) > 0;
154
    }
155
156
    public function deleteRevisions(int $reviewId): void
157
    {
158
        $revisionIds = glsr(Query::class)->revisionIds((int) $reviewId);
159
        foreach ($revisionIds as $revisionId) {
160
            wp_delete_post_revision($revisionId);
161
        }
162
    }
163
164
    /**
165
     * @return Review|false
166
     */
167
    public function duplicate(int $reviewId)
168
    {
169
        if (!Review::isReview($reviewId)) {
170
            return false;
171
        }
172
        $review = glsr_get_review($reviewId);
173
        if (!$review->isValid()) {
174
            return false;
175
        }
176
        $data = $review->toArray();
177
        $data['author_id'] = get_current_user_id();
178
        $data['is_approved'] = $data['is_approved'] && glsr()->can('publish_posts');
179
        if (!$duplicate = glsr_create_review($data)) {
180
            return false;
181
        }
182
        foreach ($review->meta() as $key => $value) {
183
            if (!str_starts_with($key, '_submitted')) {
184
                update_post_meta($duplicate->ID, $key, $value);
185
            }
186
        }
187
        glsr(PostMeta::class)->set($duplicate->ID, 'duplicated_from', $review->ID);
188
        return $duplicate;
189
    }
190
191 17
    public function get(int $reviewId, bool $bypassCache = false): Review
192
    {
193 17
        return glsr(Query::class)->review($reviewId, $bypassCache);
194
    }
195
196
    public function reviews(array $args = []): Reviews
197
    {
198
        $args = (new NormalizePaginationArgs($args))->toArray();
199
        $results = glsr(Query::class)->reviews($args);
200
        $total = $this->total($args, $results);
201
        $reviews = new Reviews($results, $total, $args);
202
        glsr()->action('get/reviews', $reviews, $args);
203
        return $reviews;
204
    }
205
206 17
    public function submittedMeta(Request $request): array
207
    {
208 17
        $excludedKeys = [
209 17
            '_action',
210 17
            '_ajax_request',
211 17
            '_frcaptcha',
212 17
            '_hcaptcha',
213 17
            '_nonce',
214 17
            '_procaptcha',
215 17
            '_recaptcha',
216 17
            '_referer',
217 17
            '_turnstile',
218 17
            'form_id',
219 17
            'form_signature',
220 17
        ];
221 17
        $submitted = $request->toArray($excludedKeys);
222 17
        return array_filter($submitted, fn ($value) => !Helper::isEmpty($value));
223
    }
224
225
    public function total(array $args = [], array $reviews = []): int
226
    {
227
        return glsr(Query::class)->totalReviews($args, $reviews);
228
    }
229
230 1
    public function unassignPost(Review $review, int $postId): bool
231
    {
232 1
        if (!glsr()->can('unassign_post', $postId)) {
233
            return false;
234
        }
235 1
        $where = [
236 1
            'post_id' => $postId,
237 1
            'rating_id' => $review->rating_id,
238 1
        ];
239 1
        if ($result = glsr(Database::class)->delete('assigned_posts', $where)) {
240 1
            glsr(Cache::class)->delete($review->ID, 'reviews');
241 1
            glsr(CountManager::class)->posts($postId);
242
        }
243 1
        return Cast::toInt($result) > 0;
244
    }
245
246 1
    public function unassignTerm(Review $review, int $termId): bool
247
    {
248 1
        $where = [
249 1
            'rating_id' => $review->rating_id,
250 1
            'term_id' => $termId,
251 1
        ];
252 1
        if ($result = glsr(Database::class)->delete('assigned_terms', $where)) {
253 1
            glsr(Cache::class)->delete($review->ID, 'reviews');
254 1
            glsr(CountManager::class)->terms($termId);
255
        }
256 1
        return Cast::toInt($result) > 0;
257
    }
258
259 1
    public function unassignUser(Review $review, int $userId): bool
260
    {
261 1
        $where = [
262 1
            'rating_id' => $review->rating_id,
263 1
            'user_id' => $userId,
264 1
        ];
265 1
        if ($result = glsr(Database::class)->delete('assigned_users', $where)) {
266 1
            glsr(Cache::class)->delete($review->ID, 'reviews');
267 1
            glsr(CountManager::class)->users($userId);
268
        }
269 1
        return Cast::toInt($result) > 0;
270
    }
271
272
    /**
273
     * @return Review|false
274
     */
275
    public function update(int $reviewId, array $data = [])
276
    {
277
        $oldPost = get_post($reviewId);
278
        if (-1 === $this->updateRating($reviewId, $data)) {
279
            glsr_log('update rating failed');
280
            return false;
281
        }
282
        if (-1 === $this->updateReview($reviewId, $data)) {
283
            glsr_log('update review failed');
284
            return false;
285
        }
286
        $this->updateCustom($reviewId, $data);
287
        $this->updateResponse($reviewId, $data);
288
        $review = $this->get($reviewId);
289
        if (isset($data['assigned_posts'])) {
290
            $assignedPosts = glsr(Sanitizer::class)->sanitizePostIds($data['assigned_posts']);
291
            glsr()->action('review/updated/post_ids', $review, $assignedPosts); // triggers a recount of assigned posts
292
        }
293
        if (isset($data['assigned_terms'])) {
294
            $assignedTerms = glsr(Sanitizer::class)->sanitizeTermIds($data['assigned_terms']);
295
            wp_set_object_terms($reviewId, $assignedTerms, glsr()->taxonomy); // triggers a recount of assigned_terms
296
        }
297
        if (isset($data['assigned_users'])) {
298
            $assignedUsers = glsr(Sanitizer::class)->sanitizeUserIds($data['assigned_users']);
299
            glsr()->action('review/updated/user_ids', $review, $assignedUsers); // triggers a recount of assigned posts
300
        }
301
        $review->refresh();
302
        glsr()->action('review/updated', $review, $data, $oldPost);
303
        return $review;
304
    }
305
306
    public function updateAssignedPost(int $postId): bool
307
    {
308
        $result = glsr(Database::class)->update('assigned_posts',
309
            ['is_published' => $this->isPublishedPost($postId)],
310
            ['post_id' => Cast::toInt($postId)]
311
        );
312
        return Cast::toInt($result) > 0;
313
    }
314
315
    public function updateCustom(int $reviewId, array $values = []): void
316
    {
317
        $data = glsr(CustomFieldsDefaults::class)->merge($values);
318
        $data = Arr::prefixKeys($data, 'custom_');
319
        foreach ($data as $metaKey => $metaValue) {
320
            glsr(PostMeta::class)->set($reviewId, $metaKey, $metaValue);
321
        }
322
    }
323
324
    public function updateRating(int $reviewId, array $values = []): int
325
    {
326
        $review = $this->get($reviewId);
327
        glsr(Cache::class)->delete($reviewId, 'reviews');
328
        $sanitized = glsr(RatingDefaults::class)->restrict($values);
329
        $data = array_intersect_key($sanitized, $values);
330
        if (empty($data)) {
331
            return 0;
332
        }
333
        $result = glsr(Database::class)->update('ratings', $data, [
334
            'review_id' => $reviewId,
335
        ]);
336
        if (false === $result) {
337
            return -1;
338
        }
339
        $rating = $data['rating'] ?? '';
340
        if (is_numeric($rating) && $review->rating !== $data['rating']) {
341
            glsr(CountManager::class)->recalculate();
342
        }
343
        return Cast::toInt($result);
344
    }
345
346
    public function updateResponse(int $reviewId, array $values): int
347
    {
348
        if (!array_key_exists('response', $values)) {
349
            return 0;
350
        }
351
        $response = Arr::getAs('string', $values, 'response');
352
        $response = glsr(Sanitizer::class)->sanitizeTextHtml($response);
353
        $review = glsr_get_review($reviewId);
354
        if (empty($response) && empty($review->response)) {
355
            return 0;
356
        }
357
        glsr(PostMeta::class)->set($review->ID, 'response', $response); // prefixed metakey
358
        // This should run immediately after saving the response
359
        // but before adding the "response_by" meta_value!
360
        glsr()->action('review/responded', $review, $response);
361
        glsr(PostMeta::class)->set($review->ID, 'response_by', get_current_user_id()); // prefixed metakey
362
        glsr(Cache::class)->delete($review->ID, 'reviews');
363
        return 1;
364
    }
365
366
    public function updateReview(int $reviewId, array $values = []): int
367
    {
368
        if (glsr()->post_type !== get_post_type($reviewId)) {
369
            glsr_log()->error("Review update failed: Post ID [{$reviewId}] is not a review.");
370
            return -1;
371
        }
372
        glsr(Cache::class)->delete($reviewId, 'reviews');
373
        $sanitized = glsr(UpdateReviewDefaults::class)->restrict($values);
374
        if ($data = array_intersect_key($sanitized, $values)) {
375
            $data = array_filter([
376
                'post_content' => Arr::get($data, 'content'),
377
                'post_date' => Arr::get($data, 'date'),
378
                'post_date_gmt' => Arr::get($data, 'date_gmt'),
379
                'post_status' => Arr::get($data, 'status'),
380
                'post_title' => Arr::get($data, 'title'),
381
            ]);
382
        }
383
        if (empty($data)) {
384
            return 0;
385
        }
386
        $data = wp_parse_args(['ID' => $reviewId], $data);
387
        $result = wp_update_post($data, true);
388
        if (is_wp_error($result)) {
389
            glsr_log()->error($result->get_error_message())->debug($data);
390
            return -1;
391
        }
392
        return Cast::toInt($result);
393
    }
394
395 2
    protected function isPublishedPost(int $postId): bool
396
    {
397 2
        $isPublished = 'publish' === get_post_status($postId);
398 2
        return glsr()->filterBool('post/is-published', $isPublished, $postId);
399
    }
400
401 17
    protected function postStatus(CreateReview $command): string
402
    {
403 17
        $isApproved = $command->is_approved;
404 17
        $isFormSubmission = !defined('WP_IMPORTING') && !glsr()->retrieve('glsr_create_review', false);
405 17
        if ($isFormSubmission) {
406 14
            $requireApproval = glsr(OptionManager::class)->getBool('settings.general.require.approval');
407 14
            $requireApprovalForRating = glsr(OptionManager::class)->getInt('settings.general.require.approval_for', 5);
408 14
            $isApproved = !$requireApproval || $command->rating > $requireApprovalForRating;
409
        }
410 17
        return !$isApproved || $command->isBlacklisted()
411
            ? 'pending'
412 17
            : 'publish';
413
    }
414
}
415