features.drush.inc 31 KB

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