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 +911 -243 1.312.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
@@ -157,51 +198,103 @@
157 198 $this->restNamespace = $restNamespace;
158 199 $this->uri = $uri;
159 200 $this->handler = $handler;
160 201 $this->method = $method;
161 -
162 - $this->preparefrontendHandlers($handler);
163 202 }
164 203
165 204 /**
166 205 * Map the route to be used in front-end.
167 - *
168 - * @param mixed $handler
169 - * @return null
206 + *
207 + * @param mixed $handler
208 + * @return self
170 209 */
171 - protected function preparefrontendHandlers($handler)
210 + public function preparefrontendHandlers()
172 211 {
212 + $handler = $this->handler;
213 +
173 214 $endpointsUrl = $this->app->config->get('app.slug') . '/__endpoints';
174 -
175 - if (!str_contains($this->app->request->url(), $endpointsUrl)) {
176 - return;
215 +
216 + if (get_option('permalink_structure')) {
217 + $url = $this->app->request->url();
218 + } else {
219 + $url = $this->app->request->query('rest_route');
177 220 }
178 221
179 - if ($handler instanceof Closure) {
180 - return;
222 + if (
223 + !str_contains($url ?? '', $endpointsUrl)
224 + || $handler instanceof Closure
225 + ) {
226 + return $this;
181 227 }
182 228
183 - $action = trim($this->app->parseRestHandler($handler), '\\');
184 -
185 - [$controller, $cb] = explode('@', $action);
229 + [$controller, $cb] = Str::parseCallback($this->parseAction($handler));
186 230
231 + $this->endpointSignature = [$controller, "_{$cb}"];
232 +
187 233 $controller = str_replace('\\', '.', $controller);
188 234
235 + // @phpstan-ignore-next-line
189 236 $endpoints = $this->app->endpoints;
190 -
237 +
191 238 $endpoints[$controller]["_{$cb}"] = [
192 239 'uri' => $this->uri,
193 - 'methods' => explode(',', $this->method)
240 + 'methods' => explode(',', $this->method),
241 + 'policy' => $this->getPolicyName()
194 242 ];
195 243
244 + // @phpstan-ignore-next-line
196 245 $this->app->endpoints = $endpoints;
246 +
247 + return $this;
197 248 }
198 249
199 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 + /**
200 293 * Alternative constructor
201 - *
294 + *
202 295 * @param \FluentBoards\Framework\Foundation\Application $app
203 - * @param string $restNamespace
296 + * @param string $namespace
204 297 * @param string $uri
205 298 * @param string $handler
206 299 * @param string $method
207 300 * @return self
@@ -212,11 +305,11 @@
212 305 }
213 306
214 307 /**
215 308 * Set route meta
216 - *
217 - * @param string $key
218 - * @param mixed $value
309 + *
310 + * @param string $key
311 + * @param mixed $value
219 312 * @return self
220 313 */
221 314 public function meta($key, $value = null)
222 315 {
@@ -228,10 +321,10 @@
228 321 }
229 322
230 323 /**
231 324 * Get route meta
232 - *
233 - * @param string $key
325 + *
326 + * @param string $key
234 327 * @return mixed
235 328 */
236 329 public function getMeta($key = '')
237 330 {
@@ -237,15 +330,15 @@
237 330 {
238 331 if (isset($this->meta[$key])) {
239 332 return $this->meta[$key];
240 333 }
241 -
334 +
242 335 return $this->meta;
243 336 }
244 337
245 338 /**
246 339 * Get route options
247 - *
340 + *
248 341 * @return mixed
249 342 */
250 343 public function getOptions()
251 344 {
@@ -253,10 +346,10 @@
253 346 }
254 347
255 348 /**
256 349 * Get route options
257 - *
258 - * @param string $key
350 + *
351 + * @param string $key
259 352 * @return mixed
260 353 */
261 354 public function getOption($key = null)
262 355 {
@@ -264,9 +357,9 @@
264 357 }
265 358
266 359 /**
267 360 * Get route action information
268 - * @param string $key
361 + * @param string $key
269 362 * @return mixed
270 363 */
271 364 public function getAction($key = '')
272 365 {
@@ -272,17 +365,17 @@
272 365 {
273 366 if ($key && array_key_exists($key, $this->actionInfo)) {
274 367 return $this->actionInfo[$key];
275 368 }
276 -
369 +
277 370 return $this->actionInfo;
278 371 }
279 372
280 373 /**
281 374 * Set a where constrain into the route
282 - *
283 - * @param string $identifier
284 - * @param string $value
375 + *
376 + * @param string $identifier
377 + * @param string $value
285 378 * @return self
286 379 */
287 380 public function where($identifier, $value = null)
288 381 {
@@ -298,10 +391,10 @@
298 391 }
299 392
300 393 /**
301 394 * Add an integer type route constraint
302 - *
303 - * @param string $identifiers
395 + *
396 + * @param string $identifiers
304 397 * @return self
305 398 */
306 399 public function int($identifiers)
307 400 {
@@ -315,10 +408,10 @@
315 408 }
316 409
317 410 /**
318 411 * Add an alpha type route constraint
319 - *
320 - * @param string $identifiers
412 + *
413 + * @param string $identifiers
321 414 * @return self
322 415 */
323 416 public function alpha($identifiers)
324 417 {
@@ -332,10 +425,10 @@
332 425 }
333 426
334 427 /**
335 428 * Add an alphanum type route constraint
336 - *
337 - * @param string $identifiers
429 + *
430 + * @param string $identifiers
338 431 * @return self
339 432 */
340 433 public function alphaNum($identifiers)
341 434 {
@@ -349,10 +442,10 @@
349 442 }
350 443
351 444 /**
352 445 * Add an alphanumdash type route constraint
353 - *
354 - * @param string $identifiers
446 + *
447 + * @param string $identifiers
355 448 * @return self
356 449 */
357 450 public function alphaNumDash($identifiers)
358 451 {
@@ -366,10 +459,10 @@
366 459 }
367 460
368 461 /**
369 462 * Set the route before middleware
370 - *
371 - * @param array|string $middleware
463 + *
464 + * @param array|string $middleware
372 465 * @return self
373 466 */
374 467 public function before(...$middleware)
375 468 {
@@ -377,10 +470,10 @@
377 470 }
378 471
379 472 /**
380 473 * Set the route after middleware
381 - *
382 - * @param array|string $middleware
474 + *
475 + * @param array|string $middleware
383 476 * @return self
384 477 */
385 478 public function after(...$middleware)
386 479 {
@@ -388,9 +481,9 @@
388 481 }
389 482
390 483 /**
391 484 * Set the route middleware
392 - * @param array $middleware
485 + * @param array $middleware
393 486 * @return self
394 487 */
395 488 public function middleware($type = 'before', ...$middleware)
396 489 {
@@ -405,12 +498,25 @@
405 498 return $this;
406 499 }
407 500
408 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 + /**
409 515 * Set the route policy
410 - *
411 - * @param mixed $handler
412 - * @param string|null $method
516 + *
517 + * @param mixed $handler
518 + * @param string|null $method
413 519 * @return self
414 520 */
415 521 public function withPolicy($handler, $method = null)
416 522 {
@@ -425,15 +531,83 @@
425 531 debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 4)
426 532 );
427 533 }
428 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 +
429 575 return $this;
430 576 }
431 577
432 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 + /**
433 606 * Resolve and set policy with namespace for add-ons
434 - *
435 - * @param null
607 + *
608 + * @param array $backTrace
609 + * @return void
436 610 */
437 611 protected function setPolicyHandlerWithNamespace($backTrace)
438 612 {
439 613 $last = end($backTrace);
@@ -442,9 +616,9 @@
442 616
443 617 $class = $last['class'];
444 618
445 619 $namespace = substr(__NAMESPACE__, 0, strpos(__NAMESPACE__, '\\'));
446 -
620 +
447 621 $calledClassNamespace = substr($class, 0, strpos($class, '\\'));
448 622
449 623 if ($namespace != $calledClassNamespace) {
450 624 $ns = $calledClassNamespace . '\\App\\Http\\Policies\\';
@@ -452,51 +626,211 @@
452 626 }
453 627 }
454 628
455 629 /**
456 - * Set the namespace for controller/action
457 - * @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
458 651 * @return null
459 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 + */
460 664 public function withNamespace($ns)
461 665 {
462 - $this->namespace = implode('\\', $ns);
666 + if (is_array($ns)) {
667 + $this->namespace = implode('\\', $ns);
668 + } else {
669 + $this->namespace = trim($ns, '\\');
670 + }
463 671 }
464 672
465 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 + /**
466 740 * Register the rest endpoint
467 - *
741 + *
468 742 * @return null
469 743 */
470 744 public function register()
471 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 + {
472 763 $this->setOptions();
764 + }
473 765
474 - $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 + }
475 775
476 - return register_rest_route(
477 - $this->restNamespace, $uri, $this->getOptions()
478 - );
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;
479 786 }
480 787
481 788 /**
482 789 * Set route options
483 - *
790 + *
484 791 * @return null
485 792 */
486 793 protected function setOptions()
487 794 {
488 - $this->options = [
489 - 'args' => [],
490 - 'methods' => $this->method,
491 - 'callback' => [$this, 'callback'],
492 - '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 + ],
493 814 ];
494 815 }
495 816
496 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 + /**
497 831 * Get item from predefined regex
498 - * @param string $value
832 + * @param string $value
499 833 * @return string
500 834 */
501 835 protected function getValue($value)
502 836 {
@@ -508,10 +842,10 @@
508 842 }
509 843
510 844 /**
511 845 * Compikle the rest route to regex
512 - *
513 - * @param string $uri
846 + *
847 + * @param string $uri
514 848 * @return string compiled rest endpoint
515 849 */
516 850 protected function compileRoute($uri)
517 851 {
@@ -516,12 +850,12 @@
516 850 protected function compileRoute($uri)
517 851 {
518 852 $params = [];
519 853
520 - $compiledUri = preg_replace_callback('#/{(.*?)}#', function($match) use (&$params, $uri) {
854 + $compiledUri = preg_replace_callback('#/{(.*?)}#', function ($match) use (&$params, $uri) {
521 855 // Default regx
522 856 $regx = '[^\s(?!/)]+';
523 -
857 +
524 858 $param = trim($match[1]);
525 859
526 860 if ($isOptional = strpos($param, '?')) {
527 861 $param = trim($param, '?');
@@ -531,9 +865,9 @@
531 865 throw new InvalidArgumentException(
532 866 "Duplicate parameter name '{$param}' found in {$uri}.", 500
533 867 );
534 868 }
535 -
869 +
536 870 $params[] = $param;
537 871
538 872 if (isset($this->wheres[$param])) {
539 873 $regx = $this->wheres[$param];
@@ -543,11 +877,11 @@
543 877
544 878 if ($isOptional) {
545 879 $pattern = "(?:" . $pattern . ")?";
546 880 }
547 -
881 +
548 882 $this->options['args'][$param]['required'] = !$isOptional;
549 -
883 +
550 884 return $pattern;
551 885
552 886 }, $uri);
553 887
@@ -555,89 +889,241 @@
555 889 }
556 890
557 891 /**
558 892 * Route handler
559 - *
560 - * @return mixed
893 + *
894 + * @return \WP_REST_Response
561 895 */
562 896 public function callback()
563 897 {
564 898 try {
565 -
566 - $response = $this->app->call(
567 - $this->action, $this->getControllerParameters()
899 + $this->response = $this->handleAfterMiddleware(
900 + $this->dispatchRouteAction()
568 901 );
569 902
570 - if ($response instanceof WPFluentResponse) {
571 - $response = $response->toArray();
572 - }
903 + return $this->handleResponse($this->response);
573 904
574 - if (!($response instanceof WP_REST_Response)) {
575 - if (is_wp_error($response)) {
576 - $response = $this->app->response->wpErrorToResponse($response);
577 - } else {
578 - $response = $this->app->response->sendSuccess($response);
579 - }
580 - }
581 -
582 - if (!$this->skipMiddleware) {
583 - $response = $this->app->make(Pipeline::class)
584 - ->send($response)
585 - ->through($this->collectMiddleWare('after'))
586 - ->then(function($response) {
587 - if (!$response instanceof WP_REST_Response) {
588 - $response = new WP_REST_Response($response);
589 - }
590 - return $response;
591 - });
592 -
593 - if (!$response) {
594 - $response = $this->app->request->abort();
595 - }
596 - }
597 -
598 - return $response;
599 -
600 905 } catch (ValidationException $e) {
601 906 return $this->app->response->sendError(
602 907 $e->errors(), $e->getCode()
603 908 );
604 - } catch (ModelNotFoundException $e) {
909 + } catch (ModelNotFoundException $e) {
605 910 return $this->app->response->sendError([
606 911 'message' => $e->getMessage()
607 912 ], 404);
608 - } catch (Exception $e) {
609 - return $this->app->response->sendError([
610 - 'message' => $e->getMessage()
611 - ], $e->getCode() ?: 500);
913 + } catch (Throwable $e) {
914 + return $this->handleUnknownException(
915 + $e, $this->response ? $this->response->get_headers() : []
916 + );
612 917 }
613 918 }
614 919
615 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 + /**
616 1101 * Permission callback for route
617 - * @param \WP_REST_Request $wpRestRequest
1102 + * @param \WP_REST_Request $wpRestRequest
618 1103 * @return mixed
619 1104 */
620 1105 public function permissionCallback($wpRestRequest)
621 1106 {
622 1107 try {
623 -
1108 + $this->parameters = null;
1109 + $this->substitutedParameters = null;
624 1110 $this->app->instance('route', $this);
625 -
626 - if (!$this->app->bound('wprestrequest')) {
627 - $this->app->instance('wprestrequest', $wpRestRequest);
628 - $this->app->request->mergeInputsFromRestRequest($wpRestRequest);
1111 + $this->app->instance('wprestrequest', $wpRestRequest);
1112 + $this->app->request->mergeInputsFromRestRequest($wpRestRequest);
1113 + $this->prepareCallbacks($this->app->request);
629 1114
630 - if (method_exists($this, 'prepareCallbacks')) {
631 - $this->prepareCallbacks($this->app->request);
632 - }
1115 + if (!$this->isThisValidSignedRoute()) {
1116 + throw new Exception('Invalid Signature', 403);
633 1117 }
634 1118
635 1119 $response = $this->app->make(Pipeline::class)
636 1120 ->send($this->app->request)
637 1121 ->through($this->collectMiddleWare('before'))
638 - ->then(function($request) {
639 - return $this->dispatchPermissionHandler();
1122 + ->then(function ($request) {
1123 + if ($request && $request instanceof Request) {
1124 + return $this->dispatchPermissionHandler();
1125 + }
640 1126 });
641 1127
642 1128 if (is_wp_error($response)) {
643 1129 throw new Exception(
@@ -649,9 +1135,10 @@
649 1135 if ($response instanceof WP_REST_Response) {
650 1136 $data = $response->get_data();
651 1137
652 1138 throw new Exception(
653 - $data['message'] ?? $response->get_status(), $response->get_status()
1139 + $data['message'] ?? $response->get_status(),
1140 + $response->get_status()
654 1141 );
655 1142 }
656 1143
657 1144 return $response;
@@ -656,35 +1143,100 @@
656 1143
657 1144 return $response;
658 1145
659 1146 } catch (Exception $e) {
660 - $this->skipMiddleware = true;
661 - $this->action = function() use ($e) {
662 - return $this->app->response->sendError(
663 - ['message' => $e->getMessage()], $e->getCode()
664 - );
665 - };
1147 + return new WP_Error(
1148 + 'Permission Callback Error',
1149 + $e->getMessage(), [
1150 + 'status' => $e->getCode() ?: 403
1151 + ]
1152 + );
666 1153 }
667 1154 }
668 1155
669 1156 /**
670 - * Dispatches the permission handler
1157 + * Checks if the route is signed and needs validation.
671 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 + *
672 1185 * @return bool|null
673 1186 */
674 1187 protected function dispatchPermissionHandler()
675 1188 {
676 - if ($this->permissionHandler) {
677 - return $this->app->call(
678 - $this->permissionHandler,
679 - $this->getControllerParameters()
680 - );
1189 + if (!$this->permissionHandler) {
1190 + return true;
681 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;
682 1211 }
683 1212
684 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 + /**
685 1237 * Gether route params after substituted the params
686 - *
1238 + *
687 1239 * @return array
688 1240 */
689 1241 protected function getControllerParameters()
690 1242 {
@@ -691,9 +1243,9 @@
691 1243 $routeParameters = [];
692 1244
693 1245 if (!$this->substitutedParameters) {
694 1246 if ($routeParameters = $this->getParameter()) {
695 - $routeParameters = $this->SubstituteParameters($routeParameters);
1247 + $routeParameters = $this->substituteParameters($routeParameters);
696 1248 }
697 1249 } else {
698 1250 $routeParameters = $this->substitutedParameters;
699 1251 }
@@ -705,9 +1257,9 @@
705 1257 * Added the ability to add middleware so we can intercept
706 1258 * the request without modifying the source code again
707 1259 * and again. The middleware class will implement
708 1260 * the handle method as given below:
709 - *
1261 + *
710 1262 * public function handle($request, $next)
711 1263 *
712 1264 * And must return $next($request) to handle the request.
713 1265 * Otherwise return nothing to abort the request.
@@ -712,9 +1264,9 @@
712 1264 * And must return $next($request) to handle the request.
713 1265 * Otherwise return nothing to abort the request.
714 1266 * Optionally, you may call the abort method:
715 1267 * return $request->abort(code, message);
716 - *
1268 + *
717 1269 * @param string $type
718 1270 * @return array
719 1271 */
720 1272 protected function collectMiddleWare($type = 'before')
@@ -735,12 +1287,13 @@
735 1287 foreach ($this->middleware[$type] as $routeMiddleware) {
736 1288
737 1289 if (is_object($routeMiddleware)) {
738 1290 $handler = $routeMiddleware;
1291 + } elseif (class_exists($routeMiddleware)) {
1292 + $handler = $this->resolveMiddlewareFrom($routeMiddleware);
739 1293 } else {
740 1294 $pieces = explode(':', $routeMiddleware);
741 1295 $handler = Arr::get($routeArray, $key = reset($pieces));
742 -
743 1296 if (isset($pieces[1])) {
744 1297 $handler = $this->resolveMiddleware($handler, $pieces);
745 1298 }
746 1299 }
@@ -753,9 +1306,9 @@
753 1306 $msg = "No middleware is assigned for the key: {$key} in {$mpath} array.";
754 1307 } else {
755 1308 $msg = "Could't resolve middleware.";
756 1309 }
757 -
1310 +
758 1311 throw new InvalidArgumentException($msg);
759 1312 }
760 1313 }
761 1314
@@ -762,12 +1315,25 @@
762 1315 return $callableMiddleware;
763 1316 }
764 1317
765 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 + /**
766 1332 * Resolve the middleware
767 - *
768 - * @param mixed $handler
769 - * @param aray $pieces
1333 + *
1334 + * @param mixed $handler
1335 + * @param array $pieces
770 1336 * @return object
771 1337 */
772 1338 protected function resolveMiddleware($handler, $pieces)
773 1339 {
@@ -775,38 +1341,44 @@
775 1341 $handler = $this->wrapMiddleware($handler, $pieces);
776 1342 } elseif (is_string($handler)) {
777 1343 $handler = $handler . ':' . str_replace(' ', '', end($pieces));
778 1344 }
779 -
1345 +
780 1346 return $handler;
781 1347 }
782 1348
783 1349 /**
784 1350 * Create a class to wrap the middleware
785 - *
786 - * @param mixed $handler
787 - * @param aray $pieces
1351 + *
1352 + * @param mixed $handler
1353 + * @param array $pieces
788 1354 * @return object
789 1355 */
790 1356 protected function wrapMiddleware($handler, $pieces)
791 1357 {
792 1358 $params = str_replace(' ', '', end($pieces));
793 -
1359 +
794 1360 $params = explode(',', $params);
795 1361
796 1362 return new class ($handler, $params) {
797 1363 protected $handler, $params = null;
1364 +
798 1365 public function __construct($handler, $params) {
799 1366 $this->handler = $handler;
800 1367 $this->params = $params;
801 1368 }
802 - public function handle($target, $next) {
1369 +
1370 + public function handle($r, $next) {
803 1371 if (is_callable($this->handler)) {
804 - return ($this->handler)($target, $next, ...$this->params);
1372 + return ($this->handler)($r, $next, ...$this->params);
805 1373 } else {
806 - return $this->handler->handle(
807 - $target, $next, ...$this->params
808 - );
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);
809 1381 }
810 1382 }
811 1383 };
812 1384 }
@@ -812,11 +1384,12 @@
812 1384 }
813 1385
814 1386 /**
815 1387 * Add the middleware in the stack
816 - *
1388 + *
817 1389 * @param array &$stack All callable middleware for the route
818 - * @param null
1390 + * @param string $middleware
1391 + * @return void
819 1392 */
820 1393 protected function addMiddlewareInTheStack(&$stack, $middleware)
821 1394 {
822 1395 if (!in_array($middleware, $stack)) {
@@ -825,10 +1398,10 @@
825 1398 }
826 1399
827 1400 /**
828 1401 * Resolve the policy handler
829 - *
830 - * @param string $policyHandler
1402 + *
1403 + * @param string $policyHandler
831 1404 * @return mixed
832 1405 */
833 1406 protected function getPolicyHandler($policyHandler)
834 1407 {
@@ -840,15 +1413,17 @@
840 1413 return $policyHandler;
841 1414 }
842 1415
843 1416 if (is_string($policyHandler)) {
844 -
1417 +
845 1418 if (function_exists($policyHandler)) {
846 1419 return $policyHandler;
847 1420 }
848 1421
849 - $policyHandlerFunction = substr($policyHandler, strrpos($policyHandler, '\\') + 1);
850 -
1422 + $policyHandlerFunction = substr(
1423 + $policyHandler, strrpos($policyHandler, '\\') + 1
1424 + );
1425 +
851 1426 if (function_exists($policyHandlerFunction)) {
852 1427 return $policyHandlerFunction;
853 1428 }
854 1429 }
@@ -859,16 +1434,13 @@
859 1434
860 1435 if (is_string($policyHandler) && $this->handler instanceof Closure) {
861 1436
862 1437 if (class_exists($policyHandler)) {
863 -
864 - $reflection = new \ReflectionClass($policyHandler);
865 -
1438 +
1439 + $reflection = new ReflectionClass($policyHandler);
1440 +
866 1441 if ($reflection->hasMethod('verifyRequest')) {
867 -
868 - $policyHandler = $policyHandler . '@' . 'verifyRequest';
869 -
870 - return $policyHandler;
1442 + return $policyHandler . '@' . 'verifyRequest';
871 1443 }
872 1444 } elseif (function_exists($policyHandler)) {
873 1445 return $policyHandler;
874 1446 }
@@ -876,30 +1448,35 @@
876 1448 throw new InvalidArgumentException(
877 1449 'Explicit policy handler is required while using a closure as route callback.'
878 1450 );
879 1451 }
880 -
1452 +
881 1453 if ($policyHandler && !function_exists($policyHandler)) {
882 - if (is_string($this->handler) && strpos($this->handler, '@') !== false) {
883 - list($_, $method) = explode('@', $this->handler);
884 - $policyHandler = $policyHandler . '@' . $method;
885 - } else if (is_array($this->handler)) {
886 - $policyHandler = $policyHandler . '@' . $this->handler[1];
887 - }
1454 + [$_, $method] = is_array($this->handler)
1455 + ? [$this->handler[0], $this->handler[1] ?? '__invoke']
1456 + : Str::parseCallback($this->handler, '__invoke');
1457 +
1458 + $policyHandler .= '@' . $method;
888 1459 }
889 1460
890 1461 return $policyHandler ?: [$this, 'defaultPolicyHandler'];
891 1462 }
892 1463
1464 + /**
1465 + * Check if the policy handler is parseable.
1466 + *
1467 + * @param string $policyHandler
1468 + * @return boolean
1469 + */
893 1470 protected function isPolicyHandlerParseable($policyHandler)
894 1471 {
895 1472 return (strpos($policyHandler, '@') === true
896 - || strpos($policyHandler, '::') === true);
1473 + || strpos($policyHandler, '::') === true);
897 1474 }
898 1475
899 1476 /**
900 1477 * Default/Fallback policy handler for the route
901 - *
1478 + *
902 1479 * @return bool
903 1480 */
904 1481 public function defaultPolicyHandler()
905 1482 {
@@ -907,45 +1484,115 @@
907 1484 }
908 1485
909 1486 /**
910 1487 * Parse the rest and permission/policy handlers
911 - *
912 - * @param \WP_REST_Request $request
1488 + *
1489 + * @param \WP_REST_Request $request
913 1490 * @return null
914 1491 * @throws \BadMethodCallException
915 1492 */
916 1493 public function prepareCallbacks($request)
917 1494 {
918 - $handler = $this->app->parseRestHandler(
919 - $this->handler, $this->namespace
920 - );
1495 + $handler = $this->app->parseRestHandler($this->handler, $this->namespace);
921 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 + {
922 1547 if ($handler instanceof Closure) {
923 - $action = 'Closure';
924 - $controller = null;
925 - } else {
926 - $handler = trim($handler, '\\');
927 - $action = explode('@', $handler);
928 - $pieces = explode('\\', $action[0]);
929 - $controller = end($pieces);
1548 + return ['Closure', null];
930 1549 }
931 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 + {
932 1582 try {
933 1583 $policyHandler = $this->app->parsePolicyHandler(
934 1584 $this->getPolicyHandler($this->policyHandler)
935 1585 );
936 -
1586 +
937 1587 if ($policyHandler) {
938 1588 $this->permissionHandler = $policyHandler;
939 1589
940 - // Adjust policy handler if the method was explicitly given
941 - if (is_string($this->policyHandler)) {
942 - if (is_array($policyHandler) && isset($policyHandler[1])) {
943 - if ($pieces = explode('@', $this->policyHandler)) {
944 - if (isset($pieces[1])) {
945 - $this->permissionHandler[1] = $pieces[1];
946 - }
947 - }
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];
948 1595 }
949 1596 }
950 1597
951 1598 if (!is_callable($this->permissionHandler)) {
@@ -953,52 +1600,43 @@
953 1600 }
954 1601 }
955 1602
956 1603 } catch (Exception $e) {
957 - $pHandler = $this->policyHandler;
958 - if (is_array($this->permissionHandler) && $this->permissionHandler) {
959 - $pHandler = is_object($this->permissionHandler[0]) ?
960 - get_class($this->permissionHandler[0]) . ':' . $this->permissionHandler[1] :
961 - $this->permissionHandler[0] . ':' . $this->permissionHandler[1];
962 - }
963 -
964 - throw new BadMethodCallException(
965 - "The permission callback {$pHandler} is invalid or not callable."
966 - );
1604 + throw $this->invalidPolicyHandlerException();
967 1605 }
968 1606
969 - if (is_array($policyHandler)) {
1607 + // Convert object controller to class string for endpoint metadata
1608 + if (is_array($policyHandler) && is_object($policyHandler[0])) {
970 1609 $policyHandler[0] = get_class($policyHandler[0]);
971 1610 }
972 1611
973 - $this->actionInfo = [
974 - 'handler' => is_object($handler) ? $action : $handler,
975 - 'controller' => $controller,
976 - 'method' => is_array($action) ? $action[1] : null,
977 - 'path' => $this->uri,
978 - 'http_method' => $request->get_method(),
979 - 'full_uri' => $request->get_route(),
980 - 'permission_callback' => $policyHandler,
981 - 'compiled_url' => $this->compiled
982 - ];
1612 + return $policyHandler;
1613 + }
983 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;
984 1623
985 - $this->action = $handler;
986 -
987 - if ($routeParameters = $this->getParameter()) {
988 - $this->substitutedParameters = $this->SubstituteParameters(
989 - $routeParameters
990 - );
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];
991 1628 }
992 1629
993 -
994 - return $this->action;
1630 + return new BadMethodCallException(
1631 + "The permission callback {$pHandler} is invalid or not callable."
1632 + );
995 1633 }
996 1634
997 1635 /**
998 - * Get route one or more parameters
999 - * @param string $key
1000 - *
1636 + * Get one or more route parameters
1637 + * @param string $key
1638 + *
1001 1639 * @return mixed
1002 1640 */
1003 1641 public function getParameter($key = null)
1004 1642 {
@@ -1009,10 +1647,40 @@
1009 1647 return $key ? $this->parameters[$key] : $this->parameters;
1010 1648 }
1011 1649
1012 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 + /**
1013 1681 * Dynamically access a route parameter.
1014 - *
1682 + *
1015 1683 * @param string $key
1016 1684 * @return mixed
1017 1685 */
1018 1686 public function __get($key)