PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 2.1.0
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v2.1.0
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
← All changes | vendor/wpfluent/framework/src/WPFluent/Http/Route.php +931 -227 1.212.1.0 View file →
@@ -3,22 +3,30 @@
3 3 namespace FluentBoards\Framework\Http;
4 4
5 5 use Closure;
6 6 use Exception;
7 +use Throwable;
8 +use WP_Error;
7 9 use WP_REST_Request;
8 10 use WP_REST_Response;
11 +use ReflectionClass;
9 12 use BadMethodCallException;
10 13 use InvalidArgumentException;
11 14 use FluentBoards\Framework\Support\Arr;
15 +use FluentBoards\Framework\Support\Str;
12 16 use FluentBoards\Framework\Support\Pipeline;
17 +use FluentBoards\Framework\Http\Request\Request;
18 +use FluentBoards\Framework\Http\Request\WPUserProxy;
19 +use FluentBoards\Framework\Http\SubstituteParameters;
20 +use FluentBoards\Framework\Http\Middleware\RateLimiter;
13 21 use FluentBoards\Framework\Validator\ValidationException;
14 22 use FluentBoards\Framework\Database\Orm\ModelNotFoundException;
15 -use FluentBoards\Framework\Response\Response as WPFluentResponse;
23 +use FluentBoards\Framework\Http\Response\Response as WPFluentResponse;
16 24
17 25 class Route
18 26 {
19 - use SubstituteRouteParametersTrait;
20 -
27 + use SubstituteParameters;
28 +
21 29 /**
22 30 * Application Instance
23 31 * @var \FluentBoards\Framework\Foundation\Application
24 32 */
@@ -24,8 +32,14 @@
24 32 */
25 33 protected $app = null;
26 34
27 35 /**
36 + * Route name
37 + * @var string
38 + */
39 + protected $name = null;
40 +
41 + /**
28 42 * Rest namespace from config
29 43 * @var string
30 44 */
31 45 protected $restNamespace = null;
@@ -30,13 +44,19 @@
30 44 */
31 45 protected $restNamespace = null;
32 46
33 47 /**
48 + * Whether this route should override existing routes at the same URI.
49 + * @var bool
50 + */
51 + protected $shouldOverride = false;
52 +
53 + /**
34 54 * Full URI
35 55 * @var string
36 56 */
37 57 protected $uri = null;
38 -
58 +
39 59 /**
40 60 * Compiled rest endpoint
41 61 * @var string
42 62 */
@@ -52,9 +72,9 @@
52 72 * Rest Handler/Callback before parsing
53 73 * @var string
54 74 */
55 75 protected $handler = null;
56 -
76 +
57 77 /**
58 78 * Rest Handler/Callback after parsing
59 79 * @var callable|string
60 80 */
@@ -64,9 +84,9 @@
64 84 * Rest route action info after parsing
65 85 * @var array
66 86 */
67 87 protected $actionInfo = [];
68 -
88 +
69 89 /**
70 90 * Policy Handler/Callback after parsing
71 91 * @var string
72 92 */
@@ -76,9 +96,9 @@
76 96 * HTTP Methods
77 97 * @var string
78 98 */
79 99 protected $method = null;
80 -
100 +
81 101 /**
82 102 * Rest options
83 103 * @var array
84 104 */
@@ -94,9 +114,9 @@
94 114 * Rest namespace
95 115 * @var string
96 116 */
97 117 protected $namespace = null;
98 -
118 +
99 119 /**
100 120 * Policy Handler/Callback after parsing
101 121 * @var callable|string
102 122 */
@@ -112,9 +132,9 @@
112 132 ];
113 133
114 134 /**
115 135 * Skips middlewar if true
116 - *
136 + *
117 137 * @var boolean
118 138 */
119 139 protected $skipMiddleware = false;
120 140
@@ -136,16 +156,37 @@
136 156 protected $parameters = null;
137 157
138 158 /**
139 159 * Route substituted parameters
140 - *
160 + *
141 161 * @var null|array
142 162 */
143 163 protected $substitutedParameters = [];
144 164
145 165 /**
166 + * Is route signed
167 + *
168 + * @var boolean
169 + */
170 + protected $signed = false;
171 +
172 + /**
173 + * Route signature.
174 + *
175 + * @var array
176 + */
177 + protected $endpointSignature = [];
178 +
179 + /**
180 + * Response instance
181 + *
182 + * @var \WP_REST_Response
183 + */
184 + protected $response = null;
185 +
186 + /**
146 187 * Construct the route instance
147 - *
188 + *
148 189 * @param \FluentBoards\Framework\Foundation\Application $app
149 190 * @param string $restNamespace
150 191 * @param string $uri
151 192 * @param string $handler
@@ -160,12 +201,100 @@
160 201 $this->method = $method;
161 202 }
162 203
163 204 /**
205 + * Map the route to be used in front-end.
206 + *
207 + * @param mixed $handler
208 + * @return self
209 + */
210 + public function preparefrontendHandlers()
211 + {
212 + $handler = $this->handler;
213 +
214 + $endpointsUrl = $this->app->config->get('app.slug') . '/__endpoints';
215 +
216 + if (get_option('permalink_structure')) {
217 + $url = $this->app->request->url();
218 + } else {
219 + $url = $this->app->request->query('rest_route');
220 + }
221 +
222 + if (
223 + !str_contains($url ?? '', $endpointsUrl)
224 + || $handler instanceof Closure
225 + ) {
226 + return $this;
227 + }
228 +
229 + [$controller, $cb] = Str::parseCallback($this->parseAction($handler));
230 +
231 + $this->endpointSignature = [$controller, "_{$cb}"];
232 +
233 + $controller = str_replace('\\', '.', $controller);
234 +
235 + // @phpstan-ignore-next-line
236 + $endpoints = $this->app->endpoints;
237 +
238 + $endpoints[$controller]["_{$cb}"] = [
239 + 'uri' => $this->uri,
240 + 'methods' => explode(',', $this->method),
241 + 'policy' => $this->getPolicyName()
242 + ];
243 +
244 + // @phpstan-ignore-next-line
245 + $this->app->endpoints = $endpoints;
246 +
247 + return $this;
248 + }
249 +
250 + /**
251 + * Get a display name for the route's policy handler.
252 + *
253 + * @return string|null
254 + */
255 + protected function getPolicyName()
256 + {
257 + if (!$this->policyHandler) {
258 + return null;
259 + }
260 +
261 + if ($this->policyHandler instanceof Closure) {
262 + return 'Closure';
263 + }
264 +
265 + $name = $this->policyHandler;
266 +
267 + if (is_string($name) && !$this->app->hasNamespace($name)) {
268 + $name = $this->app->__namespace__ . '\\App\\Http\\Policies\\' . $name;
269 + }
270 +
271 + return $name;
272 + }
273 +
274 + /**
275 + * Parse the action from the handler.
276 + *
277 + * @param mixed $handler
278 + * @return string
279 + */
280 + protected function parseAction($handler)
281 + {
282 + $action = $this->app->parseRestHandler($handler, $this->namespace);
283 + $action = trim($action, '\\');
284 +
285 + if (!str_contains($action, '@')) {
286 + $action .= '@__invoke';
287 + }
288 +
289 + return $action;
290 + }
291 +
292 + /**
164 293 * Alternative constructor
165 - *
294 + *
166 295 * @param \FluentBoards\Framework\Foundation\Application $app
167 - * @param string $restNamespace
296 + * @param string $namespace
168 297 * @param string $uri
169 298 * @param string $handler
170 299 * @param string $method
171 300 * @return self
@@ -176,11 +305,11 @@
176 305 }
177 306
178 307 /**
179 308 * Set route meta
180 - *
181 - * @param string $key
182 - * @param mixed $value
309 + *
310 + * @param string $key
311 + * @param mixed $value
183 312 * @return self
184 313 */
185 314 public function meta($key, $value = null)
186 315 {
@@ -192,10 +321,10 @@
192 321 }
193 322
194 323 /**
195 324 * Get route meta
196 - *
197 - * @param string $key
325 + *
326 + * @param string $key
198 327 * @return mixed
199 328 */
200 329 public function getMeta($key = '')
201 330 {
@@ -201,15 +330,15 @@
201 330 {
202 331 if (isset($this->meta[$key])) {
203 332 return $this->meta[$key];
204 333 }
205 -
334 +
206 335 return $this->meta;
207 336 }
208 337
209 338 /**
210 339 * Get route options
211 - *
340 + *
212 341 * @return mixed
213 342 */
214 343 public function getOptions()
215 344 {
@@ -217,10 +346,10 @@
217 346 }
218 347
219 348 /**
220 349 * Get route options
221 - *
222 - * @param string $key
350 + *
351 + * @param string $key
223 352 * @return mixed
224 353 */
225 354 public function getOption($key = null)
226 355 {
@@ -228,9 +357,9 @@
228 357 }
229 358
230 359 /**
231 360 * Get route action information
232 - * @param string $key
361 + * @param string $key
233 362 * @return mixed
234 363 */
235 364 public function getAction($key = '')
236 365 {
@@ -236,17 +365,17 @@
236 365 {
237 366 if ($key && array_key_exists($key, $this->actionInfo)) {
238 367 return $this->actionInfo[$key];
239 368 }
240 -
369 +
241 370 return $this->actionInfo;
242 371 }
243 372
244 373 /**
245 374 * Set a where constrain into the route
246 - *
247 - * @param string $identifier
248 - * @param string $value
375 + *
376 + * @param string $identifier
377 + * @param string $value
249 378 * @return self
250 379 */
251 380 public function where($identifier, $value = null)
252 381 {
@@ -262,10 +391,10 @@
262 391 }
263 392
264 393 /**
265 394 * Add an integer type route constraint
266 - *
267 - * @param string $identifiers
395 + *
396 + * @param string $identifiers
268 397 * @return self
269 398 */
270 399 public function int($identifiers)
271 400 {
@@ -279,10 +408,10 @@
279 408 }
280 409
281 410 /**
282 411 * Add an alpha type route constraint
283 - *
284 - * @param string $identifiers
412 + *
413 + * @param string $identifiers
285 414 * @return self
286 415 */
287 416 public function alpha($identifiers)
288 417 {
@@ -296,10 +425,10 @@
296 425 }
297 426
298 427 /**
299 428 * Add an alphanum type route constraint
300 - *
301 - * @param string $identifiers
429 + *
430 + * @param string $identifiers
302 431 * @return self
303 432 */
304 433 public function alphaNum($identifiers)
305 434 {
@@ -313,10 +442,10 @@
313 442 }
314 443
315 444 /**
316 445 * Add an alphanumdash type route constraint
317 - *
318 - * @param string $identifiers
446 + *
447 + * @param string $identifiers
319 448 * @return self
320 449 */
321 450 public function alphaNumDash($identifiers)
322 451 {
@@ -330,10 +459,10 @@
330 459 }
331 460
332 461 /**
333 462 * Set the route before middleware
334 - *
335 - * @param array|string $middleware
463 + *
464 + * @param array|string $middleware
336 465 * @return self
337 466 */
338 467 public function before(...$middleware)
339 468 {
@@ -341,10 +470,10 @@
341 470 }
342 471
343 472 /**
344 473 * Set the route after middleware
345 - *
346 - * @param array|string $middleware
474 + *
475 + * @param array|string $middleware
347 476 * @return self
348 477 */
349 478 public function after(...$middleware)
350 479 {
@@ -352,9 +481,9 @@
352 481 }
353 482
354 483 /**
355 484 * Set the route middleware
356 - * @param array $middleware
485 + * @param array $middleware
357 486 * @return self
358 487 */
359 488 public function middleware($type = 'before', ...$middleware)
360 489 {
@@ -369,12 +498,25 @@
369 498 return $this;
370 499 }
371 500
372 501 /**
502 + * Set the default route policy.
503 + *
504 + * @return self
505 + */
506 + public function withDefaultPolicy()
507 + {
508 + return $this->withPolicy(
509 + // @phpstan-ignore-next-line
510 + $this->app->__namespace__.'\\App\\Http\\Policies\\Policy'
511 + );
512 + }
513 +
514 + /**
373 515 * Set the route policy
374 - *
375 - * @param mixed $handler
376 - * @param string|null $method
516 + *
517 + * @param mixed $handler
518 + * @param string|null $method
377 519 * @return self
378 520 */
379 521 public function withPolicy($handler, $method = null)
380 522 {
@@ -389,15 +531,83 @@
389 531 debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 4)
390 532 );
391 533 }
392 534
535 + return $this->addRouteInfo($handler);
536 + }
537 +
538 + /**
539 + * Check if the request is from CLI;
540 + *
541 + * @return bool
542 + */
543 + protected function fromCli()
544 + {
545 + $hash = $this->app->request->header('X-From-CLI');
546 +
547 + $slugHash = md5($this->app->config->get('app.slug'));
548 +
549 + return $hash === $slugHash;
550 + }
551 +
552 + /**
553 + * Add route information for CLI command.
554 + *
555 + * @param mixed $handler
556 + * @return self
557 + */
558 + protected function addRouteInfo($handler)
559 + {
560 + if (!$this->fromCli()) {
561 + return $this;
562 + }
563 +
564 + if ($handler instanceof Closure) {
565 + $policyHandler = 'Closure';
566 + } else {
567 + $policyHandler = $this->resolvePolicyHandler();
568 + if (is_array($policyHandler)) {
569 + $policyHandler = implode('@', $policyHandler);
570 + }
571 + }
572 +
573 + $this->injectProp('policy', $policyHandler);
574 +
393 575 return $this;
394 576 }
395 577
396 578 /**
579 + * Inject property into route infio.
580 + *
581 + * @param string $key
582 + * @param mixed $value
583 + * @return void
584 + */
585 + public function injectProp($key, $value)
586 + {
587 + if (!$this->endpointSignature) {
588 + return;
589 + }
590 +
591 + [$controller, $cbKey] = $this->endpointSignature;
592 +
593 + $controllerKey = str_replace('\\', '.', $controller);
594 +
595 + // @phpstan-ignore-next-line
596 + $endpoints = $this->app->endpoints;
597 +
598 + if (isset($endpoints[$controllerKey][$cbKey])) {
599 + $endpoints[$controllerKey][$cbKey][$key] = $value;
600 + // @phpstan-ignore-next-line
601 + $this->app->endpoints = $endpoints;
602 + }
603 + }
604 +
605 + /**
397 606 * Resolve and set policy with namespace for add-ons
398 - *
399 - * @param null
607 + *
608 + * @param array $backTrace
609 + * @return void
400 610 */
401 611 protected function setPolicyHandlerWithNamespace($backTrace)
402 612 {
403 613 $last = end($backTrace);
@@ -406,9 +616,9 @@
406 616
407 617 $class = $last['class'];
408 618
409 619 $namespace = substr(__NAMESPACE__, 0, strpos(__NAMESPACE__, '\\'));
410 -
620 +
411 621 $calledClassNamespace = substr($class, 0, strpos($class, '\\'));
412 622
413 623 if ($namespace != $calledClassNamespace) {
414 624 $ns = $calledClassNamespace . '\\App\\Http\\Policies\\';
@@ -416,51 +626,211 @@
416 626 }
417 627 }
418 628
419 629 /**
420 - * Set the namespace for controller/action
421 - * @param string $ns
630 + * Set the name for the route.
631 + *
632 + * @param string $name
633 + * @return self
634 + */
635 + public function name($name)
636 + {
637 + if (!$this->name) {
638 + $this->name = $name;
639 + } else {
640 + $this->name .= $name;
641 + }
642 +
643 + // @phpstan-ignore-next-line
644 + return $this->app->router->setNamedRoute($this->name, $this);
645 + }
646 +
647 + /**
648 + * Set the name for the route.
649 + *
650 + * @param string $name
422 651 * @return null
423 652 */
653 + public function withName($name)
654 + {
655 + $this->name = implode('', $name);
656 + }
657 +
658 + /**
659 + * Set the namespace for controller/action.
660 + *
661 + * @param string $ns
662 + * @return null
663 + */
424 664 public function withNamespace($ns)
425 665 {
426 - $this->namespace = implode('\\', $ns);
666 + if (is_array($ns)) {
667 + $this->namespace = implode('\\', $ns);
668 + } else {
669 + $this->namespace = trim($ns, '\\');
670 + }
427 671 }
428 672
429 673 /**
674 + * Sign the route.
675 + *
676 + * @return $this
677 + */
678 + public function signed()
679 + {
680 + $this->signed = true;
681 +
682 + return $this;
683 + }
684 +
685 + /**
686 + * Apply rate limit to the route.
687 + *
688 + * @param int $limit Number of allowed requests.
689 + * @param int $interval Time interval in seconds.
690 + *
691 + * @return $this
692 + */
693 + public function rateLimit($limit, $interval)
694 + {
695 + // Since the rate limiter is applied twice because
696 + // WordPress sends an extra request for every
697 + // request, so we need to double the limit.
698 +
699 + $rateLimiter = new RateLimiter($limit * 2, $interval);
700 +
701 + $this->middleware('before', $rateLimiter);
702 +
703 + return $this;
704 + }
705 +
706 + /**
707 + * Apply a rate limit to the route for per minute.
708 + *
709 + * @param int $limit The maximum number of requests allowed per minute.
710 + * @return $this
711 + */
712 + public function rateLimitPerMinute($limit)
713 + {
714 + return $this->rateLimit($limit, MINUTE_IN_SECONDS);
715 + }
716 +
717 + /**
718 + * Apply an hourly rate limit to the route.
719 + *
720 + * @param int $limit The maximum number of requests allowed per hour.
721 + * @return $this
722 + */
723 + public function rateLimitHourly($limit)
724 + {
725 + return $this->rateLimit($limit, HOUR_IN_SECONDS);
726 + }
727 +
728 + /**
729 + * Apply a daily basis (24 hours) rate limit to the route.
730 + *
731 + * @param int $limit The maximum number of requests allowed per day.
732 + * @return $this
733 + */
734 + public function rateLimitDaily($limit)
735 + {
736 + return $this->rateLimit($limit, DAY_IN_SECONDS);
737 + }
738 +
739 + /**
430 740 * Register the rest endpoint
431 - *
741 + *
432 742 * @return null
433 743 */
434 744 public function register()
435 745 {
746 + $this->updateRouteOptions();
747 +
748 + return register_rest_route(
749 + $this->restNamespace,
750 + $this->getRouteUri(),
751 + $this->getOptions(),
752 + $this->shouldOverride
753 + );
754 + }
755 +
756 + /**
757 + * Update route options before registering.
758 + *
759 + * @return void
760 + */
761 + protected function updateRouteOptions()
762 + {
436 763 $this->setOptions();
764 + }
437 765
438 - $uri = '/' . trim($this->compileRoute($this->uri), '/');
766 + /**
767 + * Get normalized uri for the current route.
768 + *
769 + * @return string
770 + */
771 + protected function getRouteUri()
772 + {
773 + return '/' . trim($this->compileRoute($this->uri), '/');
774 + }
439 775
440 - return register_rest_route(
441 - $this->restNamespace, $uri, $this->getOptions()
442 - );
776 + /**
777 + * Mark this route to override any existing route at the same URI.
778 + *
779 + * @return $this
780 + */
781 + public function override()
782 + {
783 + $this->shouldOverride = true;
784 +
785 + return $this;
443 786 }
444 787
445 788 /**
446 789 * Set route options
447 - *
790 + *
448 791 * @return null
449 792 */
450 793 protected function setOptions()
451 794 {
452 - $this->options = [
453 - 'args' => [],
454 - 'methods' => $this->method,
455 - 'callback' => [$this, 'callback'],
456 - 'permission_callback' => [$this, 'permissionCallback']
795 + $this->options = array_merge(
796 + $this->options, $this->getDefaultOptions()
797 + );
798 + }
799 +
800 + /**
801 + * Get default options.
802 + *
803 + * @return array
804 + */
805 + protected function getDefaultOptions()
806 + {
807 + return [
808 + [
809 + 'methods' => $this->method,
810 + 'callback' => [$this, 'callback'],
811 + 'permission_callback' => [$this, 'permissionCallback'],
812 + 'args' => [],
813 + ],
457 814 ];
458 815 }
459 816
460 817 /**
818 + * Generate and return the schema for the route.
819 + *
820 + * @return self
821 + * @see https://developer.wordpress.org/rest-api/extending-the-rest-api/schema
822 + */
823 + public function schema($schema)
824 + {
825 + $this->options['schema'] = fn() => $schema;
826 +
827 + return $this;
828 + }
829 +
830 + /**
461 831 * Get item from predefined regex
462 - * @param string $value
832 + * @param string $value
463 833 * @return string
464 834 */
465 835 protected function getValue($value)
466 836 {
@@ -472,10 +842,10 @@
472 842 }
473 843
474 844 /**
475 845 * Compikle the rest route to regex
476 - *
477 - * @param string $uri
846 + *
847 + * @param string $uri
478 848 * @return string compiled rest endpoint
479 849 */
480 850 protected function compileRoute($uri)
481 851 {
@@ -480,12 +850,12 @@
480 850 protected function compileRoute($uri)
481 851 {
482 852 $params = [];
483 853
484 - $compiledUri = preg_replace_callback('#/{(.*?)}#', function($match) use (&$params, $uri) {
854 + $compiledUri = preg_replace_callback('#/{(.*?)}#', function ($match) use (&$params, $uri) {
485 855 // Default regx
486 856 $regx = '[^\s(?!/)]+';
487 -
857 +
488 858 $param = trim($match[1]);
489 859
490 860 if ($isOptional = strpos($param, '?')) {
491 861 $param = trim($param, '?');
@@ -495,9 +865,9 @@
495 865 throw new InvalidArgumentException(
496 866 "Duplicate parameter name '{$param}' found in {$uri}.", 500
497 867 );
498 868 }
499 -
869 +
500 870 $params[] = $param;
501 871
502 872 if (isset($this->wheres[$param])) {
503 873 $regx = $this->wheres[$param];
@@ -507,11 +877,11 @@
507 877
508 878 if ($isOptional) {
509 879 $pattern = "(?:" . $pattern . ")?";
510 880 }
511 -
881 +
512 882 $this->options['args'][$param]['required'] = !$isOptional;
513 -
883 +
514 884 return $pattern;
515 885
516 886 }, $uri);
517 887
@@ -519,89 +889,241 @@
519 889 }
520 890
521 891 /**
522 892 * Route handler
523 - *
524 - * @return mixed
893 + *
894 + * @return \WP_REST_Response
525 895 */
526 896 public function callback()
527 897 {
528 898 try {
529 -
530 - $response = $this->app->call(
531 - $this->action, $this->getControllerParameters()
899 + $this->response = $this->handleAfterMiddleware(
900 + $this->dispatchRouteAction()
532 901 );
533 902
534 - if ($response instanceof WPFluentResponse) {
535 - $response = $response->toArray();
536 - }
903 + return $this->handleResponse($this->response);
537 904
538 - if (!($response instanceof WP_REST_Response)) {
539 - if (is_wp_error($response)) {
540 - $response = $this->app->response->wpErrorToResponse($response);
541 - } else {
542 - $response = $this->app->response->sendSuccess($response);
543 - }
544 - }
545 -
546 - if (!$this->skipMiddleware) {
547 - $response = $this->app->make(Pipeline::class)
548 - ->send($response)
549 - ->through($this->collectMiddleWare('after'))
550 - ->then(function($response) {
551 - if (!$response instanceof WP_REST_Response) {
552 - $response = new WP_REST_Response($response);
553 - }
554 - return $response;
555 - });
556 -
557 - if (!$response) {
558 - $response = $this->app->request->abort();
559 - }
560 - }
561 -
562 - return $response;
563 -
564 905 } catch (ValidationException $e) {
565 906 return $this->app->response->sendError(
566 907 $e->errors(), $e->getCode()
567 908 );
568 - } catch (ModelNotFoundException $e) {
909 + } catch (ModelNotFoundException $e) {
569 910 return $this->app->response->sendError([
570 911 'message' => $e->getMessage()
571 912 ], 404);
572 - } catch (Exception $e) {
573 - return $this->app->response->sendError([
574 - 'message' => $e->getMessage()
575 - ], $e->getCode() ?: 500);
913 + } catch (Throwable $e) {
914 + return $this->handleUnknownException(
915 + $e, $this->response ? $this->response->get_headers() : []
916 + );
576 917 }
577 918 }
578 919
579 920 /**
921 + * Handle response from route.
922 + *
923 + * @param \WP_REST_Response $response
924 + * @return \WP_REST_Response
925 + */
926 + protected function handleResponse($response)
927 + {
928 + if ($response->get_status() >= 400) {
929 + $this->fireExceptionEvent(
930 + new Exception(
931 + $this->extractErrorMessage($response),
932 + $response->get_status()
933 + )
934 + );
935 + }
936 +
937 + return $response;
938 + }
939 +
940 + /**
941 + * Extract error message from response data.
942 + *
943 + * @param \WP_REST_Response $response
944 + * @return string
945 + */
946 + protected function extractErrorMessage($response)
947 + {
948 + $data = $response->get_data();
949 +
950 + if (is_string($data)) {
951 + return $data;
952 + }
953 +
954 + if (is_array($data) && isset($data['message'])) {
955 + return $data['message'];
956 + }
957 +
958 + if ($data instanceof WP_Error) {
959 + return $data->get_error_message();
960 + }
961 +
962 + return 'Unknown error';
963 + }
964 +
965 + /**
966 + * Throw an exception based on the status code.
967 + *
968 + * @param string $message
969 + * @param int $status
970 + * @return null
971 + * @throws \Exception
972 + */
973 + protected function throwException($message, $status)
974 + {
975 + $class = sprintf(
976 + 'WpOrg\Requests\Exception\Http\Status%d', $status
977 + );
978 +
979 + if (!class_exists($class)) {
980 + $class = 'WpOrg\Requests\Exception\Http';
981 + }
982 +
983 + throw new $class($message, $status);
984 + }
985 +
986 + /**
987 + * Handle exception and send error response.
988 + *
989 + * @param Throwable $e
990 + * @return \WP_REST_Response
991 + */
992 + protected function handleUnknownException(Throwable $e, $headers = [])
993 + {
994 + $data = [];
995 +
996 + $this->fireExceptionEvent($e);
997 +
998 + if ($this->app->isDebugOn()) {
999 + $data = [
1000 + 'file' => $e->getFile(),
1001 + 'line' => $e->getLine(),
1002 + ];
1003 + }
1004 +
1005 + return $this->app->response->sendError([
1006 + 'code' => 'plugin_exception',
1007 + 'data' => $data,
1008 + 'message' => $e->getMessage(),
1009 + ], $e->getCode() ?: 500, $headers);
1010 + }
1011 +
1012 + /**
1013 + * Dispatch the route action.
1014 + *
1015 + * @return \WP_REST_Response
1016 + */
1017 + protected function dispatchRouteAction()
1018 + {
1019 + $response = $this->app->call(
1020 + $this->action, $this->getControllerParameters()
1021 + );
1022 +
1023 + if ($response instanceof WPFluentResponse) {
1024 + $response = $response->toArray();
1025 + } elseif (!($response instanceof WP_REST_Response)) {
1026 + $response = !is_wp_error($response) ?
1027 + $this->app->response->sendSuccess($response) :
1028 + $this->app->response->wpErrorToResponse($response);
1029 + }
1030 +
1031 + return $response;
1032 + }
1033 +
1034 + /**
1035 + * Handle after middleware if any.
1036 + *
1037 + * @param mixed $response
1038 + * @return mixed
1039 + */
1040 + protected function handleAfterMiddleware($response)
1041 + {
1042 + if (!$this->skipMiddleware) {
1043 + $response = $this->app->make(Pipeline::class)
1044 + ->send(new WPFluentResponse($response))
1045 + ->through($this->collectMiddleWare('after'))
1046 + ->then(function($response) {
1047 + return $this->normalize($response);
1048 + });
1049 +
1050 + if (!$response) {
1051 + $response = $this->app->request->abort();
1052 + }
1053 + }
1054 +
1055 + return $response;
1056 + }
1057 +
1058 + /**
1059 + * Normalize the response.
1060 + *
1061 + * @param mixed $response
1062 + * @return mixed
1063 + */
1064 + protected function normalize($response)
1065 + {
1066 + if ($response instanceof WPFluentResponse) {
1067 + $response = $response->toArray();
1068 + }
1069 +
1070 + if (!$response instanceof WP_REST_Response) {
1071 + return new WP_REST_Response($response);
1072 + }
1073 +
1074 + return $response;
1075 + }
1076 +
1077 + /**
1078 + * Fire exception action hook.
1079 + *
1080 + * @param Exception $exception
1081 + * @return void
1082 + */
1083 + protected function fireExceptionEvent($exception)
1084 + {
1085 + if ($this->app->isDebugOn() || defined('FLUENT_BRIDGE_SECRET')) {
1086 + $message = sprintf(
1087 + "%s in %s:%d\nStack trace:\n%s\n",
1088 + $exception->getMessage(),
1089 + $exception->getFile(),
1090 + $exception->getLine(),
1091 + $exception->getTraceAsString()
1092 + );
1093 +
1094 + error_log($message);
1095 +
1096 + $this->app->doAction('fluent_exception', $exception);
1097 + }
1098 + }
1099 +
1100 + /**
580 1101 * Permission callback for route
581 - * @param \WP_REST_Request $wpRestRequest
1102 + * @param \WP_REST_Request $wpRestRequest
582 1103 * @return mixed
583 1104 */
584 1105 public function permissionCallback($wpRestRequest)
585 1106 {
586 1107 try {
587 -
1108 + $this->parameters = null;
1109 + $this->substitutedParameters = null;
588 1110 $this->app->instance('route', $this);
589 -
590 - if (!$this->app->bound('wprestrequest')) {
591 - $this->app->instance('wprestrequest', $wpRestRequest);
592 - $this->app->request->mergeInputsFromRestRequest($wpRestRequest);
1111 + $this->app->instance('wprestrequest', $wpRestRequest);
1112 + $this->app->request->mergeInputsFromRestRequest($wpRestRequest);
1113 + $this->prepareCallbacks($this->app->request);
593 1114
594 - if (method_exists($this, 'prepareCallbacks')) {
595 - $this->prepareCallbacks($this->app->request);
596 - }
1115 + if (!$this->isThisValidSignedRoute()) {
1116 + throw new Exception('Invalid Signature', 403);
597 1117 }
598 1118
599 1119 $response = $this->app->make(Pipeline::class)
600 1120 ->send($this->app->request)
601 1121 ->through($this->collectMiddleWare('before'))
602 - ->then(function($request) {
603 - return $this->dispatchPermissionHandler();
1122 + ->then(function ($request) {
1123 + if ($request && $request instanceof Request) {
1124 + return $this->dispatchPermissionHandler();
1125 + }
604 1126 });
605 1127
606 1128 if (is_wp_error($response)) {
607 1129 throw new Exception(
@@ -613,9 +1135,10 @@
613 1135 if ($response instanceof WP_REST_Response) {
614 1136 $data = $response->get_data();
615 1137
616 1138 throw new Exception(
617 - $data['message'] ?? $response->get_status(), $response->get_status()
1139 + $data['message'] ?? $response->get_status(),
1140 + $response->get_status()
618 1141 );
619 1142 }
620 1143
621 1144 return $response;
@@ -620,35 +1143,100 @@
620 1143
621 1144 return $response;
622 1145
623 1146 } catch (Exception $e) {
624 - $this->skipMiddleware = true;
625 - $this->action = function() use ($e) {
626 - return $this->app->response->sendError(
627 - ['message' => $e->getMessage()], $e->getCode()
628 - );
629 - };
1147 + return new WP_Error(
1148 + 'Permission Callback Error',
1149 + $e->getMessage(), [
1150 + 'status' => $e->getCode() ?: 403
1151 + ]
1152 + );
630 1153 }
631 1154 }
632 1155
633 1156 /**
634 - * Dispatches the permission handler
1157 + * Checks if the route is signed and needs validation.
635 1158 *
1159 + * @return boolean [description]
1160 + */
1161 + protected function isThisValidSignedRoute()
1162 + {
1163 + if (!$this->signed) return true;
1164 +
1165 + $request = $this->app->make('request');
1166 +
1167 + if ($this->app->make('url')->validate($request->getFullUrl())) {
1168 + parse_str($this->app->make('encrypter')->decrypt(
1169 + $this->app->request->get('_data')
1170 + ), $query);
1171 +
1172 + $this->app->request->merge(
1173 + Arr::except($query, ['expires_at'])
1174 + );
1175 +
1176 + $this->app->request->forget('_data');
1177 +
1178 + return true;
1179 + }
1180 + }
1181 +
1182 + /**
1183 + * Dispatches the permission handler.
1184 + *
636 1185 * @return bool|null
637 1186 */
638 1187 protected function dispatchPermissionHandler()
639 1188 {
640 - if ($this->permissionHandler) {
641 - return $this->app->call(
642 - $this->permissionHandler,
643 - $this->getControllerParameters()
644 - );
1189 + if (!$this->permissionHandler) {
1190 + return true;
645 1191 }
1192 +
1193 + $isValid = $this->app->call(
1194 + $this->permissionHandler,
1195 + $this->getControllerParameters()
1196 + );
1197 +
1198 + if (is_object($isValid)) {
1199 + if ($this->isUser($isValid)) {
1200 + $isValid = $isValid->id();
1201 + } else {
1202 + $this->throwInvalidPolicy();
1203 + }
1204 + }
1205 +
1206 + if (!is_bool($isValid) && !is_int($isValid) && !is_null($isValid)) {
1207 + $this->throwInvalidPolicy();
1208 + }
1209 +
1210 + return (bool) $isValid;
646 1211 }
647 1212
648 1213 /**
1214 + * Checks if the user is an instance of WPUserProxy.
1215 + *
1216 + * @param WPUserProxy $user
1217 + * @return bool
1218 + */
1219 + protected function isUser($user)
1220 + {
1221 + return $user instanceof WPUserProxy;
1222 + }
1223 +
1224 + /**
1225 + * Throw invalid policy handling exception.
1226 + *
1227 + * @return InvalidArgumentException
1228 + */
1229 + protected function throwInvalidPolicy()
1230 + {
1231 + throw new InvalidArgumentException(
1232 + 'The policy must return a boolean, integer, null, or a WPUserProxy instance.', 500
1233 + );
1234 + }
1235 +
1236 + /**
649 1237 * Gether route params after substituted the params
650 - *
1238 + *
651 1239 * @return array
652 1240 */
653 1241 protected function getControllerParameters()
654 1242 {
@@ -655,9 +1243,9 @@
655 1243 $routeParameters = [];
656 1244
657 1245 if (!$this->substitutedParameters) {
658 1246 if ($routeParameters = $this->getParameter()) {
659 - $routeParameters = $this->SubstituteParameters($routeParameters);
1247 + $routeParameters = $this->substituteParameters($routeParameters);
660 1248 }
661 1249 } else {
662 1250 $routeParameters = $this->substitutedParameters;
663 1251 }
@@ -669,9 +1257,9 @@
669 1257 * Added the ability to add middleware so we can intercept
670 1258 * the request without modifying the source code again
671 1259 * and again. The middleware class will implement
672 1260 * the handle method as given below:
673 - *
1261 + *
674 1262 * public function handle($request, $next)
675 1263 *
676 1264 * And must return $next($request) to handle the request.
677 1265 * Otherwise return nothing to abort the request.
@@ -676,9 +1264,9 @@
676 1264 * And must return $next($request) to handle the request.
677 1265 * Otherwise return nothing to abort the request.
678 1266 * Optionally, you may call the abort method:
679 1267 * return $request->abort(code, message);
680 - *
1268 + *
681 1269 * @param string $type
682 1270 * @return array
683 1271 */
684 1272 protected function collectMiddleWare($type = 'before')
@@ -699,12 +1287,13 @@
699 1287 foreach ($this->middleware[$type] as $routeMiddleware) {
700 1288
701 1289 if (is_object($routeMiddleware)) {
702 1290 $handler = $routeMiddleware;
1291 + } elseif (class_exists($routeMiddleware)) {
1292 + $handler = $this->resolveMiddlewareFrom($routeMiddleware);
703 1293 } else {
704 1294 $pieces = explode(':', $routeMiddleware);
705 1295 $handler = Arr::get($routeArray, $key = reset($pieces));
706 -
707 1296 if (isset($pieces[1])) {
708 1297 $handler = $this->resolveMiddleware($handler, $pieces);
709 1298 }
710 1299 }
@@ -717,9 +1306,9 @@
717 1306 $msg = "No middleware is assigned for the key: {$key} in {$mpath} array.";
718 1307 } else {
719 1308 $msg = "Could't resolve middleware.";
720 1309 }
721 -
1310 +
722 1311 throw new InvalidArgumentException($msg);
723 1312 }
724 1313 }
725 1314
@@ -726,12 +1315,25 @@
726 1315 return $callableMiddleware;
727 1316 }
728 1317
729 1318 /**
1319 + * Resolve a middleware from a class.
1320 + *
1321 + * @param string $class
1322 + * @return \Closure
1323 + */
1324 + protected function resolveMiddlewareFrom($class)
1325 + {
1326 + return static function ($r, $next, ...$params) use ($class) {
1327 + return (new $class)->handle($r, $next, ...$params);
1328 + };
1329 + }
1330 +
1331 + /**
730 1332 * Resolve the middleware
731 - *
732 - * @param mixed $handler
733 - * @param aray $pieces
1333 + *
1334 + * @param mixed $handler
1335 + * @param array $pieces
734 1336 * @return object
735 1337 */
736 1338 protected function resolveMiddleware($handler, $pieces)
737 1339 {
@@ -739,38 +1341,44 @@
739 1341 $handler = $this->wrapMiddleware($handler, $pieces);
740 1342 } elseif (is_string($handler)) {
741 1343 $handler = $handler . ':' . str_replace(' ', '', end($pieces));
742 1344 }
743 -
1345 +
744 1346 return $handler;
745 1347 }
746 1348
747 1349 /**
748 1350 * Create a class to wrap the middleware
749 - *
750 - * @param mixed $handler
751 - * @param aray $pieces
1351 + *
1352 + * @param mixed $handler
1353 + * @param array $pieces
752 1354 * @return object
753 1355 */
754 1356 protected function wrapMiddleware($handler, $pieces)
755 1357 {
756 1358 $params = str_replace(' ', '', end($pieces));
757 -
1359 +
758 1360 $params = explode(',', $params);
759 1361
760 1362 return new class ($handler, $params) {
761 1363 protected $handler, $params = null;
1364 +
762 1365 public function __construct($handler, $params) {
763 1366 $this->handler = $handler;
764 1367 $this->params = $params;
765 1368 }
766 - public function handle($target, $next) {
1369 +
1370 + public function handle($r, $next) {
767 1371 if (is_callable($this->handler)) {
768 - return ($this->handler)($target, $next, ...$this->params);
1372 + return ($this->handler)($r, $next, ...$this->params);
769 1373 } else {
770 - return $this->handler->handle(
771 - $target, $next, ...$this->params
772 - );
1374 + if (!method_exists($this->handler, 'handle')) {
1375 + $class = get_class($this->handler);
1376 + throw new InvalidArgumentException(
1377 + "The {$class} must implement the handle method."
1378 + );
1379 + }
1380 + return $this->handler->handle($r, $next, ...$this->params);
773 1381 }
774 1382 }
775 1383 };
776 1384 }
@@ -776,11 +1384,12 @@
776 1384 }
777 1385
778 1386 /**
779 1387 * Add the middleware in the stack
780 - *
1388 + *
781 1389 * @param array &$stack All callable middleware for the route
782 - * @param null
1390 + * @param string $middleware
1391 + * @return void
783 1392 */
784 1393 protected function addMiddlewareInTheStack(&$stack, $middleware)
785 1394 {
786 1395 if (!in_array($middleware, $stack)) {
@@ -789,10 +1398,10 @@
789 1398 }
790 1399
791 1400 /**
792 1401 * Resolve the policy handler
793 - *
794 - * @param string $policyHandler
1402 + *
1403 + * @param string $policyHandler
795 1404 * @return mixed
796 1405 */
797 1406 protected function getPolicyHandler($policyHandler)
798 1407 {
@@ -804,15 +1413,17 @@
804 1413 return $policyHandler;
805 1414 }
806 1415
807 1416 if (is_string($policyHandler)) {
808 -
1417 +
809 1418 if (function_exists($policyHandler)) {
810 1419 return $policyHandler;
811 1420 }
812 1421
813 - $policyHandlerFunction = substr($policyHandler, strrpos($policyHandler, '\\') + 1);
814 -
1422 + $policyHandlerFunction = substr(
1423 + $policyHandler, strrpos($policyHandler, '\\') + 1
1424 + );
1425 +
815 1426 if (function_exists($policyHandlerFunction)) {
816 1427 return $policyHandlerFunction;
817 1428 }
818 1429 }
@@ -823,16 +1434,13 @@
823 1434
824 1435 if (is_string($policyHandler) && $this->handler instanceof Closure) {
825 1436
826 1437 if (class_exists($policyHandler)) {
827 -
828 - $reflection = new \ReflectionClass($policyHandler);
829 -
1438 +
1439 + $reflection = new ReflectionClass($policyHandler);
1440 +
830 1441 if ($reflection->hasMethod('verifyRequest')) {
831 -
832 - $policyHandler = $policyHandler . '@' . 'verifyRequest';
833 -
834 - return $policyHandler;
1442 + return $policyHandler . '@' . 'verifyRequest';
835 1443 }
836 1444 } elseif (function_exists($policyHandler)) {
837 1445 return $policyHandler;
838 1446 }
@@ -840,30 +1448,35 @@
840 1448 throw new InvalidArgumentException(
841 1449 'Explicit policy handler is required while using a closure as route callback.'
842 1450 );
843 1451 }
844 -
1452 +
845 1453 if ($policyHandler && !function_exists($policyHandler)) {
846 - if (is_string($this->handler) && strpos($this->handler, '@') !== false) {
847 - list($_, $method) = explode('@', $this->handler);
848 - $policyHandler = $policyHandler . '@' . $method;
849 - } else if (is_array($this->handler)) {
850 - $policyHandler = $policyHandler . '@' . $this->handler[1];
851 - }
1454 + [$_, $method] = is_array($this->handler)
1455 + ? [$this->handler[0], $this->handler[1] ?? '__invoke']
1456 + : Str::parseCallback($this->handler, '__invoke');
1457 +
1458 + $policyHandler .= '@' . $method;
852 1459 }
853 1460
854 1461 return $policyHandler ?: [$this, 'defaultPolicyHandler'];
855 1462 }
856 1463
1464 + /**
1465 + * Check if the policy handler is parseable.
1466 + *
1467 + * @param string $policyHandler
1468 + * @return boolean
1469 + */
857 1470 protected function isPolicyHandlerParseable($policyHandler)
858 1471 {
859 1472 return (strpos($policyHandler, '@') === true
860 - || strpos($policyHandler, '::') === true);
1473 + || strpos($policyHandler, '::') === true);
861 1474 }
862 1475
863 1476 /**
864 1477 * Default/Fallback policy handler for the route
865 - *
1478 + *
866 1479 * @return bool
867 1480 */
868 1481 public function defaultPolicyHandler()
869 1482 {
@@ -871,45 +1484,115 @@
871 1484 }
872 1485
873 1486 /**
874 1487 * Parse the rest and permission/policy handlers
875 - *
876 - * @param \WP_REST_Request $request
1488 + *
1489 + * @param \WP_REST_Request $request
877 1490 * @return null
878 1491 * @throws \BadMethodCallException
879 1492 */
880 1493 public function prepareCallbacks($request)
881 1494 {
882 - $handler = $this->app->parseRestHandler(
883 - $this->handler, $this->namespace
884 - );
1495 + $handler = $this->app->parseRestHandler($this->handler, $this->namespace);
885 1496
1497 + [$action, $controller] = $this->resolveHandlerDetails($handler);
1498 +
1499 + $policyHandler = $this->resolvePolicyHandler();
1500 +
1501 + $this->actionInfo = [
1502 + 'handler' => is_object($handler) ? $action : $handler,
1503 + 'controller' => $controller,
1504 + 'method' => $this->getMethodName($action, $handler),
1505 + 'path' => $this->uri,
1506 + 'http_method' => $request->get_method(),
1507 + 'full_uri' => $request->get_route(),
1508 + 'permission_callback' => $policyHandler,
1509 + 'compiled_url' => $this->compiled
1510 + ];
1511 +
1512 + $this->action = $handler;
1513 +
1514 + if ($routeParameters = $this->getParameter()) {
1515 + $this->substitutedParameters = $this->substituteParameters($routeParameters);
1516 + }
1517 +
1518 + return $this->action;
1519 + }
1520 +
1521 + /**
1522 + * Get the method name to build action info.
1523 + *
1524 + * @param mixed $action
1525 + * @param mixed $handler
1526 + * @return string|null
1527 + */
1528 + protected function getMethodName($action, $handler)
1529 + {
1530 + $method = is_array($action) ? $action[1] ?? '__invoke' : null;
1531 +
1532 + if (is_null($method) && is_object($handler)) {
1533 + $method = '__invoke';
1534 + }
1535 +
1536 + return $method;
1537 + }
1538 +
1539 + /**
1540 + * Resolve the handler details.
1541 + *
1542 + * @param mixed $handler
1543 + * @return array
1544 + */
1545 + protected function resolveHandlerDetails($handler)
1546 + {
886 1547 if ($handler instanceof Closure) {
887 - $action = 'Closure';
888 - $controller = null;
889 - } else {
890 - $handler = trim($handler, '\\');
891 - $action = explode('@', $handler);
892 - $pieces = explode('\\', $action[0]);
893 - $controller = end($pieces);
1548 + return ['Closure', null];
894 1549 }
895 1550
1551 + if (is_object($handler)) {
1552 + $class = get_class($handler);
1553 + return [$class, $class];
1554 + }
1555 +
1556 + $handler = trim($handler, '\\');
1557 + [$controller, $method] = Str::parseCallback($handler, '__invoke');
1558 + $controllerName = $this->extractControllerName($controller);
1559 +
1560 + return [[$controller, $method], $controllerName];
1561 + }
1562 +
1563 + /**
1564 + * Extract the controller name from the FQCN.
1565 + *
1566 + * @param string $fqcn
1567 + * @return string
1568 + */
1569 + protected function extractControllerName($fqcn)
1570 + {
1571 + $parts = explode('\\', $fqcn);
1572 + return end($parts);
1573 + }
1574 +
1575 + /**
1576 + * Parse and validate the policy handler.
1577 + *
1578 + * @return array
1579 + */
1580 + protected function resolvePolicyHandler()
1581 + {
896 1582 try {
897 1583 $policyHandler = $this->app->parsePolicyHandler(
898 1584 $this->getPolicyHandler($this->policyHandler)
899 1585 );
900 -
1586 +
901 1587 if ($policyHandler) {
902 1588 $this->permissionHandler = $policyHandler;
903 1589
904 - // Adjust policy handler if the method was explicitly given
905 - if (is_string($this->policyHandler)) {
906 - if (is_array($policyHandler) && isset($policyHandler[1])) {
907 - if ($pieces = explode('@', $this->policyHandler)) {
908 - if (isset($pieces[1])) {
909 - $this->permissionHandler[1] = $pieces[1];
910 - }
911 - }
1590 + // Adjust method if explicitly given in string policy handler
1591 + if (is_string($this->policyHandler) && is_array($policyHandler) && isset($policyHandler[1])) {
1592 + $pieces = explode('@', $this->policyHandler);
1593 + if (isset($pieces[1])) {
1594 + $this->permissionHandler[1] = $pieces[1];
912 1595 }
913 1596 }
914 1597
915 1598 if (!is_callable($this->permissionHandler)) {
@@ -917,52 +1600,43 @@
917 1600 }
918 1601 }
919 1602
920 1603 } catch (Exception $e) {
921 - $pHandler = $this->policyHandler;
922 - if (is_array($this->permissionHandler) && $this->permissionHandler) {
923 - $pHandler = is_object($this->permissionHandler[0]) ?
924 - get_class($this->permissionHandler[0]) . ':' . $this->permissionHandler[1] :
925 - $this->permissionHandler[0] . ':' . $this->permissionHandler[1];
926 - }
927 -
928 - throw new BadMethodCallException(
929 - "The permission callback {$pHandler} is invalid or not callable."
930 - );
1604 + throw $this->invalidPolicyHandlerException();
931 1605 }
932 1606
933 - if (is_array($policyHandler)) {
1607 + // Convert object controller to class string for endpoint metadata
1608 + if (is_array($policyHandler) && is_object($policyHandler[0])) {
934 1609 $policyHandler[0] = get_class($policyHandler[0]);
935 1610 }
936 1611
937 - $this->actionInfo = [
938 - 'handler' => is_object($handler) ? $action : $handler,
939 - 'controller' => $controller,
940 - 'method' => is_array($action) ? $action[1] : null,
941 - 'path' => $this->uri,
942 - 'http_method' => $request->get_method(),
943 - 'full_uri' => $request->get_route(),
944 - 'permission_callback' => $policyHandler,
945 - 'compiled_url' => $this->compiled
946 - ];
1612 + return $policyHandler;
1613 + }
947 1614
1615 + /**
1616 + * Build and throw an exception for invalid policy handlers.
1617 + *
1618 + * @throws \BadMethodCallException
1619 + */
1620 + protected function invalidPolicyHandlerException()
1621 + {
1622 + $pHandler = $this->policyHandler;
948 1623
949 - $this->action = $handler;
950 -
951 - if ($routeParameters = $this->getParameter()) {
952 - $this->substitutedParameters = $this->SubstituteParameters(
953 - $routeParameters
954 - );
1624 + if (is_array($this->permissionHandler) && $this->permissionHandler) {
1625 + $pHandler = is_object($this->permissionHandler[0])
1626 + ? get_class($this->permissionHandler[0]) . ':' . $this->permissionHandler[1]
1627 + : $this->permissionHandler[0] . ':' . $this->permissionHandler[1];
955 1628 }
956 1629
957 -
958 - return $this->action;
1630 + return new BadMethodCallException(
1631 + "The permission callback {$pHandler} is invalid or not callable."
1632 + );
959 1633 }
960 1634
961 1635 /**
962 - * Get route one or more parameters
963 - * @param string $key
964 - *
1636 + * Get one or more route parameters
1637 + * @param string $key
1638 + *
965 1639 * @return mixed
966 1640 */
967 1641 public function getParameter($key = null)
968 1642 {
@@ -973,10 +1647,40 @@
973 1647 return $key ? $this->parameters[$key] : $this->parameters;
974 1648 }
975 1649
976 1650 /**
1651 + * Get the name of the route.
1652 + *
1653 + * @return string
1654 + */
1655 + public function getName()
1656 + {
1657 + return $this->name;
1658 + }
1659 +
1660 + /**
1661 + * Get the url of the route.
1662 + *
1663 + * @return string
1664 + */
1665 + public function getUrl()
1666 + {
1667 + return $this->uri;
1668 + }
1669 +
1670 + /**
1671 + * Get the url of the route.
1672 + *
1673 + * @return string
1674 + */
1675 + public function uri()
1676 + {
1677 + return $this->getUrl();
1678 + }
1679 +
1680 + /**
977 1681 * Dynamically access a route parameter.
978 - *
1682 + *
979 1683 * @param string $key
980 1684 * @return mixed
981 1685 */
982 1686 public function __get($key)