| Arrayable | Closure | null */ protected array | Arrayable | Closure | null $acceptedFileTypes = null; protected bool | Closure $isDeletable = true; protected bool | Closure $isDownloadable = false; protected bool | Closure $isOpenable = false; protected bool | Closure $isPasteable = true; protected bool | Closure $isPreviewable = true; protected bool | Closure $isReorderable = false; protected string | Closure | null $directory = null; protected string | Closure | null $diskName = null; protected bool | Closure $isMultiple = false; protected int | Closure | null $maxSize = null; protected int | Closure | null $minSize = null; protected int | Closure | null $maxParallelUploads = null; protected int | Closure | null $maxFiles = null; protected int | Closure | null $minFiles = null; protected bool | Closure $shouldPreserveFilenames = false; protected bool | Closure $shouldMoveFiles = false; protected bool | Closure $shouldStoreFiles = true; protected bool | Closure $shouldFetchFileInformation = true; protected string | Closure | null $fileNamesStatePath = null; protected string | Closure $visibility = 'public'; protected ?Closure $deleteUploadedFileUsing = null; protected ?Closure $getUploadedFileNameForStorageUsing = null; protected ?Closure $getUploadedFileUsing = null; protected ?Closure $reorderUploadedFilesUsing = null; protected ?Closure $saveUploadedFileUsing = null; protected function setUp(): void { parent::setUp(); $this->afterStateHydrated(static function (BaseFileUpload $component, string | array | null $state): void { if (blank($state)) { $component->state([]); return; } $shouldFetchFileInformation = $component->shouldFetchFileInformation(); $files = collect(Arr::wrap($state)) ->filter(static function (string $file) use ($component, $shouldFetchFileInformation): bool { if (blank($file)) { return false; } if (! $shouldFetchFileInformation) { return true; } try { return $component->getDisk()->exists($file); } catch (UnableToCheckFileExistence $exception) { return false; } }) ->mapWithKeys(static fn (string $file): array => [((string) Str::uuid()) => $file]) ->all(); $component->state($files); }); $this->afterStateUpdated(static function (BaseFileUpload $component, $state) { if ($state instanceof TemporaryUploadedFile) { return; } if (blank($state)) { return; } if (is_array($state)) { return; } $component->state([(string) Str::uuid() => $state]); }); $this->beforeStateDehydrated(static function (BaseFileUpload $component): void { $component->saveUploadedFiles(); }); $this->dehydrateStateUsing(static function (BaseFileUpload $component, ?array $state): string | array | null | TemporaryUploadedFile { $files = array_values($state ?? []); if ($component->isMultiple()) { return $files; } return $files[0] ?? null; }); $this->getUploadedFileUsing(static function (BaseFileUpload $component, string $file, string | array | null $storedFileNames): ?array { /** @var FilesystemAdapter $storage */ $storage = $component->getDisk(); $shouldFetchFileInformation = $component->shouldFetchFileInformation(); if ($shouldFetchFileInformation) { try { if (! $storage->exists($file)) { return null; } } catch (UnableToCheckFileExistence $exception) { return null; } } $url = null; if ($component->getVisibility() === 'private') { try { $url = $storage->temporaryUrl( $file, now()->addMinutes(5), ); } catch (Throwable $exception) { // This driver does not support creating temporary URLs. } } $url ??= $storage->url($file); return [ 'name' => ($component->isMultiple() ? ($storedFileNames[$file] ?? null) : $storedFileNames) ?? basename($file), 'size' => $shouldFetchFileInformation ? $storage->size($file) : 0, 'type' => $shouldFetchFileInformation ? $storage->mimeType($file) : null, 'url' => $url, ]; }); $this->getUploadedFileNameForStorageUsing(static function (BaseFileUpload $component, TemporaryUploadedFile $file) { return $component->shouldPreserveFilenames() ? $file->getClientOriginalName() : (Str::ulid() . '.' . $file->getClientOriginalExtension()); }); $this->saveUploadedFileUsing(static function (BaseFileUpload $component, TemporaryUploadedFile $file): ?string { try { if (! $file->exists()) { return null; } } catch (UnableToCheckFileExistence $exception) { return null; } if ( $component->shouldMoveFiles() && ($component->getDiskName() == (fn (): string => $this->disk)->call($file)) ) { $newPath = trim($component->getDirectory() . '/' . $component->getUploadedFileNameForStorage($file), '/'); $component->getDisk()->move((fn (): string => $this->path)->call($file), $newPath); return $newPath; } $storeMethod = $component->getVisibility() === 'public' ? 'storePubliclyAs' : 'storeAs'; return $file->{$storeMethod}( $component->getDirectory(), $component->getUploadedFileNameForStorage($file), $component->getDiskName(), ); }); } protected function callAfterStateUpdatedHook(Closure $hook): void { /** @var array $state */ $state = $this->getState() ?? []; /** @var array $oldState */ $oldState = $this->getOldState() ?? []; $this->evaluate($hook, [ 'state' => $this->isMultiple() ? $state : Arr::first($state), 'old' => $this->isMultiple() ? $oldState : Arr::first($oldState), ]); } /** * @param array | Arrayable | Closure $types */ public function acceptedFileTypes(array | Arrayable | Closure $types): static { $this->acceptedFileTypes = $types; $this->rule(static function (BaseFileUpload $component) { $types = implode(',', ($component->getAcceptedFileTypes() ?? [])); return "mimetypes:{$types}"; }); return $this; } public function deletable(bool | Closure $condition = true): static { $this->isDeletable = $condition; return $this; } public function directory(string | Closure | null $directory): static { $this->directory = $directory; return $this; } public function disk(string | Closure | null $name): static { $this->diskName = $name; return $this; } public function downloadable(bool | Closure $condition = true): static { $this->isDownloadable = $condition; return $this; } public function openable(bool | Closure $condition = true): static { $this->isOpenable = $condition; return $this; } public function reorderable(bool | Closure $condition = true): static { $this->isReorderable = $condition; return $this; } public function pasteable(bool | Closure $condition = true): static { $this->isPasteable = $condition; return $this; } public function previewable(bool | Closure $condition = true): static { $this->isPreviewable = $condition; return $this; } /** * @deprecated Use `downloadable()` instead. */ public function enableDownload(bool | Closure $condition = true): static { $this->downloadable($condition); return $this; } /** * @deprecated Use `openable()` instead. */ public function enableOpen(bool | Closure $condition = true): static { $this->openable($condition); return $this; } /** * @deprecated Use `reorderable()` instead. */ public function enableReordering(bool | Closure $condition = true): static { $this->reorderable($condition); return $this; } /** * @deprecated Use `previewable()` instead. */ public function disablePreview(bool | Closure $condition = true): static { $this->previewable(fn (BaseFileUpload $component): bool => ! $component->evaluate($condition)); return $this; } public function storeFileNamesIn(string | Closure | null $statePath): static { $this->fileNamesStatePath = $statePath; return $this; } public function preserveFilenames(bool | Closure $condition = true): static { $this->shouldPreserveFilenames = $condition; return $this; } public function moveFiles(bool | Closure $condition = true): static { $this->shouldMoveFiles = $condition; return $this; } /** * @deprecated Use `moveFiles()` instead. */ public function moveFile(bool | Closure $condition = true): static { $this->moveFiles($condition); return $this; } public function fetchFileInformation(bool | Closure $condition = true): static { $this->shouldFetchFileInformation = $condition; return $this; } public function maxSize(int | Closure | null $size): static { $this->maxSize = $size; $this->rule(static function (BaseFileUpload $component): string { $size = $component->getMaxSize(); return "max:{$size}"; }); return $this; } public function minSize(int | Closure | null $size): static { $this->minSize = $size; $this->rule(static function (BaseFileUpload $component): string { $size = $component->getMinSize(); return "min:{$size}"; }); return $this; } public function maxParallelUploads(int | Closure | null $count): static { $this->maxParallelUploads = $count; return $this; } public function maxFiles(int | Closure | null $count): static { $this->maxFiles = $count; return $this; } public function minFiles(int | Closure | null $count): static { $this->minFiles = $count; return $this; } public function multiple(bool | Closure $condition = true): static { $this->isMultiple = $condition; return $this; } public function storeFiles(bool | Closure $condition = true): static { $this->shouldStoreFiles = $condition; return $this; } /** * @deprecated Use `storeFiles()` instead. */ public function storeFile(bool | Closure $condition = true): static { $this->storeFiles($condition); return $this; } public function visibility(string | Closure | null $visibility): static { $this->visibility = $visibility; return $this; } public function deleteUploadedFileUsing(?Closure $callback): static { $this->deleteUploadedFileUsing = $callback; return $this; } public function getUploadedFileUsing(?Closure $callback): static { $this->getUploadedFileUsing = $callback; return $this; } public function reorderUploadedFilesUsing(?Closure $callback): static { $this->reorderUploadedFilesUsing = $callback; return $this; } public function saveUploadedFileUsing(?Closure $callback): static { $this->saveUploadedFileUsing = $callback; return $this; } public function isDeletable(): bool { return (bool) $this->evaluate($this->isDeletable); } public function isDownloadable(): bool { return (bool) $this->evaluate($this->isDownloadable); } public function isOpenable(): bool { return (bool) $this->evaluate($this->isOpenable); } public function isPasteable(): bool { return (bool) $this->evaluate($this->isPasteable); } public function isPreviewable(): bool { return (bool) $this->evaluate($this->isPreviewable); } public function isReorderable(): bool { return (bool) $this->evaluate($this->isReorderable); } /** * @return array | null */ public function getAcceptedFileTypes(): ?array { $types = $this->evaluate($this->acceptedFileTypes); if ($types instanceof Arrayable) { $types = $types->toArray(); } return $types; } public function getDirectory(): ?string { return $this->evaluate($this->directory); } public function getDisk(): Filesystem { return Storage::disk($this->getDiskName()); } public function getDiskName(): string { return $this->evaluate($this->diskName) ?? config('filament.default_filesystem_disk'); } public function getMaxFiles(): ?int { return $this->evaluate($this->maxFiles); } public function getMinFiles(): ?int { return $this->evaluate($this->minFiles); } public function getMaxSize(): ?int { return $this->evaluate($this->maxSize); } public function getMinSize(): ?int { return $this->evaluate($this->minSize); } public function getMaxParallelUploads(): ?int { return $this->evaluate($this->maxParallelUploads); } public function getVisibility(): string { return $this->evaluate($this->visibility); } public function shouldPreserveFilenames(): bool { return (bool) $this->evaluate($this->shouldPreserveFilenames); } public function shouldMoveFiles(): bool { return (bool) $this->evaluate($this->shouldMoveFiles); } public function shouldFetchFileInformation(): bool { return (bool) $this->evaluate($this->shouldFetchFileInformation); } public function shouldStoreFiles(): bool { return (bool) $this->evaluate($this->shouldStoreFiles); } public function getFileNamesStatePath(): ?string { if (! $this->fileNamesStatePath) { return null; } return $this->generateRelativeStatePath($this->fileNamesStatePath); } /** * @return array */ public function getValidationRules(): array { $rules = [ $this->getRequiredValidationRule(), 'array', ]; if (filled($count = $this->getMaxFiles())) { $rules[] = "max:{$count}"; } if (filled($count = $this->getMinFiles())) { $rules[] = "min:{$count}"; } $rules[] = function (string $attribute, array $value, Closure $fail): void { $files = array_filter($value, fn (TemporaryUploadedFile | string $file): bool => $file instanceof TemporaryUploadedFile); $name = $this->getName(); $validationMessages = $this->getValidationMessages(); $validator = Validator::make( [$name => $files], ["{$name}.*" => ['file', ...parent::getValidationRules()]], $validationMessages ? ["{$name}.*" => $validationMessages] : [], ["{$name}.*" => $this->getValidationAttribute()], ); if (! $validator->fails()) { return; } $fail($validator->errors()->first()); }; return $rules; } public function deleteUploadedFile(string $fileKey): static { $file = $this->removeUploadedFile($fileKey); if (blank($file)) { return $this; } $callback = $this->deleteUploadedFileUsing; if (! $callback) { return $this; } $this->evaluate($callback, [ 'file' => $file, ]); return $this; } public function removeUploadedFile(string $fileKey): string | TemporaryUploadedFile | null { $files = $this->getState(); $file = $files[$fileKey] ?? null; if (! $file) { return null; } if (is_string($file)) { $this->removeStoredFileName($file); } elseif ($file instanceof TemporaryUploadedFile) { $file->delete(); } unset($files[$fileKey]); $this->state($files); return $file; } public function removeStoredFileName(string $file): void { $statePath = $this->fileNamesStatePath; if (blank($statePath)) { return; } $this->evaluate(function (BaseFileUpload $component, Get $get, Set $set) use ($file, $statePath) { if (! $component->isMultiple()) { $set($statePath, null); return; } $fileNames = $get($statePath) ?? []; if (array_key_exists($file, $fileNames)) { unset($fileNames[$file]); } $set($statePath, $fileNames); }); } /** * @param array $fileKeys */ public function reorderUploadedFiles(array $fileKeys): void { if (! $this->isReorderable) { return; } $fileKeys = array_flip($fileKeys); $state = collect($this->getState()) ->sortBy(static fn ($file, $fileKey) => $fileKeys[$fileKey] ?? null) // $fileKey may not be present in $fileKeys if it was added to the state during the reorder call ->all(); $this->state($state); } /** * @return array | null */ public function getUploadedFiles(): ?array { $urls = []; foreach ($this->getState() ?? [] as $fileKey => $file) { if ($file instanceof TemporaryUploadedFile) { $urls[$fileKey] = null; continue; } $callback = $this->getUploadedFileUsing; if (! $callback) { return [$fileKey => null]; } $urls[$fileKey] = $this->evaluate($callback, [ 'file' => $file, 'storedFileNames' => $this->getStoredFileNames(), ]) ?: null; } return $urls; } public function saveUploadedFiles(): void { if (blank($this->getState())) { $this->state([]); return; } if (! $this->shouldStoreFiles()) { return; } $state = array_filter(array_map(function (TemporaryUploadedFile | string $file) { if (! $file instanceof TemporaryUploadedFile) { return $file; } $callback = $this->saveUploadedFileUsing; if (! $callback) { $file->delete(); return $file; } $storedFile = $this->evaluate($callback, [ 'file' => $file, ]); if ($storedFile === null) { return null; } $this->storeFileName($storedFile, $file->getClientOriginalName()); $file->delete(); return $storedFile; }, Arr::wrap($this->getState()))); if ($this->isReorderable && ($callback = $this->reorderUploadedFilesUsing)) { $state = $this->evaluate($callback, [ 'state' => $state, ]); } $this->state($state); } public function storeFileName(string $file, string $fileName): void { $statePath = $this->fileNamesStatePath; if (blank($statePath)) { return; } $this->evaluate(function (BaseFileUpload $component, Get $get, Set $set) use ($file, $fileName, $statePath) { if (! $component->isMultiple()) { $set($statePath, $fileName); return; } $fileNames = $get($statePath) ?? []; $fileNames[$file] = $fileName; $set($statePath, $fileNames); }); } /** * @return string | array | null */ public function getStoredFileNames(): string | array | null { $state = null; $statePath = $this->fileNamesStatePath; if (filled($statePath)) { $state = $this->evaluate(fn (Get $get) => $get($statePath)); } if (blank($state) && $this->isMultiple()) { return []; } return $state; } public function isMultiple(): bool { return (bool) $this->evaluate($this->isMultiple); } public function getUploadedFileNameForStorageUsing(?Closure $callback): static { $this->getUploadedFileNameForStorageUsing = $callback; return $this; } public function getUploadedFileNameForStorage(TemporaryUploadedFile $file): string { return $this->evaluate($this->getUploadedFileNameForStorageUsing, [ 'file' => $file, ]); } /** * @return array */ public function getStateToDehydrate(): array { $state = parent::getStateToDehydrate(); if ($fileNamesStatePath = $this->getFileNamesStatePath()) { $state = [ ...$state, $fileNamesStatePath => $this->getStoredFileNames(), ]; } return $state; } /** * @param array> $rules */ public function dehydrateValidationRules(array &$rules): void { parent::dehydrateValidationRules($rules); if ($fileNamesStatePath = $this->getFileNamesStatePath()) { $rules[$fileNamesStatePath] = ['nullable']; } } }