Skip to content

Commit

Permalink
Clean up uploaders (#5725)
Browse files Browse the repository at this point in the history
* wip

* clean up

* add get uploadedFilesFromRequest to clean up uploaders

* Apply fixes from StyleCI

[ci skip] [skip ci]

* fix type cast

* add summernote uploader

* update summernote

* add summernote

* add more test assets

---------

Co-authored-by: StyleCI Bot <[email protected]>
  • Loading branch information
pxpm and StyleCIBot authored Dec 5, 2024
1 parent 8ece6d2 commit 714f500
Show file tree
Hide file tree
Showing 10 changed files with 110 additions and 36 deletions.
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

0 comments on commit 714f500

Please sign in to comment.