Passed
Push — master ( 136870...398928 )
by Quentin
53:06 queued 43:01
created

BlockMaker::checkRepeaters()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 7
c 1
b 0
f 0
nc 3
nop 1
dl 0
loc 15
rs 10
1
<?php
2
3
namespace A17\Twill\Services\Blocks;
4
5
use Illuminate\Console\Command;
6
use Illuminate\Filesystem\Filesystem;
7
use Illuminate\Support\Str;
8
use Mockery\Exception;
9
use SplFileInfo;
10
11
class BlockMaker
12
{
13
    /**
14
     * @var Filesystem
15
     */
16
    protected $files;
17
18
    /**
19
     * @var \A17\Twill\Services\Blocks\BlockCollection
20
     */
21
    protected $blockCollection;
22
23
    /**
24
     * @var \Illuminate\Console\Command
25
     */
26
    protected $command;
27
28
    /**
29
     * @var \A17\Twill\Services\Blocks\Block
30
     */
31
    protected $blockBase;
32
33
    /**
34
     * @var string`
0 ignored issues
show
Documentation Bug introduced by
The doc comment string` at position 0 could not be parsed: Unknown type name 'string`' at position 0 in string`.
Loading history...
35
     */
36
    protected $icon;
37
38
    /**
39
     * @param Filesystem $files
40
     * @param \A17\Twill\Services\Blocks\BlockCollection $blockCollection
41
     */
42
    public function __construct(
43
        Filesystem $files,
44
        BlockCollection $blockCollection
45
    ) {
46
        $this->files = $files;
47
48
        $this->blockCollection = $blockCollection;
49
    }
50
51
    /**
52
     * @return \A17\Twill\Services\Blocks\BlockCollection
53
     */
54
    public function getBlockCollection()
55
    {
56
        return $this->blockCollection;
57
    }
58
59
    /**
60
     * Make a new block.
61
     *
62
     * @param $blockName
63
     * @param $baseName
64
     * @param $iconName
65
     * @return mixed
66
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
67
     * @throws \Exception
68
     */
69
    public function make($blockName, $baseName, $iconName)
70
    {
71
        $this->info('Creating block...');
72
73
        if (
74
            !$this->checkBlockStub($baseName) ||
75
            !$this->checkIconFile($iconName) ||
76
            !$this->checkBlockBaseFormat(
77
                $stubFileName = $this->blockBase->file->getPathName()
78
            )
79
        ) {
80
            return false;
81
        }
82
83
        if (
84
            !$this->checkBlockFile(
85
                $blockFile = $this->makeBlockPath(
86
                    $blockIdentifier = $this->makeBlockIdentifier($blockName)
87
                )
88
            )
89
        ) {
90
            return false;
91
        }
92
93
        $this->blockBase = $this->makeBlock(
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->makeBlock($blockN...conName, $stubFileName) of type string is incompatible with the declared type A17\Twill\Services\Blocks\Block of property $blockBase.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
94
            $blockName,
95
            $iconName,
96
            $stubFileName
97
        );
98
99
        if (
100
            !$this->checkRepeaters(
101
                $repeaters = $this->generateRepeaters(
102
                    $baseName,
103
                    $blockIdentifier,
104
                    $this->blockBase
105
                )
106
            )
107
        ) {
108
            return false;
109
        }
110
111
        return $this->saveAllFiles(
112
            $blockName,
113
            $blockFile,
114
            $repeaters,
115
            $blockIdentifier
116
        );
117
    }
118
119
    /**
120
     * @param $baseName
121
     * @return bool
122
     * @throws \Exception
123
     */
124
    protected function checkBlockStub($baseName)
125
    {
126
        if (blank($this->blockBase = $this->getBlockByName($baseName))) {
127
            $this->error("Block '{$baseName}' doesn't exists.");
128
129
            return false;
130
        }
131
132
        return true;
133
    }
134
135
    /**
136
     * @param $iconName
137
     * @return bool
138
     */
139
    protected function checkIconFile($iconName)
140
    {
141
        if (blank($this->icon = $this->getIconFile($iconName))) {
142
            $this->error("Icon '{$iconName}' doesn't exists.");
143
144
            return false;
145
        }
146
147
        return true;
148
    }
149
150
    /**
151
     * @param $blockFile
152
     * @return bool
153
     */
154
    protected function checkBlockFile($blockFile)
155
    {
156
        $this->info("File: {$blockFile}");
157
158
        if ($this->files->exists($blockFile)) {
159
            $this->error('A file with the same name already exists.');
160
161
            return false;
162
        }
163
164
        return true;
165
    }
166
167
    protected function checkBlockBaseFormat($stubFileName)
168
    {
169
        if (!$this->blockBase->isNewFormat) {
170
            $this->error(
171
                "The block file '{$stubFileName}' format is the old one."
172
            );
173
174
            $this->error('Please upgrade it before using as template.');
175
176
            return false;
177
        }
178
179
        return true;
180
    }
181
182
    protected function checkRepeaters($repeaters)
183
    {
184
        foreach ($repeaters as $repeater) {
185
            $this->info("Repeater file: {$repeater['newRepeaterPath']}");
186
187
            if ($this->files->exists($repeater['newRepeaterPath'])) {
188
                $this->error(
189
                    'A repeater file with the same name already exists.'
190
                );
191
192
                return false;
193
            }
194
        }
195
196
        return true;
197
    }
198
199
    /**
200
     * @param $blockName
201
     * @param $iconName
202
     * @param $stubFileName
203
     * @param null|string $stub
204
     * @return string|string[]|null
205
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
206
     */
207
    public function makeBlock(
208
        $blockName,
209
        $iconName,
210
        $stubFileName = null,
211
        $stub = null
212
    ) {
213
        $stub = $stub ?? $this->files->get($stubFileName);
214
215
        $title = $this->makeBlockTitle($blockName);
216
217
        $stub = preg_replace(
218
            [
219
                "/@twillPropTitle\('(.*)'\)/",
220
                "/@twillBlockTitle\('(.*)'\)/",
221
                "/@twillRepeaterTitle\('(.*)'\)/",
222
            ],
223
            [
224
                "@twillPropTitle('{$title}')",
225
                "@twillBlockTitle('{$title}')",
226
                "@twillRepeaterTitle('{$title}')",
227
            ],
228
            $stub
229
        );
230
231
        $stub = preg_replace(
232
            [
233
                "/@twillPropGroup\('twill'\)/",
234
                "/@twillBlockGroup\('twill'\)/",
235
                "/@twillRepeaterGroup\('twill'\)/",
236
            ],
237
            [
238
                "@twillPropGroup('app')",
239
                "@twillBlockGroup('app')",
240
                "@twillRepeaterGroup('app')",
241
            ],
242
            $stub
243
        );
244
245
        $stub = preg_replace(
246
            [
247
                "/@twillPropIcon\('(.*)'\)/",
248
                "/@twillBlockIcon\('(.*)'\)/",
249
            ],
250
            [
251
                "@twillPropIcon('{$iconName}')",
252
                "@twillBlockIcon('{$iconName}')",
253
            ],
254
            $stub
255
        );
256
257
        $stub = preg_replace(
258
            [
259
                "/@twillPropCompiled\('(.*)'\)\n/",
260
                "/@twillBlockCompiled\('(.*)'\)\n/",
261
                "/@twillRepeaterCompiled\('(.*)'\)\n/",
262
            ],
263
            "",
264
            $stub
265
        );
266
267
        $stub = preg_replace(
268
            [
269
                "/@twillPropComponent\('(.*)'\)\n/",
270
                "/@twillBlockComponent\('(.*)'\)\n/",
271
                "/@twillRepeaterComponent\('(.*)'\)\n/",
272
            ],
273
            "",
274
            $stub
275
        );
276
277
        return $stub;
278
    }
279
280
    /**
281
     * @param $blockName
282
     * @return string
283
     * @throws \Exception
284
     */
285
    protected function makeBlockIdentifier($blockName)
286
    {
287
        return (new Block(
288
            $this->blockBase->file,
289
            $this->blockBase->type,
290
            $this->blockBase->source
291
        ))->makeName($blockName);
292
    }
293
294
    /**
295
     * @param string $blockIdentifier
296
     * @param string $type
297
     * @return string
298
     */
299
    protected function makeBlockPath(string $blockIdentifier, $type = 'block')
300
    {
301
        $destination = config(
302
            "twill.block_editor.directories.destination.{$type}s"
303
        );
304
305
        if (!$this->files->exists($destination)) {
306
            if (
307
                !config('twill.block_editor.directories.destination.make_dir')
308
            ) {
309
                throw new Exception(
310
                    "Destination directory does not exists: {$destination}"
311
                );
312
            }
313
314
            $this->files->makeDirectory($destination, 0755, true);
315
        }
316
317
        return "{$destination}/{$blockIdentifier}.blade.php";
318
    }
319
320
    /**
321
     * @param $string
322
     * @return string
323
     */
324
    public function makeBlockTitle($string)
325
    {
326
        $string = Str::kebab($string);
327
328
        $string = str_replace(['-', '_'], ' ', $string);
329
330
        return Str::title($string);
331
    }
332
333
    /**
334
     * @param $block
335
     * @param array $sources
336
     * @return mixed
337
     * @throws \Exception
338
     */
339
    public function getBlockByName($block, $sources = [])
340
    {
341
        return $this->blockCollection->findByName($block, $sources);
342
    }
343
344
    /**
345
     * @param $icon
346
     * @return mixed
347
     */
348
    public function getIconFile($icon, $addExtension = true)
349
    {
350
        if ($addExtension) {
351
            $icon .= '.svg';
352
        }
353
354
        return collect(
355
            config('twill.block_editor.directories.source.icons')
356
        )->reduce(function ($keep, $path) use ($icon) {
357
            if ($keep) {
358
                return $keep;
359
            }
360
361
            if (!$this->files->exists($path)) {
362
                return null;
363
            }
364
365
            return collect($this->files->files($path))->reduce(function ($keep, SplFileInfo $file) use ($icon) {
366
                if ($keep) {
367
                    return $keep;
368
                }
369
370
                return $file->getFilename() === $icon ? $file->getPathName() : null;
371
            }, null);
372
373
        }, null);
374
    }
375
376
    /**
377
     * @param $baseName
378
     * @param $blockName
379
     * @param $blockBase
380
     * @return \Illuminate\Support\Collection
381
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
382
     */
383
    public function generateRepeaters($baseName, $blockName, &$blockBase)
384
    {
385
        preg_match_all(
386
            '/@formField(.*\'repeater\'.*\[.*=>.*\'(.*)\'].*)/',
387
            $blockBase,
388
            $matches
389
        );
390
391
        $repeaters = collect();
392
393
        if (count($matches) === 0) {
394
            return null;
395
        }
396
397
        foreach ($matches[2] as $index => $repeaterName) {
398
            if (Str::startsWith($repeaterName, $baseName)) {
399
                $newRepeater = $this->createRepeater(
400
                    $repeaterName,
401
                    $baseName,
402
                    $blockName,
403
                    $blockBase,
404
                    $matches[0][$index]
405
                );
406
407
                // Get the update version of the block stub, to be used on next repeaters
408
                $blockBase = $newRepeater['newBlockStub'];
409
410
                $oldRepeaterTag = $matches[0][0];
411
                $newRepeaterTag = str_replace(
412
                    "'{$repeaterName}'",
413
                    "'{$newRepeater['newRepeaterName']}'",
414
                    $oldRepeaterTag
415
                );
416
417
                $blockBase = str_replace(
418
                    $oldRepeaterTag,
419
                    $newRepeaterTag,
420
                    $blockBase
421
                );
422
423
                $repeaters->push($newRepeater);
424
            }
425
        }
426
427
        return $repeaters;
428
    }
429
430
    /**
431
     * @param $repeaterName
432
     * @param $baseName
433
     * @param $blockName
434
     * @param $blockBase
435
     * @param $blockString
436
     * @return array
437
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
438
     * @throws \Exception
439
     */
440
    public function createRepeater(
441
        $repeaterName,
442
        $baseName,
443
        $blockName,
444
        $blockBase,
445
        $blockString
446
    ) {
447
        $baseRepeater = $this->blockCollection->findByName($repeaterName);
448
449
        return [
450
            'baseRepeater' => $baseRepeater,
451
452
            'newRepeaterName' => ($newRepeaterName =
453
                $blockName . Str::after($repeaterName, $baseName)),
454
455
            'newRepeaterStub' => $this->makeBlock(
456
                $newRepeaterName,
457
                null,
458
                null,
459
                $baseRepeater->contents
460
            ),
461
462
            'newRepeaterTitle' => $this->makeBlockTitle($newRepeaterName),
463
464
            'newRepeaterPath' => $this->makeBlockPath(
465
                $newRepeaterName,
466
                Block::TYPE_REPEATER
467
            ),
468
469
            'newBlockString' => ($newBlockString = str_replace(
470
                "'{$repeaterName}'",
471
                "'{$newRepeaterName}'",
472
                $blockString
473
            )),
474
475
            'newBlockStub' => str_replace(
476
                $blockString,
477
                $newBlockString,
478
                $blockBase
479
            ),
480
        ];
481
    }
482
483
    public function put($filePath, $contents)
484
    {
485
        $directory = dirname($filePath);
486
487
        if (!$this->files->exists($directory)) {
488
            $this->files->makeDirectory($directory, 0755, true);
489
        }
490
491
        $this->files->put($filePath, $contents);
492
    }
493
494
    /**
495
     * @param $blockName
496
     * @param string $blockFile
497
     * @param \Illuminate\Support\Collection $repeaters
498
     * @param string $blockIdentifier
499
     * @return bool
500
     */
501
    protected function saveAllFiles(
502
        $blockName,
503
        string $blockFile,
504
        $repeaters,
505
        string $blockIdentifier
506
    ) {
507
        $this->put($blockFile, $this->blockBase);
508
509
        $this->info("Block {$blockName} was created.");
510
511
        foreach ($repeaters as $repeater) {
512
            $this->put(
513
                $repeater['newRepeaterPath'],
514
                $repeater['newRepeaterStub']
515
            );
516
        }
517
518
        $this->info("Block is ready to use with the name '{$blockIdentifier}'");
519
520
        return true;
521
    }
522
523
    /**
524
     * @param \Illuminate\Console\Command $command
525
     * @return BlockMaker
526
     */
527
    public function setCommand(Command $command)
528
    {
529
        $this->command = $command;
530
531
        return $this;
532
    }
533
534
    /**
535
     * @param $message
536
     */
537
    public function info($message)
538
    {
539
        if ($this->command) {
540
            $this->command->displayInfo($message);
541
        }
542
    }
543
544
    /**
545
     * @param $message
546
     */
547
    public function error($message)
548
    {
549
        if ($this->command) {
550
            $this->command->displayError($message);
551
        }
552
    }
553
}
554