Merge branch 'filamentphp:3.x' into configurable-export-routes

This commit is contained in:
Raj Siva-Rajah
2025-03-31 18:33:19 +10:30
committed by GitHub
32 changed files with 962 additions and 332 deletions
@@ -0,0 +1,101 @@
name: Unexpected v4 breaking change
description: If a breaking change is not documented in the upgrade guide and not automatically upgraded by the script, report it here
labels: unexpected major version breaking change,unconfirmed,high priority
projects: ['filamentphp/2']
body:
- type: markdown
attributes:
value: |
Before opening a report, please search the existing issues (both open and closed).
---
Thank you for taking the time to file a report. To address this issue as fast as possible, we need some information.
- type: dropdown
id: package
attributes:
label: Package
description: Which package is this report for?
options:
- filament/actions
- filament/filament
- filament/forms
- filament/infolists
- filament/notifications
- filament/spark-billing-provider
- filament/spatie-laravel-google-fonts-plugin
- filament/spatie-laravel-media-library-plugin
- filament/spatie-laravel-settings-plugin
- filament/spatie-laravel-tags-plugin
- filament/spatie-laravel-translatable-plugin
- filament/support
- filament/tables
- filament/widgets
- Other
default: 1
validations:
required: true
- type: input
id: package-version
attributes:
label: Package Version
description: Please provide the full version of the package you have installed.
placeholder: v3.0.0
validations:
required: true
- type: input
id: laravel-version
attributes:
label: Laravel Version
description: Please provide the full Laravel version of your project.
placeholder: v10.0.0
validations:
required: true
- type: input
id: livewire-version
attributes:
label: Livewire Version
description: Please provide the full Livewire version of your project, if applicable.
placeholder: v3.0.0
- type: input
id: php-version
attributes:
label: PHP Version
description: Please provide the full PHP version of your server.
placeholder: PHP 8.3.0
validations:
required: true
- type: textarea
id: description
attributes:
label: Breaking change description
description: What happened when you experienced the breaking change?
validations:
required: true
- type: textarea
id: expectation
attributes:
label: Expected behavior
description: What did you expect to happen instead?
validations:
required: true
- type: textarea
id: steps
attributes:
label: Steps to reproduce
description: Which steps do we need to take to reproduce the problem? Any code examples need to be **as short as possible**, remove any code that is unrelated to the issue. **This issue will be automatically closed and not reviewed if detailed replication steps are missing.**
validations:
required: true
- type: input
id: reproduction
attributes:
label: Reproduction repository (issue will be closed if this is not valid)
description: The URL of a public GitHub repository which reproduces the problem. **Please do not link to your actual project**, what we need instead is a _minimal_ reproduction in a fresh project without any unnecessary code. This means it doesn\'t matter if your real project is private / confidential, since we want a link to a separate, isolated reproduction. This allows us to fix the problem much quicker. **This issue will be automatically closed and not reviewed if this is missing. Please make sure to format the URL starting with `https://github.com` - only repositories hosted on GitHub are accepted.** [Need a headstart? We have a template Filament project for you.](https://filament-issue.unitedbycode.com)
validations:
required: true
- type: textarea
id: logs
attributes:
label: Relevant log output
description: If applicable, provide relevant log output. No need for backticks here.
render: shell
+16
View File
@@ -0,0 +1,16 @@
name: 'Deploy website on release'
on:
release:
types: [published]
jobs:
forge-deploy:
name: 'Laravel Forge Deploy'
runs-on: ubuntu-latest
steps:
- name: Deploy
uses: jbrooksuk/laravel-forge-action@v1.0.2
with:
trigger_url: ${{ secrets.WEBSITE_DEPLOYMENT_TRIGGER_URL }}
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v41
uses: tj-actions/changed-files@v46
with:
files: |
packages/**/resources/lang/en/**
+648 -277
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -9,10 +9,10 @@
"@tailwindcss/forms": "^0.5.2",
"@tailwindcss/typography": "^0.5.4",
"autoprefixer": "^10.4.7",
"laravel-vite-plugin": "^0.7.2",
"laravel-vite-plugin": "^1.2.0",
"postcss": "^8.4.14",
"postcss-nesting": "^13.0.0",
"tailwindcss": "^3.4.0",
"vite": "^4.5.9"
"vite": "^6.2.3"
}
}
@@ -102,8 +102,6 @@ trait InteractsWithActions
$result = $action->callAfter() ?? $result;
$this->afterActionCalled();
$action->commitDatabaseTransaction();
} catch (Halt $exception) {
$exception->shouldRollbackDatabaseTransaction() ?
$action->rollBackDatabaseTransaction() :
@@ -131,6 +129,8 @@ trait InteractsWithActions
throw $exception;
}
$action->commitDatabaseTransaction();
if (store($this)->has('redirect')) {
return $result;
}
@@ -25,7 +25,12 @@ class XlsxDownloader implements Downloader
if ($disk->exists($filePath = $directory . DIRECTORY_SEPARATOR . $fileName)) {
$response = $disk->download($filePath);
ob_end_clean();
if (ob_get_length() > 0) {
ob_end_clean();
}
$response->headers->set('X-Vapor-Base64-Encode', 'True');
return $response;
}
File diff suppressed because one or more lines are too long
@@ -754,21 +754,30 @@ export default function fileUploadFormComponent({
}
}
import am from 'filepond/locale/am-et'
import ar from 'filepond/locale/ar-ar'
import az from 'filepond/locale/az-az'
import ca from 'filepond/locale/ca-ca'
import ckb from 'filepond/locale/ku-ckb'
import cs from 'filepond/locale/cs-cz'
import da from 'filepond/locale/da-dk'
import de from 'filepond/locale/de-de'
import el from 'filepond/locale/el-el'
import en from 'filepond/locale/en-en'
import es from 'filepond/locale/es-es'
import fa from 'filepond/locale/fa_ir'
import fi from 'filepond/locale/fi-fi'
import fr from 'filepond/locale/fr-fr'
import he from 'filepond/locale/he-he'
import hr from 'filepond/locale/hr-hr'
import hu from 'filepond/locale/hu-hu'
import id from 'filepond/locale/id-id'
import it from 'filepond/locale/it-it'
import ja from 'filepond/locale/ja-ja'
import km from 'filepond/locale/km-km'
import ko from 'filepond/locale/ko-kr'
import lt from 'filepond/locale/lt-lt'
import lv from 'filepond/locale/lv-lv'
import nl from 'filepond/locale/nl-nl'
import no from 'filepond/locale/no_nb'
import pl from 'filepond/locale/pl-pl'
@@ -776,6 +785,7 @@ import pt_BR from 'filepond/locale/pt-br'
import pt_PT from 'filepond/locale/pt-br'
import ro from 'filepond/locale/ro-ro'
import ru from 'filepond/locale/ru-ru'
import sk from 'filepond/locale/sk-sk'
import sv from 'filepond/locale/sv_se'
import tr from 'filepond/locale/tr-tr'
import uk from 'filepond/locale/uk-ua'
@@ -784,21 +794,30 @@ import zh_CN from 'filepond/locale/zh-cn'
import zh_TW from 'filepond/locale/zh-tw'
const locales = {
am,
ar,
az,
ca,
ckb,
cs,
da,
de,
el,
en,
es,
fa,
fi,
fr,
he,
hr,
hu,
id,
it,
ja,
km,
ko,
lt,
lv,
nl,
no,
pl,
@@ -806,6 +825,7 @@ const locales = {
pt_PT,
ro,
ru,
sk,
sv,
tr,
uk,
@@ -8,7 +8,6 @@ use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Facades\Storage;
use League\Flysystem\UnableToCheckFileExistence;
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
use SplFileInfo;
use Throwable;
trait HasFileAttachments
@@ -97,7 +96,7 @@ trait HasFileAttachments
return $this->evaluate($this->fileAttachmentsVisibility);
}
protected function handleFileAttachmentUpload(SplFileInfo $file): mixed
protected function handleFileAttachmentUpload(TemporaryUploadedFile $file): mixed
{
$storeMethod = $this->getFileAttachmentsVisibility() === 'public' ? 'storePublicly' : 'store';
@@ -117,15 +116,15 @@ trait HasFileAttachments
return null;
}
if ($storage->getVisibility($file) === 'private') {
try {
try {
if ($storage->getVisibility($file) === 'private') {
return $storage->temporaryUrl(
$file,
now()->addMinutes(5),
);
} catch (Throwable $exception) {
// This driver does not support creating temporary URLs.
}
} catch (Throwable $exception) {
// This driver does not support creating temporary URLs.
}
return $storage->url($file);
@@ -81,8 +81,6 @@ trait HasFormComponentActions
]);
$result = $action->callAfter() ?? $result;
$action->commitDatabaseTransaction();
} catch (Halt $exception) {
$exception->shouldRollbackDatabaseTransaction() ?
$action->rollBackDatabaseTransaction() :
@@ -110,6 +108,8 @@ trait HasFormComponentActions
throw $exception;
}
$action->commitDatabaseTransaction();
if (store($this)->has('redirect')) {
return $result;
}
@@ -98,7 +98,7 @@
>
@foreach ($limitedState as $stateItem)
<img
src="{{ filled($stateItem) ? $getImageUrl($stateItem) : $defaultImageUrl }}"
src="{{ filled($stateItem) ? ($getImageUrl($stateItem) ?? $defaultImageUrl) : $defaultImageUrl }}"
{{
$getExtraImgAttributeBag()
->class([
@@ -124,8 +124,6 @@ trait InteractsWithInfolists
]);
$result = $action->callAfter() ?? $result;
$action->commitDatabaseTransaction();
} catch (Halt $exception) {
$exception->shouldRollbackDatabaseTransaction() ?
$action->rollBackDatabaseTransaction() :
@@ -153,6 +151,8 @@ trait InteractsWithInfolists
throw $exception;
}
$action->commitDatabaseTransaction();
if (store($this)->has('redirect')) {
return $result;
}
@@ -170,6 +170,45 @@ public function infolist(Infolist $infolist): Infolist
}
```
## Customizing relation managers for a specific view page
You can specify which relation managers should appear on a view page by defining a `getAllRelationManagers()` method:
```php
protected function getAllRelationManagers(): array
{
return [
CustomerAddressesRelationManager::class,
CustomerContactsRelationManager::class,
];
}
```
This is useful when you have [multiple view pages](#creating-another-view-page) and need different relation managers on
each page:
```php
// ViewCustomer.php
protected function getAllRelationManagers(): array
{
return [
RelationManagers\OrdersRelationManager::class,
RelationManagers\SubscriptionsRelationManager::class,
];
}
// ViewCustomerContact.php
protected function getAllRelationManagers(): array
{
return [
RelationManagers\ContactsRelationManager::class,
RelationManagers\AddressesRelationManager::class,
];
}
```
If `getAllRelationManagers()` isn't defined, any relation managers defined in the resource will be used.
## Adding view pages to resource sub-navigation
If you're using [resource sub-navigation](getting-started#resource-sub-navigation), you can register this page as normal in `getRecordSubNavigation()` of the resource:
@@ -160,8 +160,6 @@ class EditProfile extends Page
$this->handleRecordUpdate($this->getUser(), $data);
$this->callHook('afterSave');
$this->commitDatabaseTransaction();
} catch (Halt $exception) {
$exception->shouldRollbackDatabaseTransaction() ?
$this->rollBackDatabaseTransaction() :
@@ -174,6 +172,8 @@ class EditProfile extends Page
throw $exception;
}
$this->commitDatabaseTransaction();
if (request()->hasSession() && array_key_exists('password', $data)) {
request()->session()->put([
'password_hash_' . Filament::getAuthGuard() => $data['password'],
@@ -125,8 +125,6 @@ abstract class EditTenantProfile extends Page
$this->handleRecordUpdate($this->tenant, $data);
$this->callHook('afterSave');
$this->commitDatabaseTransaction();
} catch (Halt $exception) {
$exception->shouldRollbackDatabaseTransaction() ?
$this->rollBackDatabaseTransaction() :
@@ -139,6 +137,8 @@ abstract class EditTenantProfile extends Page
throw $exception;
}
$this->commitDatabaseTransaction();
$this->getSavedNotification()?->send();
if ($redirectUrl = $this->getRedirectUrl()) {
@@ -91,8 +91,6 @@ abstract class RegisterTenant extends SimplePage
$this->form->model($this->tenant)->saveRelationships();
$this->callHook('afterRegister');
$this->commitDatabaseTransaction();
} catch (Halt $exception) {
$exception->shouldRollbackDatabaseTransaction() ?
$this->rollBackDatabaseTransaction() :
@@ -105,6 +103,8 @@ abstract class RegisterTenant extends SimplePage
throw $exception;
}
$this->commitDatabaseTransaction();
if ($redirectUrl = $this->getRedirectUrl()) {
$this->redirect($redirectUrl, navigate: FilamentView::hasSpaMode() && is_app_url($redirectUrl));
}
@@ -2,13 +2,14 @@
namespace Filament\Panel\Concerns;
use Closure;
use Filament\AvatarProviders\UiAvatarsProvider;
trait HasAvatars
{
protected string $defaultAvatarProvider = UiAvatarsProvider::class;
protected string | Closure $defaultAvatarProvider = UiAvatarsProvider::class;
public function defaultAvatarProvider(string $provider): static
public function defaultAvatarProvider(string | Closure $provider): static
{
$this->defaultAvatarProvider = $provider;
@@ -17,6 +18,6 @@ trait HasAvatars
public function getDefaultAvatarProvider(): string
{
return $this->defaultAvatarProvider;
return $this->evaluate($this->defaultAvatarProvider);
}
}
@@ -13,12 +13,20 @@ trait HasRelationManagers
#[Url]
public ?string $activeRelationManager = null;
/**
* @return array<class-string<RelationManager> | RelationGroup | RelationManagerConfiguration>
*/
protected function getAllRelationManagers(): array
{
return $this->getResource()::getRelations();
}
/**
* @return array<class-string<RelationManager> | RelationGroup | RelationManagerConfiguration>
*/
public function getRelationManagers(): array
{
$managers = $this->getResource()::getRelations();
$managers = $this->getAllRelationManagers();
return array_filter(
$managers,
@@ -96,8 +96,6 @@ class CreateRecord extends Page
$this->form->model($this->getRecord())->saveRelationships();
$this->callHook('afterCreate');
$this->commitDatabaseTransaction();
} catch (Halt $exception) {
$exception->shouldRollbackDatabaseTransaction() ?
$this->rollBackDatabaseTransaction() :
@@ -110,6 +108,8 @@ class CreateRecord extends Page
throw $exception;
}
$this->commitDatabaseTransaction();
$this->rememberData();
$this->getCreatedNotification()?->send();
@@ -153,8 +153,6 @@ class EditRecord extends Page
$this->handleRecordUpdate($this->getRecord(), $data);
$this->callHook('afterSave');
$this->commitDatabaseTransaction();
} catch (Halt $exception) {
$exception->shouldRollbackDatabaseTransaction() ?
$this->rollBackDatabaseTransaction() :
@@ -167,6 +165,8 @@ class EditRecord extends Page
throw $exception;
}
$this->commitDatabaseTransaction();
$this->rememberData();
if ($shouldSendSavedNotification) {
@@ -202,8 +202,6 @@ class EditRecord extends Page
$this->handleRecordUpdate($this->getRecord(), $data);
$this->callHook('afterSave');
$this->commitDatabaseTransaction();
} catch (Halt $exception) {
$exception->shouldRollbackDatabaseTransaction() ?
$this->rollBackDatabaseTransaction() :
@@ -216,6 +214,8 @@ class EditRecord extends Page
throw $exception;
}
$this->commitDatabaseTransaction();
$this->rememberData();
}
@@ -81,8 +81,6 @@ class SettingsPage extends Page
$settings->save();
$this->callHook('afterSave');
$this->commitDatabaseTransaction();
} catch (Halt $exception) {
$exception->shouldRollbackDatabaseTransaction() ?
$this->rollBackDatabaseTransaction() :
@@ -95,6 +93,8 @@ class SettingsPage extends Page
throw $exception;
}
$this->commitDatabaseTransaction();
$this->rememberData();
$this->getSavedNotification()?->send();
@@ -126,7 +126,7 @@ class CheckTranslationsCommand extends Command implements PromptsForMissingInput
warning("[!] Package filament/{$package} has {$missingKeysCount} missing translation " . Str::plural('key', $missingKeysCount) . " and {$removedKeysCount} removed translation " . Str::plural('key', $removedKeysCount) . " for {$locale}.\n");
} elseif ($missingKeysCount > 0) {
warning("[!] Package filament/{$package} has {$missingKeysCount} missing translation " . Str::plural('key', $missingKeysCount) . " for {$locale}.\n");
} elseif ($removedKeysCount > 0) {
} else {
warning("[!] Package filament/{$package} has {$removedKeysCount} removed translation " . Str::plural('key', $removedKeysCount) . " for {$locale}.\n");
}
})
@@ -84,7 +84,7 @@
>
@foreach ($limitedState as $stateItem)
<img
src="{{ filled($stateItem) ? $getImageUrl($stateItem) : $defaultImageUrl }}"
src="{{ filled($stateItem) ? ($getImageUrl($stateItem) ?? $defaultImageUrl) : $defaultImageUrl }}"
{{
$getExtraImgAttributeBag()
->class([
@@ -1,3 +1,7 @@
@php
$locale = app()->getLocale();
@endphp
@props([
'allSelectableRecordsCount',
'deselectAllRecordsAction' => 'deselectAllRecords',
@@ -30,7 +34,7 @@
<span
x-text="
window.pluralize(@js(__('filament-tables::table.selection_indicator.selected_count')), {{ $selectedRecordsPropertyName }}.length, {
count: {{ $selectedRecordsPropertyName }}.length,
count: new Intl.NumberFormat(@js(str_replace('_', '-', $locale))).format({{ $selectedRecordsPropertyName }}.length),
})
"
class="text-sm font-medium leading-6 text-gray-700 dark:text-gray-200"
@@ -49,7 +53,7 @@
{{-- Make sure the Alpine attributes get re-evaluated after a Livewire request: --}}
:wire:key="$this->getId() . 'table.selection.indicator.actions.select-all.' . $allSelectableRecordsCount . '.' . $page"
>
{{ trans_choice('filament-tables::table.selection_indicator.actions.select_all.label', $allSelectableRecordsCount, ['count' => \Illuminate\Support\Number::format($allSelectableRecordsCount)]) }}
{{ trans_choice('filament-tables::table.selection_indicator.actions.select_all.label', $allSelectableRecordsCount, ['count' => \Illuminate\Support\Number::format($allSelectableRecordsCount, locale: $locale)]) }}
</x-filament::link>
<x-filament::link
+2 -2
View File
@@ -113,8 +113,6 @@ trait HasActions
]);
$result = $action->callAfter() ?? $result;
$action->commitDatabaseTransaction();
} catch (Halt $exception) {
$exception->shouldRollbackDatabaseTransaction() ?
$action->rollBackDatabaseTransaction() :
@@ -142,6 +140,8 @@ trait HasActions
throw $exception;
}
$action->commitDatabaseTransaction();
if (store($this)->has('redirect')) {
return $result;
}
@@ -84,8 +84,6 @@ trait HasBulkActions
]);
$result = $action->callAfter() ?? $result;
$action->commitDatabaseTransaction();
} catch (Halt $exception) {
$exception->shouldRollbackDatabaseTransaction() ?
$action->rollBackDatabaseTransaction() :
@@ -113,6 +111,8 @@ trait HasBulkActions
throw $exception;
}
$action->commitDatabaseTransaction();
if (store($this)->has('redirect')) {
return $result;
}
@@ -10,6 +10,9 @@ use Filament\Tables\Table;
use Filament\Tests\Models\Post;
use Filament\Tests\Panels\Fixtures\Resources\PostResource\Pages;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use RuntimeException;
class PostResource extends Resource
{
@@ -57,6 +60,16 @@ class PostResource extends Resource
->actions([
Tables\Actions\ViewAction::make(),
Tables\Actions\EditAction::make(),
Tables\Actions\Action::make('randomize_title')
->databaseTransaction()
->action(action: function (Post $record) {
DB::afterCommit(function () {
throw new RuntimeException('This exception, happening after the successful commit of the current transaction, should not trigger a rollback by Filament.');
});
$record->title = Str::random(10);
$record->save();
}),
Tables\Actions\DeleteAction::make(),
])
->bulkActions([
@@ -4,7 +4,10 @@ namespace Filament\Tests\Panels\Fixtures\Resources\PostResource\Pages;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
use Filament\Tests\Models\Post;
use Filament\Tests\Panels\Fixtures\Resources\PostResource;
use Illuminate\Support\Facades\DB;
use RuntimeException;
class EditPost extends EditRecord
{
@@ -17,6 +20,16 @@ class EditPost extends EditRecord
Actions\ActionGroup::make([
Actions\DeleteAction::make(),
]),
Actions\Action::make('randomize_title')
->databaseTransaction()
->action(action: function (Post $record) {
DB::afterCommit(function () {
throw new RuntimeException('This exception, happening after the successful commit of the current transaction, should not trigger a rollback by Filament.');
});
$record->title = 'Test';
$record->save();
}),
];
}
@@ -5,6 +5,7 @@ use Filament\Tests\Models\Post;
use Filament\Tests\Panels\Fixtures\Resources\PostResource;
use Filament\Tests\Panels\Fixtures\Resources\PostResource\Pages\EditPost;
use Filament\Tests\Panels\Resources\TestCase;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use function Filament\Tests\livewire;
@@ -109,3 +110,23 @@ it('can refresh data', function () {
'title' => $newPostTitle,
]);
});
test('actions will not interfere with database transactions on an error', function () {
$post = Post::factory()->create();
$transactionLevel = DB::transactionLevel();
try {
livewire(PostResource\Pages\EditPost::class, [
'record' => $post->getKey(),
])
->callAction('randomize_title');
} catch (Exception $e) {
// This can be catched and handled somewhere else, code continues...
}
// Original transaction level should be unaffected...
expect(DB::transactionLevel())
->toBe($transactionLevel);
});
@@ -7,6 +7,7 @@ use Filament\Tests\Panels\Fixtures\Resources\PostResource;
use Filament\Tests\Panels\Fixtures\Resources\PostResource\Pages\ListPosts;
use Filament\Tests\Panels\Fixtures\Resources\UserResource;
use Filament\Tests\Panels\Resources\TestCase;
use Illuminate\Support\Facades\DB;
use function Filament\Tests\livewire;
use function Pest\Laravel\assertSoftDeleted;
@@ -115,3 +116,21 @@ it('can bulk delete posts', function () {
assertSoftDeleted($post);
}
});
test('table actions will not interfere with database transactions on an error', function () {
$post = Post::factory()->create();
$transactionLevel = DB::transactionLevel();
try {
livewire(PostResource\Pages\ListPosts::class)
->callTableAction('randomize_title', $post);
} catch (Exception $e) {
// This can be catched and handled somewhere else, code continues...
}
// Original transaction level should be unaffected...
expect(DB::transactionLevel())
->toBe($transactionLevel);
});