- rename /drush/features.drush.inc to features.drush8.inc. this prevents loading it with Drush9.
- place drush.services.yml at module root
- place FeaturesCommands.php at in a new src/Commands directory
drush cr
to get your commands into drupal container
Created
June 11, 2017 11:15
-
-
Save weitzman/663485af507433e2331d9f2bcb519e42 to your computer and use it in GitHub Desktop.
Port Features to Drush9
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
services: | |
features.commands: | |
class: \Drupal\features\Commands\FeaturesCommands | |
arguments: ['@features_assigner', '@features.manager', '@features_generator', '@config_update.config_diff', '@config.storage'] | |
tags: | |
- { name: drush.command } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Drupal\features\Commands; | |
use Consolidation\OutputFormatters\StructuredData\RowsOfFields; | |
use Drupal\Component\Diff\DiffFormatter; | |
use Drupal\config_update\ConfigDiffInterface; | |
use Drupal\Core\Config\StorageInterface; | |
use Drupal\features\FeaturesAssignerInterface; | |
use Drupal\features\FeaturesGeneratorInterface; | |
use Drupal\features\FeaturesManagerInterface; | |
use Drupal\features\Plugin\FeaturesGeneration\FeaturesGenerationWrite; | |
use Drush\Commands\DrushCommands; | |
use Drush\Exceptions\UserAbortException; | |
/** | |
* Provides Drush integration. | |
*/ | |
class FeaturesCommands extends DrushCommands { | |
protected $featuresAssigner; | |
protected $featuresManager; | |
protected $featuresGenerator; | |
protected $configDiff; | |
protected $configStorage; | |
public function __construct(FeaturesAssignerInterface $featuresAssigner, FeaturesManagerInterface $featuresManager, FeaturesGeneratorInterface $featuresGenerator, ConfigDiffInterface $configDiff, StorageInterface $configStorage) { | |
parent::__construct(); | |
$this->featuresAssigner = $featuresAssigner; | |
$this->featuresManager = $featuresManager; | |
$this->featuresGenerator = $featuresGenerator; | |
$this->configDiff = $configDiff; | |
$this->configStorage = $configStorage; | |
} | |
/** | |
* Display current Features settings. | |
* | |
* @command features-status | |
* @param $keys Specify any config keys to dump. | |
* @option bundle Use a specific bundle namespace. | |
* @aliases fs | |
*/ | |
public function status(array $keys, $options = ['bundle' => null]) { | |
$assigner = $this->getAssigner($options); | |
$manager = $this->featuresManager; | |
$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)', | |
array( | |
'@name' => $current_bundle->getName(), | |
'@machine_name' => $current_bundle->getMachineName(), | |
))); | |
} | |
drush_print(dt('Export folder: @folder', array('@folder' => $export_settings['folder']))); | |
$dt_args = array('@methods' => implode(', ', array_keys($methods))); | |
drush_print(dt('The following assignment methods are enabled:')); | |
drush_print(dt(' @methods', $dt_args)); | |
if (!empty($keys)) { | |
$config = $manager->getConfigCollection(); | |
if (count($keys) > 1) { | |
print_r(array_keys($config)); | |
} | |
else { | |
print_r($config[$keys[0]]); | |
} | |
} | |
} | |
/** | |
* 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. | |
* | |
* @command features-list-packages | |
* @param $package_name 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. | |
* @option bundle Use a specific bundle namespace. | |
* @usage drush features-list-packages | |
* Display a list of all existing features and packages available to be generated. | |
* @usage drush features-list-packages 'example_article' | |
* Display a list of all configuration objects assigned to the 'example_article' package. | |
* @field-labels | |
* name: Name | |
* machine_name: Machine name | |
* status: Status | |
* version: Version | |
* state: State | |
* @aliases fl | |
*/ | |
public function listPackages($package_name = NULL, $options = ['format' => 'table', 'bundle' => null]) { | |
$assigner = $this->getAssigner($options); | |
$current_bundle = $assigner->getBundle(); | |
$namespace = $current_bundle->isDefault() ? '' : $current_bundle->getMachineName(); | |
$manager = $this->featuresManager; | |
$packages = $manager->getPackages(); | |
$packages = $manager->filterPackages($packages, $namespace); | |
$result = array(); | |
// If no package was specified, list all packages. | |
if (empty($package_name)) { | |
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()] = array( | |
'name' => $package->getName(), | |
'machine_name' => $package->getMachineName(), | |
'status' => $manager->statusLabel($package->getStatus()), | |
'version' => $package->getVersion(), | |
'state' => ($state != FeaturesManagerInterface::STATE_DEFAULT) | |
? $manager->stateLabel($state) | |
: '', | |
); | |
} | |
return new RowsOfFields($result); | |
} | |
// If a valid package was listed, list its configuration. | |
else { | |
// @todo. I suggest changing this command to return YAML with config as nested values. | |
} | |
// If no matching package found, return an error. | |
$this->logger()->warning(dt('Package "@package" not found.', array('@package' => $package_name))); | |
return FALSE; | |
} | |
/** | |
* Import module config from all installed features. | |
* | |
* @command features-import-all | |
* @option bundle Use a specific bundle namespace. | |
* @usage drush features-import-all | |
* Import module config from all installed features. | |
* @aliases fra,fia,fim-all | |
*/ | |
public function importAll($options = ['bundle' => null]) { | |
$assigner = $this->getAssigner($options); | |
$current_bundle = $assigner->getBundle(); | |
$namespace = $current_bundle->isDefault() ? '' : $current_bundle->getMachineName(); | |
$manager = $this->featuresManager; | |
$packages = $manager->getPackages(); | |
$packages = $manager->filterPackages($packages, $namespace); | |
$overridden = array(); | |
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)) { | |
$this->import($overridden); | |
} | |
else { | |
$this->logger->info(dt('Current state already matches active config, aborting.')); | |
} | |
} | |
/** | |
* Export the configuration on your site into a custom module. | |
* | |
* @command features-export | |
* @param $packages A space delimited list of features to export. | |
* @option add-profile Package features into an install profile. | |
* @option bundle Use a specific bundle namespace. | |
* @usage drush features-export | |
* Export all available packages. | |
* @usage drush features-export example_article example_page | |
* Export the example_article and example_page packages. | |
* @usage drush features-export --add-profile | |
* Export all available packages and add them to an install profile. | |
* @aliases fex,fu,fua,fu-all | |
*/ | |
public function export(array $packages, $options = ['add-profile' => null, 'bundle' => null]) { | |
$assigner = $this->getAssigner($options); | |
$manager = $this->featuresManager; | |
$generator = $this->featuresGenerator; | |
$current_bundle = $assigner->getBundle(); | |
if ($options['add-profile']) { | |
if ($current_bundle->isDefault) { | |
throw new \Exception((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])) { | |
throw new \Exception(dt("The package @name does not exist.", array('@name' => $name))); | |
} | |
} | |
if (empty($packages)) { | |
$packages = $all_packages; | |
$dt_args = array('@modules' => implode(', ', array_keys($packages))); | |
drush_print(dt('The following extensions will be exported: @modules', $dt_args)); | |
if (!$this->io()->confirm('Do you really want to continue?')) { | |
throw new UserAbortException(); | |
} | |
} | |
// 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.", array('@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 (!$this->io()->confirm($message)) { | |
throw new UserAbortException(); | |
} | |
} | |
// Use the write generation method. | |
$method_id = FeaturesGenerationWrite::METHOD_ID; | |
$result = $generator->generatePackages($method_id, $current_bundle, array_keys($packages)); | |
foreach ($result as $message) { | |
$method = $message['success'] ? 'success' : 'error'; | |
$this->logger()->$method(dt($message['message'], $message['variables'])); | |
} | |
} | |
/** | |
* Add a config item to a feature package. | |
* | |
* @command features-add | |
* @todo @param $feature Feature package to export and add config to. | |
* @param $components Patterns of config to add, see features-components for the format of patterns. | |
* @option bundle Use a specific bundle namespace. | |
* @aliases fa,fe | |
*/ | |
public function add($components = null, $options = ['bundle' => null]) { | |
if ($components) { | |
$assigner = $this->getAssigner($options); | |
$manager = $this->featuresManager; | |
$generator = $this->featuresGenerator; | |
$current_bundle = $assigner->getBundle(); | |
$module = array_shift($args); | |
if (empty($args)) { | |
throw new \Exception('No components supplied.'); | |
} | |
$components = $this->componentList(); | |
$options = array( | |
'exported' => FALSE, | |
); | |
$filtered_components = $this->componentFilter($components, $args, $options); | |
$items = $filtered_components['components']; | |
if (empty($items)) { | |
throw new \Exception('No components to add.'); | |
} | |
$packages = array($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.", array('@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 (!$this->io()->confirm($message)) { | |
throw new UserAbortException(); | |
} | |
} | |
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', array('@name' => $full_name, '@directory' => $path))); | |
if (!$this->io()->confirm(dt('Do you really want to continue?'))) { | |
throw new UserAbortException(); | |
} | |
} | |
$config = $this->buildConfig($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) { | |
$method = $message['success'] ? 'success' : 'error'; | |
$this->logger()->$method(dt($message['message'], $message['variables'])); | |
} | |
} | |
else { | |
throw new \Exception('No feature name given.'); | |
} | |
} | |
/** | |
* List features components. | |
* | |
* @command features-components | |
* @param $patterns The features components type to list. Omit this argument to list all components. | |
* @option exported Show only components that have been exported. | |
* @option not-exported Show only components that have not been exported. | |
* @option bundle Use a specific bundle namespace. | |
* @aliases fc | |
* @field-labels | |
* source: Available sources | |
* | |
* @return RowsOfFields | |
*/ | |
public function components(array $patterns, $options = ['format' => 'table', 'exported' => null, 'not-exported' => null, 'bundle' => null]) { | |
$args = $patterns; | |
$assigner = $this->getAssigner($options); | |
$components = $this->componentList(); | |
ksort($components); | |
// If no args supplied, prompt with a list. | |
if (empty($args)) { | |
$types = array_keys($components); | |
array_unshift($types, 'all'); | |
$choice = $this->io()->choice('Enter a number to choose which component type to list.', $types); | |
if ($choice === FALSE) { | |
return; | |
} | |
$args = ($choice == 0) ? array('*') : array($types[$choice]); | |
} | |
$options = array( | |
'provided by' => TRUE, | |
); | |
if ($options['exported']) { | |
$options['not exported'] = FALSE; | |
} | |
elseif ($options['not-exported']) { | |
$options['exported'] = FALSE; | |
} | |
$filtered_components = $this->componentFilter($components, $args, $options); | |
if ($filtered_components) { | |
return $this->componentPrint($filtered_components); | |
} | |
} | |
/** | |
* Show the difference between the active config and the default config stored in a feature package. | |
* | |
* @command features-diff | |
* @param $feature The feature in question. | |
* @option ctypes Comma separated list of component types to limit the output to. Defaults to all types. | |
* @option lines Generate diffs with <n> lines of context instead of the usual two. | |
* @option bundle Use a specific bundle namespace. | |
* @aliases fd | |
*/ | |
public function diff($feature, $options = ['ctypes' => null, 'lines' => null, 'bundle' => null]) { | |
$manager = $this->featuresManager; | |
$assigner = $this->getAssigner($options); | |
$assigner->assignConfigPackages(); | |
$module = $feature; | |
$filter_ctypes = $options["ctypes"]; | |
if ($filter_ctypes) { | |
$filter_ctypes = explode(',', $filter_ctypes); | |
} | |
$feature = $manager->loadPackage($module, TRUE); | |
if (empty($feature)) { | |
throw new \Exception(dt('No such feature is available: @module', array('@module' => $module))); | |
} | |
$lines = $options['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.', array('@module' => $module))); | |
} | |
else { | |
$config_diff = $this->configDiff; | |
$active_storage = $this->configStorage; | |
// 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 = array(); | |
} else { | |
$active = $manager->getActiveStorage()->read($name); | |
$extension = $manager->getExtensionStorages()->read($name); | |
if (empty($extension)) { | |
$extension = array(); | |
$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", array('@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); | |
} | |
} | |
} | |
} | |
} | |
} | |
/** | |
* Import a module config into your site. | |
* | |
* @command features-import | |
* @param $feature A space delimited list of features or feature:component pairs to import. | |
* @option force Force import even if config is not overridden. | |
* @option bundle Use a specific bundle namespace. | |
* @usage 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 | |
*/ | |
public function import(array $feature, $options = ['force' => null, 'bundle' => null]) { | |
if ($feature) { | |
// Determine if revert should be forced. | |
$force = $options['force']; | |
// Determine if -y was supplied. If so, we can filter out needless output | |
// from this command. | |
$skip_confirmation = drush_get_context('DRUSH_AFFIRMATIVE'); | |
$manager = $this->featuresManager; | |
// Parse list of arguments. | |
$modules = array(); | |
foreach ($feature 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] = array(); | |
} | |
$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)) { | |
throw new \Exception(dt('No such feature is available: @module', $dt_args)); | |
} | |
if ($feature->getStatus() != FeaturesManagerInterface::STATUS_INSTALLED) { | |
throw new \Exception(dt('No such feature is installed: @module', $dt_args)); | |
} | |
// 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)) { | |
$this->logger()->info(dt('Current state already matches active config, aborting.')); | |
} 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 || $this->io()->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])) { | |
$this->logger()->info(dt('Imported @module : @component.', $dt_args)); | |
} elseif (isset($config_imported['updated'][$component])) { | |
$this->logger()->info(dt('Reverted @module : @component.', $dt_args)); | |
} elseif (!isset($config_to_create[$component])) { | |
$this->logger()->info(dt('Skipping @module : @component.', $dt_args)); | |
} else { | |
$this->logger()->error(dt('Error importing @module : @component.', $dt_args)); | |
} | |
} | |
} | |
} | |
} | |
else { | |
drush_invoke_process('@self', 'features-list-packages', [], $options); | |
} | |
} | |
public function getAssigner($options) { | |
$assigner = $this->featuresAssigner; | |
$bundle_name = $options['bundle']; | |
if (!empty($bundle_name)) { | |
$bundle = $assigner->applyBundle($bundle_name); | |
if ($bundle->getMachineName() != $bundle_name) { | |
$this->logger()->warning(dt('Bundle @name not found. Using default.', array('@name' => $bundle_name))); | |
} | |
} | |
else { | |
$assigner->assignConfigPackages(); | |
} | |
return $assigner; | |
} | |
/** | |
* Returns an array of full config names given a array[$type][$component]. | |
* | |
* @param array $items | |
* The items to return data for. | |
*/ | |
function buildConfig(array $items) { | |
$result = array(); | |
foreach ($items as $config_type => $item) { | |
foreach ($item as $item_name => $title) { | |
$result[] = $this->featuresManager->getFullName($config_type, $item_name); | |
} | |
} | |
return $result; | |
} | |
/** | |
* Returns a listing of all known components, indexed by source. | |
*/ | |
function componentList() { | |
$result = array(); | |
$config = $this->featuresManager->getConfigCollection(); | |
foreach ($config as $item_name => $item) { | |
$result[$item->getType()][$item->getShortName()] = $item->getLabel(); | |
} | |
return $result; | |
} | |
/** | |
* Filters components by patterns. | |
*/ | |
function componentFilter($all_components, $patterns = array(), $options = array()) { | |
$options += array( | |
'exported' => TRUE, | |
'not exported' => TRUE, | |
'provided by' => FALSE, | |
); | |
$pool = array(); | |
// Maps exported components to feature modules. | |
$components_map = $this->componentMap(); | |
// 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 = array(); | |
foreach ($patterns as $pattern) { | |
// Rewrite * to %. Let users use both as wildcard. | |
$pattern = strtr($pattern, array('*' => '%')); | |
$sources = array(); | |
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, '/'), array('%' => '.*')); | |
$preg_component_pattern = strtr(preg_quote($component_pattern, '/'), array('%' => '.*')); | |
// 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 = array(); | |
// 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 = array($source_pattern); | |
} | |
else { | |
throw new \Exception(dt('Ambiguous source "@source", matches @matches', array( | |
'@source' => $source_pattern, | |
'@matches' => implode(', ', $matches), | |
))); | |
} | |
} | |
// Loose the indexes preg_grep preserved. | |
$sources = array_values($matches); | |
} | |
else { | |
throw new \Exception(dt('No @state sources match "@source"', array('@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 = array($component_pattern); | |
} | |
else { | |
throw new \Exception(dt('Ambiguous component "@component", matches @matches', array( | |
'@component' => $component_pattern, | |
'@matches' => implode(', ', $matches), | |
))); | |
} | |
} | |
if (!is_array($selected[$source])) { | |
$selected[$source] = array(); | |
} | |
$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] != '^') { | |
throw new \Exception(dt('No @state @source components match "@component"', array( | |
'@state' => $state_string, | |
'@component' => $component_pattern, | |
'@source' => $source, | |
))); | |
} | |
} | |
} | |
} | |
// Lastly, provide feature module information on the selected components, if | |
// requested. | |
$provided_by = array(); | |
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 array( | |
'components' => $selected, | |
'sources' => $provided_by, | |
); | |
} | |
/** | |
* Provides a component to feature map (port of features_get_component_map). | |
*/ | |
function componentMap() { | |
$result = array(); | |
$manager = $this->featuresManager; | |
// 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] = array(); | |
} | |
if (!empty($item->getPackage())) { | |
$package = $packages[$item->getPackage()]; | |
$result[$type][$short_name][] = $package->getMachineName(); | |
} | |
} | |
return $result; | |
} | |
/** | |
* Prints a list of filtered components. | |
*/ | |
function componentPrint($filtered_components) { | |
$rows = []; | |
foreach ($filtered_components['components'] as $source => $components) { | |
foreach ($components as $name => $value) { | |
$row = array('source' => $source . ':' . $name); | |
if (isset($filtered_components['sources'][$source . ':' . $name])) { | |
$row['source'] = dt('Provided by') . ': ' . $filtered_components['sources'][$source . ':' . $name]; | |
} | |
$rows[] = $row; | |
} | |
} | |
return new RowsOfFields($rows); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment