From 8242bbd63718981c4db8c59a11f36a55660eab6c Mon Sep 17 00:00:00 2001
From: Eric Bremner <ebremner@uwaterloo.ca>
Date: Fri, 3 Dec 2021 13:52:34 +0000
Subject: [PATCH] ISTWCMS-5234: adding class to perform feature reverts
 programtically

---
 src/Helpers/UwFeatureRevert.php | 203 ++++++++++++++++++++++++++++++++
 uw_cfg_common.services.yml      |   4 +
 2 files changed, 207 insertions(+)
 create mode 100644 src/Helpers/UwFeatureRevert.php

diff --git a/src/Helpers/UwFeatureRevert.php b/src/Helpers/UwFeatureRevert.php
new file mode 100644
index 00000000..a53325e1
--- /dev/null
+++ b/src/Helpers/UwFeatureRevert.php
@@ -0,0 +1,203 @@
+<?php
+
+namespace Drupal\uw_cfg_common\Helpers;
+
+use Drupal\features\FeaturesManagerInterface;
+use Drupal\features\ConfigurationItem;
+use Drupal\features\Package;
+use Drupal\config_update\ConfigRevertInterface;
+
+/**
+ * Class to perform feature revert.
+ */
+class UwFeatureRevert {
+
+  /**
+   * Features manager.
+   *
+   * @var \Drupal\features\FeaturesManagerInterface
+   */
+  private $featuresManager;
+
+  /**
+   * Config update revert.
+   *
+   * @var \Drupal\config_update\ConfigRevertInterface
+   */
+  private $configRevert;
+
+  /**
+   * Default constructor.
+   *
+   * @param \Drupal\features\FeaturesManagerInterface $featuresManager
+   *   Features manager.
+   * @param Drupal\config_update\ConfigRevertInterface $configRevert
+   *   The config update revert.
+   */
+  public function __construct(
+    FeaturesManagerInterface $featuresManager,
+    ConfigRevertInterface $configRevert
+  ) {
+    $this->featuresManager = $featuresManager;
+    $this->configRevert = $configRevert;
+  }
+
+  /**
+   * Load the features from the module.
+   *
+   * @param array $module
+   *   Array of modules.
+   * @param bool $any
+   *   Flag for all modules.
+   *
+   * @return \Drupal\features\Package
+   *   The list of packages.
+   */
+  private function drushFeaturesLoadFeature(array $module, bool $any = FALSE): Package {
+
+    // Get the features manager.
+    $manager = $this->featuresManager;
+
+    // Get teh feature.
+    $feature = $manager->getPackage($module);
+
+    // Check if this get any or not a feature module.
+    if ($any && !isset($feature)) {
+
+      // See if this is a non-features module.
+      $module_handler = \Drupal::moduleHandler();
+      $modules = $module_handler->getModuleList();
+
+      // If it is not a feature module, set it up as one.
+      if (!empty($modules[$module])) {
+        $extension = $modules[$module];
+        $feature   = $manager->initPackageFromExtension($extension);
+        $config    = $manager->listExtensionConfig($extension);
+        $feature->setConfig($config);
+        $feature->setStatus(FeaturesManagerInterface::STATUS_INSTALLED);
+      }
+    }
+
+    return $feature;
+  }
+
+  /**
+   * Copy from file features.drush.inc.
+   */
+  public function import($args) {
+
+    // Determine if revert should be forced.
+    $force = TRUE;
+
+    // Determine if -y was supplied. If so, we can filter out needless output
+    // from this command.
+    $skip_confirmation = TRUE;
+
+    // Get the feature manager.
+    $manager = $this->featuresManager;
+
+    // Get the config update revert.
+    $config_revert = $this->configRevert;
+
+    // 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) {
+
+      // Setup the modules array.
+      $dt_args['@module'] = $module;
+
+      // Get the feature information.
+      $feature = $this->drushFeaturesLoadFeature($module, TRUE);
+
+      // If the feature is empty, throw an error and exit.
+      if (empty($feature)) {
+        \Drupal::logger('UwFeatureRevert')
+          ->error('No such feature is available: @module', $dt_args);
+        return;
+      }
+
+      // If the feature status is not set as active, throw error and exit.
+      if ($feature->getStatus() != FeaturesManagerInterface::STATUS_INSTALLED) {
+        \Drupal::logger('UwFeatureRevert')
+          ->error('No such feature is installed: @module', $dt_args);
+        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)) {
+        \Drupal::logger('UwFeatureRevert')
+          ->notice('Current state already matches active config, aborting.');
+      }
+      else {
+        $config = $manager->getConfigCollection();
+        foreach ($components as $component) {
+          $dt_args['@component'] = $component;
+          if ($skip_confirmation) {
+
+            // If there is no config for this present, import it.
+            if (!isset($config[$component])) {
+
+              // Import missing component.
+              $item = $manager->getConfigType($component);
+              $type = ConfigurationItem::fromConfigStringToConfigType($item['type']);
+              $config_revert->import($type, $item['name_short']);
+              \Drupal::logger('d_application_api')
+                ->notice('Import @module : @component.', $dt_args);
+            }
+            else {
+
+              // Revert existing component.
+              $item = $config[$component];
+              $type = ConfigurationItem::fromConfigStringToConfigType($item->getType());
+              $config_revert->revert($type, $item->getShortName());
+              \Drupal::logger('UwFeatureRevert')
+                ->notice('Reverted @module : @component.', $dt_args);
+            }
+          }
+          else {
+            \Drupal::logger('UwFeatureRevert')
+              ->notice('Skipping @module : @component.', $dt_args);
+          }
+        }
+      }
+    }
+  }
+
+}
diff --git a/uw_cfg_common.services.yml b/uw_cfg_common.services.yml
index e7878616..9cd4b918 100644
--- a/uw_cfg_common.services.yml
+++ b/uw_cfg_common.services.yml
@@ -29,3 +29,7 @@ services:
     class: '\Drupal\uw_cfg_common\EventSubscriber\UwLayoutBuilderEventSubscriber'
     tags:
       - { name: 'event_subscriber' }
+  uw_cfg_common.features:
+    class: Drupal\uw_cfg_common\Helpers\UwFeatureRevert
+    arguments: ['@features.manager']
+
-- 
GitLab