features.drush.inc 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044
  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. // If Features is enabled display the configured default export path,
  17. // otherwise just show the default.
  18. if (defined('FEATURES_DEFAULT_EXPORT_PATH')) {
  19. $path = variable_get('features_default_export_path', FEATURES_DEFAULT_EXPORT_PATH);
  20. }
  21. else {
  22. $path = 'sites/all/modules';
  23. }
  24. $items['features-list'] = array(
  25. 'description' => "List all the available features for your site.",
  26. 'options' => array(
  27. 'status' => "Feature status, can be 'enabled', 'disabled' or 'all'",
  28. ),
  29. 'drupal dependencies' => array('features'),
  30. 'aliases' => array('fl', 'features'),
  31. 'outputformat' => array(
  32. 'default' => 'table',
  33. 'pipe-format' => 'list',
  34. 'field-labels' => array('name' => 'Name', 'feature' => 'Feature', 'status' => 'Status', 'version' => 'Version', 'state' => 'State'),
  35. 'output-data-type' => 'format-table',
  36. ),
  37. );
  38. $items['features-export'] = array(
  39. 'description' => "Export a feature from your site into a module.",
  40. 'arguments' => array(
  41. 'feature' => 'Feature name to export.',
  42. 'components' => 'Patterns of components to include, see features-components for the format of patterns.'
  43. ),
  44. 'options' => array(
  45. 'destination' => "Destination path (from Drupal root) of the exported feature. Defaults to '" . $path . "'.",
  46. 'version-set' => "Specify a version number for the feature.",
  47. 'version-increment' => "Increment the feature's version number.",
  48. 'ignore-conflicts' => "Ignore conflicts and export all components.",
  49. ),
  50. 'drupal dependencies' => array('features'),
  51. 'aliases' => array('fe'),
  52. );
  53. $items['features-add'] = array(
  54. 'description' => "Add a component to a feature module. (DEPRECATED: use features-export)",
  55. 'arguments' => array(
  56. 'feature' => 'Feature name to add to.',
  57. 'components' => 'List of components to add.',
  58. ),
  59. 'options' => array(
  60. 'version-set' => "Specify a version number for the feature.",
  61. 'version-increment' => "Increment the feature's version number.",
  62. ),
  63. 'drupal dependencies' => array('features'),
  64. 'aliases' => array('fa'),
  65. );
  66. $items['features-components'] = array(
  67. 'description' => 'List features components.',
  68. 'arguments' => array(
  69. 'patterns' => 'The features components type to list. Omit this argument to list all components.',
  70. ),
  71. 'options' => array(
  72. 'exported' => array(
  73. 'description' => 'Show only components that have been exported.',
  74. ),
  75. 'not-exported' => array(
  76. 'description' => 'Show only components that have not been exported.',
  77. ),
  78. 'info-style' => array(
  79. 'description' => 'Export components in format suitable for using in an info file.',
  80. ),
  81. ),
  82. 'aliases' => array('fc'),
  83. );
  84. $items['features-update'] = array(
  85. 'description' => "Update a feature module on your site.",
  86. 'arguments' => array(
  87. 'feature' => 'A space delimited list of features.',
  88. ),
  89. 'options' => array(
  90. 'version-set' => "Specify a version number for the feature.",
  91. 'version-increment' => "Increment the feature's version number.",
  92. ),
  93. 'drupal dependencies' => array('features'),
  94. 'aliases' => array('fu'),
  95. );
  96. $items['features-update-all'] = array(
  97. 'description' => "Update all feature modules on your site.",
  98. 'arguments' => array(
  99. 'feature_exclude' => 'A space-delimited list of features to exclude from being updated.',
  100. ),
  101. 'drupal dependencies' => array('features'),
  102. 'aliases' => array('fu-all', 'fua'),
  103. );
  104. $items['features-revert'] = array(
  105. 'description' => "Revert a feature module on your site.",
  106. 'arguments' => array(
  107. 'feature' => 'A space delimited list of features or feature.component pairs.',
  108. ),
  109. 'options' => array(
  110. 'force' => "Force revert even if Features assumes components' state are default.",
  111. ),
  112. 'examples' => array(
  113. '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".',
  114. 'drush fr foo.node foo.taxonomy bar --force' => 'Revert node and taxonomy components of feature "foo". Revert all components of feature "bar".',
  115. ),
  116. 'drupal dependencies' => array('features'),
  117. 'aliases' => array('fr'),
  118. );
  119. $items['features-revert-all'] = array(
  120. 'description' => "Revert all enabled feature module on your site.",
  121. 'arguments' => array(
  122. 'feature_exclude' => 'A space-delimited list of features to exclude from being reverted.',
  123. ),
  124. 'options' => array(
  125. 'force' => "Force revert even if Features assumes components' state are default.",
  126. ),
  127. 'drupal dependencies' => array('features'),
  128. 'aliases' => array('fr-all', 'fra'),
  129. );
  130. $items['features-diff'] = array(
  131. 'description' => "Show the difference between the default and overridden state of a feature.",
  132. 'arguments' => array(
  133. 'feature' => 'The feature in question.',
  134. ),
  135. 'options' => array(
  136. 'ctypes' => 'Comma separated list of component types to limit the output to. Defaults to all types.',
  137. 'lines' => 'Generate diffs with <n> lines of context instead of the usual two.',
  138. ),
  139. 'drupal dependencies' => array('features', 'diff'),
  140. 'aliases' => array('fd'),
  141. );
  142. $items['features-diff-all'] = array(
  143. 'description' => "Show the code difference for all enabled features not in their default state.",
  144. 'arguments' => array(
  145. 'feature_exclude' => 'A space-delimited list of features to exclude from being reverted.',
  146. ),
  147. 'options' => array(
  148. 'force' => "Bypass the confirmations. This is useful if you want to output all of the diffs to a log file.",
  149. ),
  150. 'drupal dependencies' => array('features', 'diff'),
  151. 'aliases' => array('fda'),
  152. );
  153. return $items;
  154. }
  155. /**
  156. * Implements hook_drush_help().
  157. */
  158. function features_drush_help($section) {
  159. // If Features is enabled display the configured default export path,
  160. // otherwise just show the default.
  161. if (defined('FEATURES_DEFAULT_EXPORT_PATH')) {
  162. $path = variable_get('features_default_export_path', FEATURES_DEFAULT_EXPORT_PATH);
  163. }
  164. else {
  165. $path = 'sites/all/modules';
  166. }
  167. switch ($section) {
  168. case 'drush:features':
  169. return dt("List all the available features for your site.");
  170. case 'drush:features-export':
  171. 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 '@path'. 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.", array('@path' => $path));
  172. case 'drush:features-components':
  173. return dt("List feature components matching patterns. The listing may be limited to exported/not-exported components.
  174. 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%\").
  175. 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.
  176. 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.
  177. Lastly, a pattern without a colon is interpreted as having \":%\" appended, for easy listing of all components of a source.
  178. ");
  179. case 'drush:features-update':
  180. 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.");
  181. case 'drush:features-update-all':
  182. return dt("Update all feature modules on your site.");
  183. case 'drush:features-revert':
  184. return dt("Revert a feature module on your site.");
  185. case 'drush:features-revert-all':
  186. return dt("Revert all enabled feature module on your site.");
  187. case 'drush:features-diff':
  188. return dt("Show a diff of a feature module.");
  189. case 'drush:features-add':
  190. 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.");
  191. }
  192. }
  193. /**
  194. * Get a list of all feature modules.
  195. */
  196. function drush_features_list() {
  197. $status = drush_get_option('status') ? drush_get_option('status') : 'all';
  198. if (!in_array($status, array('enabled', 'disabled', 'all'))) {
  199. return drush_set_error('', dt('!status is not valid', array('!status' => $status)));
  200. }
  201. module_load_include('inc', 'features', 'features.export');
  202. // Sort the Features list before compiling the output.
  203. $features = features_get_features(NULL, TRUE);
  204. ksort($features);
  205. $rows = array();
  206. foreach ($features as $k => $m) {
  207. switch (features_get_storage($m->name)) {
  208. case FEATURES_DEFAULT:
  209. case FEATURES_REBUILDABLE:
  210. $storage = '';
  211. break;
  212. case FEATURES_OVERRIDDEN:
  213. $storage = dt('Overridden');
  214. break;
  215. case FEATURES_NEEDS_REVIEW:
  216. $storage = dt('Needs review');
  217. break;
  218. }
  219. if (
  220. ($m->status == 0 && ($status == 'all' || $status == 'disabled')) ||
  221. ($m->status == 1 && ($status == 'all' || $status == 'enabled'))
  222. ) {
  223. $rows[$k] = array(
  224. 'name' => $m->info['name'],
  225. 'feature' => $m->name,
  226. 'status' => $m->status ? dt('Enabled') : dt('Disabled'),
  227. 'version' => $m->info['version'],
  228. 'state' => $storage
  229. );
  230. }
  231. }
  232. if (version_compare(DRUSH_VERSION, '6.0', '<')) {
  233. drush_print_table($rows, TRUE);
  234. }
  235. return $rows;
  236. }
  237. /**
  238. * List components, with pattern matching.
  239. */
  240. function drush_features_components() {
  241. $args = func_get_args();
  242. $components = _drush_features_component_list();
  243. ksort($components);
  244. // If no args supplied, prompt with a list.
  245. if (empty($args)) {
  246. $types = array_keys($components);
  247. array_unshift($types, 'all');
  248. $choice = drush_choice($types, 'Enter a number to choose which component type to list.');
  249. if ($choice === FALSE) {
  250. return;
  251. }
  252. $args = ($choice == 0) ? array('*') : array($types[$choice]);
  253. }
  254. $options = array(
  255. 'provided by' => TRUE,
  256. );
  257. if (drush_get_option(array('exported', 'e'), NULL)) {
  258. $options['not exported'] = FALSE;
  259. }
  260. elseif (drush_get_option(array('not-exported', 'o'), NULL)) {
  261. $options['exported'] = FALSE;
  262. }
  263. if (drush_get_option(array('info-style', 'is'), NULL)) {
  264. $options['info style'] = TRUE;
  265. }
  266. $filtered_components = _drush_features_component_filter($components, $args, $options);
  267. if ($filtered_components){
  268. _drush_features_component_print($filtered_components, $options);
  269. }
  270. }
  271. /**
  272. * Returns a listing of all known components, indexed by source.
  273. */
  274. function _drush_features_component_list() {
  275. $components = array();
  276. foreach (features_get_feature_components() as $source => $info) {
  277. if ($options = features_invoke($source, 'features_export_options')) {
  278. foreach ($options as $name => $title) {
  279. $components[$source][$name] = $title;
  280. }
  281. }
  282. }
  283. return $components;
  284. }
  285. /**
  286. * Filters components by patterns.
  287. */
  288. function _drush_features_component_filter($all_components, $patterns = array(), $options = array()) {
  289. $options += array(
  290. 'exported' => TRUE,
  291. 'not exported' => TRUE,
  292. 'provided by' => FALSE,
  293. );
  294. $pool = array();
  295. // Maps exported components to feature modules.
  296. $components_map = features_get_component_map();
  297. // First filter on exported state.
  298. foreach ($all_components as $source => $components) {
  299. foreach ($components as $name => $title) {
  300. $exported = !empty($components_map[$source][$name]);
  301. if ($exported) {
  302. if ($options['exported']) {
  303. $pool[$source][$name] = $title;
  304. }
  305. }
  306. else {
  307. if ($options['not exported']) {
  308. $pool[$source][$name] = $title;
  309. }
  310. }
  311. }
  312. }
  313. $state_string = '';
  314. if (!$options['exported']) {
  315. $state_string = 'unexported';
  316. }
  317. elseif (!$options['not exported']) {
  318. $state_string = 'exported';
  319. }
  320. $selected = array();
  321. foreach ($patterns as $pattern) {
  322. // Rewrite * to %. Let users use both as wildcard.
  323. $pattern = strtr($pattern, array('*' => '%'));
  324. $sources = array();
  325. if (strpos($pattern, ':') !== FALSE) {
  326. list($source_pattern, $component_pattern) = explode(':', $pattern, 2);
  327. }
  328. else {
  329. $source_pattern = $pattern;
  330. $component_pattern = '';
  331. }
  332. // If source is empty, use a pattern.
  333. if ($source_pattern == '') {
  334. $source_pattern = '%';
  335. }
  336. if ($component_pattern == '') {
  337. $component_pattern = '%';
  338. }
  339. $preg_source_pattern = strtr(preg_quote($source_pattern, '/'), array('%' => '.*'));
  340. $preg_component_pattern = strtr(preg_quote($component_pattern, '/'), array('%' => '.*'));
  341. /*
  342. * If it isn't a pattern, but a simple string, we don't anchor the
  343. * pattern, this allows for abbreviating. Else, we do, as this seems more
  344. * natural for patterns.
  345. */
  346. if (strpos($source_pattern, '%') !== FALSE) {
  347. $preg_source_pattern = '^' . $preg_source_pattern . '$';
  348. }
  349. if (strpos($component_pattern, '%') !== FALSE) {
  350. $preg_component_pattern = '^' . $preg_component_pattern . '$';
  351. }
  352. $matches = array();
  353. // Find the sources.
  354. $all_sources = array_keys($pool);
  355. $matches = preg_grep('/' . $preg_source_pattern . '/', $all_sources);
  356. if (sizeof($matches) > 0) {
  357. // If we have multiple matches and the source string wasn't a
  358. // pattern, check if one of the matches is equal to the pattern, and
  359. // use that, or error out.
  360. if (sizeof($matches) > 1 and $preg_source_pattern[0] != '^') {
  361. if (in_array($source_pattern, $matches)) {
  362. $matches = array($source_pattern);
  363. }
  364. else {
  365. return drush_set_error('', dt('Ambiguous source "!source", matches !matches', array('!source' => $source_pattern, '!matches' => join(', ', $matches))));
  366. }
  367. }
  368. // Loose the indexes preg_grep preserved.
  369. $sources = array_values($matches);
  370. }
  371. else {
  372. return drush_set_error('', dt('No !state sources match "!source"', array('!state' => $state_string, '!source' => $source_pattern)));
  373. }
  374. // Now find the components.
  375. foreach ($sources as $source) {
  376. // Find the components.
  377. $all_components = array_keys($pool[$source]);
  378. // See if there's any matches.
  379. $matches = preg_grep('/' . $preg_component_pattern . '/', $all_components);
  380. if (sizeof($matches) > 0) {
  381. // If we have multiple matches and the components string wasn't a
  382. // pattern, check if one of the matches is equal to the pattern, and
  383. // use that, or error out.
  384. if (sizeof($matches) > 1 and $preg_component_pattern[0] != '^') {
  385. if (in_array($component_pattern, $matches)) {
  386. $matches = array($component_pattern);
  387. }
  388. else {
  389. return drush_set_error('', dt('Ambiguous component "!component", matches !matches', array('!component' => $component_pattern, '!matches' => join(', ', $matches))));
  390. }
  391. }
  392. if (empty($selected[$source])) {
  393. $selected[$source] = array();
  394. }
  395. $selected[$source] += array_intersect_key($pool[$source], array_flip($matches));
  396. }
  397. else {
  398. // No matches. If the source was a pattern, just carry on, else
  399. // error out. Allows for patterns like :*field*
  400. if ($preg_source_pattern[0] != '^') {
  401. return drush_set_error('', dt('No !state !source components match "!component"', array('!state' => $state_string, '!component' => $component_pattern, '!source' => $source)));
  402. }
  403. }
  404. }
  405. }
  406. // Lastly, provide feature module information on the selected components, if
  407. // requested.
  408. $provided_by = array();
  409. if ($options['provided by'] && $options['exported'] ) {
  410. foreach ($selected as $source => $components) {
  411. foreach ($components as $name => $title) {
  412. $exported = !empty($components_map[$source][$name]);
  413. if ($exported) {
  414. $provided_by[$source . ':' . $name] = join(', ', $components_map[$source][$name]);
  415. }
  416. }
  417. }
  418. }
  419. return array(
  420. 'components' => $selected,
  421. 'sources' => $provided_by,
  422. );
  423. }
  424. /**
  425. * Prints a list of filtered components.
  426. */
  427. function _drush_features_component_print($filtered_components, $options = array()) {
  428. $rows = array(array(dt('Available sources')));
  429. foreach ($filtered_components['components'] as $source => $components) {
  430. foreach ($components as $name => $value) {
  431. if (!empty($options['info style'])) {
  432. // Output as .info file style.
  433. $row = array('features[' . $source . '][] = "' . $name . '"');
  434. }
  435. else {
  436. $row = array($source .':'. $name);
  437. }
  438. if (isset($filtered_components['sources'][$source .':'. $name])) {
  439. $row[] = dt('Provided by') . ': ' . $filtered_components['sources'][$source .':'. $name];
  440. }
  441. $rows[] = $row;
  442. }
  443. }
  444. drush_print_table($rows, TRUE);
  445. }
  446. /**
  447. * Add a component to a features module, or create a new module with
  448. * the selected components.
  449. */
  450. function drush_features_export() {
  451. if ($args = func_get_args()) {
  452. $module = array_shift($args);
  453. if (empty($args)) {
  454. return drush_set_error('', 'No components supplied.');
  455. }
  456. $components = _drush_features_component_list();
  457. $options = array();
  458. if (!drush_get_option('ignore-conflicts', FALSE)) {
  459. $options['exported'] = FALSE;
  460. }
  461. $filtered_components = _drush_features_component_filter($components, $args, $options);
  462. $items = $filtered_components['components'];
  463. if (empty($items)) {
  464. return drush_set_error('', 'No components to add.');
  465. }
  466. $items = array_map('array_keys', $items);
  467. if (($feature = features_load_feature($module, TRUE)) && module_exists($module)) {
  468. module_load_include('inc', 'features', 'features.export');
  469. _features_populate($items, $feature->info, $feature->name);
  470. _drush_features_export($feature->info, $feature->name, dirname($feature->filename));
  471. }
  472. elseif ($feature) {
  473. _features_drush_set_error($module, 'FEATURES_FEATURE_NOT_ENABLED');
  474. }
  475. else {
  476. // Same logic as in _drush_features_export. Should be refactored.
  477. $destination = drush_get_option(array('destination'), variable_get('features_default_export_path', FEATURES_DEFAULT_EXPORT_PATH));
  478. $directory = isset($directory) ? $directory : $destination . '/' . $module;
  479. drush_print(dt('Will create a new module in !dir', array('!dir' => $directory)));
  480. if (!drush_confirm(dt('Do you really want to continue?'))) {
  481. drush_die('Aborting.');
  482. }
  483. $export = _drush_features_generate_export($items, $module);
  484. _features_populate($items, $export['info'], $export['name']);
  485. _drush_features_export($export['info'], $module, $directory);
  486. }
  487. }
  488. else {
  489. return drush_set_error('', 'No feature name given.');
  490. }
  491. }
  492. /**
  493. * Add a component to a features module
  494. * the selected components.
  495. *
  496. * This is DEPRECATED, but keeping it around for a bit longer for user migration
  497. */
  498. function drush_features_add() {
  499. drush_print(dt('features-add is DEPRECATED.'));
  500. drush_print(dt('Calling features-export instead.'));
  501. drush_features_export();
  502. }
  503. /**
  504. * Update an existing feature module.
  505. */
  506. function drush_features_update() {
  507. if ($args = func_get_args()) {
  508. foreach ($args as $module) {
  509. if (($feature = features_load_feature($module, TRUE)) && module_exists($module)) {
  510. _drush_features_export($feature->info, $feature->name, dirname($feature->filename));
  511. }
  512. elseif ($feature) {
  513. _features_drush_set_error($module, 'FEATURES_FEATURE_NOT_ENABLED');
  514. }
  515. else {
  516. _features_drush_set_error($module);
  517. }
  518. }
  519. }
  520. else {
  521. // By default just show contexts that are available.
  522. $rows = array(array(dt('Available features')));
  523. foreach (features_get_features(NULL, TRUE) as $name => $info) {
  524. $rows[] = array($name);
  525. }
  526. drush_print_table($rows, TRUE);
  527. }
  528. }
  529. /**
  530. * Update all enabled features. Optionally pass in a list of features to
  531. * exclude from being updated.
  532. */
  533. function drush_features_update_all() {
  534. $features_to_update = array();
  535. $features_to_exclude = func_get_args();
  536. foreach (features_get_features() as $module) {
  537. if ($module->status && !in_array($module->name, $features_to_exclude)) {
  538. $features_to_update[] = $module->name;
  539. }
  540. }
  541. $dt_args = array('!modules' => implode(', ', $features_to_update));
  542. drush_print(dt('The following modules will be updated: !modules', $dt_args));
  543. if (!drush_confirm(dt('Do you really want to continue?'))) {
  544. return drush_user_abort('Aborting.');
  545. }
  546. // If we got here, set affirmative to TRUE, so that the user doesn't have to
  547. // confirm each and every feature. Start off by storing the current value,
  548. // so we can set it back afteward.
  549. $skip_confirmation = drush_get_context('DRUSH_AFFIRMATIVE');
  550. drush_set_context('DRUSH_AFFIRMATIVE', TRUE);
  551. // Now update all the features.
  552. drush_invoke('features-update', $features_to_update);
  553. // Now set it back as it was, in case other commands are called after this.
  554. drush_set_context('DRUSH_AFFIRMATIVE', $skip_confirmation);
  555. }
  556. /**
  557. * Write a module to the site dir.
  558. *
  559. * @param $info
  560. * The feature info associative array.
  561. * @param $module_name
  562. * Optional. The name for the exported module.
  563. */
  564. function _drush_features_export($info, $module_name = NULL, $directory = NULL) {
  565. $root = drush_get_option(array('r', 'root'), drush_locate_root());
  566. $skip_confirmation = drush_get_context('DRUSH_AFFIRMATIVE');
  567. if ($root) {
  568. $destination = drush_get_option(array('destination'), variable_get('features_default_export_path', FEATURES_DEFAULT_EXPORT_PATH));
  569. $directory = isset($directory) ? $directory : $destination . '/' . $module_name;
  570. if (is_dir($directory)) {
  571. // If we aren't skipping confirmation and the directory already exists,
  572. // prompt the user. This message most make sense for but fe and fu.
  573. if (!$skip_confirmation && !drush_confirm(dt('Module located at !dir will be updated. Do you want to continue?', array('!dir' => $directory)))) {
  574. drush_die('Aborting.');
  575. }
  576. }
  577. else {
  578. drush_op('mkdir', $directory);
  579. }
  580. if (is_dir($directory)) {
  581. // Ensure that the export will be created in the English language.
  582. // The export language must be set before flushing caches as that can
  583. // result into translatables being statically cached.
  584. $language = _features_export_language();
  585. drupal_flush_all_caches();
  586. $export = _drush_features_generate_export($info, $module_name);
  587. $files = features_export_render($export, $module_name, TRUE);
  588. // Restore the language
  589. _features_export_language($language);
  590. // Copy any files if _files key is there.
  591. if (!empty($files['_files'])) {
  592. foreach ($files['_files'] as $file_name => $file_info) {
  593. // See if files are in a sub directory.
  594. if (strpos($file_name, '/')) {
  595. $file_directory = $directory . '/' . substr($file_name, 0, strrpos($file_name, '/'));
  596. if (!is_dir($file_directory)) {
  597. drush_op('mkdir', $file_directory);
  598. }
  599. }
  600. if (!empty($file_info['file_path'])) {
  601. drush_op('file_unmanaged_copy', $file_info['file_path'], "{$directory}/{$file_name}", FILE_EXISTS_REPLACE);
  602. }
  603. elseif (!empty($file_info['file_content'])) {
  604. drush_op('file_put_contents', "{$directory}/{$file_name}", $file_info['file_content']);
  605. }
  606. else {
  607. drush_log(dt("Entry for @file_name.in !module is invalid. ", array('!module' => $module_name, '@file_name' => $file_name)), 'ok');
  608. }
  609. }
  610. unset($files['_files']);
  611. }
  612. foreach ($files as $extension => $file_contents) {
  613. if (!in_array($extension, array('module', 'info'))) {
  614. $extension .= '.inc';
  615. }
  616. drush_op('file_put_contents', "{$directory}/{$module_name}.$extension", $file_contents);
  617. }
  618. drush_log(dt("Created module: !module in !directory", array('!module' => $module_name, '!directory' => $directory)), 'ok');
  619. }
  620. else {
  621. drush_die(dt('Couldn\'t create directory !directory', array('!directory' => $directory)));
  622. }
  623. }
  624. else {
  625. drush_die(dt('Couldn\'t locate site root'));
  626. }
  627. }
  628. /**
  629. * Helper function for _drush_feature_export.
  630. *
  631. * @param $info
  632. * The feature info associative array.
  633. * @param $module_name
  634. * Optional. The name for the exported module.
  635. */
  636. function _drush_features_generate_export(&$info, &$module_name) {
  637. module_load_include('inc', 'features', 'features.export');
  638. $export = features_populate($info, $module_name);
  639. if (!features_load_feature($module_name)) {
  640. $export['name'] = $module_name;
  641. }
  642. // Set the feature version if the --version-set or --version-increment option is passed.
  643. if ($version = drush_get_option(array('version-set'))) {
  644. preg_match('/^(?P<core>\d+\.x)-(?P<major>\d+)\.(?P<patch>\d+)-?(?P<extra>\w+)?$/', $version, $matches);
  645. if (!isset($matches['core'], $matches['major'])) {
  646. drush_die(dt('Please enter a valid version with core and major version number. Example: !example', array('!example' => '7.x-1.0')));
  647. }
  648. $export['version'] = $version;
  649. }
  650. elseif ($version = drush_get_option(array('version-increment'))) {
  651. // Determine current version and increment it.
  652. $export_load = features_export_prepare($export, $module_name);
  653. $version = $export_load['version'];
  654. $version_explode = explode('.', $version);
  655. $version_minor = array_pop($version_explode);
  656. // Increment minor version number if numeric or not a dev release.
  657. if (is_numeric($version_minor) || strpos($version_minor, 'dev') !== (strlen($version_minor) - 3)) {
  658. // Check for prefixed versions (alpha, beta, rc).
  659. if (ctype_digit($version_minor)) {
  660. ++$version_minor;
  661. }
  662. else {
  663. // Split version number parts.
  664. $pattern = '/([0-9]-[a-z]+([0-9]+))/';
  665. $matches = array();
  666. preg_match($pattern, $version_minor, $matches);
  667. $number = array_pop($matches);
  668. ++$number;
  669. $pattern = '/[0-9]+$/';
  670. $version_minor = preg_replace($pattern, $number, $version_minor);
  671. }
  672. }
  673. array_push($version_explode, $version_minor);
  674. // Rebuild version string.
  675. $version = implode('.', $version_explode);
  676. $export['version'] = $version;
  677. }
  678. return $export;
  679. }
  680. /**
  681. * Revert a feature to it's code definition.
  682. * Optionally accept a list of components to revert.
  683. */
  684. function drush_features_revert() {
  685. if ($args = func_get_args()) {
  686. module_load_include('inc', 'features', 'features.export');
  687. features_include();
  688. // Determine if revert should be forced.
  689. $force = drush_get_option('force');
  690. // Determine if -y was supplied. If so, we can filter out needless output
  691. // from this command.
  692. $skip_confirmation = drush_get_context('DRUSH_AFFIRMATIVE');
  693. // Parse list of arguments.
  694. $modules = array();
  695. foreach ($args as $arg) {
  696. $arg = explode('.', $arg);
  697. $module = array_shift($arg);
  698. $component = array_shift($arg);
  699. if (isset($module)) {
  700. if (empty($component)) {
  701. // If we received just a feature name, this means that we need all of it's components.
  702. $modules[$module] = TRUE;
  703. }
  704. elseif ($modules[$module] !== TRUE) {
  705. if (!isset($modules[$module])) {
  706. $modules[$module] = array();
  707. }
  708. $modules[$module][] = $component;
  709. }
  710. }
  711. }
  712. // Process modules.
  713. foreach ($modules as $module => $components_needed) {
  714. $dt_args['@module'] = $module;
  715. if (($feature = features_load_feature($module, TRUE)) && module_exists($module)) {
  716. $components = array();
  717. // Forcefully revert all components of a feature.
  718. if ($force) {
  719. foreach (array_keys($feature->info['features']) as $component) {
  720. if (features_hook($component, 'features_revert')) {
  721. $components[] = $component;
  722. }
  723. }
  724. }
  725. // Only revert components that are detected to be Overridden/Needs review/rebuildable.
  726. else {
  727. $states = features_get_component_states(array($feature->name), FALSE);
  728. foreach ($states[$feature->name] as $component => $state) {
  729. $revertable_states = array(FEATURES_OVERRIDDEN, FEATURES_NEEDS_REVIEW, FEATURES_REBUILDABLE);
  730. if (in_array($state, $revertable_states) && features_hook($component, 'features_revert')) {
  731. $components[] = $component;
  732. }
  733. }
  734. }
  735. if (!empty($components_needed) && is_array($components_needed)) {
  736. $components = array_intersect($components, $components_needed);
  737. }
  738. if (empty($components)) {
  739. drush_log(dt('Current state already matches defaults, aborting.'), 'ok');
  740. }
  741. else {
  742. foreach ($components as $component) {
  743. $dt_args['@component'] = $component;
  744. $confirmation_message = 'Do you really want to revert @module.@component?';
  745. if ($skip_confirmation || drush_confirm(dt($confirmation_message, $dt_args))) {
  746. if (features_feature_is_locked($module, $component)) {
  747. drush_log(dt('Skipping locked @module.@component.', $dt_args), 'ok');
  748. }
  749. else {
  750. features_revert(array($module => array($component)));
  751. drush_log(dt('Reverted @module.@component.', $dt_args), 'ok');
  752. }
  753. }
  754. else {
  755. drush_log(dt('Skipping @module.@component.', $dt_args), 'ok');
  756. }
  757. }
  758. }
  759. }
  760. elseif ($feature) {
  761. _features_drush_set_error($module, 'FEATURES_FEATURE_NOT_ENABLED');
  762. }
  763. else {
  764. _features_drush_set_error($module);
  765. }
  766. }
  767. }
  768. else {
  769. drush_print_table(drush_features_list());
  770. return;
  771. }
  772. }
  773. /**
  774. * Revert all enabled features to their definitions in code.
  775. *
  776. * @param ...
  777. * (Optional) A list of features to exclude from being reverted.
  778. */
  779. function drush_features_revert_all() {
  780. module_load_include('inc', 'features', 'features.export');
  781. $force = drush_get_option('force');
  782. $features_to_exclude = func_get_args();
  783. $features_to_revert = array();
  784. foreach (features_get_features(NULL, TRUE) as $module) {
  785. if ($module->status && !in_array($module->name, $features_to_exclude)) {
  786. // If forced, add module regardless of status.
  787. if ($force) {
  788. $features_to_revert[] = $module->name;
  789. }
  790. else {
  791. switch (features_get_storage($module->name)) {
  792. case FEATURES_OVERRIDDEN:
  793. case FEATURES_NEEDS_REVIEW:
  794. case FEATURES_REBUILDABLE:
  795. $features_to_revert[] = $module->name;
  796. break;
  797. }
  798. }
  799. }
  800. }
  801. if ($features_to_revert) {
  802. $dt_args = array('!modules' => implode(', ', $features_to_revert));
  803. drush_print(dt('The following modules will be reverted: !modules', $dt_args));
  804. // Confirm that the user would like to continue. If not, simply abort.
  805. if (!drush_confirm(dt('Do you really want to continue?'))) {
  806. return drush_user_abort('Aborting.');
  807. }
  808. drush_invoke('features-revert', $features_to_revert);
  809. }
  810. else {
  811. drush_log(dt('Current state already matches defaults, aborting.'), 'ok');
  812. }
  813. }
  814. /**
  815. * Show the diff of a feature module.
  816. */
  817. function drush_features_diff() {
  818. if (!$args = func_get_args()) {
  819. drush_print_table(drush_features_list());
  820. return;
  821. }
  822. $module = $args[0];
  823. $filter_ctypes = drush_get_option("ctypes");
  824. if ($filter_ctypes) {
  825. $filter_ctypes = explode(',', $filter_ctypes);
  826. }
  827. $feature = features_load_feature($module);
  828. if (!module_exists($module)) {
  829. drush_log(dt('No such feature is enabled: ' . $module), 'error');
  830. return;
  831. }
  832. module_load_include('inc', 'features', 'features.export');
  833. $overrides = features_detect_overrides($feature);
  834. if (empty($overrides)) {
  835. drush_log(dt('Feature is in its default state. No diff needed.'), 'ok');
  836. return;
  837. }
  838. module_load_include('inc', 'diff', 'diff.engine');
  839. if (!class_exists('DiffFormatter')) {
  840. if (drush_confirm(dt('It seems that the Diff module is not available. Would you like to download and enable it?'))) {
  841. // Download it if it's not already here.
  842. $project_info = drush_get_projects();
  843. if (empty($project_info['diff']) && !drush_invoke('dl', array('diff'))) {
  844. return drush_set_error(dt('Diff module could not be downloaded.'));
  845. }
  846. if (!drush_invoke('en', array('diff'))) {
  847. return drush_set_error(dt('Diff module could not be enabled.'));
  848. }
  849. }
  850. else {
  851. return drush_set_error(dt('Diff module is not enabled.'));
  852. }
  853. // If we're still here, now we can include the diff.engine again.
  854. module_load_include('inc', 'diff', 'diff.engine');
  855. }
  856. $lines = (int) drush_get_option('lines');
  857. $lines = $lines > 0 ? $lines : 2;
  858. $formatter = new DiffFormatter();
  859. $formatter->leading_context_lines = $lines;
  860. $formatter->trailing_context_lines = $lines;
  861. $formatter->show_header = FALSE;
  862. if (drush_get_context('DRUSH_NOCOLOR')) {
  863. $red = $green = "%s";
  864. }
  865. else {
  866. $red = "\033[31;40m\033[1m%s\033[0m";
  867. $green = "\033[0;32;40m\033[1m%s\033[0m";
  868. }
  869. // Print key for colors
  870. drush_print(dt('Legend: '));
  871. drush_print(sprintf($red, dt('Code: drush features-revert will remove the overrides.')));
  872. drush_print(sprintf($green, dt('Overrides: drush features-update will update the exported feature with the displayed overrides')));
  873. drush_print();
  874. if ($filter_ctypes) {
  875. $overrides = array_intersect_key($overrides, array_flip($filter_ctypes));
  876. }
  877. foreach ($overrides as $component => $items) {
  878. $diff = new Diff(explode("\n", $items['default']), explode("\n", $items['normal']));
  879. drush_print();
  880. drush_print(dt("Component type: !component", array('!component' => $component)));
  881. $rows = explode("\n", $formatter->format($diff));
  882. foreach ($rows as $row) {
  883. if (strpos($row, '>') === 0) {
  884. drush_print(sprintf($green, $row));
  885. }
  886. elseif (strpos($row, '<') === 0) {
  887. drush_print(sprintf($red, $row));
  888. }
  889. else {
  890. drush_print($row);
  891. }
  892. }
  893. }
  894. }
  895. /**
  896. * Diff all enabled features that are not in their default state.
  897. *
  898. * @param ...
  899. * (Optional) A list of features to exclude from being reverted.
  900. */
  901. function drush_features_diff_all() {
  902. module_load_include('inc', 'features', 'features.export');
  903. $features_to_exclude = func_get_args();
  904. $features_to_revert = array();
  905. foreach (features_get_features(NULL, TRUE) as $module) {
  906. if ($module->status && !in_array($module->name, $features_to_exclude)) {
  907. switch (features_get_storage($module->name)) {
  908. case FEATURES_OVERRIDDEN:
  909. case FEATURES_NEEDS_REVIEW:
  910. case FEATURES_REBUILDABLE:
  911. $features_to_diff[] = $module->name;
  912. break;
  913. }
  914. }
  915. }
  916. if ($features_to_diff) {
  917. // Check if the user wants to apply the force option.
  918. $force = drush_get_option('force');
  919. if($force) {
  920. foreach ($features_to_diff as $module) {
  921. drush_print(dt('Diff for !module:', array('!module' => $module)));
  922. drush_invoke_process(drush_sitealias_get_record('@self'), 'features-diff', array($module));
  923. }
  924. }
  925. else {
  926. drush_print(dt('A diff will be performed for the following modules: !modules',
  927. array('!modules' => implode(', ', $features_to_diff))
  928. ));
  929. if (drush_confirm(dt('Do you want to continue?'))) {
  930. foreach ($features_to_diff as $module) {
  931. if (drush_confirm(dt('Diff !module?', array('!module' => $module)))) {
  932. drush_invoke_process(drush_sitealias_get_record('@self'), 'features-diff', array($module));
  933. }
  934. }
  935. }
  936. else {
  937. return drush_user_abort('Aborting.');
  938. }
  939. }
  940. }
  941. }
  942. /**
  943. * Helper function to call drush_set_error().
  944. *
  945. * @param $feature
  946. * The string name of the feature.
  947. * @param $error
  948. * A text string identifying the type of error.
  949. * @return
  950. * FALSE. See drush_set_error().
  951. */
  952. function _features_drush_set_error($feature, $error = '') {
  953. $args = array('!feature' => $feature);
  954. switch ($error) {
  955. case 'FEATURES_FEATURE_NOT_ENABLED':
  956. $message = 'The feature !feature is not enabled.';
  957. break;
  958. case 'FEATURES_COMPONENT_NOT_FOUND':
  959. $message = 'The given component !feature could not be found.';
  960. break;
  961. default:
  962. $error = 'FEATURES_FEATURE_NOT_FOUND';
  963. $message = 'The feature !feature could not be found.';
  964. }
  965. return drush_set_error($error, dt($message, $args));
  966. }