DirectoryList.php 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. <?php
  2. /**
  3. * Application file system directories dictionary.
  4. *
  5. * Provides information about what directories are available in the application.
  6. * Serves as a customization point to specify different directories or add your own.
  7. *
  8. * Copyright © Magento, Inc. All rights reserved.
  9. * See COPYING.txt for license details.
  10. */
  11. namespace Magento\Framework\Filesystem;
  12. /**
  13. * A list of directories
  14. *
  15. * Each list item consists of:
  16. * - a directory path in the filesystem
  17. * - optionally, a URL path
  18. *
  19. * This object is intended to be immutable (a "value object").
  20. * The defaults are pre-defined and can be modified only by inheritors of this class.
  21. * Through the constructor, it is possible to inject custom paths or URL paths, but impossible to inject new types.
  22. */
  23. class DirectoryList
  24. {
  25. /**#@+
  26. * Keys of directory configuration
  27. */
  28. const PATH = 'path';
  29. const URL_PATH = 'uri';
  30. /**#@- */
  31. /**
  32. * System base temporary directory
  33. */
  34. const SYS_TMP = 'sys_tmp';
  35. /**
  36. * Root path
  37. *
  38. * @var string
  39. */
  40. private $root;
  41. /**
  42. * Directories configurations
  43. *
  44. * @var array
  45. */
  46. private $directories;
  47. /**
  48. * Predefined types/paths
  49. *
  50. * @return array
  51. */
  52. public static function getDefaultConfig()
  53. {
  54. return [self::SYS_TMP => [self::PATH => '']];
  55. }
  56. /**
  57. * Validates format and contents of given configuration
  58. *
  59. * @param array $config
  60. * @return void
  61. * @throws \InvalidArgumentException
  62. */
  63. public static function validate($config)
  64. {
  65. if (!is_array($config)) {
  66. throw new \InvalidArgumentException('Unexpected value type.');
  67. }
  68. $defaultConfig = static::getDefaultConfig();
  69. foreach ($config as $type => $row) {
  70. if (!is_array($row)) {
  71. throw new \InvalidArgumentException('Unexpected value type.');
  72. }
  73. if (!isset($defaultConfig[$type])) {
  74. throw new \InvalidArgumentException("Unknown type: {$type}");
  75. }
  76. if (!isset($row[self::PATH]) && !isset($row[self::URL_PATH])) {
  77. throw new \InvalidArgumentException("Missing required keys at: {$type}");
  78. }
  79. }
  80. }
  81. /**
  82. * Constructor
  83. *
  84. * @param string $root
  85. * @param array $config
  86. */
  87. public function __construct($root, array $config = [])
  88. {
  89. static::validate($config);
  90. $this->root = $this->normalizePath($root);
  91. $this->directories = static::getDefaultConfig();
  92. $this->directories[self::SYS_TMP] = [self::PATH => realpath(sys_get_temp_dir())];
  93. // inject custom values from constructor
  94. foreach ($this->directories as $code => $dir) {
  95. foreach ([self::PATH, self::URL_PATH] as $key) {
  96. if (isset($config[$code][$key])) {
  97. $this->directories[$code][$key] = $config[$code][$key];
  98. }
  99. }
  100. }
  101. // filter/validate values
  102. foreach ($this->directories as $code => $dir) {
  103. $path = $this->normalizePath($dir[self::PATH]);
  104. if (!$this->isAbsolute($path)) {
  105. $path = $this->prependRoot($path);
  106. }
  107. $this->directories[$code][self::PATH] = $path;
  108. if (isset($dir[self::URL_PATH])) {
  109. $this->assertUrlPath($dir[self::URL_PATH]);
  110. }
  111. }
  112. }
  113. /**
  114. * Converts slashes in path to a conventional unix-style
  115. *
  116. * @param string $path
  117. * @return string
  118. */
  119. private function normalizePath($path)
  120. {
  121. return str_replace('\\', '/', $path);
  122. }
  123. /**
  124. * Validates a URL path
  125. *
  126. * Path must be usable as a fragment of a URL path.
  127. * For interoperability and security purposes, no uppercase or "upper directory" paths like "." or ".."
  128. *
  129. * @param string $urlPath
  130. * @return void
  131. * @throws \InvalidArgumentException
  132. */
  133. private function assertUrlPath($urlPath)
  134. {
  135. if (!preg_match('/^([a-z0-9_]+[a-z0-9\._]*(\/[a-z0-9_]+[a-z0-9\._]*)*)?$/', $urlPath)) {
  136. throw new \InvalidArgumentException(
  137. "URL path must be relative directory path in lowercase with '/' directory separator: '{$urlPath}'"
  138. );
  139. }
  140. }
  141. /**
  142. * Concatenates root directory path with a relative path
  143. *
  144. * @param string $path
  145. * @return string
  146. */
  147. protected function prependRoot($path)
  148. {
  149. $root = $this->getRoot();
  150. return $root . ($root && $path ? '/' : '') . $path;
  151. }
  152. /**
  153. * Determine if a path is absolute
  154. *
  155. * @param string $path
  156. * @return bool
  157. */
  158. protected function isAbsolute($path)
  159. {
  160. $path = strtr($path, '\\', '/');
  161. if (strpos($path, '/') === 0) {
  162. //is UnixRoot
  163. return true;
  164. } elseif (preg_match('#^\w{1}:/#', $path)) {
  165. //is WindowsRoot
  166. return true;
  167. } elseif (parse_url($path, PHP_URL_SCHEME) !== null) {
  168. //is WindowsLetter
  169. return true;
  170. }
  171. return false;
  172. }
  173. /**
  174. * Gets a filesystem path of the root directory
  175. *
  176. * @return string
  177. */
  178. public function getRoot()
  179. {
  180. return $this->root;
  181. }
  182. /**
  183. * Gets a filesystem path of a directory
  184. *
  185. * @param string $code
  186. * @return string
  187. * @throws \Magento\Framework\Exception\FileSystemException
  188. */
  189. public function getPath($code)
  190. {
  191. $this->assertCode($code);
  192. return $this->directories[$code][self::PATH];
  193. }
  194. /**
  195. * Gets URL path of a directory
  196. *
  197. * @param string $code
  198. * @return string|bool
  199. */
  200. public function getUrlPath($code)
  201. {
  202. $this->assertCode($code);
  203. if (!isset($this->directories[$code][self::URL_PATH])) {
  204. return false;
  205. }
  206. return $this->directories[$code][self::URL_PATH];
  207. }
  208. /**
  209. * Asserts that specified directory code is in the registry
  210. *
  211. * @param string $code
  212. * @throws \Magento\Framework\Exception\FileSystemException
  213. * @return void
  214. */
  215. private function assertCode($code)
  216. {
  217. if (!isset($this->directories[$code])) {
  218. throw new \Magento\Framework\Exception\FileSystemException(
  219. new \Magento\Framework\Phrase('Unknown directory type: \'%1\'', [$code])
  220. );
  221. }
  222. }
  223. }