Completed
Pull Request — master (#274)
by
unknown
63:55 queued 33:13
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 HTMLPurifier;
6
use Thinktomorrow\Chief\Sets\SetReference;
7
use Thinktomorrow\Chief\Modules\TextModule;
8
use Thinktomorrow\Chief\Relations\ActsAsParent;
9
use Thinktomorrow\Chief\Modules\PagetitleModule;
10
use Thinktomorrow\Chief\Modules\Application\CreateModule;
11
use Thinktomorrow\Chief\Modules\Application\UpdateModule;
12
use Thinktomorrow\Chief\FlatReferences\FlatReferenceFactory;
13
use Thinktomorrow\Chief\FlatReferences\FlatReferenceCollection;
14
15
class UpdateSections
16
{
17
    /** @var ActsAsParent */
18
    private $model;
19
20
    /** @var array */
21
    private $relation_references;
22
23
    /** @var array */
24
    private $text_modules;
25
26
    /**@var array */
27
    private $set_refs;
28
29
    /** @var array */
30
    private $sorting;
31
32 40
    private function __construct(ActsAsParent $model, array $relation_references, array $text_modules, array $set_refs, array $sorting)
33
    {
34 40
        $this->model = $model;
35 40
        $this->relation_references = $relation_references;
36 40
        $this->text_modules = $text_modules;
37 40
        $this->set_refs = $set_refs;
38 40
        $this->sorting = $sorting;
39 40
    }
40
41 40
    public static function forModel(ActsAsParent $model, array $relation_references, array $text_modules, array $set_refs, array $sorting)
42
    {
43 40
        return new static($model, $relation_references, $text_modules, $set_refs, $sorting);
44
    }
45
46 40
    public function updateModules()
47
    {
48
        // Remove existing relations expect the text ones
49 40
        $this->removeExistingModules();
50
51 40
        if (empty($this->relation_references)) {
52 36
            return $this;
53
        }
54
55 4
        $referred_instances = FlatReferenceCollection::fromFlatReferences($this->relation_references);
56
57 4
        foreach ($referred_instances as $instance) {
58 4
            $this->model->adoptChild($instance, ['sort' => 0]);
59
        }
60
61 4
        return $this;
62
    }
63
64 40
    public function updateSets()
65
    {
66 40
        $this->removeExistingSets();
67
68 40
        foreach ($this->set_refs as $flat_set_ref) {
69 2
            if (!$flat_set_ref) {
70
                continue;
71
            }
72
73 2
            $stored_set_ref = $this->findOrCreateStoredSetReference($flat_set_ref);
74
75 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

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

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