diff --git a/composer.json b/composer.json index 4b484e137e983ce04ea39b6aa07e27abab9125d7..ed71c864f14920440731846e59f65564ccaa27eb 100644 --- a/composer.json +++ b/composer.json @@ -1,17 +1,18 @@ { - "description": "Enables administrators to package configuration into modules", - "license": "GPL-2.0-or-later", - "minimum-stability": "dev", - "name": "drupal/features", - "require": { - "drupal/config_update": "^1.4" - }, - "type": "drupal-module", - "extra": { - "drush": { - "services": { - "drush.services.yml": "^9 || ^10" - } + "name": "drupal/features", + "description": "Enables administrators to package configuration into modules", + "type": "drupal-module", + "license": "GPL-2.0-or-later", + "homepage": "https://www.drupal.org/project/features", + "require": { + "drupal/core": "^9.4 || ^10", + "drupal/config_update": "^1.4 || ^2" + }, + "extra": { + "drush": { + "services": { + "drush.services.yml": "^10 || ^11" + } + } } - } } diff --git a/drush/features.drush8.inc b/drush/features.drush8.inc deleted file mode 100644 index 7e51c320a4f9658e4d0f83d297c9314fb20ea715..0000000000000000000000000000000000000000 --- a/drush/features.drush8.inc +++ /dev/null @@ -1,907 +0,0 @@ -<?php - -/** - * @file - * Features module drush integration. - */ - -use Drupal\features\FeaturesBundleInterface; -use Drupal\features\FeaturesManagerInterface; -use Drupal\features\Plugin\FeaturesGeneration\FeaturesGenerationWrite; -use Drupal\Component\Diff\DiffFormatter; - -/** - * Implements hook_drush_command(). - */ -function features_drush_command() { - $items = []; - - $items['features-status'] = [ - 'description' => 'Display current Features settings.', - 'aliases' => ['fs'], - ]; - - $items['features-list-packages'] = [ - 'description' => 'Display a list of all existing features and packages available to be generated. If a package name is provided as an argument, then all of the configuration objects assigned to that package will be listed.', - 'examples' => [ - "drush features-list-packages" => 'Display a list of all existing featurea and packages available to be generated.', - "drush features-list-packages 'example_article'" => "Display a list of all configuration objects assigned to the 'example_article' package.", - ], - 'arguments' => [ - 'package' => 'The package to list. Optional; if specified, lists all configuration objects assigned to that package. If no package is specified, lists all of the features.', - ], - 'outputformat' => [ - 'default' => 'table', - 'pipe-format' => 'list', - 'field-labels' => [ - 'name' => 'Name', - 'machine_name' => 'Machine name', - 'status' => 'Status', - 'version' => 'Version', - 'state' => 'State', - 'object' => 'Configuration object', - ], - 'output-data-type' => 'format-table', - ], - 'aliases' => ['fl'], - ]; - - $items['features-import-all'] = [ - 'description' => 'Import module config from all installed features.', - 'examples' => [ - "drush features-import-all" => 'Import module config from all installed features.', - ], - 'aliases' => ['fra', 'fia', 'fim-all'], - ]; - - $items['features-export'] = [ - 'description' => "Export the configuration on your site into a custom module.", - 'arguments' => [ - 'package' => 'A space delimited list of features to export.', - ], - 'options' => [ - 'add-profile' => 'Package features into an install profile.', - ], - 'examples' => [ - "drush features-export" => 'Export all available packages.', - "drush features-export example_article example_page" => "Export the example_article and example_page packages.", - "drush features-export --add-profile" => "Export all available packages and add them to an install profile.", - ], - // Add previous "fu" alias for compatibility. - 'aliases' => ['fex', 'fu', 'fua', 'fu-all'], - ]; - - $items['features-add'] = [ - 'description' => "Add a config item to a feature package.", - 'arguments' => [ - 'feature' => 'Feature package to export and add config to.', - 'components' => 'Patterns of config to add, see features-components for the format of patterns.', - ], - 'aliases' => ['fa', 'fe'], - ]; - - $items['features-components'] = [ - 'description' => 'List features components.', - 'arguments' => [ - 'patterns' => 'The features components type to list. Omit this argument to list all components.', - ], - 'options' => [ - 'exported' => [ - 'description' => 'Show only components that have been exported.', - ], - 'not-exported' => [ - 'description' => 'Show only components that have not been exported.', - ], - ], - 'aliases' => ['fc'], - ]; - - $items['features-diff'] = [ - 'description' => "Show the difference between the active config and the default config stored in a feature package.", - 'arguments' => [ - 'feature' => 'The feature in question.', - ], - 'options' => [ - 'ctypes' => 'Comma separated list of component types to limit the output to. Defaults to all types.', - 'lines' => 'Generate diffs with <n> lines of context instead of the usual two.', - ], - 'aliases' => ['fd'], - ]; - - $items['features-import'] = [ - 'description' => "Import a module config into your site.", - 'arguments' => [ - 'feature' => 'A space delimited list of features or feature:component pairs to import.', - ], - 'options' => [ - 'force' => "Force import even if config is not overridden.", - ], - 'examples' => [ - 'drush features-import foo:node.type.page foo:taxonomy.vocabulary.tags bar' => 'Import node and taxonomy config of feature "foo". Import all config of feature "bar".', - ], - 'aliases' => ['fim', 'fr'], - ]; - - foreach ($items as $name => &$item) { - $item['options']['bundle'] = [ - 'description' => 'Use a specific bundle namespace.', - ]; - } - - return $items; -} - -/** - * Applies global options for Features drush commands. - * - * The option --name="bundle_name" sets the bundle namespace. - * - * @return \Drupal\features\FeaturesAssignerInterface - */ -function _drush_features_options() { - /** @var \Drupal\features\FeaturesAssignerInterface $assigner */ - $assigner = \Drupal::service('features_assigner'); - $bundle_name = drush_get_option('bundle'); - if (!empty($bundle_name)) { - $bundle = $assigner->applyBundle($bundle_name); - if ($bundle->getMachineName() != $bundle_name) { - drush_log(dt('Bundle @name not found. Using default.', ['@name' => $bundle_name]), 'warning'); - } - } - else { - $assigner->assignConfigPackages(); - } - return $assigner; -} - -/** - * Provides Drush command callback for features-status. - */ -function drush_features_status() { - $args = func_get_args(); - $assigner = _drush_features_options(); - - /** @var \Drupal\features\FeaturesManagerInterface $manager */ - $manager = \Drupal::service('features.manager'); - $current_bundle = $assigner->getBundle(); - $export_settings = $manager->getExportSettings(); - $methods = $assigner->getEnabledAssigners(); - if ($current_bundle->isDefault()) { - drush_print(dt('Current bundle: none')); - } - else { - drush_print(dt('Current bundle: @name (@machine_name)', - [ - '@name' => $current_bundle->getName(), - '@machine_name' => $current_bundle->getMachineName(), - ])); - } - drush_print(dt('Export folder: @folder', ['@folder' => $export_settings['folder']])); - $dt_args = ['@methods' => implode(', ', array_keys($methods))]; - drush_print(dt('The following assignment methods are enabled:')); - drush_print(dt(' @methods', $dt_args)); - - if (!empty($args)) { - $config = $manager->getConfigCollection(); - if (count($args) > 1) { - print_r(array_keys($config)); - } - else { - print_r($config[$args[0]]); - } - } -} - -/** - * Drush command callback for features-list-packages. - * - * @param string $package_name - * (optional) The package name. - * - * @return array|bool - */ -function drush_features_list_packages($package_name = '') { - $assigner = _drush_features_options(); - $current_bundle = $assigner->getBundle(); - $namespace = $current_bundle->isDefault() ? FeaturesBundleInterface::DEFAULT_BUNDLE : $current_bundle->getMachineName(); - - /** @var \Drupal\features\FeaturesManagerInterface $manager */ - $manager = \Drupal::service('features.manager'); - $packages = $manager->getPackages(); - - $packages = $manager->filterPackages($packages, $namespace); - $result = []; - - // If no package was specified, list all packages. - if (empty($package_name)) { - drush_hide_output_fields(['object']); - foreach ($packages as $package) { - $overrides = $manager->detectOverrides($package); - $state = $package->getState(); - if (!empty($overrides) && ($package->getStatus() != FeaturesManagerInterface::STATUS_NO_EXPORT)) { - $state = FeaturesManagerInterface::STATE_OVERRIDDEN; - } - - $result[$package->getMachineName()] = [ - 'name' => $package->getName(), - 'machine_name' => $package->getMachineName(), - 'status' => $manager->statusLabel($package->getStatus()), - 'version' => $package->getVersion(), - 'state' => ($state != FeaturesManagerInterface::STATE_DEFAULT) ? $manager->stateLabel($state) : '', - ]; - } - return $result; - } - // If a valid package was listed, list its configuration. - else { - foreach ($packages as $package) { - if ($package->getMachineName() == $package_name) { - drush_hide_output_fields([ - 'machine_name', - 'name', - 'status', - 'version', - 'state', - ]); - foreach ($package->getConfig() as $item_name) { - $result[$item_name] = [ - 'object' => $item_name, - ]; - } - return $result; - } - } - - } - - // If no matching package found, return an error. - drush_log(dt('Package "@package" not found.', ['@package' => $package_name]), 'warning'); - return FALSE; -} - -/** - * Drush command callback for features-import-all. - */ -function drush_features_import_all() { - $assigner = _drush_features_options(); - $current_bundle = $assigner->getBundle(); - $namespace = $current_bundle->isDefault() ? FeaturesBundleInterface::DEFAULT_BUNDLE : $current_bundle->getMachineName(); - - /** @var \Drupal\features\FeaturesManagerInterface $manager */ - $manager = \Drupal::service('features.manager'); - $packages = $manager->getPackages(); - $packages = $manager->filterPackages($packages, $namespace); - $overridden = []; - - foreach ($packages as $package) { - $overrides = $manager->detectOverrides($package); - $missing = $manager->detectMissing($package); - if ((!empty($missing) || !empty($overrides)) && ($package->getStatus() == FeaturesManagerInterface::STATUS_INSTALLED)) { - $overridden[] = $package->getMachineName(); - } - } - - if (!empty($overridden)) { - call_user_func_array('drush_features_import', $overridden); - } - else { - drush_log(dt('Current state already matches active config, aborting.'), 'ok'); - } -} - -/** - * Provides Drush command callback for features-export. - */ -function drush_features_export($packages = NULL) { - $packages = func_get_args(); - $assigner = _drush_features_options(); - - /** @var \Drupal\features\FeaturesManagerInterface $manager */ - $manager = \Drupal::service('features.manager'); - /** @var \Drupal\features\FeaturesGeneratorInterface $generator */ - $generator = \Drupal::service('features_generator'); - - $current_bundle = $assigner->getBundle(); - - if (drush_get_option('add-profile')) { - if ($current_bundle->isDefault) { - return drush_set_error('', dt("Must specify a profile name with --name")); - } - $current_bundle->setIsProfile(TRUE); - } - - $all_packages = $manager->getPackages(); - foreach ($packages as $name) { - if (!isset($all_packages[$name])) { - return drush_set_error('', dt("The package @name does not exist.", ['@name' => $name])); - } - } - - if (empty($packages)) { - $packages = $all_packages; - $dt_args = ['@modules' => implode(', ', array_keys($packages))]; - drush_print(dt('The following extensions will be exported: @modules', $dt_args)); - if (!drush_confirm(dt('Do you really want to continue?'))) { - return drush_user_abort('Aborting.'); - } - } - - // If any packages exist, confirm before overwriting. - if ($existing_packages = $manager->listPackageDirectories($packages, $current_bundle)) { - foreach ($existing_packages as $name => $directory) { - drush_print(dt("The extension @name already exists at @directory.", ['@name' => $name, '@directory' => $directory])); - } - // Apparently, format_plural is not always available. - if (count($existing_packages) == 1) { - $message = dt('Would you like to overwrite it?'); - } - else { - $message = dt('Would you like to overwrite them?'); - } - if (!drush_confirm($message)) { - return drush_user_abort(); - } - } - - // Use the write generation method. - $method_id = FeaturesGenerationWrite::METHOD_ID; - $result = $generator->generatePackages($method_id, $current_bundle, $packages); - - foreach ($result as $message) { - $type = $message['success'] ? 'success' : 'error'; - drush_log($message['message'], $message['variables'], $type); - } -} - -/** - * Adds a component to a features module. - * - * @param - * The selected components. - */ -function drush_features_add() { - if ($args = func_get_args()) { - $assigner = _drush_features_options(); - - /** @var \Drupal\features\FeaturesManagerInterface $manager */ - $manager = \Drupal::service('features.manager'); - /** @var \Drupal\features\FeaturesGeneratorInterface $generator */ - $generator = \Drupal::service('features_generator'); - - $current_bundle = $assigner->getBundle(); - - $module = array_shift($args); - if (empty($args)) { - return drush_set_error('', 'No components supplied.'); - } - $components = _drush_features_component_list(); - $options = [ - 'exported' => FALSE, - ]; - - $filtered_components = _drush_features_component_filter($components, $args, $options); - $items = $filtered_components['components']; - - if (empty($items)) { - return drush_set_error('', 'No components to add.'); - } - - $packages = [$module]; - // If any packages exist, confirm before overwriting. - if ($existing_packages = $manager->listPackageDirectories($packages)) { - foreach ($existing_packages as $name => $directory) { - drush_print(dt("The extension @name already exists at @directory.", ['@name' => $name, '@directory' => $directory])); - } - // Apparently, format_plural is not always available. - if (count($existing_packages) == 1) { - $message = dt('Would you like to overwrite it?'); - } - else { - $message = dt('Would you like to overwrite them?'); - } - if (!drush_confirm($message)) { - return drush_user_abort(); - } - } - else { - $package = $manager->initPackage($module, NULL, '', 'module', $current_bundle); - list($full_name, $path) = $manager->getExportInfo($package, $current_bundle); - drush_print(dt('Will create a new extension @name in @directory', ['@name' => $full_name, '@directory' => $path])); - if (!drush_confirm(dt('Do you really want to continue?'))) { - drush_die('Aborting.'); - } - } - - $config = _drush_features_build_config($items); - - $manager->assignConfigPackage($module, $config); - - // Use the write generation method. - $method_id = FeaturesGenerationWrite::METHOD_ID; - $result = $generator->generatePackages($method_id, $current_bundle, $packages); - - foreach ($result as $message) { - $type = $message['success'] ? 'success' : 'error'; - drush_log($message['message'], $message['variables'], $type); - } - } - else { - return drush_set_error('', 'No feature name given.'); - } -} - -/** - * Lists components, with pattern matching. - */ -function drush_features_components() { - $args = func_get_args(); - _drush_features_options(); - - $components = _drush_features_component_list(); - ksort($components); - // If no args supplied, prompt with a list. - if (empty($args)) { - $types = array_keys($components); - array_unshift($types, 'all'); - $choice = drush_choice($types, 'Enter a number to choose which component type to list.'); - if ($choice === FALSE) { - return; - } - - $args = ($choice == 0) ? ['*'] : [$types[$choice]]; - } - $options = [ - 'provided by' => TRUE, - ]; - if (drush_get_option(['exported', 'e'], NULL)) { - $options['not exported'] = FALSE; - } - elseif (drush_get_option(['not-exported', 'o'], NULL)) { - $options['exported'] = FALSE; - } - - $filtered_components = _drush_features_component_filter($components, $args, $options); - if ($filtered_components) { - _drush_features_component_print($filtered_components); - } -} - -/** - * Lists the differences in the package config vs the active store. - * - * @param string $package - * The machine name of a package. - */ -function drush_features_diff() { - if (!$args = func_get_args()) { - drush_print_table(drush_features_list_packages()); - return; - } - - /** @var \Drupal\features\FeaturesManagerInterface $manager */ - $manager = \Drupal::service('features.manager'); - /** @var \Drupal\features\FeaturesAssignerInterface $assigner */ - $assigner = \Drupal::service('features_assigner'); - $assigner->assignConfigPackages(); - - $module = $args[0]; - $filter_ctypes = drush_get_option("ctypes"); - if ($filter_ctypes) { - $filter_ctypes = explode(',', $filter_ctypes); - } - - $feature = $manager->loadPackage($module, TRUE); - if (empty($feature)) { - drush_log(dt('No such feature is available: @module', ['@module' => $module]), 'error'); - return; - } - - $lines = drush_get_option('lines'); - $lines = isset($lines) ? $lines : 2; - - $formatter = new DiffFormatter(); - $formatter->leading_context_lines = $lines; - $formatter->trailing_context_lines = $lines; - $formatter->show_header = FALSE; - - if (drush_get_context('DRUSH_NOCOLOR')) { - $red = $green = "%s"; - } - else { - $red = "\033[31;40m\033[1m%s\033[0m"; - $green = "\033[0;32;40m\033[1m%s\033[0m"; - } - - $overrides = $manager->detectOverrides($feature); - $missing = $manager->reorderMissing($manager->detectMissing($feature)); - $overrides = array_merge($overrides, $missing); - - if (empty($overrides)) { - drush_print(dt('Active config matches stored config for @module.', ['@module' => $module])); - } - else { - /** @var \Drupal\config_update\ConfigDiffInterface $config_diff */ - $config_diff = \Drupal::service('config_update.config_diff'); - /** @var \Drupal\Core\Config\StorageInterface $active_storage */ - $active_storage = \Drupal::service('config.storage'); - - // Print key for colors. - drush_print(dt('Legend: ')); - drush_print(sprintf($red, dt('Code: drush features-import will replace the active config with the displayed code.'))); - drush_print(sprintf($green, dt('Active: drush features-export will update the exported feature with the displayed active config'))); - - foreach ($overrides as $name) { - $message = ''; - if (in_array($name, $missing)) { - $message = sprintf($red, t('(missing from active)')); - $extension = []; - } - else { - $active = $manager->getActiveStorage()->read($name); - $extension = $manager->getExtensionStorages()->read($name); - if (empty($extension)) { - $extension = []; - $message = sprintf($green, t('(not exported)')); - } - $diff = $config_diff->diff($extension, $active); - $rows = explode("\n", $formatter->format($diff)); - } - drush_print(); - drush_print(dt("Config @name @message", ['@name' => $name, '@message' => $message])); - if (!empty($extension)) { - foreach ($rows as $row) { - if (strpos($row, '>') === 0) { - drush_print(sprintf($green, $row)); - } - elseif (strpos($row, '<') === 0) { - drush_print(sprintf($red, $row)); - } - else { - drush_print($row); - } - } - } - } - } -} - -/** - * Imports module config into the active store. - * - * Same as the old "revert" functionality. - */ -function drush_features_import() { - if ($args = func_get_args()) { - _drush_features_options(); - - // Determine if revert should be forced. - $force = drush_get_option('force'); - // Determine if -y was supplied. If so, we can filter out needless output - // from this command. - $skip_confirmation = drush_get_context('DRUSH_AFFIRMATIVE'); - - /** @var \Drupal\features\FeaturesManagerInterface $manager */ - $manager = \Drupal::service('features.manager'); - - // Parse list of arguments. - $modules = []; - foreach ($args as $arg) { - $arg = explode(':', $arg); - $module = array_shift($arg); - $component = array_shift($arg); - - if (isset($module)) { - if (empty($component)) { - // If we received just a feature name, this means that we need all of - // its components. - $modules[$module] = TRUE; - } - elseif ($modules[$module] !== TRUE) { - if (!isset($modules[$module])) { - $modules[$module] = []; - } - $modules[$module][] = $component; - } - } - } - - // Process modules. - foreach ($modules as $module => $components_needed) { - - $dt_args['@module'] = $module; - /** @var \Drupal\features\Package $feature */ - $feature = $manager->loadPackage($module, TRUE); - if (empty($feature)) { - drush_log(dt('No such feature is available: @module', $dt_args), 'error'); - return; - } - - if ($feature->getStatus() != FeaturesManagerInterface::STATUS_INSTALLED) { - drush_log(dt('No such feature is installed: @module', $dt_args), 'error'); - return; - } - - // Forcefully revert all components of a feature. - if ($force) { - $components = $feature->getConfigOrig(); - } - // Only revert components that are detected to be Overridden. - else { - $components = $manager->detectOverrides($feature); - $missing = $manager->reorderMissing($manager->detectMissing($feature)); - // Be sure to import missing components first. - $components = array_merge($missing, $components); - } - - if (!empty($components_needed) && is_array($components_needed)) { - $components = array_intersect($components, $components_needed); - } - - if (empty($components)) { - drush_log(dt('Current state already matches active config, aborting.'), 'ok'); - } - else { - // Determine which config the user wants to import/revert. - $config_to_create = []; - foreach ($components as $component) { - $dt_args['@component'] = $component; - $confirmation_message = 'Do you really want to import @module : @component?'; - if ($skip_confirmation || drush_confirm(dt($confirmation_message, $dt_args))) { - $config_to_create[$component] = ''; - } - } - - // Perform the import/revert. - $config_imported = $manager->createConfiguration($config_to_create); - - // List the results. - foreach ($components as $component) { - $dt_args['@component'] = $component; - if (isset($config_imported['new'][$component])) { - drush_log(dt('Imported @module : @component.', $dt_args), 'ok'); - } - elseif (isset($config_imported['updated'][$component])) { - drush_log(dt('Reverted @module : @component.', $dt_args), 'ok'); - } - elseif (!isset($config_to_create[$component])) { - drush_log(dt('Skipping @module : @component.', $dt_args), 'ok'); - } - else { - drush_log(dt('Error importing @module : @component.', $dt_args), 'error'); - } - } - } - } - } - else { - drush_print_table(drush_features_list_packages()); - return; - } -} - -/** - * Returns an array of full config names given a array[$type][$component]. - * - * @param array $items - * The items to return data for. - */ -function _drush_features_build_config(array $items) { - /** @var \Drupal\features\FeaturesManagerInterface $manager */ - $manager = \Drupal::service('features.manager'); - $result = []; - foreach ($items as $config_type => $item) { - foreach ($item as $item_name => $title) { - $result[] = $manager->getFullName($config_type, $item_name); - } - } - return $result; -} - -/** - * Returns a listing of all known components, indexed by source. - */ -function _drush_features_component_list() { - $result = []; - /** @var \Drupal\features\FeaturesManagerInterface $manager */ - $manager = \Drupal::service('features.manager'); - $config = $manager->getConfigCollection(); - foreach ($config as $item_name => $item) { - $result[$item->getType()][$item->getShortName()] = $item->getLabel(); - } - return $result; -} - -/** - * Filters components by patterns. - */ -function _drush_features_component_filter($all_components, $patterns = [], $options = []) { - $options += [ - 'exported' => TRUE, - 'not exported' => TRUE, - 'provided by' => FALSE, - ]; - $pool = []; - // Maps exported components to feature modules. - $components_map = _drush_features_get_component_map(); - // First filter on exported state. - foreach ($all_components as $source => $components) { - foreach ($components as $name => $title) { - $exported = count($components_map[$source][$name]) > 0; - if ($exported) { - if ($options['exported']) { - $pool[$source][$name] = $title; - } - } - else { - if ($options['not exported']) { - $pool[$source][$name] = $title; - } - } - } - } - - $state_string = ''; - - if (!$options['exported']) { - $state_string = 'unexported'; - } - elseif (!$options['not exported']) { - $state_string = 'exported'; - } - - $selected = []; - foreach ($patterns as $pattern) { - // Rewrite * to %. Let users use both as wildcard. - $pattern = strtr($pattern, ['*' => '%']); - $sources = []; - list($source_pattern, $component_pattern) = explode(':', $pattern, 2); - // If source is empty, use a pattern. - if ($source_pattern == '') { - $source_pattern = '%'; - } - if ($component_pattern == '') { - $component_pattern = '%'; - } - - $preg_source_pattern = strtr(preg_quote($source_pattern, '/'), ['%' => '.*']); - $preg_component_pattern = strtr(preg_quote($component_pattern, '/'), ['%' => '.*']); - // If it isn't a pattern, but a simple string, we don't anchor the - // pattern. This allows for abbreviating. Otherwise, we do, as this seems - // more natural for patterns. - if (strpos($source_pattern, '%') !== FALSE) { - $preg_source_pattern = '^' . $preg_source_pattern . '$'; - } - if (strpos($component_pattern, '%') !== FALSE) { - $preg_component_pattern = '^' . $preg_component_pattern . '$'; - } - $matches = []; - - // Find the sources. - $all_sources = array_keys($pool); - $matches = preg_grep('/' . $preg_source_pattern . '/', $all_sources); - if (count($matches) > 0) { - // If we have multiple matches and the source string wasn't a - // pattern, check if one of the matches is equal to the pattern, and - // use that, or error out. - if (count($matches) > 1 and $preg_source_pattern[0] != '^') { - if (in_array($source_pattern, $matches)) { - $matches = [$source_pattern]; - } - else { - return drush_set_error('', dt('Ambiguous source "@source", matches @matches', [ - '@source' => $source_pattern, - '@matches' => implode(', ', $matches), - ])); - } - } - // Loose the indexes preg_grep preserved. - $sources = array_values($matches); - } - else { - return drush_set_error('', dt('No @state sources match "@source"', ['@state' => $state_string, '@source' => $source_pattern])); - } - - // Now find the components. - foreach ($sources as $source) { - // Find the components. - $all_components = array_keys($pool[$source]); - // See if there's any matches. - $matches = preg_grep('/' . $preg_component_pattern . '/', $all_components); - if (count($matches) > 0) { - // If we have multiple matches and the components string wasn't a - // pattern, check if one of the matches is equal to the pattern, and - // use that, or error out. - if (count($matches) > 1 and $preg_component_pattern[0] != '^') { - if (in_array($component_pattern, $matches)) { - $matches = [$component_pattern]; - } - else { - return drush_set_error('', dt('Ambiguous component "@component", matches @matches', [ - '@component' => $component_pattern, - '@matches' => implode(', ', $matches), - ])); - } - } - if (!is_array($selected[$source])) { - $selected[$source] = []; - } - $selected[$source] += array_intersect_key($pool[$source], array_flip($matches)); - } - else { - // No matches. If the source was a pattern, just carry on, else - // error out. Allows for patterns like :*field*. - if ($preg_source_pattern[0] != '^') { - return drush_set_error('', dt('No @state @source components match "@component"', [ - '@state' => $state_string, - '@component' => $component_pattern, - '@source' => $source, - ])); - } - } - } - } - - // Lastly, provide feature module information on the selected components, if - // requested. - $provided_by = []; - if ($options['provided by'] && $options['exported']) { - foreach ($selected as $source => $components) { - foreach ($components as $name => $title) { - $exported = count($components_map[$source][$name]) > 0; - if ($exported) { - $provided_by[$source . ':' . $name] = implode(', ', $components_map[$source][$name]); - } - } - } - } - - return [ - 'components' => $selected, - 'sources' => $provided_by, - ]; -} - -/** - * Provides a component to feature map (port of features_get_component_map). - */ -function _drush_features_get_component_map() { - $result = []; - /** @var \Drupal\features\FeaturesManagerInterface $manager */ - $manager = \Drupal::service('features.manager'); - // Recalc full config list without running assignments. - $config = $manager->getConfigCollection(); - $packages = $manager->getPackages(); - - foreach ($config as $item_name => $item) { - $type = $item->getType(); - $short_name = $item->getShortName(); - $name = $item->getName(); - if (!isset($result[$type][$short_name])) { - $result[$type][$short_name] = []; - } - if (!empty($item->getPackage())) { - $package = $packages[$item->getPackage()]; - $result[$type][$short_name][] = $package->getMachineName(); - } - } - - return $result; -} - -/** - * Prints a list of filtered components. - */ -function _drush_features_component_print($filtered_components) { - $rows = [[dt('Available sources')]]; - foreach ($filtered_components['components'] as $source => $components) { - foreach ($components as $name => $value) { - $row = [$source . ':' . $name]; - if (isset($filtered_components['sources'][$source . ':' . $name])) { - $row[] = dt('Provided by') . ': ' . $filtered_components['sources'][$source . ':' . $name]; - } - $rows[] = $row; - } - } - - drush_print_table($rows, TRUE); -} diff --git a/features.info.yml b/features.info.yml index e4472942ad007ccda79e51f90f45e30c4c0f8480..f2ded86201591097575adb8115a4259cb1daa9f1 100644 --- a/features.info.yml +++ b/features.info.yml @@ -2,7 +2,7 @@ name: 'Features' type: module description: 'Enables administrators to package configuration into modules.' package: Development -core_version_requirement: ^8.8 || ^9 +core_version_requirement: ^9.4 || ^10 dependencies: - drupal:config - config_update:config_update diff --git a/features.post_update.php b/features.post_update.php index 1ef317a7f2e7489325c721323d6ed5b4aa13d51a..307fa94683edcb495a89b0155fd5eec27e0dadf5 100644 --- a/features.post_update.php +++ b/features.post_update.php @@ -25,3 +25,10 @@ function features_post_update_features_assigner_args() { function features_post_update_prefixed_dependencies() { // Empty post-update hook. } + +/** + * Clear caches due to changes in features_assigner service arguments. + */ +function features_post_update_features_assigner_args2() { + // Empty post-update hook. +} diff --git a/features.services.yml b/features.services.yml index c42aa80f6c0e2fcd7d5cc26d4a33a99145ef1b7a..2b9e1e275c00bee6c88b0129c143ca4ffcd18271 100644 --- a/features.services.yml +++ b/features.services.yml @@ -7,7 +7,7 @@ services: arguments: ['@container.namespaces', '@cache.discovery', '@module_handler'] features_assigner: class: Drupal\features\FeaturesAssigner - arguments: ['@features.manager', '@plugin.manager.features_assignment_method', '@entity_type.manager', '@config.factory', '@config.storage', '%install_profile%'] + arguments: ['@features.manager', '@plugin.manager.features_assignment_method', '@entity_type.manager', '@config.factory', '@config.storage', '%install_profile%', '@request_stack'] calls: - [initFeaturesManager] features_generator: @@ -17,7 +17,7 @@ services: - [initFeaturesManager] features.manager: class: Drupal\features\FeaturesManager - arguments: ['@app.root', '@entity_type.manager', '@config.factory', '@config.storage', '@config.manager', '@module_handler', '@features.config_update', '@extension.list.module'] + arguments: ['%app.root%', '@entity_type.manager', '@config.factory', '@config.storage', '@config.manager', '@module_handler', '@features.config_update', '@extension.list.module', '@extension.path.resolver'] features.config_update: class: Drupal\config_update\ConfigReverter @@ -25,16 +25,25 @@ services: features.extension_storage: class: Drupal\features\FeaturesInstallStorage - arguments: ['@config.storage'] + arguments: ['@config.storage', '@extension.path.resolver'] features.extension_optional_storage: class: Drupal\features\FeaturesInstallStorage - arguments: ['@config.storage', 'config/optional'] + arguments: ['@config.storage', '@extension.path.resolver', 'config/optional'] features.config.installer: class: Drupal\features\FeaturesConfigInstaller decorates: config.installer - arguments: ['@features.config.installer.inner', '@features.manager', '@config.factory', '@config.storage', '@config.typed', '@config.manager', '@event_dispatcher', '%install_profile%'] + arguments: + - '@features.config.installer.inner' + - '@features.manager' + - '@config.factory' + - '@config.storage' + - '@config.typed' + - '@config.manager' + - '@event_dispatcher' + - '%install_profile%' + - '@extension.path.resolver' decoration_priority: 9 logger.channel.features: diff --git a/modules/features_ui/features_ui.info.yml b/modules/features_ui/features_ui.info.yml index 2574aea4a805641b6c718b55f83e38fad0c8be6f..c5b9406493e7196dec7212736f9635762b342ed2 100644 --- a/modules/features_ui/features_ui.info.yml +++ b/modules/features_ui/features_ui.info.yml @@ -2,7 +2,7 @@ name: Features UI type: module description: 'Provides the user interface for Features.' package: Development -core_version_requirement: ^8.8 || ^9 +core_version_requirement: ^9.4 || ^10 configure: features.assignment dependencies: - features:features diff --git a/modules/features_ui/src/Form/AssignmentBaseForm.php b/modules/features_ui/src/Form/AssignmentBaseForm.php index 23f0b2aa23930a1c38cf3b8713483a31ff75f40b..0bfe709d1b3cf43a4c86b7accb4b44d8886ecb0d 100644 --- a/modules/features_ui/src/Form/AssignmentBaseForm.php +++ b/modules/features_ui/src/Form/AssignmentBaseForm.php @@ -27,7 +27,7 @@ class AssignmentBaseForm extends AssignmentFormBase { // Pass the last argument to limit the select to config entity types that // provide bundles for other entity types. - $this->setConfigTypeSelect($form, $settings['types']['config'], $this->t('base'), TRUE); + $this->setConfigTypeSelect($form, $settings['types']['config'], $this->t('base')); // Pass the last argument to limit the select to content entity types do // not have config entity provided bundles, thus avoiding duplication with // the config type select options. diff --git a/modules/features_ui/src/Form/FeaturesEditForm.php b/modules/features_ui/src/Form/FeaturesEditForm.php index 75cb3103c932c4c60fb4e084485bf96d45bf08f3..a2d71318cc87ddb31e8e3939c6284b7a8a09e4e7 100644 --- a/modules/features_ui/src/Form/FeaturesEditForm.php +++ b/modules/features_ui/src/Form/FeaturesEditForm.php @@ -152,7 +152,10 @@ class FeaturesEditForm extends FormBase { * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state, $featurename = '') { - $session = $this->getRequest()->getSession(); + if ($this->getRequest()->hasSession()) { + $session = $this->getRequest()->getSession(); + } + $trigger = $form_state->getTriggeringElement(); if (isset($trigger['#name']) && $trigger['#name'] == 'package') { // Save current bundle name for later ajax callback. diff --git a/modules/features_ui/tests/src/Functional/FeaturesBundleUiTest.php b/modules/features_ui/tests/src/Functional/FeaturesBundleUiTest.php index 6a0efabca8f4d8ba8155c89bb1ad2904cd58de76..33351bf180e55ffa34a96f4f2c645895b3b24457 100644 --- a/modules/features_ui/tests/src/Functional/FeaturesBundleUiTest.php +++ b/modules/features_ui/tests/src/Functional/FeaturesBundleUiTest.php @@ -91,14 +91,14 @@ class FeaturesBundleUiTest extends BrowserTestBase { // Check initial form. $this->drupalGet('admin/config/development/features/bundle/_exclude/default'); - $this->assertFieldChecked('edit-types-config-features-bundle', 'features_bundle is checked'); - $this->assertNoFieldChecked('edit-types-config-system-simple', 'system_simple is not checked'); - $this->assertNoFieldChecked('edit-types-config-user-role', 'user_role is not checked'); - $this->assertFieldChecked('edit-curated', 'curated is checked'); - $this->assertFieldChecked('edit-module-namespace', 'namespace is checked'); + $this->assertSession()->checkboxChecked('edit-types-config-features-bundle'); + $this->assertSession()->checkboxNotChecked('edit-types-config-system-simple'); + $this->assertSession()->checkboxNotChecked('edit-types-config-user-role'); + $this->assertSession()->checkboxChecked('edit-curated'); + $this->assertSession()->checkboxChecked('edit-module-namespace'); // Configure the form. - $this->drupalPostForm(NULL, [ + $this->submitForm([ 'types[config][system_simple]' => TRUE, 'types[config][user_role]' => FALSE, 'curated' => TRUE, @@ -107,11 +107,11 @@ class FeaturesBundleUiTest extends BrowserTestBase { // Check form results. $this->drupalGet('admin/config/development/features/bundle/_exclude/default'); - $this->assertFieldChecked('edit-types-config-features-bundle', 'Saved, features_bundle is checked'); - $this->assertFieldChecked('edit-types-config-system-simple', 'Saved, system_simple is checked'); - $this->assertNoFieldChecked('edit-types-config-user-role', 'Saved, user_role is not checked'); - $this->assertFieldChecked('edit-curated', 'Saved, curated is checked'); - $this->assertNoFieldChecked('edit-module-namespace', 'Saved, namespace is not checked'); + $this->assertSession()->checkboxChecked('edit-types-config-features-bundle'); + $this->assertSession()->checkboxChecked('edit-types-config-system-simple'); + $this->assertSession()->checkboxNotChecked('edit-types-config-user-role'); + $this->assertSession()->checkboxChecked('edit-curated'); + $this->assertSession()->checkboxNotChecked('edit-module-namespace'); // Check final values. $settings = $this->defaultBundle()->getAssignmentSettings('exclude'); @@ -143,19 +143,19 @@ class FeaturesBundleUiTest extends BrowserTestBase { // Can we visit the config page with no settings? $this->drupalGet('admin/config/development/features/bundle/_exclude/default'); - $this->assertNoFieldChecked('edit-types-config-features-bundle', 'features_bundle is not checked'); - $this->assertNoFieldChecked('edit-types-config-system-simple', 'system_simple is not checked'); - $this->assertNoFieldChecked('edit-types-config-user-role', 'user_role is not checked'); - $this->assertNoFieldChecked('edit-curated', 'curated is not checked'); - $this->assertNoFieldChecked('edit-module-namespace', 'namespace is not checked'); + $this->assertSession()->checkboxNotChecked('edit-types-config-features-bundle'); + $this->assertSession()->checkboxNotChecked('edit-types-config-system-simple'); + $this->assertSession()->checkboxNotChecked('edit-types-config-user-role'); + $this->assertSession()->checkboxNotChecked('edit-curated'); + $this->assertSession()->checkboxNotChecked('edit-module-namespace'); // Can we enable the method? $this->drupalGet('admin/config/development/features/bundle'); - $this->assertNoFieldChecked('edit-enabled-exclude', 'Exclude disabled'); - $this->drupalPostForm(NULL, [ + $this->assertSession()->checkboxNotChecked('edit-enabled-exclude'); + $this->submitForm([ 'enabled[exclude]' => TRUE, ], 'Save settings'); - $this->assertFieldChecked('edit-enabled-exclude', 'Exclude enabled'); + $this->assertSession()->checkboxChecked('edit-enabled-exclude'); // Check new settings. $settings = $this->defaultBundle()->getAssignmentSettings('exclude'); @@ -168,9 +168,10 @@ class FeaturesBundleUiTest extends BrowserTestBase { // Can we run assignment with no settings? $this->drupalGet('admin/config/development/features'); + $this->drupalGet('admin/config/development/features/bundle/_exclude/default'); // Can we configure the method? - $this->drupalPostForm('admin/config/development/features/bundle/_exclude/default', [ + $this->submitForm([ 'types[config][system_simple]' => TRUE, 'types[config][user_role]' => FALSE, 'curated' => TRUE, @@ -179,11 +180,11 @@ class FeaturesBundleUiTest extends BrowserTestBase { // Check form results. $this->drupalGet('admin/config/development/features/bundle/_exclude/default'); - $this->assertNoFieldChecked('edit-types-config-features-bundle', 'Saved, features_bundle is not checked'); - $this->assertFieldChecked('edit-types-config-system-simple', 'Saved, system_simple is checked'); - $this->assertNoFieldChecked('edit-types-config-user-role', 'Saved, user_role is not checked'); - $this->assertFieldChecked('edit-curated', 'Saved, curated is checked'); - $this->assertNoFieldChecked('edit-module-namespace', 'Saved, namespace is not checked'); + $this->assertSession()->checkboxNotChecked('edit-types-config-features-bundle'); + $this->assertSession()->checkboxChecked('edit-types-config-system-simple'); + $this->assertSession()->checkboxNotChecked('edit-types-config-user-role'); + $this->assertSession()->checkboxChecked('edit-curated'); + $this->assertSession()->checkboxNotChecked('edit-module-namespace'); // Check final values. $settings = $this->defaultBundle()->getAssignmentSettings('exclude'); diff --git a/modules/features_ui/tests/src/Functional/FeaturesCreateUiTest.php b/modules/features_ui/tests/src/Functional/FeaturesCreateUiTest.php index 27197b67f4d7d7858fab32e820d9bc321c41d927..3fc72335051aa74f2e40285200568ce8433730b2 100644 --- a/modules/features_ui/tests/src/Functional/FeaturesCreateUiTest.php +++ b/modules/features_ui/tests/src/Functional/FeaturesCreateUiTest.php @@ -45,7 +45,7 @@ class FeaturesCreateUiTest extends BrowserTestBase { $this->drupalPlaceBlock('local_actions_block'); $this->drupalGet('admin/config/development/features'); $this->clickLink('Create new feature'); - $this->assertResponse(200); + $this->assertSession()->statusCodeEquals(200); $edit = [ 'name' => 'Test feature', @@ -55,10 +55,10 @@ class FeaturesCreateUiTest extends BrowserTestBase { 'system_simple[sources][selected][system.theme]' => TRUE, 'system_simple[sources][selected][user.settings]' => TRUE, ]; - $this->drupalPostForm(NULL, $edit, 'Download Archive'); + $this->submitForm($edit, 'Download Archive'); - $this->assertResponse(200); - $archive = $this->getRawContent(); + $this->assertSession()->statusCodeEquals(200); + $archive = $this->getSession()->getPage()->getContent(); $filename = tempnam($this->tempFilesDirectory, 'feature'); file_put_contents($filename, $archive); @@ -89,12 +89,12 @@ class FeaturesCreateUiTest extends BrowserTestBase { // Ensure that the features listing renders the right content. $this->drupalGet('admin/config/development/features'); $tds = $this->xpath('//table[contains(@class, "features-listing")]/tbody/tr[td[3] = "' . $feature_name . '"]/td'); - $this->assertLink('Test feature'); + $this->assertSession()->linkExists('Test feature'); $this->assertEquals($feature_name, $tds[2]->getText()); $description_column = $tds[3]->getText(); $this->assertTrue(strpos($description_column, 'system.theme') !== FALSE); $this->assertTrue(strpos($description_column, 'user.settings') !== FALSE); - $this->assertRaw('Test description: <strong>giraffe</strong>'); + $this->assertSession()->responseContains('Test description: <strong>giraffe</strong>'); $this->assertEquals('Uninstalled', $tds[5]->getText()); $this->assertEquals('', $tds[6]->getText()); @@ -104,7 +104,7 @@ class FeaturesCreateUiTest extends BrowserTestBase { 'system_simple[included][system.theme]' => FALSE, 'user_role[sources][selected][authenticated]' => TRUE, ]; - $this->drupalPostForm(NULL, $edit, 'Write'); + $this->submitForm($edit, 'Write'); $info_filename = $module_path . '/' . $feature_name . '.info.yml'; $parsed_info = Yaml::decode(file_get_contents($info_filename)); @@ -124,7 +124,8 @@ class FeaturesCreateUiTest extends BrowserTestBase { // Install new feature module. $edit = []; $edit['modules[' . $feature_name . '][enable]'] = TRUE; - $this->drupalPostForm('admin/modules', $edit, 'Install'); + $this->drupalGet('admin/modules'); + $this->submitForm($edit, 'Install'); // Check that the feature is listed as installed. $this->drupalGet('admin/config/development/features'); @@ -143,9 +144,10 @@ class FeaturesCreateUiTest extends BrowserTestBase { $tds = $this->xpath('//table[contains(@class, "features-listing")]/tbody/tr[td[3] = "' . $feature_name . '"]/td'); $this->assertTrue(strpos($tds[6]->getText(), 'Changed') !== FALSE); + $this->drupalGet('admin/modules/uninstall'); // Uninstall the module. - $this->drupalPostForm('admin/modules/uninstall', ['uninstall[' . $feature_name . ']' => $feature_name], 'Uninstall'); + $this->submitForm(['uninstall[' . $feature_name . ']' => $feature_name], 'Uninstall'); $this->submitForm([], 'Uninstall'); $this->drupalGet('admin/config/development/features'); @@ -155,12 +157,13 @@ class FeaturesCreateUiTest extends BrowserTestBase { $this->clickLink('Changed'); $this->drupalGet('admin/config/development/features/diff/' . $feature_name); - $this->assertRaw('<td class="diff-context diff-deletedline">anonymous : <span class="diffchange">Giraffe</span></td>'); - $this->assertRaw('<td class="diff-context diff-addedline">anonymous : <span class="diffchange">Anonymous</span></td>'); + $this->assertSession()->responseContains('<td class="diff-context diff-deletedline">anonymous : <span class="diffchange">Giraffe</span></td>'); + $this->assertSession()->responseContains('<td class="diff-context diff-addedline">anonymous : <span class="diffchange">Anonymous</span></td>'); $edit = []; $edit['modules[' . $feature_name . '][enable]'] = TRUE; - $this->drupalPostForm('admin/modules', $edit, 'Install'); + $this->drupalGet('admin/modules'); + $this->submitForm($edit, 'Install'); $this->drupalGet('admin/config/development/features'); $tds = $this->xpath('//table[contains(@class, "features-listing")]/tbody/tr[td[3] = "' . $feature_name . '"]/td'); @@ -180,7 +183,7 @@ class FeaturesCreateUiTest extends BrowserTestBase { $this->assertTrue(strpos($tds[6]->getText(), 'Changed') !== FALSE); $this->clickLink('Test feature'); - $this->drupalPostForm(NULL, [], 'Write'); + $this->submitForm([], 'Write'); $this->drupalGet('admin/config/development/features'); $tds = $this->xpath('//table[contains(@class, "features-listing")]/tbody/tr[td[3] = "' . $feature_name . '"]/td'); diff --git a/src/FeaturesAssigner.php b/src/FeaturesAssigner.php index 73bfae167dfb4da23597031cb57b773f42e2e229..6dfc7a08dd8f18652ccccfdf447d450b92ac88c8 100644 --- a/src/FeaturesAssigner.php +++ b/src/FeaturesAssigner.php @@ -5,9 +5,10 @@ namespace Drupal\features; use Drupal\Component\Plugin\PluginManagerInterface; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Config\ExtensionInstallStorage; -use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Config\StorageInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; +use Symfony\Component\HttpFoundation\RequestStack; /** * Class responsible for performing package assignment. @@ -78,6 +79,13 @@ class FeaturesAssigner implements FeaturesAssignerInterface { */ protected $installProfile; + /** + * The current request. + * + * @var \Symfony\Component\HttpFoundation\Request + */ + protected $request; + /** * Constructs a new FeaturesAssigner object. * @@ -94,13 +102,14 @@ class FeaturesAssigner implements FeaturesAssignerInterface { * @param string $install_profile * The name of the currently active installation profile. */ - public function __construct(FeaturesManagerInterface $features_manager, PluginManagerInterface $assigner_manager, EntityTypeManagerInterface $entity_type_manager, ConfigFactoryInterface $config_factory, StorageInterface $config_storage, $install_profile) { + public function __construct(FeaturesManagerInterface $features_manager, PluginManagerInterface $assigner_manager, EntityTypeManagerInterface $entity_type_manager, ConfigFactoryInterface $config_factory, StorageInterface $config_storage, $install_profile, RequestStack $request_stack) { $this->featuresManager = $features_manager; $this->assignerManager = $assigner_manager; $this->entityTypeManager = $entity_type_manager; $this->configFactory = $config_factory; $this->configStorage = $config_storage; $this->installProfile = $install_profile; + $this->request = $request_stack->getCurrentRequest(); $this->bundles = $this->getBundleList(); $this->currentBundle = $this->getBundle(FeaturesBundleInterface::DEFAULT_BUNDLE); // Ensure bundle information is fresh. @@ -259,8 +268,8 @@ class FeaturesAssigner implements FeaturesAssignerInterface { */ public function setCurrent(FeaturesBundleInterface $bundle) { $this->currentBundle = $bundle; - $session = \Drupal::request()->getSession(); - if (isset($session)) { + if ($this->request->hasSession()) { + $session = $this->request->getSession(); $session->set('features_current_bundle', $bundle->getMachineName()); } return $bundle; @@ -429,8 +438,8 @@ class FeaturesAssigner implements FeaturesAssignerInterface { */ public function loadBundle($machine_name = NULL) { if (!isset($machine_name)) { - $session = \Drupal::request()->getSession(); - if (isset($session)) { + if ($this->request->hasSession()) { + $session = $this->request->getSession(); $machine_name = isset($session) ? $session->get('features_current_bundle', FeaturesBundleInterface::DEFAULT_BUNDLE) : FeaturesBundleInterface::DEFAULT_BUNDLE; } } diff --git a/src/FeaturesBundleInterface.php b/src/FeaturesBundleInterface.php index 83dfbf5a9bc034dbd43fd8e099477872fb266375..07cd609e2a63f6888fa29859148a2960f5726122 100644 --- a/src/FeaturesBundleInterface.php +++ b/src/FeaturesBundleInterface.php @@ -7,6 +7,8 @@ namespace Drupal\features; */ interface FeaturesBundleInterface { + const CORE_VERSION_REQUIREMENT = '^9.4 || ^10'; + const DEFAULT_BUNDLE = 'default'; /** diff --git a/src/FeaturesConfigInstaller.php b/src/FeaturesConfigInstaller.php index d79577e68d62485a3c666bd6b60e74bdec4f05fb..00c66932bde6e8c19c4dc415a4e71ffc7eae3dd9 100644 --- a/src/FeaturesConfigInstaller.php +++ b/src/FeaturesConfigInstaller.php @@ -2,12 +2,13 @@ namespace Drupal\features; +use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Config\ConfigInstaller; use Drupal\Core\Config\ConfigInstallerInterface; -use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Config\ConfigManagerInterface; use Drupal\Core\Config\StorageInterface; use Drupal\Core\Config\TypedConfigManagerInterface; -use Drupal\Core\Config\ConfigManagerInterface; +use Drupal\Core\Extension\ExtensionPathResolver; use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** @@ -51,9 +52,11 @@ class FeaturesConfigInstaller extends ConfigInstaller { * The event dispatcher. * @param string $install_profile * The name of the currently active installation profile. + * @param \Drupal\Core\Extension\ExtensionPathResolver $extension_path_resolver + * The extension path resolver. */ - public function __construct(ConfigInstallerInterface $config_installer, FeaturesManagerInterface $features_manager, ConfigFactoryInterface $config_factory, StorageInterface $active_storage, TypedConfigManagerInterface $typed_config, ConfigManagerInterface $config_manager, EventDispatcherInterface $event_dispatcher, $install_profile) { - parent::__construct($config_factory, $active_storage, $typed_config, $config_manager, $event_dispatcher, $install_profile); + public function __construct(ConfigInstallerInterface $config_installer, FeaturesManagerInterface $features_manager, ConfigFactoryInterface $config_factory, StorageInterface $active_storage, TypedConfigManagerInterface $typed_config, ConfigManagerInterface $config_manager, EventDispatcherInterface $event_dispatcher, $install_profile, ExtensionPathResolver $extension_path_resolver) { + parent::__construct($config_factory, $active_storage, $typed_config, $config_manager, $event_dispatcher, $install_profile, $extension_path_resolver); $this->configInstaller = $config_installer; $this->featuresManager = $features_manager; } diff --git a/src/FeaturesExtensionStorages.php b/src/FeaturesExtensionStorages.php index 1030aad8f34fe1fc3dbca033e227a1bc0ddaf756..6f9ea120488278acffa3e43e0198a72d3c7585c8 100644 --- a/src/FeaturesExtensionStorages.php +++ b/src/FeaturesExtensionStorages.php @@ -5,6 +5,7 @@ namespace Drupal\features; use Drupal\Core\Config\InstallStorage; use Drupal\Core\Config\StorageInterface; use Drupal\Core\Extension\Extension; +use Drupal\Core\Extension\ExtensionPathResolver; /** * Wraps FeaturesInstallStorage to support multiple configuration directories. @@ -18,6 +19,13 @@ class FeaturesExtensionStorages implements FeaturesExtensionStoragesInterface { */ protected $configStorage; + /** + * Instance of the extension path resolver service. + * + * @var \Drupal\Core\Extension\ExtensionPathResolver + */ + protected ExtensionPathResolver $extensionPathResolver; + /** * The extension storages. * @@ -37,9 +45,12 @@ class FeaturesExtensionStorages implements FeaturesExtensionStoragesInterface { * * @param \Drupal\Core\Config\StorageInterface $config_storage * The configuration storage. + * @param \Drupal\Core\Extension\ExtensionPathResolver $extension_path_resolver + * Instance of the extension path resolver service. */ - public function __construct(StorageInterface $config_storage) { + public function __construct(StorageInterface $config_storage, ExtensionPathResolver $extension_path_resolver) { $this->configStorage = $config_storage; + $this->extensionPathResolver = $extension_path_resolver; } /** @@ -53,7 +64,7 @@ class FeaturesExtensionStorages implements FeaturesExtensionStoragesInterface { * {@inheritdoc} */ public function addStorage($directory = InstallStorage::CONFIG_INSTALL_DIRECTORY) { - $this->extensionStorages[$directory] = new FeaturesInstallStorage($this->configStorage, $directory); + $this->extensionStorages[$directory] = new FeaturesInstallStorage($this->configStorage, $this->extensionPathResolver, $directory); $this->reset(); } diff --git a/src/FeaturesInstallStorage.php b/src/FeaturesInstallStorage.php index b7e90c00396c09a7a1b707300528cdb671891bf0..e1bd40a5ace65fbe502888576b028254389d6241 100644 --- a/src/FeaturesInstallStorage.php +++ b/src/FeaturesInstallStorage.php @@ -2,10 +2,11 @@ namespace Drupal\features; -use Drupal\Core\Site\Settings; use Drupal\Core\Config\ExtensionInstallStorage; use Drupal\Core\Config\StorageInterface; use Drupal\Core\Extension\ExtensionDiscovery; +use Drupal\Core\Extension\ExtensionPathResolver; +use Drupal\Core\Site\Settings; /** * Storage to access configuration and schema in installed extensions. @@ -19,6 +20,13 @@ use Drupal\Core\Extension\ExtensionDiscovery; */ class FeaturesInstallStorage extends ExtensionInstallStorage { + /** + * Instance of the extension path resolver service. + * + * @var \Drupal\Core\Extension\ExtensionPathResolver + */ + protected ExtensionPathResolver $extensionPathResolver; + /** * Overrides \Drupal\Core\Config\ExtensionInstallStorage::__construct(). * @@ -27,6 +35,8 @@ class FeaturesInstallStorage extends ExtensionInstallStorage { * @param \Drupal\Core\Config\StorageInterface $config_storage * The active configuration store where the list of enabled modules and * themes is stored. + * @param \Drupal\Core\Extension\ExtensionPathResolver $extension_path_resolver + * Instance of the extension path resolver service. * @param string $directory * The directory to scan in each extension to scan for files. Defaults to * 'config/install'. This parameter will be mandatory in Drupal 9.0.0. @@ -41,7 +51,8 @@ class FeaturesInstallStorage extends ExtensionInstallStorage { * (optional) The current installation profile. This parameter will be * mandatory in Drupal 9.0.0. */ - public function __construct(StorageInterface $config_storage, $directory = self::CONFIG_INSTALL_DIRECTORY, $collection = StorageInterface::DEFAULT_COLLECTION, $include_profile = TRUE, $profile = NULL) { + public function __construct(StorageInterface $config_storage, ExtensionPathResolver $extension_path_resolver, $directory = self::CONFIG_INSTALL_DIRECTORY, $collection = StorageInterface::DEFAULT_COLLECTION, $include_profile = TRUE, $profile = NULL) { + $this->extensionPathResolver = $extension_path_resolver; // @todo: determine if we should be setting $include_profile to FALSE. parent::__construct($config_storage, $directory, $collection, FALSE, $profile); } @@ -113,7 +124,7 @@ class FeaturesInstallStorage extends ExtensionInstallStorage { // file location so we can use drupal_get_path() on the active profile // during the module scan. // @todo Remove as part of https://www.drupal.org/node/2186491 - drupal_get_filename('profile', $profile, $profile_list[$profile]->getPathname()); + $this->extensionPathResolver->getPathname('profile', $profile); } // CHANGED START: Put Features modules first in list returned. // to allow features to override config provided by other extensions. diff --git a/src/FeaturesManager.php b/src/FeaturesManager.php index 644ab22e4c2178962668f657de0001934e23075c..b8b8dbffdf96316c9835a7cbfa1ab9e547f499d2 100644 --- a/src/FeaturesManager.php +++ b/src/FeaturesManager.php @@ -1,23 +1,23 @@ <?php namespace Drupal\features; -use Drupal\Core\Config\ImmutableConfig; -use Drupal; use Drupal\Component\Serialization\Yaml; use Drupal\Component\Utility\NestedArray; +use Drupal\config_update\ConfigRevertInterface; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Config\ConfigManagerInterface; +use Drupal\Core\Config\ImmutableConfig; use Drupal\Core\Config\InstallStorage; use Drupal\Core\Config\StorageInterface; -use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Entity\EntityTypeInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Extension\Dependency; use Drupal\Core\Extension\Extension; use Drupal\Core\Extension\ExtensionDiscovery; +use Drupal\Core\Extension\ExtensionPathResolver; use Drupal\Core\Extension\ModuleExtensionList; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; -use Drupal\config_update\ConfigRevertInterface; /** * The FeaturesManager provides helper functions for building packages. @@ -149,8 +149,10 @@ class FeaturesManager implements FeaturesManagerInterface { * The config revert interface. * @param \Drupal\Core\Extension\ModuleExtensionList $module_extension_list * The module extension list service. + * @param \Drupal\Core\Extension\ExtensionPathResolver $extension_path_resolver + * Instance of the extension path resolver service. */ - public function __construct($root, EntityTypeManagerInterface $entity_type_manager, ConfigFactoryInterface $config_factory, StorageInterface $config_storage, ConfigManagerInterface $config_manager, ModuleHandlerInterface $module_handler, ConfigRevertInterface $config_reverter, ModuleExtensionList $module_extension_list) { + public function __construct($root, EntityTypeManagerInterface $entity_type_manager, ConfigFactoryInterface $config_factory, StorageInterface $config_storage, ConfigManagerInterface $config_manager, ModuleHandlerInterface $module_handler, ConfigRevertInterface $config_reverter, ModuleExtensionList $module_extension_list, ExtensionPathResolver $extension_path_resolver) { $this->root = $root; $this->entityTypeManager = $entity_type_manager; $this->configStorage = $config_storage; @@ -160,7 +162,7 @@ class FeaturesManager implements FeaturesManagerInterface { $this->configReverter = $config_reverter; $this->moduleExtensionList = $module_extension_list; $this->settings = $config_factory->getEditable('features.settings'); - $this->extensionStorages = new FeaturesExtensionStoragesByDirectory($this->configStorage); + $this->extensionStorages = new FeaturesExtensionStoragesByDirectory($this->configStorage, $extension_path_resolver); $this->extensionStorages->addStorage(InstallStorage::CONFIG_INSTALL_DIRECTORY); $this->extensionStorages->addStorage(InstallStorage::CONFIG_OPTIONAL_DIRECTORY); $this->packages = []; @@ -788,7 +790,7 @@ class FeaturesManager implements FeaturesManagerInterface { /** @var \Drupal\features\Package[] $packages */ foreach ($packages as $package) { foreach ($package->getConfig() as $item_name) { - if (!empty($config_collection[$item_name]->getData()['dependencies']['config'])) { + if (!empty($config_collection[$item_name]) && !empty($config_collection[$item_name]->getData()['dependencies']['config'])) { foreach ($config_collection[$item_name]->getData()['dependencies']['config'] as $dependency_name) { if (isset($config_collection[$dependency_name]) && // For configuration in the @@ -1030,6 +1032,9 @@ class FeaturesManager implements FeaturesManagerInterface { if ($package->getConfig()) { // Add configuration files. foreach ($package->getConfig() as $name) { + if (empty($config_collection[$name])) { + continue; + } $config = $config_collection[$name]; $package->appendFile([ diff --git a/src/Package.php b/src/Package.php index 8a747a994d4e19ead7e9a31c18b694b73fa96c6c..09ee4a7ca57740cdaa90242c7cc38166b0cf2025 100644 --- a/src/Package.php +++ b/src/Package.php @@ -41,13 +41,12 @@ class Package { protected $version = ''; /** - * The package core version requirement.. + * The package type. * * @var string - * @todo: Make coreVersionRequirement a property of the - * FeaturesBundleInterface object. For now, hard-code it. + * @todo This could be fetched from the extension object. */ - protected $coreVersionRequirement = '^8.9 || ^9'; + protected $coreVersionRequirement = FeaturesBundleInterface::CORE_VERSION_REQUIREMENT; /** * The package type. diff --git a/src/Plugin/FeaturesGeneration/FeaturesGenerationArchive.php b/src/Plugin/FeaturesGeneration/FeaturesGenerationArchive.php index abcbe4a448725108ad6ac972c752156807630999..1d4b94ec06433af86b78aca21b771e0259c1201e 100644 --- a/src/Plugin/FeaturesGeneration/FeaturesGenerationArchive.php +++ b/src/Plugin/FeaturesGeneration/FeaturesGenerationArchive.php @@ -3,12 +3,12 @@ namespace Drupal\features\Plugin\FeaturesGeneration; use Drupal\Core\Access\CsrfTokenGenerator; -use Drupal\Core\Plugin\ContainerFactoryPluginInterface; -use Drupal\features\FeaturesGenerationMethodBase; use Drupal\Core\Archiver\ArchiveTar; -use Drupal\Core\File\FileSystem; +use Drupal\Core\File\FileSystemInterface; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\features\FeaturesBundleInterface; +use Drupal\features\FeaturesGenerationMethodBase; use Drupal\features\Package; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -41,7 +41,7 @@ class FeaturesGenerationArchive extends FeaturesGenerationMethodBase implements /** * The file system service. * - * @var \Drupal\Core\File\FileSystem + * @var \Drupal\Core\File\FileSystemInterface */ protected $fileSystem; @@ -52,10 +52,10 @@ class FeaturesGenerationArchive extends FeaturesGenerationMethodBase implements * The app root. * @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token * The CSRF token generator. - * @param \Drupal\Core\File\FileSystem $file_system + * @param \Drupal\Core\File\FileSystemInterface $file_system * The file system service. */ - public function __construct($root, CsrfTokenGenerator $csrf_token, FileSystem $file_system) { + public function __construct($root, CsrfTokenGenerator $csrf_token, FileSystemInterface $file_system) { $this->root = $root; $this->csrfToken = $csrf_token; $this->fileSystem = $file_system; @@ -66,7 +66,7 @@ class FeaturesGenerationArchive extends FeaturesGenerationMethodBase implements */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { return new static( - $container->get('app.root'), + $container->getParameter('app.root'), $container->get('csrf_token'), $container->get('file_system') ); diff --git a/src/Plugin/FeaturesGeneration/FeaturesGenerationWrite.php b/src/Plugin/FeaturesGeneration/FeaturesGenerationWrite.php index 98d10ea279d4a58e7c5983803a3d5f23876a8366..aede6d42302da5ab15a21edf0826f3895f6b0329 100644 --- a/src/Plugin/FeaturesGeneration/FeaturesGenerationWrite.php +++ b/src/Plugin/FeaturesGeneration/FeaturesGenerationWrite.php @@ -58,7 +58,7 @@ class FeaturesGenerationWrite extends FeaturesGenerationMethodBase implements Co */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { return new static( - $container->get('app.root'), + $container->getParameter('app.root'), $container->get('file_system') ); } diff --git a/tests/modules/test_feature/test_feature.info.yml b/tests/modules/test_feature/test_feature.info.yml index 4e847b63178e88930eadcd92d4a4e0f88c47af27..432a876d4df7340145c594df13bbc58796210354 100644 --- a/tests/modules/test_feature/test_feature.info.yml +++ b/tests/modules/test_feature/test_feature.info.yml @@ -1,7 +1,5 @@ -name: feature +name: test_feature type: module -core_version_requirement: ^8.8 || ^9 -package: Test -# Force this to install after features. +package: Testing dependencies: - features:features diff --git a/tests/modules/test_feature_generation/src/Plugin/FeaturesGeneration/FeaturesGenerationArchiveAlter.php b/tests/modules/test_feature_generation/src/Plugin/FeaturesGeneration/FeaturesGenerationArchiveAlter.php new file mode 100644 index 0000000000000000000000000000000000000000..4fac1278e2f84284809fd553f91fff6131524c74 --- /dev/null +++ b/tests/modules/test_feature_generation/src/Plugin/FeaturesGeneration/FeaturesGenerationArchiveAlter.php @@ -0,0 +1,24 @@ +<?php + +namespace Drupal\test_feature_generation\Plugin\FeaturesGeneration; + +use Drupal\features\Plugin\FeaturesGeneration\FeaturesGenerationArchive; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * FeaturesGenerationArchiveAlter. + */ +class FeaturesGenerationArchiveAlter extends FeaturesGenerationArchive { + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + 'vfs://drupal', + $container->get('csrf_token'), + $container->get('file_system') + ); + } + +} diff --git a/tests/modules/test_feature_generation/src/Plugin/FeaturesGeneration/FeaturesGenerationWriteAlter.php b/tests/modules/test_feature_generation/src/Plugin/FeaturesGeneration/FeaturesGenerationWriteAlter.php new file mode 100644 index 0000000000000000000000000000000000000000..c752f58a416ef46c357d65ff4d3e9d3c2f0f131f --- /dev/null +++ b/tests/modules/test_feature_generation/src/Plugin/FeaturesGeneration/FeaturesGenerationWriteAlter.php @@ -0,0 +1,23 @@ +<?php + +namespace Drupal\test_feature_generation\Plugin\FeaturesGeneration; + +use Drupal\features\Plugin\FeaturesGeneration\FeaturesGenerationWrite; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * FeaturesGenerationWriteAlter. + */ +class FeaturesGenerationWriteAlter extends FeaturesGenerationWrite { + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + 'vfs://drupal', + $container->get('file_system') + ); + } + +} diff --git a/tests/modules/test_feature_generation/test_feature_generation.info.yml b/tests/modules/test_feature_generation/test_feature_generation.info.yml new file mode 100644 index 0000000000000000000000000000000000000000..4a946259a5df8f3f3dc176c71676859bf69a2950 --- /dev/null +++ b/tests/modules/test_feature_generation/test_feature_generation.info.yml @@ -0,0 +1,5 @@ +name: Test feature generation +type: module +package: Testing +dependencies: + - features:features diff --git a/tests/modules/test_feature_generation/test_feature_generation.module b/tests/modules/test_feature_generation/test_feature_generation.module new file mode 100644 index 0000000000000000000000000000000000000000..0b9297c046059b43eccec9dc3c27954ff0bdff30 --- /dev/null +++ b/tests/modules/test_feature_generation/test_feature_generation.module @@ -0,0 +1,17 @@ +<?php + +/** + * @file + * Main hooks for Test feature generation module. + */ + +use Drupal\test_feature_generation\Plugin\FeaturesGeneration\FeaturesGenerationArchiveAlter; +use Drupal\test_feature_generation\Plugin\FeaturesGeneration\FeaturesGenerationWriteAlter; + +/** + * Implements hook_element_plugin_alter() + */ +function test_feature_generation_features_generation_info_alter(array &$definitions) { + $definitions['write']['class'] = FeaturesGenerationWriteAlter::class; + $definitions['archive']['class'] = FeaturesGenerationArchiveAlter::class; +} diff --git a/tests/modules/test_mybundle_core/test_mybundle_core.info.yml b/tests/modules/test_mybundle_core/test_mybundle_core.info.yml index b8eb9cc850cacf193dcdd0de89c98f35fd646b6b..4c6e2d6b92a2954eda7aa6e14d7c17985371c20e 100644 --- a/tests/modules/test_mybundle_core/test_mybundle_core.info.yml +++ b/tests/modules/test_mybundle_core/test_mybundle_core.info.yml @@ -1,7 +1,5 @@ name: Test Core type: module -core_version_requirement: ^8.9 || ^9 -package: Test -# Ensure features is enabled first +package: Testing dependencies: - features:features diff --git a/tests/src/Kernel/Entity/FeaturesBundleIntegrationTest.php b/tests/src/Kernel/Entity/FeaturesBundleIntegrationTest.php index 38b20005f2350585831eb2833ae321176bef6221..c20234ec3630e27822d0f01410211365c6688762 100644 --- a/tests/src/Kernel/Entity/FeaturesBundleIntegrationTest.php +++ b/tests/src/Kernel/Entity/FeaturesBundleIntegrationTest.php @@ -15,7 +15,7 @@ class FeaturesBundleIntegrationTest extends KernelTestBase { /** * {@inheritdoc} */ - public static $modules = ['features']; + protected static $modules = ['features']; /** * {@inheritDoc} diff --git a/tests/src/Kernel/FeaturesAssignTest.php b/tests/src/Kernel/FeaturesAssignTest.php index 948432f663a21d89368867a4fdab2d6241698156..d53d7200eec9ffd0d660cd04875716270c1acccd 100644 --- a/tests/src/Kernel/FeaturesAssignTest.php +++ b/tests/src/Kernel/FeaturesAssignTest.php @@ -23,7 +23,7 @@ class FeaturesAssignTest extends KernelTestBase { /** * {@inheritdoc} */ - public static $modules = ['features', 'node', 'system', 'user', self::TEST_INSTALLED_PACKAGE]; + protected static $modules = ['features', 'node', 'system', 'user', self::TEST_INSTALLED_PACKAGE]; /** * The Feature Manager. @@ -57,7 +57,7 @@ class FeaturesAssignTest extends KernelTestBase { /** * {@inheritdoc} */ - protected function setUp() { + protected function setUp(): void { parent::setUp(); $this->installConfig('features'); diff --git a/tests/src/Kernel/FeaturesAssignerTest.php b/tests/src/Kernel/FeaturesAssignerTest.php index a98c7776a4058181db4adeae63bbe165e038441c..7a2e2589411a7fe6332ddac9f4fa1bbdf6636e20 100644 --- a/tests/src/Kernel/FeaturesAssignerTest.php +++ b/tests/src/Kernel/FeaturesAssignerTest.php @@ -13,14 +13,14 @@ class FeaturesAssignerTest extends KernelTestBase { /** * {@inheritdoc} */ - public static $modules = ['system', 'config']; + protected static $modules = ['system', 'config']; protected $strictConfigSchema = FALSE; /** * {@inheritdoc} */ - protected function setUp() { + protected function setUp(): void { parent::setUp(); // We need system.site in order to run $this->configImporter->import(). $this->installConfig('system'); diff --git a/tests/src/Kernel/FeaturesGenerateTest.php b/tests/src/Kernel/FeaturesGenerateTest.php index 8e40b47606c4eac5e4c5dc638ca83b4fd99daa46..98b6d8d3d043c9602e4ac11234e837f81c0debfd 100644 --- a/tests/src/Kernel/FeaturesGenerateTest.php +++ b/tests/src/Kernel/FeaturesGenerateTest.php @@ -2,10 +2,11 @@ namespace Drupal\Tests\features\Kernel; -use Drupal\features\Entity\FeaturesBundle; -use Drupal\KernelTests\KernelTestBase; use Drupal\Component\Serialization\Yaml; use Drupal\Core\Archiver\ArchiveTar; +use Drupal\features\Entity\FeaturesBundle; +use Drupal\features\FeaturesBundleInterface; +use Drupal\KernelTests\KernelTestBase; use org\bovigo\vfs\vfsStream; /** @@ -14,16 +15,18 @@ use org\bovigo\vfs\vfsStream; class FeaturesGenerateTest extends KernelTestBase { const PACKAGE_NAME = 'my_test_package'; + const BUNDLE_NAME = 'giraffe'; /** * {@inheritdoc} */ - public static $modules = [ + protected static $modules = [ 'features', 'node', 'system', 'user', + 'test_feature_generation', ]; /** @@ -51,7 +54,7 @@ class FeaturesGenerateTest extends KernelTestBase { /** * {@inheritdoc} */ - protected function setUp() { + protected function setUp(): void { parent::setUp(); $this->installConfig('features'); @@ -92,7 +95,7 @@ class FeaturesGenerateTest extends KernelTestBase { $expected_info = [ "name" => "My test package", "type" => "module", - "core_version_requirement" => "^8.9 || ^9", + "core_version_requirement" => FeaturesBundleInterface::CORE_VERSION_REQUIREMENT, ]; $info = Yaml::decode($archive->extractInString(self::PACKAGE_NAME . '/' . self::PACKAGE_NAME . '.info.yml')); $this->assertEquals($expected_info, $info, 'Incorrect info file generated'); @@ -130,16 +133,11 @@ class FeaturesGenerateTest extends KernelTestBase { public function testExportWrite() { // Set a fake drupal root, so the testbot can also write into it. vfsStream::setup('drupal'); - \Drupal::getContainer()->set('app.root', 'vfs://drupal'); $this->featuresManager->setRoot('vfs://drupal'); - $package = $this->featuresManager->getPackage(self::PACKAGE_NAME); // Find out where package will be exported. - list($full_name, $path) = $this->featuresManager->getExportInfo($package, $this->assigner->getBundle()); + [$full_name, $path] = $this->featuresManager->getExportInfo($package, $this->assigner->getBundle()); $path = 'vfs://drupal/' . $path . '/' . $full_name; - if (file_exists($path)) { - file_unmanaged_delete_recursive($path); - } $this->assertFalse(file_exists($path), 'Package directory already exists.'); $this->generator->generatePackages('write', $this->assigner->getBundle(), [self::PACKAGE_NAME]); @@ -152,7 +150,7 @@ class FeaturesGenerateTest extends KernelTestBase { $expected_info = [ "name" => "My test package", "type" => "module", - "core_version_requirement" => "^8.9 || ^9", + "core_version_requirement" => FeaturesBundleInterface::CORE_VERSION_REQUIREMENT, ]; $info = Yaml::decode(file_get_contents($info_file_uri)); $this->assertEquals($expected_info, $info, 'Incorrect info file generated'); @@ -182,7 +180,7 @@ class FeaturesGenerateTest extends KernelTestBase { $expected_info = [ "name" => "My test package", "type" => "module", - "core_version_requirement" => "^8.9 || ^9", + "core_version_requirement" => FeaturesBundleInterface::CORE_VERSION_REQUIREMENT, "dependencies" => ["drupal:node", "drupal:user"], "mykey" => "test value", ]; @@ -213,7 +211,7 @@ class FeaturesGenerateTest extends KernelTestBase { $expected_info = [ "name" => "My test package", "type" => "module", - "core_version_requirement" => "^8.9 || ^9", + "core_version_requirement" => FeaturesBundleInterface::CORE_VERSION_REQUIREMENT, "dependencies" => ["drupal:node", "drupal:user"], "mykey" => "test value", ]; diff --git a/tests/src/Kernel/FeaturesManagerKernelTest.php b/tests/src/Kernel/FeaturesManagerKernelTest.php index b81e23d0b3df16c55e5f0c854e647835dfe5f548..152723bf9d9afbf4ed4e4d403072fb342c1fd982 100644 --- a/tests/src/Kernel/FeaturesManagerKernelTest.php +++ b/tests/src/Kernel/FeaturesManagerKernelTest.php @@ -15,7 +15,7 @@ class FeaturesManagerKernelTest extends KernelTestBase { /** * {@inheritdoc} */ - public static $modules = ['system', 'config', 'features']; + protected static $modules = ['system', 'config', 'features']; protected $strictConfigSchema = FALSE; @@ -36,7 +36,7 @@ class FeaturesManagerKernelTest extends KernelTestBase { /** * {@inheritdoc} */ - protected function setUp() { + protected function setUp(): void { parent::setUp(); $this->installConfig('features'); diff --git a/tests/src/Unit/FeaturesBundleTest.php b/tests/src/Unit/FeaturesBundleTest.php index 07ee1994d0fded79b8c7ee38c472fef89b67b0f7..1478e2be040d4b0ad089abd6a29cc99a49ce0b16 100644 --- a/tests/src/Unit/FeaturesBundleTest.php +++ b/tests/src/Unit/FeaturesBundleTest.php @@ -4,6 +4,7 @@ namespace Drupal\Tests\features\Unit; use Drupal\features\Entity\FeaturesBundle; use Drupal\Tests\UnitTestCase; +use Prophecy\PhpUnit\ProphecyTrait; /** * @coversDefaultClass Drupal\features\Entity\FeaturesBundle @@ -11,10 +12,12 @@ use Drupal\Tests\UnitTestCase; */ class FeaturesBundleTest extends UnitTestCase { + use ProphecyTrait; + /** * {@inheritDoc} */ - public function setUp() { + public function setUp(): void { parent::setUp(); // Mock an assigner. @@ -54,33 +57,33 @@ class FeaturesBundleTest extends UnitTestCase { ], 'features_bundle'); // Get assignments and attributes. - $this->assertArrayEquals( - $bundle->getEnabledAssignments(), + $this->assertEquals( ['foo' => 'foo'], + $bundle->getEnabledAssignments(), 'Can get enabled assignments' ); - $this->assertArrayEquals( - $bundle->getAssignmentWeights(), + $this->assertEquals( ['foo' => 0, 'bar' => 1], + $bundle->getAssignmentWeights(), 'Can get assignment weights' ); - $this->assertArrayEquals( - $bundle->getAssignmentSettings('foo'), + $this->assertEquals( $settings['foo'], + $bundle->getAssignmentSettings('foo'), 'Can get assignment settings' ); - $this->assertArrayEquals( - $bundle->getAssignmentSettings(), + $this->assertEquals( $settings, + $bundle->getAssignmentSettings(), 'Can get all assignment settings' ); // Change settings. $settings['foo']['my_setting'] = 97; $bundle->setAssignmentSettings('foo', $settings['foo']); - $this->assertArrayEquals( - $bundle->getAssignmentSettings('foo'), + $this->assertEquals( $settings['foo'], + $bundle->getAssignmentSettings('foo'), 'Can change assignment settings' ); @@ -88,28 +91,28 @@ class FeaturesBundleTest extends UnitTestCase { $settings['foo']['weight'] = 1; $settings['bar']['weight'] = 0; $bundle->setAssignmentWeights(['foo' => 1, 'bar' => 0]); - $this->assertArrayEquals( - $bundle->getAssignmentWeights(), + $this->assertEquals( ['foo' => 1, 'bar' => 0], + $bundle->getAssignmentWeights(), 'Can change assignment weights' ); - $this->assertArrayEquals( - $bundle->getAssignmentSettings(), + $this->assertEquals( $settings, + $bundle->getAssignmentSettings(), 'Weight changes are reflected in settings' ); // Enable existing assignment. $settings['bar']['enabled'] = TRUE; $bundle->setEnabledAssignments(['foo', 'bar']); - $this->assertArrayEquals( - $bundle->getEnabledAssignments(), + $this->assertEquals( ['foo' => 'foo', 'bar' => 'bar'], + $bundle->getEnabledAssignments(), 'Can enable assignment' ); - $this->assertArrayEquals( - $bundle->getAssignmentSettings(), + $this->assertEquals( $settings, + $bundle->getAssignmentSettings(), 'Enabled assignment status is reflected in settings' ); @@ -117,14 +120,14 @@ class FeaturesBundleTest extends UnitTestCase { $settings['foo']['enabled'] = FALSE; $settings['bar']['enabled'] = FALSE; $bundle->setEnabledAssignments([]); - $this->assertArrayEquals( - $bundle->getEnabledAssignments(), + $this->assertEquals( [], + $bundle->getEnabledAssignments(), 'Can disable assignments' ); - $this->assertArrayEquals( - $bundle->getAssignmentSettings(), + $this->assertEquals( $settings, + $bundle->getAssignmentSettings(), 'Disabled assignment status is reflected in settings' ); @@ -132,15 +135,15 @@ class FeaturesBundleTest extends UnitTestCase { $settings['foo']['enabled'] = TRUE; $settings['iggy'] = ['enabled' => TRUE, 'weight' => 0, 'new_setting' => 3]; $bundle->setEnabledAssignments(['foo', 'iggy']); - $this->assertArrayEquals( - $bundle->getEnabledAssignments(), + $this->assertEquals( ['foo' => 'foo', 'iggy' => 'iggy'], + $bundle->getEnabledAssignments(), 'Can enable new assignment' ); $bundle->setAssignmentSettings('iggy', $settings['iggy']); - $this->assertArrayEquals( - $bundle->getAssignmentSettings(), + $this->assertEquals( $settings, + $bundle->getAssignmentSettings(), 'New enabled assignment status is reflected in settings' ); diff --git a/tests/src/Unit/FeaturesManagerTest.php b/tests/src/Unit/FeaturesManagerTest.php index 640d3ae7555e17f38b3a2802564422908c9c6b56..2205d3c25fe1fc93478f37ff85b6ad10cbe67649 100644 --- a/tests/src/Unit/FeaturesManagerTest.php +++ b/tests/src/Unit/FeaturesManagerTest.php @@ -4,19 +4,20 @@ namespace Drupal\Tests\features\Unit; use Drupal\Component\Serialization\Yaml; use Drupal\config_update\ConfigDiffInterface; +use Drupal\config_update\ConfigRevertInterface; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Config\ConfigManagerInterface; use Drupal\Core\Config\InstallStorage; use Drupal\Core\Config\StorageInterface; use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\Extension\Extension; +use Drupal\Core\Extension\ExtensionPathResolver; use Drupal\Core\Extension\ModuleExtensionList; use Drupal\Core\Extension\ModuleHandlerInterface; -use Drupal\config_update\ConfigRevertInterface; +use Drupal\features\ConfigurationItem; use Drupal\features\Entity\FeaturesBundle; use Drupal\features\FeaturesAssignerInterface; use Drupal\features\FeaturesBundleInterface; -use Drupal\features\ConfigurationItem; use Drupal\features\FeaturesExtensionStoragesInterface; use Drupal\features\FeaturesManager; use Drupal\features\FeaturesManagerInterface; @@ -24,6 +25,7 @@ use Drupal\features\Package; use Drupal\Tests\UnitTestCase; use org\bovigo\vfs\vfsStream; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; /** * @coversDefaultClass Drupal\features\FeaturesManager @@ -37,6 +39,7 @@ class FeaturesManagerTest extends UnitTestCase { * The name of the install profile. */ const PROFILE_NAME = 'my_profile'; + use ProphecyTrait; /** * The feature manager interface. @@ -48,61 +51,61 @@ class FeaturesManagerTest extends UnitTestCase { /** * The entity type manager object. * - * @var \Drupal\Core\Entity\EntityTypeManagerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Entity\EntityTypeManagerInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $entityTypeManager; /** * The storage interface object. * - * @var \Drupal\Core\Config\StorageInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Config\StorageInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $configStorage; /** * The config factory. * - * @var \Drupal\Core\Config\ConfigFactoryInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Config\ConfigFactoryInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $configFactory; /** * The config manager. * - * @var \Drupal\Core\Config\ConfigManagerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Config\ConfigManagerInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $configManager; /** * The module handler. * - * @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $moduleHandler; /** * The extension. * - * @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $configReverter; /** * The module extension list mock. * - * @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $moduleExtensionList; /** * {@inheritdoc} */ - public function setUp() { + public function setUp(): void { parent::setUp(); $container = new ContainerBuilder(); $container->set('string_translation', $this->getStringTranslationStub()); - $container->set('app.root', $this->root); + $container->setParameter('app.root', $this->root); // Since in Drupal 8.3 the "\Drupal::installProfile()" was introduced // then we have to spoof a value for the "install_profile" parameter // because it will be used by "ExtensionInstallStorage" class, which @@ -126,6 +129,7 @@ class FeaturesManagerTest extends UnitTestCase { $this->configStorage = $this->createMock(StorageInterface::class); $this->configManager = $this->createMock(ConfigManagerInterface::class); $this->moduleHandler = $this->createMock(ModuleHandlerInterface::class); + $this->extensionPathResolver = $this->createMock(ExtensionPathResolver::class); // getModuleList should return an array of extension objects. // but we just need isset($module_list[$provider]) for // ::getConfigDependency() and ::assignInterPackageDependencies(). @@ -158,19 +162,19 @@ class FeaturesManagerTest extends UnitTestCase { $this->moduleExtensionList->expects($this->any()) ->method('getExtensionInfo') ->willReturn([]); - $this->featuresManager = new FeaturesManager($this->root, $this->entityTypeManager, $this->configFactory, $this->configStorage, $this->configManager, $this->moduleHandler, $this->configReverter, $this->moduleExtensionList); + $this->featuresManager = new FeaturesManager($this->root, $this->entityTypeManager, $this->configFactory, $this->configStorage, $this->configManager, $this->moduleHandler, $this->configReverter, $this->moduleExtensionList, $this->extensionPathResolver); } protected function setupVfsWithTestFeature() { vfsStream::setup('drupal'); - \Drupal::getContainer()->set('app.root', 'vfs://drupal'); + \Drupal::getContainer()->setParameter('app.root', 'vfs://drupal'); vfsStream::create([ 'modules' => [ 'test_feature' => [ 'test_feature.info.yml' => <<<EOT name: Test feature type: module -core_version_requirement: "^8.8 || ^9" +core_version_requirement: "^9.4 | ^10" description: test description EOT , @@ -231,7 +235,7 @@ EOT $this->assertEquals($packages, $this->featuresManager->getPackages()); $this->assertEquals('bar', $this->featuresManager->getPackage('foo')); $this->featuresManager->reset(); - $this->assertArrayEquals([], $this->featuresManager->getPackages()); + $this->assertEquals([], $this->featuresManager->getPackages()); $this->assertNull($this->featuresManager->getPackage('foo')); } @@ -242,7 +246,7 @@ EOT public function testConfigCollection() { $config = ['config' => new ConfigurationItem('', [])]; $this->featuresManager->setConfigCollection($config); - $this->assertArrayEquals($config, $this->featuresManager->getConfigCollection()); + $this->assertEquals($config, $this->featuresManager->getConfigCollection()); } /** @@ -371,7 +375,7 @@ EOT $bundle->isDefault()->willReturn(TRUE); $assigner->getBundle()->willReturn($bundle->reveal()); // Use the wrapper because we need ::drupalGetProfile(). - $features_manager = new TestFeaturesManager($this->root, $this->entityTypeManager, $this->configFactory, $this->configStorage, $this->configManager, $this->moduleHandler, $this->configReverter, $this->moduleExtensionList); + $features_manager = new TestFeaturesManager($this->root, $this->entityTypeManager, $this->configFactory, $this->configStorage, $this->configManager, $this->moduleHandler, $this->configReverter, $this->moduleExtensionList, $this->extensionPathResolver); $features_manager->setAssigner($assigner->reveal()); $features_manager->setConfigCollection($this->getAssignInterPackageDependenciesConfigCollection()); @@ -438,7 +442,7 @@ EOT $bundle->getMachineName()->willReturn('giraffe'); $assigner->getBundle('giraffe')->willReturn($bundle->reveal()); // Use the wrapper because we need ::drupalGetProfile(). - $features_manager = new TestFeaturesManager($this->root, $this->entityTypeManager, $this->configFactory, $this->configStorage, $this->configManager, $this->moduleHandler, $this->configReverter, $this->moduleExtensionList); + $features_manager = new TestFeaturesManager($this->root, $this->entityTypeManager, $this->configFactory, $this->configStorage, $this->configManager, $this->moduleHandler, $this->configReverter, $this->moduleExtensionList, $this->extensionPathResolver); $features_manager->setAssigner($assigner->reveal()); $features_manager->setConfigCollection($this->getAssignInterPackageDependenciesConfigCollection()); @@ -581,7 +585,7 @@ EOT 'key2' => 'value0', ]); - $features_manager = new TestFeaturesManager($this->root, $this->entityTypeManager, $this->configFactory, $config_storage->reveal(), $this->configManager, $this->moduleHandler, $this->configReverter, $this->moduleExtensionList); + $features_manager = new TestFeaturesManager($this->root, $this->entityTypeManager, $this->configFactory, $config_storage->reveal(), $this->configManager, $this->moduleHandler, $this->configReverter, $this->moduleExtensionList, $this->extensionPathResolver); $features_manager->setExtensionStorages($extension_storage->reveal()); $this->assertEquals(['test_overridden'], $features_manager->detectOverrides($package)); @@ -770,7 +774,7 @@ EOT 'key' => 'value', ]); - $features_manager = new TestFeaturesManager($this->root, $this->entityTypeManager, $this->configFactory, $this->configStorage, $this->configManager, $this->moduleHandler, $this->configReverter, $this->moduleExtensionList); + $features_manager = new TestFeaturesManager($this->root, $this->entityTypeManager, $this->configFactory, $this->configStorage, $this->configManager, $this->moduleHandler, $this->configReverter, $this->moduleExtensionList, $this->extensionPathResolver); $features_manager->setExtensionStorages($extension_storage->reveal()); $this->assertEmpty($features_manager->detectNew($package)); @@ -785,7 +789,7 @@ EOT $extension_storage = $this->prophesize(FeaturesExtensionStoragesInterface::class); $extension_storage->read('test_config')->willReturn(FALSE); - $features_manager = new TestFeaturesManager($this->root, $this->entityTypeManager, $this->configFactory, $this->configStorage, $this->configManager, $this->moduleHandler, $this->configReverter, $this->moduleExtensionList); + $features_manager = new TestFeaturesManager($this->root, $this->entityTypeManager, $this->configFactory, $this->configStorage, $this->configManager, $this->moduleHandler, $this->configReverter, $this->moduleExtensionList, $this->extensionPathResolver); $features_manager->setExtensionStorages($extension_storage->reveal()); $this->assertEquals(['test_config'], $features_manager->detectNew($package)); @@ -811,8 +815,8 @@ EOT $data = []; $data['empty-info'] = [[], [], []]; $data['override-info'] = [ - ['name' => 'New name', 'core_version_requirement' => '^8.8 || ^9'], - ['name' => 'Old name', 'core_version_requirement' => '^8.8 || ^9'], + ['name' => 'New name', 'core_version_requirement' => FeaturesBundleInterface::CORE_VERSION_REQUIREMENT], + ['name' => 'Old name', 'core_version_requirement' => FeaturesBundleInterface::CORE_VERSION_REQUIREMENT], ['name' => 'New name'], ]; $data['dependency-merging'] = [ @@ -831,7 +835,7 @@ EOT public function testInitPackageWithNewPackage() { $bundle = new FeaturesBundle(['machine_name' => 'test'], 'features_bundle'); - $features_manager = new TestFeaturesManager($this->root, $this->entityTypeManager, $this->configFactory, $this->configStorage, $this->configManager, $this->moduleHandler, $this->configReverter, $this->moduleExtensionList); + $features_manager = new TestFeaturesManager($this->root, $this->entityTypeManager, $this->configFactory, $this->configStorage, $this->configManager, $this->moduleHandler, $this->configReverter, $this->moduleExtensionList, $this->extensionPathResolver); $features_manager->setAllModules([]); $package = $features_manager->initPackage('test_feature', 'test name', 'test description', 'module', $bundle); @@ -854,7 +858,7 @@ EOT public function testInitPackageWithExistingPackage() { $bundle = new FeaturesBundle(['machine_name' => 'test'], 'features_bundle'); - $features_manager = new TestFeaturesManager('vfs://drupal', $this->entityTypeManager, $this->configFactory, $this->configStorage, $this->configManager, $this->moduleHandler, $this->configReverter, $this->moduleExtensionList); + $features_manager = new TestFeaturesManager('vfs://drupal', $this->entityTypeManager, $this->configFactory, $this->configStorage, $this->configManager, $this->moduleHandler, $this->configReverter, $this->moduleExtensionList, $this->extensionPathResolver); $this->setupVfsWithTestFeature(); $extension = new Extension('vfs://drupal', 'module', 'modules/test_feature/test_feature.info.yml'); @@ -922,7 +926,7 @@ EOT $this->assertEquals(Yaml::encode([ 'name' => 'Test feature', 'type' => 'module', - 'core_version_requirement' => '^8.9 || ^9', + 'core_version_requirement' => FeaturesBundleInterface::CORE_VERSION_REQUIREMENT, ]), $files['info']['string']); $this->assertEquals(Yaml::encode(TRUE), $files['features']['string']); @@ -954,7 +958,7 @@ EOT ], ], ]); - $this->featuresManager = new FeaturesManager($this->root, $this->entityTypeManager, $config_factory, $this->configStorage, $this->configManager, $this->moduleHandler, $this->configReverter, $this->moduleExtensionList); + $this->featuresManager = new FeaturesManager($this->root, $this->entityTypeManager, $config_factory, $this->configStorage, $this->configManager, $this->moduleHandler, $this->configReverter, $this->moduleExtensionList, $this->extensionPathResolver); $package = new Package('test_feature'); $result = $this->featuresManager->getExportInfo($package); @@ -973,7 +977,7 @@ EOT ], ], ]); - $this->featuresManager = new FeaturesManager($this->root, $this->entityTypeManager, $config_factory, $this->configStorage, $this->configManager, $this->moduleHandler, $this->configReverter, $this->moduleExtensionList); + $this->featuresManager = new FeaturesManager($this->root, $this->entityTypeManager, $config_factory, $this->configStorage, $this->configManager, $this->moduleHandler, $this->configReverter, $this->moduleExtensionList, $this->extensionPathResolver); $package = new Package('test_feature'); $bundle = new FeaturesBundle(['machine_name' => 'test_bundle'], 'features_bundle');