Commit fc63f5e0 authored by alexpott's avatar alexpott

Issue #2050759 by tim.plunkett, fietserwin: Move drupal_chmod and other code...

Issue #2050759 by tim.plunkett, fietserwin: Move drupal_chmod and other code in file.inc to a Drupal\Core\File\FileSystem class
parent 7736ba08
......@@ -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']
......
This diff is collapsed.
......@@ -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);
......
<?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));
}
}
<?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);
}
......@@ -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 . '://');