Skip to content

Commit

Permalink
fix repeatable relationship uploads
Browse files Browse the repository at this point in the history
  • Loading branch information
pxpm committed Jan 10, 2024
1 parent 1afa91a commit 29b5351
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 24 deletions.
21 changes: 20 additions & 1 deletion src/app/Library/Uploaders/MultipleFiles.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public function uploadFiles(Model $entry, $value = null)
$value = false;
}

$filesToDelete = collect(CRUD::getRequest()->get('clear_'.$this->getNameForRequest()))->flatten()->toArray();
$filesToDelete = $this->getFilesToDeleteFromRequest();
$value = $value ?? collect(CRUD::getRequest()->file($this->getNameForRequest()))->flatten()->toArray();
$previousFiles = $this->getPreviousFiles($entry) ?? [];

Expand Down Expand Up @@ -81,4 +81,23 @@ public function uploadRepeatableFiles($files, $previousRepeatableValues, $entry

return $fileOrder;
}

protected function hasDeletedFiles($value): bool
{
return empty($this->getFilesToDeleteFromRequest()) ? false : true;
}

protected function getEntryAttributeValue(Model $entry)
{
$value = $entry->{$this->getAttributeName()};

return isset($entry->getCasts()[$this->getName()]) ? $value : json_encode($value);
}

private function getFilesToDeleteFromRequest(): array
{
return collect(CRUD::getRequest()->get('clear_'.$this->getNameForRequest()))->flatten()->toArray();
}


}
10 changes: 10 additions & 0 deletions src/app/Library/Uploaders/SingleBase64Image.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,14 @@ public function uploadRepeatableFiles($values, $previousRepeatableValues, $entry

return $values;
}

protected function shouldUploadFiles($value): bool
{
return $value && is_string($value) && Str::startsWith($value, 'data:image');
}

protected function shouldKeepPreviousValueUnchanged(Model $entry, $entryValue): bool
{
return $entry->exists && is_string($entryValue) && !Str::startsWith($entryValue, 'data:image');
}
}
24 changes: 23 additions & 1 deletion src/app/Library/Uploaders/SingleFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ public function uploadFiles(Model $entry, $value = null)
$value = $value ?? CrudPanelFacade::getRequest()->file($this->getName());
$previousFile = $this->getPreviousFiles($entry);

if($value === false && $previousFile) {
Storage::disk($this->getDisk())->delete($previousFile);
return null;
}

if ($value && is_file($value) && $value->isValid()) {
if ($previousFile) {
Storage::disk($this->getDisk())->delete($previousFile);
Expand All @@ -28,7 +33,6 @@ public function uploadFiles(Model $entry, $value = null)

return null;
}

return $previousFile;
}

Expand All @@ -55,4 +59,22 @@ public function uploadRepeatableFiles($values, $previousRepeatableValues, $entry

return $orderedFiles;
}

/**
* Single file uploaders send no value when they are not dirty
*/
protected function shouldKeepPreviousValueUnchanged(Model $entry, $entryValue): bool
{
return is_string($entryValue);
}

protected function hasDeletedFiles($entryValue): bool
{
return $entryValue === null;
}

protected function shouldUploadFiles($value): bool
{
return is_a($value, 'Illuminate\Http\UploadedFile', true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ public function getRepeatableContainerName(): ?string;

public function getIdentifier(): string;

public function getNameForRequest(): string;

public function shouldDeleteFiles(): bool;

public function canHandleMultipleFiles(): bool;
Expand Down
8 changes: 1 addition & 7 deletions src/app/Library/Uploaders/Support/RegisterUploadEvents.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,7 @@ private function setupModelEvents(string $model, UploaderInterface $uploader): v

if ($this->crudObjectType === 'field') {
$model::saving(function ($entry) use ($uploader) {
$updatedCountKey = 'uploaded_'.($uploader->getRepeatableContainerName() ?? $uploader->getName()).'_count';

CRUD::set($updatedCountKey, CRUD::get($updatedCountKey) ?? 0);

$entry = $uploader->storeUploadedFiles($entry);

CRUD::set($updatedCountKey, CRUD::get($updatedCountKey) + 1);
});
}
// if the entry is already retrieved from database, don't register the event
Expand All @@ -132,7 +126,7 @@ private function setupModelEvents(string $model, UploaderInterface $uploader): v

$retrieveModel::retrieved(function ($entry) use ($uploader) {
if ($entry->translationEnabled()) {
$locale = request('_locale', \App::getLocale());
$locale = request('_locale', app()->getLocale());
if (in_array($locale, array_keys($entry->getAvailableLocales()))) {
$entry->setLocale($locale);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Backpack\CRUD\app\Library\Uploaders\Support\Interfaces\UploaderInterface;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;

Expand Down Expand Up @@ -44,33 +45,87 @@ protected function uploadRepeatableFiles($values, $previousValues, $entry = null

protected function handleRepeatableFiles(Model $entry): Model
{
if ($this->isRelationship) {
return $this->processRelationshipRepeatableUploaders($entry);
}

$values = collect(CRUD::getRequest()->get($this->getRepeatableContainerName()));
$files = collect(CRUD::getRequest()->file($this->getRepeatableContainerName()));
$value = $this->mergeValuesRecursive($values, $files);

if ($this->isRelationship) {
return $this->uploadRelationshipFiles($entry, $value);
}

$entry->{$this->getRepeatableContainerName()} = json_encode($this->processRepeatableUploads($entry, $value));

return $entry;
}

private function uploadRelationshipFiles(Model $entry, mixed $value): Model
private function processRelationshipRepeatableUploaders(Model $entry)
{
$modelCount = CRUD::get('uploaded_'.$this->getRepeatableContainerName().'_count');
$value = $value->slice($modelCount, 1)->toArray();
foreach(app('UploadersRepository')->getRepeatableUploadersFor($this->getRepeatableContainerName()) as $uploader) {
$entry = $uploader->uploadRelationshipFiles($entry);
}

foreach (app('UploadersRepository')->getRepeatableUploadersFor($this->getRepeatableContainerName()) as $uploader) {
if (array_key_exists($modelCount, $value) && array_key_exists($uploader->getAttributeName(), $value[$modelCount])) {
$entry->{$uploader->getAttributeName()} = $uploader->uploadFiles($entry, $value[$modelCount][$uploader->getAttributeName()]);
return $entry;
}

protected function uploadRelationshipFiles(Model $entry): Model
{
$entryValue = $this->getFilesFromEntry($entry);

if($this->handleMultipleFiles && is_string($entryValue)) {
try {
$entryValue = json_decode($entryValue, true);
} catch (\Exception) {
return $entry;
}
}

if ($this->hasDeletedFiles($entryValue)) {
$entry->{$this->getAttributeName()} = $this->uploadFiles($entry, false);
$this->updatedPreviousFiles = $this->getEntryAttributeValue($entry);
}

if($this->shouldKeepPreviousValueUnchanged($entry, $entryValue)) {
$entry->{$this->getAttributeName()} = $this->updatedPreviousFiles ?? $this->getEntryOriginalValue($entry);
return $entry;
}

if($this->shouldUploadFiles($entryValue)) {
$entry->{$this->getAttributeName()} = $this->uploadFiles($entry, $entryValue);
}

return $entry;
}

protected function getFilesFromEntry(Model $entry)
{
return $entry->getAttribute($this->getAttributeName());
}

protected function getEntryAttributeValue(Model $entry)
{
return $entry->{$this->getAttributeName()};
}

protected function getEntryOriginalValue(Model $entry)
{
return $entry->getOriginal($this->getAttributeName());
}

protected function shouldUploadFiles($entryValue): bool
{
return true;
}

protected function shouldKeepPreviousValueUnchanged(Model $entry, $entryValue): bool
{
return $entry->exists && ($entryValue === null || $entryValue === [null]);
}

protected function hasDeletedFiles($entryValue): bool
{
return $entryValue === false || $entryValue === null || $entryValue === [null];
}

protected function processRepeatableUploads(Model $entry, Collection $values): Collection
{
foreach (app('UploadersRepository')->getRepeatableUploadersFor($this->getRepeatableContainerName()) as $uploader) {
Expand Down Expand Up @@ -239,6 +294,13 @@ private function getValuesWithPathStripped(array|string|null $item, UploaderInte
}

private function deleteRelationshipFiles(Model $entry): void
{
foreach (app('UploadersRepository')->getRepeatableUploadersFor($this->getRepeatableContainerName()) as $uploader) {
$uploader->deleteRepeatableRelationFiles($entry);
}
}

private function deleteRepeatableRelationFiles(Model $entry)
{
if (in_array($this->getRepeatableRelationType(), ['BelongsToMany', 'MorphToMany'])) {
$pivotAttributes = $entry->getAttributes();
Expand All @@ -258,6 +320,16 @@ private function deleteRelationshipFiles(Model $entry): void
return;
}

if($this->handleMultipleFiles && is_string($files)) {
try {
$files = json_decode($files, true);
} catch (\Exception) {
Log::error('Could not parse files for deletion pivot entry with key: '. $entry->getKey() . ' and uploader: ' . $this->getName());
return;
}
}


if (is_array($files)) {
foreach ($files as $value) {
$value = Str::start($value, $this->getPath());
Expand Down
24 changes: 19 additions & 5 deletions src/app/Library/Uploaders/Uploader.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ abstract class Uploader implements UploaderInterface
*/
private bool $isRelationship = false;

/**
* When previous files are updated, we need to keep track of them so that we don't add deleted files to the new list.
*/
private $updatedPreviousFiles = null;

public function __construct(array $crudObject, array $configuration)
{
$this->name = $crudObject['name'];
Expand Down Expand Up @@ -237,22 +242,27 @@ private function retrieveFiles(Model $entry): Model

private function deleteFiles(Model $entry)
{
$values = $entry->{$this->name};
$values = $entry->{$this->getAttributeName()};

if($values === null) {
return;
}

if ($this->handleMultipleFiles) {
// ensure we have an array of values when field is not casted in model.
if (! isset($entry->getCasts()[$this->name]) && is_string($values)) {
$values = json_decode($values, true);
}
foreach ($values as $value) {
Storage::disk($this->disk)->delete($this->path.$value);
foreach ($values ?? [] as $value) {
$value = Str::start($value, $this->path);
Storage::disk($this->disk)->delete($value);
}

return;
}

$values = Str::after($values, $this->path);
Storage::disk($this->disk)->delete($this->path.$values);
$values = Str::start($values, $this->path);
Storage::disk($this->disk)->delete($values);
}

private function performFileDeletion(Model $entry)
Expand All @@ -278,6 +288,10 @@ private function getPathFromConfiguration(array $crudObject, array $configuratio

private function getOriginalValue(Model $entry, $field = null)
{
if($this->updatedPreviousFiles !== null) {
return $this->updatedPreviousFiles;
}

$previousValue = $entry->getOriginal($field ?? $this->getAttributeName());

if (! $previousValue) {
Expand Down

0 comments on commit 29b5351

Please sign in to comment.