You are here

FeaturesAssigner.php in Features 8.4

Same filename and directory in other branches
  1. 8.3 src/FeaturesAssigner.php

Namespace

Drupal\features

File

src/FeaturesAssigner.php
View source
<?php

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\StringTranslation\StringTranslationTrait;

/**
 * Class responsible for performing package assignment.
 */
class FeaturesAssigner implements FeaturesAssignerInterface {
  use StringTranslationTrait;

  /**
   * The package assignment method plugin manager.
   *
   * @var \Drupal\Component\Plugin\PluginManagerInterface
   */
  protected $assignerManager;

  /**
   * The features manager.
   *
   * @var \Drupal\features\FeaturesManagerInterface
   */
  protected $featuresManager;

  /**
   * The configuration factory.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected $configFactory;

  /**
   * The configuration storage.
   *
   * @var \Drupal\Core\Config\StorageInterface
   */
  protected $configStorage;

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * Local cache for package assignment method instances.
   *
   * @var array
   */
  protected $methods;

  /**
   * Bundles.
   *
   * @var \Drupal\features\FeaturesBundleInterface[]
   */
  protected $bundles;

  /**
   * Currently active bundle.
   *
   * @var \Drupal\features\FeaturesBundleInterface
   */
  protected $currentBundle;

  /**
   * The name of the currently active installation profile.
   *
   * @var string
   */
  protected $installProfile;

  /**
   * Constructs a new FeaturesAssigner object.
   *
   * @param \Drupal\features\FeaturesManagerInterface $features_manager
   *   The features manager.
   * @param \Drupal\Component\Plugin\PluginManagerInterface $assigner_manager
   *   The package assignment methods plugin manager.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The configuration factory.
   * @param \Drupal\Core\Config\StorageInterface $config_storage
   *   The configuration factory.
   * @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) {
    $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->bundles = $this
      ->getBundleList();
    $this->currentBundle = $this
      ->getBundle(FeaturesBundleInterface::DEFAULT_BUNDLE);

    // Ensure bundle information is fresh.
    $this
      ->createBundlesFromPackages();
  }

  /**
   * Initializes the injected features manager with the assigner.
   *
   * This should be called right after instantiating the assigner to make it
   * available to the features manager without introducing a circular
   * dependency.
   */
  public function initFeaturesManager() {
    $this->featuresManager
      ->setAssigner($this);
  }

  /**
   * {@inheritdoc}
   */
  public function reset() {
    $this->methods = [];
    $this->featuresManager
      ->reset();
  }

  /**
   * Gets enabled assignment methods.
   *
   * @return array
   *   An array of enabled assignment methods, sorted by weight.
   */
  public function getEnabledAssigners() {
    $enabled = $this->currentBundle
      ->getEnabledAssignments();
    $weights = $this->currentBundle
      ->getAssignmentWeights();
    foreach ($enabled as $key => $value) {
      $enabled[$key] = $weights[$key];
    }
    asort($enabled);
    return $enabled;
  }

  /**
   * Clean up the package list after all config has been assigned.
   */
  protected function cleanup() {
    $packages = $this->featuresManager
      ->getPackages();
    foreach ($packages as $index => $package) {
      if ($package
        ->getStatus() === FeaturesManagerInterface::STATUS_NO_EXPORT && empty($package
        ->getConfig()) && empty($package
        ->getConfigOrig())) {
        unset($packages[$index]);
      }
    }
    $this->featuresManager
      ->setPackages($packages);
  }

  /**
   * {@inheritdoc}
   */
  public function assignConfigPackages($force = FALSE) {
    foreach ($this
      ->getEnabledAssigners() as $method_id => $info) {
      $this
        ->applyAssignmentMethod($method_id, $force);
    }
    $this
      ->cleanup();
  }

  /**
   * {@inheritdoc}
   */
  public function applyAssignmentMethod($method_id, $force = FALSE) {
    $this
      ->getAssignmentMethodInstance($method_id)
      ->assignPackages($force);
  }

  /**
   * {@inheritdoc}
   */
  public function getAssignmentMethods() {
    return $this->assignerManager
      ->getDefinitions();
  }

  /**
   * Returns an instance of the specified package assignment method.
   *
   * @param string $method_id
   *   The string identifier of the package assignment method to use to package
   *   configuration.
   *
   * @return \Drupal\features\FeaturesAssignmentMethodInterface
   *   The package assignment method instance.
   */
  protected function getAssignmentMethodInstance($method_id) {
    if (!isset($this->methods[$method_id])) {
      $instance = $this->assignerManager
        ->createInstance($method_id, []);
      $instance
        ->setFeaturesManager($this->featuresManager);
      $instance
        ->setAssigner($this);
      $instance
        ->setEntityTypeManager($this->entityTypeManager);
      $instance
        ->setConfigFactory($this->configFactory);
      $this->methods[$method_id] = $instance;
    }
    return $this->methods[$method_id];
  }

  /**
   * {@inheritdoc}
   */
  public function purgeConfiguration() {

    // Ensure that we are getting the defined package assignment information.
    // An invocation of \Drupal\Core\Extension\ModuleHandler::install() or
    // \Drupal\Core\Extension\ModuleHandler::uninstall() could invalidate the
    // cached information.
    $this->assignerManager
      ->clearCachedDefinitions();
    $this->featuresManager
      ->reset();
  }

  /**
   * {@inheritdoc}
   */
  public function getBundle($name = NULL) {
    if (empty($name)) {
      return $this->currentBundle;
    }
    elseif (isset($this->bundles[$name])) {
      return $this->bundles[$name];
    }
    return NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function setBundle(FeaturesBundleInterface $bundle, $current = TRUE) {
    $this->bundles[$bundle
      ->getMachineName()] = $bundle;
    if (isset($this->currentBundle) && ($current || $bundle
      ->getMachineName() == $this->currentBundle
      ->getMachineName())) {
      $this->currentBundle = $bundle;
    }
  }

  /**
   * {@inheritdoc}
   */
  public function findBundle(array $info, $features_info = NULL) {
    $bundle = NULL;
    if (!empty($features_info['bundle'])) {
      $bundle = $this
        ->getBundle($features_info['bundle']);
    }
    elseif (!empty($info['package'])) {
      $bundle = $this
        ->findBundleByName($info['package']);
    }
    if (!isset($bundle)) {

      // Return the default bundle.
      return $this
        ->getBundle(FeaturesBundleInterface::DEFAULT_BUNDLE);
    }
    return $bundle;
  }

  /**
   * {@inheritdoc}
   */
  public function setCurrent(FeaturesBundleInterface $bundle) {
    $this->currentBundle = $bundle;
    $session = \Drupal::request()
      ->getSession();
    if (isset($session)) {
      $session
        ->set('features_current_bundle', $bundle
        ->getMachineName());
    }
    return $bundle;
  }

  /**
   * {@inheritdoc}
   */
  public function getBundleList() {
    if (empty($this->bundles)) {
      $this->bundles = [];
      foreach ($this->entityTypeManager
        ->getStorage('features_bundle')
        ->loadMultiple() as $machine_name => $bundle) {
        $this->bundles[$machine_name] = $bundle;
      }
    }
    return $this->bundles;
  }

  /**
   * {@inheritdoc}
   */
  public function findBundleByName($name, $create = FALSE) {
    $bundles = $this
      ->getBundleList();
    foreach ($bundles as $machine_name => $bundle) {
      if ($name == $bundle
        ->getName()) {
        return $bundle;
      }
    }
    $machine_name = strtolower(str_replace([
      ' ',
      '-',
    ], '_', $name));
    if (isset($bundles[$machine_name])) {
      return $bundles[$machine_name];
    }
    return NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function createBundleFromDefault($machine_name, $name = NULL, $description = NULL, $is_profile = FALSE, $profile_name = NULL) {

    // Duplicate the default bundle to get its default configuration.
    $default = $this
      ->getBundle(FeaturesBundleInterface::DEFAULT_BUNDLE);
    if (!$default) {

      // If we don't have the default installed, generate it from the install
      // config file.
      $ext_storage = new ExtensionInstallStorage($this->configStorage, ExtensionInstallStorage::CONFIG_INSTALL_DIRECTORY, ExtensionInstallStorage::DEFAULT_COLLECTION, TRUE, $this->installProfile);
      $record = $ext_storage
        ->read('features.bundle.default');
      $bundle_storage = $this->entityTypeManager
        ->getStorage('features_bundle');
      $default = $bundle_storage
        ->createFromStorageRecord($record);
    }

    /** @var \Drupal\features\Entity\FeaturesBundle $bundle */
    $bundle = $default
      ->createDuplicate();
    $bundle
      ->setMachineName($machine_name);
    $name = !empty($name) ? $name : $machine_name;
    $bundle
      ->setName($name);
    if (isset($description)) {
      $bundle
        ->setDescription($description);
    }
    else {
      $bundle
        ->setDescription(t('Auto-generated bundle from package @name', [
        '@name' => $name,
      ]));
    }
    $bundle
      ->setIsProfile($is_profile);
    if (isset($profile_name)) {
      $bundle
        ->setProfileName($profile_name);
    }
    $bundle
      ->save();
    $this
      ->setBundle($bundle);
    return $bundle;
  }

  /**
   * {@inheritdoc}
   */
  public function createBundlesFromPackages() {
    $existing_bundles = $this
      ->getBundleList();
    $new_bundles = [];

    // Only parse from installed features.
    $modules = $this->featuresManager
      ->getFeaturesModules(NULL, TRUE);
    foreach ($modules as $module) {
      $info = $this->featuresManager
        ->getExtensionInfo($module);

      // @todo This entire function could be simplified a lot using packages.
      $features_info = $this->featuresManager
        ->getFeaturesInfo($module);

      // Create a new bundle if:
      // - the feature specifies a bundle and
      // - that bundle doesn't yet exist locally.
      // Allow profiles to override previous values.
      if (!empty($features_info['bundle']) && !isset($existing_bundles[$features_info['bundle']]) && (!in_array($features_info['bundle'], $new_bundles) || $info['type'] == 'profile')) {
        if ($info['type'] == 'profile') {
          $new_bundle = [
            'name' => $info['name'],
            'description' => $info['description'],
            'is_profile' => TRUE,
            'profile_name' => $module
              ->getName(),
          ];
        }
        else {
          $new_bundle = [
            'name' => isset($info['package']) ? $info['package'] : ucwords(str_replace('_', ' ', $features_info['bundle'])),
            'description' => NULL,
            'is_profile' => FALSE,
            'profile_name' => NULL,
          ];
        }
        $new_bundle['machine_name'] = $features_info['bundle'];
        $new_bundles[$new_bundle['machine_name']] = $new_bundle;
      }
    }
    foreach ($new_bundles as $new_bundle) {
      $this
        ->createBundleFromDefault($new_bundle['machine_name'], $new_bundle['name'], $new_bundle['description'], $new_bundle['is_profile']);
    }
  }

  /**
   * {@inheritdoc}
   */
  public function getBundleOptions() {
    $list = $this
      ->getBundleList();
    $result = [];
    foreach ($list as $machine_name => $bundle) {
      $result[$machine_name] = $bundle
        ->getName();
    }
    return $result;
  }

  /**
   * {@inheritdoc}
   */
  public function applyBundle($machine_name = NULL) {
    $this
      ->reset();
    $bundle = $this
      ->loadBundle($machine_name);
    if (isset($bundle)) {
      $this
        ->assignConfigPackages();
      return $this->currentBundle;
    }
    return NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function renameBundle($old_machine, $new_machine) {
    $is_current = isset($this->currentBundle) && $old_machine == $this->currentBundle
      ->getMachineName();
    $bundle = $this
      ->getBundle($old_machine);
    if ($bundle
      ->getMachineName() != '') {

      // Remove old bundle from the list if it's not the Default bundle.
      unset($this->bundles[$old_machine]);
    }
    $bundle
      ->setMachineName($new_machine);
    $this
      ->setBundle($bundle);

    // Put the bundle into the list with the correct name.
    $this->bundles[$bundle
      ->getMachineName()] = $bundle;
    if ($is_current) {
      $this
        ->setCurrent($bundle);
    }
    return $bundle;
  }

  /**
   * {@inheritdoc}
   */
  public function loadBundle($machine_name = NULL) {
    if (!isset($machine_name)) {
      $session = \Drupal::request()
        ->getSession();
      if (isset($session)) {
        $machine_name = isset($session) ? $session
          ->get('features_current_bundle', FeaturesBundleInterface::DEFAULT_BUNDLE) : FeaturesBundleInterface::DEFAULT_BUNDLE;
      }
    }
    $bundle = $this
      ->getBundle($machine_name);
    if (!isset($bundle)) {

      // If bundle no longer exists then return default.
      $bundle = $this->bundles[FeaturesBundleInterface::DEFAULT_BUNDLE];
    }
    return $this
      ->setCurrent($bundle);
  }

  /**
   * {@inheritdoc}
   */
  public function removeBundle($machine_name) {
    $bundle = $this
      ->getBundle($machine_name);
    if (isset($bundle) && !$bundle
      ->isDefault()) {
      unset($this->bundles[$machine_name]);
      $bundle
        ->remove();
    }
  }

}

Classes

Namesort descending Description
FeaturesAssigner Class responsible for performing package assignment.