Passed
Push — main ( a0e9c5...805967 )
by Paul
07:42
created

Router::checkAjaxRequest()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 3.576

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 4
c 1
b 0
f 0
nc 4
nop 1
dl 0
loc 7
ccs 3
cts 5
cp 0.6
crap 3.576
rs 10
1
<?php
2
3
namespace GeminiLabs\SiteReviews;
4
5
use GeminiLabs\SiteReviews\Modules\Notice;
6
7
class Router
8
{
9
    /**
10
     * @action wp_ajax_glsr_action
11
     */
12 8
    public function routeAdminAjaxRequest(): void
13
    {
14 8
        $request = Request::inputPost();
15 8
        $this->checkAjaxRequest($request);
16 8
        $this->checkAjaxNonce($request, 'admin');
17 8
        $this->post('ajax', $request);
18
        wp_die();
19
    }
20
21
    /**
22
     * A routed admin GET request will look like this: /wp-admin/?glsr_=.
23
     * @action admin_init
24
     */
25 8
    public function routeAdminGetRequest(): void
26
    {
27 8
        $request = Request::inputGet();
28 8
        if (!empty($request->action)) {
29
            $this->get('admin', $request);
30
        }
31
    }
32
33
    /**
34
     * @action admin_init
35
     */
36 8
    public function routeAdminPostRequest(): void
37
    {
38 8
        $request = Request::inputPost();
39 8
        if ($this->isValidRequest($request)) {
40
            check_admin_referer($request->_action); // die() called if nonce is invalid, assumes _wpnonce
41
            $this->post('admin', $request);
42
        }
43
    }
44
45
    /**
46
     * @action wp_ajax_nopriv_glsr_action
47
     */
48
    public function routePublicAjaxRequest(): void
49
    {
50
        $request = Request::inputPost();
51
        $this->checkAjaxRequest($request);
52
        $this->checkAjaxNonce($request, 'public');
53
        $this->post('ajax', $request);
54
        wp_die();
55
    }
56
57
    /**
58
     * A routed public GET request will look like this: ?glsr_=.
59
     * @action parse_request
60
     */
61
    public function routePublicGetRequest(): void
62
    {
63
        $request = Request::inputGet();
64
        if (!empty($request->action)) {
65
            $this->get('public', $request);
66
        }
67
    }
68
69
    /**
70
     * @action init
71
     */
72
    public function routePublicPostRequest(): void
73
    {
74
        if (glsr()->isAdmin()) {
75
            return;
76
        }
77
        $request = Request::inputPost();
78
        if ($this->isValidRequest($request) && $this->isValidPublicNonce($request)) {
79
            $this->post('public', $request);
80
        }
81
    }
82
83 8
    protected function checkAjaxNonce(Request $request, string $type): void
84
    {
85 8
        $unguardedActions = 'admin' === $type
86 8
            ? $this->unguardedAdminActions()
87 8
            : $this->unguardedPublicActions();
88 8
        if (in_array($request->_action, $unguardedActions)) {
89
            return;
90
        }
91 8
        if (empty($request->_nonce)) {
92
            $this->sendAjaxError('AJAX request is missing a nonce', $request, 400, 'Unauthorized request');
93
        }
94 8
        if (!wp_verify_nonce($request->_nonce, $request->_action)) {
95
            $this->sendAjaxError('AJAX request failed the nonce check', $request, 403, 'Unauthorized request');
96
        }
97
    }
98
99 8
    protected function checkAjaxRequest(Request $request): void
100
    {
101 8
        if (empty($request->_action)) {
102
            $this->sendAjaxError('AJAX request must include an action', $request, 400, 'Invalid request');
103
        }
104 8
        if (empty($request->_ajax_request)) {
105
            $this->sendAjaxError('AJAX request is invalid', $request, 400, 'Invalid request');
106
        }
107
    }
108
109
    protected function get(string $type, Request $request): void
110
    {
111
        $hook = "route/get/{$type}/{$request->action}";
112
        glsr()->action('route/request', $request, $hook);
113
        glsr()->action($hook, $request);
114
        if (0 === did_action(glsr()->id.'/'.$hook)) {
115
            glsr_log()->warning('Unknown '.$type.' router GET request: '.$request->action);
116
        }
117
    }
118
119
    protected function isValidPublicNonce(Request $request): bool
120
    {
121
        // only require a nonce for public requests if user is logged in, this avoids
122
        // potential caching issues since unauthenticated requests should nenever be destructive.
123
        if (is_user_logged_in() && !wp_verify_nonce($request->_nonce, $request->_action)) {
124
            glsr_log()->warning('nonce check failed for public request')->debug($request);
125
            return false;
126
        }
127
        return true;
128
    }
129
130 8
    protected function isValidRequest(Request $request): bool
131
    {
132 8
        return !empty($request->_action) && empty($request->_ajax_request);
133
    }
134
135 8
    protected function post(string $type, Request $request): void
136
    {
137 8
        $hook = "route/{$type}/{$request->_action}";
138 8
        glsr()->action('route/request', $request, $hook);
139 8
        glsr()->action($hook, $request);
140
        if (0 === did_action(glsr()->id.'/'.$hook)) {
141
            glsr_log()->warning('Unknown '.$type.' router POST request: '.$request->_action);
142
        }
143
    }
144
145
    protected function sendAjaxError(string $error, Request $request, int $errCode, string $message): void
146
    {
147
        $data = [
148
            'code' => $errCode,
149
            'error' => $error,
150
            'message' => $message ?: $error,
151
            'notices' => '',
152
        ];
153
        if ('submit-review' === $request->_action) {
154
            $data['message'] = __('The form could not be submitted. Please notify the site administrator.', 'site-reviews');
155
        }
156
        if (glsr()->isAdmin()) {
157
            glsr(Notice::class)->addError(_x('There was an error (try reloading the page).', 'admin-text', 'site-reviews').' <code>'.$error.'</code>');
158
            $data['notices'] = glsr(Notice::class)->get();
159
        }
160
        glsr_log()->error($error)->debug($request->toArray());
161
        wp_send_json_error($data);
162
    }
163
164 8
    protected function unguardedAdminActions(): array
165
    {
166 8
        return glsr()->filterArray('router/admin/unguarded-actions', [
167 8
            'dismiss-notice',
168 8
            'fetch-paged-reviews',
169 8
            'verified-review',
170 8
        ]);
171
    }
172
173
    protected function unguardedPublicActions(): array
174
    {
175
        return glsr()->filterArray('router/public/unguarded-actions', [
176
            'dismiss-notice',
177
            'fetch-paged-reviews',
178
            'submit-review',
179
            'verified-review',
180
        ]);
181
    }
182
}
183