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

[WIP] Next CRUD Version #5715

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ name: PHP Tests

on:
push:
pull_request:
schedule:
- cron: '0 0 * * *'

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ composer.lock
.phpunit.result.cache
src/public/packages/
/.phpunit.cache
coverage/

7 changes: 5 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,12 @@
]
},
"scripts": {
"test": "vendor/bin/phpunit --testdox",
"test": [
"@putenv XDEBUG_MODE=off",
"vendor/bin/phpunit"
],
"test-failing": "vendor/bin/phpunit --order-by=defects --stop-on-failure",
"test-coverage": "XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-text"
"test-coverage": "XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-html=coverage"
},
"extra": {
"branch-alias": {
Expand Down
3 changes: 3 additions & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<include>
<directory suffix=".php">./src/app/Library/CrudPanel/Traits/</directory>
<directory>./src/app/Library/Validation/</directory>
<directory>./src/app/Library/Uploaders/</directory>
<directory suffix=".php">./src/app/Library/CrudPanel/</directory>
<directory suffix=".php">./src/app/Models/Traits/</directory>
<file>./src/app/Library/Widget.php</file>
Expand All @@ -35,9 +36,11 @@
</source>
<php>
<env name="APP_ENV" value="testing"/>
<env name="APP_KEY" value="AckfSECXIvnK5r28GVIWUAxmbBSjTsmF"/>
<env name="BCRYPT_ROUNDS" value="12"/>
<env name="CACHE_DRIVER" value="array"/>
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_FOREIGN_KEYS" value="true"/>
<env name="DB_DATABASE" value=":memory:"/>
<env name="MAIL_MAILER" value="array"/>
<env name="QUEUE_CONNECTION" value="sync"/>
Expand Down
1 change: 1 addition & 0 deletions src/app/Http/Controllers/Operations/ListOperation.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ protected function setupListDefaults()

$this->crud->operation('list', function () {
$this->crud->loadDefaultOperationSettingsFromConfig();
$this->crud->setOperationSetting('datatablesUrl', $this->crud->getRoute());
});
}

Expand Down
4 changes: 2 additions & 2 deletions src/app/Library/CrudPanel/Traits/FieldsProtectedMethods.php
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,8 @@ protected function makeSureSubfieldsHaveNecessaryAttributes($field)
$subfield['name'] = Str::replace(' ', '', $subfield['name']);

$subfield['parentFieldName'] = $field['name'];
$subfield['baseFieldName'] = is_array($subfield['name']) ? implode(',', $subfield['name']) : $subfield['name'];
$subfield['baseFieldName'] = Str::afterLast($subfield['baseFieldName'], '.');

if (! isset($field['model'])) {
// we're inside a simple 'repeatable' with no model/relationship, so
Expand All @@ -294,8 +296,6 @@ protected function makeSureSubfieldsHaveNecessaryAttributes($field)
$currentEntity = $subfield['baseEntity'] ?? $field['entity'];
$subfield['baseModel'] = $subfield['baseModel'] ?? $field['model'];
$subfield['baseEntity'] = isset($field['baseEntity']) ? $field['baseEntity'].'.'.$currentEntity : $currentEntity;
$subfield['baseFieldName'] = is_array($subfield['name']) ? implode(',', $subfield['name']) : $subfield['name'];
$subfield['baseFieldName'] = Str::afterLast($subfield['baseFieldName'], '.');
}

$field['subfields'][$key] = $this->makeSureFieldHasNecessaryAttributes($subfield);
Expand Down
34 changes: 28 additions & 6 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 @@ -57,9 +57,16 @@ public function uploadFiles(Model $entry, $value = null)
}
}

return isset($entry->getCasts()[$this->getName()]) ? $previousFiles : json_encode($previousFiles);
$previousFiles = array_values($previousFiles);

if (empty($previousFiles)) {
return null;
}

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

/** @codeCoverageIgnore */
public function uploadRepeatableFiles($files, $previousRepeatableValues, $entry = null)
{
$fileOrder = $this->getFileOrderFromRequest();
Expand All @@ -73,11 +80,26 @@ public function uploadRepeatableFiles($files, $previousRepeatableValues, $entry
}
}
}
// create a temporary variable that we can unset keys
// everytime one is found. That way we avoid iterating
// already handled keys (notice we do a deep array copy)
$tempFileOrder = array_map(function ($item) {
return $item;
}, $fileOrder);

foreach ($previousRepeatableValues as $previousRow => $previousFiles) {
foreach ($previousFiles ?? [] as $key => $file) {
$key = array_search($file, $fileOrder, true);
if ($key === false) {
$previousFileInArray = array_filter($tempFileOrder, function ($items, $key) use ($file, $tempFileOrder) {
$found = array_search($file, $items ?? [], true);
if ($found !== false) {
Arr::forget($tempFileOrder, $key.'.'.$found);

return true;
}

return false;
}, ARRAY_FILTER_USE_BOTH);
if ($file && ! $previousFileInArray) {
Storage::disk($this->getDisk())->delete($file);
}
}
Expand All @@ -86,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
13 changes: 9 additions & 4 deletions src/app/Library/Uploaders/SingleBase64Image.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;

/** @codeCoverageIgnore */
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 @@ -51,7 +51,7 @@ public function uploadRepeatableFiles($values, $previousRepeatableValues, $entry
}
}

$imagesToDelete = array_diff($previousRepeatableValues, $values);
$imagesToDelete = array_diff(array_filter($previousRepeatableValues), $values);

foreach ($imagesToDelete as $image) {
Storage::disk($this->getDisk())->delete($image);
Expand All @@ -60,13 +60,18 @@ 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');
}

protected function shouldKeepPreviousValueUnchanged(Model $entry, $entryValue): bool
public function shouldKeepPreviousValueUnchanged(Model $entry, $entryValue): bool
{
return $entry->exists && is_string($entryValue) && ! Str::startsWith($entryValue, 'data:image');
}

public function getUploadedFilesFromRequest()
{
return CRUD::getRequest()->get($this->getNameForRequest());
}
}
18 changes: 11 additions & 7 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 @@ -38,6 +37,7 @@ public function uploadFiles(Model $entry, $value = null)
return $previousFile;
}

/** @codeCoverageIgnore */
public function uploadRepeatableFiles($values, $previousRepeatableValues, $entry = null)
{
$orderedFiles = $this->getFileOrderFromRequest();
Expand All @@ -53,9 +53,13 @@ public function uploadRepeatableFiles($values, $previousRepeatableValues, $entry
}

foreach ($previousRepeatableValues as $row => $file) {
if ($file && ! isset($orderedFiles[$row])) {
$orderedFiles[$row] = null;
Storage::disk($this->getDisk())->delete($file);
if ($file) {
if (! isset($orderedFiles[$row])) {
$orderedFiles[$row] = null;
}
if (! in_array($file, $orderedFiles)) {
Storage::disk($this->getDisk())->delete($file);
}
}
}

Expand All @@ -65,17 +69,17 @@ public function uploadRepeatableFiles($values, $previousRepeatableValues, $entry
/**
* Single file uploaders send no value when they are not dirty.
*/
protected function shouldKeepPreviousValueUnchanged(Model $entry, $entryValue): bool
public function shouldKeepPreviousValueUnchanged(Model $entry, $entryValue): bool
{
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
19 changes: 17 additions & 2 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 All @@ -30,6 +34,8 @@ public function repeats(string $repeatableContainerName): self;

public function relationship(bool $isRelation): self;

public function fake(bool|string $isFake): self;

/**
* Getters.
*/
Expand All @@ -53,11 +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;

/**
* Strategy methods.
*/
public function shouldDeleteFiles(): bool;

public function hasDeletedFiles($entryValue): bool;

public function shouldUploadFiles(mixed $value): bool;

public function shouldKeepPreviousValueUnchanged(Model $entry, mixed $entryValue): bool;
}
17 changes: 15 additions & 2 deletions src/app/Library/Uploaders/Support/RegisterUploadEvents.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ private function registerSubfieldEvent(array $subfield, bool $registerModelEvent
$uploader = $this->getUploader($subfield, $this->uploaderConfiguration);
$crudObject = $this->crudObject->getAttributes();
$uploader = $uploader->repeats($crudObject['name']);
$uploader = $uploader->fake((isset($crudObject['fake']) && $crudObject['fake']) ? ($crudObject['store_in'] ?? 'extras') : false);

// If this uploader is already registered bail out. We may endup here multiple times when doing modifications to the crud object.
// Changing `subfields` properties will call the macros again. We prevent duplicate entries by checking
Expand Down Expand Up @@ -139,6 +140,14 @@ private function setupModelEvents(string $model, UploaderInterface $uploader): v
$uploader->deleteUploadedFiles($entry);
});

// if the uploader is a relationship and handles repeatable files, we will also register the deleting event on the
// parent model. that way we can control the deletion of the files when the parent model is deleted.
if ($uploader->isRelationship() && $uploader->handleRepeatableFiles) {
app('crud')->model::deleting(function ($entry) use ($uploader) {
$uploader->deleteUploadedFiles($entry);
});
}

app('UploadersRepository')->markAsHandled($uploader->getIdentifier());
}

Expand All @@ -154,9 +163,13 @@ private function setupModelEvents(string $model, UploaderInterface $uploader): v
*/
private function getUploader(array $crudObject, array $uploaderConfiguration): UploaderInterface
{
$customUploader = isset($uploaderConfiguration['uploader']) && class_exists($uploaderConfiguration['uploader']);
$hasCustomUploader = isset($uploaderConfiguration['uploader']);

if ($hasCustomUploader && ! is_a($uploaderConfiguration['uploader'], UploaderInterface::class, true)) {
throw new Exception('Invalid uploader class provided for '.$this->crudObjectType.' type: '.$crudObject['type']);
}

if ($customUploader) {
if ($hasCustomUploader) {
return $uploaderConfiguration['uploader']::for($crudObject, $uploaderConfiguration);
}

Expand Down
Loading
Loading