From 057945c96d354258fda5e83a4c809721c65d30a4 Mon Sep 17 00:00:00 2001 From: Baspa Date: Fri, 17 Oct 2025 09:20:43 +0200 Subject: [PATCH 01/15] view media --- src/Resources/MediaResource.php | 125 +++++++++++++++++++++++++++++++- 1 file changed, 124 insertions(+), 1 deletion(-) diff --git a/src/Resources/MediaResource.php b/src/Resources/MediaResource.php index 7980de6..3981fa6 100644 --- a/src/Resources/MediaResource.php +++ b/src/Resources/MediaResource.php @@ -9,13 +9,20 @@ use Backstage\Media\Resources\MediaResource\ListMedia; use Filament\Actions\DeleteAction; use Filament\Actions\DeleteBulkAction; +use Filament\Actions\ViewAction; use Filament\Facades\Filament; +use Filament\Infolists\Components\CodeEntry; +use Filament\Infolists\Components\IconEntry; +use Filament\Infolists\Components\ImageEntry; +use Filament\Infolists\Components\TextEntry; use Filament\Resources\Resource; +use Filament\Schemas\Components\Section; use Filament\Schemas\Schema; use Filament\Tables\Columns\IconColumn; use Filament\Tables\Columns\TextColumn; use Filament\Tables\Table; use Illuminate\Support\Str; +use Phiki\Grammar\Grammar; class MediaResource extends Resource { @@ -72,7 +79,7 @@ public static function getNavigationBadge(): ?string if (Filament::hasTenancy() && config('backstage.media.is_tenant_aware')) { return static::getEloquentQuery() - ->where(config('backstage.media.tenant_relationship') . '_ulid', Filament::getTenant()->id) + ->where(config('backstage.media.tenant_relationship').'_ulid', Filament::getTenant()->id) ->count(); } @@ -116,6 +123,122 @@ public static function table(Table $table): Table ]) ->recordActions([ + ViewAction::make() + ->hiddenLabel() + ->tooltip(__('View')) + ->slideOver() + ->schema([ + Section::make(__('File Information')) + ->schema([ + TextEntry::make('original_filename') + ->label(__('Original Filename')) + ->copyable(), + TextEntry::make('filename') + ->label(__('Filename')) + ->copyable(), + TextEntry::make('extension') + ->label(__('Extension')) + ->badge(), + TextEntry::make('mime_type') + ->label(__('MIME Type')) + ->badge(), + TextEntry::make('size') + ->label(__('File Size')) + ->formatStateUsing(function ($state) { + if (! $state) { + return null; + } + + $units = ['B', 'KB', 'MB', 'GB', 'TB']; + $bytes = (int) $state; + + for ($i = 0; $bytes > 1024 && $i < count($units) - 1; $i++) { + $bytes /= 1024; + } + + return round($bytes, 2).' '.$units[$i]; + }), + IconEntry::make('public') + ->label(__('Public')) + ->boolean(), + ]) + ->columns(2), + + Section::make(__('File Preview')) + ->schema([ + ImageEntry::make('url') + ->label(__('Preview')) + ->height(200) + ->visible(fn ($record) => $record && $record->mime_type && str_starts_with($record->mime_type, 'image/')), + TextEntry::make('url') + ->label(__('File URL')) + ->copyable() + ->url(fn ($state) => $state) + ->openUrlInNewTab(), + ]), + + Section::make(__('Technical Details')) + ->schema([ + TextEntry::make('disk') + ->label(__('Storage Disk')) + ->badge(), + TextEntry::make('checksum') + ->label(__('Checksum')) + ->copyable() + ->visible(fn ($record) => $record && $record->checksum), + TextEntry::make('width') + ->label(__('Width')) + ->visible(fn ($record) => $record && $record->width) + ->suffix('px'), + TextEntry::make('height') + ->label(__('Height')) + ->visible(fn ($record) => $record && $record->height) + ->suffix('px'), + TextEntry::make('created_at') + ->label(__('Created At')) + ->dateTime(), + TextEntry::make('updated_at') + ->label(__('Updated At')) + ->dateTime(), + ]) + ->columns(2) + ->collapsible(), + + Section::make(__('Metadata')) + ->schema([ + CodeEntry::make('metadata') + ->label(__('Metadata')) + ->hiddenLabel() + ->formatStateUsing(function ($state) { + if (! $state) { + return null; + } + + // If it's already a string, try to decode it + if (is_string($state)) { + $decoded = json_decode($state, true); + if (json_last_error() === JSON_ERROR_NONE) { + $state = $decoded; + } else { + // If it's not valid JSON, return as-is + return $state; + } + } + + if (empty($state)) { + return null; + } + + // Ensure proper JSON formatting with indentation + return json_encode($state, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + }) + ->grammar(Grammar::Json) + ->visible(fn ($record) => $record && $record->metadata) + ->columnSpanFull() + ->copyable(), + ]) + ->collapsible(), + ]), DeleteAction::make() ->hiddenLabel() ->tooltip(__('Delete')), From 4fd3f9aff369f0dd5c48165e71babef6812d3fc1 Mon Sep 17 00:00:00 2001 From: Baspa Date: Sat, 1 Nov 2025 19:54:16 +0100 Subject: [PATCH 02/15] use custom schema --- src/Resources/MediaResource.php | 231 +++++++++++++++++--------------- 1 file changed, 120 insertions(+), 111 deletions(-) diff --git a/src/Resources/MediaResource.php b/src/Resources/MediaResource.php index 3981fa6..1329af6 100644 --- a/src/Resources/MediaResource.php +++ b/src/Resources/MediaResource.php @@ -79,7 +79,7 @@ public static function getNavigationBadge(): ?string if (Filament::hasTenancy() && config('backstage.media.is_tenant_aware')) { return static::getEloquentQuery() - ->where(config('backstage.media.tenant_relationship').'_ulid', Filament::getTenant()->id) + ->where(config('backstage.media.tenant_relationship') . '_ulid', Filament::getTenant()->id) ->count(); } @@ -128,116 +128,7 @@ public static function table(Table $table): Table ->tooltip(__('View')) ->slideOver() ->schema([ - Section::make(__('File Information')) - ->schema([ - TextEntry::make('original_filename') - ->label(__('Original Filename')) - ->copyable(), - TextEntry::make('filename') - ->label(__('Filename')) - ->copyable(), - TextEntry::make('extension') - ->label(__('Extension')) - ->badge(), - TextEntry::make('mime_type') - ->label(__('MIME Type')) - ->badge(), - TextEntry::make('size') - ->label(__('File Size')) - ->formatStateUsing(function ($state) { - if (! $state) { - return null; - } - - $units = ['B', 'KB', 'MB', 'GB', 'TB']; - $bytes = (int) $state; - - for ($i = 0; $bytes > 1024 && $i < count($units) - 1; $i++) { - $bytes /= 1024; - } - - return round($bytes, 2).' '.$units[$i]; - }), - IconEntry::make('public') - ->label(__('Public')) - ->boolean(), - ]) - ->columns(2), - - Section::make(__('File Preview')) - ->schema([ - ImageEntry::make('url') - ->label(__('Preview')) - ->height(200) - ->visible(fn ($record) => $record && $record->mime_type && str_starts_with($record->mime_type, 'image/')), - TextEntry::make('url') - ->label(__('File URL')) - ->copyable() - ->url(fn ($state) => $state) - ->openUrlInNewTab(), - ]), - - Section::make(__('Technical Details')) - ->schema([ - TextEntry::make('disk') - ->label(__('Storage Disk')) - ->badge(), - TextEntry::make('checksum') - ->label(__('Checksum')) - ->copyable() - ->visible(fn ($record) => $record && $record->checksum), - TextEntry::make('width') - ->label(__('Width')) - ->visible(fn ($record) => $record && $record->width) - ->suffix('px'), - TextEntry::make('height') - ->label(__('Height')) - ->visible(fn ($record) => $record && $record->height) - ->suffix('px'), - TextEntry::make('created_at') - ->label(__('Created At')) - ->dateTime(), - TextEntry::make('updated_at') - ->label(__('Updated At')) - ->dateTime(), - ]) - ->columns(2) - ->collapsible(), - - Section::make(__('Metadata')) - ->schema([ - CodeEntry::make('metadata') - ->label(__('Metadata')) - ->hiddenLabel() - ->formatStateUsing(function ($state) { - if (! $state) { - return null; - } - - // If it's already a string, try to decode it - if (is_string($state)) { - $decoded = json_decode($state, true); - if (json_last_error() === JSON_ERROR_NONE) { - $state = $decoded; - } else { - // If it's not valid JSON, return as-is - return $state; - } - } - - if (empty($state)) { - return null; - } - - // Ensure proper JSON formatting with indentation - return json_encode($state, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); - }) - ->grammar(Grammar::Json) - ->visible(fn ($record) => $record && $record->metadata) - ->columnSpanFull() - ->copyable(), - ]) - ->collapsible(), + ...self::getFormSchema(), ]), DeleteAction::make() ->hiddenLabel() @@ -252,6 +143,124 @@ public static function table(Table $table): Table ->recordUrl(false); } + public static function getFormSchema(): array + { + $schema = [ + Section::make(__('File Information')) + ->schema([ + TextEntry::make('original_filename') + ->label(__('Original Filename')) + ->copyable(), + TextEntry::make('filename') + ->label(__('Filename')) + ->copyable(), + TextEntry::make('extension') + ->label(__('Extension')) + ->badge(), + TextEntry::make('mime_type') + ->label(__('MIME Type')) + ->badge(), + TextEntry::make('size') + ->label(__('File Size')) + ->formatStateUsing(function ($state) { + if (! $state) { + return null; + } + + $units = ['B', 'KB', 'MB', 'GB', 'TB']; + $bytes = (int) $state; + + for ($i = 0; $bytes > 1024 && $i < count($units) - 1; $i++) { + $bytes /= 1024; + } + + return round($bytes, 2) . ' ' . $units[$i]; + }), + IconEntry::make('public') + ->label(__('Public')) + ->boolean(), + ]) + ->columns(2), + + Section::make(__('File Preview')) + ->schema([ + ImageEntry::make('url') + ->label(__('Preview')) + ->height(200) + ->visible(fn ($record) => $record && $record->mime_type && str_starts_with($record->mime_type, 'image/')), + TextEntry::make('url') + ->label(__('File URL')) + ->copyable() + ->url(fn ($state) => $state) + ->openUrlInNewTab(), + ]), + + Section::make(__('Technical Details')) + ->schema([ + TextEntry::make('disk') + ->label(__('Storage Disk')) + ->badge(), + TextEntry::make('checksum') + ->label(__('Checksum')) + ->copyable() + ->visible(fn ($record) => $record && $record->checksum), + TextEntry::make('width') + ->label(__('Width')) + ->visible(fn ($record) => $record && $record->width) + ->suffix('px'), + TextEntry::make('height') + ->label(__('Height')) + ->visible(fn ($record) => $record && $record->height) + ->suffix('px'), + TextEntry::make('created_at') + ->label(__('Created At')) + ->dateTime(), + TextEntry::make('updated_at') + ->label(__('Updated At')) + ->dateTime(), + ]) + ->columns(2) + ->collapsible(), + + Section::make(__('Metadata')) + ->schema([ + CodeEntry::make('metadata') + ->label(__('Metadata')) + ->hiddenLabel() + ->formatStateUsing(function ($state) { + if (! $state) { + return null; + } + + // If it's already a string, try to decode it + if (is_string($state)) { + $decoded = json_decode($state, true); + if (json_last_error() === JSON_ERROR_NONE) { + $state = $decoded; + } else { + // If it's not valid JSON, return as-is + return $state; + } + } + + if (empty($state)) { + return null; + } + + // Ensure proper JSON formatting with indentation + return json_encode($state, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + }) + ->grammar(Grammar::Json) + ->visible(fn ($record) => $record && $record->metadata) + ->columnSpanFull() + ->copyable(), + ]) + ->collapsible(), + ]; + + return $schema; + } + public static function getPages(): array { return [ From a9184f830592365ee36f3be0a20789baffad0a8f Mon Sep 17 00:00:00 2001 From: Baspa Date: Thu, 6 Nov 2025 14:58:53 +0100 Subject: [PATCH 03/15] wip --- README.md | 6 ++++-- composer.json | 20 ++++++++++--------- .../add_alt_column_to_media_table.php.stub | 20 +++++++++++++++++++ 3 files changed, 35 insertions(+), 11 deletions(-) create mode 100644 database/migrations/add_alt_column_to_media_table.php.stub diff --git a/README.md b/README.md index b54ec11..db1ce90 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,8 @@ You can publish and run the migrations with: ```bash php artisan vendor:publish --tag="media-migrations" +php artisan vendor:publish --provider="Backstage\Translations\Laravel\TranslationServiceProvider" +php artisan migrate ``` > [!NOTE] @@ -228,8 +230,8 @@ Please review [our security policy](../../security/policy) on how to report secu ## Credits -- [Baspa](https://github.com/vormkracht10) -- [All Contributors](../../contributors) +- [Baspa](https://github.com/vormkracht10) +- [All Contributors](../../contributors) ## License diff --git a/composer.json b/composer.json index 73ec7bb..9e99257 100644 --- a/composer.json +++ b/composer.json @@ -21,9 +21,11 @@ ], "require": { "php": "^8.1", + "backstage/fields": "@dev", + "backstage/laravel-translations": "^0.3.2", "filament/filament": "^4.0", - "spatie/laravel-package-tools": "^1.15.0", - "backstage/fields": "^0.8.0" + "phiki/phiki": "^2.0", + "spatie/laravel-package-tools": "^1.15.0" }, "require-dev": { "laravel/pint": "^1.0", @@ -59,6 +61,7 @@ "sort-packages": true, "allow-plugins": { "pestphp/pest-plugin": true, + "php-http/discovery": true, "phpstan/extension-installer": true } }, @@ -73,17 +76,16 @@ } }, "repositories": { - "backstage/fields": { - "type": "git", - "url": "git@github.com:backstagephp/fields.git" - }, "backstage/cms": { "type": "git", "url": "https://github.com/backstagephp/core.git" }, - "saade/filament-adjacency-list": { - "type": "git", - "url": "git@github.com:backstagephp/filament-adjacency-list.git" + "backstage/fields": { + "type": "path", + "url": "../fields", + "options": { + "symlink": true + } } }, "minimum-stability": "dev", diff --git a/database/migrations/add_alt_column_to_media_table.php.stub b/database/migrations/add_alt_column_to_media_table.php.stub new file mode 100644 index 0000000..5b11c9a --- /dev/null +++ b/database/migrations/add_alt_column_to_media_table.php.stub @@ -0,0 +1,20 @@ +getTable(), function (Blueprint $table) { + $table->text('alt')->after('height'); + }); + } + + public function down(): void + { + Schema::dropIfExists(app(config('backstage.media.model'))->getTable()); + } +}; \ No newline at end of file From 950d212b5c543661c6c00c90682996b09b9de541 Mon Sep 17 00:00:00 2001 From: Baspa Date: Thu, 6 Nov 2025 14:59:32 +0100 Subject: [PATCH 04/15] fix morph map --- src/MediaServiceProvider.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/MediaServiceProvider.php b/src/MediaServiceProvider.php index 07d7af3..b3ca093 100644 --- a/src/MediaServiceProvider.php +++ b/src/MediaServiceProvider.php @@ -73,7 +73,7 @@ public function packageBooted(): void // Handle Stubs if (app()->runningInConsole()) { - foreach (app(Filesystem::class)->files(__DIR__ . '/../stubs/') as $file) { + foreach (app(Filesystem::class)->files(__DIR__.'/../stubs/') as $file) { $this->publishes([ $file->getRealPath() => base_path("stubs/media/{$file->getFilename()}"), ], 'media-stubs'); @@ -81,7 +81,7 @@ public function packageBooted(): void } Relation::enforceMorphMap([ - 'media' => 'Backstage\Media\Models\Media', + 'media' => config('backstage.media.model'), ]); // Testing @@ -101,7 +101,7 @@ protected function getAssets(): array return [ // AlpineComponent::make('media', __DIR__ . '/../resources/dist/components/media.js'), // Css::make('media-styles', __DIR__ . '/../resources/dist/media.css'), - Js::make('media-scripts', __DIR__ . '/../resources/dist/media.js'), + Js::make('media-scripts', __DIR__.'/../resources/dist/media.js'), ]; } @@ -146,6 +146,7 @@ protected function getMigrations(): array 'create_media_table', 'create_media_relationships_table', 'add_tenant_aware_column_to_media_table', + 'add_alt_column_to_media_table', ]; } } From 02e109d4050afcbe70864a590a5e5cb2ba6f1c3d Mon Sep 17 00:00:00 2001 From: Baspa <10845460+Baspa@users.noreply.github.com> Date: Thu, 6 Nov 2025 13:59:53 +0000 Subject: [PATCH 05/15] Fix styling --- src/MediaServiceProvider.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MediaServiceProvider.php b/src/MediaServiceProvider.php index b3ca093..927e2c4 100644 --- a/src/MediaServiceProvider.php +++ b/src/MediaServiceProvider.php @@ -73,7 +73,7 @@ public function packageBooted(): void // Handle Stubs if (app()->runningInConsole()) { - foreach (app(Filesystem::class)->files(__DIR__.'/../stubs/') as $file) { + foreach (app(Filesystem::class)->files(__DIR__ . '/../stubs/') as $file) { $this->publishes([ $file->getRealPath() => base_path("stubs/media/{$file->getFilename()}"), ], 'media-stubs'); @@ -101,7 +101,7 @@ protected function getAssets(): array return [ // AlpineComponent::make('media', __DIR__ . '/../resources/dist/components/media.js'), // Css::make('media-styles', __DIR__ . '/../resources/dist/media.css'), - Js::make('media-scripts', __DIR__.'/../resources/dist/media.js'), + Js::make('media-scripts', __DIR__ . '/../resources/dist/media.js'), ]; } From 9aae5e46b1f6008d2a91348b471ab8da4c646ba8 Mon Sep 17 00:00:00 2001 From: Baspa Date: Thu, 6 Nov 2025 15:30:45 +0100 Subject: [PATCH 06/15] fix symlinks --- composer.json | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 9e99257..2454016 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ ], "require": { "php": "^8.1", - "backstage/fields": "@dev", + "backstage/fields": "^0.8.0", "backstage/laravel-translations": "^0.3.2", "filament/filament": "^4.0", "phiki/phiki": "^2.0", @@ -81,11 +81,8 @@ "url": "https://github.com/backstagephp/core.git" }, "backstage/fields": { - "type": "path", - "url": "../fields", - "options": { - "symlink": true - } + "type": "git", + "url": "https://github.com/backstagephp/fields.git" } }, "minimum-stability": "dev", From 8ffa1322731f6921320573d1dd6d3502113a2295 Mon Sep 17 00:00:00 2001 From: Baspa Date: Thu, 6 Nov 2025 15:41:41 +0100 Subject: [PATCH 07/15] only support PHP 8.3 and up --- .github/workflows/phpstan.yml | 2 +- .github/workflows/run-tests.yml | 2 +- composer.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index 3855a08..d9306d6 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -16,7 +16,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.1' + php-version: '8.3' coverage: none - name: Install composer dependencies diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 7c60f1f..1053cba 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -13,7 +13,7 @@ jobs: fail-fast: true matrix: os: [ubuntu-latest, windows-latest] - php: [8.2, 8.1] + php: [8.3] laravel: [10.*] stability: [prefer-lowest, prefer-stable] include: diff --git a/composer.json b/composer.json index 2454016..ec95c71 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.3", "backstage/fields": "^0.8.0", "backstage/laravel-translations": "^0.3.2", "filament/filament": "^4.0", From cfea5c20529dd939afb5b1904915d8fcc9d59451 Mon Sep 17 00:00:00 2001 From: Baspa Date: Thu, 6 Nov 2025 15:43:38 +0100 Subject: [PATCH 08/15] use correct Laravel versions --- .github/workflows/run-tests.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 1053cba..39901fe 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -14,12 +14,12 @@ jobs: matrix: os: [ubuntu-latest, windows-latest] php: [8.3] - laravel: [10.*] + laravel: [11.*] stability: [prefer-lowest, prefer-stable] include: - - laravel: 10.* - testbench: 8.* - carbon: 2.* + - laravel: 11.* + testbench: 9.* + carbon: 3.* name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }} From 54242829807447a4efa645c977f0bb2ebc8d9a49 Mon Sep 17 00:00:00 2001 From: Baspa Date: Thu, 6 Nov 2025 15:46:42 +0100 Subject: [PATCH 09/15] wip --- .phpunit.cache/test-results | 1 + tests/TestCase.php | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 .phpunit.cache/test-results diff --git a/.phpunit.cache/test-results b/.phpunit.cache/test-results new file mode 100644 index 0000000..fe50f57 --- /dev/null +++ b/.phpunit.cache/test-results @@ -0,0 +1 @@ +{"version":"pest_2.36.0","defects":[],"times":{"P\\Tests\\ExampleTest::__pest_evaluable_it_can_test":0.021,"P\\Tests\\ArchTest::__pest_evaluable_it_will_not_use_debugging_functions":0.193}} \ No newline at end of file diff --git a/tests/TestCase.php b/tests/TestCase.php index e4fca90..70bb390 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -20,6 +20,8 @@ class TestCase extends Orchestra { + protected static $latestResponse; + protected function setUp(): void { parent::setUp(); From acf401104341987c8b644cfa0a099dbb46c29208 Mon Sep 17 00:00:00 2001 From: Baspa Date: Thu, 6 Nov 2025 15:48:16 +0100 Subject: [PATCH 10/15] fix invalid config --- phpstan.neon.dist | 1 - 1 file changed, 1 deletion(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index a91953b..260b5e1 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -10,5 +10,4 @@ parameters: tmpDir: build/phpstan checkOctaneCompatibility: true checkModelProperties: true - checkMissingIterableValueType: false From 1d59cbc8a8ee9989d2a950982d9df529f902de92 Mon Sep 17 00:00:00 2001 From: Baspa Date: Thu, 6 Nov 2025 15:59:18 +0100 Subject: [PATCH 11/15] fix: phpstan errors --- config/backstage/media.php | 2 +- phpstan.neon.dist | 2 ++ src/Concerns/HasMedia.php | 3 +++ src/Media.php | 9 ++++++--- src/MediaPlugin.php | 2 +- src/Models/Media.php | 20 +++++++++++++++++++- src/Pages/Media/Library.php | 2 +- src/Resources/MediaResource.php | 14 +++++++++----- src/Resources/MediaResource/CreateMedia.php | 13 ++++++++++--- src/Resources/MediaResource/EditMedia.php | 2 +- 10 files changed, 53 insertions(+), 16 deletions(-) diff --git a/config/backstage/media.php b/config/backstage/media.php index 1fa9091..9dae345 100644 --- a/config/backstage/media.php +++ b/config/backstage/media.php @@ -21,7 +21,7 @@ 'directory' => 'media', - 'disk' => env('FILAMENT_FILESYSTEM_DISK', 'public'), + 'disk' => config('filesystems.default', 'public'), 'should_preserve_filenames' => false, diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 260b5e1..7980f31 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -10,4 +10,6 @@ parameters: tmpDir: build/phpstan checkOctaneCompatibility: true checkModelProperties: true + ignoreErrors: + - identifier: trait.unused diff --git a/src/Concerns/HasMedia.php b/src/Concerns/HasMedia.php index 20faa6f..2c76f64 100644 --- a/src/Concerns/HasMedia.php +++ b/src/Concerns/HasMedia.php @@ -6,6 +6,9 @@ use Illuminate\Database\Eloquent\Relations\MorphToMany; use Illuminate\Support\Collection; +/** + * @mixin Model + */ trait HasMedia { /** diff --git a/src/Media.php b/src/Media.php index c550a57..6aad62f 100644 --- a/src/Media.php +++ b/src/Media.php @@ -51,14 +51,15 @@ public static function create(array | string $data): array } } - $media[] = Model::updateOrCreate([ - 'site_ulid' => Filament::getTenant()->ulid, + $tenant = Filament::getTenant(); + $mediaModel = Model::updateOrCreate([ + 'site_ulid' => $tenant && property_exists($tenant, 'ulid') ? $tenant->ulid : null, 'disk' => config('backstage.media.disk'), 'original_filename' => pathinfo($filename, PATHINFO_FILENAME), 'checksum' => md5_file($fullPath), ], [ 'filename' => $filename, - 'uploaded_by' => auth()->user()->id, + 'uploaded_by' => auth()->user()?->id, 'extension' => $extension, 'mime_type' => $mimeType, 'size' => $fileSize, @@ -66,6 +67,8 @@ public static function create(array | string $data): array 'height' => $fileInfo['height'] ?? null, 'public' => config('backstage.media.visibility') === 'public', ]); + + $media[] = $mediaModel; } return $media; diff --git a/src/MediaPlugin.php b/src/MediaPlugin.php index 72a4cd3..a846645 100644 --- a/src/MediaPlugin.php +++ b/src/MediaPlugin.php @@ -200,7 +200,7 @@ public function getTenantModel(): ?string } /** - * @return class-string + * @return class-string */ public function getModelItem(): string { diff --git a/src/Models/Media.php b/src/Models/Media.php index 7c272e1..e7a9d14 100644 --- a/src/Models/Media.php +++ b/src/Models/Media.php @@ -12,6 +12,24 @@ use Illuminate\Support\Facades\Storage; use Symfony\Component\HttpFoundation\StreamedResponse; +/** + * @property string $ulid + * @property string $filename + * @property string $path + * @property string $mime_type + * @property int $size + * @property int|null $width + * @property int|null $height + * @property string|null $alt + * @property array|null $metadata + * @property int|null $uploaded_by + * @property string|null $tenant_ulid + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property \Illuminate\Support\Carbon|null $deleted_at + * @property-read string $humanReadableSize + * @property-read string $src + */ class Media extends Model { use HasUlids; @@ -68,7 +86,7 @@ protected static function booted(): void if ($tenantRelationship && class_exists($tenantModel)) { $currentTenant = Filament::getTenant(); - if ($currentTenant) { + if ($currentTenant && property_exists($currentTenant, 'ulid')) { $model->{$tenantRelationship . '_ulid'} = $currentTenant->ulid; } } diff --git a/src/Pages/Media/Library.php b/src/Pages/Media/Library.php index c4b40b3..147047d 100644 --- a/src/Pages/Media/Library.php +++ b/src/Pages/Media/Library.php @@ -90,7 +90,7 @@ protected function getActions(): array public static function getNavigationLabel(): string { - return MediaPlugin::get()->getNavigationLabel() ?? Str::title(static::getPluralModelLabel()) ?? Str::title(static::getModelLabel()); + return MediaPlugin::get()->getNavigationLabel() ?? __('Media Library'); } public static function getNavigationIcon(): string diff --git a/src/Resources/MediaResource.php b/src/Resources/MediaResource.php index 1329af6..1aae135 100644 --- a/src/Resources/MediaResource.php +++ b/src/Resources/MediaResource.php @@ -53,7 +53,7 @@ public static function getPluralModelLabel(): string public static function getNavigationLabel(): string { - return MediaPlugin::get()->getNavigationLabel() ?? Str::title(static::getPluralModelLabel()) ?? Str::title(static::getModelLabel()); + return MediaPlugin::get()->getNavigationLabel() ?: (Str::title(static::getPluralModelLabel()) ?: Str::title(static::getModelLabel())); } public static function getNavigationIcon(): string @@ -78,12 +78,16 @@ public static function getNavigationBadge(): ?string } if (Filament::hasTenancy() && config('backstage.media.is_tenant_aware')) { - return static::getEloquentQuery() - ->where(config('backstage.media.tenant_relationship') . '_ulid', Filament::getTenant()->id) + $tenant = Filament::getTenant(); + $tenantId = $tenant && property_exists($tenant, 'id') ? $tenant->id : null; + $count = static::getEloquentQuery() + ->where(config('backstage.media.tenant_relationship') . '_ulid', $tenantId) ->count(); + + return (string) $count; } - return number_format(static::getModel()::count()); + return (string) static::getModel()::count(); } public static function shouldRegisterNavigation(): bool @@ -186,7 +190,7 @@ public static function getFormSchema(): array ->schema([ ImageEntry::make('url') ->label(__('Preview')) - ->height(200) + ->imageHeight(200) ->visible(fn ($record) => $record && $record->mime_type && str_starts_with($record->mime_type, 'image/')), TextEntry::make('url') ->label(__('File URL')) diff --git a/src/Resources/MediaResource/CreateMedia.php b/src/Resources/MediaResource/CreateMedia.php index a121895..b29a234 100644 --- a/src/Resources/MediaResource/CreateMedia.php +++ b/src/Resources/MediaResource/CreateMedia.php @@ -19,6 +19,8 @@ public static function getResource(): string public function handleRecordCreation(array $data): Model { + $firstMedia = null; + foreach ($data['media'] as $file) { // Get the full path on the configured disk $fullPath = Storage::disk(config('backstage.media.disk'))->path($file); @@ -53,8 +55,9 @@ public function handleRecordCreation(array $data): Model } } - $first = Media::create([ - 'site_ulid' => Filament::getTenant()->ulid, + $tenant = Filament::getTenant(); + $media = Media::create([ + 'site_ulid' => $tenant && property_exists($tenant, 'ulid') ? $tenant->ulid : null, 'disk' => config('backstage.media.disk'), 'uploaded_by' => auth()->id(), 'filename' => $filename, @@ -66,9 +69,13 @@ public function handleRecordCreation(array $data): Model 'checksum' => md5_file($fullPath), 'public' => config('backstage.media.visibility') === 'public', // TODO: Should be configurable in the form itself ]); + + if ($firstMedia === null) { + $firstMedia = $media; + } } - return $first; + return $firstMedia ?? Media::first(); // return static::getModel()::create($data); } } diff --git a/src/Resources/MediaResource/EditMedia.php b/src/Resources/MediaResource/EditMedia.php index ea17b6d..81e5933 100644 --- a/src/Resources/MediaResource/EditMedia.php +++ b/src/Resources/MediaResource/EditMedia.php @@ -23,7 +23,7 @@ public function getHeaderActions(): array Action::make('preview') ->label(__('Preview')) ->color('gray') - ->url($this->record->url, shouldOpenInNewTab: true), + ->url(fn () => (is_object($this->record) && property_exists($this->record, 'url')) ? $this->record->url : null, shouldOpenInNewTab: true), DeleteAction::make(), ]; } From 0d2168a989ba110160fd56e65acde323b7171257 Mon Sep 17 00:00:00 2001 From: Baspa <10845460+Baspa@users.noreply.github.com> Date: Thu, 6 Nov 2025 14:59:44 +0000 Subject: [PATCH 12/15] Fix styling --- src/Pages/Media/Library.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Pages/Media/Library.php b/src/Pages/Media/Library.php index 147047d..a26218c 100644 --- a/src/Pages/Media/Library.php +++ b/src/Pages/Media/Library.php @@ -14,7 +14,6 @@ use Filament\Schemas\Schema; use Filament\Support\Enums\Width; use Illuminate\Support\Collection; -use Illuminate\Support\Str; use Livewire\WithPagination; class Library extends Page implements HasForms From 0f9738dc7aa663cf771e0b12014f7d18d51d3461 Mon Sep 17 00:00:00 2001 From: Baspa Date: Thu, 6 Nov 2025 16:05:49 +0100 Subject: [PATCH 13/15] wip --- .github/workflows/phpstan.yml | 1 + .github/workflows/run-tests.yml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index d9306d6..337c133 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -17,6 +17,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: '8.3' + extensions: sockets coverage: none - name: Install composer dependencies diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 39901fe..073786e 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -31,7 +31,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo + extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo, sockets coverage: none - name: Setup problem matchers From 9cdd26ba58863bcd012f6d62b60f8cdc9ac76d6f Mon Sep 17 00:00:00 2001 From: Baspa Date: Thu, 6 Nov 2025 16:10:51 +0100 Subject: [PATCH 14/15] wip --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 073786e..3656254 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -42,7 +42,7 @@ jobs: - name: Install dependencies run: | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" "nesbot/carbon:${{ matrix.carbon }}" --no-interaction --no-update - composer update --${{ matrix.stability }} --prefer-dist --no-interaction + composer update --${{ matrix.stability }} --prefer-dist --no-interaction --ignore-platform-req=ext-pcntl - name: List Installed Dependencies run: composer show -D From 2142d6e95953201ba6ee039be285562f4e9ac453 Mon Sep 17 00:00:00 2001 From: Bas van Dinther Date: Thu, 6 Nov 2025 16:16:48 +0100 Subject: [PATCH 15/15] Delete .phpunit.cache/test-results --- .phpunit.cache/test-results | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .phpunit.cache/test-results diff --git a/.phpunit.cache/test-results b/.phpunit.cache/test-results deleted file mode 100644 index fe50f57..0000000 --- a/.phpunit.cache/test-results +++ /dev/null @@ -1 +0,0 @@ -{"version":"pest_2.36.0","defects":[],"times":{"P\\Tests\\ExampleTest::__pest_evaluable_it_can_test":0.021,"P\\Tests\\ArchTest::__pest_evaluable_it_will_not_use_debugging_functions":0.193}} \ No newline at end of file