Passed
Pull Request — 2.x (#1360)
by Harings
14:11
created

TwillServiceProvider::addViewComposers()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 26
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
eloc 14
c 0
b 0
f 0
nc 8
nop 0
dl 0
loc 26
ccs 0
cts 0
cp 0
crap 20
rs 9.7998
1
<?php
2
3
namespace A17\Twill;
4
5
use A17\Twill\View\Components\BlockEditor;
6
use A17\Twill\View\Components\Checkbox;
7
use Exception;
8
use A17\Twill\Commands\BlockMake;
9
use A17\Twill\Commands\Build;
10
use A17\Twill\Commands\CapsuleInstall;
11
use A17\Twill\Commands\CreateSuperAdmin;
12
use A17\Twill\Commands\Dev;
13
use A17\Twill\Commands\GenerateBlocks;
14
use A17\Twill\Commands\Install;
15
use A17\Twill\Commands\ListBlocks;
16
use A17\Twill\Commands\ListIcons;
17
use A17\Twill\Commands\MakeCapsule;
18
use A17\Twill\Commands\MakeSingleton;
19
use A17\Twill\Commands\ModuleMake;
20
use A17\Twill\Commands\ModuleMakeDeprecated;
21
use A17\Twill\Commands\RefreshLQIP;
22
use A17\Twill\Commands\RefreshCrops;
23
use A17\Twill\Commands\SyncLang;
24
use A17\Twill\Commands\Update;
25
use A17\Twill\Http\ViewComposers\ActiveNavigation;
26
use A17\Twill\Http\ViewComposers\CurrentUser;
27
use A17\Twill\Http\ViewComposers\FilesUploaderConfig;
28
use A17\Twill\Http\ViewComposers\Localization;
29
use A17\Twill\Http\ViewComposers\MediasUploaderConfig;
30
use A17\Twill\Models\Block;
31
use A17\Twill\Models\File;
32
use A17\Twill\Models\Media;
33
use A17\Twill\Models\User;
34
use A17\Twill\Services\Capsules\HasCapsules;
35
use A17\Twill\Services\FileLibrary\FileService;
36
use A17\Twill\Services\MediaLibrary\ImageService;
37
use Astrotomic\Translatable\TranslatableServiceProvider;
38
use Cartalyst\Tags\TagsServiceProvider;
39
use Illuminate\Database\Eloquent\Relations\Relation;
40
use Illuminate\Foundation\AliasLoader;
41
use Illuminate\Support\Facades\Blade;
42
use Illuminate\Support\Facades\View;
43
use Illuminate\Support\ServiceProvider;
44
use Illuminate\Support\Str;
45
use Spatie\Activitylog\ActivitylogServiceProvider;
46
use PragmaRX\Google2FAQRCode\Google2FA as Google2FAQRCode;
47
48
class TwillServiceProvider extends ServiceProvider
49
{
50
    use HasCapsules;
0 ignored issues
show
Bug introduced by
The trait A17\Twill\Services\Capsules\HasCapsules requires the property $command which is not provided by A17\Twill\TwillServiceProvider.
Loading history...
51
52
    /**
53
     * The Twill version.
54
     *
55
     * @var string
56
     */
57
    const VERSION = '2.6.0';
58
59
    /**
60
     * Service providers to be registered.
61
     *
62
     * @var string[]
63
     */
64
    protected $providers = [
65
        RouteServiceProvider::class,
66
        AuthServiceProvider::class,
67
        ValidationServiceProvider::class,
68
        TranslatableServiceProvider::class,
69 73
        TagsServiceProvider::class,
70
        ActivitylogServiceProvider::class,
71 73
        CapsulesServiceProvider::class,
72
    ];
73 73
74 73
    private $migrationsCounter = 0;
0 ignored issues
show
introduced by
The private property $migrationsCounter is not used, and could be removed.
Loading history...
75 73
76
    /**
77 73
     * Bootstraps the package services.
78
     *
79 73
     * @return void
80 73
     */
81
    public function boot()
82 73
    {
83 73
        $this->requireHelpers();
84 73
85
        $this->publishConfigs();
86
        $this->publishMigrations();
87
        $this->publishAssets();
88
89 73
        $this->registerCommands();
90
91 73
        $this->registerAndPublishViews();
92 73
        $this->registerAndPublishTranslations();
93 73
94 73
        $this->extendBlade();
95 73
        $this->addViewComposers();
96 73
97 73
        $this->check2FA();
98
    }
99
100
    /**
101
     * @return void
102
     */
103
    private function requireHelpers()
104 73
    {
105
        require_once __DIR__ . '/Helpers/routes_helpers.php';
106 73
        require_once __DIR__ . '/Helpers/i18n_helpers.php';
107
        require_once __DIR__ . '/Helpers/media_library_helpers.php';
108 73
        require_once __DIR__ . '/Helpers/frontend_helpers.php';
109 73
        require_once __DIR__ . '/Helpers/migrations_helpers.php';
110
        require_once __DIR__ . '/Helpers/helpers.php';
111 73
    }
112 73
113
    /**
114
     * Registers the package services.
115
     *
116
     * @return void
117
     */
118 73
    public function register()
119 73
    {
120
        $this->mergeConfigs();
121
122
        $this->registerProviders();
123
        $this->registerAliases();
124
125
        // Only works as of laravel 7.
126 73
        if (self::supportsBladeComponents()) {
127
            Blade::componentNamespace('A17\\Twill\\View\\Components', 'twill');
128 73
        }
129 73
130
        Relation::morphMap([
131
            'users' => User::class,
132 73
            'media' => Media::class,
133 73
            'files' => File::class,
134 8
            'blocks' => Block::class,
135 73
        ]);
136
137
        config(['twill.version' => $this->version()]);
138 73
    }
139 73
140 3
    public static function supportsBladeComponents(): bool {
141 73
        return (int)explode('.', app()->version())[0] >= 8;
0 ignored issues
show
introduced by
The method version() does not exist on Illuminate\Container\Container. Are you sure you never get this type here, but always one of the subclasses? ( Ignorable by Annotation )

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

141
        return (int)explode('.', app()->/** @scrutinizer ignore-call */ version())[0] >= 8;
Loading history...
142
    }
143 73
144
    /**
145
     * Registers the package service providers.
146
     *
147
     * @return void
148
     */
149
    private function registerProviders()
150 73
    {
151
        foreach ($this->providers as $provider) {
152 73
            $this->app->register($provider);
153
        }
154 73
155 73
        if (config('twill.enabled.media-library')) {
156
            $this->app->singleton('imageService', function () {
157
                return $this->app->make(config('twill.media_library.image_service'));
158 73
            });
159 73
        }
160
161
        if (config('twill.enabled.file-library')) {
162 73
            $this->app->singleton('fileService', function () {
163
                return $this->app->make(config('twill.file_library.file_service'));
164
            });
165
        }
166
    }
167
168
    /**
169 73
     * Registers the package facade aliases.
170
     *
171 73
     * @return void
172 73
     */
173 73
    private function registerAliases()
174
    {
175
        $loader = AliasLoader::getInstance();
176
177 73
        if (config('twill.enabled.media-library')) {
178 73
            $loader->alias('ImageService', ImageService::class);
179
        }
180
181
        if (config('twill.enabled.file-library')) {
182 73
            $loader->alias('FileService', FileService::class);
183 73
        }
184 73
    }
185 73
186
    /**
187
     * Defines the package configuration files for publishing.
188
     *
189 73
     * @return void
190 73
     */
191
    private function publishConfigs()
192 73
    {
193
        if (config('twill.enabled.users-management')) {
194 73
            config(['auth.providers.twill_users' => [
195 73
                'driver' => 'eloquent',
196 73
                'model' => User::class,
197 73
            ]]);
198
199
            config(['auth.guards.twill_users' => [
200
                'driver' => 'session',
201
                'provider' => 'twill_users',
202
            ]]);
203
204 73
            if (blank(config('auth.passwords.twill_users'))) {
205
                config(['auth.passwords.twill_users' => [
206 73
                    'provider' => 'twill_users',
207 73
                    'table' => config('twill.password_resets_table', 'twill_password_resets'),
208 73
                    'expire' => 60,
209 73
                    'throttle' => 60,
210 73
                ]]);
211 73
            }
212 73
        }
213 73
214 73
        config(['activitylog.enabled' => config('twill.enabled.dashboard') ? true : config('twill.enabled.activitylog')]);
215 73
        config(['activitylog.subject_returns_soft_deleted_models' => true]);
216 73
217 73
        config(['analytics.service_account_credentials_json' => config('twill.dashboard.analytics.service_account_credentials_json', storage_path('app/analytics/service-account-credentials.json'))]);
218 73
219
        $this->publishes([__DIR__ . '/../config/twill-publish.php' => config_path('twill.php')], 'config');
220 73
        $this->publishes([__DIR__ . '/../config/twill-navigation.php' => config_path('twill-navigation.php')], 'config');
221 73
        $this->publishes([__DIR__ . '/../config/translatable.php' => config_path('translatable.php')], 'config');
222 73
    }
223
224
    /**
225 73
     * Merges the package configuration files into the given configuration namespaces.
226 73
     *
227 73
     * @return void
228
     */
229
    private function mergeConfigs()
230 73
    {
231 73
        $this->mergeConfigFrom(__DIR__ . '/../config/twill.php', 'twill');
232
        $this->mergeConfigFrom(__DIR__ . '/../config/frontend.php', 'twill.frontend');
233 73
        $this->mergeConfigFrom(__DIR__ . '/../config/debug.php', 'twill.debug');
234
        $this->mergeConfigFrom(__DIR__ . '/../config/seo.php', 'twill.seo');
235 73
        $this->mergeConfigFrom(__DIR__ . '/../config/blocks.php', 'twill.block_editor');
236 73
        $this->mergeConfigFrom(__DIR__ . '/../config/enabled.php', 'twill.enabled');
237 73
        $this->mergeConfigFrom(__DIR__ . '/../config/file-library.php', 'twill.file_library');
238 73
        $this->mergeConfigFrom(__DIR__ . '/../config/media-library.php', 'twill.media_library');
239 73
        $this->mergeConfigFrom(__DIR__ . '/../config/imgix.php', 'twill.imgix');
240 73
        $this->mergeConfigFrom(__DIR__ . '/../config/glide.php', 'twill.glide');
241
        $this->mergeConfigFrom(__DIR__ . '/../config/twicpics.php', 'twill.twicpics');
242 73
        $this->mergeConfigFrom(__DIR__ . '/../config/dashboard.php', 'twill.dashboard');
243
        $this->mergeConfigFrom(__DIR__ . '/../config/oauth.php', 'twill.oauth');
244 73
        $this->mergeConfigFrom(__DIR__ . '/../config/disks.php', 'filesystems.disks');
245
246 73
        if (config('twill.media_library.endpoint_type') === 'local'
247 73
            && config('twill.media_library.disk') === 'twill_media_library') {
248
            $this->setLocalDiskUrl('media');
249
        }
250 73
251 73
        if (config('twill.file_library.endpoint_type') === 'local'
252 73
            && config('twill.file_library.disk') === 'twill_file_library') {
253
            $this->setLocalDiskUrl('file');
254 73
        }
255 73
256 73
        $this->mergeConfigFrom(__DIR__ . '/../config/services.php', 'services');
257
    }
258 73
259
    private function setLocalDiskUrl($type)
260 73
    {
261 69
        config([
262
            'filesystems.disks.twill_' . $type . '_library.url' => request()->getScheme()
263 69
            . '://'
264 69
            . str_replace(['http://', 'https://'], '', config('app.url'))
265 69
            . '/storage/'
266
            . trim(config('twill.' . $type . '_library.local_path'), '/ '),
267 73
        ]);
268
    }
269
270
    private function publishMigrations()
271
    {
272 73
        if (config('twill.load_default_migrations', true)) {
273
            $this->loadMigrationsFrom(__DIR__ . '/../migrations/default');
274 73
        }
275 73
276 73
        $this->publishes([
277 73
            __DIR__ . '/../migrations/default' => database_path('migrations'),
278
        ], 'migrations');
279
280
        $this->publishOptionalMigration('users-2fa');
281
        $this->publishOptionalMigration('users-oauth');
282 73
    }
283
284 73
    private function publishOptionalMigration($feature)
285
    {
286 73
        if (config('twill.enabled.' . $feature, false)) {
287 73
            $this->loadMigrationsFrom(__DIR__ . '/../migrations/optional/' . $feature);
288 73
289
            $this->publishes([
290
                __DIR__ . '/../migrations/optional/' . $feature => database_path('migrations'),
291
            ], 'migrations');
292
        }
293 73
    }
294
295 73
    /**
296 73
     * @return void
297
     */
298
    private function publishAssets()
299
    {
300
        $this->publishes([
301
            __DIR__ . '/../dist' => public_path(),
302
        ], 'assets');
303
    }
304
305
    /**
306
     * @return void
307
     */
308
    private function registerAndPublishViews()
309
    {
310 73
        $viewPath = __DIR__ . '/../views';
311
312
        $this->loadViewsFrom($viewPath, 'twill');
313
        $this->publishes([$viewPath => resource_path('views/vendor/twill')], 'views');
314
    }
315
316
    /**
317 5
     * @return void
318
     */
319 5
    private function registerCommands()
320
    {
321 5
        $this->commands([
322
            Install::class,
323 5
            ModuleMake::class,
324
            MakeCapsule::class,
325 5
            MakeSingleton::class,
326 5
            ModuleMakeDeprecated::class,
327 5
            BlockMake::class,
328 5
            ListIcons::class,
329 4
            ListBlocks::class,
330
            CreateSuperAdmin::class,
331
            RefreshLQIP::class,
332 5
            RefreshCrops::class,
333
            GenerateBlocks::class,
334
            Build::class,
335
            Update::class,
336
            Dev::class,
337
            SyncLang::class,
338
            CapsuleInstall::class,
339
        ]);
340 73
    }
341
342 73
    /**
343
     * @param string $view
344 73
     * @param string $expression
345
     * @return string
346 73
     */
347
    private function includeView($view, $expression): string
348 73
    {
349
        [$name] = str_getcsv($expression, ',', '\'');
350
351 73
        $partialNamespace = view()->exists('admin.' . $view . $name) ? 'admin.' : 'twill::';
352
353 73
        $view = $partialNamespace . $view . $name;
354 5
355 73
        if (
356
            self::supportsBladeComponents() &&
357 73
            class_exists(Blade::getClassComponentNamespaces()['twill'] . '\\' . Str::studly($name))
358
        ) {
359 3
            $expression = explode(',', $expression);
360
            array_shift($expression);
361 3
            $expression = implode(',', $expression);
362 3
            if ($expression === "") {
363
                $expression = '[]';
364 3
            }
365 3
            $expression = str_replace("'", "\\'", $expression);
366 3
367 3
            $php = '<?php' . PHP_EOL;
368
            $php .= "\$data = eval('return $expression;');";
369 3
            $php .= '$attributes = "";';
370
            $php .= 'foreach(array_keys($data) as $attribute) {';
371
            $php .= '  $attributes .= " :$attribute=\'$" . $attribute . "\'";';
372
            $php .= '}' . PHP_EOL;
373 3
            $php .= 'if ($renderForBlocks ?? false) {';
374 3
            $php .= '  $attributes .= " :render-for-blocks=\'true\'";';
375 3
            $php .= '}';
376 3
            $php .= 'if ($renderForModal ?? false) {';
377 2
            $php .= '  $attributes .= " :render-for-modal=\'true\'";';
378
            $php .= '}';
379
            $php .= '$name = "' . $name . '";';
380 3
            $php .= 'echo Blade::render("<x-twill::$name $attributes />", $data); ?>';
381 3
382 3
            return $php;
383 3
        }
384 3
385 3
        // Legacy behaviour.
386 3
        // @TODO: Not sure if we should keep this.
387 3
        $expression = explode(',', $expression);
388 3
        array_shift($expression);
389
        $expression = "(" . implode(',', $expression) . ")";
390
        if ($expression === "()") {
391 73
            $expression = '([])';
392
        }
393 73
394 1
        return "<?php echo \$__env->make('{$view}', \Illuminate\Support\Arr::except(get_defined_vars(), ['__data', '__path']))->with{$expression}->render(); ?>";
395 1
    }
396 1
397 73
    /**
398
     * Defines the package additional Blade Directives.
399 73
     *
400 1
     * @return void
401 73
     */
402
    private function extendBlade()
403 73
    {
404 73
        $blade = $this->app['view']->getEngineResolver()->resolve('blade')->getCompiler();
405 73
406 73
        $blade->directive('dd', function ($param) {
407 73
            return "<?php dd({$param}); ?>";
408
        });
409 73
410 73
        $blade->directive('dumpData', function ($data) {
411 73
            return sprintf("<?php (new Symfony\Component\VarDumper\VarDumper)->dump(%s); exit; ?>",
412 73
                null != $data ? $data : "get_defined_vars()");
413 73
        });
414 73
415
        $blade->directive('formField', function ($expression) {
416
            return $this->includeView('partials.form._', $expression);
417 73
        });
418
419
        $blade->directive('partialView', function ($expression) {
420
            $expressionAsArray = str_getcsv($expression, ',', '\'');
421
422
            [$moduleName, $viewName] = $expressionAsArray;
423
            $partialNamespace = 'twill::partials';
424 73
425
            $viewModule = "twillViewName($moduleName, '{$viewName}')";
426 73
            $viewApplication = "'admin.partials.{$viewName}'";
427 73
            $viewModuleTwill = "'twill::'.$moduleName.'.{$viewName}'";
428
            $view = $partialNamespace . "." . $viewName;
429
430 73
            if (!isset($moduleName) || is_null($moduleName)) {
431 73
                $viewModule = $viewApplication;
432
            }
433
434 73
            $expression = explode(',', $expression);
435 73
            $expression = array_slice($expression, 2);
436
            $expression = "(" . implode(',', $expression) . ")";
437
            if ($expression === "()") {
438 73
                $expression = '([])';
439
            }
440 73
441 52
            return "<?php
442 52
            if( view()->exists($viewModule)) {
443
                echo \$__env->make($viewModule, \Illuminate\Support\Arr::except(get_defined_vars(), ['__data', '__path']))->with{$expression}->render();
444 52
            } elseif( view()->exists($viewApplication)) {
445
                echo \$__env->make($viewApplication, \Illuminate\Support\Arr::except(get_defined_vars(), ['__data', '__path']))->with{$expression}->render();
446 52
            } elseif( view()->exists($viewModuleTwill)) {
447 73
                echo \$__env->make($viewModuleTwill, \Illuminate\Support\Arr::except(get_defined_vars(), ['__data', '__path']))->with{$expression}->render();
448
            } elseif( view()->exists('$view')) {
449 73
                echo \$__env->make('$view', \Illuminate\Support\Arr::except(get_defined_vars(), ['__data', '__path']))->with{$expression}->render();
450 73
            }
451
            ?>";
452
        });
453
454
        $blade->directive('pushonce', function ($expression) {
455
            [$pushName, $pushSub] = explode(':', trim(substr($expression, 1, -1)));
456
            $key = '__pushonce_' . $pushName . '_' . str_replace('-', '_', $pushSub);
457 73
            return "<?php if(! isset(\$__env->{$key})): \$__env->{$key} = 1; \$__env->startPush('{$pushName}'); ?>";
458
        });
459 73
460
        $blade->directive('endpushonce', function () {
461 73
            return '<?php $__env->stopPush(); endif; ?>';
462 73
        });
463 73
464
        $blade->component('twill::partials.form.utils._fieldset', 'formFieldset');
465
        $blade->component('twill::partials.form.utils._columns', 'formColumns');
466
        $blade->component('twill::partials.form.utils._collapsed_fields', 'formCollapsedFields');
467
        $blade->component('twill::partials.form.utils._connected_fields', 'formConnectedFields');
468
        $blade->component('twill::partials.form.utils._inline_checkboxes', 'formInlineCheckboxes');
469
470 73
        if (method_exists($blade, 'aliasComponent')) {
471
            $blade->aliasComponent('twill::partials.form.utils._fieldset', 'formFieldset');
472 73
            $blade->aliasComponent('twill::partials.form.utils._columns', 'formColumns');
473
            $blade->aliasComponent('twill::partials.form.utils._collapsed_fields', 'formCollapsedFields');
474
            $blade->aliasComponent('twill::partials.form.utils._connected_fields', 'formConnectedFields');
475
            $blade->aliasComponent('twill::partials.form.utils._inline_checkboxes', 'formInlineCheckboxes');
476
        }
477
    }
478
479
    /**
480
     * Registers the package additional View Composers.
481
     *
482
     * @return void
483
     */
484
    private function addViewComposers()
485
    {
486
        if (config('twill.enabled.users-management')) {
487
            View::composer(['admin.*', 'twill::*'], CurrentUser::class);
488
        }
489
490
        if (config('twill.enabled.media-library')) {
491
            View::composer('twill::layouts.main', MediasUploaderConfig::class);
492
        }
493
494
        if (config('twill.enabled.file-library')) {
495
            View::composer('twill::layouts.main', FilesUploaderConfig::class);
496
        }
497
498
        View::composer('twill::partials.navigation.*', ActiveNavigation::class);
499
500
        View::composer(['admin.*', 'templates.*', 'twill::*'], function ($view) {
501
            $with = array_merge([
502
                'renderForBlocks' => false,
503
                'renderForModal' => false,
504
            ], $view->getData());
505
506
            return $view->with($with);
507
        });
508
509
        View::composer(['admin.*', 'twill::*'], Localization::class);
510
    }
511
512
    /**
513
     * Registers and publishes the package additional translations.
514
     *
515
     * @return void
516
     */
517
    private function registerAndPublishTranslations()
518
    {
519
        $translationPath = __DIR__ . '/../lang';
520
521
        $this->loadTranslationsFrom($translationPath, 'twill');
522
        $this->publishes([$translationPath => resource_path('lang/vendor/twill')], 'translations');
523
    }
524
525
    /**
526
     * Get the version number of Twill.
527
     *
528
     * @return string
529
     */
530
    public function version()
531
    {
532
        return static::VERSION;
533
    }
534
535
    /**
536
     * In case 2FA is enabled, we need to check if a QRCode compatible package is
537
     * installed.
538
     */
539
    public function check2FA()
540
    {
541
        if (!$this->app->runningInConsole() || !config('twill.enabled.users-2fa')) {
542
            return;
543
        }
544
545
        if (blank((new Google2FAQRCode())->getQrCodeService()))
546
        {
547
            throw new Exception(
548
                "Twill ERROR: As you have 2FA enabled, you also need to install a QRCode service package, please check https://github.com/antonioribeiro/google2fa-qrcode#built-in-qrcode-rendering-services"
549
            );
550
        }
551
    }
552
}
553