PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 1.0.94
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v1.0.94
2.10.0 2.10.01 2.9.1 2.9.0 2.8.1 2.8.0 2.7.7 2.7.5 2.7.0 2.6.01 2.6.0 2.5.0 2.4.01 trunk 1.0.90 1.0.91 1.0.92 1.0.93 1.0.94 1.0.95 1.0.96 1.0.97 1.0.98 1.0.99 1.1.0 All 77 releases
fluent-community / vendor / wpfluent / framework / src / WPFluent / Http / Route.php

Route.php in FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses 1.0.94, at vendor/wpfluent/framework/src/WPFluent/Http/Route.php

1,184 lines 29.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCommunity\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 FluentCommunity\Framework\Support\Arr;
13 use FluentCommunity\Framework\Support\Pipeline;
14 use FluentCommunity\Framework\Http\Request\Request;
15 use FluentCommunity\Framework\Http\Middleware\RateLimiter;
16 use FluentCommunity\Framework\Validator\ValidationException;
17 use FluentCommunity\Framework\Database\Orm\ModelNotFoundException;
18 use FluentCommunity\Framework\Response\Response as WPFluentResponse;
19
20 class Route
21 {
22 use SubstituteRouteParametersTrait;
23
24 /**
25 * Application Instance
26 * @var \FluentCommunity\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 \FluentCommunity\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 \FluentCommunity\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 $rateLimiter = new RateLimiter($limit * 2, $interval);
504
505 $this->middleware('before', $rateLimiter);
506
507 return $this;
508 }
509
510 /**
511 * Apply a rate limit to the route for per minute.
512 *
513 * @param int $limit The maximum number of requests allowed per minute.
514 * @return $this
515 */
516 public function rateLimitPerMinute($limit)
517 {
518 return $this->rateLimit($limit, MINUTE_IN_SECONDS);
519 }
520
521 /**
522 * Apply an hourly rate limit to the route.
523 *
524 * @param int $limit The maximum number of requests allowed per hour.
525 * @return $this
526 */
527 public function rateLimitHourly($limit)
528 {
529 return $this->rateLimit($limit, HOUR_IN_SECONDS);
530 }
531
532 /**
533 * Apply a daily basis (24 hours) rate limit to the route.
534 *
535 * @param int $limit The maximum number of requests allowed per day.
536 * @return $this
537 */
538 public function rateLimitDaily($limit)
539 {
540 return $this->rateLimit($limit, DAY_IN_SECONDS);
541 }
542
543 /**
544 * Register the rest endpoint
545 *
546 * @return null
547 */
548 public function register()
549 {
550 $this->setOptions();
551
552 $uri = '/' . trim($this->compileRoute($this->uri), '/');
553
554 return register_rest_route(
555 $this->restNamespace, $uri, $this->getOptions()
556 );
557 }
558
559 /**
560 * Set route options
561 *
562 * @return null
563 */
564 protected function setOptions()
565 {
566 $this->options = [
567 [
568 'methods' => $this->method,
569 'callback' => [$this, 'callback'],
570 'permission_callback' => [$this, 'permissionCallback'],
571 'args' => [],
572 ],
573 'schema' => [$this, 'getSchema'],
574 ];
575 }
576
577 /**
578 * Generate and return the schema for the route.
579 *
580 * @return \Closure
581 * @see https://developer.wordpress.org/rest-api/extending-the-rest-api/schema
582 */
583 public function getSchema()
584 {
585 return function () {
586 return [];
587 };
588 }
589
590 /**
591 * Get item from predefined regex
592 * @param string $value
593 * @return string
594 */
595 protected function getValue($value)
596 {
597 if (array_key_exists($value, $this->predefinedNamedRegx)) {
598 return $this->predefinedNamedRegx[$value];
599 }
600
601 return $value;
602 }
603
604 /**
605 * Compikle the rest route to regex
606 *
607 * @param string $uri
608 * @return string compiled rest endpoint
609 */
610 protected function compileRoute($uri)
611 {
612 $params = [];
613
614 $compiledUri = preg_replace_callback('#/{(.*?)}#', function ($match) use (&$params, $uri) {
615 // Default regx
616 $regx = '[^\s(?!/)]+';
617
618 $param = trim($match[1]);
619
620 if ($isOptional = strpos($param, '?')) {
621 $param = trim($param, '?');
622 }
623
624 if (in_array($param, $params)) {
625 throw new InvalidArgumentException(
626 "Duplicate parameter name '{$param}' found in {$uri}.", 500
627 );
628 }
629
630 $params[] = $param;
631
632 if (isset($this->wheres[$param])) {
633 $regx = $this->wheres[$param];
634 }
635
636 $pattern = "/(?P<" . $param . ">" . $regx . ")";
637
638 if ($isOptional) {
639 $pattern = "(?:" . $pattern . ")?";
640 }
641
642 $this->options['args'][$param]['required'] = !$isOptional;
643
644 return $pattern;
645
646 }, $uri);
647
648 return $this->compiled = $compiledUri;
649 }
650
651 /**
652 * Route handler
653 *
654 * @return mixed
655 */
656 public function callback()
657 {
658 try {
659 return $this->handleAfterMiddleware(
660 $response = $this->dispatchRouteAction()
661 );
662
663 } catch (ValidationException $e) {
664 return $this->app->response->sendError(
665 $e->errors(), $e->getCode()
666 );
667 } catch (ModelNotFoundException $e) {
668 return $this->app->response->sendError([
669 'message' => $e->getMessage()
670 ], 404);
671 } catch (Exception $e) {
672 return $this->app->response->sendError([
673 'message' => $e->getMessage()
674 ], $e->getCode() ?: 500);
675 }
676 }
677
678 /**
679 * Dispatch the route action.
680 *
681 * @return mixed
682 */
683 protected function dispatchRouteAction()
684 {
685 $response = $this->app->call(
686 $this->action, $this->getControllerParameters()
687 );
688
689 if ($response instanceof WPFluentResponse) {
690 $response = $response->toArray();
691 } elseif (!($response instanceof WP_REST_Response)) {
692 $response = !is_wp_error($response) ?
693 $this->app->response->sendSuccess($response) :
694 $this->app->response->wpErrorToResponse($response);
695 }
696
697 return $response;
698 }
699
700 /**
701 * Handle after middleware if any.
702 *
703 * @param mixed $response
704 * @return mixed
705 */
706 protected function handleAfterMiddleware($response)
707 {
708 if (!$this->skipMiddleware) {
709 $response = $this->app->make(Pipeline::class)
710 ->send($response)
711 ->through($this->collectMiddleWare('after'))
712 ->then(function ($response) {
713 if (!$response instanceof WP_REST_Response) {
714 $response = new WP_REST_Response($response);
715 }
716 return $response;
717 });
718
719 if (!$response) {
720 $response = $this->app->request->abort();
721 }
722 }
723
724 return $response;
725 }
726
727 /**
728 * Permission callback for route
729 * @param \WP_REST_Request $wpRestRequest
730 * @return mixed
731 */
732 public function permissionCallback($wpRestRequest)
733 {
734 try {
735 $this->app->instance('route', $this);
736 $this->app->instance('wprestrequest', $wpRestRequest);
737 $this->app->request->mergeInputsFromRestRequest($wpRestRequest);
738 $this->prepareCallbacks($this->app->request);
739
740 if (!$this->isThisValidSignedRoute()) {
741 throw new Exception('Invalid Signature', 403);
742 }
743
744 $response = $this->app->make(Pipeline::class)
745 ->send($this->app->request)
746 ->through($this->collectMiddleWare('before'))
747 ->then(function ($request) {
748 if ($request && $request instanceof Request) {
749 return $this->dispatchPermissionHandler();
750 }
751 });
752
753 if (is_wp_error($response)) {
754 throw new Exception(
755 $response->get_error_message(),
756 is_int($code = $response->get_error_code()) ? $code : 403
757 );
758 }
759
760 if ($response instanceof WP_REST_Response) {
761 $data = $response->get_data();
762
763 throw new Exception(
764 $data['message'] ?? $response->get_status(),
765 $response->get_status()
766 );
767 }
768
769 return $response;
770
771 } catch (Exception $e) {
772 return new WP_Error(
773 'Permission Callback Error',
774 $e->getMessage(), [
775 'status' => $e->getCode() ?: 403
776 ]
777 );
778 }
779 }
780
781 /**
782 * Checks if the route is signed and needs validation.
783 *
784 * @return boolean [description]
785 */
786 protected function isThisValidSignedRoute()
787 {
788 if (!$this->signed) return true;
789
790 $request = $this->app->make('request');
791
792 if ($this->app->make('url')->validate($request->getFullUrl())) {
793 parse_str($this->app->make('encrypter')->decrypt(
794 $this->app->request->get('_data')
795 ), $query);
796
797 $this->app->request->merge(
798 Arr::except($query, ['expires_at'])
799 );
800
801 $this->app->request->forget('_data');
802
803 return true;
804 }
805 }
806
807 /**
808 * Dispatches the permission handler
809 *
810 * @return bool|null
811 */
812 protected function dispatchPermissionHandler()
813 {
814 if ($this->permissionHandler) {
815 return $this->app->call(
816 $this->permissionHandler,
817 $this->getControllerParameters()
818 );
819 }
820 }
821
822 /**
823 * Gether route params after substituted the params
824 *
825 * @return array
826 */
827 protected function getControllerParameters()
828 {
829 $routeParameters = [];
830
831 if (!$this->substitutedParameters) {
832 if ($routeParameters = $this->getParameter()) {
833 $routeParameters = $this->SubstituteParameters($routeParameters);
834 }
835 } else {
836 $routeParameters = $this->substitutedParameters;
837 }
838
839 return $routeParameters;
840 }
841
842 /**
843 * Added the ability to add middleware so we can intercept
844 * the request without modifying the source code again
845 * and again. The middleware class will implement
846 * the handle method as given below:
847 *
848 * public function handle($request, $next)
849 *
850 * And must return $next($request) to handle the request.
851 * Otherwise return nothing to abort the request.
852 * Optionally, you may call the abort method:
853 * return $request->abort(code, message);
854 *
855 * @param string $type
856 * @return array
857 */
858 protected function collectMiddleWare($type = 'before')
859 {
860 $middleware = $this->app['config']->get('middleware', []);
861
862 $callableMiddleware = Arr::get($middleware, "global.{$type}", []);
863
864 $routeArray = [];
865
866 if (isset($middleware['route'])) {
867 $routeArray = $middleware['route'];
868 if (isset($routeArray[$type])) {
869 $routeArray = $routeArray[$type];
870 }
871 }
872
873 foreach ($this->middleware[$type] as $routeMiddleware) {
874
875 if (is_object($routeMiddleware)) {
876 $handler = $routeMiddleware;
877 } elseif (class_exists($routeMiddleware)) {
878 $handler = $this->resolveMiddlewareFrom($routeMiddleware);
879 } else {
880 $pieces = explode(':', $routeMiddleware);
881 $handler = Arr::get($routeArray, $key = reset($pieces));
882 if (isset($pieces[1])) {
883 $handler = $this->resolveMiddleware($handler, $pieces);
884 }
885 }
886
887 if (isset($handler)) {
888 $this->addMiddlewareInTheStack($callableMiddleware, $handler);
889 } else {
890 if (isset($key)) {
891 $mpath = 'config.middleware.route.' . $type;
892 $msg = "No middleware is assigned for the key: {$key} in {$mpath} array.";
893 } else {
894 $msg = "Could't resolve middleware.";
895 }
896
897 throw new InvalidArgumentException($msg);
898 }
899 }
900
901 return $callableMiddleware;
902 }
903
904 /**
905 * Resolve a middleware from a class.
906 *
907 * @param string $class
908 * @return \Closure
909 */
910 protected function resolveMiddlewareFrom($class)
911 {
912 return (new $class);
913 return static function ($r, $next, ...$params) use ($class) {
914 return (new $class)->handle($r, $next, ...$params);
915 };
916 }
917
918 /**
919 * Resolve the middleware
920 *
921 * @param mixed $handler
922 * @param aray $pieces
923 * @return object
924 */
925 protected function resolveMiddleware($handler, $pieces)
926 {
927 if (is_object($handler)) {
928 $handler = $this->wrapMiddleware($handler, $pieces);
929 } elseif (is_string($handler)) {
930 $handler = $handler . ':' . str_replace(' ', '', end($pieces));
931 }
932
933 return $handler;
934 }
935
936 /**
937 * Create a class to wrap the middleware
938 *
939 * @param mixed $handler
940 * @param aray $pieces
941 * @return object
942 */
943 protected function wrapMiddleware($handler, $pieces)
944 {
945 $params = str_replace(' ', '', end($pieces));
946
947 $params = explode(',', $params);
948
949 return new class ($handler, $params) {
950 protected $handler, $params = null;
951
952 public function __construct($handler, $params)
953 {
954 $this->handler = $handler;
955 $this->params = $params;
956 }
957
958 public function handle($r, $next)
959 {
960 if (is_callable($this->handler)) {
961 return ($this->handler)($r, $next, ...$this->params);
962 } else {
963 if (!method_exists($this->handler, 'handle')) {
964 $class = get_class($this->handler);
965 throw new InvalidArgumentException(
966 "The {$class} must implement the handle method."
967 );
968 }
969 return $this->handler->handle($r, $next, ...$this->params);
970 }
971 }
972 };
973 }
974
975 /**
976 * Add the middleware in the stack
977 *
978 * @param array &$stack All callable middleware for the route
979 * @param null
980 */
981 protected function addMiddlewareInTheStack(&$stack, $middleware)
982 {
983 if (!in_array($middleware, $stack)) {
984 $stack[] = $middleware;
985 }
986 }
987
988 /**
989 * Resolve the policy handler
990 *
991 * @param string $policyHandler
992 * @return mixed
993 */
994 protected function getPolicyHandler($policyHandler)
995 {
996 if (!$policyHandler) {
997 return [$this, 'defaultPolicyHandler'];
998 }
999
1000 if (is_callable($policyHandler)) {
1001 return $policyHandler;
1002 }
1003
1004 if (is_string($policyHandler)) {
1005
1006 if (function_exists($policyHandler)) {
1007 return $policyHandler;
1008 }
1009
1010 $policyHandlerFunction = substr($policyHandler, strrpos($policyHandler, '\\') + 1);
1011
1012 if (function_exists($policyHandlerFunction)) {
1013 return $policyHandlerFunction;
1014 }
1015 }
1016
1017 if ($this->isPolicyHandlerParseable($policyHandler)) {
1018 return $policyHandler;
1019 }
1020
1021 if (is_string($policyHandler) && $this->handler instanceof Closure) {
1022
1023 if (class_exists($policyHandler)) {
1024
1025 $reflection = new \ReflectionClass($policyHandler);
1026
1027 if ($reflection->hasMethod('verifyRequest')) {
1028
1029 $policyHandler = $policyHandler . '@' . 'verifyRequest';
1030
1031 return $policyHandler;
1032 }
1033 } elseif (function_exists($policyHandler)) {
1034 return $policyHandler;
1035 }
1036
1037 throw new InvalidArgumentException(
1038 'Explicit policy handler is required while using a closure as route callback.'
1039 );
1040 }
1041
1042 if ($policyHandler && !function_exists($policyHandler)) {
1043 if (is_string($this->handler) && strpos($this->handler, '@') !== false) {
1044 list($_, $method) = explode('@', $this->handler);
1045 $policyHandler = $policyHandler . '@' . $method;
1046 } else if (is_array($this->handler)) {
1047 $policyHandler = $policyHandler . '@' . $this->handler[1];
1048 }
1049 }
1050
1051 return $policyHandler ?: [$this, 'defaultPolicyHandler'];
1052 }
1053
1054 protected function isPolicyHandlerParseable($policyHandler)
1055 {
1056 return (strpos($policyHandler, '@') === true
1057 || strpos($policyHandler, '::') === true);
1058 }
1059
1060 /**
1061 * Default/Fallback policy handler for the route
1062 *
1063 * @return bool
1064 */
1065 public function defaultPolicyHandler()
1066 {
1067 return true;
1068 }
1069
1070 /**
1071 * Parse the rest and permission/policy handlers
1072 *
1073 * @param \WP_REST_Request $request
1074 * @return null
1075 * @throws \BadMethodCallException
1076 */
1077 public function prepareCallbacks($request)
1078 {
1079 $handler = $this->app->parseRestHandler(
1080 $this->handler, $this->namespace
1081 );
1082
1083 if ($handler instanceof Closure) {
1084 $action = 'Closure';
1085 $controller = null;
1086 } else {
1087 $handler = trim($handler, '\\');
1088 $action = explode('@', $handler);
1089 $pieces = explode('\\', $action[0]);
1090 $controller = end($pieces);
1091 }
1092
1093 try {
1094 $policyHandler = $this->app->parsePolicyHandler(
1095 $this->getPolicyHandler($this->policyHandler)
1096 );
1097
1098 if ($policyHandler) {
1099 $this->permissionHandler = $policyHandler;
1100
1101 // Adjust policy handler if the method was explicitly given
1102 if (is_string($this->policyHandler)) {
1103 if (is_array($policyHandler) && isset($policyHandler[1])) {
1104 if ($pieces = explode('@', $this->policyHandler)) {
1105 if (isset($pieces[1])) {
1106 $this->permissionHandler[1] = $pieces[1];
1107 }
1108 }
1109 }
1110 }
1111
1112 if (!is_callable($this->permissionHandler)) {
1113 throw new Exception;
1114 }
1115 }
1116
1117 } catch (Exception $e) {
1118 $pHandler = $this->policyHandler;
1119 if (is_array($this->permissionHandler) && $this->permissionHandler) {
1120 $pHandler = is_object($this->permissionHandler[0]) ?
1121 get_class($this->permissionHandler[0]) . ':' . $this->permissionHandler[1] :
1122 $this->permissionHandler[0] . ':' . $this->permissionHandler[1];
1123 }
1124
1125 throw new BadMethodCallException(
1126 "The permission callback {$pHandler} is invalid or not callable."
1127 );
1128 }
1129
1130 if (is_array($policyHandler)) {
1131 $policyHandler[0] = get_class($policyHandler[0]);
1132 }
1133
1134 $this->actionInfo = [
1135 'handler' => is_object($handler) ? $action : $handler,
1136 'controller' => $controller,
1137 'method' => is_array($action) ? $action[1] : null,
1138 'path' => $this->uri,
1139 'http_method' => $request->get_method(),
1140 'full_uri' => $request->get_route(),
1141 'permission_callback' => $policyHandler,
1142 'compiled_url' => $this->compiled
1143 ];
1144
1145
1146 $this->action = $handler;
1147
1148 if ($routeParameters = $this->getParameter()) {
1149 $this->substitutedParameters = $this->SubstituteParameters(
1150 $routeParameters
1151 );
1152 }
1153
1154
1155 return $this->action;
1156 }
1157
1158 /**
1159 * Get one or more route parameters
1160 * @param string $key
1161 *
1162 * @return mixed
1163 */
1164 public function getParameter($key = null)
1165 {
1166 if (is_null($this->parameters)) {
1167 $this->parameters = $this->app->request->get_url_params();
1168 }
1169
1170 return $key ? $this->parameters[$key] : $this->parameters;
1171 }
1172
1173 /**
1174 * Dynamically access a route parameter.
1175 *
1176 * @param string $key
1177 * @return mixed
1178 */
1179 public function __get($key)
1180 {
1181 return $this->getParameter($key);
1182 }
1183 }
1184