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
370 changes: 52 additions & 318 deletions phpstan-baseline.neon

Large diffs are not rendered by default.

29 changes: 16 additions & 13 deletions src/FilamentLibraryPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Filament\Contracts\Plugin;
use Filament\Navigation\NavigationItem;
use Filament\Panel;
use Tapp\FilamentLibrary\Resources\LibraryItemResource;

class FilamentLibraryPlugin implements Plugin
{
Expand Down Expand Up @@ -58,47 +59,49 @@ public static function isLibraryAdmin($user): bool

public function register(Panel $panel): void
{
$panelId = $panel->getId();

$panel
->resources([
\Tapp\FilamentLibrary\Resources\LibraryItemResource::class,
LibraryItemResource::class,
])
->navigationItems([
NavigationItem::make('Library')
->url('/library')
->url(fn () => LibraryItemResource::getUrl('index'))
->icon('heroicon-o-building-library')
->group('Resource Library')
->sort(1)
->isActiveWhen(fn () => request()->is('library')),
->isActiveWhen(fn () => request()->routeIs("filament.{$panelId}.resources.library.index")),
NavigationItem::make('Search All')
->url('/library/search-all')
->url(fn () => LibraryItemResource::getUrl('search-all'))
->icon('heroicon-o-magnifying-glass')
->group('Resource Library')
->sort(2)
->isActiveWhen(fn () => request()->is('library/search-all')),
->isActiveWhen(fn () => request()->routeIs("filament.{$panelId}.resources.library.search-all")),
NavigationItem::make('My Documents')
->url('/library/my-documents')
->url(fn () => LibraryItemResource::getUrl('my-documents'))
->icon('heroicon-o-folder')
->group('Resource Library')
->sort(3)
->isActiveWhen(fn () => request()->is('library/my-documents')),
->isActiveWhen(fn () => request()->routeIs("filament.{$panelId}.resources.library.my-documents")),
NavigationItem::make('Shared with Me')
->url('/library/shared-with-me')
->url(fn () => LibraryItemResource::getUrl('shared-with-me'))
->icon('heroicon-o-share')
->group('Resource Library')
->sort(4)
->isActiveWhen(fn () => request()->is('library/shared-with-me')),
->isActiveWhen(fn () => request()->routeIs("filament.{$panelId}.resources.library.shared-with-me")),
NavigationItem::make('Created by Me')
->url('/library/created-by-me')
->url(fn () => LibraryItemResource::getUrl('created-by-me'))
->icon('heroicon-o-user')
->group('Resource Library')
->sort(5)
->isActiveWhen(fn () => request()->is('library/created-by-me')),
->isActiveWhen(fn () => request()->routeIs("filament.{$panelId}.resources.library.created-by-me")),
NavigationItem::make('Favorites')
->url('/library/favorites')
->url(fn () => LibraryItemResource::getUrl('favorites'))
->icon('heroicon-o-star')
->group('Resource Library')
->sort(6)
->isActiveWhen(fn () => request()->is('library/favorites')),
->isActiveWhen(fn () => request()->routeIs("filament.{$panelId}.resources.library.favorites")),
]);
}

Expand Down
24 changes: 17 additions & 7 deletions src/Middleware/RedirectToCorrectEditPage.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Tapp\FilamentLibrary\Middleware;

use Closure;
use Filament\Facades\Filament;
use Illuminate\Http\Request;
use Tapp\FilamentLibrary\Models\LibraryItem;

Expand All @@ -13,20 +14,29 @@ class RedirectToCorrectEditPage
*/
public function handle(Request $request, Closure $next)
{
// Check if this is an edit route for library items
if ($request->routeIs('filament.admin.resources.library.edit')) {
$panel = Filament::getCurrentPanel();

if (! $panel) {
return $next($request);
}

$panelId = $panel->getId();

// Check if this is an edit route for library items in any panel
if ($request->routeIs("filament.{$panelId}.resources.library.edit")) {
$recordId = $request->route('record');

if ($recordId) {
$libraryItem = LibraryItem::find($recordId);

if ($libraryItem) {
// Redirect to the correct edit page based on type
$editUrl = match ($libraryItem->type) {
'folder' => route('filament.admin.resources.library.edit-folder', ['record' => $recordId]),
'file' => route('filament.admin.resources.library.edit-file', ['record' => $recordId]),
'link' => route('filament.admin.resources.library.edit-link', ['record' => $recordId]),
default => route('filament.admin.resources.library.edit-folder', ['record' => $recordId]),
$type = $libraryItem->type ?? 'folder';
$editUrl = match ($type) {
'folder' => route("filament.{$panelId}.resources.library.edit-folder", ['record' => $recordId]),
'file' => route("filament.{$panelId}.resources.library.edit-file", ['record' => $recordId]),
'link' => route("filament.{$panelId}.resources.library.edit-link", ['record' => $recordId]),
default => route("filament.{$panelId}.resources.library.edit-folder", ['record' => $recordId]),
};

return redirect($editUrl);
Expand Down
55 changes: 41 additions & 14 deletions src/Models/LibraryItem.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,27 @@
use Spatie\MediaLibrary\InteractsWithMedia;
use Spatie\MediaLibrary\MediaCollections\Models\Media;

/**
* @property int $id
* @property string $name
* @property string $slug
* @property string $type
* @property int|null $parent_id
* @property int $created_by
* @property int|null $updated_by
* @property string|null $external_url
* @property string|null $link_description
* @property string|null $general_access
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at
* @property-read LibraryItem|null $parent
* @property-read \Illuminate\Database\Eloquent\Collection<int, LibraryItem> $children
* @property-read \Illuminate\Database\Eloquent\Model $creator
* @property-read \Illuminate\Database\Eloquent\Model|null $updater
* @property-read \Illuminate\Database\Eloquent\Collection<int, LibraryItemPermission> $permissions
* @property-read \Illuminate\Database\Eloquent\Collection<int, LibraryItemTag> $tags
*/
class LibraryItem extends Model implements HasMedia
{
use HasFactory;
Expand Down Expand Up @@ -58,14 +79,16 @@ protected static function boot(): void

static::created(function (self $item) {
// Copy parent folder permissions to the new item
if ($item->parent_id) {
if ($item->parent_id && $item->parent) {
$parentPermissions = $item->parent->permissions()->get();

foreach ($parentPermissions as $permission) {
$item->permissions()->create([
'user_id' => $permission->user_id,
'role' => $permission->role,
]);
if (isset($permission->user_id) && isset($permission->role)) {
$item->permissions()->create([
'user_id' => $permission->user_id,
'role' => $permission->role,
]);
}
}
}
});
Expand Down Expand Up @@ -103,7 +126,9 @@ public function children(): HasMany
*/
public function creator(): BelongsTo
{
return $this->belongsTo(\App\Models\User::class, 'created_by')->withDefault(function () {
$userModel = config('filament-library.user_model', config('auth.providers.users.model', 'App\\Models\\User'));

return $this->belongsTo($userModel, 'created_by')->withDefault(function () {
// Check if 'name' field exists
if (\Illuminate\Support\Facades\Schema::hasColumn('users', 'name')) {
return [
Expand All @@ -126,7 +151,9 @@ public function creator(): BelongsTo
*/
public function updater(): BelongsTo
{
return $this->belongsTo(\App\Models\User::class, 'updated_by');
$userModel = config('filament-library.user_model', config('auth.providers.users.model', 'App\\Models\\User'));

return $this->belongsTo($userModel, 'updated_by');
}

/**
Expand Down Expand Up @@ -199,7 +226,7 @@ public function getEffectiveRole($user): ?string
->where('user_id', $user->id)
->first();

if ($directPermission) {
if ($directPermission && isset($directPermission->role)) {
return $directPermission->role;
}

Expand All @@ -221,13 +248,13 @@ public function getEffectiveRole($user): ?string
/**
* Get the current owner of this item.
*/
public function getCurrentOwner(): ?\App\Models\User
public function getCurrentOwner(): ?\Illuminate\Database\Eloquent\Model
{
$ownerPermission = $this->permissions()
->where('role', 'owner')
->first();

if ($ownerPermission) {
if ($ownerPermission && $ownerPermission->user) {
return $ownerPermission->user;
}

Expand All @@ -248,7 +275,7 @@ public function isCreatorOwner(): bool
/**
* Transfer ownership to another user.
*/
public function transferOwnership(\App\Models\User $newOwner): void
public function transferOwnership(\Illuminate\Database\Eloquent\Model $newOwner): void
{
// Remove existing owner permissions
$this->permissions()->where('role', 'owner')->delete();
Expand All @@ -271,7 +298,7 @@ public function transferOwnership(\App\Models\User $newOwner): void
/**
* Ensure a user has a personal folder (like Google Drive's "My Drive").
*/
public static function ensurePersonalFolder(\App\Models\User $user): self
public static function ensurePersonalFolder(\Illuminate\Database\Eloquent\Model $user): self
{
// Check if user already has a personal folder via the relationship
if ($user->personal_folder_id) {
Expand Down Expand Up @@ -306,7 +333,7 @@ public static function ensurePersonalFolder(\App\Models\User $user): self
/**
* Get a user's personal folder.
*/
public static function getPersonalFolder(\App\Models\User $user): ?self
public static function getPersonalFolder(\Illuminate\Database\Eloquent\Model $user): ?self
{
if (! $user->personal_folder_id) {
return null;
Expand All @@ -318,7 +345,7 @@ public static function getPersonalFolder(\App\Models\User $user): ?self
/**
* Generate the personal folder name for a user.
*/
public static function getPersonalFolderName(\App\Models\User $user): string
public static function getPersonalFolderName(\Illuminate\Database\Eloquent\Model $user): string
{
// Try to get a display name from various user fields
$name = $user->first_name ?? $user->name ?? $user->email ?? 'User';
Expand Down
10 changes: 10 additions & 0 deletions src/Models/LibraryItemPermission.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

/**
* @property int $id
* @property int $library_item_id
* @property int $user_id
* @property string $role
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read LibraryItem $libraryItem
* @property-read \Illuminate\Database\Eloquent\Model $user
*/
class LibraryItemPermission extends Model
{
use HasFactory;
Expand Down
6 changes: 3 additions & 3 deletions src/Resources/LibraryItemResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -293,17 +293,17 @@ public static function table(Table $table): Table
->toolbarActions([
BulkActionGroup::make([
DeleteBulkAction::make()
->visible(fn (): bool => auth()->user() && auth()->user()->can('delete', LibraryItem::class))
->visible(fn (): bool => auth()->user() && auth()->user()->can('deleteAny', LibraryItem::class))
->successRedirectUrl(function () {
// For bulk actions, redirect to current folder (maintain current location)
$currentParent = request()->get('parent');

return static::getUrl('index', $currentParent ? ['parent' => $currentParent] : []);
}),
RestoreBulkAction::make()
->visible(fn (): bool => auth()->user() && auth()->user()->can('delete', LibraryItem::class)),
->visible(fn (): bool => auth()->user() && auth()->user()->can('restoreAny', LibraryItem::class)),
ForceDeleteBulkAction::make()
->visible(fn (): bool => auth()->user() && auth()->user()->can('delete', LibraryItem::class))
->visible(fn (): bool => auth()->user() && auth()->user()->can('forceDeleteAny', LibraryItem::class))
->successRedirectUrl(function () {
// For bulk actions, redirect to current folder (maintain current location)
$currentParent = request()->get('parent');
Expand Down
8 changes: 5 additions & 3 deletions src/Resources/Pages/ListLibraryItems.php
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ protected function getTableQuery(): \Illuminate\Database\Eloquent\Builder

public function getTitle(): string
{
if ($this->parentFolder) {
if ($this->parentFolder && isset($this->parentFolder->name)) {
return $this->parentFolder->name;
}

Expand All @@ -263,7 +263,7 @@ public function getTitle(): string

public function getSubheading(): ?string
{
if ($this->parentFolder && $this->parentFolder->link_description) {
if ($this->parentFolder && isset($this->parentFolder->link_description) && $this->parentFolder->link_description) {
return $this->parentFolder->link_description;
}

Expand Down Expand Up @@ -298,7 +298,9 @@ public function getBreadcrumbs(): array
// Generate URLs more efficiently
$baseUrl = static::getResource()::getUrl('index');
foreach ($path as $folder) {
$breadcrumbs[$baseUrl . '?parent=' . $folder->id] = $folder->name;
if (isset($folder->id) && isset($folder->name)) {
$breadcrumbs[$baseUrl . '?parent=' . $folder->id] = $folder->name;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Schema as SchemaFacade;
use Tapp\FilamentLibrary\Models\LibraryItem;
use Tapp\FilamentLibrary\Models\LibraryItemPermission;

class LibraryItemPermissionsRelationManager extends RelationManager
Expand All @@ -41,7 +42,7 @@ public static function canAccess(): bool

// For non-admins, check if they have share permission on the current record
$record = static::getOwnerRecord();
if ($record && $record->hasPermission($user, 'share')) {
if ($record instanceof LibraryItem && $record->hasPermission($user, 'share')) {
return true;
}

Expand All @@ -50,7 +51,7 @@ public static function canAccess(): bool

public static function canViewForRecord(Model $ownerRecord, string $pageClass): bool
{
return $ownerRecord->hasPermission(auth()->user(), 'share');
return $ownerRecord instanceof LibraryItem && $ownerRecord->hasPermission(auth()->user(), 'share');
}

/**
Expand Down Expand Up @@ -173,28 +174,28 @@ public function table(Table $table): Table
])
->headerActions([
CreateAction::make()
->visible(fn () => $this->ownerRecord->hasPermission(auth()->user(), 'share')),
->visible(fn () => $this->ownerRecord instanceof LibraryItem && $this->ownerRecord->hasPermission(auth()->user(), 'share')),
])
->heading('User Permissions')
->description('Owner: Share and edit. Editor/Viewer: Standard permissions.')
->actions([
->recordActions([
EditAction::make()
->visible(fn () => $this->ownerRecord->hasPermission(auth()->user(), 'share')),
->visible(fn () => $this->ownerRecord instanceof LibraryItem && $this->ownerRecord->hasPermission(auth()->user(), 'share')),
DeleteAction::make()
->visible(fn () => $this->ownerRecord->hasPermission(auth()->user(), 'share')),
->visible(fn () => $this->ownerRecord instanceof LibraryItem && $this->ownerRecord->hasPermission(auth()->user(), 'share')),
])
->bulkActions([
->toolbarActions([
BulkActionGroup::make([
DeleteBulkAction::make()
->visible(fn () => $this->ownerRecord->hasPermission(auth()->user(), 'share')),
->visible(fn () => $this->ownerRecord instanceof LibraryItem && $this->ownerRecord->hasPermission(auth()->user(), 'share')),
]),
])
->emptyStateHeading('No permissions assigned')
->emptyStateDescription('Add users to grant them specific permissions on this item.')
->emptyStateActions([
CreateAction::make()
->label('Add Permission')
->visible(fn () => $this->ownerRecord->hasPermission(auth()->user(), 'share')),
->visible(fn () => $this->ownerRecord instanceof LibraryItem && $this->ownerRecord->hasPermission(auth()->user(), 'share')),
]);
}
}
Loading