Skip to content

Commit 3953677

Browse files
committed
implement duplicate file detection
1 parent 2470fec commit 3953677

5 files changed

Lines changed: 171 additions & 71 deletions

File tree

packages/media/resources/lang/de/fields.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,4 +95,8 @@
9595
'file_type' => 'Dateityp',
9696
'apply_selection' => 'Auswahl übernehmen',
9797
'close' => 'Schließen',
98+
99+
// Duplicate Files
100+
'duplicate_file' => 'Doppelte Datei',
101+
'duplicate_file_message' => 'Die Datei ":fileName" existiert bereits in der Mediathek.',
98102
];

packages/media/resources/lang/en/fields.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@
6161
'operation_error' => 'Operation Error',
6262
'file_operation_error' => 'The file ":fileName" could not be operated on.',
6363

64+
// Duplicate Files
65+
'duplicate_file' => 'Duplicate File',
66+
'duplicate_file_message' => 'The file ":fileName" already exists in the media library.',
67+
6468
// Metadata
6569
'metadata' => 'Metadata',
6670
'alt_text' => 'Alt Text',

packages/media/src/Http/Livewire/MediaPickerModal.php

Lines changed: 62 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -73,15 +73,15 @@ public function mount(?int $modelId = null, ?string $modelClass = null): void
7373

7474
$this->modelClass = $this->modelClass ? str_replace('\\\\', '\\', $this->modelClass) : null;
7575

76-
if ($this->modelClass && ! class_exists($this->modelClass)) {
76+
if ($this->modelClass && !class_exists($this->modelClass)) {
7777
throw new \Exception(__('media::fields.class_not_found', ['class' => $this->modelClass]));
7878
}
7979

8080
if ($this->modelId && $this->modelClass) {
8181
$this->model = app($this->modelClass)::find($this->modelId);
8282
}
8383

84-
if (! $this->modelId || ! $this->model) {
84+
if (!$this->modelId || !$this->model) {
8585
$this->modelId = 0;
8686
}
8787
}
@@ -91,13 +91,34 @@ public function form(Form $form): Form
9191
$upload = FileUpload::make(__('files'))
9292
->label(__('media::fields.upload'))
9393
->afterStateUpdated(function ($state) {
94-
if (! $state) {
94+
if (!$state) {
9595
return;
9696
}
9797

9898
$processedFiles = session('processed_files', []);
9999

100-
if (! is_array($state)) {
100+
if (!is_array($state)) {
101+
$fileHash = hash_file('sha256', $state->getRealPath());
102+
$fileName = $state->getClientOriginalName();
103+
104+
$existingMedia = Media::whereHas('translations', function ($query) use ($fileName) {
105+
$query->where('name', $fileName);
106+
})->orWhere(function ($query) use ($fileHash) {
107+
$query->where('custom_properties->file_hash', $fileHash);
108+
})->first();
109+
110+
if ($existingMedia) {
111+
\Filament\Notifications\Notification::make()
112+
->warning()
113+
->title(__('media::fields.duplicate_file'))
114+
->body(__('media::fields.duplicate_file_message', [
115+
'fileName' => $fileName,
116+
]))
117+
->persistent()
118+
->send();
119+
return;
120+
}
121+
101122
$model = new Media;
102123
$model->exists = true;
103124

@@ -116,6 +137,8 @@ public function form(Form $form): Form
116137
$media->uploader_type = $user ? get_class($user) : null;
117138
$media->uploader_id = $user?->id;
118139

140+
$media->setCustomProperty('file_hash', $fileHash);
141+
119142
if (str_starts_with($media->mime_type, 'image/')) {
120143
[$width, $height] = getimagesize($media->getPath());
121144
$media->setCustomProperty('dimensions', [
@@ -131,6 +154,27 @@ public function form(Form $form): Form
131154
continue;
132155
}
133156

157+
$fileHash = hash_file('sha256', $tempFile->getRealPath());
158+
$fileName = $tempFile->getClientOriginalName();
159+
160+
$existingMedia = Media::whereHas('translations', function ($query) use ($fileName) {
161+
$query->where('name', $fileName);
162+
})->orWhere(function ($query) use ($fileHash) {
163+
$query->where('custom_properties->file_hash', $fileHash);
164+
})->first();
165+
166+
if ($existingMedia) {
167+
\Filament\Notifications\Notification::make()
168+
->warning()
169+
->title(__('media::fields.duplicate_file'))
170+
->body(__('media::fields.duplicate_file_message', [
171+
'fileName' => $fileName,
172+
]))
173+
->persistent()
174+
->send();
175+
continue;
176+
}
177+
134178
$model = new Media;
135179
$model->exists = true;
136180

@@ -149,6 +193,8 @@ public function form(Form $form): Form
149193
$media->uploader_type = $user ? get_class($user) : null;
150194
$media->uploader_id = $user?->id;
151195

196+
$media->setCustomProperty('file_hash', $fileHash);
197+
152198
if (str_starts_with($media->mime_type, 'image/')) {
153199
[$width, $height] = getimagesize($media->getPath());
154200
$media->setCustomProperty('dimensions', [
@@ -241,7 +287,7 @@ public function toggleMediaSelection(int $mediaId)
241287
$this->selectedMediaIds[] = $mediaId;
242288
}
243289
} else {
244-
if (! empty($this->selectedMediaIds) && $this->selectedMediaIds[0] === $mediaId) {
290+
if (!empty($this->selectedMediaIds) && $this->selectedMediaIds[0] === $mediaId) {
245291
$this->selectedMediaIds = [];
246292
} else {
247293
$this->selectedMediaIds = [$mediaId];
@@ -256,7 +302,7 @@ public function toggleMediaSelection(int $mediaId)
256302
if (isset($media->uploader->name)) {
257303
$uploaderName = $media->uploader->name;
258304
} elseif (isset($media->uploader->first_name) && isset($media->uploader->last_name)) {
259-
$uploaderName = $media->uploader->first_name.' '.$media->uploader->last_name;
305+
$uploaderName = $media->uploader->first_name . ' ' . $media->uploader->last_name;
260306
}
261307
}
262308

@@ -301,7 +347,7 @@ public function applySelection()
301347
$selectedMedia = Media::whereIn('id', $this->selectedMediaIds)->get();
302348

303349
if ($selectedMedia->isNotEmpty()) {
304-
if (! $this->multiple) {
350+
if (!$this->multiple) {
305351
$media = $selectedMedia->first();
306352
$this->dispatch('mediaSelected', [
307353
'id' => $media->id,
@@ -311,7 +357,7 @@ public function applySelection()
311357
'name' => $media->getAttribute('name'),
312358
]);
313359
} else {
314-
$selectedMediaData = $selectedMedia->map(fn ($media) => [
360+
$selectedMediaData = $selectedMedia->map(fn($media) => [
315361
'id' => $media->id,
316362
'url' => $media->getUrl(),
317363
'file_name' => $media->file_name,
@@ -364,10 +410,10 @@ public function render()
364410
$media = Media::query()
365411
->when($this->searchQuery, function ($query) {
366412
$query->where(function ($subQuery) {
367-
$subQuery->where('file_name', 'like', '%'.$this->searchQuery.'%')
368-
->orWhereRaw('LOWER(JSON_EXTRACT(translations, "$.*.title")) LIKE ?', ['%'.strtolower($this->searchQuery).'%'])
369-
->orWhereRaw('LOWER(JSON_EXTRACT(translations, "$.*.description")) LIKE ?', ['%'.strtolower($this->searchQuery).'%'])
370-
->orWhereRaw('LOWER(JSON_EXTRACT(translations, "$.*.alt")) LIKE ?', ['%'.strtolower($this->searchQuery).'%']);
413+
$subQuery->where('file_name', 'like', '%' . $this->searchQuery . '%')
414+
->orWhereRaw('LOWER(JSON_EXTRACT(translations, "$.*.title")) LIKE ?', ['%' . strtolower($this->searchQuery) . '%'])
415+
->orWhereRaw('LOWER(JSON_EXTRACT(translations, "$.*.description")) LIKE ?', ['%' . strtolower($this->searchQuery) . '%'])
416+
->orWhereRaw('LOWER(JSON_EXTRACT(translations, "$.*.alt")) LIKE ?', ['%' . strtolower($this->searchQuery) . '%']);
371417
});
372418
})
373419
->when($this->fileTypeFilter, function ($query) {
@@ -446,25 +492,25 @@ public function render()
446492
$uploader = $media->uploader;
447493
if ($uploader && method_exists($uploader, 'getName')) {
448494
return [
449-
'id' => $media->uploader_type.'::'.$media->uploader_id,
495+
'id' => $media->uploader_type . '::' . $media->uploader_id,
450496
'name' => $uploader->getName(),
451497
];
452498
}
453499
if ($uploader && isset($uploader->name)) {
454500
return [
455-
'id' => $media->uploader_type.'::'.$media->uploader_id,
501+
'id' => $media->uploader_type . '::' . $media->uploader_id,
456502
'name' => $uploader->name,
457503
];
458504
}
459505

460506
return null;
461507
})
462508
->filter()
463-
->unique(fn (array $item): string => $item['id'])
509+
->unique(fn(array $item): string => $item['id'])
464510
->pluck('name', 'id')
465511
->toArray();
466512

467-
if (! empty($uploaders)) {
513+
if (!empty($uploaders)) {
468514
$typeName = class_basename($type);
469515
$uploaderOptions[$typeName] = $uploaders;
470516
}

0 commit comments

Comments
 (0)