Test Setup Failed
Push — master ( 5d739f...e22c69 )
by Ben
06:33
created

UpdateSections::removeExistingSets()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 3.1406

Importance

Changes 0
Metric Value
eloc 4
c 0
b 0
f 0
dl 0
loc 7
ccs 3
cts 4
cp 0.75
rs 10
cc 3
nc 3
nop 0
crap 3.1406
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Thinktomorrow\Chief\PageBuilder;
6
7
use Illuminate\Support\Collection;
8
use Thinktomorrow\Chief\Sets\SetReference;
9
use Thinktomorrow\Chief\Modules\TextModule;
10
use Thinktomorrow\Chief\Relations\Relation;
11
use Thinktomorrow\Chief\Relations\ActsAsParent;
12
use Thinktomorrow\Chief\Modules\PagetitleModule;
13
use Thinktomorrow\Chief\Modules\Application\CreateModule;
14
use Thinktomorrow\Chief\Modules\Application\UpdateModule;
15
use Thinktomorrow\Chief\FlatReferences\FlatReferenceFactory;
16
use Thinktomorrow\Chief\FlatReferences\FlatReferenceCollection;
17
use Thinktomorrow\Chief\Sets\StoredSetReference;
18
19
class UpdateSections
20
{
21
    /** @var ActsAsParent */
22
    private $model;
23
24
    /** @var array */
25
    private $relation_references;
26
27
    /** @var array */
28
    private $text_modules;
29
30
    /**@var array */
31 42
    private $set_refs;
32
33 42
    /** @var array */
34 42
    private $sorting;
35 42
36 42
    final private function __construct(ActsAsParent $model, array $relation_references, array $text_modules, array $set_refs, array $sorting)
37 42
    {
38 42
        $this->model = $model;
39
        $this->relation_references = $relation_references;
40 42
        $this->text_modules = $text_modules;
41
        $this->set_refs = $set_refs;
42 42
        $this->sorting = $sorting;
43
    }
44
45 42
    public static function forModel(ActsAsParent $model, array $relation_references, array $text_modules, array $set_refs, array $sorting)
46
    {
47
        return new static($model, $relation_references, $text_modules, $set_refs, $sorting);
48 42
    }
49
50 42
    public function updateModules()
51 38
    {
52
        $referred_instances = FlatReferenceCollection::fromFlatReferences($this->relation_references);
53
54 4
        $this->removeModules($referred_instances);
55
56 4
        foreach ($referred_instances as $i => $instance) {
57 4
            if(!$relation = Relation::find($this->model->getMorphClass(), $this->model->id, $instance->getMorphClass(), $instance->id)) {
0 ignored issues
show
Bug introduced by
Accessing id on the interface Thinktomorrow\Chief\Relations\ActsAsParent suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
Unused Code introduced by
The assignment to $relation is dead and can be removed.
Loading history...
Bug introduced by
The method getMorphClass() does not exist on Thinktomorrow\Chief\Relations\ActsAsParent. Since it exists in all sub-types, consider adding an abstract or default implementation to Thinktomorrow\Chief\Relations\ActsAsParent. ( Ignorable by Annotation )

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

57
            if(!$relation = Relation::find($this->model->/** @scrutinizer ignore-call */ getMorphClass(), $this->model->id, $instance->getMorphClass(), $instance->id)) {
Loading history...
58
                $this->model->adoptChild($instance, ['sort' => $i]);
59
            }
60 4
        }
61
62
        return $this;
63 42
    }
64
65 42
    public function updateSets()
66
    {
67 42
        $stored_set_refs = collect($this->set_refs)->reject(function($ref){
68 2
            return !$ref;
69
        })->map(function($flat_set_ref){
70
            return $this->findOrCreateStoredSetReference($flat_set_ref);
71
        });
72 2
73
        $this->removeSets($stored_set_refs);
0 ignored issues
show
Bug introduced by
$stored_set_refs of type Tightenco\Collect\Support\Collection is incompatible with the type Illuminate\Support\Collection expected by parameter $referred_instances of Thinktomorrow\Chief\Page...eSections::removeSets(). ( Ignorable by Annotation )

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

73
        $this->removeSets(/** @scrutinizer ignore-type */ $stored_set_refs);
Loading history...
74 2
75
        foreach ($stored_set_refs as $i => $stored_set_ref) {
76
            if(!$relation = Relation::find($this->model->getMorphClass(), $this->model->id, $stored_set_ref->getMorphClass(), $stored_set_ref->id)) {
0 ignored issues
show
Bug introduced by
Accessing id on the interface Thinktomorrow\Chief\Relations\ActsAsParent suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
Unused Code introduced by
The assignment to $relation is dead and can be removed.
Loading history...
77 42
                $this->model->adoptChild($stored_set_ref, ['sort' => $i]);
78
            }
79
        }
80 42
81
        return $this;
82 42
    }
83 11
84 7
    /**
85
     * Get all module children and only those not passed so we know to delete these ones
86 4
     * @param Collection $referred_instances
87
     */
88 42
    private function removeModules(Collection $referred_instances): void
89
    {
90 42
        $this->model->children()->filter(function ($instance) {
91
            return (!$instance instanceof StoredSetReference && !$instance instanceof TextModule && !$instance instanceof PagetitleModule);
92 42
        })->reject(function ($instance) use ($referred_instances) {
93 10
            // If this model still exists in the update request, we'll reject it from the removed list.
94 10
            return $referred_instances->filter(function ($referred_instance) use ($instance) {
95
                return (get_class($referred_instance) == get_class($instance) && $referred_instance->id == $instance->id);
96
            })->isNotEmpty();
97
        })->each(function ($instance) {
98 42
            $this->model->rejectChild($instance);
99
        });
100 42
    }
101
102 42
    /**
103 37
     * Get all module children and only those not passed so we know to delete these ones
104
     * @param Collection $referred_instances
105
     */
106 5
    private function removeSets(Collection $referred_instances): void
107
    {
108
        $this->model->children()->filter(function ($instance) {
109 5
            return ($instance instanceof StoredSetReference);
110 1
        })->reject(function ($instance) use ($referred_instances) {
111 1
            // If this model still exists in the update request, we'll reject it from the removed list.
112 1
            return $referred_instances->filter(function ($referred_instance) use ($instance) {
113 1
                return (get_class($referred_instance) == get_class($instance) && $referred_instance->id == $instance->id);
114
            })->isNotEmpty();
115
        })->each(function ($instance) {
116
            $this->model->rejectChild($instance);
117
        });
118
    }
119 4
120 4
//    private function removeExistingModules()
121 4
//    {
122 4
//        foreach ($this->model->children() as $instance) {
123
//            if ($instance instanceof StoredSetReference || $instance instanceof TextModule || $instance instanceof PagetitleModule) {
124
//                continue;
125
//            }
126
//            $this->model->rejectChild($instance);
127 5
//        }
128
//    }
129
//
130 5
//    private function removeExistingSets()
131
//    {
132
//        foreach ($this->model->children() as $instance) {
133 5
//            if (!$instance instanceof StoredSetReference) {
134 5
//                continue;
135
//            }
136
//            $this->model->rejectChild($instance);
137 5
//        }
138
//    }
139
140 42
    public function addTextModules()
141
    {
142 42
        if (!isset($this->text_modules['new']) || empty($this->text_modules['new'])) {
143 35
            return $this;
144
        }
145
146 7
        foreach ($this->text_modules['new'] as $text_module) {
147 7
148
            // Create pagetitle text module
149
            if (isset($text_module['type']) && $text_module['type'] == 'pagetitle') {
150
                $module = app(CreateModule::class)->handle(
151
                    (new PagetitleModule())->morphKey(),
152 7
                    $text_module['slug'],
153 3
                    $this->model->getMorphClass(),
154 3
                    $this->model->id
0 ignored issues
show
Bug introduced by
Accessing id on the interface Thinktomorrow\Chief\Relations\ActsAsParent suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
155
                );
156
            } // Create page specific text module
157 4
            else {
158 4
                $module = app(CreateModule::class)->handle(
159
                    (new TextModule())->morphKey(),
160
                    $text_module['slug'],
161
                    $this->model->getMorphClass(),
162 4
                    $this->model->id
163
                );
164
            }
165 7
166
            // Connect to page - sorting will be set later on...
167
            $this->model->adoptChild($module, ['sort' => 0]);
168 3
169
            // Add content
170 3
            app(UpdateModule::class)->handle($module->id, $module->slug, $text_module['trans'], [], []);
171
172
            // Change slug representation in sorting to proper flat reference
173
            $index = (false !== $key = array_search($module->slug, $this->sorting)) ? $key : null;
174 3
            $this->sorting[$index] = $module->flatReference()->get();
175
        }
176
177 3
        return $this;
178 3
    }
179
180 42
    public function updateTextModules()
181
    {
182 42
        if (!isset($this->text_modules['replace']) || empty($this->text_modules['replace'])) {
183
            return $this;
184 42
        }
185
186
        foreach ($this->text_modules['replace'] as $text_module) {
187
            if (!$module = FlatReferenceFactory::fromString($text_module['id'])->instance()) {
188 5
                continue;
189
            }
190
191
            // Do not update if content of text is completely empty. We will remove this module instead
192
            if ($this->isTextCompletelyEmpty($text_module['trans'])) {
193 5
                $this->removeTextualModule($module);
194 5
                continue;
195
            }
196 5
197
            foreach ($text_module['trans'] as $locale => $content) {
198
                $text_module['trans'][$locale]['content'] = $content['content'];
199
            }
200 5
201
            // Replace content
202
            app(UpdateModule::class)->handle($module->id, $module->slug, $text_module['trans'], [], []);
203 42
        }
204
205
        return $this;
206
    }
207
208
    private function removeTextualModule($module)
209
    {
210
        if (!$module instanceof TextModule && !$module instanceof PagetitleModule) {
211
            throw new \Exception('Invalid request to remove non textual module');
212
        }
213 7
214
        $this->model->rejectChild($module);
215 7
216
        // In case of a textual module, we also delete the module itself
217 7
        $module->delete();
218 7
    }
219 7
220 7
    public function sort()
221
    {
222 7
        $children = $this->model->children();
223 4
224 7
        foreach ($this->sorting as $sorting => $reference) {
225
226
            // Reference can be null in case that the module has been removed (empty selection). This will avoid
227
            // in case of duplicate module references that the removed module will be used for the sorting instead.
228
            if (!$reference) {
229 7
                continue;
230
            }
231
232
            $child = $children->first(function ($c) use ($reference) {
233
                return $c->flatReference()->get() == $reference;
234
            });
235
236
            if (!$child) {
237
                continue;
238
            }
239
240 7
            $this->model->sortChild($child, $sorting);
0 ignored issues
show
Bug introduced by
The method sortChild() does not exist on Thinktomorrow\Chief\Relations\ActsAsParent. Since it exists in all sub-types, consider adding an abstract or default implementation to Thinktomorrow\Chief\Relations\ActsAsParent. ( Ignorable by Annotation )

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

240
            $this->model->/** @scrutinizer ignore-call */ 
241
                          sortChild($child, $sorting);
Loading history...
241
        }
242 7
243 7
        return $this;
244
    }
245
246 7
    /**
247
     * Do we consider the translation payload to be 'empty'. This means
248
     * that each line of the translation only contains spaces or empty tags.
249 2
     *
250
     * @param $trans
251 2
     * @return bool
252
     */
253
    private function isTextCompletelyEmpty($trans): bool
254 2
    {
255 1
        $is_completely_empty = true;
256
257
        foreach ($trans as $locale => $lines) {
258 1
            foreach ($lines as $key => $line) {
259
                $stripped_line = $this->stripTagsBlacklist($line, ['p', 'br']);
260
                $stripped_line = trim($stripped_line);
261
262
                if ($stripped_line) {
263
                    $is_completely_empty = false;
264
                    break;
265
                }
266
            }
267
        }
268
269
        return $is_completely_empty;
270
    }
271
272
    /**
273
     * Pass a list of not allowed tags as they will be stripped out from the value.
274
     * e.g. ['p', 'br' ]
275
     *
276
     * @param $value
277
     * @param array $blacklist
278
     * @return mixed
279
     */
280
    private function stripTagsBlacklist($value, $blacklist = [])
281
    {
282
        foreach ($blacklist as $tag) {
283
            $value = preg_replace('/<\/?' . $tag . '(.|\s)*?>/', '', $value);
284
        }
285
286
        return $value;
287
    }
288
289
    private function findOrCreateStoredSetReference(string $flat_set_ref)
290
    {
291
        list($className, $id) = explode('@', $flat_set_ref);
292
293
        /** If set reference is not stored yet, we will do this now */
294
        if ($className == SetReference::class) {
295
            return SetReference::find($id)->store();
296
        }
297
298
        return FlatReferenceFactory::fromString($flat_set_ref)->instance();
299
    }
300
}
301