Passed
Push — master ( f2fe9c...c37bd3 )
by Philippe
02:30 queued 12s
created

UpdateSections::updateSets()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3.0175

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 3
nop 0
dl 0
loc 15
ccs 7
cts 8
cp 0.875
crap 3.0175
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Thinktomorrow\Chief\PageBuilder;
4
5
use Thinktomorrow\Chief\Sets\SetReference;
6
use Thinktomorrow\Chief\Modules\TextModule;
7
use Thinktomorrow\Chief\Relations\ActsAsParent;
8
use Thinktomorrow\Chief\Modules\PagetitleModule;
9
use Thinktomorrow\Chief\Modules\Application\CreateModule;
10
use Thinktomorrow\Chief\Modules\Application\UpdateModule;
11
use Thinktomorrow\Chief\FlatReferences\FlatReferenceFactory;
12
use Thinktomorrow\Chief\FlatReferences\FlatReferenceCollection;
13
14
class UpdateSections
15
{
16
    /** @var ActsAsParent */
17
    private $model;
18
19
    /** @var array */
20
    private $relation_references;
21
22
    /** @var array */
23
    private $text_modules;
24
25
    /**@var array */
26
    private $set_refs;
27
28
    /** @var array */
29
    private $sorting;
30
31 42
    private function __construct(ActsAsParent $model, array $relation_references, array $text_modules, array $set_refs, array $sorting)
32
    {
33 42
        $this->model = $model;
34 42
        $this->relation_references = $relation_references;
35 42
        $this->text_modules = $text_modules;
36 42
        $this->set_refs = $set_refs;
37 42
        $this->sorting = $sorting;
38 42
    }
39
40 42
    public static function forModel(ActsAsParent $model, array $relation_references, array $text_modules, array $set_refs, array $sorting)
41
    {
42 42
        return new static($model, $relation_references, $text_modules, $set_refs, $sorting);
43
    }
44
45 42
    public function updateModules()
46
    {
47
        // Remove existing relations expect the text ones
48 42
        $this->removeExistingModules();
49
50 42
        if (empty($this->relation_references)) {
51 38
            return $this;
52
        }
53
54 4
        $referred_instances = FlatReferenceCollection::fromFlatReferences($this->relation_references);
55
56 4
        foreach ($referred_instances as $instance) {
57 4
            $this->model->adoptChild($instance, ['sort' => 0]);
58
        }
59
60 4
        return $this;
61
    }
62
63 42
    public function updateSets()
64
    {
65 42
        $this->removeExistingSets();
66
67 42
        foreach ($this->set_refs as $flat_set_ref) {
68 2
            if (!$flat_set_ref) {
69
                continue;
70
            }
71
72 2
            $stored_set_ref = $this->findOrCreateStoredSetReference($flat_set_ref);
73
74 2
            $this->model->adoptChild($stored_set_ref, ['sort' => 0]);
0 ignored issues
show
Bug introduced by
It seems like $stored_set_ref can also be of type Illuminate\Database\Eloquent\Model; however, parameter $child of Thinktomorrow\Chief\Rela...sAsParent::adoptChild() does only seem to accept Thinktomorrow\Chief\Relations\ActsAsChild, maybe add an additional type check? ( Ignorable by Annotation )

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

74
            $this->model->adoptChild(/** @scrutinizer ignore-type */ $stored_set_ref, ['sort' => 0]);
Loading history...
75
        }
76
77 42
        return $this;
78
    }
79
80 42
    private function removeExistingModules()
81
    {
82 42
        foreach ($this->model->children() as $instance) {
83 11
            if ($instance instanceof StoredSetReference || $instance instanceof TextModule || $instance instanceof PagetitleModule) {
0 ignored issues
show
Bug introduced by
The type Thinktomorrow\Chief\PageBuilder\StoredSetReference was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
84 7
                continue;
85
            }
86 4
            $this->model->rejectChild($instance);
87
        }
88 42
    }
89
90 42
    private function removeExistingSets()
91
    {
92 42
        foreach ($this->model->children() as $instance) {
93 10
            if (! $instance instanceof StoredSetReference) {
94 10
                continue;
95
            }
96
            $this->model->rejectChild($instance);
97
        }
98 42
    }
99
100 42
    public function addTextModules()
101
    {
102 42
        if (!isset($this->text_modules['new']) || empty($this->text_modules['new'])) {
103 37
            return $this;
104
        }
105
106 5
        foreach ($this->text_modules['new'] as $text_module) {
107
108
            // Create pagetitle text module
109 5
            if (isset($text_module['type']) && $text_module['type'] == 'pagetitle') {
110 1
                $module = app(CreateModule::class)->handle(
111 1
                    (new PagetitleModule)->morphKey(),
112 1
                    $text_module['slug'],
113 1
                    $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...
114
                );
115
            }
116
117
            // Create page specific text module
118
            else {
119 4
                $module = app(CreateModule::class)->handle(
120 4
                    (new TextModule)->morphKey(),
121 4
                    $text_module['slug'],
122 4
                    $this->model->id
123
                );
124
            }
125
126
            // Connect to page - sorting will be set later on...
127 5
            $this->model->adoptChild($module, ['sort' => 0]);
128
129
            // Add content
130 5
            app(UpdateModule::class)->handle($module->id, $module->slug, $text_module['trans'], [], []);
131
132
            // Change slug representation in sorting to proper flat reference
133 5
            $index = (false !== $key = array_search($module->slug, $this->sorting)) ? $key : null;
134 5
            $this->sorting[$index] = $module->flatReference()->get();
135
        }
136
137 5
        return $this;
138
    }
139
140 42
    public function updateTextModules()
141
    {
142 42
        if (!isset($this->text_modules['replace']) || empty($this->text_modules['replace'])) {
143 35
            return $this;
144
        }
145
146 7
        foreach ($this->text_modules['replace'] as $text_module) {
147 7
            if (! $module = FlatReferenceFactory::fromString($text_module['id'])->instance()) {
148
                continue;
149
            }
150
151
            // Do not update if content of text is completely empty. We will remove this module instead
152 7
            if ($this->isTextCompletelyEmpty($text_module['trans'])) {
153 3
                $this->removeTextualModule($module);
154 3
                continue;
155
            }
156
157 4
            foreach ($text_module['trans'] as $locale => $content) {
158 4
                $text_module['trans'][$locale]['content'] = $content['content'];
159
            }
160
161
            // Replace content
162 4
            app(UpdateModule::class)->handle($module->id, $module->slug, $text_module['trans'], [], []);
163
        }
164
165 7
        return $this;
166
    }
167
168 3
    private function removeTextualModule($module)
169
    {
170 3
        if (! $module instanceof TextModule && ! $module instanceof PagetitleModule) {
171
            throw new \Exception('Invalid request to remove non textual module');
172
        }
173
174 3
        $this->model->rejectChild($module);
175
176
        // In case of a textual module, we also delete the module itself
177 3
        $module->delete();
178 3
    }
179
180 42
    public function sort()
181
    {
182 42
        $children = $this->model->children();
183
184 42
        foreach ($this->sorting as $sorting => $reference) {
185
186
            // Reference can be null in case that the module has been removed (empty selection). This will avoid
187
            // in case of duplicate module references that the removed module will be used for the sorting instead.
188 5
            if (!$reference) {
189
                continue;
190
            }
191
192
            $child = $children->first(function ($c) use ($reference) {
193 5
                return $c->flatReference()->get() == $reference;
194 5
            });
195
196 5
            if (!$child) {
197
                continue;
198
            }
199
200 5
            $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

200
            $this->model->/** @scrutinizer ignore-call */ 
201
                          sortChild($child, $sorting);
Loading history...
201
        }
202
203 42
        return $this;
204
    }
205
206
    /**
207
     * Do we consider the translation payload to be 'empty'. This means
208
     * that each line of the translation only contains spaces or empty tags.
209
     *
210
     * @param $trans
211
     * @return bool
212
     */
213 7
    private function isTextCompletelyEmpty($trans): bool
214
    {
215 7
        $is_completely_empty = true;
216
217 7
        foreach ($trans as $locale => $lines) {
218 7
            foreach ($lines as $key => $line) {
219 7
                $stripped_line = $this->stripTagsBlacklist($line, ['p', 'br']);
220 7
                $stripped_line = trim($stripped_line);
221
222 7
                if ($stripped_line) {
223 4
                    $is_completely_empty = false;
224 7
                    break;
225
                }
226
            }
227
        }
228
229 7
        return $is_completely_empty;
230
    }
231
232
    /**
233
     * Pass a list of not allowed tags as they will be stripped out from the value.
234
     * e.g. ['p', 'br' ]
235
     *
236
     * @param $value
237
     * @param array $blacklist
238
     * @return mixed
239
     */
240 7
    private function stripTagsBlacklist($value, $blacklist = [])
241
    {
242 7
        foreach ($blacklist as $tag) {
243 7
            $value = preg_replace('/<\/?' . $tag . '(.|\s)*?>/', '', $value);
244
        }
245
246 7
        return $value;
247
    }
248
249 2
    private function findOrCreateStoredSetReference(string $flat_set_ref)
250
    {
251 2
        list($className, $id) = explode('@', $flat_set_ref);
252
253
        /** If set reference is not stored yet, we will do this now */
254 2
        if ($className == SetReference::class) {
255 1
            return SetReference::find($id)->store();
256
        }
257
258 1
        return FlatReferenceFactory::fromString($flat_set_ref)->instance();
259
    }
260
}
261