diff --git a/config/services/drupal-console/feature.yml b/config/services/drupal-console/feature.yml new file mode 100644 index 000000000..9351c3b5c --- /dev/null +++ b/config/services/drupal-console/feature.yml @@ -0,0 +1,9 @@ +services: + feature_debug: + class: Drupal\Console\Command\Features\DebugCommand + tags: + - { name: drupal.command } + feature_import: + class: Drupal\Console\Command\Features\ImportCommand + tags: + - { name: drupal.command } diff --git a/src/Command/Features/DebugCommand.php b/src/Command/Features/DebugCommand.php new file mode 100644 index 000000000..ee2dd49f8 --- /dev/null +++ b/src/Command/Features/DebugCommand.php @@ -0,0 +1,77 @@ +setName('features:debug') + ->setDescription($this->trans('commands.features.debug.description')) + ->addArgument( + 'bundle', + InputArgument::OPTIONAL, + $this->trans('commands.features.debug.arguments.bundle') + ); + + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new DrupalStyle($input, $output); + $bundle= $input->getArgument('bundle'); + + $tableHeader = [ + $this->trans('commands.features.debug.messages.bundle'), + $this->trans('commands.features.debug.messages.name'), + $this->trans('commands.features.debug.messages.machine_name'), + $this->trans('commands.features.debug.messages.status'), + $this->trans('commands.features.debug.messages.state'), + ]; + + $tableRows = []; + + $features = $this->getFeatureList($bundle); + + foreach ($features as $feature) { + $tableRows[] = [$feature['bundle_name'],$feature['name'], $feature['machine_name'], $feature['status'],$feature['state']]; + } + + $io->table($tableHeader, $tableRows, 'compact'); + } + + + } diff --git a/src/Command/Features/ImportCommand.php b/src/Command/Features/ImportCommand.php new file mode 100644 index 000000000..9bf508ece --- /dev/null +++ b/src/Command/Features/ImportCommand.php @@ -0,0 +1,88 @@ +setName('features:import') + ->setDescription($this->trans('commands.features.import.description')) + ->addOption( + 'bundle', + '', + InputOption::VALUE_OPTIONAL, + $this->trans('commands.features.import.options.packages') + ) + ->addArgument('packages',InputArgument::IS_ARRAY,$this->trans('commands.features.import.arguments.packages')); + + + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new DrupalStyle($input, $output); + + $packages = $input->getArgument('packages'); + $bundle = $input->getOption('bundle'); + + if ($bundle) { + $packages = $this->getPackagesByBundle($bundle); + } + + $this->getAssigner($bundle); + $this->importFeature($io,$packages); + + } + /** + * {@inheritdoc} + */ + protected function interact(InputInterface $input, OutputInterface $output) + { + $io = new DrupalStyle($input, $output); + + $packages = $input->getArgument('packages'); + $bundle = $input->getOption('bundle'); + + if (!$packages && !$bundle) { + // @see Drupal\Console\Command\Shared\FeatureTrait::packageQuestion + $bundle = $this->packageQuestion($io); + $input->setArgument('packages', $bundle); + } + } + + } diff --git a/src/Command/Shared/FeatureTrait.php b/src/Command/Shared/FeatureTrait.php new file mode 100644 index 000000000..8f7f27002 --- /dev/null +++ b/src/Command/Shared/FeatureTrait.php @@ -0,0 +1,241 @@ +getPackagesByBundle($bundle); + + if (empty($packages)) { + throw new \Exception('No packages available'); + } + + $package = $io->choiceNoList( + $this->trans('commands.features.import.questions.packages'), + $packages + ); + + return $package; + + } + + + /** + * @param bool $bundle_name + * + * @return \Drupal\features\FeaturesAssignerInterface + */ + protected function getAssigner($bundle_name) { + /** @var \Drupal\features\FeaturesAssignerInterface $assigner */ + $assigner = \Drupal::service('features_assigner'); + if (!empty($bundle_name)) { + $bundle = $assigner->applyBundle($bundle_name); + + if ($bundle->getMachineName() != $bundle_name) { + + } + } + // return configuration for default bundle + else { + $assigner->assignConfigPackages(); + } + return $assigner; + } + + + + /** + * Get a list of features. + * + * @param bundle + * + * @return features + */ + protected function getFeatureList($io,$bundle) { + + $features = []; + $manager = $this->getFeatureManager(); + $modules = $this->getPackagesByBundle($bundle); + + foreach ($modules as $module_name) { + $feature = $manager->loadPackage($module_name,TRUE); + $overrides = $manager->detectOverrides($feature); + + $state = $feature->getState(); + + if (!empty($overrides) && ($feature->getStatus() != FeaturesManagerInterface::STATUS_NO_EXPORT)) { + $state = FeaturesManagerInterface::STATE_OVERRIDDEN; + } + + if ($feature->getStatus() != FeaturesManagerInterface::STATUS_NO_EXPORT) { + + $features[$feature->getMachineName()] = array( + 'name' => $feature->getName(), + 'machine_name' => $feature->getMachineName(), + 'bundle_name' => $feature->getBundle(), + 'status' => $manager->statusLabel($feature->getStatus()), + 'state' => ($state != FeaturesManagerInterface::STATE_DEFAULT) + ? $manager->stateLabel($state) + : '', + ); + } + + } + + return $features; + + } + + + protected function importFeature($io,$packages) { + + $manager = $this->getFeatureManager(); + + /** @var \Drupal\config_update\ConfigRevertInterface $config_revert */ + $config_revert = \Drupal::service('features.config_update'); + + $config = $manager->getConfigCollection(); + $modules = (is_array($packages)) ? $packages : array($packages); + $overridden = [] ; + foreach ($modules as $module_name) { + + $package = $manager->loadPackage($module_name,TRUE); + + if (empty($package)) { + $io->warning( + sprintf( + $this->trans('commands.features.import.messages.not-available'), + $module_name + ) + ); + continue; + } + + if ($package->getStatus() != FeaturesManagerInterface::STATUS_INSTALLED) { + $io->warning( + sprintf( + $this->trans('commands.features.import.messages.uninstall'), + $module_name + ) + ); + continue; + } + + $overrides = $manager->detectOverrides($package); + $missing = $manager->reorderMissing($manager->detectMissing($package)); + + if (!empty($overrides) || !empty($missing) && ($package->getStatus() == FeaturesManagerInterface::STATUS_INSTALLED)) { + $overridden[] = array_merge($missing,$overrides); + } + } + + // Process only missing or overriden features + $components = $overridden; + + if (empty($components)){ + $io->warning( + sprintf( + $this->trans('commands.features.import.messages.nothing'), + $components + ) + ); + + return ; + } + else { + + $this->import($io,$components); + } + } + + public function import($io,$components) { + $manager = $this->getFeatureManager(); + /** @var \Drupal\config_update\ConfigRevertInterface $config_revert */ + $config_revert = \Drupal::service('features.config_update'); + + $config = $manager->getConfigCollection(); + + foreach ($components as $component) { + foreach ($component as $feature) { + + if (!isset($config[$feature])) { + + //Import missing component. + $item = $manager->getConfigType($feature); + $type = ConfigurationItem::fromConfigStringToConfigType($item['type']); + $config_revert->import($type, $item['name_short']); + $io->info( + sprintf( + $this->trans('commands.features.import.messages.importing'), + $feature + ) + ); + + } + + else { + + // Revert existing component. + $item = $config[$feature]; + $type = ConfigurationItem::fromConfigStringToConfigType($item->getType()); + $config_revert->revert($type, $item->getShortName()); + $io->info( + sprintf( + $this->trans('commands.features.import.messages.reverting'), + $feature + ) + ); + } + } + } + + } + + + public function getPackagesByBundle($bundle) { + + $packages = []; + $manager = $this->getFeatureManager(); + $assigner = $this->getAssigner($bundle); + $current_bundle = $assigner->getBundle(); + + // List all packages availables + if ($current_bundle->getMachineName() == 'default') { + $current_bundle = NULL; + + } + + $packages = array_keys($manager->getFeaturesModules($current_bundle)); + + return $packages; + + } + + public function getFeatureManager(){ + return \Drupal::service('features.manager'); + } + +}