FileSystem.php 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. <?php
  2. namespace Drupal\Core\File;
  3. use Drupal\Core\Site\Settings;
  4. use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
  5. use Psr\Log\LoggerInterface;
  6. /**
  7. * Provides helpers to operate on files and stream wrappers.
  8. */
  9. class FileSystem implements FileSystemInterface {
  10. /**
  11. * Default mode for new directories. See self::chmod().
  12. */
  13. const CHMOD_DIRECTORY = 0775;
  14. /**
  15. * Default mode for new files. See self::chmod().
  16. */
  17. const CHMOD_FILE = 0664;
  18. /**
  19. * The site settings.
  20. *
  21. * @var \Drupal\Core\Site\Settings
  22. */
  23. protected $settings;
  24. /**
  25. * The file logger channel.
  26. *
  27. * @var \Psr\Log\LoggerInterface
  28. */
  29. protected $logger;
  30. /**
  31. * The stream wrapper manager.
  32. *
  33. * @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface
  34. */
  35. protected $streamWrapperManager;
  36. /**
  37. * Constructs a new FileSystem.
  38. *
  39. * @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager
  40. * The stream wrapper manager.
  41. * @param \Drupal\Core\Site\Settings $settings
  42. * The site settings.
  43. * @param \Psr\Log\LoggerInterface $logger
  44. * The file logger channel.
  45. */
  46. public function __construct(StreamWrapperManagerInterface $stream_wrapper_manager, Settings $settings, LoggerInterface $logger) {
  47. $this->streamWrapperManager = $stream_wrapper_manager;
  48. $this->settings = $settings;
  49. $this->logger = $logger;
  50. }
  51. /**
  52. * {@inheritdoc}
  53. */
  54. public function moveUploadedFile($filename, $uri) {
  55. $result = @move_uploaded_file($filename, $uri);
  56. // PHP's move_uploaded_file() does not properly support streams if
  57. // open_basedir is enabled so if the move failed, try finding a real path
  58. // and retry the move operation.
  59. if (!$result) {
  60. if ($realpath = $this->realpath($uri)) {
  61. $result = move_uploaded_file($filename, $realpath);
  62. }
  63. else {
  64. $result = move_uploaded_file($filename, $uri);
  65. }
  66. }
  67. return $result;
  68. }
  69. /**
  70. * {@inheritdoc}
  71. */
  72. public function chmod($uri, $mode = NULL) {
  73. if (!isset($mode)) {
  74. if (is_dir($uri)) {
  75. $mode = $this->settings->get('file_chmod_directory', static::CHMOD_DIRECTORY);
  76. }
  77. else {
  78. $mode = $this->settings->get('file_chmod_file', static::CHMOD_FILE);
  79. }
  80. }
  81. if (@chmod($uri, $mode)) {
  82. return TRUE;
  83. }
  84. $this->logger->error('The file permissions could not be set on %uri.', ['%uri' => $uri]);
  85. return FALSE;
  86. }
  87. /**
  88. * {@inheritdoc}
  89. */
  90. public function unlink($uri, $context = NULL) {
  91. $scheme = $this->uriScheme($uri);
  92. if (!$this->validScheme($scheme) && (substr(PHP_OS, 0, 3) == 'WIN')) {
  93. chmod($uri, 0600);
  94. }
  95. if ($context) {
  96. return unlink($uri, $context);
  97. }
  98. else {
  99. return unlink($uri);
  100. }
  101. }
  102. /**
  103. * {@inheritdoc}
  104. */
  105. public function realpath($uri) {
  106. // If this URI is a stream, pass it off to the appropriate stream wrapper.
  107. // Otherwise, attempt PHP's realpath. This allows use of this method even
  108. // for unmanaged files outside of the stream wrapper interface.
  109. if ($wrapper = $this->streamWrapperManager->getViaUri($uri)) {
  110. return $wrapper->realpath();
  111. }
  112. return realpath($uri);
  113. }
  114. /**
  115. * {@inheritdoc}
  116. */
  117. public function dirname($uri) {
  118. $scheme = $this->uriScheme($uri);
  119. if ($this->validScheme($scheme)) {
  120. return $this->streamWrapperManager->getViaScheme($scheme)->dirname($uri);
  121. }
  122. else {
  123. return dirname($uri);
  124. }
  125. }
  126. /**
  127. * {@inheritdoc}
  128. */
  129. public function basename($uri, $suffix = NULL) {
  130. $separators = '/';
  131. if (DIRECTORY_SEPARATOR != '/') {
  132. // For Windows OS add special separator.
  133. $separators .= DIRECTORY_SEPARATOR;
  134. }
  135. // Remove right-most slashes when $uri points to directory.
  136. $uri = rtrim($uri, $separators);
  137. // Returns the trailing part of the $uri starting after one of the directory
  138. // separators.
  139. $filename = preg_match('@[^' . preg_quote($separators, '@') . ']+$@', $uri, $matches) ? $matches[0] : '';
  140. // Cuts off a suffix from the filename.
  141. if ($suffix) {
  142. $filename = preg_replace('@' . preg_quote($suffix, '@') . '$@', '', $filename);
  143. }
  144. return $filename;
  145. }
  146. /**
  147. * {@inheritdoc}
  148. */
  149. public function mkdir($uri, $mode = NULL, $recursive = FALSE, $context = NULL) {
  150. if (!isset($mode)) {
  151. $mode = $this->settings->get('file_chmod_directory', static::CHMOD_DIRECTORY);
  152. }
  153. // If the URI has a scheme, don't override the umask - schemes can handle
  154. // this issue in their own implementation.
  155. if ($this->uriScheme($uri)) {
  156. return $this->mkdirCall($uri, $mode, $recursive, $context);
  157. }
  158. // If recursive, create each missing component of the parent directory
  159. // individually and set the mode explicitly to override the umask.
  160. if ($recursive) {
  161. // Ensure the path is using DIRECTORY_SEPARATOR, and trim off any trailing
  162. // slashes because they can throw off the loop when creating the parent
  163. // directories.
  164. $uri = rtrim(str_replace('/', DIRECTORY_SEPARATOR, $uri), DIRECTORY_SEPARATOR);
  165. // Determine the components of the path.
  166. $components = explode(DIRECTORY_SEPARATOR, $uri);
  167. // If the filepath is absolute the first component will be empty as there
  168. // will be nothing before the first slash.
  169. if ($components[0] == '') {
  170. $recursive_path = DIRECTORY_SEPARATOR;
  171. // Get rid of the empty first component.
  172. array_shift($components);
  173. }
  174. else {
  175. $recursive_path = '';
  176. }
  177. // Don't handle the top-level directory in this loop.
  178. array_pop($components);
  179. // Create each component if necessary.
  180. foreach ($components as $component) {
  181. $recursive_path .= $component;
  182. if (!file_exists($recursive_path)) {
  183. if (!$this->mkdirCall($recursive_path, $mode, FALSE, $context)) {
  184. return FALSE;
  185. }
  186. // Not necessary to use self::chmod() as there is no scheme.
  187. if (!chmod($recursive_path, $mode)) {
  188. return FALSE;
  189. }
  190. }
  191. $recursive_path .= DIRECTORY_SEPARATOR;
  192. }
  193. }
  194. // Do not check if the top-level directory already exists, as this condition
  195. // must cause this function to fail.
  196. if (!$this->mkdirCall($uri, $mode, FALSE, $context)) {
  197. return FALSE;
  198. }
  199. // Not necessary to use self::chmod() as there is no scheme.
  200. return chmod($uri, $mode);
  201. }
  202. /**
  203. * Helper function. Ensures we don't pass a NULL as a context resource to
  204. * mkdir().
  205. *
  206. * @see self::mkdir()
  207. */
  208. protected function mkdirCall($uri, $mode, $recursive, $context) {
  209. if (is_null($context)) {
  210. return mkdir($uri, $mode, $recursive);
  211. }
  212. else {
  213. return mkdir($uri, $mode, $recursive, $context);
  214. }
  215. }
  216. /**
  217. * {@inheritdoc}
  218. */
  219. public function rmdir($uri, $context = NULL) {
  220. $scheme = $this->uriScheme($uri);
  221. if (!$this->validScheme($scheme) && (substr(PHP_OS, 0, 3) == 'WIN')) {
  222. chmod($uri, 0700);
  223. }
  224. if ($context) {
  225. return rmdir($uri, $context);
  226. }
  227. else {
  228. return rmdir($uri);
  229. }
  230. }
  231. /**
  232. * {@inheritdoc}
  233. */
  234. public function tempnam($directory, $prefix) {
  235. $scheme = $this->uriScheme($directory);
  236. if ($this->validScheme($scheme)) {
  237. $wrapper = $this->streamWrapperManager->getViaScheme($scheme);
  238. if ($filename = tempnam($wrapper->getDirectoryPath(), $prefix)) {
  239. return $scheme . '://' . static::basename($filename);
  240. }
  241. else {
  242. return FALSE;
  243. }
  244. }
  245. else {
  246. // Handle as a normal tempnam() call.
  247. return tempnam($directory, $prefix);
  248. }
  249. }
  250. /**
  251. * {@inheritdoc}
  252. */
  253. public function uriScheme($uri) {
  254. if (preg_match('/^([\w\-]+):\/\/|^(data):/', $uri, $matches)) {
  255. // The scheme will always be the last element in the matches array.
  256. return array_pop($matches);
  257. }
  258. return FALSE;
  259. }
  260. /**
  261. * {@inheritdoc}
  262. */
  263. public function validScheme($scheme) {
  264. if (!$scheme) {
  265. return FALSE;
  266. }
  267. return class_exists($this->streamWrapperManager->getClass($scheme));
  268. }
  269. }