View source
<?php
namespace Drupal\Core\File;
use Drupal\Component\FileSystem\FileSystem as FileSystemComponent;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\File\Exception\DirectoryNotReadyException;
use Drupal\Core\File\Exception\FileException;
use Drupal\Core\File\Exception\FileExistsException;
use Drupal\Core\File\Exception\FileNotExistsException;
use Drupal\Core\File\Exception\FileWriteException;
use Drupal\Core\File\Exception\NotRegularDirectoryException;
use Drupal\Core\File\Exception\NotRegularFileException;
use Drupal\Core\Site\Settings;
use Drupal\Core\StreamWrapper\PublicStream;
use Drupal\Core\StreamWrapper\StreamWrapperManager;
use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
use Psr\Log\LoggerInterface;
class FileSystem implements FileSystemInterface {
const CHMOD_DIRECTORY = 0775;
const CHMOD_FILE = 0664;
protected $settings;
protected $logger;
protected $streamWrapperManager;
public function __construct(StreamWrapperManagerInterface $stream_wrapper_manager, Settings $settings, LoggerInterface $logger) {
$this->streamWrapperManager = $stream_wrapper_manager;
$this->settings = $settings;
$this->logger = $logger;
}
public function moveUploadedFile($filename, $uri) {
$result = @move_uploaded_file($filename, $uri);
if (!$result) {
if ($realpath = $this
->realpath($uri)) {
$result = move_uploaded_file($filename, $realpath);
}
else {
$result = move_uploaded_file($filename, $uri);
}
}
return $result;
}
public function chmod($uri, $mode = NULL) {
if (!isset($mode)) {
if (is_dir($uri)) {
$mode = $this->settings
->get('file_chmod_directory', static::CHMOD_DIRECTORY);
}
else {
$mode = $this->settings
->get('file_chmod_file', static::CHMOD_FILE);
}
}
if (@chmod($uri, $mode)) {
return TRUE;
}
$this->logger
->error('The file permissions could not be set on %uri.', [
'%uri' => $uri,
]);
return FALSE;
}
public function unlink($uri, $context = NULL) {
if (!$this->streamWrapperManager
->isValidUri($uri) && substr(PHP_OS, 0, 3) == 'WIN') {
chmod($uri, 0600);
}
if ($context) {
return unlink($uri, $context);
}
else {
return unlink($uri);
}
}
public function realpath($uri) {
if ($wrapper = $this->streamWrapperManager
->getViaUri($uri)) {
return $wrapper
->realpath();
}
return realpath($uri);
}
public function dirname($uri) {
$scheme = StreamWrapperManager::getScheme($uri);
if ($this->streamWrapperManager
->isValidScheme($scheme)) {
return $this->streamWrapperManager
->getViaScheme($scheme)
->dirname($uri);
}
else {
return dirname($uri);
}
}
public function basename($uri, $suffix = NULL) {
$separators = '/';
if (DIRECTORY_SEPARATOR != '/') {
$separators .= DIRECTORY_SEPARATOR;
}
$uri = rtrim($uri, $separators);
$filename = preg_match('@[^' . preg_quote($separators, '@') . ']+$@', $uri, $matches) ? $matches[0] : '';
if ($suffix) {
$filename = preg_replace('@' . preg_quote($suffix, '@') . '$@', '', $filename);
}
return $filename;
}
public function mkdir($uri, $mode = NULL, $recursive = FALSE, $context = NULL) {
if (!isset($mode)) {
$mode = $this->settings
->get('file_chmod_directory', static::CHMOD_DIRECTORY);
}
if (StreamWrapperManager::getScheme($uri)) {
return $this
->mkdirCall($uri, $mode, $recursive, $context);
}
if ($recursive) {
$uri = rtrim(str_replace('/', DIRECTORY_SEPARATOR, $uri), DIRECTORY_SEPARATOR);
$components = explode(DIRECTORY_SEPARATOR, $uri);
if ($components[0] == '') {
$recursive_path = DIRECTORY_SEPARATOR;
array_shift($components);
}
else {
$recursive_path = '';
}
array_pop($components);
foreach ($components as $component) {
$recursive_path .= $component;
if (!file_exists($recursive_path)) {
if (!$this
->mkdirCall($recursive_path, $mode, FALSE, $context)) {
return FALSE;
}
if (!chmod($recursive_path, $mode)) {
return FALSE;
}
}
$recursive_path .= DIRECTORY_SEPARATOR;
}
}
if (!$this
->mkdirCall($uri, $mode, FALSE, $context)) {
return FALSE;
}
return chmod($uri, $mode);
}
protected function mkdirCall($uri, $mode, $recursive, $context) {
if (is_null($context)) {
return mkdir($uri, $mode, $recursive);
}
else {
return mkdir($uri, $mode, $recursive, $context);
}
}
public function rmdir($uri, $context = NULL) {
if (!$this->streamWrapperManager
->isValidUri($uri) && substr(PHP_OS, 0, 3) == 'WIN') {
chmod($uri, 0700);
}
if ($context) {
return rmdir($uri, $context);
}
else {
return rmdir($uri);
}
}
public function tempnam($directory, $prefix) {
$scheme = StreamWrapperManager::getScheme($directory);
if ($this->streamWrapperManager
->isValidScheme($scheme)) {
$wrapper = $this->streamWrapperManager
->getViaScheme($scheme);
if ($filename = tempnam($wrapper
->getDirectoryPath(), $prefix)) {
return $scheme . '://' . static::basename($filename);
}
else {
return FALSE;
}
}
else {
return tempnam($directory, $prefix);
}
}
public function uriScheme($uri) {
@trigger_error('FileSystem::uriScheme() is deprecated in drupal:8.8.0. It will be removed from drupal:9.0.0. Use \\Drupal\\Core\\StreamWrapper\\StreamWrapperManagerInterface::getScheme() instead. See https://www.drupal.org/node/3035273', E_USER_DEPRECATED);
return StreamWrapperManager::getScheme($uri);
}
public function validScheme($scheme) {
@trigger_error('FileSystem::validScheme() is deprecated in drupal:8.8.0 and will be removed before drupal:9.0.0. Use \\Drupal\\Core\\StreamWrapper\\StreamWrapperManagerInterface::isValidScheme() instead. See https://www.drupal.org/node/3035273', E_USER_DEPRECATED);
return $this->streamWrapperManager
->isValidScheme($scheme);
}
public function copy($source, $destination, $replace = self::EXISTS_RENAME) {
$this
->prepareDestination($source, $destination, $replace);
if (!@copy($source, $destination)) {
$real_source = $this
->realpath($source) ?: $source;
$real_destination = $this
->realpath($destination) ?: $destination;
if ($real_source === FALSE || $real_destination === FALSE || !@copy($real_source, $real_destination)) {
$this->logger
->error("The specified file '%source' could not be copied to '%destination'.", [
'%source' => $source,
'%destination' => $destination,
]);
throw new FileWriteException("The specified file '{$source}' could not be copied to '{$destination}'.");
}
}
$this
->chmod($destination);
return $destination;
}
public function delete($path) {
if (is_file($path)) {
if (!$this
->unlink($path)) {
$this->logger
->error("Failed to unlink file '%path'.", [
'%path' => $path,
]);
throw new FileException("Failed to unlink file '{$path}'.");
}
return TRUE;
}
if (is_dir($path)) {
$this->logger
->error("Cannot delete '%path' because it is a directory. Use deleteRecursive() instead.", [
'%path' => $path,
]);
throw new NotRegularFileException("Cannot delete '{$path}' because it is a directory. Use deleteRecursive() instead.");
}
if (!file_exists($path)) {
$this->logger
->notice('The file %path was not deleted because it does not exist.', [
'%path' => $path,
]);
return TRUE;
}
$this->logger
->error("The file '%path' is not of a recognized type so it was not deleted.", [
'%path' => $path,
]);
throw new NotRegularFileException("The file '{$path}' is not of a recognized type so it was not deleted.");
}
public function deleteRecursive($path, callable $callback = NULL) {
if ($callback) {
call_user_func($callback, $path);
}
if (is_dir($path)) {
$dir = dir($path);
while (($entry = $dir
->read()) !== FALSE) {
if ($entry == '.' || $entry == '..') {
continue;
}
$entry_path = $path . '/' . $entry;
$this
->deleteRecursive($entry_path, $callback);
}
$dir
->close();
return $this
->rmdir($path);
}
return $this
->delete($path);
}
public function move($source, $destination, $replace = self::EXISTS_RENAME) {
$this
->prepareDestination($source, $destination, $replace);
if (!$this->streamWrapperManager
->isValidUri($source) && substr(PHP_OS, 0, 3) == 'WIN') {
chmod($source, 0600);
}
$real_source = $this
->realpath($source) ?: $source;
$real_destination = $this
->realpath($destination) ?: $destination;
if (!@rename($real_source, $real_destination)) {
if (!@copy($real_source, $real_destination)) {
$this->logger
->error("The specified file '%source' could not be moved to '%destination'.", [
'%source' => $source,
'%destination' => $destination,
]);
throw new FileWriteException("The specified file '{$source}' could not be moved to '{$destination}'.");
}
if (!@unlink($real_source)) {
$this->logger
->error("The source file '%source' could not be unlinked after copying to '%destination'.", [
'%source' => $source,
'%destination' => $destination,
]);
throw new FileException("The source file '{$source}' could not be unlinked after copying to '{$destination}'.");
}
}
$this
->chmod($destination);
return $destination;
}
protected function prepareDestination($source, &$destination, $replace) {
$original_source = $source;
if (!file_exists($source)) {
if (($realpath = $this
->realpath($original_source)) !== FALSE) {
$this->logger
->error("File '%original_source' ('%realpath') could not be copied because it does not exist.", [
'%original_source' => $original_source,
'%realpath' => $realpath,
]);
throw new FileNotExistsException("File '{$original_source}' ('{$realpath}') could not be copied because it does not exist.");
}
else {
$this->logger
->error("File '%original_source' could not be copied because it does not exist.", [
'%original_source' => $original_source,
]);
throw new FileNotExistsException("File '{$original_source}' could not be copied because it does not exist.");
}
}
if ($this
->prepareDirectory($destination)) {
$destination = $this->streamWrapperManager
->normalizeUri($destination . '/' . $this
->basename($source));
}
else {
$dirname = $this
->dirname($destination);
if (!$this
->prepareDirectory($dirname)) {
$this->logger
->error("The specified file '%original_source' could not be copied because the destination directory is not properly configured. This may be caused by a problem with file or directory permissions.", [
'%original_source' => $original_source,
]);
throw new DirectoryNotReadyException("The specified file '{$original_source}' could not be copied because the destination directory is not properly configured. This may be caused by a problem with file or directory permissions.");
}
}
$destination = $this
->getDestinationFilename($destination, $replace);
if ($destination === FALSE) {
$this->logger
->error("File '%original_source' could not be copied because a file by that name already exists in the destination directory ('%destination').", [
'%original_source' => $original_source,
'%destination' => $destination,
]);
throw new FileExistsException("File '{$original_source}' could not be copied because a file by that name already exists in the destination directory ('{$destination}').");
}
$real_source = $this
->realpath($source);
$real_destination = $this
->realpath($destination);
if ($source == $destination || $real_source !== FALSE && $real_source == $real_destination) {
$this->logger
->error("File '%source' could not be copied because it would overwrite itself.", [
'%source' => $source,
]);
throw new FileException("File '{$source}' could not be copied because it would overwrite itself.");
}
}
public function saveData($data, $destination, $replace = self::EXISTS_RENAME) {
$temp_name = $this
->tempnam('temporary://', 'file');
if (file_put_contents($temp_name, $data) === FALSE) {
$this->logger
->error("Temporary file '%temp_name' could not be created.", [
'%temp_name' => $temp_name,
]);
throw new FileWriteException("Temporary file '{$temp_name}' could not be created.");
}
return $this
->move($temp_name, $destination, $replace);
}
public function prepareDirectory(&$directory, $options = self::MODIFY_PERMISSIONS) {
if (!$this->streamWrapperManager
->isValidUri($directory)) {
$directory = rtrim($directory, '/\\');
}
if (!is_dir($directory)) {
if (!($options & static::CREATE_DIRECTORY)) {
return FALSE;
}
$success = @$this
->mkdir($directory, NULL, TRUE);
if ($success) {
return TRUE;
}
if (!is_dir($directory)) {
return FALSE;
}
}
$writable = is_writable($directory);
if (!$writable && $options & static::MODIFY_PERMISSIONS) {
return $this
->chmod($directory);
}
return $writable;
}
public function getDestinationFilename($destination, $replace) {
$basename = $this
->basename($destination);
if (!Unicode::validateUtf8($basename)) {
throw new FileException(sprintf("Invalid filename '%s'", $basename));
}
if (file_exists($destination)) {
switch ($replace) {
case FileSystemInterface::EXISTS_REPLACE:
break;
case FileSystemInterface::EXISTS_RENAME:
$directory = $this
->dirname($destination);
$destination = $this
->createFilename($basename, $directory);
break;
case FileSystemInterface::EXISTS_ERROR:
return FALSE;
}
}
return $destination;
}
public function createFilename($basename, $directory) {
$original = $basename;
$basename = preg_replace('/[\\x00-\\x1F]/u', '_', $basename);
if (preg_last_error() !== PREG_NO_ERROR) {
throw new FileException(sprintf("Invalid filename '%s'", $original));
}
if (substr(PHP_OS, 0, 3) == 'WIN') {
$basename = str_replace([
':',
'*',
'?',
'"',
'<',
'>',
'|',
], '_', $basename);
}
if (substr($directory, -1) == '/') {
$separator = '';
}
else {
$separator = '/';
}
$destination = $directory . $separator . $basename;
if (file_exists($destination)) {
$pos = strrpos($basename, '.');
if ($pos !== FALSE) {
$name = substr($basename, 0, $pos);
$ext = substr($basename, $pos);
}
else {
$name = $basename;
$ext = '';
}
$counter = 0;
do {
$destination = $directory . $separator . $name . '_' . $counter++ . $ext;
} while (file_exists($destination));
}
return $destination;
}
public function getTempDirectory() {
$temporary_directory = $this->settings
->get('file_temp_path');
if (!empty($temporary_directory)) {
return $temporary_directory;
}
if (\Drupal::hasContainer()) {
$temporary_directory = \Drupal::config('system.file')
->get('path.temporary');
if (!empty($temporary_directory)) {
@trigger_error("The 'system.file' config 'path.temporary' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Set 'file_temp_path' in settings.php instead. See https://www.drupal.org/node/3039255", E_USER_DEPRECATED);
return $temporary_directory;
}
}
$temporary_directory = FileSystemComponent::getOsTemporaryDirectory();
if (empty($temporary_directory)) {
$temporary_directory = PublicStream::basePath() . '/tmp';
$temporary_directory = str_replace('\\', '/', $temporary_directory);
}
return $temporary_directory;
}
public function scanDirectory($dir, $mask, array $options = []) {
$options += [
'callback' => 0,
'recurse' => TRUE,
'key' => 'uri',
'min_depth' => 0,
];
$dir = $this->streamWrapperManager
->normalizeUri($dir);
if (!is_dir($dir)) {
throw new NotRegularDirectoryException("{$dir} is not a directory.");
}
if (!isset($options['nomask'])) {
$ignore_directories = $this->settings
->get('file_scan_ignore_directories', []);
array_walk($ignore_directories, function (&$value) {
$value = preg_quote($value, '/');
});
$options['nomask'] = '/^' . implode('|', $ignore_directories) . '$/';
}
$options['key'] = in_array($options['key'], [
'uri',
'filename',
'name',
]) ? $options['key'] : 'uri';
return $this
->doScanDirectory($dir, $mask, $options);
}
protected function doScanDirectory($dir, $mask, array $options = [], $depth = 0) {
$files = [];
if ($handle = @opendir($dir)) {
while (FALSE !== ($filename = readdir($handle))) {
if ($filename[0] != '.' && !preg_match($options['nomask'], $filename)) {
if (substr($dir, -1) == '/') {
$uri = "{$dir}{$filename}";
}
else {
$uri = "{$dir}/{$filename}";
}
if ($options['recurse'] && is_dir($uri)) {
$files = array_merge($this
->doScanDirectory($uri, $mask, $options, $depth + 1), $files);
}
elseif ($depth >= $options['min_depth'] && preg_match($mask, $filename)) {
$file = new \stdClass();
$file->uri = $uri;
$file->filename = $filename;
$file->name = pathinfo($filename, PATHINFO_FILENAME);
$key = $options['key'];
$files[$file->{$key}] = $file;
if ($options['callback']) {
$options['callback']($uri);
}
}
}
}
closedir($handle);
}
else {
$this->logger
->error('@dir can not be opened', [
'@dir' => $dir,
]);
}
return $files;
}
}