Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ public function panel(Panel $panel): Panel

**Location**: App panel (defaults to `{panel-path}/help-articles`, configurable via `->slug()`)
**Access**: Authenticated users only
**Features**: Read-only access to public help articles
**Features**: Read-only access to help articles that are not hidden, including articles without public access

### Guest Plugin (for public access)

Expand Down Expand Up @@ -257,15 +257,16 @@ public function panel(Panel $panel): Panel
Help articles are available in three different locations depending on your setup:

1. **Admin Panel** (`/admin/help-articles`): For editing and managing help articles
2. **App Panel** (configurable, default `{panel-path}/help-articles`): For authenticated users to view public help articles
2. **App Panel** (configurable, default `{panel-path}/help-articles`): For authenticated users to view help articles that are not hidden
3. **Guest Panel** (configurable, default `{panel-path}/help`): For public/guest users to view public help articles

The frontend and guest panel URLs can be customized using the plugin's `->slug()` method when registering the plugin (see plugin documentation above).

## Features

- **Admin Panel**: Full CRUD operations for help articles
- **Frontend/Guest**: Read-only access to public help articles
- **Frontend**: Read-only access for authenticated users, including articles that are not public
- **Guest**: Read-only access to public help articles
- **Rich Content**: HTML content support with iframe embedding
- **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)
Expand Down
5 changes: 3 additions & 2 deletions src/FilamentHelpFrontendPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Filament\Contracts\Plugin;
use Filament\Panel;
use Tapp\FilamentHelp\Resources\Frontend\HelpArticleResource;

class FilamentHelpFrontendPlugin implements Plugin
{
Expand Down Expand Up @@ -36,11 +37,11 @@ public function register(Panel $panel): void
throw new \InvalidArgumentException('Slug cannot be empty. Please provide a non-empty slug when registering the plugin.');
}

\Tapp\FilamentHelp\Resources\Frontend\HelpArticleResource::setSlug($slug);
HelpArticleResource::setSlug($slug);

$panel
->resources([
\Tapp\FilamentHelp\Resources\Frontend\HelpArticleResource::class,
HelpArticleResource::class,
]);
}

Expand Down
5 changes: 3 additions & 2 deletions src/FilamentHelpGuestPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Filament\Contracts\Plugin;
use Filament\Panel;
use Tapp\FilamentHelp\Resources\Guest\HelpArticleResource;

class FilamentHelpGuestPlugin implements Plugin
{
Expand Down Expand Up @@ -38,11 +39,11 @@ public function register(Panel $panel): void
throw new \InvalidArgumentException('Default slug cannot be empty. Please provide a non-empty slug when registering the plugin.');
}

\Tapp\FilamentHelp\Resources\Guest\HelpArticleResource::setSlug($slug);
HelpArticleResource::setSlug($slug);

$panel
->resources([
\Tapp\FilamentHelp\Resources\Guest\HelpArticleResource::class,
HelpArticleResource::class,
]);
}

Expand Down
3 changes: 2 additions & 1 deletion src/FilamentHelpPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Filament\Contracts\Plugin;
use Filament\Panel;
use Tapp\FilamentHelp\Resources\HelpArticleResource;

class FilamentHelpPlugin implements Plugin
{
Expand All @@ -21,7 +22,7 @@ public function register(Panel $panel): void
{
$panel
->resources([
\Tapp\FilamentHelp\Resources\HelpArticleResource::class,
HelpArticleResource::class,
]);
}

Expand Down
1 change: 0 additions & 1 deletion src/Resources/Frontend/HelpArticleResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@ public static function canDelete($record): bool
public static function getEloquentQuery(): Builder
{
$query = parent::getEloquentQuery()
->public()
->visible();

// Apply tenant scoping if enabled
Expand Down
9 changes: 4 additions & 5 deletions src/Resources/Frontend/Pages/ViewHelpArticle.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,11 @@ class ViewHelpArticle extends ViewRecord

protected string $view = 'filament-help::resources.frontend.help-article-resource.pages.view-help-article';

public function mount(int | string $record): void
public function mount(int|string $record): void
{
$this->record = $this->resolveRecord($record);

// Ensure the article is public
if (! $this->record->is_public) {

if ($this->record->is_hidden) {
abort(404);
}
}
Expand All @@ -35,7 +34,7 @@ public function getHeading(): string

public function getSubheading(): string
{
return 'Last updated ' . $this->record->updated_at->format('M j, Y');
return 'Last updated '.$this->record->updated_at->format('M j, Y');
}

public function getBreadcrumbs(): array
Expand Down
33 changes: 22 additions & 11 deletions src/Resources/HelpArticleResource/Pages/ViewHelpArticle.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,20 +54,31 @@ protected function getShareUrl(): string
$record = $this->record;

if ($record->is_public) {
// Public link for unauthenticated users (guest panel)
$guestPanel = Filament::getPanel('guest');
if ($guestPanel) {
// Temporarily set the current panel to generate the correct URL
$originalPanel = Filament::getCurrentPanel();
Filament::setCurrentPanel($guestPanel);
$url = GuestHelpArticleResource::getUrl('view', ['record' => $record->slug]);
Filament::setCurrentPanel($originalPanel);
$guestPanelId = $this->panelIdForResource(GuestHelpArticleResource::class);

return $url;
if (filled($guestPanelId)) {
return GuestHelpArticleResource::getUrl('view', ['record' => $record->slug], panel: $guestPanelId);
}
}

// Authenticated frontend link (not admin)
return FrontendHelpArticleResource::getUrl('view', ['record' => $record->slug]);
return FrontendHelpArticleResource::getUrl(
'view',
['record' => $record->slug],
panel: $this->panelIdForResource(FrontendHelpArticleResource::class),
);
}

/**
* @param class-string $resource
*/
protected function panelIdForResource(string $resource): ?string
{
foreach (Filament::getPanels() as $panel) {
if (in_array($resource, $panel->getResources(), true)) {
return $panel->getId();
}
}

return null;
}
}
3 changes: 2 additions & 1 deletion tests/Feature/AdminHelpArticleResourceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use Filament\Schemas\Schema;
use Tapp\FilamentHelp\Models\HelpArticle;
use Tapp\FilamentHelp\Resources\HelpArticleResource;
use Tapp\FilamentHelp\Resources\HelpArticleResource\Pages\ViewHelpArticle;

it('can create help article through factory', function () {
$helpArticle = HelpArticle::factory()->create([
Expand Down Expand Up @@ -49,7 +50,7 @@
'name' => 'Adding an Evaluation Course',
]);

$page = new \Tapp\FilamentHelp\Resources\HelpArticleResource\Pages\ViewHelpArticle;
$page = new ViewHelpArticle;
$page->record = $helpArticle;

expect($page->getTitle())->toBe('Adding an Evaluation Course');
Expand Down
4 changes: 2 additions & 2 deletions tests/Feature/FrontendHelpTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
$originalSlug = $helpArticle->slug;

$helpArticle->update(['name' => 'Updated Name']);

expect($helpArticle->slug)->not->toBe($originalSlug);
expect($helpArticle->slug)->toBe('updated-name');
});
Expand All @@ -38,7 +38,7 @@
]);

$foundArticle = HelpArticle::where('slug', 'test-article')->first();

expect($foundArticle)->not->toBeNull();
expect($foundArticle->name)->toBe('Test Article');
});
Expand Down
90 changes: 90 additions & 0 deletions tests/Feature/HelpArticleVisibilityTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php

use Filament\Facades\Filament;
use Illuminate\Support\Facades\Route;
use Tapp\FilamentHelp\Models\HelpArticle;
use Tapp\FilamentHelp\Resources\Frontend\HelpArticleResource as FrontendHelpArticleResource;
use Tapp\FilamentHelp\Resources\Frontend\Pages\ViewHelpArticle as FrontendViewHelpArticle;
use Tapp\FilamentHelp\Resources\Guest\HelpArticleResource as GuestHelpArticleResource;
use Tapp\FilamentHelp\Resources\HelpArticleResource\Pages\ViewHelpArticle;

beforeEach(function () {
Filament::setCurrentPanel(Filament::getPanel('admin'));
});

it('shares a private article on the signed-in panel while admin is current', function () {
$article = HelpArticle::factory()->private()->create([
'name' => 'Private policies',
'slug' => 'private-policies',
]);

expect(Route::has('filament.admin.resources.help.view'))->toBeFalse();
expect(shareUrl($article))->toBe(route('filament.app.resources.help.view', ['record' => 'private-policies']));
});

it('shares a public article on the guest panel while admin is current', function () {
$article = HelpArticle::factory()->public()->create([
'name' => 'Public guide',
'slug' => 'public-guide',
]);

expect(shareUrl($article))->toBe(route('filament.guest.resources.help-articles.view', ['record' => 'public-guide']));
});

it('lists private articles for the signed-in help page and hides drafts', function () {
HelpArticle::factory()->private()->create(['slug' => 'private-policies']);
HelpArticle::factory()->public()->create(['slug' => 'public-guide']);
HelpArticle::factory()->private()->hidden()->create(['slug' => 'draft-article']);

Filament::setCurrentPanel(Filament::getPanel('app'));

$slugs = FrontendHelpArticleResource::getEloquentQuery()->pluck('slug');

expect($slugs->all())->toContain('private-policies', 'public-guide')
->not->toContain('draft-article');
});

it('opens a private article on the signed-in help page', function () {
$article = HelpArticle::factory()->private()->create([
'name' => 'Private policies',
'slug' => 'private-policies',
]);

Filament::setCurrentPanel(Filament::getPanel('app'));

$page = new FrontendViewHelpArticle;
$page->mount($article->slug);

expect($page->getRecord()->is($article))->toBeTrue()
->and($page->getTitle())->toBe('Private policies');
});

it('does not open a hidden article on the signed-in help page', function () {
$article = HelpArticle::factory()->hidden()->create([
'slug' => 'draft-article',
]);

$this->get(route('filament.app.resources.help.view', ['record' => $article->slug]))
->assertNotFound();
});

it('does not open a private article on the guest help page', function () {
$article = HelpArticle::factory()->private()->create([
'slug' => 'private-policies',
]);

expect(GuestHelpArticleResource::getEloquentQuery()->pluck('slug')->all())->not->toContain('private-policies');

$this->get(route('filament.guest.resources.help-articles.view', ['record' => $article->slug]))
->assertNotFound();
});

function shareUrl(HelpArticle $article): string
{
$page = new ViewHelpArticle;
$page->record = $article;

$method = new ReflectionMethod(ViewHelpArticle::class, 'getShareUrl');

return $method->invoke($page);
}
33 changes: 25 additions & 8 deletions tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@

namespace Tapp\FilamentHelp\Tests;

use Filament\Actions\ActionsServiceProvider;
use Filament\Facades\Filament;
use Filament\FilamentServiceProvider;
use Filament\Panel;
use Filament\Support\SupportServiceProvider;
use Illuminate\Database\Eloquent\Factories\Factory;
use Livewire\LivewireServiceProvider;
use Orchestra\Testbench\TestCase as Orchestra;
use Tapp\FilamentHelp\FilamentHelpServiceProvider;
use Tapp\FilamentHelp\Resources\HelpArticleResource;

class TestCase extends Orchestra
{
Expand All @@ -24,8 +29,8 @@ protected function getPackageProviders($app)
return [
LivewireServiceProvider::class,
FilamentServiceProvider::class,
\Filament\Actions\ActionsServiceProvider::class,
\Filament\Support\SupportServiceProvider::class,
ActionsServiceProvider::class,
SupportServiceProvider::class,
FilamentHelpServiceProvider::class,
];
}
Expand All @@ -37,27 +42,39 @@ public function getEnvironmentSetUp($app)

$migration = include __DIR__.'/../database/migrations/create_help_articles_table.php.stub';
$migration->up();

$addIsHiddenMigration = include __DIR__.'/../database/migrations/add_is_hidden_to_help_articles_table.php.stub';
$addIsHiddenMigration->up();

// Match an app that puts the signed-in help page on `help` and the guest page on `help-articles`.
\Tapp\FilamentHelp\Resources\Frontend\HelpArticleResource::setSlug('help');
\Tapp\FilamentHelp\Resources\Guest\HelpArticleResource::setSlug('help-articles');

// Set up a basic Filament panel for testing (admin)
\Filament\Facades\Filament::registerPanel(
\Filament\Panel::make()
Filament::registerPanel(
Panel::make()
->id('admin')
->resources([
\Tapp\FilamentHelp\Resources\HelpArticleResource::class,
HelpArticleResource::class,
])
);

// Register the frontend panel for testing (app)
\Filament\Facades\Filament::registerPanel(
\Filament\Panel::make()
Filament::registerPanel(
Panel::make()
->id('app')
->default() // Make 'app' the default panel for frontend tests
->resources([
\Tapp\FilamentHelp\Resources\Frontend\HelpArticleResource::class,
])
);

Filament::registerPanel(
Panel::make()
->id('guest')
->resources([
\Tapp\FilamentHelp\Resources\Guest\HelpArticleResource::class,
])
);
}
}
Loading