PluginProbe ʕ •ᴥ•ʔ
FAPI Member / 2.2.35
FAPI Member v2.2.35
2.2.35 2.2.34 2.2.33 2.2.32 trunk 1.9.47 2.1.18 2.2.24 2.2.25 2.2.26 2.2.28 2.2.29 2.2.30 2.2.31
fapi-member / src / Api / V2 / ApiController.php
fapi-member / src / Api / V2 Last commit date
Endpoints 2 weeks ago ApiController.php 2 weeks ago
ApiController.php
290 lines
1 <?php declare(strict_types=1);
2
3 namespace FapiMember\Api\V2;
4
5 use FapiMember\FapiMemberPlugin;
6 use FapiMember\Library\SmartEmailing\Types\Arrays;
7 use FapiMember\Library\SmartEmailing\Types\IntType;
8 use FapiMember\Library\SmartEmailing\Types\StringType;
9 use FapiMember\Model\Enums\Alert;
10 use FapiMember\Model\Enums\Keys\OptionKey;
11 use FapiMember\Model\Enums\Types\RequestMethodType;
12 use FapiMember\Model\Enums\UserPermission;
13 use FapiMember\Utils\AlertProvider;
14 use Throwable;
15 use WP_REST_Request;
16
17 class ApiController
18 {
19 private array $freeAccessEndpoints = [
20 'memberships' => ['unlockLevelForLoggedInUser'],
21 ];
22
23 public function handleRequest(WP_REST_Request $request): void
24 {
25 $params = $request->get_query_params();
26 $controllerName = trim(str_replace('/fapi/v2/', '', $params['rest_route']), '/');
27 $route = 'FapiMember\\Api\\V2\\Endpoints\\' . ucfirst($controllerName) . 'Controller';
28
29 if (isset($params['action'])) {
30 $action = $params['action'];
31 } elseif (isset($params['id'])) {
32 $action = 'get';
33 } else {
34 $action = 'list';
35 }
36
37 $actionFunction = 'handle' . ucfirst($action);
38
39 $this->authenticate($controllerName, $action, $request);
40
41 try {
42 $controller = new $route();
43 } catch (Throwable) {
44 $this->callbackError([
45 'class'=> self::class,
46 'description' => "Specified endpoint doesn't exist.",
47 ]);
48 }
49
50 if (!is_callable([$controller, $action])) {
51 $this->callbackError([
52 'class'=> $controller::class,
53 'description' => "Specified action doesn't exist. Action: " . $action,
54 ]);
55 }
56
57
58 if (is_callable([$this, $actionFunction])) {
59 $data = $this->$actionFunction($request, $controller, $action);
60 } else {
61 $data = $controller->$action($request);
62 }
63
64 wp_send_json($data);
65 }
66
67 private function handleList(WP_REST_Request $request, mixed $controller, string $action): mixed
68 {
69 if ($request->get_method() !== RequestMethodType::GET) {
70 $this->wrongMethodError(RequestMethodType::GET);
71 }
72
73 return $controller->$action();
74 }
75
76
77 private function handleGet(WP_REST_Request $request, mixed $controller, string $action): mixed
78 {
79 if ($request->get_method() !== RequestMethodType::GET) {
80 $this->wrongMethodError(RequestMethodType::GET);
81 }
82
83 $params = $request->get_params();
84
85 if (!isset($params['id'])) {
86 $this->missingParameterError('id');
87 }
88
89 try {
90 $id = IntType::extract($params, 'id');
91 } catch (Throwable) {
92 $this->invalidParameterError('id');
93 }
94
95 return $controller->$action($id);
96 }
97
98 private function handleDelete(WP_REST_Request $request, mixed $controller, string $action): bool
99 {
100 if ($request->get_method() !== RequestMethodType::POST) {
101 $this->wrongMethodError(RequestMethodType::POST);
102 }
103
104 $body = json_decode($request->get_body(), true);
105
106 $id = $this->extractParam($body, 'id', IntType::class);
107
108 return $controller->$action($id);
109 }
110
111 private function handleCreate(WP_REST_Request $request, mixed $controller, string $action): bool
112 {
113 if ($request->get_method() !== RequestMethodType::POST) {
114 $this->wrongMethodError(RequestMethodType::POST);
115 }
116
117 $body = json_decode($request->get_body(), true);
118
119 return $controller->$action($body);
120 }
121
122 private function handleUpdate(WP_REST_Request $request, mixed $controller, string $action): bool
123 {
124 if ($request->get_method() !== RequestMethodType::POST) {
125 $this->wrongMethodError(RequestMethodType::POST);
126 }
127
128 $body = json_decode($request->get_body(), true);
129
130 $id = $this->extractParam($body, 'id', IntType::class);
131 $data = $this->extractParam($body, 'data', Arrays::class);
132
133 return $controller->$action($id, $data);
134 }
135
136
137 public function extractParam(array $array, string $key, string $type): mixed
138 {
139 if (!isset($array[$key])) {
140 $this->missingParameterError($key);
141 }
142
143
144 $param = $this->extractParamOrNull($array, $key, $type);
145
146 if ($param === null) {
147 $this->invalidParameterError($key);
148
149 } else {
150 return $param;
151 }
152 }
153
154 public function extractParamOrNull(array $array, string $key, string $type): mixed
155 {
156 if (!isset($array[$key])) {
157 return null;
158 }
159
160 try {
161 return $type::extractOrNull($array, $key);
162 } catch (Throwable) {
163 return null;
164 }
165 }
166
167 public function callbackSettingsSaved(mixed $data = []): never
168 {
169 $this->callbackResponse($data, Alert::SETTINGS_SAVED);
170 }
171
172 public function callbackError(array $data, string $alert = Alert::INTERNAL_ERROR): never
173 {
174 $data[FapiMemberPlugin::FAPI_MEMBER_PLUGIN_VERSION_KEY ] = FAPI_MEMBER_PLUGIN_VERSION;
175 $data['alert'] = AlertProvider::getError($alert);
176
177 wp_send_json_error(
178 $data,
179 400,
180 );
181
182 die;
183 }
184
185 /**
186 * @param array $data
187 * @return never
188 */
189 public function callbackSuccess($data = []): never
190 {
191 wp_send_json_success(
192 $data,
193 200,
194 );
195
196 die;
197 }
198
199 public function callbackResponse(array $data, string $alert): never
200 {
201 $data[FapiMemberPlugin::FAPI_MEMBER_PLUGIN_VERSION_KEY ] = FAPI_MEMBER_PLUGIN_VERSION;
202 $data['alert'] = AlertProvider::getError($alert);
203
204 wp_send_json_success(
205 $data,
206 200,
207 );
208
209 die;
210 }
211
212
213 public function wrongMethodError(string $method): never
214 {
215 $this->callbackError([
216 'class'=> ApiController::class,
217 'description' => "wrong request method. Expecting: " . $method,
218 ]);
219 }
220
221 public function missingParameterError(string $parameter): never
222 {
223 $this->callbackError([
224 'class'=> self::class,
225 'description' => "Missing parameter '" . $parameter . "'",
226 ]);
227 }
228
229 public function invalidParameterError(string $parameter): never
230 {
231 $this->callbackError([
232 'class'=> self::class,
233 'description' => "Invalid parameter '" . $parameter . "'",
234 ]);
235 }
236
237 public function checkRequestMethod(\WP_REST_Request $request, string $method): void
238 {
239 if ($request->get_method() !== $method) {
240 $this->wrongMethodError($method);
241 }
242 }
243
244 private function authenticate(array|string $controllerName, mixed $action, WP_REST_Request $request): void
245 {
246 if (
247 isset($this->freeAccessEndpoints[$controllerName]) &&
248 in_array($action, $this->freeAccessEndpoints[$controllerName], true)
249 ) {
250 return;
251 }
252
253 try {
254 $body = json_decode($request->get_body(), true) ?? [];
255 } catch (Throwable) {
256 $body = [];
257 }
258
259 $token = $this->extractParamOrNull($body, 'token', StringType::class);
260
261 if ($token !== null) {
262 if ($token !== get_option(OptionKey::TOKEN, null)) {
263 $this->callbackError([
264 'class' => self::class,
265 'description' => "Permission denied. Invalid token provided.",
266 ]);
267 }
268
269 return;
270 }
271
272 $nonce = $request->get_header('X-WP-Nonce');
273
274 if (!wp_verify_nonce($nonce, 'wp_rest')) {
275 $this->callbackError([
276 'class' => self::class,
277 'description' => "Permission denied. Invalid Nonce provided.",
278 ]);
279 }
280
281 if (!current_user_can(UserPermission::REQUIRED_CAPABILITY)) {
282 $this->callbackError([
283 'class' => self::class,
284 'description' => "Permission denied. Missing required capability.",
285 ]);
286 }
287 }
288
289 }
290