From fc63f5e0c43bfad13ca7cc453a50a90e1fc74dd7 Mon Sep 17 00:00:00 2001 From: Alex Pott Date: Sun, 1 Feb 2015 17:09:10 +0000 Subject: [PATCH] Issue #2050759 by tim.plunkett, fietserwin: Move drupal_chmod and other code in file.inc to a Drupal\Core\File\FileSystem class --- core/core.services.yml | 8 + core/includes/file.inc | 380 ++---------------- core/includes/install.core.inc | 7 +- core/lib/Drupal/Core/File/FileSystem.php | 307 ++++++++++++++ .../Drupal/Core/File/FileSystemInterface.php | 245 +++++++++++ .../StreamWrapper/StreamWrapperManager.php | 118 +----- .../StreamWrapperManagerInterface.php | 159 ++++++++ .../modules/simpletest/src/KernelTestBase.php | 6 +- core/modules/simpletest/src/TestBase.php | 5 +- core/modules/simpletest/src/WebTestBase.php | 17 +- .../system/src/Form/FileSystemForm.php | 8 +- .../Tests/Bootstrap/GetFilenameUnitTest.php | 22 + .../Tests/DrupalKernel/DrupalKernelTest.php | 9 + .../src/Tests/File/UnmanagedCopyTest.php | 9 +- .../src/Tests/File/UnmanagedMoveTest.php | 5 +- .../src/Tests/Routing/RouteProviderTest.php | 1 + .../Drupal/Tests/Core/File/FileSystemTest.php | 183 +++++++++ 17 files changed, 1025 insertions(+), 464 deletions(-) create mode 100644 core/lib/Drupal/Core/File/FileSystem.php create mode 100644 core/lib/Drupal/Core/File/FileSystemInterface.php create mode 100644 core/lib/Drupal/Core/StreamWrapper/StreamWrapperManagerInterface.php create mode 100644 core/tests/Drupal/Tests/Core/File/FileSystemTest.php diff --git a/core/core.services.yml b/core/core.services.yml index 6ab79570bad..29a734ae622 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 d47fbc44f05..034a997459d 100644 --- a/core/includes/file.inc +++ b/core/includes/file.inc @@ -10,20 +10,26 @@ use Drupal\Component\Utility\UrlHelper; 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 4191cdf0019..f98c64d53bf 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -14,6 +14,7 @@ use Drupal\Core\Installer\Exception\NoProfilesException; 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 00000000000..aedfdf6faaf --- /dev/null +++ b/core/lib/Drupal/Core/File/FileSystem.php @@ -0,0 +1,307 @@ +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 00000000000..57009039c1d --- /dev/null +++ b/core/lib/Drupal/Core/File/FileSystemInterface.php @@ -0,0 +1,245 @@ +wrappers[$filter])) { @@ -120,20 +69,7 @@ class StreamWrapperManager extends ContainerAware { } /** - * 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 @@ class StreamWrapperManager extends ContainerAware { } /** - * 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 @@ class StreamWrapperManager extends ContainerAware { } /** - * 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 @@ class StreamWrapperManager extends ContainerAware { } /** - * 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 @@ class StreamWrapperManager extends ContainerAware { } /** - * 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 00000000000..34e5f814427 --- /dev/null +++ b/core/lib/Drupal/Core/StreamWrapper/StreamWrapperManagerInterface.php @@ -0,0 +1,159 @@ +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 @@ abstract class KernelTestBase extends TestBase { // 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 b4901af4314..6ef3e942d4c 100644 --- a/core/modules/simpletest/src/TestBase.php +++ b/core/modules/simpletest/src/TestBase.php @@ -1282,6 +1282,10 @@ abstract class TestBase { // 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 @@ abstract class TestBase { 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 b24d762942e..0b6b770adc4 100644 --- a/core/modules/simpletest/src/WebTestBase.php +++ b/core/modules/simpletest/src/WebTestBase.php @@ -1005,12 +1005,27 @@ abstract class WebTestBase extends TestBase { // 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 368f318d08b..40bd93797a2 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\PrivateStream; 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 a7bcca74ace..9baf5cdfd96 100644 --- a/core/modules/system/src/Tests/Bootstrap/GetFilenameUnitTest.php +++ b/core/modules/system/src/Tests/Bootstrap/GetFilenameUnitTest.php @@ -16,12 +16,34 @@ use Drupal\simpletest\KernelTestBase; */ 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 af8ce27481a..892047810d6 100644 --- a/core/modules/system/src/Tests/DrupalKernel/DrupalKernelTest.php +++ b/core/modules/system/src/Tests/DrupalKernel/DrupalKernelTest.php @@ -35,6 +35,15 @@ class DrupalKernelTest extends KernelTestBase { ))); } + /** + * {@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 0169702ba15..f525c1d5325 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 @@ class UnmanagedCopyTest extends FileTestBase { $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 @@ class UnmanagedCopyTest extends FileTestBase { $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 @@ class UnmanagedCopyTest extends FileTestBase { $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 @@ class UnmanagedCopyTest extends FileTestBase { $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 e2bece8731e..ea39c545e6a 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 @@ class UnmanagedMoveTest extends FileTestBase { $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 @@ class UnmanagedMoveTest extends FileTestBase { $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 be8862c6724..6ba9f9743cd 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 00000000000..ba39d1766d9 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/File/FileSystemTest.php @@ -0,0 +1,183 @@ +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); + } + +}