File: /home/posscale/public_html/printmanager/vendor/filament/forms/src/Components/BaseFileUpload.php
<?php
namespace Filament\Forms\Components;
use Closure;
use Filament\Forms\Get;
use Filament\Forms\Set;
use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use League\Flysystem\UnableToCheckFileExistence;
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
use Throwable;
class BaseFileUpload extends Field implements Contracts\HasNestedRecursiveValidationRules
{
use Concerns\HasNestedRecursiveValidationRules;
use Concerns\HasUploadingMessage;
/**
* @var array<string> | 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<string | TemporaryUploadedFile> $state */
$state = $this->getState() ?? [];
/** @var array<string | TemporaryUploadedFile> $oldState */
$oldState = $this->getOldState() ?? [];
$this->evaluate($hook, [
'state' => $this->isMultiple() ? $state : Arr::first($state),
'old' => $this->isMultiple() ? $oldState : Arr::first($oldState),
]);
}
/**
* @param array<string> | 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<string> | 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<mixed>
*/
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<array-key> $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<array{name: string, size: int, type: string, url: string} | null> | 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<string, string> | 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<string, string>
*/
public function getStateToDehydrate(): array
{
$state = parent::getStateToDehydrate();
if ($fileNamesStatePath = $this->getFileNamesStatePath()) {
$state = [
...$state,
$fileNamesStatePath => $this->getStoredFileNames(),
];
}
return $state;
}
/**
* @param array<string, array<mixed>> $rules
*/
public function dehydrateValidationRules(array &$rules): void
{
parent::dehydrateValidationRules($rules);
if ($fileNamesStatePath = $this->getFileNamesStatePath()) {
$rules[$fileNamesStatePath] = ['nullable'];
}
}
}