From f604ce7e5487be5532d9ccf142e6474e9f1005df Mon Sep 17 00:00:00 2001 From: Stephen Williamson Date: Sat, 23 May 2026 09:14:57 -0400 Subject: [PATCH] Guard help article tenancy schema --- database/factories/HelpArticleFactory.php | 20 ++++----- src/Models/HelpArticle.php | 36 ++++++++-------- .../Frontend/HelpArticleResource.php | 12 +++--- src/Resources/Guest/HelpArticleResource.php | 18 ++++---- src/Resources/HelpArticleResource.php | 33 +++++++-------- src/Support/Tenancy.php | 41 +++++++++++++++++++ tests/Unit/TenancyTest.php | 18 ++++++++ 7 files changed, 120 insertions(+), 58 deletions(-) create mode 100644 src/Support/Tenancy.php create mode 100644 tests/Unit/TenancyTest.php diff --git a/database/factories/HelpArticleFactory.php b/database/factories/HelpArticleFactory.php index 82a811a1..3dde1e03 100644 --- a/database/factories/HelpArticleFactory.php +++ b/database/factories/HelpArticleFactory.php @@ -4,9 +4,10 @@ use Illuminate\Database\Eloquent\Factories\Factory; use Tapp\FilamentHelp\Models\HelpArticle; +use Tapp\FilamentHelp\Support\Tenancy; /** - * @extends \Illuminate\Database\Eloquent\Factories\Factory<\Tapp\FilamentHelp\Models\HelpArticle> + * @extends Factory */ class HelpArticleFactory extends Factory { @@ -19,13 +20,12 @@ public function definition(): array 'is_public' => $this->faker->boolean(70), // 70% chance of being public 'content' => $this->faker->paragraphs(3, true), ]; - + // Add tenant column if tenancy is enabled - if (config('filament-help.tenancy.enabled', false)) { - $tenantColumn = config('filament-help.tenancy.column') ?? 'team_id'; - $definition[$tenantColumn] = null; // Will be set explicitly or by model boot logic + if (Tenancy::hasTenantColumn()) { + $definition[Tenancy::column()] = null; // Will be set explicitly or by model boot logic } - + return $definition; } @@ -52,14 +52,12 @@ public function hidden(): static public function forTeam($team): static { - if (! config('filament-help.tenancy.enabled', false)) { + if (! Tenancy::hasTenantColumn()) { return $this; } - - $tenantColumn = config('filament-help.tenancy.column') ?? 'team_id'; - + return $this->state(fn (array $attributes) => [ - $tenantColumn => is_object($team) ? $team->id : $team, + Tenancy::column() => is_object($team) ? $team->id : $team, ]); } } diff --git a/src/Models/HelpArticle.php b/src/Models/HelpArticle.php index 377f2449..f6af5438 100644 --- a/src/Models/HelpArticle.php +++ b/src/Models/HelpArticle.php @@ -2,8 +2,10 @@ namespace Tapp\FilamentHelp\Models; +use Filament\Facades\Filament; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Tapp\FilamentHelp\Support\Tenancy; class HelpArticle extends Model { @@ -21,11 +23,10 @@ class HelpArticle extends Model public function __construct(array $attributes = []) { parent::__construct($attributes); - + // Dynamically add tenant column to fillable if tenancy is enabled - if (config('filament-help.tenancy.enabled', false)) { - $tenantColumn = config('filament-help.tenancy.column') ?? 'team_id'; - $this->fillable[] = $tenantColumn; + if (Tenancy::isEnabled()) { + $this->fillable[] = Tenancy::column(); } } @@ -49,33 +50,30 @@ public function scopeVisible($query) */ public function scopeForTenant($query, $tenant) { - if (! config('filament-help.tenancy.enabled', false)) { + if (! Tenancy::hasTenantColumn(static::class)) { return $query; } - - $tenantColumn = config('filament-help.tenancy.column') ?? 'team_id'; - - return $query->where($tenantColumn, $tenant->id); + + return $query->where(Tenancy::column(), $tenant->id); } /** * Define your tenant relationship here. - * + * * Example for Team: - * + * * public function team(): BelongsTo * { * return $this->belongsTo(\App\Models\Team::class); * } - * + * * Or for Organization: - * + * * public function organization(): BelongsTo * { * return $this->belongsTo(\App\Models\Organization::class); * } */ - protected static function boot() { parent::boot(); @@ -86,11 +84,11 @@ protected static function boot() } // Auto-assign team_id from current Filament tenant if available and enabled - if (config('filament-help.tenancy.enabled', false) && config('filament-help.tenancy.auto_assign', true)) { - $tenantColumn = config('filament-help.tenancy.column') ?? 'team_id'; - - if (empty($article->{$tenantColumn}) && class_exists(\Filament\Facades\Filament::class)) { - $tenant = \Filament\Facades\Filament::getTenant(); + if (Tenancy::hasTenantColumn(static::class) && config('filament-help.tenancy.auto_assign', true)) { + $tenantColumn = Tenancy::column(); + + if (empty($article->{$tenantColumn}) && class_exists(Filament::class)) { + $tenant = Filament::getTenant(); if ($tenant) { $article->{$tenantColumn} = $tenant->id; } diff --git a/src/Resources/Frontend/HelpArticleResource.php b/src/Resources/Frontend/HelpArticleResource.php index 27de6053..baa9d785 100644 --- a/src/Resources/Frontend/HelpArticleResource.php +++ b/src/Resources/Frontend/HelpArticleResource.php @@ -8,8 +8,11 @@ use Filament\Support\Enums\Alignment; use Filament\Tables\Columns\Layout\Stack; use Filament\Tables\Table; +use Illuminate\Database\Eloquent\Builder; +use Tapp\FilamentHelp\Models\HelpArticle; use Tapp\FilamentHelp\Resources\Frontend\Pages\ListHelpArticles; use Tapp\FilamentHelp\Resources\Frontend\Pages\ViewHelpArticle; +use Tapp\FilamentHelp\Support\Tenancy; use Tapp\FilamentHelp\Tables\Components\HelpArticleCardColumn; class HelpArticleResource extends Resource @@ -18,7 +21,7 @@ class HelpArticleResource extends Resource public static function getModel(): string { - return static::$model ?? config('filament-help.model', \Tapp\FilamentHelp\Models\HelpArticle::class); + return static::$model ?? config('filament-help.model', HelpArticle::class); } protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-question-mark-circle'; @@ -97,18 +100,17 @@ public static function canDelete($record): bool return false; } - public static function getEloquentQuery(): \Illuminate\Database\Eloquent\Builder + public static function getEloquentQuery(): Builder { $query = parent::getEloquentQuery() ->public() ->visible(); // Apply tenant scoping if enabled - if (config('filament-help.tenancy.enabled', false) && config('filament-help.tenancy.scoping.frontend', true)) { + if (Tenancy::hasTenantColumn(static::getModel()) && config('filament-help.tenancy.scoping.frontend', true)) { $tenant = Filament::getTenant(); if ($tenant) { - $tenantColumn = config('filament-help.tenancy.column') ?? 'team_id'; - $query->where($tenantColumn, $tenant->id); + $query->where(Tenancy::column(), $tenant->id); } } diff --git a/src/Resources/Guest/HelpArticleResource.php b/src/Resources/Guest/HelpArticleResource.php index e813067a..20492baf 100644 --- a/src/Resources/Guest/HelpArticleResource.php +++ b/src/Resources/Guest/HelpArticleResource.php @@ -3,12 +3,17 @@ namespace Tapp\FilamentHelp\Resources\Guest; use Filament\Actions\ViewAction; +use Filament\Facades\Filament; +use Filament\Panel; use Filament\Resources\Resource; use Filament\Support\Enums\Alignment; use Filament\Tables\Columns\Layout\Stack; use Filament\Tables\Table; +use Illuminate\Database\Eloquent\Builder; +use Tapp\FilamentHelp\Models\HelpArticle; use Tapp\FilamentHelp\Resources\Guest\Pages\ListHelpArticles; use Tapp\FilamentHelp\Resources\Guest\Pages\ViewHelpArticle; +use Tapp\FilamentHelp\Support\Tenancy; use Tapp\FilamentHelp\Tables\Components\HelpArticleCardColumn; class HelpArticleResource extends Resource @@ -17,7 +22,7 @@ class HelpArticleResource extends Resource public static function getModel(): string { - return static::$model ?? config('filament-help.model', \Tapp\FilamentHelp\Models\HelpArticle::class); + return static::$model ?? config('filament-help.model', HelpArticle::class); } protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-question-mark-circle'; @@ -41,7 +46,7 @@ public static function setSlug(string $slug): void static::$slug = $slug; } - public static function getSlug(?\Filament\Panel $panel = null): string + public static function getSlug(?Panel $panel = null): string { return static::$slug; } @@ -94,18 +99,17 @@ public static function canDelete($record): bool return false; } - public static function getEloquentQuery(): \Illuminate\Database\Eloquent\Builder + public static function getEloquentQuery(): Builder { $query = parent::getEloquentQuery() ->public() ->visible(); // Apply tenant scoping if enabled - if (config('filament-help.tenancy.enabled', false) && config('filament-help.tenancy.scoping.guest', false)) { - $tenant = \Filament\Facades\Filament::getTenant(); + if (Tenancy::hasTenantColumn(static::getModel()) && config('filament-help.tenancy.scoping.guest', false)) { + $tenant = Filament::getTenant(); if ($tenant) { - $tenantColumn = config('filament-help.tenancy.column') ?? 'team_id'; - $query->where($tenantColumn, $tenant->id); + $query->where(Tenancy::column(), $tenant->id); } } diff --git a/src/Resources/HelpArticleResource.php b/src/Resources/HelpArticleResource.php index b25cca1b..85b293d2 100644 --- a/src/Resources/HelpArticleResource.php +++ b/src/Resources/HelpArticleResource.php @@ -5,6 +5,7 @@ use Filament\Actions\ActionGroup; use Filament\Actions\EditAction; use Filament\Actions\ViewAction; +use Filament\Facades\Filament; use Filament\Forms; use Filament\Resources\Resource; use Filament\Schemas\Components\Utilities\Get; @@ -13,10 +14,11 @@ use Filament\Tables; use Filament\Tables\Enums\RecordActionsPosition; use Filament\Tables\Table; -use Filament\Tables\Actions\BulkActionGroup; -use Filament\Tables\Actions\DeleteBulkAction; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Str; +use Tapp\FilamentHelp\Models\HelpArticle; use Tapp\FilamentHelp\Resources\HelpArticleResource\Pages; +use Tapp\FilamentHelp\Support\Tenancy; class HelpArticleResource extends Resource { @@ -24,17 +26,17 @@ class HelpArticleResource extends Resource public static function getModel(): string { - return static::$model ?? config('filament-help.model', \Tapp\FilamentHelp\Models\HelpArticle::class); + return static::$model ?? config('filament-help.model', HelpArticle::class); } protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-question-mark-circle'; public static function getTenantOwnershipRelationshipName(): string { - if (config('filament-help.tenancy.enabled', false)) { + if (Tenancy::isEnabled()) { return config('filament-help.tenancy.relationship') ?? 'team'; } - + return parent::getTenantOwnershipRelationshipName(); } @@ -81,11 +83,11 @@ public static function form(Schema $schema): Schema ->label('Hidden (Draft/Archived)') ->helperText('When enabled, this article will be hidden from everyone except admins. Hidden articles are not accessible via public URL, even if public access is enabled. Use this for draft or archived articles.') ->default(false), - Forms\Components\Textarea::make('embed') - ->label('Embed (HTML)') - ->rows(4) - ->helperText('Some embed tags contain style rules that may need to be removed or edited to render properly.') - ->columnSpanFull(), + Forms\Components\Textarea::make('embed') + ->label('Embed (HTML)') + ->rows(4) + ->helperText('Some embed tags contain style rules that may need to be removed or edited to render properly.') + ->columnSpanFull(), Forms\Components\RichEditor::make('content') ->label('Content') ->toolbarButtons([ @@ -106,7 +108,7 @@ public static function form(Schema $schema): Schema 'attachFiles', ]) ->extraInputAttributes([ - 'style' => 'min-height: 200px;' + 'style' => 'min-height: 200px;', ]), ]); } @@ -173,16 +175,15 @@ public static function getPages(): array ]; } - public static function getEloquentQuery(): \Illuminate\Database\Eloquent\Builder + public static function getEloquentQuery(): Builder { $query = parent::getEloquentQuery(); // Apply tenant scoping if tenancy is enabled - if (config('filament-help.tenancy.enabled', false) && config('filament-help.tenancy.scoping.admin', true)) { - $tenant = \Filament\Facades\Filament::getTenant(); + if (Tenancy::hasTenantColumn(static::getModel()) && config('filament-help.tenancy.scoping.admin', true)) { + $tenant = Filament::getTenant(); if ($tenant) { - $tenantColumn = config('filament-help.tenancy.column') ?? 'team_id'; - $query->where($tenantColumn, $tenant->id); + $query->where(Tenancy::column(), $tenant->id); } } diff --git a/src/Support/Tenancy.php b/src/Support/Tenancy.php new file mode 100644 index 00000000..8e85fa01 --- /dev/null +++ b/src/Support/Tenancy.php @@ -0,0 +1,41 @@ +getTable()) + && Schema::hasColumn($model->getTable(), static::column()); + } +} diff --git a/tests/Unit/TenancyTest.php b/tests/Unit/TenancyTest.php new file mode 100644 index 00000000..3fe717e4 --- /dev/null +++ b/tests/Unit/TenancyTest.php @@ -0,0 +1,18 @@ +set('filament-help.tenancy.enabled', true); + + HelpArticle::factory()->public()->create(['name' => 'Shared Article']); + + $articles = HelpArticle::query() + ->forTenant((object) ['id' => 1]) + ->get(); + + expect(Tenancy::hasTenantColumn())->toBeFalse() + ->and($articles)->toHaveCount(1) + ->and($articles->first()->name)->toBe('Shared Article'); +});