Passed
Pull Request — 2.x (#1308)
by Harings
07:04
created

Block::helper()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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

217
        $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...
218
        $this->max = (int) $this->parseProperty('max', $contents, $this->name, 999);
219
        $this->group = $this->parseProperty('group', $contents, $this->name, 'app');
220
        $this->icon = $this->parseProperty('icon', $contents, $this->name, 'text');
221
        $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

221
        $this->compiled = (boolean) $this->parseProperty('compiled', $contents, $this->name, /** @scrutinizer ignore-type */ false);
Loading history...
222 69
        $this->component = $this->parseProperty('component', $contents, $this->name, "a17-block-{$this->name}");
223 69
        $this->isNewFormat = $this->isNewFormat($contents);
224
        $this->contents = $contents;
225
226
        $this->parseArrayProperty('ValidationRules', $contents, $this->name, function ($value) {
227
            $this->rules = $value ?? [];
0 ignored issues
show
Documentation Bug introduced by
It seems like $value ?? array() can also be of type array. However, the property $rules is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
228
        });
229
230 69
        $this->parseArrayProperty('ValidationRulesForTranslatedFields', $contents, $this->name, function ($value) {
231 69
            $this->rulesForTranslatedFields = $value ?? [];
0 ignored issues
show
Documentation Bug introduced by
It seems like $value ?? array() can also be of type array. However, the property $rulesForTranslatedFields is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
232
        });
233
234
        $this->parseMixedProperty('titleField', $contents, $this->name, function ($value, $options) {
235
            $this->titleField = $value;
236
            $this->hideTitlePrefix = (boolean) ($options['hidePrefix'] ?? false);
237 69
        });
238 69
239
        return $this;
240
    }
241
242
    public function helper(): ?TwillBlock {
243
        if (!$this->helper) {
244
            $this->helper = TwillBlock::getBlockClassForName($this->name);
245
        }
246
        return $this->helper;
247
    }
248
249
    /**
250
     * Checks both the blade file or helper class for validation rules. Returns in order the first one with data.
251
     */
252
    public function getRules(): array {
253 69
        if (!empty($this->rules)) {
254
            return $this->rules;
255 69
        }
256
257 69
        if ($this->helper) {
258
            return $this->helper->getRules();
259
        }
260
261
        return [];
262
    }
263 69
264
    /**
265 69
     * Checks both the blade file or helper class for validation rules. Returns in order the first one with data.
266
     */
267
    public function getRulesForTranslatedFields(): array {
268
        if (!empty($this->rulesForTranslatedFields)) {
269
            return $this->rulesForTranslatedFields;
270
        }
271
272 5
        if ($this->helper) {
273
            return $this->helper->getRulesForTranslatedFields();
274 5
        }
275 5
276
        return [];
277 5
    }
278
279
    /**
280
     * Parse a string property directive in the form of `@twillTypeProperty('value')`.
281
     *
282
     * @param string $property
283
     * @param string $block
284
     * @param string $blockName
285
     * @param string|null $default
286 5
     * @return string
287
     * @throws \Exception
288 5
     */
289 5
    public function parseProperty(
290
        $property,
291
        $block,
292 5
        $blockName,
293
        $default = null
294
    ) {
295
        $bladeProperty = ucfirst($property);
296
297
        foreach (['twillProp', 'twillBlock', 'twillRepeater'] as $pattern) {
298
            preg_match("/@{$pattern}{$bladeProperty}\('(.*)'\)/", $block, $matches);
299
300
            if (filled($matches)) {
301
                return $matches[1];
302
            }
303
        }
304
305
        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

305
        return $this->parsePropertyFallback($property, $blockName, /** @scrutinizer ignore-type */ $default);
Loading history...
306
    }
307
308
    /**
309
     * Parse an array property directive in the form of `@twillTypeProperty([...])`
310
     * and pass the result to a given callback.
311
     *
312
     * @param string $property
313
     * @param string $block
314
     * @param string $blockName
315
     * @param Callable $callback  Should have the following signature: `function (array $value)`
316
     * @return void
317
     * @throws \Exception
318
     */
319
    public function parseArrayProperty(
320
        $property,
321
        $block,
322
        $blockName,
323
        $callback
324
    ): void {
325
        $this->parseMixedProperty($property, $block, $blockName, function($value) use ($callback) {
326
            $callback($value);
327
        });
328
    }
329
330
    /**
331
     * Parse a mixed property directive in the form of `@twillTypeProperty('value', [...])`
332
     * and pass the result to a given callback.
333
     *
334
     * @param string $property
335
     * @param string $block
336
     * @param string $blockName
337
     * @param Callable $callback  Should have the following signature: `function ($value, $options)`
338
     * @return mixed
339
     * @throws \Exception
340
     */
341
    public function parseMixedProperty(
342
        $property,
343
        $block,
344
        $blockName,
345
        $callback
346
    ) {
347
        $bladeProperty = ucfirst($property);
348
349
        foreach (['twillProp', 'twillBlock', 'twillRepeater'] as $pattern) {
350
            // Regexp modifiers:
351
            //   `s`  allows newlines as part of the `.*` match
352
            //   `U`  stops the match at the first closing parenthesis
353
            preg_match("/@{$pattern}{$bladeProperty}\((.*)\)/sU", $block, $matches);
354
355
            if (filled($matches)) {
356
                // Wrap the match in array notation and feed it to `eval` to get an actual array.
357
                // In this context, we're only interested in the first two possible values.
358
                $content = "[{$matches[1]}]";
359
                $parsedContent = eval("return {$content};");
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
360
                $value = $parsedContent[0] ?? null;
361
                $options = $parsedContent[1] ?? null;
362
363
                return $callback($value, $options);
364
            }
365
        };
366
367
        $value = $this->parseProperty($property, $block, $blockName, null);
368
369
        return $callback($value, null);
370
    }
371
372
    /**
373
     * @param $property
374
     * @param $blockName
375
     * @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...
376
     * @return mixed
377
     * @throws \Exception
378
     */
379
    private function parsePropertyFallback(
380
        $property,
381
        $blockName,
382
        $default = null
383
    ) {
384
        if (
385
            $value = config(
386
                "twill.block_editor.blocks.{$blockName}.{$property}"
387
            )
388
        ) {
389
            return $value;
390
        }
391
392
        if (
393
            $value = config(
394
                "twill.block_editor.repeaters.{$blockName}.{$property}"
395
            )
396
        ) {
397
            return $value;
398
        }
399
400
        if ($configBlock = collect(config("twill.block_editor.blocks"))->filter(function ($block) use ($blockName) {
401
            return Str::contains($block['component'], $blockName);
402
        })->first()) {
403
            if ($value = ($configBlock[$property] ?? null)) {
404
                return $value;
405
            }
406
        }
407
        if ($configRepeater = collect(config("twill.block_editor.repeaters"))->filter(function ($repeater) use ($blockName) {
408
            return Str::contains($repeater['component'], $blockName);
409
        })->first()) {
410
            if ($value = ($configRepeater[$property] ?? null)) {
411
                return $value;
412
            }
413
        }
414
415
        if ($property !== 'title') {
416
            return $default;
417
        }
418
419
        // Title is mandatory
420
        throw new Exception(
421
            "Block {$blockName} does not exists or the mandatory property '{$property}' " .
422
            "was not found on this block. If you are still using blocks on the twill.php " .
423
            "file, please check if the block is present and properly configured."
424
        );
425
    }
426
427
    /**
428
     * @param $block
429
     * @return bool
430
     */
431
    public function isNewFormat($block)
432
    {
433
        preg_match("/@twill(Prop|Block|Repeater).*\('(.*)'\)/", $block, $propMatches);
434
435
        return filled($propMatches);
436
    }
437
438
    /**
439
     * @return string
440
     */
441
    public function getFileName()
442
    {
443
        return $this->file ? $this->file->getFileName() : 'Custom Vue file';
444
    }
445
446
    /**
447
     * @return string
448
     * @throws \Throwable
449
     */
450
    public function render()
451
    {
452
        return BladeCompiler::render(
453
            self::removeSpecialBladeTags($this->contents),
454
            [
455
                'renderForBlocks' => true,
456
            ]
457
        );
458
    }
459
460
    /**
461
     * @param $contents
462
     * @return string
463
     */
464
    public static function removeSpecialBladeTags($contents)
465
    {
466
        return preg_replace([
467
            "/@twillProp.*\((.*)\)/sU",
468
            "/@twillBlock.*\((.*)\)/sU",
469
            "/@twillRepeater.*\((.*)\)/sU",
470
        ], '', $contents);
471
    }
472
}
473