mirror of
https://github.com/archtechx/tenancy.git
synced 2025-12-12 14:14:04 +00:00
184 lines
6.9 KiB
PHP
184 lines
6.9 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace Stancl\Tenancy\Concerns;
|
||
|
||
use Closure;
|
||
use Illuminate\Contracts\Http\Kernel;
|
||
use Illuminate\Http\Request;
|
||
use Illuminate\Routing\Controllers\HasMiddleware;
|
||
use Illuminate\Routing\Route;
|
||
use Illuminate\Routing\Router;
|
||
use Illuminate\Support\Arr;
|
||
use Illuminate\Support\Facades\Route as RouteFacade;
|
||
use Stancl\Tenancy\Enums\RouteMode;
|
||
|
||
// todo@refactor move this logic to some dedicated static class?
|
||
|
||
/**
|
||
* @mixin \Stancl\Tenancy\Tenancy
|
||
*/
|
||
trait DealsWithRouteContexts
|
||
{
|
||
/**
|
||
* Get route's middleware context (tenant, central or universal).
|
||
* The context is determined by the route's middleware.
|
||
*
|
||
* If the route has the 'universal' middleware, the context is universal,
|
||
* and the route is accessible from both contexts.
|
||
* The universal flag has the highest priority.
|
||
*
|
||
* If the route has the 'central' middleware, the context is central.
|
||
* If the route has the 'tenant' middleware, or any tenancy identification middleware, the context is tenant.
|
||
*
|
||
* If the route doesn't have any of the mentioned middleware,
|
||
* the context is determined by the `tenancy.default_route_mode` config.
|
||
*/
|
||
public static function getRouteMode(Route $route): RouteMode
|
||
{
|
||
// If the route is universal, you have to determine its actual context using
|
||
// the identification middleware's determineUniversalRouteContextFromRequest
|
||
if (static::routeIsUniversal($route)) {
|
||
return RouteMode::UNIVERSAL;
|
||
}
|
||
|
||
if (static::routeHasMiddleware($route, 'central')) {
|
||
return RouteMode::CENTRAL;
|
||
}
|
||
|
||
// If the route is flagged as tenant or it has identification middleware, consider it tenant
|
||
if (static::routeHasMiddleware($route, 'tenant') || static::routeHasIdentificationMiddleware($route)) {
|
||
return RouteMode::TENANT;
|
||
}
|
||
|
||
return config('tenancy.default_route_mode');
|
||
}
|
||
|
||
public static function routeIsUniversal(Route $route): bool
|
||
{
|
||
$routeFlaggedAsUniversal = static::routeHasMiddleware($route, 'universal');
|
||
$universalFlagUsedInGlobalStack = app(Kernel::class)->hasMiddleware('universal');
|
||
$defaultRouteModeIsUniversal = config('tenancy.default_route_mode') === RouteMode::UNIVERSAL;
|
||
|
||
return $routeFlaggedAsUniversal || $universalFlagUsedInGlobalStack || $defaultRouteModeIsUniversal;
|
||
}
|
||
|
||
/**
|
||
* Get middleware of the passed route (without controller middleware and middleware from the global stack).
|
||
*
|
||
* First, get the surface-level route middleware (`$route->middleware()`).
|
||
* The surface-level middleware could contain middleware groups,
|
||
* and to accurately get all the specific middleware, we need to unpack them.
|
||
* The unpacked middleware groups could also have middleware groups inside them,
|
||
* so we further unpack these, three times.
|
||
*
|
||
* For example, a route has a 'surface' middleware group.
|
||
* The 'surface' group has a 'first-level' group, and that group has a 'second-level' group (three middleware group layers).
|
||
* The 'second-level' group has a specific middleware (e.g. SomeMiddleware).
|
||
* Using the getRouteMiddleware method on that route will get you all the middleware the route has, including SomeMiddleware.
|
||
*
|
||
* Note that the unpacking doesn't go further than three layers – if 'second-level' had 'third-level' that would have ThirdLevelMiddleware,
|
||
* the middleware returned by this method won't include ThirdLevelMiddleware because the 'third-level' group won't get unpacked.
|
||
*/
|
||
public static function getRouteMiddleware(Route $route): array
|
||
{
|
||
$routeMiddleware = $route->middleware();
|
||
$controllerClass = $route->getControllerClass();
|
||
|
||
if ($controllerClass && is_a($controllerClass, HasMiddleware::class, true)) {
|
||
$routeMiddleware = array_merge($routeMiddleware, $route->controllerMiddleware());
|
||
}
|
||
|
||
$middlewareGroups = RouteFacade::getMiddlewareGroups();
|
||
$unpackGroupMiddleware = function (array $middleware) use ($middlewareGroups) {
|
||
$innerMiddleware = [];
|
||
|
||
foreach ($middleware as $inner) {
|
||
if (! $inner instanceof Closure && isset($middlewareGroups[$inner])) {
|
||
$innerMiddleware = Arr::wrap($middlewareGroups[$inner]);
|
||
}
|
||
}
|
||
|
||
return $innerMiddleware;
|
||
};
|
||
|
||
return array_unique(array_merge(
|
||
$routeMiddleware,
|
||
$firstLevelUnpackedGroupMiddleware = $unpackGroupMiddleware($routeMiddleware),
|
||
$secondLevelUnpackedGroupMiddleware = $unpackGroupMiddleware($firstLevelUnpackedGroupMiddleware),
|
||
$unpackGroupMiddleware($secondLevelUnpackedGroupMiddleware)
|
||
));
|
||
}
|
||
|
||
/**
|
||
* Checks whether any of the passed middleware are present in the route's middleware stack.
|
||
*/
|
||
public static function routeHasMiddleware(Route $route, string|array $middlewares): bool
|
||
{
|
||
$routeMiddleware = static::getRouteMiddleware($route);
|
||
|
||
foreach (Arr::wrap($middlewares) as $middleware) {
|
||
if (in_array($middleware, $routeMiddleware)) {
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* Check if a route has identification middleware.
|
||
*/
|
||
public static function routeHasIdentificationMiddleware(Route $route): bool
|
||
{
|
||
return static::routeHasMiddleware($route, static::middleware());
|
||
}
|
||
|
||
/**
|
||
* Check if route uses kernel identification (identification middleware is in the global stack and the route doesn't have route-level identification middleware).
|
||
*/
|
||
public static function routeUsesKernelIdentification(Route $route): bool
|
||
{
|
||
return ! static::routeHasIdentificationMiddleware($route) &&
|
||
tenancy()->globalStackHasMiddleware(static::middleware());
|
||
}
|
||
|
||
/**
|
||
* Checks whether any of the passed middleware are present in the global middleware stack.
|
||
*
|
||
* @param class-string|array $identificationMiddleware
|
||
*/
|
||
public static function globalStackHasMiddleware(string|array $identificationMiddleware = []): bool
|
||
{
|
||
/** @var Kernel $kernel */
|
||
$kernel = app(Kernel::class);
|
||
|
||
foreach (Arr::wrap($identificationMiddleware) as $middleware) {
|
||
if ($kernel->hasMiddleware($middleware)) {
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* Obtain route from the passed request.
|
||
* If the route isn't directly available on the request,
|
||
* find the route that matches the passed request.
|
||
*/
|
||
public static function getRoute(Request $request): Route
|
||
{
|
||
/** @var ?Route $route */
|
||
$route = $request->route();
|
||
|
||
if (! $route) {
|
||
/** @var Router $router */
|
||
$router = app(Router::class);
|
||
$route = $router->getRoutes()->match($request);
|
||
}
|
||
|
||
return $route;
|
||
}
|
||
}
|