Passed
Push — ci/2.5-prep ( 118748 )
by Quentin
07:55
created

Block   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 377
Duplicated Lines 0 %

Importance

Changes 7
Bugs 0 Features 0
Metric Value
eloc 123
dl 0
loc 377
rs 9.84
c 7
b 0
f 0
wmc 32

13 Methods

Rating   Name   Duplication   Size   Complexity  
A render() 0 6 1
A __construct() 0 22 3
A isNewFormat() 0 5 1
A setSource() 0 5 1
A toShortList() 0 8 1
A getFileName() 0 3 2
A parseMixedProperty() 0 29 3
A makeName() 0 3 1
B parsePropertyFallback() 0 45 8
A parse() 0 20 3
A parseProperty() 0 17 3
A toList() 0 17 4
A removeSpecialBladeTags() 0 7 1
1
<?php
2
3
namespace A17\Twill\Services\Blocks;
4
5
use Exception;
6
use Illuminate\Support\Str;
7
8
class Block
9
{
10
    const SOURCE_APP = 'app';
11
12
    const SOURCE_TWILL = 'twill';
13
14
    const SOURCE_CUSTOM = 'custom';
15
16
    const TYPE_BLOCK = 'block';
17
18
    const TYPE_REPEATER = 'repeater';
19
20
    /**
21
     * @var string
22
     */
23
    public $title;
24
25
    /**
26
     * @var string
27
     */
28
    public $titleField;
29
30
    /**
31
     * @var boolean
32
     */
33
    public $hideTitlePrefix;
34
35
    /**
36
     * @var string
37
     */
38
    public $trigger;
39
40
    /**
41
     * @var string
42
     */
43
    public $source;
44
45
    /**
46
     * @var string
47
     */
48
    public $name;
49
50
    /**
51
     * @var string
52
     */
53
    public $group;
54
55
    /**
56
     * @var string
57
     */
58
    public $type;
59
60
    /**
61
     * @var string
62
     */
63
    public $icon;
64
65
    /**
66
     * @var boolean
67
     */
68
    public $compiled;
69
70
    /**
71
     * @var string
72
     */
73
    public $component;
74
75
    /**
76
     * @var integer
77
     */
78
    public $max = 999;
79
80
    /**
81
     * @var boolean
82
     */
83
    public $isNewFormat;
84
85
    /**
86
     * @var \Symfony\Component\Finder\SplFileInfo
87
     */
88
    public $file;
89
90
    /**
91
     * @var string
92
     */
93
    public $fileName;
94
95
    /**
96
     * @var string
97
     */
98
    public $contents;
99
100
    /**
101
     * Block constructor.
102
     * @param $file
103
     * @param $type
104
     * @param $source
105
     * @param $name
106
     * @throws \Exception
107
     */
108
    public function __construct($file, $type, $source, $name = null)
109
    {
110
        $this->file = $file;
111
112
        $this->type = $type;
113
114
        $this->source = $source;
115
116
        $this->fileName = $this->getFilename();
117
118
        $this->name = $name ?? Str::before(
119
            $this->file->getFilename(),
120
            '.blade.php'
121
        );
122
123
        if ($type === self::TYPE_BLOCK
124
            && config('twill.block_editor.repeaters.' . $this->name) !== null
125
        ) {
126
            $this->type = self::TYPE_REPEATER;
127
        }
128
129
        $this->parse();
130
    }
131
132
    /**
133
     * @param $source
134
     * @return $this
135
     */
136
    public function setSource($source)
137
    {
138
        $this->source = $source;
139
140
        return $this;
141
    }
142
143
    /**
144
     * @return \Illuminate\Support\Collection
145
     */
146
    public function toList()
147
    {
148
        return collect([
149
            'title' => $this->title,
150
            'titleField' => $this->titleField,
151
            'hideTitlePrefix' => $this->hideTitlePrefix,
152
            'trigger' => $this->trigger,
153
            'name' => $this->name,
154
            'group' => $this->group,
155
            'type' => $this->type,
156
            'icon' => $this->icon,
157
            'compiled' => $this->compiled ? 'yes' : '-',
158
            'source' => $this->source,
159
            'new_format' => $this->isNewFormat ? 'yes' : '-',
160
            'file' => $this->getFilename(),
161
            'component' => $this->component,
162
            'max' => $this->type === self::TYPE_REPEATER ? $this->max : null,
163
        ]);
164
    }
165
166
    /**
167
     * @return \Illuminate\Support\Collection
168
     */
169
    public function toShortList()
170
    {
171
        return collect([
172
            'title' => $this->title,
173
            'name' => $this->name,
174
            'group' => $this->group,
175
            'type' => $this->type,
176
            'icon' => $this->icon,
177
        ]);
178
    }
179
180
    /**
181
     * @param $name
182
     * @return string
183
     */
184
    public function makeName($name)
185
    {
186
        return Str::kebab($name);
187
    }
188
189
    /**
190
     * @return $this
191
     * @throws \Exception
192
     */
193
    public function parse()
194
    {
195
        $contents = $this->file ? file_get_contents((string) $this->file->getPathName()) : '';
196
197
        $this->title = $this->parseProperty('title', $contents, $this->name);
198
        $this->trigger = $this->parseProperty('trigger', $contents, $this->name, $this->type === self::TYPE_REPEATER ? twillTrans('twill::lang.fields.block-editor.add-item') : null);
0 ignored issues
show
Bug introduced by
It seems like $this->type === self::TY...ditor.add-item') : null can also be of type array and array; however, parameter $default of A17\Twill\Services\Blocks\Block::parseProperty() does only seem to accept null|string, 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

198
        $this->trigger = $this->parseProperty('trigger', $contents, $this->name, /** @scrutinizer ignore-type */ $this->type === self::TYPE_REPEATER ? twillTrans('twill::lang.fields.block-editor.add-item') : null);
Loading history...
199
        $this->max = (int) $this->parseProperty('max', $contents, $this->name, 999);
200
        $this->group = $this->parseProperty('group', $contents, $this->name, 'app');
201
        $this->icon = $this->parseProperty('icon', $contents, $this->name, 'text');
202
        $this->compiled = (boolean) $this->parseProperty('compiled', $contents, $this->name, false);
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type null|string expected by parameter $default of A17\Twill\Services\Blocks\Block::parseProperty(). ( Ignorable by Annotation )

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

202
        $this->compiled = (boolean) $this->parseProperty('compiled', $contents, $this->name, /** @scrutinizer ignore-type */ false);
Loading history...
203
        $this->component = $this->parseProperty('component', $contents, $this->name, "a17-block-{$this->name}");
204
        $this->isNewFormat = $this->isNewFormat($contents);
205
        $this->contents = $contents;
206
207
        $this->parseMixedProperty('titleField', $contents, $this->name, function ($value, $options) {
208
            $this->titleField = $value;
209
            $this->hideTitlePrefix = (boolean) ($options['hidePrefix'] ?? false);
210
        });
211
212
        return $this;
213
    }
214
215
    /**
216
     * Parse a string property directive in the form of `@twillTypeProperty('value')`.
217
     *
218
     * @param string $property
219
     * @param string $block
220
     * @param string $blockName
221
     * @param string|null $default
222
     * @return string
223
     * @throws \Exception
224
     */
225
    public function parseProperty(
226
        $property,
227
        $block,
228
        $blockName,
229
        $default = null
230
    ) {
231
        $bladeProperty = ucfirst($property);
232
233
        foreach (['twillProp', 'twillBlock', 'twillRepeater'] as $pattern) {
234
            preg_match("/@{$pattern}{$bladeProperty}\('(.*)'\)/", $block, $matches);
235
236
            if (filled($matches)) {
237
                return $matches[1];
238
            }
239
        }
240
241
        return $this->parsePropertyFallback($property, $blockName, $default);
0 ignored issues
show
Bug introduced by
It seems like $default can also be of type string; however, parameter $default of A17\Twill\Services\Block...parsePropertyFallback() does only seem to accept null, 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

241
        return $this->parsePropertyFallback($property, $blockName, /** @scrutinizer ignore-type */ $default);
Loading history...
242
    }
243
244
    /**
245
     * Parse a mixed property directive in the form of `@twillTypeProperty('value', [...])`
246
     * and pass the result to a given callback.
247
     *
248
     * @param string $property
249
     * @param string $block
250
     * @param string $blockName
251
     * @param Callable $callback  Should have the following signature: `function ($value, $options)`
252
     * @return mixed
253
     * @throws \Exception
254
     */
255
    public function parseMixedProperty(
256
        $property,
257
        $block,
258
        $blockName,
259
        $callback
260
    ) {
261
        $bladeProperty = ucfirst($property);
262
263
        foreach (['twillProp', 'twillBlock', 'twillRepeater'] as $pattern) {
264
            // Regexp modifiers:
265
            //   `s`  allows newlines as part of the `.*` match
266
            //   `U`  stops the match at the first closing parenthesis
267
            preg_match("/@{$pattern}{$bladeProperty}\((.*)\)/sU", $block, $matches);
268
269
            if (filled($matches)) {
270
                // Wrap the match in array notation and feed it to `eval` to get an actual array.
271
                // In this context, we're only interested in the first two possible values.
272
                $content = "[{$matches[1]}]";
273
                $parsedContent = eval("return {$content};");
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
274
                $value = $parsedContent[0] ?? null;
275
                $options = $parsedContent[1] ?? null;
276
277
                return $callback($value, $options);
278
            }
279
        };
280
281
        $value = $this->parseProperty($property, $block, $blockName, null);
282
283
        return $callback($value, null);
284
    }
285
286
    /**
287
     * @param $property
288
     * @param $blockName
289
     * @param null $default
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $default is correct as it would always require null to be passed?
Loading history...
290
     * @return array
291
     * @throws \Exception
292
     */
293
    private function parsePropertyFallback(
294
        $property,
295
        $blockName,
296
        $default = null
297
    ) {
298
        if (
299
            $value = config(
300
                "twill.block_editor.blocks.{$blockName}.{$property}"
301
            )
302
        ) {
303
            return $value;
304
        }
305
306
        if (
307
            $value = config(
308
                "twill.block_editor.repeaters.{$blockName}.{$property}"
309
            )
310
        ) {
311
            return $value;
312
        }
313
314
        if ($configBlock = collect(config("twill.block_editor.blocks"))->filter(function ($block) use ($blockName) {
315
            return Str::contains($block['component'], $blockName);
316
        })->first()) {
317
            if ($value = ($configBlock[$property] ?? null)) {
318
                return $value;
319
            }
320
        }
321
        if ($configRepeater = collect(config("twill.block_editor.repeaters"))->filter(function ($repeater) use ($blockName) {
322
            return Str::contains($repeater['component'], $blockName);
323
        })->first()) {
324
            if ($value = ($configRepeater[$property] ?? null)) {
325
                return $value;
326
            }
327
        }
328
329
        if ($property !== 'title') {
330
            return $default;
331
        }
332
333
        // Title is mandatory
334
        throw new Exception(
335
            "Block {$blockName} does not exists or the mandatory property '{$property}' " .
336
            "was not found on this block. If you are still using blocks on the twill.php " .
337
            "file, please check if the block is present and properly configured."
338
        );
339
    }
340
341
    /**
342
     * @param $block
343
     * @return bool
344
     */
345
    public function isNewFormat($block)
346
    {
347
        preg_match("/@twill(Prop|Block|Repeater).*\('(.*)'\)/", $block, $propMatches);
348
349
        return filled($propMatches);
350
    }
351
352
    /**
353
     * @return string
354
     */
355
    public function getFileName()
356
    {
357
        return $this->file ? $this->file->getFileName() : 'Custom Vue file';
358
    }
359
360
    /**
361
     * @return string
362
     * @throws \Symfony\Component\Debug\Exception\FatalThrowableError
363
     */
364
    public function render()
365
    {
366
        return BladeCompiler::render(
367
            self::removeSpecialBladeTags($this->contents),
368
            [
369
                'renderForBlocks' => true,
370
            ]
371
        );
372
    }
373
374
    /**
375
     * @param $contents
376
     * @return string
377
     */
378
    public static function removeSpecialBladeTags($contents)
379
    {
380
        return preg_replace([
381
            "/@twillProp.*\((.*)\)/sU",
382
            "/@twillBlock.*\((.*)\)/sU",
383
            "/@twillRepeater.*\((.*)\)/sU",
384
        ], '', $contents);
385
    }
386
}
387