diff --git a/features.admin.inc b/features.admin.inc index 46752c8..e259de3 100644 --- a/features.admin.inc +++ b/features.admin.inc @@ -1139,8 +1139,13 @@ function features_admin_form($form, $form_state) { ); // Add in recreate link + $markup = l(t('Recreate'), "admin/structure/features/{$name}/recreate", array('attributes' => array('class' => array('admin-update')))); + if (features_get_module_status($name) == FEATURES_MODULE_ENABLED) { + // If module is not disabled, add in import link + $markup .= ' ' . l(t('Import'), "admin/structure/features/{$name}/import", array('attributes' => array('class' => array('admin-update')))); + } $form[$package]['actions'][$name] = array( - '#markup' => l(t('Recreate'), "admin/structure/features/{$name}/recreate", array('attributes' => array('class' => array('admin-update')))), + '#markup' => $markup, ); } } @@ -1278,7 +1283,11 @@ function features_admin_components_revert(&$form, &$form_state) { features_include(); $module = $form_state['values']['module']; $revert = array($module => array()); - foreach (array_filter($form_state['values']['revert']) as $component => $status) { + $components = array_filter($form_state['values']['revert']); + if ($components == array()) { + $components = $form_state['values']['revert']; + } + foreach ($components as $component => $status) { $revert[$module][] = $component; drupal_set_message(t('Reverted all @component components for @module.', array('@component' => $component, '@module' => $module))); } @@ -1374,6 +1383,22 @@ function features_cleanup() { } /** + * Page callback to import a feature back into the database. + * + * @param $feature + * The loaded feature object to be importd. + */ +function features_feature_import($feature) { + $destination = $_SERVER['HTTP_REFERER']; + features_include(); + $module = $feature->name; + foreach ($feature->info['features'] as $component => $items) { + features_invoke($component, 'features_import', $items, $module); + } + drupal_goto($destination); +} + +/** * Page callback to display the differences between what's in code and * what is in the db. * diff --git a/features.api.php b/features.api.php index d7c7ede..0b9e3cf 100644 --- a/features.api.php +++ b/features.api.php @@ -276,6 +276,18 @@ function hook_features_export_alter(&$export, $module_name) { } /** + * TODO + */ +function hook_features_import($data, &$export, $module_name) { + // The following is the simplest implementation of a straight object export + // with no further export processors called. + foreach ($data as $component) { + $export['mycomponent'][$component] = $component; + } + return array(); +} + +/** * Alter the pipe array for a given component. This hook should be implemented * with the name of the component type in place of `component` in the function * name, e.g. `features_pipe_views_alter()` will alter the pipe for the Views diff --git a/features.drush.inc b/features.drush.inc index 696825d..d73a467 100644 --- a/features.drush.inc +++ b/features.drush.inc @@ -85,6 +85,19 @@ function features_drush_command() { ), 'aliases' => array('fc'), ); + $items['features-import'] = array( + 'description' => "Import a feature into the database.", + 'drupal dependencies' => array('features'), + 'aliases' => array('fi'), + ); + $items['features-import-all'] = array( + 'description' => "Import all features into the database.", + 'drupal dependencies' => array('features'), + 'options' => array( + 'exclude' => "A comma-separated list of features to exclude from import.", + ), + 'aliases' => array('fi-all', 'fcoa'), + ); $items['features-update'] = array( 'description' => "Update a feature module on your site.", 'arguments' => array( @@ -188,6 +201,10 @@ Patterns uses * or % for matching multiple sources/components. Unlike shorthands Lastly, a pattern without a colon is interpreted as having \":%\" appended, for easy listing of all components of a source. "); + case 'drush:features-import': + return dt("Import a feature into the database."); + case 'drush:features-import-all': + return dt("Import all features into the database."); case 'drush:features-update': 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."); case 'drush:features-update-all': @@ -542,6 +559,58 @@ function drush_features_add() { /** + * Import a feature into the database. + */ +function drush_features_import() { + if ($args = func_get_args()) { + foreach ($args as $module) { + if (($feature = feature_load($module, TRUE)) && module_exists($module)) { + drush_log(dt("Importing features for module !module", array('!module' => $module)), 'notice'); + _drush_features_import($feature->info['features'], $module); + } + elseif ($feature) { + _features_drush_set_error($module, 'FEATURES_FEATURE_NOT_ENABLED'); + } + else { + _features_drush_set_error($module); + } + } + } + else { + // By default just show contexts that are available. + $rows = array(array(dt('Available features'))); + foreach (features_get_features(NULL, TRUE) as $name => $info) { + $rows[] = array($name); + } + drush_print_table($rows, TRUE); + } +} + +/** + * Import all features into the database. + */ +function drush_features_import_all() { + $features_to_import= array(); + $features_to_exclude = _convert_csv_to_array(drush_get_option('exclude')); + + $features = features_get_features(); + foreach ($features as $module) { + if ($module->status && !in_array($module->name, $features_to_exclude)) { + $features_to_import[] = $module->name; + } + } + drush_print(dt('The following modules will be imported: !modules', array('!modules' => implode(', ', $features_to_import)))); + if (drush_confirm(dt('Do you really want to continue?'))) { + foreach ($features_to_import as $module_name) { + drush_invoke_process('@self', 'features-import', array($module_name)); + } + } + else { + return drush_user_abort(); + } +} + +/** * Update an existing feature module. */ function drush_features_update() { @@ -726,6 +795,17 @@ function _drush_features_generate_export(&$info, &$module_name) { } /** + * Import a feature into the database. + */ +function _drush_features_import($features = array(), $module_name = '') { + features_include(); + foreach ($features as $component => $items) { + drush_log(dt("Running import for component !component", array('!component' => $component)), 'notice'); + features_invoke($component, 'features_import', $module_name); + } +} + +/** * Revert a feature to it's code definition. * Optionally accept a list of components to revert. */ diff --git a/features.module b/features.module index 3c48d71..2c0f764 100644 --- a/features.module +++ b/features.module @@ -153,6 +153,18 @@ function features_menu() { 'file' => "features.admin.inc", 'weight' => 11, ); + $items['admin/structure/features/%feature/import'] = array( + 'title' => 'Import', + 'description' => 'Import a feature into the database.', + 'page callback' => 'features_feature_import', + 'page arguments' => array(3), + 'load arguments' => array(3, TRUE), + 'access callback' => 'features_access_import_feature', + 'access arguments' => array(3), + 'type' => MENU_LOCAL_TASK, + 'file' => "features.admin.inc", + 'weight' => 12, + ); if (module_exists('diff')) { $items['admin/structure/features/%feature/diff'] = array( 'title' => 'Review overrides', @@ -917,6 +929,14 @@ function features_access_override_actions($feature) { } /** + * Menu access callback for whether a user should be able to + * import a given feature. + */ +function features_access_import_feature($feature) { + return (user_access('administer features') && (features_get_module_status($feature->name) == FEATURES_MODULE_ENABLED)); +} + +/** * Implements hook_form_alter() for system_modules form(). */ function features_form_system_modules_alter(&$form) { diff --git a/includes/features.context.inc b/includes/features.context.inc index 2da59a7..772502a 100644 --- a/includes/features.context.inc +++ b/includes/features.context.inc @@ -52,3 +52,20 @@ function context_features_revert($module = NULL) { context_invalidate_cache(); return $return; } + + +/** + * Implementation of hook_features_import(). + */ +function context_features_import($module, $component) { + features_include(); + module_load_include('inc', 'features', 'features.export'); + + if ($objects = features_get_default($component, $module)) { + foreach ($objects as $context_name => $config) { + if ($context = context_load($context_name)) { + context_save($context); + } + } + } +} diff --git a/includes/features.ctools.inc b/includes/features.ctools.inc index 387cece..cf574b1 100644 --- a/includes/features.ctools.inc +++ b/includes/features.ctools.inc @@ -29,6 +29,9 @@ function ctools_features_declare_functions($reset = FALSE) { if (!function_exists("{$component}_features_revert")) { $code .= 'function '. $component .'_features_revert($module) { return ctools_component_features_revert("'. $component .'", $module); }'; } + if (!function_exists("{$component}_features_import")) { + $code .= 'function '. $component .'_features_import($module) { return ctools_component_features_import("'. $component .'", $module); }'; + } eval($code); } } @@ -227,6 +230,25 @@ function ctools_component_features_revert($component, $module) { } } +function ctools_component_features_import($component, $module){ + features_include(); + module_load_include('inc', 'features', 'features.export'); + + if ($objects = features_get_default($component, $module)) { + foreach ($objects as $name => $object) { + // Some things (like views) do not use the machine name as key + // and need to be loaded explicitly in order to be deleted. + $object = ctools_export_crud_load($component, $name); + if ($object && $object->in_code_only ) { + $object->export_type= 2; + $result = ctools_export_crud_save($component,$object); + continue; + } + } + } + return; +} + /** * Helper function to return various ctools information for components. */ @@ -346,6 +368,29 @@ function page_manager_pages_features_export_render($module, $data) { return ctools_component_features_export_render('page_manager_pages', $module, $data); } +function page_manager_pages_features_import($module, $component) { + features_include(); + module_load_include('inc', 'features', 'features.export'); + + if ($objects = features_get_default($component, $module)) { + foreach ($objects as $name => $object) { + // Some things (like views) do not use the machine name as key + // and need to be loaded explicitly in order to be deleted. + $object = ctools_export_crud_load($component, $name); + if ($object && $object->in_code_only ) { + $object->export_type= 2; + $result = ctools_export_crud_save($component,$object); + + foreach ( $object->default_handlers as $handler ){ + $result = ctools_export_crud_save('page_manager_handlers',$handler); + continue; + } + continue; + } + } + } +} + /** * Implements hook_features_revert() for page_manager. */ diff --git a/includes/features.image.inc b/includes/features.image.inc index b2058b7..80b7bc5 100644 --- a/includes/features.image.inc +++ b/includes/features.image.inc @@ -84,6 +84,45 @@ function image_features_revert($module) { } /** + * Implementation of hook_features_import(). + */ + +function image_features_import($module = '', $component) { + features_include(); + module_load_include('inc', 'features', 'features.export'); + + if ($objects = features_get_default($component, $module)) { + foreach ( $objects as $name => $config ){ + $style = image_style_load($name); + // Styles + if (isset($style['isid'])) { + if (!is_numeric($style['isid'])) { + _image_style_and_effects_save($style); + } + } + else { + _image_style_and_effects_save($style); + } + } + } +} + +/** + * Helper function for image_features_import(). + * Saves a single style together with its effects. + */ +function _image_style_and_effects_save($style) { + $style = image_style_save($style); + // Actions + if (is_numeric($style['isid'])) { + foreach ($style['effects'] as $effect) { + $effect['isid'] = $style['isid']; + image_effect_save($effect); + } + } +} + +/** * Remove unnecessary keys for export. */ function _image_features_style_sanitize(array &$style) { diff --git a/includes/features.node.inc b/includes/features.node.inc index 25a2c1c..cf6bfc0 100644 --- a/includes/features.node.inc +++ b/includes/features.node.inc @@ -172,3 +172,31 @@ function node_features_enable_feature($module) { } } } + +/** + * Implementation of hook_features_import(). + * + * When a features module is importd, modify any node types it provides + * so responsibility is delegated to the node module. + * + * @param $items + * Array of names of node types to be importd. + * @param $module + * Name of module that has been importd. + */ +function node_features_import($module, $component) { + features_include(); + module_load_include('inc', 'features', 'features.export'); + + if ($objects = features_get_default($component, $module)) { + foreach ($objects as $type_name => $config) { + // Load the type. + $type_info = node_type_load($type_name); + $type_info->module = 'node'; + $type_info->custom = 1; + $type_info->modified = 1; + $type_info->locked = 0; + node_type_save($type_info); + } + } +}