Passed
Push — master ( 3f7c6c...01af58 )
by Philippe
05:53
created

UpdateSections::findOrCreateStoredSetReference()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 4
c 0
b 0
f 0
dl 0
loc 10
ccs 1
cts 1
cp 1
rs 10
cc 2
nc 2
nop 1
crap 2
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 34
32
    private function __construct(ActsAsParent $model, array $relation_references, array $text_modules, array $set_refs, array $sorting)
33 34
    {
34 34
        $this->model = $model;
35 34
        $this->relation_references = $relation_references;
36 34
        $this->text_modules = $text_modules;
37 34
        $this->set_refs = $set_refs;
38 34
        $this->sorting = $sorting;
39
    }
40 34
41
    public static function forModel(ActsAsParent $model, array $relation_references, array $text_modules, array $set_refs, array $sorting)
42 34
    {
43
        return new static($model, $relation_references, $text_modules, $set_refs, $sorting);
44
    }
45 34
46
    public function updateModules()
47
    {
48 34
        // Remove existing relations expect the text ones
49
        $this->removeExistingModules();
50 34
51 30
        if (empty($this->relation_references)) {
52
            return $this;
53
        }
54 4
55
        $referred_instances = FlatReferenceCollection::fromFlatReferences($this->relation_references);
56 4
57 4
        foreach ($referred_instances as $instance) {
58
            $this->model->adoptChild($instance, ['sort' => 0]);
59
        }
60 4
61
        return $this;
62
    }
63 34
64
    public function updateSets()
65 34
    {
66
        $this->removeExistingSets();
67 34
68 2
        foreach ($this->set_refs as $flat_set_ref) {
69
            if (!$flat_set_ref) {
70
                continue;
71
            }
72 2
73
            $stored_set_ref = $this->findOrCreateStoredSetReference($flat_set_ref);
74 2
75
            $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 34
78
        return $this;
79
    }
80 34
81
    private function removeExistingModules()
82 34
    {
83 11
        foreach ($this->model->children() as $instance) {
84 7
            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
                continue;
86 4
            }
87
            $this->model->rejectChild($instance);
88 34
        }
89
    }
90 34
91
    private function removeExistingSets()
92 34
    {
93 10
        foreach ($this->model->children() as $instance) {
94 10
            if (! $instance instanceof StoredSetReference) {
95
                continue;
96
            }
97
            $this->model->rejectChild($instance);
98 34
        }
99
    }
100 34
101
    public function addTextModules()
102 34
    {
103 29
        if (!isset($this->text_modules['new']) || empty($this->text_modules['new'])) {
104
            return $this;
105
        }
106 5
107
        foreach ($this->text_modules['new'] as $text_module) {
108
109 5
            // Create pagetitle text module
110 1
            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
                    $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 4
            else {
120 4
                $module = app(CreateModule::class)->handle(
121 4
                    (new TextModule)->morphKey(),
122 4
                    $text_module['slug'],
123
                    $this->model->id
124
                );
125
            }
126
127 5
            // Connect to page - sorting will be set later on...
128
            $this->model->adoptChild($module, ['sort' => 0]);
129
130 5
            // Add content
131
            app(UpdateModule::class)->handle($module->id, $module->slug, $text_module['trans'], [], []);
132
133 5
            // Change slug representation in sorting to proper flat reference
134 5
            $index = (false !== $key = array_search($module->slug, $this->sorting)) ? $key : null;
135
            $this->sorting[$index] = $module->flatReference()->get();
136
        }
137 5
138
        return $this;
139
    }
140 34
141
    public function updateTextModules()
142 34
    {
143 27
        if (!isset($this->text_modules['replace']) || empty($this->text_modules['replace'])) {
144
            return $this;
145
        }
146 7
147 7
        foreach ($this->text_modules['replace'] as $text_module) {
148
            if (! $module = FlatReferenceFactory::fromString($text_module['id'])->instance()) {
149
                continue;
150
            }
151
152 7
            // Do not update if content of text is completely empty. We will remove this module instead
153 3
            if ($this->isTextCompletelyEmpty($text_module['trans'])) {
154 3
                $this->removeTextualModule($module);
155
                continue;
156
            }
157
           
158 4
            foreach($text_module['trans'] as $locale => $content)
159
            {
160
                $purifier = new HTMLPurifier();
161 7
                $sanitized_text = $purifier->purify( $content['content'] );
162
163
                $text_module['trans'][$locale]['content'] = $sanitized_text;
164 3
            }
165
            
166 3
            // Replace content
167
            app(UpdateModule::class)->handle($module->id, $module->slug, $text_module['trans'], [], []);
168
        }
169
170 3
        return $this;
171
    }
172
173 3
    private function removeTextualModule($module)
174 3
    {
175
        if (! $module instanceof TextModule && ! $module instanceof PagetitleModule) {
176 34
            throw new \Exception('Invalid request to remove non textual module');
177
        }
178 34
179
        $this->model->rejectChild($module);
180 34
181
        // In case of a textual module, we also delete the module itself
182
        $module->delete();
183
    }
184 5
185
    public function sort()
186
    {
187
        $children = $this->model->children();
188
189 5
        foreach ($this->sorting as $sorting => $reference) {
190 5
191
            // Reference can be null in case that the module has been removed (empty selection). This will avoid
192 5
            // in case of duplicate module references that the removed module will be used for the sorting instead.
193
            if (!$reference) {
194
                continue;
195
            }
196 5
197
            $child = $children->first(function ($c) use ($reference) {
198
                return $c->flatReference()->get() == $reference;
199 34
            });
200
201
            if (!$child) {
202
                continue;
203
            }
204
205
            $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

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