From d082cecff10d346ed9d60a7335ecdfb4ae606948 Mon Sep 17 00:00:00 2001 From: Yuriy Martini Date: Thu, 5 Mar 2020 10:19:52 +0100 Subject: [PATCH 1/2] a huge code refactoring with some code improvements --- resources/js/components/IndexField.vue | 15 +-- src/DuplicateField.php | 56 ++++++++++- src/Http/Controllers/DuplicateController.php | 98 ++++++++++++++------ 3 files changed, 134 insertions(+), 35 deletions(-) diff --git a/resources/js/components/IndexField.vue b/resources/js/components/IndexField.vue index 4d813b0..e46d1d6 100644 --- a/resources/js/components/IndexField.vue +++ b/resources/js/components/IndexField.vue @@ -30,12 +30,15 @@ export default { onClick() { axios .post("/nova-vendor/jackabox/nova-duplicate", { - model: this.field.model ? this.field.model : "", - id: this.field.id ? this.field.id : "", - resource: this.field.resource ? this.field.resource : "", - relations: this.field.relations ? this.field.relations : "", - except: this.field.except ? this.field.except : "", - override: this.field.override ? this.field.override : "" + model_class: this.field.model_class, + model_key_name: this.field.model_key_name, + model_key_value: this.field.model_key_value, + resource: this.field.resource, + except: this.field.except || null, + override: this.field.override || null, + relations: this.field.relations || null, + relations_except: this.field.relations_except || null, + relations_override: this.field.relations_override || null, }) .then(response => { window.location.replace(response.data.destination); diff --git a/src/DuplicateField.php b/src/DuplicateField.php index e28049a..00e3f23 100644 --- a/src/DuplicateField.php +++ b/src/DuplicateField.php @@ -14,10 +14,62 @@ class DuplicateField extends Field */ public $component = 'duplicate-field'; - public function __construct(string $name, ? string $attribute = null, ? mixed $resolveCallback = null) + public function __construct(string $name, Model $model, string $resource) { - parent::__construct(null, null, null); + parent::__construct($name); + + $this->withMeta([ + 'model_class' => get_class($model), + 'model_key_name' => $model->getKeyName(), + 'model_key_value' => $model->getKey(), + 'resource' => $resource, + ]); $this->onlyOnIndex(); } + + /** + * @param string[] $attributes + * @return $this + */ + public function except(array $attributes) + { + return $this->withMeta(['except' => $attributes]); + } + + /** + * @param string[] $attributes + * @return $this + */ + public function override(array $attributes) + { + return $this->withMeta(['override' => $attributes]); + } + + /** + * @param string[] $relations + * @return $this + */ + public function relations(array $relations) + { + return $this->withMeta(['relations' => $relations]); + } + + /** + * @param string[] $except + * @return $this + */ + public function relationsExcept(array $except) + { + return $this->withMeta(['relations_except' => $except]); + } + + /** + * @param string[] $override + * @return $this + */ + public function relationsOverride(array $override) + { + return $this->withMeta(['relations_override' => $override]); + } } diff --git a/src/Http/Controllers/DuplicateController.php b/src/Http/Controllers/DuplicateController.php index 31bde12..f6fc01c 100644 --- a/src/Http/Controllers/DuplicateController.php +++ b/src/Http/Controllers/DuplicateController.php @@ -2,59 +2,103 @@ namespace Jackabox\DuplicateField\Http\Controllers; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Http\Request; use Illuminate\Routing\Controller; +use Illuminate\Support\Arr; class DuplicateController extends Controller { /** * Duplicate a nova field and all of the relations defined. + * @param Request $request + * @return array */ public function duplicate(Request $request) { - // Replicate the model - $model = $request->model::where('id', $request->id)->first(); + $modelClass = $request->post('model_class'); + $modelKeyName = $request->post('model_key_name'); + $modelKeyValue = $request->post('model_key_value'); + $resource = $request->post('resource'); + + $except = Arr::wrap($request->post('except')); + $override = Arr::wrap($request->post('override')); + $relations = Arr::wrap($request->post('relations')); + $relationsExcept = Arr::wrap($request->get('relations_except')); + $relationsOverride = Arr::wrap($request->post('relations_override')); + + /** @var Model|null $model */ + $model = $modelClass::where($modelKeyName, $modelKeyValue)->first(); if (!$model) { return [ 'status' => 404, 'message' => 'No model found.', - 'destination' => config('nova.url') . config('nova.path') . '/resources/' . $request->resource . '/' + 'destination' => config('nova.url') . config('nova.path') . '/resources/' . $resource . '/' ]; } - $newModel = $model->replicate($request->except); + $newModel = $this->replicate($model, $except, $override, $relations, $relationsExcept, $relationsOverride); - if (is_array($request->override)) { - foreach ($request->override as $field => $value) { - $newModel->{$field} = $value; - } + // return response and redirect. + return [ + 'status' => 200, + 'message' => 'Done', + 'destination' => url(config('nova.path') . '/resources/' . $resource . '/' . $newModel->getKey()) + ]; + } + + /** + * @param Model $model + * @param array $except + * @param array $override + * @param array $relations + * @param array $relationsExcept + * @param array $relationsOverride + * @return Model + */ + private function replicate(Model $model, array $except = [], array $override = [], array $relations = [], array $relationsExcept = [], array $relationsOverride = []) + { + $newModel = $model->replicate(array_keys($except)); + + foreach ($override as $field => $value) { + $newModel->{$field} = $value; } $newModel->push(); - if (isset($request->relations) && !empty($request->relations)) { - // load the relations - $model->load($request->relations); + $this->replicateRelations($model, $newModel, $relations, $relationsExcept, $relationsOverride); - foreach ($model->getRelations() as $relation => $items) { - // works for hasMany - foreach ($items as $item) { - // clean up our models, remove the id and remove the appends - unset($item->id); - $item->setAppends([]); + return $newModel; + } - // create a relation on the new model with the data. - $newModel->{$relation}()->create($item->toArray()); - } - } + /** + * todo: implement deep relations replication + * @param Model $originalModel + * @param Model $newModel + * @param array $relations + * @param array $except + * @param array $override + */ + private function replicateRelations(Model $originalModel, Model $newModel, array $relations = [], array $except = [], array $override = []) + { + // tested only with hasMany + + if (!count($relations)){ + return; } - // return response and redirect. - return [ - 'status' => 200, - 'message' => 'Done', - 'destination' => url(config('nova.path') . '/resources/' . $request->resource . '/' . $newModel->id) - ]; + $originalModel->load($relations); + + foreach ($originalModel->getRelations() as $relationName => $items) { + /** @var BelongsTo $relation */ + $relation = $newModel->{$relationName}(); + $relationOverride = array_merge(Arr::wrap(Arr::get($override, $relationName)), [$relation->getForeignKeyName() => $newModel->getKey()]); + /** @var Model $item */ + foreach ($items as $item) { + $this->replicate($item, $except, $relationOverride); + } + } } } From 0260952dd262dd730ecfebdf5a6c4d973cddb1a8 Mon Sep 17 00:00:00 2001 From: Yuriy Martini Date: Wed, 11 Mar 2020 22:09:16 +0100 Subject: [PATCH 2/2] Updated README.md --- README.md | 58 +++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index e6c4d6f..e086ca7 100644 --- a/README.md +++ b/README.md @@ -14,24 +14,66 @@ Allow users to duplicate a record through the Laravel Nova Admin Panel along wit composer require jackabox/nova-duplicate-field ``` +### Basic usage Reference the duplicate field at the top of your Nova resource and then include the necessary code within the fields. ```php use Jackabox\DuplicateField\DuplicateField + +// ... + +DuplicateField::make('Duplicate', $this->model(), static::uriKey()), +``` + +### Customization + +#### Except attributes +Pass an array of attributes to not replicate. + +```php +DuplicateField::make('Duplicate', $this->model(), static::uriKey()) + ->except(['status']), +``` + +#### Override attributes +Pass an array of attributes with values to override. + +```php +DuplicateField::make('Duplicate', $this->model(), static::uriKey()) + ->override(['status' => 'pending']), ``` +#### Relations +Pass an array of relations to replicate also. + +```php +DuplicateField::make('Duplicate', $this->model(), static::uriKey()) + ->relations(['translations']), +``` + +#### Relations except attributes +Pass an array of attributes for each relation to not replicate. + +```php +DuplicateField::make('Duplicate', $this->model(), static::uriKey()) + ->relations(['translations']) + ->relationsExcept([ + 'translations' => ['slug'], + ]), +``` + +#### Relations except attributes +Pass an array of attributes for each relation to override. + ```php -DuplicateField::make('Duplicate') - ->withMeta([ - 'resource' => 'specialisms', // resource url - 'model' => 'App\Models\Specialism', // model path - 'id' => $this->id, // id of record - 'relations' => ['one', 'two'], // an array of any relations to load (nullable). - 'except' => ['status'], // an array of fields to not replicate (nullable). - 'override' => ['status' => 'pending'] // an array of fields and values which will be set on the modal after duplicating (nullable). +DuplicateField::make('Duplicate', $this->model(), static::uriKey()) + ->relations(['translations']) + ->relationsOverride([ + 'translations' => ['title' => 'New value'], ]), ``` +### Note Duplicate field only works on the index view at the moment (plans to expand this are coming) and already passes through `onlyOnIndex()` as an option. ### Hooking Into Replication