diff --git a/features.admin.inc b/features.admin.inc
index 5de989b..15ef2df 100644
--- a/features.admin.inc
+++ b/features.admin.inc
@@ -267,12 +267,15 @@ function _features_export_form_components(&$form, &$form_state) {
module_load_include('inc', 'features', 'features.export');
$feature_export = _features_export_generate($export, $form_state, $feature);
- $feature_export = features_export_prepare($feature_export, $feature->name);
+ $feature_export = features_export_prepare($feature_export, $feature->name, TRUE);
$info = features_export_info($feature_export);
drupal_add_js(array('features' => array('info' => $info)), 'setting');
}
+ // determine any components that are deprecated
+ $deprecated = features_get_deprecated($export['components']);
+
$sections = array('included', 'detected', 'added');
foreach ($export['components'] as $component => $component_info) {
$label = (isset($component_info['name']) ?
@@ -287,42 +290,72 @@ function _features_export_form_components(&$form, &$form_state) {
if ($count + count($component_info['options']['sources']) > 0) {
- $form['export'][$component] = array(
- '#markup' => '',
- '#tree' => TRUE,
+ if (!empty($deprecated[$component])) {
+ // only show deprecated component if it has some exports
+ if (!empty($component_info['options']['included'])) {
+ $form['export'][$component] = array(
+ '#markup' => '',
+ '#tree' => TRUE,
+ );
+
+ $form['export'][$component]['deprecated'] = array(
+ '#type' => 'fieldset',
+ '#title' => $label . " (" . t('DEPRECATED') . ")",
+ '#tree' => TRUE,
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ '#attributes' => array('class' => array('features-export-component')),
+ );
+ $list = ' ';
+ foreach ($component_info['options']['included'] as $key) {
+ $list .= "$key";
+ }
+ $form['export'][$component]['deprecated']['selected'] = array(
+ '#prefix' => "
",
+ '#markup' => $list,
+ '#suffix' => "
",
+ );
+ }
+ }
+ else {
+ $form['export'][$component] = array(
+ '#markup' => '',
+ '#tree' => TRUE,
+ );
+
+ $form['export'][$component]['sources'] = array(
+ '#type' => 'fieldset',
+ '#title' => $label,
+ '#tree' => TRUE,
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ '#attributes' => array('class' => array('features-export-component')),
+ '#prefix' => "",
);
- $form['export'][$component]['sources'] = array(
- '#type' => 'fieldset',
- '#title' => $label,
- '#tree' => TRUE,
- '#collapsible' => TRUE,
- '#collapsed' => TRUE,
- '#attributes' => array('class' => array('features-export-component')),
- '#prefix' => "
",
- );
- $form['export'][$component]['sources']['selected'] = array(
- '#type' => 'checkboxes',
- '#id' => "edit-sources-$component_name",
- '#options' => features_dom_encode_options($component_info['options']['sources']),
- '#default_value' => features_dom_encode_options($component_info['selected']['sources'], FALSE),
- '#attributes' => array(
- 'class' => array('component-select'),
- ),
- );
-
- foreach ($sections as $section) {
- $form['export'][$component][$section] = array(
+ $form['export'][$component]['sources']['selected'] = array(
'#type' => 'checkboxes',
- '#options' => !empty($component_info['options'][$section]) ?
- features_dom_encode_options($component_info['options'][$section]) : array(),
- '#default_value' => !empty($component_info['selected'][$section]) ?
- features_dom_encode_options($component_info['selected'][$section], FALSE) : array(),
- '#attributes' => array('class' => array('component-' . $section)),
+ '#id' => "edit-sources-$component_name",
+ '#options' => features_dom_encode_options($component_info['options']['sources']),
+ '#default_value' => features_dom_encode_options($component_info['selected']['sources'], FALSE),
+ '#attributes' => array(
+ 'class' => array('component-select'),
+ ),
);
+
+ foreach ($sections as $section) {
+ $form['export'][$component][$section] = array(
+ '#type' => 'checkboxes',
+ '#options' => !empty($component_info['options'][$section]) ?
+ features_dom_encode_options($component_info['options'][$section]) : array(),
+ '#default_value' => !empty($component_info['selected'][$section]) ?
+ features_dom_encode_options($component_info['selected'][$section], FALSE) : array(),
+ '#attributes' => array('class' => array('component-' . $section)),
+ );
+ }
+ $form['export'][$component][$sections[0]]['#prefix'] =
+ "
';
}
- $form['export'][$component][$sections[0]]['#prefix'] =
- "
';
}
}
$form['export']['features_legend'] = array(
diff --git a/features.export.inc b/features.export.inc
index 37f772c..6718793 100644
--- a/features.export.inc
+++ b/features.export.inc
@@ -170,6 +170,13 @@ function _features_export_maximize_dependencies($dependencies, $module_name = ''
/**
* Prepare a feature export array into a finalized info array.
+ * @param $export
+ * An exported feature definition.
+ * @param $module_name
+ * The name of the module to be exported.
+ * @param $reset
+ * Boolean flag for resetting the module cache. Only set to true when
+ * doing a final export for delivery.
*/
function features_export_prepare($export, $module_name, $reset = FALSE) {
$existing = features_get_modules($module_name, $reset);
@@ -186,9 +193,16 @@ function features_export_prepare($export, $module_name, $reset = FALSE) {
$defaults = $existing ? $existing->info : array('core' => '7.x', 'package' => 'Features');
$export = array_merge($defaults, $export);
+ $deprecated = features_get_deprecated();
// Cleanup info array
foreach ($export['features'] as $component => $data) {
- $export['features'][$component] = array_keys($data);
+ // if performing the final export, do not export deprecated components
+ if ($reset && !empty($deprecated[$component])) {
+ unset($export['features'][$component]);
+ }
+ else {
+ $export['features'][$component] = array_keys($data);
+ }
}
if (isset($export['dependencies'])) {
$export['dependencies'] = array_values($export['dependencies']);
@@ -252,9 +266,14 @@ function features_export_render($export, $module_name, $reset = FALSE) {
// Generate hook code
$component_hooks = features_export_render_hooks($export, $module_name, $reset);
$components = features_get_components();
+ $deprecated = features_get_deprecated($components);
// Group component code into their respective files
foreach ($component_hooks as $component => $hooks) {
+ if ($reset && !empty($deprecated[$component])) {
+ // skip deprecated components on final export
+ continue;
+ }
$file = array('name' => 'features');
if (isset($components[$component]['default_file'])) {
switch ($components[$component]['default_file']) {
diff --git a/features.module b/features.module
index d9ce8d6..c8c13e0 100644
--- a/features.module
+++ b/features.module
@@ -954,3 +954,23 @@ function features_hook_info() {
);
return array_fill_keys($hooks, array('group' => 'features'));
}
+
+/**
+ * Returns an array of deprecated components
+ * Rather than deprecating the component directly, we look for other components
+ * that supersedes the component
+ * @param $components
+ * The array of components (component_info) from features_get_components typically.
+ */
+function features_get_deprecated($components = array()) {
+ if (empty($components)) {
+ $components = features_get_components();
+ }
+ $deprecated = array();
+ foreach ($components as $component => $component_info) {
+ if (!empty($component_info['supersedes'])) {
+ $deprecated[$component_info['supersedes']] = $component_info['supersedes'];
+ }
+ }
+ return $deprecated;
+}
diff --git a/includes/features.field.inc b/includes/features.field.inc
index c396668..18dc6d3 100644
--- a/includes/features.field.inc
+++ b/includes/features.field.inc
@@ -6,14 +6,340 @@
function field_features_api() {
return array(
'field' => array(
+ // this is deprecated by field_base and field_instance
+ // but retained for compatibility with older exports
'name' => t('Fields'),
'default_hook' => 'field_default_fields',
'default_file' => FEATURES_DEFAULTS_INCLUDED,
'feature_source' => TRUE,
+ ),
+ 'field_base' => array(
+ 'name' => t('Field Bases'),
+ 'default_hook' => 'field_default_field_bases',
+ 'default_file' => FEATURES_DEFAULTS_INCLUDED,
+ 'feature_source' => TRUE,
+ 'supersedes' => 'field',
+ ),
+ 'field_instance' => array(
+ 'name' => t('Field Instances'),
+ 'default_hook' => 'field_default_field_instances',
+ 'default_file' => FEATURES_DEFAULTS_INCLUDED,
+ 'feature_source' => TRUE,
+ 'supersedes' => 'field',
)
);
}
+/**
+ * Implements hook_features_export_options().
+ */
+function field_base_features_export_options() {
+ $options = array();
+ $fields = field_info_fields();
+ foreach ($fields as $field_name => $field) {
+ $options[$field_name] = $field_name;
+ }
+ return $options;
+}
+
+/**
+ * Implements hook_features_export_options().
+ */
+function field_instance_features_export_options() {
+ $options = array();
+ $instances = field_info_instances();
+ foreach ($instances as $entity_type => $bundles) {
+ foreach ($bundles as $bundle => $fields) {
+ foreach ($fields as $field) {
+ $identifier = "{$entity_type}-{$bundle}-{$field['field_name']}";
+ $options[$identifier] = $identifier;
+ }
+ }
+ }
+ return $options;
+}
+
+/**
+ * Implements hook_features_export().
+ */
+function field_base_features_export($data, &$export, $module_name = '') {
+ $pipe = array();
+ $map = features_get_default_map('field_base');
+
+ // The field_default_field_bases() hook integration is provided by the
+ // features module so we need to add it as a dependency.
+ $export['dependencies']['features'] = 'features';
+
+ foreach ($data as $identifier) {
+ if ($base = features_field_base_load($identifier)) {
+ // If this field is already provided by another module, remove the field
+ // and add the other module as a dependency.
+ if (isset($map[$identifier]) && $map[$identifier] != $module_name) {
+ if (isset($export['features']['field_base'][$identifier])) {
+ unset($export['features']['field_base'][$identifier]);
+ }
+ $module = $map[$identifier];
+ $export['dependencies'][$module] = $module;
+ }
+ // If the field has not yet been exported, add it
+ else {
+ $export['features']['field_base'][$identifier] = $identifier;
+ $export['dependencies'][$base['module']] = $base['module'];
+ if ($base['storage']['type'] != variable_get('field_storage_default', 'field_sql_storage')) {
+ $export['dependencies'][$base['storage']['module']] = $base['storage']['module'];
+ }
+ // If taxonomy field, add in the vocabulary
+ if ($base['type'] == 'taxonomy_term_reference' && !empty($base['settings']['allowed_values'])) {
+ foreach ($base['settings']['allowed_values'] as $allowed_values) {
+ if (!empty($allowed_values['vocabulary'])) {
+ $pipe['taxonomy'][] = $allowed_values['vocabulary'];
+ }
+ }
+ }
+ }
+ }
+ }
+ return $pipe;
+}
+
+/**
+ * Implements hook_features_export().
+ */
+function field_instance_features_export($data, &$export, $module_name = '') {
+ $pipe = array('field_base' => array());
+ $map = features_get_default_map('field_instance');
+
+ // The field_default_field_instances() hook integration is provided by the
+ // features module so we need to add it as a dependency.
+ $export['dependencies']['features'] = 'features';
+
+ foreach ($data as $identifier) {
+ if ($instance = features_field_instance_load($identifier)) {
+ // If this field is already provided by another module, remove the field
+ // and add the other module as a dependency.
+ if (isset($map[$identifier]) && $map[$identifier] != $module_name) {
+ if (isset($export['features']['field_instance'][$identifier])) {
+ unset($export['features']['field_instance'][$identifier]);
+ }
+ $module = $map[$identifier];
+ $export['dependencies'][$module] = $module;
+ }
+ // If the field has not yet been exported, add it
+ else {
+ $export['features']['field_instance'][$identifier] = $identifier;
+ $export['dependencies'][$instance['widget']['module']] = $instance['widget']['module'];
+ foreach ($instance['display'] as $key => $display) {
+ if (isset($display['module'])) {
+ $export['dependencies'][$display['module']] = $display['module'];
+ // @TODO: handle the pipe to image styles
+ }
+ }
+ $pipe['field_base'][] = $instance['field_name'];
+ }
+ }
+ }
+ return $pipe;
+}
+
+/**
+ * Implements hook_features_export_render().
+ */
+function field_base_features_export_render($module, $data, $export = NULL) {
+ $translatables = $code = array();
+ $code[] = ' $field_bases = array();';
+ $code[] = '';
+ foreach ($data as $identifier) {
+ if ($field = features_field_base_load($identifier)) {
+ unset($field['columns']);
+ // unset($field['locked']);
+ // Only remove the 'storage' declaration if the field is using the default
+ // storage type.
+ if ($field['storage']['type'] == variable_get('field_storage_default', 'field_sql_storage')) {
+ unset($field['storage']);
+ }
+ // If we still have a storage declaration here it means that a non-default
+ // storage type was altered into to the field definition. And noone would
+ // never need to change the 'details' key, so don't render it.
+ if (isset($field['storage']['details'])) {
+ unset($field['storage']['details']);
+ }
+
+ _field_instance_features_export_sort($field);
+ $field_export = features_var_export($field, ' ');
+ $field_identifier = features_var_export($identifier);
+ $code[] = " // Exported field_base: {$field_identifier}";
+ $code[] = " \$field_bases[{$field_identifier}] = {$field_export};";
+ $code[] = "";
+ }
+ }
+ $code[] = ' return $field_bases;';
+ $code = implode("\n", $code);
+ return array('field_default_field_bases' => $code);
+}
+
+/**
+ * Implements hook_features_export_render().
+ */
+function field_instance_features_export_render($module, $data, $export = NULL) {
+ $translatables = $code = array();
+
+ $code[] = ' $field_instances = array();';
+ $code[] = '';
+
+ foreach ($data as $identifier) {
+ if ($instance = features_field_instance_load($identifier)) {
+ _field_instance_features_export_sort($instance);
+ $field_export = features_var_export($instance, ' ');
+ $instance_identifier = features_var_export($identifier);
+ $code[] = " // Exported field_instance: {$instance_identifier}";
+ $code[] = " \$field_instances[{$instance_identifier}] = {$field_export};";
+ $code[] = "";
+
+ if (!empty($instance['label'])) {
+ $translatables[] = $instance['label'];
+ }
+ if (!empty($instance['description'])) {
+ $translatables[] = $instance['description'];
+ }
+ }
+ }
+ if (!empty($translatables)) {
+ $code[] = features_translatables_export($translatables, ' ');
+ }
+ $code[] = ' return $field_instances;';
+ $code = implode("\n", $code);
+ return array('field_default_field_instances' => $code);
+}
+
+// Helper to enforce consistency in field export arrays.
+function _field_instance_features_export_sort(&$field, $sort = TRUE) {
+
+ // Some arrays are not sorted to preserve order (for example allowed_values).
+ static $sort_blacklist = array(
+ 'allowed_values',
+ 'format_handlers',
+ );
+
+ if ($sort) {
+ ksort($field);
+ }
+ foreach ($field as $k => $v) {
+ if (is_array($v)) {
+ _field_instance_features_export_sort($field[$k], !in_array($k, $sort_blacklist));
+ }
+ }
+}
+
+/**
+ * Implements hook_features_revert().
+ */
+function field_base_features_revert($module) {
+ field_base_features_rebuild($module);
+}
+
+/**
+ * Implements hook_features_revert().
+ */
+function field_instance_features_revert($module) {
+ field_instance_features_rebuild($module);
+}
+
+/**
+ * Implements of hook_features_rebuild().
+ * Rebuilds fields from code defaults.
+ */
+function field_base_features_rebuild($module) {
+ if ($fields = features_get_default('field_base', $module)) {
+ field_info_cache_clear();
+
+ // Load all the existing field bases up-front so that we don't
+ // have to rebuild the cache all the time.
+ $existing_fields = field_info_fields();
+
+ foreach ($fields as $field) {
+ // Create or update field.
+ if (isset($existing_fields[$field['field_name']])) {
+ $existing_field = $existing_fields[$field['field_name']];
+ if ($field + $existing_field != $existing_field) {
+ field_update_field($field);
+ }
+ }
+ else {
+ field_create_field($field);
+ $existing_fields[$field['field_name']] = $field;
+ }
+ variable_set('menu_rebuild_needed', TRUE);
+ }
+ }
+}
+
+/**
+ * Implements of hook_features_rebuild().
+ * Rebuilds field instances from code defaults.
+ */
+function field_instance_features_rebuild($module) {
+ if ($instances = features_get_default('field_instance', $module)) {
+ field_info_cache_clear();
+
+ // Load all the existing instances up-front so that we don't
+ // have to rebuild the cache all the time.
+ $existing_instances = field_info_instances();
+
+ foreach ($instances as $field_instance) {
+ // If the field base information does not exist yet, cancel out.
+ if (!field_info_field($field_instance['field_name'])) {
+ continue;
+ }
+
+ // Create or update field instance.
+ if (isset($existing_instances[$field_instance['entity_type']][$field_instance['bundle']][$field_instance['field_name']])) {
+ $existing_instance = $existing_instances[$field_instance['entity_type']][$field_instance['bundle']][$field_instance['field_name']];
+ if ($field_instance + $existing_instance != $existing_instance) {
+ field_update_instance($field_instance);
+ }
+ }
+ else {
+ field_create_instance($field_instance);
+ $existing_instances[$field_instance['entity_type']][$field_instance['bundle']][$field_instance['field_name']] = $field_instance;
+ }
+ }
+
+ if ($instances) {
+ variable_set('menu_rebuild_needed', TRUE);
+ }
+ }
+}
+
+/**
+ * Load a field base configuration by a field_name identifier.
+ */
+function features_field_base_load($field_name) {
+ if ($field_info = field_info_field($field_name)) {
+ unset($field_info['id']);
+ unset($field_info['bundles']);
+ return $field_info;
+ }
+ return FALSE;
+}
+
+/**
+ * Load a field's instance configuration by an entity_type-bundle-field_name
+ * identifier.
+ */
+function features_field_instance_load($identifier) {
+ list($entity_type, $bundle, $field_name) = explode('-', $identifier);
+ if ($instance_info = field_info_instance($entity_type, $field_name, $bundle)) {
+ unset($instance_info['id']);
+ unset($instance_info['field_id']);
+ return $instance_info;
+ }
+ return FALSE;
+}
+
+/* ----- DEPRECATED FIELD EXPORT -----
+ * keep this code for backward compatibility with older exports
+ * until v3.x
+ */
/**
* Implements hook_features_export_options().
diff --git a/includes/features.node.inc b/includes/features.node.inc
index af35e25..7c45558 100644
--- a/includes/features.node.inc
+++ b/includes/features.node.inc
@@ -44,6 +44,7 @@ function node_features_export($data, &$export, $module_name = '') {
$fields = field_info_instances('node', $type);
foreach ($fields as $name => $field) {
$pipe['field'][] = "node-{$field['bundle']}-{$field['field_name']}";
+ $pipe['field_instance'][] = "node-{$field['bundle']}-{$field['field_name']}";
}
}
}
diff --git a/includes/features.taxonomy.inc b/includes/features.taxonomy.inc
index 5ac4ec5..a7c85cd 100644
--- a/includes/features.taxonomy.inc
+++ b/includes/features.taxonomy.inc
@@ -49,6 +49,7 @@ function taxonomy_features_export($data, &$export, $module_name = '') {
$fields = field_info_instances('taxonomy_term', $machine_name);
foreach ($fields as $name => $field) {
$pipe['field'][] = "taxonomy_term-{$field['bundle']}-{$field['field_name']}";
+ $pipe['field_instance'][] = "taxonomy_term-{$field['bundle']}-{$field['field_name']}";
}
}
}