features.drush.inc 33 KB

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