PageRenderTime 68ms CodeModel.GetById 7ms RepoModel.GetById 0ms app.codeStats 0ms

/system/src/Grav/Common/Config/Config.php

https://gitlab.com/x33n/grav
PHP | 383 lines | 298 code | 45 blank | 40 comment | 39 complexity | 9dc1920d0393557ba8f7098ac2f4d356 MD5 | raw file
  1. <?php
  2. namespace Grav\Common\Config;
  3. use Grav\Common\File\CompiledYamlFile;
  4. use Grav\Common\Grav;
  5. use Grav\Common\Data\Data;
  6. use RocketTheme\Toolbox\Blueprints\Blueprints;
  7. use RocketTheme\Toolbox\File\PhpFile;
  8. use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
  9. /**
  10. * The Config class contains configuration information.
  11. *
  12. * @author RocketTheme
  13. * @license MIT
  14. */
  15. class Config extends Data
  16. {
  17. protected $grav;
  18. protected $streams = [
  19. 'system' => [
  20. 'type' => 'ReadOnlyStream',
  21. 'prefixes' => [
  22. '' => ['system'],
  23. ]
  24. ],
  25. 'user' => [
  26. 'type' => 'ReadOnlyStream',
  27. 'prefixes' => [
  28. '' => ['user'],
  29. ]
  30. ],
  31. 'blueprints' => [
  32. 'type' => 'ReadOnlyStream',
  33. 'prefixes' => [
  34. '' => ['user://blueprints', 'system/blueprints'],
  35. ]
  36. ],
  37. 'config' => [
  38. 'type' => 'ReadOnlyStream',
  39. 'prefixes' => [
  40. '' => ['user://config', 'system/config'],
  41. ]
  42. ],
  43. 'plugins' => [
  44. 'type' => 'ReadOnlyStream',
  45. 'prefixes' => [
  46. '' => ['user://plugins'],
  47. ]
  48. ],
  49. 'plugin' => [
  50. 'type' => 'ReadOnlyStream',
  51. 'prefixes' => [
  52. '' => ['user://plugins'],
  53. ]
  54. ],
  55. 'themes' => [
  56. 'type' => 'ReadOnlyStream',
  57. 'prefixes' => [
  58. '' => ['user://themes'],
  59. ]
  60. ],
  61. 'cache' => [
  62. 'type' => 'Stream',
  63. 'prefixes' => [
  64. '' => ['cache'],
  65. 'images' => ['images']
  66. ]
  67. ],
  68. 'log' => [
  69. 'type' => 'Stream',
  70. 'prefixes' => [
  71. '' => ['logs']
  72. ]
  73. ]
  74. ];
  75. protected $blueprintFiles = [];
  76. protected $configFiles = [];
  77. protected $checksum;
  78. protected $timestamp;
  79. protected $configLookup;
  80. protected $blueprintLookup;
  81. protected $pluginLookup;
  82. protected $finder;
  83. protected $environment;
  84. protected $messages = [];
  85. public function __construct(array $items = array(), Grav $grav = null, $environment = null)
  86. {
  87. $this->grav = $grav ?: Grav::instance();
  88. $this->finder = new ConfigFinder;
  89. $this->environment = $environment ?: 'localhost';
  90. $this->messages[] = 'Environment Name: ' . $this->environment;
  91. if (isset($items['@class'])) {
  92. if ($items['@class'] != get_class($this)) {
  93. throw new \InvalidArgumentException('Unrecognized config cache file!');
  94. }
  95. // Loading pre-compiled configuration.
  96. $this->timestamp = (int) $items['timestamp'];
  97. $this->checksum = $items['checksum'];
  98. $this->items = (array) $items['data'];
  99. } else {
  100. // Make sure that
  101. if (!isset($items['streams']['schemes'])) {
  102. $items['streams']['schemes'] = [];
  103. }
  104. $items['streams']['schemes'] += $this->streams;
  105. $items = $this->autoDetectEnvironmentConfig($items);
  106. $this->messages[] = $items['streams']['schemes']['config']['prefixes'][''];
  107. parent::__construct($items);
  108. }
  109. $this->check();
  110. }
  111. public function key()
  112. {
  113. return $this->checksum();
  114. }
  115. public function reload()
  116. {
  117. $this->check();
  118. $this->init();
  119. return $this;
  120. }
  121. protected function check()
  122. {
  123. $streams = isset($this->items['streams']['schemes']) ? $this->items['streams']['schemes'] : null;
  124. if (!is_array($streams)) {
  125. throw new \InvalidArgumentException('Configuration is missing streams.schemes!');
  126. }
  127. $diff = array_keys(array_diff_key($this->streams, $streams));
  128. if ($diff) {
  129. throw new \InvalidArgumentException(
  130. sprintf('Configuration is missing keys %s from streams.schemes!', implode(', ', $diff))
  131. );
  132. }
  133. }
  134. public function debug()
  135. {
  136. foreach ($this->messages as $message) {
  137. $this->grav['debugger']->addMessage($message);
  138. }
  139. }
  140. public function init()
  141. {
  142. /** @var UniformResourceLocator $locator */
  143. $locator = $this->grav['locator'];
  144. $this->configLookup = $locator->findResources('config://');
  145. $this->blueprintLookup = $locator->findResources('blueprints://config');
  146. $this->pluginLookup = $locator->findResources('plugins://');
  147. if (!isset($this->checksum)) {
  148. $this->messages[] = 'No cached configuration, compiling new configuration..';
  149. } elseif ($this->checksum() != $this->checksum) {
  150. $this->messages[] = 'Configuration checksum mismatch, reloading configuration..';
  151. } else {
  152. $this->messages[] = 'Configuration checksum matches, using cached version.';
  153. return;
  154. }
  155. $this->loadCompiledBlueprints($this->blueprintLookup, $this->pluginLookup, 'master');
  156. $this->loadCompiledConfig($this->configLookup, $this->pluginLookup, 'master');
  157. $this->initializeLocator($locator);
  158. }
  159. public function checksum()
  160. {
  161. $checkBlueprints = $this->get('system.cache.check.blueprints', false);
  162. $checkConfig = $this->get('system.cache.check.config', true);
  163. $checkSystem = $this->get('system.cache.check.system', true);
  164. if (!$checkBlueprints && !$checkConfig && !$checkSystem) {
  165. $this->messages[] = 'Skip configuration timestamp check.';
  166. return false;
  167. }
  168. // Generate checksum according to the configuration settings.
  169. if (!$checkConfig) {
  170. $this->messages[] = 'Check configuration timestamps from system.yaml files.';
  171. // Just check changes in system.yaml files and ignore all the other files.
  172. $cc = $checkSystem ? $this->finder->locateConfigFile($this->configLookup, 'system') : [];
  173. } else {
  174. $this->messages[] = 'Check configuration timestamps from all configuration files.';
  175. // Check changes in all configuration files.
  176. $cc = $this->finder->locateConfigFiles($this->configLookup, $this->pluginLookup);
  177. }
  178. if ($checkBlueprints) {
  179. $this->messages[] = 'Check blueprint timestamps from all blueprint files.';
  180. $cb = $this->finder->locateBlueprintFiles($this->blueprintLookup, $this->pluginLookup);
  181. } else {
  182. $cb = [];
  183. }
  184. return md5(json_encode([$cc, $cb]));
  185. }
  186. protected function autoDetectEnvironmentConfig($items)
  187. {
  188. $environment = $this->environment;
  189. $env_stream = 'user://'.$environment.'/config';
  190. if (file_exists(USER_DIR.$environment.'/config')) {
  191. array_unshift($items['streams']['schemes']['config']['prefixes'][''], $env_stream);
  192. }
  193. return $items;
  194. }
  195. protected function loadCompiledBlueprints($blueprints, $plugins, $filename = null)
  196. {
  197. $checksum = md5(json_encode($blueprints));
  198. $filename = $filename
  199. ? CACHE_DIR . 'compiled/blueprints/' . $filename . '-' . $this->environment . '.php'
  200. : CACHE_DIR . 'compiled/blueprints/' . $checksum . '-' . $this->environment . '.php';
  201. $file = PhpFile::instance($filename);
  202. $cache = $file->exists() ? $file->content() : null;
  203. $blueprintFiles = $this->finder->locateBlueprintFiles($blueprints, $plugins);
  204. $checksum .= ':'.md5(json_encode($blueprintFiles));
  205. $class = get_class($this);
  206. // Load real file if cache isn't up to date (or is invalid).
  207. if (
  208. !is_array($cache)
  209. || !isset($cache['checksum'])
  210. || !isset($cache['@class'])
  211. || $cache['checksum'] != $checksum
  212. || $cache['@class'] != $class
  213. ) {
  214. // Attempt to lock the file for writing.
  215. $file->lock(false);
  216. // Load blueprints.
  217. $this->blueprints = new Blueprints;
  218. foreach ($blueprintFiles as $files) {
  219. $this->loadBlueprintFiles($files);
  220. }
  221. $cache = [
  222. '@class' => $class,
  223. 'checksum' => $checksum,
  224. 'files' => $blueprintFiles,
  225. 'data' => $this->blueprints->toArray()
  226. ];
  227. // If compiled file wasn't already locked by another process, save it.
  228. if ($file->locked() !== false) {
  229. $this->messages[] = 'Saving compiled blueprints.';
  230. $file->save($cache);
  231. $file->unlock();
  232. }
  233. } else {
  234. $this->blueprints = new Blueprints($cache['data']);
  235. }
  236. }
  237. protected function loadCompiledConfig($configs, $plugins, $filename = null)
  238. {
  239. $checksum = md5(json_encode($configs));
  240. $filename = $filename
  241. ? CACHE_DIR . 'compiled/config/' . $filename . '-' . $this->environment . '.php'
  242. : CACHE_DIR . 'compiled/config/' . $checksum . '-' . $this->environment . '.php';
  243. $file = PhpFile::instance($filename);
  244. $cache = $file->exists() ? $file->content() : null;
  245. $configFiles = $this->finder->locateConfigFiles($configs, $plugins);
  246. $checksum .= ':'.md5(json_encode($configFiles));
  247. $class = get_class($this);
  248. // Load real file if cache isn't up to date (or is invalid).
  249. if (
  250. !is_array($cache)
  251. || !isset($cache['checksum'])
  252. || !isset($cache['@class'])
  253. || $cache['checksum'] != $checksum
  254. || $cache['@class'] != $class
  255. ) {
  256. // Attempt to lock the file for writing.
  257. $file->lock(false);
  258. // Load configuration.
  259. foreach ($configFiles as $files) {
  260. $this->loadConfigFiles($files);
  261. }
  262. $cache = [
  263. '@class' => $class,
  264. 'timestamp' => time(),
  265. 'checksum' => $this->checksum(),
  266. 'data' => $this->toArray()
  267. ];
  268. // If compiled file wasn't already locked by another process, save it.
  269. if ($file->locked() !== false) {
  270. $this->messages[] = 'Saving compiled configuration.';
  271. $file->save($cache);
  272. $file->unlock();
  273. }
  274. }
  275. $this->items = $cache['data'];
  276. }
  277. /**
  278. * Load blueprints.
  279. *
  280. * @param array $files
  281. */
  282. public function loadBlueprintFiles(array $files)
  283. {
  284. foreach ($files as $name => $item) {
  285. $file = CompiledYamlFile::instance($item['file']);
  286. $this->blueprints->embed($name, $file->content(), '/');
  287. }
  288. }
  289. /**
  290. * Load configuration.
  291. *
  292. * @param array $files
  293. */
  294. public function loadConfigFiles(array $files)
  295. {
  296. foreach ($files as $name => $item) {
  297. $file = CompiledYamlFile::instance($item['file']);
  298. $this->join($name, $file->content(), '/');
  299. }
  300. }
  301. /**
  302. * Initialize resource locator by using the configuration.
  303. *
  304. * @param UniformResourceLocator $locator
  305. */
  306. public function initializeLocator(UniformResourceLocator $locator)
  307. {
  308. $locator->reset();
  309. $schemes = (array) $this->get('streams.schemes', []);
  310. foreach ($schemes as $scheme => $config) {
  311. if (isset($config['paths'])) {
  312. $locator->addPath($scheme, '', $config['paths']);
  313. }
  314. if (isset($config['prefixes'])) {
  315. foreach ($config['prefixes'] as $prefix => $paths) {
  316. $locator->addPath($scheme, $prefix, $paths);
  317. }
  318. }
  319. }
  320. }
  321. /**
  322. * Get available streams and their types from the configuration.
  323. *
  324. * @return array
  325. */
  326. public function getStreams()
  327. {
  328. $schemes = [];
  329. foreach ((array) $this->get('streams.schemes') as $scheme => $config) {
  330. $type = !empty($config['type']) ? $config['type'] : 'ReadOnlyStream';
  331. if ($type[0] != '\\') {
  332. $type = '\\RocketTheme\\Toolbox\\StreamWrapper\\' . $type;
  333. }
  334. $schemes[$scheme] = $type;
  335. }
  336. return $schemes;
  337. }
  338. }