Passed
Push — develop ( 6f3ffa...e1a061 )
by Paul
14:04
created

get_items_permissions_check()   A

Complexity

Conditions 5
Paths 8

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
eloc 8
dl 0
loc 14
ccs 0
cts 10
cp 0
rs 9.6111
c 0
b 0
f 0
cc 5
nc 8
nop 1
crap 30
1
<?php
2
3
namespace GeminiLabs\SiteReviews\Controllers\Api\Version1;
4
5
use GeminiLabs\SiteReviews\Controllers\Api\Version1\Parameters\ReviewParameters;
6
use GeminiLabs\SiteReviews\Controllers\Api\Version1\Permissions\ReviewPermissions;
7
use GeminiLabs\SiteReviews\Controllers\Api\Version1\Response\PrepareReviewData;
8
use GeminiLabs\SiteReviews\Controllers\Api\Version1\Schema\ReviewSchema;
9
use GeminiLabs\SiteReviews\Review;
10
use GeminiLabs\SiteReviews\Reviews;
11
use GeminiLabs\SiteReviews\Shortcodes\SiteReviewsShortcode;
12
13
class RestReviewController extends \WP_REST_Controller
14
{
15
    use ReviewPermissions;
1 ignored issue
show
introduced by
The trait GeminiLabs\SiteReviews\C...sions\ReviewPermissions requires some properties which are not provided by GeminiLabs\SiteReviews\C...n1\RestReviewController: $ID, $taxonomy, $is_approved
Loading history...
16
17
    public function __construct()
18
    {
19
        $obj = get_post_type_object(glsr()->post_type);
20
        $this->namespace = !empty($obj->rest_namespace) ? $obj->rest_namespace : glsr()->id.'/v1';
1 ignored issue
show
Documentation Bug introduced by
It seems like ! empty($obj->rest_names...ce : glsr()->id . '/v1' can also be of type boolean. However, the property $namespace is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
21
        $this->rest_base = 'reviews';
22
    }
23
24
    /**
25
     * @param \WP_REST_Request $request
26
     *
27
     * @return \WP_REST_Response|\WP_Error
28
     */
29
    public function create_item($request)
30
    {
31
        $review = glsr_create_review($request->get_params());
32
        if (false === $review || !$review->isValid()) {
33
            $error = _x('Review creation failed, please check the Site Reviews console log for more details.', 'admin-text', 'site-reviews');
34
            return new \WP_Error('rest_review_create_item', $error, ['status' => 500]);
35
        }
36
        if ($request['_rendered']) {
37
            $response = $this->renderedItems($request);
38
        } else {
39
            $data = $this->prepare_item_for_response($review, $request);
40
            $response = rest_ensure_response($data);
41
        }
42
        $response->set_status(201);
43
        $response->header('Location', rest_url(sprintf('%s/%s/%d', $this->namespace, $this->rest_base, $review->ID)));
44
        return $response;
45
    }
46
47
    /**
48
     * @param \WP_REST_Request $request
49
     *
50
     * @return \WP_REST_Response|\WP_Error
51
     */
52
    public function delete_item($request)
53
    {
54
        $request->set_param('context', 'edit');
55
        $review = glsr_get_review($request['id']);
56
        if ((bool) $request['force']) {
57
            $previous = $this->prepare_item_for_response($review, $request);
58
            $result = wp_delete_post($review->ID, true);
59
            if (false === $result) {
60
                $error = _x('The review cannot be deleted.', 'admin-text', 'site-reviews');
61
                return new \WP_Error('rest_cannot_delete', $error, ['status' => 500]);
62
            }
63
            return rest_ensure_response([
64
                'deleted' => true,
65
                'previous' => $previous->get_data(),
66
            ]);
67
        }
68
        if (EMPTY_TRASH_DAYS < 1) {
69
            $error = sprintf(_x('The review does not support trashing. Set "%s" to delete.', 'admin-text', 'site-reviews'), 'force=true');
70
            return new \WP_Error('rest_trash_not_supported', $error, ['status' => 501]);
71
        }
72
        if ('trash' === get_post_status($review->ID)) {
73
            $error = _x('The review has already been deleted.', 'admin-text', 'site-reviews');
74
            return new \WP_Error('rest_already_trashed', $error, ['status' => 410]);
75
        }
76
        if (!wp_trash_post($review->ID)) {
77
            $error = _x('The review cannot be deleted.', 'admin-text', 'site-reviews');
78
            return new \WP_Error('rest_cannot_delete', $error, ['status' => 500]);
79
        }
80
        return $this->prepare_item_for_response($review, $request);
81
    }
82
83
    /**
84
     * @return array
85
     */
86
    public function get_collection_params()
87
    {
88
        return glsr(ReviewParameters::class)->parameters(
89
            $this->get_context_param(['default' => 'view'])
90
        );
91
    }
92
93
    /**
94
     * @param \WP_REST_Request $request
95
     *
96
     * @return \WP_REST_Response|\WP_Error
97
     */
98
    public function get_item($request)
99
    {
100
        if ($request['_rendered']) {
101
            return $this->renderedItem($request);
102
        }
103
        $review = glsr_get_review($request['id']);
104
        $data = $this->prepare_item_for_response($review, $request);
105
        return rest_ensure_response($data);
106
    }
107
108
    /**
109
     * @return array
110
     */
111
    public function get_item_schema()
112
    {
113
        if (empty($this->schema)) {
114
            $this->schema = glsr(ReviewSchema::class)->schema();
115
        }
116
        return $this->add_additional_fields_schema($this->schema);
117
    }
118
119
    /**
120
     * @param \WP_REST_Request $request
121
     *
122
     * @return \WP_REST_Response|\WP_Error
123
     */
124
    public function get_items($request)
125
    {
126
        if ($request['_rendered']) {
127
            return $this->renderedItems($request);
128
        }
129
        $results = glsr_get_reviews($this->normalizedArgs($request));
130
        $reviews = [];
131
        foreach ($results->reviews as $review) {
132
            if ($this->has_read_permission($review)) {
133
                $data = $this->prepare_item_for_response($review, $request);
134
                $reviews[] = $this->prepare_response_for_collection($data);
135
            }
136
        }
137
        if ($results->args['page'] > $results->max_num_pages && $results->total > 0) {
138
            $error = _x('The page number requested is larger than the number of pages available.', 'admin-text', 'site-reviews');
139
            return new \WP_Error('rest_invalid_page_number', $error, ['status' => 400]);
140
        }
141
        $response = rest_ensure_response($reviews);
142
        return $this->prepareResponse($response, $request, $results);
143
    }
144
145
    /**
146
     * @param Review           $review
147
     * @param \WP_REST_Request $request
148
     *
149
     * @return \WP_REST_Response
150
     */
151
    public function prepare_item_for_response($review, $request)
152
    {
153
        $context = $request['context'] ?? 'view';
154
        $prepare = new PrepareReviewData(
155
            $this->get_fields_for_response($request),
156
            $review,
157
            $request
158
        );
159
        $data = $prepare->data();
160
        $data = $this->add_additional_fields_to_object($data, $request);
161
        $data = $this->filter_response_by_context($data, $context);
162
        $response = rest_ensure_response($data);
163
        $response->add_links($this->prepareLinks($review));
164
        if ('edit' === $context) {
165
            $response->add_links($this->prepareLinksForEdit($review));
166
        }
167
        return $response; // @todo filter this, i.e. "rest_prepare_{glsr()->post_type}"
168
    }
169
170
    /**
171
     * @return void
172
     */
173
    public function register_routes()
174
    {
175
        register_rest_route($this->namespace, "/{$this->rest_base}", [
176
            'schema' => [$this, 'get_public_item_schema'],
177
            [
178
                'args' => $this->get_collection_params(),
179
                'callback' => [$this, 'get_items'],
180
                'methods' => \WP_REST_Server::READABLE,
181
                'permission_callback' => [$this, 'get_items_permissions_check'],
182
            ],
183
            [
184
                'args' => $this->get_endpoint_args_for_item_schema(\WP_REST_Server::CREATABLE),
185
                'callback' => [$this, 'create_item'],
186
                'methods' => \WP_REST_Server::CREATABLE,
187
                'permission_callback' => [$this, 'create_item_permissions_check'],
188
            ],
189
        ]);
190
        register_rest_route($this->namespace, "/{$this->rest_base}".'/(?P<id>[\d]+)', [
191
            'args' => [
192
                'id' => [
193
                    'description' => _x('Unique identifier for the object.', 'admin-text', 'site-reviews'),
194
                    'type' => 'integer',
195
                ],
196
            ],
197
            'schema' => [$this, 'get_public_item_schema'],
198
            [
199
                'args' => [
200
                    'context' => $this->get_context_param(['default' => 'view']),
201
                ],
202
                'callback' => [$this, 'get_item'],
203
                'methods' => \WP_REST_Server::READABLE,
204
                'permission_callback' => [$this, 'get_item_permissions_check'],
205
            ],
206
            [
207
                'args' => $this->get_endpoint_args_for_item_schema(\WP_REST_Server::EDITABLE),
208
                'callback' => [$this, 'update_item'],
209
                'methods' => \WP_REST_Server::EDITABLE,
210
                'permission_callback' => [$this, 'update_item_permissions_check'],
211
            ],
212
            [
213
                'args' => [
214
                    'force' => [
215
                        'default' => false,
216
                        'description' => _x('Whether to bypass Trash and force deletion.', 'admin-text', 'site-reviews'),
217
                        'type' => 'boolean',
218
                    ],
219
                ],
220
                'callback' => [$this, 'delete_item'],
221
                'methods' => \WP_REST_Server::DELETABLE,
222
                'permission_callback' => [$this, 'delete_item_permissions_check'],
223
            ],
224
        ]);
225
    }
226
227
    /**
228
     * @param \WP_REST_Request $request
229
     *
230
     * @return \WP_REST_Response|\WP_Error
231
     */
232
    public function update_item($request)
233
    {
234
        $review = glsr_update_review($request['id'], $request->get_params());
235
        if (!$review) {
236
            $error = _x('Review update failed, please check the Site Reviews console log for more details.', 'admin-text', 'site-reviews');
237
            return new \WP_Error('rest_update_review', $error, ['status' => 500]);
238
        }
239
        $request->set_param('context', 'edit');
240
        if ($request['_rendered']) {
241
            return $this->renderedItem($request);
242
        }
243
        $data = $this->prepare_item_for_response($review, $request);
244
        $response = rest_ensure_response($data);
245
        return $response;
246
    }
247
248
    protected function normalizedArgs(\WP_REST_Request $request): array
249
    {
250
        $args = [];
251
        $registered = $this->get_collection_params();
252
        foreach ($registered as $key => $params) {
253
            if (isset($request[$key])) {
254
                $args[$key] = $request[$key];
255
            }
256
        }
257
        if (empty($args['date'])) {
258
            $args['date'] = [
259
                'after' => $args['after'] ?? '',
260
                'before' => $args['before'] ?? '',
261
            ];
262
        }
263
        return glsr()->filterArray("rest-api/{$this->rest_base}/args", $args, $request);
264
    }
265
266
    protected function prepareLinks(Review $review): array
267
    {
268
        $base = "{$this->namespace}/{$this->rest_base}";
269
        $revisions = wp_get_post_revisions($review->ID, ['fields' => 'ids']);
270
        $revisionCount = count($revisions);
271
        $links = [
272
            'self' => [
273
                'href' => rest_url(trailingslashit($base).$review->ID),
274
            ],
275
            'collection' => [
276
                'href' => rest_url($base),
277
            ],
278
            'about' => [
279
                'href' => rest_url('wp/v2/types/'.glsr()->post_type),
280
            ],
281
            'https://api.w.org/attachment' => [
282
                'href' => add_query_arg('parent', $review->ID, rest_url('wp/v2/media')),
283
            ],
284
            'https://api.w.org/term' => [
285
                'embeddable' => true,
286
                'href' => add_query_arg('post', $review->ID, rest_url('wp/v2/'.glsr()->taxonomy)),
287
                'taxonomy' => glsr()->taxonomy,
288
            ],
289
            'version-history' => [
290
                'count' => $revisionCount,
291
                'href' => rest_url(trailingslashit($base)."{$review->ID}/revisions"),
292
            ],
293
        ];
294
        if ($revisionCount > 0) {
295
            $lastRevision = array_shift($revisions);
296
            $links['predecessor-version'] = [
297
                'href' => rest_url(trailingslashit($base)."{$review->ID}/revisions/{$lastRevision}"),
298
                'id' => $lastRevision,
299
            ];
300
        }
301
        if (!empty($review->user_id)) {
302
            $links['author'] = [
303
                'embeddable' => true,
304
                'href' => rest_url("wp/v2/users/{$review->user_id}"),
305
            ];
306
        }
307
        if (post_type_supports(glsr()->post_type, 'comments')) {
308
            $links['replies'] = [
309
                'embeddable' => true,
310
                'href' => add_query_arg('post', $review->ID, rest_url('wp/v2/comments')),
311
            ];
312
        }
313
        return $links;
314
    }
315
316
    protected function prepareLinksForEdit(Review $review): array
317
    {
318
        $links = [];
319
        $reviewRestUrl = rest_url(trailingslashit("{$this->namespace}/{$this->rest_base}").$review->ID);
320
        $taxonomy = get_taxonomy(glsr()->taxonomy);
321
        if (glsr()->can('publish_posts')) {
322
            $links['https://api.w.org/action-publish'] = [
323
                'href' => $reviewRestUrl,
324
            ];
325
        }
326
        if (glsr()->can('edit_others_posts')) {
327
            $links['https://api.w.org/action-assign-author'] = [
328
                'href' => $reviewRestUrl,
329
            ];
330
        }
331
        if (current_user_can($taxonomy->cap->edit_terms)) {
332
            $links['https://api.w.org/action-create-'.glsr()->taxonomy] = [
333
                'href' => $reviewRestUrl,
334
            ];
335
        }
336
        if (current_user_can($taxonomy->cap->assign_terms)) {
337
            $links['https://api.w.org/action-assign-'.glsr()->taxonomy] = [
338
                'href' => $reviewRestUrl,
339
            ];
340
        }
341
        return $links;
342
    }
343
344
    protected function prepareResponse(\WP_REST_Response $response, \WP_REST_Request $request, Reviews $reviews): \WP_REST_Response
345
    {
346
        $page = $reviews->args['page'];
347
        $ratings = glsr_get_ratings($this->normalizedArgs($request));
348
        $response->header('X-GLSR-Average', (string) $ratings->average);
349
        $response->header('X-GLSR-Ranking', (string) $ratings->ranking);
350
        $response->header('X-WP-Total', (string) $reviews->total);
351
        $response->header('X-WP-TotalPages', (string) $reviews->max_num_pages);
352
        $parameters = $request->get_query_params();
353
        $base = add_query_arg(urlencode_deep($parameters), rest_url(sprintf('%s/%s', $this->namespace, $this->rest_base)));
354
        if ($page > 1) {
355
            $prevPage = $page - 1;
356
            if ($prevPage > $reviews->max_num_pages) {
357
                $prevPage = $reviews->max_num_pages;
358
            }
359
            $prevLink = add_query_arg('page', $prevPage, $base);
360
            $response->link_header('prev', $prevLink);
361
        }
362
        if ($reviews->max_num_pages > $page) {
363
            $nextPage = $page + 1;
364
            $nextLink = add_query_arg('page', $nextPage, $base);
365
            $response->link_header('next', $nextLink);
366
        }
367
        return $response;
368
    }
369
370
    protected function renderedItem(\WP_REST_Request $request): \WP_REST_Response
371
    {
372
        $args = $this->normalizedArgs($request);
373
        $args['hide'] = $request['_hide'] ?? $request['_rendered_hide'] ?? '';
374
        $review = glsr_get_review($request['id']);
375
        return rest_ensure_response([
376
            'rendered' => (string) $review->build($args),
377
        ]);
378
    }
379
380
    protected function renderedItems(\WP_REST_Request $request): \WP_REST_Response
381
    {
382
        $args = $this->normalizedArgs($request);
383
        $args['hide'] = $request['_hide'] ?? $request['_rendered_hide'] ?? '';
384
        $html = glsr(SiteReviewsShortcode::class)
385
            ->normalize($args)
386
            ->buildReviewsHtml();
387
        $response = rest_ensure_response([
388
            'pagination' => $html->getPagination($wrap = false),
389
            'rendered' => $html->getReviews(),
390
        ]);
391
        return $this->prepareResponse($response, $request, $html->reviews);
392
    }
393
}
394