Setup.php 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. <?php
  2. /**
  3. * @package Grav\Common\Config
  4. *
  5. * @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
  6. * @license MIT License; see LICENSE file for details.
  7. */
  8. namespace Grav\Common\Config;
  9. use Grav\Common\File\CompiledYamlFile;
  10. use Grav\Common\Data\Data;
  11. use Grav\Common\Utils;
  12. use Pimple\Container;
  13. use Psr\Http\Message\ServerRequestInterface;
  14. use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
  15. class Setup extends Data
  16. {
  17. /**
  18. * @var array Environment aliases normalized to lower case.
  19. */
  20. public static $environments = [
  21. '' => 'unknown',
  22. '127.0.0.1' => 'localhost',
  23. '::1' => 'localhost'
  24. ];
  25. /**
  26. * @var string Current environment normalized to lower case.
  27. */
  28. public static $environment;
  29. protected $streams = [
  30. 'system' => [
  31. 'type' => 'ReadOnlyStream',
  32. 'prefixes' => [
  33. '' => ['system'],
  34. ]
  35. ],
  36. 'user' => [
  37. 'type' => 'ReadOnlyStream',
  38. 'force' => true,
  39. 'prefixes' => [
  40. '' => ['user'],
  41. ]
  42. ],
  43. 'environment' => [
  44. 'type' => 'ReadOnlyStream'
  45. // If not defined, environment will be set up in the constructor.
  46. ],
  47. 'asset' => [
  48. 'type' => 'Stream',
  49. 'prefixes' => [
  50. '' => ['assets'],
  51. ]
  52. ],
  53. 'blueprints' => [
  54. 'type' => 'ReadOnlyStream',
  55. 'prefixes' => [
  56. '' => ['environment://blueprints', 'user://blueprints', 'system/blueprints'],
  57. ]
  58. ],
  59. 'config' => [
  60. 'type' => 'ReadOnlyStream',
  61. 'prefixes' => [
  62. '' => ['environment://config', 'user://config', 'system/config'],
  63. ]
  64. ],
  65. 'plugins' => [
  66. 'type' => 'ReadOnlyStream',
  67. 'prefixes' => [
  68. '' => ['user://plugins'],
  69. ]
  70. ],
  71. 'plugin' => [
  72. 'type' => 'ReadOnlyStream',
  73. 'prefixes' => [
  74. '' => ['user://plugins'],
  75. ]
  76. ],
  77. 'themes' => [
  78. 'type' => 'ReadOnlyStream',
  79. 'prefixes' => [
  80. '' => ['user://themes'],
  81. ]
  82. ],
  83. 'languages' => [
  84. 'type' => 'ReadOnlyStream',
  85. 'prefixes' => [
  86. '' => ['environment://languages', 'user://languages', 'system/languages'],
  87. ]
  88. ],
  89. 'cache' => [
  90. 'type' => 'Stream',
  91. 'force' => true,
  92. 'prefixes' => [
  93. '' => ['cache'],
  94. 'images' => ['images']
  95. ]
  96. ],
  97. 'log' => [
  98. 'type' => 'Stream',
  99. 'force' => true,
  100. 'prefixes' => [
  101. '' => ['logs']
  102. ]
  103. ],
  104. 'backup' => [
  105. 'type' => 'Stream',
  106. 'force' => true,
  107. 'prefixes' => [
  108. '' => ['backup']
  109. ]
  110. ],
  111. 'tmp' => [
  112. 'type' => 'Stream',
  113. 'force' => true,
  114. 'prefixes' => [
  115. '' => ['tmp']
  116. ]
  117. ],
  118. 'image' => [
  119. 'type' => 'Stream',
  120. 'prefixes' => [
  121. '' => ['user://images', 'system://images']
  122. ]
  123. ],
  124. 'page' => [
  125. 'type' => 'ReadOnlyStream',
  126. 'prefixes' => [
  127. '' => ['user://pages']
  128. ]
  129. ],
  130. 'user-data' => [
  131. 'type' => 'Stream',
  132. 'force' => true,
  133. 'prefixes' => [
  134. '' => ['user://data']
  135. ]
  136. ],
  137. 'account' => [
  138. 'type' => 'ReadOnlyStream',
  139. 'prefixes' => [
  140. '' => ['user://accounts']
  141. ]
  142. ],
  143. ];
  144. /**
  145. * @param Container|array $container
  146. */
  147. public function __construct($container)
  148. {
  149. // If no environment is set, make sure we get one (CLI or hostname).
  150. if (!static::$environment) {
  151. if (\defined('GRAV_CLI')) {
  152. static::$environment = 'cli';
  153. } else {
  154. /** @var ServerRequestInterface $request */
  155. $request = $container['request'];
  156. $host = $request->getUri()->getHost();
  157. static::$environment = Utils::substrToString($host, ':');
  158. }
  159. }
  160. // Resolve server aliases to the proper environment.
  161. $environment = $this->environments[static::$environment] ?? static::$environment;
  162. // Pre-load setup.php which contains our initial configuration.
  163. // Configuration may contain dynamic parts, which is why we need to always load it.
  164. // If "GRAV_SETUP_PATH" has been defined, use it, otherwise use defaults.
  165. $file = \defined('GRAV_SETUP_PATH') ? GRAV_SETUP_PATH : GRAV_ROOT . '/setup.php';
  166. $setup = is_file($file) ? (array) include $file : [];
  167. // Add default streams defined in beginning of the class.
  168. if (!isset($setup['streams']['schemes'])) {
  169. $setup['streams']['schemes'] = [];
  170. }
  171. $setup['streams']['schemes'] += $this->streams;
  172. // Initialize class.
  173. parent::__construct($setup);
  174. // Set up environment.
  175. $this->def('environment', $environment);
  176. $this->def('streams.schemes.environment.prefixes', ['' => ["user://{$this->get('environment')}"]]);
  177. }
  178. /**
  179. * @return $this
  180. * @throws \RuntimeException
  181. * @throws \InvalidArgumentException
  182. */
  183. public function init()
  184. {
  185. $locator = new UniformResourceLocator(GRAV_ROOT);
  186. $files = [];
  187. $guard = 5;
  188. do {
  189. $check = $files;
  190. $this->initializeLocator($locator);
  191. $files = $locator->findResources('config://streams.yaml');
  192. if ($check === $files) {
  193. break;
  194. }
  195. // Update streams.
  196. foreach (array_reverse($files) as $path) {
  197. $file = CompiledYamlFile::instance($path);
  198. $content = (array)$file->content();
  199. if (!empty($content['schemes'])) {
  200. $this->items['streams']['schemes'] = $content['schemes'] + $this->items['streams']['schemes'];
  201. }
  202. }
  203. } while (--$guard);
  204. if (!$guard) {
  205. throw new \RuntimeException('Setup: Configuration reload loop detected!');
  206. }
  207. // Make sure we have valid setup.
  208. $this->check($locator);
  209. return $this;
  210. }
  211. /**
  212. * Initialize resource locator by using the configuration.
  213. *
  214. * @param UniformResourceLocator $locator
  215. * @throws \BadMethodCallException
  216. */
  217. public function initializeLocator(UniformResourceLocator $locator)
  218. {
  219. $locator->reset();
  220. $schemes = (array) $this->get('streams.schemes', []);
  221. foreach ($schemes as $scheme => $config) {
  222. if (isset($config['paths'])) {
  223. $locator->addPath($scheme, '', $config['paths']);
  224. }
  225. $override = $config['override'] ?? false;
  226. $force = $config['force'] ?? false;
  227. if (isset($config['prefixes'])) {
  228. foreach ((array)$config['prefixes'] as $prefix => $paths) {
  229. $locator->addPath($scheme, $prefix, $paths, $override, $force);
  230. }
  231. }
  232. }
  233. }
  234. /**
  235. * Get available streams and their types from the configuration.
  236. *
  237. * @return array
  238. */
  239. public function getStreams()
  240. {
  241. $schemes = [];
  242. foreach ((array) $this->get('streams.schemes') as $scheme => $config) {
  243. $type = $config['type'] ?? 'ReadOnlyStream';
  244. if ($type[0] !== '\\') {
  245. $type = '\\RocketTheme\\Toolbox\\StreamWrapper\\' . $type;
  246. }
  247. $schemes[$scheme] = $type;
  248. }
  249. return $schemes;
  250. }
  251. /**
  252. * @param UniformResourceLocator $locator
  253. * @throws \InvalidArgumentException
  254. * @throws \BadMethodCallException
  255. * @throws \RuntimeException
  256. */
  257. protected function check(UniformResourceLocator $locator)
  258. {
  259. $streams = $this->items['streams']['schemes'] ?? null;
  260. if (!\is_array($streams)) {
  261. throw new \InvalidArgumentException('Configuration is missing streams.schemes!');
  262. }
  263. $diff = array_keys(array_diff_key($this->streams, $streams));
  264. if ($diff) {
  265. throw new \InvalidArgumentException(
  266. sprintf('Configuration is missing keys %s from streams.schemes!', implode(', ', $diff))
  267. );
  268. }
  269. try {
  270. if (!$locator->findResource('environment://config', true)) {
  271. // If environment does not have its own directory, remove it from the lookup.
  272. $this->set('streams.schemes.environment.prefixes', ['config' => []]);
  273. $this->initializeLocator($locator);
  274. }
  275. // Create security.yaml if it doesn't exist.
  276. $filename = $locator->findResource('config://security.yaml', true, true);
  277. $security_file = CompiledYamlFile::instance($filename);
  278. $security_content = (array)$security_file->content();
  279. if (!isset($security_content['salt'])) {
  280. $security_content = array_merge($security_content, ['salt' => Utils::generateRandomString(14)]);
  281. $security_file->content($security_content);
  282. $security_file->save();
  283. $security_file->free();
  284. }
  285. } catch (\RuntimeException $e) {
  286. throw new \RuntimeException(sprintf('Grav failed to initialize: %s', $e->getMessage()), 500, $e);
  287. }
  288. }
  289. }