FeaturesAssigner.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. <?php
  2. namespace Drupal\features;
  3. use Drupal\Component\Plugin\PluginManagerInterface;
  4. use Drupal\Core\Config\ConfigFactoryInterface;
  5. use Drupal\Core\Config\ExtensionInstallStorage;
  6. use Drupal\Core\Entity\EntityTypeManagerInterface;
  7. use Drupal\Core\Config\StorageInterface;
  8. use Drupal\Core\StringTranslation\StringTranslationTrait;
  9. use Drupal\features\Entity\FeaturesBundle;
  10. /**
  11. * Class responsible for performing package assignment.
  12. */
  13. class FeaturesAssigner implements FeaturesAssignerInterface {
  14. use StringTranslationTrait;
  15. /**
  16. * The package assignment method plugin manager.
  17. *
  18. * @var \Drupal\Component\Plugin\PluginManagerInterface
  19. */
  20. protected $assignerManager;
  21. /**
  22. * The features manager.
  23. *
  24. * @var \Drupal\features\FeaturesManagerInterface
  25. */
  26. protected $featuresManager;
  27. /**
  28. * The configuration factory.
  29. *
  30. * @var \Drupal\Core\Config\ConfigFactoryInterface
  31. */
  32. protected $configFactory;
  33. /**
  34. * The configuration storage.
  35. *
  36. * @var \Drupal\Core\Config\StorageInterface
  37. */
  38. protected $configStorage;
  39. /**
  40. * The entity type manager.
  41. *
  42. * @var \Drupal\Core\Entity\EntityTypeManagerInterface
  43. */
  44. protected $entityTypeManager;
  45. /**
  46. * Local cache for package assignment method instances.
  47. *
  48. * @var array
  49. */
  50. protected $methods;
  51. /**
  52. * Bundles.
  53. *
  54. * @var array of \Drupal\features\FeaturesBundleInterface
  55. */
  56. protected $bundles;
  57. /**
  58. * Currently active bundle.
  59. *
  60. * @var \Drupal\features\FeaturesBundleInterface
  61. */
  62. protected $currentBundle;
  63. /**
  64. * Constructs a new FeaturesAssigner object.
  65. *
  66. * @param \Drupal\features\FeaturesManagerInterface $features_manager
  67. * The features manager.
  68. * @param \Drupal\Component\Plugin\PluginManagerInterface $assigner_manager
  69. * The package assignment methods plugin manager.
  70. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
  71. * The entity type manager.
  72. * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
  73. * The configuration factory.
  74. * @param \Drupal\Core\Config\StorageInterface $config_storage
  75. * The configuration factory.
  76. */
  77. public function __construct(FeaturesManagerInterface $features_manager, PluginManagerInterface $assigner_manager, EntityTypeManagerInterface $entity_type_manager, ConfigFactoryInterface $config_factory, StorageInterface $config_storage) {
  78. $this->featuresManager = $features_manager;
  79. $this->assignerManager = $assigner_manager;
  80. $this->entityTypeManager = $entity_type_manager;
  81. $this->configFactory = $config_factory;
  82. $this->configStorage = $config_storage;
  83. $this->bundles = $this->getBundleList();
  84. $this->currentBundle = $this->getBundle(FeaturesBundleInterface::DEFAULT_BUNDLE);
  85. // Ensure bundle information is fresh.
  86. $this->createBundlesFromPackages();
  87. }
  88. /**
  89. * Initializes the injected features manager with the assigner.
  90. *
  91. * This should be called right after instantiating the assigner to make it
  92. * available to the features manager without introducing a circular
  93. * dependency.
  94. */
  95. public function initFeaturesManager() {
  96. $this->featuresManager->setAssigner($this);
  97. }
  98. /**
  99. * {@inheritdoc}
  100. */
  101. public function reset() {
  102. $this->methods = [];
  103. $this->featuresManager->reset();
  104. }
  105. /**
  106. * Gets enabled assignment methods.
  107. *
  108. * @return array
  109. * An array of enabled assignment methods, sorted by weight.
  110. */
  111. public function getEnabledAssigners() {
  112. $enabled = $this->currentBundle->getEnabledAssignments();
  113. $weights = $this->currentBundle->getAssignmentWeights();
  114. foreach ($enabled as $key => $value) {
  115. $enabled[$key] = $weights[$key];
  116. }
  117. asort($enabled);
  118. return $enabled;
  119. }
  120. /**
  121. * Clean up the package list after all config has been assigned
  122. */
  123. protected function cleanup() {
  124. $packages = $this->featuresManager->getPackages();
  125. foreach ($packages as $index => $package) {
  126. if ($package->getStatus() === FeaturesManagerInterface::STATUS_NO_EXPORT && empty($package->getConfig()) && empty($package->getConfigOrig())) {
  127. unset($packages[$index]);
  128. }
  129. }
  130. $this->featuresManager->setPackages($packages);
  131. }
  132. /**
  133. * {@inheritdoc}
  134. */
  135. public function assignConfigPackages($force = FALSE) {
  136. foreach ($this->getEnabledAssigners() as $method_id => $info) {
  137. $this->applyAssignmentMethod($method_id, $force);
  138. }
  139. $this->cleanup();
  140. }
  141. /**
  142. * {@inheritdoc}
  143. */
  144. public function applyAssignmentMethod($method_id, $force = FALSE) {
  145. $this->getAssignmentMethodInstance($method_id)->assignPackages($force);
  146. }
  147. /**
  148. * {@inheritdoc}
  149. */
  150. public function getAssignmentMethods() {
  151. return $this->assignerManager->getDefinitions();
  152. }
  153. /**
  154. * Returns an instance of the specified package assignment method.
  155. *
  156. * @param string $method_id
  157. * The string identifier of the package assignment method to use to package
  158. * configuration.
  159. *
  160. * @return \Drupal\features\FeaturesAssignmentMethodInterface
  161. */
  162. protected function getAssignmentMethodInstance($method_id) {
  163. if (!isset($this->methods[$method_id])) {
  164. $instance = $this->assignerManager->createInstance($method_id, []);
  165. $instance->setFeaturesManager($this->featuresManager);
  166. $instance->setAssigner($this);
  167. $instance->setEntityTypeManager($this->entityTypeManager);
  168. $instance->setConfigFactory($this->configFactory);
  169. $this->methods[$method_id] = $instance;
  170. }
  171. return $this->methods[$method_id];
  172. }
  173. /**
  174. * {@inheritdoc}
  175. */
  176. public function purgeConfiguration() {
  177. // Ensure that we are getting the defined package assignment information.
  178. // An invocation of \Drupal\Core\Extension\ModuleHandler::install() or
  179. // \Drupal\Core\Extension\ModuleHandler::uninstall() could invalidate the
  180. // cached information.
  181. $this->assignerManager->clearCachedDefinitions();
  182. $this->featuresManager->reset();
  183. }
  184. /**
  185. * {@inheritdoc}
  186. */
  187. public function getBundle($name = NULL) {
  188. if (empty($name)) {
  189. return $this->currentBundle;
  190. }
  191. elseif (isset($this->bundles[$name])) {
  192. return $this->bundles[$name];
  193. }
  194. return NULL;
  195. }
  196. /**
  197. * {@inheritdoc}
  198. */
  199. public function setBundle(FeaturesBundleInterface $bundle, $current = TRUE) {
  200. $this->bundles[$bundle->getMachineName()] = $bundle;
  201. if (isset($this->currentBundle) && ($current || ($bundle->getMachineName() == $this->currentBundle->getMachineName()))) {
  202. $this->currentBundle = $bundle;
  203. }
  204. }
  205. /**
  206. * {@inheritdoc}
  207. */
  208. public function findBundle(array $info, $features_info = NULL) {
  209. $bundle = NULL;
  210. if (!empty($features_info['bundle'])) {
  211. $bundle = $this->getBundle($features_info['bundle']);
  212. }
  213. elseif (!empty($info['package'])) {
  214. $bundle = $this->findBundleByName($info['package']);
  215. }
  216. if (!isset($bundle)) {
  217. // Return the default bundle.
  218. return $this->getBundle(FeaturesBundleInterface::DEFAULT_BUNDLE);
  219. }
  220. return $bundle;
  221. }
  222. /**
  223. * {@inheritdoc}
  224. */
  225. public function setCurrent(FeaturesBundleInterface $bundle) {
  226. $this->currentBundle = $bundle;
  227. $session = \Drupal::request()->getSession();
  228. if (isset($session)) {
  229. $session->set('features_current_bundle', $bundle->getMachineName());
  230. }
  231. return $bundle;
  232. }
  233. /**
  234. * {@inheritdoc}
  235. */
  236. public function getBundleList() {
  237. if (empty($this->bundles)) {
  238. $this->bundles = [];
  239. foreach ($this->entityTypeManager->getStorage('features_bundle')->loadMultiple() as $machine_name => $bundle) {
  240. $this->bundles[$machine_name] = $bundle;
  241. }
  242. }
  243. return $this->bundles;
  244. }
  245. /**
  246. * {@inheritdoc}
  247. */
  248. public function findBundleByName($name, $create = FALSE) {
  249. $bundles = $this->getBundleList();
  250. foreach ($bundles as $machine_name => $bundle) {
  251. if ($name == $bundle->getName()) {
  252. return $bundle;
  253. }
  254. }
  255. $machine_name = strtolower(str_replace([' ', '-'], '_', $name));
  256. if (isset($bundles[$machine_name])) {
  257. return $bundles[$machine_name];
  258. }
  259. return NULL;
  260. }
  261. /**
  262. * {@inheritdoc}
  263. */
  264. public function createBundleFromDefault($machine_name, $name = NULL, $description = NULL, $is_profile = FALSE, $profile_name = NULL) {
  265. // Duplicate the default bundle to get its default configuration.
  266. $default = $this->getBundle(FeaturesBundleInterface::DEFAULT_BUNDLE);
  267. if (!$default) {
  268. // If we don't have the default installed, generate it from the install
  269. // config file.
  270. $ext_storage = new ExtensionInstallStorage($this->configStorage);
  271. $record = $ext_storage->read('features.bundle.default');
  272. $bundle_storage = $this->entityTypeManager->getStorage('features_bundle');
  273. $default = $bundle_storage->createFromStorageRecord($record);
  274. }
  275. /** @var \Drupal\features\Entity\FeaturesBundle $bundle */
  276. $bundle = $default->createDuplicate();
  277. $bundle->setMachineName($machine_name);
  278. $name = !empty($name) ? $name : $machine_name;
  279. $bundle->setName($name);
  280. if (isset($description)) {
  281. $bundle->setDescription($description);
  282. }
  283. else {
  284. $bundle->setDescription(t('Auto-generated bundle from package @name', ['@name' => $name]));
  285. }
  286. $bundle->setIsProfile($is_profile);
  287. if (isset($profile_name)) {
  288. $bundle->setProfileName($profile_name);
  289. }
  290. $bundle->save();
  291. $this->setBundle($bundle);
  292. return $bundle;
  293. }
  294. /**
  295. * {@inheritdoc}
  296. */
  297. public function createBundlesFromPackages() {
  298. $existing_bundles = $this->getBundleList();
  299. $new_bundles = [];
  300. // Only parse from installed features.
  301. $modules = $this->featuresManager->getFeaturesModules(NULL, TRUE);
  302. foreach ($modules as $module) {
  303. $info = $this->featuresManager->getExtensionInfo($module);
  304. // @todo This entire function could be simplified a lot using packages.
  305. $features_info = $this->featuresManager->getFeaturesInfo($module);
  306. // Create a new bundle if:
  307. // - the feature specifies a bundle and
  308. // - that bundle doesn't yet exist locally.
  309. // Allow profiles to override previous values.
  310. if (!empty($features_info['bundle']) &&
  311. !isset($existing_bundles[$features_info['bundle']]) &&
  312. (!in_array($features_info['bundle'], $new_bundles) || $info['type'] == 'profile')) {
  313. if ($info['type'] == 'profile') {
  314. $new_bundle = [
  315. 'name' => $info['name'],
  316. 'description' => $info['description'],
  317. 'is_profile' => TRUE,
  318. 'profile_name' => $module->getName(),
  319. ];
  320. }
  321. else {
  322. $new_bundle = [
  323. 'name' => isset($info['package']) ? $info['package'] : ucwords(str_replace('_', ' ', $features_info['bundle'])),
  324. 'description' => NULL,
  325. 'is_profile' => FALSE,
  326. 'profile_name' => NULL,
  327. ];
  328. }
  329. $new_bundle['machine_name'] = $features_info['bundle'];
  330. $new_bundles[$new_bundle['machine_name']] = $new_bundle;
  331. }
  332. }
  333. foreach ($new_bundles as $new_bundle) {
  334. $this->createBundleFromDefault($new_bundle['machine_name'], $new_bundle['name'], $new_bundle['description'], $new_bundle['is_profile']);
  335. }
  336. }
  337. /**
  338. * {@inheritdoc}
  339. */
  340. public function getBundleOptions() {
  341. $list = $this->getBundleList();
  342. $result = [];
  343. foreach ($list as $machine_name => $bundle) {
  344. $result[$machine_name] = $bundle->getName();
  345. }
  346. return $result;
  347. }
  348. /**
  349. * {@inheritdoc}
  350. */
  351. public function applyBundle($machine_name = NULL) {
  352. $this->reset();
  353. $bundle = $this->loadBundle($machine_name);
  354. if (isset($bundle)) {
  355. $this->assignConfigPackages();
  356. return $this->currentBundle;
  357. }
  358. return NULL;
  359. }
  360. /**
  361. * {@inheritdoc}
  362. */
  363. public function renameBundle($old_machine, $new_machine) {
  364. $is_current = (isset($this->currentBundle) && ($old_machine == $this->currentBundle->getMachineName()));
  365. $bundle = $this->getBundle($old_machine);
  366. if ($bundle->getMachineName() != '') {
  367. // Remove old bundle from the list if it's not the Default bundle.
  368. unset($this->bundles[$old_machine]);
  369. }
  370. $bundle->setMachineName($new_machine);
  371. $this->setBundle($bundle);
  372. // Put the bundle into the list with the correct name.
  373. $this->bundles[$bundle->getMachineName()] = $bundle;
  374. if ($is_current) {
  375. $this->setCurrent($bundle);
  376. }
  377. return $bundle;
  378. }
  379. /**
  380. * {@inheritdoc}
  381. */
  382. public function loadBundle($machine_name = NULL) {
  383. if (!isset($machine_name)) {
  384. $session = \Drupal::request()->getSession();
  385. if (isset($session)) {
  386. $machine_name = isset($session) ? $session->get('features_current_bundle', FeaturesBundleInterface::DEFAULT_BUNDLE) : FeaturesBundleInterface::DEFAULT_BUNDLE;
  387. }
  388. }
  389. $bundle = $this->getBundle($machine_name);
  390. if (!isset($bundle)) {
  391. // If bundle no longer exists then return default.
  392. $bundle = $this->bundles[FeaturesBundleInterface::DEFAULT_BUNDLE];
  393. }
  394. return $this->setCurrent($bundle);
  395. }
  396. /**
  397. * {@inheritdoc}
  398. */
  399. public function removeBundle($machine_name) {
  400. $bundle = $this->getBundle($machine_name);
  401. if (isset($bundle) && !$bundle->isDefault()) {
  402. unset($this->bundles[$machine_name]);
  403. $bundle->remove();
  404. }
  405. }
  406. }