added features
This commit is contained in:
@@ -0,0 +1,351 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
/**
|
||||
* Contains some configuration together with metadata like the name + package.
|
||||
*
|
||||
* @todo Should the object be immutable?
|
||||
* @todo Should this object have an interface?
|
||||
*/
|
||||
class ConfigurationItem {
|
||||
|
||||
/**
|
||||
* Prefixed configuration item name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* Configuration item name without prefix.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $shortName;
|
||||
|
||||
/**
|
||||
* Human readable name of configuration item.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $label;
|
||||
|
||||
/**
|
||||
* Type of configuration.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* The contents of the configuration item in exported format.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $data;
|
||||
|
||||
/**
|
||||
* Array of names of dependent configuration items.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $dependents = [];
|
||||
|
||||
/**
|
||||
* Feature subdirectory to export item to.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $subdirectory;
|
||||
|
||||
/**
|
||||
* Machine name of a package the configuration is assigned to.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $package;
|
||||
|
||||
/**
|
||||
* Whether the configuration is marked as excluded.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $excluded = FALSE;
|
||||
|
||||
/**
|
||||
* Whether the configuration provider is excluded.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $providerExcluded = FALSE;
|
||||
|
||||
/**
|
||||
* The provider of the config item.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $provider;
|
||||
|
||||
/**
|
||||
* Array of package names that this item should be excluded from.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $packageExcluded = [];
|
||||
|
||||
/**
|
||||
* Creates a new ConfigurationItem instance.
|
||||
*
|
||||
* @param string $name
|
||||
* The config name.
|
||||
* @param array $data
|
||||
* The config data.
|
||||
* @param array $additional_properties
|
||||
* (optional) Additional properties set on the object.
|
||||
*/
|
||||
public function __construct($name, array $data, array $additional_properties = []) {
|
||||
$this->name = $name;
|
||||
$this->data = $data;
|
||||
|
||||
$properties = get_object_vars($this);
|
||||
foreach ($additional_properties as $property => $value) {
|
||||
if (!array_key_exists($property, $properties)) {
|
||||
throw new \InvalidArgumentException('Invalid property: ' . $property);
|
||||
}
|
||||
$this->{$property} = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the config type usable in configuration.
|
||||
*
|
||||
* By default Drupal uses system.simple as config type, which cannot be used
|
||||
* inside configuration itself. Therefore convert it to system_simple.
|
||||
*
|
||||
* @param string $type
|
||||
* The config type provided by core.
|
||||
*
|
||||
* @return string
|
||||
* The config type as string without dots.
|
||||
*/
|
||||
public static function fromConfigTypeToConfigString($type) {
|
||||
return $type == 'system.simple' ? FeaturesManagerInterface::SYSTEM_SIMPLE_CONFIG : $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a config type string in configuration back to the config type.
|
||||
*
|
||||
* @param string $type
|
||||
* The config type as string without dots.
|
||||
*
|
||||
* @return string
|
||||
* The config type provided by core.
|
||||
*/
|
||||
public static function fromConfigStringToConfigType($type) {
|
||||
return $type == FeaturesManagerInterface::SYSTEM_SIMPLE_CONFIG ? 'system.simple' : $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getName() {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $name
|
||||
*
|
||||
* @return ConfigurationItem
|
||||
*/
|
||||
public function setName($name) {
|
||||
$this->name = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getShortName() {
|
||||
return $this->shortName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $shortName
|
||||
*
|
||||
* @return ConfigurationItem
|
||||
*/
|
||||
public function setShortName($shortName) {
|
||||
$this->shortName = $shortName;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getLabel() {
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $label
|
||||
*
|
||||
* @return ConfigurationItem
|
||||
*/
|
||||
public function setLabel($label) {
|
||||
$this->label = $label;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getType() {
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $type
|
||||
*
|
||||
* @return ConfigurationItem
|
||||
*/
|
||||
public function setType($type) {
|
||||
$this->type = $type;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getData() {
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed array
|
||||
*
|
||||
* @return ConfigurationItem
|
||||
*/
|
||||
public function setData(array $data) {
|
||||
$this->data = $data;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getDependents() {
|
||||
return $this->dependents;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $dependents
|
||||
*
|
||||
* @return ConfigurationItem
|
||||
*/
|
||||
public function setDependents($dependents) {
|
||||
$this->dependents = $dependents;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getSubdirectory() {
|
||||
return $this->subdirectory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $subdirectory
|
||||
*
|
||||
* @return ConfigurationItem
|
||||
*/
|
||||
public function setSubdirectory($subdirectory) {
|
||||
$this->subdirectory = $subdirectory;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getPackage() {
|
||||
return $this->package;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $package
|
||||
*
|
||||
* @return ConfigurationItem
|
||||
*/
|
||||
public function setPackage($package) {
|
||||
$this->package = $package;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function isExcluded() {
|
||||
return $this->excluded;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param boolean $excluded
|
||||
*
|
||||
* @return ConfigurationItem
|
||||
*/
|
||||
public function setExcluded($excluded) {
|
||||
$this->excluded = $excluded;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function isProviderExcluded() {
|
||||
return $this->providerExcluded;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param boolean $providerExcluded
|
||||
*
|
||||
* @return ConfigurationItem
|
||||
*/
|
||||
public function setProviderExcluded($providerExcluded) {
|
||||
$this->providerExcluded = $providerExcluded;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getProvider() {
|
||||
return $this->provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $provider
|
||||
*/
|
||||
public function setProvider($provider) {
|
||||
$this->provider = $provider;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getPackageExcluded() {
|
||||
return $this->packageExcluded;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $packageExcluded
|
||||
*
|
||||
* @return ConfigurationItem
|
||||
*/
|
||||
public function setPackageExcluded($packageExcluded) {
|
||||
$this->packageExcluded = $packageExcluded;
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features\Controller;
|
||||
|
||||
use Drupal\Core\Access\CsrfTokenGenerator;
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\system\FileDownloadController;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
|
||||
/**
|
||||
* Returns responses for config module routes.
|
||||
*/
|
||||
class FeaturesController implements ContainerInjectionInterface {
|
||||
|
||||
/**
|
||||
* The file download controller.
|
||||
*
|
||||
* @var \Drupal\system\FileDownloadController
|
||||
*/
|
||||
protected $fileDownloadController;
|
||||
|
||||
/**
|
||||
* The CSRF token generator.
|
||||
*
|
||||
* @var \Drupal\Core\Access\CsrfTokenGenerator
|
||||
*/
|
||||
protected $csrfToken;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
new FileDownloadController(),
|
||||
$container->get('csrf_token')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a FeaturesController object.
|
||||
*
|
||||
* @param \Drupal\system\FileDownloadController $file_download_controller
|
||||
* The file download controller.
|
||||
* @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
|
||||
* The CSRF token generator.
|
||||
*/
|
||||
public function __construct(FileDownloadController $file_download_controller, CsrfTokenGenerator $csrf_token) {
|
||||
$this->fileDownloadController = $file_download_controller;
|
||||
$this->csrfToken = $csrf_token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads a tarball of the site configuration.
|
||||
*
|
||||
* @param string $uri
|
||||
* The URI to download.
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The request.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\BinaryFileResponse
|
||||
* The downloaded file.
|
||||
*/
|
||||
public function downloadExport($uri, Request $request) {
|
||||
if ($uri) {
|
||||
// @todo Simplify once https://www.drupal.org/node/2630920 is solved.
|
||||
if (!$this->csrfToken->validate($request->query->get('token'), $uri)) {
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
|
||||
$request = new Request(array('file' => $uri));
|
||||
return $this->fileDownloadController->download($request, 'temporary');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features\Entity;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityBase;
|
||||
use Drupal\features\FeaturesAssignmentMethodInterface;
|
||||
use Drupal\features\FeaturesBundleInterface;
|
||||
|
||||
/**
|
||||
* Defines a features bundle.
|
||||
* @todo Better description
|
||||
*
|
||||
* @ConfigEntityType(
|
||||
* id = "features_bundle",
|
||||
* label = @Translation("Features bundle"),
|
||||
* handlers = {
|
||||
* },
|
||||
* admin_permission = "administer site configuration",
|
||||
* config_prefix = "bundle",
|
||||
* entity_keys = {
|
||||
* "id" = "machine_name",
|
||||
* "label" = "name"
|
||||
* },
|
||||
* links = {
|
||||
* },
|
||||
* config_export = {
|
||||
* "name",
|
||||
* "machine_name",
|
||||
* "description",
|
||||
* "assignments",
|
||||
* "profile_name",
|
||||
* "is_profile",
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class FeaturesBundle extends ConfigEntityBase implements FeaturesBundleInterface {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @var
|
||||
*/
|
||||
protected $machine_name;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $description;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $assignments = [];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $profile_name;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $is_profile;
|
||||
|
||||
public function id() {
|
||||
// @todo Convert it to $this->id in the long run.
|
||||
return $this->getMachineName();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isDefault() {
|
||||
return $this->machine_name == static::DEFAULT_BUNDLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMachineName() {
|
||||
return $this->machine_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setMachineName($machine_name) {
|
||||
$this->machine_name = $machine_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName() {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setName($name) {
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFullName($short_name) {
|
||||
if ($this->isDefault() || $this->inBundle($short_name)) {
|
||||
return $short_name;
|
||||
}
|
||||
else {
|
||||
return $this->machine_name . '_' . $short_name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getShortName($machine_name) {
|
||||
if (!$this->isProfilePackage($machine_name) && $this->inBundle($machine_name)) {
|
||||
return substr($machine_name, strlen($this->getMachineName()) + 1, strlen($machine_name) - strlen($this->getMachineName()) - 1);
|
||||
}
|
||||
return $machine_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function inBundle($machine_name) {
|
||||
return ($this->isProfilePackage($machine_name) || strpos($machine_name, $this->machine_name . '_') === 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isProfilePackage($machine_name) {
|
||||
return ($this->isProfile() && $machine_name == $this->getProfileName());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDescription() {
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setDescription($description) {
|
||||
$this->description = $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isProfile() {
|
||||
return $this->is_profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setIsProfile($value) {
|
||||
$this->is_profile = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getProfileName() {
|
||||
$name = $this->isProfile() ? $this->profile_name : '';
|
||||
return !empty($name) ? $name : drupal_get_profile();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setProfileName($machine_name) {
|
||||
$this->profile_name = $machine_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getEnabledAssignments() {
|
||||
$list = array();
|
||||
foreach ($this->assignments as $method_id => $method) {
|
||||
if ($method['enabled']) {
|
||||
$list[$method_id] = $method_id;
|
||||
}
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setEnabledAssignments(array $assignments) {
|
||||
// Add any new assignments that we don't yet know about.
|
||||
$new_assignments = array_diff($assignments, array_keys($this->assignments));
|
||||
foreach ($new_assignments as $method_id) {
|
||||
$this->assignments[$method_id] = $this->getAssignmentSettings($method_id);
|
||||
}
|
||||
|
||||
foreach ($this->assignments as $method_id => &$method) {
|
||||
$method['enabled'] = in_array($method_id, $assignments);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAssignmentWeights() {
|
||||
$list = array();
|
||||
foreach ($this->assignments as $method_id => $method) {
|
||||
$list[$method_id] = $method['weight'];
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setAssignmentWeights(array $assignments) {
|
||||
foreach ($this->assignments as $method_id => &$method) {
|
||||
if (isset($assignments[$method_id])) {
|
||||
$method['weight'] = $assignments[$method_id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return array of default settings for the given plugin method
|
||||
*
|
||||
* @param $method_id
|
||||
* @return array
|
||||
*/
|
||||
protected function getDefaultSettings($method_id) {
|
||||
$settings = ['enabled' => FALSE, 'weight' => 0];
|
||||
|
||||
$manager = \Drupal::service('plugin.manager.features_assignment_method');
|
||||
$definition = $manager->getDefinition($method_id);
|
||||
|
||||
if (isset($definition['weight'])) {
|
||||
$settings['weight'] = $definition['weight'];
|
||||
}
|
||||
if (isset($definition['default_settings'])) {
|
||||
$settings += $definition['default_settings'];
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAssignmentSettings($method_id = NULL) {
|
||||
if (isset($method_id)) {
|
||||
if (isset($this->assignments[$method_id])) {
|
||||
return $this->assignments[$method_id];
|
||||
}
|
||||
else {
|
||||
// Use defaults.
|
||||
return $this->getDefaultSettings($method_id);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$list = array();
|
||||
foreach (array_keys($this->assignments) as $method_id) {
|
||||
$list[$method_id] = $this->getAssignmentSettings($method_id);
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setAssignmentSettings($method_id, array $settings) {
|
||||
if (isset($method_id)) {
|
||||
$this->assignments[$method_id] = $settings;
|
||||
}
|
||||
else {
|
||||
foreach ($settings as $method_id => $method_settings) {
|
||||
if (!empty($method_settings)) {
|
||||
$this->setAssignmentSettings($method_id, $method_settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function remove() {
|
||||
$this->delete();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,446 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
use Drupal\Component\Plugin\PluginManagerInterface;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Config\ExtensionInstallStorage;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Config\StorageInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\features\Entity\FeaturesBundle;
|
||||
|
||||
/**
|
||||
* 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 manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityManagerInterface
|
||||
*/
|
||||
protected $entityManager;
|
||||
|
||||
/**
|
||||
* Local cache for package assignment method instances.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $methods;
|
||||
|
||||
/**
|
||||
* Bundles.
|
||||
*
|
||||
* @var array of \Drupal\features\FeaturesBundleInterface
|
||||
*/
|
||||
protected $bundles;
|
||||
|
||||
/**
|
||||
* Currently active bundle.
|
||||
*
|
||||
* @var \Drupal\features\FeaturesBundleInterface
|
||||
*/
|
||||
protected $currentBundle;
|
||||
|
||||
/**
|
||||
* 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\EntityManagerInterface $entity_manager
|
||||
* The entity manager.
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The configuration factory.
|
||||
* @param \Drupal\Core\Config\StorageInterface $config_storage
|
||||
* The configuration factory.
|
||||
*/
|
||||
public function __construct(FeaturesManagerInterface $features_manager, PluginManagerInterface $assigner_manager, EntityManagerInterface $entity_manager, ConfigFactoryInterface $config_factory, StorageInterface $config_storage) {
|
||||
$this->featuresManager = $features_manager;
|
||||
$this->assignerManager = $assigner_manager;
|
||||
$this->entityManager = $entity_manager;
|
||||
$this->configFactory = $config_factory;
|
||||
$this->configStorage = $config_storage;
|
||||
$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 = array();
|
||||
$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
|
||||
*/
|
||||
protected function getAssignmentMethodInstance($method_id) {
|
||||
if (!isset($this->methods[$method_id])) {
|
||||
$instance = $this->assignerManager->createInstance($method_id, array());
|
||||
$instance->setFeaturesManager($this->featuresManager);
|
||||
$instance->setAssigner($this);
|
||||
$instance->setEntityManager($this->entityManager);
|
||||
$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 = array();
|
||||
foreach ($this->entityManager->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(array(' ', '-'), '_', $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);
|
||||
$record = $ext_storage->read('features.bundle.default');
|
||||
$bundle_storage = $this->entityManager->getStorage('features_bundle');
|
||||
$default = $bundle_storage->createFromStorageRecord($record);
|
||||
}
|
||||
|
||||
/** @var \Drupal\features\Entity\FeaturesBundle $bundle */
|
||||
$bundle = $default->createDuplicate();
|
||||
|
||||
$bundle->setMachineName($machine_name);
|
||||
$bundle->setName($name);
|
||||
if (isset($description)) {
|
||||
$bundle->setDescription($description);
|
||||
}
|
||||
else {
|
||||
$bundle->setDescription(t('Auto-generated bundle from package @name', array('@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) {
|
||||
$new_bundle = $this->createBundleFromDefault($new_bundle['machine_name'], $new_bundle['name'], $new_bundle['description'], $new_bundle['is_profile']);
|
||||
drupal_set_message($this->t('Features bundle @name automatically created.', ['@name' => $new_bundle->getName()]));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getBundleOptions() {
|
||||
$list = $this->getBundleList();
|
||||
$result = array();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
/**
|
||||
* Common interface for features assignment services.
|
||||
*
|
||||
* The feature API is based on two major concepts:
|
||||
* - Packages: modules into which configuration is packaged.
|
||||
* - Package assignment methods: responsible for `determining
|
||||
* which package to assign a given piece of configuration to.
|
||||
* Assignment methods are customizable.
|
||||
*
|
||||
* Features defines several package assignment methods, which are simple plugin
|
||||
* classes that implement a particular logic to assign pieces of configuration
|
||||
* to a given package (module).
|
||||
*
|
||||
* Modules can define additional package assignment methods by simply providing
|
||||
* the related plugins, and alter existing methods through
|
||||
* hook_features_assignment_method_info_alter(). Here is an example
|
||||
* snippet:
|
||||
* @code
|
||||
* function mymodule_features_assignment_method_info_alter(&$assignment_info) {
|
||||
* // Replace the original plugin with our own implementation.
|
||||
* $method_id = \Drupal\features\Plugin\FeaturesAssignment\FeaturesAssignmentBaseType::METHOD_ID;
|
||||
* $assignment_info[$method_id]['class'] = 'Drupal\my_module\Plugin\FeaturesAssignment\MyFeaturesAssignmentBaseType';
|
||||
* }
|
||||
*
|
||||
* class MyFeaturesAssignmentBaseType extends FeaturesAssignmentBaseType {
|
||||
* public function assignPackages($force = FALSE) {
|
||||
* // Insert customization here.
|
||||
* }
|
||||
* }
|
||||
* ?>
|
||||
* @endcode
|
||||
*
|
||||
* For more information, see
|
||||
* @link http://drupal.org/node/2404473 Developing for Features 3.x @endlink
|
||||
*/
|
||||
interface FeaturesAssignerInterface {
|
||||
|
||||
/**
|
||||
* The package assignment method id for the package assigner itself.
|
||||
*/
|
||||
const METHOD_ID = 'assigner-default';
|
||||
|
||||
/**
|
||||
* Resets the assigned packages and the method instances.
|
||||
*/
|
||||
public function reset();
|
||||
|
||||
/**
|
||||
* Apply all enabled package assignment methods.
|
||||
*
|
||||
* @param bool $force
|
||||
* (optional) If TRUE, assign config regardless of restrictions such as it
|
||||
* being already assigned to a package.
|
||||
*/
|
||||
public function assignConfigPackages($force = FALSE);
|
||||
|
||||
/**
|
||||
* Applies a given package assignment method.
|
||||
*
|
||||
* @param string $method_id
|
||||
* The string identifier of the package assignment method to use to package
|
||||
* configuration.
|
||||
* @param bool $force
|
||||
* (optional) If TRUE, assign config regardless of restrictions such as it
|
||||
* being already assigned to a package.
|
||||
*/
|
||||
public function applyAssignmentMethod($method_id, $force = FALSE);
|
||||
|
||||
/**
|
||||
* Returns the enabled package assignment methods.
|
||||
*
|
||||
* @return array
|
||||
* An array of package assignment method IDs.
|
||||
*/
|
||||
public function getAssignmentMethods();
|
||||
|
||||
/**
|
||||
* Resaves the configuration to purge missing assignment methods.
|
||||
*/
|
||||
public function purgeConfiguration();
|
||||
|
||||
/**
|
||||
* Returns a FeaturesBundle object.
|
||||
*
|
||||
* @param string $name
|
||||
* machine name of package set. If omitted, returns the current bundle.
|
||||
*
|
||||
* @return \Drupal\features\FeaturesBundleInterface
|
||||
* A features bundle object.
|
||||
*/
|
||||
public function getBundle($name = NULL);
|
||||
|
||||
/**
|
||||
* Stores a features bundle.
|
||||
*
|
||||
* Added to list if machine_name is new.
|
||||
*
|
||||
* @param \Drupal\features\FeaturesBundleInterface $bundle
|
||||
* A features bundle.
|
||||
* @param bool $current
|
||||
* Determine if the current bundle is set to $bundle.
|
||||
* If False, the current bundle is only updated if it already has the same
|
||||
* machine name as the $bundle.
|
||||
*/
|
||||
public function setBundle(FeaturesBundleInterface $bundle, $current = TRUE);
|
||||
|
||||
/**
|
||||
* Searches for a bundle that matches the $info.yml or $features.yml export.
|
||||
*
|
||||
* Creates a new bundle as needed.
|
||||
*
|
||||
* @param array $info
|
||||
* The bundle info.
|
||||
* @param mixed $features_info
|
||||
* The features info.
|
||||
*
|
||||
* @return \Drupal\features\FeaturesBundleInterface
|
||||
* A bundle.
|
||||
*/
|
||||
public function findBundle(array $info, $features_info = NULL);
|
||||
|
||||
/**
|
||||
* Sets the currently active bundle.
|
||||
*
|
||||
* Updates value in current SESSION.
|
||||
*
|
||||
* @param \Drupal\features\FeaturesBundleInterface $bundle
|
||||
* A features bundle object.
|
||||
*/
|
||||
public function setCurrent(FeaturesBundleInterface $bundle);
|
||||
|
||||
/**
|
||||
* Returns an array of all existing features bundles.
|
||||
*
|
||||
* @return \Drupal\features\FeaturesBundleInterface[]
|
||||
* Keyed by machine_name with value of
|
||||
* \Drupal\features\FeaturesBundleInterface.
|
||||
*/
|
||||
public function getBundleList();
|
||||
|
||||
/**
|
||||
* Returns a named bundle.
|
||||
*
|
||||
* First searches by Human name, then by machine_name.
|
||||
*
|
||||
* @param string $name
|
||||
* The bundle name to search by.
|
||||
*
|
||||
* @return \Drupal\features\FeaturesBundleInterface
|
||||
* A features bundle object.
|
||||
*/
|
||||
public function findBundleByName($name);
|
||||
|
||||
/**
|
||||
* Creates a new bundle by duplicating the default bundle and customizing.
|
||||
*
|
||||
* @param string $machine_name
|
||||
* Machine name.
|
||||
* @param string $name
|
||||
* (optional) Human readable name of the bundle.
|
||||
* @param string $description
|
||||
* (optional) Description of the bundle.
|
||||
* @param bool $is_profile
|
||||
* (optional) TRUE if a profile is used with this bundle.
|
||||
* @param string $profile_name
|
||||
* (optional) The machine name of the profile.
|
||||
*
|
||||
* @return \Drupal\features\FeaturesBundleInterface
|
||||
* A features bundle object.
|
||||
*/
|
||||
public function createBundleFromDefault($name, $machine_name = '', $description = '', $is_profile = FALSE, $profile_name = NULL);
|
||||
|
||||
/**
|
||||
* Creates bundles by parsing information from installed packages.
|
||||
*/
|
||||
public function createBundlesFromPackages();
|
||||
|
||||
/**
|
||||
* Returns an array of bundle names suitable for a select option list.
|
||||
*
|
||||
* @return array
|
||||
* An array of bundles, keyed by machine_name, with values being human
|
||||
* readable names.
|
||||
*/
|
||||
public function getBundleOptions();
|
||||
|
||||
/**
|
||||
* Makes the named bundle the current bundle.
|
||||
*
|
||||
* @param string $machine_name
|
||||
* The name of a features bundle. If omitted, gets the last bundle from the
|
||||
* Session.
|
||||
*
|
||||
* @return \Drupal\features\FeaturesBundleInterface
|
||||
* A features bundle object.
|
||||
*/
|
||||
public function applyBundle($machine_name = NULL);
|
||||
|
||||
/**
|
||||
* Renames a bundle.
|
||||
*
|
||||
* @param string $old_machine
|
||||
* The old machine name of a bundle.
|
||||
* @param string $new_machine
|
||||
* The new machine name of a bundle.
|
||||
*
|
||||
* @return \Drupal\features\FeaturesBundleInterface
|
||||
* A features bundle object.
|
||||
*/
|
||||
public function renameBundle($old_machine, $new_machine);
|
||||
|
||||
/**
|
||||
* Loads a named bundle.
|
||||
*
|
||||
* @param string $machine_name
|
||||
* (optional) The name of a features bundle.
|
||||
* Defaults to NULL, gets the last bundle from the session.
|
||||
*
|
||||
* @return \Drupal\features\FeaturesBundleInterface
|
||||
* A features bundle object.
|
||||
*/
|
||||
public function loadBundle($machine_name = NULL);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Plugin\PluginBase;
|
||||
|
||||
/**
|
||||
* Base class for package assignment methods.
|
||||
*/
|
||||
abstract class FeaturesAssignmentMethodBase extends PluginBase implements FeaturesAssignmentMethodInterface {
|
||||
/**
|
||||
* The features manager.
|
||||
*
|
||||
* @var \Drupal\features\FeaturesManagerInterface
|
||||
*/
|
||||
protected $featuresManager;
|
||||
|
||||
/**
|
||||
* The features assigner.
|
||||
*
|
||||
* @var \Drupal\features\FeaturesAssignerInterface
|
||||
*/
|
||||
protected $assigner;
|
||||
|
||||
/**
|
||||
* The entity manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityManagerInterface
|
||||
*/
|
||||
protected $entityManager;
|
||||
|
||||
/**
|
||||
* The configuration factory.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigFactoryInterface
|
||||
*/
|
||||
protected $configFactory;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setfeaturesManager(FeaturesManagerInterface $features_manager) {
|
||||
$this->featuresManager = $features_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setAssigner(FeaturesAssignerInterface $assigner) {
|
||||
$this->assigner = $assigner;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setEntityManager(EntityManagerInterface $entity_manager) {
|
||||
$this->entityManager = $entity_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setConfigFactory(ConfigFactoryInterface $config_factory) {
|
||||
$this->configFactory = $config_factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns configuration of the types specified in a setting to a package.
|
||||
*
|
||||
* @param string $machine_name
|
||||
* Machine name of the package.
|
||||
* @param bool $force
|
||||
* (optional) If TRUE, assign config regardless of restrictions such as it
|
||||
* being already assigned to a package.
|
||||
*/
|
||||
protected function assignPackageByConfigTypes($machine_name, $force = FALSE) {
|
||||
$current_bundle = $this->assigner->getBundle();
|
||||
$settings = $current_bundle->getAssignmentSettings($this->getPluginId());
|
||||
$types = $settings['types']['config'];
|
||||
|
||||
$config_collection = $this->featuresManager->getConfigCollection();
|
||||
|
||||
foreach ($config_collection as $item_name => $item) {
|
||||
// Don't assign configuration that's provided by an extension.
|
||||
if (in_array($item->getType(), $types) && !($item->isProviderExcluded())) {
|
||||
try {
|
||||
$this->featuresManager->assignConfigPackage($machine_name, [$item_name]);
|
||||
}
|
||||
catch (\Exception $exception) {
|
||||
\Drupal::logger('features')->error($exception->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns a given subdirectory to configuration of specified types.
|
||||
*
|
||||
* @param string $subdirectory
|
||||
* The subdirectory that designated configuration should be exported to.
|
||||
*/
|
||||
protected function assignSubdirectoryByConfigTypes($subdirectory) {
|
||||
$current_bundle = $this->assigner->getBundle();
|
||||
$settings = $current_bundle->getAssignmentSettings($this->getPluginId());
|
||||
$types = $settings['types']['config'];
|
||||
|
||||
if (!empty($types)) {
|
||||
$config_collection = $this->featuresManager->getConfigCollection();
|
||||
|
||||
foreach ($config_collection as &$item) {
|
||||
if (in_array($item->getType(), $types)) {
|
||||
$item->setSubdirectory($subdirectory);
|
||||
}
|
||||
}
|
||||
// Clean up the $item pass by reference.
|
||||
unset($item);
|
||||
|
||||
$this->featuresManager->setConfigCollection($config_collection);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
use Drupal\Component\Plugin\PluginInspectionInterface;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
|
||||
/**
|
||||
* Interface for package assignment classes.
|
||||
*/
|
||||
interface FeaturesAssignmentMethodInterface extends PluginInspectionInterface {
|
||||
|
||||
/**
|
||||
* Injects the features manager.
|
||||
*
|
||||
* @param \Drupal\features\FeaturesManagerInterface $features_manager
|
||||
* The features manager to be used to retrieve the configuration list and
|
||||
* the already assigned packages.
|
||||
*/
|
||||
public function setFeaturesManager(FeaturesManagerInterface $features_manager);
|
||||
|
||||
/**
|
||||
* Injects the features assigner.
|
||||
*
|
||||
* @param \Drupal\features\FeaturesAssignerInterface $assigner
|
||||
* The features assigner to be used to retrieve the bundle configuration.
|
||||
*/
|
||||
public function setAssigner(FeaturesAssignerInterface $assigner);
|
||||
|
||||
/**
|
||||
* Injects the entity manager.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
|
||||
* The entity manager to be used to retrieve entity information.
|
||||
*/
|
||||
public function setEntityManager(EntityManagerInterface $entity_manager);
|
||||
|
||||
/**
|
||||
* Injects the configuration factory.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The configuration factory to be used to retrieve configuration values.
|
||||
*/
|
||||
public function setConfigFactory(ConfigFactoryInterface $config_factory);
|
||||
|
||||
/**
|
||||
* Performs package assignment.
|
||||
*
|
||||
* @param bool $force
|
||||
* (optional) If TRUE, assign config regardless of restrictions such as it
|
||||
* being already assigned to a package.
|
||||
*/
|
||||
public function assignPackages($force = FALSE);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Plugin\DefaultPluginManager;
|
||||
|
||||
/**
|
||||
* Manages configuration packaging methods.
|
||||
*/
|
||||
class FeaturesAssignmentMethodManager extends DefaultPluginManager {
|
||||
|
||||
/**
|
||||
* Constructs a new FeaturesAssignmentMethodManager object.
|
||||
*
|
||||
* @param \Traversable $namespaces
|
||||
* An object that implements \Traversable which contains the root paths
|
||||
* keyed by the corresponding namespace to look for plugin implementations.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
|
||||
* An object that implements CacheBackendInterface.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* An object that implements ModuleHandlerInterface.
|
||||
*/
|
||||
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
|
||||
parent::__construct('Plugin/FeaturesAssignment', $namespaces, $module_handler,
|
||||
'Drupal\features\FeaturesAssignmentMethodInterface');
|
||||
$this->alterInfo('features_assignment_info');
|
||||
$this->setCacheBackend($cache_backend, 'features_assignment_methods');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
/**
|
||||
* Provides an interface for the FeaturesBundle object.
|
||||
*/
|
||||
interface FeaturesBundleInterface {
|
||||
|
||||
const DEFAULT_BUNDLE = 'default';
|
||||
|
||||
/**
|
||||
* Determines whether the current bundle is the default one.
|
||||
*
|
||||
* @return bool
|
||||
* Returns TRUE if this is the default bundle.
|
||||
*/
|
||||
public function isDefault();
|
||||
|
||||
/**
|
||||
* Returns the machine name of a bundle.
|
||||
*
|
||||
* @return string
|
||||
* The machine name of a bundle.
|
||||
*
|
||||
* @see \Drupal\features\FeaturesBundleInterface::setMachineName()
|
||||
*/
|
||||
public function getMachineName();
|
||||
|
||||
/**
|
||||
* Sets the machine name of a bundle.
|
||||
*
|
||||
* @param string $machine_name
|
||||
* The machine name of a bundle.
|
||||
*
|
||||
* @see \Drupal\features\FeaturesBundleInterface::getMachineName()
|
||||
*/
|
||||
public function setMachineName($machine_name);
|
||||
|
||||
/**
|
||||
* Gets the human readable name of a bundle.
|
||||
*
|
||||
* @return string
|
||||
* The human readable name of a bundle.
|
||||
*
|
||||
* @see \Drupal\features\FeaturesBundleInterface::setName()
|
||||
*/
|
||||
public function getName();
|
||||
|
||||
/**
|
||||
* Sets the human readable name of a bundle.
|
||||
*
|
||||
* @param string $name
|
||||
* The human readable name of a bundle.
|
||||
*
|
||||
* @see \Drupal\features\FeaturesBundleInterface::getName()
|
||||
*/
|
||||
public function setName($name);
|
||||
|
||||
/**
|
||||
* Returns a full machine name prefixed with the bundle name.
|
||||
*
|
||||
* @param string $short_name
|
||||
* The short machine_name of a bundle.
|
||||
*
|
||||
* @return string
|
||||
* The full machine_name of a bundle.
|
||||
*/
|
||||
public function getFullName($short_name);
|
||||
|
||||
/**
|
||||
* Returns a short machine name not prefixed with the bundle name.
|
||||
*
|
||||
* @param string $machine_name
|
||||
* The full machine_name of a bundle.
|
||||
*
|
||||
* @return string
|
||||
* The short machine_name of a bundle.
|
||||
*/
|
||||
public function getShortName($machine_name);
|
||||
|
||||
/**
|
||||
* Determines if the $machine_name is prefixed by the bundle machine name.
|
||||
*
|
||||
* @param string $machine_name
|
||||
* The machine name of a package.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the machine name is prefixed by the bundle machine name.
|
||||
*/
|
||||
public function inBundle($machine_name);
|
||||
|
||||
/**
|
||||
* Determines if the package with $machine_name is the bundle profile.
|
||||
*
|
||||
* @param string $machine_name
|
||||
* The machine name of a package.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the package with $machine_name is the bundle profile.
|
||||
*/
|
||||
public function isProfilePackage($machine_name);
|
||||
|
||||
/**
|
||||
* Gets the description of a bundle.
|
||||
*
|
||||
* @return string
|
||||
* The description of a bundle.
|
||||
*
|
||||
* @see \Drupal\features\FeaturesBundleInterface::setDescription()
|
||||
*/
|
||||
public function getDescription();
|
||||
|
||||
/**
|
||||
* Sets the description of a bundle.
|
||||
*
|
||||
* @param string $description
|
||||
* The description of a bundle.
|
||||
*
|
||||
* @see \Drupal\features\FeaturesBundleInterface::getDescription()
|
||||
*/
|
||||
public function setDescription($description);
|
||||
|
||||
/**
|
||||
* Gets option for using a profile with this bundle.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if a profile is used with this profile.
|
||||
*/
|
||||
public function isProfile();
|
||||
|
||||
/**
|
||||
* Sets option for using a profile with this bundle.
|
||||
*
|
||||
* @param bool $value
|
||||
* TRUE if a profile is used with this bundle.
|
||||
*/
|
||||
public function setIsProfile($value);
|
||||
|
||||
/**
|
||||
* Returns the machine name of the profile.
|
||||
*
|
||||
* If the bundle doesn't use a profile, return the current site profile.
|
||||
*
|
||||
* @return string
|
||||
* THe machie name of a profile.
|
||||
*
|
||||
* @see \Drupal\features\FeaturesBundleInterface::setProfileName()
|
||||
*/
|
||||
public function getProfileName();
|
||||
|
||||
/**
|
||||
* Sets the name of the profile associated with this bundle.
|
||||
*
|
||||
* @param string $machine_name
|
||||
* The machine name of a profile.
|
||||
*
|
||||
* @see \Drupal\features\FeaturesBundleInterface::getProfileName()
|
||||
*/
|
||||
public function setProfileName($machine_name);
|
||||
|
||||
/**
|
||||
* Gets the list of enabled assignment methods.
|
||||
*
|
||||
* @return array
|
||||
* An array of method IDs keyed by assignment method IDs.
|
||||
*
|
||||
* @see \Drupal\features\FeaturesBundleInterface::setEnabledAssignments()
|
||||
*/
|
||||
public function getEnabledAssignments();
|
||||
|
||||
/**
|
||||
* Sets the list of enabled assignment methods.
|
||||
*
|
||||
* @param array $assignments
|
||||
* An array of values keyed by assignment method IDs. Non-empty value is
|
||||
* enabled.
|
||||
*
|
||||
* @see \Drupal\features\FeaturesBundleInterface::getEnabledAssignments()
|
||||
*/
|
||||
public function setEnabledAssignments(array $assignments);
|
||||
|
||||
/**
|
||||
* Gets the weights of the assignment methods.
|
||||
*
|
||||
* @return array
|
||||
* An array keyed by assignment method_id with a numeric weight.
|
||||
*
|
||||
* @see \Drupal\features\FeaturesBundleInterface::setAssignmentWeights()
|
||||
*/
|
||||
public function getAssignmentWeights();
|
||||
|
||||
/**
|
||||
* Sets the weights of the assignment methods.
|
||||
*
|
||||
* @param array $assignments
|
||||
* An array keyed by assignment method_id with a numeric weight value.
|
||||
*
|
||||
* @see \Drupal\features\FeaturesBundleInterface::getAssignmentWeights()
|
||||
*/
|
||||
public function setAssignmentWeights(array $assignments);
|
||||
|
||||
/**
|
||||
* Gets settings specific to an assignment method.
|
||||
*
|
||||
* @param string $method_id
|
||||
* The ID of an assignment method. If NULL, return all assignment settings
|
||||
* keyed by method_id.
|
||||
*
|
||||
* @return array
|
||||
* An array of settings. Format specific to assignment method.
|
||||
*
|
||||
* @see \Drupal\features\FeaturesBundleInterface::setAssignmentSettings()
|
||||
*/
|
||||
public function getAssignmentSettings($method_id = NULL);
|
||||
|
||||
/**
|
||||
* Sets settings specific to an assignment method.
|
||||
*
|
||||
* @param string $method_id
|
||||
* The ID of an assignment method. If NULL, all $settings are given keyed
|
||||
* by method_ID.
|
||||
* @param array $settings
|
||||
* An array of setting values.
|
||||
*
|
||||
* @see \Drupal\features\FeaturesBundleInterface::getAssignmentSettings()
|
||||
*/
|
||||
public function setAssignmentSettings($method_id, array $settings);
|
||||
|
||||
/**
|
||||
* Saves the bundle to the active config.
|
||||
*/
|
||||
public function save();
|
||||
|
||||
/**
|
||||
* Removes the bundle from the active config.
|
||||
*/
|
||||
public function remove();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigDependencyManager;
|
||||
use Drupal\Core\Config\Entity\ConfigEntityDependency;
|
||||
|
||||
/**
|
||||
* Class FeaturesConfigDependencyManager
|
||||
* @package Drupal\features
|
||||
*/
|
||||
class FeaturesConfigDependencyManager extends ConfigDependencyManager{
|
||||
|
||||
protected $sorted_graph;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDependentEntities($type, $name) {
|
||||
$dependent_entities = array();
|
||||
|
||||
$entities_to_check = array();
|
||||
if ($type == 'config') {
|
||||
$entities_to_check[] = $name;
|
||||
}
|
||||
else {
|
||||
if ($type == 'module' || $type == 'theme' || $type == 'content') {
|
||||
$dependent_entities = array_filter($this->data, function (ConfigEntityDependency $entity) use ($type, $name) {
|
||||
return $entity->hasDependency($type, $name);
|
||||
});
|
||||
}
|
||||
// If checking content, module, or theme dependencies, discover which
|
||||
// entities are dependent on the entities that have a direct dependency.
|
||||
foreach ($dependent_entities as $entity) {
|
||||
$entities_to_check[] = $entity->getConfigDependencyName();
|
||||
}
|
||||
}
|
||||
$dependencies = array_merge($this->createGraphConfigEntityDependencies($entities_to_check), $dependent_entities);
|
||||
if (!$this->sorted_graph) {
|
||||
// Sort dependencies in the reverse order of the graph. So the least
|
||||
// dependent is at the top. For example, this ensures that fields are
|
||||
// always after field storages. This is because field storages need to be
|
||||
// created before a field.
|
||||
$this->sorted_graph = $this->getGraph();
|
||||
uasort($this->sorted_graph, array($this, 'sortGraph'));
|
||||
}
|
||||
return array_replace(array_intersect_key($this->sorted_graph, $dependencies), $dependencies);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setData(array $data) {
|
||||
parent::setData($data);
|
||||
$this->sorted_graph = NULL;
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
use Drupal\Core\Config\ConfigInstaller;
|
||||
use Drupal\Core\Config\StorageInterface;
|
||||
|
||||
/**
|
||||
* Class for customizing the test for pre existing configuration.
|
||||
*
|
||||
* Copy of ConfigInstaller with findPreExistingConfiguration() modified to
|
||||
* allow Feature modules to be installed.
|
||||
*/
|
||||
class FeaturesConfigInstaller extends ConfigInstaller {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function findPreExistingConfiguration(StorageInterface $storage) {
|
||||
// CHANGE START
|
||||
// Override
|
||||
// Drupal\Core\Config\ConfigInstaller::findPreExistingConfiguration().
|
||||
// Allow config that already exists coming from Features.
|
||||
/** @var \Drupal\features\FeaturesManagerInterface $manager */
|
||||
$manager = \Drupal::service('features.manager');
|
||||
$features_config = array_keys($manager->listExistingConfig());
|
||||
// Map array so we can use isset instead of in_array for faster access.
|
||||
$features_config = array_combine($features_config, $features_config);
|
||||
// CHANGE END.
|
||||
$existing_configuration = array();
|
||||
// Gather information about all the supported collections.
|
||||
$collection_info = $this->configManager->getConfigCollectionInfo();
|
||||
|
||||
foreach ($collection_info->getCollectionNames() as $collection) {
|
||||
$config_to_create = array_keys($this->getConfigToCreate($storage, $collection));
|
||||
$active_storage = $this->getActiveStorages($collection);
|
||||
foreach ($config_to_create as $config_name) {
|
||||
if ($active_storage->exists($config_name)) {
|
||||
// CHANGE START
|
||||
// Test if config is part of a Feature package.
|
||||
if (!isset($features_config[$config_name])) {
|
||||
// CHANGE END.
|
||||
$existing_configuration[$collection][] = $config_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $existing_configuration;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
use Drupal\Core\Config\InstallStorage;
|
||||
use Drupal\Core\Config\StorageInterface;
|
||||
use Drupal\Core\Extension\Extension;
|
||||
|
||||
/**
|
||||
* Wraps FeaturesInstallStorage to support multiple configuration
|
||||
* directories.
|
||||
*/
|
||||
class FeaturesExtensionStorages implements FeaturesExtensionStoragesInterface {
|
||||
|
||||
/**
|
||||
* The target storage.
|
||||
*
|
||||
* @var \Drupal\Core\Config\StorageInterface
|
||||
*/
|
||||
protected $configStorage;
|
||||
|
||||
/**
|
||||
* The extension storages.
|
||||
*
|
||||
* @var \Drupal\Core\Config\StorageInterface[]
|
||||
*/
|
||||
protected $extensionStorages;
|
||||
|
||||
/**
|
||||
* Configuration provided by extension storages.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $configurationLists;
|
||||
|
||||
/**
|
||||
* Constructs a new FeaturesExtensionStorages object.
|
||||
*
|
||||
* @param \Drupal\Core\Config\StorageInterface $config_storage
|
||||
* The configuration storage.
|
||||
*/
|
||||
public function __construct(StorageInterface $config_storage) {
|
||||
$this->configStorage = $config_storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getExtensionStorages() {
|
||||
return $this->extensionStorages;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addStorage($directory = InstallStorage::CONFIG_INSTALL_DIRECTORY) {
|
||||
$this->extensionStorages[$directory] = new FeaturesInstallStorage($this->configStorage, $directory);
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function read($name) {
|
||||
$list = $this->listAllByDirectory('');
|
||||
if (isset($list[$name])) {
|
||||
$directory = $list[$name];
|
||||
return $this->extensionStorages[$directory]->read($name);
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function listAll($prefix = '') {
|
||||
return array_keys($this->listAllByDirectory($prefix));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function listExtensionConfig(Extension $extension) {
|
||||
$extension_config = [];
|
||||
foreach ($this->extensionStorages as $directory => $extension_storage) {
|
||||
$extension_config = array_merge($extension_config, array_keys($extension_storage->getComponentNames([
|
||||
$extension->getName() => $extension,
|
||||
])));
|
||||
}
|
||||
return $extension_config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets packages and configuration assignment.
|
||||
*/
|
||||
protected function reset() {
|
||||
$this->configurationLists = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all configuration available from extensions.
|
||||
*
|
||||
* @param string $prefix
|
||||
* (optional) The prefix to search for. If omitted, all configuration object
|
||||
* names that exist are returned.
|
||||
*
|
||||
* @return array
|
||||
* An array with configuration item names as keys and configuration
|
||||
* directories as values.
|
||||
*/
|
||||
protected function listAllByDirectory($prefix = '') {
|
||||
if (!isset($this->configurationLists[$prefix])) {
|
||||
$this->configurationLists[$prefix] = [];
|
||||
foreach ($this->extensionStorages as $directory => $extension_storage) {
|
||||
$this->configurationLists[$prefix] += array_fill_keys($extension_storage->listAll($prefix), $directory);
|
||||
}
|
||||
}
|
||||
return $this->configurationLists[$prefix];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
use Drupal\Core\Config\InstallStorage;
|
||||
use Drupal\Core\Extension\Extension;
|
||||
|
||||
/**
|
||||
* The FeaturesExtensionStorages provides a collection of extension storages,
|
||||
* one for each supported configuration directory.
|
||||
*
|
||||
* Typically this will include the install and optional directories defined by
|
||||
* Drupal core, but may also include any extension configuration directories
|
||||
* added by contributed modules.
|
||||
*
|
||||
* This class serves as a partial wrapper to
|
||||
* Drupal\Core\Config\StorageInterface, providing a subset of methods that can
|
||||
* be called to apply to all available extension storages. For example,
|
||||
* FeaturesExtensionStoragesInterface::read() will read an extension-provided
|
||||
* configuration item regardless of which extension storage directory it is
|
||||
* provided in.
|
||||
*/
|
||||
interface FeaturesExtensionStoragesInterface {
|
||||
|
||||
/**
|
||||
* Returns all registered extension storages.
|
||||
*
|
||||
* @return FeaturesInstallStorage[]
|
||||
* Array of install storages keyed by configuration directory.
|
||||
*/
|
||||
public function getExtensionStorages();
|
||||
|
||||
/**
|
||||
* Adds a storage.
|
||||
*
|
||||
* @param string $directory
|
||||
* (optional) The configuration directory. If omitted,
|
||||
* InstallStorage::CONFIG_INSTALL_DIRECTORY will be used.
|
||||
*/
|
||||
public function addStorage($directory = InstallStorage::CONFIG_INSTALL_DIRECTORY);
|
||||
|
||||
/**
|
||||
* Reads configuration data from the storages.
|
||||
*
|
||||
* @param string $name
|
||||
* The name of a configuration object to load.
|
||||
*
|
||||
* @return array|bool
|
||||
* The configuration data stored for the configuration object name. If no
|
||||
* configuration data exists for the given name, FALSE is returned.
|
||||
*/
|
||||
public function read($name);
|
||||
|
||||
/**
|
||||
* Gets configuration object names starting with a given prefix.
|
||||
*
|
||||
* Given the following configuration objects:
|
||||
* - node.type.article
|
||||
* - node.type.page
|
||||
*
|
||||
* Passing the prefix 'node.type.' will return an array containing the above
|
||||
* names.
|
||||
*
|
||||
* @param string $prefix
|
||||
* (optional) The prefix to search for. If omitted, all configuration object
|
||||
* names that exist are returned.
|
||||
*
|
||||
* @return array
|
||||
* An array containing matching configuration object names.
|
||||
*/
|
||||
public function listAll($prefix = '');
|
||||
|
||||
/**
|
||||
* Lists names of configuration objects provided by a given extension.
|
||||
*
|
||||
* If a $name and/or $namespace is specified, only matching modules will be
|
||||
* returned. Otherwise, all install are returned.
|
||||
*
|
||||
* @param mixed $extension
|
||||
* A string name of an extension or a full Extension object.
|
||||
*
|
||||
* @return array
|
||||
* An array of configuration object names.
|
||||
*/
|
||||
public function listExtensionConfig(Extension $extension);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
use Drupal\Component\Serialization\Yaml;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Base class for package assignment methods.
|
||||
*/
|
||||
abstract class FeaturesGenerationMethodBase implements FeaturesGenerationMethodInterface {
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* The features manager.
|
||||
*
|
||||
* @var \Drupal\features\FeaturesManagerInterface
|
||||
*/
|
||||
protected $featuresManager;
|
||||
|
||||
/**
|
||||
* The features assigner.
|
||||
*
|
||||
* @var \Drupal\features\FeaturesAssignerInterface
|
||||
*/
|
||||
protected $assigner;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setFeaturesManager(FeaturesManagerInterface $features_manager) {
|
||||
$this->featuresManager = $features_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setAssigner(FeaturesAssignerInterface $assigner) {
|
||||
$this->assigner = $assigner;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function exportFormSubmit(array &$form, FormStateInterface $form_state) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges an info file into a package's info file.
|
||||
*
|
||||
* @param string $package_info
|
||||
* The Yaml encoded package info.
|
||||
* @param string $info_file_uri
|
||||
* The info file's URI.
|
||||
*/
|
||||
protected function mergeInfoFile($package_info, $info_file_uri) {
|
||||
$package_info = Yaml::decode($package_info);
|
||||
/** @var \Drupal\Core\Extension\InfoParserInterface $existing_info */
|
||||
$existing_info = \Drupal::service('info_parser')->parse($info_file_uri);
|
||||
return Yaml::encode($this->featuresManager->mergeInfoArray($existing_info, $package_info));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prepare(array &$packages = array(), FeaturesBundleInterface $bundle = NULL) {
|
||||
// If no packages were specified, get all packages.
|
||||
if (empty($packages)) {
|
||||
$packages = $this->featuresManager->getPackages();
|
||||
}
|
||||
|
||||
// If any packages exist, read in their files.
|
||||
$existing_packages = $this->featuresManager->listPackageDirectories(array_keys($packages), $bundle);
|
||||
|
||||
foreach ($packages as &$package) {
|
||||
list($full_name, $path) = $this->featuresManager->getExportInfo($package, $bundle);
|
||||
if (empty($package->getDirectory())) {
|
||||
$package->setDirectory($path);
|
||||
}
|
||||
|
||||
// If this is the profile, its directory is already assigned.
|
||||
if (!isset($bundle) || !$bundle->isProfilePackage($package->getMachineName())) {
|
||||
$package->setDirectory($package->getDirectory() . '/' . $full_name);
|
||||
}
|
||||
|
||||
$this->preparePackage($package, $existing_packages, $bundle);
|
||||
}
|
||||
// Clean up the $package pass by reference.
|
||||
unset($package);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs any required changes on a package prior to generation.
|
||||
*
|
||||
* @param \Drupal\features\Package $package
|
||||
* The package to be prepared.
|
||||
* @param array $existing_packages
|
||||
* An array of existing packages with machine names as keys and paths as
|
||||
* values.
|
||||
* @param \Drupal\features\FeaturesBundleInterface $bundle
|
||||
* Optional bundle used for export
|
||||
*/
|
||||
abstract protected function preparePackage(Package $package, array $existing_packages, FeaturesBundleInterface $bundle = NULL);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Interface for package assignment classes.
|
||||
*/
|
||||
interface FeaturesGenerationMethodInterface {
|
||||
|
||||
/**
|
||||
* Injects the features manager.
|
||||
*
|
||||
* @param \Drupal\features\FeaturesManagerInterface $features_manager
|
||||
* The features manager to be used to retrieve the configuration
|
||||
* list and the assigned packages.
|
||||
*/
|
||||
public function setFeaturesManager(FeaturesManagerInterface $features_manager);
|
||||
|
||||
/**
|
||||
* Injects the features assigner.
|
||||
*
|
||||
* @param \Drupal\features\FeaturesAssignerInterface $assigner
|
||||
* The features assigner to be used to retrieve the bundle configuration.
|
||||
*/
|
||||
public function setAssigner(FeaturesAssignerInterface $assigner);
|
||||
|
||||
/**
|
||||
* Prepares packages for generation.
|
||||
*
|
||||
* @param array $packages
|
||||
* Array of package data.
|
||||
* @param \Drupal\features\FeaturesBundleInterface $bundle
|
||||
* The optional bundle used for the generation. Used to generate profiles.
|
||||
*
|
||||
* @return array
|
||||
* An array of packages data.
|
||||
*/
|
||||
public function prepare(array &$packages = array(), FeaturesBundleInterface $bundle = NULL);
|
||||
|
||||
/**
|
||||
* Performs package generation.
|
||||
*
|
||||
* @param array $packages
|
||||
* Array of package data.
|
||||
* @param \Drupal\features\FeaturesBundleInterface $bundle
|
||||
* The optional bundle used for the generation. Used to generate profiles.
|
||||
*
|
||||
* @return array
|
||||
* Array of results for profile and/or packages, each result including the
|
||||
* following keys:
|
||||
* - 'success': boolean TRUE or FALSE for successful writing.
|
||||
* - 'display': boolean TRUE if the message should be displayed to the
|
||||
* user, otherwise FALSE.
|
||||
* - 'message': a message about the result of the operation.
|
||||
* - 'variables': an array of substitutions to be used in the message.
|
||||
*/
|
||||
public function generate(array $packages = array(), FeaturesBundleInterface $bundle = NULL);
|
||||
|
||||
/**
|
||||
* Responds to the submission of
|
||||
* \Drupal\features_ui\Form\FeaturesExportForm.
|
||||
*/
|
||||
public function exportFormSubmit(array &$form, FormStateInterface $form_state);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Plugin\DefaultPluginManager;
|
||||
|
||||
/**
|
||||
* Manages configuration packaging methods.
|
||||
*/
|
||||
class FeaturesGenerationMethodManager extends DefaultPluginManager {
|
||||
|
||||
/**
|
||||
* Constructs a new FeaturesGenerationMethodManager object.
|
||||
*
|
||||
* @param \Traversable $namespaces
|
||||
* An object that implements \Traversable which contains the root paths
|
||||
* keyed by the corresponding namespace to look for plugin implementations.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
|
||||
* An object that implements CacheBackendInterface.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* An object that implements ModuleHandlerInterface.
|
||||
*/
|
||||
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
|
||||
parent::__construct('Plugin/FeaturesGeneration', $namespaces, $module_handler, 'Drupal\features\FeaturesGenerationMethodInterface');
|
||||
$this->cacheBackend = $cache_backend;
|
||||
$this->cacheKeyPrefix = 'features_generation_methods';
|
||||
$this->cacheKey = 'features_generation_methods';
|
||||
$this->alterInfo('features_generation_info');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
use Drupal\Component\Plugin\PluginManagerInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Class responsible for performing package generation.
|
||||
*/
|
||||
class FeaturesGenerator implements FeaturesGeneratorInterface {
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* The package generation method plugin manager.
|
||||
*
|
||||
* @var \Drupal\Component\Plugin\PluginManagerInterface
|
||||
*/
|
||||
protected $generatorManager;
|
||||
|
||||
/**
|
||||
* The features manager.
|
||||
*
|
||||
* @var \Drupal\features\FeaturesManagerInterface
|
||||
*/
|
||||
protected $featuresManager;
|
||||
|
||||
/**
|
||||
* The features assigner.
|
||||
*
|
||||
* @var \Drupal\features\FeaturesAssignerInterface
|
||||
*/
|
||||
protected $assigner;
|
||||
|
||||
/**
|
||||
* Local cache for package generation method instances.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $methods;
|
||||
|
||||
/**
|
||||
* Constructs a new FeaturesGenerator object.
|
||||
*
|
||||
* @param \Drupal\features\FeaturesManagerInterface $features_manager
|
||||
* The features manager.
|
||||
* @param \Drupal\Component\Plugin\PluginManagerInterface $generator_manager
|
||||
* The package generation methods plugin manager.
|
||||
*/
|
||||
public function __construct(FeaturesManagerInterface $features_manager, PluginManagerInterface $generator_manager, FeaturesAssignerInterface $assigner) {
|
||||
$this->featuresManager = $features_manager;
|
||||
$this->generatorManager = $generator_manager;
|
||||
$this->assigner = $assigner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the injected features manager with the generator.
|
||||
*
|
||||
* This should be called right after instantiating the generator to make it
|
||||
* available to the features manager without introducing a circular
|
||||
* dependency.
|
||||
*/
|
||||
public function initFeaturesManager() {
|
||||
$this->featuresManager->setGenerator($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function reset() {
|
||||
$this->methods = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applyGenerationMethod($method_id, array $packages = array(), FeaturesBundleInterface $bundle = NULL) {
|
||||
$method = $this->getGenerationMethodInstance($method_id);
|
||||
$method->prepare($packages, $bundle);
|
||||
return $method->generate($packages, $bundle);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applyExportFormSubmit($method_id, &$form, FormStateInterface $form_state) {
|
||||
$method = $this->getGenerationMethodInstance($method_id);
|
||||
$method->exportFormSubmit($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getGenerationMethods() {
|
||||
return $this->generatorManager->getDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of the specified package generation method.
|
||||
*
|
||||
* @param string $method_id
|
||||
* The string identifier of the package generation method to use to package
|
||||
* configuration.
|
||||
*
|
||||
* @return \Drupal\features\FeaturesGenerationMethodInterface
|
||||
*/
|
||||
protected function getGenerationMethodInstance($method_id) {
|
||||
if (!isset($this->methods[$method_id])) {
|
||||
$instance = $this->generatorManager->createInstance($method_id, array());
|
||||
$instance->setFeaturesManager($this->featuresManager);
|
||||
$instance->setAssigner($this->assigner);
|
||||
$this->methods[$method_id] = $instance;
|
||||
}
|
||||
return $this->methods[$method_id];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function generatePackages($method_id, FeaturesBundleInterface $bundle, array $package_names = array()) {
|
||||
$this->featuresManager->setPackageBundleNames($bundle, $package_names);
|
||||
return $this->generate($method_id, $bundle, $package_names);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a file representation of configuration packages and, optionally,
|
||||
* an install profile.
|
||||
*
|
||||
* @param string $method_id
|
||||
* The ID of the generation method to use.
|
||||
* @param \Drupal\features\FeaturesBundleInterface $bundle
|
||||
* The bundle used for the generation.
|
||||
* @param string[] $package_names
|
||||
* Names of packages to be generated. If none are specified, all
|
||||
* available packages will be added.
|
||||
*
|
||||
* @return array
|
||||
* Array of results for profile and/or packages, each result including the
|
||||
* following keys:
|
||||
* - 'success': boolean TRUE or FALSE for successful writing.
|
||||
* - 'display': boolean TRUE if the message should be displayed to the
|
||||
* user, otherwise FALSE.
|
||||
* - 'message': a message about the result of the operation.
|
||||
* - 'variables': an array of substitutions to be used in the message.
|
||||
*/
|
||||
protected function generate($method_id, FeaturesBundleInterface $bundle, array $package_names = array()) {
|
||||
$packages = $this->featuresManager->getPackages();
|
||||
|
||||
// Filter out the packages that weren't requested.
|
||||
if (!empty($package_names)) {
|
||||
$packages = array_intersect_key($packages, array_fill_keys($package_names, NULL));
|
||||
}
|
||||
|
||||
$this->featuresManager->assignInterPackageDependencies($bundle, $packages);
|
||||
|
||||
// Prepare the files.
|
||||
$this->featuresManager->prepareFiles($packages);
|
||||
|
||||
$return = $this->applyGenerationMethod($method_id, $packages, $bundle);
|
||||
|
||||
foreach ($return as $message) {
|
||||
if ($message['display']) {
|
||||
$type = $message['success'] ? 'status' : 'error';
|
||||
drupal_set_message($this->t($message['message'], $message['variables']), $type);
|
||||
}
|
||||
$type = $message['success'] ? 'notice' : 'error';
|
||||
\Drupal::logger('features')->{$type}($message['message'], $message['variables']);
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Common interface for features generation services.
|
||||
*
|
||||
* The configuration packaging API is based on two major concepts:
|
||||
* - Packages: modules into which configuration is packaged.
|
||||
* - Package generation methods: responsible for `determining
|
||||
* which package to assign a given piece of configuration to.
|
||||
* Generation methods are customizable.
|
||||
*
|
||||
* Features defines two package generation methods, which are simple plugin
|
||||
* classes that implement a particular logic to assign pieces of configuration
|
||||
* to a given package (module).
|
||||
*
|
||||
* Modules can define additional package generation methods by simply providing
|
||||
* the related plugins, and alter existing methods through
|
||||
* hook_features_generation_method_info_alter(). Here is an example
|
||||
* snippet:
|
||||
* @code
|
||||
* function mymodule_features_generation_method_info_alter(&$generation_info) {
|
||||
* // Replace the original plugin with our own implementation.
|
||||
* $method_id = \Drupal\features\Plugin\FeaturesGeneration\FeaturesGenerationArchive::METHOD_ID;
|
||||
* $generation_info[$method_id]['class'] = 'Drupal\my_module\Plugin\FeaturesGeneration\MyFeaturesGenerationArchive';
|
||||
* }
|
||||
*
|
||||
* class MyFeaturesGenerationArchive extends FeaturesGenerationArchive {
|
||||
* public function generate(array $packages = array(), FeaturesBundleInterface $bundle = NULL) {
|
||||
* // Insert customization here.
|
||||
* }
|
||||
* }
|
||||
* ?>
|
||||
* @endcode
|
||||
*
|
||||
* For more information, see
|
||||
* @link http://drupal.org/node/2404473 Developing for Features 3.x @endlink
|
||||
*/
|
||||
interface FeaturesGeneratorInterface {
|
||||
|
||||
/**
|
||||
* The package generation method id for the package generator itself.
|
||||
*/
|
||||
const METHOD_ID = 'generator-default';
|
||||
|
||||
/**
|
||||
* Resets the assigned packages and the method instances.
|
||||
*/
|
||||
public function reset();
|
||||
|
||||
/**
|
||||
* Apply a given package generation method.
|
||||
*
|
||||
* @param string $method_id
|
||||
* The string identifier of the package generation method to use to package
|
||||
* configuration.
|
||||
* @param array $packages
|
||||
* Array of package data.
|
||||
* @param \Drupal\features\FeaturesBundleInterface $bundle
|
||||
* The optional bundle used for the generation. Used to generate profiles.
|
||||
*
|
||||
* @return array
|
||||
* Array of results for profile and/or packages, each result including the
|
||||
* following keys:
|
||||
* - 'success': boolean TRUE or FALSE for successful writing.
|
||||
* - 'display': boolean TRUE if the message should be displayed to the
|
||||
* user, otherwise FALSE.
|
||||
* - 'message': a message about the result of the operation.
|
||||
* - 'variables': an array of substitutions to be used in the message.
|
||||
*/
|
||||
public function applyGenerationMethod($method_id, array $packages = array(), FeaturesBundleInterface $bundle = NULL);
|
||||
|
||||
/**
|
||||
* Responds to the submission of
|
||||
* \Drupal\features_ui\Form\FeaturesExportForm.
|
||||
*/
|
||||
public function applyExportFormSubmit($method_id, &$form, FormStateInterface $form_state);
|
||||
|
||||
/**
|
||||
* Returns the enabled package generation methods.
|
||||
*
|
||||
* @return array
|
||||
* An array of package generation method definitions keyed by method id.
|
||||
*/
|
||||
public function getGenerationMethods();
|
||||
|
||||
/**
|
||||
* Generates file representations of configuration packages.
|
||||
*
|
||||
* @param string $method_id
|
||||
* The ID of the generation method to use.
|
||||
* @param \Drupal\features\FeaturesBundleInterface $bundle
|
||||
* The bundle used for the generation.
|
||||
* @param array $package_names
|
||||
* Array of names of packages to be generated. If none are specified, all
|
||||
* available packages will be added.
|
||||
*/
|
||||
public function generatePackages($method_id, FeaturesBundleInterface $bundle, array $package_names = array());
|
||||
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
use Drupal\Core\Site\Settings;
|
||||
use Drupal\Core\Config\ExtensionInstallStorage;
|
||||
use Drupal\Core\Config\StorageInterface;
|
||||
use Drupal\Core\Extension\ExtensionDiscovery;
|
||||
|
||||
/**
|
||||
* Storage to access configuration and schema in installed extensions.
|
||||
*
|
||||
* Overrides the normal ExtensionInstallStorage to prevent profile from
|
||||
* overriding.
|
||||
*
|
||||
* Also supports modules that are not installed yet.
|
||||
*
|
||||
* @see \Drupal\Core\Config\ExtensionInstallStorage
|
||||
*/
|
||||
class FeaturesInstallStorage extends ExtensionInstallStorage {
|
||||
|
||||
/**
|
||||
* Overrides \Drupal\Core\Config\ExtensionInstallStorage::__construct().
|
||||
*
|
||||
* Sets includeProfile to FALSE.
|
||||
*
|
||||
* @param \Drupal\Core\Config\StorageInterface $config_storage
|
||||
* The active configuration store where the list of installed modules and
|
||||
* themes is stored.
|
||||
* @param string $directory
|
||||
* The directory to scan in each extension to scan for files. Defaults to
|
||||
* 'config/install'.
|
||||
* @param string $collection
|
||||
* (optional) The collection to store configuration in. Defaults to the
|
||||
* default collection.
|
||||
*/
|
||||
public function __construct(StorageInterface $config_storage, $directory = self::CONFIG_INSTALL_DIRECTORY, $collection = StorageInterface::DEFAULT_COLLECTION) {
|
||||
parent::__construct($config_storage, $directory, $collection, FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of all config object names and their folders.
|
||||
*
|
||||
* The list is based on installed modules and themes. The active
|
||||
* configuration storage is used rather than
|
||||
* \Drupal\Core\Extension\ModuleHandler and
|
||||
* \Drupal\Core\Extension\ThemeHandler in order to resolve circular
|
||||
* dependencies between these services and
|
||||
* \Drupal\Core\Config\ConfigInstaller and
|
||||
* \Drupal\Core\Config\TypedConfigManager.
|
||||
*
|
||||
* NOTE: This code is copied from ExtensionInstallStorage::getAllFolders() with
|
||||
* the following changes (Notes in CHANGED below)
|
||||
* - Load all modules whether installed or not
|
||||
*
|
||||
* @return array
|
||||
* An array mapping config object names with directories.
|
||||
*/
|
||||
public function getAllFolders() {
|
||||
if (!isset($this->folders)) {
|
||||
$this->folders = array();
|
||||
$this->folders += $this->getCoreNames();
|
||||
|
||||
$install_profile = Settings::get('install_profile');
|
||||
$profile = drupal_get_profile();
|
||||
$extensions = $this->configStorage->read('core.extension');
|
||||
// @todo Remove this scan as part of https://www.drupal.org/node/2186491
|
||||
$listing = new ExtensionDiscovery(\Drupal::root());
|
||||
|
||||
// CHANGED START: Add profile directories for any bundles that use a profile.
|
||||
$profile_directories = [];
|
||||
if ($profile) {
|
||||
$profile_directories[] = drupal_get_path('profile', $profile);
|
||||
}
|
||||
if ($this->includeProfile) {
|
||||
// Add any profiles used in bundles.
|
||||
/** @var \Drupal\features\FeaturesAssignerInterface $assigner */
|
||||
$assigner = \Drupal::service('features_assigner');
|
||||
$bundles = $assigner->getBundleList();
|
||||
foreach ($bundles as $bundle_name => $bundle) {
|
||||
if ($bundle->isProfile()) {
|
||||
// Register the profile directory.
|
||||
$profile_directory = 'profiles/' . $bundle->getProfileName();
|
||||
if (is_dir($profile_directory)) {
|
||||
$profile_directories[] = $profile_directory;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$listing->setProfileDirectories($profile_directories);
|
||||
// CHANGED END
|
||||
|
||||
if (!empty($extensions['module'])) {
|
||||
|
||||
// CHANGED START: Find ANY modules, not just installed ones.
|
||||
//$modules = $extensions['module'];
|
||||
$module_list_scan = $listing->scan('module');
|
||||
$modules = $module_list_scan;
|
||||
// CHANGED END
|
||||
|
||||
// Remove the install profile as this is handled later.
|
||||
unset($modules[$install_profile]);
|
||||
$profile_list = $listing->scan('profile');
|
||||
if ($profile && isset($profile_list[$profile])) {
|
||||
// Prime the drupal_get_filename() static cache with the profile info
|
||||
// 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());
|
||||
}
|
||||
// CHANGED START: Put Features modules first in list returned.
|
||||
// to allow features to override config provided by other extensions.
|
||||
$featuresManager = \Drupal::service('features.manager');
|
||||
$features_list = array();
|
||||
$module_list = array();
|
||||
foreach (array_keys($module_list_scan) as $module) {
|
||||
if ($featuresManager->isFeatureModule($module_list_scan[$module])) {
|
||||
$features_list[$module] = $module_list_scan[$module];
|
||||
}
|
||||
else {
|
||||
$module_list[$module] = $module_list_scan[$module];
|
||||
}
|
||||
}
|
||||
$this->folders += $this->getComponentNames($features_list);
|
||||
$this->folders += $this->getComponentNames($module_list);
|
||||
// CHANGED END
|
||||
}
|
||||
if (!empty($extensions['theme'])) {
|
||||
$theme_list_scan = $listing->scan('theme');
|
||||
foreach (array_keys($extensions['theme']) as $theme) {
|
||||
if (isset($theme_list_scan[$theme])) {
|
||||
$theme_list[$theme] = $theme_list_scan[$theme];
|
||||
}
|
||||
}
|
||||
$this->folders += $this->getComponentNames($theme_list);
|
||||
}
|
||||
|
||||
if ($this->includeProfile) {
|
||||
// The install profile can override module default configuration. We do
|
||||
// this by replacing the config file path from the module/theme with the
|
||||
// install profile version if there are any duplicates.
|
||||
if (isset($profile)) {
|
||||
if (!isset($profile_list)) {
|
||||
$profile_list = $listing->scan('profile');
|
||||
}
|
||||
if (isset($profile_list[$profile])) {
|
||||
$profile_folders = $this->getComponentNames(array($profile_list[$profile]));
|
||||
$this->folders = $profile_folders + $this->folders;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->folders;
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,601 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
use Drupal\Core\Extension\Extension;
|
||||
|
||||
/**
|
||||
* Provides an interface for the FeaturesManager.
|
||||
*/
|
||||
interface FeaturesManagerInterface {
|
||||
|
||||
/**
|
||||
* Simple configuration.
|
||||
*
|
||||
* Core uses system.simple, but since we're using this key in configuration
|
||||
* arrays we can't include a period.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2297311
|
||||
*/
|
||||
const SYSTEM_SIMPLE_CONFIG = 'system_simple';
|
||||
|
||||
/**
|
||||
* Constants for package/module status.
|
||||
*/
|
||||
const STATUS_NO_EXPORT = 0;
|
||||
const STATUS_UNINSTALLED = 1;
|
||||
const STATUS_INSTALLED = 2;
|
||||
const STATUS_DEFAULT = self::STATUS_NO_EXPORT;
|
||||
|
||||
/**
|
||||
* Constants for package/module state.
|
||||
*/
|
||||
const STATE_DEFAULT = 0;
|
||||
const STATE_OVERRIDDEN = 1;
|
||||
|
||||
/**
|
||||
* Returns the active config store.
|
||||
*
|
||||
* @return \Drupal\Core\Config\StorageInterface
|
||||
*/
|
||||
public function getActiveStorage();
|
||||
|
||||
/**
|
||||
* Returns a set of config storages.
|
||||
*
|
||||
* This method is used for support of multiple extension configuration
|
||||
* directories, including the core-provided install and optional directories.
|
||||
*
|
||||
* @return \Drupal\Core\Config\StorageInterface[]
|
||||
*/
|
||||
public function getExtensionStorages();
|
||||
|
||||
/**
|
||||
* Resets packages and configuration assignment.
|
||||
*/
|
||||
public function reset();
|
||||
|
||||
/**
|
||||
* Gets an array of site configuration.
|
||||
*
|
||||
* @param bool $reset
|
||||
* If TRUE, recalculate the configuration (undo all assignment methods).
|
||||
*
|
||||
* @return \Drupal\features\ConfigurationItem[]
|
||||
* An array of items, each with the following keys:
|
||||
* - 'name': prefixed configuration item name.
|
||||
* - 'name_short': configuration item name without prefix.
|
||||
* - 'label': human readable name of configuration item.
|
||||
* - 'type': type of configuration.
|
||||
* - 'data': the contents of the configuration item in exported format.
|
||||
* - 'dependents': array of names of dependent configuration items.
|
||||
* - 'subdirectory': feature subdirectory to export item to.
|
||||
* - 'package': machine name of a package the configuration is assigned to.
|
||||
* - 'extension_provided': whether the configuration is provided by an
|
||||
* extension.
|
||||
* - 'package_excluded': array of package names that this item should be
|
||||
* excluded from.
|
||||
*/
|
||||
public function getConfigCollection($reset = FALSE);
|
||||
|
||||
/**
|
||||
* Sets an array of site configuration.
|
||||
*
|
||||
* @param \Drupal\features\ConfigurationItem[] $config_collection
|
||||
* An array of items.
|
||||
*/
|
||||
public function setConfigCollection(array $config_collection);
|
||||
|
||||
/**
|
||||
* Gets an array of packages.
|
||||
*
|
||||
* @return \Drupal\features\Package[]
|
||||
* An array of items, each with the following keys:
|
||||
* - 'machine_name': machine name of the package such as 'example_article'.
|
||||
* 'article'.
|
||||
* - 'name': human readable name of the package such as 'Example Article'.
|
||||
* - 'description': description of the package.
|
||||
* - 'type': type of Drupal project ('module').
|
||||
* - 'core': Drupal core compatibility ('8.x').
|
||||
* - 'dependencies': array of module dependencies.
|
||||
* - 'themes': array of names of themes to install.
|
||||
* - 'config': array of names of configuration items.
|
||||
* - 'status': status of the package. Valid values are:
|
||||
* - FeaturesManagerInterface::STATUS_NO_EXPORT
|
||||
* - FeaturesManagerInterface::STATUS_INSTALLED
|
||||
* - FeaturesManagerInterface::STATUS_UNINSTALLED
|
||||
* - 'version': version of the extension.
|
||||
* - 'state': state of the extension. Valid values are:
|
||||
* - FeaturesManagerInterface::STATE_DEFAULT
|
||||
* - FeaturesManagerInterface::STATE_OVERRIDDEN
|
||||
* - 'directory': the extension's directory.
|
||||
* - 'files' array of files, each having the following keys:
|
||||
* - 'filename': the name of the file.
|
||||
* - 'subdirectory': any subdirectory of the file within the extension
|
||||
* directory.
|
||||
* - 'string': the contents of the file.
|
||||
* - 'bundle': name of the features bundle this package belongs to.
|
||||
* - 'extension': \Drupal\Core\Extension\Extension object.
|
||||
* - 'info': the original info array from an existing package.
|
||||
* - 'config_info': the original config of the module.
|
||||
*
|
||||
* @see \Drupal\features\FeaturesManagerInterface::setPackages()
|
||||
*/
|
||||
public function getPackages();
|
||||
|
||||
/**
|
||||
* Sets an array of packages.
|
||||
*
|
||||
* @param \Drupal\features\Package[] $packages
|
||||
* An array of packages.
|
||||
*/
|
||||
public function setPackages(array $packages);
|
||||
|
||||
/**
|
||||
* Gets a specific package.
|
||||
*
|
||||
* @param string $machine_name
|
||||
* Full machine name of package.
|
||||
*
|
||||
* @return \Drupal\features\Package
|
||||
* Package data.
|
||||
*
|
||||
* @see \Drupal\features\FeaturesManagerInterface::getPackages()
|
||||
*/
|
||||
public function getPackage($machine_name);
|
||||
|
||||
/**
|
||||
* Gets a specific package.
|
||||
* Similar to getPackage but will also match package FullName
|
||||
*
|
||||
* @param string $machine_name
|
||||
* Full machine name of package.
|
||||
*
|
||||
* @return \Drupal\features\Package
|
||||
* Package data.
|
||||
*
|
||||
* @see \Drupal\features\FeaturesManagerInterface::getPackages()
|
||||
*/
|
||||
public function findPackage($machine_name);
|
||||
|
||||
/**
|
||||
* Updates a package definition in the package list.
|
||||
*
|
||||
* NOTE: This does not "export" the package; it simply updates the internal
|
||||
* data.
|
||||
*
|
||||
* @param \Drupal\features\Package $package
|
||||
* The package.
|
||||
*/
|
||||
public function setPackage(Package $package);
|
||||
|
||||
/**
|
||||
* Filters the supplied package list by the given namespace.
|
||||
*
|
||||
* @param \Drupal\features\Package[] $packages
|
||||
* An array of packages.
|
||||
* @param string $namespace
|
||||
* The namespace to use.
|
||||
* @param bool $only_exported
|
||||
* If true, only filter out packages that are exported
|
||||
*
|
||||
* @return \Drupal\features\Package[]
|
||||
* An array of packages.
|
||||
*/
|
||||
public function filterPackages(array $packages, $namespace = '', $only_exported = FALSE);
|
||||
|
||||
/**
|
||||
* Gets a reference to a package assigner.
|
||||
*
|
||||
* @return \Drupal\features\FeaturesAssignerInterface
|
||||
* The package assigner.
|
||||
*/
|
||||
public function getAssigner();
|
||||
|
||||
/**
|
||||
* Injects the package assigner.
|
||||
*
|
||||
* @param \Drupal\features\FeaturesAssignerInterface $assigner
|
||||
* The package assigner.
|
||||
*/
|
||||
public function setAssigner(FeaturesAssignerInterface $assigner);
|
||||
|
||||
/**
|
||||
* Gets a reference to a package generator.
|
||||
*
|
||||
* @return \Drupal\features\FeaturesGeneratorInterface
|
||||
* The package generator.
|
||||
*/
|
||||
public function getGenerator();
|
||||
|
||||
/**
|
||||
* Injects the package generator.
|
||||
*
|
||||
* @param \Drupal\features\FeaturesGeneratorInterface $generator
|
||||
* The package generator.
|
||||
*/
|
||||
public function setGenerator(FeaturesGeneratorInterface $generator);
|
||||
|
||||
/**
|
||||
* Returns the current export settings.
|
||||
*
|
||||
* @return array
|
||||
* An array with the following keys:
|
||||
* - 'folder' - subdirectory to export packages to.
|
||||
* - 'namespace' - module namespace being exported.
|
||||
*/
|
||||
public function getExportSettings();
|
||||
|
||||
/**
|
||||
* Returns the current general features settings.
|
||||
*
|
||||
* @return \Drupal\Core\Config\Config
|
||||
* A config object containing settings.
|
||||
*/
|
||||
public function getSettings();
|
||||
|
||||
/**
|
||||
* Returns the contents of an extensions info.yml file.
|
||||
*
|
||||
* @param \Drupal\Core\Extension\Extension $extension
|
||||
* An Extension object.
|
||||
*
|
||||
* @return array
|
||||
* An array representing data in an info.yml file.
|
||||
*/
|
||||
public function getExtensionInfo(Extension $extension);
|
||||
|
||||
/**
|
||||
* Determine if extension is enabled
|
||||
*
|
||||
* @param \Drupal\Core\Extension\Extension $extension
|
||||
* @return bool
|
||||
*/
|
||||
public function extensionEnabled(Extension $extension);
|
||||
|
||||
/**
|
||||
* Initializes a configuration package.
|
||||
*
|
||||
* @param string $machine_name
|
||||
* Machine name of the package.
|
||||
* @param string $name
|
||||
* (optional) Human readable name of the package.
|
||||
* @param string $description
|
||||
* (optional) Description of the package.
|
||||
* @param string $type
|
||||
* (optional) Type of project.
|
||||
* @param \Drupal\features\FeaturesBundleInterface $bundle
|
||||
* (optional) Bundle to use to add profile directories to the scan.
|
||||
* @param \Drupal\Core\Extension\Extension $extension
|
||||
* (optional) An Extension object.
|
||||
* @return array
|
||||
* The created package array.
|
||||
*/
|
||||
public function initPackage($machine_name, $name = NULL, $description = '', $type = 'module', FeaturesBundleInterface $bundle = NULL, Extension $extension = NULL);
|
||||
|
||||
/**
|
||||
* Initializes a configuration package using module info data.
|
||||
*
|
||||
* @param \Drupal\Core\Extension\Extension $extension
|
||||
* An Extension object.
|
||||
*
|
||||
* @return \Drupal\features\Package
|
||||
* The created package array.
|
||||
*/
|
||||
public function initPackageFromExtension(Extension $extension);
|
||||
|
||||
/**
|
||||
* Lists directories in which packages are present.
|
||||
*
|
||||
* This method scans to find package modules whether or not they are
|
||||
* currently active (installed). As well as the directories that are
|
||||
* usually scanned for modules and profiles, a profile directory for the
|
||||
* current profile is scanned if it exists. For example, if the value
|
||||
* for $bundle->getProfileName() is 'example', a
|
||||
* directory profiles/example will be scanned if it exists. Therefore, when
|
||||
* regenerating package modules, existing ones from a prior export will be
|
||||
* recognized.
|
||||
*
|
||||
* @param string[] $machine_names
|
||||
* Package machine names to return directories for. If omitted, return all
|
||||
* directories.
|
||||
* @param \Drupal\features\FeaturesBundleInterface $bundle
|
||||
* Optional bundle to use to add profile directories to the scan.
|
||||
*
|
||||
* @return array
|
||||
* Array of package directories keyed by package machine name.
|
||||
*/
|
||||
public function listPackageDirectories(array $machine_names = array(), FeaturesBundleInterface $bundle = NULL);
|
||||
|
||||
/**
|
||||
* Assigns a set of configuration items to a given package or profile.
|
||||
*
|
||||
* @param string $package_name
|
||||
* Machine name of a package or the profile.
|
||||
* @param string[] $item_names
|
||||
* Configuration item names.
|
||||
* @param bool $force
|
||||
* (optional) If TRUE, assign config regardless of restrictions such as it
|
||||
* being already assigned to a package.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function assignConfigPackage($package_name, array $item_names, $force = FALSE);
|
||||
|
||||
/**
|
||||
* Assigns configuration items with names matching given strings to given
|
||||
* packages.
|
||||
*
|
||||
* @param array $patterns
|
||||
* Array with string patterns as keys and package machine names as values.
|
||||
*/
|
||||
public function assignConfigByPattern(array $patterns);
|
||||
|
||||
/**
|
||||
* For given configuration items, assigns any dependent configuration to the
|
||||
* same package.
|
||||
*
|
||||
* @param string[] $item_names
|
||||
* Configuration item names.
|
||||
* @param string $package
|
||||
* Short machine name of package to assign dependent config to. If NULL,
|
||||
* use the current package of the parent config items.
|
||||
*/
|
||||
public function assignConfigDependents(array $item_names = NULL, $package = NULL);
|
||||
|
||||
/**
|
||||
* Adds the optional bundle prefix to package machine names.
|
||||
*
|
||||
* @param \Drupal\features\FeaturesBundleInterface $bundle
|
||||
* The bundle used for the generation.
|
||||
* @param string[] &$package_names
|
||||
* (optional) Array of package names, passed by reference.
|
||||
*/
|
||||
public function setPackageBundleNames(FeaturesBundleInterface $bundle, array &$package_names = []);
|
||||
|
||||
/**
|
||||
* Assigns dependencies from config items into the package.
|
||||
*
|
||||
* @param \Drupal\features\Package[] $packages
|
||||
* An array of packages. NULL for all packages
|
||||
*/
|
||||
public function assignPackageDependencies(Package $package = NULL);
|
||||
|
||||
/**
|
||||
* Assigns dependencies between packages based on configuration dependencies.
|
||||
*
|
||||
* \Drupal\features\FeaturesBundleInterface::setPackageBundleNames() must be
|
||||
* called prior to calling this method.
|
||||
*
|
||||
* @param \Drupal\features\FeaturesBundleInterface $bundle
|
||||
* A features bundle.
|
||||
* @param \Drupal\features\Package[] $packages
|
||||
* An array of packages.
|
||||
*/
|
||||
public function assignInterPackageDependencies(FeaturesBundleInterface $bundle, array &$packages);
|
||||
|
||||
/**
|
||||
* Merges two info arrays and processes the resulting array.
|
||||
*
|
||||
* Ensures values are unique and sorted.
|
||||
*
|
||||
* @param array $info1
|
||||
* The first array.
|
||||
* @param array $info2
|
||||
* The second array.
|
||||
* @param string[] $keys
|
||||
* Keys to merge. If not specified, all keys present will be merged.
|
||||
*
|
||||
* @return array
|
||||
* An array with the merged and processed results.
|
||||
*
|
||||
* @fixme Should this be moved to the package object or a related helper?
|
||||
*/
|
||||
public function mergeInfoArray(array $info1, array $info2, array $keys = array());
|
||||
|
||||
/**
|
||||
* Lists the types of configuration available on the site.
|
||||
*
|
||||
* @param boolean $bundles_only
|
||||
* Whether to list only configuration types that provide bundles.
|
||||
*
|
||||
* @return array
|
||||
* An array with machine name keys and human readable values.
|
||||
*/
|
||||
public function listConfigTypes($bundles_only = FALSE);
|
||||
|
||||
/**
|
||||
* Lists stored configuration for a given configuration type.
|
||||
*
|
||||
* @param string $config_type
|
||||
* The type of configuration.
|
||||
*/
|
||||
public function listConfigByType($config_type);
|
||||
|
||||
/**
|
||||
* Returns a list of all modules present on the site's file system.
|
||||
*
|
||||
* @return Drupal\Core\Extension\Extension[]
|
||||
* An array of extension objects.
|
||||
*/
|
||||
public function getAllModules();
|
||||
|
||||
/**
|
||||
* Returns a list of Features modules regardless of if they are installed.
|
||||
*
|
||||
* @param \Drupal\features\FeaturesBundleInterface $bundle
|
||||
* Optional bundle to filter module list.
|
||||
* If given, only modules matching the bundle namespace will be returned.
|
||||
* If the bundle uses a profile, only modules in the profile will be
|
||||
* returned.
|
||||
* @param bool $installed
|
||||
* List only installed modules.
|
||||
*
|
||||
* @return Drupal\Core\Extension\Extension[]
|
||||
* An array of extension objects.
|
||||
*/
|
||||
public function getFeaturesModules(FeaturesBundleInterface $bundle = NULL, $installed = FALSE);
|
||||
|
||||
/**
|
||||
* Lists names of configuration objects provided by a given extension.
|
||||
*
|
||||
* @param \Drupal\Core\Extension\Extension $extension
|
||||
* An Extension object.
|
||||
*
|
||||
* @return array
|
||||
* An array of configuration object names.
|
||||
*/
|
||||
public function listExtensionConfig(Extension $extension);
|
||||
|
||||
/**
|
||||
* Lists names of configuration items provided by existing Features modules.
|
||||
*
|
||||
* @param bool $installed
|
||||
* List only installed Features.
|
||||
* @param \Drupal\features\FeaturesBundleInterface $bundle
|
||||
* (optional) Bundle to find existing configuration for.
|
||||
*
|
||||
* @return array
|
||||
* An array with config names as keys and providing module names as values.
|
||||
*/
|
||||
public function listExistingConfig($installed = FALSE, FeaturesBundleInterface $bundle = NULL);
|
||||
|
||||
/**
|
||||
* Iterates through packages and prepares file names and contents.
|
||||
*
|
||||
* @param array $packages
|
||||
* An array of packages.
|
||||
*/
|
||||
public function prepareFiles(array $packages);
|
||||
|
||||
/**
|
||||
* Returns the full name of a config item.
|
||||
*
|
||||
* @param string $type
|
||||
* The config type, or '' to indicate $name is already prefixed.
|
||||
* @param string $name
|
||||
* The config name, without prefix.
|
||||
*
|
||||
* @return string
|
||||
* The config item's full name.
|
||||
*/
|
||||
public function getFullName($type, $name);
|
||||
|
||||
/**
|
||||
* Returns the short name and type of a full config name.
|
||||
*
|
||||
* @param string $fullname
|
||||
* The full configuration name
|
||||
* @return array
|
||||
* 'type' => string the config type
|
||||
* 'name_short' => string the short config name, without prefix.
|
||||
*/
|
||||
public function getConfigType($fullname);
|
||||
|
||||
/**
|
||||
* Returns the full machine name and directory for exporting a package.
|
||||
*
|
||||
* @param \Drupal\features\Package $package
|
||||
* The package.
|
||||
* @param \Drupal\features\FeaturesBundleInterface $bundle
|
||||
* Optional bundle being used for export.
|
||||
*
|
||||
* @return array
|
||||
* An array with the full name as the first item and directory as second
|
||||
* item.
|
||||
*/
|
||||
public function getExportInfo(Package $package, FeaturesBundleInterface $bundle = NULL);
|
||||
|
||||
/**
|
||||
* Determines if the module is a Features package, optinally testing by
|
||||
* bundle.
|
||||
*
|
||||
* @param \Drupal\Core\Extension\Extension $module
|
||||
* An extension object.
|
||||
* @param \Drupal\features\FeaturesBundleInterface $bundle
|
||||
* (optional) Bundle to filter by.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the given module is a Features package of the given bundle (if any).
|
||||
*/
|
||||
public function isFeatureModule(Extension $module, FeaturesBundleInterface $bundle);
|
||||
|
||||
/**
|
||||
* Determines which config is overridden in a package.
|
||||
*
|
||||
* @param \Drupal\features\Package $feature
|
||||
* The package array.
|
||||
* The 'state' property is updated if overrides are detected.
|
||||
* @param bool $include_new
|
||||
* If set, include newly detected config not yet exported.
|
||||
*
|
||||
* @result array $different
|
||||
* The array of config items that are overridden.
|
||||
*
|
||||
* @see \Drupal\features\FeaturesManagerInterface::detectNew()
|
||||
*/
|
||||
public function detectOverrides(Package $feature, $include_new = FALSE);
|
||||
|
||||
/**
|
||||
* Determines which config has not been exported to the feature.
|
||||
*
|
||||
* Typically added as an auto-detected dependency.
|
||||
*
|
||||
* @param \Drupal\features\Package $feature
|
||||
* The package array.
|
||||
*
|
||||
* @return array
|
||||
* The array of config items that are overridden.
|
||||
*/
|
||||
public function detectNew(Package $feature);
|
||||
|
||||
/**
|
||||
* Determines which config is exported in the feature but not in the active.
|
||||
*
|
||||
* @param \Drupal\features\Package $feature
|
||||
* The package array.
|
||||
*
|
||||
* @return array
|
||||
* The array of config items that are missing from active store.
|
||||
*/
|
||||
public function detectMissing(Package $feature);
|
||||
|
||||
/**
|
||||
* Sort the Missing config into order by dependencies.
|
||||
* @param array $missing config items
|
||||
* @return array of config items in dependency order
|
||||
*/
|
||||
public function reorderMissing(array $missing);
|
||||
|
||||
/**
|
||||
* Helper function that returns a translatable label for the different status
|
||||
* constants.
|
||||
*
|
||||
* @param int $status
|
||||
* A status constant.
|
||||
*
|
||||
* @return string
|
||||
* A translatable label.
|
||||
*/
|
||||
public function statusLabel($status);
|
||||
|
||||
/**
|
||||
* Helper function that returns a translatable label for the different state
|
||||
* constants.
|
||||
*
|
||||
* @param int $state
|
||||
* A state constant.
|
||||
*
|
||||
* @return string
|
||||
* A translatable label.
|
||||
*/
|
||||
public function stateLabel($state);
|
||||
|
||||
/**
|
||||
* @param \Drupal\Core\Extension\Extension $extension
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFeaturesInfo(Extension $extension);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\Core\DependencyInjection\ServiceProviderBase;
|
||||
|
||||
/**
|
||||
* Service provider implementation for Features to override config.installer.
|
||||
*
|
||||
* @ingroup container
|
||||
*/
|
||||
class FeaturesServiceProvider extends ServiceProviderBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function alter(ContainerBuilder $container) {
|
||||
// Override the config.installer class with a new class.
|
||||
$definition = $container->getDefinition('config.installer');
|
||||
$definition->setClass('Drupal\features\FeaturesConfigInstaller');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,549 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features;
|
||||
|
||||
/**
|
||||
* Defines a value object for storing package related data.
|
||||
*
|
||||
* A package contains of a name, version number, containing config etc.
|
||||
*/
|
||||
class Package {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $machineName = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $name = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $description = '';
|
||||
|
||||
/**
|
||||
* @todo This could be fetched from the extension object.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $version = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $core = '8.x';
|
||||
|
||||
/**
|
||||
* @todo This could be fetched from the extension object.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'module';
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $themes = [];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $bundle;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $excluded = [];
|
||||
|
||||
/**
|
||||
* @var string[]|bool
|
||||
*/
|
||||
protected $required = false;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $info = [];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $dependencies = [];
|
||||
|
||||
/**
|
||||
* @todo This could be fetched from the extension object.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $status;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* @todo This could be fetched from the extension object.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $directory;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $files;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Extension\Extension
|
||||
*/
|
||||
protected $extension;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $config = [];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $configOrig = [];
|
||||
|
||||
/**
|
||||
* Creates a new Package instance.
|
||||
*
|
||||
* @param string $machine_name
|
||||
* The machine name.
|
||||
* @param array $additional_properties
|
||||
* (optional) Additional properties of the object.
|
||||
*/
|
||||
public function __construct($machine_name, array $additional_properties = []) {
|
||||
$this->machineName = $machine_name;
|
||||
|
||||
$properties = get_object_vars($this);
|
||||
foreach ($additional_properties as $property => $value) {
|
||||
if (!array_key_exists($property, $properties)) {
|
||||
throw new \InvalidArgumentException('Invalid property: ' . $property);
|
||||
}
|
||||
$this->{$property} = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getMachineName() {
|
||||
return $this->machineName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return TRUE if the machine_name already has the bundle prefix.
|
||||
*
|
||||
* @param string $machine_name
|
||||
* @param string $bundle_name
|
||||
* @return bool
|
||||
*/
|
||||
protected function inBundle($machine_name, $bundle_name) {
|
||||
return strpos($machine_name, $bundle_name . '_') === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFullName() {
|
||||
if (empty($this->bundle) || $this->inBundle($this->machineName, $this->bundle)) {
|
||||
return $this->machineName;
|
||||
}
|
||||
else {
|
||||
return $this->bundle . '_' . $this->machineName;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName() {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription() {
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getVersion() {
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getStatus() {
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getConfig() {
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a new filename.
|
||||
*
|
||||
* @param string $config
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function appendConfig($config) {
|
||||
$this->config[] = $config;
|
||||
$this->config = array_unique($this->config);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeConfig($name) {
|
||||
$this->config = array_diff($this->config, [$name]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getBundle() {
|
||||
return $this->bundle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getExcluded() {
|
||||
return $this->excluded;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getRequired() {
|
||||
return $this->required;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function getRequiredAll() {
|
||||
$config_orig = $this->getConfigOrig();
|
||||
$info = is_array($this->required) ? $this->required : array();
|
||||
$diff = array_diff($config_orig, $info);
|
||||
// Mark all as required if required:true, or required is empty, or
|
||||
// if required contains all the exported config
|
||||
return empty($diff) || empty($info);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getConfigOrig() {
|
||||
return $this->configOrig;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getCore() {
|
||||
return $this->core;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getType() {
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \string[]
|
||||
*/
|
||||
public function getThemes() {
|
||||
return $this->themes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getInfo() {
|
||||
return $this->info;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getState() {
|
||||
return $this->state;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDirectory() {
|
||||
return $this->directory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getFiles() {
|
||||
return $this->files;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Drupal\Core\Extension\Extension
|
||||
*/
|
||||
public function getExtension() {
|
||||
return $this->extension;
|
||||
}
|
||||
|
||||
public function getDependencies() {
|
||||
return $this->dependencies;
|
||||
}
|
||||
|
||||
public function removeDependency($name) {
|
||||
$this->dependencies = array_diff($this->dependencies, [$name]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDependencyInfo() {
|
||||
return isset($this->info['dependencies']) ? $this->info['dependencies'] : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the features info.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFeaturesInfo() {
|
||||
$info = [];
|
||||
if (!empty($this->bundle)) {
|
||||
$info['bundle'] = $this->bundle;
|
||||
}
|
||||
if (!empty($this->excluded)) {
|
||||
$info['excluded'] = $this->excluded;
|
||||
}
|
||||
if ($this->required !== FALSE) {
|
||||
$info['required'] = $this->required;
|
||||
}
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new machine name.
|
||||
*
|
||||
* @param string $machine_name
|
||||
* The machine name
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setMachineName($machine_name) {
|
||||
$this->machineName = $machine_name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setName($name) {
|
||||
$this->name = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $description
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setDescription($description) {
|
||||
$this->description = $description;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $version
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setVersion($version) {
|
||||
$this->version = $version;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $bundle
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setBundle($bundle) {
|
||||
$this->bundle = $bundle;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $info
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setInfo($info) {
|
||||
$this->info = $info;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \string[] $features_info
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setFeaturesInfo($features_info) {
|
||||
if (isset($features_info['bundle'])) {
|
||||
$this->setBundle($features_info['bundle']);
|
||||
}
|
||||
$this->setRequired(isset($features_info['required']) ? $features_info['required'] : false);
|
||||
$this->setExcluded(isset($features_info['excluded']) ? $features_info['excluded'] : array());
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \string[] $dependencies
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setDependencies($dependencies) {
|
||||
$this->dependencies = $dependencies;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $dependency
|
||||
*
|
||||
* return $this
|
||||
*/
|
||||
public function appendDependency($dependency) {
|
||||
$this->dependencies[] = $dependency;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $status
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setStatus($status) {
|
||||
$this->status = $status;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \string[] $config
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setConfig($config) {
|
||||
$this->config = $config;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $excluded
|
||||
*/
|
||||
public function setExcluded($excluded) {
|
||||
$this->excluded = $excluded;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $required
|
||||
*/
|
||||
public function setRequired($required) {
|
||||
$this->required = $required;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $core
|
||||
*/
|
||||
public function setCore($core) {
|
||||
$this->core = $core;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
*/
|
||||
public function setType($type) {
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \string[] $themes
|
||||
*/
|
||||
public function setThemes($themes) {
|
||||
$this->themes = $themes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $state
|
||||
*/
|
||||
public function setState($state) {
|
||||
$this->state = $state;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $directory
|
||||
*/
|
||||
public function setDirectory($directory) {
|
||||
$this->directory = $directory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \string[] $files
|
||||
*/
|
||||
public function setFiles($files) {
|
||||
$this->files = $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $file_array
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function appendFile(array $file_array, $key = NULL) {
|
||||
if (!isset($key)) {
|
||||
$this->files[] = $file_array;
|
||||
}
|
||||
else {
|
||||
$this->files[$key] = $file_array;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Drupal\Core\Extension\Extension $extension
|
||||
*/
|
||||
public function setExtension($extension) {
|
||||
$this->extension = $extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \string[] $configOrig
|
||||
*/
|
||||
public function setConfigOrig($configOrig) {
|
||||
$this->configOrig = $configOrig;
|
||||
}
|
||||
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features\Plugin\FeaturesAssignment;
|
||||
|
||||
use Drupal\component\Utility\Unicode;
|
||||
use Drupal\features\FeaturesAssignmentMethodBase;
|
||||
|
||||
/**
|
||||
* Class for assigning configuration to packages based on entity types.
|
||||
*
|
||||
* @Plugin(
|
||||
* id = "base",
|
||||
* weight = -2,
|
||||
* name = @Translation("Base type"),
|
||||
* description = @Translation("Use designated types of configuration as the base for configuration package modules. For example, if content types are selected as a base type, a package will be generated for each content type and will include all configuration dependent on that content type."),
|
||||
* config_route_name = "features.assignment_base",
|
||||
* default_settings = {
|
||||
* "types" = {
|
||||
* "config" = {},
|
||||
* "content" = {}
|
||||
* }
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class FeaturesAssignmentBaseType extends FeaturesAssignmentMethodBase {
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function assignPackages($force = FALSE) {
|
||||
$current_bundle = $this->assigner->getBundle();
|
||||
$settings = $current_bundle->getAssignmentSettings($this->getPluginId());
|
||||
$config_base_types = $settings['types']['config'];
|
||||
|
||||
$config_types = $this->featuresManager->listConfigTypes();
|
||||
$config_collection = $this->featuresManager->getConfigCollection();
|
||||
|
||||
foreach ($config_collection as $item_name => $item) {
|
||||
if (in_array($item->getType(), $config_base_types)) {
|
||||
if (is_null($this->featuresManager->findPackage($item->getShortName())) && !$item->getPackage()) {
|
||||
$description = $this->t('Provides @label @type and related configuration.', array('@label' => $item->getLabel(), '@type' => Unicode::strtolower($config_types[$item->getType()])));
|
||||
if (isset($item->getData()['description'])) {
|
||||
$description .= ' ' . $item->getData()['description'];
|
||||
}
|
||||
$this->featuresManager->initPackage($item->getShortName(), $item->getLabel(), $description, 'module', $current_bundle);
|
||||
// Update list with the package we just added.
|
||||
try {
|
||||
$this->featuresManager->assignConfigPackage($item->getShortName(), [$item_name]);
|
||||
}
|
||||
catch (\Exception $exception) {
|
||||
\Drupal::logger('features')->error($exception->getMessage());
|
||||
}
|
||||
$this->featuresManager->assignConfigDependents([$item_name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$entity_types = $this->entityManager->getDefinitions();
|
||||
|
||||
$content_base_types = $settings['types']['content'];
|
||||
foreach ($content_base_types as $entity_type_id) {
|
||||
if (!isset($packages[$entity_type_id]) && isset($entity_types[$entity_type_id])) {
|
||||
$label = $entity_types[$entity_type_id]->getLabel();
|
||||
$description = $this->t('Provide @label related configuration.', array('@label' => $label));
|
||||
$this->featuresManager->initPackage($entity_type_id, $label, $description, 'module', $current_bundle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features\Plugin\FeaturesAssignment;
|
||||
|
||||
use Drupal\features\FeaturesAssignmentMethodBase;
|
||||
|
||||
/**
|
||||
* Class for assigning configuration to a core package based on entity types.
|
||||
*
|
||||
* @Plugin(
|
||||
* id = "core",
|
||||
* weight = 5,
|
||||
* name = @Translation("Core type"),
|
||||
* description = @Translation("Assign designated types of configuration to a core configuration package module. For example, if image styles are selected as a core type, a core package will be generated and image styles will be assigned to it."),
|
||||
* config_route_name = "features.assignment_core",
|
||||
* default_settings = {
|
||||
* "types" = {
|
||||
* "config" = {},
|
||||
* }
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class FeaturesAssignmentCoreType extends FeaturesAssignmentMethodBase {
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function assignPackages($force = FALSE) {
|
||||
$current_bundle = $this->assigner->getBundle();
|
||||
$machine_name = 'core';
|
||||
$name = $this->t('Core');
|
||||
$description = $this->t('Provides core components required by other features.');
|
||||
$this->featuresManager->initPackage($machine_name, $name, $description, 'module', $current_bundle);
|
||||
$this->assignPackageByConfigTypes($machine_name, $force);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features\Plugin\FeaturesAssignment;
|
||||
|
||||
use Drupal\features\FeaturesAssignmentMethodBase;
|
||||
|
||||
/**
|
||||
* Class for assigning configuration to packages based on configuration
|
||||
* dependencies.
|
||||
*
|
||||
* @Plugin(
|
||||
* id = "dependency",
|
||||
* weight = 15,
|
||||
* name = @Translation("Dependency"),
|
||||
* description = @Translation("Add to packages configuration dependent on items already in that package."),
|
||||
* )
|
||||
*/
|
||||
class FeaturesAssignmentDependency extends FeaturesAssignmentMethodBase {
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function assignPackages($force = FALSE) {
|
||||
$this->featuresManager->assignConfigDependents();
|
||||
}
|
||||
|
||||
}
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features\Plugin\FeaturesAssignment;
|
||||
|
||||
use Drupal\features\FeaturesAssignmentMethodBase;
|
||||
|
||||
/**
|
||||
* Class for excluding configuration from packages.
|
||||
*
|
||||
* @Plugin(
|
||||
* id = "exclude",
|
||||
* weight = -5,
|
||||
* name = @Translation("Exclude"),
|
||||
* description = @Translation("Exclude configuration items from packaging by various methods including by configuration type."),
|
||||
* config_route_name = "features.assignment_exclude",
|
||||
* default_settings = {
|
||||
* "curated" = FALSE,
|
||||
* "module" = {
|
||||
* "installed" = FALSE,
|
||||
* "profile" = FALSE,
|
||||
* "namespace" = FALSE,
|
||||
* "namespace_any" = FALSE,
|
||||
* },
|
||||
* "types" = { "config" = {} }
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class FeaturesAssignmentExclude extends FeaturesAssignmentMethodBase {
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function assignPackages($force = FALSE) {
|
||||
$current_bundle = $this->assigner->getBundle();
|
||||
$settings = $current_bundle->getAssignmentSettings($this->getPluginId());
|
||||
|
||||
$config_collection = $this->featuresManager->getConfigCollection();
|
||||
|
||||
// Exclude by configuration type.
|
||||
$exclude_types = $settings['types']['config'];
|
||||
if (!empty($exclude_types)) {
|
||||
foreach ($config_collection as $item_name => $item) {
|
||||
// Don't exclude already-assigned items.
|
||||
if (empty($item->getPackage()) && in_array($item->getType(), $exclude_types)) {
|
||||
$item->setExcluded(TRUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Exclude configuration already provided by modules.
|
||||
$exclude_module = $settings['module'];
|
||||
if (!empty($exclude_module['installed'])) {
|
||||
$install_list = $this->featuresManager->getExtensionStorages()->listAll();
|
||||
|
||||
// There are two settings that can limit what's included.
|
||||
// First, we can skip configuration provided by the install profile.
|
||||
$module_profile = !empty($exclude_module['profile']);
|
||||
// Second, we can skip configuration provided by namespaced modules.
|
||||
$module_namespace = !empty($exclude_module['namespace']);
|
||||
if ($module_profile || $module_namespace) {
|
||||
$profile_list = [];
|
||||
$extension_list = [];
|
||||
// Load the names of any configuration objects provided by the install
|
||||
// profile.
|
||||
if ($module_profile) {
|
||||
$all_modules = $this->featuresManager->getAllModules();
|
||||
// FeaturesBundleInterface::getProfileName() would return the profile
|
||||
// for the current bundle, if any. We want the profile that was
|
||||
// installed.
|
||||
$profile_name = drupal_get_profile();
|
||||
if (isset($all_modules[$profile_name])) {
|
||||
$profile_list = $this->featuresManager->listExtensionConfig($all_modules[$profile_name]);
|
||||
// If the configuration has been assigned to a feature that's
|
||||
// present on the file system, don't make an exception for it.
|
||||
foreach ($all_modules as $name => $extension) {
|
||||
if ($name != $profile_name && $this->featuresManager->isFeatureModule($extension)) {
|
||||
$profile_list = array_diff($profile_list, $this->featuresManager->listExtensionConfig($extension));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Load the names of any configuration objects provided by modules
|
||||
// having the namespace of the current package set.
|
||||
if ($module_namespace) {
|
||||
$modules = $this->featuresManager->getFeaturesModules($current_bundle);
|
||||
foreach ($modules as $extension) {
|
||||
// Only make exception for non-exported modules
|
||||
if (!empty($exclude_module['namespace_any']) || !isset($all_modules[$extension->getName()])) {
|
||||
$extension_list = array_merge($extension_list, $this->featuresManager->listExtensionConfig($extension));
|
||||
}
|
||||
}
|
||||
}
|
||||
// If any configuration was found, remove it from the list.
|
||||
$install_list = array_diff($install_list, $profile_list, $extension_list);
|
||||
}
|
||||
foreach ($install_list as $item_name) {
|
||||
if (isset($config_collection[$item_name])) {
|
||||
// Flag extension-provided configuration, which should not be added
|
||||
// to regular features but can be added to an install profile.
|
||||
$config_collection[$item_name]->setProviderExcluded(TRUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Exclude configuration items on a curated list of site-specific
|
||||
// configuration.
|
||||
if ($settings['curated']) {
|
||||
$item_names = [
|
||||
'core.extension',
|
||||
'field.settings',
|
||||
'field_ui.settings',
|
||||
'filter.settings',
|
||||
'forum.settings',
|
||||
'image.settings',
|
||||
'node.settings',
|
||||
'system.authorize',
|
||||
'system.date',
|
||||
'system.file',
|
||||
'system.diff',
|
||||
'system.logging',
|
||||
'system.maintenance',
|
||||
'system.performance',
|
||||
'system.site',
|
||||
'update.settings',
|
||||
];
|
||||
foreach ($item_names as $item_name) {
|
||||
unset($config_collection[$item_name]);
|
||||
}
|
||||
// Unset role-related actions that are automatically created by the
|
||||
// User module.
|
||||
// @see user_user_role_insert()
|
||||
$prefixes = [
|
||||
'system.action.user_add_role_action.',
|
||||
'system.action.user_remove_role_action.',
|
||||
];
|
||||
foreach (array_keys($config_collection) as $item_name) {
|
||||
foreach ($prefixes as $prefix) {
|
||||
if (strpos($item_name, $prefix) === 0) {
|
||||
unset($config_collection[$item_name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register the updated data.
|
||||
$this->featuresManager->setConfigCollection($config_collection);
|
||||
}
|
||||
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features\Plugin\FeaturesAssignment;
|
||||
|
||||
use Drupal\features\FeaturesAssignmentMethodBase;
|
||||
use Drupal\features\FeaturesManagerInterface;
|
||||
|
||||
/**
|
||||
* Class for assigning existing modules to packages.
|
||||
*
|
||||
* @Plugin(
|
||||
* id = "existing",
|
||||
* weight = 12,
|
||||
* name = @Translation("Existing"),
|
||||
* description = @Translation("Add exported config to existing packages."),
|
||||
* )
|
||||
*/
|
||||
class FeaturesAssignmentExisting extends FeaturesAssignmentMethodBase {
|
||||
/**
|
||||
* Calls assignConfigPackage without allowing exceptions to abort us.
|
||||
*
|
||||
* @param string $machine_name
|
||||
* Machine name of package.
|
||||
* @param \Drupal\Core\Extension\Extension $extension
|
||||
* An Extension object.
|
||||
*/
|
||||
protected function safeAssignConfig($machine_name, $extension) {
|
||||
$config = $this->featuresManager->listExtensionConfig($extension);
|
||||
try {
|
||||
$this->featuresManager->assignConfigPackage($machine_name, $config);
|
||||
}
|
||||
catch (\Exception $exception) {
|
||||
\Drupal::logger('features')->error($exception->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function assignPackages($force = FALSE) {
|
||||
$packages = $this->featuresManager->getPackages();
|
||||
|
||||
// Assign config to installed modules first.
|
||||
foreach ($packages as $name => $package) {
|
||||
// @todo Introduce $package->isInstalled() and / or $package->isUninstalled().
|
||||
if ($package->getStatus() === FeaturesManagerInterface::STATUS_INSTALLED) {
|
||||
$this->safeAssignConfig($package->getMachineName(), $package->getExtension());
|
||||
}
|
||||
}
|
||||
// Now assign to uninstalled modules.
|
||||
foreach ($packages as $name => $package) {
|
||||
if ($package->getStatus() === FeaturesManagerInterface::STATUS_UNINSTALLED) {
|
||||
$this->safeAssignConfig($package->getMachineName(), $package->getExtension());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features\Plugin\FeaturesAssignment;
|
||||
|
||||
use Drupal\Component\Graph\Graph;
|
||||
use Drupal\features\FeaturesAssignmentMethodBase;
|
||||
|
||||
/**
|
||||
* Class for assigning configuration to packages based on forward dependencies.
|
||||
*
|
||||
* @Plugin(
|
||||
* id = "forward_dependency",
|
||||
* weight = 20,
|
||||
* name = @Translation("Forward dependency"),
|
||||
* description = @Translation("Add to packages configuration on which items in the package depend."),
|
||||
* )
|
||||
*/
|
||||
class FeaturesAssignmentForwardDependency extends FeaturesAssignmentMethodBase {
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function assignPackages($force = FALSE) {
|
||||
$config_collection = $this->featuresManager->getConfigCollection();
|
||||
$ordered = $this->dependencyOrder($config_collection);
|
||||
|
||||
foreach ($ordered as $name) {
|
||||
$item = $config_collection[$name];
|
||||
if ($item->getPackage()) {
|
||||
// Already has a package, not our business.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find packages of dependent items.
|
||||
$dependent_packages = [];
|
||||
foreach ($item->getDependents() as $dependent) {
|
||||
if (isset($config_collection[$dependent])) {
|
||||
if ($package = $config_collection[$dependent]->getPackage()) {
|
||||
$dependent_packages[$package] = $package;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If zero or multiple packages, we don't know what to do.
|
||||
if (count($dependent_packages) == 1) {
|
||||
$package = key($dependent_packages);
|
||||
$this->featuresManager->assignConfigPackage($package, [$name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get config items such that each item comes before anything it depends on.
|
||||
*
|
||||
* @param \Drupal\features\ConfigurationItem[] $config_collection
|
||||
* A collection of configuration items.
|
||||
*
|
||||
* @return string[]
|
||||
* The names of configuration items, in dependency order.
|
||||
*/
|
||||
protected function dependencyOrder($config_collection) {
|
||||
// Populate a graph.
|
||||
$graph = [];
|
||||
foreach ($config_collection as $config) {
|
||||
$graph[$config->getName()] = [];
|
||||
foreach ($config->getDependents() as $dependent) {
|
||||
$graph[$config->getName()]['edges'][$dependent] = 1;
|
||||
}
|
||||
}
|
||||
$graph_object = new Graph($graph);
|
||||
$graph = $graph_object->searchAndSort();
|
||||
|
||||
// Order by inverse weight.
|
||||
$weights = array_column($graph, 'weight');
|
||||
array_multisort($weights, SORT_DESC, $graph);
|
||||
return array_keys($graph);
|
||||
}
|
||||
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features\Plugin\FeaturesAssignment;
|
||||
|
||||
use Drupal\features\FeaturesAssignmentMethodBase;
|
||||
|
||||
/**
|
||||
* Class for assigning configuration to packages based on namespaces.
|
||||
*
|
||||
* @Plugin(
|
||||
* id = "namespace",
|
||||
* weight = 0,
|
||||
* name = @Translation("Namespace"),
|
||||
* description = @Translation("Add to packages configuration with a machine name containing that package's machine name."),
|
||||
* )
|
||||
*/
|
||||
class FeaturesAssignmentNamespace extends FeaturesAssignmentMethodBase {
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function assignPackages($force = FALSE) {
|
||||
$packages = array_keys($this->featuresManager->getPackages());
|
||||
$this->featuresManager->assignConfigByPattern(array_combine($packages, $packages));
|
||||
}
|
||||
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features\Plugin\FeaturesAssignment;
|
||||
|
||||
use Drupal\Core\Config\InstallStorage;
|
||||
use Drupal\features\FeaturesAssignmentMethodBase;
|
||||
|
||||
/**
|
||||
* Class for assigning configuration to the
|
||||
* InstallStorage::CONFIG_OPTIONAL_DIRECTORY based on entity types.
|
||||
*
|
||||
* @Plugin(
|
||||
* id = "optional",
|
||||
* weight = 0,
|
||||
* name = @Translation("Optional type"),
|
||||
* description = @Translation("Assign designated types of configuration to the 'config/optional' install directory. For example, if views are selected as optional, views assigned to any feature will be exported to the 'config/optional' directory and will not create a dependency on the Views module."),
|
||||
* config_route_name = "features.assignment_optional",
|
||||
* default_settings = {
|
||||
* "types" = {
|
||||
* "config" = {},
|
||||
* }
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class FeaturesAssignmentOptionalType extends FeaturesAssignmentMethodBase {
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function assignPackages($force = FALSE) {
|
||||
$this->assignSubdirectoryByConfigTypes(InstallStorage::CONFIG_OPTIONAL_DIRECTORY);
|
||||
}
|
||||
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features\Plugin\FeaturesAssignment;
|
||||
|
||||
use Drupal\features\FeaturesAssignmentMethodBase;
|
||||
|
||||
/**
|
||||
* Class for assigning existing modules to packages.
|
||||
*
|
||||
* @Plugin(
|
||||
* id = "packages",
|
||||
* weight = -20,
|
||||
* name = @Translation("Packages"),
|
||||
* description = @Translation("Detect and add existing package modules."),
|
||||
* )
|
||||
*/
|
||||
class FeaturesAssignmentPackages extends FeaturesAssignmentMethodBase {
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function assignPackages($force = FALSE) {
|
||||
$bundle = $this->assigner->getBundle();
|
||||
$existing = $this->featuresManager->getFeaturesModules();
|
||||
foreach ($existing as $extension) {
|
||||
$package = $this->featuresManager->initPackageFromExtension($extension);
|
||||
$short_name = $package->getMachineName();
|
||||
|
||||
// Copy over package excluded settings, if any.
|
||||
if (!$package->getExcluded()) {
|
||||
$config_collection = $this->featuresManager->getConfigCollection();
|
||||
foreach ($package->getExcluded() as $config_name) {
|
||||
if (isset($config_collection[$config_name])) {
|
||||
$package_excluded = $config_collection[$config_name]->getPackageExcluded();
|
||||
$package_excluded[] = $short_name;
|
||||
$config_collection[$config_name]->setPackageExcluded($package_excluded);
|
||||
}
|
||||
}
|
||||
$this->featuresManager->setConfigCollection($config_collection);
|
||||
}
|
||||
|
||||
// Assign required components, if any.
|
||||
if ($package->getRequired() !== FALSE) {
|
||||
$config = $package->getRequired();
|
||||
if (empty($config) || !is_array($config)) {
|
||||
// if required is "true" or empty, add all config as required
|
||||
$config = $this->featuresManager->listExtensionConfig($extension);
|
||||
}
|
||||
$this->featuresManager->assignConfigPackage($short_name, $config);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features\Plugin\FeaturesAssignment;
|
||||
|
||||
use Drupal\features\FeaturesAssignmentMethodBase;
|
||||
use Drupal\Core\Config\InstallStorage;
|
||||
|
||||
/**
|
||||
* Class for adding configuration for the optional install profile.
|
||||
*
|
||||
* @Plugin(
|
||||
* id = "profile",
|
||||
* weight = 10,
|
||||
* name = @Translation("Profile"),
|
||||
* description = @Translation("Add configuration and other files to the optional install profile from the Drupal core Standard install profile. Without these additions, a generated install profile will be missing some important initial setup."),
|
||||
* config_route_name = "features.assignment_profile",
|
||||
* default_settings = {
|
||||
* "curated" = FALSE,
|
||||
* "standard" = {
|
||||
* "files" = FALSE,
|
||||
* "dependencies" = FALSE,
|
||||
* },
|
||||
* "types" = { "config" = {} }
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class FeaturesAssignmentProfile extends FeaturesAssignmentMethodBase {
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function assignPackages($force = FALSE) {
|
||||
$current_bundle = $this->assigner->getBundle();
|
||||
|
||||
if ($current_bundle->isProfile()) {
|
||||
$settings = $current_bundle->getAssignmentSettings($this->getPluginId());
|
||||
|
||||
// Ensure the profile package exists.
|
||||
$profile_name = $current_bundle->getProfileName();
|
||||
|
||||
$profile_package = $this->featuresManager->getPackage($profile_name);
|
||||
if (empty($profile_package)) {
|
||||
$profile_package = $this->featuresManager->initPackage($profile_name, $current_bundle->getName(), $current_bundle->getDescription(), 'profile', $current_bundle);
|
||||
}
|
||||
|
||||
// Assign configuration by type.
|
||||
$this->assignPackageByConfigTypes($profile_name, $force);
|
||||
|
||||
// Include a curated list of configuration.
|
||||
if ($settings['curated']) {
|
||||
$config_collection = $this->featuresManager->getConfigCollection();
|
||||
$item_names = [
|
||||
'automated_cron.settings',
|
||||
'system.cron',
|
||||
'system.theme',
|
||||
];
|
||||
$theme_settings = $this->configFactory->get('system.theme');
|
||||
foreach (['default', 'admin'] as $key) {
|
||||
$item_names[] = $theme_settings->get($key) . '.settings';
|
||||
}
|
||||
foreach ($item_names as $item_name) {
|
||||
if (isset($config_collection[$item_name])) {
|
||||
try {
|
||||
$this->featuresManager->assignConfigPackage($profile_name, [$item_name]);
|
||||
}
|
||||
catch (\Exception $exception) {
|
||||
\Drupal::logger('features')->error($exception->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only read in from the Standard profile if this profile doesn't already
|
||||
// exist.
|
||||
$package_directories = $this->featuresManager->listPackageDirectories(array(), $current_bundle);
|
||||
if (!isset($package_directories[$profile_name])) {
|
||||
$standard_directory = 'core/profiles/standard';
|
||||
// Conditionally add files from the 'standard' install profile.
|
||||
if ($settings['standard']['files']) {
|
||||
// Add configuration from the Standard profile.
|
||||
$config_collection = $this->featuresManager->getConfigCollection();
|
||||
$subdirectory = InstallStorage::CONFIG_INSTALL_DIRECTORY;
|
||||
$item_names = $this->listRequiredStandardConfig();
|
||||
foreach ($item_names as $item_name) {
|
||||
// If the configuration is present on the site, assign it.
|
||||
if (isset($config_collection[$item_name])) {
|
||||
// Only assign it if it's not already assigned to a package.
|
||||
// @todo: if it's provided by a module, add a dependency.
|
||||
if (!$config_collection[$item_name]->getPackage()) {
|
||||
$this->featuresManager->assignConfigPackage($profile_name, [$item_name], $force);
|
||||
// Reload the profile to refresh the config array after the addition.
|
||||
$profile_package = $this->featuresManager->getPackage($profile_name);
|
||||
}
|
||||
// If it's already assigned to a package in the current bundle,
|
||||
// add a dependency.
|
||||
else {
|
||||
$machine_name = $current_bundle->getFullName($config_collection[$item_name]->getPackage());
|
||||
if (!in_array($machine_name, $profile_package->getDependencies())) {
|
||||
$profile_package->appendDependency($machine_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Otherwise, copy it over from Standard.
|
||||
else {
|
||||
$filename = $item_name . '.yml';
|
||||
$profile_package->appendFile([
|
||||
'filename' => $filename,
|
||||
'subdirectory' => $subdirectory,
|
||||
'string' => file_get_contents($standard_directory . '/' . $subdirectory . '/' . $filename)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// Add .profile and .install files from Standard.
|
||||
$files = [
|
||||
'install',
|
||||
'profile',
|
||||
];
|
||||
// Iterate through the files.
|
||||
foreach ($files as $extension) {
|
||||
$filename = $standard_directory . '/standard.' . $extension;
|
||||
if (file_exists($filename)) {
|
||||
// Read the file contents.
|
||||
$string = file_get_contents($filename);
|
||||
// Substitute the profile's machine name and name for the Standard
|
||||
// profile's equivalents.
|
||||
$string = str_replace(
|
||||
['standard', 'Standard'],
|
||||
[$profile_name, $current_bundle->getName()],
|
||||
$string
|
||||
);
|
||||
// Add the files to those to be output.
|
||||
$profile_package->appendFile([
|
||||
'filename' => $profile_name . '.' . $extension,
|
||||
'subdirectory' => NULL,
|
||||
'string' => $string
|
||||
], $extension);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Conditionally merge in module and theme dependencies from the
|
||||
// 'standard' install profile.
|
||||
if ($settings['standard']['dependencies']) {
|
||||
$info_file_uri = $standard_directory . '/standard.info.yml';
|
||||
if (file_exists($info_file_uri)) {
|
||||
$profile_info = \Drupal::service('info_parser')->parse($info_file_uri);
|
||||
$info = [
|
||||
'dependencies' => $profile_package->getDependencies(),
|
||||
'themes' => $profile_package->getThemes(),
|
||||
];
|
||||
$info = $this->featuresManager->mergeInfoArray($info, $profile_info);
|
||||
$profile_package->setDependencies($info['dependencies']);
|
||||
$profile_package->setThemes($info['themes']);
|
||||
}
|
||||
}
|
||||
$this->featuresManager->setPackage($profile_package);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of configuration items required by the Standard install
|
||||
* profile.
|
||||
*
|
||||
* If install code is adapted from the Standard profile, these configuration
|
||||
* items will be required.
|
||||
*
|
||||
* @return array
|
||||
* An array of configuration item names.
|
||||
*/
|
||||
protected function listRequiredStandardConfig() {
|
||||
return [
|
||||
'contact.form.feedback',
|
||||
'user.role.administrator'
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features\Plugin\FeaturesAssignment;
|
||||
|
||||
use Drupal\features\FeaturesAssignmentMethodBase;
|
||||
|
||||
/**
|
||||
* Class for assigning configuration to a site package based on entity types.
|
||||
*
|
||||
* @Plugin(
|
||||
* id = "site",
|
||||
* weight = 7,
|
||||
* name = @Translation("Site type"),
|
||||
* description = @Translation("Assign designated types of configuration to a site configuration package module. For example, if image styles are selected as a site type, a site package will be generated and image styles will be assigned to it."),
|
||||
* config_route_name = "features.assignment_site",
|
||||
* default_settings = {
|
||||
* "types" = {
|
||||
* "config" = {},
|
||||
* }
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class FeaturesAssignmentSiteType extends FeaturesAssignmentMethodBase {
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function assignPackages($force = FALSE) {
|
||||
$current_bundle = $this->assigner->getBundle();
|
||||
$machine_name = 'site';
|
||||
$name = $this->t('Site');
|
||||
$description = $this->t('Provides site components.');
|
||||
$this->featuresManager->initPackage($machine_name, $name, $description, 'module', $current_bundle);
|
||||
$this->assignPackageByConfigTypes($machine_name, $force);
|
||||
}
|
||||
|
||||
}
|
||||
+270
@@ -0,0 +1,270 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features\Plugin\FeaturesGeneration;
|
||||
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\features\FeaturesGenerationMethodBase;
|
||||
use Drupal\Core\Archiver\ArchiveTar;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\features\FeaturesBundleInterface;
|
||||
use Drupal\features\Package;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Class for generating a compressed archive of packages.
|
||||
*
|
||||
* @Plugin(
|
||||
* id = \Drupal\features\Plugin\FeaturesGeneration\FeaturesGenerationArchive::METHOD_ID,
|
||||
* weight = -2,
|
||||
* name = @Translation("Download Archive"),
|
||||
* description = @Translation("Generate packages and optional profile as a compressed archive for download."),
|
||||
* )
|
||||
*/
|
||||
class FeaturesGenerationArchive extends FeaturesGenerationMethodBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The CSRF token generator.
|
||||
*
|
||||
* @var \Drupal\Core\Access\CsrfTokenGenerator
|
||||
*/
|
||||
protected $csrfToken;
|
||||
|
||||
/**
|
||||
* Creates a new FeaturesGenerationArchive instance.
|
||||
*
|
||||
* @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
|
||||
* The CSRF token generator.
|
||||
*/
|
||||
public function __construct(\Drupal\Core\Access\CsrfTokenGenerator $csrf_token) {
|
||||
$this->csrfToken = $csrf_token;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$container->get('csrf_token')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The package generation method id.
|
||||
*/
|
||||
const METHOD_ID = 'archive';
|
||||
|
||||
/**
|
||||
* The filename being written.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $archiveName;
|
||||
|
||||
/**
|
||||
* Reads and merges in existing files for a given package or profile.
|
||||
*/
|
||||
protected function preparePackage(Package $package, array $existing_packages, FeaturesBundleInterface $bundle = NULL) {
|
||||
if (isset($existing_packages[$package->getMachineName()])) {
|
||||
$existing_directory = $existing_packages[$package->getMachineName()];
|
||||
// Scan for all files.
|
||||
$files = file_scan_directory($existing_directory, '/.*/');
|
||||
foreach ($files as $file) {
|
||||
// Skip files in the any existing configuration directory, as these
|
||||
// will be replaced.
|
||||
foreach (array_keys($this->featuresManager->getExtensionStorages()->getExtensionStorages()) as $directory) {
|
||||
if (strpos($file->uri, $directory) !== FALSE) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
// Merge in the info file.
|
||||
if ($file->name == $package->getMachineName() . '.info') {
|
||||
$files = $package->getFiles();
|
||||
$files['info']['string'] = $this->mergeInfoFile($package->getFiles()['info']['string'], $file->uri);
|
||||
$package->setFiles($files);
|
||||
}
|
||||
// Read in remaining files.
|
||||
else {
|
||||
// Determine if the file is within a subdirectory of the
|
||||
// extension's directory.
|
||||
$file_directory = dirname($file->uri);
|
||||
if ($file_directory !== $existing_directory) {
|
||||
$subdirectory = substr($file_directory, strlen($existing_directory) + 1);
|
||||
}
|
||||
else {
|
||||
$subdirectory = NULL;
|
||||
}
|
||||
$package->appendFile([
|
||||
'filename' => $file->filename,
|
||||
'subdirectory' => $subdirectory,
|
||||
'string' => file_get_contents($file->uri)
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function generate(array $packages = array(), FeaturesBundleInterface $bundle = NULL) {
|
||||
|
||||
// If no packages were specified, get all packages.
|
||||
if (empty($packages)) {
|
||||
$packages = $this->featuresManager->getPackages();
|
||||
}
|
||||
|
||||
// Determine the best name for the tar archive.
|
||||
// Single package export, so name by package name.
|
||||
if (count($packages) == 1) {
|
||||
$filename = current($packages)->getMachineName();
|
||||
}
|
||||
// Profile export, so name by profile.
|
||||
elseif (isset($bundle) && $bundle->isProfile()) {
|
||||
$filename = $bundle->getProfileName();
|
||||
}
|
||||
// Non-default bundle, so name by bundle.
|
||||
elseif (isset($bundle) && !$bundle->isDefault()) {
|
||||
$filename = $bundle->getMachineName();
|
||||
}
|
||||
// Set a fallback name.
|
||||
else {
|
||||
$filename = 'generated_features';
|
||||
}
|
||||
|
||||
$return = [];
|
||||
|
||||
$this->archiveName = $filename . '.tar.gz';
|
||||
$archive_name = file_directory_temp() . '/' . $this->archiveName;
|
||||
if (file_exists($archive_name)) {
|
||||
file_unmanaged_delete($archive_name);
|
||||
}
|
||||
|
||||
$archiver = new ArchiveTar($archive_name);
|
||||
|
||||
// Add package files.
|
||||
foreach ($packages as $package) {
|
||||
if (count($packages) == 1) {
|
||||
// Single module export, so don't generate entire modules dir structure.
|
||||
$package->setDirectory($package->getMachineName());
|
||||
}
|
||||
$this->generatePackage($return, $package, $archiver);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a package or profile's files to an archive.
|
||||
*
|
||||
* @param array &$return
|
||||
* The return value, passed by reference.
|
||||
* @param \Drupal\features\Package $package
|
||||
* The package or profile.
|
||||
* @param ArchiveTar $archiver
|
||||
* The archiver.
|
||||
*/
|
||||
protected function generatePackage(array &$return, Package $package, ArchiveTar $archiver) {
|
||||
$success = TRUE;
|
||||
foreach ($package->getFiles() as $file) {
|
||||
try {
|
||||
$this->generateFile($package->getDirectory(), $file, $archiver);
|
||||
}
|
||||
catch (\Exception $exception) {
|
||||
$this->failure($return, $package, $exception);
|
||||
$success = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($success) {
|
||||
$this->success($return, $package);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a successful package or profile archive operation.
|
||||
*
|
||||
* @param array &$return
|
||||
* The return value, passed by reference.
|
||||
* @param \Drupal\features\Package $package
|
||||
* The package or profile.
|
||||
*/
|
||||
protected function success(array &$return, Package $package) {
|
||||
$type = $package->getType() == 'module' ? $this->t('Package') : $this->t('Profile');
|
||||
$return[] = [
|
||||
'success' => TRUE,
|
||||
// Archive writing doesn't merit a message, and if done through the UI
|
||||
// would appear on the subsequent page load.
|
||||
'display' => FALSE,
|
||||
'message' => '@type @package written to archive.',
|
||||
'variables' => [
|
||||
'@type' => $type,
|
||||
'@package' => $package->getName(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a failed package or profile archive operation.
|
||||
*
|
||||
* @param array &$return
|
||||
* The return value, passed by reference.
|
||||
* @param \Drupal\features\Package $package
|
||||
* The package or profile.
|
||||
* @param \Exception $exception
|
||||
* The exception object.
|
||||
* @param string $message
|
||||
* Error message when there isn't an Exception object.
|
||||
*/
|
||||
protected function failure(array &$return, Package $package, \Exception $exception = NULL, $message = '') {
|
||||
$type = $package->getType() == 'module' ? $this->t('Package') : $this->t('Profile');
|
||||
$return[] = [
|
||||
'success' => FALSE,
|
||||
// Archive writing doesn't merit a message, and if done through the UI
|
||||
// would appear on the subsequent page load.
|
||||
'display' => FALSE,
|
||||
'message' => '@type @package not written to archive. Error: @error.',
|
||||
'variables' => [
|
||||
'@type' => $type,
|
||||
'@package' => $package->getName(),
|
||||
'@error' => isset($exception) ? $exception->getMessage() : $message,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a file to the file system, creating its directory as needed.
|
||||
*
|
||||
* @param string $directory
|
||||
* The extension's directory.
|
||||
* @param array $file
|
||||
* Array with the following keys:
|
||||
* - 'filename': the name of the file.
|
||||
* - 'subdirectory': any subdirectory of the file within the extension
|
||||
* directory.
|
||||
* - 'string': the contents of the file.
|
||||
* @param ArchiveTar $archiver
|
||||
* The archiver.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function generateFile($directory, array $file, ArchiveTar $archiver) {
|
||||
$filename = $directory;
|
||||
if (!empty($file['subdirectory'])) {
|
||||
$filename .= '/' . $file['subdirectory'];
|
||||
}
|
||||
$filename .= '/' . $file['filename'];
|
||||
// Set the mode to 0644 rather than the default of 0600.
|
||||
if ($archiver->addString($filename, $file['string'], FALSE, ['mode' => 0644]) === FALSE) {
|
||||
throw new \Exception($this->t('Failed to archive file @filename.', ['@filename' => $file['filename']]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function exportFormSubmit(array &$form, FormStateInterface $form_state) {
|
||||
// Redirect to the archive file download.
|
||||
$form_state->setRedirect('features.export_download', ['uri' => $this->archiveName, 'token' => $this->csrfToken->get($this->archiveName)]);
|
||||
}
|
||||
|
||||
}
|
||||
+225
@@ -0,0 +1,225 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features\Plugin\FeaturesGeneration;
|
||||
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\features\FeaturesGenerationMethodBase;
|
||||
use Drupal\features\FeaturesBundleInterface;
|
||||
use Drupal\features\Package;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Class for writing packages to the local file system.
|
||||
*
|
||||
* @Plugin(
|
||||
* id = \Drupal\features\Plugin\FeaturesGeneration\FeaturesGenerationWrite::METHOD_ID,
|
||||
* weight = 2,
|
||||
* name = @Translation("Write"),
|
||||
* description = @Translation("Write packages and optional profile to the file system."),
|
||||
* )
|
||||
*/
|
||||
class FeaturesGenerationWrite extends FeaturesGenerationMethodBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The package generation method id.
|
||||
*/
|
||||
const METHOD_ID = 'write';
|
||||
|
||||
/**
|
||||
* The app root.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $root;
|
||||
|
||||
/**
|
||||
* Creates a new FeaturesGenerationWrite instance.
|
||||
*
|
||||
* @param string $root
|
||||
* The app root.
|
||||
*/
|
||||
public function __construct($root) {
|
||||
$this->root = $root;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$container->get('app.root')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads and merges in existing files for a given package or profile.
|
||||
*
|
||||
* @param \Drupal\features\Package &$package
|
||||
* The package.
|
||||
* @param array $existing_packages
|
||||
* An array of existing packages.
|
||||
* @param \Drupal\features\FeaturesBundleInterface $bundle
|
||||
* The bundle the package belongs to.
|
||||
*/
|
||||
protected function preparePackage(Package $package, array $existing_packages, FeaturesBundleInterface $bundle = NULL) {
|
||||
// If this package is already present, prepare files.
|
||||
if (isset($existing_packages[$package->getMachineName()])) {
|
||||
$existing_directory = $existing_packages[$package->getMachineName()];
|
||||
|
||||
$package->setDirectory($existing_directory);
|
||||
|
||||
// Merge in the info file.
|
||||
$info_file_uri = $this->root . '/' . $existing_directory . '/' . $package->getMachineName() . '.info.yml';
|
||||
if (file_exists($info_file_uri)) {
|
||||
$files = $package->getFiles();
|
||||
$files['info']['string'] = $this->mergeInfoFile($package->getFiles()['info']['string'], $info_file_uri);
|
||||
$package->setFiles($files);
|
||||
}
|
||||
|
||||
// Remove the config directories, as they will be replaced.
|
||||
foreach (array_keys($this->featuresManager->getExtensionStorages()->getExtensionStorages()) as $directory) {
|
||||
$config_directory = $this->root . '/' . $existing_directory . '/' . $directory;
|
||||
if (is_dir($config_directory)) {
|
||||
file_unmanaged_delete_recursive($config_directory);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function generate(array $packages = array(), FeaturesBundleInterface $bundle = NULL) {
|
||||
// If no packages were specified, get all packages.
|
||||
if (empty($packages)) {
|
||||
$packages = $this->featuresManager->getPackages();
|
||||
}
|
||||
|
||||
$return = [];
|
||||
|
||||
// Add package files.
|
||||
// We need to update the system.module.files state because it's cached.
|
||||
// Cannot just call system_rebuild_module_data() because $listing->scan() has
|
||||
// it's own internal static cache that we cannot clear at this point.
|
||||
$files = \Drupal::state()->get('system.module.files');
|
||||
foreach ($packages as $package) {
|
||||
$this->generatePackage($return, $package);
|
||||
if (!isset($files[$package->getMachineName()]) && isset($package->getFiles()['info'])) {
|
||||
$files[$package->getMachineName()] = $package->getDirectory() . '/' . $package->getFiles()['info']['filename'];
|
||||
}
|
||||
}
|
||||
|
||||
// Rebuild system module cache
|
||||
\Drupal::state()->set('system.module.files', $files);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a package or profile's files to the file system.
|
||||
*
|
||||
* @param array &$return
|
||||
* The return value, passed by reference.
|
||||
* @param \Drupal\features\Package $package
|
||||
* The package or profile.
|
||||
*/
|
||||
protected function generatePackage(array &$return, Package $package) {
|
||||
if (!$package->getFiles()) {
|
||||
$this->failure($return, $package, NULL, $this->t('No configuration was selected to be exported.'));
|
||||
return;
|
||||
}
|
||||
$success = TRUE;
|
||||
foreach ($package->getFiles() as $file) {
|
||||
try {
|
||||
$this->generateFile($package->getDirectory(), $file);
|
||||
}
|
||||
catch (\Exception $exception) {
|
||||
$this->failure($return, $package, $exception);
|
||||
$success = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($success) {
|
||||
$this->success($return, $package);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a successful package or profile write operation.
|
||||
*
|
||||
* @param array &$return
|
||||
* The return value, passed by reference.
|
||||
* @param \Drupal\features\Package $package
|
||||
* The package or profile.
|
||||
*/
|
||||
protected function success(array &$return, Package $package) {
|
||||
$type = $package->getType() == 'module' ? $this->t('Package') : $this->t('Profile');
|
||||
$return[] = [
|
||||
'success' => TRUE,
|
||||
'display' => TRUE,
|
||||
'message' => '@type @package written to @directory.',
|
||||
'variables' => [
|
||||
'@type' => $type,
|
||||
'@package' => $package->getName(),
|
||||
'@directory' => $package->getDirectory(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a failed package or profile write operation.
|
||||
*
|
||||
* @param array &$return
|
||||
* The return value, passed by reference.
|
||||
* @param \Drupal\features\Package $package
|
||||
* The package or profile.
|
||||
* @param \Exception $exception
|
||||
* The exception object.
|
||||
* @param string $message
|
||||
* Error message when there isn't an Exception object.
|
||||
*/
|
||||
protected function failure(array &$return, Package $package, \Exception $exception = NULL, $message = '') {
|
||||
$type = $package->getType() == 'module' ? $this->t('Package') : $this->t('Profile');
|
||||
$return[] = [
|
||||
'success' => FALSE,
|
||||
'display' => TRUE,
|
||||
'message' => '@type @package not written to @directory. Error: @error.',
|
||||
'variables' => [
|
||||
'@type' => $type,
|
||||
'@package' => $package->getName(),
|
||||
'@directory' => $package->getDirectory(),
|
||||
'@error' => isset($exception) ? $exception->getMessage() : $message,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a file to the file system, creating its directory as needed.
|
||||
*
|
||||
* @param string $directory
|
||||
* The extension's directory.
|
||||
* @param array $file
|
||||
* Array with the following keys:
|
||||
* - 'filename': the name of the file.
|
||||
* - 'subdirectory': any subdirectory of the file within the extension
|
||||
* directory.
|
||||
* - 'string': the contents of the file.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function generateFile($directory, array $file) {
|
||||
if (!empty($file['subdirectory'])) {
|
||||
$directory .= '/' . $file['subdirectory'];
|
||||
}
|
||||
$directory = $this->root . '/' . $directory;
|
||||
if (!is_dir($directory)) {
|
||||
if (drupal_mkdir($directory, NULL, TRUE) === FALSE) {
|
||||
throw new \Exception($this->t('Failed to create directory @directory.', ['@directory' => $directory]));
|
||||
}
|
||||
}
|
||||
if (file_put_contents($directory . '/' . $file['filename'], $file['string']) === FALSE) {
|
||||
throw new \Exception($this->t('Failed to write file @filename.', ['@filename' => $file['filename']]));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file was generated via php core/scripts/generate-proxy-class.php 'Drupal\features\FeaturesConfigInstaller' "modules/contrib/features/src".
|
||||
*/
|
||||
|
||||
namespace Drupal\features\ProxyClass {
|
||||
|
||||
/**
|
||||
* Provides a proxy class for \Drupal\features\FeaturesConfigInstaller.
|
||||
*
|
||||
* @see \Drupal\Component\ProxyBuilder
|
||||
*/
|
||||
class FeaturesConfigInstaller implements \Drupal\Core\Config\ConfigInstallerInterface
|
||||
{
|
||||
|
||||
use \Drupal\Core\DependencyInjection\DependencySerializationTrait;
|
||||
|
||||
/**
|
||||
* The id of the original proxied service.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $drupalProxyOriginalServiceId;
|
||||
|
||||
/**
|
||||
* The real proxied service, after it was lazy loaded.
|
||||
*
|
||||
* @var \Drupal\features\FeaturesConfigInstaller
|
||||
*/
|
||||
protected $service;
|
||||
|
||||
/**
|
||||
* The service container.
|
||||
*
|
||||
* @var \Symfony\Component\DependencyInjection\ContainerInterface
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* Constructs a ProxyClass Drupal proxy object.
|
||||
*
|
||||
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
|
||||
* The container.
|
||||
* @param string $drupal_proxy_original_service_id
|
||||
* The service ID of the original service.
|
||||
*/
|
||||
public function __construct(\Symfony\Component\DependencyInjection\ContainerInterface $container, $drupal_proxy_original_service_id)
|
||||
{
|
||||
$this->container = $container;
|
||||
$this->drupalProxyOriginalServiceId = $drupal_proxy_original_service_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazy loads the real service from the container.
|
||||
*
|
||||
* @return object
|
||||
* Returns the constructed real service.
|
||||
*/
|
||||
protected function lazyLoadItself()
|
||||
{
|
||||
if (!isset($this->service)) {
|
||||
$this->service = $this->container->get($this->drupalProxyOriginalServiceId);
|
||||
}
|
||||
|
||||
return $this->service;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function installDefaultConfig($type, $name)
|
||||
{
|
||||
return $this->lazyLoadItself()->installDefaultConfig($type, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function installOptionalConfig(\Drupal\Core\Config\StorageInterface $storage = NULL, $dependency = array (
|
||||
))
|
||||
{
|
||||
return $this->lazyLoadItself()->installOptionalConfig($storage, $dependency);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function installCollectionDefaultConfig($collection)
|
||||
{
|
||||
return $this->lazyLoadItself()->installCollectionDefaultConfig($collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setSourceStorage(\Drupal\Core\Config\StorageInterface $storage)
|
||||
{
|
||||
return $this->lazyLoadItself()->setSourceStorage($storage);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSourceStorage()
|
||||
{
|
||||
return $this->lazyLoadItself()->getSourceStorage();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setSyncing($status)
|
||||
{
|
||||
return $this->lazyLoadItself()->setSyncing($status);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isSyncing()
|
||||
{
|
||||
return $this->lazyLoadItself()->isSyncing();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function checkConfigurationToInstall($type, $name)
|
||||
{
|
||||
return $this->lazyLoadItself()->checkConfigurationToInstall($type, $name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user