Completed
Push — 4 ( d2cbf5...ae5aa4 )
by Maxime
05:55 queued 05:46
created

CoreKernel::getThemeResourceLoader()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Core;
4
5
use InvalidArgumentException;
6
use Monolog\Handler\StreamHandler;
7
use Monolog\Logger;
8
use Psr\Log\LoggerInterface;
9
use SilverStripe\Config\Collections\CachedConfigCollection;
10
use SilverStripe\Control\Director;
11
use SilverStripe\Control\HTTPResponse;
12
use SilverStripe\Control\HTTPResponse_Exception;
13
use SilverStripe\Core\Cache\ManifestCacheFactory;
14
use SilverStripe\Core\Config\ConfigLoader;
15
use SilverStripe\Core\Config\CoreConfigFactory;
16
use SilverStripe\Core\Injector\Injector;
17
use SilverStripe\Core\Injector\InjectorLoader;
18
use SilverStripe\Core\Injector\SilverStripeServiceConfigurationLocator;
19
use SilverStripe\Core\Manifest\ClassLoader;
20
use SilverStripe\Core\Manifest\ClassManifest;
21
use SilverStripe\Core\Manifest\ModuleLoader;
22
use SilverStripe\Core\Manifest\ModuleManifest;
23
use SilverStripe\Dev\DebugView;
24
use SilverStripe\Dev\Install\DatabaseAdapterRegistry;
25
use SilverStripe\Logging\ErrorHandler;
26
use SilverStripe\ORM\DB;
27
use SilverStripe\View\PublicThemes;
28
use SilverStripe\View\SSViewer;
29
use SilverStripe\View\ThemeManifest;
30
use SilverStripe\View\ThemeResourceLoader;
31
use SilverStripe\Dev\Deprecation;
32
33
/**
34
 * Simple Kernel container
35
 */
36
class CoreKernel implements Kernel
37
{
38
    /**
39
     * @var Kernel
40
     */
41
    protected $nestedFrom = null;
42
43
    /**
44
     * @var Injector
45
     */
46
    protected $container = null;
47
48
    /**
49
     * @var string
50
     */
51
    protected $enviroment = null;
52
53
    /**
54
     * @var ClassLoader
55
     */
56
    protected $classLoader = null;
57
58
    /**
59
     * @var ModuleLoader
60
     */
61
    protected $moduleLoader = null;
62
63
    /**
64
     * @var ConfigLoader
65
     */
66
    protected $configLoader = null;
67
68
    /**
69
     * @var InjectorLoader
70
     */
71
    protected $injectorLoader = null;
72
73
    /**
74
     * @var ThemeResourceLoader
75
     */
76
    protected $themeResourceLoader = null;
77
78
    protected $basePath = null;
79
80
    /**
81
     * Indicates whether the Kernel has been booted already
82
     *
83
     * @var bool
84
     */
85
    private $booted = false;
86
87
    /**
88
     * Indicates whether the Kernel has been flushed on boot
89
     * Unitialized before boot
90
     *
91
     * @var bool
92
     */
93
    private $flush;
94
95
    /**
96
     * Create a new kernel for this application
97
     *
98
     * @param string $basePath Path to base dir for this application
99
     */
100
    public function __construct($basePath)
101
    {
102
        $this->basePath = $basePath;
103
104
        // Initialise the dependency injector as soon as possible, as it is
105
        // subsequently used by some of the following code
106
        $injectorLoader = InjectorLoader::inst();
107
        $injector = new Injector(['locator' => SilverStripeServiceConfigurationLocator::class]);
108
        $injectorLoader->pushManifest($injector);
109
        $this->setInjectorLoader($injectorLoader);
110
111
        // Manifest cache factory
112
        $manifestCacheFactory = $this->buildManifestCacheFactory();
113
114
        // Class loader
115
        $classLoader = ClassLoader::inst();
116
        $classLoader->pushManifest(new ClassManifest($basePath, $manifestCacheFactory));
117
        $this->setClassLoader($classLoader);
118
119
        // Module loader
120
        $moduleLoader = ModuleLoader::inst();
121
        $moduleManifest = new ModuleManifest($basePath, $manifestCacheFactory);
122
        $moduleLoader->pushManifest($moduleManifest);
123
        $this->setModuleLoader($moduleLoader);
124
125
        // Config loader
126
        // @todo refactor CoreConfigFactory
127
        $configFactory = new CoreConfigFactory($manifestCacheFactory);
128
        $configManifest = $configFactory->createRoot();
129
        $configLoader = ConfigLoader::inst();
130
        $configLoader->pushManifest($configManifest);
131
        $this->setConfigLoader($configLoader);
132
133
        // Load template manifest
134
        $themeResourceLoader = ThemeResourceLoader::inst();
135
        $themeResourceLoader->addSet(SSViewer::PUBLIC_THEME, new PublicThemes());
136
        $themeResourceLoader->addSet(SSViewer::DEFAULT_THEME, new ThemeManifest(
137
            $basePath,
138
            null, // project is defined in config, and this argument is deprecated
139
            $manifestCacheFactory
140
        ));
141
        $this->setThemeResourceLoader($themeResourceLoader);
142
    }
143
144
    /**
145
     * Get the environment type
146
     *
147
     * @return string
148
     *
149
     * @deprecated 5.0 use Director::get_environment_type() instead. Since 5.0 it should return only if kernel overrides. No checking SESSION or Environment.
150
     */
151
    public function getEnvironment()
152
    {
153
        // Check set
154
        if ($this->enviroment) {
155
            return $this->enviroment;
156
        }
157
158
        // Check saved session
159
        $env = $this->sessionEnvironment();
0 ignored issues
show
Deprecated Code introduced by
The function SilverStripe\Core\CoreKernel::sessionEnvironment() has been deprecated: 5.0 Use Director::get_session_environment_type() instead ( Ignorable by Annotation )

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

159
        $env = /** @scrutinizer ignore-deprecated */ $this->sessionEnvironment();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
160
        if ($env) {
161
            return $env;
162
        }
163
164
        // Check getenv
165
        if ($env = Environment::getEnv('SS_ENVIRONMENT_TYPE')) {
166
            return $env;
167
        }
168
169
        return self::LIVE;
170
    }
171
172
    /**
173
     * Check or update any temporary environment specified in the session.
174
     *
175
     * @return null|string
176
     *
177
     * @deprecated 5.0 Use Director::get_session_environment_type() instead
178
     */
179
    protected function sessionEnvironment()
180
    {
181
        if (!$this->booted) {
182
            // session is not initialyzed yet, neither is manifest
183
            return null;
184
        }
185
186
        return Director::get_session_environment_type();
187
    }
188
189
    public function boot($flush = false)
190
    {
191
        $this->flush = $flush;
192
193
        $this->bootPHP();
194
        $this->bootManifests($flush);
195
        $this->bootErrorHandling();
196
        $this->bootDatabaseEnvVars();
197
        $this->bootConfigs();
198
        $this->bootDatabaseGlobals();
199
        $this->validateDatabase();
200
201
        $this->booted = true;
202
    }
203
204
    /**
205
     * Include all _config.php files
206
     */
207
    protected function bootConfigs()
208
    {
209
        global $project;
210
        $projectBefore = $project;
211
        $config = ModuleManifest::config();
212
        // After loading all other app manifests, include _config.php files
213
        $this->getModuleLoader()->getManifest()->activateConfig();
214
        if ($project && $project !== $projectBefore) {
215
            Deprecation::notice('5.0', '$project global is deprecated');
216
            $config->set('project', $project);
217
        }
218
    }
219
220
    /**
221
     * Load default database configuration from the $database and $databaseConfig globals
222
     */
223
    protected function bootDatabaseGlobals()
224
    {
225
        // Now that configs have been loaded, we can check global for database config
226
        global $databaseConfig;
227
        global $database;
228
229
        // Case 1: $databaseConfig global exists. Merge $database in as needed
230
        if (!empty($databaseConfig)) {
231
            if (!empty($database)) {
232
                $databaseConfig['database'] =  $this->getDatabasePrefix() . $database . $this->getDatabaseSuffix();
233
            }
234
235
            // Only set it if its valid, otherwise ignore $databaseConfig entirely
236
            if (!empty($databaseConfig['database'])) {
237
                DB::setConfig($databaseConfig);
238
239
                return;
240
            }
241
        }
242
243
        // Case 2: $database merged into existing config
244
        if (!empty($database)) {
245
            $existing = DB::getConfig();
246
            $existing['database'] = $this->getDatabasePrefix() . $database . $this->getDatabaseSuffix();
247
248
            DB::setConfig($existing);
249
        }
250
    }
251
252
    /**
253
     * Load default database configuration from environment variable
254
     */
255
    protected function bootDatabaseEnvVars()
256
    {
257
        // Set default database config
258
        $databaseConfig = $this->getDatabaseConfig();
259
        $databaseConfig['database'] = $this->getDatabaseName();
260
        DB::setConfig($databaseConfig);
261
    }
262
263
    /**
264
     * Check that the database configuration is valid, throwing an HTTPResponse_Exception if it's not
265
     *
266
     * @throws HTTPResponse_Exception
267
     */
268
    protected function validateDatabase()
269
    {
270
        $databaseConfig = DB::getConfig();
271
        // Gracefully fail if no DB is configured
272
        if (empty($databaseConfig['database'])) {
273
            $msg = 'Silverstripe Framework requires a "database" key in DB::getConfig(). ' .
274
                'Did you forget to set SS_DATABASE_NAME or SS_DATABASE_CHOOSE_NAME in your environment?';
275
            $this->detectLegacyEnvironment();
276
            $this->redirectToInstaller($msg);
277
        }
278
    }
279
280
    /**
281
     * Check if there's a legacy _ss_environment.php file
282
     *
283
     * @throws HTTPResponse_Exception
284
     */
285
    protected function detectLegacyEnvironment()
286
    {
287
        // Is there an _ss_environment.php file?
288
        if (!file_exists($this->basePath . '/_ss_environment.php') &&
289
            !file_exists(dirname($this->basePath) . '/_ss_environment.php')
290
        ) {
291
            return;
292
        }
293
294
        // Build error response
295
        $dv = new DebugView();
296
        $body = implode([
297
            $dv->renderHeader(),
298
            $dv->renderInfo(
299
                "Configuration Error",
300
                Director::absoluteBaseURL()
301
            ),
302
            $dv->renderParagraph(
303
                'You need to replace your _ss_environment.php file with a .env file, or with environment variables.<br><br>'
304
                . 'See the <a href="https://docs.silverstripe.org/en/4/getting_started/environment_management/">'
305
                . 'Environment Management</a> docs for more information.'
306
            ),
307
            $dv->renderFooter()
308
        ]);
309
310
        // Raise error
311
        $response = new HTTPResponse($body, 500);
312
        throw new HTTPResponse_Exception($response);
313
    }
314
315
    /**
316
     * If missing configuration, redirect to install.php if it exists.
317
     * Otherwise show a server error to the user.
318
     *
319
     * @param string $msg Optional message to show to the user on an installed project (install.php missing).
320
     */
321
    protected function redirectToInstaller($msg = '')
322
    {
323
        // Error if installer not available
324
        if (!file_exists(Director::publicFolder() . '/install.php')) {
325
            throw new HTTPResponse_Exception(
326
                $msg,
327
                500
328
            );
329
        }
330
331
        // Redirect to installer
332
        $response = new HTTPResponse();
333
        $response->redirect(Director::absoluteURL('install.php'));
334
        throw new HTTPResponse_Exception($response);
335
    }
336
337
    /**
338
     * Load database config from environment
339
     *
340
     * @return array
341
     */
342
    protected function getDatabaseConfig()
343
    {
344
        /** @skipUpgrade */
345
        $databaseConfig = [
346
            "type" => Environment::getEnv('SS_DATABASE_CLASS') ?: 'MySQLDatabase',
347
            "server" => Environment::getEnv('SS_DATABASE_SERVER') ?: 'localhost',
348
            "username" => Environment::getEnv('SS_DATABASE_USERNAME') ?: null,
349
            "password" => Environment::getEnv('SS_DATABASE_PASSWORD') ?: null,
350
        ];
351
352
        // Set the port if called for
353
        $dbPort = Environment::getEnv('SS_DATABASE_PORT');
354
        if ($dbPort) {
355
            $databaseConfig['port'] = $dbPort;
356
        }
357
358
        // Set the timezone if called for
359
        $dbTZ = Environment::getEnv('SS_DATABASE_TIMEZONE');
360
        if ($dbTZ) {
361
            $databaseConfig['timezone'] = $dbTZ;
362
        }
363
364
        // For schema enabled drivers:
365
        $dbSchema = Environment::getEnv('SS_DATABASE_SCHEMA');
366
        if ($dbSchema) {
367
            $databaseConfig["schema"] = $dbSchema;
368
        }
369
370
        // For SQlite3 memory databases (mainly for testing purposes)
371
        $dbMemory = Environment::getEnv('SS_DATABASE_MEMORY');
372
        if ($dbMemory) {
373
            $databaseConfig["memory"] = $dbMemory;
374
        }
375
376
        // Allow database adapters to handle their own configuration
377
        DatabaseAdapterRegistry::autoconfigure($databaseConfig);
378
        return $databaseConfig;
379
    }
380
381
    /**
382
     * @return string
383
     */
384
    protected function getDatabasePrefix()
385
    {
386
        return Environment::getEnv('SS_DATABASE_PREFIX') ?: '';
387
    }
388
389
    /**
390
     * @return string
391
     */
392
    protected function getDatabaseSuffix()
393
    {
394
        return Environment::getEnv('SS_DATABASE_SUFFIX') ?: '';
395
    }
396
397
    /**
398
     * Get name of database
399
     *
400
     * @return string
401
     */
402
    protected function getDatabaseName()
403
    {
404
        // Check globals
405
        global $database;
406
407
        if (!empty($database)) {
408
            return $this->getDatabasePrefix() . $database . $this->getDatabaseSuffix();
409
        }
410
411
        global $databaseConfig;
412
413
        if (!empty($databaseConfig['database'])) {
414
            return $databaseConfig['database']; // Note: Already includes prefix
415
        }
416
417
        // Check environment
418
        $database = Environment::getEnv('SS_DATABASE_NAME');
419
420
        if ($database) {
421
            return $this->getDatabasePrefix() . $database . $this->getDatabaseSuffix();
422
        }
423
424
        // Auto-detect name
425
        $chooseName = Environment::getEnv('SS_DATABASE_CHOOSE_NAME');
426
427
        if ($chooseName) {
428
            // Find directory to build name from
429
            $loopCount = (int)$chooseName;
430
            $databaseDir = $this->basePath;
431
            for ($i = 0; $i < $loopCount-1; $i++) {
432
                $databaseDir = dirname($databaseDir);
433
            }
434
435
            // Build name
436
            $database = str_replace('.', '', basename($databaseDir));
437
            $prefix = $this->getDatabasePrefix();
438
439
            if ($prefix) {
440
                $prefix = 'SS_';
441
            } else {
442
                // If no prefix, hard-code prefix into database global
443
                $prefix = '';
444
                $database = 'SS_' . $database;
445
            }
446
447
            return $prefix . $database;
448
        }
449
450
        // no DB name (may be optional for some connectors)
451
        return null;
452
    }
453
454
    /**
455
     * Initialise PHP with default variables
456
     */
457
    protected function bootPHP()
458
    {
459
        if ($this->getEnvironment() === self::LIVE) {
0 ignored issues
show
Deprecated Code introduced by
The function SilverStripe\Core\CoreKernel::getEnvironment() has been deprecated: 5.0 use Director::get_environment_type() instead. Since 5.0 it should return only if kernel overrides. No checking SESSION or Environment. ( Ignorable by Annotation )

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

459
        if (/** @scrutinizer ignore-deprecated */ $this->getEnvironment() === self::LIVE) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
460
            // limited to fatal errors and warnings in live mode
461
            error_reporting(E_ALL & ~(E_DEPRECATED | E_STRICT | E_NOTICE));
462
        } else {
463
            // Report all errors in dev / test mode
464
            error_reporting(E_ALL | E_STRICT);
465
        }
466
467
        /**
468
         * Ensure we have enough memory
469
         */
470
        Environment::increaseMemoryLimitTo('64M');
471
472
        // Ensure we don't run into xdebug's fairly conservative infinite recursion protection limit
473
        if (function_exists('xdebug_enable')) {
474
            $current = ini_get('xdebug.max_nesting_level');
475
            if ((int)$current < 200) {
476
                ini_set('xdebug.max_nesting_level', 200);
477
            }
478
        }
479
480
        /**
481
         * Set default encoding
482
         */
483
        mb_http_output('UTF-8');
484
        mb_internal_encoding('UTF-8');
485
        mb_regex_encoding('UTF-8');
486
487
        /**
488
         * Enable better garbage collection
489
         */
490
        gc_enable();
491
    }
492
493
    /**
494
     * @return ManifestCacheFactory
495
     */
496
    protected function buildManifestCacheFactory()
497
    {
498
        return new ManifestCacheFactory([
499
            'namespace' => 'manifestcache',
500
            'directory' => TEMP_PATH,
501
        ]);
502
    }
503
504
    /**
505
     * @return bool
506
     */
507
    protected function getIncludeTests()
508
    {
509
        return false;
510
    }
511
512
    /**
513
     * When manifests are discovering files, tests files in modules using the following CI library type will be ignored.
514
     *
515
     * The purpose of this method is to avoid loading PHPUnit test files with incompatible definitions.
516
     *
517
     * @return string[] List of CI types to ignore as defined by `Module`.
518
     */
519
    protected function getIgnoredCIConfigs(): array
520
    {
521
        return [];
522
    }
523
524
    /**
525
     * Boot all manifests
526
     *
527
     * @param bool $flush
528
     */
529
    protected function bootManifests($flush)
530
    {
531
        // Setup autoloader
532
        $this->getClassLoader()->init(
533
            $this->getIncludeTests(),
534
            $flush,
535
            $this->getIgnoredCIConfigs()
536
        );
537
538
        // Find modules
539
        $this->getModuleLoader()->init(
540
            $this->getIncludeTests(),
541
            $flush,
542
            $this->getIgnoredCIConfigs()
543
        );
544
545
        // Flush config
546
        if ($flush) {
547
            $config = $this->getConfigLoader()->getManifest();
548
            if ($config instanceof CachedConfigCollection) {
549
                $config->setFlush(true);
550
            }
551
        }
552
        // tell modules to sort, now that config is available
553
        $this->getModuleLoader()->getManifest()->sort();
554
555
        // Find default templates
556
        $defaultSet = $this->getThemeResourceLoader()->getSet('$default');
557
        if ($defaultSet instanceof ThemeManifest) {
558
            $defaultSet->setProject(
559
                ModuleManifest::config()->get('project')
560
            );
561
            $defaultSet->init(
562
                $this->getIncludeTests(),
563
                $flush,
564
                $this->getIgnoredCIConfigs()
565
            );
566
        }
567
    }
568
569
    /**
570
     * Turn on error handling
571
     */
572
    protected function bootErrorHandling()
573
    {
574
        // Register error handler
575
        $errorHandler = Injector::inst()->get(ErrorHandler::class);
576
        $errorHandler->start();
577
578
        // Register error log file
579
        $errorLog = Environment::getEnv('SS_ERROR_LOG');
580
        if ($errorLog) {
581
            $logger = Injector::inst()->get(LoggerInterface::class);
582
            if ($logger instanceof Logger) {
583
                $logger->pushHandler(new StreamHandler($this->basePath . '/' . $errorLog, Logger::WARNING));
584
            } else {
585
                user_error("SS_ERROR_LOG setting only works with Monolog, you are using another logger", E_USER_WARNING);
586
            }
587
        }
588
    }
589
590
    public function shutdown()
591
    {
592
    }
593
594
    public function nest()
595
    {
596
        // Clone this kernel, nesting config / injector manifest containers
597
        $kernel = clone $this;
598
        $kernel->setConfigLoader($this->configLoader->nest());
599
        $kernel->setInjectorLoader($this->injectorLoader->nest());
600
        $kernel->nestedFrom = $this;
601
        return $kernel;
602
    }
603
604
    public function activate()
605
    {
606
        $this->configLoader->activate();
607
        $this->injectorLoader->activate();
608
609
        // Self register
610
        $this->getInjectorLoader()
611
            ->getManifest()
612
            ->registerService($this, Kernel::class);
613
        return $this;
614
    }
615
616
    public function getNestedFrom()
617
    {
618
        return $this->nestedFrom;
619
    }
620
621
    public function getContainer()
622
    {
623
        return $this->getInjectorLoader()->getManifest();
624
    }
625
626
    public function setInjectorLoader(InjectorLoader $injectorLoader)
627
    {
628
        $this->injectorLoader = $injectorLoader;
629
        $injectorLoader
630
            ->getManifest()
631
            ->registerService($this, Kernel::class);
632
        return $this;
633
    }
634
635
    public function getInjectorLoader()
636
    {
637
        return $this->injectorLoader;
638
    }
639
640
    public function getClassLoader()
641
    {
642
        return $this->classLoader;
643
    }
644
645
    public function setClassLoader(ClassLoader $classLoader)
646
    {
647
        $this->classLoader = $classLoader;
648
        return $this;
649
    }
650
651
    public function getModuleLoader()
652
    {
653
        return $this->moduleLoader;
654
    }
655
656
    public function setModuleLoader(ModuleLoader $moduleLoader)
657
    {
658
        $this->moduleLoader = $moduleLoader;
659
        return $this;
660
    }
661
662
    public function setEnvironment($environment)
663
    {
664
        if (!in_array($environment, [self::DEV, self::TEST, self::LIVE, null])) {
665
            throw new InvalidArgumentException(
666
                "Director::set_environment_type passed '$environment'.  It should be passed dev, test, or live"
667
            );
668
        }
669
        $this->enviroment = $environment;
670
        return $this;
671
    }
672
673
    public function getConfigLoader()
674
    {
675
        return $this->configLoader;
676
    }
677
678
    public function setConfigLoader($configLoader)
679
    {
680
        $this->configLoader = $configLoader;
681
        return $this;
682
    }
683
684
    public function getThemeResourceLoader()
685
    {
686
        return $this->themeResourceLoader;
687
    }
688
689
    public function setThemeResourceLoader($themeResourceLoader)
690
    {
691
        $this->themeResourceLoader = $themeResourceLoader;
692
        return $this;
693
    }
694
695
    /**
696
     * Returns whether the Kernel has been flushed on boot
697
     *
698
     * @return bool|null null if the kernel hasn't been booted yet
699
     */
700
    public function isFlushed()
701
    {
702
        return $this->flush;
703
    }
704
}
705