features.drush.inc 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815
  1. <?php
  2. /**
  3. * @file
  4. * Features module drush integration.
  5. */
  6. /**
  7. * Implements hook_drush_command().
  8. *
  9. * @return
  10. * An associative array describing your command(s).
  11. *
  12. * @see drush_parse_command()
  13. */
  14. function features_drush_command() {
  15. $items = array();
  16. $items['features-list'] = array(
  17. 'description' => "List all the available features for your site.",
  18. 'drupal dependencies' => array('features'),
  19. 'aliases' => array('fl', 'features'),
  20. );
  21. $items['features-export'] = array(
  22. 'description' => "Export a feature from your site into a module.",
  23. 'arguments' => array(
  24. 'feature' => 'Feature name to export.',
  25. 'components' => 'Patterns of components to include, see features-components for the format of patterns.'
  26. ),
  27. 'options' => array(
  28. 'destination' => "Destination path (from Drupal root) of the exported feature. Defaults to 'sites/all/modules'",
  29. 'version-set' => "Specify a version number for the feature.",
  30. 'version-increment' => "Increment the feature's version number.",
  31. ),
  32. 'drupal dependencies' => array('features'),
  33. 'aliases' => array('fe'),
  34. );
  35. $items['features-add'] = array(
  36. 'description' => "Add a component to a feature module. (DEPRECATED: use features-export)",
  37. 'arguments' => array(
  38. 'feature' => 'Feature name to add to.',
  39. 'components' => 'List of components to add.',
  40. ),
  41. 'options' => array(
  42. 'version-set' => "Specify a version number for the feature.",
  43. 'version-increment' => "Increment the feature's version number.",
  44. ),
  45. 'drupal dependencies' => array('features'),
  46. 'aliases' => array('fa'),
  47. );
  48. $items['features-components'] = array(
  49. 'description' => 'List features components.',
  50. 'arguments' => array(
  51. 'patterns' => 'The features components type to list. Omit this argument to list all components.',
  52. ),
  53. 'options' => array(
  54. 'exported' => array(
  55. 'description' => 'Show only components that have been exported.',
  56. ),
  57. 'not-exported' => array(
  58. 'description' => 'Show only components that have not been exported.',
  59. ),
  60. ),
  61. 'aliases' => array('fc'),
  62. );
  63. $items['features-update'] = array(
  64. 'description' => "Update a feature module on your site.",
  65. 'arguments' => array(
  66. 'feature' => 'A space delimited list of features.',
  67. ),
  68. 'options' => array(
  69. 'version-set' => "Specify a version number for the feature.",
  70. 'version-increment' => "Increment the feature's version number.",
  71. ),
  72. 'drupal dependencies' => array('features'),
  73. 'aliases' => array('fu'),
  74. );
  75. $items['features-update-all'] = array(
  76. 'description' => "Update all feature modules on your site.",
  77. 'arguments' => array(
  78. 'feature_exclude' => 'A space-delimited list of features to exclude from being updated.',
  79. ),
  80. 'drupal dependencies' => array('features'),
  81. 'aliases' => array('fu-all', 'fua'),
  82. );
  83. $items['features-revert'] = array(
  84. 'description' => "Revert a feature module on your site.",
  85. 'arguments' => array(
  86. 'feature' => 'A space delimited list of features or feature.component pairs.',
  87. ),
  88. 'options' => array(
  89. 'force' => "Force revert even if Features assumes components' state are default.",
  90. ),
  91. 'examples' => array(
  92. 'drush fr foo.node foo.taxonomy bar' => 'Revert node and taxonomy components of feature "foo", but only if they are overriden. Revert all overriden components of feature "bar".',
  93. 'drush fr foo.node foo.taxonomy bar --force' => 'Revert node and taxonomy components of feature "foo". Revert all components of feature "bar".',
  94. ),
  95. 'drupal dependencies' => array('features'),
  96. 'aliases' => array('fr'),
  97. );
  98. $items['features-revert-all'] = array(
  99. 'description' => "Revert all enabled feature module on your site.",
  100. 'arguments' => array(
  101. 'feature_exclude' => 'A space-delimited list of features to exclude from being reverted.',
  102. ),
  103. 'options' => array(
  104. 'force' => "Force revert even if Features assumes components' state are default.",
  105. ),
  106. 'drupal dependencies' => array('features'),
  107. 'aliases' => array('fr-all', 'fra'),
  108. );
  109. $items['features-diff'] = array(
  110. 'description' => "Show the difference between the default and overridden state of a feature.",
  111. 'arguments' => array(
  112. 'feature' => 'The feature in question.',
  113. ),
  114. 'drupal dependencies' => array('features', 'diff'),
  115. 'aliases' => array('fd'),
  116. );
  117. return $items;
  118. }
  119. /**
  120. * Implements hook_drush_help().
  121. */
  122. function features_drush_help($section) {
  123. switch ($section) {
  124. case 'drush:features':
  125. return dt("List all the available features for your site.");
  126. case 'drush:features-export':
  127. return dt("Export a feature from your site into a module. If called with no arguments, display a list of available components. If called with a single argument, attempt to create a feature including the given component with the same name. The option '--destination=foo' may be used to specify the path (from Drupal root) where the feature should be created. The default destination is 'sites/all/modules'. The option '--version-set=foo' may be used to specify a version number for the feature or the option '--version-increment' may also to increment the feature's version number.");
  128. case 'drush:features-components':
  129. return dt("List feature components matching patterns. The listing may be limited to exported/not-exported components.
  130. A component pattern consists of a source, a colon and a component. Both source and component may be a full name (as in \"dependencies\"), a shorthand (for instance \"dep\") or a pattern (like \"%denci%\").
  131. Shorthands are unique shortenings of a name. They will only match if exactly one option contains the shorthand. So in a standard installation, \"dep\" will work for dependencies, but \"user\" wont, as it matches both user_permission and user_role.
  132. Patterns uses * or % for matching multiple sources/components. Unlike shorthands, patterns must match the whole name, so \"field:%article%\" should be used to select all fields containing \"article\" (which could both be those on the node type article, as well as those fields named article). * and % are equivalent, but the latter doesn't have to be escaped in UNIX shells.
  133. Lastly, a pattern without a colon is interpreted as having \":%\" appended, for easy listing of all components of a source.
  134. ");
  135. case 'drush:features-update':
  136. return dt("Update a feature module on your site. The option '--version-set=foo' may be used to specify a version number for the feature or the option '--version-increment' may also to increment the feature's version number.");
  137. case 'drush:features-update-all':
  138. return dt("Update all feature modules on your site.");
  139. case 'drush:features-revert':
  140. return dt("Revert a feature module on your site.");
  141. case 'drush:features-revert-all':
  142. return dt("Revert all enabled feature module on your site.");
  143. case 'drush:features-diff':
  144. return dt("Show a diff of a feature module.");
  145. case 'drush:features-add':
  146. return dt("Add a component to a feature module. The option '--version-set=foo' may be used to specify a version number for the feature or the option '--version-increment' may also to increment the feature's version number.");
  147. }
  148. }
  149. /**
  150. * Get a list of all feature modules.
  151. */
  152. function drush_features_list() {
  153. module_load_include('inc', 'features', 'features.export');
  154. $rows = array(array(dt('Name'), dt('Feature'), dt('Status'), dt('Version'), dt('State')));
  155. foreach (features_get_features(NULL, TRUE) as $k => $m) {
  156. switch (features_get_storage($m->name)) {
  157. case FEATURES_DEFAULT:
  158. case FEATURES_REBUILDABLE:
  159. $storage = '';
  160. break;
  161. case FEATURES_OVERRIDDEN:
  162. $storage = dt('Overridden');
  163. break;
  164. case FEATURES_NEEDS_REVIEW:
  165. $storage = dt('Needs review');
  166. break;
  167. }
  168. $rows[] = array(
  169. $m->info['name'],
  170. $m->name,
  171. $m->status ? dt('Enabled') : dt('Disabled'),
  172. $m->info['version'],
  173. $storage
  174. );
  175. }
  176. drush_print_table($rows, TRUE);
  177. }
  178. /**
  179. * List components, with pattern matching.
  180. */
  181. function drush_features_components() {
  182. $args = func_get_args();
  183. $components = _drush_features_component_list();
  184. // If no args supplied, prompt with a list.
  185. if (empty($args)) {
  186. $types = array_keys($components);
  187. array_unshift($types, 'all');
  188. $choice = drush_choice($types, 'Enter a number to choose which component type to list.');
  189. if ($choice === FALSE) {
  190. return;
  191. }
  192. $args = ($choice == 0) ? array('*') : array($types[$choice]);
  193. }
  194. $options = array(
  195. 'provided by' => TRUE,
  196. );
  197. if (drush_get_option(array('exported', 'e'), NULL)) {
  198. $options['not exported'] = FALSE;
  199. }
  200. elseif (drush_get_option(array('not-exported', 'o'), NULL)) {
  201. $options['exported'] = FALSE;
  202. }
  203. $filtered_components = _drush_features_component_filter($components, $args, $options);
  204. if ($filtered_components){
  205. _drush_features_component_print($filtered_components);
  206. }
  207. }
  208. /**
  209. * Returns a listing of all known components, indexed by source.
  210. */
  211. function _drush_features_component_list() {
  212. $components = array();
  213. foreach (features_get_feature_components() as $source => $info) {
  214. if ($options = features_invoke($source, 'features_export_options')) {
  215. foreach ($options as $name => $title) {
  216. $components[$source][$name] = $title;
  217. }
  218. }
  219. }
  220. return $components;
  221. }
  222. /**
  223. * Filters components by patterns.
  224. */
  225. function _drush_features_component_filter($all_components, $patterns = array(), $options = array()) {
  226. $options += array(
  227. 'exported' => TRUE,
  228. 'not exported' => TRUE,
  229. 'provided by' => FALSE,
  230. );
  231. $pool = array();
  232. // Maps exported components to feature modules.
  233. $components_map = features_get_component_map();
  234. // First filter on exported state.
  235. foreach ($all_components as $source => $components) {
  236. foreach ($components as $name => $title) {
  237. $exported = sizeof($components_map[$source][$name]) > 0;
  238. if ($exported) {
  239. if ($options['exported']) {
  240. $pool[$source][$name] = $title;
  241. }
  242. }
  243. else {
  244. if ($options['not exported']) {
  245. $pool[$source][$name] = $title;
  246. }
  247. }
  248. }
  249. }
  250. $state_string = '';
  251. if (!$options['exported']) {
  252. $state_string = 'unexported';
  253. }
  254. elseif (!$options['not exported']) {
  255. $state_string = 'exported';
  256. }
  257. $selected = array();
  258. foreach ($patterns as $pattern) {
  259. // Rewrite * to %. Let users use both as wildcard.
  260. $pattern = strtr($pattern, array('*' => '%'));
  261. $sources = array();
  262. list($source_pattern, $component_pattern) = explode(':', $pattern, 2);
  263. // If source is empty, use a pattern.
  264. if ($source_pattern == '') {
  265. $source_pattern = '%';
  266. }
  267. if ($component_pattern == '') {
  268. $component_pattern = '%';
  269. }
  270. $preg_source_pattern = strtr(preg_quote($source_pattern, '/'), array('%' => '.*'));
  271. $preg_component_pattern = strtr(preg_quote($component_pattern, '/'), array('%' => '.*'));
  272. /*
  273. * If it isn't a pattern, but a simple string, we don't anchor the
  274. * pattern, this allows for abbreviating. Else, we do, as this seems more
  275. * natural for patterns.
  276. */
  277. if (strpos($source_pattern, '%') !== FALSE) {
  278. $preg_source_pattern = '^' . $preg_source_pattern . '$';
  279. }
  280. if (strpos($component_pattern, '%') !== FALSE) {
  281. $preg_component_pattern = '^' . $preg_component_pattern . '$';
  282. }
  283. $matches = array();
  284. // Find the sources.
  285. $all_sources = array_keys($pool);
  286. $matches = preg_grep('/' . $preg_source_pattern . '/', $all_sources);
  287. if (sizeof($matches) > 0) {
  288. // If we have multiple matches and the source string wasn't a
  289. // pattern, check if one of the matches is equal to the pattern, and
  290. // use that, or error out.
  291. if (sizeof($matches) > 1 and $preg_source_pattern[0] != '^') {
  292. if (in_array($source_pattern, $matches)) {
  293. $matches = array($source_pattern);
  294. }
  295. else {
  296. return drush_set_error('', dt('Ambiguous source "!source", matches !matches', array('!source' => $source_pattern, '!matches' => join(', ', $matches))));
  297. }
  298. }
  299. // Loose the indexes preg_grep preserved.
  300. $sources = array_values($matches);
  301. }
  302. else {
  303. return drush_set_error('', dt('No !state sources match "!source"', array('!state' => $state_string, '!source' => $source_pattern)));
  304. }
  305. // Now find the components.
  306. foreach ($sources as $source) {
  307. // Find the components.
  308. $all_components = array_keys($pool[$source]);
  309. // See if there's any matches.
  310. $matches = preg_grep('/' . $preg_component_pattern . '/', $all_components);
  311. if (sizeof($matches) > 0) {
  312. // If we have multiple matches and the components string wasn't a
  313. // pattern, check if one of the matches is equal to the pattern, and
  314. // use that, or error out.
  315. if (sizeof($matches) > 1 and $preg_component_pattern[0] != '^') {
  316. if (in_array($component_pattern, $matches)) {
  317. $matches = array($component_pattern);
  318. }
  319. else {
  320. return drush_set_error('', dt('Ambiguous component "!component", matches !matches', array('!component' => $component_pattern, '!matches' => join(', ', $matches))));
  321. }
  322. }
  323. if (!is_array($selected[$source])) {
  324. $selected[$source] = array();
  325. }
  326. $selected[$source] += array_intersect_key($pool[$source], array_flip($matches));
  327. }
  328. else {
  329. // No matches. If the source was a pattern, just carry on, else
  330. // error out. Allows for patterns like :*field*
  331. if ($preg_source_pattern[0] != '^') {
  332. return drush_set_error('', dt('No !state !source components match "!component"', array('!state' => $state_string, '!component' => $component_pattern, '!source' => $source)));
  333. }
  334. }
  335. }
  336. }
  337. // Lastly, provide feature module information on the selected components, if
  338. // requested.
  339. $provided_by = array();
  340. if ($options['provided by'] && $options['exported'] ) {
  341. foreach ($selected as $source => $components) {
  342. foreach ($components as $name => $title) {
  343. $exported = sizeof($components_map[$source][$name]) > 0;
  344. if ($exported) {
  345. $provided_by[$source . ':' . $name] = join(', ', $components_map[$source][$name]);
  346. }
  347. }
  348. }
  349. }
  350. return array(
  351. 'components' => $selected,
  352. 'sources' => $provided_by,
  353. );
  354. }
  355. /**
  356. * Prints a list of filtered components.
  357. */
  358. function _drush_features_component_print($filtered_components) {
  359. $rows = array(array(dt('Available sources')));
  360. foreach ($filtered_components['components'] as $source => $components) {
  361. foreach ($components as $name => $value) {
  362. $row = array($source .':'. $name);
  363. if (isset($filtered_components['sources'][$source .':'. $name])) {
  364. $row[] = dt('Provided by') . ': ' . $filtered_components['sources'][$source .':'. $name];
  365. }
  366. $rows[] = $row;
  367. }
  368. }
  369. drush_print_table($rows, TRUE);
  370. }
  371. /**
  372. * Add a component to a features module, or create a new module with
  373. * the selected components.
  374. */
  375. function drush_features_export() {
  376. if ($args = func_get_args()) {
  377. $module = array_shift($args);
  378. if (empty($args)) {
  379. return drush_set_error('', 'No components supplied.');
  380. }
  381. $components = _drush_features_component_list();
  382. $options = array(
  383. 'exported' => FALSE,
  384. );
  385. $filtered_components = _drush_features_component_filter($components, $args, $options);
  386. $items = $filtered_components['components'];
  387. if (empty($items)) {
  388. return drush_set_error('', 'No components to add.');
  389. }
  390. $items = array_map('array_keys', $items);
  391. if (($feature = features_load_feature($module, TRUE)) && module_exists($module)) {
  392. module_load_include('inc', 'features', 'features.export');
  393. _features_populate($items, $feature->info, $feature->name);
  394. _drush_features_export($feature->info, $feature->name, dirname($feature->filename));
  395. }
  396. elseif ($feature) {
  397. _features_drush_set_error($module, 'FEATURES_FEATURE_NOT_ENABLED');
  398. }
  399. else {
  400. // Same logic as in _drush_features_export. Should be refactored.
  401. $destination = drush_get_option(array('destination'), 'sites/all/modules');
  402. $directory = isset($directory) ? $directory : $destination . '/' . $module;
  403. drush_print(dt('Will create a new module in !dir', array('!dir' => $directory)));
  404. if (!drush_confirm(dt('Do you really want to continue?'))) {
  405. drush_die('Aborting.');
  406. }
  407. }
  408. }
  409. else {
  410. return drush_set_error('', 'No feature name given.');
  411. }
  412. }
  413. /**
  414. * Add a component to a features module
  415. * the selected components.
  416. *
  417. * This is DEPRECATED, but keeping it around for a bit longer for user migration
  418. */
  419. function drush_features_add() {
  420. drush_print(dt('features_add is DEPRECATED.'));
  421. drush_print(dt('Calling features_export instead.'));
  422. drush_features_export();
  423. }
  424. /**
  425. * Update an existing feature module.
  426. */
  427. function drush_features_update() {
  428. if ($args = func_get_args()) {
  429. foreach ($args as $module) {
  430. if (($feature = features_load_feature($module, TRUE)) && module_exists($module)) {
  431. _drush_features_export($feature->info, $feature->name, dirname($feature->filename));
  432. }
  433. else if ($feature) {
  434. _features_drush_set_error($module, 'FEATURES_FEATURE_NOT_ENABLED');
  435. }
  436. else {
  437. _features_drush_set_error($module);
  438. }
  439. }
  440. }
  441. else {
  442. // By default just show contexts that are available.
  443. $rows = array(array(dt('Available features')));
  444. foreach (features_get_features(NULL, TRUE) as $name => $info) {
  445. $rows[] = array($name);
  446. }
  447. drush_print_table($rows, TRUE);
  448. }
  449. }
  450. /**
  451. * Update all enabled features. Optionally pass in a list of features to
  452. * exclude from being updated.
  453. */
  454. function drush_features_update_all() {
  455. $features_to_update = array();
  456. $features_to_exclude = func_get_args();
  457. foreach (features_get_features() as $module) {
  458. if ($module->status && !in_array($module->name, $features_to_exclude)) {
  459. $features_to_update[] = $module->name;
  460. }
  461. }
  462. drush_print(dt('The following modules will be updated: !modules', array('!modules' => implode(', ', $features_to_update))));
  463. if (drush_confirm(dt('Do you really want to continue?'))) {
  464. foreach ($features_to_update as $module_name) {
  465. drush_invoke_process(drush_sitealias_get_record('@self'), 'features-update', array($module_name));
  466. }
  467. }
  468. else {
  469. drush_die('Aborting.');
  470. }
  471. }
  472. /**
  473. * Write a module to the site dir.
  474. *
  475. * @param $info
  476. * The feature info associative array.
  477. * @param $module_name
  478. * Optional. The name for the exported module.
  479. */
  480. function _drush_features_export($info, $module_name = NULL, $directory = NULL) {
  481. $root = drush_get_option(array('r', 'root'), drush_locate_root());
  482. if ($root) {
  483. $destination = drush_get_option(array('destination'), 'sites/all/modules');
  484. $directory = isset($directory) ? $directory : $destination . '/' . $module_name;
  485. if (is_dir($directory)) {
  486. drush_print(dt('Module appears to already exist in !dir', array('!dir' => $directory)));
  487. if (!drush_confirm(dt('Do you really want to continue?'))) {
  488. drush_die('Aborting.');
  489. }
  490. }
  491. else {
  492. drush_op('mkdir', $directory);
  493. }
  494. if (is_dir($directory)) {
  495. drupal_flush_all_caches();
  496. module_load_include('inc', 'features', 'features.export');
  497. $export = features_populate($info, $module_name);
  498. if (!features_load_feature($module_name)) {
  499. $export['name'] = $module_name;
  500. }
  501. // Set the feature version if the --version-set or --version-increment option is passed.
  502. if ($version = drush_get_option(array('version-set'))) {
  503. preg_match('/^(?P<core>\d+\.x)-(?P<major>\d+)\.(?P<patch>\d+)-?(?P<extra>\w+)?$/', $version, $matches);
  504. if (!isset($matches['core'], $matches['major'])) {
  505. drush_die(dt('Please enter a valid version with core and major version number. Example: !example', array('!example' => '7.x-1.0')));
  506. }
  507. $export['version'] = $version;
  508. }
  509. else if ($version = drush_get_option(array('version-increment'))) {
  510. // Determine current version and increment it.
  511. $export_load = features_export_prepare($export, $module_name);
  512. $version = $export_load['version'];
  513. $version_explode = explode('.', $version);
  514. $version_minor = array_pop($version_explode);
  515. // Increment minor version number if numeric or not a dev release.
  516. if (is_numeric($version_minor) || strpos($version_minor, 'dev') !== (strlen($version_minor) - 3)) {
  517. ++$version_minor;
  518. }
  519. array_push($version_explode, $version_minor);
  520. // Rebuild version string.
  521. $version = implode('.', $version_explode);
  522. $export['version'] = $version;
  523. }
  524. $files = features_export_render($export, $module_name, TRUE);
  525. foreach ($files as $extension => $file_contents) {
  526. if (!in_array($extension, array('module', 'info'))) {
  527. $extension .= '.inc';
  528. }
  529. drush_op('file_put_contents', "{$directory}/{$module_name}.$extension", $file_contents);
  530. }
  531. drush_log(dt("Created module: !module in !directory", array('!module' => $module_name, '!directory' => $directory)), 'ok');
  532. }
  533. else {
  534. drush_die(dt('Couldn\'t create directory !directory', array('!directory' => $directory)));
  535. }
  536. }
  537. else {
  538. drush_die(dt('Couldn\'t locate site root'));
  539. }
  540. }
  541. /**
  542. * Revert a feature to it's code definition.
  543. * Optionally accept a list of components to revert.
  544. */
  545. function drush_features_revert() {
  546. if ($args = func_get_args()) {
  547. module_load_include('inc', 'features', 'features.export');
  548. features_include();
  549. // Determine if revert should be forced.
  550. $force = drush_get_option('force');
  551. // Parse list of arguments.
  552. $modules = array();
  553. foreach ($args as $arg) {
  554. list($module, $component) = explode('.', $arg);
  555. if (isset($module)) {
  556. if (empty($component)) {
  557. // If we received just a feature name, this means that we need all of it's components.
  558. $modules[$module] = TRUE;
  559. }
  560. elseif ($modules[$module] !== TRUE) {
  561. if (!isset($modules[$module])) {
  562. $modules[$module] = array();
  563. }
  564. $modules[$module][] = $component;
  565. }
  566. }
  567. }
  568. // Process modules.
  569. foreach ($modules as $module => $components_needed) {
  570. if (($feature = features_load_feature($module, TRUE)) && module_exists($module)) {
  571. $components = array();
  572. // Forcefully revert all components of a feature.
  573. if ($force) {
  574. foreach (array_keys($feature->info['features']) as $component) {
  575. if (features_hook($component, 'features_revert')) {
  576. $components[] = $component;
  577. }
  578. }
  579. }
  580. // Only revert components that are detected to be Overridden/Needs review/rebuildable.
  581. else {
  582. $states = features_get_component_states(array($feature->name), FALSE);
  583. foreach ($states[$feature->name] as $component => $state) {
  584. if (in_array($state, array(FEATURES_OVERRIDDEN, FEATURES_NEEDS_REVIEW, FEATURES_REBUILDABLE)) && features_hook($component, 'features_revert')) {
  585. $components[] = $component;
  586. }
  587. }
  588. }
  589. if (!empty($components_needed) && is_array($components_needed)) {
  590. $components = array_intersect($components, $components_needed);
  591. }
  592. if (empty($components)) {
  593. drush_log(dt('Current state already matches defaults, aborting.'), 'ok');
  594. }
  595. else {
  596. foreach ($components as $component) {
  597. if (drush_confirm(dt('Do you really want to revert @component?', array('@component' => $component)))) {
  598. features_revert(array($module => array($component)));
  599. drush_log(dt('Reverted @component.', array('@component' => $component)), 'ok');
  600. }
  601. else {
  602. drush_log(dt('Skipping @component.', array('@component' => $component)), 'ok');
  603. }
  604. }
  605. }
  606. }
  607. else if ($feature) {
  608. _features_drush_set_error($module, 'FEATURES_FEATURE_NOT_ENABLED');
  609. }
  610. else {
  611. _features_drush_set_error($module);
  612. }
  613. }
  614. }
  615. else {
  616. drush_features_list();
  617. return;
  618. }
  619. }
  620. /**
  621. * Revert all enabled features to their definitions in code.
  622. *
  623. * @param ...
  624. * (Optional) A list of features to exclude from being reverted.
  625. */
  626. function drush_features_revert_all() {
  627. module_load_include('inc', 'features', 'features.export');
  628. $force = drush_get_option('force');
  629. $features_to_exclude = func_get_args();
  630. $features_to_revert = array();
  631. foreach (features_get_features(NULL, TRUE) as $module) {
  632. if ($module->status && !in_array($module->name, $features_to_exclude)) {
  633. // If forced, add module regardless of status.
  634. if ($force) {
  635. $features_to_revert[] = $module->name;
  636. }
  637. else {
  638. switch (features_get_storage($module->name)) {
  639. case FEATURES_OVERRIDDEN:
  640. case FEATURES_NEEDS_REVIEW:
  641. case FEATURES_REBUILDABLE:
  642. $features_to_revert[] = $module->name;
  643. break;
  644. }
  645. }
  646. }
  647. }
  648. if ($features_to_revert) {
  649. drush_print(dt('The following modules will be reverted: !modules', array('!modules' => implode(', ', $features_to_revert))));
  650. if (drush_confirm(dt('Do you really want to continue?'))) {
  651. foreach ($features_to_revert as $module) {
  652. drush_invoke_process(drush_sitealias_get_record('@self'), 'features-revert', array($module), array('force' => $force, '#integrate' => TRUE));
  653. }
  654. }
  655. else {
  656. return drush_user_abort('Aborting.');
  657. }
  658. }
  659. else {
  660. drush_log(dt('Current state already matches defaults, aborting.'), 'ok');
  661. }
  662. }
  663. /**
  664. * Show the diff of a feature module.
  665. */
  666. function drush_features_diff() {
  667. if (!$args = func_get_args()) {
  668. drush_features_list();
  669. return;
  670. }
  671. $module = $args[0];
  672. $feature = features_load_feature($module);
  673. if (!module_exists($module)) {
  674. drush_log(dt('No such feature is enabled: ' . $module), 'error');
  675. return;
  676. }
  677. module_load_include('inc', 'features', 'features.export');
  678. $overrides = features_detect_overrides($feature);
  679. if (empty($overrides)) {
  680. drush_log(dt('Feature is in its default state. No diff needed.'), 'ok');
  681. return;
  682. }
  683. module_load_include('inc', 'diff', 'diff.engine');
  684. if (!class_exists('DiffFormatter')) {
  685. if (drush_confirm(dt('It seems that the Diff module is not available. Would you like to download and enable it?'))) {
  686. // Download it if it's not already here.
  687. $project_info = drush_get_projects();
  688. if (empty($project_info['diff']) && !drush_invoke_process(drush_sitealias_get_record('@self'), 'dl', array('diff'), array('#integrate' => TRUE))) {
  689. return drush_set_error(dt('Diff module could not be downloaded.'));
  690. }
  691. if (!drush_invoke_process(drush_sitealias_get_record('@self'), 'en', array('diff'), array('#integrate' => TRUE))) {
  692. return drush_set_error(dt('Diff module could not be enabled.'));
  693. }
  694. }
  695. else {
  696. return drush_set_error(dt('Diff module is not enabled.'));
  697. }
  698. // If we're still here, now we can include the diff.engine again.
  699. module_load_include('inc', 'diff', 'diff.engine');
  700. }
  701. $formatter = new DiffFormatter();
  702. $formatter->leading_context_lines = 2;
  703. $formatter->trailing_context_lines = 2;
  704. $formatter->show_header = FALSE;
  705. if (drush_get_context('DRUSH_NOCOLOR')) {
  706. $red = $green = "%s";
  707. }
  708. else {
  709. $red = "\033[31;40m\033[1m%s\033[0m";
  710. $green = "\033[0;32;40m\033[1m%s\033[0m";
  711. }
  712. // Print key for colors
  713. drush_print(dt('Legend: '));
  714. drush_print(sprintf($red, dt('Code: drush features-revert will remove the overrides.')));
  715. drush_print(sprintf($green, dt('Overrides: drush features-update will update the exported feature with the displayed overrides')));
  716. drush_print();
  717. foreach ($overrides as $component => $items) {
  718. $diff = new Diff(explode("\n", $items['default']), explode("\n", $items['normal']));
  719. drush_print();
  720. drush_print(dt("Component: !component", array('!component' => $component)));
  721. $rows = explode("\n", $formatter->format($diff));
  722. foreach ($rows as $row) {
  723. if (strpos($row, '>') === 0) {
  724. drush_print(sprintf($green, $row));
  725. }
  726. elseif (strpos($row, '<') === 0) {
  727. drush_print(sprintf($red, $row));
  728. }
  729. else {
  730. drush_print($row);
  731. }
  732. }
  733. }
  734. }
  735. /**
  736. * Helper function to call drush_set_error().
  737. *
  738. * @param $feature
  739. * The string name of the feature.
  740. * @param $error
  741. * A text string identifying the type of error.
  742. * @return
  743. * FALSE. See drush_set_error().
  744. */
  745. function _features_drush_set_error($feature, $error = '') {
  746. $args = array('!feature' => $feature);
  747. switch ($error) {
  748. case 'FEATURES_FEATURE_NOT_ENABLED':
  749. $message = 'The feature !feature is not enabled.';
  750. break;
  751. case 'FEATURES_COMPONENT_NOT_FOUND':
  752. $message = 'The given component !feature could not be found.';
  753. break;
  754. default:
  755. $error = 'FEATURES_FEATURE_NOT_FOUND';
  756. $message = 'The feature !feature could not be found.';
  757. }
  758. return drush_set_error($error, dt($message, $args));
  759. }