danielebarchiesi@4: "List all the available features for your site.", danielebarchiesi@4: 'options' => array( danielebarchiesi@4: 'status' => "Feature status, can be 'enabled', 'disabled' or 'all'", danielebarchiesi@4: ), danielebarchiesi@4: 'drupal dependencies' => array('features'), danielebarchiesi@4: 'aliases' => array('fl', 'features'), danielebarchiesi@4: ); danielebarchiesi@4: $items['features-export'] = array( danielebarchiesi@4: 'description' => "Export a feature from your site into a module.", danielebarchiesi@4: 'arguments' => array( danielebarchiesi@4: 'feature' => 'Feature name to export.', danielebarchiesi@4: 'components' => 'Patterns of components to include, see features-components for the format of patterns.' danielebarchiesi@4: ), danielebarchiesi@4: 'options' => array( danielebarchiesi@4: 'destination' => "Destination path (from Drupal root) of the exported feature. Defaults to 'sites/all/modules/features'", danielebarchiesi@4: 'version-set' => "Specify a version number for the feature.", danielebarchiesi@4: 'version-increment' => "Increment the feature's version number.", danielebarchiesi@4: ), danielebarchiesi@4: 'drupal dependencies' => array('features'), danielebarchiesi@4: 'aliases' => array('fe'), danielebarchiesi@4: ); danielebarchiesi@4: $items['features-add'] = array( danielebarchiesi@4: 'description' => "Add a component to a feature module. (DEPRECATED: use features-export)", danielebarchiesi@4: 'arguments' => array( danielebarchiesi@4: 'feature' => 'Feature name to add to.', danielebarchiesi@4: 'components' => 'List of components to add.', danielebarchiesi@4: ), danielebarchiesi@4: 'options' => array( danielebarchiesi@4: 'version-set' => "Specify a version number for the feature.", danielebarchiesi@4: 'version-increment' => "Increment the feature's version number.", danielebarchiesi@4: ), danielebarchiesi@4: 'drupal dependencies' => array('features'), danielebarchiesi@4: 'aliases' => array('fa'), danielebarchiesi@4: ); danielebarchiesi@4: $items['features-components'] = array( danielebarchiesi@4: 'description' => 'List features components.', danielebarchiesi@4: 'arguments' => array( danielebarchiesi@4: 'patterns' => 'The features components type to list. Omit this argument to list all components.', danielebarchiesi@4: ), danielebarchiesi@4: 'options' => array( danielebarchiesi@4: 'exported' => array( danielebarchiesi@4: 'description' => 'Show only components that have been exported.', danielebarchiesi@4: ), danielebarchiesi@4: 'not-exported' => array( danielebarchiesi@4: 'description' => 'Show only components that have not been exported.', danielebarchiesi@4: ), danielebarchiesi@4: ), danielebarchiesi@4: 'aliases' => array('fc'), danielebarchiesi@4: ); danielebarchiesi@4: $items['features-update'] = array( danielebarchiesi@4: 'description' => "Update a feature module on your site.", danielebarchiesi@4: 'arguments' => array( danielebarchiesi@4: 'feature' => 'A space delimited list of features.', danielebarchiesi@4: ), danielebarchiesi@4: 'options' => array( danielebarchiesi@4: 'version-set' => "Specify a version number for the feature.", danielebarchiesi@4: 'version-increment' => "Increment the feature's version number.", danielebarchiesi@4: ), danielebarchiesi@4: 'drupal dependencies' => array('features'), danielebarchiesi@4: 'aliases' => array('fu'), danielebarchiesi@4: ); danielebarchiesi@4: $items['features-update-all'] = array( danielebarchiesi@4: 'description' => "Update all feature modules on your site.", danielebarchiesi@4: 'arguments' => array( danielebarchiesi@4: 'feature_exclude' => 'A space-delimited list of features to exclude from being updated.', danielebarchiesi@4: ), danielebarchiesi@4: 'drupal dependencies' => array('features'), danielebarchiesi@4: 'aliases' => array('fu-all', 'fua'), danielebarchiesi@4: ); danielebarchiesi@4: $items['features-revert'] = array( danielebarchiesi@4: 'description' => "Revert a feature module on your site.", danielebarchiesi@4: 'arguments' => array( danielebarchiesi@4: 'feature' => 'A space delimited list of features or feature.component pairs.', danielebarchiesi@4: ), danielebarchiesi@4: 'options' => array( danielebarchiesi@4: 'force' => "Force revert even if Features assumes components' state are default.", danielebarchiesi@4: ), danielebarchiesi@4: 'examples' => array( danielebarchiesi@4: '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".', danielebarchiesi@4: 'drush fr foo.node foo.taxonomy bar --force' => 'Revert node and taxonomy components of feature "foo". Revert all components of feature "bar".', danielebarchiesi@4: ), danielebarchiesi@4: 'drupal dependencies' => array('features'), danielebarchiesi@4: 'aliases' => array('fr'), danielebarchiesi@4: ); danielebarchiesi@4: $items['features-revert-all'] = array( danielebarchiesi@4: 'description' => "Revert all enabled feature module on your site.", danielebarchiesi@4: 'arguments' => array( danielebarchiesi@4: 'feature_exclude' => 'A space-delimited list of features to exclude from being reverted.', danielebarchiesi@4: ), danielebarchiesi@4: 'options' => array( danielebarchiesi@4: 'force' => "Force revert even if Features assumes components' state are default.", danielebarchiesi@4: ), danielebarchiesi@4: 'drupal dependencies' => array('features'), danielebarchiesi@4: 'aliases' => array('fr-all', 'fra'), danielebarchiesi@4: ); danielebarchiesi@4: $items['features-diff'] = array( danielebarchiesi@4: 'description' => "Show the difference between the default and overridden state of a feature.", danielebarchiesi@4: 'arguments' => array( danielebarchiesi@4: 'feature' => 'The feature in question.', danielebarchiesi@4: ), danielebarchiesi@4: 'options' => array( danielebarchiesi@4: 'lines' => 'Generate diffs with lines of context instead of the usual two.', danielebarchiesi@4: ), danielebarchiesi@4: 'drupal dependencies' => array('features', 'diff'), danielebarchiesi@4: 'aliases' => array('fd'), danielebarchiesi@4: ); danielebarchiesi@4: danielebarchiesi@4: return $items; danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Implements hook_drush_help(). danielebarchiesi@4: */ danielebarchiesi@4: function features_drush_help($section) { danielebarchiesi@4: switch ($section) { danielebarchiesi@4: case 'drush:features': danielebarchiesi@4: return dt("List all the available features for your site."); danielebarchiesi@4: case 'drush:features-export': danielebarchiesi@4: return dt("Export a feature from your site into a module. If called with no arguments, display a list of available components. If called with a single argument, attempt to create a feature including the given component with the same name. The option '--destination=foo' may be used to specify the path (from Drupal root) where the feature should be created. The default destination is 'sites/all/modules/features'. 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."); danielebarchiesi@4: case 'drush:features-components': danielebarchiesi@4: return dt("List feature components matching patterns. The listing may be limited to exported/not-exported components. danielebarchiesi@4: danielebarchiesi@4: 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%\"). danielebarchiesi@4: danielebarchiesi@4: 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. danielebarchiesi@4: danielebarchiesi@4: 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. danielebarchiesi@4: danielebarchiesi@4: Lastly, a pattern without a colon is interpreted as having \":%\" appended, for easy listing of all components of a source. danielebarchiesi@4: "); danielebarchiesi@4: case 'drush:features-update': danielebarchiesi@4: 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."); danielebarchiesi@4: case 'drush:features-update-all': danielebarchiesi@4: return dt("Update all feature modules on your site."); danielebarchiesi@4: case 'drush:features-revert': danielebarchiesi@4: return dt("Revert a feature module on your site."); danielebarchiesi@4: case 'drush:features-revert-all': danielebarchiesi@4: return dt("Revert all enabled feature module on your site."); danielebarchiesi@4: case 'drush:features-diff': danielebarchiesi@4: return dt("Show a diff of a feature module."); danielebarchiesi@4: case 'drush:features-add': danielebarchiesi@4: 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."); danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Get a list of all feature modules. danielebarchiesi@4: */ danielebarchiesi@4: function drush_features_list() { danielebarchiesi@4: $status = drush_get_option('status') ? drush_get_option('status') : 'all'; danielebarchiesi@4: if (!in_array($status, array('enabled', 'disabled', 'all'))) { danielebarchiesi@4: return drush_set_error('', dt('!status is not valid', array('!status' => $status))); danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: module_load_include('inc', 'features', 'features.export'); danielebarchiesi@4: $rows = array(array(dt('Name'), dt('Feature'), dt('Status'), dt('Version'), dt('State'))); danielebarchiesi@4: danielebarchiesi@4: // Sort the Features list before compiling the output. danielebarchiesi@4: $features = features_get_features(NULL, TRUE); danielebarchiesi@4: ksort($features); danielebarchiesi@4: danielebarchiesi@4: foreach ($features as $k => $m) { danielebarchiesi@4: switch (features_get_storage($m->name)) { danielebarchiesi@4: case FEATURES_DEFAULT: danielebarchiesi@4: case FEATURES_REBUILDABLE: danielebarchiesi@4: $storage = ''; danielebarchiesi@4: break; danielebarchiesi@4: case FEATURES_OVERRIDDEN: danielebarchiesi@4: $storage = dt('Overridden'); danielebarchiesi@4: break; danielebarchiesi@4: case FEATURES_NEEDS_REVIEW: danielebarchiesi@4: $storage = dt('Needs review'); danielebarchiesi@4: break; danielebarchiesi@4: } danielebarchiesi@4: if ( danielebarchiesi@4: ($m->status == 0 && ($status == 'all' || $status == 'disabled')) || danielebarchiesi@4: ($m->status == 1 && ($status == 'all' || $status == 'enabled')) danielebarchiesi@4: ) { danielebarchiesi@4: $rows[] = array( danielebarchiesi@4: $m->info['name'], danielebarchiesi@4: $m->name, danielebarchiesi@4: $m->status ? dt('Enabled') : dt('Disabled'), danielebarchiesi@4: $m->info['version'], danielebarchiesi@4: $storage danielebarchiesi@4: ); danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: drush_print_table($rows, TRUE); danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * List components, with pattern matching. danielebarchiesi@4: */ danielebarchiesi@4: function drush_features_components() { danielebarchiesi@4: $args = func_get_args(); danielebarchiesi@4: $components = _drush_features_component_list(); danielebarchiesi@4: // If no args supplied, prompt with a list. danielebarchiesi@4: if (empty($args)) { danielebarchiesi@4: $types = array_keys($components); danielebarchiesi@4: array_unshift($types, 'all'); danielebarchiesi@4: $choice = drush_choice($types, 'Enter a number to choose which component type to list.'); danielebarchiesi@4: if ($choice === FALSE) { danielebarchiesi@4: return; danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: $args = ($choice == 0) ? array('*') : array($types[$choice]); danielebarchiesi@4: } danielebarchiesi@4: $options = array( danielebarchiesi@4: 'provided by' => TRUE, danielebarchiesi@4: ); danielebarchiesi@4: if (drush_get_option(array('exported', 'e'), NULL)) { danielebarchiesi@4: $options['not exported'] = FALSE; danielebarchiesi@4: } danielebarchiesi@4: elseif (drush_get_option(array('not-exported', 'o'), NULL)) { danielebarchiesi@4: $options['exported'] = FALSE; danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: $filtered_components = _drush_features_component_filter($components, $args, $options); danielebarchiesi@4: if ($filtered_components){ danielebarchiesi@4: _drush_features_component_print($filtered_components); danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Returns a listing of all known components, indexed by source. danielebarchiesi@4: */ danielebarchiesi@4: function _drush_features_component_list() { danielebarchiesi@4: $components = array(); danielebarchiesi@4: foreach (features_get_feature_components() as $source => $info) { danielebarchiesi@4: if ($options = features_invoke($source, 'features_export_options')) { danielebarchiesi@4: foreach ($options as $name => $title) { danielebarchiesi@4: $components[$source][$name] = $title; danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: return $components; danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Filters components by patterns. danielebarchiesi@4: */ danielebarchiesi@4: function _drush_features_component_filter($all_components, $patterns = array(), $options = array()) { danielebarchiesi@4: $options += array( danielebarchiesi@4: 'exported' => TRUE, danielebarchiesi@4: 'not exported' => TRUE, danielebarchiesi@4: 'provided by' => FALSE, danielebarchiesi@4: ); danielebarchiesi@4: $pool = array(); danielebarchiesi@4: // Maps exported components to feature modules. danielebarchiesi@4: $components_map = features_get_component_map(); danielebarchiesi@4: // First filter on exported state. danielebarchiesi@4: foreach ($all_components as $source => $components) { danielebarchiesi@4: foreach ($components as $name => $title) { danielebarchiesi@4: $exported = sizeof($components_map[$source][$name]) > 0; danielebarchiesi@4: if ($exported) { danielebarchiesi@4: if ($options['exported']) { danielebarchiesi@4: $pool[$source][$name] = $title; danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: else { danielebarchiesi@4: if ($options['not exported']) { danielebarchiesi@4: $pool[$source][$name] = $title; danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: $state_string = ''; danielebarchiesi@4: danielebarchiesi@4: if (!$options['exported']) { danielebarchiesi@4: $state_string = 'unexported'; danielebarchiesi@4: } danielebarchiesi@4: elseif (!$options['not exported']) { danielebarchiesi@4: $state_string = 'exported'; danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: $selected = array(); danielebarchiesi@4: foreach ($patterns as $pattern) { danielebarchiesi@4: // Rewrite * to %. Let users use both as wildcard. danielebarchiesi@4: $pattern = strtr($pattern, array('*' => '%')); danielebarchiesi@4: $sources = array(); danielebarchiesi@4: list($source_pattern, $component_pattern) = explode(':', $pattern, 2); danielebarchiesi@4: // If source is empty, use a pattern. danielebarchiesi@4: if ($source_pattern == '') { danielebarchiesi@4: $source_pattern = '%'; danielebarchiesi@4: } danielebarchiesi@4: if ($component_pattern == '') { danielebarchiesi@4: $component_pattern = '%'; danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: $preg_source_pattern = strtr(preg_quote($source_pattern, '/'), array('%' => '.*')); danielebarchiesi@4: $preg_component_pattern = strtr(preg_quote($component_pattern, '/'), array('%' => '.*')); danielebarchiesi@4: /* danielebarchiesi@4: * If it isn't a pattern, but a simple string, we don't anchor the danielebarchiesi@4: * pattern, this allows for abbreviating. Else, we do, as this seems more danielebarchiesi@4: * natural for patterns. danielebarchiesi@4: */ danielebarchiesi@4: if (strpos($source_pattern, '%') !== FALSE) { danielebarchiesi@4: $preg_source_pattern = '^' . $preg_source_pattern . '$'; danielebarchiesi@4: } danielebarchiesi@4: if (strpos($component_pattern, '%') !== FALSE) { danielebarchiesi@4: $preg_component_pattern = '^' . $preg_component_pattern . '$'; danielebarchiesi@4: } danielebarchiesi@4: $matches = array(); danielebarchiesi@4: danielebarchiesi@4: // Find the sources. danielebarchiesi@4: $all_sources = array_keys($pool); danielebarchiesi@4: $matches = preg_grep('/' . $preg_source_pattern . '/', $all_sources); danielebarchiesi@4: if (sizeof($matches) > 0) { danielebarchiesi@4: // If we have multiple matches and the source string wasn't a danielebarchiesi@4: // pattern, check if one of the matches is equal to the pattern, and danielebarchiesi@4: // use that, or error out. danielebarchiesi@4: if (sizeof($matches) > 1 and $preg_source_pattern[0] != '^') { danielebarchiesi@4: if (in_array($source_pattern, $matches)) { danielebarchiesi@4: $matches = array($source_pattern); danielebarchiesi@4: } danielebarchiesi@4: else { danielebarchiesi@4: return drush_set_error('', dt('Ambiguous source "!source", matches !matches', array('!source' => $source_pattern, '!matches' => join(', ', $matches)))); danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: // Loose the indexes preg_grep preserved. danielebarchiesi@4: $sources = array_values($matches); danielebarchiesi@4: } danielebarchiesi@4: else { danielebarchiesi@4: return drush_set_error('', dt('No !state sources match "!source"', array('!state' => $state_string, '!source' => $source_pattern))); danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: danielebarchiesi@4: // Now find the components. danielebarchiesi@4: foreach ($sources as $source) { danielebarchiesi@4: // Find the components. danielebarchiesi@4: $all_components = array_keys($pool[$source]); danielebarchiesi@4: // See if there's any matches. danielebarchiesi@4: $matches = preg_grep('/' . $preg_component_pattern . '/', $all_components); danielebarchiesi@4: if (sizeof($matches) > 0) { danielebarchiesi@4: // If we have multiple matches and the components string wasn't a danielebarchiesi@4: // pattern, check if one of the matches is equal to the pattern, and danielebarchiesi@4: // use that, or error out. danielebarchiesi@4: if (sizeof($matches) > 1 and $preg_component_pattern[0] != '^') { danielebarchiesi@4: if (in_array($component_pattern, $matches)) { danielebarchiesi@4: $matches = array($component_pattern); danielebarchiesi@4: } danielebarchiesi@4: else { danielebarchiesi@4: return drush_set_error('', dt('Ambiguous component "!component", matches !matches', array('!component' => $component_pattern, '!matches' => join(', ', $matches)))); danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: if (!is_array($selected[$source])) { danielebarchiesi@4: $selected[$source] = array(); danielebarchiesi@4: } danielebarchiesi@4: $selected[$source] += array_intersect_key($pool[$source], array_flip($matches)); danielebarchiesi@4: } danielebarchiesi@4: else { danielebarchiesi@4: // No matches. If the source was a pattern, just carry on, else danielebarchiesi@4: // error out. Allows for patterns like :*field* danielebarchiesi@4: if ($preg_source_pattern[0] != '^') { danielebarchiesi@4: return drush_set_error('', dt('No !state !source components match "!component"', array('!state' => $state_string, '!component' => $component_pattern, '!source' => $source))); danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: // Lastly, provide feature module information on the selected components, if danielebarchiesi@4: // requested. danielebarchiesi@4: $provided_by = array(); danielebarchiesi@4: if ($options['provided by'] && $options['exported'] ) { danielebarchiesi@4: foreach ($selected as $source => $components) { danielebarchiesi@4: foreach ($components as $name => $title) { danielebarchiesi@4: $exported = sizeof($components_map[$source][$name]) > 0; danielebarchiesi@4: if ($exported) { danielebarchiesi@4: $provided_by[$source . ':' . $name] = join(', ', $components_map[$source][$name]); danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: return array( danielebarchiesi@4: 'components' => $selected, danielebarchiesi@4: 'sources' => $provided_by, danielebarchiesi@4: ); danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Prints a list of filtered components. danielebarchiesi@4: */ danielebarchiesi@4: function _drush_features_component_print($filtered_components) { danielebarchiesi@4: $rows = array(array(dt('Available sources'))); danielebarchiesi@4: foreach ($filtered_components['components'] as $source => $components) { danielebarchiesi@4: foreach ($components as $name => $value) { danielebarchiesi@4: $row = array($source .':'. $name); danielebarchiesi@4: if (isset($filtered_components['sources'][$source .':'. $name])) { danielebarchiesi@4: $row[] = dt('Provided by') . ': ' . $filtered_components['sources'][$source .':'. $name]; danielebarchiesi@4: } danielebarchiesi@4: $rows[] = $row; danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: drush_print_table($rows, TRUE); danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Add a component to a features module, or create a new module with danielebarchiesi@4: * the selected components. danielebarchiesi@4: */ danielebarchiesi@4: function drush_features_export() { danielebarchiesi@4: if ($args = func_get_args()) { danielebarchiesi@4: $module = array_shift($args); danielebarchiesi@4: if (empty($args)) { danielebarchiesi@4: return drush_set_error('', 'No components supplied.'); danielebarchiesi@4: } danielebarchiesi@4: $components = _drush_features_component_list(); danielebarchiesi@4: $options = array( danielebarchiesi@4: 'exported' => FALSE, danielebarchiesi@4: ); danielebarchiesi@4: danielebarchiesi@4: $filtered_components = _drush_features_component_filter($components, $args, $options); danielebarchiesi@4: $items = $filtered_components['components']; danielebarchiesi@4: danielebarchiesi@4: if (empty($items)) { danielebarchiesi@4: return drush_set_error('', 'No components to add.'); danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: $items = array_map('array_keys', $items); danielebarchiesi@4: danielebarchiesi@4: if (($feature = features_load_feature($module, TRUE)) && module_exists($module)) { danielebarchiesi@4: module_load_include('inc', 'features', 'features.export'); danielebarchiesi@4: _features_populate($items, $feature->info, $feature->name); danielebarchiesi@4: _drush_features_export($feature->info, $feature->name, dirname($feature->filename)); danielebarchiesi@4: } danielebarchiesi@4: elseif ($feature) { danielebarchiesi@4: _features_drush_set_error($module, 'FEATURES_FEATURE_NOT_ENABLED'); danielebarchiesi@4: } danielebarchiesi@4: else { danielebarchiesi@4: // Same logic as in _drush_features_export. Should be refactored. danielebarchiesi@4: $destination = drush_get_option(array('destination'), 'sites/all/modules/features'); danielebarchiesi@4: $directory = isset($directory) ? $directory : $destination . '/' . $module; danielebarchiesi@4: drush_print(dt('Will create a new module in !dir', array('!dir' => $directory))); danielebarchiesi@4: if (!drush_confirm(dt('Do you really want to continue?'))) { danielebarchiesi@4: drush_die('Aborting.'); danielebarchiesi@4: } danielebarchiesi@4: $export = _drush_features_generate_export($items, $module); danielebarchiesi@4: _features_populate($items, $export[info], $export[name]); danielebarchiesi@4: _drush_features_export($export['info'], $module, $directory); danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: else { danielebarchiesi@4: return drush_set_error('', 'No feature name given.'); danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Add a component to a features module danielebarchiesi@4: * the selected components. danielebarchiesi@4: * danielebarchiesi@4: * This is DEPRECATED, but keeping it around for a bit longer for user migration danielebarchiesi@4: */ danielebarchiesi@4: function drush_features_add() { danielebarchiesi@4: drush_print(dt('features-add is DEPRECATED.')); danielebarchiesi@4: drush_print(dt('Calling features-export instead.')); danielebarchiesi@4: drush_features_export(); danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Update an existing feature module. danielebarchiesi@4: */ danielebarchiesi@4: function drush_features_update() { danielebarchiesi@4: if ($args = func_get_args()) { danielebarchiesi@4: foreach ($args as $module) { danielebarchiesi@4: if (($feature = features_load_feature($module, TRUE)) && module_exists($module)) { danielebarchiesi@4: _drush_features_export($feature->info, $feature->name, dirname($feature->filename)); danielebarchiesi@4: } danielebarchiesi@4: else if ($feature) { danielebarchiesi@4: _features_drush_set_error($module, 'FEATURES_FEATURE_NOT_ENABLED'); danielebarchiesi@4: } danielebarchiesi@4: else { danielebarchiesi@4: _features_drush_set_error($module); danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: else { danielebarchiesi@4: // By default just show contexts that are available. danielebarchiesi@4: $rows = array(array(dt('Available features'))); danielebarchiesi@4: foreach (features_get_features(NULL, TRUE) as $name => $info) { danielebarchiesi@4: $rows[] = array($name); danielebarchiesi@4: } danielebarchiesi@4: drush_print_table($rows, TRUE); danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Update all enabled features. Optionally pass in a list of features to danielebarchiesi@4: * exclude from being updated. danielebarchiesi@4: */ danielebarchiesi@4: function drush_features_update_all() { danielebarchiesi@4: $features_to_update = array(); danielebarchiesi@4: $features_to_exclude = func_get_args(); danielebarchiesi@4: foreach (features_get_features() as $module) { danielebarchiesi@4: if ($module->status && !in_array($module->name, $features_to_exclude)) { danielebarchiesi@4: $features_to_update[] = $module->name; danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: drush_print(dt('The following modules will be updated: !modules', array('!modules' => implode(', ', $features_to_update)))); danielebarchiesi@4: if (drush_confirm(dt('Do you really want to continue?'))) { danielebarchiesi@4: foreach ($features_to_update as $module_name) { danielebarchiesi@4: drush_invoke_process(drush_sitealias_get_record('@self'), 'features-update', array($module_name)); danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: else { danielebarchiesi@4: drush_die('Aborting.'); danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Write a module to the site dir. danielebarchiesi@4: * danielebarchiesi@4: * @param $info danielebarchiesi@4: * The feature info associative array. danielebarchiesi@4: * @param $module_name danielebarchiesi@4: * Optional. The name for the exported module. danielebarchiesi@4: */ danielebarchiesi@4: function _drush_features_export($info, $module_name = NULL, $directory = NULL) { danielebarchiesi@4: $root = drush_get_option(array('r', 'root'), drush_locate_root()); danielebarchiesi@4: if ($root) { danielebarchiesi@4: $destination = drush_get_option(array('destination'), 'sites/all/modules/features'); danielebarchiesi@4: $directory = isset($directory) ? $directory : $destination . '/' . $module_name; danielebarchiesi@4: if (is_dir($directory)) { danielebarchiesi@4: drush_print(dt('Module appears to already exist in !dir', array('!dir' => $directory))); danielebarchiesi@4: if (!drush_confirm(dt('Do you really want to continue?'))) { danielebarchiesi@4: drush_die('Aborting.'); danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: else { danielebarchiesi@4: drush_op('mkdir', $directory); danielebarchiesi@4: } danielebarchiesi@4: if (is_dir($directory)) { danielebarchiesi@4: drupal_flush_all_caches(); danielebarchiesi@4: $export = _drush_features_generate_export($info, $module_name); danielebarchiesi@4: $files = features_export_render($export, $module_name, TRUE); danielebarchiesi@4: foreach ($files as $extension => $file_contents) { danielebarchiesi@4: if (!in_array($extension, array('module', 'info'))) { danielebarchiesi@4: $extension .= '.inc'; danielebarchiesi@4: } danielebarchiesi@4: drush_op('file_put_contents', "{$directory}/{$module_name}.$extension", $file_contents); danielebarchiesi@4: } danielebarchiesi@4: drush_log(dt("Created module: !module in !directory", array('!module' => $module_name, '!directory' => $directory)), 'ok'); danielebarchiesi@4: } danielebarchiesi@4: else { danielebarchiesi@4: drush_die(dt('Couldn\'t create directory !directory', array('!directory' => $directory))); danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: else { danielebarchiesi@4: drush_die(dt('Couldn\'t locate site root')); danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Helper function for _drush_feature_export. danielebarchiesi@4: * danielebarchiesi@4: * @param $info danielebarchiesi@4: * The feature info associative array. danielebarchiesi@4: * @param $module_name danielebarchiesi@4: * Optional. The name for the exported module. danielebarchiesi@4: */ danielebarchiesi@4: function _drush_features_generate_export(&$info, &$module_name) { danielebarchiesi@4: module_load_include('inc', 'features', 'features.export'); danielebarchiesi@4: $export = features_populate($info, $module_name); danielebarchiesi@4: if (!features_load_feature($module_name)) { danielebarchiesi@4: $export['name'] = $module_name; danielebarchiesi@4: } danielebarchiesi@4: // Set the feature version if the --version-set or --version-increment option is passed. danielebarchiesi@4: if ($version = drush_get_option(array('version-set'))) { danielebarchiesi@4: preg_match('/^(?P\d+\.x)-(?P\d+)\.(?P\d+)-?(?P\w+)?$/', $version, $matches); danielebarchiesi@4: if (!isset($matches['core'], $matches['major'])) { danielebarchiesi@4: drush_die(dt('Please enter a valid version with core and major version number. Example: !example', array('!example' => '7.x-1.0'))); danielebarchiesi@4: } danielebarchiesi@4: $export['version'] = $version; danielebarchiesi@4: } danielebarchiesi@4: else if ($version = drush_get_option(array('version-increment'))) { danielebarchiesi@4: // Determine current version and increment it. danielebarchiesi@4: $export_load = features_export_prepare($export, $module_name); danielebarchiesi@4: $version = $export_load['version']; danielebarchiesi@4: $version_explode = explode('.', $version); danielebarchiesi@4: $version_minor = array_pop($version_explode); danielebarchiesi@4: // Increment minor version number if numeric or not a dev release. danielebarchiesi@4: if (is_numeric($version_minor) || strpos($version_minor, 'dev') !== (strlen($version_minor) - 3)) { danielebarchiesi@4: // Check for prefixed versions (alpha, beta, rc). danielebarchiesi@4: if (ctype_digit($version_minor)) { danielebarchiesi@4: ++$version_minor; danielebarchiesi@4: } danielebarchiesi@4: else { danielebarchiesi@4: // Split version number parts. danielebarchiesi@4: $pattern = '/([0-9]-[a-z]+([0-9])+)/'; danielebarchiesi@4: $matches = array(); danielebarchiesi@4: preg_match($pattern, $version_minor, $matches); danielebarchiesi@4: $number = array_pop($matches); danielebarchiesi@4: ++$number; danielebarchiesi@4: $pattern = '/[0-9]+$/'; danielebarchiesi@4: $version_minor = preg_replace($pattern, $number, $version_minor); danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: array_push($version_explode, $version_minor); danielebarchiesi@4: // Rebuild version string. danielebarchiesi@4: $version = implode('.', $version_explode); danielebarchiesi@4: $export['version'] = $version; danielebarchiesi@4: } danielebarchiesi@4: return $export; danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Revert a feature to it's code definition. danielebarchiesi@4: * Optionally accept a list of components to revert. danielebarchiesi@4: */ danielebarchiesi@4: function drush_features_revert() { danielebarchiesi@4: if ($args = func_get_args()) { danielebarchiesi@4: module_load_include('inc', 'features', 'features.export'); danielebarchiesi@4: features_include(); danielebarchiesi@4: danielebarchiesi@4: // Determine if revert should be forced. danielebarchiesi@4: $force = drush_get_option('force'); danielebarchiesi@4: danielebarchiesi@4: // Parse list of arguments. danielebarchiesi@4: $modules = array(); danielebarchiesi@4: foreach ($args as $arg) { danielebarchiesi@4: $arg = explode('.', $arg); danielebarchiesi@4: $module = array_shift($arg); danielebarchiesi@4: $component = array_shift($arg); danielebarchiesi@4: danielebarchiesi@4: if (isset($module)) { danielebarchiesi@4: if (empty($component)) { danielebarchiesi@4: // If we received just a feature name, this means that we need all of it's components. danielebarchiesi@4: $modules[$module] = TRUE; danielebarchiesi@4: } danielebarchiesi@4: elseif ($modules[$module] !== TRUE) { danielebarchiesi@4: if (!isset($modules[$module])) { danielebarchiesi@4: $modules[$module] = array(); danielebarchiesi@4: } danielebarchiesi@4: $modules[$module][] = $component; danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: // Process modules. danielebarchiesi@4: foreach ($modules as $module => $components_needed) { danielebarchiesi@4: if (($feature = features_load_feature($module, TRUE)) && module_exists($module)) { danielebarchiesi@4: danielebarchiesi@4: $components = array(); danielebarchiesi@4: // Forcefully revert all components of a feature. danielebarchiesi@4: if ($force) { danielebarchiesi@4: foreach (array_keys($feature->info['features']) as $component) { danielebarchiesi@4: if (features_hook($component, 'features_revert')) { danielebarchiesi@4: $components[] = $component; danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: // Only revert components that are detected to be Overridden/Needs review/rebuildable. danielebarchiesi@4: else { danielebarchiesi@4: $states = features_get_component_states(array($feature->name), FALSE); danielebarchiesi@4: foreach ($states[$feature->name] as $component => $state) { danielebarchiesi@4: if (in_array($state, array(FEATURES_OVERRIDDEN, FEATURES_NEEDS_REVIEW, FEATURES_REBUILDABLE)) && features_hook($component, 'features_revert')) { danielebarchiesi@4: $components[] = $component; danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: if (!empty($components_needed) && is_array($components_needed)) { danielebarchiesi@4: $components = array_intersect($components, $components_needed); danielebarchiesi@4: } danielebarchiesi@4: if (empty($components)) { danielebarchiesi@4: drush_log(dt('Current state already matches defaults, aborting.'), 'ok'); danielebarchiesi@4: } danielebarchiesi@4: else { danielebarchiesi@4: foreach ($components as $component) { danielebarchiesi@4: if (drush_confirm(dt('Do you really want to revert @component?', array('@component' => $component)))) { danielebarchiesi@4: features_revert(array($module => array($component))); danielebarchiesi@4: drush_log(dt('Reverted @component.', array('@component' => $component)), 'ok'); danielebarchiesi@4: } danielebarchiesi@4: else { danielebarchiesi@4: drush_log(dt('Skipping @component.', array('@component' => $component)), 'ok'); danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: else if ($feature) { danielebarchiesi@4: _features_drush_set_error($module, 'FEATURES_FEATURE_NOT_ENABLED'); danielebarchiesi@4: } danielebarchiesi@4: else { danielebarchiesi@4: _features_drush_set_error($module); danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: else { danielebarchiesi@4: drush_features_list(); danielebarchiesi@4: return; danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Revert all enabled features to their definitions in code. danielebarchiesi@4: * danielebarchiesi@4: * @param ... danielebarchiesi@4: * (Optional) A list of features to exclude from being reverted. danielebarchiesi@4: */ danielebarchiesi@4: function drush_features_revert_all() { danielebarchiesi@4: module_load_include('inc', 'features', 'features.export'); danielebarchiesi@4: $force = drush_get_option('force'); danielebarchiesi@4: $features_to_exclude = func_get_args(); danielebarchiesi@4: danielebarchiesi@4: $features_to_revert = array(); danielebarchiesi@4: foreach (features_get_features(NULL, TRUE) as $module) { danielebarchiesi@4: if ($module->status && !in_array($module->name, $features_to_exclude)) { danielebarchiesi@4: // If forced, add module regardless of status. danielebarchiesi@4: if ($force) { danielebarchiesi@4: $features_to_revert[] = $module->name; danielebarchiesi@4: } danielebarchiesi@4: else { danielebarchiesi@4: switch (features_get_storage($module->name)) { danielebarchiesi@4: case FEATURES_OVERRIDDEN: danielebarchiesi@4: case FEATURES_NEEDS_REVIEW: danielebarchiesi@4: case FEATURES_REBUILDABLE: danielebarchiesi@4: $features_to_revert[] = $module->name; danielebarchiesi@4: break; danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: if ($features_to_revert) { danielebarchiesi@4: drush_print(dt('The following modules will be reverted: !modules', array('!modules' => implode(', ', $features_to_revert)))); danielebarchiesi@4: if (drush_confirm(dt('Do you really want to continue?'))) { danielebarchiesi@4: foreach ($features_to_revert as $module) { danielebarchiesi@4: drush_invoke_process(drush_sitealias_get_record('@self'), 'features-revert', array($module), array('force' => $force, '#integrate' => TRUE)); danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: else { danielebarchiesi@4: return drush_user_abort('Aborting.'); danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: else { danielebarchiesi@4: drush_log(dt('Current state already matches defaults, aborting.'), 'ok'); danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Show the diff of a feature module. danielebarchiesi@4: */ danielebarchiesi@4: function drush_features_diff() { danielebarchiesi@4: if (!$args = func_get_args()) { danielebarchiesi@4: drush_features_list(); danielebarchiesi@4: return; danielebarchiesi@4: } danielebarchiesi@4: $module = $args[0]; danielebarchiesi@4: $feature = features_load_feature($module); danielebarchiesi@4: if (!module_exists($module)) { danielebarchiesi@4: drush_log(dt('No such feature is enabled: ' . $module), 'error'); danielebarchiesi@4: return; danielebarchiesi@4: } danielebarchiesi@4: module_load_include('inc', 'features', 'features.export'); danielebarchiesi@4: $overrides = features_detect_overrides($feature); danielebarchiesi@4: if (empty($overrides)) { danielebarchiesi@4: drush_log(dt('Feature is in its default state. No diff needed.'), 'ok'); danielebarchiesi@4: return; danielebarchiesi@4: } danielebarchiesi@4: module_load_include('inc', 'diff', 'diff.engine'); danielebarchiesi@4: danielebarchiesi@4: if (!class_exists('DiffFormatter')) { danielebarchiesi@4: if (drush_confirm(dt('It seems that the Diff module is not available. Would you like to download and enable it?'))) { danielebarchiesi@4: // Download it if it's not already here. danielebarchiesi@4: $project_info = drush_get_projects(); danielebarchiesi@4: if (empty($project_info['diff']) && !drush_invoke_process(drush_sitealias_get_record('@self'), 'dl', array('diff'), array('#integrate' => TRUE))) { danielebarchiesi@4: return drush_set_error(dt('Diff module could not be downloaded.')); danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: if (!drush_invoke_process(drush_sitealias_get_record('@self'), 'en', array('diff'), array('#integrate' => TRUE))) { danielebarchiesi@4: return drush_set_error(dt('Diff module could not be enabled.')); danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: else { danielebarchiesi@4: return drush_set_error(dt('Diff module is not enabled.')); danielebarchiesi@4: } danielebarchiesi@4: // If we're still here, now we can include the diff.engine again. danielebarchiesi@4: module_load_include('inc', 'diff', 'diff.engine'); danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: $lines = (int) drush_get_option('lines'); danielebarchiesi@4: $lines = $lines > 0 ? $lines : 2; danielebarchiesi@4: danielebarchiesi@4: $formatter = new DiffFormatter(); danielebarchiesi@4: $formatter->leading_context_lines = $lines; danielebarchiesi@4: $formatter->trailing_context_lines = $lines; danielebarchiesi@4: $formatter->show_header = FALSE; danielebarchiesi@4: danielebarchiesi@4: if (drush_get_context('DRUSH_NOCOLOR')) { danielebarchiesi@4: $red = $green = "%s"; danielebarchiesi@4: } danielebarchiesi@4: else { danielebarchiesi@4: $red = "\033[31;40m\033[1m%s\033[0m"; danielebarchiesi@4: $green = "\033[0;32;40m\033[1m%s\033[0m"; danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: // Print key for colors danielebarchiesi@4: drush_print(dt('Legend: ')); danielebarchiesi@4: drush_print(sprintf($red, dt('Code: drush features-revert will remove the overrides.'))); danielebarchiesi@4: drush_print(sprintf($green, dt('Overrides: drush features-update will update the exported feature with the displayed overrides'))); danielebarchiesi@4: drush_print(); danielebarchiesi@4: danielebarchiesi@4: foreach ($overrides as $component => $items) { danielebarchiesi@4: $diff = new Diff(explode("\n", $items['default']), explode("\n", $items['normal'])); danielebarchiesi@4: drush_print(); danielebarchiesi@4: drush_print(dt("Component: !component", array('!component' => $component))); danielebarchiesi@4: $rows = explode("\n", $formatter->format($diff)); danielebarchiesi@4: foreach ($rows as $row) { danielebarchiesi@4: if (strpos($row, '>') === 0) { danielebarchiesi@4: drush_print(sprintf($green, $row)); danielebarchiesi@4: } danielebarchiesi@4: elseif (strpos($row, '<') === 0) { danielebarchiesi@4: drush_print(sprintf($red, $row)); danielebarchiesi@4: } danielebarchiesi@4: else { danielebarchiesi@4: drush_print($row); danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Helper function to call drush_set_error(). danielebarchiesi@4: * danielebarchiesi@4: * @param $feature danielebarchiesi@4: * The string name of the feature. danielebarchiesi@4: * @param $error danielebarchiesi@4: * A text string identifying the type of error. danielebarchiesi@4: * @return danielebarchiesi@4: * FALSE. See drush_set_error(). danielebarchiesi@4: */ danielebarchiesi@4: function _features_drush_set_error($feature, $error = '') { danielebarchiesi@4: $args = array('!feature' => $feature); danielebarchiesi@4: danielebarchiesi@4: switch ($error) { danielebarchiesi@4: case 'FEATURES_FEATURE_NOT_ENABLED': danielebarchiesi@4: $message = 'The feature !feature is not enabled.'; danielebarchiesi@4: break; danielebarchiesi@4: case 'FEATURES_COMPONENT_NOT_FOUND': danielebarchiesi@4: $message = 'The given component !feature could not be found.'; danielebarchiesi@4: break; danielebarchiesi@4: default: danielebarchiesi@4: $error = 'FEATURES_FEATURE_NOT_FOUND'; danielebarchiesi@4: $message = 'The feature !feature could not be found.'; danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: return drush_set_error($error, dt($message, $args)); danielebarchiesi@4: }