diff --git a/features.api.php b/features.api.php index 7c24024..a7e96b5 100644 --- a/features.api.php +++ b/features.api.php @@ -239,6 +239,40 @@ function hook_features_export_alter(&$export, $module_name) { } /** + * Component hook. The hook should be implemented using the name ot the + * component, not the module, eg. [component]_features_import() rather than + * [module]_features_import(). + * + * Saves components back to the database. + */ +function hook_features_import($items, $module_name) { + // The following is the simplest implementation of a straight object export + // with no further export processors called. + foreach ($items as $component) { + if ($object = component_load($component)) { + component_save($object); + } + } + return array(); +} + +/** + * Component hook. The hook should be implemented using the name ot the + * component, not the module, eg. [component]_features_component_status() rather than + * [module]_features_component_status(). + * + * Returns the current storage status of a component. + */ +function hook_features_component_status($name) { + // The following is the simplest implementation of a straight object export + // with no further export processors called. + if ($object = component_load($component)) { + $object->export_type == EXPORT_IN_CODE ? FEATURES_COMPONENT_IN_CODE : FEATURES_COMPONENT_IN_DATABASE; + } + return FEATURES_COMPONENT_NOT_FOUND; +} + +/** * 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 4244aee..3d2dab3 100644 --- a/features.drush.inc +++ b/features.drush.inc @@ -63,6 +63,19 @@ function features_drush_command() { ), 'aliases' => array('fc'), ); + $items['features-import'] = array( + 'description' => "Import a feature to the database.", + 'drupal dependencies' => array('features'), + 'aliases' => array('fi'), + ); + $items['features-import-all'] = array( + 'description' => "Import all features to the database.", + 'drupal dependencies' => array('features'), + 'options' => array( + 'exclude' => "A comma-separated list of features to exclude from import.", + ), + 'aliases' => array('fia'), + ); $items['features-update'] = array( 'description' => "Update a feature module on your site.", 'arguments' => array( @@ -151,6 +164,10 @@ Lastly, a pattern without a colon is interpreted as having \":%\" appended, for return dt("Revert all enabled feature module on your site."); case 'drush:features-revert': return dt("Revert a feature module on your site."); + case 'drush:features-import': + return dt("Import a feature to the database."); + case 'drush:features-import-all': + return dt("Import all features to the database."); case 'drush:features-diff': return dt("Show a diff of a feature module."); case 'drush:features-add': @@ -456,6 +473,57 @@ function drush_features_add() { drush_features_export(); } +/** + * Import a feature to 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); + } + else if ($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 to 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. @@ -578,6 +646,36 @@ function _drush_features_export($info, $module_name = NULL, $directory = NULL) { } /** + * Import a feature to the database. + */ +function _drush_features_import($features = array(), $module_name = '') { + features_include(); + $force = drush_get_option('force'); + foreach ($features as $component => $items) { + if (!features_hook($component, 'features_import')) { + drush_log(dt("Unable to import !component for @module_name (no hook_features_import.)", array('!component' => $component, '@module_name' => $module_name)), 'wa'); + } + else { + $function = features_hook($component, 'features_component_status'); + if (!$force) { + foreach ($items as $key => $item) { + if ($function($item) != FEATURES_COMPONENT_IN_CODE) { + unset($items[$key]); + } + } + } + if ($items) { + drush_log(dt("Running import for component !component (@items) for @module_name", array('!component' => $component, '@module_name' => $module_name, '@items' => implode(', ', $items))), 'notice'); + features_invoke($component, 'features_import', $items, $module_name); + } + else { + drush_log(dt("Unable to import !component for !module_name (no code only items were found for this feature and component).", array('!component' => $component, '!module_name' => $module_name)), 'notice'); + } + } + } +} + +/** * 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 ce54575..bdc175a 100644 --- a/features.module +++ b/features.module @@ -25,6 +25,9 @@ define('FEATURES_CHECKING', 6); define('FEATURES_ALTER_TYPE_NORMAL', 'normal'); define('FEATURES_ALTER_TYPE_INLINE', 'inline'); define('FEATURES_ALTER_TYPE_NONE', 'none'); +define('FEATURES_COMPONENT_NOT_FOUND', 0); +define('FEATURES_COMPONENT_IN_CODE', 1); +define('FEATURES_COMPONENT_IN_DATABASE', 2); // Duration of rebuild semaphore: 10 minutes. define('FEATURES_SEMAPHORE_TIMEOUT', 10 * 60); diff --git a/includes/features.ctools.inc b/includes/features.ctools.inc index f5a2d66..c177893 100644 --- a/includes/features.ctools.inc +++ b/includes/features.ctools.inc @@ -29,6 +29,12 @@ 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($items, $module) { return ctools_component_features_import("'. $component .'", $items, $module); }'; + } + if (!function_exists("{$component}_features_component_status")) { + $code .= 'function '. $component .'_features_component_status($name) { return ctools_component_features_component_status("'. $component .'", $name); }'; + } eval($code); } } @@ -226,6 +232,27 @@ function ctools_component_features_revert($component, $module) { } /** + * Master implementation of hook_features_import() for all ctools components. + */ +function ctools_component_features_import($component, $items, $module) { + foreach ($items as $name) { + if ($object = ctools_export_crud_load($component, $name)) { + ctools_export_crud_save($component, $name); + } + } +} + +/** + * Master implementation of hook_features_component_status() for all ctools components. + */ +function ctools_component_features_component_status($component, $name) { + if ($object = ctools_export_crud_load($component, $name)) { + return $object->export_type == EXPORT_IN_CODE ? FEATURES_COMPONENT_IN_CODE : FEATURES_COMPONENT_IN_DATABASE; + } + return FEATURES_COMPONENT_NOT_FOUND; +} + +/** * Helper function to return various ctools information for components. */ function _ctools_features_get_info($identifier = NULL, $reset = FALSE) { diff --git a/includes/features.image.inc b/includes/features.image.inc index 2b5eb27..d01cf5e 100644 --- a/includes/features.image.inc +++ b/includes/features.image.inc @@ -99,3 +99,24 @@ function _image_features_style_sanitize(&$style, $child = FALSE) { } } } + +/** + * Implementation of hook_features_import(). + */ +function image_features_import($items = array(), $module_name = '') { + foreach ($items as $item) { + if ($style = image_style_load($item)) { + image_style_save($style); + } + } +} + +/** + * Implementation of hook_features_component_status(). + */ +function image_features_component_status($name) { + if ($style = image_style_load($name)) { + return $style['storage'] == IMAGE_STORAGE_DEFAULT ? FEATURES_COMPONENT_IN_CODE : FEATURES_COMPONENT_IN_DATABASE; + } + return FEATURES_COMPONENT_NOT_FOUND; +} diff --git a/includes/features.node.inc b/includes/features.node.inc index af35e25..a2d64f7 100644 --- a/includes/features.node.inc +++ b/includes/features.node.inc @@ -159,3 +159,25 @@ function node_features_enable($module) { } } } + +/** + * Implementation of hook_features_import(). + */ +function node_features_import($items = array(), $module_name = '') { + // Delegate responsibility to node module. + $fields = array('module' => 'node', 'modified' => 1, 'custom' => 1); + db_update('node_type') + ->fields($fields) + ->condition('type', $items) + ->execute(); +} + +/** + * Implementation of hook_features_component_status(). + */ +function node_features_component_status($name) { + if ($type = node_type_load($name)) { + return empty($type->modified) && empty($type->custom) ? FEATURES_COMPONENT_IN_CODE : FEATURES_COMPONENT_IN_DATABASE; + } + return FEATURES_COMPONENT_NOT_FOUND; +}