HandleBlocks   B
last analyzed

Complexity

Total Complexity 43

Size/Duplication

Total Lines 320
Duplicated Lines 0 %

Test Coverage

Coverage 61.63%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 159
dl 0
loc 320
ccs 90
cts 146
cp 0.6163
rs 8.96
c 3
b 0
f 0
wmc 43

9 Methods

Rating   Name   Duplication   Size   Complexity  
A getChildrenBlocks() 0 21 4
A buildBlock() 0 6 1
A createBlock() 0 11 1
A afterSaveHandleBlocks() 0 11 2
A hydrateHandleBlocks() 0 20 5
A getChildBlocks() 0 17 3
C getFormFieldsHandleBlocks() 0 104 15
A getBlocks() 0 13 4
B getBlockBrowsers() 0 49 8

How to fix   Complexity   

Complex Class

Complex classes like HandleBlocks often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use HandleBlocks, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace A17\Twill\Repositories\Behaviors;
4
5
use A17\Twill\Models\Behaviors\HasMedias;
6
use A17\Twill\Repositories\BlockRepository;
7
use A17\Twill\Services\Blocks\BlockCollection;
8
use Illuminate\Support\Collection;
9
use Log;
10
use Schema;
11
use Symfony\Component\Routing\Exception\RouteNotFoundException;
12
13
trait HandleBlocks
14
{
15
    /**
16
     * @param \A17\Twill\Models\Model $object
17
     * @param array $fields
18 2
     * @param int $fakeBlockId
19
     * @param int|null $parentId
20 2
     * @param \Illuminate\Support\Collection|null $blocksFromFields
21
     * @param \Illuminate\Support\Collection|null $mainCollection
22
     * @param int|null $mainCollection|void
23
     * @return \A17\Twill\Models\Model
24 2
     */
25 2
    public function hydrateHandleBlocks($object, $fields, &$fakeBlockId = 0, $parentId = null, $blocksFromFields = null, $mainCollection = null)
26 2
    {
27 2
        if ($this->shouldIgnoreFieldBeforeSave('blocks')) {
0 ignored issues
show
Bug introduced by
It seems like shouldIgnoreFieldBeforeSave() 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

27
        if ($this->/** @scrutinizer ignore-call */ shouldIgnoreFieldBeforeSave('blocks')) {
Loading history...
28 2
            return;
29
        }
30 2
31 2
        $firstItem = false;
32
        $blocksCollection = Collection::make();
0 ignored issues
show
Unused Code introduced by
The assignment to $blocksCollection is dead and can be removed.
Loading history...
33
        if ($mainCollection === null) {
34 2
            $firstItem = true;
35 2
            $mainCollection = Collection::make();
36 2
        }
37 2
        if ($blocksFromFields === null) {
38
            $blocksFromFields = $this->getBlocks($object, $fields);
39
        }
40 2
41
        $blockRepository = app(BlockRepository::class);
42 2
        $blocksCollection = $this->getChildrenBlocks($blocksFromFields, $blockRepository, $parentId, $fakeBlockId, $mainCollection);
43
        $object->setRelation('blocks', $firstItem ? $mainCollection : $blocksCollection);
44 2
        return $object;
45 1
    }
46
47
    protected function getChildrenBlocks($blocks, $blockRepository, $parentId, &$fakeBlockId, $mainCollection)
48 1
    {
49
        $childBlocksCollection = Collection::make();
50 1
51 1
        foreach ($blocks as $childBlock) {
52 1
            if ($parentId) {
53 1
                $childBlock['parent_id'] = $parentId;
54 1
            }
55
            $newChildBlock = $blockRepository->createForPreview($childBlock);
56
57 1
            $fakeBlockId++;
58 1
            $newChildBlock->id = $fakeBlockId;
59
            if (!empty($childBlock['blocks'])) {
60 2
                $childBlockHydrated = $this->hydrateHandleBlocks($newChildBlock, $childBlock, $fakeBlockId, $newChildBlock->id, $childBlock['blocks'], $mainCollection);
61
                $newChildBlock->setRelation('children', $childBlockHydrated->blocks);
0 ignored issues
show
Bug introduced by
The property blocks does not seem to exist on A17\Twill\Models\Model. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
62
            }
63
64
            $mainCollection->push($newChildBlock);
65
            $childBlocksCollection->push($newChildBlock);
66
        }
67
        return $childBlocksCollection;
68 21
    }
69
70 21
    /**
71
     * @param \A17\Twill\Models\Model $object
72
     * @param array $fields
73
     * @return void
74 21
     */
75
    public function afterSaveHandleBlocks($object, $fields)
76 21
    {
77 21
        if ($this->shouldIgnoreFieldBeforeSave('blocks')) {
78 2
            return;
79 21
        }
80 21
81
        $blockRepository = app(BlockRepository::class);
82
83
        $blockRepository->bulkDelete($object->blocks()->pluck('id')->toArray());
84
        $this->getBlocks($object, $fields)->each(function ($block) use ($object, $blockRepository) {
0 ignored issues
show
Unused Code introduced by
The import $object is not used and could be removed.

This check looks for imports that have been defined, but are not used in the scope.

Loading history...
85
            $this->createBlock($blockRepository, $block);
86
        });
87
    }
88
89
    /**
90 2
     * Create a block from formFields, and recursively create it's child blocks
91
     *
92 2
     * @param  \A17\Twill\Repositories\BlockRepository $blockRepository
93
     * @param  array $blockFields
94
     * @return \A17\Twill\Models\Block $blockCreated
95 2
     */
96
    private function createBlock(BlockRepository $blockRepository, $blockFields)
97
    {
98 2
        $blockCreated = $blockRepository->create($blockFields);
99
100 2
        // Handle child blocks
101
        $blockFields['blocks']->each(function ($childBlock) use ($blockCreated, $blockRepository) {
102
            $childBlock['parent_id'] = $blockCreated->id;
103
            $this->createBlock($blockRepository, $childBlock);
104
        });
105
106
        return $blockCreated;
107
    }
108 21
109
    /**
110 21
     * @param \A17\Twill\Models\Model $object
111 21
     * @param array $fields
112
     * @return \Illuminate\Support\Collection
113 2
     */
114 2
    private function getBlocks($object, $fields)
115 2
    {
116 2
        $blocks = Collection::make();
117
        if (isset($fields['blocks']) && is_array($fields['blocks'])) {
118 2
            foreach ($fields['blocks'] as $index => $block) {
119
                $block = $this->buildBlock($block, $object);
120
                $block['position'] = $index + 1;
121 21
                $block['blocks'] = $this->getChildBlocks($object, $block);
122
123
                $blocks->push($block);
124
            }
125
        }
126
        return $blocks;
127
    }
128
129
    /**
130
     * Recursively generate child blocks from the fields of a block
131
     *
132 2
     * @param  \A17\Twill\Models\Model $object
133
     * @param  array $parentBlockFields
134 2
     * @return \Illuminate\Support\Collection
135
     */
136 2
    private function getChildBlocks($object, $parentBlockFields)
137
    {
138
        $childBlocksList = Collection::make();
139
140
        foreach ($parentBlockFields['blocks'] as $childKey => $childBlocks) {
141
            foreach ($childBlocks as $index => $childBlock) {
142
                $childBlock = $this->buildBlock($childBlock, $object, true);
143
                $childBlock['child_key'] = $childKey;
144
                $childBlock['position'] = $index + 1;
145
                $childBlock['editor_name'] = $parentBlockFields['editor_name'] ?? 'default';
146
                $childBlock['blocks'] = $this->getChildBlocks($object, $childBlock);
147 2
148
                $childBlocksList->push($childBlock);
149
            }
150
        }
151
152
        return $childBlocksList;
153
    }
154
155
    /**
156 2
     * @param array $block
157
     * @param \A17\Twill\Models\Model $object
158 2
     * @param bool $repeater
159 2
     * @return array
160
     */
161 2
    private function buildBlock($block, $object, $repeater = false)
162
    {
163
        $block['blockable_id'] = $object->id;
164
        $block['blockable_type'] = $object->getMorphClass();
165
166
        return app(BlockRepository::class)->buildFromCmsArray($block, $repeater);
167
    }
168
169 5
    /**
170
     * @param \A17\Twill\Models\Model $object
171 5
     * @param array $fields
172
     * @return array
173 5
     */
174
    public function getFormFieldsHandleBlocks($object, $fields)
175 5
    {
176
        $fields['blocks'] = null;
177 5
178
        if ($object->has('blocks')) {
179 1
            $blocksList = app(BlockCollection::class)->list()->keyBy('name');
180 1
181 1
            foreach ($object->blocks as $block) {
0 ignored issues
show
Bug introduced by
The property blocks does not seem to exist on A17\Twill\Models\Model. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
182
                $isInRepeater = isset($block->parent_id);
183 1
                $configKey = $isInRepeater ? 'repeaters' : 'blocks';
0 ignored issues
show
Unused Code introduced by
The assignment to $configKey is dead and can be removed.
Loading history...
184
                $blockTypeConfig = $blocksList[$block->type] ?? null;
185
186
                if (is_null($blockTypeConfig)) {
187
                    continue;
188 1
                }
189 1
190 1
                $blockItem = [
191 1
                    'id' => $block->id,
192
                    'type' => $blockTypeConfig['component'],
193
                    'title' => $blockTypeConfig['title'],
194 1
                    'name' => $block->editor_name ?? 'default',
195
                    'titleField' => $blockTypeConfig['titleField'],
196
                    'hideTitlePrefix' => $blockTypeConfig['hideTitlePrefix'],
197
                    'attributes' => $blockTypeConfig['attributes'] ?? [],
198
                ];
199
200
                if ($isInRepeater) {
201 1
                    $fields['blocksRepeaters']["blocks-{$block->parent_id}_{$block->child_key}"][] = $blockItem + [
202 1
                        'trigger' => $blockTypeConfig['trigger'],
203
                    ] + (isset($blockTypeConfig['max']) ? [
204
                        'max' => $blockTypeConfig['max'],
205
                    ] : []);
206 1
                } else {
207 1
                    $fields['blocks'][$blockItem['name']][] = $blockItem + [
208 1
                        'icon' => $blockTypeConfig['icon'],
209
                    ];
210 1
                }
211 1
212
                $fields['blocksFields'][] = Collection::make($block['content'])->filter(function ($value, $key) {
213 1
                    return $key !== "browsers";
214
                })->map(function ($value, $key) use ($block) {
215 1
                    return [
216
                        'name' => "blocks[$block->id][$key]",
217 1
                        'value' => $value,
218
                    ];
219 1
                })->filter()->values()->toArray();
220
221
                $blockFormFields = app(BlockRepository::class)->getFormFields($block);
222
223
                $medias = $blockFormFields['medias'];
224
225
                if ($medias) {
226
                    if (config('twill.media_library.translated_form_fields', false)) {
227
                        $fields['blocksMedias'][] = Collection::make($medias)->mapWithKeys(function ($mediasByLocale, $locale) use ($block) {
228
                            return Collection::make($mediasByLocale)->mapWithKeys(function ($value, $key) use ($block, $locale) {
229
                                return [
230
                                    "blocks[$block->id][$key][$locale]" => $value,
231
                                ];
232
                            });
233
                        })->filter()->toArray();
234
                    } else {
235
                        $fields['blocksMedias'][] = Collection::make($medias)->mapWithKeys(function ($value, $key) use ($block) {
236
                            return [
237 1
                                "blocks[$block->id][$key]" => $value,
238
                            ];
239 1
                        })->filter()->toArray();
240
                    }
241
                }
242
243
                $files = $blockFormFields['files'];
244
245
                if ($files) {
246
                    Collection::make($files)->each(function ($rolesWithFiles, $locale) use (&$fields, $block) {
247
                        $fields['blocksFiles'][] = Collection::make($rolesWithFiles)->mapWithKeys(function ($files, $role) use ($locale, $block) {
248
                            return [
249 1
                                "blocks[$block->id][$role][$locale]" => $files,
250
                            ];
251
                        })->toArray();
252
                    });
253
                }
254 5
255 1
                if (isset($block['content']['browsers'])) {
256
                    $fields['blocksBrowsers'][] = $this->getBlockBrowsers($block);
257
                }
258 5
            }
259
260
            if ($fields['blocksFields'] ?? false) {
261
                $fields['blocksFields'] = call_user_func_array('array_merge', $fields['blocksFields'] ?? []);
262 5
            }
263
264
            if ($fields['blocksMedias'] ?? false) {
265
                $fields['blocksMedias'] = call_user_func_array('array_merge', $fields['blocksMedias'] ?? []);
266 5
            }
267
268
            if ($fields['blocksFiles'] ?? false) {
269
                $fields['blocksFiles'] = call_user_func_array('array_merge', $fields['blocksFiles'] ?? []);
270
            }
271 5
272
            if ($fields['blocksBrowsers'] ?? false) {
273
                $fields['blocksBrowsers'] = call_user_func_array('array_merge', $fields['blocksBrowsers'] ?? []);
274
            }
275
        }
276
277
        return $fields;
278
    }
279
280
    /**
281
     * @param \A17\Twill\Models\Block $block
282
     * @return array
283
     */
284
    protected function getBlockBrowsers($block)
285
    {
286
        return Collection::make($block['content']['browsers'])->mapWithKeys(function ($ids, $relation) use ($block) {
287
            if (Schema::hasTable(config('twill.related_table', 'twill_related')) && $block->getRelated($relation)->isNotEmpty()) {
288
                $items = $this->getFormFieldsForRelatedBrowser($block, $relation);
0 ignored issues
show
Bug introduced by
It seems like getFormFieldsForRelatedBrowser() 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

288
                /** @scrutinizer ignore-call */ 
289
                $items = $this->getFormFieldsForRelatedBrowser($block, $relation);
Loading history...
289
                foreach ($items as &$item) {
290
                    if (!isset($item['edit'])) {
291
                        try {
292
                            $item['edit'] = moduleRoute(
293
                                $relation,
294
                                config('twill.block_editor.browser_route_prefixes.' . $relation),
295
                                'edit',
296
                                $item['id']
297
                            );
298
                        } catch (RouteNotFoundException $e) {
299
                            report($e);
300
                            Log::notice(
301
                                "Twill warning: The url for the \"{$relation}\" browser items can't " .
302
                                "be resolved. You might be missing a {$relation} key in your " .
303
                                "twill.block_editor.browser_route_prefixes configuration."
304
                            );
305
                        }
306
                    }
307
                }
308
            } else {
309
                $relationRepository = $this->getModelRepository($relation);
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

309
                /** @scrutinizer ignore-call */ 
310
                $relationRepository = $this->getModelRepository($relation);
Loading history...
310
                $relatedItems = $relationRepository->get([], ['id' => $ids], [], -1);
311
                $sortedRelatedItems = array_flip($ids);
312
313
                foreach ($relatedItems as $item) {
314
                    $sortedRelatedItems[$item->id] = $item;
315
                }
316
317
                $items = Collection::make(array_values($sortedRelatedItems))->filter(function ($value) {
318
                    return is_object($value);
319
                })->map(function ($relatedElement) use ($relation) {
320
                    return [
321
                        'id' => $relatedElement->id,
322
                        'name' => $relatedElement->titleInBrowser ?? $relatedElement->title,
323
                        'edit' => moduleRoute($relation, config('twill.block_editor.browser_route_prefixes.' . $relation), 'edit', $relatedElement->id),
324
                    ] + (classHasTrait($relatedElement, HasMedias::class) ? [
325
                        'thumbnail' => $relatedElement->defaultCmsImage(['w' => 100, 'h' => 100]),
326
                    ] : []);
327
                })->toArray();
328
            }
329
            return [
330
                "blocks[$block->id][$relation]" => $items,
331
            ];
332
        })->filter()->toArray();
333
    }
334
}
335