Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clean up uploaders #5725

Merged
merged 9 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/app/Library/Uploaders/MultipleFiles.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public function uploadFiles(Model $entry, $value = null)
}

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

if (is_array($previousFiles) && empty($previousFiles[0] ?? [])) {
Expand Down Expand Up @@ -108,12 +108,12 @@ public function uploadRepeatableFiles($files, $previousRepeatableValues, $entry
return $fileOrder;
}

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

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

Expand Down
8 changes: 6 additions & 2 deletions src/app/Library/Uploaders/SingleBase64Image.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ class SingleBase64Image extends Uploader
{
public function uploadFiles(Model $entry, $value = null)
{
$value = $value ?? CRUD::getRequest()->get($this->getName());
$previousImage = $this->getPreviousFiles($entry);

if (! $value && $previousImage) {
Expand Down Expand Up @@ -61,7 +60,7 @@ public function uploadRepeatableFiles($values, $previousRepeatableValues, $entry
return $values;
}

protected function shouldUploadFiles($value): bool
public function shouldUploadFiles($value): bool
{
return $value && is_string($value) && Str::startsWith($value, 'data:image');
}
Expand All @@ -70,4 +69,9 @@ public function shouldKeepPreviousValueUnchanged(Model $entry, $entryValue): boo
{
return $entry->exists && is_string($entryValue) && ! Str::startsWith($entryValue, 'data:image');
}

public function getUploadedFilesFromRequest()
{
return CRUD::getRequest()->get($this->getNameForRequest());
}
}
5 changes: 2 additions & 3 deletions src/app/Library/Uploaders/SingleFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ class SingleFile extends Uploader
{
public function uploadFiles(Model $entry, $value = null)
{
$value = $value ?? CrudPanelFacade::getRequest()->file($this->getName());
$previousFile = $this->getPreviousFiles($entry);

if ($value === false && $previousFile) {
Expand Down Expand Up @@ -75,12 +74,12 @@ public function shouldKeepPreviousValueUnchanged(Model $entry, $entryValue): boo
return is_string($entryValue);
}

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

protected function shouldUploadFiles($value): bool
public function shouldUploadFiles($value): bool
{
return is_a($value, 'Illuminate\Http\UploadedFile', true);
}
Expand Down
17 changes: 12 additions & 5 deletions src/app/Library/Uploaders/Support/Interfaces/UploaderInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,14 @@ public static function for(array $field, array $configuration): UploaderInterfac
/**
* Default implementation functions.
*/

// method called on `saving` event to store and update the entry with the uploaded files
public function storeUploadedFiles(Model $entry);

// method called on `retrieved` event to populated the uploaded files in the entry
public function retrieveUploadedFiles(Model $entry);

// method called on `deleting` event to delete the uploaded files
public function deleteUploadedFiles(Model $entry);

/**
Expand Down Expand Up @@ -55,17 +59,20 @@ public function getIdentifier(): string;

public function getNameForRequest(): string;

public function shouldDeleteFiles(): bool;

public function canHandleMultipleFiles(): bool;

public function isRelationship(): bool;

public function getPreviousFiles(Model $entry): mixed;

public function getValueWithoutPath(?string $value = null): ?string;
/**
* Strategy methods.
*/
public function shouldDeleteFiles(): bool;

public function hasDeletedFiles($entryValue): bool;

public function isFake(): bool;
public function shouldUploadFiles(mixed $value): bool;

public function getFakeAttribute(): bool|string;
public function shouldKeepPreviousValueUnchanged(Model $entry, mixed $entryValue): bool;
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ protected function handleRepeatableFiles(Model $entry): Model
return $this->processRelationshipRepeatableUploaders($entry);
}

$processedEntryValues = $this->processRepeatableUploads($entry, $value);
$processedEntryValues = $this->processRepeatableUploads($entry, $value)->toArray();

if ($this->isFake()) {
$fakeValues = $entry->{$this->getFakeAttribute()} ?? [];
Expand Down Expand Up @@ -147,17 +147,7 @@ protected function getEntryOriginalValue(Model $entry)
return $entry->getOriginal($this->getAttributeName());
}

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

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

protected function processRepeatableUploads(Model $entry, Collection $values): array
protected function processRepeatableUploads(Model $entry, Collection $values): Collection
{
foreach (app('UploadersRepository')->getRepeatableUploadersFor($this->getRepeatableContainerName()) as $uploader) {
$uploadedValues = $uploader->uploadRepeatableFiles($values->pluck($uploader->getAttributeName())->toArray(), $this->getPreviousRepeatableValues($entry, $uploader));
Expand All @@ -169,7 +159,7 @@ protected function processRepeatableUploads(Model $entry, Collection $values): a
});
}

return $values->toArray();
return $values;
}

private function retrieveRepeatableFiles(Model $entry): Model
Expand Down
32 changes: 25 additions & 7 deletions src/app/Library/Uploaders/Uploader.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Backpack\CRUD\app\Library\Uploaders;

use Backpack\CRUD\app\Library\CrudPanel\CrudPanelFacade as CRUD;
use Backpack\CRUD\app\Library\Uploaders\Support\Interfaces\UploaderInterface;
use Backpack\CRUD\app\Library\Uploaders\Support\Traits\HandleFileNaming;
use Backpack\CRUD\app\Library\Uploaders\Support\Traits\HandleRepeatableUploads;
Expand Down Expand Up @@ -76,17 +77,19 @@ public function storeUploadedFiles(Model $entry): Model
return $this->handleRepeatableFiles($entry);
}

$values = $this->getUploadedFilesFromRequest();

if ($this->attachedToFakeField) {
$fakeFieldValue = $entry->{$this->attachedToFakeField};
$fakeFieldValue = is_string($fakeFieldValue) ? json_decode($fakeFieldValue, true) : (array) $fakeFieldValue;
$fakeFieldValue[$this->getAttributeName()] = $this->uploadFiles($entry);
$fakeFieldValue[$this->getAttributeName()] = $this->uploadFiles($entry, $values);

$entry->{$this->attachedToFakeField} = isset($entry->getCasts()[$this->attachedToFakeField]) ? $fakeFieldValue : json_encode($fakeFieldValue);

return $entry;
}

$entry->{$this->getAttributeName()} = $this->uploadFiles($entry);
$entry->{$this->getAttributeName()} = $this->uploadFiles($entry, $values);

return $entry;
}
Expand Down Expand Up @@ -151,6 +154,21 @@ public function shouldDeleteFiles(): bool
return $this->deleteWhenEntryIsDeleted;
}

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

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

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

public function getIdentifier(): string
{
if ($this->handleRepeatableFiles) {
Expand Down Expand Up @@ -191,6 +209,11 @@ public function getValueWithoutPath(?string $value = null): ?string
return $value ? Str::after($value, $this->path) : null;
}

public function getUploadedFilesFromRequest()
{
return CRUD::getRequest()->file($this->getNameForRequest());
}

public function isFake(): bool
{
return $this->attachedToFakeField !== false;
Expand All @@ -201,11 +224,6 @@ public function getFakeAttribute(): bool|string
return $this->attachedToFakeField;
}

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

/*******************************
* Setters - fluently configure the uploader
*******************************/
Expand Down
61 changes: 58 additions & 3 deletions src/resources/views/crud/fields/summernote.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
name="{{ $field['name'] }}"
data-init-function="bpFieldInitSummernoteElement"
data-options="{{ json_encode($field['options']) }}"
data-upload-enabled="{{ isset($field['withFiles']) || isset($field['withMedia']) || isset($field['imageUploadEndpoint']) ? 'true' : 'false'}}"
data-upload-endpoint="{{ isset($field['imageUploadEndpoint']) ? $field['imageUploadEndpoint'] : 'false'}}"
data-upload-operation="{{ $crud->get('ajax-upload.formOperation') }}"
bp-field-main-input
@include('crud::fields.inc.attributes', ['default_class' => 'form-control summernote'])
>{{ old_empty_or_null($field['name'], '') ?? $field['value'] ?? $field['default'] ?? '' }}</textarea>
Expand All @@ -31,8 +34,8 @@
{{-- FIELD CSS - will be loaded in the after_styles section --}}
@push('crud_fields_styles')
{{-- include summernote css --}}
@basset('https://unpkg.com/summernote@0.8.20/dist/summernote-lite.min.css')
@basset('https://unpkg.com/summernote@0.8.20/dist/font/summernote.woff2', false)
@basset('https://unpkg.com/summernote@0.9.1/dist/summernote-lite.min.css')
@basset('https://unpkg.com/summernote@0.9.1/dist/font/summernote.woff2', false)
@bassetBlock('backpack/crud/fields/summernote-field.css')
<style type="text/css">
.note-editor.note-frame .note-status-output, .note-editor.note-airframe .note-status-output {
Expand All @@ -45,7 +48,7 @@
{{-- FIELD JS - will be loaded in the after_scripts section --}}
@push('crud_fields_scripts')
{{-- include summernote js --}}
@basset('https://unpkg.com/summernote@0.8.20/dist/summernote-lite.min.js')
@basset('https://unpkg.com/summernote@0.9.1/dist/summernote-lite.min.js')
@bassetBlock('backpack/crud/fields/summernote-field.js')
<script>
function bpFieldInitSummernoteElement(element) {
Expand All @@ -54,7 +57,59 @@ function bpFieldInitSummernoteElement(element) {
let summernotCallbacks = {
onChange: function(contents, $editable) {
element.val(contents).trigger('change');
},
}

if(element.data('upload-enabled') === true){
let imageUploadEndpoint = element.data('upload-endpoint') !== false ? element.data('upload-endpoint') : '{{ url($crud->route. '/ajax-upload') }}';
let paramName = typeof element.attr('data-repeatable-input-name') !== 'undefined' ? element.closest('[data-repeatable-identifier]').attr('data-repeatable-identifier')+'#'+element.attr('data-repeatable-input-name') : element.attr('name');
summernotCallbacks.onImageUpload = function(file) {
var data = new FormData();
data.append(paramName, file[0]);
data.append('_token', document.querySelector('meta[name="csrf-token"]').getAttribute('content'));
data.append('fieldName', paramName);
data.append('operation', element.data('upload-operation'));

var xhr = new XMLHttpRequest();
xhr.open('POST', imageUploadEndpoint, true);
xhr.setRequestHeader('Accept', 'application/json');

xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
var response = JSON.parse(xhr.responseText);
element.summernote('insertImage', response.data.filePath);
} else {
var response = JSON.parse(xhr.responseText);
let errorBagName = paramName;
// it's in a repeatable field
if(errorBagName.includes('#')) {
errorBagName = errorBagName.replace('#', '.0.');
}
let errorMessages = typeof response.errors !== 'undefined' ? response.errors[errorBagName].join('<br/>') : response + '<br/>';

let summernoteTextarea = element[0];

// remove previous error messages
summernoteTextarea.parentNode.querySelector('.invalid-feedback')?.remove();

// add the red text classes
summernoteTextarea.parentNode.classList.add('text-danger');

// create the error message container
let errorContainer = document.createElement("div");
errorContainer.classList.add('invalid-feedback', 'd-block');
errorContainer.innerHTML = errorMessages;
summernoteTextarea.parentNode.appendChild(errorContainer);
}
};

xhr.onerror = function() {
console.error('An error occurred during the upload process');
};

xhr.send(data);
}

}

element.on('CrudField:disable', function(e) {
Expand Down
Binary file added tests/config/Uploads/assets/avatar7.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/config/Uploads/assets/pic7.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public function up(): void
$table->json('upload_multiple')->nullable();
$table->json('dropzone')->nullable();
$table->json('easymde')->nullable();
$table->json('summernote')->nullable();
$table->json('repeatable')->nullable();
$table->json('extras')->nullable();
});
Expand Down
Loading