"Fossies" - the Fresh Open Source Software Archive

Member "drupal-8.9.10/core/lib/Drupal/Core/Extension/ExtensionList.php" (26 Nov 2020, 17093 Bytes) of package /linux/www/drupal-8.9.10.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "ExtensionList.php" see the Fossies "Dox" file reference documentation.

    1 <?php
    2 
    3 namespace Drupal\Core\Extension;
    4 
    5 use Drupal\Core\Cache\CacheBackendInterface;
    6 use Drupal\Core\Database\DatabaseExceptionWrapper;
    7 use Drupal\Core\Extension\Exception\UnknownExtensionException;
    8 use Drupal\Core\State\StateInterface;
    9 
   10 /**
   11  * Provides available extensions.
   12  *
   13  * The extension list is per extension type, like module, theme and profile.
   14  *
   15  * @internal
   16  *   This class is not yet stable and therefore there are no guarantees that the
   17  *   internal implementations including constructor signature and protected
   18  *   properties / methods will not change over time. This will be reviewed after
   19  *   https://www.drupal.org/project/drupal/issues/2940481
   20  */
   21 abstract class ExtensionList {
   22 
   23   /**
   24    * The type of the extension: "module", "theme" or "profile".
   25    *
   26    * @var string
   27    */
   28   protected $type;
   29 
   30   /**
   31    * The app root.
   32    *
   33    * @var string
   34    */
   35   protected $root;
   36 
   37   /**
   38    * The cache.
   39    *
   40    * @var \Drupal\Core\Cache\CacheBackendInterface
   41    */
   42   protected $cache;
   43 
   44   /**
   45    * Default values to be merged into *.info.yml file arrays.
   46    *
   47    * @var mixed[]
   48    */
   49   protected $defaults = [];
   50 
   51   /**
   52    * The info parser.
   53    *
   54    * @var \Drupal\Core\Extension\InfoParserInterface
   55    */
   56   protected $infoParser;
   57 
   58   /**
   59    * The module handler.
   60    *
   61    * @var \Drupal\Core\Extension\ModuleHandlerInterface
   62    */
   63   protected $moduleHandler;
   64 
   65   /**
   66    * The cached extensions.
   67    *
   68    * @var \Drupal\Core\Extension\Extension[]|null
   69    */
   70   protected $extensions;
   71 
   72   /**
   73    * Static caching for extension info.
   74    *
   75    * Access this property's value through static::getAllInfo().
   76    *
   77    * @var array[]|null
   78    *   Keys are extension names, and values their info arrays (mixed[]).
   79    *
   80    * @see \Drupal\Core\Extension\ExtensionList::getAllAvailableInfo
   81    */
   82   protected $extensionInfo;
   83 
   84   /**
   85    * A list of extension folder names keyed by extension name.
   86    *
   87    * @var string[]|null
   88    */
   89   protected $pathNames;
   90 
   91   /**
   92    * A list of extension folder names directly added in code (not discovered).
   93    *
   94    * It is important to keep a separate list to ensure that it takes priority
   95    * over the discovered extension folders.
   96    *
   97    * @var string[]
   98    *
   99    * @internal
  100    */
  101   protected $addedPathNames = [];
  102 
  103   /**
  104    * The state store.
  105    *
  106    * @var \Drupal\Core\State\StateInterface
  107    */
  108   protected $state;
  109 
  110   /**
  111    * The install profile used by the site.
  112    *
  113    * @var string
  114    */
  115   protected $installProfile;
  116 
  117   /**
  118    * Constructs a new instance.
  119    *
  120    * @param string $root
  121    *   The app root.
  122    * @param string $type
  123    *   The extension type.
  124    * @param \Drupal\Core\Cache\CacheBackendInterface $cache
  125    *   The cache.
  126    * @param \Drupal\Core\Extension\InfoParserInterface $info_parser
  127    *   The info parser.
  128    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
  129    *   The module handler.
  130    * @param \Drupal\Core\State\StateInterface $state
  131    *   The state.
  132    * @param string $install_profile
  133    *   The install profile used by the site.
  134    */
  135   public function __construct($root, $type, CacheBackendInterface $cache, InfoParserInterface $info_parser, ModuleHandlerInterface $module_handler, StateInterface $state, $install_profile) {
  136     $this->root = $root;
  137     $this->type = $type;
  138     $this->cache = $cache;
  139     $this->infoParser = $info_parser;
  140     $this->moduleHandler = $module_handler;
  141     $this->state = $state;
  142     $this->installProfile = $install_profile;
  143   }
  144 
  145   /**
  146    * Returns the extension discovery.
  147    *
  148    * @return \Drupal\Core\Extension\ExtensionDiscovery
  149    */
  150   protected function getExtensionDiscovery() {
  151     return new ExtensionDiscovery($this->root);
  152   }
  153 
  154   /**
  155    * Resets the stored extension list.
  156    *
  157    * We don't reset statically added filenames, as it is a static cache which
  158    * logically can't change. This is done for performance reasons of the
  159    * installer.
  160    *
  161    * @return $this
  162    */
  163   public function reset() {
  164     $this->extensions = NULL;
  165     $this->cache->delete($this->getListCacheId());
  166     $this->extensionInfo = NULL;
  167     $this->cache->delete($this->getInfoCacheId());
  168     $this->pathNames = NULL;
  169 
  170     try {
  171       $this->state->delete($this->getPathnamesCacheId());
  172     }
  173     catch (DatabaseExceptionWrapper $e) {
  174       // Ignore exceptions caused by a non existing {key_value} table in the
  175       // early installer.
  176     }
  177 
  178     $this->cache->delete($this->getPathnamesCacheId());
  179     // @todo In the long run it would be great to add the reset, but the early
  180     //   installer fails due to that. https://www.drupal.org/node/2719315 could
  181     //   help to resolve with that.
  182     return $this;
  183   }
  184 
  185   /**
  186    * Returns the extension list cache ID.
  187    *
  188    * @return string
  189    *   The list cache ID.
  190    */
  191   protected function getListCacheId() {
  192     return 'core.extension.list.' . $this->type;
  193   }
  194 
  195   /**
  196    * Returns the extension info cache ID.
  197    *
  198    * @return string
  199    *   The info cache ID.
  200    */
  201   protected function getInfoCacheId() {
  202     return "system.{$this->type}.info";
  203   }
  204 
  205   /**
  206    * Returns the extension filenames cache ID.
  207    *
  208    * @return string
  209    *   The filename cache ID.
  210    */
  211   protected function getPathnamesCacheId() {
  212     return "system.{$this->type}.files";
  213   }
  214 
  215   /**
  216    * Determines if an extension exists in the filesystem.
  217    *
  218    * @param string $extension_name
  219    *   The machine name of the extension.
  220    *
  221    * @return bool
  222    *   TRUE if the extension exists (regardless installed or not) and FALSE if
  223    *   not.
  224    */
  225   public function exists($extension_name) {
  226     $extensions = $this->getList();
  227     return isset($extensions[$extension_name]);
  228   }
  229 
  230   /**
  231    * Returns the human-readable name of the extension.
  232    *
  233    * @param string $extension_name
  234    *   The machine name of the extension.
  235    *
  236    * @return string
  237    *   The human-readable name of the extension.
  238    *
  239    * @throws \Drupal\Core\Extension\Exception\UnknownExtensionException
  240    *   If there is no extension with the supplied machine name.
  241    */
  242   public function getName($extension_name) {
  243     return $this->get($extension_name)->info['name'];
  244   }
  245 
  246   /**
  247    * Returns a single extension.
  248    *
  249    * @param string $extension_name
  250    *   The machine name of the extension.
  251    *
  252    * @return \Drupal\Core\Extension\Extension
  253    *   A processed extension object for the extension with the specified machine
  254    *   name.
  255    *
  256    * @throws \Drupal\Core\Extension\Exception\UnknownExtensionException
  257    *   If there is no extension with the supplied name.
  258    */
  259   public function get($extension_name) {
  260     $extensions = $this->getList();
  261     if (isset($extensions[$extension_name])) {
  262       return $extensions[$extension_name];
  263     }
  264 
  265     throw new UnknownExtensionException("The {$this->type} $extension_name does not exist.");
  266   }
  267 
  268   /**
  269    * Returns all available extensions.
  270    *
  271    * @return \Drupal\Core\Extension\Extension[]
  272    *   Processed extension objects, keyed by machine name.
  273    */
  274   public function getList() {
  275     if ($this->extensions !== NULL) {
  276       return $this->extensions;
  277     }
  278     if ($cache = $this->cache->get($this->getListCacheId())) {
  279       $this->extensions = $cache->data;
  280       return $this->extensions;
  281     }
  282     $extensions = $this->doList();
  283     $this->cache->set($this->getListCacheId(), $extensions);
  284     $this->extensions = $extensions;
  285     return $this->extensions;
  286   }
  287 
  288   /**
  289    * Scans the available extensions.
  290    *
  291    * Overriding this method gives other code the chance to add additional
  292    * extensions to this raw listing.
  293    *
  294    * @return \Drupal\Core\Extension\Extension[]
  295    *   Unprocessed extension objects, keyed by machine name.
  296    */
  297   protected function doScanExtensions() {
  298     return $this->getExtensionDiscovery()->scan($this->type);
  299   }
  300 
  301   /**
  302    * Builds the list of extensions.
  303    *
  304    * @return \Drupal\Core\Extension\Extension[]
  305    *   Processed extension objects, keyed by machine name.
  306    *
  307    * @throws \Drupal\Core\Extension\InfoParserException
  308    *   If one of the .info.yml files is incomplete, or causes a parsing error.
  309    */
  310   protected function doList() {
  311     // Find extensions.
  312     $extensions = $this->doScanExtensions();
  313 
  314     // Read info files for each extension.
  315     foreach ($extensions as $extension_name => $extension) {
  316       $extension->info = $this->createExtensionInfo($extension);
  317 
  318       // Invoke hook_system_info_alter() to give installed modules a chance to
  319       // modify the data in the .info.yml files if necessary.
  320       $this->moduleHandler->alter('system_info', $extension->info, $extension, $this->type);
  321     }
  322 
  323     return $extensions;
  324   }
  325 
  326   /**
  327    * Returns information about a specified extension.
  328    *
  329    * This function returns the contents of the .info.yml file for the specified
  330    * extension.
  331    *
  332    * @param string $extension_name
  333    *   The name of an extension whose information shall be returned.
  334    *
  335    * @return mixed[]
  336    *   An associative array of extension information.
  337    *
  338    * @throws \Drupal\Core\Extension\Exception\UnknownExtensionException
  339    *   If there is no extension with the supplied name.
  340    */
  341   public function getExtensionInfo($extension_name) {
  342     $all_info = $this->getAllInstalledInfo();
  343     if (isset($all_info[$extension_name])) {
  344       return $all_info[$extension_name];
  345     }
  346     throw new UnknownExtensionException("The {$this->type} $extension_name does not exist or is not installed.");
  347   }
  348 
  349   /**
  350    * Returns an array of info files information of available extensions.
  351    *
  352    * This function returns the processed contents (with added defaults) of the
  353    * .info.yml files.
  354    *
  355    * @return array[]
  356    *   An associative array of extension information arrays, keyed by extension
  357    *   name.
  358    */
  359   public function getAllAvailableInfo() {
  360     if ($this->extensionInfo === NULL) {
  361       $cache_id = $this->getInfoCacheId();
  362       if ($cache = $this->cache->get($cache_id)) {
  363         $info = $cache->data;
  364       }
  365       else {
  366         $info = $this->recalculateInfo();
  367         $this->cache->set($cache_id, $info);
  368       }
  369       $this->extensionInfo = $info;
  370     }
  371 
  372     return $this->extensionInfo;
  373   }
  374 
  375   /**
  376    * Returns a list of machine names of installed extensions.
  377    *
  378    * @return string[]
  379    *   The machine names of all installed extensions of this type.
  380    */
  381   abstract protected function getInstalledExtensionNames();
  382 
  383   /**
  384    * Returns an array of info files information of installed extensions.
  385    *
  386    * This function returns the processed contents (with added defaults) of the
  387    * .info.yml files.
  388    *
  389    * @return array[]
  390    *   An associative array of extension information arrays, keyed by extension
  391    *   name.
  392    */
  393   public function getAllInstalledInfo() {
  394     return array_intersect_key($this->getAllAvailableInfo(), array_flip($this->getInstalledExtensionNames()));
  395   }
  396 
  397   /**
  398    * Generates the information from .info.yml files for extensions of this type.
  399    *
  400    * @return array[]
  401    *   An array of arrays of .info.yml entries keyed by the machine name.
  402    */
  403   protected function recalculateInfo() {
  404     return array_map(function (Extension $extension) {
  405       return $extension->info;
  406     }, $this->getList());
  407   }
  408 
  409   /**
  410    * Returns a list of extension file paths keyed by machine name.
  411    *
  412    * @return string[]
  413    */
  414   public function getPathnames() {
  415     if ($this->pathNames === NULL) {
  416       $cache_id = $this->getPathnamesCacheId();
  417       if ($cache = $this->cache->get($cache_id)) {
  418         $path_names = $cache->data;
  419       }
  420       // We use $file_names below.
  421       elseif (!$path_names = $this->state->get($cache_id)) {
  422         $path_names = $this->recalculatePathnames();
  423         // Store filenames to allow static::getPathname() to retrieve them
  424         // without having to rebuild or scan the filesystem.
  425         $this->state->set($cache_id, $path_names);
  426         $this->cache->set($cache_id, $path_names);
  427       }
  428       $this->pathNames = $path_names;
  429     }
  430     return $this->pathNames;
  431   }
  432 
  433   /**
  434    * Generates a sorted list of .info.yml file locations for all extensions.
  435    *
  436    * @return string[]
  437    *   An array of .info.yml file locations keyed by the extension machine name.
  438    */
  439   protected function recalculatePathnames() {
  440     $extensions = $this->getList();
  441     ksort($extensions);
  442 
  443     return array_map(function (Extension $extension) {
  444       return $extension->getPathname();
  445     }, $extensions);
  446   }
  447 
  448   /**
  449    * Sets the pathname for an extension.
  450    *
  451    * This method is used in the Drupal bootstrapping phase, when the extension
  452    * system is not fully initialized, to manually set locations of modules and
  453    * profiles needed to complete bootstrapping.
  454    *
  455    * It is not recommended to call this method except in those rare cases.
  456    *
  457    * @param string $extension_name
  458    *   The machine name of the extension.
  459    * @param string $pathname
  460    *   The pathname of the extension which is to be set explicitly rather
  461    *   than by consulting the dynamic extension listing.
  462    *
  463    * @internal
  464    *
  465    * @see ::getPathname
  466    */
  467   public function setPathname($extension_name, $pathname) {
  468     $this->addedPathNames[$extension_name] = $pathname;
  469   }
  470 
  471   /**
  472    * Gets the info file path for an extension.
  473    *
  474    * The info path, whether provided, cached, or retrieved from the database, is
  475    * only returned if the file exists.
  476    *
  477    * This function plays a key role in allowing Drupal's extensions (modules,
  478    * themes, profiles, theme_engines, etc.) to be located in different places
  479    * depending on a site's configuration. For example, a module 'foo' may
  480    * legally be located in any of these four places:
  481    *
  482    * - core/modules/foo/foo.info.yml
  483    * - modules/foo/foo.info.yml
  484    * - sites/all/modules/foo/foo.info.yml
  485    * - sites/example.com/modules/foo/foo.info.yml
  486    *
  487    * while a theme 'bar' may be located in any of the following four places:
  488    *
  489    * - core/themes/bar/bar.info.yml
  490    * - themes/bar/bar.info.yml
  491    * - sites/all/themes/bar/bar.info.yml
  492    * - sites/example.com/themes/bar/bar.info.yml
  493    *
  494    * An installation profile maybe be located in any of the following places:
  495    *
  496    * - core/profiles/baz/baz.info.yml
  497    * - profiles/baz/baz.info.yml
  498    *
  499    * Calling ExtensionList::getPathname('foo') will give you one of the above,
  500    * depending on where the extension is located and what type it is.
  501    *
  502    * @param string $extension_name
  503    *   The machine name of the extension for which the pathname is requested.
  504    *
  505    * @return string
  506    *   The drupal-root relative filename and path of the requested extension's
  507    *   .info.yml file.
  508    *
  509    * @throws \Drupal\Core\Extension\Exception\UnknownExtensionException
  510    *   If there is no extension with the supplied machine name.
  511    */
  512   public function getPathname($extension_name) {
  513     if (isset($this->addedPathNames[$extension_name])) {
  514       return $this->addedPathNames[$extension_name];
  515     }
  516     elseif (isset($this->pathNames[$extension_name])) {
  517       return $this->pathNames[$extension_name];
  518     }
  519     elseif (($path_names = $this->getPathnames()) && isset($path_names[$extension_name])) {
  520       return $path_names[$extension_name];
  521     }
  522     throw new UnknownExtensionException("The {$this->type} $extension_name does not exist.");
  523   }
  524 
  525   /**
  526    * Gets the path to an extension of a specific type (module, theme, etc.).
  527    *
  528    * The path is the directory in which the .info file is located. This name is
  529    * coming from \SplFileInfo.
  530    *
  531    * @param string $extension_name
  532    *   The machine name of the extension for which the path is requested.
  533    *
  534    * @return string
  535    *   The Drupal-root-relative path to the specified extension.
  536    *
  537    * @throws \Drupal\Core\Extension\Exception\UnknownExtensionException
  538    *   If there is no extension with the supplied name.
  539    */
  540   public function getPath($extension_name) {
  541     return dirname($this->getPathname($extension_name));
  542   }
  543 
  544   /**
  545    * Creates the info value for an extension object.
  546    *
  547    * @param \Drupal\Core\Extension\Extension $extension
  548    *   The extension whose info is to be altered.
  549    *
  550    * @return array
  551    *   The extension info array.
  552    */
  553   protected function createExtensionInfo(Extension $extension) {
  554     $info = $this->infoParser->parse($extension->getPathname());
  555 
  556     // Add the info file modification time, so it becomes available for
  557     // contributed extensions to use for ordering extension lists.
  558     $info['mtime'] = $extension->getMTime();
  559 
  560     // Merge extension type-specific defaults.
  561     $info += $this->defaults;
  562 
  563     return $info;
  564   }
  565 
  566   /**
  567    * Tests the compatibility of an extension.
  568    *
  569    * @param string $name
  570    *   The extension name to check.
  571    *
  572    * @return bool
  573    *   TRUE if the extension is incompatible and FALSE if not.
  574    *
  575    * @throws \Drupal\Core\Extension\Exception\UnknownExtensionException
  576    *   If there is no extension with the supplied name.
  577    */
  578   public function checkIncompatibility($name) {
  579     $extension = $this->get($name);
  580     return $extension->info['core_incompatible'] || (isset($extension->info['php']) && version_compare(phpversion(), $extension->info['php']) < 0);
  581   }
  582 
  583 }