<?php
namespace SilverStripe\Core;
use InvalidArgumentException;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use Psr\Log\LoggerInterface;
use SilverStripe\Config\Collections\CachedConfigCollection;
use SilverStripe\Control\Director;
use SilverStripe\Control\HTTPResponse;
use SilverStripe\Control\HTTPResponse_Exception;
use SilverStripe\Control\Middleware\AllowedHostsMiddleware;
use SilverStripe\Core\Cache\ManifestCacheFactory;
use SilverStripe\Core\Config\ConfigLoader;
use SilverStripe\Core\Config\CoreConfigFactory;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Core\Injector\InjectorLoader;
use SilverStripe\Core\Injector\SilverStripeServiceConfigurationLocator;
use SilverStripe\Core\Manifest\ClassLoader;
use SilverStripe\Core\Manifest\ClassManifest;
use SilverStripe\Core\Manifest\ModuleLoader;
use SilverStripe\Core\Manifest\ModuleManifest;
use SilverStripe\Dev\DebugView;
use SilverStripe\Logging\ErrorHandler;
use SilverStripe\View\PublicThemes;
use SilverStripe\View\SSViewer;
use SilverStripe\View\ThemeManifest;
use SilverStripe\View\ThemeResourceLoader;
use Exception;
* Simple Kernel container
*/
abstract class BaseKernel implements Kernel
{
* @var Kernel
*/
protected $nestedFrom = null;
* @var Injector
*/
protected $container = null;
* @var string
*/
protected $enviroment = null;
* @var ClassLoader
*/
protected $classLoader = null;
* @var ModuleLoader
*/
protected $moduleLoader = null;
* @var ConfigLoader
*/
protected $configLoader = null;
* @var InjectorLoader
*/
protected $injectorLoader = null;
* @var ThemeResourceLoader
*/
protected $themeResourceLoader = null;
protected $basePath = null;
* Indicates whether the Kernel has been booted already
*
* @var bool
*/
private $booted = false;
* Create a new kernel for this application
*
* @param string $basePath Path to base dir for this application
*/
public function __construct($basePath)
{
$this->basePath = $basePath;
$injectorLoader = InjectorLoader::inst();
$injector = new Injector(['locator' => SilverStripeServiceConfigurationLocator::class]);
$injectorLoader->pushManifest($injector);
$this->setInjectorLoader($injectorLoader);
$manifestCacheFactory = $this->buildManifestCacheFactory();
$classLoader = ClassLoader::inst();
$classLoader->pushManifest(new ClassManifest($basePath, $manifestCacheFactory));
$this->setClassLoader($classLoader);
$moduleLoader = ModuleLoader::inst();
$moduleManifest = new ModuleManifest($basePath, $manifestCacheFactory);
$moduleLoader->pushManifest($moduleManifest);
$this->setModuleLoader($moduleLoader);
$configLoader = ConfigLoader::inst();
if (!$configLoader->hasManifest()) {
$configFactory = new CoreConfigFactory($manifestCacheFactory);
$configManifest = $configFactory->createRoot();
$configLoader->pushManifest($configManifest);
}
$this->setConfigLoader($configLoader);
$themeResourceLoader = ThemeResourceLoader::inst();
$themeResourceLoader->addSet(SSViewer::PUBLIC_THEME, new PublicThemes());
$themeResourceLoader->addSet(SSViewer::DEFAULT_THEME, new ThemeManifest(
$basePath,
null, // project is defined in config, and this argument is deprecated
$manifestCacheFactory
));
$this->setThemeResourceLoader($themeResourceLoader);
}
* Initialise PHP with default variables
*/
protected function bootPHP()
{
if ($this->getEnvironment() === BaseKernel::LIVE) {
error_reporting(E_ALL & ~(E_DEPRECATED | E_NOTICE));
} else {
error_reporting(E_ALL);
}
* Ensure we have enough memory
*/
Environment::increaseMemoryLimitTo('64M');
if (function_exists('xdebug_enable')) {
$current = ini_get('xdebug.max_nesting_level');
if ((int)$current < 200) {
ini_set('xdebug.max_nesting_level', 200);
}
}
* Set default encoding
*/
mb_http_output('UTF-8');
mb_internal_encoding('UTF-8');
mb_regex_encoding('UTF-8');
* Enable better garbage collection
*/
gc_enable();
}
* Boot all manifests
*
* @param bool $flush
*/
protected function bootManifests($flush)
{
$this->getClassLoader()->init(
$this->getIncludeTests(),
$flush
);
$this->getModuleLoader()->init(
$this->getIncludeTests(),
$flush
);
if ($flush) {
$config = $this->getConfigLoader()->getManifest();
if ($config instanceof CachedConfigCollection) {
$config->setFlush(true);
}
}
$this->getModuleLoader()->getManifest()->sort();
$defaultSet = $this->getThemeResourceLoader()->getSet('$default');
if ($defaultSet instanceof ThemeManifest) {
$defaultSet->setProject(
ModuleManifest::config()->get('project')
);
$defaultSet->init(
$this->getIncludeTests(),
$flush
);
}
}
* Include all _config.php files
*/
protected function bootConfigs()
{
$this->getModuleLoader()->getManifest()->activateConfig();
$this->validateConfiguration();
}
* Turn on error handling
* @throws Exception
*/
protected function bootErrorHandling()
{
$errorHandler = Injector::inst()->get(ErrorHandler::class);
$errorHandler->start();
$errorLog = Environment::getEnv('SS_ERROR_LOG');
if ($errorLog) {
$logger = Injector::inst()->get(LoggerInterface::class);
if ($logger instanceof Logger) {
$logger->pushHandler(new StreamHandler($this->basePath . '/' . $errorLog, Logger::WARNING));
} else {
user_error("SS_ERROR_LOG setting only works with Monolog, you are using another logger", E_USER_WARNING);
}
}
}
* Get the environment type
*
* @return string
*/
public function getEnvironment()
{
if ($this->enviroment) {
return $this->enviroment;
}
if ($env = Environment::getEnv('SS_ENVIRONMENT_TYPE')) {
return $env;
}
return BaseKernel::LIVE;
}
abstract public function boot($flush = false);
abstract public function isFlushed(): ?bool;
* Check if there's a legacy _ss_environment.php file
*
* @throws HTTPResponse_Exception
*/
protected function detectLegacyEnvironment()
{
if (!file_exists($this->basePath . '/_ss_environment.php') &&
!file_exists(dirname($this->basePath ?? '') . '/_ss_environment.php')
) {
return;
}
$dv = new DebugView();
$body = implode([
$dv->renderHeader(),
$dv->renderInfo(
"Configuration Error",
Director::absoluteBaseURL()
),
$dv->renderParagraph(
'You need to replace your _ss_environment.php file with a .env file, or with environment variables.<br><br>'
. 'See the <a href="https://docs.silverstripe.org/en/4/getting_started/environment_management/">'
. 'Environment Management</a> docs for more information.'
),
$dv->renderFooter()
]);
$response = new HTTPResponse($body, 500);
throw new HTTPResponse_Exception($response);
}
* @return ManifestCacheFactory
*/
protected function buildManifestCacheFactory()
{
return new ManifestCacheFactory([
'namespace' => 'manifestcache',
'directory' => TEMP_PATH,
]);
}
* @return bool
*/
protected function getIncludeTests()
{
return false;
}
* @param bool $bool
*/
protected function setBooted(bool $bool): void
{
$this->booted = $bool;
}
* Check whether the kernel has booted or not
*/
public function getBooted(): bool
{
return $this->booted;
}
public function shutdown()
{
}
public function nest()
{
$kernel = clone $this;
$kernel->setConfigLoader($this->configLoader->nest());
$kernel->setInjectorLoader($this->injectorLoader->nest());
$kernel->nestedFrom = $this;
return $kernel;
}
public function activate()
{
$this->configLoader->activate();
$this->injectorLoader->activate();
$this->getInjectorLoader()
->getManifest()
->registerService($this, Kernel::class);
return $this;
}
public function getNestedFrom()
{
return $this->nestedFrom;
}
public function getContainer()
{
return $this->getInjectorLoader()->getManifest();
}
public function setInjectorLoader(InjectorLoader $injectorLoader)
{
$this->injectorLoader = $injectorLoader;
$injectorLoader
->getManifest()
->registerService($this, Kernel::class);
return $this;
}
public function getInjectorLoader()
{
return $this->injectorLoader;
}
public function getClassLoader()
{
return $this->classLoader;
}
public function setClassLoader(ClassLoader $classLoader)
{
$this->classLoader = $classLoader;
return $this;
}
public function getModuleLoader()
{
return $this->moduleLoader;
}
public function setModuleLoader(ModuleLoader $moduleLoader)
{
$this->moduleLoader = $moduleLoader;
return $this;
}
public function setEnvironment($environment)
{
if (!in_array($environment, [BaseKernel::DEV, BaseKernel::TEST, BaseKernel::LIVE, null])) {
throw new InvalidArgumentException(
"Director::set_environment_type passed '$environment'. It should be passed dev, test, or live"
);
}
$this->enviroment = $environment;
return $this;
}
public function getConfigLoader()
{
return $this->configLoader;
}
public function setConfigLoader($configLoader)
{
$this->configLoader = $configLoader;
return $this;
}
public function getThemeResourceLoader()
{
return $this->themeResourceLoader;
}
public function setThemeResourceLoader($themeResourceLoader)
{
$this->themeResourceLoader = $themeResourceLoader;
return $this;
}
* Validate configuration of the application is in a good state, ready for use.
*
* This method can be used to warn developers of any misconfiguration, or configuration
* which is missing but should be set according to best practice.
*
* In some cases, this could be used to halt execution if configuration critical to operation
* has not been set.
*/
protected function validateConfiguration(): void
{
$allowedHostsMiddleware = Injector::inst()->get(AllowedHostsMiddleware::class, true);
if (empty($allowedHostsMiddleware->getAllowedHosts())) {
Injector::inst()->get(LoggerInterface::class)->warning(
'Allowed hosts has not been set. Your application could be vulnerable to host header injection attacks.'
. ' Either set the SS_ALLOWED_HOSTS environment variable or the AllowedHosts property on ' . AllowedHostsMiddleware::class
);
}
}
}