Issue #3124762 by Spokje, dww, johnwebdev, paulocs, piyuesh23, Suresh Prabhu Parkala, Deepak Goyal, catch, larowlan, rpsu, xjm, andypost, alexpott, daffie, longwave, fgm, Wim Leers, anmolgoyal74: Add 'lifecycle' key to .info.yml files
							parent
							
								
									cc5b85edd8
								
							
						
					
					
						commit
						37bd3765c3
					
				| 
						 | 
				
			
			@ -0,0 +1,8 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Core\Extension\Exception;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Exception thrown when the extension is obsolete on install.
 | 
			
		||||
 */
 | 
			
		||||
class ObsoleteExtensionException extends \Exception {}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,61 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Core\Extension;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Extension lifecycle.
 | 
			
		||||
 *
 | 
			
		||||
 * The lifecycle of an extension (module/theme etc) can go through the following
 | 
			
		||||
 * progression:
 | 
			
		||||
 * 1. Starts "experimental".
 | 
			
		||||
 * 2. Stabilizes and goes "stable".
 | 
			
		||||
 * 3. Eventually (maybe), becomes "deprecated" when being phased out.
 | 
			
		||||
 * 4. Finally (maybe), becomes "obsolete" and can't be enabled anymore.
 | 
			
		||||
 */
 | 
			
		||||
final class ExtensionLifecycle {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The string used to identify the lifecycle in an .info.yml file.
 | 
			
		||||
   */
 | 
			
		||||
  const LIFECYCLE_IDENTIFIER = 'lifecycle';
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Extension is experimental. Warnings will be shown if installed.
 | 
			
		||||
   */
 | 
			
		||||
  const EXPERIMENTAL = 'experimental';
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Extension is stable. This is the default value of any extension.
 | 
			
		||||
   */
 | 
			
		||||
  const STABLE = 'stable';
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Extension is deprecated. Warnings will be shown if still installed.
 | 
			
		||||
   */
 | 
			
		||||
  const DEPRECATED = 'deprecated';
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Extension is obsolete and installation will be prevented.
 | 
			
		||||
   */
 | 
			
		||||
  const OBSOLETE = 'obsolete';
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Determines if a given extension lifecycle string is valid.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $lifecycle
 | 
			
		||||
   *   The lifecycle to validate.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if the lifecycle is valid, otherwise FALSE.
 | 
			
		||||
   */
 | 
			
		||||
  public static function isValid(string $lifecycle) : bool {
 | 
			
		||||
    $valid_values = [
 | 
			
		||||
      self::EXPERIMENTAL,
 | 
			
		||||
      self::STABLE,
 | 
			
		||||
      self::DEPRECATED,
 | 
			
		||||
      self::OBSOLETE,
 | 
			
		||||
    ];
 | 
			
		||||
    return in_array($lifecycle, $valid_values, TRUE);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -107,6 +107,16 @@ class InfoParserDynamic implements InfoParserInterface {
 | 
			
		|||
      if (isset($parsed_info['version']) && $parsed_info['version'] === 'VERSION') {
 | 
			
		||||
        $parsed_info['version'] = \Drupal::VERSION;
 | 
			
		||||
      }
 | 
			
		||||
      $parsed_info += [ExtensionLifecycle::LIFECYCLE_IDENTIFIER => ExtensionLifecycle::STABLE];
 | 
			
		||||
      if (!ExtensionLifecycle::isValid($parsed_info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER])) {
 | 
			
		||||
        $valid_values = [
 | 
			
		||||
          ExtensionLifecycle::EXPERIMENTAL,
 | 
			
		||||
          ExtensionLifecycle::STABLE,
 | 
			
		||||
          ExtensionLifecycle::DEPRECATED,
 | 
			
		||||
          ExtensionLifecycle::OBSOLETE,
 | 
			
		||||
        ];
 | 
			
		||||
        throw new InfoParserException("'lifecycle: {$parsed_info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER]}' is not valid in $filename. Valid values are: '" . implode("', '", $valid_values) . "'.");
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $parsed_info;
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,6 +20,8 @@ interface InfoParserInterface {
 | 
			
		|||
   * - name: The real name of the module for display purposes. (Required)
 | 
			
		||||
   * - description: A brief description of the module.
 | 
			
		||||
   * - type: whether it is for a module or theme. (Required)
 | 
			
		||||
   * - lifecycle: [experimental|stable|deprecated|obsolete]. A description of
 | 
			
		||||
   *   the current phase in the lifecycle of the module, theme or profile.
 | 
			
		||||
   *
 | 
			
		||||
   * Information stored in a module .info.yml file:
 | 
			
		||||
   * - dependencies: An array of dependency strings. Each is in the form
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,6 +8,7 @@ use Drupal\Core\Database\Connection;
 | 
			
		|||
use Drupal\Core\DrupalKernelInterface;
 | 
			
		||||
use Drupal\Core\Entity\EntityStorageException;
 | 
			
		||||
use Drupal\Core\Entity\FieldableEntityInterface;
 | 
			
		||||
use Drupal\Core\Extension\Exception\ObsoleteExtensionException;
 | 
			
		||||
use Drupal\Core\Installer\InstallerKernel;
 | 
			
		||||
use Drupal\Core\Serialization\Yaml;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -106,6 +107,9 @@ class ModuleInstaller implements ModuleInstallerInterface {
 | 
			
		|||
      if (!empty($module_data[$module]->info['core_incompatible'])) {
 | 
			
		||||
        throw new MissingDependencyException("Unable to install modules: module '$module' is incompatible with this version of Drupal core.");
 | 
			
		||||
      }
 | 
			
		||||
      if ($module_data[$module]->info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::OBSOLETE) {
 | 
			
		||||
        throw new ObsoleteExtensionException("Unable to install modules: module '$module' is obsolete.");
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if ($enable_dependencies) {
 | 
			
		||||
      $module_list = $module_list ? array_combine($module_list, $module_list) : [];
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
name: 'Entity Reference'
 | 
			
		||||
type: module
 | 
			
		||||
description: 'Deprecated. All the functionality has been moved to Core.'
 | 
			
		||||
description: 'Obsolete. All the functionality has been moved to Core.'
 | 
			
		||||
lifecycle: obsolete
 | 
			
		||||
package: Field types
 | 
			
		||||
version: VERSION
 | 
			
		||||
hidden: true
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,7 +17,7 @@ class EntityReferenceFileUploadTest extends BrowserTestBase {
 | 
			
		|||
 | 
			
		||||
  use TestFileCreationTrait;
 | 
			
		||||
 | 
			
		||||
  protected static $modules = ['entity_reference', 'node', 'file'];
 | 
			
		||||
  protected static $modules = ['node', 'file'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,7 @@ name: 'Field Layout'
 | 
			
		|||
type: module
 | 
			
		||||
description: 'Allows users to configure the display and form display by arranging fields in several columns.'
 | 
			
		||||
package: Core (Experimental)
 | 
			
		||||
lifecycle: experimental
 | 
			
		||||
version: VERSION
 | 
			
		||||
dependencies:
 | 
			
		||||
  - drupal:layout_discovery
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,7 @@ namespace Drupal\help\Controller;
 | 
			
		|||
 | 
			
		||||
use Drupal\Core\Cache\CacheableMetadata;
 | 
			
		||||
use Drupal\Core\Controller\ControllerBase;
 | 
			
		||||
use Drupal\Core\Extension\ExtensionLifecycle;
 | 
			
		||||
use Drupal\Core\Extension\ModuleExtensionList;
 | 
			
		||||
use Drupal\Core\Routing\RouteMatchInterface;
 | 
			
		||||
use Drupal\help\HelpSectionManager;
 | 
			
		||||
| 
						 | 
				
			
			@ -132,7 +133,7 @@ class HelpController extends ControllerBase {
 | 
			
		|||
      $build['#title'] = $module_name;
 | 
			
		||||
 | 
			
		||||
      $info = $this->moduleExtensionList->getExtensionInfo($name);
 | 
			
		||||
      if ($info['package'] === 'Core (Experimental)') {
 | 
			
		||||
      if ($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::EXPERIMENTAL) {
 | 
			
		||||
        $this->messenger()->addWarning($this->t('This module is experimental. <a href=":url">Experimental modules</a> are provided for testing purposes only. Use at your own risk.', [':url' => 'https://www.drupal.org/core/experimental']));
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,7 @@ name: Help Topics
 | 
			
		|||
type: module
 | 
			
		||||
description: 'Displays help topics provided by themes and modules.'
 | 
			
		||||
package: Core (Experimental)
 | 
			
		||||
lifecycle: experimental
 | 
			
		||||
version: VERSION
 | 
			
		||||
dependencies:
 | 
			
		||||
  - drupal:help
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,7 @@
 | 
			
		|||
 | 
			
		||||
namespace Drupal\Tests\help_topics\Functional;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Extension\ExtensionLifecycle;
 | 
			
		||||
use Drupal\Tests\BrowserTestBase;
 | 
			
		||||
use Drupal\help_topics\HelpTopicDiscovery;
 | 
			
		||||
use PHPUnit\Framework\ExpectationFailedException;
 | 
			
		||||
| 
						 | 
				
			
			@ -279,7 +280,11 @@ class HelpTopicsSyntaxTest extends BrowserTestBase {
 | 
			
		|||
    // Find the extensions of this type, even if they are not installed, but
 | 
			
		||||
    // excluding test ones.
 | 
			
		||||
    $lister = \Drupal::service('extension.list.' . $type);
 | 
			
		||||
    foreach (array_keys($lister->getAllAvailableInfo()) as $name) {
 | 
			
		||||
    foreach ($lister->getAllAvailableInfo() as $name => $info) {
 | 
			
		||||
      // Skip obsolete modules.
 | 
			
		||||
      if (isset($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER]) && $info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::OBSOLETE) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      $path = $lister->getPath($name);
 | 
			
		||||
      // You can tell test modules because they are in package 'Testing', but
 | 
			
		||||
      // test themes are only known by being found in test directories. So...
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,7 @@
 | 
			
		|||
namespace Drupal\Tests\jsonapi\Functional;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Entity\EntityTypeInterface;
 | 
			
		||||
use Drupal\Core\Extension\ExtensionLifecycle;
 | 
			
		||||
use Drupal\Tests\BrowserTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
| 
						 | 
				
			
			@ -38,7 +39,7 @@ class TestCoverageTest extends BrowserTestBase {
 | 
			
		|||
        && empty($module->info['hidden'])
 | 
			
		||||
        && $module->status == FALSE
 | 
			
		||||
        && $module->info['package'] !== 'Testing'
 | 
			
		||||
        && $module->info['package'] !== 'Core (Experimental)';
 | 
			
		||||
        && $module->info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] !== ExtensionLifecycle::EXPERIMENTAL;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $this->container->get('module_installer')->install(array_keys($stable_core_modules));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,7 @@ name: 'Migrate Drupal Multilingual'
 | 
			
		|||
type: module
 | 
			
		||||
description: 'Provides a requirement for multilingual migrations.'
 | 
			
		||||
package: 'Core (Experimental)'
 | 
			
		||||
lifecycle: experimental
 | 
			
		||||
version: VERSION
 | 
			
		||||
dependencies:
 | 
			
		||||
  - drupal:migrate_drupal
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,7 @@
 | 
			
		|||
namespace Drupal\Tests\rest\Functional\EntityResource;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Entity\EntityTypeInterface;
 | 
			
		||||
use Drupal\Core\Extension\ExtensionLifecycle;
 | 
			
		||||
use Drupal\Tests\BrowserTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
| 
						 | 
				
			
			@ -42,7 +43,7 @@ class EntityResourceRestTestCoverageTest extends BrowserTestBase {
 | 
			
		|||
        empty($module->info['hidden']) &&
 | 
			
		||||
        $module->status == FALSE &&
 | 
			
		||||
        $module->info['package'] !== 'Testing' &&
 | 
			
		||||
        $module->info['package'] !== 'Core (Experimental)';
 | 
			
		||||
        $module->info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] !== ExtensionLifecycle::EXPERIMENTAL;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $this->container->get('module_installer')->install(array_keys($stable_core_modules));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -140,8 +140,8 @@ class SearchPageCacheTagsTest extends BrowserTestBase {
 | 
			
		|||
   */
 | 
			
		||||
  public function testSearchTagsBubbling() {
 | 
			
		||||
 | 
			
		||||
    // Install field UI and entity reference modules.
 | 
			
		||||
    $this->container->get('module_installer')->install(['field_ui', 'entity_reference']);
 | 
			
		||||
    // Install field UI module.
 | 
			
		||||
    $this->container->get('module_installer')->install(['field_ui']);
 | 
			
		||||
    $this->resetAll();
 | 
			
		||||
 | 
			
		||||
    // Creates a new content type that will have an entity reference.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
name: Testing
 | 
			
		||||
type: module
 | 
			
		||||
description: 'Deprecated. SimpleTest has been removed from core.'
 | 
			
		||||
description: 'Obsolete. SimpleTest has been removed from core.'
 | 
			
		||||
lifecycle: obsolete
 | 
			
		||||
package: Core
 | 
			
		||||
version: VERSION
 | 
			
		||||
hidden: true
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,6 +6,7 @@ use Drupal\Core\Config\PreExistingConfigException;
 | 
			
		|||
use Drupal\Core\Config\UnmetDependenciesException;
 | 
			
		||||
use Drupal\Core\Access\AccessManagerInterface;
 | 
			
		||||
use Drupal\Core\Extension\Extension;
 | 
			
		||||
use Drupal\Core\Extension\ExtensionLifecycle;
 | 
			
		||||
use Drupal\Core\Extension\InfoParserException;
 | 
			
		||||
use Drupal\Core\Extension\ModuleDependencyMessageTrait;
 | 
			
		||||
use Drupal\Core\Extension\ModuleExtensionList;
 | 
			
		||||
| 
						 | 
				
			
			@ -404,7 +405,7 @@ class ModulesListForm extends FormBase {
 | 
			
		|||
      elseif (($checkbox = $form_state->getValue(['modules', $name], FALSE)) && $checkbox['enable']) {
 | 
			
		||||
        $modules['install'][$name] = $data[$name]->info['name'];
 | 
			
		||||
        // Identify experimental modules.
 | 
			
		||||
        if ($data[$name]->info['package'] == 'Core (Experimental)') {
 | 
			
		||||
        if ($data[$name]->info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::EXPERIMENTAL) {
 | 
			
		||||
          $modules['experimental'][$name] = $data[$name]->info['name'];
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
| 
						 | 
				
			
			@ -418,7 +419,7 @@ class ModulesListForm extends FormBase {
 | 
			
		|||
          $modules['install'][$dependency] = $data[$dependency]->info['name'];
 | 
			
		||||
 | 
			
		||||
          // Identify experimental modules.
 | 
			
		||||
          if ($data[$dependency]->info['package'] == 'Core (Experimental)') {
 | 
			
		||||
          if ($data[$dependency]->info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::EXPERIMENTAL) {
 | 
			
		||||
            $modules['experimental'][$dependency] = $data[$dependency]->info['name'];
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,6 +15,7 @@ use Drupal\Core\Database\Database;
 | 
			
		|||
use Drupal\Core\DrupalKernel;
 | 
			
		||||
use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
 | 
			
		||||
use Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema;
 | 
			
		||||
use Drupal\Core\Extension\ExtensionLifecycle;
 | 
			
		||||
use Drupal\Core\File\FileSystemInterface;
 | 
			
		||||
use Drupal\Core\Link;
 | 
			
		||||
use Drupal\Core\Site\Settings;
 | 
			
		||||
| 
						 | 
				
			
			@ -67,7 +68,7 @@ function system_requirements($phase) {
 | 
			
		|||
    $enabled_modules = \Drupal::moduleHandler()->getModuleList();
 | 
			
		||||
    foreach ($enabled_modules as $module => $data) {
 | 
			
		||||
      $info = \Drupal::service('extension.list.module')->getExtensionInfo($module);
 | 
			
		||||
      if (isset($info['package']) && $info['package'] === 'Core (Experimental)') {
 | 
			
		||||
      if (isset($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER]) && $info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::EXPERIMENTAL) {
 | 
			
		||||
        $experimental_modules[$module] = $info['name'];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,4 +2,5 @@ name: 'Experimental Requirements Test'
 | 
			
		|||
type: module
 | 
			
		||||
description: 'Module in the experimental package to test hook_requirements() with an experimental module.'
 | 
			
		||||
package: Core (Experimental)
 | 
			
		||||
lifecycle: experimental
 | 
			
		||||
version: VERSION
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,4 +2,5 @@ name: 'Experimental Test'
 | 
			
		|||
type: module
 | 
			
		||||
description: 'Module in the experimental package to test experimental functionality.'
 | 
			
		||||
package: Core (Experimental)
 | 
			
		||||
lifecycle: experimental
 | 
			
		||||
version: 8.y.x-unstable
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,6 @@
 | 
			
		|||
name: 'System obsolete status test'
 | 
			
		||||
type: module
 | 
			
		||||
description: 'Support module for testing an obsolete module extension.'
 | 
			
		||||
package: Testing
 | 
			
		||||
version: VERSION
 | 
			
		||||
lifecycle: obsolete
 | 
			
		||||
| 
						 | 
				
			
			@ -2,6 +2,7 @@
 | 
			
		|||
 | 
			
		||||
namespace Drupal\Tests\system\Functional\Module;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Extension\ExtensionLifecycle;
 | 
			
		||||
use Drupal\Core\Logger\RfcLogLevel;
 | 
			
		||||
use Drupal\workspaces\Entity\Workspace;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -104,13 +105,13 @@ class InstallUninstallTest extends ModuleTestBase {
 | 
			
		|||
 | 
			
		||||
      // Install the module.
 | 
			
		||||
      $edit = [];
 | 
			
		||||
      $package = $module->info['package'];
 | 
			
		||||
      $lifecycle = $module->info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER];
 | 
			
		||||
      $edit['modules[' . $name . '][enable]'] = TRUE;
 | 
			
		||||
      $this->drupalGet('admin/modules');
 | 
			
		||||
      $this->submitForm($edit, 'Install');
 | 
			
		||||
 | 
			
		||||
      // Handle experimental modules, which require a confirmation screen.
 | 
			
		||||
      if ($package == 'Core (Experimental)') {
 | 
			
		||||
      if ($lifecycle === ExtensionLifecycle::EXPERIMENTAL) {
 | 
			
		||||
        $this->assertSession()->pageTextContains('Are you sure you wish to enable experimental modules?');
 | 
			
		||||
        if (count($modules_to_install) > 1) {
 | 
			
		||||
          // When there are experimental modules, needed dependencies do not
 | 
			
		||||
| 
						 | 
				
			
			@ -210,7 +211,7 @@ class InstallUninstallTest extends ModuleTestBase {
 | 
			
		|||
    foreach ($all_modules as $name => $module) {
 | 
			
		||||
      $edit['modules[' . $name . '][enable]'] = TRUE;
 | 
			
		||||
      // Track whether there is at least one experimental module.
 | 
			
		||||
      if ($module->info['package'] == 'Core (Experimental)') {
 | 
			
		||||
      if ($module->info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::EXPERIMENTAL) {
 | 
			
		||||
        $experimental = TRUE;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -45,7 +45,6 @@ class EntityReferenceSelectionReferenceableTest extends KernelTestBase {
 | 
			
		|||
    'system',
 | 
			
		||||
    'user',
 | 
			
		||||
    'field',
 | 
			
		||||
    'entity_reference',
 | 
			
		||||
    'node',
 | 
			
		||||
    'entity_test',
 | 
			
		||||
  ];
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,7 @@ type: module
 | 
			
		|||
description: 'Allows users to stage content or preview a full site by using multiple workspaces on a single site.'
 | 
			
		||||
version: VERSION
 | 
			
		||||
package: Core (Experimental)
 | 
			
		||||
lifecycle: experimental
 | 
			
		||||
configure: entity.workspace.collection
 | 
			
		||||
dependencies:
 | 
			
		||||
 - drupal:user
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,7 @@ description: Imports the content for the Umami demo.
 | 
			
		|||
type: module
 | 
			
		||||
version: VERSION
 | 
			
		||||
package: 'Core (Experimental)'
 | 
			
		||||
lifecycle: experimental
 | 
			
		||||
hidden: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  - drupal:field
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,6 +6,7 @@ use Drupal\Core\Config\Entity\ConfigEntityDependency;
 | 
			
		|||
use Drupal\Core\Config\FileStorage;
 | 
			
		||||
use Drupal\Core\Config\InstallStorage;
 | 
			
		||||
use Drupal\Core\Config\StorageInterface;
 | 
			
		||||
use Drupal\Core\Extension\ExtensionLifecycle;
 | 
			
		||||
use Drupal\KernelTests\AssertConfigTrait;
 | 
			
		||||
use Drupal\KernelTests\FileSystemModuleDiscoveryDataProviderTrait;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
| 
						 | 
				
			
			@ -165,7 +166,7 @@ class DefaultConfigTest extends KernelTestBase {
 | 
			
		|||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $info = $this->container->get('extension.list.module')->getExtensionInfo($module);
 | 
			
		||||
        if (!isset($info['package']) || $info['package'] !== 'Core (Experimental)') {
 | 
			
		||||
        if (!isset($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER]) || $info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] !== ExtensionLifecycle::EXPERIMENTAL) {
 | 
			
		||||
          $this->fail("$config_name provided by $module does not exist after installing all dependencies");
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,7 @@ namespace Drupal\KernelTests\Core\Extension;
 | 
			
		|||
 | 
			
		||||
use Drupal\Core\Database\Database;
 | 
			
		||||
use Drupal\Core\Extension\MissingDependencyException;
 | 
			
		||||
use Drupal\Core\Extension\Exception\ObsoleteExtensionException;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
use Symfony\Component\Routing\Exception\RouteNotFoundException;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -134,4 +135,15 @@ class ModuleInstallerTest extends KernelTestBase {
 | 
			
		|||
    $this->assertTrue($this->container->get('module_installer')->install(['system_incompatible_core_version_dependencies_test'], FALSE));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests trying to install an obsolete module.
 | 
			
		||||
   *
 | 
			
		||||
   * @covers ::install
 | 
			
		||||
   */
 | 
			
		||||
  public function testObsoleteInstall() {
 | 
			
		||||
    $this->expectException(ObsoleteExtensionException::class);
 | 
			
		||||
    $this->expectExceptionMessage("Unable to install modules: module 'system_status_obsolete_test' is obsolete.");
 | 
			
		||||
    $this->container->get('module_installer')->install(['system_status_obsolete_test']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,7 @@
 | 
			
		|||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Theme;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Extension\ExtensionLifecycle;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
| 
						 | 
				
			
			@ -64,7 +65,7 @@ class Stable9LibraryOverrideTest extends KernelTestBase {
 | 
			
		|||
    $all_modules = array_filter($all_modules, function ($module) {
 | 
			
		||||
      // Filter contrib, hidden, experimental, already enabled modules, and
 | 
			
		||||
      // modules in the Testing package.
 | 
			
		||||
      if ($module->origin !== 'core' || !empty($module->info['hidden']) || $module->status == TRUE || $module->info['package'] == 'Testing' || $module->info['package'] == 'Core (Experimental)') {
 | 
			
		||||
      if ($module->origin !== 'core' || !empty($module->info['hidden']) || $module->status == TRUE || $module->info['package'] == 'Testing' || $module->info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::EXPERIMENTAL) {
 | 
			
		||||
        return FALSE;
 | 
			
		||||
      }
 | 
			
		||||
      return TRUE;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,7 @@
 | 
			
		|||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Theme;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Extension\ExtensionLifecycle;
 | 
			
		||||
use Drupal\Core\Theme\Registry;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -63,7 +64,7 @@ class Stable9TemplateOverrideTest extends KernelTestBase {
 | 
			
		|||
    $all_modules = array_filter($all_modules, function ($module) {
 | 
			
		||||
      // Filter contrib, hidden, experimental, already enabled modules, and
 | 
			
		||||
      // modules in the Testing package.
 | 
			
		||||
      if ($module->origin !== 'core' || !empty($module->info['hidden']) || $module->status == TRUE || $module->info['package'] == 'Testing' || $module->info['package'] == 'Core (Experimental)') {
 | 
			
		||||
      if ($module->origin !== 'core' || !empty($module->info['hidden']) || $module->status == TRUE || $module->info['package'] == 'Testing' || $module->info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::EXPERIMENTAL) {
 | 
			
		||||
        return FALSE;
 | 
			
		||||
      }
 | 
			
		||||
      return TRUE;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,7 @@
 | 
			
		|||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Theme;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Extension\ExtensionLifecycle;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
| 
						 | 
				
			
			@ -64,7 +65,7 @@ class StableLibraryOverrideTest extends KernelTestBase {
 | 
			
		|||
    $all_modules = array_filter($all_modules, function ($module) {
 | 
			
		||||
      // Filter contrib, hidden, experimental, already enabled modules, and
 | 
			
		||||
      // modules in the Testing package.
 | 
			
		||||
      if ($module->origin !== 'core' || !empty($module->info['hidden']) || $module->status == TRUE || $module->info['package'] == 'Testing' || $module->info['package'] == 'Core (Experimental)') {
 | 
			
		||||
      if ($module->origin !== 'core' || !empty($module->info['hidden']) || $module->status == TRUE || $module->info['package'] == 'Testing' || $module->info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::EXPERIMENTAL) {
 | 
			
		||||
        return FALSE;
 | 
			
		||||
      }
 | 
			
		||||
      return TRUE;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,7 @@
 | 
			
		|||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Theme;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Extension\ExtensionLifecycle;
 | 
			
		||||
use Drupal\Core\Theme\Registry;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -61,7 +62,7 @@ class StableTemplateOverrideTest extends KernelTestBase {
 | 
			
		|||
    $all_modules = array_filter($all_modules, function ($module) {
 | 
			
		||||
      // Filter contrib, hidden, experimental, already enabled modules, and
 | 
			
		||||
      // modules in the Testing package.
 | 
			
		||||
      if ($module->origin !== 'core' || !empty($module->info['hidden']) || $module->status == TRUE || $module->info['package'] == 'Testing' || $module->info['package'] == 'Core (Experimental)') {
 | 
			
		||||
      if ($module->origin !== 'core' || !empty($module->info['hidden']) || $module->status == TRUE || $module->info['package'] == 'Testing' || $module->info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::EXPERIMENTAL) {
 | 
			
		||||
        return FALSE;
 | 
			
		||||
      }
 | 
			
		||||
      return TRUE;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,7 @@
 | 
			
		|||
 | 
			
		||||
namespace Drupal\Tests\Core\Extension;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Extension\ExtensionLifecycle;
 | 
			
		||||
use Drupal\Core\Extension\InfoParser;
 | 
			
		||||
use Drupal\Core\Extension\InfoParserException;
 | 
			
		||||
use Drupal\Tests\UnitTestCase;
 | 
			
		||||
| 
						 | 
				
			
			@ -691,4 +692,110 @@ INFO;
 | 
			
		|||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests an info file with valid lifecycle values.
 | 
			
		||||
   *
 | 
			
		||||
   * @covers ::parse
 | 
			
		||||
   *
 | 
			
		||||
   * @dataProvider providerValidLifecycle
 | 
			
		||||
   */
 | 
			
		||||
  public function testValidLifecycle($lifecycle, $expected) {
 | 
			
		||||
    $info = <<<INFO
 | 
			
		||||
package: Core
 | 
			
		||||
core: 8.x
 | 
			
		||||
version: VERSION
 | 
			
		||||
type: module
 | 
			
		||||
name: Module for That
 | 
			
		||||
INFO;
 | 
			
		||||
    if (!empty($lifecycle)) {
 | 
			
		||||
      $info .= "\nlifecycle: $lifecycle\n";
 | 
			
		||||
    }
 | 
			
		||||
    vfsStream::setup('modules');
 | 
			
		||||
    $filename = "lifecycle-$lifecycle.info.yml";
 | 
			
		||||
    vfsStream::create([
 | 
			
		||||
      'fixtures' => [
 | 
			
		||||
        $filename => $info,
 | 
			
		||||
      ],
 | 
			
		||||
    ]);
 | 
			
		||||
    $info_values = $this->infoParser->parse(vfsStream::url("modules/fixtures/$filename"));
 | 
			
		||||
    $this->assertSame($expected, $info_values[ExtensionLifecycle::LIFECYCLE_IDENTIFIER]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Data provider for testValidLifecycle().
 | 
			
		||||
   */
 | 
			
		||||
  public function providerValidLifecycle() {
 | 
			
		||||
    return [
 | 
			
		||||
      'empty' => [
 | 
			
		||||
        '',
 | 
			
		||||
        ExtensionLifecycle::STABLE,
 | 
			
		||||
      ],
 | 
			
		||||
      'experimental' => [
 | 
			
		||||
        ExtensionLifecycle::EXPERIMENTAL,
 | 
			
		||||
        ExtensionLifecycle::EXPERIMENTAL,
 | 
			
		||||
      ],
 | 
			
		||||
      'stable' => [
 | 
			
		||||
        ExtensionLifecycle::STABLE,
 | 
			
		||||
        ExtensionLifecycle::STABLE,
 | 
			
		||||
      ],
 | 
			
		||||
      'deprecated' => [
 | 
			
		||||
        ExtensionLifecycle::DEPRECATED,
 | 
			
		||||
        ExtensionLifecycle::DEPRECATED,
 | 
			
		||||
      ],
 | 
			
		||||
      'obsolete' => [
 | 
			
		||||
        ExtensionLifecycle::OBSOLETE,
 | 
			
		||||
        ExtensionLifecycle::OBSOLETE,
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests an info file with invalid lifecycle values.
 | 
			
		||||
   *
 | 
			
		||||
   * @covers ::parse
 | 
			
		||||
   *
 | 
			
		||||
   * @dataProvider providerInvalidLifecycle
 | 
			
		||||
   */
 | 
			
		||||
  public function testInvalidLifecycle($lifecycle, $exception_message) {
 | 
			
		||||
    $info = <<<INFO
 | 
			
		||||
package: Core
 | 
			
		||||
core: 8.x
 | 
			
		||||
version: VERSION
 | 
			
		||||
type: module
 | 
			
		||||
name: Module for That
 | 
			
		||||
INFO;
 | 
			
		||||
    $info .= "\nlifecycle: $lifecycle\n";
 | 
			
		||||
    vfsStream::setup('modules');
 | 
			
		||||
    $filename = "lifecycle-$lifecycle.info.txt";
 | 
			
		||||
    vfsStream::create([
 | 
			
		||||
      'fixtures' => [
 | 
			
		||||
        $filename => $info,
 | 
			
		||||
      ],
 | 
			
		||||
    ]);
 | 
			
		||||
    $this->expectException('\Drupal\Core\Extension\InfoParserException');
 | 
			
		||||
    $this->expectExceptionMessage($exception_message);
 | 
			
		||||
    $info_values = $this->infoParser->parse(vfsStream::url("modules/fixtures/$filename"));
 | 
			
		||||
    $this->assertEmpty($info_values);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Data provider for testInvalidLifecycle().
 | 
			
		||||
   */
 | 
			
		||||
  public function providerInvalidLifecycle() {
 | 
			
		||||
    return [
 | 
			
		||||
      'bogus' => [
 | 
			
		||||
        'bogus',
 | 
			
		||||
        "'lifecycle: bogus' is not valid",
 | 
			
		||||
      ],
 | 
			
		||||
      'two words' => [
 | 
			
		||||
        'deprecated obsolete',
 | 
			
		||||
        "'lifecycle: deprecated obsolete' is not valid",
 | 
			
		||||
      ],
 | 
			
		||||
      'wrong case' => [
 | 
			
		||||
        'Experimental',
 | 
			
		||||
        "'lifecycle: Experimental' is not valid",
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue