From dd907896b159b8a384847e3c9479b771b7308f90 Mon Sep 17 00:00:00 2001 From: catch <catch@35733.no-reply.drupal.org> Date: Wed, 24 Jul 2019 11:29:08 +0100 Subject: [PATCH] Issue #3035312 by kim.pepper, andypost, martin107, yogeshmpawar, naveenvalecha, pguillard, Berdir, dww, claudiu.cristea, jibran, Mile23: Move file_scan_directory() to file_system service --- core/includes/file.inc | 82 +++------------- core/includes/install.core.inc | 8 +- core/includes/install.inc | 14 +-- core/includes/theme.inc | 5 +- .../Core/Asset/CssCollectionOptimizer.php | 4 +- .../Core/Asset/JsCollectionOptimizer.php | 4 +- .../NotRegularDirectoryException.php | 8 ++ core/lib/Drupal/Core/File/FileSystem.php | 94 +++++++++++++++++++ .../Drupal/Core/File/FileSystemInterface.php | 39 ++++++++ .../Translator/FileTranslation.php | 26 ++++- core/lib/Drupal/Core/Updater/Updater.php | 9 +- .../file/tests/file_test/file_test.module | 2 +- .../src/Functional/ImageAdminStylesTest.php | 6 +- .../src/Functional/ImageFieldValidateTest.php | 6 +- .../src/Functional/ImageStyleFlushTest.php | 6 +- core/modules/locale/locale.bulk.inc | 5 +- core/modules/locale/locale.install | 10 +- core/modules/locale/locale.translation.inc | 12 ++- core/modules/media/media.install | 2 +- .../InstallTranslationFilePatternTest.php | 13 ++- core/modules/update/update.module | 8 +- core/scripts/run-tests.sh | 2 +- .../Core/File/FileSystemDeprecationTest.php | 7 ++ .../Core/File/RemoteFileScanDirectoryTest.php | 3 +- .../Core/File/ScanDirectoryTest.php | 64 +++++++++---- .../Core/Installer/InstallerLanguageTest.php | 2 +- .../Drupal/Tests/TestFileCreationTrait.php | 8 +- sites/default/default.settings.php | 2 +- 28 files changed, 315 insertions(+), 136 deletions(-) create mode 100644 core/lib/Drupal/Core/File/Exception/NotRegularDirectoryException.php rename core/modules/system/tests/src/{Unit => Kernel}/Installer/InstallTranslationFilePatternTest.php (83%) diff --git a/core/includes/file.inc b/core/includes/file.inc index 765467fd6388..bef4d253e4c2 100644 --- a/core/includes/file.inc +++ b/core/includes/file.inc @@ -992,81 +992,23 @@ function file_unmanaged_save_data($data, $destination = NULL, $replace = FILE_EX * @return * An associative array (keyed on the chosen key) of objects with 'uri', * 'filename', and 'name' properties corresponding to the matched files. + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. + * Use \Drupal\Core\File\FileSystemInterface::scanDirectory() instead. + * + * @see https://www.drupal.org/node/3038437 */ function file_scan_directory($dir, $mask, $options = [], $depth = 0) { - // Merge in defaults. - $options += [ - 'callback' => 0, - 'recurse' => TRUE, - 'key' => 'uri', - 'min_depth' => 0, - ]; - // Normalize $dir only once. - if ($depth == 0) { - /** @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager */ - $stream_wrapper_manager = \Drupal::service('stream_wrapper_manager'); - $dir = $stream_wrapper_manager->normalizeUri($dir); - $dir_has_slash = (substr($dir, -1) === '/'); - } - - // Allow directories specified in settings.php to be ignored. You can use this - // to not check for files in common special-purpose directories. For example, - // node_modules and bower_components. Ignoring irrelevant directories is a - // performance boost. - if (!isset($options['nomask'])) { - $ignore_directories = Settings::get('file_scan_ignore_directories', []); - array_walk($ignore_directories, function (&$value) { - $value = preg_quote($value, '/'); - }); - $default_nomask = '/^' . implode('|', $ignore_directories) . '$/'; - } - - $options['key'] = in_array($options['key'], ['uri', 'filename', 'name']) ? $options['key'] : 'uri'; + @trigger_error('file_scan_directory() is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\File\FileSystemInterface::scanDirectory() instead. See https://www.drupal.org/node/3038437', E_USER_DEPRECATED); $files = []; - // Avoid warnings when opendir does not have the permissions to open a - // directory. - if (is_dir($dir)) { - if ($handle = @opendir($dir)) { - while (FALSE !== ($filename = readdir($handle))) { - // Skip this file if it matches the nomask or starts with a dot. - if ($filename[0] != '.' - && !(isset($options['nomask']) && preg_match($options['nomask'], $filename)) - && !(!empty($default_nomask) && preg_match($default_nomask, $filename)) - ) { - if ($depth == 0 && $dir_has_slash) { - $uri = "$dir$filename"; - } - else { - $uri = "$dir/$filename"; - } - if ($options['recurse'] && is_dir($uri)) { - // Give priority to files in this folder by merging them in after - // any subdirectory files. - $files = array_merge(file_scan_directory($uri, $mask, $options, $depth + 1), $files); - } - elseif ($depth >= $options['min_depth'] && preg_match($mask, $filename)) { - // Always use this match over anything already set in $files with - // the same $options['key']. - $file = new stdClass(); - $file->uri = $uri; - $file->filename = $filename; - $file->name = pathinfo($filename, PATHINFO_FILENAME); - $key = $options['key']; - $files[$file->$key] = $file; - if ($options['callback']) { - $options['callback']($uri); - } - } - } - } - - closedir($handle); - } - else { - \Drupal::logger('file')->error('@dir can not be opened', ['@dir' => $dir]); + try { + if (is_dir($dir)) { + $files = \Drupal::service('file_system')->scanDirectory($dir, $mask, $options); } } - + catch (FileException $e) { + // Ignore and return empty array for BC. + } return $files; } diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index 6910245723d5..b3b462e19ec4 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -444,7 +444,9 @@ function install_begin_request($class_loader, &$install_state) { else { $directory = $site_path . '/files/translations'; } - $container->set('string_translator.file_translation', new FileTranslation($directory)); + /** @var \Drupal\Core\File\FileSystemInterface $file_system */ + $file_system = $container->get('file_system'); + $container->set('string_translator.file_translation', new FileTranslation($directory, $file_system)); $container->get('string_translation') ->addTranslator($container->get('string_translator.file_translation')); @@ -1324,9 +1326,9 @@ function _install_select_profile(&$install_state) { * * @return * An associative array of file URIs keyed by language code. URIs as - * returned by file_scan_directory(). + * returned by FileSystemInterface::scanDirectory(). * - * @see file_scan_directory() + * @see \Drupal\Core\File\FileSystemInterface::scanDirectory() */ function install_find_translations() { $translations = []; diff --git a/core/includes/install.inc b/core/includes/install.inc index d07d7d66e5c9..360290430b19 100644 --- a/core/includes/install.inc +++ b/core/includes/install.inc @@ -5,15 +5,15 @@ * API functions for installing modules and themes. */ -use Drupal\Core\Extension\Dependency; -use Drupal\Component\Utility\Unicode; -use Drupal\Core\File\FileSystemInterface; -use Symfony\Component\HttpFoundation\RedirectResponse; use Drupal\Component\Utility\Crypt; use Drupal\Component\Utility\OpCodeCache; +use Drupal\Component\Utility\Unicode; use Drupal\Component\Utility\UrlHelper; +use Drupal\Core\Extension\Dependency; use Drupal\Core\Extension\ExtensionDiscovery; +use Drupal\Core\File\FileSystemInterface; use Drupal\Core\Site\Settings; +use Symfony\Component\HttpFoundation\RedirectResponse; /** * Requirement severity -- Informational message only. @@ -166,9 +166,11 @@ function drupal_get_database_types() { // The internal database driver name is any valid PHP identifier. $mask = '/^' . DRUPAL_PHP_FUNCTION_PATTERN . '$/'; - $files = file_scan_directory(DRUPAL_ROOT . '/core/lib/Drupal/Core/Database/Driver', $mask, ['recurse' => FALSE]); + /** @var \Drupal\Core\File\FileSystemInterface $file_system */ + $file_system = \Drupal::service('file_system'); + $files = $file_system->scanDirectory(DRUPAL_ROOT . '/core/lib/Drupal/Core/Database/Driver', $mask, ['recurse' => FALSE]); if (is_dir(DRUPAL_ROOT . '/drivers/lib/Drupal/Driver/Database')) { - $files += file_scan_directory(DRUPAL_ROOT . '/drivers/lib/Drupal/Driver/Database/', $mask, ['recurse' => FALSE]); + $files += $file_system->scanDirectory(DRUPAL_ROOT . '/drivers/lib/Drupal/Driver/Database/', $mask, ['recurse' => FALSE]); } foreach ($files as $file) { if (file_exists($file->uri . '/Install/Tasks.php')) { diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 131e4f68ad42..5fec94c6920f 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -216,7 +216,10 @@ function drupal_find_theme_templates($cache, $extension, $path) { // Escape the periods in the extension. $regex = '/' . str_replace('.', '\.', $extension) . '$/'; // Get a listing of all template files in the path to search. - $files = file_scan_directory($path, $regex, ['key' => 'filename']); + $files = []; + if (is_dir($path)) { + $files = \Drupal::service('file_system')->scanDirectory($path, $regex, ['key' => 'filename']); + } // Find templates that implement registered theme hooks and include that in // what is returned so that the registry knows that the theme has this diff --git a/core/lib/Drupal/Core/Asset/CssCollectionOptimizer.php b/core/lib/Drupal/Core/Asset/CssCollectionOptimizer.php index 94712188bf16..26ed07f79edb 100644 --- a/core/lib/Drupal/Core/Asset/CssCollectionOptimizer.php +++ b/core/lib/Drupal/Core/Asset/CssCollectionOptimizer.php @@ -196,7 +196,9 @@ public function deleteAll() { $this->fileSystem->delete($uri); } }; - file_scan_directory('public://css', '/.*/', ['callback' => $delete_stale]); + if (is_dir('public://css')) { + $this->fileSystem->scanDirectory('public://css', '/.*/', ['callback' => $delete_stale]); + } } } diff --git a/core/lib/Drupal/Core/Asset/JsCollectionOptimizer.php b/core/lib/Drupal/Core/Asset/JsCollectionOptimizer.php index a02299ff4960..dc66652b6cea 100644 --- a/core/lib/Drupal/Core/Asset/JsCollectionOptimizer.php +++ b/core/lib/Drupal/Core/Asset/JsCollectionOptimizer.php @@ -198,7 +198,9 @@ public function deleteAll() { $this->fileSystem->delete($uri); } }; - file_scan_directory('public://js', '/.*/', ['callback' => $delete_stale]); + if (is_dir('public://js')) { + $this->fileSystem->scanDirectory('public://js', '/.*/', ['callback' => $delete_stale]); + } } } diff --git a/core/lib/Drupal/Core/File/Exception/NotRegularDirectoryException.php b/core/lib/Drupal/Core/File/Exception/NotRegularDirectoryException.php new file mode 100644 index 000000000000..2f438aa4872d --- /dev/null +++ b/core/lib/Drupal/Core/File/Exception/NotRegularDirectoryException.php @@ -0,0 +1,8 @@ +<?php + +namespace Drupal\Core\File\Exception; + +/** + * Exception thrown when a target is not a regular directory (e.g. a file). + */ +class NotRegularDirectoryException extends FileException {} diff --git a/core/lib/Drupal/Core/File/FileSystem.php b/core/lib/Drupal/Core/File/FileSystem.php index a85701bd3b63..54bbe419ee02 100644 --- a/core/lib/Drupal/Core/File/FileSystem.php +++ b/core/lib/Drupal/Core/File/FileSystem.php @@ -8,6 +8,7 @@ use Drupal\Core\File\Exception\FileExistsException; use Drupal\Core\File\Exception\FileNotExistsException; use Drupal\Core\File\Exception\FileWriteException; +use Drupal\Core\File\Exception\NotRegularDirectoryException; use Drupal\Core\File\Exception\NotRegularFileException; use Drupal\Core\Site\Settings; use Drupal\Core\StreamWrapper\StreamWrapperManager; @@ -625,4 +626,97 @@ public function createFilename($basename, $directory) { return $destination; } + /** + * {@inheritdoc} + */ + public function scanDirectory($dir, $mask, array $options = []) { + // Merge in defaults. + $options += [ + 'callback' => 0, + 'recurse' => TRUE, + 'key' => 'uri', + 'min_depth' => 0, + ]; + $dir = $this->streamWrapperManager->normalizeUri($dir); + if (!is_dir($dir)) { + throw new NotRegularDirectoryException("$dir is not a directory."); + } + // Allow directories specified in settings.php to be ignored. You can use + // this to not check for files in common special-purpose directories. For + // example, node_modules and bower_components. Ignoring irrelevant + // directories is a performance boost. + if (!isset($options['nomask'])) { + $ignore_directories = $this->settings->get('file_scan_ignore_directories', []); + array_walk($ignore_directories, function (&$value) { + $value = preg_quote($value, '/'); + }); + $options['nomask'] = '/^' . implode('|', $ignore_directories) . '$/'; + } + $options['key'] = in_array($options['key'], ['uri', 'filename', 'name']) ? $options['key'] : 'uri'; + return $this->doScanDirectory($dir, $mask, $options); + } + + /** + * Internal function to handle directory scanning with recursion. + * + * @param string $dir + * The base directory or URI to scan, without trailing slash. + * @param string $mask + * The preg_match() regular expression for files to be included. + * @param array $options + * The options as per ::scanDirectory(). + * @param int $depth + * The current depth of recursion. + * + * @return array + * An associative array as per ::scanDirectory(). + * + * @throws \Drupal\Core\File\Exception\NotRegularDirectoryException + * If the directory does not exist. + * + * @see \Drupal\Core\File\FileSystemInterface::scanDirectory() + */ + protected function doScanDirectory($dir, $mask, array $options = [], $depth = 0) { + $files = []; + // Avoid warnings when opendir does not have the permissions to open a + // directory. + if ($handle = @opendir($dir)) { + while (FALSE !== ($filename = readdir($handle))) { + // Skip this file if it matches the nomask or starts with a dot. + if ($filename[0] != '.' && !(preg_match($options['nomask'], $filename))) { + if (substr($dir, -1) == '/') { + $uri = "$dir$filename"; + } + else { + $uri = "$dir/$filename"; + } + if ($options['recurse'] && is_dir($uri)) { + // Give priority to files in this folder by merging them in after + // any subdirectory files. + $files = array_merge($this->doScanDirectory($uri, $mask, $options, $depth + 1), $files); + } + elseif ($depth >= $options['min_depth'] && preg_match($mask, $filename)) { + // Always use this match over anything already set in $files with + // the same $options['key']. + $file = new \stdClass(); + $file->uri = $uri; + $file->filename = $filename; + $file->name = pathinfo($filename, PATHINFO_FILENAME); + $key = $options['key']; + $files[$file->$key] = $file; + if ($options['callback']) { + $options['callback']($uri); + } + } + } + } + closedir($handle); + } + else { + $this->logger->error('@dir can not be opened', ['@dir' => $dir]); + } + + return $files; + } + } diff --git a/core/lib/Drupal/Core/File/FileSystemInterface.php b/core/lib/Drupal/Core/File/FileSystemInterface.php index e8440a9a2cbb..8bcad1db3724 100644 --- a/core/lib/Drupal/Core/File/FileSystemInterface.php +++ b/core/lib/Drupal/Core/File/FileSystemInterface.php @@ -472,4 +472,43 @@ public function createFilename($basename, $directory); */ public function getDestinationFilename($destination, $replace); + /** + * Finds all files that match a given mask in a given directory. + * + * Directories and files beginning with a dot are excluded; this prevents + * hidden files and directories (such as SVN working directories) from being + * scanned. Use the umask option to skip configuration directories to + * eliminate the possibility of accidentally exposing configuration + * information. Also, you can use the base directory, recurse, and min_depth + * options to improve performance by limiting how much of the filesystem has + * to be traversed. + * + * @param string $dir + * The base directory or URI to scan, without trailing slash. + * @param string $mask + * The preg_match() regular expression for files to be included. + * @param array $options + * An associative array of additional options, with the following elements: + * - 'nomask': The preg_match() regular expression for files to be excluded. + * Defaults to the 'file_scan_ignore_directories' setting. + * - 'callback': The callback function to call for each match. There is no + * default callback. + * - 'recurse': When TRUE, the directory scan will recurse the entire tree + * starting at the provided directory. Defaults to TRUE. + * - 'key': The key to be used for the returned associative array of files. + * Possible values are 'uri', for the file's URI; 'filename', for the + * basename of the file; and 'name' for the name of the file without the + * extension. Defaults to 'uri'. + * - 'min_depth': Minimum depth of directories to return files from. + * Defaults to 0. + * + * @return array + * An associative array (keyed on the chosen key) of objects with 'uri', + * 'filename', and 'name' properties corresponding to the matched files. + * + * @throws \Drupal\Core\File\Exception\NotRegularDirectoryException + * If the directory does not exist. + */ + public function scanDirectory($dir, $mask, array $options = []); + } diff --git a/core/lib/Drupal/Core/StringTranslation/Translator/FileTranslation.php b/core/lib/Drupal/Core/StringTranslation/Translator/FileTranslation.php index deee5f4f0557..b096ba390c3f 100644 --- a/core/lib/Drupal/Core/StringTranslation/Translator/FileTranslation.php +++ b/core/lib/Drupal/Core/StringTranslation/Translator/FileTranslation.php @@ -4,6 +4,7 @@ use Drupal\Component\Gettext\PoStreamReader; use Drupal\Component\Gettext\PoMemoryWriter; +use Drupal\Core\File\FileSystemInterface; /** * File based string translation. @@ -22,15 +23,29 @@ class FileTranslation extends StaticTranslation { */ protected $directory; + /** + * The file system. + * + * @var \Drupal\Core\File\FileSystemInterface + */ + protected $fileSystem; + /** * Constructs a StaticTranslation object. * * @param string $directory * The directory to retrieve file translations from. + * @param \Drupal\Core\File\FileSystemInterface $file_system + * The file system service. */ - public function __construct($directory) { + public function __construct($directory, FileSystemInterface $file_system = NULL) { parent::__construct(); $this->directory = $directory; + if (!isset($file_system)) { + @trigger_error('Calling FileTranslation::__construct() without the $file_system argument is deprecated in drupal:8.8.0. The $file_system argument will be required in drupal:9.0.0. See https://www.drupal.org/node/3038437', E_USER_DEPRECATED); + $file_system = \Drupal::service('file_system'); + } + $this->fileSystem = $file_system; } /** @@ -65,12 +80,15 @@ protected function getLanguage($langcode) { * * @return array * An associative array of file information objects keyed by file URIs as - * returned by file_scan_directory(). + * returned by FileSystemInterface::scanDirectory(). * - * @see file_scan_directory() + * @see \Drupal\Core\File\FileSystemInterface::scanDirectory() */ public function findTranslationFiles($langcode = NULL) { - $files = file_scan_directory($this->directory, $this->getTranslationFilesPattern($langcode), ['recurse' => FALSE]); + $files = []; + if (is_dir($this->directory)) { + $files = $this->fileSystem->scanDirectory($this->directory, $this->getTranslationFilesPattern($langcode), ['recurse' => FALSE]); + } return $files; } diff --git a/core/lib/Drupal/Core/Updater/Updater.php b/core/lib/Drupal/Core/Updater/Updater.php index ff673aa74eb3..9f63291ca12f 100644 --- a/core/lib/Drupal/Core/Updater/Updater.php +++ b/core/lib/Drupal/Core/Updater/Updater.php @@ -106,12 +106,17 @@ public static function getUpdaterFromDirectory($directory) { * Path to the info file. */ public static function findInfoFile($directory) { - $info_files = file_scan_directory($directory, '/.*\.info.yml$/'); + /** @var \Drupal\Core\File\FileSystemInterface $file_system */ + $file_system = \Drupal::service('file_system'); + $info_files = []; + if (is_dir($directory)) { + $info_files = $file_system->scanDirectory($directory, '/.*\.info.yml$/'); + } if (!$info_files) { return FALSE; } foreach ($info_files as $info_file) { - if (mb_substr($info_file->filename, 0, -9) == \Drupal::service('file_system')->basename($directory)) { + if (mb_substr($info_file->filename, 0, -9) == $file_system->basename($directory)) { // Info file Has the same name as the directory, return it. return $info_file->uri; } diff --git a/core/modules/file/tests/file_test/file_test.module b/core/modules/file/tests/file_test/file_test.module index 06fe7ae99966..e31547a62686 100644 --- a/core/modules/file/tests/file_test/file_test.module +++ b/core/modules/file/tests/file_test/file_test.module @@ -314,7 +314,7 @@ function file_test_validator(File $file, $errors) { } /** - * Helper function for testing file_scan_directory(). + * Helper function for testing FileSystemInterface::scanDirectory(). * * Each time the function is called the file is stored in a static variable. * When the function is called with no $filepath parameter, the results are diff --git a/core/modules/image/tests/src/Functional/ImageAdminStylesTest.php b/core/modules/image/tests/src/Functional/ImageAdminStylesTest.php index eacd78869982..30b600bdf149 100644 --- a/core/modules/image/tests/src/Functional/ImageAdminStylesTest.php +++ b/core/modules/image/tests/src/Functional/ImageAdminStylesTest.php @@ -44,7 +44,11 @@ public function createSampleImage(ImageStyleInterface $style) { * Count the number of images currently create for a style. */ public function getImageCount(ImageStyleInterface $style) { - return count(file_scan_directory('public://styles/' . $style->id(), '/.*/')); + $count = 0; + if (is_dir('public://styles/' . $style->id())) { + $count = count(\Drupal::service('file_system')->scanDirectory('public://styles/' . $style->id(), '/.*/')); + } + return $count; } /** diff --git a/core/modules/image/tests/src/Functional/ImageFieldValidateTest.php b/core/modules/image/tests/src/Functional/ImageFieldValidateTest.php index 86ad32d579d6..d7d8c3c05ef3 100644 --- a/core/modules/image/tests/src/Functional/ImageFieldValidateTest.php +++ b/core/modules/image/tests/src/Functional/ImageFieldValidateTest.php @@ -40,7 +40,11 @@ public function testValid() { $this->drupalPostForm(NULL, [], t('Save')); // Get invalid image test files from simpletest. - $files = file_scan_directory(drupal_get_path('module', 'simpletest') . '/files', '/invalid-img-.*/'); + $dir = drupal_get_path('module', 'simpletest') . '/files'; + $files = []; + if (is_dir($dir)) { + $files = $file_system->scanDirectory($dir, '/invalid-img-.*/'); + } $invalid_image_files = []; foreach ($files as $file) { $invalid_image_files[$file->filename] = $file; diff --git a/core/modules/image/tests/src/Functional/ImageStyleFlushTest.php b/core/modules/image/tests/src/Functional/ImageStyleFlushTest.php index 7410fdc6b371..17490119e2ee 100644 --- a/core/modules/image/tests/src/Functional/ImageStyleFlushTest.php +++ b/core/modules/image/tests/src/Functional/ImageStyleFlushTest.php @@ -42,7 +42,11 @@ public function createSampleImage($style, $wrapper) { * Count the number of images currently created for a style in a wrapper. */ public function getImageCount($style, $wrapper) { - return count(file_scan_directory($wrapper . '://styles/' . $style->id(), '/.*/')); + $count = 0; + if (is_dir($wrapper . '://styles/' . $style->id())) { + $count = count(\Drupal::service('file_system')->scanDirectory($wrapper . '://styles/' . $style->id(), '/.*/')); + } + return $count; } /** diff --git a/core/modules/locale/locale.bulk.inc b/core/modules/locale/locale.bulk.inc index 593ff31437ea..37c4522092cc 100644 --- a/core/modules/locale/locale.bulk.inc +++ b/core/modules/locale/locale.bulk.inc @@ -97,7 +97,10 @@ function locale_translate_get_interface_translation_files(array $projects = [], // {project}-{version}.{langcode}.po. // Only files of known projects and languages will be returned. $directory = \Drupal::config('locale.settings')->get('translation.path'); - $result = file_scan_directory($directory, '![a-z_]+(\-[0-9a-z\.\-\+]+|)\.[^\./]+\.po$!', ['recurse' => FALSE]); + $result = []; + if (is_dir($directory)) { + $result = \Drupal::service('file_system')->scanDirectory($directory, '![a-z_]+(\-[0-9a-z\.\-\+]+|)\.[^\./]+\.po$!', ['recurse' => FALSE]); + } foreach ($result as $file) { // Update the file object with project name and version from the file name. diff --git a/core/modules/locale/locale.install b/core/modules/locale/locale.install index 13eeb2265b8a..c018be03e058 100644 --- a/core/modules/locale/locale.install +++ b/core/modules/locale/locale.install @@ -32,10 +32,12 @@ function locale_uninstall() { if (is_dir($locale_js_directory)) { $locale_javascripts = \Drupal::state()->get('locale.translation.javascript') ?: []; + /** @var \Drupal\Core\File\FileSystemInterface $file_system */ + $file_system = \Drupal::service('file_system'); foreach ($locale_javascripts as $langcode => $file_suffix) { if (!empty($file_suffix)) { try { - \Drupal::service('file_system')->delete($locale_js_directory . '/' . $langcode . '_' . $file_suffix . '.js'); + $file_system->delete($locale_js_directory . '/' . $langcode . '_' . $file_suffix . '.js'); } catch (FileException $e) { // Ignore and continue. @@ -43,8 +45,10 @@ function locale_uninstall() { } } // Delete the JavaScript translations directory if empty. - if (!file_scan_directory($locale_js_directory, '/.*/')) { - \Drupal::service('file_system')->rmdir($locale_js_directory); + if (is_dir($locale_js_directory)) { + if (!$file_system->scanDirectory($locale_js_directory, '/.*/')) { + $file_system->rmdir($locale_js_directory); + } } } diff --git a/core/modules/locale/locale.translation.inc b/core/modules/locale/locale.translation.inc index 2ef38f0a1322..2672d5c38a56 100644 --- a/core/modules/locale/locale.translation.inc +++ b/core/modules/locale/locale.translation.inc @@ -175,11 +175,13 @@ function locale_translation_source_check_file($source) { $directory = $source_file->directory; $filename = '/' . preg_quote($source_file->filename) . '$/'; - if ($files = file_scan_directory($directory, $filename, ['key' => 'name', 'recurse' => FALSE])) { - $file = current($files); - $source_file->uri = $file->uri; - $source_file->timestamp = filemtime($file->uri); - return $source_file; + if (is_dir($directory)) { + if ($files = \Drupal::service('file_system')->scanDirectory($directory, $filename, ['key' => 'name', 'recurse' => FALSE])) { + $file = current($files); + $source_file->uri = $file->uri; + $source_file->timestamp = filemtime($file->uri); + return $source_file; + } } } return FALSE; diff --git a/core/modules/media/media.install b/core/modules/media/media.install index 5bf11c2ce42e..7d8764c03ebd 100644 --- a/core/modules/media/media.install +++ b/core/modules/media/media.install @@ -24,7 +24,7 @@ function media_install() { $file_system = \Drupal::service('file_system'); $file_system->prepareDirectory($destination, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS); - $files = file_scan_directory($source, '/.*\.(svg|png|jpg|jpeg|gif)$/'); + $files = $file_system->scanDirectory($source, '/.*\.(svg|png|jpg|jpeg|gif)$/'); foreach ($files as $file) { // When reinstalling the media module we don't want to copy the icons when // they already exist. The icons could be replaced (by a contrib module or diff --git a/core/modules/system/tests/src/Unit/Installer/InstallTranslationFilePatternTest.php b/core/modules/system/tests/src/Kernel/Installer/InstallTranslationFilePatternTest.php similarity index 83% rename from core/modules/system/tests/src/Unit/Installer/InstallTranslationFilePatternTest.php rename to core/modules/system/tests/src/Kernel/Installer/InstallTranslationFilePatternTest.php index 0dd7510e89ae..b541ed8cb385 100644 --- a/core/modules/system/tests/src/Unit/Installer/InstallTranslationFilePatternTest.php +++ b/core/modules/system/tests/src/Kernel/Installer/InstallTranslationFilePatternTest.php @@ -1,16 +1,21 @@ <?php -namespace Drupal\Tests\system\Unit\Installer; +namespace Drupal\Tests\system\Kernel\Installer; use Drupal\Core\StringTranslation\Translator\FileTranslation; -use Drupal\Tests\UnitTestCase; +use Drupal\KernelTests\KernelTestBase; /** * Tests for installer language support. * * @group Installer */ -class InstallTranslationFilePatternTest extends UnitTestCase { +class InstallTranslationFilePatternTest extends KernelTestBase { + + /** + * {@inheritdoc} + */ + protected static $modules = ['system']; /** * @var \Drupal\Core\StringTranslation\Translator\FileTranslation @@ -27,7 +32,7 @@ class InstallTranslationFilePatternTest extends UnitTestCase { */ protected function setup() { parent::setUp(); - $this->fileTranslation = new FileTranslation('filename'); + $this->fileTranslation = new FileTranslation('filename', $this->container->get('file_system')); $method = new \ReflectionMethod('\Drupal\Core\StringTranslation\Translator\FileTranslation', 'getTranslationFilesPattern'); $method->setAccessible(TRUE); $this->filePatternMethod = $method; diff --git a/core/modules/update/update.module b/core/modules/update/update.module index c783d7e82bbd..96ddedef64d1 100644 --- a/core/modules/update/update.module +++ b/core/modules/update/update.module @@ -690,7 +690,9 @@ function update_verify_update_archive($project, $archive_file, $directory) { // functionality). $compatible_project = FALSE; $incompatible = []; - $files = file_scan_directory("$directory/$project", '/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.info.yml$/', ['key' => 'name', 'min_depth' => 0]); + /** @var \Drupal\Core\File\FileSystemInterface $file_system */ + $file_system = \Drupal::service('file_system'); + $files = $file_system->scanDirectory("$directory/$project", '/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.info.yml$/', ['key' => 'name', 'min_depth' => 0]); foreach ($files as $file) { // Get the .info.yml file for the module or theme this file belongs to. $info = \Drupal::service('info_parser')->parse($file->uri); @@ -705,8 +707,6 @@ function update_verify_update_archive($project, $archive_file, $directory) { } } - /** @var \Drupal\Core\File\FileSystemInterface $file_system */ - $file_system = \Drupal::service('file_system'); if (empty($files)) { $errors[] = t('%archive_file does not contain any .info.yml files.', ['%archive_file' => $file_system->basename($archive_file)]); } @@ -805,7 +805,7 @@ function update_clear_update_disk_cache() { // Search for files and directories in base folder only without recursion. foreach ($directories as $directory) { - file_scan_directory($directory, '/.*/', ['callback' => 'update_delete_file_if_stale', 'recurse' => FALSE]); + \Drupal::service('file_system')->scanDirectory($directory, '/.*/', ['callback' => 'update_delete_file_if_stale', 'recurse' => FALSE]); } } diff --git a/core/scripts/run-tests.sh b/core/scripts/run-tests.sh index f08956e61fc4..fcc3f2e602f0 100755 --- a/core/scripts/run-tests.sh +++ b/core/scripts/run-tests.sh @@ -1084,7 +1084,7 @@ function simpletest_script_get_test_list() { else { $directory = DRUPAL_ROOT . "/" . $args['directory']; } - foreach (file_scan_directory($directory, '/\.php$/', $ignore) as $file) { + foreach (\Drupal::service('file_system')->scanDirectory($directory, '/\.php$/', $ignore) as $file) { // '/Tests/' can be contained anywhere in the file's path (there can be // sub-directories below /Tests), but must be contained literally. // Case-insensitive to match all Simpletest and PHPUnit tests: diff --git a/core/tests/Drupal/KernelTests/Core/File/FileSystemDeprecationTest.php b/core/tests/Drupal/KernelTests/Core/File/FileSystemDeprecationTest.php index 58893d1fc471..c081e035f256 100644 --- a/core/tests/Drupal/KernelTests/Core/File/FileSystemDeprecationTest.php +++ b/core/tests/Drupal/KernelTests/Core/File/FileSystemDeprecationTest.php @@ -259,4 +259,11 @@ public function testRealpath() { $this->assertNotEmpty(drupal_realpath('public://')); } + /** + * @expectedDeprecation file_scan_directory() is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\File\FileSystemInterface::scanDirectory() instead. See https://www.drupal.org/node/3038437 + */ + public function testDeprecatedScanDirectory() { + $this->assertNotNull(file_scan_directory('temporary://', '/^NONEXISTINGFILENAME/')); + } + } diff --git a/core/tests/Drupal/KernelTests/Core/File/RemoteFileScanDirectoryTest.php b/core/tests/Drupal/KernelTests/Core/File/RemoteFileScanDirectoryTest.php index e3c68a72f336..e07b1e745a73 100644 --- a/core/tests/Drupal/KernelTests/Core/File/RemoteFileScanDirectoryTest.php +++ b/core/tests/Drupal/KernelTests/Core/File/RemoteFileScanDirectoryTest.php @@ -3,8 +3,9 @@ namespace Drupal\KernelTests\Core\File; /** - * Tests the file_scan_directory() function. + * Tests \Drupal\Core\File\FileSystemInterface::scanDirectory(). * + * @coversDefaultClass \Drupal\Core\File\FileSystem * @group File */ class RemoteFileScanDirectoryTest extends ScanDirectoryTest { diff --git a/core/tests/Drupal/KernelTests/Core/File/ScanDirectoryTest.php b/core/tests/Drupal/KernelTests/Core/File/ScanDirectoryTest.php index a096ae852677..0a0dfc1e15c0 100644 --- a/core/tests/Drupal/KernelTests/Core/File/ScanDirectoryTest.php +++ b/core/tests/Drupal/KernelTests/Core/File/ScanDirectoryTest.php @@ -3,8 +3,9 @@ namespace Drupal\KernelTests\Core\File; /** - * Tests the file_scan_directory() function. + * Tests \Drupal\Core\File\FileSystem::scanDirectory. * + * @coversDefaultClass \Drupal\Core\File\FileSystem * @group File */ class ScanDirectoryTest extends FileTestBase { @@ -21,6 +22,16 @@ class ScanDirectoryTest extends FileTestBase { */ protected $path; + /** + * The file system. + * + * @var \Drupal\Core\File\FileSystemInterface + */ + protected $fileSystem; + + /** + * {@inheritdoc} + */ protected function setUp() { parent::setUp(); // Hardcode the location of the simpletest files as it is already known @@ -28,15 +39,18 @@ protected function setUp() { // location from drupal_get_filename() in a cached way. // @todo Remove as part of https://www.drupal.org/node/2186491 $this->path = 'core/modules/simpletest/files'; + $this->fileSystem = $this->container->get('file_system'); } /** * Check the format of the returned values. + * + * @covers ::scanDirectory */ public function testReturn() { // Grab a listing of all the JavaScript files and check that they're // passed to the callback. - $all_files = file_scan_directory($this->path, '/^javascript-/'); + $all_files = $this->fileSystem->scanDirectory($this->path, '/^javascript-/'); ksort($all_files); $this->assertEqual(2, count($all_files), 'Found two, expected javascript files.'); @@ -57,11 +71,13 @@ public function testReturn() { /** * Check that the callback function is called correctly. + * + * @covers ::scanDirectory */ public function testOptionCallback() { // When nothing is matched nothing should be passed to the callback. - $all_files = file_scan_directory($this->path, '/^NONEXISTINGFILENAME/', ['callback' => 'file_test_file_scan_callback']); + $all_files = $this->fileSystem->scanDirectory($this->path, '/^NONEXISTINGFILENAME/', ['callback' => 'file_test_file_scan_callback']); $this->assertEqual(0, count($all_files), 'No files were found.'); $results = file_test_file_scan_callback(); file_test_file_scan_callback_reset(); @@ -69,7 +85,7 @@ public function testOptionCallback() { // Grab a listing of all the JavaScript files and check that they're // passed to the callback. - $all_files = file_scan_directory($this->path, '/^javascript-/', ['callback' => 'file_test_file_scan_callback']); + $all_files = $this->fileSystem->scanDirectory($this->path, '/^javascript-/', ['callback' => 'file_test_file_scan_callback']); $this->assertEqual(2, count($all_files), 'Found two, expected javascript files.'); $results = file_test_file_scan_callback(); file_test_file_scan_callback_reset(); @@ -78,87 +94,97 @@ public function testOptionCallback() { /** * Check that the no-mask parameter is honored. + * + * @covers ::scanDirectory */ public function testOptionNoMask() { // Grab a listing of all the JavaScript files. - $all_files = file_scan_directory($this->path, '/^javascript-/'); + $all_files = $this->fileSystem->scanDirectory($this->path, '/^javascript-/'); $this->assertEqual(2, count($all_files), 'Found two, expected javascript files.'); // Now use the nomask parameter to filter out the .script file. - $filtered_files = file_scan_directory($this->path, '/^javascript-/', ['nomask' => '/.script$/']); + $filtered_files = $this->fileSystem->scanDirectory($this->path, '/^javascript-/', ['nomask' => '/.script$/']); $this->assertEqual(1, count($filtered_files), 'Filtered correctly.'); } /** * Check that key parameter sets the return value's key. + * + * @covers ::scanDirectory */ public function testOptionKey() { // "filename", for the path starting with $dir. $expected = [$this->path . '/javascript-1.txt', $this->path . '/javascript-2.script']; - $actual = array_keys(file_scan_directory($this->path, '/^javascript-/', ['key' => 'filepath'])); + $actual = array_keys($this->fileSystem->scanDirectory($this->path, '/^javascript-/', ['key' => 'filepath'])); sort($actual); $this->assertEqual($expected, $actual, 'Returned the correct values for the filename key.'); // "basename", for the basename of the file. $expected = ['javascript-1.txt', 'javascript-2.script']; - $actual = array_keys(file_scan_directory($this->path, '/^javascript-/', ['key' => 'filename'])); + $actual = array_keys($this->fileSystem->scanDirectory($this->path, '/^javascript-/', ['key' => 'filename'])); sort($actual); $this->assertEqual($expected, $actual, 'Returned the correct values for the basename key.'); // "name" for the name of the file without an extension. $expected = ['javascript-1', 'javascript-2']; - $actual = array_keys(file_scan_directory($this->path, '/^javascript-/', ['key' => 'name'])); + $actual = array_keys($this->fileSystem->scanDirectory($this->path, '/^javascript-/', ['key' => 'name'])); sort($actual); $this->assertEqual($expected, $actual, 'Returned the correct values for the name key.'); // Invalid option that should default back to "filename". $expected = [$this->path . '/javascript-1.txt', $this->path . '/javascript-2.script']; - $actual = array_keys(file_scan_directory($this->path, '/^javascript-/', ['key' => 'INVALID'])); + $actual = array_keys($this->fileSystem->scanDirectory($this->path, '/^javascript-/', ['key' => 'INVALID'])); sort($actual); $this->assertEqual($expected, $actual, 'An invalid key defaulted back to the default.'); } /** * Check that the recurse option descends into subdirectories. + * + * @covers ::scanDirectory */ public function testOptionRecurse() { - $files = file_scan_directory($this->path . '/..', '/^javascript-/', ['recurse' => FALSE]); + $files = $this->fileSystem->scanDirectory($this->path . '/..', '/^javascript-/', ['recurse' => FALSE]); $this->assertTrue(empty($files), "Without recursion couldn't find javascript files."); - $files = file_scan_directory($this->path . '/..', '/^javascript-/', ['recurse' => TRUE]); + $files = $this->fileSystem->scanDirectory($this->path . '/..', '/^javascript-/', ['recurse' => TRUE]); $this->assertEqual(2, count($files), 'With recursion we found the expected javascript files.'); } /** * Check that the min_depth options lets us ignore files in the starting * directory. + * + * @covers ::scanDirectory */ public function testOptionMinDepth() { - $files = file_scan_directory($this->path, '/^javascript-/', ['min_depth' => 0]); + $files = $this->fileSystem->scanDirectory($this->path, '/^javascript-/', ['min_depth' => 0]); $this->assertEqual(2, count($files), 'No minimum-depth gets files in current directory.'); - $files = file_scan_directory($this->path, '/^javascript-/', ['min_depth' => 1]); + $files = $this->fileSystem->scanDirectory($this->path, '/^javascript-/', ['min_depth' => 1]); $this->assertTrue(empty($files), 'Minimum-depth of 1 successfully excludes files from current directory.'); } /** - * Tests file_scan_directory() obeys 'file_scan_ignore_directories' setting. + * Tests ::scanDirectory obeys 'file_scan_ignore_directories' setting. + * + * @covers ::scanDirectory */ public function testIgnoreDirectories() { - $files = file_scan_directory('core/modules/system/tests/fixtures/IgnoreDirectories', '/\.txt$/'); + $files = $this->fileSystem->scanDirectory('core/modules/system/tests/fixtures/IgnoreDirectories', '/\.txt$/'); $this->assertCount(2, $files, '2 text files found when not ignoring directories.'); $this->setSetting('file_scan_ignore_directories', ['frontend_framework']); - $files = file_scan_directory('core/modules/system/tests/fixtures/IgnoreDirectories', '/\.txt$/'); + $files = $this->fileSystem->scanDirectory('core/modules/system/tests/fixtures/IgnoreDirectories', '/\.txt$/'); $this->assertCount(1, $files, '1 text files found when ignoring directories called "frontend_framework".'); // Ensure that the directories in file_scan_ignore_directories are escaped // using preg_quote. $this->setSetting('file_scan_ignore_directories', ['frontend.*']); - $files = file_scan_directory('core/modules/system/tests/fixtures/IgnoreDirectories', '/\.txt$/'); + $files = $this->fileSystem->scanDirectory('core/modules/system/tests/fixtures/IgnoreDirectories', '/\.txt$/'); $this->assertCount(2, $files, '2 text files found when ignoring a directory that is not there.'); - $files = file_scan_directory('core/modules/system/tests/fixtures/IgnoreDirectories', '/\.txt$/', ['nomask' => '/^something_thing_else$/']); + $files = $this->fileSystem->scanDirectory('core/modules/system/tests/fixtures/IgnoreDirectories', '/\.txt$/', ['nomask' => '/^something_thing_else$/']); $this->assertCount(2, $files, '2 text files found when an "nomask" option is passed in.'); } diff --git a/core/tests/Drupal/KernelTests/Core/Installer/InstallerLanguageTest.php b/core/tests/Drupal/KernelTests/Core/Installer/InstallerLanguageTest.php index e0bb6df68469..a265ed4ba033 100644 --- a/core/tests/Drupal/KernelTests/Core/Installer/InstallerLanguageTest.php +++ b/core/tests/Drupal/KernelTests/Core/Installer/InstallerLanguageTest.php @@ -28,7 +28,7 @@ public function testInstallerTranslationFiles() { // Hardcode the simpletest module location as we don't yet know where it is. // @todo Remove as part of https://www.drupal.org/node/2186491 - $file_translation = new FileTranslation('core/modules/simpletest/files/translations'); + $file_translation = new FileTranslation('core/modules/simpletest/files/translations', $this->container->get('file_system')); foreach ($expected_translation_files as $langcode => $files_expected) { $files_found = $file_translation->findTranslationFiles($langcode); $this->assertTrue(count($files_found) == count($files_expected), new FormattableMarkup('@count installer languages found.', ['@count' => count($files_expected)])); diff --git a/core/tests/Drupal/Tests/TestFileCreationTrait.php b/core/tests/Drupal/Tests/TestFileCreationTrait.php index e4c699242f10..ad664e4108af 100644 --- a/core/tests/Drupal/Tests/TestFileCreationTrait.php +++ b/core/tests/Drupal/Tests/TestFileCreationTrait.php @@ -52,6 +52,8 @@ trait TestFileCreationTrait { * List of files in public:// that match the filter(s). */ protected function getTestFiles($type, $size = NULL) { + /** @var \Drupal\Core\File\FileSystemInterface $file_system */ + $file_system = \Drupal::service('file_system'); if (empty($this->generatedTestFiles)) { // Generate binary test files. $lines = [64, 1024]; @@ -69,9 +71,9 @@ protected function getTestFiles($type, $size = NULL) { // Copy other test files from simpletest. $original = drupal_get_path('module', 'simpletest') . '/files'; - $files = file_scan_directory($original, '/(html|image|javascript|php|sql)-.*/'); + $files = $file_system->scanDirectory($original, '/(html|image|javascript|php|sql)-.*/'); foreach ($files as $file) { - \Drupal::service('file_system')->copy($file->uri, PublicStream::basePath()); + $file_system->copy($file->uri, PublicStream::basePath()); } $this->generatedTestFiles = TRUE; @@ -80,7 +82,7 @@ protected function getTestFiles($type, $size = NULL) { $files = []; // Make sure type is valid. if (in_array($type, ['binary', 'html', 'image', 'javascript', 'php', 'sql', 'text'])) { - $files = file_scan_directory('public://', '/' . $type . '\-.*/'); + $files = $file_system->scanDirectory('public://', '/' . $type . '\-.*/'); // If size is set then remove any files that are not of that size. if ($size !== NULL) { diff --git a/sites/default/default.settings.php b/sites/default/default.settings.php index d88742c677ae..e882dd138dd7 100644 --- a/sites/default/default.settings.php +++ b/sites/default/default.settings.php @@ -703,7 +703,7 @@ * with common frontend tools and recursive scanning of directories looking for * extensions. * - * @see file_scan_directory() + * @see \Drupal\Core\File\FileSystemInterface::scanDirectory() * @see \Drupal\Core\Extension\ExtensionDiscovery::scanDirectory() */ $settings['file_scan_ignore_directories'] = [ -- GitLab