Version 1
[yaffs-website] / web / core / lib / Drupal / Core / Extension / Discovery / RecursiveExtensionFilterIterator.php
diff --git a/web/core/lib/Drupal/Core/Extension/Discovery/RecursiveExtensionFilterIterator.php b/web/core/lib/Drupal/Core/Extension/Discovery/RecursiveExtensionFilterIterator.php
new file mode 100644 (file)
index 0000000..ac69f01
--- /dev/null
@@ -0,0 +1,165 @@
+<?php
+
+namespace Drupal\Core\Extension\Discovery;
+
+/**
+ * Filters a RecursiveDirectoryIterator to discover extensions.
+ *
+ * To ensure the best possible performance for extension discovery, this
+ * filter implementation hard-codes a range of assumptions about directories
+ * in which Drupal extensions may appear and in which not. Every unnecessary
+ * subdirectory tree recursion is avoided.
+ *
+ * The list of globally ignored directory names is defined in the
+ * RecursiveExtensionFilterIterator::$blacklist property.
+ *
+ * In addition, all 'config' directories are skipped, unless the directory path
+ * ends with 'modules/config', so as to still find the config module provided by
+ * Drupal core and still allow that module to be overridden with a custom config
+ * module.
+ *
+ * Lastly, ExtensionDiscovery instructs this filter to additionally skip all
+ * 'tests' directories at regular runtime, since just with Drupal core only, the
+ * discovery process yields 4x more extensions when tests are not ignored.
+ *
+ * @see ExtensionDiscovery::scan()
+ * @see ExtensionDiscovery::scanDirectory()
+ *
+ * @todo Use RecursiveCallbackFilterIterator instead of the $acceptTests
+ *   parameter forwarding once PHP 5.4 is available.
+ */
+class RecursiveExtensionFilterIterator extends \RecursiveFilterIterator {
+
+  /**
+   * List of base extension type directory names to scan.
+   *
+   * Only these directory names are considered when starting a filesystem
+   * recursion in a search path.
+   *
+   * @var array
+   */
+  protected $whitelist = [
+    'profiles',
+    'modules',
+    'themes',
+  ];
+
+  /**
+   * List of directory names to skip when recursing.
+   *
+   * These directories are globally ignored in the recursive filesystem scan;
+   * i.e., extensions (of all types) are not able to use any of these names,
+   * because their directory names will be skipped.
+   *
+   * @var array
+   */
+  protected $blacklist = [
+    // Object-oriented code subdirectories.
+    'src',
+    'lib',
+    'vendor',
+    // Front-end.
+    'assets',
+    'css',
+    'files',
+    'images',
+    'js',
+    'misc',
+    'templates',
+    // Legacy subdirectories.
+    'includes',
+    // Test subdirectories.
+    'fixtures',
+    // @todo ./tests/Drupal should be ./tests/src/Drupal
+    'Drupal',
+  ];
+
+  /**
+   * Whether to include test directories when recursing.
+   *
+   * @var bool
+   */
+  protected $acceptTests = FALSE;
+
+  /**
+   * Construct a RecursiveExtensionFilterIterator.
+   *
+   * @param \RecursiveIterator $iterator
+   *   The iterator to filter.
+   * @param array $blacklist
+   *   (optional) Add to the blacklist of directories that should be filtered
+   *   out during the iteration.
+   */
+  public function __construct(\RecursiveIterator $iterator, array $blacklist = []) {
+    parent::__construct($iterator);
+    $this->blacklist = array_merge($this->blacklist, $blacklist);
+  }
+
+  /**
+   * Controls whether test directories will be scanned.
+   *
+   * @param bool $flag
+   *   Pass FALSE to skip all test directories in the discovery. If TRUE,
+   *   extensions in test directories will be discovered and only the global
+   *   directory blacklist in RecursiveExtensionFilterIterator::$blacklist is
+   *   applied.
+   */
+  public function acceptTests($flag = FALSE) {
+    $this->acceptTests = $flag;
+    if (!$this->acceptTests) {
+      $this->blacklist[] = 'tests';
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getChildren() {
+    $filter = parent::getChildren();
+    // Pass on the blacklist.
+    $filter->blacklist = $this->blacklist;
+    // Pass the $acceptTests flag forward to child iterators.
+    $filter->acceptTests($this->acceptTests);
+    return $filter;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function accept() {
+    $name = $this->current()->getFilename();
+    // FilesystemIterator::SKIP_DOTS only skips '.' and '..', but not hidden
+    // directories (like '.git').
+    if ($name[0] == '.') {
+      return FALSE;
+    }
+    if ($this->isDir()) {
+      // If this is a subdirectory of a base search path, only recurse into the
+      // fixed list of expected extension type directory names. Required for
+      // scanning the top-level/root directory; without this condition, we would
+      // recurse into the whole filesystem tree that possibly contains other
+      // files aside from Drupal.
+      if ($this->current()->getSubPath() == '') {
+        return in_array($name, $this->whitelist, TRUE);
+      }
+      // 'config' directories are special-cased here, because every extension
+      // contains one. However, those default configuration directories cannot
+      // contain extensions. The directory name cannot be globally skipped,
+      // because core happens to have a directory of an actual module that is
+      // named 'config'. By explicitly testing for that case, we can skip all
+      // other config directories, and at the same time, still allow the core
+      // config module to be overridden/replaced in a profile/site directory
+      // (whereas it must be located directly in a modules directory).
+      if ($name == 'config') {
+        return substr($this->current()->getPathname(), -14) == 'modules/config';
+      }
+      // Accept the directory unless the name is blacklisted.
+      return !in_array($name, $this->blacklist, TRUE);
+    }
+    else {
+      // Only accept extension info files.
+      return substr($name, -9) == '.info.yml';
+    }
+  }
+
+}