Block   A
last analyzed

Complexity

Total Complexity 32

Size/Duplication

Total Lines 379
Duplicated Lines 0 %

Test Coverage

Coverage 82.93%

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 124
dl 0
loc 379
ccs 68
cts 82
cp 0.8293
rs 9.84
c 5
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
    const PREG_REPLACE_INNER = '(?:\'|")(.*)(?:\'|")';
21
22
    /**
23
     * @var string
24
     */
25
    public $title;
26
27
    /**
28
     * @var string
29
     */
30
    public $titleField;
31
32
    /**
33
     * @var boolean
34
     */
35
    public $hideTitlePrefix;
36
37
    /**
38
     * @var string
39
     */
40
    public $trigger;
41
42
    /**
43
     * @var string
44
     */
45
    public $source;
46
47
    /**
48
     * @var string
49
     */
50
    public $name;
51
52
    /**
53
     * @var string
54
     */
55
    public $group;
56
57
    /**
58
     * @var string
59
     */
60
    public $type;
61
62
    /**
63
     * @var string
64
     */
65
    public $icon;
66
67
    /**
68
     * @var boolean
69
     */
70
    public $compiled;
71
72
    /**
73
     * @var string
74
     */
75
    public $component;
76
77
    /**
78
     * @var integer
79
     */
80
    public $max = 999;
81
82
    /**
83
     * @var boolean
84
     */
85
    public $isNewFormat;
86
87
    /**
88
     * @var \Symfony\Component\Finder\SplFileInfo
89
     */
90
    public $file;
91
92
    /**
93
     * @var string
94
     */
95
    public $fileName;
96
97 69
    /**
98
     * @var string
99 69
     */
100
    public $contents;
101 69
102
    /**
103 69
     * Block constructor.
104
     * @param $file
105 69
     * @param $type
106
     * @param $source
107 69
     * @param $name
108 69
     * @throws \Exception
109 69
     */
110
    public function __construct($file, $type, $source, $name = null)
111
    {
112 69
        $this->file = $file;
113 69
114
        $this->type = $type;
115
116
        $this->source = $source;
117
118 69
        $this->fileName = $this->getFilename();
119 69
120
        $this->name = $name ?? Str::before(
121
            $this->file->getFilename(),
122
            '.blade.php'
123
        );
124
125 69
        if ($type === self::TYPE_BLOCK
126
            && config('twill.block_editor.repeaters.' . $this->name) !== null
127 69
        ) {
128
            $this->type = self::TYPE_REPEATER;
129 69
        }
130
131
        $this->parse();
132
    }
133
134
    /**
135 11
     * @param $source
136
     * @return $this
137 11
     */
138 11
    public function setSource($source)
139 11
    {
140 11
        $this->source = $source;
141 11
142 11
        return $this;
143 11
    }
144 11
145 11
    /**
146 11
     * @return \Illuminate\Support\Collection
147 11
     */
148 11
    public function toList()
149 11
    {
150
        return collect([
151
            'title' => $this->title,
152
            'titleField' => $this->titleField,
153
            'hideTitlePrefix' => $this->hideTitlePrefix,
154
            'trigger' => $this->trigger,
155
            'name' => $this->name,
156
            'group' => $this->group,
157
            'type' => $this->type,
158
            'icon' => $this->icon,
159
            'compiled' => $this->compiled ? 'yes' : '-',
160
            'source' => $this->source,
161
            'new_format' => $this->isNewFormat ? 'yes' : '-',
162
            'file' => $this->getFilename(),
163
            'component' => $this->component,
164
            'max' => $this->type === self::TYPE_REPEATER ? $this->max : null,
165
        ]);
166
    }
167
168
    /**
169
     * @return \Illuminate\Support\Collection
170
     */
171 3
    public function toShortList()
172
    {
173 3
        return collect([
174
            'title' => $this->title,
175
            'name' => $this->name,
176
            'group' => $this->group,
177
            'type' => $this->type,
178
            'icon' => $this->icon,
179
        ]);
180 69
    }
181
182 69
    /**
183
     * @param $name
184 69
     * @return string
185 69
     */
186 69
    public function makeName($name)
187 69
    {
188 69
        return Str::kebab($name);
189 69
    }
190 69
191 69
    /**
192 69
     * @return $this
193
     * @throws \Exception
194 69
     */
195
    public function parse()
196
    {
197
        $contents = $this->file ? file_get_contents((string) $this->file->getPathName()) : '';
198
199
        $this->title = $this->parseProperty('title', $contents, $this->name);
200
        $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

200
        $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...
201
        $this->max = (int) $this->parseProperty('max', $contents, $this->name, 999);
202
        $this->group = $this->parseProperty('group', $contents, $this->name, 'app');
203
        $this->icon = $this->parseProperty('icon', $contents, $this->name, 'text');
204
        $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

204
        $this->compiled = (boolean) $this->parseProperty('compiled', $contents, $this->name, /** @scrutinizer ignore-type */ false);
Loading history...
205 69
        $this->component = $this->parseProperty('component', $contents, $this->name, "a17-block-{$this->name}");
206
        $this->isNewFormat = $this->isNewFormat($contents);
207
        $this->contents = $contents;
208
209
        $this->parseMixedProperty('titleField', $contents, $this->name, function ($value, $options) {
210
            $this->titleField = $value;
211 69
            $this->hideTitlePrefix = (boolean) ($options['hidePrefix'] ?? false);
212
        });
213 69
214 69
        return $this;
215
    }
216 69
217 69
    /**
218
     * Parse a string property directive in the form of `@twillTypeProperty('value')`.
219
     *
220
     * @param string $property
221
     * @param string $block
222 69
     * @param string $blockName
223 69
     * @param string|null $default
224
     * @return string
225
     * @throws \Exception
226
     */
227
    public function parseProperty(
228
        $property,
229
        $block,
230 69
        $blockName,
231 69
        $default = null
232
    ) {
233
        $bladeProperty = ucfirst($property);
234
235
        foreach (['twillProp', 'twillBlock', 'twillRepeater'] as $pattern) {
236
            preg_match("/@{$pattern}{$bladeProperty}\(" . self::PREG_REPLACE_INNER . "\)/", $block, $matches);
237 69
238 69
            if (filled($matches)) {
239
                return $matches[1];
240
            }
241
        }
242
243
        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

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