123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- <?php
- namespace Drupal\Core\Extension;
- use Composer\Semver\Semver;
- use Drupal\Component\Serialization\Exception\InvalidDataTypeException;
- use Drupal\Core\Serialization\Yaml;
- /**
- * Parses dynamic .info.yml files that might change during the page request.
- */
- class InfoParserDynamic implements InfoParserInterface {
- /**
- * The root directory of the Drupal installation.
- *
- * @var string
- */
- protected $root;
- /**
- * The earliest Drupal version that supports the 'core_version_requirement'.
- */
- const FIRST_CORE_VERSION_REQUIREMENT_SUPPORTED_VERSION = '8.7.7';
- /**
- * InfoParserDynamic constructor.
- *
- * @param string|null $app_root
- * The root directory of the Drupal installation.
- */
- public function __construct(string $app_root = NULL) {
- if ($app_root === NULL) {
- // @todo https://www.drupal.org/project/drupal/issues/3087975 Require
- // $app_root argument.
- $app_root = \Drupal::hasService('app.root') ? (string) \Drupal::service('app.root') : DRUPAL_ROOT;
- }
- $this->root = $app_root;
- }
- /**
- * {@inheritdoc}
- */
- public function parse($filename) {
- if (!file_exists($filename)) {
- $parsed_info = [];
- }
- else {
- try {
- $parsed_info = Yaml::decode(file_get_contents($filename));
- }
- catch (InvalidDataTypeException $e) {
- throw new InfoParserException("Unable to parse $filename " . $e->getMessage());
- }
- $missing_keys = array_diff($this->getRequiredKeys(), array_keys($parsed_info));
- if (!empty($missing_keys)) {
- throw new InfoParserException('Missing required keys (' . implode(', ', $missing_keys) . ') in ' . $filename);
- }
- if (!isset($parsed_info['core']) && !isset($parsed_info['core_version_requirement'])) {
- if (strpos($filename, 'core/') === 0 || strpos($filename, $this->root . '/core/') === 0) {
- // Core extensions do not need to specify core compatibility: they are
- // by definition compatible so a sensible default is used. Core
- // modules are allowed to provide these for testing purposes.
- $parsed_info['core_version_requirement'] = \Drupal::VERSION;
- }
- elseif (isset($parsed_info['package']) && $parsed_info['package'] === 'Testing') {
- // Modules in the testing package are exempt as well. This makes it
- // easier for contrib to use test modules.
- $parsed_info['core_version_requirement'] = \Drupal::VERSION;
- }
- else {
- // Non-core extensions must specify core compatibility.
- throw new InfoParserException("The 'core' or the 'core_version_requirement' key must be present in " . $filename);
- }
- }
- if (isset($parsed_info['core']) && !preg_match("/^\d\.x$/", $parsed_info['core'])) {
- throw new InfoParserException("Invalid 'core' value \"{$parsed_info['core']}\" in " . $filename);
- }
- if (isset($parsed_info['core_version_requirement'])) {
- try {
- $supports_pre_core_version_requirement_version = static::isConstraintSatisfiedByPreviousVersion($parsed_info['core_version_requirement'], static::FIRST_CORE_VERSION_REQUIREMENT_SUPPORTED_VERSION);
- }
- catch (\UnexpectedValueException $e) {
- throw new InfoParserException("The 'core_version_requirement' constraint ({$parsed_info['core_version_requirement']}) is not a valid value in $filename");
- }
- // If the 'core_version_requirement' constraint does not satisfy any
- // Drupal 8 versions before 8.7.7 then 'core' cannot be set or it will
- // effectively support all versions of Drupal 8 because
- // 'core_version_requirement' will be ignored in previous versions.
- if (!$supports_pre_core_version_requirement_version && isset($parsed_info['core'])) {
- throw new InfoParserException("The 'core_version_requirement' constraint ({$parsed_info['core_version_requirement']}) requires the 'core' key not be set in " . $filename);
- }
- // 'core_version_requirement' can not be used to specify Drupal 8
- // versions before 8.7.7 because these versions do not use the
- // 'core_version_requirement' key. Do not throw the exception if the
- // constraint also is satisfied by 8.0.0-alpha1 to allow constraints
- // such as '^8' or '^8 || ^9'.
- if ($supports_pre_core_version_requirement_version && !Semver::satisfies('8.0.0-alpha1', $parsed_info['core_version_requirement'])) {
- throw new InfoParserException("The 'core_version_requirement' can not be used to specify compatibility for a specific version before " . static::FIRST_CORE_VERSION_REQUIREMENT_SUPPORTED_VERSION . " in $filename");
- }
- }
- // Determine if the extension is compatible with the current version of
- // Drupal core.
- $core_version_constraint = isset($parsed_info['core_version_requirement']) ? $parsed_info['core_version_requirement'] : $parsed_info['core'];
- $parsed_info['core_incompatible'] = !Semver::satisfies(\Drupal::VERSION, $core_version_constraint);
- if (isset($parsed_info['version']) && $parsed_info['version'] === 'VERSION') {
- $parsed_info['version'] = \Drupal::VERSION;
- }
- // Special backwards compatible handling profiles and their 'dependencies'
- // key.
- if ($parsed_info['type'] === 'profile' && isset($parsed_info['dependencies']) && !array_key_exists('install', $parsed_info)) {
- // Only trigger the deprecation message if we are actually using the
- // profile with the missing 'install' key. This avoids triggering the
- // deprecation when scanning all the available install profiles.
- global $install_state;
- if (isset($install_state['parameters']['profile'])) {
- $pattern = '@' . preg_quote(DIRECTORY_SEPARATOR . $install_state['parameters']['profile'] . '.info.yml') . '$@';
- if (preg_match($pattern, $filename)) {
- @trigger_error("The install profile $filename only implements a 'dependencies' key. As of Drupal 8.6.0 profile's support a new 'install' key for modules that should be installed but not depended on. See https://www.drupal.org/node/2952947.", E_USER_DEPRECATED);
- }
- }
- // Move dependencies to install so that if a profile has both
- // dependencies and install then dependencies are real.
- $parsed_info['install'] = $parsed_info['dependencies'];
- $parsed_info['dependencies'] = [];
- }
- }
- return $parsed_info;
- }
- /**
- * Returns an array of keys required to exist in .info.yml file.
- *
- * @return array
- * An array of required keys.
- */
- protected function getRequiredKeys() {
- return ['type', 'name'];
- }
- /**
- * Determines if a constraint is satisfied by earlier versions of Drupal 8.
- *
- * @param string $constraint
- * A core semantic version constraint.
- * @param string $version
- * A core version.
- *
- * @return bool
- * TRUE if the constraint is satisfied by a core version prior to the
- * provided version.
- */
- protected static function isConstraintSatisfiedByPreviousVersion($constraint, $version) {
- static $evaluated_constraints = [];
- // Any particular constraint and version combination only needs to be
- // evaluated once.
- if (!isset($evaluated_constraints[$constraint][$version])) {
- $evaluated_constraints[$constraint][$version] = FALSE;
- foreach (static::getAllPreviousCoreVersions($version) as $previous_version) {
- if (Semver::satisfies($previous_version, $constraint)) {
- $evaluated_constraints[$constraint][$version] = TRUE;
- // The constraint only has to satisfy one previous version so break
- // when the first one is found.
- break;
- }
- }
- }
- return $evaluated_constraints[$constraint][$version];
- }
- /**
- * Gets all the versions of Drupal 8 before a specific version.
- *
- * @param string $version
- * The version to get versions before.
- *
- * @return array
- * All of the applicable Drupal 8 releases.
- */
- protected static function getAllPreviousCoreVersions($version) {
- static $versions_lists = [];
- // Check if list of previous versions for the specified version has already
- // been created.
- if (empty($versions_lists[$version])) {
- // Loop through all minor versions including 8.7.
- foreach (range(0, 7) as $minor) {
- // The largest patch number in a release was 17 in 8.6.17. Use 27 to
- // leave room for future security releases.
- foreach (range(0, 27) as $patch) {
- $patch_version = "8.$minor.$patch";
- if ($patch_version === $version) {
- // Reverse the order of the versions so that they will be evaluated
- // from the most recent versions first.
- $versions_lists[$version] = array_reverse($versions_lists[$version]);
- return $versions_lists[$version];
- }
- if ($patch === 0) {
- // If this is a '0' patch release like '8.1.0' first create the
- // pre-release versions such as '8.1.0-alpha1' and '8.1.0-rc1'.
- foreach (['alpha', 'beta', 'rc'] as $prerelease) {
- // The largest prerelease number was in 8.0.0-beta16.
- foreach (range(0, 16) as $prerelease_number) {
- $versions_lists[$version][] = "$patch_version-$prerelease$prerelease_number";
- }
- }
- }
- $versions_lists[$version][] = $patch_version;
- }
- }
- }
- return $versions_lists[$version];
- }
- }
|