diff --git a/core/includes/common.inc b/core/includes/common.inc index f1a7ce0..8aa1425 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -7,6 +7,7 @@ use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Datetime\DrupalDateTime; use Drupal\Core\Database\Database; +use Drupal\Core\System\SystemListRecursiveFilterIterator; use Drupal\Core\Template\Attribute; /** @@ -5159,7 +5160,7 @@ function drupal_cron_cleanup() { * - 'filename': File name. * - 'name': Name of file without the extension. */ -function drupal_system_listing($mask, $directory, $key = 'name', $min_depth = 1) { +function drupal_system_listing($mask, $directory, $key = 'name', $min_depth = 1, $procedural = FALSE) { $config = conf_path(); $files = array(); @@ -5199,15 +5200,48 @@ function drupal_system_listing($mask, $directory, $key = 'name', $min_depth = 1) if (!function_exists('file_scan_directory')) { require_once DRUPAL_ROOT . '/core/includes/file.inc'; } + $skip_extension_dirs = array('config', 'lib', 'templates', 'js', 'css'); foreach ($searchdir as $dir) { - $files_to_add = file_scan_directory($dir, $mask, array( - 'key' => $key, - 'min_depth' => $min_depth, - // Do not recurse into ./lib directories; they cannot contain extensions. - // We also skip templates, css, and js directories. - // @todo Find a way to skip ./config directories (but not modules/config). - 'nomask' => '/^(CVS|lib|templates|css|js)$/', - )); + if (!$procedural) { + $files_to_add = array(); + if (!is_dir($dir)) { + continue; + } + $flags = \FilesystemIterator::UNIX_PATHS | \FilesystemIterator::SKIP_DOTS; + $flags |= \FilesystemIterator::CURRENT_AS_SELF; + if ($key !== 'uri') { + $flags |= \FilesystemIterator::KEY_AS_FILENAME; + } + $directory = new \RecursiveDirectoryIterator($dir, $flags); + $filter = new SystemListRecursiveFilterIterator($directory, $mask, array_flip($skip_extension_dirs)); + $flattened = new \RecursiveIteratorIterator($filter); + // All consuming functions expect an array containing file objects and not + // an iterator. They also expect $uri, $filename, and $name properties. + // @todo Something down the line tries to serialize all $files but neither + // RecursiveDirectoryIterator nor SplFileInfo objects can be serialized. + foreach ($flattened as $filename => $iterator) { + $file = new stdClass; + $file->uri = $iterator->getPathName(); + $file->dirname = $iterator->getPath(); + $file->filename = $iterator->getFileName(); + // @todo SplFileInfo->getBasename() does not strip the extension without + // specifying it. Also note that pathinfo()'s PATHINFO_FILENAME + // returns the basename without extension, *not* the filename as + // returned by SplFileInfo->getFileName(). + $file->name = pathinfo($filename, PATHINFO_FILENAME); + $files_to_add[$file->{$key}] = $file; + } + } + else { + $files_to_add = file_scan_directory($dir, $mask, array( + 'key' => $key, + 'min_depth' => $min_depth, + // Do not recurse into ./lib directories; they cannot contain extensions. + // We also skip templates, css, and js directories. + // @todo Find a way to skip ./config directories (but not modules/config). + 'nomask' => '/^(' . implode('|', $skip_extension_dirs) . ')$/', + )); + } // Duplicate files found in later search directories take precedence over // earlier ones, so we want them to overwrite keys in our resulting diff --git a/core/lib/Drupal/Core/System/SystemListRecursiveFilterIterator.php b/core/lib/Drupal/Core/System/SystemListRecursiveFilterIterator.php new file mode 100644 index 0000000..3c781d6 --- /dev/null +++ b/core/lib/Drupal/Core/System/SystemListRecursiveFilterIterator.php @@ -0,0 +1,87 @@ +hasExtension = $has_extension; + $this->regex = $regex; + $this->skipExtensionDirs = $skip_dirs; + parent::__construct($iterator); + } + + /** + * Overrides \OuterRecursiveIterator::getChildren(). + * + * Every RecursiveIterator implementation that uses additional constructor + * arguments has to override this method to instantiate child iterator + * instances with the required arguments. + */ + public function getChildren() { + return new self($this->getInnerIterator()->getChildren(), $this->regex, $this->skipExtensionDirs, $this->hasExtension); + } + + /** + * Implements \RecursiveFilterIterator::accept(). + * + * This relies on the fact that RecursiveDirectoryIterator processes files + * within each directory, before processing subdirectories. Due to that, we + * first check whether a directory contains an extension that matches, and if + * that is the case, we skip all subdirectories that are defined in + * $skipExtensionDirs. + * + * @return bool + * A Boolean indicating: + * - For files: TRUE if the filename matches the regular expression. + * - For directories: TRUE if the directory should be recursed into. + */ + public function accept() { + if ($this->isDir()) { + // If the parent directory does not contain a matching extension filename, + // recurse into all subdirectories. + if (!$this->hasExtension) { + return TRUE; + } + // Otherwise, check each subdirectory for whether we are allowed to + // recurse into it. + return !isset($this->skipExtensionDirs[$this->current()->getFilename()]); + } + else { + $is_extension = preg_match($this->regex, $this->current()->getFilename()); + if ($is_extension) { + $this->hasExtension = TRUE; + } + return $is_extension; + } + } + +}