PluginProbe
Fluent Support – Helpdesk & Customer Support Ticket System / 1.10.1
Fluent Support – Helpdesk & Customer Support Ticket System v1.10.1
2.4.0 2.3.2 2.3.1 2.3.0 2.2.1 2.2.0 trunk 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.10.5 1.4.0 1.4.1 1.4.2 1.4.5 1.4.6 1.4.7 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 All 68 releases
fluent-support / vendor / wpfluent / framework / src / WPFluent / Http / Route.php

Route.php in Fluent Support – Helpdesk & Customer Support Ticket System 1.10.1, at vendor/wpfluent/framework/src/WPFluent/Http/Route.php

921 lines 23.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentSupport\Framework\Http;
4
5 use Closure;
6 use Exception;
7 use WP_REST_Request;
8 use WP_REST_Response;
9 use BadMethodCallException;
10 use InvalidArgumentException;
11 use FluentSupport\Framework\Support\Arr;
12 use FluentSupport\Framework\Support\Pipeline;
13 use FluentSupport\Framework\Validator\ValidationException;
14 use FluentSupport\Framework\Database\Orm\ModelNotFoundException;
15 use FluentSupport\Framework\Response\Response as WPFluentResponse;
16
17 class Route
18 {
19 use SubstituteRouteParametersTrait;
20
21 /**
22 * Application Instance
23 * @var \FluentSupport\Framework\Foundation\Application
24 */
25 protected $app = null;
26
27 /**
28 * Rest namespace from config
29 * @var string
30 */
31 protected $restNamespace = null;
32
33 /**
34 * Full URI
35 * @var string
36 */
37 protected $uri = null;
38
39 /**
40 * Compiled rest endpoint
41 * @var string
42 */
43 protected $compiled = null;
44
45 /**
46 * Route meta data
47 * @var array
48 */
49 protected $meta = [];
50
51 /**
52 * Rest Handler/Callback before parsing
53 * @var string
54 */
55 protected $handler = null;
56
57 /**
58 * Rest Handler/Callback after parsing
59 * @var callable|string
60 */
61 protected $action = null;
62
63 /**
64 * Rest route action info after parsing
65 * @var array
66 */
67 protected $actionInfo = [];
68
69 /**
70 * Policy Handler/Callback after parsing
71 * @var string
72 */
73 protected $permissionHandler = [];
74
75 /**
76 * HTTP Methods
77 * @var string
78 */
79 protected $method = null;
80
81 /**
82 * Rest options
83 * @var array
84 */
85 protected $options = [];
86
87 /**
88 * Route where constraints
89 * @var array
90 */
91 protected $wheres = [];
92
93 /**
94 * Rest namespace
95 * @var string
96 */
97 protected $namespace = null;
98
99 /**
100 * Policy Handler/Callback after parsing
101 * @var callable|string
102 */
103 protected $policyHandler = null;
104
105 /**
106 * Route Middleware
107 * @var array
108 */
109 protected $middleware = [
110 'before' => [],
111 'after' => []
112 ];
113
114 /**
115 * Predefined Regex foe where constraints
116 * @var array
117 */
118 protected $predefinedNamedRegx = [
119 'int' => '[0-9]+',
120 'alpha' => '[a-zA-Z]+',
121 'alpha_num' => '[a-zA-Z0-9]+',
122 'alpha_num_dash' => '[a-zA-Z0-9-_]+'
123 ];
124
125 /**
126 * Route parameters
127 * @var null|array
128 */
129 protected static $parameters = null;
130
131 /**
132 * Route substituted parameters
133 *
134 * @var null|array
135 */
136 protected static $substitutedParameters = [];
137
138 /**
139 * Construct the route instance
140 *
141 * @param \FluentSupport\Framework\Foundation\Application $app
142 * @param string $restNamespace
143 * @param string $uri
144 * @param string $handler
145 * @param string $method
146 */
147 public function __construct($app, $restNamespace, $uri, $handler, $method)
148 {
149 $this->app = $app;
150 $this->restNamespace = $restNamespace;
151 $this->uri = $uri;
152 $this->handler = $handler;
153 $this->method = $method;
154 }
155
156 /**
157 * Alternative constructor
158 *
159 * @param \FluentSupport\Framework\Foundation\Application $app
160 * @param string $restNamespace
161 * @param string $uri
162 * @param string $handler
163 * @param string $method
164 * @return self
165 */
166 public static function create($app, $namespace, $uri, $handler, $method)
167 {
168 return new static($app, $namespace, $uri, $handler, $method);
169 }
170
171 /**
172 * Set route meta
173 *
174 * @param string $key
175 * @param mixed $value
176 * @return self
177 */
178 public function meta($key, $value = null)
179 {
180 $meta = is_array($key) ? $key : [$key => $value];
181
182 $this->meta = array_merge($this->meta, $meta);
183
184 return $this;
185 }
186
187 /**
188 * Get route meta
189 *
190 * @param string $key
191 * @return mixed
192 */
193 public function getMeta($key = '')
194 {
195 if (isset($this->meta[$key])) {
196 return $this->meta[$key];
197 }
198
199 return $this->meta;
200 }
201
202 /**
203 * Get route options
204 *
205 * @param string $key
206 * @return mixed
207 */
208 public function getOption($key = null)
209 {
210 return $key ? $this->options[$key] : $this->options;
211 }
212
213 /**
214 * Get route action information
215 * @param string $key
216 * @return mixed
217 */
218 public function getAction($key = '')
219 {
220 if ($key && array_key_exists($key, $this->actionInfo)) {
221 return $this->actionInfo[$key];
222 }
223
224 return $this->actionInfo;
225 }
226
227 /**
228 * Set a where constrain into the route
229 *
230 * @param string $identifier
231 * @param string $value
232 * @return self
233 */
234 public function where($identifier, $value = null)
235 {
236 if (!is_null($value)) {
237 $this->wheres[$identifier] = $this->getValue($value);
238 } else {
239 foreach ($identifier as $key => $value) {
240 $this->wheres[$key] = $this->getValue($value);
241 }
242 }
243
244 return $this;
245 }
246
247 /**
248 * Add an integer type route constraint
249 *
250 * @param string $identifiers
251 * @return self
252 */
253 public function int($identifiers)
254 {
255 $identifiers = is_array($identifiers) ? $identifiers : func_get_args();
256
257 foreach ($identifiers as $identifier) {
258 $this->wheres[$identifier] = '[0-9]+';
259 }
260
261 return $this;
262 }
263
264 /**
265 * Add an alpha type route constraint
266 *
267 * @param string $identifiers
268 * @return self
269 */
270 public function alpha($identifiers)
271 {
272 $identifiers = is_array($identifiers) ? $identifiers : func_get_args();
273
274 foreach ($identifiers as $identifier) {
275 $this->wheres[$identifier] = '[a-zA-Z]+';
276 }
277
278 return $this;
279 }
280
281 /**
282 * Add an alphanum type route constraint
283 *
284 * @param string $identifiers
285 * @return self
286 */
287 public function alphaNum($identifiers)
288 {
289 $identifiers = is_array($identifiers) ? $identifiers : func_get_args();
290
291 foreach ($identifiers as $identifier) {
292 $this->wheres[$identifier] = '[a-zA-Z0-9]+';
293 }
294
295 return $this;
296 }
297
298 /**
299 * Add an alphanumdash type route constraint
300 *
301 * @param string $identifiers
302 * @return self
303 */
304 public function alphaNumDash($identifiers)
305 {
306 $identifiers = is_array($identifiers) ? $identifiers : func_get_args();
307
308 foreach ($identifiers as $identifier) {
309 $this->wheres[$identifier] = '[a-zA-Z0-9-_]+';
310 }
311
312 return $this;
313 }
314
315 /**
316 * Set the route before middleware
317 *
318 * @param array|string $middleware
319 * @return self
320 */
321 public function before(...$middleware)
322 {
323 return $this->middleware('before', ...$middleware);
324 }
325
326 /**
327 * Set the route after middleware
328 *
329 * @param array|string $middleware
330 * @return self
331 */
332 public function after(...$middleware)
333 {
334 return $this->middleware('after', ...$middleware);
335 }
336
337 /**
338 * Set the route middleware
339 * @param array $middleware
340 * @return self
341 */
342 public function middleware($type = 'before', ...$middleware)
343 {
344 if (is_array($middleware[0])) {
345 $middleware = reset($middleware);
346 }
347
348 $this->middleware[$type] = array_merge(
349 $this->middleware[$type], $middleware
350 );
351
352 return $this;
353 }
354
355 /**
356 * Set the route policy
357 *
358 * @param mixed $handler
359 * @param string|null $method
360 * @return self
361 */
362 public function withPolicy($handler, $method = null)
363 {
364 if (is_array($handler = $method ? func_get_args() : $handler)) {
365 $handler = implode('@', $handler);
366 }
367
368 $this->policyHandler = $handler;
369
370 if (is_string($handler) && !$this->app->hasNamespace($handler)) {
371 $this->setPolicyHandlerWithNamespace(
372 debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 4)
373 );
374 }
375
376 return $this;
377 }
378
379 /**
380 * Resolve and set policy with namespace for add-ons
381 *
382 * @param null
383 */
384 protected function setPolicyHandlerWithNamespace($backTrace)
385 {
386 $last = end($backTrace);
387
388 if (!isset($last['class'])) return;
389
390 $class = $last['class'];
391
392 $namespace = substr(__NAMESPACE__, 0, strpos(__NAMESPACE__, '\\'));
393
394 $calledClassNamespace = substr($class, 0, strpos($class, '\\'));
395
396 if ($namespace != $calledClassNamespace) {
397 $ns = $calledClassNamespace . '\\App\\Http\\Policies\\';
398 $this->policyHandler = $ns . $this->policyHandler;
399 }
400 }
401
402 /**
403 * Set the namespace for controller/action
404 * @param string $ns
405 * @return null
406 */
407 public function withNamespace($ns)
408 {
409 $this->namespace = implode('\\', $ns);
410 }
411
412 /**
413 * Register the rest endpoint
414 *
415 * @return null
416 */
417 public function register()
418 {
419 $this->setOptions();
420
421 $uri = $this->compileRoute($this->uri);
422
423 return register_rest_route($this->restNamespace, "/{$uri}", $this->options);
424 }
425
426 /**
427 * Set route options
428 *
429 * @return null
430 */
431 protected function setOptions()
432 {
433 $this->options = [
434 'args' => [],
435 'methods' => $this->method,
436 'callback' => [$this, 'callback'],
437 'permission_callback' => [$this, 'permissionCallback']
438 ];
439 }
440
441 /**
442 * Get item from predefined regex
443 * @param string $value
444 * @return string
445 */
446 protected function getValue($value)
447 {
448 if (array_key_exists($value, $this->predefinedNamedRegx)) {
449 return $this->predefinedNamedRegx[$value];
450 }
451
452 return $value;
453 }
454
455 /**
456 * Compikle the rest route to regex
457 *
458 * @param string $uri
459 * @return string compiled rest endpoint
460 */
461 protected function compileRoute($uri)
462 {
463 $params = [];
464
465 $compiledUri = preg_replace_callback('#/{(.*?)}#', function($match) use (&$params, $uri) {
466 // Default regx
467 $regx = '[^\s(?!/)]+';
468
469 $param = trim($match[1]);
470
471 if ($isOptional = strpos($param, '?')) {
472 $param = trim($param, '?');
473 }
474
475 if (in_array($param, $params)) {
476 throw new InvalidArgumentException(
477 "Duplicate parameter name '{$param}' found in {$uri}.", 500
478 );
479 }
480
481 $params[] = $param;
482
483 if (isset($this->wheres[$param])) {
484 $regx = $this->wheres[$param];
485 }
486
487 $pattern = "/(?P<" . $param . ">" . $regx . ")";
488
489 if ($isOptional) {
490 $pattern = "(?:" . $pattern . ")?";
491 }
492
493 $this->options['args'][$param]['required'] = !$isOptional;
494
495 return $pattern;
496
497 }, $uri);
498
499 return $this->compiled = $compiledUri;
500 }
501
502 /**
503 * Route handler
504 *
505 * @return mixed
506 */
507 public function callback()
508 {
509 try {
510
511 $response = $this->app->call(
512 $this->action, $this->getControllerParameters()
513 );
514
515 if ($response instanceof WPFluentResponse) {
516 $response = $response->toArray();
517 }
518
519 if (!($response instanceof WP_REST_Response)) {
520 if (is_wp_error($response)) {
521 $response = $this->app->response->wpErrorToResponse($response);
522 } else {
523 $response = $this->app->response->sendSuccess($response);
524 }
525 }
526
527 return $this->app->make(Pipeline::class)
528 ->send($response)
529 ->through($this->collectMiddleWare('after'))
530 ->then(function($response) {
531 if (!$response instanceof WP_REST_Response) {
532 $response = new WP_REST_Response($response);
533 }
534 return $response;
535 });
536
537 } catch (ValidationException $e) {
538 return $this->app->response->sendError(
539 $e->errors(), $e->getCode()
540 );
541 } catch (ModelNotFoundException $e) {
542 return $this->app->response->sendError([
543 'message' => $e->getMessage()
544 ], 404);
545 } catch (Exception $e) {
546 return $this->app->response->sendError([
547 'message' => $e->getMessage()
548 ], $e->getCode());
549 }
550 }
551
552 /**
553 * Permission callback for route
554 * @param \WP_REST_Request $wpRestRequest
555 * @return mixed
556 */
557 public function permissionCallback($wpRestRequest)
558 {
559 $this->app->instance('route', $this);
560
561 if (!$this->app->bound('wprestrequest')) {
562 $this->app->instance('wprestrequest', $wpRestRequest);
563 $this->app->request->mergeInputsFromRestRequest($wpRestRequest);
564
565 if (method_exists($this, 'prepareCallbacks')) {
566 $this->prepareCallbacks($this->app->request);
567 }
568 }
569
570 return $this->app->make(Pipeline::class)
571 ->send($this->app->request)
572 ->through($this->collectMiddleWare('before'))
573 ->then(function($request) {
574 return $this->dispatchPermissionHandler();
575 });
576 }
577
578 /**
579 * Dispatches the permission handler
580 *
581 * @return bool|null
582 */
583 protected function dispatchPermissionHandler()
584 {
585 if ($this->permissionHandler) {
586 return $this->app->call(
587 $this->permissionHandler,
588 $this->getControllerParameters()
589 );
590 }
591 }
592
593 /**
594 * Gether route params after substituted the params
595 *
596 * @return array
597 */
598 protected function getControllerParameters()
599 {
600 $routeParameters = [];
601
602 if (!static::$substitutedParameters) {
603 if ($routeParameters = $this->getParameter()) {
604 $routeParameters = $this->SubstituteParameters($routeParameters);
605 }
606 } else {
607 $routeParameters = static::$substitutedParameters;
608 }
609
610 return $routeParameters;
611 }
612
613 /**
614 * Added the ability to add middleware so we can intercept
615 * the request without modifying the source code again
616 * and again. The middleware class will implement
617 * the handle method as given below:
618 *
619 * public function handle($request, $next)
620 *
621 * And must return $next($request) to handle the request.
622 * Otherwise return nothing to abort the request.
623 * Optionally, you may call the abort method:
624 * return $request->abort(code, message);
625 *
626 * @param string $type
627 * @return array
628 */
629 protected function collectMiddleWare($type = 'before')
630 {
631 $middleware = $this->app['config']->get('middleware', []);
632
633 $callableMiddleware = Arr::get($middleware, "global.{$type}", []);
634
635 $routeArray = [];
636
637 if (isset($middleware['route'])) {
638 $routeArray = $middleware['route'];
639 if (isset($routeArray[$type])) {
640 $routeArray = $routeArray[$type];
641 }
642 }
643
644 if (!$routeArray) return [];
645
646 foreach ($this->middleware[$type] as $routeMiddleware) {
647
648 if (is_object($routeMiddleware)) {
649 $handler = $routeMiddleware;
650 } else {
651 $pieces = explode(':', $routeMiddleware);
652 $handler = Arr::get($routeArray, $key = reset($pieces));
653
654 if (isset($pieces[1])) {
655 $handler = $this->resolveMiddleware($handler, $pieces);
656 }
657 }
658
659 if (isset($handler)) {
660 $this->addMiddlewareInTheStack($callableMiddleware, $handler);
661 } else {
662 if (isset($key)) {
663 $mpath = 'config.middleware.route.' . $type;
664 $msg = "No middleware is assigned for the key: {$key} in {$mpath} array.";
665 } else {
666 $msg = "Could't resolve middleware.";
667 }
668
669 throw new InvalidArgumentException($msg);
670 }
671 }
672
673 return $callableMiddleware;
674 }
675
676 /**
677 * Resolve the middleware
678 *
679 * @param mixed $handler
680 * @param aray $pieces
681 * @return object
682 */
683 protected function resolveMiddleware($handler, $pieces)
684 {
685 if (is_object($handler)) {
686 $handler = $this->wrapMiddleware($handler, $pieces);
687 } elseif (is_string($handler)) {
688 $handler = $handler . ':' . str_replace(' ', '', end($pieces));
689 }
690
691 return $handler;
692 }
693
694 /**
695 * Create a class to wrap the middleware
696 *
697 * @param mixed $handler
698 * @param aray $pieces
699 * @return object
700 */
701 protected function wrapMiddleware($handler, $pieces)
702 {
703 $params = str_replace(' ', '', end($pieces));
704
705 $params = explode(',', $params);
706
707 return new class ($handler, $params) {
708 protected $handler, $params = null;
709 public function __construct($handler, $params) {
710 $this->handler = $handler;
711 $this->params = $params;
712 }
713 public function handle($target, $next) {
714 if (is_callable($this->handler)) {
715 return ($this->handler)($target, $next, ...$this->params);
716 } else {
717 return $this->handler->handle(
718 $target, $next, ...$this->params
719 );
720 }
721 }
722 };
723 }
724
725 /**
726 * Add the middleware in the stack
727 *
728 * @param array &$stack All callable middleware for the route
729 * @param null
730 */
731 protected function addMiddlewareInTheStack(&$stack, $middleware)
732 {
733 if (!in_array($middleware, $stack)) {
734 $stack[] = $middleware;
735 }
736 }
737
738 /**
739 * Resolve the policy handler
740 *
741 * @param string $policyHandler
742 * @return mixed
743 */
744 protected function getPolicyHandler($policyHandler)
745 {
746 if (!$policyHandler) {
747 return [$this, 'defaultPolicyHandler'];
748 }
749
750 if ($policyHandler instanceof Closure) {
751 return $policyHandler;
752 }
753
754 if ($this->isPolicyHandlerParseable($policyHandler)) {
755 return $policyHandler;
756 }
757
758 if (is_string($policyHandler) && $this->handler instanceof Closure) {
759
760 if (class_exists($policyHandler)) {
761
762 $reflection = new \ReflectionClass($policyHandler);
763
764 if ($reflection->hasMethod('verifyRequest')) {
765
766 $policyHandler = $policyHandler . '@' . 'verifyRequest';
767
768 return $policyHandler;
769 }
770 } elseif (function_exists($policyHandler)) {
771 return $policyHandler;
772 }
773
774 throw new InvalidArgumentException(
775 'Explicit policy handler is required while using a closure as route callback.'
776 );
777 }
778
779 if ($policyHandler && !function_exists($policyHandler)) {
780 if (is_string($this->handler) && strpos($this->handler, '@') !== false) {
781 list($_, $method) = explode('@', $this->handler);
782 $policyHandler = $policyHandler . '@' . $method;
783 } else if (is_array($this->handler)) {
784 $policyHandler = $policyHandler . '@' . $this->handler[1];
785 }
786 }
787
788 return $policyHandler ?: [$this, 'defaultPolicyHandler'];
789 }
790
791 protected function isPolicyHandlerParseable($policyHandler)
792 {
793 return (strpos($policyHandler, '@') === true
794 || strpos($policyHandler, '::') === true);
795 }
796
797 /**
798 * Default/Fallback policy handler for the route
799 *
800 * @return bool
801 */
802 public function defaultPolicyHandler()
803 {
804 return true;
805 }
806
807 /**
808 * Parse the rest and permission/policy handlers
809 *
810 * @param \WP_REST_Request $request
811 * @return null
812 * @throws \BadMethodCallException
813 */
814 public function prepareCallbacks($request)
815 {
816 $handler = $this->app->parseRestHandler(
817 $this->handler, $this->namespace
818 );
819
820 if ($handler instanceof Closure) {
821 $action = 'Closure';
822 $controller = null;
823 } else {
824 $handler = trim($handler, '\\');
825 $action = explode('@', $handler);
826 $pieces = explode('\\', $action[0]);
827 $controller = end($pieces);
828 }
829
830 try {
831 $policyHandler = $this->app->parsePolicyHandler(
832 $this->getPolicyHandler($this->policyHandler)
833 );
834
835 if ($policyHandler) {
836 $this->permissionHandler = $policyHandler;
837
838 // Adjust policy handler if the method was explicitly given
839 if (is_string($this->policyHandler)) {
840 if (is_array($policyHandler) && isset($policyHandler[1])) {
841 if ($pieces = explode('@', $this->policyHandler)) {
842 if (isset($pieces[1])) {
843 $this->permissionHandler[1] = $pieces[1];
844 }
845 }
846 }
847 }
848
849 if (!is_callable($this->permissionHandler)) {
850 throw new Exception;
851 }
852 }
853
854 } catch (Exception $e) {
855 $pHandler = $this->policyHandler;
856 if (is_array($this->permissionHandler) && $this->permissionHandler) {
857 $pHandler = is_object($this->permissionHandler[0]) ?
858 get_class($this->permissionHandler[0]) . ':' . $this->permissionHandler[1] :
859 $this->permissionHandler[0] . ':' . $this->permissionHandler[1];
860 }
861
862 throw new BadMethodCallException(
863 "The permission callback {$pHandler} is invalid or not callable."
864 );
865 }
866
867 if (is_array($policyHandler)) {
868 $policyHandler[0] = get_class($policyHandler[0]);
869 }
870
871 $this->actionInfo = [
872 'handler' => is_object($handler) ? $action : $handler,
873 'controller' => $controller,
874 'method' => is_array($action) ? $action[1] : null,
875 'path' => $this->uri,
876 'http_method' => $request->get_method(),
877 'full_uri' => $request->get_route(),
878 'permission_callback' => $policyHandler,
879 'compiled_url' => $this->compiled
880 ];
881
882
883 $this->action = $handler;
884
885 if ($routeParameters = $this->getParameter()) {
886 static::$substitutedParameters = $this->SubstituteParameters(
887 $routeParameters
888 );
889 }
890
891
892 return $this->action;
893 }
894
895 /**
896 * Get route one or more parameters
897 * @param string $key
898 *
899 * @return mixed
900 */
901 public function getParameter($key = null)
902 {
903 if (is_null(static::$parameters)) {
904 static::$parameters = $this->app->request->get_url_params();
905 }
906
907 return $key ? static::$parameters[$key] : static::$parameters;
908 }
909
910 /**
911 * Dynamically access a route parameter.
912 *
913 * @param string $key
914 * @return mixed
915 */
916 public function __get($key)
917 {
918 return $this->getParameter($key);
919 }
920 }
921