Passed
Pull Request — 2.x (#1446)
by Harings
13:09
created

ModuleMake::createCapsuleSingletonSeeder()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 30
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 21
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 30
ccs 0
cts 0
cp 0
crap 2
rs 9.584
1
<?php
2
3
namespace A17\Twill\Commands;
4
5
use A17\Twill\Facades\TwillCapsules;
6
use A17\Twill\Helpers\Capsule;
7
use Illuminate\Config\Repository as Config;
8
use Illuminate\Filesystem\Filesystem;
9
use Illuminate\Support\Collection;
10
use Illuminate\Support\Composer;
11
use Illuminate\Support\Facades\File;
12
use Illuminate\Support\Str;
13
14
class ModuleMake extends Command
15
{
16
    /**
17
     * The name and signature of the console command.
18
     *
19
     * @var string
20
     */
21
    protected $signature = 'twill:make:module {moduleName}
22
        {--B|hasBlocks}
23
        {--T|hasTranslation}
24
        {--S|hasSlug}
25
        {--M|hasMedias}
26
        {--F|hasFiles}
27
        {--P|hasPosition}
28
        {--R|hasRevisions}
29
        {--N|hasNesting}
30
        {--all}';
31
32
    /**
33
     * The console command description.
34
     *
35
     * @var string
36
     */
37
    protected $description = 'Create a new Twill Module';
38
39
    /**
40
     * @var Filesystem
41
     */
42
    protected $files;
43
44
    /**
45
     * @var Composer
46
     */
47
    protected $composer;
48
49
    /**
50
     * @var string[]
51
     */
52
    protected $modelTraits;
53
54
    /**
55
     * @var string[]
56
     */
57
    protected $repositoryTraits;
58
59
    /**
60
     * @var Config
61
     */
62
    protected $config;
63
64
    /**
65
     * @var bool
66
     */
67
    protected $blockable;
68
69
    /**
70
     * @var bool
71
     */
72
    protected $translatable;
73
74
    /**
75
     * @var bool
76
     */
77
    protected $sluggable;
78
79
    /**
80
     * @var bool
81
     */
82
    protected $mediable;
83
84
    /**
85
     * @var bool
86
     */
87
    protected $fileable;
88
89
    /**
90
     * @var bool
91
     */
92
    protected $sortable;
93
94
    /**
95
     * @var bool
96
     */
97
    protected $revisionable;
98
99
    /**
100
     * @var bool
101
     */
102
    protected $nestable;
103
104
    /**
105 69
     * @var bool
106
     */
107 69
    protected $defaultsAnswserToNo;
108
109 69
    /**
110 69
     * @var bool
111 69
     */
112
    protected $isCapsule = false;
113 69
114 69
    /**
115 69
     * @var bool
116 69
     */
117 69
    protected $isSingleton = false;
118 69
119 69
    /**
120
     * @var string
121 69
     */
122
    protected $moduleBasePath;
123 69
124 69
    /**
125 69
     * @var \A17\Twill\Helpers\Capsule
126
     */
127
    protected $capsule;
128
129
    /**
130
     * If true we save to flat directories.
131
     *
132 1
     * @var bool
133
     */
134 1
    protected $customDirs = false;
135
136 1
    /**
137 1
     * @param Filesystem $files
138
     * @param Composer $composer
139
     * @param Config $config
140
     */
141
    public function __construct(Filesystem $files, Composer $composer, Config $config)
142
    {
143
        parent::__construct();
144 1
145 1
        $this->files = $files;
146 1
        $this->composer = $composer;
147
        $this->config = $config;
148 1
149 1
        $this->blockable = false;
150
        $this->translatable = false;
151
        $this->sluggable = false;
152 1
        $this->mediable = false;
153 1
        $this->fileable = false;
154 1
        $this->sortable = false;
155 1
        $this->revisionable = false;
156 1
        $this->nestable = false;
157 1
158 1
        $this->defaultsAnswserToNo = false;
159
160
        $this->modelTraits = [
161 1
            'HasBlocks',
162 1
            'HasTranslation',
163 1
            'HasSlug',
164 1
            'HasMedias',
165 1
            'HasFiles',
166 1
            'HasRevisions',
167 1
            'HasPosition',
168
            'HasNesting',
169
        ];
170 1
        $this->repositoryTraits = [
171
            'HandleBlocks',
172 1
            'HandleTranslations',
173 1
            'HandleSlugs',
174 1
            'HandleMedias',
175 1
            'HandleFiles',
176 1
            'HandleRevisions',
177 1
            '',
178
            'HandleNesting',
179 1
        ];
180 1
    }
181
182 1
    protected function checkCapsuleDirectory($dir)
183 1
    {
184 1
        if (file_exists($dir)) {
185 1
            if (!$this->option('force')) {
186
                $answer = $this->choice(
187
                    "Capsule path exists ($dir). Erase and overwrite?",
188
                    ['no', 'yes'],
189
                    $this->defaultsAnswserToNo
190 1
                        ? 0
191
                        : 1
192 1
                );
193
            }
194 1
195 1
            if ('yes' === ($answer ?? 'no') || $this->option('force')) {
196
                File::deleteDirectory($dir);
197
198
                if (file_exists($dir)) {
199
                    $this->info("Directory could not be deleted. Aborted.");
200
                    die;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
201
                }
202
            } else {
203
                $this->info("Aborted");
204 1
205
                die;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
206 1
            }
207 1
        }
208
    }
209 1
210
    /**
211 1
     * Executes the console command.
212
     *
213 1
     * @return mixed
214 1
     */
215
    public function handle()
216 1
    {
217
        // e.g. newsItems
218 1
        $moduleName = Str::camel(Str::plural(lcfirst($this->argument('moduleName'))));
0 ignored issues
show
Bug introduced by
It seems like $this->argument('moduleName') can also be of type array and null; however, parameter $string of lcfirst() does only seem to accept 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

218
        $moduleName = Str::camel(Str::plural(lcfirst(/** @scrutinizer ignore-type */ $this->argument('moduleName'))));
Loading history...
219 1
220 1
        // e.g. newsItem
221 1
        $singularModuleName = Str::camel(lcfirst($this->argument('moduleName')));
222
223
        // e.g. NewsItems
224 1
        $moduleTitle = Str::studly($moduleName);
225 1
226
        // e.g. NewsItem
227
        $modelName = Str::studly(Str::singular($moduleName));
228
229
        if ($this->isCapsule && $this->option('packageDirectory') && $this->option('packageNamespace')) {
230
            $dir = base_path() . '/' . $this->option('packageDirectory') . '/src/Twill/Capsules/' . $moduleTitle;
231
            if (!$this->confirm('Creating capsule in ' . $dir, true)) {
232
                exit(1);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
233 1
            }
234 1
            $this->customDirs = true;
235 1
            $this->capsule = new Capsule(
236 1
                $moduleTitle,
237
                $this->option('packageNamespace') . '\\Twill\\Capsules',
238 1
                $dir
239
            );
240 1
        } elseif ($this->isCapsule) {
241
            $this->capsule = TwillCapsules::makeProjectCapsule($moduleTitle);
242 1
        }
243
244 1
        $enabledOptions = Collection::make($this->options())->only([
245
            'hasBlocks',
246
            'hasTranslation',
247
            'hasSlug',
248
            'hasMedias',
249
            'hasFiles',
250
            'hasPosition',
251
            'hasRevisions',
252
            'hasNesting',
253 1
        ])->filter(function ($enabled) {
254
            return $enabled;
255 1
        });
256
257 1
        if (count($enabledOptions) > 0) {
258
            $this->defaultsAnswserToNo = true;
259 1
        }
260 1
261
        $this->blockable = $this->checkOption('hasBlocks');
262 1
        $this->translatable = $this->checkOption('hasTranslation');
263
        $this->sluggable = $this->checkOption('hasSlug');
264 1
        $this->mediable = $this->checkOption('hasMedias');
265 1
        $this->fileable = $this->checkOption('hasFiles');
266 1
        $this->sortable = $this->checkOption('hasPosition');
267 1
        $this->revisionable = $this->checkOption('hasRevisions');
268
        $this->nestable = $this->checkOption('hasNesting');
269
270 1
        if ($this->nestable) {
271
            $this->sortable = true;
272
        }
273 1
274 1
        $activeTraits = [
275
            $this->blockable,
276 1
            $this->translatable,
277
            $this->sluggable,
278 1
            $this->mediable,
279
            $this->fileable,
280 1
            $this->revisionable,
281
            $this->sortable,
282
            $this->nestable,
283 1
        ];
284 1
285
        $this->createCapsuleNamespace($moduleTitle, $modelName);
286 1
        $this->createCapsulePath($moduleTitle, $modelName);
287
288 1
        $this->createMigration($moduleName);
289
        $this->createModels($modelName, $activeTraits);
290 1
        $this->createRepository($modelName, $activeTraits);
291
        $this->createController($moduleName, $modelName);
292
        $this->createRequest($modelName);
293 1
        $this->createViews($moduleName);
294
295 1
        if ($this->isCapsule) {
296 1
            if ($this->isSingleton) {
297 1
                $this->createCapsuleSingletonSeeder();
298
            }
299
            else {
300
                $this->createCapsuleSeed();
301 1
            }
302
            $this->createCapsuleRoutes();
303 1
        } elseif ($this->isSingleton) {
304
            $this->createSingletonSeed($modelName);
305 1
            $this->info("\nAdd to routes/admin.php:\n");
306
            $this->info("    Route::singleton('{$singularModuleName}');\n");
307 1
        } else {
308 1
            $this->info("\nAdd to routes/admin.php:\n");
309
            $this->info("    Route::module('{$moduleName}');\n");
310
        }
311 1
312 1
        $navModuleName = $this->isSingleton ? $singularModuleName : $moduleName;
313
        $navTitle = $this->isSingleton ? $modelName : $moduleTitle;
314
        $navType = $this->isSingleton ? 'singleton' : 'module';
315
316
        if (!$this->customDirs) {
317 1
            $this->info("Setup a new CMS menu item in config/twill-navigation.php:\n");
318 1
            $this->info("    '{$navModuleName}' => [");
319 1
            $this->info("        'title' => '{$navTitle}',");
320 1
            $this->info("        '{$navType}' => true,");
321 1
            $this->info("    ],\n");
322
323 1
            if ($this->isCapsule) {
324 1
                $this->info("Setup your new Capsule in config/twill.php:\n");
325 1
                $this->info("    'capsules' => [");
326 1
                $this->info("        'list' => [");
327
                $this->info("            [");
328 1
                $this->info("                'name' => '{$this->capsule->name}',");
329
                $this->info("                'enabled' => true,");
330 1
                $this->info("            ],");
331 1
                $this->info("        ],");
332
                $this->info("    ],\n");
333 1
            }
334
335 1
            if ($this->isSingleton) {
336 1
                $this->info("Migrate your database & seed your singleton module:\n");
337 1
                $this->info("    php artisan migrate\n");
338 1
                $this->info("    php artisan db:seed {$modelName}Seeder\n");
339 1
            } else {
340
                $this->info("Migrate your database.\n");
341
            }
342
        }
343
344 1
        $this->info("Enjoy.");
345
346
        if ($this->nestable && !class_exists('\Kalnoy\Nestedset\NestedSet')) {
347
            $this->warn("\nTo support module nesting, you must install the `kalnoy/nestedset` package:");
348
            $this->warn("\n    composer require kalnoy/nestedset\n");
349
        }
350
351
        $this->composer->dumpAutoloads();
352
    }
353
354
    /**
355 1
     * Creates a new module database migration file.
356
     *
357 1
     * @param string $moduleName
358
     * @return void
359 1
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
360
     */
361 1
    private function createMigration($moduleName = 'items')
362
    {
363 1
        $table = Str::snake($moduleName);
364 1
        $tableClassName = Str::studly($table);
365 1
366
        $migrationName = 'create_' . $table . '_tables';
367
368
        if (!count(glob($this->databasePath('migrations/*' . $migrationName . '.php')))) {
369 1
            $migrationPath = $this->databasePath() . '/migrations';
370
371 1
            $this->makeDir($migrationPath);
372
373 1
            $fullPath = $this->laravel['migration.creator']->create($migrationName, $migrationPath);
374
375 1
            $stub = str_replace(
376
                ['{{table}}', '{{singularTableName}}', '{{tableClassName}}'],
377 1
                [$table, Str::singular($table), $tableClassName],
378 1
                $this->files->get(__DIR__ . '/stubs/migration.stub')
379
            );
380
381
            if ($this->translatable) {
382
                $stub = preg_replace('/{{!hasTranslation}}[\s\S]+?{{\/!hasTranslation}}/', '', $stub);
383
            } else {
384
                $stub = str_replace([
385
                    '{{!hasTranslation}}',
386
                    '{{/!hasTranslation}}',
387
                ], '', $stub);
388 1
            }
389
390 1
            $stub = $this->renderStubForOption($stub, 'hasTranslation', $this->translatable);
391
            $stub = $this->renderStubForOption($stub, 'hasSlug', $this->sluggable);
392 1
            $stub = $this->renderStubForOption($stub, 'hasRevisions', $this->revisionable);
393
            $stub = $this->renderStubForOption($stub, 'hasPosition', $this->sortable);
394 1
            $stub = $this->renderStubForOption($stub, 'hasNesting', $this->nestable);
395 1
396 1
            $stub = preg_replace('/\}\);[\s\S]+?Schema::create/', "});\n\n        Schema::create", $stub);
397 1
398
            $this->files->put($fullPath, $stub);
399
400 1
            $this->info("Migration created successfully! Add some fields!");
401
        }
402 1
    }
403 1
404
    /**
405
     * Creates new model class files for the given model name and traits.
406
     *
407
     * @param string $modelName
408
     * @param array $activeTraits
409
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
410
     */
411
    private function createModels($modelName = 'Item', $activeTraits = [])
412 1
    {
413
        $modelClassName = $this->namespace('models', 'Models', $modelName);
414 1
415
        $modelsDir = $this->isCapsule ? $this->capsule->getModelsDir() : 'Models';
416 1
417
        $this->makeTwillDirectory($modelsDir);
418 1
419
        if ($this->translatable) {
420 1
            $this->makeTwillDirectory($baseDir = $this->isCapsule ? $this->capsule->getModelsDir() : "$modelsDir/Translations");
421
422 1
            $modelTranslationClassName = $modelName . 'Translation';
423 1
424
            $stub = str_replace(
425
                [
426
                    '{{modelTranslationClassName}}',
427
                    '{{modelClassWithNamespace}}',
428
                    '{{modelClassName}}',
429
                    '{{namespace}}',
430
                    '{{baseTranslationModel}}',
431
                ],
432 1
                [
433
                    $modelTranslationClassName,
434 1
                    $modelClassName,
435
                    $modelName,
436 1
                    $this->namespace('models', 'Models\Translations'),
437
                    config('twill.base_translation_model'),
438 1
                ],
439
                $this->files->get(__DIR__ . '/stubs/model_translation.stub')
440 1
            );
441
442 1
            $this->putTwillStub(twill_path("$baseDir/" . $modelTranslationClassName . '.php'), $stub);
443 1
        }
444
445 1
        if ($this->sluggable) {
446
            $this->makeTwillDirectory($baseDir = $this->isCapsule ? $this->capsule->getModelsDir() : "$modelsDir/Slugs");
447 1
448 1
            $modelSlugClassName = $modelName . 'Slug';
449
450
            $stub = str_replace(
451
                [
452
                    '{{modelSlugClassName}}',
453
                    '{{modelClassWithNamespace}}',
454
                    '{{modelName}}',
455
                    '{{namespace}}',
456
                    '{{baseSlugModel}}',
457
                ],
458
                [
459
                    $modelSlugClassName,
460
                    $modelClassName,
461
                    Str::snake($modelName),
462
                    $this->namespace('models', 'Models\Slugs'),
463
                    config('twill.base_slug_model'),
464
                ],
465
                $this->files->get(__DIR__ . '/stubs/model_slug.stub')
466
            );
467
468
            $this->putTwillStub(twill_path("$baseDir/" . $modelSlugClassName . '.php'), $stub);
469
        }
470
471
        if ($this->revisionable) {
472
            $this->makeTwillDirectory($baseDir = $this->isCapsule ? $this->capsule->getModelsDir() : "$modelsDir/Revisions");
473
474
            $modelRevisionClassName = $modelName . 'Revision';
475
476
            $stub = str_replace(
477
                [
478
                    '{{modelRevisionClassName}}',
479
                    '{{modelClassWithNamespace}}',
480
                    '{{modelName}}',
481
                    '{{namespace}}',
482
                    '{{baseRevisionModel}}',
483
                ],
484
                [
485
                    $modelRevisionClassName,
486
                    $modelClassName,
487
                    Str::snake($modelName),
488
                    $this->namespace('models', 'Models\Revisions'),
489
                    config('twill.base_revision_model'),
490
                ],
491
                $this->files->get(__DIR__ . '/stubs/model_revision.stub')
492
            );
493
494
            $this->putTwillStub(twill_path("$baseDir/" . $modelRevisionClassName . '.php'), $stub);
495
        }
496
497
        $activeModelTraits = [];
498
499
        foreach ($activeTraits as $index => $traitIsActive) {
500
            if ($traitIsActive) {
501
                !isset($this->modelTraits[$index]) ?: $activeModelTraits[] = $this->modelTraits[$index];
502
            }
503
        }
504
505
        $activeModelTraitsString = empty($activeModelTraits) ? '' : 'use ' . rtrim(
506
                implode(', ', $activeModelTraits),
507
                ', '
508
            ) . ';';
509
510
        $activeModelTraitsImports = empty($activeModelTraits) ? '' : "use A17\Twill\Models\Behaviors\\" . implode(
511
                ";\nuse A17\Twill\Models\Behaviors\\",
512
                $activeModelTraits
513
            ) . ";";
514
515
        $activeModelImplements = $this->sortable ? 'implements Sortable' : '';
516
517
        if ($this->sortable) {
518
            $activeModelTraitsImports .= "\nuse A17\Twill\Models\Behaviors\Sortable;";
519
        }
520
521
        $stub = str_replace([
522
            '{{modelClassName}}',
523
            '{{modelTraits}}',
524
            '{{modelImports}}',
525
            '{{modelImplements}}',
526
            '{{namespace}}',
527
            '{{baseModel}}',
528
        ], [
529
            $modelName,
530
            $activeModelTraitsString,
531
            $activeModelTraitsImports,
532
            $activeModelImplements,
533
            $this->namespace('models', 'Models'),
534
            config('twill.base_model'),
535
        ], $this->files->get(__DIR__ . '/stubs/model.stub'));
536
537
        $stub = $this->renderStubForOption($stub, 'hasTranslation', $this->translatable);
538
        $stub = $this->renderStubForOption($stub, 'hasSlug', $this->sluggable);
539
        $stub = $this->renderStubForOption($stub, 'hasMedias', $this->mediable);
540
        $stub = $this->renderStubForOption($stub, 'hasPosition', $this->sortable);
541
542
        $this->putTwillStub(twill_path("$modelsDir/" . $modelName . '.php'), $stub);
543
544
        $this->info("Models created successfully! Fill your fillables!");
545
    }
546
547
    private function renderStubForOption($stub, $option, $enabled)
548
    {
549
        if ($enabled) {
550
            $stub = str_replace([
551
                '{{' . $option . '}}',
552
                '{{/' . $option . '}}',
553
            ], '', $stub);
554
        } else {
555
            $stub = preg_replace('/{{' . $option . '}}[\s\S]+?{{\/' . $option . '}}/', '', $stub);
556
        }
557
558
        return $stub;
559
    }
560
561
    /**
562
     * Creates new repository class file for the given model name.
563
     *
564
     * @param string $modelName
565
     * @param array $activeTraits
566
     * @return void
567
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
568
     */
569
    private function createRepository($modelName = 'Item', $activeTraits = [])
570
    {
571
        $modelsDir = $this->isCapsule ? $this->capsule->getRepositoriesDir() : 'Repositories';
572
573
        $modelClass = $this->isCapsule ? $this->capsule->getModel() : config(
574
                'twill.namespace'
575
            ) . "\Models\\{$modelName}";
576
577
        $this->makeTwillDirectory($modelsDir);
578
579
        $repositoryClassName = $modelName . 'Repository';
580
581
        $activeRepositoryTraits = [];
582
583
        foreach ($activeTraits as $index => $traitIsActive) {
584
            if ($traitIsActive) {
585
                !isset($this->repositoryTraits[$index]) ?: $activeRepositoryTraits[] = $this->repositoryTraits[$index];
586
            }
587
        }
588
589
        $activeRepositoryTraits = array_filter($activeRepositoryTraits);
590
591
        $activeRepositoryTraitsString = empty($activeRepositoryTraits) ? '' : 'use ' . (empty($activeRepositoryTraits) ? "" : rtrim(
592
                    implode(', ', $activeRepositoryTraits),
593
                    ', '
594
                ) . ';');
595
596
        $activeRepositoryTraitsImports = empty($activeRepositoryTraits) ? '' : "use A17\Twill\Repositories\Behaviors\\" . implode(
597
                ";\nuse A17\Twill\Repositories\Behaviors\\",
598
                $activeRepositoryTraits
599
            ) . ";";
600
601
        $stub = str_replace(
602
            [
603
                '{{repositoryClassName}}',
604
                '{{modelName}}',
605
                '{{repositoryTraits}}',
606
                '{{repositoryImports}}',
607
                '{{namespace}}',
608
                '{{modelClass}}',
609
                '{{baseRepository}}',
610
            ],
611
            [
612
                $repositoryClassName,
613
                $modelName,
614
                $activeRepositoryTraitsString,
615
                $activeRepositoryTraitsImports,
616
                $this->namespace('repositories', 'Repositories'),
617
                $modelClass,
618
                config('twill.base_repository'),
619
            ],
620
            $this->files->get(__DIR__ . '/stubs/repository.stub')
621
        );
622
623
        $this->putTwillStub(twill_path("{$modelsDir}/" . $repositoryClassName . '.php'), $stub);
624
625
        $this->info("Repository created successfully! Control all the things!");
626
    }
627
628
    /**
629
     * Create a new controller class file for the given module name and model name.
630
     *
631
     * @param string $moduleName
632
     * @param string $modelName
633
     * @return void
634
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
635
     */
636
    private function createController($moduleName = 'items', $modelName = 'Item')
637
    {
638
        $controllerClassName = $modelName . 'Controller';
639
640
        $dir = $this->isCapsule ? $this->capsule->getControllersDir() : 'Http/Controllers/Admin';
641
642
        if ($this->isSingleton) {
643
            $baseController = config('twill.base_singleton_controller');
644
        } elseif ($this->nestable) {
645
            $baseController = config('twill.base_nested_controller');
646
        } else {
647
            $baseController = config('twill.base_controller');
648
        }
649
650
        $this->makeTwillDirectory($dir);
651
652
        $stub = str_replace(
653
            ['{{moduleName}}', '{{controllerClassName}}', '{{namespace}}', '{{baseController}}'],
654
            [
655
                $moduleName,
656
                $controllerClassName,
657
                $this->namespace('controllers', 'Http\Controllers\Admin'),
658
                $baseController,
659
            ],
660
            $this->files->get(__DIR__ . '/stubs/controller.stub')
661
        );
662
663
        $permalinkOption = '';
664
        $reorderOption = '';
665
666
        if (!$this->sluggable) {
667
            $permalinkOption = "'permalink' => false,";
668
        }
669
670
        if ($this->nestable) {
671
            $reorderOption = "'reorder' => true,";
672
673
            $stub = str_replace(['{{hasNesting}}', '{{/hasNesting}}'], '', $stub);
674
        } else {
675
            $stub = preg_replace('/{{hasNesting}}[\s\S]+?{{\/hasNesting}}/', '', $stub);
676
        }
677
678
        $stub = str_replace(
679
            ['{{permalinkOption}}', '{{reorderOption}}'],
680
            [$permalinkOption, $reorderOption],
681
            $stub
682
        );
683
684
        // Remove lines including only whitespace, leave true empty lines untouched
685
        $stub = preg_replace('/^[\s]+\n/m', '', $stub);
686
687
        $this->putTwillStub(twill_path("$dir/" . $controllerClassName . '.php'), $stub);
688
689
        $this->info("Controller created successfully! Define your index/browser/form endpoints options!");
690
    }
691
692
    /**
693
     * Creates a new request class file for the given model name.
694
     *
695
     * @param string $modelName
696
     * @return void
697
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
698
     */
699
    private function createRequest($modelName = 'Item')
700
    {
701
        $dir = $this->isCapsule ? $this->capsule->getRequestsDir() : 'Http/Requests/Admin';
702
703
        $this->makeTwillDirectory($dir);
704
705
        $requestClassName = $modelName . 'Request';
706
707
        $stub = str_replace(
708
            ['{{requestClassName}}', '{{namespace}}', '{{baseRequest}}'],
709
            [$requestClassName, $this->namespace('requests', 'Http\Requests\Admin'), config('twill.base_request')],
710
            $this->files->get(__DIR__ . '/stubs/request.stub')
711
        );
712
713
        $this->putTwillStub(twill_path("{$dir}/" . $requestClassName . '.php'), $stub);
714
715
        $this->info("Form request created successfully! Add some validation rules!");
716
    }
717
718
    /**
719
     * Creates appropriate module Blade view files.
720
     *
721
     * @param string $moduleName
722
     * @return void
723
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
724
     */
725
    private function createViews($moduleName = 'items')
726
    {
727
        $viewsPath = $this->viewPath($moduleName);
728
729
        $this->makeTwillDirectory($viewsPath);
730
731
        $formView = $this->translatable ? 'form_translatable' : 'form';
732
733
        $this->putTwillStub(
734
            $viewsPath . '/form.blade.php',
735
            $this->files->get(__DIR__ . '/stubs/' . $formView . '.blade.stub')
736
        );
737
738
        $this->info("Form view created successfully! Include your form fields using @formField directives!");
739
    }
740
741
    /**
742
     * Creates a basic routes file for the Capsule.
743
     *
744
     * @param string $moduleName
745
     * @return void
746
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
747
     */
748
    public function createCapsuleRoutes(): void
749
    {
750
        $this->makeDir($this->capsule->getRoutesFile());
751
752
        $stubFile = $this->isSingleton ? 'routes_singleton_admin.stub' : 'routes_admin.stub';
753
754
        $contents = str_replace(
755
            '{{moduleName}}',
756
            $this->capsule->getModule(),
757
            $this->files->get(__DIR__ . '/stubs/' . $stubFile)
758
        );
759
760
        $this->putTwillStub($this->capsule->getRoutesFile(), $contents);
761
762
        $this->info("Routes file created successfully!");
763
    }
764
765
    /**
766
     * Creates a new capsule database seed file.
767
     *
768
     * @param string $moduleName
769
     * @return void
770
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
771
     */
772
    private function createCapsuleSeed(): void
773
    {
774
        $this->makeDir($this->capsule->getSeedsPsr4Path());
775
776
        $stub = $this->files->get(__DIR__ . '/stubs/database_seeder_capsule.stub');
777
778
        $stub = str_replace('{namespace}', $this->capsule->getSeedsNamespace(), $stub);
779
780
        $this->files->put("{$this->capsule->getSeedsPsr4Path()}/DatabaseSeeder.php", $stub);
781
782
        $this->info("Seed created successfully!");
783
    }
784
785
    /**
786
     * Creates a new singleton module database seed file.
787
     *
788
     * @param string $modelName
789
     * @return void
790
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
791
     */
792
    private function createSingletonSeed($modelName = 'Item')
793
    {
794
        $repositoryName = $modelName . 'Repository';
795
        $seederName = $modelName . 'Seeder';
796
797
        $dir = $this->databasePath('seeders');
798
799
        $this->makeTwillDirectory($dir);
800
801
        $stub = $this->files->get(__DIR__ . '/stubs/database_seeder_singleton.stub');
802
803
        $stub = $this->replaceVariables([
804
            'seederNamespace' => 'Database\\Seeders',
805
            'seederClassName' => $seederName,
806
            'modelClass' => "App\\Models\\$modelName",
807
            'modelClassName' => $modelName,
808
            'repositoryClass' => "App\\Repositories\\$repositoryName",
809
            'repositoryClassName' => $repositoryName,
810
        ], $stub);
811
812
        $stub = $this->replaceConditionals([
813
            'hasTranslations' => $this->translatable,
814
            '!hasTranslations' => !$this->translatable,
815
        ], $stub);
816
817
        $stub = $this->removeEmptyLinesWithOnlySpaces($stub);
818
819
        $this->files->put("{$dir}/{$seederName}.php", $stub);
820
821
        $this->info("Seed created successfully!");
822
    }
823
824
    private function createCapsuleSingletonSeeder(): void {
825
        $modelName = $this->capsule->getSingular();
826
        $repositoryName = $this->capsule->getSingular() . 'Repository';
827
        $seederName = $this->capsule->getSingular() . 'Seeder';
828
829
        $dir = $this->databasePath('seeders');
830
831
        $this->makeTwillDirectory($dir);
832
833
        $stub = $this->files->get(__DIR__ . '/stubs/database_seeder_singleton.stub');
834
835
        $stub = $this->replaceVariables([
836
            'seederNamespace' => $this->capsule->getDatabaseNamespace() . '\\Seeders',
837
            'seederClassName' => $seederName,
838
            'modelClass' => $this->capsule->getModelNamespace() . "\\$modelName",
839
            'modelClassName' => $modelName,
840
            'repositoryClass' => $this->capsule->getRepositoryClass(),
841
            'repositoryClassName' => $repositoryName,
842
        ], $stub);
843
844
        $stub = $this->replaceConditionals([
845
            'hasTranslations' => $this->translatable,
846
            '!hasTranslations' => !$this->translatable,
847
        ], $stub);
848
849
        $stub = $this->removeEmptyLinesWithOnlySpaces($stub);
850
851
        $this->files->put("{$dir}/{$seederName}.php", $stub);
852
853
        $this->info("Seed created successfully!");
854
    }
855
856
    private function checkOption($option)
857
    {
858
        if (!$this->hasOption($option)) {
859
            return false;
860
        }
861
862
        if ($this->option($option) || $this->option('all')) {
863
            return true;
864
        }
865
866
        $questions = [
867
            'hasBlocks' => 'Do you need to use the block editor on this module?',
868
            'hasTranslation' => 'Do you need to translate content on this module?',
869
            'hasSlug' => 'Do you need to generate slugs on this module?',
870
            'hasMedias' => 'Do you need to attach images on this module?',
871
            'hasFiles' => 'Do you need to attach files on this module?',
872
            'hasPosition' => 'Do you need to manage the position of records on this module?',
873
            'hasRevisions' => 'Do you need to enable revisions on this module?',
874
            'hasNesting' => 'Do you need to enable nesting on this module?',
875
        ];
876
877
        $defaultAnswers = [
878
            'hasNesting' => 0,
879
        ];
880
881
        $currentDefaultAnswer = $this->defaultsAnswserToNo ? 0 : ($defaultAnswers[$option] ?? 1);
882
883
        return 'yes' === $this->choice($questions[$option], ['no', 'yes'], $currentDefaultAnswer);
884
    }
885
886
    public function createCapsulePath($moduleName, $modelName)
887
    {
888
        if (!$this->isCapsule) {
889
            $this->moduleBasePath = base_path();
890
891
            return;
892
        }
893
894
        $this->checkCapsuleDirectory(
895
            $this->moduleBasePath = $this->capsule->getPsr4Path()
896
        );
897
898
        $this->makeDir($this->moduleBasePath);
899
    }
900
901
    public function createCapsuleNamespace($module, $model)
902
    {
903
        $base = config('twill.capsules.namespace');
904
905
        $this->capsuleNamespace = "{$base}\\{$module}";
0 ignored issues
show
Bug Best Practice introduced by
The property capsuleNamespace does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
906
    }
907
908
    public function databasePath($path = '')
909
    {
910
        if (!$this->isCapsule) {
911
            return database_path($path);
912
        }
913
914
        return $this->capsule->getDatabasePsr4Path() . ($path ? '/' . $path : '');
915
    }
916
917
    public function makeDir($dir)
918
    {
919
        $info = pathinfo($dir);
920
921
        $dir = isset($info['extension']) ? $info['dirname'] : $dir;
922
923
        if (!is_dir($dir)) {
924
            mkdir($dir, 0755, true);
925
        }
926
927
        if (!is_dir($dir)) {
928
            $this->info("It wasn't possible to create capsule directory $dir");
929
930
            die;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
931
        }
932
    }
933
934
    public function makeTwillDirectory($path)
935
    {
936
        if ($this->customDirs) {
937
            $this->makeDir($path);
938
        }
939
        else {
940
            make_twill_directory($path);
941
        }
942
    }
943
944
    public function putTwillStub(string $path, string $stub): void
945
    {
946
        if ($this->customDirs) {
947
            $stub = str_replace(
948
                'namespace App\\',
949
                sprintf('namespace %s\\', config('twill.namespace')),
950
                $stub
951
            );
952
953
            file_put_contents($path, $stub);
954
        }
955
        else {
956
            twill_put_stub($path, $stub);
957
        }
958
    }
959
960
    public function namespace($type, $suffix, $class = null)
961
    {
962
        $class = (filled($class) ? "\\$class" : '');
963
964
        if (!$this->isCapsule) {
965
            return "App\\{$suffix}{$class}";
966
        }
967
968
        if ($type === 'models') {
969
            return $this->capsule->getModelNamespace() . $class;
970
        }
971
972
        if ($type === 'repositories') {
973
            return $this->capsule->getRepositoriesNamespace() . $class;
974
        }
975
976
        if ($type === 'controllers') {
977
            return $this->capsule->getControllersNamespace() . $class;
978
        }
979
980
        if ($type === 'requests') {
981
            return $this->capsule->getRequestsNamespace() . $class;
982
        }
983
984
        throw new \Exception('Missing Implementation.');
985
    }
986
987
    public function viewPath($moduleName)
988
    {
989
        if (!$this->isCapsule) {
990
            return $this->config->get('view.paths')[0] . '/admin/' . $moduleName;
991
        }
992
993
        $dir = "$this->moduleBasePath/resources/views/admin";
994
        $this->makeDir($dir);
995
996
        return $dir;
997
    }
998
999
    /**
1000
     * @param array $variables
1001
     * @param string $stub
1002
     * @param array|null $delimiters
1003
     * @return string
1004
     */
1005
    public function replaceVariables($variables, $stub, $delimiters = null)
1006
    {
1007
        $delimiters = $delimiters ?: ['{{', '}}'];
1008
1009
        foreach ($variables as $key => $value) {
1010
            $key = "{$delimiters[0]}{$key}{$delimiters[1]}";
1011
1012
            $stub = str_replace($key, $value, $stub);
1013
        }
1014
1015
        return $stub;
1016
    }
1017
1018
    /**
1019
     * @param array $variables
1020
     * @param string $stub
1021
     * @param array|null $delimiters
1022
     * @return string
1023
     */
1024
    public function replaceConditionals($conditionals, $stub, $delimiters = null)
1025
    {
1026
        $delimiters = $delimiters ?: ['{{', '}}'];
1027
1028
        foreach ($conditionals as $key => $value) {
1029
            $start = "{$delimiters[0]}{$key}{$delimiters[1]}";
1030
            $end = "{$delimiters[0]}\/{$key}{$delimiters[1]}";
1031
1032
            if ((bool)$value) {
1033
                // replace delimiters only
1034
                $stub = preg_replace("/$start/", '', $stub);
1035
                $stub = preg_replace("/$end/", '', $stub);
1036
            } else {
1037
                // replace delimiters and everything between
1038
                $anything = '[\s\S]+?';
1039
                $stub = preg_replace("/{$start}{$anything}{$end}/", '', $stub);
1040
            }
1041
        }
1042
1043
        return $stub;
1044
    }
1045
1046
    /**
1047
     * @param string $stub
1048
     * @return string
1049
     */
1050
    public function removeEmptyLinesWithOnlySpaces($stub)
1051
    {
1052
        return preg_replace('/^ +\n/m', '', $stub);
1053
    }
1054
}
1055