Passed
Pull Request — 1.2 (#558)
by
unknown
09:10
created

HandleRepeaters::updateRepeaterMorphMany()   B

Complexity

Conditions 8
Paths 18

Size

Total Lines 45
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 72

Importance

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