PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 1.41
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v1.41
2.1.0 2.0.15 2.0.12 2.0.10 2.0.4 2.0.1 2.0.0 1.95.3 1.95.2 1.95 1.91.6 trunk 1.11 1.12 1.13 1.20 1.21 1.22 1.23 1.30 1.31 1.32 1.35 1.40 1.41 All 42 releases
fluent-boards / vendor / wpfluent / framework / src / WPFluent / Http / Route.php

Route.php in FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration 1.41, at vendor/wpfluent/framework/src/WPFluent/Http/Route.php

1,188 lines 29.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentBoards\Framework\Http;
4
5 use Closure;
6 use Exception;
7 use WP_Error;
8 use WP_REST_Request;
9 use WP_REST_Response;
10 use BadMethodCallException;
11 use InvalidArgumentException;
12 use FluentBoards\Framework\Support\Arr;
13 use FluentBoards\Framework\Support\Pipeline;
14 use FluentBoards\Framework\Http\Request\Request;
15 use FluentBoards\Framework\Http\Middleware\RateLimiter;
16 use FluentBoards\Framework\Validator\ValidationException;
17 use FluentBoards\Framework\Database\Orm\ModelNotFoundException;
18 use FluentBoards\Framework\Response\Response as WPFluentResponse;
19
20 class Route
21 {
22 use SubstituteRouteParametersTrait;
23
24 /**
25 * Application Instance
26 * @var \FluentBoards\Framework\Foundation\Application
27 */
28 protected $app = null;
29
30 /**
31 * Rest namespace from config
32 * @var string
33 */
34 protected $restNamespace = null;
35
36 /**
37 * Full URI
38 * @var string
39 */
40 protected $uri = null;
41
42 /**
43 * Compiled rest endpoint
44 * @var string
45 */
46 protected $compiled = null;
47
48 /**
49 * Route meta data
50 * @var array
51 */
52 protected $meta = [];
53
54 /**
55 * Rest Handler/Callback before parsing
56 * @var string
57 */
58 protected $handler = null;
59
60 /**
61 * Rest Handler/Callback after parsing
62 * @var callable|string
63 */
64 protected $action = null;
65
66 /**
67 * Rest route action info after parsing
68 * @var array
69 */
70 protected $actionInfo = [];
71
72 /**
73 * Policy Handler/Callback after parsing
74 * @var string
75 */
76 protected $permissionHandler = [];
77
78 /**
79 * HTTP Methods
80 * @var string
81 */
82 protected $method = null;
83
84 /**
85 * Rest options
86 * @var array
87 */
88 protected $options = [];
89
90 /**
91 * Route where constraints
92 * @var array
93 */
94 protected $wheres = [];
95
96 /**
97 * Rest namespace
98 * @var string
99 */
100 protected $namespace = null;
101
102 /**
103 * Policy Handler/Callback after parsing
104 * @var callable|string
105 */
106 protected $policyHandler = null;
107
108 /**
109 * Route Middleware
110 * @var array
111 */
112 protected $middleware = [
113 'before' => [],
114 'after' => []
115 ];
116
117 /**
118 * Skips middlewar if true
119 *
120 * @var boolean
121 */
122 protected $skipMiddleware = false;
123
124 /**
125 * Predefined Regex foe where constraints
126 * @var array
127 */
128 protected $predefinedNamedRegx = [
129 'int' => '[0-9]+',
130 'alpha' => '[a-zA-Z]+',
131 'alpha_num' => '[a-zA-Z0-9]+',
132 'alpha_num_dash' => '[a-zA-Z0-9-_]+'
133 ];
134
135 /**
136 * Route parameters
137 * @var null|array
138 */
139 protected $parameters = null;
140
141 /**
142 * Route substituted parameters
143 *
144 * @var null|array
145 */
146 protected $substitutedParameters = [];
147
148 /**
149 * Is route signed
150 *
151 * @var boolean
152 */
153 protected $signed = false;
154
155 /**
156 * Construct the route instance
157 *
158 * @param \FluentBoards\Framework\Foundation\Application $app
159 * @param string $restNamespace
160 * @param string $uri
161 * @param string $handler
162 * @param string $method
163 */
164 public function __construct($app, $restNamespace, $uri, $handler, $method)
165 {
166 $this->app = $app;
167 $this->restNamespace = $restNamespace;
168 $this->uri = $uri;
169 $this->handler = $handler;
170 $this->method = $method;
171
172 $this->preparefrontendHandlers($handler);
173 }
174
175 /**
176 * Map the route to be used in front-end.
177 *
178 * @param mixed $handler
179 * @return null
180 */
181 protected function preparefrontendHandlers($handler)
182 {
183 $endpointsUrl = $this->app->config->get('app.slug') . '/__endpoints';
184
185 if (get_option('permalink_structure')) {
186 $url = $this->app->request->url();
187 } else {
188 $url = $this->app->request->query('rest_route');
189 }
190
191 if (!str_contains($url ?? '', $endpointsUrl)) {
192 return;
193 }
194
195 if ($handler instanceof Closure) {
196 return;
197 }
198
199 $action = trim($this->app->parseRestHandler($handler), '\\');
200
201 [$controller, $cb] = explode('@', $action);
202
203 $controller = str_replace('\\', '.', $controller);
204
205 $endpoints = $this->app->endpoints;
206
207 $endpoints[$controller]["_{$cb}"] = [
208 'uri' => $this->uri,
209 'methods' => explode(',', $this->method)
210 ];
211
212 $this->app->endpoints = $endpoints;
213 }
214
215 /**
216 * Alternative constructor
217 *
218 * @param \FluentBoards\Framework\Foundation\Application $app
219 * @param string $restNamespace
220 * @param string $uri
221 * @param string $handler
222 * @param string $method
223 * @return self
224 */
225 public static function create($app, $namespace, $uri, $handler, $method)
226 {
227 return new static($app, $namespace, $uri, $handler, $method);
228 }
229
230 /**
231 * Set route meta
232 *
233 * @param string $key
234 * @param mixed $value
235 * @return self
236 */
237 public function meta($key, $value = null)
238 {
239 $meta = is_array($key) ? $key : [$key => $value];
240
241 $this->meta = array_merge($this->meta, $meta);
242
243 return $this;
244 }
245
246 /**
247 * Get route meta
248 *
249 * @param string $key
250 * @return mixed
251 */
252 public function getMeta($key = '')
253 {
254 if (isset($this->meta[$key])) {
255 return $this->meta[$key];
256 }
257
258 return $this->meta;
259 }
260
261 /**
262 * Get route options
263 *
264 * @return mixed
265 */
266 public function getOptions()
267 {
268 return $this->getOption();
269 }
270
271 /**
272 * Get route options
273 *
274 * @param string $key
275 * @return mixed
276 */
277 public function getOption($key = null)
278 {
279 return $key ? $this->options[$key] : $this->options;
280 }
281
282 /**
283 * Get route action information
284 * @param string $key
285 * @return mixed
286 */
287 public function getAction($key = '')
288 {
289 if ($key && array_key_exists($key, $this->actionInfo)) {
290 return $this->actionInfo[$key];
291 }
292
293 return $this->actionInfo;
294 }
295
296 /**
297 * Set a where constrain into the route
298 *
299 * @param string $identifier
300 * @param string $value
301 * @return self
302 */
303 public function where($identifier, $value = null)
304 {
305 if (!is_null($value)) {
306 $this->wheres[$identifier] = $this->getValue($value);
307 } else {
308 foreach ($identifier as $key => $value) {
309 $this->wheres[$key] = $this->getValue($value);
310 }
311 }
312
313 return $this;
314 }
315
316 /**
317 * Add an integer type route constraint
318 *
319 * @param string $identifiers
320 * @return self
321 */
322 public function int($identifiers)
323 {
324 $identifiers = is_array($identifiers) ? $identifiers : func_get_args();
325
326 foreach ($identifiers as $identifier) {
327 $this->wheres[$identifier] = '[0-9]+';
328 }
329
330 return $this;
331 }
332
333 /**
334 * Add an alpha type route constraint
335 *
336 * @param string $identifiers
337 * @return self
338 */
339 public function alpha($identifiers)
340 {
341 $identifiers = is_array($identifiers) ? $identifiers : func_get_args();
342
343 foreach ($identifiers as $identifier) {
344 $this->wheres[$identifier] = '[a-zA-Z]+';
345 }
346
347 return $this;
348 }
349
350 /**
351 * Add an alphanum type route constraint
352 *
353 * @param string $identifiers
354 * @return self
355 */
356 public function alphaNum($identifiers)
357 {
358 $identifiers = is_array($identifiers) ? $identifiers : func_get_args();
359
360 foreach ($identifiers as $identifier) {
361 $this->wheres[$identifier] = '[a-zA-Z0-9]+';
362 }
363
364 return $this;
365 }
366
367 /**
368 * Add an alphanumdash type route constraint
369 *
370 * @param string $identifiers
371 * @return self
372 */
373 public function alphaNumDash($identifiers)
374 {
375 $identifiers = is_array($identifiers) ? $identifiers : func_get_args();
376
377 foreach ($identifiers as $identifier) {
378 $this->wheres[$identifier] = '[a-zA-Z0-9-_]+';
379 }
380
381 return $this;
382 }
383
384 /**
385 * Set the route before middleware
386 *
387 * @param array|string $middleware
388 * @return self
389 */
390 public function before(...$middleware)
391 {
392 return $this->middleware('before', ...$middleware);
393 }
394
395 /**
396 * Set the route after middleware
397 *
398 * @param array|string $middleware
399 * @return self
400 */
401 public function after(...$middleware)
402 {
403 return $this->middleware('after', ...$middleware);
404 }
405
406 /**
407 * Set the route middleware
408 * @param array $middleware
409 * @return self
410 */
411 public function middleware($type = 'before', ...$middleware)
412 {
413 if (is_array($middleware[0])) {
414 $middleware = reset($middleware);
415 }
416
417 $this->middleware[$type] = array_merge(
418 $this->middleware[$type], $middleware
419 );
420
421 return $this;
422 }
423
424 /**
425 * Set the route policy
426 *
427 * @param mixed $handler
428 * @param string|null $method
429 * @return self
430 */
431 public function withPolicy($handler, $method = null)
432 {
433 if (is_array($handler = $method ? func_get_args() : $handler)) {
434 $handler = implode('@', $handler);
435 }
436
437 $this->policyHandler = $handler;
438
439 if (is_string($handler) && !$this->app->hasNamespace($handler)) {
440 $this->setPolicyHandlerWithNamespace(
441 debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 4)
442 );
443 }
444
445 return $this;
446 }
447
448 /**
449 * Resolve and set policy with namespace for add-ons
450 *
451 * @param null
452 */
453 protected function setPolicyHandlerWithNamespace($backTrace)
454 {
455 $last = end($backTrace);
456
457 if (!isset($last['class'])) return;
458
459 $class = $last['class'];
460
461 $namespace = substr(__NAMESPACE__, 0, strpos(__NAMESPACE__, '\\'));
462
463 $calledClassNamespace = substr($class, 0, strpos($class, '\\'));
464
465 if ($namespace != $calledClassNamespace) {
466 $ns = $calledClassNamespace . '\\App\\Http\\Policies\\';
467 $this->policyHandler = $ns . $this->policyHandler;
468 }
469 }
470
471 /**
472 * Set the namespace for controller/action
473 * @param string $ns
474 * @return null
475 */
476 public function withNamespace($ns)
477 {
478 $this->namespace = implode('\\', $ns);
479 }
480
481 /**
482 * Sign the route.
483 *
484 * @return $this
485 */
486 public function signed()
487 {
488 $this->signed = true;
489
490 return $this;
491 }
492
493 /**
494 * Apply rate limit to the route.
495 *
496 * @param int $limit Number of allowed requests.
497 * @param int $interval Time interval in seconds.
498 *
499 * @return $this
500 */
501 public function rateLimit($limit, $interval)
502 {
503 // Since the rate limiter is applied twice because
504 // WordPress sends an extra request for every
505 // request, so we need to double the limit.
506
507 $rateLimiter = new RateLimiter($limit * 2, $interval);
508
509 $this->middleware('before', $rateLimiter);
510
511 return $this;
512 }
513
514 /**
515 * Apply a rate limit to the route for per minute.
516 *
517 * @param int $limit The maximum number of requests allowed per minute.
518 * @return $this
519 */
520 public function rateLimitPerMinute($limit)
521 {
522 return $this->rateLimit($limit, MINUTE_IN_SECONDS);
523 }
524
525 /**
526 * Apply an hourly rate limit to the route.
527 *
528 * @param int $limit The maximum number of requests allowed per hour.
529 * @return $this
530 */
531 public function rateLimitHourly($limit)
532 {
533 return $this->rateLimit($limit, HOUR_IN_SECONDS);
534 }
535
536 /**
537 * Apply a daily basis (24 hours) rate limit to the route.
538 *
539 * @param int $limit The maximum number of requests allowed per day.
540 * @return $this
541 */
542 public function rateLimitDaily($limit)
543 {
544 return $this->rateLimit($limit, DAY_IN_SECONDS);
545 }
546
547 /**
548 * Register the rest endpoint
549 *
550 * @return null
551 */
552 public function register()
553 {
554 $this->setOptions();
555
556 $uri = '/' . trim($this->compileRoute($this->uri), '/');
557
558 return register_rest_route(
559 $this->restNamespace, $uri, $this->getOptions()
560 );
561 }
562
563 /**
564 * Set route options
565 *
566 * @return null
567 */
568 protected function setOptions()
569 {
570 $this->options = [
571 [
572 'methods' => $this->method,
573 'callback' => [$this, 'callback'],
574 'permission_callback' => [$this, 'permissionCallback'],
575 'args' => [],
576 ],
577 'schema' => [$this, 'getSchema'],
578 ];
579 }
580
581 /**
582 * Generate and return the schema for the route.
583 *
584 * @return \Closure
585 * @see https://developer.wordpress.org/rest-api/extending-the-rest-api/schema
586 */
587 public function getSchema()
588 {
589 return function () {
590 return [];
591 };
592 }
593
594 /**
595 * Get item from predefined regex
596 * @param string $value
597 * @return string
598 */
599 protected function getValue($value)
600 {
601 if (array_key_exists($value, $this->predefinedNamedRegx)) {
602 return $this->predefinedNamedRegx[$value];
603 }
604
605 return $value;
606 }
607
608 /**
609 * Compikle the rest route to regex
610 *
611 * @param string $uri
612 * @return string compiled rest endpoint
613 */
614 protected function compileRoute($uri)
615 {
616 $params = [];
617
618 $compiledUri = preg_replace_callback('#/{(.*?)}#', function ($match) use (&$params, $uri) {
619 // Default regx
620 $regx = '[^\s(?!/)]+';
621
622 $param = trim($match[1]);
623
624 if ($isOptional = strpos($param, '?')) {
625 $param = trim($param, '?');
626 }
627
628 if (in_array($param, $params)) {
629 throw new InvalidArgumentException(
630 "Duplicate parameter name '{$param}' found in {$uri}.", 500
631 );
632 }
633
634 $params[] = $param;
635
636 if (isset($this->wheres[$param])) {
637 $regx = $this->wheres[$param];
638 }
639
640 $pattern = "/(?P<" . $param . ">" . $regx . ")";
641
642 if ($isOptional) {
643 $pattern = "(?:" . $pattern . ")?";
644 }
645
646 $this->options['args'][$param]['required'] = !$isOptional;
647
648 return $pattern;
649
650 }, $uri);
651
652 return $this->compiled = $compiledUri;
653 }
654
655 /**
656 * Route handler
657 *
658 * @return mixed
659 */
660 public function callback()
661 {
662 try {
663 return $this->handleAfterMiddleware(
664 $response = $this->dispatchRouteAction()
665 );
666
667 } catch (ValidationException $e) {
668 return $this->app->response->sendError(
669 $e->errors(), $e->getCode()
670 );
671 } catch (ModelNotFoundException $e) {
672 return $this->app->response->sendError([
673 'message' => $e->getMessage()
674 ], 404);
675 } catch (Exception $e) {
676 return $this->app->response->sendError([
677 'message' => $e->getMessage()
678 ], $e->getCode() ?: 500);
679 }
680 }
681
682 /**
683 * Dispatch the route action.
684 *
685 * @return mixed
686 */
687 protected function dispatchRouteAction()
688 {
689 $response = $this->app->call(
690 $this->action, $this->getControllerParameters()
691 );
692
693 if ($response instanceof WPFluentResponse) {
694 $response = $response->toArray();
695 } elseif (!($response instanceof WP_REST_Response)) {
696 $response = !is_wp_error($response) ?
697 $this->app->response->sendSuccess($response) :
698 $this->app->response->wpErrorToResponse($response);
699 }
700
701 return $response;
702 }
703
704 /**
705 * Handle after middleware if any.
706 *
707 * @param mixed $response
708 * @return mixed
709 */
710 protected function handleAfterMiddleware($response)
711 {
712 if (!$this->skipMiddleware) {
713 $response = $this->app->make(Pipeline::class)
714 ->send($response)
715 ->through($this->collectMiddleWare('after'))
716 ->then(function ($response) {
717 if (!$response instanceof WP_REST_Response) {
718 $response = new WP_REST_Response($response);
719 }
720 return $response;
721 });
722
723 if (!$response) {
724 $response = $this->app->request->abort();
725 }
726 }
727
728 return $response;
729 }
730
731 /**
732 * Permission callback for route
733 * @param \WP_REST_Request $wpRestRequest
734 * @return mixed
735 */
736 public function permissionCallback($wpRestRequest)
737 {
738 try {
739 $this->app->instance('route', $this);
740 $this->app->instance('wprestrequest', $wpRestRequest);
741 $this->app->request->mergeInputsFromRestRequest($wpRestRequest);
742 $this->prepareCallbacks($this->app->request);
743
744 if (!$this->isThisValidSignedRoute()) {
745 throw new Exception('Invalid Signature', 403);
746 }
747
748 $response = $this->app->make(Pipeline::class)
749 ->send($this->app->request)
750 ->through($this->collectMiddleWare('before'))
751 ->then(function ($request) {
752 if ($request && $request instanceof Request) {
753 return $this->dispatchPermissionHandler();
754 }
755 });
756
757 if (is_wp_error($response)) {
758 throw new Exception(
759 $response->get_error_message(),
760 is_int($code = $response->get_error_code()) ? $code : 403
761 );
762 }
763
764 if ($response instanceof WP_REST_Response) {
765 $data = $response->get_data();
766
767 throw new Exception(
768 $data['message'] ?? $response->get_status(),
769 $response->get_status()
770 );
771 }
772
773 return $response;
774
775 } catch (Exception $e) {
776 return new WP_Error(
777 'Permission Callback Error',
778 $e->getMessage(), [
779 'status' => $e->getCode() ?: 403
780 ]
781 );
782 }
783 }
784
785 /**
786 * Checks if the route is signed and needs validation.
787 *
788 * @return boolean [description]
789 */
790 protected function isThisValidSignedRoute()
791 {
792 if (!$this->signed) return true;
793
794 $request = $this->app->make('request');
795
796 if ($this->app->make('url')->validate($request->getFullUrl())) {
797 parse_str($this->app->make('encrypter')->decrypt(
798 $this->app->request->get('_data')
799 ), $query);
800
801 $this->app->request->merge(
802 Arr::except($query, ['expires_at'])
803 );
804
805 $this->app->request->forget('_data');
806
807 return true;
808 }
809 }
810
811 /**
812 * Dispatches the permission handler
813 *
814 * @return bool|null
815 */
816 protected function dispatchPermissionHandler()
817 {
818 if ($this->permissionHandler) {
819 return $this->app->call(
820 $this->permissionHandler,
821 $this->getControllerParameters()
822 );
823 }
824 }
825
826 /**
827 * Gether route params after substituted the params
828 *
829 * @return array
830 */
831 protected function getControllerParameters()
832 {
833 $routeParameters = [];
834
835 if (!$this->substitutedParameters) {
836 if ($routeParameters = $this->getParameter()) {
837 $routeParameters = $this->SubstituteParameters($routeParameters);
838 }
839 } else {
840 $routeParameters = $this->substitutedParameters;
841 }
842
843 return $routeParameters;
844 }
845
846 /**
847 * Added the ability to add middleware so we can intercept
848 * the request without modifying the source code again
849 * and again. The middleware class will implement
850 * the handle method as given below:
851 *
852 * public function handle($request, $next)
853 *
854 * And must return $next($request) to handle the request.
855 * Otherwise return nothing to abort the request.
856 * Optionally, you may call the abort method:
857 * return $request->abort(code, message);
858 *
859 * @param string $type
860 * @return array
861 */
862 protected function collectMiddleWare($type = 'before')
863 {
864 $middleware = $this->app['config']->get('middleware', []);
865
866 $callableMiddleware = Arr::get($middleware, "global.{$type}", []);
867
868 $routeArray = [];
869
870 if (isset($middleware['route'])) {
871 $routeArray = $middleware['route'];
872 if (isset($routeArray[$type])) {
873 $routeArray = $routeArray[$type];
874 }
875 }
876
877 foreach ($this->middleware[$type] as $routeMiddleware) {
878
879 if (is_object($routeMiddleware)) {
880 $handler = $routeMiddleware;
881 } elseif (class_exists($routeMiddleware)) {
882 $handler = $this->resolveMiddlewareFrom($routeMiddleware);
883 } else {
884 $pieces = explode(':', $routeMiddleware);
885 $handler = Arr::get($routeArray, $key = reset($pieces));
886 if (isset($pieces[1])) {
887 $handler = $this->resolveMiddleware($handler, $pieces);
888 }
889 }
890
891 if (isset($handler)) {
892 $this->addMiddlewareInTheStack($callableMiddleware, $handler);
893 } else {
894 if (isset($key)) {
895 $mpath = 'config.middleware.route.' . $type;
896 $msg = "No middleware is assigned for the key: {$key} in {$mpath} array.";
897 } else {
898 $msg = "Could't resolve middleware.";
899 }
900
901 throw new InvalidArgumentException($msg);
902 }
903 }
904
905 return $callableMiddleware;
906 }
907
908 /**
909 * Resolve a middleware from a class.
910 *
911 * @param string $class
912 * @return \Closure
913 */
914 protected function resolveMiddlewareFrom($class)
915 {
916 return (new $class);
917 return static function ($r, $next, ...$params) use ($class) {
918 return (new $class)->handle($r, $next, ...$params);
919 };
920 }
921
922 /**
923 * Resolve the middleware
924 *
925 * @param mixed $handler
926 * @param aray $pieces
927 * @return object
928 */
929 protected function resolveMiddleware($handler, $pieces)
930 {
931 if (is_object($handler)) {
932 $handler = $this->wrapMiddleware($handler, $pieces);
933 } elseif (is_string($handler)) {
934 $handler = $handler . ':' . str_replace(' ', '', end($pieces));
935 }
936
937 return $handler;
938 }
939
940 /**
941 * Create a class to wrap the middleware
942 *
943 * @param mixed $handler
944 * @param aray $pieces
945 * @return object
946 */
947 protected function wrapMiddleware($handler, $pieces)
948 {
949 $params = str_replace(' ', '', end($pieces));
950
951 $params = explode(',', $params);
952
953 return new class ($handler, $params) {
954 protected $handler, $params = null;
955
956 public function __construct($handler, $params)
957 {
958 $this->handler = $handler;
959 $this->params = $params;
960 }
961
962 public function handle($r, $next)
963 {
964 if (is_callable($this->handler)) {
965 return ($this->handler)($r, $next, ...$this->params);
966 } else {
967 if (!method_exists($this->handler, 'handle')) {
968 $class = get_class($this->handler);
969 throw new InvalidArgumentException(
970 "The {$class} must implement the handle method."
971 );
972 }
973 return $this->handler->handle($r, $next, ...$this->params);
974 }
975 }
976 };
977 }
978
979 /**
980 * Add the middleware in the stack
981 *
982 * @param array &$stack All callable middleware for the route
983 * @param null
984 */
985 protected function addMiddlewareInTheStack(&$stack, $middleware)
986 {
987 if (!in_array($middleware, $stack)) {
988 $stack[] = $middleware;
989 }
990 }
991
992 /**
993 * Resolve the policy handler
994 *
995 * @param string $policyHandler
996 * @return mixed
997 */
998 protected function getPolicyHandler($policyHandler)
999 {
1000 if (!$policyHandler) {
1001 return [$this, 'defaultPolicyHandler'];
1002 }
1003
1004 if (is_callable($policyHandler)) {
1005 return $policyHandler;
1006 }
1007
1008 if (is_string($policyHandler)) {
1009
1010 if (function_exists($policyHandler)) {
1011 return $policyHandler;
1012 }
1013
1014 $policyHandlerFunction = substr($policyHandler, strrpos($policyHandler, '\\') + 1);
1015
1016 if (function_exists($policyHandlerFunction)) {
1017 return $policyHandlerFunction;
1018 }
1019 }
1020
1021 if ($this->isPolicyHandlerParseable($policyHandler)) {
1022 return $policyHandler;
1023 }
1024
1025 if (is_string($policyHandler) && $this->handler instanceof Closure) {
1026
1027 if (class_exists($policyHandler)) {
1028
1029 $reflection = new \ReflectionClass($policyHandler);
1030
1031 if ($reflection->hasMethod('verifyRequest')) {
1032
1033 $policyHandler = $policyHandler . '@' . 'verifyRequest';
1034
1035 return $policyHandler;
1036 }
1037 } elseif (function_exists($policyHandler)) {
1038 return $policyHandler;
1039 }
1040
1041 throw new InvalidArgumentException(
1042 'Explicit policy handler is required while using a closure as route callback.'
1043 );
1044 }
1045
1046 if ($policyHandler && !function_exists($policyHandler)) {
1047 if (is_string($this->handler) && strpos($this->handler, '@') !== false) {
1048 list($_, $method) = explode('@', $this->handler);
1049 $policyHandler = $policyHandler . '@' . $method;
1050 } else if (is_array($this->handler)) {
1051 $policyHandler = $policyHandler . '@' . $this->handler[1];
1052 }
1053 }
1054
1055 return $policyHandler ?: [$this, 'defaultPolicyHandler'];
1056 }
1057
1058 protected function isPolicyHandlerParseable($policyHandler)
1059 {
1060 return (strpos($policyHandler, '@') === true
1061 || strpos($policyHandler, '::') === true);
1062 }
1063
1064 /**
1065 * Default/Fallback policy handler for the route
1066 *
1067 * @return bool
1068 */
1069 public function defaultPolicyHandler()
1070 {
1071 return true;
1072 }
1073
1074 /**
1075 * Parse the rest and permission/policy handlers
1076 *
1077 * @param \WP_REST_Request $request
1078 * @return null
1079 * @throws \BadMethodCallException
1080 */
1081 public function prepareCallbacks($request)
1082 {
1083 $handler = $this->app->parseRestHandler(
1084 $this->handler, $this->namespace
1085 );
1086
1087 if ($handler instanceof Closure) {
1088 $action = 'Closure';
1089 $controller = null;
1090 } else {
1091 $handler = trim($handler, '\\');
1092 $action = explode('@', $handler);
1093 $pieces = explode('\\', $action[0]);
1094 $controller = end($pieces);
1095 }
1096
1097 try {
1098 $policyHandler = $this->app->parsePolicyHandler(
1099 $this->getPolicyHandler($this->policyHandler)
1100 );
1101
1102 if ($policyHandler) {
1103 $this->permissionHandler = $policyHandler;
1104
1105 // Adjust policy handler if the method was explicitly given
1106 if (is_string($this->policyHandler)) {
1107 if (is_array($policyHandler) && isset($policyHandler[1])) {
1108 if ($pieces = explode('@', $this->policyHandler)) {
1109 if (isset($pieces[1])) {
1110 $this->permissionHandler[1] = $pieces[1];
1111 }
1112 }
1113 }
1114 }
1115
1116 if (!is_callable($this->permissionHandler)) {
1117 throw new Exception;
1118 }
1119 }
1120
1121 } catch (Exception $e) {
1122 $pHandler = $this->policyHandler;
1123 if (is_array($this->permissionHandler) && $this->permissionHandler) {
1124 $pHandler = is_object($this->permissionHandler[0]) ?
1125 get_class($this->permissionHandler[0]) . ':' . $this->permissionHandler[1] :
1126 $this->permissionHandler[0] . ':' . $this->permissionHandler[1];
1127 }
1128
1129 throw new BadMethodCallException(
1130 "The permission callback {$pHandler} is invalid or not callable."
1131 );
1132 }
1133
1134 if (is_array($policyHandler)) {
1135 $policyHandler[0] = get_class($policyHandler[0]);
1136 }
1137
1138 $this->actionInfo = [
1139 'handler' => is_object($handler) ? $action : $handler,
1140 'controller' => $controller,
1141 'method' => is_array($action) ? $action[1] : null,
1142 'path' => $this->uri,
1143 'http_method' => $request->get_method(),
1144 'full_uri' => $request->get_route(),
1145 'permission_callback' => $policyHandler,
1146 'compiled_url' => $this->compiled
1147 ];
1148
1149
1150 $this->action = $handler;
1151
1152 if ($routeParameters = $this->getParameter()) {
1153 $this->substitutedParameters = $this->SubstituteParameters(
1154 $routeParameters
1155 );
1156 }
1157
1158
1159 return $this->action;
1160 }
1161
1162 /**
1163 * Get one or more route parameters
1164 * @param string $key
1165 *
1166 * @return mixed
1167 */
1168 public function getParameter($key = null)
1169 {
1170 if (is_null($this->parameters)) {
1171 $this->parameters = $this->app->request->get_url_params();
1172 }
1173
1174 return $key ? $this->parameters[$key] : $this->parameters;
1175 }
1176
1177 /**
1178 * Dynamically access a route parameter.
1179 *
1180 * @param string $key
1181 * @return mixed
1182 */
1183 public function __get($key)
1184 {
1185 return $this->getParameter($key);
1186 }
1187 }
1188