diff --git a/core/core.services.yml b/core/core.services.yml index 6ab79570bad8f135801d58f8b40933a7c8fa08dc..29a734ae62246191160f24290693b305bb7130e9 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -189,6 +189,9 @@ services: factory_class: Drupal\Core\Database\Database factory_method: getConnection arguments: [default] + file_system: + class: Drupal\Core\File\FileSystem + arguments: ['@stream_wrapper_manager', '@settings', '@logger.channel.file'] form_builder: class: Drupal\Core\Form\FormBuilder arguments: ['@form_validator', '@form_submitter', '@form_cache', '@module_handler', '@event_dispatcher', '@request_stack', '@class_resolver', '@theme.manager', '@?csrf_token'] @@ -236,6 +239,11 @@ services: logger.channel.cron: parent: logger.channel_base arguments: ['cron'] + logger.channel.file: + class: Drupal\Core\Logger\LoggerChannel + factory_method: get + factory_service: logger.factory + arguments: ['file'] logger.channel.form: parent: logger.channel_base arguments: ['form'] diff --git a/core/includes/file.inc b/core/includes/file.inc index d47fbc44f054c1930a4ae6311c39b155c1bc9a6e..034a997459d4eeccb633865eba3899db18ac02bc 100644 --- a/core/includes/file.inc +++ b/core/includes/file.inc @@ -10,20 +10,26 @@ use Drupal\Component\PhpStorage\FileStorage; use Drupal\Component\Utility\Bytes; use Drupal\Component\Utility\String; -use Drupal\Core\Site\Settings; +use Drupal\Core\File\FileSystem; use Drupal\Core\StreamWrapper\PublicStream; use Drupal\Core\StreamWrapper\StreamWrapperInterface; use Drupal\Core\StreamWrapper\PrivateStream; /** * Default mode for new directories. See drupal_chmod(). + * + * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0. + * Use \Drupal\Core\File\FileSystem::CHMOD_DIRECTORY. */ -const FILE_CHMOD_DIRECTORY = 0775; +const FILE_CHMOD_DIRECTORY = FileSystem::CHMOD_DIRECTORY; /** * Default mode for new files. See drupal_chmod(). + * + * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0. + * Use \Drupal\Core\File\FileSystem::CHMOD_FILE. */ -const FILE_CHMOD_FILE = 0664; +const FILE_CHMOD_FILE = FileSystem::CHMOD_FILE; /** * @defgroup file File interface @@ -129,40 +135,21 @@ function file_stream_wrapper_get_class($scheme) { /** * Returns the scheme of a URI (e.g. a stream). * - * @param string $uri - * A stream, referenced as "scheme://target" or "data:target". - * - * @return string - * A string containing the name of the scheme, or FALSE if none. For example, - * the URI "public://example.txt" would return "public". - * - * @see file_uri_target() + * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0. + * Use \Drupal\Core\File\FileSystem::uriScheme(). */ function file_uri_scheme($uri) { - if (preg_match('/^([\w\-]+):\/\/|^(data):/', $uri, $matches)) { - // The scheme will always be the last element in the matches array. - return array_pop($matches); - } - - return FALSE; + return \Drupal::service('file_system')->uriScheme($uri); } /** * Checks that the scheme of a stream URI is valid. * - * Confirms that there is a registered stream handler for the provided scheme - * and that it is callable. This is useful if you want to confirm a valid - * scheme without creating a new instance of the registered handler. - * - * @param string $scheme - * A URI scheme, a stream is referenced as "scheme://target". - * - * @return bool - * Returns TRUE if the string is the name of a validated stream, - * or FALSE if the scheme does not have a registered handler. + * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0. + * Use \Drupal\Core\File\FileSystem::validScheme(). */ function file_stream_wrapper_valid_scheme($scheme) { - return $scheme && class_exists(file_stream_wrapper_get_class($scheme)); + return \Drupal::service('file_system')->validScheme($scheme); } @@ -957,38 +944,11 @@ function file_unmanaged_delete_recursive($path, $callback = NULL) { /** * Moves an uploaded file to a new location. * - * PHP's move_uploaded_file() does not properly support streams if open_basedir - * is enabled, so this function fills that gap. - * - * Compatibility: normal paths and stream wrappers. - * - * @param $filename - * The filename of the uploaded file. - * @param $uri - * A string containing the destination URI of the file. - * - * @return - * TRUE on success, or FALSE on failure. - * - * @see move_uploaded_file() - * @see http://drupal.org/node/515192 - * @ingroup php_wrappers + * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0. + * Use \Drupal\Core\File\FileSystem::moveUploadedFile(). */ function drupal_move_uploaded_file($filename, $uri) { - $result = @move_uploaded_file($filename, $uri); - // PHP's move_uploaded_file() does not properly support streams if - // open_basedir is enabled so if the move failed, try finding a real path and - // retry the move operation. - if (!$result) { - if ($realpath = drupal_realpath($uri)) { - $result = move_uploaded_file($filename, $realpath); - } - else { - $result = move_uploaded_file($filename, $uri); - } - } - - return $result; + return \Drupal::service('file_system')->moveUploadedFile($filename, $uri); } /** @@ -1179,338 +1139,82 @@ function file_get_mimetype($uri, $mapping = NULL) { /** * Sets the permissions on a file or directory. * - * This function will use the file_chmod_directory and - * file_chmod_file settings for the default modes for directories - * and uploaded/generated files. By default these will give everyone read access - * so that users accessing the files with a user account without the webserver - * group (e.g. via FTP) can read these files, and give group write permissions - * so webserver group members (e.g. a vhost account) can alter files uploaded - * and owned by the webserver. - * - * PHP's chmod does not support stream wrappers so we use our wrapper - * implementation which interfaces with chmod() by default. Contrib wrappers - * may override this behavior in their implementations as needed. - * - * @param $uri - * A string containing a URI file, or directory path. - * @param $mode - * Integer value for the permissions. Consult PHP chmod() documentation for - * more information. - * - * @return bool - * TRUE for success, FALSE in the event of an error. - * - * @ingroup php_wrappers + * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0. + * Use \Drupal\Core\File\FileSystem::chmod(). */ function drupal_chmod($uri, $mode = NULL) { - if (!isset($mode)) { - if (is_dir($uri)) { - $mode = Settings::get('file_chmod_directory', FILE_CHMOD_DIRECTORY); - } - else { - $mode = Settings::get('file_chmod_file', FILE_CHMOD_FILE); - } - } - - if (@chmod($uri, $mode)) { - return TRUE; - } - - \Drupal::logger('file')->error('The file permissions could not be set on %uri.', array('%uri' => $uri)); - return FALSE; + return \Drupal::service('file_system')->chmod($uri, $mode); } /** * Deletes a file. * - * PHP's unlink() is broken on Windows, as it can fail to remove a file - * when it has a read-only flag set. - * - * @param $uri - * A URI or pathname. - * @param $context - * Refer to http://php.net/manual/ref.stream.php - * - * @return - * Boolean TRUE on success, or FALSE on failure. - * - * @see unlink() - * @ingroup php_wrappers + * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0. + * Use \Drupal\Core\File\FileSystem::unlink(). */ function drupal_unlink($uri, $context = NULL) { - $scheme = file_uri_scheme($uri); - if (!file_stream_wrapper_valid_scheme($scheme) && (substr(PHP_OS, 0, 3) == 'WIN')) { - chmod($uri, 0600); - } - if ($context) { - return unlink($uri, $context); - } - else { - return unlink($uri); - } + return \Drupal::service('file_system')->unlink($uri, $context); } /** * Resolves the absolute filepath of a local URI or filepath. * - * The use of drupal_realpath() is discouraged, because it does not work for - * remote URIs. Except in rare cases, URIs should not be manually resolved. - * - * Only use this function if you know that the stream wrapper in the URI uses - * the local file system, and you need to pass an absolute path to a function - * that is incompatible with stream URIs. - * - * @param string $uri - * A stream wrapper URI or a filepath, possibly including one or more symbolic - * links. - * - * @return string|false - * The absolute local filepath (with no symbolic links), or FALSE on failure. - * - * @see \Drupal\Core\StreamWrapper\StreamWrapperInterface::realpath() - * @see http://php.net/manual/function.realpath.php - * @ingroup php_wrappers + * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0. + * Use \Drupal\Core\File\FileSystem::realpath(). */ function drupal_realpath($uri) { - // If this URI is a stream, pass it off to the appropriate stream wrapper. - // Otherwise, attempt PHP's realpath. This allows use of drupal_realpath even - // for unmanaged files outside of the stream wrapper interface. - if ($wrapper = file_stream_wrapper_get_instance_by_uri($uri)) { - return $wrapper->realpath(); - } - - return realpath($uri); + return \Drupal::service('file_system')->realpath($uri); } /** * Gets the name of the directory from a given path. * - * PHP's dirname() does not properly pass streams, so this function fills - * that gap. It is backwards compatible with normal paths and will use - * PHP's dirname() as a fallback. - * - * Compatibility: normal paths and stream wrappers. - * - * @param $uri - * A URI or path. - * - * @return - * A string containing the directory name. - * - * @see dirname() - * @see http://drupal.org/node/515192 - * @ingroup php_wrappers + * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0. + * Use \Drupal\Core\File\FileSystem::dirname(). */ function drupal_dirname($uri) { - $scheme = file_uri_scheme($uri); - - if (file_stream_wrapper_valid_scheme($scheme)) { - return file_stream_wrapper_get_instance_by_scheme($scheme)->dirname($uri); - } - else { - return dirname($uri); - } + return \Drupal::service('file_system')->dirname($uri); } /** * Gets the filename from a given path. * - * PHP's basename() does not properly support streams or filenames beginning - * with a non-US-ASCII character. - * - * @see http://bugs.php.net/bug.php?id=37738 - * @see basename() - * - * @ingroup php_wrappers + * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0. + * Use \Drupal\Core\File\FileSystem::basename(). */ function drupal_basename($uri, $suffix = NULL) { - $separators = '/'; - if (DIRECTORY_SEPARATOR != '/') { - // For Windows OS add special separator. - $separators .= DIRECTORY_SEPARATOR; - } - // Remove right-most slashes when $uri points to directory. - $uri = rtrim($uri, $separators); - // Returns the trailing part of the $uri starting after one of the directory - // separators. - $filename = preg_match('@[^' . preg_quote($separators, '@') . ']+$@', $uri, $matches) ? $matches[0] : ''; - // Cuts off a suffix from the filename. - if ($suffix) { - $filename = preg_replace('@' . preg_quote($suffix, '@') . '$@', '', $filename); - } - return $filename; + return \Drupal::service('file_system')->basename($uri, $suffix); } /** * Creates a directory, optionally creating missing components in the path to * the directory. * - * When PHP's mkdir() creates a directory, the requested mode is affected by the - * process's umask. This function overrides the umask and sets the mode - * explicitly for all directory components created. - * - * @param $uri - * A URI or pathname. - * @param $mode - * Mode given to created directories. Defaults to the directory mode - * configured in the Drupal installation. It must have a leading zero. - * @param $recursive - * Create directories recursively, defaults to FALSE. Cannot work with a mode - * which denies writing or execution to the owner of the process. - * @param $context - * Refer to http://php.net/manual/ref.stream.php - * - * @return - * Boolean TRUE on success, or FALSE on failure. - * - * @see mkdir() - * @see http://drupal.org/node/515192 - * @ingroup php_wrappers - * - * @todo Update with open_basedir compatible recursion logic from - * \Drupal\Component\PhpStorage\FileStorage::ensureDirectory(). + * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0. + * Use \Drupal\Core\File\FileSystem::mkdir(). */ function drupal_mkdir($uri, $mode = NULL, $recursive = FALSE, $context = NULL) { - if (!isset($mode)) { - $mode = Settings::get('file_chmod_directory', FILE_CHMOD_DIRECTORY); - } - - // If the URI has a scheme, don't override the umask - schemes can handle this - // issue in their own implementation. - if (file_uri_scheme($uri)) { - return _drupal_mkdir_call($uri, $mode, $recursive, $context); - } - - // If recursive, create each missing component of the parent directory - // individually and set the mode explicitly to override the umask. - if ($recursive) { - // Ensure the path is using DIRECTORY_SEPARATOR. - $uri = str_replace('/', DIRECTORY_SEPARATOR, $uri); - // Determine the components of the path. - $components = explode(DIRECTORY_SEPARATOR, $uri); - // If the filepath is absolute the first component will be empty as there - // will be nothing before the first slash. - if ($components[0] == '') { - $recursive_path = DIRECTORY_SEPARATOR; - // Get rid of the empty first component. - array_shift($components); - } - else { - $recursive_path = ''; - } - // Don't handle the top-level directory in this loop. - array_pop($components); - // Create each component if necessary. - foreach ($components as $component) { - $recursive_path .= $component; - - if (!file_exists($recursive_path)) { - if (!_drupal_mkdir_call($recursive_path, $mode, FALSE, $context)) { - return FALSE; - } - // Not necessary to use drupal_chmod() as there is no scheme. - if (!chmod($recursive_path, $mode)) { - return FALSE; - } - } - - $recursive_path .= DIRECTORY_SEPARATOR; - } - } - - // Do not check if the top-level directory already exists, as this condition - // must cause this function to fail. - if (!_drupal_mkdir_call($uri, $mode, FALSE, $context)) { - return FALSE; - } - // Not necessary to use drupal_chmod() as there is no scheme. - return chmod($uri, $mode); -} - -/** - * Helper function. Ensures we don't pass a NULL as a context resource to - * mkdir(). - * - * @see drupal_mkdir() - */ -function _drupal_mkdir_call($uri, $mode, $recursive, $context) { - if (is_null($context)) { - return mkdir($uri, $mode, $recursive); - } - else { - return mkdir($uri, $mode, $recursive, $context); - } + return \Drupal::service('file_system')->mkdir($uri, $mode, $recursive, $context); } /** * Removes a directory. * - * PHP's rmdir() is broken on Windows, as it can fail to remove a directory - * when it has a read-only flag set. - * - * @param $uri - * A URI or pathname. - * @param $context - * Refer to http://php.net/manual/ref.stream.php - * - * @return - * Boolean TRUE on success, or FALSE on failure. - * - * @see rmdir() - * @ingroup php_wrappers + * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0. + * Use \Drupal\Core\File\FileSystem::rmdir(). */ function drupal_rmdir($uri, $context = NULL) { - $scheme = file_uri_scheme($uri); - if (!file_stream_wrapper_valid_scheme($scheme) && (substr(PHP_OS, 0, 3) == 'WIN')) { - chmod($uri, 0700); - } - if ($context) { - return rmdir($uri, $context); - } - else { - return rmdir($uri); - } + return \Drupal::service('file_system')->rmdir($uri, $context); } /** * Creates a file with a unique filename in the specified directory. * - * PHP's tempnam() does not return a URI like we want. This function - * will return a URI if given a URI, or it will return a filepath if - * given a filepath. - * - * Compatibility: normal paths and stream wrappers. - * - * @param $directory - * The directory where the temporary filename will be created. - * @param $prefix - * The prefix of the generated temporary filename. - * Note: Windows uses only the first three characters of prefix. - * - * @return - * The new temporary filename, or FALSE on failure. - * - * @see tempnam() - * @see http://drupal.org/node/515192 - * @ingroup php_wrappers + * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0. + * Use \Drupal\Core\File\FileSystem::tempnam(). */ function drupal_tempnam($directory, $prefix) { - $scheme = file_uri_scheme($directory); - - if (file_stream_wrapper_valid_scheme($scheme)) { - $wrapper = file_stream_wrapper_get_instance_by_scheme($scheme); - - if ($filename = tempnam($wrapper->getDirectoryPath(), $prefix)) { - return $scheme . '://' . drupal_basename($filename); - } - else { - return FALSE; - } - } - else { - // Handle as a normal tempnam() call. - return tempnam($directory, $prefix); - } + return \Drupal::service('file_system')->tempnam($directory, $prefix); } /** diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index 4191cdf001914f30ac37964fecb1ec5a033fcfe5..f98c64d53bf61785c463a88d2f70fc73ec21dc25 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -14,6 +14,7 @@ use Drupal\Core\Installer\InstallerKernel; use Drupal\Core\Language\Language; use Drupal\Core\Language\LanguageManager; +use Drupal\Core\Logger\LoggerChannelFactory; use Drupal\Core\Site\Settings; use Drupal\Core\StringTranslation\Translator\FileTranslation; use Drupal\Core\Extension\ExtensionDiscovery; @@ -343,8 +344,12 @@ function install_begin_request($class_loader, &$install_state) { // Register the stream wrapper manager. $container ->register('stream_wrapper_manager', 'Drupal\Core\StreamWrapper\StreamWrapperManager') - ->addArgument(new Reference('module_handler')) ->addMethodCall('setContainer', array(new Reference('service_container'))); + $container + ->register('file_system', 'Drupal\Core\File\FileSystem') + ->addArgument(new Reference('stream_wrapper_manager')) + ->addArgument(Settings::getInstance()) + ->addArgument((new LoggerChannelFactory())->get('file')); \Drupal::setContainer($container); diff --git a/core/lib/Drupal/Core/File/FileSystem.php b/core/lib/Drupal/Core/File/FileSystem.php new file mode 100644 index 0000000000000000000000000000000000000000..aedfdf6faaf820294728e608ebbdb6c1b4e9ea29 --- /dev/null +++ b/core/lib/Drupal/Core/File/FileSystem.php @@ -0,0 +1,307 @@ +<?php + +/** + * @file + * Contains \Drupal\Core\File\FileSystem. + */ + +namespace Drupal\Core\File; + +use Drupal\Core\Site\Settings; +use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface; +use Psr\Log\LoggerInterface; + +/** + * Provides helpers to operate on files and stream wrappers. + */ +class FileSystem implements FileSystemInterface { + + /** + * Default mode for new directories. See self::chmod(). + */ + const CHMOD_DIRECTORY = 0775; + + /** + * Default mode for new files. See self::chmod(). + */ + const CHMOD_FILE = 0664; + + /** + * The site settings. + * + * @var \Drupal\Core\Site\Settings + */ + protected $settings; + + /** + * The file logger channel. + * + * @var \Psr\Log\LoggerInterface + */ + protected $logger; + + /** + * The stream wrapper manager. + * + * @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface + */ + protected $streamWrapperManager; + + /** + * Constructs a new FileSystem. + * + * @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager + * The stream wrapper manager. + * @param \Drupal\Core\Site\Settings $settings + * The site settings. + * @param \Psr\Log\LoggerInterface $logger + * The file logger channel. + */ + public function __construct(StreamWrapperManagerInterface $stream_wrapper_manager, Settings $settings, LoggerInterface $logger) { + $this->streamWrapperManager = $stream_wrapper_manager; + $this->settings = $settings; + $this->logger = $logger; + } + + /** + * {@inheritdoc} + */ + public function moveUploadedFile($filename, $uri) { + $result = @move_uploaded_file($filename, $uri); + // PHP's move_uploaded_file() does not properly support streams if + // open_basedir is enabled so if the move failed, try finding a real path + // and retry the move operation. + if (!$result) { + if ($realpath = $this->realpath($uri)) { + $result = move_uploaded_file($filename, $realpath); + } + else { + $result = move_uploaded_file($filename, $uri); + } + } + + return $result; + } + + /** + * {@inheritdoc} + */ + 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.', array('%uri' => $uri)); + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function unlink($uri, $context = NULL) { + $scheme = $this->uriScheme($uri); + if (!$this->validScheme($scheme) && (substr(PHP_OS, 0, 3) == 'WIN')) { + chmod($uri, 0600); + } + if ($context) { + return unlink($uri, $context); + } + else { + return unlink($uri); + } + } + + /** + * {@inheritdoc} + */ + public function realpath($uri) { + // If this URI is a stream, pass it off to the appropriate stream wrapper. + // Otherwise, attempt PHP's realpath. This allows use of this method even + // for unmanaged files outside of the stream wrapper interface. + if ($wrapper = $this->streamWrapperManager->getViaUri($uri)) { + return $wrapper->realpath(); + } + + return realpath($uri); + } + + /** + * {@inheritdoc} + */ + public function dirname($uri) { + $scheme = $this->uriScheme($uri); + + if ($this->validScheme($scheme)) { + return $this->streamWrapperManager->getViaScheme($scheme)->dirname($uri); + } + else { + return dirname($uri); + } + } + + /** + * {@inheritdoc} + */ + public function basename($uri, $suffix = NULL) { + $separators = '/'; + if (DIRECTORY_SEPARATOR != '/') { + // For Windows OS add special separator. + $separators .= DIRECTORY_SEPARATOR; + } + // Remove right-most slashes when $uri points to directory. + $uri = rtrim($uri, $separators); + // Returns the trailing part of the $uri starting after one of the directory + // separators. + $filename = preg_match('@[^' . preg_quote($separators, '@') . ']+$@', $uri, $matches) ? $matches[0] : ''; + // Cuts off a suffix from the filename. + if ($suffix) { + $filename = preg_replace('@' . preg_quote($suffix, '@') . '$@', '', $filename); + } + return $filename; + } + + /** + * {@inheritdoc} + */ + public function mkdir($uri, $mode = NULL, $recursive = FALSE, $context = NULL) { + if (!isset($mode)) { + $mode = $this->settings->get('file_chmod_directory', static::CHMOD_DIRECTORY); + } + + // If the URI has a scheme, don't override the umask - schemes can handle + // this issue in their own implementation. + if ($this->uriScheme($uri)) { + return $this->mkdirCall($uri, $mode, $recursive, $context); + } + + // If recursive, create each missing component of the parent directory + // individually and set the mode explicitly to override the umask. + if ($recursive) { + // Ensure the path is using DIRECTORY_SEPARATOR. + $uri = str_replace('/', DIRECTORY_SEPARATOR, $uri); + // Determine the components of the path. + $components = explode(DIRECTORY_SEPARATOR, $uri); + // If the filepath is absolute the first component will be empty as there + // will be nothing before the first slash. + if ($components[0] == '') { + $recursive_path = DIRECTORY_SEPARATOR; + // Get rid of the empty first component. + array_shift($components); + } + else { + $recursive_path = ''; + } + // Don't handle the top-level directory in this loop. + array_pop($components); + // Create each component if necessary. + foreach ($components as $component) { + $recursive_path .= $component; + + if (!file_exists($recursive_path)) { + if (!$this->mkdirCall($recursive_path, $mode, FALSE, $context)) { + return FALSE; + } + // Not necessary to use self::chmod() as there is no scheme. + if (!chmod($recursive_path, $mode)) { + return FALSE; + } + } + + $recursive_path .= DIRECTORY_SEPARATOR; + } + } + + // Do not check if the top-level directory already exists, as this condition + // must cause this function to fail. + if (!$this->mkdirCall($uri, $mode, FALSE, $context)) { + return FALSE; + } + // Not necessary to use self::chmod() as there is no scheme. + return chmod($uri, $mode); + } + + /** + * Helper function. Ensures we don't pass a NULL as a context resource to + * mkdir(). + * + * @see self::mkdir() + */ + protected function mkdirCall($uri, $mode, $recursive, $context) { + if (is_null($context)) { + return mkdir($uri, $mode, $recursive); + } + else { + return mkdir($uri, $mode, $recursive, $context); + } + } + + /** + * {@inheritdoc} + */ + public function rmdir($uri, $context = NULL) { + $scheme = $this->uriScheme($uri); + if (!$this->validScheme($scheme) && (substr(PHP_OS, 0, 3) == 'WIN')) { + chmod($uri, 0700); + } + if ($context) { + return rmdir($uri, $context); + } + else { + return rmdir($uri); + } + } + + /** + * {@inheritdoc} + */ + public function tempnam($directory, $prefix) { + $scheme = $this->uriScheme($directory); + + if ($this->validScheme($scheme)) { + $wrapper = $this->streamWrapperManager->getViaScheme($scheme); + + if ($filename = tempnam($wrapper->getDirectoryPath(), $prefix)) { + return $scheme . '://' . static::basename($filename); + } + else { + return FALSE; + } + } + else { + // Handle as a normal tempnam() call. + return tempnam($directory, $prefix); + } + } + + /** + * {@inheritdoc} + */ + public function uriScheme($uri) { + if (preg_match('/^([\w\-]+):\/\/|^(data):/', $uri, $matches)) { + // The scheme will always be the last element in the matches array. + return array_pop($matches); + } + + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function validScheme($scheme) { + if (!$scheme) { + return FALSE; + } + return class_exists($this->streamWrapperManager->getClass($scheme)); + } + +} diff --git a/core/lib/Drupal/Core/File/FileSystemInterface.php b/core/lib/Drupal/Core/File/FileSystemInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..57009039c1d01552d8e6242782930ba047246fdf --- /dev/null +++ b/core/lib/Drupal/Core/File/FileSystemInterface.php @@ -0,0 +1,245 @@ +<?php + +/** + * @file + * Contains \Drupal\Core\File\FileSystemInterface. + */ + +namespace Drupal\Core\File; + +/** + * Provides an interface for helpers that operate on files and stream wrappers. + */ +interface FileSystemInterface { + + /** + * Moves an uploaded file to a new location. + * + * PHP's move_uploaded_file() does not properly support streams if + * open_basedir is enabled, so this function fills that gap. + * + * Compatibility: normal paths and stream wrappers. + * + * @param string $filename + * The filename of the uploaded file. + * @param string $uri + * A string containing the destination URI of the file. + * + * @return bool + * TRUE on success, or FALSE on failure. + * + * @see move_uploaded_file() + * @see http://drupal.org/node/515192 + * @ingroup php_wrappers + */ + public function moveUploadedFile($filename, $uri); + + /** + * Sets the permissions on a file or directory. + * + * This function will use the file_chmod_directory and + * file_chmod_file settings for the default modes for directories + * and uploaded/generated files. By default these will give everyone read + * access so that users accessing the files with a user account without the + * webserver group (e.g. via FTP) can read these files, and give group write + * permissions so webserver group members (e.g. a vhost account) can alter + * files uploaded and owned by the webserver. + * + * PHP's chmod does not support stream wrappers so we use our wrapper + * implementation which interfaces with chmod() by default. Contrib wrappers + * may override this behavior in their implementations as needed. + * + * @param string $uri + * A string containing a URI file, or directory path. + * @param int $mode + * Integer value for the permissions. Consult PHP chmod() documentation for + * more information. + * + * @return bool + * TRUE for success, FALSE in the event of an error. + * + * @ingroup php_wrappers + */ + public function chmod($uri, $mode = NULL); + + /** + * Deletes a file. + * + * PHP's unlink() is broken on Windows, as it can fail to remove a file when + * it has a read-only flag set. + * + * @param string $uri + * A URI or pathname. + * @param resource $context + * Refer to http://php.net/manual/ref.stream.php + * + * @return bool + * Boolean TRUE on success, or FALSE on failure. + * + * @see unlink() + * @ingroup php_wrappers + */ + public function unlink($uri, $context = NULL); + + /** + * Resolves the absolute filepath of a local URI or filepath. + * + * The use of this method is discouraged, because it does not work for + * remote URIs. Except in rare cases, URIs should not be manually resolved. + * + * Only use this function if you know that the stream wrapper in the URI uses + * the local file system, and you need to pass an absolute path to a function + * that is incompatible with stream URIs. + * + * @param string $uri + * A stream wrapper URI or a filepath, possibly including one or more + * symbolic links. + * + * @return string|false + * The absolute local filepath (with no symbolic links) or FALSE on failure. + * + * @see \Drupal\Core\StreamWrapper\StreamWrapperInterface::realpath() + * @see http://php.net/manual/function.realpath.php + * @ingroup php_wrappers + */ + public function realpath($uri); + + /** + * Gets the name of the directory from a given path. + * + * PHP's dirname() does not properly pass streams, so this function fills that + * gap. It is backwards compatible with normal paths and will use PHP's + * dirname() as a fallback. + * + * Compatibility: normal paths and stream wrappers. + * + * @param string $uri + * A URI or path. + * + * @return string + * A string containing the directory name. + * + * @see dirname() + * @see http://drupal.org/node/515192 + * @ingroup php_wrappers + */ + public function dirname($uri); + + /** + * Gets the filename from a given path. + * + * PHP's basename() does not properly support streams or filenames beginning + * with a non-US-ASCII character. + * + * @see http://bugs.php.net/bug.php?id=37738 + * @see basename() + * + * @ingroup php_wrappers + */ + public function basename($uri, $suffix = NULL); + + /** + * Creates a directory, optionally creating missing components in the path to + * the directory. + * + * When PHP's mkdir() creates a directory, the requested mode is affected by + * the process's umask. This function overrides the umask and sets the mode + * explicitly for all directory components created. + * + * @param string $uri + * A URI or pathname. + * @param int $mode + * Mode given to created directories. Defaults to the directory mode + * configured in the Drupal installation. It must have a leading zero. + * @param bool $recursive + * Create directories recursively, defaults to FALSE. Cannot work with a + * mode which denies writing or execution to the owner of the process. + * @param resource $context + * Refer to http://php.net/manual/ref.stream.php + * + * @return bool + * Boolean TRUE on success, or FALSE on failure. + * + * @see mkdir() + * @see http://drupal.org/node/515192 + * @ingroup php_wrappers + * + * @todo Update with open_basedir compatible recursion logic from + * \Drupal\Component\PhpStorage\FileStorage::ensureDirectory(). + */ + public function mkdir($uri, $mode = NULL, $recursive = FALSE, $context = NULL); + + /** + * Removes a directory. + * + * PHP's rmdir() is broken on Windows, as it can fail to remove a directory + * when it has a read-only flag set. + * + * @param string $uri + * A URI or pathname. + * @param resource $context + * Refer to http://php.net/manual/ref.stream.php + * + * @return bool + * Boolean TRUE on success, or FALSE on failure. + * + * @see rmdir() + * @ingroup php_wrappers + */ + public function rmdir($uri, $context = NULL); + + /** + * Creates a file with a unique filename in the specified directory. + * + * PHP's tempnam() does not return a URI like we want. This function will + * return a URI if given a URI, or it will return a filepath if given a + * filepath. + * + * Compatibility: normal paths and stream wrappers. + * + * @param string $directory + * The directory where the temporary filename will be created. + * @param string $prefix + * The prefix of the generated temporary filename. + * Note: Windows uses only the first three characters of prefix. + * + * @return string|bool + * The new temporary filename, or FALSE on failure. + * + * @see tempnam() + * @see http://drupal.org/node/515192 + * @ingroup php_wrappers + */ + public function tempnam($directory, $prefix); + + /** + * Returns the scheme of a URI (e.g. a stream). + * + * @param string $uri + * A stream, referenced as "scheme://target" or "data:target". + * + * @return string|bool + * A string containing the name of the scheme, or FALSE if none. For + * example, the URI "public://example.txt" would return "public". + * + * @see file_uri_target() + */ + public function uriScheme($uri); + + /** + * Checks that the scheme of a stream URI is valid. + * + * Confirms that there is a registered stream handler for the provided scheme + * and that it is callable. This is useful if you want to confirm a valid + * scheme without creating a new instance of the registered handler. + * + * @param string $scheme + * A URI scheme, a stream is referenced as "scheme://target". + * + * @return bool + * Returns TRUE if the string is the name of a validated stream, or FALSE if + * the scheme does not have a registered handler. + */ + public function validScheme($scheme); + +} diff --git a/core/lib/Drupal/Core/StreamWrapper/StreamWrapperManager.php b/core/lib/Drupal/Core/StreamWrapper/StreamWrapperManager.php index 101980d4055b4f7e208bcccda9e7566b338864d3..dc9f4b6c4a21fa701d71d795744fb7d3d24250fb 100644 --- a/core/lib/Drupal/Core/StreamWrapper/StreamWrapperManager.php +++ b/core/lib/Drupal/Core/StreamWrapper/StreamWrapperManager.php @@ -7,7 +7,6 @@ namespace Drupal\Core\StreamWrapper; -use Drupal\Core\Extension\ModuleHandlerInterface; use Symfony\Component\DependencyInjection\ContainerAware; /** @@ -16,7 +15,7 @@ * @see file_get_stream_wrappers() * @see \Drupal\Core\StreamWrapper\StreamWrapperInterface */ -class StreamWrapperManager extends ContainerAware { +class StreamWrapperManager extends ContainerAware implements StreamWrapperManagerInterface { /** * Contains stream wrapper info. @@ -48,57 +47,7 @@ class StreamWrapperManager extends ContainerAware { protected $wrappers = array(); /** - * Provides Drupal stream wrapper registry. - * - * A stream wrapper is an abstraction of a file system that allows Drupal to - * use the same set of methods to access both local files and remote - * resources. - * - * Provide a facility for managing and querying user-defined stream wrappers - * in PHP. PHP's internal stream_get_wrappers() doesn't return the class - * registered to handle a stream, which we need to be able to find the handler - * for class instantiation. - * - * If a module registers a scheme that is already registered with PHP, the - * existing scheme will be unregistered and replaced with the specified class. - * - * A stream is referenced as "scheme://target". - * - * The optional $filter parameter can be used to retrieve only the stream - * wrappers that are appropriate for particular usage. For example, this - * returns only stream wrappers that use local file storage: - * - * @code - * $local_stream_wrappers = file_get_stream_wrappers(StreamWrapperInterface::LOCAL); - * @endcode - * - * The $filter parameter can only filter to types containing a particular - * flag. In some cases, you may want to filter to types that do not contain a - * particular flag. For example, you may want to retrieve all stream wrappers - * that are not writable, or all stream wrappers that are not local. PHP's - * array_diff_key() function can be used to help with this. For example, this - * returns only stream wrappers that do not use local file storage: - * @code - * $remote_stream_wrappers = array_diff_key(file_get_stream_wrappers(StreamWrapperInterface::ALL), file_get_stream_wrappers(StreamWrapperInterface::LOCAL)); - * @endcode - * - * @param int $filter - * (Optional) Filters out all types except those with an on bit for each on - * bit in $filter. For example, if $filter is - * StreamWrapperInterface::WRITE_VISIBLE, which is equal to - * (StreamWrapperInterface::READ | StreamWrapperInterface::WRITE | - * StreamWrapperInterface::VISIBLE), then only stream wrappers with all - * three of these bits set are returned. Defaults to - * StreamWrapperInterface::ALL, which returns all registered stream - * wrappers. - * - * @return array - * An array keyed by scheme, with values containing an array of information - * about the stream wrapper, as returned by hook_stream_wrappers(). If - * $filter is omitted or set to StreamWrapperInterface::ALL, the entire - * Drupal stream wrapper registry is returned. Otherwise only the stream - * wrappers whose 'type' bitmask has an on bit for each bit specified in - * $filter are returned. + * {@inheritdoc} */ public function getWrappers($filter = StreamWrapperInterface::ALL) { if (isset($this->wrappers[$filter])) { @@ -120,20 +69,7 @@ public function getWrappers($filter = StreamWrapperInterface::ALL) { } /** - * Returns registered stream wrapper names. - * - * @param int $filter - * (Optional) Filters out all types except those with an on bit for each on - * bit in $filter. For example, if $filter is - * StreamWrapperInterface::WRITE_VISIBLE, which is equal to - * (StreamWrapperInterface::READ | StreamWrapperInterface::WRITE | - * StreamWrapperInterface::VISIBLE), then only stream wrappers with all - * three of these bits set are returned. Defaults to - * StreamWrapperInterface::ALL, which returns all registered stream - * wrappers. - * - * @return array - * Stream wrapper names, keyed by scheme. + * {@inheritdoc} */ public function getNames($filter = StreamWrapperInterface::ALL) { $names = array(); @@ -145,20 +81,7 @@ public function getNames($filter = StreamWrapperInterface::ALL) { } /** - * Returns registered stream wrapper descriptions. - * - * @param int $filter - * (Optional) Filters out all types except those with an on bit for each on - * bit in $filter. For example, if $filter is - * StreamWrapperInterface::WRITE_VISIBLE, which is equal to - * (StreamWrapperInterface::READ | StreamWrapperInterface::WRITE | - * StreamWrapperInterface::VISIBLE), then only stream wrappers with all - * three of these bits set are returned. Defaults to - * StreamWrapperInterface::ALL, which returns all registered stream - * wrappers. - * - * @return array - * Stream wrapper descriptions, keyed by scheme. + * {@inheritdoc} */ public function getDescriptions($filter = StreamWrapperInterface::ALL) { $descriptions = array(); @@ -170,26 +93,14 @@ public function getDescriptions($filter = StreamWrapperInterface::ALL) { } /** - * Returns a stream wrapper via scheme. - * - * @param string $scheme - * The scheme of the stream wrapper. - * - * @return \Drupal\Core\StreamWrapper\StreamWrapperInterface|bool - * A stream wrapper object, or false if the scheme is not available. + * {@inheritdoc} */ public function getViaScheme($scheme) { return $this->getWrapper($scheme, $scheme . '://'); } /** - * Returns a stream wrapper via URI. - * - * @param string $uri - * The URI of the stream wrapper. - * - * @return \Drupal\Core\StreamWrapper\StreamWrapperInterface|bool - * A stream wrapper object, or false if the scheme is not available. + * {@inheritdoc} */ public function getViaUri($uri) { $scheme = file_uri_scheme($uri); @@ -197,13 +108,7 @@ public function getViaUri($uri) { } /** - * Returns the stream wrapper class. - * - * @param string $scheme - * The stream wrapper scheme. - * - * @return string|bool - * The stream wrapper class, or false if the scheme does not exist. + * {@inheritdoc} */ public function getClass($scheme) { if (isset($this->info[$scheme])) { @@ -283,14 +188,7 @@ public function unregister() { } /** - * Registers stream wrapper with PHP. - * - * @param string $scheme - * The scheme of the stream wrapper. - * @param string $class - * The class of the stream wrapper. - * @param int $type - * The type of the stream wrapper. + * {@inheritdoc} */ public function registerWrapper($scheme, $class, $type) { if (in_array($scheme, stream_get_wrappers(), TRUE)) { diff --git a/core/lib/Drupal/Core/StreamWrapper/StreamWrapperManagerInterface.php b/core/lib/Drupal/Core/StreamWrapper/StreamWrapperManagerInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..34e5f814427ec24b265d7f9feafb9e0a622072a7 --- /dev/null +++ b/core/lib/Drupal/Core/StreamWrapper/StreamWrapperManagerInterface.php @@ -0,0 +1,159 @@ +<?php + +/** + * @file + * Contains \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface. + */ + +namespace Drupal\Core\StreamWrapper; + +/** + * Provides a StreamWrapper manager. + * + * @see file_get_stream_wrappers() + * @see \Drupal\Core\StreamWrapper\StreamWrapperInterface + */ +interface StreamWrapperManagerInterface { + + /** + * Provides Drupal stream wrapper registry. + * + * A stream wrapper is an abstraction of a file system that allows Drupal to + * use the same set of methods to access both local files and remote + * resources. + * + * Provide a facility for managing and querying user-defined stream wrappers + * in PHP. PHP's internal stream_get_wrappers() doesn't return the class + * registered to handle a stream, which we need to be able to find the + * handler + * for class instantiation. + * + * If a module registers a scheme that is already registered with PHP, the + * existing scheme will be unregistered and replaced with the specified + * class. + * + * A stream is referenced as "scheme://target". + * + * The optional $filter parameter can be used to retrieve only the stream + * wrappers that are appropriate for particular usage. For example, this + * returns only stream wrappers that use local file storage: + * + * @code + * $local_stream_wrappers = + * file_get_stream_wrappers(StreamWrapperInterface::LOCAL); + * @endcode + * + * The $filter parameter can only filter to types containing a particular + * flag. In some cases, you may want to filter to types that do not contain a + * particular flag. For example, you may want to retrieve all stream wrappers + * that are not writable, or all stream wrappers that are not local. PHP's + * array_diff_key() function can be used to help with this. For example, this + * returns only stream wrappers that do not use local file storage: + * @code + * $remote_stream_wrappers = + * array_diff_key(file_get_stream_wrappers(StreamWrapperInterface::ALL), + * file_get_stream_wrappers(StreamWrapperInterface::LOCAL)); + * @endcode + * + * @param int $filter + * (Optional) Filters out all types except those with an on bit for each on + * bit in $filter. For example, if $filter is + * StreamWrapperInterface::WRITE_VISIBLE, which is equal to + * (StreamWrapperInterface::READ | StreamWrapperInterface::WRITE | + * StreamWrapperInterface::VISIBLE), then only stream wrappers with all + * three of these bits set are returned. Defaults to + * StreamWrapperInterface::ALL, which returns all registered stream + * wrappers. + * + * @return array + * An array keyed by scheme, with values containing an array of information + * about the stream wrapper, as returned by hook_stream_wrappers(). If + * $filter is omitted or set to StreamWrapperInterface::ALL, the entire + * Drupal stream wrapper registry is returned. Otherwise only the stream + * wrappers whose 'type' bitmask has an on bit for each bit specified in + * $filter are returned. + */ + public function getWrappers($filter = StreamWrapperInterface::ALL); + + /** + * Returns registered stream wrapper names. + * + * @param int $filter + * (Optional) Filters out all types except those with an on bit for each on + * bit in $filter. For example, if $filter is + * StreamWrapperInterface::WRITE_VISIBLE, which is equal to + * (StreamWrapperInterface::READ | StreamWrapperInterface::WRITE | + * StreamWrapperInterface::VISIBLE), then only stream wrappers with all + * three of these bits set are returned. Defaults to + * StreamWrapperInterface::ALL, which returns all registered stream + * wrappers. + * + * @return array + * Stream wrapper names, keyed by scheme. + */ + public function getNames($filter = StreamWrapperInterface::ALL); + + /** + * Returns registered stream wrapper descriptions. + * + * @param int $filter + * (Optional) Filters out all types except those with an on bit for each on + * bit in $filter. For example, if $filter is + * StreamWrapperInterface::WRITE_VISIBLE, which is equal to + * (StreamWrapperInterface::READ | StreamWrapperInterface::WRITE | + * StreamWrapperInterface::VISIBLE), then only stream wrappers with all + * three of these bits set are returned. Defaults to + * StreamWrapperInterface::ALL, which returns all registered stream + * wrappers. + * + * @return array + * Stream wrapper descriptions, keyed by scheme. + */ + public function getDescriptions($filter = StreamWrapperInterface::ALL); + + /** + * Returns a stream wrapper via scheme. + * + * @param string $scheme + * The scheme of the stream wrapper. + * + * @return \Drupal\Core\StreamWrapper\StreamWrapperInterface|bool + * A stream wrapper object, or false if the scheme is not available. + */ + public function getViaScheme($scheme); + + /** + * Returns a stream wrapper via URI. + * + * @param string $uri + * The URI of the stream wrapper. + * + * @return \Drupal\Core\StreamWrapper\StreamWrapperInterface|bool + * A stream wrapper object, or false if the scheme is not available. + */ + public function getViaUri($uri); + + /** + * Returns the stream wrapper class. + * + * @param string $scheme + * The stream wrapper scheme. + * + * @return string|bool + * The stream wrapper class, or false if the scheme does not exist. + */ + public function getClass($scheme); + + /** + * Registers stream wrapper with PHP. + * + * @param string $scheme + * The scheme of the stream wrapper. + * @param string $class + * The class of the stream wrapper. + * @param int $type + * The type of the stream wrapper. + */ + public function registerWrapper($scheme, $class, $type); + +} diff --git a/core/modules/simpletest/src/KernelTestBase.php b/core/modules/simpletest/src/KernelTestBase.php index 71be96917704238a3963d31daab7ef594130f857..188af9c38caa58a308505d0dd57d03815d9eabcc 100644 --- a/core/modules/simpletest/src/KernelTestBase.php +++ b/core/modules/simpletest/src/KernelTestBase.php @@ -147,9 +147,6 @@ protected function setUp() { $this->settingsSet('container_yamls', [$testing_services_file]); } - // Create and set new configuration directories. - $this->prepareConfigDirectories(); - // Add this test class as a service provider. // @todo Remove the indirection; implement ServiceProviderInterface instead. $GLOBALS['conf']['container_service_providers']['TestServiceProvider'] = 'Drupal\simpletest\TestServiceProvider'; @@ -172,6 +169,9 @@ protected function setUp() { // method sets additional settings. new Settings($settings + Settings::getAll()); + // Create and set new configuration directories. + $this->prepareConfigDirectories(); + // Set the request scope. $this->container = $this->kernel->getContainer(); $this->container->get('request_stack')->push($request); diff --git a/core/modules/simpletest/src/TestBase.php b/core/modules/simpletest/src/TestBase.php index b4901af4314e3b742570032459b69a40750d1618..6ef3e942d4c34f40f9732285a7ccd9960d890215 100644 --- a/core/modules/simpletest/src/TestBase.php +++ b/core/modules/simpletest/src/TestBase.php @@ -1282,6 +1282,10 @@ private function restoreEnvironment() { // log to pick up any fatal errors. simpletest_log_read($this->testId, $this->databasePrefix, get_class($this)); + // Restore original dependency injection container. + $this->container = $this->originalContainer; + \Drupal::setContainer($this->originalContainer); + // Delete test site directory. file_unmanaged_delete_recursive($this->siteDirectory, array($this, 'filePreDeleteCallback')); @@ -1300,7 +1304,6 @@ private function restoreEnvironment() { new Settings($this->originalSettings); // Restore original statics and globals. - \Drupal::setContainer($this->originalContainer); $GLOBALS['config_directories'] = $this->originalConfigDirectories; // Re-initialize original stream wrappers of the parent site. diff --git a/core/modules/simpletest/src/WebTestBase.php b/core/modules/simpletest/src/WebTestBase.php index b24d762942e4bc97b63de02ba045cef04ea5d7ef..0b6b770adc4c67e9e5f21befc0107fb7385c36df 100644 --- a/core/modules/simpletest/src/WebTestBase.php +++ b/core/modules/simpletest/src/WebTestBase.php @@ -1005,12 +1005,27 @@ protected function installParameters() { // If we only have one db driver available, we cannot set the driver. include_once DRUPAL_ROOT . '/core/includes/install.inc'; - if (count(drupal_get_database_types()) == 1) { + if (count($this->getDatabaseTypes()) == 1) { unset($parameters['forms']['install_settings_form']['driver']); } return $parameters; } + /** + * Returns all supported database driver installer objects. + * + * This wraps drupal_get_database_types() for use without a current container. + * + * @return \Drupal\Core\Database\Install\Tasks[] + * An array of available database driver installer objects. + */ + protected function getDatabaseTypes() { + \Drupal::setContainer($this->originalContainer); + $database_types = drupal_get_database_types(); + \Drupal::setContainer(NULL); + return $database_types; + } + /** * Rewrites the settings.php file of the test site. * diff --git a/core/modules/system/src/Form/FileSystemForm.php b/core/modules/system/src/Form/FileSystemForm.php index 368f318d08b49d7cb26f24c45c414664a1b8a580..40bd93797a21f8951bc00fdb1dde304d13ab51be 100644 --- a/core/modules/system/src/Form/FileSystemForm.php +++ b/core/modules/system/src/Form/FileSystemForm.php @@ -15,7 +15,7 @@ use Drupal\Core\StreamWrapper\PublicStream; use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\StreamWrapper\StreamWrapperInterface; -use Drupal\Core\StreamWrapper\StreamWrapperManager; +use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -33,7 +33,7 @@ class FileSystemForm extends ConfigFormBase { /** * The stream wrapper manager. * - * @var \Drupal\Core\StreamWrapper\StreamWrapperManager + * @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface */ protected $streamWrapperManager; @@ -44,10 +44,10 @@ class FileSystemForm extends ConfigFormBase { * The factory for configuration objects. * @param \Drupal\Core\Datetime\DateFormatter $date_formatter * The date formatter service. - * @param \Drupal\Core\StreamWrapper\StreamWrapperManager $stream_wrapper_manager + * @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager * The stream wrapper manager. */ - public function __construct(ConfigFactoryInterface $config_factory, DateFormatter $date_formatter, StreamWrapperManager $stream_wrapper_manager) { + public function __construct(ConfigFactoryInterface $config_factory, DateFormatter $date_formatter, StreamWrapperManagerInterface $stream_wrapper_manager) { parent::__construct($config_factory); $this->dateFormatter = $date_formatter; $this->streamWrapperManager = $stream_wrapper_manager; diff --git a/core/modules/system/src/Tests/Bootstrap/GetFilenameUnitTest.php b/core/modules/system/src/Tests/Bootstrap/GetFilenameUnitTest.php index a7bcca74ace9e7039544655eb09bad97c4a52cca..9baf5cdfd96aff1910dd90b9d6f7da6bc5f57c73 100644 --- a/core/modules/system/src/Tests/Bootstrap/GetFilenameUnitTest.php +++ b/core/modules/system/src/Tests/Bootstrap/GetFilenameUnitTest.php @@ -16,12 +16,34 @@ */ class GetFilenameUnitTest extends KernelTestBase { + /** + * The container used by the test, moved out of the way. + * + * @var \Symfony\Component\DependencyInjection\ContainerInterface + */ + protected $previousContainer; + + /** + * {@inheritdoc} + */ protected function setUp() { parent::setUp(); + // Store the previous container. + $this->previousContainer = $this->container; $this->container = NULL; \Drupal::setContainer(NULL); } + /** + * {@inheritdoc} + */ + protected function tearDown() { + parent::tearDown(); + // Restore the previous container. + $this->container = $this->previousContainer; + \Drupal::setContainer($this->previousContainer); + } + /** * Tests that drupal_get_filename() works when the file is not in database. */ diff --git a/core/modules/system/src/Tests/DrupalKernel/DrupalKernelTest.php b/core/modules/system/src/Tests/DrupalKernel/DrupalKernelTest.php index af8ce27481af16cf67b8109ad820673c6e911d8a..892047810d62cc6e1e0cfe5036b14b41b2b22889 100644 --- a/core/modules/system/src/Tests/DrupalKernel/DrupalKernelTest.php +++ b/core/modules/system/src/Tests/DrupalKernel/DrupalKernelTest.php @@ -35,6 +35,15 @@ protected function setUp() { ))); } + /** + * {@inheritdoc} + */ + protected function prepareConfigDirectories() { + \Drupal::setContainer($this->originalContainer); + parent::prepareConfigDirectories(); + \Drupal::setContainer(NULL); + } + /** * Build a kernel for testings. * diff --git a/core/modules/system/src/Tests/File/UnmanagedCopyTest.php b/core/modules/system/src/Tests/File/UnmanagedCopyTest.php index 0169702ba157a617ced85046fec663999a0472da..f525c1d532528234100d5a21562c2a790a3c10f2 100644 --- a/core/modules/system/src/Tests/File/UnmanagedCopyTest.php +++ b/core/modules/system/src/Tests/File/UnmanagedCopyTest.php @@ -8,6 +8,7 @@ namespace Drupal\system\Tests\File; use Drupal\Core\Site\Settings; +use Drupal\Core\File\FileSystem; /** * Tests the unmanaged file copy function. @@ -29,7 +30,7 @@ function testNormal() { $this->assertEqual($new_filepath, $desired_filepath, 'Returned expected filepath.'); $this->assertTrue(file_exists($uri), 'Original file remains.'); $this->assertTrue(file_exists($new_filepath), 'New file exists.'); - $this->assertFilePermissions($new_filepath, Settings::get('file_chmod_file', FILE_CHMOD_FILE)); + $this->assertFilePermissions($new_filepath, Settings::get('file_chmod_file', FileSystem::CHMOD_FILE)); // Copying with rename. $desired_filepath = 'public://' . $this->randomMachineName(); @@ -39,7 +40,7 @@ function testNormal() { $this->assertNotEqual($newer_filepath, $desired_filepath, 'Returned expected filepath.'); $this->assertTrue(file_exists($uri), 'Original file remains.'); $this->assertTrue(file_exists($newer_filepath), 'New file exists.'); - $this->assertFilePermissions($newer_filepath, Settings::get('file_chmod_file', FILE_CHMOD_FILE)); + $this->assertFilePermissions($newer_filepath, Settings::get('file_chmod_file', FileSystem::CHMOD_FILE)); // TODO: test copying to a directory (rather than full directory/file path) // TODO: test copying normal files using normal paths (rather than only streams) @@ -69,7 +70,7 @@ function testOverwriteSelf() { $this->assertNotEqual($new_filepath, $uri, 'Copied file has a new name.'); $this->assertTrue(file_exists($uri), 'Original file exists after copying onto itself.'); $this->assertTrue(file_exists($new_filepath), 'Copied file exists after copying onto itself.'); - $this->assertFilePermissions($new_filepath, Settings::get('file_chmod_file', FILE_CHMOD_FILE)); + $this->assertFilePermissions($new_filepath, Settings::get('file_chmod_file', FileSystem::CHMOD_FILE)); // Copy the file onto itself without renaming fails. $new_filepath = file_unmanaged_copy($uri, $uri, FILE_EXISTS_ERROR); @@ -87,6 +88,6 @@ function testOverwriteSelf() { $this->assertNotEqual($new_filepath, $uri, 'Copied file has a new name.'); $this->assertTrue(file_exists($uri), 'Original file exists after copying onto itself.'); $this->assertTrue(file_exists($new_filepath), 'Copied file exists after copying onto itself.'); - $this->assertFilePermissions($new_filepath, Settings::get('file_chmod_file', FILE_CHMOD_FILE)); + $this->assertFilePermissions($new_filepath, Settings::get('file_chmod_file', FileSystem::CHMOD_FILE)); } } diff --git a/core/modules/system/src/Tests/File/UnmanagedMoveTest.php b/core/modules/system/src/Tests/File/UnmanagedMoveTest.php index e2bece8731e2d9f21d6402db7eb36df908a7c81f..ea39c545e6adccb284495f900edddc1b54410790 100644 --- a/core/modules/system/src/Tests/File/UnmanagedMoveTest.php +++ b/core/modules/system/src/Tests/File/UnmanagedMoveTest.php @@ -8,6 +8,7 @@ namespace Drupal\system\Tests\File; use Drupal\Core\Site\Settings; +use Drupal\Core\File\FileSystem; /** * Tests the unmanaged file move function. @@ -29,7 +30,7 @@ function testNormal() { $this->assertEqual($new_filepath, $desired_filepath, 'Returned expected filepath.'); $this->assertTrue(file_exists($new_filepath), 'File exists at the new location.'); $this->assertFalse(file_exists($uri), 'No file remains at the old location.'); - $this->assertFilePermissions($new_filepath, Settings::get('file_chmod_file', FILE_CHMOD_FILE)); + $this->assertFilePermissions($new_filepath, Settings::get('file_chmod_file', FileSystem::CHMOD_FILE)); // Moving with rename. $desired_filepath = 'public://' . $this->randomMachineName(); @@ -40,7 +41,7 @@ function testNormal() { $this->assertNotEqual($newer_filepath, $desired_filepath, 'Returned expected filepath.'); $this->assertTrue(file_exists($newer_filepath), 'File exists at the new location.'); $this->assertFalse(file_exists($new_filepath), 'No file remains at the old location.'); - $this->assertFilePermissions($newer_filepath, Settings::get('file_chmod_file', FILE_CHMOD_FILE)); + $this->assertFilePermissions($newer_filepath, Settings::get('file_chmod_file', FileSystem::CHMOD_FILE)); // TODO: test moving to a directory (rather than full directory/file path) // TODO: test creating and moving normal files (rather than streams) diff --git a/core/modules/system/src/Tests/Routing/RouteProviderTest.php b/core/modules/system/src/Tests/Routing/RouteProviderTest.php index be8862c6724fef08e20c0bb4ad328c9a04709dde..6ba9f9743cd12ef96e8a955407c229a6853f5f63 100644 --- a/core/modules/system/src/Tests/Routing/RouteProviderTest.php +++ b/core/modules/system/src/Tests/Routing/RouteProviderTest.php @@ -50,6 +50,7 @@ class RouteProviderTest extends KernelTestBase { protected $state; protected function setUp() { + parent::setUp(); $this->fixtures = new RoutingFixtures(); $this->routeBuilder = new NullRouteBuilder(); $this->state = new State(new KeyValueMemoryFactory()); diff --git a/core/tests/Drupal/Tests/Core/File/FileSystemTest.php b/core/tests/Drupal/Tests/Core/File/FileSystemTest.php new file mode 100644 index 0000000000000000000000000000000000000000..ba39d1766d9bdf509bdb29dd392adbf68090e8a2 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/File/FileSystemTest.php @@ -0,0 +1,183 @@ +<?php + +/** + * @file + * Contains \Drupal\Tests\Core\File\FileSystemTest. + */ + +namespace Drupal\Tests\Core\File; + +use Drupal\Core\File\FileSystem; +use Drupal\Core\Site\Settings; +use Drupal\Tests\UnitTestCase; +use org\bovigo\vfs\vfsStream; + +/** + * @coversDefaultClass \Drupal\Core\File\FileSystem + * + * @group File + */ +class FileSystemTest extends UnitTestCase { + + /** + * @var \Drupal\Core\File\FileSystem + */ + protected $fileSystem; + + /** + * The file logger channel. + * + * @var \Psr\Log\LoggerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $logger; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $settings = new Settings([]); + $stream_wrapper_manager = $this->getMock('Drupal\Core\StreamWrapper\StreamWrapperManagerInterface'); + $this->logger = $this->getMock('Psr\Log\LoggerInterface'); + $this->fileSystem = new FileSystem($stream_wrapper_manager, $settings, $this->logger); + } + + /** + * @covers ::chmod + */ + public function testChmodFile() { + vfsStream::setup('dir'); + vfsStream::create(['test.txt' => 'asdf']); + $uri = 'vfs://dir/test.txt'; + + $this->assertTrue($this->fileSystem->chmod($uri)); + $this->assertFilePermissions(FileSystem::CHMOD_FILE, $uri); + $this->assertTrue($this->fileSystem->chmod($uri, 0444)); + $this->assertFilePermissions(0444, $uri); + } + + /** + * @covers ::chmod + */ + public function testChmodDir() { + vfsStream::setup('dir'); + vfsStream::create(['nested_dir' => []]); + $uri = 'vfs://dir/nested_dir'; + + $this->assertTrue($this->fileSystem->chmod($uri)); + $this->assertFilePermissions(FileSystem::CHMOD_DIRECTORY, $uri); + $this->assertTrue($this->fileSystem->chmod($uri, 0444)); + $this->assertFilePermissions(0444, $uri); + } + + /** + * @covers ::chmod + */ + public function testChmodUnsuccessful() { + vfsStream::setup('dir'); + $this->logger->expects($this->once()) + ->method('error'); + $this->assertFalse($this->fileSystem->chmod('vfs://dir/test.txt')); + } + + /** + * @covers ::unlink + */ + public function testUnlink() { + vfsStream::setup('dir'); + vfsStream::create(['test.txt' => 'asdf']); + $uri = 'vfs://dir/test.txt'; + + $this->fileSystem = $this->getMockBuilder('Drupal\Core\File\FileSystem') + ->disableOriginalConstructor() + ->setMethods(['validScheme']) + ->getMock(); + $this->fileSystem->expects($this->once()) + ->method('validScheme') + ->willReturn(TRUE); + + $this->assertFileExists($uri); + $this->fileSystem->unlink($uri); + $this->assertFileNotExists($uri); + } + + /** + * @covers ::basename + * + * @dataProvider providerTestBasename + */ + public function testBasename($uri, $expected, $suffix = NULL) { + $this->assertSame($expected, $this->fileSystem->basename($uri, $suffix)); + } + + public function providerTestBasename() { + $data = []; + $data[] = [ + 'public://nested/dir', + 'dir', + ]; + $data[] = [ + 'public://dir/test.txt', + 'test.txt', + ]; + $data[] = [ + 'public://dir/test.txt', + 'test', + '.txt' + ]; + return $data; + } + + /** + * @covers ::uriScheme + * + * @dataProvider providerTestUriScheme + */ + public function testUriScheme($uri, $expected) { + $this->assertSame($expected, $this->fileSystem->uriScheme($uri)); + } + + public function providerTestUriScheme() { + $data = []; + $data[] = [ + 'public://filename', + 'public', + ]; + $data[] = [ + 'public://extra://', + 'public', + ]; + $data[] = [ + 'invalid', + FALSE, + ]; + return $data; + } + + /** + * Asserts that the file permissions of a given URI matches. + * + * @param int $expected_mode + * @param string $uri + * @param string $message + */ + protected function assertFilePermissions($expected_mode, $uri, $message = '') { + // Mask out all but the last three octets. + $actual_mode = fileperms($uri) & 0777; + + // PHP on Windows has limited support for file permissions. Usually each of + // "user", "group" and "other" use one octal digit (3 bits) to represent the + // read/write/execute bits. On Windows, chmod() ignores the "group" and + // "other" bits, and fileperms() returns the "user" bits in all three + // positions. $expected_mode is updated to reflect this. + if (substr(PHP_OS, 0, 3) == 'WIN') { + // Reset the "group" and "other" bits. + $expected_mode = $expected_mode & 0700; + // Shift the "user" bits to the "group" and "other" positions also. + $expected_mode = $expected_mode | $expected_mode >> 3 | $expected_mode >> 6; + } + $this->assertSame($expected_mode, $actual_mode, $message); + } + +}