features.drush8.inc 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912
  1. <?php
  2. /**
  3. * @file
  4. * Features module drush integration.
  5. */
  6. use Drupal\features\ConfigurationItem;
  7. use Drupal\features\FeaturesBundleInterface;
  8. use Drupal\features\FeaturesManagerInterface;
  9. use Drupal\features\Plugin\FeaturesGeneration\FeaturesGenerationWrite;
  10. use Drupal\Component\Diff\DiffFormatter;
  11. /**
  12. * Implements hook_drush_command().
  13. */
  14. function features_drush_command() {
  15. $items = [];
  16. $items['features-status'] = [
  17. 'description' => 'Display current Features settings.',
  18. 'aliases' => ['fs'],
  19. ];
  20. $items['features-list-packages'] = [
  21. 'description' => 'Display a list of all existing features and packages available to be generated. If a package name is provided as an argument, then all of the configuration objects assigned to that package will be listed.',
  22. 'examples' => [
  23. "drush features-list-packages" => 'Display a list of all existing featurea and packages available to be generated.',
  24. "drush features-list-packages 'example_article'" => "Display a list of all configuration objects assigned to the 'example_article' package.",
  25. ],
  26. 'arguments' => [
  27. 'package' => 'The package to list. Optional; if specified, lists all configuration objects assigned to that package. If no package is specified, lists all of the features.',
  28. ],
  29. 'outputformat' => [
  30. 'default' => 'table',
  31. 'pipe-format' => 'list',
  32. 'field-labels' => [
  33. 'name' => 'Name',
  34. 'machine_name' => 'Machine name',
  35. 'status' => 'Status',
  36. 'version' => 'Version',
  37. 'state' => 'State',
  38. 'object' => 'Configuration object',
  39. ],
  40. 'output-data-type' => 'format-table',
  41. ],
  42. 'aliases' => ['fl'],
  43. ];
  44. $items['features-import-all'] = [
  45. 'description' => 'Import module config from all installed features.',
  46. 'examples' => [
  47. "drush features-import-all" => 'Import module config from all installed features.',
  48. ],
  49. 'aliases' => ['fra', 'fia', 'fim-all'],
  50. ];
  51. $items['features-export'] = [
  52. 'description' => "Export the configuration on your site into a custom module.",
  53. 'arguments' => [
  54. 'package' => 'A space delimited list of features to export.',
  55. ],
  56. 'options' => [
  57. 'add-profile' => 'Package features into an install profile.',
  58. ],
  59. 'examples' => [
  60. "drush features-export" => 'Export all available packages.',
  61. "drush features-export example_article example_page" => "Export the example_article and example_page packages.",
  62. "drush features-export --add-profile" => "Export all available packages and add them to an install profile.",
  63. ],
  64. // Add previous "fu" alias for compatibility.
  65. 'aliases' => ['fex', 'fu', 'fua', 'fu-all'],
  66. ];
  67. $items['features-add'] = [
  68. 'description' => "Add a config item to a feature package.",
  69. 'arguments' => [
  70. 'feature' => 'Feature package to export and add config to.',
  71. 'components' => 'Patterns of config to add, see features-components for the format of patterns.',
  72. ],
  73. 'aliases' => ['fa', 'fe'],
  74. ];
  75. $items['features-components'] = [
  76. 'description' => 'List features components.',
  77. 'arguments' => [
  78. 'patterns' => 'The features components type to list. Omit this argument to list all components.',
  79. ],
  80. 'options' => [
  81. 'exported' => [
  82. 'description' => 'Show only components that have been exported.',
  83. ],
  84. 'not-exported' => [
  85. 'description' => 'Show only components that have not been exported.',
  86. ],
  87. ],
  88. 'aliases' => ['fc'],
  89. ];
  90. $items['features-diff'] = [
  91. 'description' => "Show the difference between the active config and the default config stored in a feature package.",
  92. 'arguments' => [
  93. 'feature' => 'The feature in question.',
  94. ],
  95. 'options' => [
  96. 'ctypes' => 'Comma separated list of component types to limit the output to. Defaults to all types.',
  97. 'lines' => 'Generate diffs with <n> lines of context instead of the usual two.',
  98. ],
  99. 'aliases' => ['fd'],
  100. ];
  101. $items['features-import'] = [
  102. 'description' => "Import a module config into your site.",
  103. 'arguments' => [
  104. 'feature' => 'A space delimited list of features or feature:component pairs to import.',
  105. ],
  106. 'options' => [
  107. 'force' => "Force import even if config is not overridden.",
  108. ],
  109. 'examples' => [
  110. 'drush features-import foo:node.type.page foo:taxonomy.vocabulary.tags bar' => 'Import node and taxonomy config of feature "foo". Import all config of feature "bar".',
  111. ],
  112. 'aliases' => ['fim', 'fr'],
  113. ];
  114. foreach ($items as $name => &$item) {
  115. $item['options']['bundle'] = [
  116. 'description' => 'Use a specific bundle namespace.',
  117. ];
  118. }
  119. return $items;
  120. }
  121. /**
  122. * Applies global options for Features drush commands.
  123. *
  124. * The option --name="bundle_name" sets the bundle namespace.
  125. *
  126. * @return \Drupal\features\FeaturesAssignerInterface
  127. */
  128. function _drush_features_options() {
  129. /** @var \Drupal\features\FeaturesAssignerInterface $assigner */
  130. $assigner = \Drupal::service('features_assigner');
  131. $bundle_name = drush_get_option('bundle');
  132. if (!empty($bundle_name)) {
  133. $bundle = $assigner->applyBundle($bundle_name);
  134. if ($bundle->getMachineName() != $bundle_name) {
  135. drush_log(dt('Bundle @name not found. Using default.', ['@name' => $bundle_name]), 'warning');
  136. }
  137. }
  138. else {
  139. $assigner->assignConfigPackages();
  140. }
  141. return $assigner;
  142. }
  143. /**
  144. * Provides Drush command callback for features-status.
  145. */
  146. function drush_features_status() {
  147. $args = func_get_args();
  148. $assigner = _drush_features_options();
  149. /** @var \Drupal\features\FeaturesManagerInterface $manager */
  150. $manager = \Drupal::service('features.manager');
  151. $current_bundle = $assigner->getBundle();
  152. $export_settings = $manager->getExportSettings();
  153. $methods = $assigner->getEnabledAssigners();
  154. if ($current_bundle->isDefault()) {
  155. drush_print(dt('Current bundle: none'));
  156. }
  157. else {
  158. drush_print(dt('Current bundle: @name (@machine_name)',
  159. [
  160. '@name' => $current_bundle->getName(),
  161. '@machine_name' => $current_bundle->getMachineName(),
  162. ]));
  163. }
  164. drush_print(dt('Export folder: @folder', ['@folder' => $export_settings['folder']]));
  165. $dt_args = ['@methods' => implode(', ', array_keys($methods))];
  166. drush_print(dt('The following assignment methods are enabled:'));
  167. drush_print(dt(' @methods', $dt_args));
  168. if (!empty($args)) {
  169. $config = $manager->getConfigCollection();
  170. if (count($args) > 1) {
  171. print_r(array_keys($config));
  172. }
  173. else {
  174. print_r($config[$args[0]]);
  175. }
  176. }
  177. }
  178. /**
  179. * Drush command callback for features-list-packages.
  180. *
  181. * @param string $package_name
  182. * (optional) The package name.
  183. *
  184. * @return array|bool
  185. */
  186. function drush_features_list_packages($package_name = '') {
  187. $assigner = _drush_features_options();
  188. $current_bundle = $assigner->getBundle();
  189. $namespace = $current_bundle->isDefault() ? FeaturesBundleInterface::DEFAULT_BUNDLE : $current_bundle->getMachineName();
  190. /** @var \Drupal\features\FeaturesManagerInterface $manager */
  191. $manager = \Drupal::service('features.manager');
  192. $packages = $manager->getPackages();
  193. $packages = $manager->filterPackages($packages, $namespace);
  194. $result = [];
  195. // If no package was specified, list all packages.
  196. if (empty($package_name)) {
  197. drush_hide_output_fields(['object']);
  198. foreach ($packages as $package) {
  199. $overrides = $manager->detectOverrides($package);
  200. $state = $package->getState();
  201. if (!empty($overrides) && ($package->getStatus() != FeaturesManagerInterface::STATUS_NO_EXPORT)) {
  202. $state = FeaturesManagerInterface::STATE_OVERRIDDEN;
  203. }
  204. $result[$package->getMachineName()] = [
  205. 'name' => $package->getName(),
  206. 'machine_name' => $package->getMachineName(),
  207. 'status' => $manager->statusLabel($package->getStatus()),
  208. 'version' => $package->getVersion(),
  209. 'state' => ($state != FeaturesManagerInterface::STATE_DEFAULT)
  210. ? $manager->stateLabel($state)
  211. : '',
  212. ];
  213. }
  214. return $result;
  215. }
  216. // If a valid package was listed, list its configuration.
  217. else {
  218. foreach ($packages as $package) {
  219. if ($package->getMachineName() == $package_name) {
  220. drush_hide_output_fields([
  221. 'machine_name',
  222. 'name',
  223. 'status',
  224. 'version',
  225. 'state',
  226. ]);
  227. foreach ($package->getConfig() as $item_name) {
  228. $result[$item_name] = [
  229. 'object' => $item_name,
  230. ];
  231. }
  232. return $result;
  233. }
  234. }
  235. }
  236. // If no matching package found, return an error.
  237. drush_log(dt('Package "@package" not found.', ['@package' => $package_name]), 'warning');
  238. return FALSE;
  239. }
  240. /**
  241. * Drush command callback for features-import-all.
  242. *
  243. */
  244. function drush_features_import_all() {
  245. $assigner = _drush_features_options();
  246. $current_bundle = $assigner->getBundle();
  247. $namespace = $current_bundle->isDefault() ? FeaturesBundleInterface::DEFAULT_BUNDLE : $current_bundle->getMachineName();
  248. /** @var \Drupal\features\FeaturesManagerInterface $manager */
  249. $manager = \Drupal::service('features.manager');
  250. $packages = $manager->getPackages();
  251. $packages = $manager->filterPackages($packages, $namespace);
  252. $overridden = [];
  253. foreach ($packages as $package) {
  254. $overrides = $manager->detectOverrides($package);
  255. $missing = $manager->detectMissing($package);
  256. if ((!empty($missing) || !empty($overrides)) && ($package->getStatus() == FeaturesManagerInterface::STATUS_INSTALLED)) {
  257. $overridden[] = $package->getMachineName();
  258. }
  259. }
  260. if (!empty($overridden)) {
  261. call_user_func_array('drush_features_import', $overridden);
  262. }
  263. else {
  264. drush_log(dt('Current state already matches active config, aborting.'), 'ok');
  265. }
  266. }
  267. /**
  268. * Provides Drush command callback for features-export.
  269. */
  270. function drush_features_export($packages = NULL) {
  271. $packages = func_get_args();
  272. $assigner = _drush_features_options();
  273. /** @var \Drupal\features\FeaturesManagerInterface $manager */
  274. $manager = \Drupal::service('features.manager');
  275. /** @var \Drupal\features\FeaturesGeneratorInterface $generator */
  276. $generator = \Drupal::service('features_generator');
  277. $current_bundle = $assigner->getBundle();
  278. if (drush_get_option('add-profile')) {
  279. if ($current_bundle->isDefault) {
  280. return drush_set_error('', dt("Must specify a profile name with --name"));
  281. }
  282. $current_bundle->setIsProfile(TRUE);
  283. }
  284. $all_packages = $manager->getPackages();
  285. foreach ($packages as $name) {
  286. if (!isset($all_packages[$name])) {
  287. return drush_set_error('', dt("The package @name does not exist.", ['@name' => $name]));
  288. }
  289. }
  290. if (empty($packages)) {
  291. $packages = $all_packages;
  292. $dt_args = ['@modules' => implode(', ', array_keys($packages))];
  293. drush_print(dt('The following extensions will be exported: @modules', $dt_args));
  294. if (!drush_confirm(dt('Do you really want to continue?'))) {
  295. return drush_user_abort('Aborting.');
  296. }
  297. }
  298. // If any packages exist, confirm before overwriting.
  299. if ($existing_packages = $manager->listPackageDirectories($packages, $current_bundle)) {
  300. foreach ($existing_packages as $name => $directory) {
  301. drush_print(dt("The extension @name already exists at @directory.", ['@name' => $name, '@directory' => $directory]));
  302. }
  303. // Apparently, format_plural is not always available.
  304. if (count($existing_packages) == 1) {
  305. $message = dt('Would you like to overwrite it?');
  306. }
  307. else {
  308. $message = dt('Would you like to overwrite them?');
  309. }
  310. if (!drush_confirm($message)) {
  311. return drush_user_abort();
  312. }
  313. }
  314. // Use the write generation method.
  315. $method_id = FeaturesGenerationWrite::METHOD_ID;
  316. $result = $generator->generatePackages($method_id, $current_bundle, $packages);
  317. foreach ($result as $message) {
  318. $type = $message['success'] ? 'success' : 'error';
  319. drush_log($message['message'], $message['variables'], $type);
  320. }
  321. }
  322. /**
  323. * Adds a component to a features module.
  324. *
  325. * @param
  326. * The selected components.
  327. */
  328. function drush_features_add() {
  329. if ($args = func_get_args()) {
  330. $assigner = _drush_features_options();
  331. /** @var \Drupal\features\FeaturesManagerInterface $manager */
  332. $manager = \Drupal::service('features.manager');
  333. /** @var \Drupal\features\FeaturesGeneratorInterface $generator */
  334. $generator = \Drupal::service('features_generator');
  335. $current_bundle = $assigner->getBundle();
  336. $module = array_shift($args);
  337. if (empty($args)) {
  338. return drush_set_error('', 'No components supplied.');
  339. }
  340. $components = _drush_features_component_list();
  341. $options = [
  342. 'exported' => FALSE,
  343. ];
  344. $filtered_components = _drush_features_component_filter($components, $args, $options);
  345. $items = $filtered_components['components'];
  346. if (empty($items)) {
  347. return drush_set_error('', 'No components to add.');
  348. }
  349. $packages = [$module];
  350. // If any packages exist, confirm before overwriting.
  351. if ($existing_packages = $manager->listPackageDirectories($packages)) {
  352. foreach ($existing_packages as $name => $directory) {
  353. drush_print(dt("The extension @name already exists at @directory.", ['@name' => $name, '@directory' => $directory]));
  354. }
  355. // Apparently, format_plural is not always available.
  356. if (count($existing_packages) == 1) {
  357. $message = dt('Would you like to overwrite it?');
  358. }
  359. else {
  360. $message = dt('Would you like to overwrite them?');
  361. }
  362. if (!drush_confirm($message)) {
  363. return drush_user_abort();
  364. }
  365. }
  366. else {
  367. $package = $manager->initPackage($module, NULL, '', 'module', $current_bundle);
  368. list($full_name, $path) = $manager->getExportInfo($package, $current_bundle);
  369. drush_print(dt('Will create a new extension @name in @directory', ['@name' => $full_name, '@directory' => $path]));
  370. if (!drush_confirm(dt('Do you really want to continue?'))) {
  371. drush_die('Aborting.');
  372. }
  373. }
  374. $config = _drush_features_build_config($items);
  375. $manager->assignConfigPackage($module, $config);
  376. // Use the write generation method.
  377. $method_id = FeaturesGenerationWrite::METHOD_ID;
  378. $result = $generator->generatePackages($method_id, $current_bundle, $packages);
  379. foreach ($result as $message) {
  380. $type = $message['success'] ? 'success' : 'error';
  381. drush_log($message['message'], $message['variables'], $type);
  382. }
  383. }
  384. else {
  385. return drush_set_error('', 'No feature name given.');
  386. }
  387. }
  388. /**
  389. * Lists components, with pattern matching.
  390. */
  391. function drush_features_components() {
  392. $args = func_get_args();
  393. _drush_features_options();
  394. $components = _drush_features_component_list();
  395. ksort($components);
  396. // If no args supplied, prompt with a list.
  397. if (empty($args)) {
  398. $types = array_keys($components);
  399. array_unshift($types, 'all');
  400. $choice = drush_choice($types, 'Enter a number to choose which component type to list.');
  401. if ($choice === FALSE) {
  402. return;
  403. }
  404. $args = ($choice == 0) ? ['*'] : [$types[$choice]];
  405. }
  406. $options = [
  407. 'provided by' => TRUE,
  408. ];
  409. if (drush_get_option(['exported', 'e'], NULL)) {
  410. $options['not exported'] = FALSE;
  411. }
  412. elseif (drush_get_option(['not-exported', 'o'], NULL)) {
  413. $options['exported'] = FALSE;
  414. }
  415. $filtered_components = _drush_features_component_filter($components, $args, $options);
  416. if ($filtered_components) {
  417. _drush_features_component_print($filtered_components);
  418. }
  419. }
  420. /**
  421. * Lists the differences in the package config vs the active store.
  422. *
  423. * @param string $package
  424. * The machine name of a package.
  425. */
  426. function drush_features_diff() {
  427. if (!$args = func_get_args()) {
  428. drush_print_table(drush_features_list_packages());
  429. return;
  430. }
  431. /** @var \Drupal\features\FeaturesManagerInterface $manager */
  432. $manager = \Drupal::service('features.manager');
  433. /** @var \Drupal\features\FeaturesAssignerInterface $assigner */
  434. $assigner = \Drupal::service('features_assigner');
  435. $assigner->assignConfigPackages();
  436. $module = $args[0];
  437. $filter_ctypes = drush_get_option("ctypes");
  438. if ($filter_ctypes) {
  439. $filter_ctypes = explode(',', $filter_ctypes);
  440. }
  441. $feature = $manager->loadPackage($module, TRUE);
  442. if (empty($feature)) {
  443. drush_log(dt('No such feature is available: @module', ['@module' => $module]), 'error');
  444. return;
  445. }
  446. $lines = drush_get_option('lines');
  447. $lines = isset($lines) ? $lines : 2;
  448. $formatter = new DiffFormatter();
  449. $formatter->leading_context_lines = $lines;
  450. $formatter->trailing_context_lines = $lines;
  451. $formatter->show_header = FALSE;
  452. if (drush_get_context('DRUSH_NOCOLOR')) {
  453. $red = $green = "%s";
  454. }
  455. else {
  456. $red = "\033[31;40m\033[1m%s\033[0m";
  457. $green = "\033[0;32;40m\033[1m%s\033[0m";
  458. }
  459. $overrides = $manager->detectOverrides($feature);
  460. $missing = $manager->reorderMissing($manager->detectMissing($feature));
  461. $overrides = array_merge($overrides, $missing);
  462. if (empty($overrides)) {
  463. drush_print(dt('Active config matches stored config for @module.', ['@module' => $module]));
  464. }
  465. else {
  466. /** @var \Drupal\config_update\ConfigDiffInterface $config_diff */
  467. $config_diff = \Drupal::service('config_update.config_diff');
  468. /** @var \Drupal\Core\Config\StorageInterface $active_storage */
  469. $active_storage = \Drupal::service('config.storage');
  470. // Print key for colors.
  471. drush_print(dt('Legend: '));
  472. drush_print(sprintf($red, dt('Code: drush features-import will replace the active config with the displayed code.')));
  473. drush_print(sprintf($green, dt('Active: drush features-export will update the exported feature with the displayed active config')));
  474. foreach ($overrides as $name) {
  475. $message = '';
  476. if (in_array($name, $missing)) {
  477. $message = sprintf($red, t('(missing from active)'));
  478. $extension = [];
  479. }
  480. else {
  481. $active = $manager->getActiveStorage()->read($name);
  482. $extension = $manager->getExtensionStorages()->read($name);
  483. if (empty($extension)) {
  484. $extension = [];
  485. $message = sprintf($green, t('(not exported)'));
  486. }
  487. $diff = $config_diff->diff($extension, $active);
  488. $rows = explode("\n", $formatter->format($diff));
  489. }
  490. drush_print();
  491. drush_print(dt("Config @name @message", ['@name' => $name, '@message' => $message]));
  492. if (!empty($extension)) {
  493. foreach ($rows as $row) {
  494. if (strpos($row, '>') === 0) {
  495. drush_print(sprintf($green, $row));
  496. }
  497. elseif (strpos($row, '<') === 0) {
  498. drush_print(sprintf($red, $row));
  499. }
  500. else {
  501. drush_print($row);
  502. }
  503. }
  504. }
  505. }
  506. }
  507. }
  508. /**
  509. * Imports module config into the active store.
  510. *
  511. * Same as the old "revert" functionality.
  512. */
  513. function drush_features_import() {
  514. if ($args = func_get_args()) {
  515. _drush_features_options();
  516. // Determine if revert should be forced.
  517. $force = drush_get_option('force');
  518. // Determine if -y was supplied. If so, we can filter out needless output
  519. // from this command.
  520. $skip_confirmation = drush_get_context('DRUSH_AFFIRMATIVE');
  521. /** @var \Drupal\features\FeaturesManagerInterface $manager */
  522. $manager = \Drupal::service('features.manager');
  523. // Parse list of arguments.
  524. $modules = [];
  525. foreach ($args as $arg) {
  526. $arg = explode(':', $arg);
  527. $module = array_shift($arg);
  528. $component = array_shift($arg);
  529. if (isset($module)) {
  530. if (empty($component)) {
  531. // If we received just a feature name, this means that we need all of
  532. // its components.
  533. $modules[$module] = TRUE;
  534. }
  535. elseif ($modules[$module] !== TRUE) {
  536. if (!isset($modules[$module])) {
  537. $modules[$module] = [];
  538. }
  539. $modules[$module][] = $component;
  540. }
  541. }
  542. }
  543. // Process modules.
  544. foreach ($modules as $module => $components_needed) {
  545. $dt_args['@module'] = $module;
  546. /** @var \Drupal\features\Package $feature */
  547. $feature = $manager->loadPackage($module, TRUE);
  548. if (empty($feature)) {
  549. drush_log(dt('No such feature is available: @module', $dt_args), 'error');
  550. return;
  551. }
  552. if ($feature->getStatus() != FeaturesManagerInterface::STATUS_INSTALLED) {
  553. drush_log(dt('No such feature is installed: @module', $dt_args), 'error');
  554. return;
  555. }
  556. // Forcefully revert all components of a feature.
  557. if ($force) {
  558. $components = $feature->getConfigOrig();
  559. }
  560. // Only revert components that are detected to be Overridden.
  561. else {
  562. $components = $manager->detectOverrides($feature);
  563. $missing = $manager->reorderMissing($manager->detectMissing($feature));
  564. // Be sure to import missing components first.
  565. $components = array_merge($missing, $components);
  566. }
  567. if (!empty($components_needed) && is_array($components_needed)) {
  568. $components = array_intersect($components, $components_needed);
  569. }
  570. if (empty($components)) {
  571. drush_log(dt('Current state already matches active config, aborting.'), 'ok');
  572. }
  573. else {
  574. // Determine which config the user wants to import/revert.
  575. $config_to_create = [];
  576. foreach ($components as $component) {
  577. $dt_args['@component'] = $component;
  578. $confirmation_message = 'Do you really want to import @module : @component?';
  579. if ($skip_confirmation || drush_confirm(dt($confirmation_message, $dt_args))) {
  580. $config_to_create[$component] = '';
  581. }
  582. }
  583. // Perform the import/revert.
  584. $config_imported = $manager->createConfiguration($config_to_create);
  585. // List the results.
  586. foreach ($components as $component) {
  587. $dt_args['@component'] = $component;
  588. if (isset($config_imported['new'][$component])) {
  589. drush_log(dt('Imported @module : @component.', $dt_args), 'ok');
  590. }
  591. elseif (isset($config_imported['updated'][$component])) {
  592. drush_log(dt('Reverted @module : @component.', $dt_args), 'ok');
  593. }
  594. elseif (!isset($config_to_create[$component])) {
  595. drush_log(dt('Skipping @module : @component.', $dt_args), 'ok');
  596. }
  597. else {
  598. drush_log(dt('Error importing @module : @component.', $dt_args), 'error');
  599. }
  600. }
  601. }
  602. }
  603. }
  604. else {
  605. drush_print_table(drush_features_list_packages());
  606. return;
  607. }
  608. }
  609. /**
  610. * Returns an array of full config names given a array[$type][$component].
  611. *
  612. * @param array $items
  613. * The items to return data for.
  614. */
  615. function _drush_features_build_config(array $items) {
  616. /** @var \Drupal\features\FeaturesManagerInterface $manager */
  617. $manager = \Drupal::service('features.manager');
  618. $result = [];
  619. foreach ($items as $config_type => $item) {
  620. foreach ($item as $item_name => $title) {
  621. $result[] = $manager->getFullName($config_type, $item_name);
  622. }
  623. }
  624. return $result;
  625. }
  626. /**
  627. * Returns a listing of all known components, indexed by source.
  628. */
  629. function _drush_features_component_list() {
  630. $result = [];
  631. /** @var \Drupal\features\FeaturesManagerInterface $manager */
  632. $manager = \Drupal::service('features.manager');
  633. $config = $manager->getConfigCollection();
  634. foreach ($config as $item_name => $item) {
  635. $result[$item->getType()][$item->getShortName()] = $item->getLabel();
  636. }
  637. return $result;
  638. }
  639. /**
  640. * Filters components by patterns.
  641. */
  642. function _drush_features_component_filter($all_components, $patterns = [], $options = []) {
  643. $options += [
  644. 'exported' => TRUE,
  645. 'not exported' => TRUE,
  646. 'provided by' => FALSE,
  647. ];
  648. $pool = [];
  649. // Maps exported components to feature modules.
  650. $components_map = _drush_features_get_component_map();
  651. // First filter on exported state.
  652. foreach ($all_components as $source => $components) {
  653. foreach ($components as $name => $title) {
  654. $exported = count($components_map[$source][$name]) > 0;
  655. if ($exported) {
  656. if ($options['exported']) {
  657. $pool[$source][$name] = $title;
  658. }
  659. }
  660. else {
  661. if ($options['not exported']) {
  662. $pool[$source][$name] = $title;
  663. }
  664. }
  665. }
  666. }
  667. $state_string = '';
  668. if (!$options['exported']) {
  669. $state_string = 'unexported';
  670. }
  671. elseif (!$options['not exported']) {
  672. $state_string = 'exported';
  673. }
  674. $selected = [];
  675. foreach ($patterns as $pattern) {
  676. // Rewrite * to %. Let users use both as wildcard.
  677. $pattern = strtr($pattern, ['*' => '%']);
  678. $sources = [];
  679. list($source_pattern, $component_pattern) = explode(':', $pattern, 2);
  680. // If source is empty, use a pattern.
  681. if ($source_pattern == '') {
  682. $source_pattern = '%';
  683. }
  684. if ($component_pattern == '') {
  685. $component_pattern = '%';
  686. }
  687. $preg_source_pattern = strtr(preg_quote($source_pattern, '/'), ['%' => '.*']);
  688. $preg_component_pattern = strtr(preg_quote($component_pattern, '/'), ['%' => '.*']);
  689. // If it isn't a pattern, but a simple string, we don't anchor the
  690. // pattern. This allows for abbreviating. Otherwise, we do, as this seems
  691. // more natural for patterns.
  692. if (strpos($source_pattern, '%') !== FALSE) {
  693. $preg_source_pattern = '^' . $preg_source_pattern . '$';
  694. }
  695. if (strpos($component_pattern, '%') !== FALSE) {
  696. $preg_component_pattern = '^' . $preg_component_pattern . '$';
  697. }
  698. $matches = [];
  699. // Find the sources.
  700. $all_sources = array_keys($pool);
  701. $matches = preg_grep('/' . $preg_source_pattern . '/', $all_sources);
  702. if (count($matches) > 0) {
  703. // If we have multiple matches and the source string wasn't a
  704. // pattern, check if one of the matches is equal to the pattern, and
  705. // use that, or error out.
  706. if (count($matches) > 1 and $preg_source_pattern[0] != '^') {
  707. if (in_array($source_pattern, $matches)) {
  708. $matches = [$source_pattern];
  709. }
  710. else {
  711. return drush_set_error('', dt('Ambiguous source "@source", matches @matches', [
  712. '@source' => $source_pattern,
  713. '@matches' => implode(', ', $matches),
  714. ]));
  715. }
  716. }
  717. // Loose the indexes preg_grep preserved.
  718. $sources = array_values($matches);
  719. }
  720. else {
  721. return drush_set_error('', dt('No @state sources match "@source"', ['@state' => $state_string, '@source' => $source_pattern]));
  722. }
  723. // Now find the components.
  724. foreach ($sources as $source) {
  725. // Find the components.
  726. $all_components = array_keys($pool[$source]);
  727. // See if there's any matches.
  728. $matches = preg_grep('/' . $preg_component_pattern . '/', $all_components);
  729. if (count($matches) > 0) {
  730. // If we have multiple matches and the components string wasn't a
  731. // pattern, check if one of the matches is equal to the pattern, and
  732. // use that, or error out.
  733. if (count($matches) > 1 and $preg_component_pattern[0] != '^') {
  734. if (in_array($component_pattern, $matches)) {
  735. $matches = [$component_pattern];
  736. }
  737. else {
  738. return drush_set_error('', dt('Ambiguous component "@component", matches @matches', [
  739. '@component' => $component_pattern,
  740. '@matches' => implode(', ', $matches),
  741. ]));
  742. }
  743. }
  744. if (!is_array($selected[$source])) {
  745. $selected[$source] = [];
  746. }
  747. $selected[$source] += array_intersect_key($pool[$source], array_flip($matches));
  748. }
  749. else {
  750. // No matches. If the source was a pattern, just carry on, else
  751. // error out. Allows for patterns like :*field*
  752. if ($preg_source_pattern[0] != '^') {
  753. return drush_set_error('', dt('No @state @source components match "@component"', [
  754. '@state' => $state_string,
  755. '@component' => $component_pattern,
  756. '@source' => $source,
  757. ]));
  758. }
  759. }
  760. }
  761. }
  762. // Lastly, provide feature module information on the selected components, if
  763. // requested.
  764. $provided_by = [];
  765. if ($options['provided by'] && $options['exported']) {
  766. foreach ($selected as $source => $components) {
  767. foreach ($components as $name => $title) {
  768. $exported = count($components_map[$source][$name]) > 0;
  769. if ($exported) {
  770. $provided_by[$source . ':' . $name] = implode(', ', $components_map[$source][$name]);
  771. }
  772. }
  773. }
  774. }
  775. return [
  776. 'components' => $selected,
  777. 'sources' => $provided_by,
  778. ];
  779. }
  780. /**
  781. * Provides a component to feature map (port of features_get_component_map).
  782. */
  783. function _drush_features_get_component_map() {
  784. $result = [];
  785. /** @var \Drupal\features\FeaturesManagerInterface $manager */
  786. $manager = \Drupal::service('features.manager');
  787. // Recalc full config list without running assignments.
  788. $config = $manager->getConfigCollection();
  789. $packages = $manager->getPackages();
  790. foreach ($config as $item_name => $item) {
  791. $type = $item->getType();
  792. $short_name = $item->getShortName();
  793. $name = $item->getName();
  794. if (!isset($result[$type][$short_name])) {
  795. $result[$type][$short_name] = [];
  796. }
  797. if (!empty($item->getPackage())) {
  798. $package = $packages[$item->getPackage()];
  799. $result[$type][$short_name][] = $package->getMachineName();
  800. }
  801. }
  802. return $result;
  803. }
  804. /**
  805. * Prints a list of filtered components.
  806. */
  807. function _drush_features_component_print($filtered_components) {
  808. $rows = [[dt('Available sources')]];
  809. foreach ($filtered_components['components'] as $source => $components) {
  810. foreach ($components as $name => $value) {
  811. $row = [$source . ':' . $name];
  812. if (isset($filtered_components['sources'][$source . ':' . $name])) {
  813. $row[] = dt('Provided by') . ': ' . $filtered_components['sources'][$source . ':' . $name];
  814. }
  815. $rows[] = $row;
  816. }
  817. }
  818. drush_print_table($rows, TRUE);
  819. }