mirror of
https://github.com/archtechx/tenancy.git
synced 2026-06-21 09:04:04 +00:00
Merge branch 'master' into add-log-bootstrapper
This commit is contained in:
commit
55ee7d87b5
27 changed files with 730 additions and 123 deletions
|
|
@ -16,7 +16,7 @@ use Stancl\Tenancy\Resolvers\PathTenantResolver;
|
|||
/**
|
||||
* Makes the app use TenancyUrlGenerator (instead of Illuminate\Routing\UrlGenerator) which:
|
||||
* - prefixes route names with the tenant route name prefix (PathTenantResolver::tenantRouteNamePrefix() by default)
|
||||
* - passes the tenant parameter to the link generated by route() and temporarySignedRoute() (PathTenantResolver::tenantParameterName() by default).
|
||||
* - passes the tenant parameter (PathTenantResolver::tenantParameterName() by default) to the link generated by the affected methods like route() and temporarySignedRoute().
|
||||
*
|
||||
* Used with path and query string identification.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ class Run extends Command
|
|||
protected $description = 'Run a command for tenant(s)';
|
||||
|
||||
protected $signature = 'tenants:run {commandname : The artisan command.}
|
||||
{--tenants=* : The tenant(s) to run the command for. Default: all}';
|
||||
{--tenants=* : The tenant(s) to run the command for. Default: all}
|
||||
{--skip-tenants=* : The tenant(s) to skip}';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
|
|
|
|||
|
|
@ -10,15 +10,16 @@ use Stancl\Tenancy\Database\Concerns\PendingScope;
|
|||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
/**
|
||||
* Adds 'tenants' and 'with-pending' options.
|
||||
* Adds 'tenants', 'skip-tenants', and 'with-pending' options.
|
||||
*/
|
||||
trait HasTenantOptions
|
||||
{
|
||||
protected function getOptions()
|
||||
{
|
||||
return array_merge([
|
||||
new InputOption('tenants', null, InputOption::VALUE_IS_ARRAY|InputOption::VALUE_OPTIONAL, 'The tenants to run this command for. Leave empty for all tenants', null),
|
||||
new InputOption('with-pending', null, InputOption::VALUE_NONE, 'Include pending tenants in query'), // todo@pending should we also offer without-pending? if we add this, mention in docs
|
||||
new InputOption('tenants', null, InputOption::VALUE_IS_ARRAY|InputOption::VALUE_OPTIONAL, 'The tenants to run this command for. Leave empty for all tenants', null),
|
||||
new InputOption('skip-tenants', null, InputOption::VALUE_IS_ARRAY|InputOption::VALUE_OPTIONAL, 'The tenants to skip when running this command', null),
|
||||
new InputOption('with-pending', null, InputOption::VALUE_OPTIONAL, 'Include pending tenants in query if true/1, exclude if false/0. Defaults to the tenancy.pending.include_in_queries config value.'),
|
||||
], parent::getOptions());
|
||||
}
|
||||
|
||||
|
|
@ -42,8 +43,15 @@ trait HasTenantOptions
|
|||
->when($this->option('tenants'), function ($query) {
|
||||
$query->whereIn(tenancy()->model()->getTenantKeyName(), $this->option('tenants'));
|
||||
})
|
||||
->when($this->option('skip-tenants'), function ($query) {
|
||||
$query->whereNotIn(tenancy()->model()->getTenantKeyName(), $this->option('skip-tenants'));
|
||||
})
|
||||
->when(tenancy()->model()::hasGlobalScope(PendingScope::class), function ($query) {
|
||||
$query->withPending(config('tenancy.pending.include_in_queries') ?: $this->option('with-pending'));
|
||||
$includePending = $this->input->hasParameterOption('--with-pending')
|
||||
? filter_var($this->option('with-pending') ?? true, FILTER_VALIDATE_BOOLEAN)
|
||||
: config('tenancy.pending.include_in_queries');
|
||||
|
||||
$query->withPending($includePending);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,18 @@ trait HasPending
|
|||
public static function bootHasPending(): void
|
||||
{
|
||||
static::addGlobalScope(new PendingScope());
|
||||
|
||||
static::creating(function (self $tenant): void {
|
||||
if ($tenant->pending()) {
|
||||
event(new CreatingPendingTenant($tenant));
|
||||
}
|
||||
});
|
||||
|
||||
static::created(function (self $tenant): void {
|
||||
if ($tenant->pending()) {
|
||||
event(new PendingTenantCreated($tenant));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Initialize the trait. */
|
||||
|
|
@ -49,22 +61,11 @@ trait HasPending
|
|||
*/
|
||||
public static function createPending(array $attributes = []): Model&Tenant
|
||||
{
|
||||
$tenant = null;
|
||||
|
||||
try {
|
||||
$tenant = static::create(array_merge(static::getPendingAttributes($attributes), $attributes));
|
||||
event(new CreatingPendingTenant($tenant));
|
||||
} finally {
|
||||
// Update the pending_since value only after the tenant is created so it's
|
||||
// not marked as pending until after migrations, seeders, etc are run.
|
||||
$tenant?->update([
|
||||
'pending_since' => now()->timestamp,
|
||||
]);
|
||||
}
|
||||
|
||||
event(new PendingTenantCreated($tenant));
|
||||
|
||||
return $tenant;
|
||||
return static::create(array_merge(
|
||||
static::getPendingAttributes($attributes),
|
||||
$attributes,
|
||||
['pending_since' => now()->timestamp],
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ class PendingScope implements Scope
|
|||
/**
|
||||
* Apply the scope to a given Eloquent query builder.
|
||||
*
|
||||
* @param Builder<Model> $builder
|
||||
* @param Builder<covariant Model> $builder
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
|
@ -58,8 +58,10 @@ class PendingScope implements Scope
|
|||
{
|
||||
$builder->macro('withoutPending', function (Builder $builder) {
|
||||
$builder->withoutGlobalScope(static::class)
|
||||
->whereNull($builder->getModel()->getColumnForQuery('pending_since'))
|
||||
->orWhereNull($builder->getModel()->getDataColumn());
|
||||
->where(function (Builder $query) {
|
||||
$query->whereNull($query->getModel()->getColumnForQuery('pending_since'))
|
||||
->orWhereNull($query->getModel()->getDataColumn());
|
||||
});
|
||||
|
||||
return $builder;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use Illuminate\Database\Eloquent\Scope;
|
|||
class ParentModelScope implements Scope
|
||||
{
|
||||
/**
|
||||
* @param Builder<Model> $builder
|
||||
* @param Builder<covariant Model> $builder
|
||||
*/
|
||||
public function apply(Builder $builder, Model $model): void
|
||||
{
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ use Stancl\Tenancy\Tenancy;
|
|||
class TenantScope implements Scope
|
||||
{
|
||||
/**
|
||||
* @param Builder<Model> $builder
|
||||
* @param Builder<covariant Model> $builder
|
||||
*/
|
||||
public function apply(Builder $builder, Model $model)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Stancl\Tenancy\Features;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
|
@ -61,9 +62,9 @@ class UserImpersonation implements Feature
|
|||
|
||||
Auth::guard($token->auth_guard)->loginUsingId($token->user_id, $token->remember);
|
||||
|
||||
$token->delete();
|
||||
session()->put('tenancy_impersonation_guard', $token->auth_guard);
|
||||
|
||||
session()->put('tenancy_impersonating', true);
|
||||
$token->delete();
|
||||
|
||||
return redirect($token->redirect_url);
|
||||
}
|
||||
|
|
@ -76,16 +77,30 @@ class UserImpersonation implements Feature
|
|||
|
||||
public static function isImpersonating(): bool
|
||||
{
|
||||
return session()->has('tenancy_impersonating');
|
||||
return session()->has('tenancy_impersonation_guard');
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout from the current domain and forget impersonation session.
|
||||
* Stop user impersonation by forgetting the impersonation session.
|
||||
*
|
||||
* When $logout is true, the user will also be logged out
|
||||
* from the impersonation guard stored in the session.
|
||||
*
|
||||
* Throws an exception if impersonation is not active
|
||||
* (= the impersonation guard is not in the session).
|
||||
*/
|
||||
public static function stopImpersonating(): void
|
||||
public static function stopImpersonating(bool $logout = true): void
|
||||
{
|
||||
auth()->logout();
|
||||
if (! static::isImpersonating()) {
|
||||
throw new Exception('Not currently impersonating any user.');
|
||||
}
|
||||
|
||||
session()->forget('tenancy_impersonating');
|
||||
if ($logout) {
|
||||
$guard = session()->get('tenancy_impersonation_guard');
|
||||
|
||||
auth($guard)->logout();
|
||||
}
|
||||
|
||||
session()->forget('tenancy_impersonation_guard');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,12 +22,34 @@ class DeleteDatabase implements ShouldQueue
|
|||
protected TenantWithDatabase&Model $tenant,
|
||||
) {}
|
||||
|
||||
/** Skip database deletion if the create_database internal attribute is false. */
|
||||
public static bool $skipWhenCreateDatabaseIsFalse = true;
|
||||
|
||||
/** Ignore exceptions thrown during database deletion and continue execution. */
|
||||
public static bool $ignoreFailures = false;
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
if (static::$skipWhenCreateDatabaseIsFalse && $this->tenant->getInternal('create_database') === false) {
|
||||
// If database creation was skipped, we presume deletion should also be skipped.
|
||||
// To avoid this skip, either unset the `create_database` attribute (or make it true), or
|
||||
// set the $skipWhenCreateDatabaseIsFalse static property to false.
|
||||
return;
|
||||
}
|
||||
|
||||
event(new DeletingDatabase($this->tenant));
|
||||
|
||||
$this->tenant->database()->manager()->deleteDatabase($this->tenant);
|
||||
$deleted = false;
|
||||
|
||||
event(new DatabaseDeleted($this->tenant));
|
||||
try {
|
||||
$this->tenant->database()->manager()->deleteDatabase($this->tenant);
|
||||
$deleted = true;
|
||||
} catch (\Throwable $e) {
|
||||
if (! static::$ignoreFailures) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
if ($deleted) event(new DatabaseDeleted($this->tenant));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
43
src/Jobs/DeleteTenantStorage.php
Normal file
43
src/Jobs/DeleteTenantStorage.php
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Stancl\Tenancy\Jobs;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Stancl\Tenancy\Contracts\Tenant;
|
||||
|
||||
class DeleteTenantStorage implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public function __construct(
|
||||
public Tenant $tenant,
|
||||
) {}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
if (config('tenancy.filesystem.suffix_storage_path') === false) {
|
||||
// Skip storage deletion if path suffixing is disabled
|
||||
return;
|
||||
}
|
||||
|
||||
$centralStoragePath = tenancy()->central(fn () => storage_path());
|
||||
$tenantStoragePath = tenancy()->run($this->tenant, fn () => storage_path());
|
||||
|
||||
if ($tenantStoragePath === $centralStoragePath) {
|
||||
// Check again to ensure the tenant storage path is distinct from the central storage path
|
||||
// to avoid any accidental central storage path deletion
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_dir($tenantStoragePath)) {
|
||||
File::deleteDirectory($tenantStoragePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,16 @@ class MigrateDatabase implements ShouldQueue
|
|||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* Should pending tenants be included while migrating,
|
||||
* regardless of the tenancy.pending.include_in_queries config value.
|
||||
*
|
||||
* If false, pending tenants will be specifically excluded.
|
||||
*
|
||||
* If null, default to tenancy.pending.include_in_queries config.
|
||||
*/
|
||||
public static ?bool $includePending = true;
|
||||
|
||||
public function __construct(
|
||||
protected TenantWithDatabase&Model $tenant,
|
||||
) {}
|
||||
|
|
@ -25,6 +35,7 @@ class MigrateDatabase implements ShouldQueue
|
|||
{
|
||||
Artisan::call('tenants:migrate', [
|
||||
'--tenants' => [$this->tenant->getTenantKey()],
|
||||
'--with-pending' => static::$includePending ?? config('tenancy.pending.include_in_queries'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,16 @@ class SeedDatabase implements ShouldQueue
|
|||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* Should pending tenants be included while seeding,
|
||||
* regardless of the tenancy.pending.include_in_queries config value.
|
||||
*
|
||||
* If false, pending tenants will be specifically excluded.
|
||||
*
|
||||
* If null, default to tenancy.pending.include_in_queries config.
|
||||
*/
|
||||
public static ?bool $includePending = true;
|
||||
|
||||
public function __construct(
|
||||
protected TenantWithDatabase&Model $tenant,
|
||||
) {}
|
||||
|
|
@ -25,6 +35,7 @@ class SeedDatabase implements ShouldQueue
|
|||
{
|
||||
Artisan::call('tenants:seed', [
|
||||
'--tenants' => [$this->tenant->getTenantKey()],
|
||||
'--with-pending' => static::$includePending ?? config('tenancy.pending.include_in_queries'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,11 +7,7 @@ namespace Stancl\Tenancy\Listeners;
|
|||
use Stancl\Tenancy\Events\Contracts\TenantEvent;
|
||||
|
||||
/**
|
||||
* Can be used to manually create framework directories in the tenant storage when storage_path() is scoped.
|
||||
*
|
||||
* Useful when using real-time facades which use the framework/cache directory.
|
||||
*
|
||||
* Generally not needed anymore as the directory is also created by the FilesystemTenancyBootstrapper.
|
||||
* @deprecated FilesystemTenancyBootstrapper creates the path automatically when suffix_storage_path is enabled.
|
||||
*/
|
||||
class CreateTenantStorage
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,14 +7,29 @@ namespace Stancl\Tenancy\Listeners;
|
|||
use Illuminate\Support\Facades\File;
|
||||
use Stancl\Tenancy\Events\Contracts\TenantEvent;
|
||||
|
||||
/**
|
||||
* @deprecated Use Stancl\Tenancy\Jobs\DeleteTenantStorage in a job pipeline instead.
|
||||
*/
|
||||
class DeleteTenantStorage
|
||||
{
|
||||
public function handle(TenantEvent $event): void
|
||||
{
|
||||
$path = tenancy()->run($event->tenant, fn () => storage_path());
|
||||
if (config('tenancy.filesystem.suffix_storage_path') === false) {
|
||||
// Skip storage deletion if path suffixing is disabled
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_dir($path)) {
|
||||
File::deleteDirectory($path);
|
||||
$centralStoragePath = tenancy()->central(fn () => storage_path());
|
||||
$tenantStoragePath = tenancy()->run($event->tenant, fn () => storage_path());
|
||||
|
||||
if ($tenantStoragePath === $centralStoragePath) {
|
||||
// Check again to ensure the tenant storage path is distinct from the central storage path
|
||||
// to avoid any accidental central storage path deletion
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_dir($tenantStoragePath)) {
|
||||
File::deleteDirectory($tenantStoragePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,10 +31,12 @@ class PreventAccessFromUnwantedDomains
|
|||
{
|
||||
$route = tenancy()->getRoute($request);
|
||||
|
||||
if ($this->shouldBeSkipped($route) || tenancy()->routeIsUniversal($route)) {
|
||||
if ($this->shouldBeSkipped($route)) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
// If the route is universal, neither of these checks will pass and the logic will
|
||||
// fall through to the $next($request) call at the end.
|
||||
if ($this->accessingTenantRouteFromCentralDomain($request, $route) || $this->accessingCentralRouteFromTenantDomain($request, $route)) {
|
||||
$abortRequest = static::$abortRequest ?? function () {
|
||||
abort(404);
|
||||
|
|
|
|||
|
|
@ -22,16 +22,18 @@ use Stancl\Tenancy\Resolvers\RequestDataTenantResolver;
|
|||
* - Automatically passing ['tenant' => ...] to each route() call -- if TenancyUrlGenerator::$passTenantParameterToRoutes is enabled
|
||||
* This is a more universal solution since it supports both path identification and query parameter identification.
|
||||
*
|
||||
* - Prepends route names passed to route() and URL::temporarySignedRoute()
|
||||
* with `tenant.` (or the configured prefix) if $prefixRouteNames is enabled.
|
||||
* - Prepends route names with the tenant route name prefix ('tenant.' by default,
|
||||
* configurable at tenant_route_name_prefix under PathTenantResolver) if $prefixRouteNames is enabled.
|
||||
* This is primarily useful when using route cloning with path identification.
|
||||
*
|
||||
* To bypass this behavior on any single route() call, pass the $bypassParameter as true (['central' => true] by default).
|
||||
* Affected methods: route(), toRoute(), temporarySignedRoute(), signedRoute() (the last two via the route() override).
|
||||
*
|
||||
* To bypass this behavior on any single affected method call, pass the $bypassParameter as true (['central' => true] by default).
|
||||
*/
|
||||
class TenancyUrlGenerator extends UrlGenerator
|
||||
{
|
||||
/**
|
||||
* Parameter which works as a flag for bypassing the behavior modification of route() and temporarySignedRoute().
|
||||
* Parameter which works as a flag for bypassing the behavior modification of the affected methods.
|
||||
*
|
||||
* For example, in tenant context:
|
||||
* Route::get('/', ...)->name('home');
|
||||
|
|
@ -44,12 +46,12 @@ class TenancyUrlGenerator extends UrlGenerator
|
|||
* Note: UrlGeneratorBootstrapper::$addTenantParameterToDefaults is not affected by this, though
|
||||
* it doesn't matter since it doesn't pass any extra parameters when not needed.
|
||||
*
|
||||
* @see UrlGeneratorBootstrapper
|
||||
* @see Stancl\Tenancy\Bootstrappers\UrlGeneratorBootstrapper
|
||||
*/
|
||||
public static string $bypassParameter = 'central';
|
||||
|
||||
/**
|
||||
* Should route names passed to route() or temporarySignedRoute()
|
||||
* Should route names passed to the affected methods
|
||||
* get prefixed with the tenant route name prefix.
|
||||
*
|
||||
* This is useful when using e.g. path identification with third-party packages
|
||||
|
|
@ -59,12 +61,12 @@ class TenancyUrlGenerator extends UrlGenerator
|
|||
public static bool $prefixRouteNames = false;
|
||||
|
||||
/**
|
||||
* Should the tenant parameter be passed to route() or temporarySignedRoute() calls.
|
||||
* Should the tenant parameter be passed to the affected methods.
|
||||
*
|
||||
* This is useful with path or query parameter identification. The former can be handled
|
||||
* more elegantly using UrlGeneratorBootstrapper::$addTenantParameterToDefaults.
|
||||
*
|
||||
* @see UrlGeneratorBootstrapper
|
||||
* @see Stancl\Tenancy\Bootstrappers\UrlGeneratorBootstrapper
|
||||
*/
|
||||
public static bool $passTenantParameterToRoutes = false;
|
||||
|
||||
|
|
@ -105,8 +107,18 @@ class TenancyUrlGenerator extends UrlGenerator
|
|||
public static bool $passQueryParameter = true;
|
||||
|
||||
/**
|
||||
* Override the route() method so that the route name gets prefixed
|
||||
* and the tenant parameter gets added when in tenant context.
|
||||
* Override the route() method to prefix the route name before $this->routes->getByName($name) is called
|
||||
* in the parent route() call.
|
||||
*
|
||||
* This is necessary because $this->routes->getByName($name) is called to retrieve the route
|
||||
* before passing it to toRoute(). If only the prefixed route (e.g. 'tenant.foo') is registered
|
||||
* and the original ('foo') isn't, route() would throw a RouteNotFoundException.
|
||||
* So route() has to be overridden to prefix the passed route name, even though toRoute() is overridden already.
|
||||
*
|
||||
* Only the name is taken from prepareRouteInputs() here — parameter handling
|
||||
* (adding tenant parameter, removing bypass parameter) is delegated to toRoute().
|
||||
*
|
||||
* Affects temporarySignedRoute() and signedRoute() as well since they call route() under the hood.
|
||||
*/
|
||||
public function route($name, $parameters = [], $absolute = true)
|
||||
{
|
||||
|
|
@ -114,32 +126,28 @@ class TenancyUrlGenerator extends UrlGenerator
|
|||
throw new InvalidArgumentException('Attribute [name] expects a string backed enum.');
|
||||
}
|
||||
|
||||
[$name, $parameters] = $this->prepareRouteInputs($name, Arr::wrap($parameters)); // @phpstan-ignore argument.type
|
||||
[$name] = $this->prepareRouteInputs(Arr::wrap($parameters), $name); // @phpstan-ignore argument.type
|
||||
|
||||
return parent::route($name, $parameters, $absolute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the temporarySignedRoute() method so that the route name gets prefixed
|
||||
* and the tenant parameter gets added when in tenant context.
|
||||
* Override the toRoute() to prefix the route name
|
||||
* and add the tenant parameter when in tenant context.
|
||||
*
|
||||
* Also affects route(). Even though route() is overridden separately, it delegates parameter handling to toRoute().
|
||||
*/
|
||||
public function temporarySignedRoute($name, $expiration, $parameters = [], $absolute = true)
|
||||
public function toRoute($route, $parameters, $absolute)
|
||||
{
|
||||
if ($name instanceof BackedEnum && ! is_string($name = $name->value)) {
|
||||
throw new InvalidArgumentException('Attribute [name] expects a string backed enum.');
|
||||
$name = $route->getName();
|
||||
|
||||
[$prefixedName, $parameters] = $this->prepareRouteInputs(Arr::wrap($parameters), $name);
|
||||
|
||||
if ($name && $prefixedName !== $name && $tenantRoute = $this->routes->getByName($prefixedName)) {
|
||||
$route = $tenantRoute;
|
||||
}
|
||||
|
||||
$wrappedParameters = Arr::wrap($parameters);
|
||||
|
||||
[$name, $parameters] = $this->prepareRouteInputs($name, $wrappedParameters); // @phpstan-ignore argument.type
|
||||
|
||||
if (isset($wrappedParameters[static::$bypassParameter])) {
|
||||
// If the bypass parameter was passed, we need to add it back to the parameters after prepareRouteInputs() removes it,
|
||||
// so that the underlying route() call in parent::temporarySignedRoute() can bypass the behavior modification as well.
|
||||
$parameters[static::$bypassParameter] = $wrappedParameters[static::$bypassParameter];
|
||||
}
|
||||
|
||||
return parent::temporarySignedRoute($name, $expiration, $parameters, $absolute);
|
||||
return parent::toRoute($route, $parameters, $absolute);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -155,16 +163,19 @@ class TenancyUrlGenerator extends UrlGenerator
|
|||
}
|
||||
|
||||
/**
|
||||
* Takes a route name and an array of parameters to return the prefixed route name
|
||||
* Takes an array of parameters and a route name to return the prefixed route name
|
||||
* and the route parameters with the tenant parameter added.
|
||||
*
|
||||
* To skip these modifications, pass the bypass parameter in route parameters.
|
||||
* Before returning the modified route inputs, the bypass parameter is removed from the parameters.
|
||||
*/
|
||||
protected function prepareRouteInputs(string $name, array $parameters): array
|
||||
protected function prepareRouteInputs(array $parameters, string|null $name): array
|
||||
{
|
||||
if (! $this->routeBehaviorModificationBypassed($parameters)) {
|
||||
$name = $this->routeNameOverride($name) ?? $this->prefixRouteName($name);
|
||||
if (! is_null($name)) {
|
||||
$name = $this->routeNameOverride($name) ?? $this->prefixRouteName($name);
|
||||
}
|
||||
|
||||
$parameters = $this->addTenantParameter($parameters);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ namespace Stancl\Tenancy\Resolvers;
|
|||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use Stancl\Tenancy\Contracts\Domain;
|
||||
use Stancl\Tenancy\Contracts\SingleDomainTenant;
|
||||
|
|
@ -58,7 +59,19 @@ class DomainTenantResolver extends Contracts\CachedTenantResolver
|
|||
|
||||
public static function isSubdomain(string $domain): bool
|
||||
{
|
||||
return Str::endsWith($domain, config('tenancy.identification.central_domains'));
|
||||
$centralDomains = Arr::wrap(config('tenancy.identification.central_domains'));
|
||||
|
||||
if (in_array($domain, $centralDomains, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($centralDomains as $centralDomain) {
|
||||
if (Str::endsWith($domain, '.' . $centralDomain)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function resolved(Tenant $tenant, mixed ...$args): void
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue