features.drush.inc 37 KB

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