[ 'type' => 'ReadOnlyStream', 'prefixes' => [ '' => ['system'], ] ], 'user' => [ 'type' => 'ReadOnlyStream', 'prefixes' => [ '' => ['user'], ] ], 'blueprints' => [ 'type' => 'ReadOnlyStream', 'prefixes' => [ '' => ['user://blueprints', 'system/blueprints'], ] ], 'config' => [ 'type' => 'ReadOnlyStream', 'prefixes' => [ '' => ['user://config', 'system/config'], ] ], 'plugins' => [ 'type' => 'ReadOnlyStream', 'prefixes' => [ '' => ['user://plugins'], ] ], 'plugin' => [ 'type' => 'ReadOnlyStream', 'prefixes' => [ '' => ['user://plugins'], ] ], 'themes' => [ 'type' => 'ReadOnlyStream', 'prefixes' => [ '' => ['user://themes'], ] ], 'languages' => [ 'type' => 'ReadOnlyStream', 'prefixes' => [ '' => ['user://languages', 'system/languages'], ] ], 'cache' => [ 'type' => 'Stream', 'prefixes' => [ '' => ['cache'], 'images' => ['images'] ] ], 'log' => [ 'type' => 'Stream', 'prefixes' => [ '' => ['logs'] ] ], 'backup' => [ 'type' => 'Stream', 'prefixes' => [ '' => ['backup'] ] ] ]; protected $setup = []; protected $blueprintFiles = []; protected $configFiles = []; protected $languageFiles = []; protected $checksum; protected $timestamp; protected $configLookup; protected $blueprintLookup; protected $pluginLookup; protected $languagesLookup; protected $finder; protected $environment; protected $messages = []; protected $languages; public function __construct(array $setup = array(), Grav $grav = null, $environment = null) { $this->grav = $grav ?: Grav::instance(); $this->finder = new ConfigFinder; $this->environment = $environment ?: 'localhost'; $this->messages[] = 'Environment Name: ' . $this->environment; // Make sure that if (!isset($setup['streams']['schemes'])) { $setup['streams']['schemes'] = []; } $setup['streams']['schemes'] += $this->streams; $setup = $this->autoDetectEnvironmentConfig($setup); $this->setup = $setup; parent::__construct($setup); $this->check(); } public function key() { return $this->checksum(); } public function reload() { $this->items = $this->setup; $this->check(); $this->init(); $this->debug(); return $this; } protected function check() { $streams = isset($this->items['streams']['schemes']) ? $this->items['streams']['schemes'] : null; if (!is_array($streams)) { throw new \InvalidArgumentException('Configuration is missing streams.schemes!'); } $diff = array_keys(array_diff_key($this->streams, $streams)); if ($diff) { throw new \InvalidArgumentException( sprintf('Configuration is missing keys %s from streams.schemes!', implode(', ', $diff)) ); } } public function debug() { foreach ($this->messages as $message) { $this->grav['debugger']->addMessage($message); } $this->messages = []; } public function init() { /** @var UniformResourceLocator $locator */ $locator = $this->grav['locator']; $this->configLookup = $locator->findResources('config://'); $this->blueprintLookup = $locator->findResources('blueprints://config'); $this->pluginLookup = $locator->findResources('plugins://'); $this->loadCompiledBlueprints($this->blueprintLookup, $this->pluginLookup, 'master'); $this->loadCompiledConfig($this->configLookup, $this->pluginLookup, 'master'); // process languages if supported if ($this->get('system.languages.translations', true)) { $this->languagesLookup = $locator->findResources('languages://'); $this->loadCompiledLanguages($this->languagesLookup, $this->pluginLookup, 'master'); } $this->initializeLocator($locator); } public function checksum() { if (empty($this->checksum)) { $checkBlueprints = $this->get('system.cache.check.blueprints', false); $checkLanguages = $this->get('system.cache.check.languages', false); $checkConfig = $this->get('system.cache.check.config', true); $checkSystem = $this->get('system.cache.check.system', true); if (!$checkBlueprints && !$checkLanguages && !$checkConfig && !$checkSystem) { $this->messages[] = 'Skip configuration timestamp check.'; return false; } // Generate checksum according to the configuration settings. if (!$checkConfig) { // Just check changes in system.yaml files and ignore all the other files. $cc = $checkSystem ? $this->finder->locateConfigFile($this->configLookup, 'system') : []; } else { // Check changes in all configuration files. $cc = $this->finder->locateConfigFiles($this->configLookup, $this->pluginLookup); } if ($checkBlueprints) { $cb = $this->finder->locateBlueprintFiles($this->blueprintLookup, $this->pluginLookup); } else { $cb = []; } if ($checkLanguages) { $cl = $this->finder->locateLanguageFiles($this->languagesLookup, $this->pluginLookup); } else { $cl = []; } $this->checksum = md5(json_encode([$cc, $cb, $cl])); } return $this->checksum; } protected function autoDetectEnvironmentConfig($items) { $environment = $this->environment; $env_stream = 'user://'.$environment.'/config'; if (file_exists(USER_DIR.$environment.'/config')) { array_unshift($items['streams']['schemes']['config']['prefixes'][''], $env_stream); } return $items; } protected function loadCompiledBlueprints($blueprints, $plugins, $filename = null) { $checksum = md5(json_encode($blueprints)); $filename = $filename ? CACHE_DIR . 'compiled/blueprints/' . $filename . '-' . $this->environment . '.php' : CACHE_DIR . 'compiled/blueprints/' . $checksum . '-' . $this->environment . '.php'; $file = PhpFile::instance($filename); $cache = $file->exists() ? $file->content() : null; $blueprintFiles = $this->finder->locateBlueprintFiles($blueprints, $plugins); $checksum .= ':'.md5(json_encode($blueprintFiles)); $class = get_class($this); // Load real file if cache isn't up to date (or is invalid). if ( !is_array($cache) || !isset($cache['checksum']) || !isset($cache['@class']) || $cache['checksum'] != $checksum || $cache['@class'] != $class ) { // Attempt to lock the file for writing. $file->lock(false); // Load blueprints. $this->blueprints = new Blueprints; foreach ($blueprintFiles as $files) { $this->loadBlueprintFiles($files); } $cache = [ '@class' => $class, 'checksum' => $checksum, 'files' => $blueprintFiles, 'data' => $this->blueprints->toArray() ]; // If compiled file wasn't already locked by another process, save it. if ($file->locked() !== false) { $this->messages[] = 'Saving compiled blueprints.'; $file->save($cache); $file->unlock(); } } else { $this->blueprints = new Blueprints($cache['data']); } } protected function loadCompiledConfig($configs, $plugins, $filename = null) { $filename = $filename ? CACHE_DIR . 'compiled/config/' . $filename . '-' . $this->environment . '.php' : CACHE_DIR . 'compiled/config/' . $checksum . '-' . $this->environment . '.php'; $file = PhpFile::instance($filename); $cache = $file->exists() ? $file->content() : null; $class = get_class($this); $checksum = $this->checksum(); if ( !is_array($cache) || !isset($cache['checksum']) || !isset($cache['@class']) || $cache['@class'] != $class ) { $this->messages[] = 'No cached configuration, compiling new configuration..'; } else if ($cache['checksum'] !== $checksum) { $this->messages[] = 'Configuration checksum mismatch, reloading configuration..'; } else { $this->messages[] = 'Configuration checksum matches, using cached version.'; $this->items = $cache['data']; return; } $configFiles = $this->finder->locateConfigFiles($configs, $plugins); // Attempt to lock the file for writing. $file->lock(false); // Load configuration. foreach ($configFiles as $files) { $this->loadConfigFiles($files); } $cache = [ '@class' => $class, 'timestamp' => time(), 'checksum' => $checksum, 'data' => $this->toArray() ]; // If compiled file wasn't already locked by another process, save it. if ($file->locked() !== false) { $this->messages[] = 'Saving compiled configuration.'; $file->save($cache); $file->unlock(); } $this->items = $cache['data']; } /** * @param $languages * @param $plugins * @param null $filename */ protected function loadCompiledLanguages($languages, $plugins, $filename = null) { $checksum = md5(json_encode($languages)); $filename = $filename ? CACHE_DIR . 'compiled/languages/' . $filename . '-' . $this->environment . '.php' : CACHE_DIR . 'compiled/languages/' . $checksum . '-' . $this->environment . '.php'; $file = PhpFile::instance($filename); $cache = $file->exists() ? $file->content() : null; $languageFiles = $this->finder->locateLanguageFiles($languages, $plugins); $checksum .= ':' . md5(json_encode($languageFiles)); $class = get_class($this); // Load real file if cache isn't up to date (or is invalid). if ( !is_array($cache) || !isset($cache['checksum']) || !isset($cache['@class']) || $cache['checksum'] != $checksum || $cache['@class'] != $class ) { // Attempt to lock the file for writing. $file->lock(false); // Load languages. $this->languages = new Languages; $pluginPaths = str_ireplace(GRAV_ROOT . '/', '', array_reverse($plugins)); foreach ($pluginPaths as $path) { if (isset($languageFiles[$path])) { foreach ((array) $languageFiles[$path] as $plugin => $item) { $lang_file = CompiledYamlFile::instance($item['file']); $content = $lang_file->content(); $this->languages->mergeRecursive($content); } unset($languageFiles[$path]); } } foreach ($languageFiles as $location) { foreach ($location as $lang => $item) { $lang_file = CompiledYamlFile::instance($item['file']); $content = $lang_file->content(); $this->languages->join($lang, $content, '/'); } } $cache = [ '@class' => $class, 'checksum' => $checksum, 'files' => $languageFiles, 'data' => $this->languages->toArray() ]; // If compiled file wasn't already locked by another process, save it. if ($file->locked() !== false) { $this->messages[] = 'Saving compiled languages.'; $file->save($cache); $file->unlock(); } } else { $this->languages = new Languages($cache['data']); } } /** * Load blueprints. * * @param array $files */ public function loadBlueprintFiles(array $files) { foreach ($files as $name => $item) { $file = CompiledYamlFile::instance($item['file']); $this->blueprints->embed($name, $file->content(), '/'); } } /** * Load configuration. * * @param array $files */ public function loadConfigFiles(array $files) { foreach ($files as $name => $item) { $file = CompiledYamlFile::instance($item['file']); $this->join($name, $file->content(), '/'); } } /** * Initialize resource locator by using the configuration. * * @param UniformResourceLocator $locator */ public function initializeLocator(UniformResourceLocator $locator) { $locator->reset(); $schemes = (array) $this->get('streams.schemes', []); foreach ($schemes as $scheme => $config) { if (isset($config['paths'])) { $locator->addPath($scheme, '', $config['paths']); } if (isset($config['prefixes'])) { foreach ($config['prefixes'] as $prefix => $paths) { $locator->addPath($scheme, $prefix, $paths); } } } } /** * Get available streams and their types from the configuration. * * @return array */ public function getStreams() { $schemes = []; foreach ((array) $this->get('streams.schemes') as $scheme => $config) { $type = !empty($config['type']) ? $config['type'] : 'ReadOnlyStream'; if ($type[0] != '\\') { $type = '\\RocketTheme\\Toolbox\\StreamWrapper\\' . $type; } $schemes[$scheme] = $type; } return $schemes; } public function getLanguages() { return $this->languages; } }