PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / trunk
Booking for Appointments and Events Calendar – Amelia vtrunk
2.4.9 2.4.8 2.4.7 2.4.6 2.4.5 2.4.4 2.4.3 2.4.2 2.4.1 2.4 trunk 1.2.1 1.2.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.15 1.2.16 1.2.17 1.2.18 1.2.19 1.2.2 1.2.20 1.2.21 1.2.22 1.2.23 1.2.24 1.2.25 1.2.26 1.2.27 1.2.28 1.2.29 1.2.3 1.2.30 1.2.31 1.2.32 1.2.33 1.2.34 1.2.35 1.2.36 1.2.37 1.2.38 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.1.3 2.2 2.2.1 2.3
ameliabooking / src / Application / Controller / Controller.php
ameliabooking / src / Application / Controller Last commit date
Bookable 2 months ago Booking 4 days ago Calendar 2 months ago Entities 2 months ago Google 2 months ago Import 2 months ago Mobile 2 months ago Notification 2 months ago Payment 2 months ago QrCode 2 months ago Settings 1 week ago Square 2 months ago Stash 2 months ago Stats 2 months ago Test 2 months ago User 2 months ago WhatsNew 2 months ago Controller.php 1 week ago
Controller.php
449 lines
1 <?php
2
3 namespace AmeliaBooking\Application\Controller;
4
5 use AmeliaBooking\Application\Commands\Command;
6 use AmeliaBooking\Application\Commands\CommandResult;
7 use AmeliaBooking\Application\Common\Exceptions\AccessDeniedException;
8 use AmeliaBooking\Application\Services\User\UserApplicationService;
9 use AmeliaBooking\Domain\Common\Exceptions\AuthorizationException;
10 use AmeliaBooking\Domain\Common\Exceptions\CustomException;
11 use AmeliaBooking\Domain\Common\Exceptions\PaymentValidationException;
12 use AmeliaBooking\Domain\Events\DomainEventBus;
13 use AmeliaBooking\Domain\Services\DateTime\DateTimeService;
14 use AmeliaBooking\Domain\Services\Logger\LoggerInterface;
15 use AmeliaBooking\Domain\Services\Permissions\PermissionsService;
16 use AmeliaBooking\Domain\Services\Settings\SettingsService;
17 use AmeliaBooking\Infrastructure\Common\Container;
18 use AmeliaBooking\Infrastructure\WP\SettingsService\SettingsStorage;
19 use AmeliaBooking\Infrastructure\WP\Translations\FrontendStrings;
20 use AmeliaVendor\Psr\Http\Message\ResponseInterface as Response;
21 use AmeliaVendor\Psr\Http\Message\ServerRequestInterface as Request;
22 use League\Tactician\CommandBus;
23
24 /**
25 * Class Controller
26 *
27 * @package AmeliaBooking\Application\Controller
28 */
29 abstract class Controller
30 {
31 public const STATUS_OK = 200;
32 public const STATUS_REDIRECT = 302;
33 public const STATUS_FORBIDDEN = 403;
34 public const STATUS_NOT_FOUNT = 404;
35 public const STATUS_CONFLICT = 409;
36 public const STATUS_INTERNAL_SERVER_ERROR = 500;
37
38 /**
39 * @var CommandBus
40 */
41 protected $commandBus;
42 /**
43 * @var DomainEventBus
44 */
45 protected $eventBus;
46
47 /**
48 * @var PermissionsService
49 */
50 protected $permissionsService;
51
52 /**
53 * @var LoggerInterface
54 */
55 protected $logger;
56 protected $allowedFields = [
57 'ameliaNonce',
58 'wpAmeliaNonce',
59 ];
60
61 protected $sendJustData = false;
62 /**
63 * @var UserApplicationService
64 */
65 private $userApplicationService;
66
67 /**
68 * Base Controller constructor.
69 *
70 * @param Container $container
71 * @param bool $fromApi
72 */
73 public function __construct(Container $container, $fromApi = false)
74 {
75 $this->commandBus = $container->getCommandBus();
76 $this->eventBus = $container->getEventBus();
77 $this->permissionsService = $fromApi ? $container->getApiPermissionsService() : $container->getPermissionsService();
78 $this->userApplicationService = $fromApi ? $container->getApiUserApplicationService() : $container->getUserApplicationService();
79 $this->logger = $container->getLoggerService()->channel(LoggerInterface::CHANNEL_HTTP);
80 }
81
82 /**
83 * @param Request $request
84 * @param $args
85 *
86 * @return mixed
87 */
88 abstract protected function instantiateCommand(Request $request, $args);
89
90 /**
91 * Emit a success domain event, do nothing by default
92 *
93 * @param DomainEventBus $eventBus
94 *
95 * @param CommandResult $result
96 *
97 * @return void
98 */
99 protected function emitSuccessEvent(DomainEventBus $eventBus, CommandResult $result)
100 {
101 }
102
103 /**
104 * Emit a failure domain event, do nothing by default
105 *
106 * @param DomainEventBus $eventBus
107 *
108 * @param CommandResult $data
109 *
110 * @return null
111 */
112 protected function emitFailureEvent(DomainEventBus $eventBus, CommandResult $data)
113 {
114 return null;
115 }
116
117 /**
118 * @param Request $request
119 * @param Response $response
120 * @param $args
121 *
122 * @return Response
123 * @throws \InvalidArgumentException
124 * @throws \RuntimeException
125 */
126 public function __invoke(Request $request, Response $response, $args, $validApiCall = false)
127 {
128 /** @var Command $command */
129 $command = $this->instantiateCommand($request, $args);
130
131 /** @var SettingsService $settingsService */
132 $settingsService = new SettingsService(new SettingsStorage());
133
134 if (!$validApiCall && !$command->validateNonce($request)) {
135 return $response->withStatus(self::STATUS_FORBIDDEN);
136 }
137
138 if (!$validApiCall && !$command->validateCron($request)) {
139 return $response->withStatus(self::STATUS_FORBIDDEN);
140 }
141
142 $command->setPermissionService($this->permissionsService);
143 $command->setUserApplicationService($this->userApplicationService);
144
145 try {
146 /** @var CommandResult $commandResult */
147 $commandResult = $this->commandBus->handle($command);
148 } catch (PaymentValidationException $e) {
149 $commandResult = new CommandResult();
150
151 $commandResult->setResult(CommandResult::RESULT_ERROR);
152 $commandResult->setMessage(FrontendStrings::getCommonStrings()['payment_error']);
153 $commandResult->setData(
154 [
155 'paymentSuccessful' => false,
156 ]
157 );
158 } catch (AccessDeniedException $e) {
159 $response = $response->withHeader('Content-Type', 'application/json;charset=utf-8');
160 $response = $response->withStatus(self::STATUS_FORBIDDEN);
161
162 $response->getBody()->write(
163 json_encode(
164 [
165 'data' => [
166 'message' => $e->getMessage()
167 ]
168 ]
169 )
170 );
171
172 return $response;
173 } catch (CustomException $e) {
174 try {
175 $this->logger->error('Unhandled exception in controller', [
176 'command' => get_class($command),
177 'exception' => $e,
178 ]);
179 } catch (\Throwable $loggingError) {
180 // Telemetry must not block the JSON 500 response.
181 }
182
183 $response = $response->withHeader('Content-Type', 'application/json;charset=utf-8');
184 $response = $response->withStatus(self::STATUS_INTERNAL_SERVER_ERROR);
185
186 $response->getBody()->write(
187 json_encode(
188 [
189 'data' => [
190 'message' => $e->getMessage()
191 ]
192 ]
193 )
194 );
195
196 return $response;
197 } catch (AuthorizationException $e) {
198 $commandResult = new CommandResult();
199
200 $commandResult->setResult(CommandResult::RESULT_ERROR);
201 $commandResult->setData(
202 [
203 'reauthorize' => true,
204 ]
205 );
206 }
207
208 if (in_array($commandResult->getResult(), [CommandResult::RESULT_ERROR, CommandResult::RESULT_CONFLICT], true)) {
209 $this->logger->warning('Command returned non-success result', [
210 'command' => get_class($command),
211 'result' => $commandResult->getResult(),
212 'message' => $commandResult->getMessage(),
213 ]);
214 }
215
216 if ($commandResult->getResult() === CommandResult::RESULT_ERROR) {
217 if ($settingsService->getSetting('activation', 'responseErrorAsConflict')) {
218 $commandResult->setResult(CommandResult::RESULT_CONFLICT);
219 }
220 }
221
222 if ($commandResult->getUrl() !== null) {
223 $this->emitSuccessEvent($this->eventBus, $commandResult);
224
225 /** @var Response $response */
226 $response = $response->withHeader('Location', $commandResult->getUrl());
227 $response = $response->withStatus(self::STATUS_REDIRECT);
228
229 return $response;
230 }
231
232 if ($commandResult->hasAttachment() === false && $commandResult->getHtml() === null) {
233 $responseBody = [
234 'message' => $commandResult->getMessage(),
235 'data' => $commandResult->getData()
236 ];
237
238 $this->emitSuccessEvent($this->eventBus, $commandResult);
239
240 switch ($commandResult->getResult()) {
241 case (CommandResult::RESULT_SUCCESS):
242 $response = $response->withStatus(self::STATUS_OK);
243
244 break;
245 case (CommandResult::RESULT_CONFLICT):
246 $response = $response->withStatus(self::STATUS_CONFLICT);
247
248 break;
249 default:
250 $response = $response->withStatus(self::STATUS_INTERNAL_SERVER_ERROR);
251
252 break;
253 }
254
255 /** @var Response $response */
256 $response = $response->withHeader('Content-Type', 'application/json;charset=utf-8');
257
258 $response->getBody()->write(
259 $this->sendJustData ? $commandResult->getData() :
260 json_encode(
261 $commandResult->hasDataInResponse() ?
262 $responseBody : array_merge($responseBody, ['data' => []])
263 )
264 );
265 }
266
267 if (($html = $commandResult->getHtml()) !== null) {
268 /** @var Response $response */
269 $this->emitSuccessEvent($this->eventBus, $commandResult);
270
271 switch ($commandResult->getResult()) {
272 case (CommandResult::RESULT_SUCCESS):
273 $response = $response->withStatus(self::STATUS_OK);
274
275 break;
276 case (CommandResult::RESULT_CONFLICT):
277 $response = $response->withStatus(self::STATUS_CONFLICT);
278
279 break;
280 default:
281 $response = $response->withStatus(self::STATUS_INTERNAL_SERVER_ERROR);
282
283 break;
284 }
285
286 $response = $response->withHeader('Content-Type', 'text/html; charset=utf-8');
287 $response = $response->withHeader('Cache-Control', 'max-age=0');
288
289 $response->getBody()->write($html);
290 }
291
292 if (($file = $commandResult->getFile()) !== null) {
293 /** @var Response $response */
294 $response = $response->withHeader('Content-Type', $file['type']);
295 $response = $response->withHeader('Content-Disposition', 'inline; filename=' . '"' . $file['name'] . '"');
296 $response = $response->withHeader('Cache-Control', 'max-age=0');
297
298 if (array_key_exists('size', $file)) {
299 $response = $response->withHeader('Content-Length', $file['size']);
300 }
301
302 $response->getBody()->write($file['content']);
303 }
304
305 return $response;
306 }
307
308 /**
309 * @param Command $command
310 * @param $requestBody
311 */
312 protected function setCommandFields($command, $requestBody)
313 {
314 foreach ($this->allowedFields as $field) {
315 if (!isset($requestBody[$field])) {
316 continue;
317 }
318 $command->setField($field, $requestBody[$field]);
319 }
320 }
321
322 /**
323 * @param mixed $params
324 * @param array $keys
325 */
326 protected function setArrayParams(&$params, $keys = [])
327 {
328 $names = array_merge([
329 'customers',
330 'categories',
331 'services',
332 'packages',
333 'employees',
334 'providers',
335 'providerIds',
336 'locations',
337 'locationIds',
338 'ids',
339 'events',
340 'tag',
341 'dates',
342 'types',
343 'fields',
344 'statuses',
345 'stats',
346 'bookingTypes',
347 ], $keys);
348
349 foreach ($names as $name) {
350 if (!empty($params[$name])) {
351 $params[$name] = is_array($params[$name]) ? $params[$name] : explode(',', $params[$name]);
352 }
353 }
354
355 if (isset($params['dates'][0])) {
356 $params['dates'][0] = preg_match("/^\d{4}-\d{2}-\d{2}$/", $params['dates'][0]) ?
357 $params['dates'][0] : DateTimeService::getNowDate();
358 }
359
360 if (isset($params['dates'][1]) && $params['dates'][1]) {
361 $params['dates'][1] = preg_match("/^\d{4}-\d{2}-\d{2}$/", $params['dates'][1]) ?
362 $params['dates'][1] : DateTimeService::getNowDate();
363 }
364
365 if (isset($params['date'])) {
366 $params['date'] = preg_match("/^\d{4}-\d{2}-\d{2}$/", $params['date']) ?
367 $params['date'] : DateTimeService::getNowDate();
368 }
369 }
370
371 /**
372 * @param array $data
373 * @param string $field
374 * @param string $translationField
375 *
376 * @return void
377 */
378 private function filterField(&$data, $field, $translationField)
379 {
380 if (!empty($data[$field])) {
381 global $allowedposttags;
382
383 $data[$field] = wp_kses($data[$field], $allowedposttags);
384
385 if (!empty($data['translations']) && ($translations = json_decode($data['translations'], true)) !== null) {
386 if (!empty($translations[$translationField])) {
387 foreach ($translations[$translationField] as $lang => $translation) {
388 $translations[$translationField][$lang] = wp_kses(
389 $translations[$translationField][$lang],
390 $allowedposttags
391 );
392 }
393
394 $data['translations'] = json_encode($translations);
395 }
396 }
397 }
398 }
399
400 /**
401 * @param array $requestBody
402 *
403 * @return void
404 */
405 protected function filter(&$requestBody)
406 {
407 if (!current_user_can('unfiltered_html') && $requestBody) {
408 $this->filterField($requestBody, 'description', 'description');
409 $this->filterField($requestBody, 'label', 'name');
410
411 foreach (!empty($requestBody['extras']) ? $requestBody['extras'] : [] as $index => $extra) {
412 $this->filterField($requestBody['extras'][$index], 'description', 'description');
413 }
414 }
415 }
416
417 /**
418 * Helper to set HTML content on CommandResult as an inline file
419 * so the controller can respond with a text/html body.
420 *
421 * @param CommandResult $result
422 * @param string $html
423 * @param string $filename
424 *
425 * @return void
426 */
427 protected function setResultHtml(CommandResult $result, $html, $filename = 'content.html')
428 {
429 $result->setFile([
430 'type' => 'text/html; charset=utf-8',
431 'name' => $filename,
432 'size' => strlen($html),
433 'content' => $html,
434 ]);
435 }
436
437 /**
438 * @param mixed $default
439 *
440 * @return mixed
441 */
442 public static function getParam(Request $request, string $key, $default = null)
443 {
444 $params = $request->getQueryParams();
445
446 return array_key_exists($key, $params) ? $params[$key] : $default;
447 }
448 }
449