From 4bf58cb0eca10de4a059bbc4f926dc6568cae293 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9ia=20Bohner?= Date: Sun, 28 Dec 2025 15:44:05 -0300 Subject: [PATCH 1/4] Add tenancy --- README.md | 194 +++++++++++++++++- config/filament-help.php | 70 +++++++ database/factories/HelpArticleFactory.php | 23 ++- .../create_help_articles_table.php.stub | 19 ++ src/FilamentHelpServiceProvider.php | 2 + src/Models/HelpArticle.php | 46 +++++ .../Frontend/HelpArticleResource.php | 13 +- src/Resources/Guest/HelpArticleResource.php | 13 +- src/Resources/HelpArticleResource.php | 25 +++ 9 files changed, 398 insertions(+), 7 deletions(-) create mode 100644 config/filament-help.php diff --git a/README.md b/README.md index 1edd1e63..aa8220fe 100644 --- a/README.md +++ b/README.md @@ -15,16 +15,66 @@ This plugin adds help article management to Filament with admin, frontend, and g ## Installation -You can install the package via composer: +You can install the package via Composer: ```bash composer require tapp/filament-help ``` -You can publish and run the migrations with: +You can publish the config file with: + +```bash +php artisan vendor:publish --tag="filament-help-config" +``` + +This is the contents of the published config file: + +```php +return [ + 'tenancy' => [ + // Enable or disable tenancy features globally + 'enabled' => env('FILAMENT_HELP_TENANCY_ENABLED', false), + + // The column name for the tenant relationship + 'column' => env('FILAMENT_HELP_TENANCY_COLUMN', 'team_id'), + + // The tenant model class + 'model' => null, + + // The relationship name on the HelpArticle model + 'relationship' => env('FILAMENT_HELP_TENANCY_RELATIONSHIP', 'team'), + + // Foreign key constraints + 'foreign_key' => [ + 'on_delete' => env('FILAMENT_HELP_TENANCY_ON_DELETE', 'cascade'), + 'on_update' => env('FILAMENT_HELP_TENANCY_ON_UPDATE', 'cascade'), + ], + + // Enable tenancy scoping per panel type + 'scoping' => [ + 'admin' => env('FILAMENT_HELP_TENANCY_SCOPE_ADMIN', true), + 'frontend' => env('FILAMENT_HELP_TENANCY_SCOPE_FRONTEND', true), + 'guest' => env('FILAMENT_HELP_TENANCY_SCOPE_GUEST', false), + ], + + // Automatically assign tenant on creation + 'auto_assign' => env('FILAMENT_HELP_TENANCY_AUTO_ASSIGN', true), + ], +]; +``` + +You can publish the migrations with: ```bash php artisan vendor:publish --tag="filament-help-migrations" +``` + +> [!WARNING] +> If you are using multi-tenancy please see the "Multi-Tenancy Support" instructions below **before** publishing and running migrations. + +You can run the migrations with: + +```bash php artisan migrate ``` @@ -67,7 +117,8 @@ public function panel(Panel $panel): Panel ->plugins([ FilamentHelpFrontendPlugin::make(), // Default slug is 'help-articles', so articles will be at {panel-path}/help-articles - // Customize with ->slug('custom-slug') if needed + // Customize with: + // ->slug('custom-slug') ]); } ``` @@ -96,7 +147,8 @@ public function panel(Panel $panel): Panel ->plugins([ FilamentHelpGuestPlugin::make(), // Default slug is 'help', so articles will be at /help (or {panel-path}/help) - // Customize with ->slug('custom-slug') if needed + // Customize with: + // ->slug('custom-slug') ]); } ``` @@ -127,6 +179,140 @@ The frontend and guest panel URLs can be customized using the plugin's `->slug() - **Public/Private**: Control article visibility with `is_public` flag - **Hidden/Draft**: Hide articles from public view with `is_hidden` flag (useful for drafts or archived articles) - **Search & Filter**: Find articles by name and filter by public/hidden status +- **Multi-Tenancy Support**: Optionally scope help articles to teams/organizations + +## Multi-Tenancy Support + +This package supports Filament's multi-tenancy feature, allowing you to scope help articles to specific teams or organizations. + +### Setting Up Multi-Tenancy + +#### ⚠️ Important: Configure Before Migration + +**You MUST enable and configure tenancy BEFORE running migrations!** The migrations check the tenancy configuration to determine whether to add tenant columns to the database tables. Enabling tenancy after running migrations will require manual database modifications. + +1. **Enable tenancy in the config file**: + +Publish the config file: + +```bash +php artisan vendor:publish --tag="filament-help-config" +``` + +Then update `config/filament-help.php`: + +```php +return [ + 'tenancy' => [ + 'enabled' => true, // Enable tenancy + 'model' => \App\Models\Team::class, // Your tenant model + 'column' => 'team_id', // Column name in help_articles table + 'relationship' => 'team', // Relationship name + + // Scoping per panel type + 'scoping' => [ + 'admin' => true, // Scope articles in admin panel + 'frontend' => true, // Scope articles in frontend panel + 'guest' => false, // Don't scope in guest panel (shared articles) + ], + + 'auto_assign' => true, // Auto-assign current tenant to new articles + ], +]; +``` + +Or use environment variables in your `.env` file: + +```env +FILAMENT_HELP_TENANCY_ENABLED=true +FILAMENT_HELP_TENANCY_COLUMN=team_id +FILAMENT_HELP_TENANCY_SCOPE_ADMIN=true +FILAMENT_HELP_TENANCY_SCOPE_FRONTEND=true +FILAMENT_HELP_TENANCY_SCOPE_GUEST=false +``` + +2. **Run migrations**: + +When tenancy is enabled, the migration will automatically add the tenant column to the `help_articles` table: + +```bash +php artisan migrate +``` + +3. **Configure your Filament panel with tenancy**: + +```php +// In your AdminPanelProvider.php (or wherever you configure your Filament panel) +use Tapp\FilamentHelp\FilamentHelpPlugin; + +public function panel(Panel $panel): Panel +{ + return $panel + ->tenant(\App\Models\Team::class) // Your tenant model + // ... other configuration + ->plugins([ + FilamentHelpPlugin::make(), + ]); +} +``` + +### How It Works + +When tenancy is enabled: + +- **Migration**: The `team_id` column (or your custom column name) is automatically added to the `help_articles` table during migration +- **Admin Panel**: Help articles are automatically scoped to the current tenant. Users can only see and manage articles belonging to their team. +- **Auto-assignment**: When creating a new help article, the tenant ID is automatically assigned to the current tenant. +- **Frontend/Guest**: You can control whether tenancy scoping applies to frontend and guest panels using the config. + +### Configuration Options + +#### Tenancy Column + +Change the column name if you use a different naming convention: + +```php +'column' => 'organization_id', +``` + +#### Tenant Model + +Specify your tenant model: + +```php +'model' => \App\Models\Organization::class, +``` + +#### Scoping Control + +Control which panels apply tenant scoping: + +```php +'scoping' => [ + 'admin' => true, // Articles scoped by tenant in admin + 'frontend' => true, // Articles scoped by tenant in frontend + 'guest' => false, // Articles shared across all tenants in guest panel +], +``` + +#### Foreign Key Constraints + +Configure cascade behavior: + +```php +'foreign_key' => [ + 'on_delete' => 'cascade', // or 'set null', 'restrict' + 'on_update' => 'cascade', // or 'set null', 'restrict' +], +``` + +### Disabling Tenancy + +By default, tenancy is disabled. Help articles are shared across all teams. To use this package without tenancy, simply leave the config as default or set: + +```env +FILAMENT_HELP_TENANCY_ENABLED=false +``` ## Testing diff --git a/config/filament-help.php b/config/filament-help.php new file mode 100644 index 00000000..7e712973 --- /dev/null +++ b/config/filament-help.php @@ -0,0 +1,70 @@ + [ + /* + * Enable or disable tenancy features globally. + * When enabled, a team_id column will be added to the help_articles table + * and articles will be scoped to the current tenant. + */ + 'enabled' => env('FILAMENT_HELP_TENANCY_ENABLED', false), + + /* + * The column name for the tenant relationship. + * This column will be added to the help_articles table if tenancy is enabled. + * E.g. 'team_id' + */ + 'column' => env('FILAMENT_HELP_TENANCY_COLUMN', null), + + /* + * The tenant model class. + * This should be the same model you use for Filament's tenant feature. + * E.g. \App\Models\Team::class + */ + 'model' => null, + + /* + * The relationship name on the HelpArticle model. + * This is used for Filament's tenant ownership relationship. + * E.g. 'team' + * + */ + 'relationship' => env('FILAMENT_HELP_TENANCY_RELATIONSHIP', null), + + /* + * The foreign key constraint configuration. + */ + 'foreign_key' => [ + 'on_delete' => env('FILAMENT_HELP_TENANCY_ON_DELETE', 'cascade'), // cascade, set null, restrict + 'on_update' => env('FILAMENT_HELP_TENANCY_ON_UPDATE', 'cascade'), // cascade, set null, restrict + ], + + /* + * Enable tenancy scoping per panel type. + * When false, articles will not be scoped by tenant even if global tenancy is enabled. + */ + 'scoping' => [ + 'admin' => env('FILAMENT_HELP_TENANCY_SCOPE_ADMIN', true), + 'frontend' => env('FILAMENT_HELP_TENANCY_SCOPE_FRONTEND', true), + 'guest' => env('FILAMENT_HELP_TENANCY_SCOPE_GUEST', false), + ], + + /* + * Automatically assign tenant on creation. + * When enabled, new articles will automatically get the current tenant ID assigned. + */ + 'auto_assign' => env('FILAMENT_HELP_TENANCY_AUTO_ASSIGN', true), + ], + +]; + diff --git a/database/factories/HelpArticleFactory.php b/database/factories/HelpArticleFactory.php index d5e822cf..82a811a1 100644 --- a/database/factories/HelpArticleFactory.php +++ b/database/factories/HelpArticleFactory.php @@ -14,11 +14,19 @@ class HelpArticleFactory extends Factory public function definition(): array { - return [ + $definition = [ 'name' => $this->faker->sentence(3), '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 + } + + return $definition; } public function public(): static @@ -41,4 +49,17 @@ public function hidden(): static 'is_hidden' => true, ]); } + + public function forTeam($team): static + { + if (! config('filament-help.tenancy.enabled', false)) { + return $this; + } + + $tenantColumn = config('filament-help.tenancy.column') ?? 'team_id'; + + return $this->state(fn (array $attributes) => [ + $tenantColumn => is_object($team) ? $team->id : $team, + ]); + } } diff --git a/database/migrations/create_help_articles_table.php.stub b/database/migrations/create_help_articles_table.php.stub index 108476e8..8ea08e0a 100644 --- a/database/migrations/create_help_articles_table.php.stub +++ b/database/migrations/create_help_articles_table.php.stub @@ -10,6 +10,20 @@ return new class extends Migration { Schema::create('help_articles', function (Blueprint $table) { $table->id(); + + // Add tenant column if tenancy is enabled in config + if (config('filament-help.tenancy.enabled', false)) { + $tenantColumn = config('filament-help.tenancy.column') ?? 'team_id'; + $onDelete = config('filament-help.tenancy.foreign_key.on_delete', 'cascade'); + $onUpdate = config('filament-help.tenancy.foreign_key.on_update', 'cascade'); + + $table->foreignId($tenantColumn) + ->nullable() + ->constrained('teams') + ->onDelete($onDelete) + ->onUpdate($onUpdate); + } + $table->string('name'); $table->string('slug')->unique()->nullable(); $table->boolean('is_public')->default(false); @@ -18,4 +32,9 @@ return new class extends Migration $table->timestamps(); }); } + + public function down(): void + { + Schema::dropIfExists('help_articles'); + } }; diff --git a/src/FilamentHelpServiceProvider.php b/src/FilamentHelpServiceProvider.php index d5f16ec5..0e62cd44 100644 --- a/src/FilamentHelpServiceProvider.php +++ b/src/FilamentHelpServiceProvider.php @@ -12,6 +12,7 @@ public function configurePackage(Package $package): void { $package ->name('filament-help') + ->hasConfigFile() ->hasViews() ->hasMigrations([ 'create_help_articles_table', @@ -19,6 +20,7 @@ public function configurePackage(Package $package): void ]) ->hasInstallCommand(function (InstallCommand $command) { $command + ->publishConfigFile() ->publishMigrations() ->askToRunMigrations(); }); diff --git a/src/Models/HelpArticle.php b/src/Models/HelpArticle.php index 17dd9bef..17a4c731 100644 --- a/src/Models/HelpArticle.php +++ b/src/Models/HelpArticle.php @@ -2,8 +2,10 @@ namespace Tapp\FilamentHelp\Models; +use Filament\Models\Contracts\HasTenants; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; class HelpArticle extends Model { @@ -18,6 +20,17 @@ class HelpArticle extends Model 'embed', ]; + 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; + } + } + protected $casts = [ 'is_public' => 'boolean', 'is_hidden' => 'boolean', @@ -33,6 +46,27 @@ public function scopeVisible($query) return $query->where('is_hidden', false); } + /** + * Get the team that owns this help article. + */ + public function team(): BelongsTo + { + $tenantModel = config('filament-help.tenancy.model') ?? \App\Models\Team::class; + $tenantColumn = config('filament-help.tenancy.column') ?? 'team_id'; + + return $this->belongsTo($tenantModel, $tenantColumn); + } + + /** + * Scope query to only include articles for a specific tenant/team. + */ + public function scopeForTenant($query, $tenant) + { + $tenantColumn = config('filament-help.tenancy.column') ?? 'team_id'; + + return $query->where($tenantColumn, $tenant->id); + } + protected static function boot() { parent::boot(); @@ -41,6 +75,18 @@ protected static function boot() if (empty($article->slug)) { $article->slug = \Str::slug($article->name); } + + // 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 ($tenant) { + $article->{$tenantColumn} = $tenant->id; + } + } + } }); static::updating(function ($article) { diff --git a/src/Resources/Frontend/HelpArticleResource.php b/src/Resources/Frontend/HelpArticleResource.php index f3eb0f99..e86a2ca4 100644 --- a/src/Resources/Frontend/HelpArticleResource.php +++ b/src/Resources/Frontend/HelpArticleResource.php @@ -93,8 +93,19 @@ public static function canDelete($record): bool public static function getEloquentQuery(): \Illuminate\Database\Eloquent\Builder { - return parent::getEloquentQuery() + $query = parent::getEloquentQuery() ->public() ->visible(); + + // Apply tenant scoping if enabled + if (config('filament-help.tenancy.enabled', false) && config('filament-help.tenancy.scoping.frontend', true)) { + $tenant = \Filament\Facades\Filament::getTenant(); + if ($tenant) { + $tenantColumn = config('filament-help.tenancy.column') ?? 'team_id'; + $query->where($tenantColumn, $tenant->id); + } + } + + return $query; } } diff --git a/src/Resources/Guest/HelpArticleResource.php b/src/Resources/Guest/HelpArticleResource.php index b8e04ad4..33b5aaf4 100644 --- a/src/Resources/Guest/HelpArticleResource.php +++ b/src/Resources/Guest/HelpArticleResource.php @@ -92,8 +92,19 @@ public static function canDelete($record): bool public static function getEloquentQuery(): \Illuminate\Database\Eloquent\Builder { - return parent::getEloquentQuery() + $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 ($tenant) { + $tenantColumn = config('filament-help.tenancy.column') ?? 'team_id'; + $query->where($tenantColumn, $tenant->id); + } + } + + return $query; } } diff --git a/src/Resources/HelpArticleResource.php b/src/Resources/HelpArticleResource.php index 71eb86cb..93e3a73e 100644 --- a/src/Resources/HelpArticleResource.php +++ b/src/Resources/HelpArticleResource.php @@ -21,6 +21,15 @@ class HelpArticleResource extends Resource protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-question-mark-circle'; + public static function getTenantOwnershipRelationshipName(): string + { + if (config('filament-help.tenancy.enabled', false)) { + return config('filament-help.tenancy.relationship') ?? 'team'; + } + + return parent::getTenantOwnershipRelationshipName(); + } + public static string|\UnitEnum|null $navigationGroup = 'System'; public static function shouldRegisterNavigation(): bool @@ -150,4 +159,20 @@ public static function getPages(): array 'edit' => Pages\EditHelpArticle::route('/{record}/edit'), ]; } + + public static function getEloquentQuery(): \Illuminate\Database\Eloquent\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 ($tenant) { + $tenantColumn = config('filament-help.tenancy.column') ?? 'team_id'; + $query->where($tenantColumn, $tenant->id); + } + } + + return $query; + } } From afd714454bd3985ef640bac9b7bee9743e59bdcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9ia=20Bohner?= Date: Mon, 29 Dec 2025 11:19:32 -0300 Subject: [PATCH 2/4] Update HelpArticle model --- README.md | 67 ++++++++++++++++++- config/filament-help.php | 15 +++++ src/Models/HelpArticle.php | 35 ++++++---- .../Frontend/HelpArticleResource.php | 8 ++- src/Resources/Guest/HelpArticleResource.php | 8 ++- src/Resources/HelpArticleResource.php | 8 ++- 6 files changed, 120 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index aa8220fe..c45b74fb 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,14 @@ This is the contents of the published config file: ```php return [ + /* + | Help Article Model + | + | If you extend the HelpArticle model, specify your extended model here. + | This ensures Filament resources use your extended model. + */ + 'model' => env('FILAMENT_HELP_MODEL', \Tapp\FilamentHelp\Models\HelpArticle::class), + 'tenancy' => [ // Enable or disable tenancy features globally 'enabled' => env('FILAMENT_HELP_TENANCY_ENABLED', false), @@ -225,13 +233,68 @@ Or use environment variables in your `.env` file: ```env FILAMENT_HELP_TENANCY_ENABLED=true +FILAMENT_HELP_TENANCY_MODEL=App\Models\Team FILAMENT_HELP_TENANCY_COLUMN=team_id +FILAMENT_HELP_TENANCY_RELATIONSHIP=team FILAMENT_HELP_TENANCY_SCOPE_ADMIN=true FILAMENT_HELP_TENANCY_SCOPE_FRONTEND=true FILAMENT_HELP_TENANCY_SCOPE_GUEST=false ``` -2. **Run migrations**: +2. **Add the tenant relationship to your HelpArticle model**: + +Since the package needs to support various tenant models (Team, Organization, etc.), you need to define the relationship in your application. + +Extend the `HelpArticle` model in your application: + +```php +// app/Models/HelpArticle.php +namespace App\Models; + +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Tapp\FilamentHelp\Models\HelpArticle as BaseHelpArticle; + +class HelpArticle extends BaseHelpArticle +{ + /** + * Get the team that owns the help article. + */ + public function team(): BelongsTo + { + return $this->belongsTo(\App\Models\Team::class); + } +} +``` + +Or if you use a different tenant model: + +```php +public function organization(): BelongsTo +{ + return $this->belongsTo(\App\Models\Organization::class); +} +``` + +**Important**: Make sure the relationship name matches the `relationship` config value you set (e.g., `'team'` or `'organization'`). + +Then, update your config to use your extended model: + +```php +// config/filament-help.php +return [ + 'model' => \App\Models\HelpArticle::class, + + 'tenancy' => [ + 'enabled' => true, + 'model' => \App\Models\Team::class, + 'column' => 'team_id', + 'relationship' => 'team', + // ... + ], +]; +``` + +3. **Run migrations**: When tenancy is enabled, the migration will automatically add the tenant column to the `help_articles` table: @@ -239,7 +302,7 @@ When tenancy is enabled, the migration will automatically add the tenant column php artisan migrate ``` -3. **Configure your Filament panel with tenancy**: +4. **Configure your Filament panel with tenancy**: ```php // In your AdminPanelProvider.php (or wherever you configure your Filament panel) diff --git a/config/filament-help.php b/config/filament-help.php index 7e712973..92c22625 100644 --- a/config/filament-help.php +++ b/config/filament-help.php @@ -2,6 +2,21 @@ return [ + /* + |-------------------------------------------------------------------------- + | Help Article Model + |-------------------------------------------------------------------------- + | + | If you extend the HelpArticle model in your application to add custom + | relationships (e.g., tenant relationships), specify your extended model here. + | This ensures Filament resources use your extended model instead of the base model. + | + | Example: \App\Models\HelpArticle::class + | + */ + + 'model' => \Tapp\FilamentHelp\Models\HelpArticle::class, + /* |-------------------------------------------------------------------------- | Tenancy Configuration diff --git a/src/Models/HelpArticle.php b/src/Models/HelpArticle.php index 17a4c731..377f2449 100644 --- a/src/Models/HelpArticle.php +++ b/src/Models/HelpArticle.php @@ -2,10 +2,8 @@ namespace Tapp\FilamentHelp\Models; -use Filament\Models\Contracts\HasTenants; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; -use Illuminate\Database\Eloquent\Relations\BelongsTo; class HelpArticle extends Model { @@ -46,27 +44,38 @@ public function scopeVisible($query) return $query->where('is_hidden', false); } - /** - * Get the team that owns this help article. - */ - public function team(): BelongsTo - { - $tenantModel = config('filament-help.tenancy.model') ?? \App\Models\Team::class; - $tenantColumn = config('filament-help.tenancy.column') ?? 'team_id'; - - return $this->belongsTo($tenantModel, $tenantColumn); - } - /** * Scope query to only include articles for a specific tenant/team. */ public function scopeForTenant($query, $tenant) { + if (! config('filament-help.tenancy.enabled', false)) { + return $query; + } + $tenantColumn = config('filament-help.tenancy.column') ?? 'team_id'; return $query->where($tenantColumn, $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(); diff --git a/src/Resources/Frontend/HelpArticleResource.php b/src/Resources/Frontend/HelpArticleResource.php index e86a2ca4..bf5fc2d4 100644 --- a/src/Resources/Frontend/HelpArticleResource.php +++ b/src/Resources/Frontend/HelpArticleResource.php @@ -7,14 +7,18 @@ use Filament\Support\Enums\Alignment; use Filament\Tables\Columns\Layout\Stack; use Filament\Tables\Table; -use Tapp\FilamentHelp\Models\HelpArticle; use Tapp\FilamentHelp\Resources\Frontend\Pages\ListHelpArticles; use Tapp\FilamentHelp\Resources\Frontend\Pages\ViewHelpArticle; use Tapp\FilamentHelp\Tables\Components\HelpArticleCardColumn; class HelpArticleResource extends Resource { - protected static ?string $model = HelpArticle::class; + protected static ?string $model = null; + + public static function getModel(): string + { + return static::$model ?? config('filament-help.model', \Tapp\FilamentHelp\Models\HelpArticle::class); + } protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-question-mark-circle'; diff --git a/src/Resources/Guest/HelpArticleResource.php b/src/Resources/Guest/HelpArticleResource.php index 33b5aaf4..e813067a 100644 --- a/src/Resources/Guest/HelpArticleResource.php +++ b/src/Resources/Guest/HelpArticleResource.php @@ -7,14 +7,18 @@ use Filament\Support\Enums\Alignment; use Filament\Tables\Columns\Layout\Stack; use Filament\Tables\Table; -use Tapp\FilamentHelp\Models\HelpArticle; use Tapp\FilamentHelp\Resources\Guest\Pages\ListHelpArticles; use Tapp\FilamentHelp\Resources\Guest\Pages\ViewHelpArticle; use Tapp\FilamentHelp\Tables\Components\HelpArticleCardColumn; class HelpArticleResource extends Resource { - protected static ?string $model = HelpArticle::class; + protected static ?string $model = null; + + public static function getModel(): string + { + return static::$model ?? config('filament-help.model', \Tapp\FilamentHelp\Models\HelpArticle::class); + } protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-question-mark-circle'; diff --git a/src/Resources/HelpArticleResource.php b/src/Resources/HelpArticleResource.php index 93e3a73e..50c0d97b 100644 --- a/src/Resources/HelpArticleResource.php +++ b/src/Resources/HelpArticleResource.php @@ -12,12 +12,16 @@ use Filament\Tables\Actions\BulkActionGroup; use Filament\Tables\Actions\DeleteBulkAction; use Illuminate\Support\Str; -use Tapp\FilamentHelp\Models\HelpArticle; use Tapp\FilamentHelp\Resources\HelpArticleResource\Pages; class HelpArticleResource extends Resource { - protected static ?string $model = HelpArticle::class; + protected static ?string $model = null; + + public static function getModel(): string + { + return static::$model ?? config('filament-help.model', \Tapp\FilamentHelp\Models\HelpArticle::class); + } protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-question-mark-circle'; From c5985976c0e9d5cf68844b76a5be0049bc097c68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9ia=20Bohner?= Date: Mon, 29 Dec 2025 18:27:17 -0300 Subject: [PATCH 3/4] Styles --- README.md | 6 +++++- .../add_is_hidden_to_help_articles_table.php.stub | 5 +++-- .../migrations/create_help_articles_table.php.stub | 10 ++++++---- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index c45b74fb..6f327ca6 100644 --- a/README.md +++ b/README.md @@ -249,12 +249,16 @@ Extend the `HelpArticle` model in your application: ```php // app/Models/HelpArticle.php +boolean('is_hidden')->default(false)->after('is_public'); }); } }; - diff --git a/database/migrations/create_help_articles_table.php.stub b/database/migrations/create_help_articles_table.php.stub index 8ea08e0a..a6304057 100644 --- a/database/migrations/create_help_articles_table.php.stub +++ b/database/migrations/create_help_articles_table.php.stub @@ -1,5 +1,7 @@ id(); - + // Add tenant column if tenancy is enabled in config if (config('filament-help.tenancy.enabled', false)) { $tenantColumn = config('filament-help.tenancy.column') ?? 'team_id'; $onDelete = config('filament-help.tenancy.foreign_key.on_delete', 'cascade'); $onUpdate = config('filament-help.tenancy.foreign_key.on_update', 'cascade'); - + $table->foreignId($tenantColumn) ->nullable() ->constrained('teams') ->onDelete($onDelete) ->onUpdate($onUpdate); } - + $table->string('name'); $table->string('slug')->unique()->nullable(); $table->boolean('is_public')->default(false); From b3c56ff289b50d005d08c0fc9d07d63e5734b7af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9ia=20Bohner?= Date: Tue, 6 Jan 2026 20:57:01 -0300 Subject: [PATCH 4/4] Updates --- README.md | 86 ++++++++++++++++++++++++++++++---------- config/filament-help.php | 2 +- 2 files changed, 65 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 6f327ca6..8379a0a5 100644 --- a/README.md +++ b/README.md @@ -31,43 +31,86 @@ This is the contents of the published config file: ```php return [ + /* + |-------------------------------------------------------------------------- | Help Article Model + |-------------------------------------------------------------------------- + | + | If you extend the HelpArticle model in your application to add custom + | relationships (e.g., tenant relationships), specify your extended model here. + | This ensures Filament resources use your extended model instead of the base model. + | + | Example: \App\Models\HelpArticle::class + | + */ + + 'model' => \Tapp\FilamentHelp\Models\HelpArticle::class, + + /* + |-------------------------------------------------------------------------- + | Tenancy Configuration + |-------------------------------------------------------------------------- + | + | Configure multi-tenancy settings for help articles. | - | If you extend the HelpArticle model, specify your extended model here. - | This ensures Filament resources use your extended model. */ - 'model' => env('FILAMENT_HELP_MODEL', \Tapp\FilamentHelp\Models\HelpArticle::class), 'tenancy' => [ - // Enable or disable tenancy features globally + /* + * Enable or disable tenancy features globally. + * When enabled, a team_id column will be added to the help_articles table + * and articles will be scoped to the current tenant. + */ 'enabled' => env('FILAMENT_HELP_TENANCY_ENABLED', false), - - // The column name for the tenant relationship - 'column' => env('FILAMENT_HELP_TENANCY_COLUMN', 'team_id'), - - // The tenant model class + + /* + * The column name for the tenant relationship. + * This column will be added to the help_articles table if tenancy is enabled. + * E.g. 'team_id' + */ + 'column' => env('FILAMENT_HELP_TENANCY_COLUMN', null), + + /* + * The tenant model class. + * This should be the same model you use for Filament's tenant feature. + * E.g. \App\Models\Team::class + */ 'model' => null, - - // The relationship name on the HelpArticle model - 'relationship' => env('FILAMENT_HELP_TENANCY_RELATIONSHIP', 'team'), - - // Foreign key constraints + + /* + * The relationship name on the HelpArticle model. + * This is used for Filament's tenant ownership relationship. + * E.g. 'team' + * + */ + 'relationship' => env('FILAMENT_HELP_TENANCY_RELATIONSHIP', null), + + /* + * The foreign key constraint configuration. + */ 'foreign_key' => [ - 'on_delete' => env('FILAMENT_HELP_TENANCY_ON_DELETE', 'cascade'), - 'on_update' => env('FILAMENT_HELP_TENANCY_ON_UPDATE', 'cascade'), + 'on_delete' => env('FILAMENT_HELP_TENANCY_ON_DELETE', 'cascade'), // cascade, set null, restrict + 'on_update' => env('FILAMENT_HELP_TENANCY_ON_UPDATE', 'cascade'), // cascade, set null, restrict ], - - // Enable tenancy scoping per panel type + + /* + * Enable tenancy scoping per panel type. + * When false, articles will not be scoped by tenant even if global tenancy is enabled. + */ 'scoping' => [ 'admin' => env('FILAMENT_HELP_TENANCY_SCOPE_ADMIN', true), 'frontend' => env('FILAMENT_HELP_TENANCY_SCOPE_FRONTEND', true), - 'guest' => env('FILAMENT_HELP_TENANCY_SCOPE_GUEST', false), + 'guest' => env('FILAMENT_HELP_TENANCY_SCOPE_GUEST', true), ], - - // Automatically assign tenant on creation + + /* + * Automatically assign tenant on creation. + * When enabled, new articles will automatically get the current tenant ID assigned. + */ 'auto_assign' => env('FILAMENT_HELP_TENANCY_AUTO_ASSIGN', true), ], + ]; ``` @@ -233,7 +276,6 @@ Or use environment variables in your `.env` file: ```env FILAMENT_HELP_TENANCY_ENABLED=true -FILAMENT_HELP_TENANCY_MODEL=App\Models\Team FILAMENT_HELP_TENANCY_COLUMN=team_id FILAMENT_HELP_TENANCY_RELATIONSHIP=team FILAMENT_HELP_TENANCY_SCOPE_ADMIN=true diff --git a/config/filament-help.php b/config/filament-help.php index 92c22625..b030686a 100644 --- a/config/filament-help.php +++ b/config/filament-help.php @@ -71,7 +71,7 @@ 'scoping' => [ 'admin' => env('FILAMENT_HELP_TENANCY_SCOPE_ADMIN', true), 'frontend' => env('FILAMENT_HELP_TENANCY_SCOPE_FRONTEND', true), - 'guest' => env('FILAMENT_HELP_TENANCY_SCOPE_GUEST', false), + 'guest' => env('FILAMENT_HELP_TENANCY_SCOPE_GUEST', true), ], /*