features.drush.inc 41 KB

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