Passed
Push — dependabot/npm_and_yarn/docs/w... ( a770a9...3a5b31 )
by
unknown
07:47
created

HandleRepeaters::getFormFieldsHandleRepeaters()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.0625

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 2
dl 0
loc 7
ccs 3
cts 4
cp 0.75
crap 2.0625
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace A17\Twill\Repositories\Behaviors;
4
5
use Carbon\Carbon;
6
use Illuminate\Support\Arr;
7
use Illuminate\Support\Collection;
8
use Illuminate\Support\Str;
9
10
trait HandleRepeaters
11
{
12
    /**
13
     * All repeaters used in the model, as an array of repeater names: 
14
     * [
15
     *  'article_repeater',
16
     *  'page_repeater'
17
     * ].
18
     * 
19
     * When only the repeater name is given here, its model and relation will be inferred from the name.
20
     * Each repeater's detail can also be override with an array
21
     * [
22
     *  'article_repeater',
23
     *  'page_repeater' => [
24
     *      'model' => 'Page',
25
     *      'relation' => 'pages'
26
     *  ]
27
     * ]
28
     *
29
     * @var string|array(array)|array(mix(string|array))
30
     */
31
    protected $repeaters = [];
32
    
33
    /**
34
     * @param \A17\Twill\Models\Model $object
35
     * @param array $fields
36
     * @return void
37
     */
38 31
    public function afterSaveHandleRepeaters($object, $fields)
39
    {
40 31
        foreach ($this->getRepeaters() as $repeater) {
41
            $this->updateRepeater($object, $fields, $repeater['relation'], $repeater['model'], $repeater['repeaterName']);
42
        }
43 31
    }
44
45
    /**
46
     * @param \A17\Twill\Models\Model $object
47
     * @param array $fields
48
     * @return array
49
     */
50 6
    public function getFormFieldsHandleRepeaters($object, $fields)
51
    {
52 6
        foreach ($this->getRepeaters() as $repeater) {
53
            $fields = $this->getFormFieldsForRepeater($object, $fields, $repeater['relation'], $repeater['model'], $repeater['repeaterName']);
54
        }
55
        
56 6
        return $fields;
57
    }
58
59
    public function updateRepeaterMany($object, $fields, $relation, $keepExisting = true, $model = null)
60
    {
61
        $relationFields = $fields['repeaters'][$relation] ?? [];
62
        $relationRepository = $this->getModelRepository($relation, $model);
0 ignored issues
show
Bug introduced by
It seems like getModelRepository() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

62
        /** @scrutinizer ignore-call */ 
63
        $relationRepository = $this->getModelRepository($relation, $model);
Loading history...
63
64
        if (!$keepExisting) {
65
            $object->$relation()->each(function ($repeaterElement) {
66
                $repeaterElement->forceDelete();
67
            });
68
        }
69
70
        foreach ($relationFields as $relationField) {
71
            $newRelation = $relationRepository->create($relationField);
72
            $object->$relation()->attach($newRelation->id);
73
        }
74
    }
75
76
77
    public function updateRepeaterMorphMany($object, $fields, $relation, $morph = null, $model = null, $repeaterName = null)
78
    {
79
        if (!$repeaterName) {
80
            $repeaterName = $relation;
81
        }
82
83
        $relationFields = $fields['repeaters'][$repeaterName] ?? [];
84
        $relationRepository = $this->getModelRepository($relation, $model);
85
86
        $morph = $morph ?: $relation;
87
88
        $morphFieldType = $morph.'_type';
89
        $morphFieldId = $morph.'_id';
90
91
        // if no relation field submitted, soft deletes all associated rows
92
        if (!$relationFields) {
93
            $relationRepository->updateBasic(null, [
94
                'deleted_at' => Carbon::now(),
95
            ], [
96
                $morphFieldType => $object->getMorphClass(),
97
                $morphFieldId => $object->id,
98
            ]);
99
        }
100
101
        // keep a list of updated and new rows to delete (soft delete?) old rows that were deleted from the frontend
102
        $currentIdList = [];
103
104
        foreach ($relationFields as $index => $relationField) {
105
            $relationField['position'] = $index + 1;
106
            if (isset($relationField['id']) && Str::startsWith($relationField['id'], $relation)) {
107
                // row already exists, let's update
108
                $id = str_replace($relation . '-', '', $relationField['id']);
109
                $relationRepository->update($id, $relationField);
110
                $currentIdList[] = $id;
111
            } else {
112
                // new row, let's attach to our object and create
113
                unset($relationField['id']);
114
                $newRelation = $relationRepository->create($relationField);
115
                $object->$relation()->save($newRelation);
116
                $currentIdList[] = $newRelation['id'];
117
            }
118
        }
119
120
        foreach ($object->$relation->pluck('id') as $id) {
121
            if (!in_array($id, $currentIdList)) {
122
                $relationRepository->updateBasic(null, [
123
                    'deleted_at' => Carbon::now(),
124
                ], [
125
                    'id' => $id,
126
                ]);
127
            }
128
        }
129
    }
130
131
    /**
132
     * Given relation, model and repeaterName, retrieve the repeater data from request and update the database record.
133
     *
134
     * @param  object $object
135
     * @param  array $fields
136
     * @param  string $relation
137
     * @param  string $model
138
     * @param  string $repeaterName
139
     *
140
     * @return void
141
     */
142
    public function updateRepeater($object, $fields, $relation, $model = null, $repeaterName = null)
143
    {
144
        if (!$repeaterName) {
145
            $repeaterName = $relation;
146
        }
147
148
        $relationFields = $fields['repeaters'][$repeaterName] ?? [];
149
150
        $relationRepository = $this->getModelRepository($relation, $model);
151
152
        // if no relation field submitted, soft deletes all associated rows
153
        if (!$relationFields) {
154
            $relationRepository->updateBasic(null, [
155
                'deleted_at' => Carbon::now(),
156
            ], [
157
                $this->model->getForeignKey() => $object->id,
158
            ]);
159
        }
160
161
        // keep a list of updated and new rows to delete (soft delete?) old rows that were deleted from the frontend
162
        $currentIdList = [];
163
164
        foreach ($relationFields as $index => $relationField) {
165
            $relationField['position'] = $index + 1;
166
            if (isset($relationField['id']) && Str::startsWith($relationField['id'], $relation)) {
167
                // row already exists, let's update
168
                $id = str_replace($relation . '-', '', $relationField['id']);
169
                $relationRepository->update($id, $relationField);
170
                $currentIdList[] = $id;
171
            } else {
172
                // new row, let's attach to our object and create
173
                $relationField[$this->model->getForeignKey()] = $object->id;
174
                unset($relationField['id']);
175
                $newRelation = $relationRepository->create($relationField);
176
                $currentIdList[] = $newRelation['id'];
177
            }
178
        }
179
180
        foreach ($object->$relation->pluck('id') as $id) {
181
            if (!in_array($id, $currentIdList)) {
182
                $relationRepository->updateBasic(null, [
183
                    'deleted_at' => Carbon::now(),
184
                ], [
185
                    'id' => $id,
186
                ]);
187
            }
188
        }
189
    }
190
191
    /**
192
     * Given relation, model and repeaterName, get the necessary fields for rendering a repeater
193
     *
194
     * @param  object $object
195
     * @param  array $fields
196
     * @param  string $relation
197
     * @param  string $model
198
     * @param  string $repeaterName
199
     *
200
     * @return array
201
     */
202
    public function getFormFieldsForRepeater($object, $fields, $relation, $model = null, $repeaterName = null)
203
    {
204
        if (!$repeaterName) {
205
            $repeaterName = $relation;
206
        }
207
208
        $repeaters = [];
209
        $repeatersFields = [];
210
        $repeatersBrowsers = [];
211
        $repeatersMedias = [];
212
        $repeatersFiles = [];
213
        $relationRepository = $this->getModelRepository($relation, $model);
214
        $repeatersConfig = config('twill.block_editor.repeaters');
215
216
        foreach ($object->$relation as $relationItem) {
217
            $repeaters[] = [
218
                'id' => $relation . '-' . $relationItem->id,
219
                'type' => $repeatersConfig[$repeaterName]['component'],
220
                'title' => $repeatersConfig[$repeaterName]['title'],
221
            ];
222
223
            $relatedItemFormFields = $relationRepository->getFormFields($relationItem);
224
            $translatedFields = [];
225
226
            if (isset($relatedItemFormFields['translations'])) {
227
                foreach ($relatedItemFormFields['translations'] as $key => $values) {
228
                    $repeatersFields[] = [
229
                        'name' => "blocks[$relation-$relationItem->id][$key]",
230
                        'value' => $values,
231
                    ];
232
233
                    $translatedFields[] = $key;
234
                }
235
            }
236
237
            if (isset($relatedItemFormFields['medias'])) {
238
                if (config('twill.media_library.translated_form_fields', false)) {
239
                    Collection::make($relatedItemFormFields['medias'])->each(function ($rolesWithMedias, $locale) use (&$repeatersMedias, $relation, $relationItem) {
240
                        $repeatersMedias[] = Collection::make($rolesWithMedias)->mapWithKeys(function ($medias, $role) use ($locale, $relation, $relationItem) {
241
                            return [
242
                                "blocks[$relation-$relationItem->id][$role][$locale]" => $medias,
243
                            ];
244
                        })->toArray();
245
                    });
246
                } else {
247
                    foreach ($relatedItemFormFields['medias'] as $key => $values) {
248
                        $repeatersMedias["blocks[$relation-$relationItem->id][$key]"] = $values;
249
                    }
250
                }
251
            }
252
253
            if (isset($relatedItemFormFields['files'])) {
254
                Collection::make($relatedItemFormFields['files'])->each(function ($rolesWithFiles, $locale) use (&$repeatersFiles, $relation, $relationItem) {
255
                    $repeatersFiles[] = Collection::make($rolesWithFiles)->mapWithKeys(function ($files, $role) use ($locale, $relation, $relationItem) {
256
                        return [
257
                            "blocks[$relation-$relationItem->id][$role][$locale]" => $files,
258
                        ];
259
                    })->toArray();
260
                });
261
            }
262
263
            if (isset($relatedItemFormFields['browsers'])) {
264
                foreach ($relatedItemFormFields['browsers'] as $key => $values) {
265
                    $repeatersBrowsers["blocks[$relation-$relationItem->id][$key]"] = $values;
266
                }
267
            }
268
269
            $itemFields = method_exists($relationItem, 'toRepeaterArray') ? $relationItem->toRepeaterArray() : Arr::except($relationItem->attributesToArray(), $translatedFields);
270
271
            foreach ($itemFields as $key => $value) {
272
                $repeatersFields[] = [
273
                    'name' => "blocks[$relation-$relationItem->id][$key]",
274
                    'value' => $value,
275
                ];
276
            }
277
278
        }
279
280
        if (!empty($repeatersMedias) && config('twill.media_library.translated_form_fields', false)) {
281
            $repeatersMedias = call_user_func_array('array_merge', $repeatersMedias);
282
        }
283
284
        if (!empty($repeatersFiles)) {
285
            $repeatersFiles = call_user_func_array('array_merge', $repeatersFiles);
286
        }
287
288
        $fields['repeaters'][$repeaterName] = $repeaters;
289
        $fields['repeaterFields'][$repeaterName] = $repeatersFields;
290
        $fields['repeaterMedias'][$repeaterName] = $repeatersMedias;
291
        $fields['repeaterFiles'][$repeaterName] = $repeatersFiles;
292
        $fields['repeaterBrowsers'][$repeaterName] = $repeatersBrowsers;
293
294
        return $fields;
295
    }
296
297
    /**
298
     * Get all repeaters' model and relation from the $repeaters attribute. 
299
     * The missing information will be inferred by convention of Twill.
300
     *
301
     * @return Illuminate\Support\Collection
0 ignored issues
show
Bug introduced by
The type A17\Twill\Repositories\B...nate\Support\Collection was not found. Did you mean Illuminate\Support\Collection? If so, make sure to prefix the type with \.
Loading history...
302
     */
303 32
    protected function getRepeaters()
304
    {
305
        return collect($this->repeaters)->map(function ($repeater, $key) {
306
            $repeaterName = is_string($repeater) ? $repeater : $key;
307
            return [
308
                'relation' => !empty($repeater['relation']) ? $repeater['relation'] : $this->inferRelationFromRepeaterName($repeaterName),
309
                'model' => !empty($repeater['model']) ? $repeater['model'] : $this->inferModelFromRepeaterName($repeaterName),
310
                'repeaterName' => $repeaterName
311
            ];
312 32
        })->values();
313
    }
314
315
    /**
316
     * The relation name shoud be lower camel case, ex. userGroup, contactOffice
317
     *
318
     * @param  string $repeaterName
319
     *
320
     * @return string
321
     */
322
    protected function inferRelationFromRepeaterName(string $repeaterName): string
323
    {
324
        return Str::camel($repeaterName);
325
    }
326
327
    /**
328
     * The model name should be singular upper camel case, ex. User, ArticleType
329
     *
330
     * @param  string $repeaterName
331
     *
332
     * @return string
333
     */
334
    protected function inferModelFromRepeaterName(string $repeaterName): string
335
    {
336
        return Str::studly(Str::singular($repeaterName));
337
    }
338
}
339