contrib modules security updates
This commit is contained in:
@@ -34,6 +34,13 @@ new equivalents (e.g. CCK to fields, imagecache to core image styles).
|
||||
- imagecache > image
|
||||
- content > field
|
||||
|
||||
Note on the "Generate Feature" capability
|
||||
-----------------------------------------
|
||||
Features 7.x-2.x includes the ability to "Generate a feature" which saves it
|
||||
to the server disk. This can be a time-saving task in development. It requires
|
||||
the webserver to be able to write to the very code running the site and is
|
||||
not recommended for any environment other than a firewalled-off, local
|
||||
development environment (e.g. a person working alone on their laptop).
|
||||
|
||||
Features 1.x for Drupal 7.x
|
||||
---------------------------
|
||||
@@ -62,6 +69,16 @@ modules directory for your site and enable it on the `admin/build/modules` page.
|
||||
To take full advantage of some of the workflow benefits provided by Features,
|
||||
you should install [Drush][1].
|
||||
|
||||
If you plan on creating or working with very large features (greater than 1000
|
||||
items), you may need to increase PHP's max_input_vars configuration directive.
|
||||
For example, adding the following line to your .htaccess file will increase the
|
||||
max_input_vars directive to 3000:
|
||||
|
||||
php_value max_input_vars 3000
|
||||
|
||||
If you are using Suhosin, increasing suhosin.get.max_vars,
|
||||
suhosin.post.max_vars, and suhosin.request.max_vars may also be necessary.
|
||||
|
||||
|
||||
Basic usage
|
||||
-----------
|
||||
@@ -148,7 +165,8 @@ Features provides several useful drush commands:
|
||||
|
||||
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'.
|
||||
'sites/all/modules', though this can be overridden via the Features
|
||||
settings page.
|
||||
|
||||
- `drush features-update [feature name]`
|
||||
|
||||
|
@@ -5,6 +5,99 @@
|
||||
* Forms for Features admin screens
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Settings form for features
|
||||
*/
|
||||
function features_settings_form($form, $form_state) {
|
||||
$form = array();
|
||||
|
||||
$components = features_get_components();
|
||||
uasort($components, 'features_compare_component_name');
|
||||
$form['show_components'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Show components on create/edit feature form.'),
|
||||
'#description' => t('Components with no options will not be shown no matter the setting below. Disabled components cannot be used with admin form.')
|
||||
);
|
||||
|
||||
$form['lock_components'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Lock components'),
|
||||
'#description' => t('Locked components will be prevented from ever being reverted. For example, if site builder updates a feature with new settings for a field instance, but field instance is locked, it will not update that field. If the item is purely in code, like a view, the view changed when the code is updated no matter these settings.')
|
||||
);
|
||||
$form['features_lock_mode'] = array(
|
||||
'#type' => 'radios',
|
||||
'#title' => t('Features lock mode'),
|
||||
'#options' => array(
|
||||
'rebuild' => t('Allow rebuild (prevent revert)'),
|
||||
'all' => t('Prevent rebuild and revert'),
|
||||
),
|
||||
'#description' => t('Rebuild will allow the feature to be updated till the point features has detected that the item has changed deliberately on the site, e.g. is overriden.'),
|
||||
'#default_value' => variable_get('features_lock_mode', 'all'),
|
||||
);
|
||||
foreach ($components as $component => $info) {
|
||||
if (empty($info['feature_source']) && empty($info['features_source'])) {
|
||||
continue;
|
||||
}
|
||||
$form['show_components']['features_admin_show_component_' . $component] = array(
|
||||
'#title' => t('@name (@machine)', array('@name' => $info['name'], '@machine' => $component)),
|
||||
'#type' => 'checkbox',
|
||||
'#default_value' => variable_get('features_admin_show_component_' . $component, TRUE),
|
||||
);
|
||||
if (features_hook($component, 'features_revert') || features_hook($component, 'features_rebuild')) {
|
||||
$form['lock_components']['features_component_locked_' . $component] = array(
|
||||
'#title' => t('@name (@machine)', array('@name' => $info['name'], '@machine' => $component)),
|
||||
'#type' => 'checkbox',
|
||||
'#default_value' => variable_get('features_component_locked_' . $component, FALSE),
|
||||
);
|
||||
}
|
||||
if ($component == 'menu_links' && ($menus = menu_get_menus())) {
|
||||
$form['show_components']['features_admin_menu_links'] = array(
|
||||
'#title' => t('Advanced Menu Link Settings'),
|
||||
'#type' => 'fieldset',
|
||||
'#collapsed' => TRUE,
|
||||
'#collapsible' => TRUE,
|
||||
'#states' => array(
|
||||
'invisible' => array(
|
||||
'input[name="features_admin_show_component_menu_links"]' => array('checked' => FALSE),
|
||||
),
|
||||
),
|
||||
);
|
||||
$form['show_components']['features_admin_menu_links']['features_admin_menu_links_menus'] = array(
|
||||
'#title' => t('Allowed menus for menu links'),
|
||||
'#type' => 'checkboxes',
|
||||
'#options' => array_map('check_plain', $menus),
|
||||
'#default_value' => variable_get('features_admin_menu_links_menus', array_keys(menu_get_menus())),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$form['general'] = array(
|
||||
'#title' => t('General settings'),
|
||||
'#type' => 'fieldset',
|
||||
);
|
||||
$form['general']['features_default_export_path'] = array(
|
||||
'#title' => t('Default export path'),
|
||||
'#type' => 'textfield',
|
||||
'#default_value' => variable_get('features_default_export_path', FEATURES_DEFAULT_EXPORT_PATH),
|
||||
'#description' => t('All feature exports will be automatically saved to this path, unless overridden on the individual feature.'),
|
||||
);
|
||||
$form['general']['features_rebuild_on_flush'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Rebuild features on cache clear'),
|
||||
'#default_value' => variable_get('features_rebuild_on_flush', TRUE),
|
||||
'#description' => t('If you have a large site with many features, you may experience lag on full cache clear. If disabled, features will rebuild only when viewing the features list or saving the modules list.'),
|
||||
);
|
||||
$form['general']['features_rebuild_modules_page'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Rebuild features on accessing modules list page'),
|
||||
'#default_value' => variable_get('features_rebuild_modules_page', FALSE),
|
||||
'#description' => t('If you have a large site with many features, you may experience lag on accessing the modules administration page. If disabled, features will not rebuild when viewing the modules list.'),
|
||||
);
|
||||
|
||||
return system_settings_form($form);
|
||||
}
|
||||
|
||||
/**
|
||||
* Form constructor for features export form.
|
||||
*
|
||||
@@ -22,7 +115,7 @@ function features_export_form($form, $form_state, $feature = NULL) {
|
||||
|
||||
$feature_name = !empty($feature->name) ? $feature->name : '';
|
||||
$form = array(
|
||||
'#attributes' => array('class' => array('features-export-form')),
|
||||
'#attributes' => array('class' => array('features-export-form', 'clearfix')),
|
||||
'#feature' => isset($feature) ? $feature : NULL,
|
||||
);
|
||||
$form['info'] = array(
|
||||
@@ -40,23 +133,24 @@ function features_export_form($form, $form_state, $feature = NULL) {
|
||||
'#description' => t('Example: Image gallery') . ' (' . t('Do not begin name with numbers.') . ')',
|
||||
'#type' => 'textfield',
|
||||
'#default_value' => !empty($feature->info['name']) ? $feature->info['name'] : '',
|
||||
'#attributes' => array('class' => array('feature-name')),
|
||||
);
|
||||
$form['info']['module_name'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#type' => 'machine_name',
|
||||
'#title' => t('Machine-readable name'),
|
||||
'#description' => t('Example: image_gallery') . '<br/>' . t('May only contain lowercase letters, numbers and underscores. <strong>Try to avoid conflicts with the names of existing Drupal projects.</strong>'),
|
||||
'#required' => TRUE,
|
||||
'#default_value' => $feature_name,
|
||||
'#attributes' => array('class' => array('feature-module-name')),
|
||||
'#element_validate' => array('features_export_form_validate_field'),
|
||||
'#machine_name' => array(
|
||||
'exists' => 'features_export_form_module_name_exists',
|
||||
'source' => array('info', 'name'),
|
||||
),
|
||||
);
|
||||
// If recreating this feature, disable machine name field and blank out
|
||||
// js-attachment classes to ensure the machine name cannot be changed.
|
||||
if (isset($feature)) {
|
||||
// If recreating this feature, disable machine name field to ensure the
|
||||
// machine name cannot be changed, unless user role has granted permission to
|
||||
// edit machine name of disabled features.
|
||||
if (isset($feature) && ($feature->status || !user_access('rename features'))) {
|
||||
$form['info']['module_name']['#value'] = $feature_name;
|
||||
$form['info']['module_name']['#disabled'] = TRUE;
|
||||
$form['info']['name']['#attributes'] = array();
|
||||
}
|
||||
$form['info']['description'] = array(
|
||||
'#title' => t('Description'),
|
||||
@@ -98,24 +192,26 @@ function features_export_form($form, $form_state, $feature = NULL) {
|
||||
'#default_value' => !empty($feature->info['project status url']) ? $feature->info['project status url'] : '',
|
||||
'#element_validate' => array('features_export_form_validate_field'),
|
||||
);
|
||||
$directory = (!empty($feature->filename)) ? dirname($feature->filename) : 'sites/all/modules';
|
||||
$directory = (!empty($feature->filename)) ? dirname($feature->filename) : variable_get('features_default_export_path', FEATURES_DEFAULT_EXPORT_PATH);
|
||||
if (!empty($feature_name) && substr_compare($directory, $feature_name, strlen($directory)-strlen($feature_name), strlen($feature_name)) === 0) {
|
||||
// if path ends with module_name, strip it
|
||||
$directory = dirname($directory);
|
||||
}
|
||||
$form['advanced']['generate_path'] = array(
|
||||
'#title' => t('Path to Generate feature module'),
|
||||
'#description' => t('File path for feature module. For Example: sites/all/modules or /tmp. ' .
|
||||
t('Leave blank for <strong>@path</strong>', array('@path' => $directory))),
|
||||
'#type' => 'textfield',
|
||||
'#required' => FALSE,
|
||||
'#default_value' => !empty($feature->info['project path']) ? $feature->info['project path'] : '',
|
||||
);
|
||||
$form['advanced']['generate'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Generate feature'),
|
||||
'#submit' => array('features_export_build_form_submit'),
|
||||
);
|
||||
if (user_access('generate features')) {
|
||||
$form['advanced']['generate_path'] = array(
|
||||
'#title' => t('Path to Generate feature module'),
|
||||
'#description' => t('File path for feature module. For Example: sites/all/modules/features or /tmp. ' .
|
||||
t('Leave blank for <strong>@path</strong>', array('@path' => $directory))),
|
||||
'#type' => 'textfield',
|
||||
'#required' => FALSE,
|
||||
'#default_value' => !empty($feature->info['project path']) ? $feature->info['project path'] : '',
|
||||
);
|
||||
$form['advanced']['generate'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Generate feature'),
|
||||
'#submit' => array('features_export_build_form_submit', 'features_form_rebuild'),
|
||||
);
|
||||
}
|
||||
// build the Component Listing panel on the right
|
||||
_features_export_form_components($form, $form_state);
|
||||
|
||||
@@ -149,7 +245,7 @@ function features_export_form($form, $form_state, $feature = NULL) {
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Download feature'),
|
||||
'#weight' => 10,
|
||||
'#submit' => array('features_export_build_form_submit'),
|
||||
'#submit' => array('features_export_build_form_submit', 'features_form_rebuild'),
|
||||
);
|
||||
|
||||
$form['#attached']['library'][] = array('system', 'ui.dialog');
|
||||
@@ -157,6 +253,13 @@ function features_export_form($form, $form_state, $feature = NULL) {
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Machine name existence callback for the module name.
|
||||
*/
|
||||
function features_export_form_module_name_exists($value) {
|
||||
return (bool) features_get_info('module', $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the render array elements for the Components selection on the Export form
|
||||
* @param array $feature - feature associative array
|
||||
@@ -267,62 +370,102 @@ 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']) ?
|
||||
$component_info['name'] . " <span>(" . check_plain($component) . ")</span>" : check_plain($component));
|
||||
if (!variable_get('features_admin_show_component_' . $component, TRUE)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$count = 0;
|
||||
$component_items_count = count($component_info['options']['sources']);
|
||||
$count_label = ' (<span class = "component-count">' . $component_items_count . '</span>)';
|
||||
$label = (isset($component_info['name']) ?
|
||||
$component_info['name'] . $count_label . " <span>(" . check_plain($component) . ")</span>"
|
||||
: check_plain($component) . $count_label);
|
||||
|
||||
$count = 0;
|
||||
foreach ($sections as $section) {
|
||||
$count += count($component_info['options'][$section]);
|
||||
}
|
||||
$extra_class = ($count == 0) ? 'features-export-empty' : '';
|
||||
$component_name = str_replace('_', '-', check_plain($component));
|
||||
|
||||
if ($count + count($component_info['options']['sources']) > 0) {
|
||||
if ($count + $component_items_count > 0) {
|
||||
|
||||
$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' => "<div class='features-export-parent component-$component'>",
|
||||
);
|
||||
$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'),
|
||||
),
|
||||
);
|
||||
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,
|
||||
);
|
||||
|
||||
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]['deprecated'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => $label . "<span class='features-conflict'> (" . t('DEPRECATED') . ")</span>",
|
||||
'#tree' => TRUE,
|
||||
'#collapsible' => TRUE,
|
||||
'#collapsed' => TRUE,
|
||||
'#attributes' => array('class' => array('features-export-component')),
|
||||
);
|
||||
$list = ' ';
|
||||
foreach ($component_info['options']['included'] as $key) {
|
||||
$list .= "<span class='form-type-checkbox features-conflict'>$key</span>";
|
||||
}
|
||||
$form['export'][$component]['deprecated']['selected'] = array(
|
||||
'#prefix' => "<div class='component-detected'>",
|
||||
'#markup' => $list,
|
||||
'#suffix' => "</div>",
|
||||
);
|
||||
}
|
||||
}
|
||||
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' => "<div class='features-export-parent component-$component'>",
|
||||
);
|
||||
$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(
|
||||
'#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'] =
|
||||
"<div class='component-list features-export-list $extra_class'>";
|
||||
$form['export'][$component][$sections[count($sections)-1]]['#suffix'] = '</div></div>';
|
||||
}
|
||||
$form['export'][$component][$sections[0]]['#prefix'] =
|
||||
"<div class='component-list features-export-list $extra_class'>";
|
||||
$form['export'][$component][$sections[count($sections)-1]]['#suffix'] = '</div></div>';
|
||||
}
|
||||
}
|
||||
$form['export']['features_legend'] = array(
|
||||
@@ -406,6 +549,10 @@ function _features_export_build($feature, &$form_state) {
|
||||
if ($reset) {
|
||||
unset($form_state['values'][$component]);
|
||||
}
|
||||
if (!variable_get('features_admin_show_component_' . $component, TRUE)) {
|
||||
unset($components[$component]);
|
||||
continue;
|
||||
}
|
||||
// User-selected components take precedence.
|
||||
$stub[$component] = array();
|
||||
$stub_count[$component] = 0;
|
||||
@@ -456,6 +603,7 @@ function _features_export_build($feature, &$form_state) {
|
||||
$component_export['selected'][$section] = array();
|
||||
}
|
||||
$options = features_invoke($component, 'features_export_options');
|
||||
drupal_alter('features_export_options', $options, $component);
|
||||
if (!empty($options)) {
|
||||
$exported_components = !empty($exported_features_info[$component]) ? $exported_features_info[$component] : array();
|
||||
$new_components = !empty($new_features_info[$component]) ? $new_features_info[$component] : array();
|
||||
@@ -614,7 +762,7 @@ function features_export_form_rebuild($form, &$form_state) {
|
||||
|
||||
function features_export_components_json($feature_name) {
|
||||
module_load_include('inc', 'features', 'features.export');
|
||||
$export = array();
|
||||
$export = array('features' => array());
|
||||
if (!empty($_POST['items'])) {
|
||||
$excluded = (!empty($_POST['excluded'])) ? $_POST['excluded'] : array();
|
||||
$stub = array();
|
||||
@@ -631,6 +779,8 @@ function features_export_components_json($feature_name) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$stub['dependencies'] = isset($stub['dependencies']) ? $stub['dependencies'] : array();
|
||||
$export = features_populate(array('features' => $stub, 'dependencies' => $stub['dependencies']), $feature_name);
|
||||
$export['features']['dependencies'] = $export['dependencies'];
|
||||
|
||||
@@ -667,16 +817,6 @@ function features_info_file_preview($form, &$form_state){
|
||||
*/
|
||||
function features_export_form_validate_field($element, &$form_state) {
|
||||
switch ($element['#name']) {
|
||||
case 'module_name':
|
||||
if (!preg_match('!^[a-z0-9_]+$!', $element['#value'])) {
|
||||
form_error($element, t('The machine-readable name must contain only lowercase letters, numbers, and underscores.'));
|
||||
}
|
||||
// If user is filling out the feature name for the first time and uses
|
||||
// the name of an existing module throw an error.
|
||||
else if (empty($element['#default_value']) && features_get_info('module', $element['#value'])) {
|
||||
form_error($element, t('A module by the name @name already exists on your site. Please choose a different name.', array('@name' => $element['#value'])));
|
||||
}
|
||||
break;
|
||||
case 'project_status_url':
|
||||
if (!empty($element['#value']) && !valid_url($element['#value'])) {
|
||||
form_error($element, t('The URL %url is invalid. Please enter a fully-qualified URL, such as http://www.example.com/feed.xml.', array('%url' => $element['#value'])));
|
||||
@@ -710,7 +850,7 @@ function _features_export_generate($export, $form_state, $feature = NULL) {
|
||||
}
|
||||
// If either update status-related keys are provided, add a project key
|
||||
// corresponding to the module name.
|
||||
if (!empty($form_state['values']['version']) || !empty($form_state['values']['project_status_url'])) {
|
||||
if (!empty($form_state['values']['version']) && !empty($form_state['values']['project_status_url'])) {
|
||||
$export['project'] = $form_state['values']['module_name'];
|
||||
}
|
||||
if (!empty($form_state['values']['version'])) {
|
||||
@@ -734,13 +874,18 @@ function features_export_build_form_submit($form, &$form_state) {
|
||||
$generate = ($form_state['values']['op'] == $form_state['values']['generate']);
|
||||
$module_name = $form_state['values']['module_name'];
|
||||
|
||||
if ($generate && !user_access('generate features')) {
|
||||
drupal_set_message(t("No permission for generating features."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate download
|
||||
if ($files = features_export_render($export, $module_name, TRUE)) {
|
||||
$filename = (!empty($export['version']) ? "{$module_name}-{$export['version']}" : $module_name) . '.tar';
|
||||
|
||||
if ($generate) {
|
||||
$success = TRUE;
|
||||
$destination = 'sites/all/modules';
|
||||
$destination = variable_get('features_default_export_path', FEATURES_DEFAULT_EXPORT_PATH);
|
||||
$directory = (!empty($export['project path'])) ? $export['project path'] . '/' . $module_name :
|
||||
(isset($feature->filename) ? dirname($feature->filename) : $destination . '/' . $module_name);
|
||||
if (!is_dir($directory)) {
|
||||
@@ -762,6 +907,35 @@ function features_export_build_form_submit($form, &$form_state) {
|
||||
|
||||
$tar = array();
|
||||
$filenames = array();
|
||||
// Copy any files if _files key is there.
|
||||
if (!empty($files['_files'])) {
|
||||
foreach ($files['_files'] as $file_name => $file_info) {
|
||||
if ($generate) {
|
||||
// See if files are in a sub directory.
|
||||
if (strpos($file_name, '/')) {
|
||||
$file_directory = $directory . '/' . substr($file_name, 0, strrpos($file_name, '/'));
|
||||
if (!is_dir($file_directory)) {
|
||||
mkdir($file_directory);
|
||||
}
|
||||
}
|
||||
if (!empty($file_info['file_path'])) {
|
||||
file_unmanaged_copy($file_info['file_path'], "{$directory}/{$file_name}", FILE_EXISTS_REPLACE);
|
||||
}
|
||||
elseif ($file_info['file_content']) {
|
||||
file_put_contents("{$directory}/{$file_name}", $file_info['file_content']);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!empty($file_info['file_path'])) {
|
||||
print features_tar_create("{$module_name}/{$file_name}", file_get_contents($file_info['file_path']));
|
||||
}
|
||||
elseif ($file_info['file_content']) {
|
||||
features_tar_create("{$directory}/{$file_name}", $file_info['file_content']);
|
||||
}
|
||||
}
|
||||
}
|
||||
unset($files['_files']);
|
||||
}
|
||||
foreach ($files as $extension => $file_contents) {
|
||||
if (!in_array($extension, array('module', 'info'))) {
|
||||
$extension .= '.inc';
|
||||
@@ -777,6 +951,14 @@ function features_export_build_form_submit($form, &$form_state) {
|
||||
}
|
||||
}
|
||||
if (features_get_modules($module_name, TRUE)) {
|
||||
// prevent deprecated component files from being included in download
|
||||
$deprecated = features_get_deprecated();
|
||||
foreach ($deprecated as $component) {
|
||||
$info = features_get_components($component);
|
||||
$filename = isset($info['default_file']) && $info['default_file'] == FEATURES_DEFAULTS_CUSTOM ? $info['default_filename'] : "features.{$component}";
|
||||
$filename .= '.inc';
|
||||
$filenames[] = "{$module_name}.$filename";
|
||||
}
|
||||
$module_path = drupal_get_path('module', $module_name);
|
||||
// file_scan_directory() can throw warnings when using PHP 5.3, messing
|
||||
// up the output of our file stream. Suppress errors in this one case in
|
||||
@@ -827,28 +1009,14 @@ function features_filter_hidden($module) {
|
||||
* Form constructor for the features configuration form.
|
||||
*/
|
||||
function features_admin_form($form, $form_state) {
|
||||
$features = _features_get_features_list();
|
||||
$modules = array_filter(features_get_modules(), 'features_filter_hidden');
|
||||
$conflicts = features_get_conflicts();
|
||||
|
||||
// Load export functions to use in comparison.
|
||||
module_load_include('inc', 'features', 'features.export');
|
||||
|
||||
// Clear & rebuild key caches
|
||||
features_get_info(NULL, NULL, TRUE);
|
||||
features_rebuild();
|
||||
|
||||
$modules = array_filter(features_get_modules(), 'features_filter_hidden');
|
||||
$features = array_filter(features_get_features(), 'features_filter_hidden');
|
||||
$conflicts = features_get_conflicts();
|
||||
|
||||
foreach ($modules as $key => $module) {
|
||||
if ($module->status && !empty($module->info['dependencies'])) {
|
||||
foreach ($module->info['dependencies'] as $dependent) {
|
||||
if (isset($features[$dependent])) {
|
||||
$features[$dependent]->dependents[$key] = $module->info['name'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty($features) ) {
|
||||
if (empty($features) ) {
|
||||
$form['no_features'] = array(
|
||||
'#markup' => t('No Features were found. Please use the !create_link link to create
|
||||
a new Feature module, or upload an existing Feature to your modules directory.',
|
||||
@@ -863,7 +1031,7 @@ function features_admin_form($form, $form_state) {
|
||||
ksort($features);
|
||||
foreach ($features as $name => $module) {
|
||||
$package_title = !empty($module->info['package']) ? $module->info['package'] : t('Other');
|
||||
$package = strtolower(preg_replace('/[^a-zA-Z0-9-]+/', '-', $package_title));
|
||||
$package = 'package_' . strtolower(preg_replace('/[^a-zA-Z0-9-]+/', '-', $package_title));
|
||||
|
||||
// Set up package elements
|
||||
if (!isset($form[$package])) {
|
||||
@@ -894,23 +1062,28 @@ function features_admin_form($form, $form_state) {
|
||||
}
|
||||
}
|
||||
if (!empty($unmet_dependencies)) {
|
||||
$description .= "<div class='dependencies'>" . t('Unmet dependencies: @dependencies', array('@dependencies' => implode(', ', $unmet_dependencies))) . "</div>";
|
||||
$description .= "<div class='dependencies'>" . t('Unmet dependencies: !dependencies', array('!dependencies' => implode(', ', $unmet_dependencies))) . "</div>";
|
||||
$disabled = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($module->dependents)) {
|
||||
$disabled = TRUE;
|
||||
$description .= "<div class='requirements'>". t('Required by: @dependents', array('@dependents' => implode(', ', $module->dependents))) ."</div>";
|
||||
$description .= "<div class='requirements'>". t('Required by: !dependents', array('!dependents' => implode(', ', $module->dependents))) ."</div>";
|
||||
}
|
||||
|
||||
// Detect potential conflicts
|
||||
if (!empty($conflicts[$name])) {
|
||||
$module_conflicts = array();
|
||||
foreach (array_keys($conflicts[$name]) as $conflict) {
|
||||
foreach ($conflicts[$name] as $conflict => $components) {
|
||||
$component_strings = array();
|
||||
foreach ($components as $component => $component_conflicts) {
|
||||
$component_strings[] = t('@component [@items]', array('@component' => $component, '@items' => implode(', ', $component_conflicts)));
|
||||
}
|
||||
$component_strings = implode(', ', $component_strings);
|
||||
// If conflicting module is disabled, indicate so in feature listing
|
||||
$status = !module_exists($conflict) ? FEATURES_MODULE_DISABLED : FEATURES_MODULE_CONFLICT;
|
||||
$module_conflicts[] = theme('features_module_status', array('status' => $status, 'module' => $conflict));
|
||||
$module_conflicts[] = theme('features_module_status', array('status' => $status, 'module' => $conflict)) . t(' in ') . $component_strings;
|
||||
// Only disable modules with conflicts if they are not already enabled.
|
||||
// If they are already enabled, somehow the user got themselves into a
|
||||
// bad situation and they need to be able to disable a conflicted module.
|
||||
@@ -922,6 +1095,7 @@ function features_admin_form($form, $form_state) {
|
||||
}
|
||||
|
||||
$href = "admin/structure/features/{$name}";
|
||||
$href_overridden = module_exists('diff') ? $href . '/diff' : $href;
|
||||
$module_name = (user_access('administer features')) ? l($module->info['name'], $href) : $module->info['name'];
|
||||
$form[$package]['status'][$name] = array(
|
||||
'#type' => 'checkbox',
|
||||
@@ -951,7 +1125,7 @@ function features_admin_form($form, $form_state) {
|
||||
$state .= l(t('Check'), "admin/structure/features/{$name}/status", array('attributes' => array('class' => array('admin-check'))));
|
||||
$state .= theme('features_storage_link', array('storage' => FEATURES_REBUILDING, 'path' => $href));
|
||||
$state .= theme('features_storage_link', array('storage' => FEATURES_NEEDS_REVIEW, 'path' => $href));
|
||||
$state .= theme('features_storage_link', array('storage' => FEATURES_OVERRIDDEN, 'path' => $href));
|
||||
$state .= theme('features_storage_link', array('storage' => FEATURES_OVERRIDDEN, 'path' => $href_overridden));
|
||||
$state .= theme('features_storage_link', array('storage' => FEATURES_DEFAULT, 'path' => $href));
|
||||
}
|
||||
elseif (!empty($conflicts[$name])) {
|
||||
@@ -975,7 +1149,7 @@ function features_admin_form($form, $form_state) {
|
||||
// As of 7.0 beta 2 it matters where the "vertical_tabs" element lives on the
|
||||
// the array. We add it late, but at the beginning of the array because that
|
||||
// keeps us away from trouble.
|
||||
$form = array('packages' => array('#type' => 'vertical_tabs')) + $form;
|
||||
$form = array_merge(array('packages' => array('#type' => 'vertical_tabs')), $form);
|
||||
|
||||
$form['buttons'] = array(
|
||||
'#theme' => 'features_form_buttons',
|
||||
@@ -995,11 +1169,13 @@ function features_admin_form($form, $form_state) {
|
||||
function features_admin_components($form, $form_state, $feature) {
|
||||
// Breadcrumb navigation
|
||||
$breadcrumb[] = l(t('Home'), NULL);
|
||||
$breadcrumb[] = l(t('Administration'), 'admin');
|
||||
$breadcrumb[] = l(t('Structure'), 'admin/structure');
|
||||
$breadcrumb[] = l(t('Features'), 'admin/structure/features');
|
||||
drupal_set_breadcrumb($breadcrumb);
|
||||
|
||||
module_load_include('inc', 'features', 'features.export');
|
||||
$form = array();
|
||||
$form['#feature'] = $feature;
|
||||
|
||||
// Store feature info for theme layer.
|
||||
$form['module'] = array('#type' => 'value', '#value' => $feature->name);
|
||||
@@ -1063,8 +1239,14 @@ function features_admin_components($form, $form_state, $feature) {
|
||||
else if (array_key_exists($component, $conflicts)) {
|
||||
$storage = FEATURES_CONFLICT;
|
||||
}
|
||||
// This can be removed if the css is fixed so link doesn't move when
|
||||
// ajaxing and linke moved.
|
||||
$lock_link = '<span class="features-lock-empty"></span>';
|
||||
if (user_access('administer features') && (features_hook($component, 'features_revert') || features_hook($component, 'features_rebuild'))) {
|
||||
$lock_link = ' ' . theme('features_lock_link', array('feature' => $feature->name, 'component' => $component));
|
||||
}
|
||||
$form['components'][$component] = array(
|
||||
'#markup' => theme('features_storage_link', array('storage' => $storage, 'path' => $path)),
|
||||
'#markup' => $lock_link . theme('features_storage_link', array('storage' => $storage, 'path' => $path)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1095,11 +1277,14 @@ function features_admin_components_revert(&$form, &$form_state) {
|
||||
module_load_include('inc', 'features', 'features.export');
|
||||
features_include();
|
||||
$module = $form_state['values']['module'];
|
||||
$revert = array();
|
||||
$revert = array($module => array());
|
||||
foreach (array_filter($form_state['values']['revert']) as $component => $status) {
|
||||
$revert[$module][] = $component;
|
||||
drupal_set_message(t('Reverted all <strong>@component</strong> components for <strong>@module</strong>.', array('@component' => $component, '@module' => $module)));
|
||||
}
|
||||
if (empty($revert[$module])) {
|
||||
drupal_set_message(t('Please select which components to revert.'), 'warning');
|
||||
}
|
||||
features_revert($revert);
|
||||
$form_state['redirect'] = 'admin/structure/features/' . $module;
|
||||
}
|
||||
@@ -1149,7 +1334,7 @@ function features_form_submit(&$form, &$form_state) {
|
||||
// page callback rather than as part of the submit handler as some modules
|
||||
// have includes/other directives of importance in hooks that have already
|
||||
// been called in this page load.
|
||||
$form_state['redirect'] = 'admin/structure/features/cleanup/clear';
|
||||
$form_state['redirect'] = array('admin/structure/features/cleanup', array('query' => array('token' => drupal_get_token())));
|
||||
|
||||
$features = $form['#features'];
|
||||
if (!empty($features)) {
|
||||
@@ -1166,96 +1351,26 @@ function features_form_submit(&$form, &$form_state) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Form for disabling orphaned dependencies.
|
||||
* Submit handler for the 'manage features' form rebuild button.
|
||||
*/
|
||||
function features_cleanup_form($form, $form_state, $cache_clear = FALSE) {
|
||||
$form = array();
|
||||
function features_form_rebuild() {
|
||||
cache_clear_all('features:features_list', 'cache');
|
||||
}
|
||||
|
||||
// Clear caches if we're getting a post-submit redirect that requests it.
|
||||
if ($cache_clear) {
|
||||
/**
|
||||
* Callback for clearing cache after enabling a feature.
|
||||
*/
|
||||
function features_cleanup() {
|
||||
if (!empty($_GET['token']) && drupal_valid_token($_GET['token'])) {
|
||||
drupal_flush_all_caches();
|
||||
|
||||
// The following functions need to be run because drupal_flush_all_caches()
|
||||
// runs rebuilds in the wrong order. The node type cache is rebuilt *after*
|
||||
// the menu is rebuilt, meaning that the menu tree is stale in certain
|
||||
// circumstances after drupal_flush_all_caches(). We rebuild again.
|
||||
menu_rebuild();
|
||||
}
|
||||
|
||||
// Retrieve orphaned modules and provide them as optional modules to be disabled.
|
||||
// Exclude any modules that have been added to the 'ignored' list.
|
||||
$options = array();
|
||||
$orphans = features_get_orphans();
|
||||
$ignored = variable_get('features_ignored_orphans', array());
|
||||
if (!empty($orphans)) {
|
||||
foreach ($orphans as $module) {
|
||||
if (!in_array($module->name, $ignored, TRUE)) {
|
||||
$options[$module->name] = check_plain($module->info['name']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($options)) {
|
||||
$form['orphans'] = array(
|
||||
'#title' => t('Orphaned dependencies'),
|
||||
'#description' => t('These modules are dependencies of features that have been disabled. They may be disabled without affecting other components of your website.'),
|
||||
'#type' => 'checkboxes',
|
||||
'#options' => $options,
|
||||
'#default_value' => array_keys($options),
|
||||
);
|
||||
$form['buttons'] = array('#tree' => TRUE, '#theme' => 'features_form_buttons');
|
||||
$form['buttons']['disable'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Disable selected modules'),
|
||||
'#submit' => array('features_cleanup_form_disable'),
|
||||
);
|
||||
$form['buttons']['ignore'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Leave enabled'),
|
||||
'#submit' => array('features_cleanup_form_ignore'),
|
||||
);
|
||||
}
|
||||
else {
|
||||
drupal_goto('admin/structure/features');
|
||||
}
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit handler for disable action on features_cleanup_form().
|
||||
*/
|
||||
function features_cleanup_form_disable(&$form, &$form_state) {
|
||||
if (!empty($form_state['values']['orphans'])) {
|
||||
$disable = array_keys(array_filter($form_state['values']['orphans']));
|
||||
$ignored = array_diff(array_keys($form_state['values']['orphans']), $disable);
|
||||
|
||||
// Disable any orphans that have been selected.
|
||||
module_disable($disable);
|
||||
drupal_flush_all_caches();
|
||||
|
||||
// Add enabled modules to ignored orphans list.
|
||||
$ignored_orphans = variable_get('features_ignored_orphans', array());
|
||||
foreach ($ignored as $module) {
|
||||
$ignored_orphans[$module] = $module;
|
||||
}
|
||||
variable_set('features_ignored_orphans', $ignored_orphans);
|
||||
}
|
||||
$form_state['redirect'] = 'admin/structure/features/cleanup';
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit handler for ignore action on features_cleanup_form().
|
||||
*/
|
||||
function features_cleanup_form_ignore(&$form, &$form_state) {
|
||||
if (!empty($form_state['values']['orphans'])) {
|
||||
$ignored = array_keys($form_state['values']['orphans']);
|
||||
$ignored_orphans = variable_get('features_ignored_orphans', array());
|
||||
foreach ($ignored as $module) {
|
||||
$ignored_orphans[$module] = $module;
|
||||
}
|
||||
variable_set('features_ignored_orphans', $ignored_orphans);
|
||||
}
|
||||
$form_state['redirect'] = 'admin/structure/features/cleanup';
|
||||
return MENU_NOT_FOUND;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1274,6 +1389,7 @@ function features_cleanup_form_ignore(&$form, &$form_state) {
|
||||
function features_feature_diff($feature, $component = NULL) {
|
||||
drupal_add_css(drupal_get_path('module', 'features') . '/features.css');
|
||||
module_load_include('inc', 'features', 'features.export');
|
||||
drupal_set_title($feature->info['name']);
|
||||
|
||||
$overrides = features_detect_overrides($feature);
|
||||
|
||||
@@ -1306,6 +1422,71 @@ function features_feature_diff($feature, $component = NULL) {
|
||||
return $output;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Page callback to lock a component.
|
||||
*
|
||||
* @param $feature
|
||||
* Loaded feature object to be processed for component locking.
|
||||
* @param $component
|
||||
* (optional) A specific component to lock.
|
||||
*
|
||||
* @return
|
||||
* Themed display of what is different.
|
||||
*/
|
||||
function features_admin_lock($feature, $type = 'ajax', $component = NULL) {
|
||||
if ($type == 'ajax' && !empty($_GET['token']) && drupal_valid_token($_GET['token'], 'features/' . $feature->name . '/' . ($component ? $component : '')) == $_GET['token']) {
|
||||
if (features_feature_is_locked($feature->name, $component, FALSE)) {
|
||||
features_feature_unlock($feature->name, $component);
|
||||
}
|
||||
else {
|
||||
features_feature_lock($feature->name, $component);
|
||||
}
|
||||
$commands = array();
|
||||
$new_link = theme('features_lock_link', array('feature' => $feature->name, 'component' => $component));
|
||||
$commands[] = ajax_command_replace('#features-lock-link-' . $feature->name . ($component ? '-' . $component : ''), $new_link);
|
||||
$page = array('#type' => 'ajax', '#commands' => $commands);
|
||||
ajax_deliver($page);
|
||||
}
|
||||
else {
|
||||
return drupal_get_form('features_feature_lock_confirm_form', $feature, $component);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm form for locking a feature.
|
||||
*/
|
||||
function features_feature_lock_confirm_form($form, $form_state, $feature, $component) {
|
||||
$form['#feature'] = $feature;
|
||||
$form['#component'] = $component;
|
||||
$is_locked = features_feature_is_locked($feature->name, $component, FALSE);
|
||||
$args = array(
|
||||
'@name' => $feature->name,
|
||||
'@component' => $component ? $component : t('all'),
|
||||
'!action' => $is_locked ? t('unlock') : t('lock'),
|
||||
);
|
||||
$question = t('Are you sure you want to !action this Feature @name (component @component)?', $args);
|
||||
return confirm_form($form, $question, 'admin/structure/features/' . $feature->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit callback to lock components of a feature.
|
||||
*/
|
||||
function features_feature_lock_confirm_form_submit($form, &$form_state) {
|
||||
$feature = $form['#feature']->name;
|
||||
$component = $form['#component'];
|
||||
if (features_feature_is_locked($feature, $component, FALSE)) {
|
||||
features_feature_unlock($feature, $component);
|
||||
drupal_set_message(t('Feature @name (component @component) has been unlocked.', array('@name' => $feature, '@component' => $component ? $component : t('all'))));
|
||||
}
|
||||
else {
|
||||
features_feature_lock($feature, $component);
|
||||
drupal_set_message(t('Feature @name (component @component) has been locked.', array('@name' => $feature, '@component' => $component ? $component : t('all'))));
|
||||
}
|
||||
$form_state['redirect'] = 'admin/structure/features/' . $feature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare the component names. Used to sort alphabetically.
|
||||
*/
|
||||
@@ -1434,3 +1615,42 @@ function _features_get_used($module_name = NULL) {
|
||||
$features_ignore_conflicts = $old_value;
|
||||
return $conflicts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the array of features as expected on the Manage Features form.
|
||||
* Uses caching for performance reasons if caching is enabled.
|
||||
*
|
||||
* @internal - This function might return cached result with outdated data,
|
||||
* use with caution.
|
||||
*/
|
||||
function _features_get_features_list() {
|
||||
$features = array();
|
||||
|
||||
$cache = cache_get('features:features_list');
|
||||
if ($cache) {
|
||||
$features = $cache->data;
|
||||
}
|
||||
|
||||
if (empty($features)) {
|
||||
// Clear & rebuild key caches
|
||||
features_get_info(NULL, NULL, TRUE);
|
||||
features_rebuild();
|
||||
|
||||
$modules = array_filter(features_get_modules(), 'features_filter_hidden');
|
||||
$features = array_filter(features_get_features(), 'features_filter_hidden');
|
||||
|
||||
foreach ($modules as $key => $module) {
|
||||
if ($module->status && !empty($module->info['dependencies'])) {
|
||||
foreach ($module->info['dependencies'] as $dependent) {
|
||||
if (isset($features[$dependent])) {
|
||||
$features[$dependent]->dependents[$key] = $module->info['name'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cache_set('features:features_list', $features);
|
||||
}
|
||||
|
||||
return $features;
|
||||
}
|
||||
|
@@ -42,7 +42,7 @@
|
||||
* are declared "dynamically" or are part of a family of components.
|
||||
*
|
||||
* 'alter_type': What type of alter hook this hook uses. 'normal' is called
|
||||
* after the main hook is called. 'inline' is embeded within the default hook
|
||||
* after the main hook is called. 'inline' is embedded within the default hook
|
||||
* and may not be implemented by some default hooks.
|
||||
* 'none' is no alter hook exists. Defaults to 'normal'
|
||||
*
|
||||
@@ -61,7 +61,7 @@ function hook_features_api() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Component hook. The hook should be implemented using the name ot the
|
||||
* Component hook. The hook should be implemented using the name of the
|
||||
* component, not the module, eg. [component]_features_export() rather than
|
||||
* [module]_features_export().
|
||||
*
|
||||
@@ -118,7 +118,7 @@ function hook_features_export($data, &$export, $module_name) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Component hook. The hook should be implemented using the name ot the
|
||||
* Component hook. The hook should be implemented using the name of the
|
||||
* component, not the module, eg. [component]_features_export() rather than
|
||||
* [module]_features_export().
|
||||
*
|
||||
@@ -137,7 +137,7 @@ function hook_features_export_options() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Component hook. The hook should be implemented using the name ot the
|
||||
* Component hook. The hook should be implemented using the name of the
|
||||
* component, not the module, eg. [component]_features_export() rather than
|
||||
* [module]_features_export().
|
||||
*
|
||||
@@ -157,7 +157,8 @@ function hook_features_export_options() {
|
||||
* of the module, e.g. the key for `hook_example` should simply be `example`
|
||||
* The values in the array can also be in the form of an associative array
|
||||
* with the required key of 'code' and optional key of 'args', if 'args' need
|
||||
* to be added to the hook.
|
||||
* to be added to the hook. Alternate it can be an associative array in the
|
||||
* same style as hook_features_export_files() to add additional files.
|
||||
*/
|
||||
function hook_features_export_render($module_name, $data, $export = NULL) {
|
||||
$code = array();
|
||||
@@ -171,7 +172,7 @@ function hook_features_export_render($module_name, $data, $export = NULL) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Component hook. The hook should be implemented using the name ot the
|
||||
* Component hook. The hook should be implemented using the name of the
|
||||
* component, not the module, eg. [component]_features_export() rather than
|
||||
* [module]_features_export().
|
||||
*
|
||||
@@ -194,7 +195,7 @@ function hook_features_revert($module_name) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Component hook. The hook should be implemented using the name ot the
|
||||
* Component hook. The hook should be implemented using the name of the
|
||||
* component, not the module, eg. [component]_features_export() rather than
|
||||
* [module]_features_export().
|
||||
*
|
||||
@@ -219,6 +220,42 @@ function hook_features_rebuild($module_name) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked before a restore operation is run.
|
||||
*
|
||||
* This hook is called before any of the restore operations on the components is
|
||||
* run.
|
||||
*
|
||||
* @param string $op
|
||||
* The operation that is triggered: revert, rebuild, disable, enable
|
||||
* @param array $items
|
||||
* The items handled by the operation.
|
||||
*/
|
||||
function hook_features_pre_restore($op, $items) {
|
||||
if ($op == 'rebuild') {
|
||||
// Use features rebuild to rebuild the features independent exports too.
|
||||
entity_defaults_rebuild();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked after a restore operation is run.
|
||||
*
|
||||
* This hook is called after any of the restore operations on the components is
|
||||
* run.
|
||||
*
|
||||
* @param string $op
|
||||
* The operation that is triggered: revert, rebuild, disable, enable
|
||||
* @param array $items
|
||||
* The items handled by the operation.
|
||||
*/
|
||||
function hook_features_post_restore($op, $items) {
|
||||
if ($op == 'rebuild') {
|
||||
// Use features rebuild to rebuild the features independent exports too.
|
||||
entity_defaults_rebuild();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter the final array of Component names to be exported, just prior to
|
||||
* the rendering of defaults. Allows modules a final say in whether or not
|
||||
@@ -273,11 +310,31 @@ function hook_features_pipe_COMPONENT_alter(&$pipe, $data, $export) {
|
||||
* The module being exported contained in $export['module_name'].
|
||||
*/
|
||||
function hook_features_pipe_alter(&$pipe, $data, $export) {
|
||||
if ($export['component'] == 'node' && in_array($data, 'my-node-type')) {
|
||||
if ($export['component'] == 'node' && in_array('my-node-type', $data)) {
|
||||
$pipe['dependencies'][] = 'mymodule';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add extra files to the exported file.
|
||||
*
|
||||
* @return array
|
||||
* An array of files, keyed by file name that will appear in feature and
|
||||
* with either file_path key to indicate where to copy the file from or
|
||||
* file_content key to indicate the contents of the file.
|
||||
*/
|
||||
function hook_features_export_files($module_name, $export) {
|
||||
return array('css/main.css' => array('file_content' => 'body {background-color:blue;}'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter the extra files added to the export.
|
||||
*/
|
||||
function hook_features_export_files_alter(&$files, $module_name, $export) {
|
||||
$files['css/main.css']['file_content'] = 'body {background-color:black;}';
|
||||
}
|
||||
|
||||
/**
|
||||
* @defgroup features_component_alter_hooks Feature's component alter hooks
|
||||
* @{
|
||||
@@ -293,6 +350,8 @@ function hook_features_pipe_alter(&$pipe, $data, $export) {
|
||||
*/
|
||||
|
||||
/**
|
||||
* Deprecated as of 7.x-2.0.
|
||||
*
|
||||
* Alter the default fields right before they are cached into the database.
|
||||
*
|
||||
* @param &$fields
|
||||
@@ -301,6 +360,24 @@ function hook_features_pipe_alter(&$pipe, $data, $export) {
|
||||
function hook_field_default_fields_alter(&$fields) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter the base fields right before they are cached into the database.
|
||||
*
|
||||
* @param &$fields
|
||||
* By reference. The fields that have been declared by another feature.
|
||||
*/
|
||||
function hook_field_default_field_bases_alter(&$fields) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter the field instances right before they are cached into the database.
|
||||
*
|
||||
* @param &$fields
|
||||
* By reference. The fields that have been declared by another feature.
|
||||
*/
|
||||
function hook_field_default_field_instances_alter(&$fields) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter the default fieldgroup groups right before they are cached into the
|
||||
* database.
|
||||
|
@@ -559,3 +559,21 @@ input.form-submit.features-refresh-button {
|
||||
background: #EEE;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
fieldset.features-export-component .fieldset-title .component-count {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.features-lock-icon {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.features-components h3 {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.features-lock-empty {
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
}
|
@@ -16,10 +16,28 @@
|
||||
function features_drush_command() {
|
||||
$items = array();
|
||||
|
||||
// If Features is enabled display the configured default export path,
|
||||
// otherwise just show the default.
|
||||
if (defined('FEATURES_DEFAULT_EXPORT_PATH')) {
|
||||
$path = variable_get('features_default_export_path', FEATURES_DEFAULT_EXPORT_PATH);
|
||||
}
|
||||
else {
|
||||
$path = 'sites/all/modules';
|
||||
}
|
||||
|
||||
$items['features-list'] = array(
|
||||
'description' => "List all the available features for your site.",
|
||||
'options' => array(
|
||||
'status' => "Feature status, can be 'enabled', 'disabled' or 'all'",
|
||||
),
|
||||
'drupal dependencies' => array('features'),
|
||||
'aliases' => array('fl', 'features'),
|
||||
'outputformat' => array(
|
||||
'default' => 'table',
|
||||
'pipe-format' => 'list',
|
||||
'field-labels' => array('name' => 'Name', 'feature' => 'Feature', 'status' => 'Status', 'version' => 'Version', 'state' => 'State'),
|
||||
'output-data-type' => 'format-table',
|
||||
),
|
||||
);
|
||||
$items['features-export'] = array(
|
||||
'description' => "Export a feature from your site into a module.",
|
||||
@@ -28,9 +46,10 @@ function features_drush_command() {
|
||||
'components' => 'Patterns of components to include, see features-components for the format of patterns.'
|
||||
),
|
||||
'options' => array(
|
||||
'destination' => "Destination path (from Drupal root) of the exported feature. Defaults to 'sites/all/modules'",
|
||||
'destination' => "Destination path (from Drupal root) of the exported feature. Defaults to '" . $path . "'.",
|
||||
'version-set' => "Specify a version number for the feature.",
|
||||
'version-increment' => "Increment the feature's version number.",
|
||||
'ignore-conflicts' => "Ignore conflicts and export all components.",
|
||||
),
|
||||
'drupal dependencies' => array('features'),
|
||||
'aliases' => array('fe'),
|
||||
@@ -60,6 +79,9 @@ function features_drush_command() {
|
||||
'not-exported' => array(
|
||||
'description' => 'Show only components that have not been exported.',
|
||||
),
|
||||
'info-style' => array(
|
||||
'description' => 'Export components in format suitable for using in an info file.',
|
||||
),
|
||||
),
|
||||
'aliases' => array('fc'),
|
||||
);
|
||||
@@ -114,10 +136,26 @@ function features_drush_command() {
|
||||
'arguments' => array(
|
||||
'feature' => 'The feature in question.',
|
||||
),
|
||||
'options' => array(
|
||||
'ctypes' => 'Comma separated list of component types to limit the output to. Defaults to all types.',
|
||||
'lines' => 'Generate diffs with <n> lines of context instead of the usual two.',
|
||||
),
|
||||
'drupal dependencies' => array('features', 'diff'),
|
||||
'aliases' => array('fd'),
|
||||
);
|
||||
|
||||
$items['features-diff-all'] = array(
|
||||
'description' => "Show the code difference for all enabled features not in their default state.",
|
||||
'arguments' => array(
|
||||
'feature_exclude' => 'A space-delimited list of features to exclude from being reverted.',
|
||||
),
|
||||
'options' => array(
|
||||
'force' => "Bypass the confirmations. This is useful if you want to output all of the diffs to a log file.",
|
||||
),
|
||||
'drupal dependencies' => array('features', 'diff'),
|
||||
'aliases' => array('fda'),
|
||||
);
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
@@ -125,11 +163,20 @@ function features_drush_command() {
|
||||
* Implements hook_drush_help().
|
||||
*/
|
||||
function features_drush_help($section) {
|
||||
// If Features is enabled display the configured default export path,
|
||||
// otherwise just show the default.
|
||||
if (defined('FEATURES_DEFAULT_EXPORT_PATH')) {
|
||||
$path = variable_get('features_default_export_path', FEATURES_DEFAULT_EXPORT_PATH);
|
||||
}
|
||||
else {
|
||||
$path = 'sites/all/modules';
|
||||
}
|
||||
|
||||
switch ($section) {
|
||||
case 'drush:features':
|
||||
return dt("List all the available features for your site.");
|
||||
case 'drush:features-export':
|
||||
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'. 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.");
|
||||
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));
|
||||
case 'drush:features-components':
|
||||
return dt("List feature components matching patterns. The listing may be limited to exported/not-exported components.
|
||||
|
||||
@@ -160,9 +207,19 @@ Lastly, a pattern without a colon is interpreted as having \":%\" appended, for
|
||||
* Get a list of all feature modules.
|
||||
*/
|
||||
function drush_features_list() {
|
||||
$status = drush_get_option('status') ? drush_get_option('status') : 'all';
|
||||
if (!in_array($status, array('enabled', 'disabled', 'all'))) {
|
||||
return drush_set_error('', dt('!status is not valid', array('!status' => $status)));
|
||||
}
|
||||
|
||||
module_load_include('inc', 'features', 'features.export');
|
||||
$rows = array(array(dt('Name'), dt('Feature'), dt('Status'), dt('Version'), dt('State')));
|
||||
foreach (features_get_features(NULL, TRUE) as $k => $m) {
|
||||
|
||||
// Sort the Features list before compiling the output.
|
||||
$features = features_get_features(NULL, TRUE);
|
||||
ksort($features);
|
||||
|
||||
$rows = array();
|
||||
foreach ($features as $k => $m) {
|
||||
switch (features_get_storage($m->name)) {
|
||||
case FEATURES_DEFAULT:
|
||||
case FEATURES_REBUILDABLE:
|
||||
@@ -175,15 +232,23 @@ function drush_features_list() {
|
||||
$storage = dt('Needs review');
|
||||
break;
|
||||
}
|
||||
$rows[] = array(
|
||||
$m->info['name'],
|
||||
$m->name,
|
||||
$m->status ? dt('Enabled') : dt('Disabled'),
|
||||
$m->info['version'],
|
||||
$storage
|
||||
);
|
||||
if (
|
||||
($m->status == 0 && ($status == 'all' || $status == 'disabled')) ||
|
||||
($m->status == 1 && ($status == 'all' || $status == 'enabled'))
|
||||
) {
|
||||
$rows[$k] = array(
|
||||
'name' => $m->info['name'],
|
||||
'feature' => $m->name,
|
||||
'status' => $m->status ? dt('Enabled') : dt('Disabled'),
|
||||
'version' => $m->info['version'],
|
||||
'state' => $storage
|
||||
);
|
||||
}
|
||||
}
|
||||
drush_print_table($rows, TRUE);
|
||||
if (version_compare(DRUSH_VERSION, '6.0', '<')) {
|
||||
drush_print_table($rows, TRUE);
|
||||
}
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -192,6 +257,7 @@ function drush_features_list() {
|
||||
function drush_features_components() {
|
||||
$args = func_get_args();
|
||||
$components = _drush_features_component_list();
|
||||
ksort($components);
|
||||
// If no args supplied, prompt with a list.
|
||||
if (empty($args)) {
|
||||
$types = array_keys($components);
|
||||
@@ -212,10 +278,13 @@ function drush_features_components() {
|
||||
elseif (drush_get_option(array('not-exported', 'o'), NULL)) {
|
||||
$options['exported'] = FALSE;
|
||||
}
|
||||
if (drush_get_option(array('info-style', 'is'), NULL)) {
|
||||
$options['info style'] = TRUE;
|
||||
}
|
||||
|
||||
$filtered_components = _drush_features_component_filter($components, $args, $options);
|
||||
if ($filtered_components){
|
||||
_drush_features_component_print($filtered_components);
|
||||
_drush_features_component_print($filtered_components, $options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,7 +318,7 @@ function _drush_features_component_filter($all_components, $patterns = array(),
|
||||
// First filter on exported state.
|
||||
foreach ($all_components as $source => $components) {
|
||||
foreach ($components as $name => $title) {
|
||||
$exported = sizeof($components_map[$source][$name]) > 0;
|
||||
$exported = !empty($components_map[$source][$name]);
|
||||
if ($exported) {
|
||||
if ($options['exported']) {
|
||||
$pool[$source][$name] = $title;
|
||||
@@ -277,7 +346,13 @@ function _drush_features_component_filter($all_components, $patterns = array(),
|
||||
// Rewrite * to %. Let users use both as wildcard.
|
||||
$pattern = strtr($pattern, array('*' => '%'));
|
||||
$sources = array();
|
||||
list($source_pattern, $component_pattern) = explode(':', $pattern, 2);
|
||||
if (strpos($pattern, ':') !== FALSE) {
|
||||
list($source_pattern, $component_pattern) = explode(':', $pattern, 2);
|
||||
}
|
||||
else {
|
||||
$source_pattern = $pattern;
|
||||
$component_pattern = '';
|
||||
}
|
||||
// If source is empty, use a pattern.
|
||||
if ($source_pattern == '') {
|
||||
$source_pattern = '%';
|
||||
@@ -342,7 +417,7 @@ function _drush_features_component_filter($all_components, $patterns = array(),
|
||||
return drush_set_error('', dt('Ambiguous component "!component", matches !matches', array('!component' => $component_pattern, '!matches' => join(', ', $matches))));
|
||||
}
|
||||
}
|
||||
if (!is_array($selected[$source])) {
|
||||
if (empty($selected[$source])) {
|
||||
$selected[$source] = array();
|
||||
}
|
||||
$selected[$source] += array_intersect_key($pool[$source], array_flip($matches));
|
||||
@@ -363,7 +438,7 @@ function _drush_features_component_filter($all_components, $patterns = array(),
|
||||
if ($options['provided by'] && $options['exported'] ) {
|
||||
foreach ($selected as $source => $components) {
|
||||
foreach ($components as $name => $title) {
|
||||
$exported = sizeof($components_map[$source][$name]) > 0;
|
||||
$exported = !empty($components_map[$source][$name]);
|
||||
if ($exported) {
|
||||
$provided_by[$source . ':' . $name] = join(', ', $components_map[$source][$name]);
|
||||
}
|
||||
@@ -380,11 +455,17 @@ function _drush_features_component_filter($all_components, $patterns = array(),
|
||||
/**
|
||||
* Prints a list of filtered components.
|
||||
*/
|
||||
function _drush_features_component_print($filtered_components) {
|
||||
function _drush_features_component_print($filtered_components, $options = array()) {
|
||||
$rows = array(array(dt('Available sources')));
|
||||
foreach ($filtered_components['components'] as $source => $components) {
|
||||
foreach ($components as $name => $value) {
|
||||
$row = array($source .':'. $name);
|
||||
if (!empty($options['info style'])) {
|
||||
// Output as .info file style.
|
||||
$row = array('features[' . $source . '][] = "' . $name . '"');
|
||||
}
|
||||
else {
|
||||
$row = array($source .':'. $name);
|
||||
}
|
||||
if (isset($filtered_components['sources'][$source .':'. $name])) {
|
||||
$row[] = dt('Provided by') . ': ' . $filtered_components['sources'][$source .':'. $name];
|
||||
}
|
||||
@@ -406,9 +487,11 @@ function drush_features_export() {
|
||||
return drush_set_error('', 'No components supplied.');
|
||||
}
|
||||
$components = _drush_features_component_list();
|
||||
$options = array(
|
||||
'exported' => FALSE,
|
||||
);
|
||||
$options = array();
|
||||
|
||||
if (!drush_get_option('ignore-conflicts', FALSE)) {
|
||||
$options['exported'] = FALSE;
|
||||
}
|
||||
|
||||
$filtered_components = _drush_features_component_filter($components, $args, $options);
|
||||
$items = $filtered_components['components'];
|
||||
@@ -429,12 +512,15 @@ function drush_features_export() {
|
||||
}
|
||||
else {
|
||||
// Same logic as in _drush_features_export. Should be refactored.
|
||||
$destination = drush_get_option(array('destination'), 'sites/all/modules');
|
||||
$destination = drush_get_option(array('destination'), variable_get('features_default_export_path', FEATURES_DEFAULT_EXPORT_PATH));
|
||||
$directory = isset($directory) ? $directory : $destination . '/' . $module;
|
||||
drush_print(dt('Will create a new module in !dir', array('!dir' => $directory)));
|
||||
if (!drush_confirm(dt('Do you really want to continue?'))) {
|
||||
drush_die('Aborting.');
|
||||
}
|
||||
$export = _drush_features_generate_export($items, $module);
|
||||
_features_populate($items, $export[info], $export[name]);
|
||||
_drush_features_export($export['info'], $module, $directory);
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -449,8 +535,8 @@ function drush_features_export() {
|
||||
* This is DEPRECATED, but keeping it around for a bit longer for user migration
|
||||
*/
|
||||
function drush_features_add() {
|
||||
drush_print(dt('features_add is DEPRECATED.'));
|
||||
drush_print(dt('Calling features_export instead.'));
|
||||
drush_print(dt('features-add is DEPRECATED.'));
|
||||
drush_print(dt('Calling features-export instead.'));
|
||||
drush_features_export();
|
||||
}
|
||||
|
||||
@@ -464,7 +550,7 @@ function drush_features_update() {
|
||||
if (($feature = features_load_feature($module, TRUE)) && module_exists($module)) {
|
||||
_drush_features_export($feature->info, $feature->name, dirname($feature->filename));
|
||||
}
|
||||
else if ($feature) {
|
||||
elseif ($feature) {
|
||||
_features_drush_set_error($module, 'FEATURES_FEATURE_NOT_ENABLED');
|
||||
}
|
||||
else {
|
||||
@@ -494,15 +580,20 @@ function drush_features_update_all() {
|
||||
$features_to_update[] = $module->name;
|
||||
}
|
||||
}
|
||||
drush_print(dt('The following modules will be updated: !modules', array('!modules' => implode(', ', $features_to_update))));
|
||||
if (drush_confirm(dt('Do you really want to continue?'))) {
|
||||
foreach ($features_to_update as $module_name) {
|
||||
drush_invoke_process(drush_sitealias_get_record('@self'), 'features-update', array($module_name));
|
||||
}
|
||||
}
|
||||
else {
|
||||
drush_die('Aborting.');
|
||||
$dt_args = array('!modules' => implode(', ', $features_to_update));
|
||||
drush_print(dt('The following modules will be updated: !modules', $dt_args));
|
||||
if (!drush_confirm(dt('Do you really want to continue?'))) {
|
||||
return drush_user_abort('Aborting.');
|
||||
}
|
||||
// If we got here, set affirmative to TRUE, so that the user doesn't have to
|
||||
// confirm each and every feature. Start off by storing the current value,
|
||||
// so we can set it back afteward.
|
||||
$skip_confirmation = drush_get_context('DRUSH_AFFIRMATIVE');
|
||||
drush_set_context('DRUSH_AFFIRMATIVE', TRUE);
|
||||
// Now update all the features.
|
||||
drush_invoke('features-update', $features_to_update);
|
||||
// Now set it back as it was, in case other commands are called after this.
|
||||
drush_set_context('DRUSH_AFFIRMATIVE', $skip_confirmation);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -515,12 +606,14 @@ function drush_features_update_all() {
|
||||
*/
|
||||
function _drush_features_export($info, $module_name = NULL, $directory = NULL) {
|
||||
$root = drush_get_option(array('r', 'root'), drush_locate_root());
|
||||
$skip_confirmation = drush_get_context('DRUSH_AFFIRMATIVE');
|
||||
if ($root) {
|
||||
$destination = drush_get_option(array('destination'), 'sites/all/modules');
|
||||
$destination = drush_get_option(array('destination'), variable_get('features_default_export_path', FEATURES_DEFAULT_EXPORT_PATH));
|
||||
$directory = isset($directory) ? $directory : $destination . '/' . $module_name;
|
||||
if (is_dir($directory)) {
|
||||
drush_print(dt('Module appears to already exist in !dir', array('!dir' => $directory)));
|
||||
if (!drush_confirm(dt('Do you really want to continue?'))) {
|
||||
// If we aren't skipping confirmation and the directory already exists,
|
||||
// prompt the user. This message most make sense for but fe and fu.
|
||||
if (!$skip_confirmation && !drush_confirm(dt('Module located at !dir will be updated. Do you want to continue?', array('!dir' => $directory)))) {
|
||||
drush_die('Aborting.');
|
||||
}
|
||||
}
|
||||
@@ -528,36 +621,40 @@ function _drush_features_export($info, $module_name = NULL, $directory = NULL) {
|
||||
drush_op('mkdir', $directory);
|
||||
}
|
||||
if (is_dir($directory)) {
|
||||
// Ensure that the export will be created in the English language.
|
||||
// The export language must be set before flushing caches as that can
|
||||
// result into translatables being statically cached.
|
||||
$language = _features_export_language();
|
||||
|
||||
drupal_flush_all_caches();
|
||||
module_load_include('inc', 'features', 'features.export');
|
||||
$export = features_populate($info, $module_name);
|
||||
if (!features_load_feature($module_name)) {
|
||||
$export['name'] = $module_name;
|
||||
}
|
||||
// Set the feature version if the --version-set or --version-increment option is passed.
|
||||
if ($version = drush_get_option(array('version-set'))) {
|
||||
preg_match('/^(?P<core>\d+\.x)-(?P<major>\d+)\.(?P<patch>\d+)-?(?P<extra>\w+)?$/', $version, $matches);
|
||||
if (!isset($matches['core'], $matches['major'])) {
|
||||
drush_die(dt('Please enter a valid version with core and major version number. Example: !example', array('!example' => '7.x-1.0')));
|
||||
}
|
||||
$export['version'] = $version;
|
||||
}
|
||||
else if ($version = drush_get_option(array('version-increment'))) {
|
||||
// Determine current version and increment it.
|
||||
$export_load = features_export_prepare($export, $module_name);
|
||||
$version = $export_load['version'];
|
||||
$version_explode = explode('.', $version);
|
||||
$version_minor = array_pop($version_explode);
|
||||
// Increment minor version number if numeric or not a dev release.
|
||||
if (is_numeric($version_minor) || strpos($version_minor, 'dev') !== (strlen($version_minor) - 3)) {
|
||||
++$version_minor;
|
||||
}
|
||||
array_push($version_explode, $version_minor);
|
||||
// Rebuild version string.
|
||||
$version = implode('.', $version_explode);
|
||||
$export['version'] = $version;
|
||||
}
|
||||
$export = _drush_features_generate_export($info, $module_name);
|
||||
$files = features_export_render($export, $module_name, TRUE);
|
||||
|
||||
// Restore the language
|
||||
_features_export_language($language);
|
||||
|
||||
// Copy any files if _files key is there.
|
||||
if (!empty($files['_files'])) {
|
||||
foreach ($files['_files'] as $file_name => $file_info) {
|
||||
// See if files are in a sub directory.
|
||||
if (strpos($file_name, '/')) {
|
||||
$file_directory = $directory . '/' . substr($file_name, 0, strrpos($file_name, '/'));
|
||||
if (!is_dir($file_directory)) {
|
||||
drush_op('mkdir', $file_directory);
|
||||
}
|
||||
}
|
||||
if (!empty($file_info['file_path'])) {
|
||||
drush_op('file_unmanaged_copy', $file_info['file_path'], "{$directory}/{$file_name}", FILE_EXISTS_REPLACE);
|
||||
}
|
||||
elseif (!empty($file_info['file_content'])) {
|
||||
drush_op('file_put_contents', "{$directory}/{$file_name}", $file_info['file_content']);
|
||||
}
|
||||
else {
|
||||
drush_log(dt("Entry for @file_name.in !module is invalid. ", array('!module' => $module_name, '@file_name' => $file_name)), 'ok');
|
||||
}
|
||||
}
|
||||
unset($files['_files']);
|
||||
}
|
||||
foreach ($files as $extension => $file_contents) {
|
||||
if (!in_array($extension, array('module', 'info'))) {
|
||||
$extension .= '.inc';
|
||||
@@ -575,6 +672,59 @@ function _drush_features_export($info, $module_name = NULL, $directory = NULL) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for _drush_feature_export.
|
||||
*
|
||||
* @param $info
|
||||
* The feature info associative array.
|
||||
* @param $module_name
|
||||
* Optional. The name for the exported module.
|
||||
*/
|
||||
function _drush_features_generate_export(&$info, &$module_name) {
|
||||
module_load_include('inc', 'features', 'features.export');
|
||||
$export = features_populate($info, $module_name);
|
||||
if (!features_load_feature($module_name)) {
|
||||
$export['name'] = $module_name;
|
||||
}
|
||||
// Set the feature version if the --version-set or --version-increment option is passed.
|
||||
if ($version = drush_get_option(array('version-set'))) {
|
||||
preg_match('/^(?P<core>\d+\.x)-(?P<major>\d+)\.(?P<patch>\d+)-?(?P<extra>\w+)?$/', $version, $matches);
|
||||
if (!isset($matches['core'], $matches['major'])) {
|
||||
drush_die(dt('Please enter a valid version with core and major version number. Example: !example', array('!example' => '7.x-1.0')));
|
||||
}
|
||||
$export['version'] = $version;
|
||||
}
|
||||
elseif ($version = drush_get_option(array('version-increment'))) {
|
||||
// Determine current version and increment it.
|
||||
$export_load = features_export_prepare($export, $module_name);
|
||||
$version = $export_load['version'];
|
||||
$version_explode = explode('.', $version);
|
||||
$version_minor = array_pop($version_explode);
|
||||
// Increment minor version number if numeric or not a dev release.
|
||||
if (is_numeric($version_minor) || strpos($version_minor, 'dev') !== (strlen($version_minor) - 3)) {
|
||||
// Check for prefixed versions (alpha, beta, rc).
|
||||
if (ctype_digit($version_minor)) {
|
||||
++$version_minor;
|
||||
}
|
||||
else {
|
||||
// Split version number parts.
|
||||
$pattern = '/([0-9]-[a-z]+([0-9]+))/';
|
||||
$matches = array();
|
||||
preg_match($pattern, $version_minor, $matches);
|
||||
$number = array_pop($matches);
|
||||
++$number;
|
||||
$pattern = '/[0-9]+$/';
|
||||
$version_minor = preg_replace($pattern, $number, $version_minor);
|
||||
}
|
||||
}
|
||||
array_push($version_explode, $version_minor);
|
||||
// Rebuild version string.
|
||||
$version = implode('.', $version_explode);
|
||||
$export['version'] = $version;
|
||||
}
|
||||
return $export;
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert a feature to it's code definition.
|
||||
* Optionally accept a list of components to revert.
|
||||
@@ -586,11 +736,16 @@ function drush_features_revert() {
|
||||
|
||||
// Determine if revert should be forced.
|
||||
$force = drush_get_option('force');
|
||||
// Determine if -y was supplied. If so, we can filter out needless output
|
||||
// from this command.
|
||||
$skip_confirmation = drush_get_context('DRUSH_AFFIRMATIVE');
|
||||
|
||||
// Parse list of arguments.
|
||||
$modules = array();
|
||||
foreach ($args as $arg) {
|
||||
list($module, $component) = explode('.', $arg);
|
||||
$arg = explode('.', $arg);
|
||||
$module = array_shift($arg);
|
||||
$component = array_shift($arg);
|
||||
|
||||
if (isset($module)) {
|
||||
if (empty($component)) {
|
||||
@@ -608,6 +763,7 @@ function drush_features_revert() {
|
||||
|
||||
// Process modules.
|
||||
foreach ($modules as $module => $components_needed) {
|
||||
$dt_args['@module'] = $module;
|
||||
if (($feature = features_load_feature($module, TRUE)) && module_exists($module)) {
|
||||
|
||||
$components = array();
|
||||
@@ -623,7 +779,8 @@ function drush_features_revert() {
|
||||
else {
|
||||
$states = features_get_component_states(array($feature->name), FALSE);
|
||||
foreach ($states[$feature->name] as $component => $state) {
|
||||
if (in_array($state, array(FEATURES_OVERRIDDEN, FEATURES_NEEDS_REVIEW, FEATURES_REBUILDABLE)) && features_hook($component, 'features_revert')) {
|
||||
$revertable_states = array(FEATURES_OVERRIDDEN, FEATURES_NEEDS_REVIEW, FEATURES_REBUILDABLE);
|
||||
if (in_array($state, $revertable_states) && features_hook($component, 'features_revert')) {
|
||||
$components[] = $component;
|
||||
}
|
||||
}
|
||||
@@ -637,17 +794,24 @@ function drush_features_revert() {
|
||||
}
|
||||
else {
|
||||
foreach ($components as $component) {
|
||||
if (drush_confirm(dt('Do you really want to revert @component?', array('@component' => $component)))) {
|
||||
features_revert(array($module => array($component)));
|
||||
drush_log(dt('Reverted @component.', array('@component' => $component)), 'ok');
|
||||
$dt_args['@component'] = $component;
|
||||
$confirmation_message = 'Do you really want to revert @module.@component?';
|
||||
if ($skip_confirmation || drush_confirm(dt($confirmation_message, $dt_args))) {
|
||||
if (features_feature_is_locked($module, $component)) {
|
||||
drush_log(dt('Skipping locked @module.@component.', $dt_args), 'ok');
|
||||
}
|
||||
else {
|
||||
features_revert(array($module => array($component)));
|
||||
drush_log(dt('Reverted @module.@component.', $dt_args), 'ok');
|
||||
}
|
||||
}
|
||||
else {
|
||||
drush_log(dt('Skipping @component.', array('@component' => $component)), 'ok');
|
||||
drush_log(dt('Skipping @module.@component.', $dt_args), 'ok');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ($feature) {
|
||||
elseif ($feature) {
|
||||
_features_drush_set_error($module, 'FEATURES_FEATURE_NOT_ENABLED');
|
||||
}
|
||||
else {
|
||||
@@ -656,7 +820,7 @@ function drush_features_revert() {
|
||||
}
|
||||
}
|
||||
else {
|
||||
drush_features_list();
|
||||
drush_print_table(drush_features_list());
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -692,15 +856,13 @@ function drush_features_revert_all() {
|
||||
}
|
||||
|
||||
if ($features_to_revert) {
|
||||
drush_print(dt('The following modules will be reverted: !modules', array('!modules' => implode(', ', $features_to_revert))));
|
||||
if (drush_confirm(dt('Do you really want to continue?'))) {
|
||||
foreach ($features_to_revert as $module) {
|
||||
drush_invoke_process(drush_sitealias_get_record('@self'), 'features-revert', array($module), array('force' => $force, '#integrate' => TRUE));
|
||||
}
|
||||
}
|
||||
else {
|
||||
$dt_args = array('!modules' => implode(', ', $features_to_revert));
|
||||
drush_print(dt('The following modules will be reverted: !modules', $dt_args));
|
||||
// Confirm that the user would like to continue. If not, simply abort.
|
||||
if (!drush_confirm(dt('Do you really want to continue?'))) {
|
||||
return drush_user_abort('Aborting.');
|
||||
}
|
||||
drush_invoke('features-revert', $features_to_revert);
|
||||
}
|
||||
else {
|
||||
drush_log(dt('Current state already matches defaults, aborting.'), 'ok');
|
||||
@@ -712,10 +874,15 @@ function drush_features_revert_all() {
|
||||
*/
|
||||
function drush_features_diff() {
|
||||
if (!$args = func_get_args()) {
|
||||
drush_features_list();
|
||||
drush_print_table(drush_features_list());
|
||||
return;
|
||||
}
|
||||
$module = $args[0];
|
||||
$filter_ctypes = drush_get_option("ctypes");
|
||||
if ($filter_ctypes) {
|
||||
$filter_ctypes = explode(',', $filter_ctypes);
|
||||
}
|
||||
|
||||
$feature = features_load_feature($module);
|
||||
if (!module_exists($module)) {
|
||||
drush_log(dt('No such feature is enabled: ' . $module), 'error');
|
||||
@@ -733,11 +900,11 @@ function drush_features_diff() {
|
||||
if (drush_confirm(dt('It seems that the Diff module is not available. Would you like to download and enable it?'))) {
|
||||
// Download it if it's not already here.
|
||||
$project_info = drush_get_projects();
|
||||
if (empty($project_info['diff']) && !drush_invoke_process(drush_sitealias_get_record('@self'), 'dl', array('diff'), array('#integrate' => TRUE))) {
|
||||
if (empty($project_info['diff']) && !drush_invoke('dl', array('diff'))) {
|
||||
return drush_set_error(dt('Diff module could not be downloaded.'));
|
||||
}
|
||||
|
||||
if (!drush_invoke_process(drush_sitealias_get_record('@self'), 'en', array('diff'), array('#integrate' => TRUE))) {
|
||||
if (!drush_invoke('en', array('diff'))) {
|
||||
return drush_set_error(dt('Diff module could not be enabled.'));
|
||||
}
|
||||
}
|
||||
@@ -748,9 +915,12 @@ function drush_features_diff() {
|
||||
module_load_include('inc', 'diff', 'diff.engine');
|
||||
}
|
||||
|
||||
$lines = (int) drush_get_option('lines');
|
||||
$lines = $lines > 0 ? $lines : 2;
|
||||
|
||||
$formatter = new DiffFormatter();
|
||||
$formatter->leading_context_lines = 2;
|
||||
$formatter->trailing_context_lines = 2;
|
||||
$formatter->leading_context_lines = $lines;
|
||||
$formatter->trailing_context_lines = $lines;
|
||||
$formatter->show_header = FALSE;
|
||||
|
||||
if (drush_get_context('DRUSH_NOCOLOR')) {
|
||||
@@ -767,10 +937,13 @@ function drush_features_diff() {
|
||||
drush_print(sprintf($green, dt('Overrides: drush features-update will update the exported feature with the displayed overrides')));
|
||||
drush_print();
|
||||
|
||||
if ($filter_ctypes) {
|
||||
$overrides = array_intersect_key($overrides, array_flip($filter_ctypes));
|
||||
}
|
||||
foreach ($overrides as $component => $items) {
|
||||
$diff = new Diff(explode("\n", $items['default']), explode("\n", $items['normal']));
|
||||
drush_print();
|
||||
drush_print(dt("Component: !component", array('!component' => $component)));
|
||||
drush_print(dt("Component type: !component", array('!component' => $component)));
|
||||
$rows = explode("\n", $formatter->format($diff));
|
||||
foreach ($rows as $row) {
|
||||
if (strpos($row, '>') === 0) {
|
||||
@@ -786,6 +959,62 @@ function drush_features_diff() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Diff all enabled features that are not in their default state.
|
||||
*
|
||||
* @param ...
|
||||
* (Optional) A list of features to exclude from being reverted.
|
||||
*/
|
||||
function drush_features_diff_all() {
|
||||
module_load_include('inc', 'features', 'features.export');
|
||||
$features_to_exclude = func_get_args();
|
||||
|
||||
$features_to_revert = array();
|
||||
foreach (features_get_features(NULL, TRUE) as $module) {
|
||||
if ($module->status && !in_array($module->name, $features_to_exclude)) {
|
||||
switch (features_get_storage($module->name)) {
|
||||
case FEATURES_OVERRIDDEN:
|
||||
case FEATURES_NEEDS_REVIEW:
|
||||
case FEATURES_REBUILDABLE:
|
||||
$features_to_diff[] = $module->name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($features_to_diff) {
|
||||
|
||||
// Check if the user wants to apply the force option.
|
||||
$force = drush_get_option('force');
|
||||
|
||||
if($force) {
|
||||
foreach ($features_to_diff as $module) {
|
||||
|
||||
drush_print(dt('Diff for !module:', array('!module' => $module)));
|
||||
|
||||
drush_invoke_process(drush_sitealias_get_record('@self'), 'features-diff', array($module));
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
drush_print(dt('A diff will be performed for the following modules: !modules',
|
||||
array('!modules' => implode(', ', $features_to_diff))
|
||||
));
|
||||
|
||||
if (drush_confirm(dt('Do you want to continue?'))) {
|
||||
foreach ($features_to_diff as $module) {
|
||||
if (drush_confirm(dt('Diff !module?', array('!module' => $module)))) {
|
||||
drush_invoke_process(drush_sitealias_get_record('@self'), 'features-diff', array($module));
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
return drush_user_abort('Aborting.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to call drush_set_error().
|
||||
*
|
||||
|
@@ -45,11 +45,14 @@ function features_populate($info, $module_name) {
|
||||
* @return fully populated $export array.
|
||||
*/
|
||||
function _features_populate($pipe, &$export, $module_name = '', $reset = FALSE) {
|
||||
static $processed = array();
|
||||
features_include();
|
||||
// Ensure that the export will be created in the english language.
|
||||
$language = _features_export_language();
|
||||
|
||||
if ($reset) {
|
||||
$processed = array();
|
||||
drupal_static_reset(__FUNCTION__);
|
||||
}
|
||||
$processed = &drupal_static(__FUNCTION__, array());
|
||||
features_include();
|
||||
foreach ($pipe as $component => $data) {
|
||||
// Convert already defined items to dependencies.
|
||||
// _features_resolve_dependencies($data, $export, $module_name, $component);
|
||||
@@ -89,6 +92,7 @@ function _features_populate($pipe, &$export, $module_name = '', $reset = FALSE)
|
||||
}
|
||||
}
|
||||
}
|
||||
_features_export_language($language);
|
||||
return $export;
|
||||
}
|
||||
|
||||
@@ -170,8 +174,15 @@ 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) {
|
||||
function features_export_prepare($export, $module_name, $reset = FALSE, $add_deprecated = TRUE) {
|
||||
$existing = features_get_modules($module_name, $reset);
|
||||
|
||||
// copy certain exports directly into info
|
||||
@@ -183,12 +194,19 @@ function features_export_prepare($export, $module_name, $reset = FALSE) {
|
||||
}
|
||||
|
||||
// Prepare info string -- if module exists, merge into its existing info file
|
||||
$defaults = $existing ? $existing->info : array('core' => '7.x', 'package' => 'Features');
|
||||
$defaults = !empty($existing->info) ? $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 || !$add_deprecated) && !empty($deprecated[$component])) {
|
||||
unset($export['features'][$component]);
|
||||
}
|
||||
else {
|
||||
$export['features'][$component] = array_keys($data);
|
||||
}
|
||||
}
|
||||
if (isset($export['dependencies'])) {
|
||||
$export['dependencies'] = array_values($export['dependencies']);
|
||||
@@ -199,13 +217,17 @@ function features_export_prepare($export, $module_name, $reset = FALSE) {
|
||||
|
||||
// Order info array.
|
||||
$standard_info = array();
|
||||
foreach (array_merge(array('name', 'description', 'core', 'package', 'php', 'version', 'project', 'dependencies'), $copy_list) as $item) {
|
||||
foreach (array_merge(array('name', 'description', 'core', 'package', 'version', 'project', 'dependencies'), $copy_list) as $item) {
|
||||
if (isset($export[$item])) {
|
||||
$standard_info[$item] = $export[$item];
|
||||
}
|
||||
}
|
||||
if (isset($export['php']) && ($export['php'] != DRUPAL_MINIMUM_PHP)) {
|
||||
$standard_info['php'] = $export['php'];
|
||||
}
|
||||
unset($export['php']);
|
||||
|
||||
$export = array_diff_assoc($export, $standard_info);
|
||||
$export = features_array_diff_assoc_recursive($export, $standard_info);
|
||||
ksort($export);
|
||||
return array_merge($standard_info, $export);
|
||||
}
|
||||
@@ -252,9 +274,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']) {
|
||||
@@ -272,6 +299,11 @@ function features_export_render($export, $module_name, $reset = FALSE) {
|
||||
}
|
||||
|
||||
foreach ($hooks as $hook_name => $hook_info) {
|
||||
// These are purely files that will be copied over.
|
||||
if (is_array($hook_info) && (!empty($hook_info['file_path']) || !empty($hook_info['file_content']))) {
|
||||
$code['_files'][$hook_name] = $hook_info;
|
||||
continue;
|
||||
}
|
||||
$hook_code = is_array($hook_info) ? $hook_info['code'] : $hook_info;
|
||||
$hook_args = is_array($hook_info) && !empty($hook_info['args']) ? $hook_info['args'] : '';
|
||||
$hook_file = is_array($hook_info) && !empty($hook_info['file']) ? $hook_info['file'] : $file['name'];
|
||||
@@ -282,7 +314,17 @@ function features_export_render($export, $module_name, $reset = FALSE) {
|
||||
// Finalize strings to be written to files
|
||||
$code = array_filter($code);
|
||||
foreach ($code as $filename => $contents) {
|
||||
$code[$filename] = "<?php\n/**\n * @file\n * {$module_name}.{$filename}.inc\n */\n\n". implode("\n\n", $contents) ."\n";
|
||||
if ($filename != '_files') {
|
||||
$code[$filename] = "<?php\n/**\n * @file\n * {$module_name}.{$filename}.inc\n */\n\n". implode("\n\n", $contents) ."\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Allow extra files be added to feature.
|
||||
if ($files = module_invoke_all('features_export_files', $module_name, $export)) {
|
||||
$code['_files'] = !empty($code['_files']) ? $code['_files'] + $files : $files;
|
||||
}
|
||||
if (!empty($code['_files'])) {
|
||||
drupal_alter('features_export_files', $code['_files'], $module_name, $export);
|
||||
}
|
||||
|
||||
// Generate info file output
|
||||
@@ -312,28 +354,38 @@ function features_export_render($export, $module_name, $reset = FALSE) {
|
||||
}
|
||||
}
|
||||
|
||||
// Deprecated files. Display a message for any of these files letting the
|
||||
// user know that they may be removed.
|
||||
$deprecated = array(
|
||||
"{$module_name}.defaults",
|
||||
"{$module_name}.features.views",
|
||||
"{$module_name}.features.node"
|
||||
);
|
||||
foreach (file_scan_directory(drupal_get_path('module', $module_name), '/.*/') as $file) {
|
||||
if (in_array($file->name, $deprecated, TRUE)) {
|
||||
features_log(t('The file @filename has been deprecated and can be removed.', array('@filename' => $file->filename)), 'status');
|
||||
if ($reset) {
|
||||
// only check for deprecated files on final export
|
||||
|
||||
// Deprecated files. Display a message for any of these files letting the
|
||||
// user know that they may be removed.
|
||||
$deprecated_files = array(
|
||||
"{$module_name}.defaults",
|
||||
"{$module_name}.features.views",
|
||||
"{$module_name}.features.node"
|
||||
);
|
||||
// add deprecated components
|
||||
foreach ($deprecated as $component) {
|
||||
$info = features_get_components($component);
|
||||
$filename = isset($info['default_file']) && $info['default_file'] == FEATURES_DEFAULTS_CUSTOM ? $info['default_filename'] : "features.{$component}";
|
||||
$deprecated_files[] = "{$module_name}.$filename";
|
||||
}
|
||||
elseif ($file->name === "{$module_name}.features" && empty($code['features'])) {
|
||||
// Try and remove features.inc include.
|
||||
if (strpos($code['module'], "{$module_name}.features.inc")) {
|
||||
$code['module'] = str_replace($modulefile_features_inc, $modulefile_blank, $code['module']);
|
||||
foreach (file_scan_directory(drupal_get_path('module', $module_name), '/.*/') as $file) {
|
||||
if (in_array($file->name, $deprecated_files, TRUE)) {
|
||||
features_log(t('The file @filename has been deprecated and can be removed.', array('@filename' => $file->filename)), 'status');
|
||||
}
|
||||
// If unable to remove the include, add a message to remove.
|
||||
if (strpos($code['module'], "{$module_name}.features.inc")) {
|
||||
$code['features'] = "<?php\n\n// This file is deprecated and can be removed.\n// Please remove include_once('{$module_name}.features.inc') in {$module_name}.module as well.\n";
|
||||
}
|
||||
else {
|
||||
$code['features'] = "<?php\n\n// This file is deprecated and can be removed.\n";
|
||||
elseif ($file->name === "{$module_name}.features" && empty($code['features'])) {
|
||||
// Try and remove features.inc include.
|
||||
if (strpos($code['module'], "{$module_name}.features.inc")) {
|
||||
$code['module'] = str_replace($modulefile_features_inc, $modulefile_blank, $code['module']);
|
||||
}
|
||||
// If unable to remove the include, add a message to remove.
|
||||
if (strpos($code['module'], "{$module_name}.features.inc")) {
|
||||
$code['features'] = "<?php\n\n// This file is deprecated and can be removed.\n// Please remove include_once('{$module_name}.features.inc') in {$module_name}.module as well.\n";
|
||||
}
|
||||
else {
|
||||
$code['features'] = "<?php\n\n// This file is deprecated and can be removed.\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -352,20 +404,17 @@ function features_export_render($export, $module_name, $reset = FALSE) {
|
||||
* Detect differences between DB and code components of a feature.
|
||||
*/
|
||||
function features_detect_overrides($module) {
|
||||
static $cache;
|
||||
if (!isset($cache)) {
|
||||
$cache = array();
|
||||
}
|
||||
$cache = &drupal_static(__FUNCTION__, array());
|
||||
if (!isset($cache[$module->name])) {
|
||||
// Rebuild feature from .info file description and prepare an export from current DB state.
|
||||
$export = features_populate($module->info, $module->name);
|
||||
$export = features_export_prepare($export, $module->name);
|
||||
$export = features_export_prepare($export, $module->name, FALSE, FALSE);
|
||||
|
||||
$overridden = array();
|
||||
|
||||
// Compare feature info
|
||||
_features_sanitize($module->info);
|
||||
_features_sanitize($export);
|
||||
features_sanitize($module->info);
|
||||
features_sanitize($export);
|
||||
|
||||
$compare = array('normal' => features_export_info($export), 'default' => features_export_info($module->info));
|
||||
if ($compare['normal'] !== $compare['default']) {
|
||||
@@ -378,8 +427,8 @@ function features_detect_overrides($module) {
|
||||
if ($state != FEATURES_DEFAULT) {
|
||||
$normal = features_get_normal($component, $module->name);
|
||||
$default = features_get_default($component, $module->name);
|
||||
_features_sanitize($normal);
|
||||
_features_sanitize($default);
|
||||
features_sanitize($normal, $component);
|
||||
features_sanitize($default, $component);
|
||||
|
||||
$compare = array('normal' => features_var_export($normal), 'default' => features_var_export($default));
|
||||
if (_features_linetrim($compare['normal']) !== _features_linetrim($compare['default'])) {
|
||||
@@ -521,9 +570,14 @@ function features_tar_create($name, $contents) {
|
||||
/**
|
||||
* Export var function -- from Views.
|
||||
*/
|
||||
function features_var_export($var, $prefix = '', $init = TRUE) {
|
||||
function features_var_export($var, $prefix = '', $init = TRUE, $count = 0) {
|
||||
if ($count > 50) {
|
||||
watchdog('features', 'Recursion depth reached in features_var_export', array());
|
||||
return '';
|
||||
}
|
||||
|
||||
if (is_object($var)) {
|
||||
$output = method_exists($var, 'export') ? $var->export() : features_var_export((array) $var, '', FALSE);
|
||||
$output = method_exists($var, 'export') ? $var->export() : features_var_export((array) $var, '', FALSE, $count+1);
|
||||
}
|
||||
else if (is_array($var)) {
|
||||
if (empty($var)) {
|
||||
@@ -533,7 +587,7 @@ function features_var_export($var, $prefix = '', $init = TRUE) {
|
||||
$output = "array(\n";
|
||||
foreach ($var as $key => $value) {
|
||||
// Using normal var_export on the key to ensure correct quoting.
|
||||
$output .= " " . var_export($key, TRUE) . " => " . features_var_export($value, ' ', FALSE) . ",\n";
|
||||
$output .= " " . var_export($key, TRUE) . " => " . features_var_export($value, ' ', FALSE, $count+1) . ",\n";
|
||||
}
|
||||
$output .= ')';
|
||||
}
|
||||
@@ -541,6 +595,21 @@ function features_var_export($var, $prefix = '', $init = TRUE) {
|
||||
else if (is_bool($var)) {
|
||||
$output = $var ? 'TRUE' : 'FALSE';
|
||||
}
|
||||
else if (is_int($var)) {
|
||||
$output = intval($var);
|
||||
}
|
||||
else if (is_numeric($var)) {
|
||||
$floatval = floatval($var);
|
||||
if (is_string($var) && ((string) $floatval !== $var)) {
|
||||
// Do not convert a string to a number if the string
|
||||
// representation of that number is not identical to the
|
||||
// original value.
|
||||
$output = var_export($var, TRUE);
|
||||
}
|
||||
else {
|
||||
$output = $floatval;
|
||||
}
|
||||
}
|
||||
else if (is_string($var) && strpos($var, "\n") !== FALSE) {
|
||||
// Replace line breaks in strings with a token for replacement
|
||||
// at the very end. This protects whitespace in strings from
|
||||
@@ -613,8 +682,7 @@ function features_get_signature($state = 'default', $module_name, $component, $r
|
||||
break;
|
||||
}
|
||||
if (!empty($objects)) {
|
||||
$objects = (array) $objects;
|
||||
_features_sanitize($objects);
|
||||
features_sanitize($objects, $component);
|
||||
return md5(_features_linetrim(features_var_export($objects)));
|
||||
}
|
||||
return FALSE;
|
||||
@@ -660,10 +728,10 @@ function features_semaphore($op, $component) {
|
||||
* Get normal objects for a given module/component pair.
|
||||
*/
|
||||
function features_get_normal($component, $module_name, $reset = FALSE) {
|
||||
static $cache;
|
||||
if (!isset($cache) || $reset) {
|
||||
$cache = array();
|
||||
if ($reset) {
|
||||
drupal_static_reset(__FUNCTION__);
|
||||
}
|
||||
$cache = &drupal_static(__FUNCTION__, array());
|
||||
if (!isset($cache[$module_name][$component])) {
|
||||
features_include();
|
||||
$code = NULL;
|
||||
@@ -671,7 +739,7 @@ function features_get_normal($component, $module_name, $reset = FALSE) {
|
||||
|
||||
// Special handling for dependencies component.
|
||||
if ($component === 'dependencies') {
|
||||
$cache[$module_name][$component] = isset($module->info['dependencies']) ? array_filter($module->info['dependencies'], 'module_exists') : array();
|
||||
$cache[$module_name][$component] = isset($module->info['dependencies']) ? array_filter($module->info['dependencies'], '_features_module_exists') : array();
|
||||
}
|
||||
// All other components.
|
||||
else {
|
||||
@@ -689,11 +757,22 @@ function features_get_normal($component, $module_name, $reset = FALSE) {
|
||||
return isset($cache[$module_name][$component]) ? $cache[$module_name][$component] : FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to determine if a module is enabled
|
||||
* @param $module
|
||||
* This module name comes from the .info file and can have version info in it.
|
||||
*/
|
||||
function _features_module_exists($module) {
|
||||
$parsed_dependency = drupal_parse_dependency($module);
|
||||
$name = $parsed_dependency['name'];
|
||||
return module_exists($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get defaults for a given module/component pair.
|
||||
*/
|
||||
function features_get_default($component, $module_name = NULL, $alter = TRUE, $reset = FALSE) {
|
||||
static $cache = array();
|
||||
$cache = &drupal_static(__FUNCTION__, array());
|
||||
$alter = !empty($alter); // ensure $alter is a true/false boolean
|
||||
features_include();
|
||||
features_include_defaults($component);
|
||||
@@ -765,9 +844,16 @@ function features_get_default($component, $module_name = NULL, $alter = TRUE, $r
|
||||
|
||||
/**
|
||||
* Get a map of components to their providing modules.
|
||||
*
|
||||
* @param string $component
|
||||
* @param string $attribute
|
||||
* @param callable $callback
|
||||
* @param bool $reset
|
||||
*
|
||||
* @return array|bool
|
||||
*/
|
||||
function features_get_default_map($component, $attribute = NULL, $callback = NULL, $reset = FALSE) {
|
||||
static $map = array();
|
||||
$map = &drupal_static(__FUNCTION__, array());
|
||||
|
||||
global $features_ignore_conflicts;
|
||||
if ($features_ignore_conflicts) {
|
||||
@@ -813,16 +899,20 @@ function features_get_default_map($component, $attribute = NULL, $callback = NUL
|
||||
* Retrieve an array of features/components and their current states.
|
||||
*/
|
||||
function features_get_component_states($features = array(), $rebuild_only = TRUE, $reset = FALSE) {
|
||||
static $cache;
|
||||
if (!isset($cache) || $reset) {
|
||||
$cache = array();
|
||||
}
|
||||
// Ensure that the export will be created in the English language.
|
||||
$language = _features_export_language();
|
||||
|
||||
$features = !empty($features) ? $features : array_keys(features_get_features());
|
||||
if ($reset) {
|
||||
drupal_static_reset(__FUNCTION__);
|
||||
}
|
||||
$cache = &drupal_static(__FUNCTION__, array());
|
||||
|
||||
$all_features = features_get_features();
|
||||
$features = !empty($features) ? $features : array_keys($all_features);
|
||||
|
||||
// Retrieve only rebuildable components if requested.
|
||||
features_include();
|
||||
$components = array_keys(features_get_components());
|
||||
$components = array_keys(features_get_components(NULL, NULL, $reset));
|
||||
if ($rebuild_only) {
|
||||
foreach ($components as $k => $component) {
|
||||
if (!features_hook($component, 'features_rebuild')) {
|
||||
@@ -833,8 +923,8 @@ function features_get_component_states($features = array(), $rebuild_only = TRUE
|
||||
|
||||
foreach ($features as $feature) {
|
||||
$cache[$feature] = isset($cache[$feature]) ? $cache[$feature] : array();
|
||||
if (module_exists($feature)) {
|
||||
foreach ($components as $component) {
|
||||
if (module_exists($feature) && !empty($all_features[$feature]->components)) {
|
||||
foreach (array_intersect($all_features[$feature]->components, $components) as $component) {
|
||||
if (!isset($cache[$feature][$component])) {
|
||||
$normal = features_get_signature('normal', $feature, $component, $reset);
|
||||
$default = features_get_signature('default', $feature, $component, $reset);
|
||||
@@ -905,6 +995,9 @@ function features_get_component_states($features = array(), $rebuild_only = TRUE
|
||||
foreach ($return as $k => $v) {
|
||||
$return[$k] = array_intersect_key($return[$k], array_flip($components));
|
||||
}
|
||||
|
||||
_features_export_language($language);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
@@ -919,23 +1012,51 @@ function _features_linetrim($code) {
|
||||
return implode("\n", $code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to "sanitize" an array or object.
|
||||
* Converts everything to an array, sorts the keys, removes recursion.
|
||||
* @param $array
|
||||
* @param $component string name of component
|
||||
* @param bool $remove_empty if set, remove null or empty values for assoc arrays.
|
||||
*/
|
||||
function features_sanitize(&$array, $component = NULL, $remove_empty = TRUE) {
|
||||
$array = features_remove_recursion($array);
|
||||
if (isset($component)) {
|
||||
$ignore_keys = _features_get_ignore_keys($component);
|
||||
// remove keys to be ignored
|
||||
if (count($ignore_keys)) {
|
||||
_features_remove_ignores($array, $ignore_keys);
|
||||
}
|
||||
}
|
||||
_features_sanitize($array, $remove_empty);
|
||||
}
|
||||
|
||||
/**
|
||||
* "Sanitizes" an array recursively, performing two key operations:
|
||||
* - Sort an array by its keys (assoc) or values (non-assoc)
|
||||
* - Remove any null or empty values for associative arrays (array_filter()).
|
||||
* @param bool $remove_empty if set, remove null or empty values for assoc arrays.
|
||||
*/
|
||||
function _features_sanitize(&$array) {
|
||||
function _features_sanitize(&$array, $remove_empty = TRUE) {
|
||||
if (is_object($array)) {
|
||||
$array = get_object_vars($array);
|
||||
}
|
||||
if (is_array($array)) {
|
||||
if (_features_is_assoc($array)) {
|
||||
$is_assoc = _features_is_assoc($array);
|
||||
if ($is_assoc) {
|
||||
ksort($array, SORT_STRING);
|
||||
$array = array_filter($array);
|
||||
if ($remove_empty) {
|
||||
$array = array_filter($array);
|
||||
}
|
||||
}
|
||||
else {
|
||||
sort($array);
|
||||
}
|
||||
foreach ($array as $k => $v) {
|
||||
if (is_array($v)) {
|
||||
if (is_array($v) or is_object($v)) {
|
||||
_features_sanitize($array[$k]);
|
||||
if ($remove_empty && $is_assoc && empty($array[$k])) {
|
||||
unset($array[$k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -955,3 +1076,92 @@ function _features_sanitize(&$array) {
|
||||
function _features_is_assoc($array) {
|
||||
return (is_array($array) && (0 !== count(array_diff_key($array, array_keys(array_keys($array)))) || count($array)==0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes recursion from an object or array.
|
||||
*
|
||||
* Taken from https://code.google.com/p/formaldehyde/source/browse/trunk/formaldehyde.php
|
||||
* Also used in node_export module
|
||||
*
|
||||
* @param $o mixed
|
||||
* @return mixed
|
||||
* returns a copy of the object or array with recursion removed
|
||||
*/
|
||||
function features_remove_recursion($o) {
|
||||
static $replace;
|
||||
if (!isset($replace)) {
|
||||
$replace = create_function(
|
||||
'$m',
|
||||
'$r="\x00{$m[1]}ecursion_features";return \'s:\'.strlen($r.$m[2]).\':"\'.$r.$m[2].\'";\';'
|
||||
);
|
||||
}
|
||||
if (is_array($o) || is_object($o)) {
|
||||
$re = '#(r|R):([0-9]+);#';
|
||||
$serialize = serialize($o);
|
||||
if (preg_match($re, $serialize)) {
|
||||
$last = $pos = 0;
|
||||
while (false !== ($pos = strpos($serialize, 's:', $pos))) {
|
||||
$chunk = substr($serialize, $last, $pos - $last);
|
||||
if (preg_match($re, $chunk)) {
|
||||
$length = strlen($chunk);
|
||||
$chunk = preg_replace_callback($re, $replace, $chunk);
|
||||
$serialize = substr($serialize, 0, $last) . $chunk . substr($serialize, $last + ($pos - $last));
|
||||
$pos += strlen($chunk) - $length;
|
||||
}
|
||||
$pos += 2;
|
||||
$last = strpos($serialize, ':', $pos);
|
||||
$length = substr($serialize, $pos, $last - $pos);
|
||||
$last += 4 + $length;
|
||||
$pos = $last;
|
||||
}
|
||||
$serialize = substr($serialize, 0, $last) . preg_replace_callback($re, $replace, substr($serialize, $last));
|
||||
$o = unserialize($serialize);
|
||||
}
|
||||
}
|
||||
return $o;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to removes a set of keys an object/array.
|
||||
*
|
||||
* @param $item
|
||||
* An object or array passed by reference.
|
||||
* @param $ignore_keys
|
||||
* Array of keys to be ignored. Values are the level of the key.
|
||||
* @param $level
|
||||
* Level of key to remove. Up to 2 levels deep because $item can still be
|
||||
* recursive
|
||||
*/
|
||||
function _features_remove_ignores(&$item, $ignore_keys, $level = -1) {
|
||||
$is_object = is_object($item);
|
||||
if (!is_array($item) && !is_object($item)) {
|
||||
return;
|
||||
}
|
||||
foreach ($item as $key => &$value) {
|
||||
if (isset($ignore_keys[$key]) && ($ignore_keys[$key] == $level)) {
|
||||
if ($is_object) {
|
||||
unset($item->$key);
|
||||
}
|
||||
else {
|
||||
unset($item[$key]);
|
||||
}
|
||||
}
|
||||
elseif (($level < 2) && (is_array($value) || is_object($value))) {
|
||||
_features_remove_ignores($value, $ignore_keys, $level+1);
|
||||
}
|
||||
}
|
||||
unset($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of keys to be ignored for various exportables
|
||||
* @param $component
|
||||
* The component to retrieve ignore_keys from.
|
||||
*/
|
||||
function _features_get_ignore_keys($component) {
|
||||
static $cache;
|
||||
if (!isset($cache[$component])) {
|
||||
$cache[$component] = module_invoke_all('features_ignore', $component);
|
||||
}
|
||||
return $cache[$component];
|
||||
}
|
||||
|
@@ -3,10 +3,16 @@ description = "Provides feature management for Drupal."
|
||||
core = 7.x
|
||||
package = "Features"
|
||||
files[] = tests/features.test
|
||||
test_dependencies[] = image
|
||||
test_dependencies[] = strongarm
|
||||
test_dependencies[] = taxonomy
|
||||
test_dependencies[] = views
|
||||
|
||||
; Information added by drupal.org packaging script on 2012-12-08
|
||||
version = "7.x-2.0-beta1+5-dev"
|
||||
configure = admin/structure/features/settings
|
||||
|
||||
; Information added by Drupal.org packaging script on 2016-04-18
|
||||
version = "7.x-2.10"
|
||||
core = "7.x"
|
||||
project = "features"
|
||||
datestamp = "1354928180"
|
||||
datestamp = "1461011641"
|
||||
|
||||
|
@@ -5,6 +5,15 @@
|
||||
* Install, update and uninstall functions for the features module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_schema().
|
||||
*/
|
||||
function features_schema() {
|
||||
$schema['cache_features'] = drupal_get_schema_unprocessed('system', 'cache');
|
||||
$schema['cache_features']['description'] = 'Cache table for features to store module info.';
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_install().
|
||||
*/
|
||||
@@ -22,11 +31,24 @@ function features_install() {
|
||||
*/
|
||||
function features_uninstall() {
|
||||
variable_del('features_codecache');
|
||||
variable_del('features_default_export_path');
|
||||
variable_del('features_semaphore');
|
||||
variable_del('features_ignored_orphans');
|
||||
db_delete('menu_custom')
|
||||
->condition('menu_name', 'features')
|
||||
variable_del('features_feature_locked');
|
||||
variable_del('features_lock_mode');
|
||||
|
||||
db_delete('variable')
|
||||
->condition('name', 'features_admin_show_component_%', 'LIKE')
|
||||
->execute();
|
||||
db_delete('variable')
|
||||
->condition('name', 'features_component_locked_%', 'LIKE')
|
||||
->execute();
|
||||
|
||||
if (db_table_exists('menu_custom')) {
|
||||
db_delete('menu_custom')
|
||||
->condition('menu_name', 'features')
|
||||
->execute();
|
||||
}
|
||||
db_delete('menu_links')
|
||||
->condition('module', 'features')
|
||||
->execute();
|
||||
@@ -50,7 +72,7 @@ function _features_install_menu() {
|
||||
|
||||
/**
|
||||
* Update 6100: Set module on all feature node types to 'features'.
|
||||
|
||||
*
|
||||
* This update can be re-run as needed to repair any node types that are not
|
||||
* removed after disabling the associated feature.
|
||||
*
|
||||
@@ -117,3 +139,13 @@ function features_update_6101() {
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add {cache_features} table.
|
||||
*/
|
||||
function features_update_7200() {
|
||||
if (!db_table_exists('cache_features')) {
|
||||
$schema = drupal_get_schema_unprocessed('system', 'cache');
|
||||
db_create_table('cache_features', $schema);
|
||||
}
|
||||
}
|
||||
|
@@ -100,33 +100,6 @@ jQuery.fn.sortElements = (function(){
|
||||
}).trigger('change');
|
||||
});
|
||||
|
||||
// Export form machine-readable JS
|
||||
$('.feature-name:not(.processed)', context).each(function() {
|
||||
$('.feature-name')
|
||||
.addClass('processed')
|
||||
.after(' <small class="feature-module-name-suffix"> </small>');
|
||||
if ($('.feature-module-name').val() === $('.feature-name').val().toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/_+/g, '_') || $('.feature-module-name').val() === '') {
|
||||
$('.feature-module-name').parents('.form-item').hide();
|
||||
$('.feature-name').bind('keyup change', function() {
|
||||
var machine = $(this).val().toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/_+/g, '_');
|
||||
if (machine !== '_' && machine !== '') {
|
||||
$('.feature-module-name').val(machine);
|
||||
$('.feature-module-name-suffix').empty().append(' Machine name: ' + machine + ' [').append($('<a href="#">'+ Drupal.t('Edit') +'</a>').click(function() {
|
||||
$('.feature-module-name').parents('.form-item').show();
|
||||
$('.feature-module-name-suffix').hide();
|
||||
$('.feature-name').unbind('keyup');
|
||||
return false;
|
||||
})).append(']');
|
||||
}
|
||||
else {
|
||||
$('.feature-module-name').val(machine);
|
||||
$('.feature-module-name-suffix').text('');
|
||||
}
|
||||
});
|
||||
$('.feature-name').keyup();
|
||||
}
|
||||
});
|
||||
|
||||
//View info dialog
|
||||
var infoDialog = $('#features-info-file');
|
||||
if (infoDialog.length != 0) {
|
||||
@@ -155,10 +128,12 @@ jQuery.fn.sortElements = (function(){
|
||||
if (!$(this).hasClass('features-checkall')) {
|
||||
var key = $(this).attr('name');
|
||||
var matches = key.match(/^([^\[]+)(\[.+\])?\[(.+)\]\[(.+)\]$/);
|
||||
var component = matches[1];
|
||||
var item = matches[4];
|
||||
if ((component in moduleConflicts) && (moduleConflicts[component].indexOf(item) != -1)) {
|
||||
$(this).parent().addClass('features-conflict');
|
||||
if (matches != null) {
|
||||
var component = matches[1];
|
||||
var item = matches[4];
|
||||
if ((component in moduleConflicts) && (moduleConflicts[component].indexOf(item) != -1)) {
|
||||
$(this).parent().addClass('features-conflict');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -183,7 +158,26 @@ jQuery.fn.sortElements = (function(){
|
||||
}
|
||||
}
|
||||
|
||||
function updateComponentCountInfo(item, section) {
|
||||
switch (section) {
|
||||
case 'select':
|
||||
var parent = $(item).closest('.features-export-list').siblings('.features-export-component');
|
||||
$('.component-count', parent).text(function (index, text) {
|
||||
return +text + 1;
|
||||
}
|
||||
);
|
||||
break;
|
||||
case 'added':
|
||||
case 'detected':
|
||||
var parent = $(item).closest('.features-export-component');
|
||||
$('.component-count', parent).text(function (index, text) {
|
||||
return text - 1;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function moveCheckbox(item, section, value) {
|
||||
updateComponentCountInfo(item, section);
|
||||
var curParent = item;
|
||||
if ($(item).hasClass('form-type-checkbox')) {
|
||||
item = $(item).children('input[type=checkbox]');
|
||||
@@ -298,7 +292,7 @@ jQuery.fn.sortElements = (function(){
|
||||
}
|
||||
|
||||
// Handle component selection UI
|
||||
$('#features-export-wrapper input[type=checkbox]', context).click(function() {
|
||||
$('#features-export-wrapper input[type=checkbox]:not(.processed)', context).addClass('processed').click(function() {
|
||||
_resetTimeout();
|
||||
if ($(this).hasClass('component-select')) {
|
||||
moveCheckbox(this, 'added', true);
|
||||
@@ -353,30 +347,30 @@ jQuery.fn.sortElements = (function(){
|
||||
// collapse fieldsets
|
||||
var newState = {};
|
||||
var currentState = {};
|
||||
$('#features-export-wrapper .component-select label', context).each(function() {
|
||||
$('#features-export-wrapper fieldset.features-export-component', context).each(function() {
|
||||
// expand parent fieldset
|
||||
var section = '';
|
||||
$(this).parents('fieldset.features-export-component').each(function() {
|
||||
section = $(this).attr('id');
|
||||
currentState[section] = !($(this).hasClass('collapsed'));
|
||||
if (!(section in newState)) {
|
||||
newState[section] = false;
|
||||
var section = $(this).attr('id');
|
||||
currentState[section] = !($(this).hasClass('collapsed'));
|
||||
if (!(section in newState)) {
|
||||
newState[section] = false;
|
||||
}
|
||||
|
||||
$(this).find('div.component-select label').each(function() {
|
||||
if (filter == '') {
|
||||
if (currentState[section]) {
|
||||
Drupal.toggleFieldset($('#'+section));
|
||||
currentState[section] = false;
|
||||
}
|
||||
$(this).parent().show();
|
||||
}
|
||||
else if ($(this).text().match(regex)) {
|
||||
$(this).parent().show();
|
||||
newState[section] = true;
|
||||
}
|
||||
else {
|
||||
$(this).parent().hide();
|
||||
}
|
||||
});
|
||||
if (filter == '') {
|
||||
if (currentState[section]) {
|
||||
Drupal.toggleFieldset($('#'+section));
|
||||
currentState[section] = false;
|
||||
}
|
||||
$(this).parent().show();
|
||||
}
|
||||
else if ($(this).text().match(regex)) {
|
||||
$(this).parent().show();
|
||||
newState[section] = true;
|
||||
}
|
||||
else {
|
||||
$(this).parent().hide();
|
||||
}
|
||||
});
|
||||
for (section in newState) {
|
||||
if (currentState[section] != newState[section]) {
|
||||
|
@@ -7,7 +7,7 @@
|
||||
* entities which taken together satisfy a certain use-case.
|
||||
*/
|
||||
|
||||
define('FEATURES_API', '1');
|
||||
define('FEATURES_API', '2');
|
||||
|
||||
define('FEATURES_MODULE_ENABLED', 1);
|
||||
define('FEATURES_MODULE_DISABLED', 0);
|
||||
@@ -63,6 +63,11 @@ define('FEATURES_DUPLICATES_CONFLICT', 0);
|
||||
*/
|
||||
define('FEATURES_DUPLICATES_ALLOWED', 1);
|
||||
|
||||
/**
|
||||
* The default destination path for features exported via the UI.
|
||||
*/
|
||||
define('FEATURES_DEFAULT_EXPORT_PATH', 'sites/all/modules');
|
||||
|
||||
/**
|
||||
* Implements hook_menu().
|
||||
*/
|
||||
@@ -78,9 +83,8 @@ function features_menu() {
|
||||
);
|
||||
$items['admin/structure/features/cleanup'] = array(
|
||||
'title' => 'Cleanup',
|
||||
'description' => 'Detect and disable any orphaned feature dependencies.',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('features_cleanup_form', 4),
|
||||
'description' => 'Clear cache after enabling/disabling a feature.',
|
||||
'page callback' => 'features_cleanup',
|
||||
'type' => MENU_CALLBACK,
|
||||
'file' => 'features.admin.inc',
|
||||
'weight' => 1,
|
||||
@@ -104,6 +108,18 @@ function features_menu() {
|
||||
'file' => "features.admin.inc",
|
||||
'weight' => 10,
|
||||
);
|
||||
$items['admin/structure/features/settings'] = array(
|
||||
'title' => 'Settings',
|
||||
'description' => 'Adjust settings for using features module.',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('features_settings_form'),
|
||||
'access callback' => 'user_access',
|
||||
'access arguments' => array('administer features'),
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
'file' => "features.admin.inc",
|
||||
'weight' => 11,
|
||||
);
|
||||
|
||||
|
||||
$items['admin/structure/features/%feature'] = array(
|
||||
'title callback' => 'features_get_feature_title',
|
||||
@@ -111,7 +127,7 @@ function features_menu() {
|
||||
'description' => 'Display components of a feature.',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('features_admin_components', 3),
|
||||
'load arguments' => array(3, TRUE),
|
||||
'load arguments' => array(TRUE),
|
||||
'access callback' => 'user_access',
|
||||
'access arguments' => array('administer features'),
|
||||
'type' => MENU_CALLBACK,
|
||||
@@ -130,7 +146,7 @@ function features_menu() {
|
||||
'description' => 'Recreate an existing feature.',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('features_export_form', 3),
|
||||
'load arguments' => array(3, TRUE),
|
||||
'load arguments' => array(TRUE),
|
||||
'access callback' => 'user_access',
|
||||
'access arguments' => array('administer features'),
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
@@ -143,19 +159,30 @@ function features_menu() {
|
||||
'description' => 'Compare default and current feature.',
|
||||
'page callback' => 'features_feature_diff',
|
||||
'page arguments' => array(3, 5),
|
||||
'load arguments' => array(3, TRUE),
|
||||
'load arguments' => array(TRUE),
|
||||
'access callback' => 'features_access_override_actions',
|
||||
'access arguments' => array(3),
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
'file' => 'features.admin.inc',
|
||||
);
|
||||
}
|
||||
|
||||
$items['admin/structure/features/%feature/lock'] = array(
|
||||
'title' => 'Lock',
|
||||
'description' => 'Lock a feature or components.',
|
||||
'page callback' => 'features_admin_lock',
|
||||
'page arguments' => array(3, 5, 6),
|
||||
'load arguments' => array(TRUE),
|
||||
'access arguments' => array('administer features'),
|
||||
'type' => MENU_CALLBACK,
|
||||
'file' => 'features.admin.inc',
|
||||
);
|
||||
$items['admin/structure/features/%feature/status'] = array(
|
||||
'title' => 'Status',
|
||||
'description' => 'Javascript status call back.',
|
||||
'page callback' => 'features_feature_status',
|
||||
'page arguments' => array(3),
|
||||
'load arguments' => array(3, TRUE),
|
||||
'load arguments' => array(TRUE),
|
||||
'access callback' => 'user_access',
|
||||
'access arguments' => array('administer features'),
|
||||
'type' => MENU_CALLBACK,
|
||||
@@ -197,11 +224,11 @@ function features_theme() {
|
||||
|
||||
$items = array();
|
||||
$items['features_module_status'] = array(
|
||||
'variables' => array('module' => null, 'status' => null)
|
||||
'variables' => array('module' => NULL, 'status' => NULL)
|
||||
) + $base;
|
||||
|
||||
$items['features_components'] = array(
|
||||
'variables' => array('info' => null, 'sources' => null),
|
||||
'variables' => array('info' => NULL, 'sources' => NULL),
|
||||
) + $base;
|
||||
|
||||
$items['features_component_key'] = $base;
|
||||
@@ -210,7 +237,11 @@ function features_theme() {
|
||||
) + $base;
|
||||
|
||||
$items['features_storage_link'] = array(
|
||||
'variables' => array('storage' => null, 'text' => null, 'path' => null, 'options' => array()),
|
||||
'variables' => array('storage' => NULL, 'text' => NULL, 'path' => NULL, 'options' => array()),
|
||||
) + $base;
|
||||
|
||||
$items['features_lock_link'] = array(
|
||||
'variables' => array('feature' => NULL, 'component' => NULL, 'locked' => FALSE),
|
||||
) + $base;
|
||||
|
||||
$items['features_form_components'] =
|
||||
@@ -236,10 +267,19 @@ function features_theme() {
|
||||
* Implements hook_flush_caches().
|
||||
*/
|
||||
function features_flush_caches() {
|
||||
features_rebuild();
|
||||
// Don't flush the modules cache during installation, for performance reasons.
|
||||
if (variable_get('install_task') == 'done') {
|
||||
features_get_modules(NULL, TRUE);
|
||||
if (($modules_changed = variable_get('features_modules_changed', FALSE)) || variable_get('features_rebuild_on_flush', TRUE)) {
|
||||
if ($modules_changed) {
|
||||
variable_set('features_modules_changed', FALSE);
|
||||
}
|
||||
features_rebuild();
|
||||
// Don't flush the modules cache during installation, for performance reasons.
|
||||
if (variable_get('install_task') == 'done') {
|
||||
features_get_modules(NULL, TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
if (db_table_exists('cache_features')) {
|
||||
return array('cache_features');
|
||||
}
|
||||
return array();
|
||||
}
|
||||
@@ -251,13 +291,6 @@ function features_form($node, $form_state) {
|
||||
return node_content_form($node, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implemenation of hook_node_access()
|
||||
*/
|
||||
function features_node_access($node, $op, $account) {
|
||||
return node_node_access($node, $op, $account);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_permission().
|
||||
*/
|
||||
@@ -266,10 +299,22 @@ function features_permission() {
|
||||
'administer features' => array(
|
||||
'title' => t('Administer features'),
|
||||
'description' => t('Perform administration tasks on features.'),
|
||||
'restrict access' => TRUE,
|
||||
),
|
||||
'manage features' => array(
|
||||
'title' => t('Manage features'),
|
||||
'description' => t('View, enable and disable features.'),
|
||||
'restrict access' => TRUE,
|
||||
),
|
||||
'generate features' => array(
|
||||
'title' => t('Generate features'),
|
||||
'description' => t('Allow feature exports to be generated and written directly to site.'),
|
||||
'restrict access' => TRUE,
|
||||
),
|
||||
'rename features' => array(
|
||||
'title' => t('Edit feature machine name'),
|
||||
'description' => t('Allows editing machine name of a disabled feature'),
|
||||
'restrict access' => TRUE,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -280,10 +325,10 @@ function features_permission() {
|
||||
function features_help($path, $arg) {
|
||||
switch ($path) {
|
||||
case 'admin/help#features':
|
||||
$output = file_get_contents(drupal_get_path('module', 'features') .'/README.txt');
|
||||
return module_exists('markdown') ? filter_xss_admin(module_invoke('markdown', 'filter', 'process', 0, -1, $output)) : '<pre>'. check_plain($output) .'</pre>';
|
||||
$output = file_get_contents(drupal_get_path('module', 'features') . '/README.txt');
|
||||
return module_exists('markdown') ? filter_xss_admin(module_invoke('markdown', 'filter', 'process', 0, -1, $output)) : '<pre>' . check_plain($output) . '</pre>';
|
||||
case 'admin/build/features':
|
||||
return '<p>'. t('A "Feature" is a certain type of Drupal module which contains a package of configuration that, when enabled, provides a new set of functionality for your Drupal site. Enable features by selecting the checkboxes below and clicking the Save configuration button. If the configuration of the feature has been changed its "State" will be either "overridden" or "needs review", otherwise it will be "default", indicating that the configuration has not been changed. Click on the state to see more details about the feature and its components.') .'</p>';
|
||||
return '<p>' . t('A "Feature" is a certain type of Drupal module which contains a package of configuration that, when enabled, provides a new set of functionality for your Drupal site. Enable features by selecting the checkboxes below and clicking the Save configuration button. If the configuration of the feature has been changed its "State" will be either "overridden" or "needs review", otherwise it will be "default", indicating that the configuration has not been changed. Click on the state to see more details about the feature and its components.') . '</p>';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,6 +355,15 @@ function features_modules_disabled($modules) {
|
||||
* Implements hook_modules_enabled().
|
||||
*/
|
||||
function features_modules_enabled($modules) {
|
||||
// Allow distributions to disable this behavior and rebuild the features
|
||||
// manually inside a batch.
|
||||
if (!variable_get('features_rebuild_on_module_install', TRUE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// mark modules as being changed for test in features_flush_caches
|
||||
variable_set('features_modules_changed', TRUE);
|
||||
|
||||
// Go through all modules and gather features that can be enabled.
|
||||
$items = array();
|
||||
foreach ($modules as $module) {
|
||||
@@ -319,9 +373,18 @@ function features_modules_enabled($modules) {
|
||||
}
|
||||
|
||||
if (!empty($items)) {
|
||||
// Need to include any new files.
|
||||
// @todo Redo function so can take in list of modules to include.
|
||||
features_include_defaults(NULL, TRUE);
|
||||
_features_restore('enable', $items);
|
||||
// Rebuild the list of features includes.
|
||||
features_include(TRUE);
|
||||
// Reorders components to match hook order and removes non-existant.
|
||||
$all_components = array_keys(features_get_components());
|
||||
foreach ($items as $module => $components) {
|
||||
$items[$module] = array_intersect($all_components, $components);
|
||||
}
|
||||
_features_restore('rebuild', $items);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,7 +400,7 @@ function features_include($reset = FALSE) {
|
||||
// Features provides integration on behalf of these modules.
|
||||
// The features include provides handling for the feature dependencies.
|
||||
// Note that ctools is placed last because it implements hooks "dynamically" for other modules.
|
||||
$modules = array('features', 'block', 'context', 'field', 'filter', 'image', 'locale', 'menu', 'node', 'taxonomy', 'user', 'views', 'ctools');
|
||||
$modules = array('features', 'block', 'contact', 'context', 'field', 'filter', 'image', 'locale', 'menu', 'node', 'taxonomy', 'user', 'views', 'ctools');
|
||||
|
||||
foreach (array_filter($modules, 'module_exists') as $module) {
|
||||
module_load_include('inc', 'features', "includes/features.$module");
|
||||
@@ -362,7 +425,6 @@ function features_include($reset = FALSE) {
|
||||
* collecting defaults.
|
||||
*/
|
||||
function features_include_defaults($components = NULL, $reset = FALSE) {
|
||||
static $included = array();
|
||||
static $include_components;
|
||||
|
||||
// Build an array of components that require inclusion:
|
||||
@@ -385,7 +447,7 @@ function features_include_defaults($components = NULL, $reset = FALSE) {
|
||||
$components = array_keys($include_components);
|
||||
}
|
||||
foreach ($components as $component) {
|
||||
if (isset($include_components[$component]) && (!isset($included[$component]) || $reset)) {
|
||||
if (isset($include_components[$component])) {
|
||||
$info = $include_components[$component];
|
||||
// Inclusion of ctools components.
|
||||
if (isset($info['api'], $info['module'], $info['current_version'])) {
|
||||
@@ -397,10 +459,11 @@ function features_include_defaults($components = NULL, $reset = FALSE) {
|
||||
$features = isset($features) ? $features : features_get_features(NULL, $reset);
|
||||
foreach ($features as $feature) {
|
||||
$filename = isset($info['default_file']) && $info['default_file'] == FEATURES_DEFAULTS_CUSTOM ? $info['default_filename'] : "features.{$component}";
|
||||
module_load_include('inc', $feature->name, "{$feature->name}.$filename");
|
||||
if (module_exists($feature->name) && isset($feature->info['features'][$component])) {
|
||||
module_load_include('inc', $feature->name, "{$feature->name}.$filename");
|
||||
}
|
||||
}
|
||||
}
|
||||
$included[$component] = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -455,7 +518,7 @@ function features_load_feature($name, $reset = FALSE) {
|
||||
* Return a module 'object' including .info information.
|
||||
*
|
||||
* @param $name
|
||||
* The name of the module to retrieve information for. If ommitted,
|
||||
* The name of the module to retrieve information for. If omitted,
|
||||
* an array of all available modules will be returned.
|
||||
* @param $reset
|
||||
* Whether to reset the cache.
|
||||
@@ -487,13 +550,13 @@ function features_get_components($component = NULL, $key = NULL, $reset = FALSE)
|
||||
|
||||
if ($reset || !isset($components) || !isset($component_by_key)) {
|
||||
$components = $component_by_key = array();
|
||||
if (!$reset && ($cache = cache_get('features_api'))) {
|
||||
if (!$reset && ($cache = cache_get('features_api', 'cache_features'))) {
|
||||
$components = $cache->data;
|
||||
}
|
||||
else {
|
||||
$components = module_invoke_all('features_api');
|
||||
drupal_alter('features_api', $components);
|
||||
cache_set('features_api', $components);
|
||||
cache_set('features_api', $components, 'cache_features');
|
||||
}
|
||||
|
||||
foreach ($components as $component_type => $component_information) {
|
||||
@@ -559,6 +622,8 @@ function features_hook($component, $hook, $reset = FALSE) {
|
||||
* Clear the module info cache.
|
||||
*/
|
||||
function features_install_modules($modules) {
|
||||
variable_set('features_modules_changed', TRUE);
|
||||
|
||||
module_load_include('inc', 'features', 'features.export');
|
||||
$files = system_rebuild_module_data();
|
||||
|
||||
@@ -602,7 +667,7 @@ function features_get_features($name = NULL, $reset = FALSE) {
|
||||
function features_get_info($type = 'module', $name = NULL, $reset = FALSE) {
|
||||
static $cache;
|
||||
if (!isset($cache)) {
|
||||
$cache = cache_get('features_module_info');
|
||||
$cache = cache_get('features_module_info', 'cache_features');
|
||||
}
|
||||
if (empty($cache) || $reset) {
|
||||
$data = array(
|
||||
@@ -613,6 +678,14 @@ function features_get_info($type = 'module', $name = NULL, $reset = FALSE) {
|
||||
$files = system_rebuild_module_data();
|
||||
|
||||
foreach ($files as $row) {
|
||||
// Remove modification timestamp, added in Drupal 7.33.
|
||||
if (isset($row->info['mtime'])) {
|
||||
unset($row->info['mtime']);
|
||||
}
|
||||
// Avoid false-reported feature overrides for php = 5.2.4 line in .info file.
|
||||
if (isset($row->info['php'])) {
|
||||
unset($row->info['php']);
|
||||
}
|
||||
// If module is no longer enabled, remove it from the ignored orphans list.
|
||||
if (in_array($row->name, $ignored, TRUE) && !$row->status) {
|
||||
$key = array_search($row->name, $ignored, TRUE);
|
||||
@@ -622,14 +695,30 @@ function features_get_info($type = 'module', $name = NULL, $reset = FALSE) {
|
||||
if (!empty($row->info['features'])) {
|
||||
// Fix css/js paths
|
||||
if (!empty($row->info['stylesheets'])) {
|
||||
foreach($row->info['stylesheets'] as $media => $css) {
|
||||
foreach ($row->info['stylesheets'] as $media => $css) {
|
||||
$row->info['stylesheets'][$media] = array_keys($css);
|
||||
}
|
||||
}
|
||||
if (!empty($row->info['scripts'])) {
|
||||
$row->info['scripts'] = array_keys($row->info['scripts']);
|
||||
}
|
||||
// Rework the features array, to change the vocabulary permission
|
||||
// features.
|
||||
foreach ($row->info['features'] as $component => $features) {
|
||||
if ($component == 'user_permission') {
|
||||
foreach ($features as $key => $feature) {
|
||||
// Export vocabulary permissions using the machine name, instead
|
||||
// of vocabulary id.
|
||||
_user_features_change_term_permission($feature);
|
||||
$row->info['features'][$component][$key] = $feature;
|
||||
}
|
||||
}
|
||||
}
|
||||
$data['feature'][$row->name] = $row;
|
||||
$data['feature'][$row->name]->components = array_keys($row->info['features']);
|
||||
if (!empty($row->info['dependencies'])) {
|
||||
$data['feature'][$row->name]->components[] = 'dependencies';
|
||||
}
|
||||
}
|
||||
$data['module'][$row->name] = $row;
|
||||
}
|
||||
@@ -666,14 +755,14 @@ function features_get_info($type = 'module', $name = NULL, $reset = FALSE) {
|
||||
$data['feature'] = $sorted;
|
||||
|
||||
variable_set('features_ignored_orphans', $ignored);
|
||||
cache_set("features_module_info", $data);
|
||||
cache_set('features_module_info', $data, 'cache_features');
|
||||
$cache = new stdClass();
|
||||
$cache->data = $data;
|
||||
}
|
||||
if (!empty($name)) {
|
||||
return !empty($cache->data[$type][$name]) ? clone $cache->data[$type][$name] : array();
|
||||
return !empty($cache->data[$type][$name]) ? clone $cache->data[$type][$name] : FALSE;
|
||||
}
|
||||
return !empty($cache->data[$type]) ? $cache->data[$type] : array();
|
||||
return !empty($cache->data[$type]) ? $cache->data[$type] : FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -710,6 +799,9 @@ function features_get_orphans($reset = FALSE) {
|
||||
|
||||
foreach ($orphaned as $module) {
|
||||
if (!empty($modules[$module]->required_by)) {
|
||||
foreach ($modules[$module]->required_by as $module_name => $dependency) {
|
||||
$modules[$module]->required_by[$module_name] = $dependency['name'];
|
||||
}
|
||||
// Determine whether any dependents are actually enabled.
|
||||
$dependents = array_intersect($modules[$module]->required_by, $enabled);
|
||||
if (empty($dependents)) {
|
||||
@@ -738,7 +830,7 @@ function features_get_conflicts($reset = FALSE) {
|
||||
if (isset($component_info[$type]['duplicates']) && $component_info[$type]['duplicates'] == FEATURES_DUPLICATES_ALLOWED) {
|
||||
continue;
|
||||
}
|
||||
else if (count($modules) > 1) {
|
||||
elseif (count($modules) > 1) {
|
||||
foreach ($modules as $module) {
|
||||
if (!isset($conflicts[$module])) {
|
||||
$conflicts[$module] = array();
|
||||
@@ -789,7 +881,7 @@ function features_get_module_status($module) {
|
||||
if (module_exists($module)) {
|
||||
return FEATURES_MODULE_ENABLED;
|
||||
}
|
||||
else if (features_get_modules($module)) {
|
||||
elseif (features_get_modules($module)) {
|
||||
return FEATURES_MODULE_DISABLED;
|
||||
}
|
||||
else {
|
||||
@@ -817,7 +909,7 @@ function features_access_override_actions($feature) {
|
||||
|
||||
features_include();
|
||||
module_load_include('inc', 'features', 'features.export');
|
||||
$access[$feature->name] = in_array(features_get_storage($feature->name), array(FEATURES_OVERRIDDEN, FEATURES_NEEDS_REVIEW)) && user_access('administer features');
|
||||
$access[$feature->name] = in_array(features_get_storage($feature->name), array(FEATURES_DEFAULT, FEATURES_OVERRIDDEN, FEATURES_NEEDS_REVIEW));
|
||||
}
|
||||
return $access[$feature->name];
|
||||
}
|
||||
@@ -828,13 +920,21 @@ function features_access_override_actions($feature) {
|
||||
* Implements hook_form_alter() for system_modules form().
|
||||
*/
|
||||
function features_form_system_modules_alter(&$form) {
|
||||
features_rebuild();
|
||||
if (variable_get('features_rebuild_modules_page', FALSE)) {
|
||||
features_rebuild();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the specified modules to the default state.
|
||||
*/
|
||||
function _features_restore($op, $items = array()) {
|
||||
$lockable = FALSE;
|
||||
// Set this variable in $conf if having timeout issues during install/rebuild.
|
||||
if (variable_get('features_restore_time_limit_' . $op, FALSE) !== FALSE) {
|
||||
drupal_set_time_limit(variable_get('features_restore_time_limit_' . $op, FALSE));
|
||||
}
|
||||
|
||||
module_load_include('inc', 'features', 'features.export');
|
||||
features_include();
|
||||
|
||||
@@ -843,11 +943,13 @@ function _features_restore($op, $items = array()) {
|
||||
$restore_states = array(FEATURES_OVERRIDDEN, FEATURES_REBUILDABLE, FEATURES_NEEDS_REVIEW);
|
||||
$restore_hook = 'features_revert';
|
||||
$log_action = 'Revert';
|
||||
$lockable = TRUE;
|
||||
break;
|
||||
case 'rebuild':
|
||||
$restore_states = array(FEATURES_REBUILDABLE);
|
||||
$restore_hook = 'features_rebuild';
|
||||
$log_action = 'Rebuild';
|
||||
$lockable = variable_get('features_lock_mode', 'all') == 'all';
|
||||
break;
|
||||
case 'disable':
|
||||
$restore_hook = 'features_disable_feature';
|
||||
@@ -876,8 +978,20 @@ function _features_restore($op, $items = array()) {
|
||||
}
|
||||
}
|
||||
|
||||
// Invoke global pre restore hook.
|
||||
module_invoke_all('features_pre_restore', $op, $items);
|
||||
foreach ($items as $module_name => $components) {
|
||||
// If feature is totally locked, do not execute past this stage.
|
||||
if ($lockable && features_feature_is_locked($module_name)) {
|
||||
watchdog('features', 'Tried @actioning a locked @module_name, aborted.', array('@action' => $log_action, '@module_name' => $module_name));
|
||||
continue;
|
||||
}
|
||||
foreach ($components as $component) {
|
||||
// If feature is totally locked, do not execute past this stage.
|
||||
if ($lockable && features_feature_is_locked($module_name, $component)) {
|
||||
watchdog('features', 'Tried @actioning a locked @module_name / @component, aborted.', array('@action' => $log_action, '@component' => $component, '@module_name' => $module_name));
|
||||
continue;
|
||||
}
|
||||
// Invoke pre hook
|
||||
$pre_hook = 'pre_' . $restore_hook;
|
||||
module_invoke($module_name, $pre_hook, $component);
|
||||
@@ -899,6 +1013,8 @@ function _features_restore($op, $items = array()) {
|
||||
module_invoke($module_name, $post_hook, $component);
|
||||
}
|
||||
}
|
||||
// Invoke global post restore hook.
|
||||
module_invoke_all('features_post_restore', $op, $items);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -915,6 +1031,25 @@ function features_rebuild($rebuild = array()) {
|
||||
return _features_restore('rebuild', $rebuild);
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert a single features module.
|
||||
*
|
||||
* @param string $module
|
||||
* A features module machine name. This module must be a
|
||||
* features module and enabled.
|
||||
*/
|
||||
function features_revert_module($module) {
|
||||
if (($feature = feature_load($module, TRUE)) && module_exists($module)) {
|
||||
$components = array();
|
||||
foreach (array_keys($feature->info['features']) as $component) {
|
||||
if (features_hook($component, 'features_revert')) {
|
||||
$components[] = $component;
|
||||
}
|
||||
}
|
||||
features_revert(array($module => $components));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility functions ==================================================
|
||||
*/
|
||||
@@ -951,6 +1086,209 @@ function features_hook_info() {
|
||||
'features_api',
|
||||
'features_pipe_alter',
|
||||
'features_export_alter',
|
||||
'features_export_options_alter',
|
||||
);
|
||||
return array_fill_keys($hooks, array('group' => 'features'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Change vocabularies permission, from vocab id to machine name and vice versa.
|
||||
*/
|
||||
function _user_features_change_term_permission(&$perm, $type = 'vid') {
|
||||
if (!module_exists('taxonomy')) {
|
||||
return;
|
||||
}
|
||||
// Export vocabulary permissions using the machine name, instead of vocabulary
|
||||
// id.
|
||||
if (strpos($perm, 'edit terms in ') !== FALSE || strpos($perm, 'delete terms in ') !== FALSE) {
|
||||
preg_match("/(?<=\040)([^\s]+?)$/", trim($perm), $voc_id);
|
||||
$vid = $voc_id[0];
|
||||
if (is_numeric($vid) && $type == 'vid') {
|
||||
if (function_exists('taxonomy_vocabulary_load')) {
|
||||
if ($voc = taxonomy_vocabulary_load($vid)) {
|
||||
$perm = str_replace($vid, $voc->machine_name, $perm);
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif ($type == 'machine_name') {
|
||||
if ($voc = taxonomy_vocabulary_machine_name_load($vid)) {
|
||||
$perm = str_replace($vid, $voc->vid, $perm);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively computes the difference of arrays with additional index check.
|
||||
*
|
||||
* This is a version of array_diff_assoc() that supports multidimensional
|
||||
* arrays.
|
||||
*
|
||||
* @param array $array1
|
||||
* The array to compare from.
|
||||
* @param array $array2
|
||||
* The array to compare to.
|
||||
*
|
||||
* @return array
|
||||
* Returns an array containing all the values from array1 that are not present
|
||||
* in array2.
|
||||
*/
|
||||
function features_array_diff_assoc_recursive(array $array1, array $array2) {
|
||||
$difference = array();
|
||||
foreach ($array1 as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
if (!isset($array2[$key]) || !is_array($array2[$key])) {
|
||||
$difference[$key] = $value;
|
||||
}
|
||||
else {
|
||||
$new_diff = features_array_diff_assoc_recursive($value, $array2[$key]);
|
||||
if (!empty($new_diff)) {
|
||||
$difference[$key] = $new_diff;
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif (!isset($array2[$key]) || $array2[$key] != $value) {
|
||||
$difference[$key] = $value;
|
||||
}
|
||||
}
|
||||
return $difference;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a feature or it's component is locked.
|
||||
*/
|
||||
function features_feature_is_locked($feature, $component = NULL, $check_global_component_setting = TRUE) {
|
||||
$locked = variable_get('features_feature_locked', array());
|
||||
if ($component) {
|
||||
return ($check_global_component_setting && features_component_is_locked($component)) || !empty($locked[$feature][$component]);
|
||||
}
|
||||
else {
|
||||
return !empty($locked[$feature]['_all']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a component is locked.
|
||||
*/
|
||||
function features_component_is_locked($component) {
|
||||
return variable_get('features_component_locked_' . $component, FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Locks a feature or it's component.
|
||||
*/
|
||||
function features_feature_lock($feature, $component = NULL) {
|
||||
$locked = variable_get('features_feature_locked', array());
|
||||
$locked[$feature] = !empty($locked[$feature]) ? $locked[$feature] : array();
|
||||
if ($component) {
|
||||
$locked[$feature][$component] = TRUE;
|
||||
}
|
||||
else {
|
||||
$locked[$feature]['_all'] = TRUE;
|
||||
}
|
||||
variable_set('features_feature_locked', $locked);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlocks a feature or it's component.
|
||||
*/
|
||||
function features_feature_unlock($feature, $component = NULL) {
|
||||
$locked = variable_get('features_feature_locked', array());
|
||||
if ($component) {
|
||||
unset($locked[$feature][$component]);
|
||||
}
|
||||
else {
|
||||
unset($locked[$feature]['_all']);
|
||||
}
|
||||
variable_set('features_feature_locked', $locked);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets/Returns the current language to english to ensure a proper export.
|
||||
*/
|
||||
function _features_export_language($language = NULL) {
|
||||
$current = $GLOBALS['language'];
|
||||
if (isset($language)) {
|
||||
$GLOBALS['language'] = $language;
|
||||
}
|
||||
elseif ($GLOBALS['language']->language != 'en') {
|
||||
// Create the language object as language_default() does.
|
||||
$GLOBALS['language'] = (object) array(
|
||||
'language' => 'en',
|
||||
'name' => 'English',
|
||||
'native' => 'English',
|
||||
'direction' => 0,
|
||||
'enabled' => 1,
|
||||
'plurals' => 0,
|
||||
'formula' => '',
|
||||
'domain' => '',
|
||||
'prefix' => '',
|
||||
'weight' => 0,
|
||||
'javascript' => '',
|
||||
);
|
||||
}
|
||||
return $current;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_features_ignore().
|
||||
*/
|
||||
function features_features_ignore($component) {
|
||||
// Determine which keys need to be ignored for override diff for various components.
|
||||
// Value is how many levels deep the key is.
|
||||
$ignores = array();
|
||||
switch ($component) {
|
||||
case 'views_view':
|
||||
$ignores['current_display'] = 0;
|
||||
$ignores['display_handler'] = 0;
|
||||
$ignores['handler'] = 2;
|
||||
$ignores['query'] = 0;
|
||||
$ignores['localization_plugin'] = 0;
|
||||
// Views automatically adds these two on export to set values.
|
||||
$ignores['api_version'] = 0;
|
||||
$ignores['disabled'] = 0;
|
||||
break;
|
||||
case 'image':
|
||||
$ignores['module'] = 0;
|
||||
$ignores['name'] = 0;
|
||||
$ignores['storage'] = 0;
|
||||
// Various properties are loaded into the effect in image_styles.
|
||||
$ignores['summary theme'] = 2;
|
||||
$ignores['module'] = 2;
|
||||
$ignores['label'] = 2;
|
||||
$ignores['help'] = 2;
|
||||
$ignores['form callback'] = 2;
|
||||
$ignores['effect callback'] = 2;
|
||||
$ignores['dimensions callback'] = 2;
|
||||
break;
|
||||
case 'field':
|
||||
$ignores['locked'] = 1;
|
||||
break;
|
||||
case 'field_base':
|
||||
$ignores['indexes'] = 0;
|
||||
break;
|
||||
case 'taxonomy':
|
||||
$ignores['hierarchy'] = 0;
|
||||
}
|
||||
return $ignores;
|
||||
}
|
||||
|
115
sites/all/modules/features/includes/features.contact.inc
Normal file
115
sites/all/modules/features/includes/features.contact.inc
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Features integration for 'contact' module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_features_api().
|
||||
*/
|
||||
function contact_features_api() {
|
||||
return array(
|
||||
'contact_categories' => array(
|
||||
'name' => t('Contact categories'),
|
||||
'feature_source' => TRUE,
|
||||
'default_hook' => 'contact_categories_defaults',
|
||||
'default_file' => FEATURES_DEFAULTS_INCLUDED,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_features_export_options().
|
||||
*/
|
||||
function contact_categories_features_export_options() {
|
||||
$options = array();
|
||||
$categories = db_select('contact', 'c')->fields('c')->execute()->fetchAll();
|
||||
foreach ($categories as $category) {
|
||||
$options["$category->category"] = "$category->category";
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_features_export().
|
||||
*/
|
||||
function contact_categories_features_export($data, &$export, $module_name = '') {
|
||||
$export['dependencies']['features'] = 'features';
|
||||
$export['dependencies']['contact'] = 'contact';
|
||||
|
||||
foreach ($data as $name) {
|
||||
$export['features']['contact_categories'][$name] = $name;
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_features_export_render().
|
||||
*/
|
||||
function contact_categories_features_export_render($module, $data, $export = NULL) {
|
||||
$render = array();
|
||||
foreach ($data as $name) {
|
||||
$export_category = db_select('contact', 'c')
|
||||
->fields('c', array('cid', 'category'))
|
||||
->condition('category', $name, 'LIKE')
|
||||
->execute()
|
||||
->fetchAll();
|
||||
if (isset($export_category[0]->cid) && ($category = contact_load($export_category[0]->cid))) {
|
||||
unset($category['cid']);
|
||||
$render[$name] = $category;
|
||||
}
|
||||
}
|
||||
return array('contact_categories_defaults' => ' return ' . features_var_export($render, ' ') . ';');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_features_revert().
|
||||
*/
|
||||
function contact_categories_features_revert($module) {
|
||||
return contact_categories_features_rebuild($module);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_features_rebuild().
|
||||
*/
|
||||
function contact_categories_features_rebuild($module) {
|
||||
if ($defaults = features_get_default('contact_categories', $module)) {
|
||||
foreach ($defaults as $default_category) {
|
||||
$existing_categories = db_select('contact', 'c')
|
||||
->fields('c', array('cid', 'category'))
|
||||
->execute()
|
||||
->fetchAll();
|
||||
if ($existing_categories) {
|
||||
foreach ($existing_categories as $existing_category) {
|
||||
if ($default_category['category'] == $existing_category->category) {
|
||||
db_update('contact')
|
||||
->fields(
|
||||
array(
|
||||
'recipients' => $default_category['recipients'],
|
||||
'reply' => $default_category['reply'],
|
||||
'weight' => $default_category['weight'],
|
||||
'selected' => $default_category['selected'],
|
||||
)
|
||||
)
|
||||
->condition('category', $existing_category->category, '=')
|
||||
->execute();
|
||||
}
|
||||
else {
|
||||
db_merge('contact')
|
||||
->key(array('category' => $default_category['category']))
|
||||
->fields($default_category)
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
db_merge('contact')
|
||||
->key(array('category' => $default_category['category']))
|
||||
->fields($default_category)
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -86,7 +86,6 @@ function ctools_features_export_render($module, $data) {
|
||||
$code[] = ' return array("api" => "3.0");';
|
||||
}
|
||||
else {
|
||||
$code[] = ' list($module, $api) = func_get_args();';
|
||||
$code[] = ' if ($module == "'. $info['module'] .'" && $api == "'. $info['api'] .'") {';
|
||||
$code[] = ' return array("version" => "'. $info['current_version'] .'");';
|
||||
$code[] = ' }';
|
||||
@@ -96,10 +95,13 @@ function ctools_features_export_render($module, $data) {
|
||||
$plugin_api_hook_name = ctools_plugin_api_get_hook($info['module'], $info['api']);
|
||||
|
||||
if (key_exists($plugin_api_hook_name, $component_exports)) {
|
||||
$component_exports[$plugin_api_hook_name] .= "\n" . implode("\n", $code);
|
||||
$component_exports[$plugin_api_hook_name]['code'] .= "\n" . implode("\n", $code);
|
||||
}
|
||||
else {
|
||||
$component_exports[$plugin_api_hook_name] = implode("\n", $code);
|
||||
$component_exports[$plugin_api_hook_name] = array(
|
||||
'code' => implode("\n", $code),
|
||||
'args' => '$module = NULL, $api = NULL',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -6,14 +6,375 @@
|
||||
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' => FALSE,
|
||||
),
|
||||
'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();
|
||||
foreach (field_info_fields() as $field_name => $field) {
|
||||
foreach ($field['bundles'] as $entity_type => $bundles) {
|
||||
foreach ($bundles as $bundle) {
|
||||
$identifier = "{$entity_type}-{$bundle}-{$field_name}";
|
||||
$options[$identifier] = $identifier;
|
||||
}
|
||||
}
|
||||
}
|
||||
ksort($options);
|
||||
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['foreign keys']);
|
||||
// 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 no one 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_prefix = ' // Exported field_base: ';
|
||||
$field_identifier = features_var_export($identifier);
|
||||
if (features_field_export_needs_wrap($field_prefix, $field_identifier)) {
|
||||
$code[] = rtrim($field_prefix);
|
||||
$code[] = " // {$field_identifier}.";
|
||||
}
|
||||
else {
|
||||
$code[] = $field_prefix . $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_prefix = ' // Exported field_instance: ';
|
||||
$instance_identifier = features_var_export($identifier);
|
||||
if (features_field_export_needs_wrap($instance_prefix, $instance_identifier)) {
|
||||
$code[] = rtrim($instance_prefix);
|
||||
$code[] = " // {$instance_identifier}.";
|
||||
}
|
||||
else {
|
||||
$code[] = $instance_prefix . $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) {
|
||||
uksort($field, 'strnatcmp');
|
||||
}
|
||||
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']];
|
||||
$array_diff_result = drupal_array_diff_assoc_recursive($field + $existing_field, $existing_field);
|
||||
if (!empty($array_diff_result)) {
|
||||
try {
|
||||
field_update_field($field);
|
||||
}
|
||||
catch (FieldException $e) {
|
||||
watchdog('features', 'Attempt to update field %label failed: %message', array('%label' => $field['field_name'], '%message' => $e->getMessage()), WATCHDOG_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
try {
|
||||
field_create_field($field);
|
||||
}
|
||||
catch (FieldException $e) {
|
||||
watchdog('features', 'Attempt to create field %label failed: %message', array('%label' => $field['field_name'], '%message' => $e->getMessage()), WATCHDOG_ERROR);
|
||||
}
|
||||
$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) {
|
||||
try {
|
||||
field_update_instance($field_instance);
|
||||
}
|
||||
catch (FieldException $e) {
|
||||
watchdog('features', 'Attempt to update field instance %label (in %entity entity type %bundle bundle) failed: %message', array('%label' => $field_instance['field_name'], '%entity' => $field_instance['entity_type'], '%bundle' => $field_instance['bundle'], '%message' => $e->getMessage()), WATCHDOG_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
try {
|
||||
field_create_instance($field_instance);
|
||||
}
|
||||
catch (FieldException $e) {
|
||||
watchdog('features', 'Attempt to create field instance %label (in %entity entity type %bundle bundle) failed: %message', array('%label' => $field_instance['field_name'], '%entity' => $field_instance['entity_type'], '%bundle' => $field_instance['bundle'], '%message' => $e->getMessage()), WATCHDOG_ERROR);
|
||||
}
|
||||
$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().
|
||||
@@ -37,48 +398,8 @@ function field_features_export_options() {
|
||||
*/
|
||||
function field_features_export($data, &$export, $module_name = '') {
|
||||
$pipe = array();
|
||||
$map = features_get_default_map('field');
|
||||
|
||||
// The field_default_fields() 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 ($field = features_field_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'][$identifier])) {
|
||||
unset($export['features']['field'][$identifier]);
|
||||
}
|
||||
$module = $map[$identifier];
|
||||
$export['dependencies'][$module] = $module;
|
||||
}
|
||||
// If the field has not yet been exported, add it
|
||||
else {
|
||||
$export['features']['field'][$identifier] = $identifier;
|
||||
$export['dependencies'][$field['field_config']['module']] = $field['field_config']['module'];
|
||||
if ($field['field_config']['storage']['type'] != variable_get('field_storage_default', 'field_sql_storage')) {
|
||||
$export['dependencies'][$field['field_config']['storage']['module']] = $field['field_config']['storage']['module'];
|
||||
}
|
||||
$export['dependencies'][$field['field_instance']['widget']['module']] = $field['field_instance']['widget']['module'];
|
||||
foreach ($field['field_instance']['display'] as $key => $display) {
|
||||
if (isset($display['module'])) {
|
||||
$export['dependencies'][$display['module']] = $display['module'];
|
||||
// @TODO: handle the pipe to image styles
|
||||
}
|
||||
}
|
||||
// If taxonomy field, add in the vocabulary
|
||||
if ($field['field_config']['type'] == 'taxonomy_term_reference' && !empty($field['field_config']['settings']['allowed_values'])) {
|
||||
foreach ($field['field_config']['settings']['allowed_values'] as $allowed_values) {
|
||||
if (!empty($allowed_values['vocabulary'])) {
|
||||
$pipe['taxonomy'][] = $allowed_values['vocabulary'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Convert 'field' to 'field_instance' on features-update.
|
||||
$pipe['field_instance'] = $data;
|
||||
return $pipe;
|
||||
}
|
||||
|
||||
@@ -99,7 +420,7 @@ function field_features_export_render($module, $data, $export = NULL) {
|
||||
unset($field['field_config']['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
|
||||
// storage type was altered into to the field definition. And no one would
|
||||
// never need to change the 'details' key, so don't render it.
|
||||
if (isset($field['field_config']['storage']['details'])) {
|
||||
unset($field['field_config']['storage']['details']);
|
||||
@@ -173,12 +494,23 @@ function field_features_rebuild($module) {
|
||||
$field_config = $field['field_config'];
|
||||
if (isset($existing_fields[$field_config['field_name']])) {
|
||||
$existing_field = $existing_fields[$field_config['field_name']];
|
||||
if ($field_config + $existing_field != $existing_field) {
|
||||
field_update_field($field_config);
|
||||
$array_diff_result = drupal_array_diff_assoc_recursive($field_config + $existing_field, $existing_field);
|
||||
if (!empty($array_diff_result)) {
|
||||
try {
|
||||
field_update_field($field_config);
|
||||
}
|
||||
catch (FieldException $e) {
|
||||
watchdog('features', 'Attempt to update field %label failed: %message', array('%label' => $field_config['field_name'], '%message' => $e->getMessage()), WATCHDOG_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
field_create_field($field_config);
|
||||
try {
|
||||
field_create_field($field_config);
|
||||
}
|
||||
catch (FieldException $e) {
|
||||
watchdog('features', 'Attempt to create field %label failed: %message', array('%label' => $field_config['field_name'], '%message' => $e->getMessage()), WATCHDOG_ERROR);
|
||||
}
|
||||
$existing_fields[$field_config['field_name']] = $field_config;
|
||||
}
|
||||
|
||||
@@ -186,7 +518,7 @@ function field_features_rebuild($module) {
|
||||
$field_instance = $field['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) {
|
||||
if ($field_instance + $existing_instance !== $existing_instance) {
|
||||
field_update_instance($field_instance);
|
||||
}
|
||||
}
|
||||
@@ -222,3 +554,24 @@ function features_field_load($identifier) {
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a field export line needs to be wrapped.
|
||||
*
|
||||
* Drupal code standards specify that comments should wrap at 80 characters or
|
||||
* less.
|
||||
*
|
||||
* @param string $prefix
|
||||
* The prefix to be exported before the field identifier.
|
||||
* @param string $identifier
|
||||
* The field identifier.
|
||||
*
|
||||
* @return BOOL
|
||||
* TRUE if the line should be wrapped after the prefix, else FALSE.
|
||||
*
|
||||
* @see https://www.drupal.org/node/1354
|
||||
*/
|
||||
function features_field_export_needs_wrap($prefix, $identifier) {
|
||||
// Check for 79 characters, since the comment ends with a full stop.
|
||||
return (strlen($prefix) + strlen($identifier) > 79);
|
||||
}
|
||||
|
@@ -86,16 +86,25 @@ function image_features_revert($module) {
|
||||
/**
|
||||
* Remove unnecessary keys for export.
|
||||
*/
|
||||
function _image_features_style_sanitize(&$style, $child = FALSE) {
|
||||
$omit = $child ? array('isid', 'ieid', 'storage') : array('isid', 'ieid', 'storage', 'module');
|
||||
if (is_array($style)) {
|
||||
foreach ($style as $k => $v) {
|
||||
if (in_array($k, $omit, TRUE)) {
|
||||
unset($style[$k]);
|
||||
}
|
||||
else if (is_array($v)) {
|
||||
_image_features_style_sanitize($style[$k], TRUE);
|
||||
}
|
||||
}
|
||||
function _image_features_style_sanitize(array &$style) {
|
||||
// Sanitize style: Don't export numeric IDs and things which get overwritten
|
||||
// in image_styles() or are code/storage specific. The name property will be
|
||||
// the key of the exported $style array.
|
||||
$style = array_diff_key($style, array_flip(array(
|
||||
'isid',
|
||||
'name',
|
||||
'module',
|
||||
'storage',
|
||||
)));
|
||||
|
||||
// Sanitize effects: all that needs to be kept is name, weight and data,
|
||||
// which holds all the style-specific configuration. Other keys are assumed
|
||||
// to belong to the definition of the effect itself, so not configuration.
|
||||
foreach ($style['effects'] as $id => $effect) {
|
||||
$style['effects'][$id] = array_intersect_key($effect, array_flip(array(
|
||||
'name',
|
||||
'data',
|
||||
'weight',
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
@@ -134,14 +134,19 @@ function _features_language_save($language) {
|
||||
->fields(array(
|
||||
'plurals' => empty($language->plurals) ? 0 : $language->plurals,
|
||||
'formula' => empty($language->formula) ? '' : $language->formula,
|
||||
'weight' => empty($language->weight) ? 0 : $language->weight,
|
||||
))
|
||||
->condition('language', $language->language)
|
||||
->execute();
|
||||
}
|
||||
// Update Existing language.
|
||||
else {
|
||||
// @TODO: get properties from schema.
|
||||
$properties = array('language', 'name', 'native', 'direction', 'enabled', 'plurals', 'formula', 'domain', 'prefix', 'weight');
|
||||
// Get field list from table schema.
|
||||
$properties = drupal_schema_fields_sql('languages');
|
||||
// The javascript hash is not in the imported data but should be empty
|
||||
if (!isset($language->javascript)) {
|
||||
$language->javascript = '';
|
||||
}
|
||||
|
||||
$fields = array_intersect_key((array) $language, array_flip($properties));
|
||||
db_update('languages')
|
||||
|
@@ -103,7 +103,6 @@ function menu_custom_features_export_render($module, $data) {
|
||||
$code[] = features_translatables_export($translatables, ' ');
|
||||
}
|
||||
|
||||
$code[] = '';
|
||||
$code[] = ' return $menus;';
|
||||
$code = implode("\n", $code);
|
||||
return array('menu_default_menu_custom' => $code);
|
||||
@@ -135,13 +134,14 @@ function menu_links_features_export_options() {
|
||||
// Need to set this to TRUE in order to get menu links that the
|
||||
// current user may not have access to (i.e. user/login)
|
||||
$menu_admin = TRUE;
|
||||
$menu_links = menu_parent_options(menu_get_menus(), array('mlid' => 0));
|
||||
$use_menus = array_intersect_key(menu_get_menus(), array_flip(array_filter(variable_get('features_admin_menu_links_menus', array_keys(menu_get_menus())))));
|
||||
$menu_links = menu_parent_options($use_menus, array('mlid' => 0));
|
||||
$options = array();
|
||||
foreach ($menu_links as $key => $name) {
|
||||
list($menu_name, $mlid) = explode(':', $key, 2);
|
||||
if ($mlid != 0) {
|
||||
$link = menu_link_load($mlid);
|
||||
$identifier = menu_links_features_identifier($link);
|
||||
$identifier = menu_links_features_identifier($link, TRUE);
|
||||
$options[$identifier] = "{$menu_name}: {$name}";
|
||||
}
|
||||
}
|
||||
@@ -152,8 +152,28 @@ function menu_links_features_export_options() {
|
||||
/**
|
||||
* Callback for generating the menu link exportable identifier.
|
||||
*/
|
||||
function menu_links_features_identifier($link) {
|
||||
return isset($link['menu_name'], $link['link_path']) ? "{$link['menu_name']}:{$link['link_path']}" : FALSE;
|
||||
function menu_links_features_identifier($link, $old = FALSE) {
|
||||
// Add some uniqueness to these identifiers, allowing multiple links with the same path, but different titles.
|
||||
$clean_title = features_clean_title(isset($link['title']) ? $link['title'] : $link['link_title']);
|
||||
|
||||
// The old identifier is requested.
|
||||
if ($old) {
|
||||
// if identifier already exists
|
||||
if (isset($link['options']['identifier'])) {
|
||||
return $link['options']['identifier'];
|
||||
}
|
||||
// providing backward compatibility and allowing/enabling multiple links with same paths
|
||||
else {
|
||||
$identifier = isset($link['menu_name'], $link['link_path']) ? "{$link['menu_name']}:{$link['link_path']}" : FALSE;
|
||||
// Checking if there are multiples of this identifier
|
||||
if (features_menu_link_load($identifier) !== FALSE) {
|
||||
// this is where we return the upgrade posibility for links.
|
||||
return $identifier;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return isset($link['menu_name'], $link['link_path']) ? "{$link['menu_name']}_{$clean_title}:{$link['link_path']}" : FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -168,14 +188,15 @@ function menu_links_features_export($data, &$export, $module_name = '') {
|
||||
// Collect a link to module map
|
||||
$pipe = array();
|
||||
$map = features_get_default_map('menu_links', 'menu_links_features_identifier');
|
||||
foreach ($data as $identifier) {
|
||||
foreach ($data as $key => $identifier) {
|
||||
if ($link = features_menu_link_load($identifier)) {
|
||||
// If this link is provided by a different module, add it as a dependency.
|
||||
$new_identifier = menu_links_features_identifier($link, empty($export));
|
||||
if (isset($map[$identifier]) && $map[$identifier] != $module_name) {
|
||||
$export['dependencies'][$map[$identifier]] = $map[$identifier];
|
||||
}
|
||||
else {
|
||||
$export['features']['menu_links'][$identifier] = $identifier;
|
||||
$export['features']['menu_links'][$new_identifier] = $new_identifier;
|
||||
}
|
||||
// For now, exclude a variety of common menus from automatic export.
|
||||
// They may still be explicitly included in a Feature if the builder
|
||||
@@ -191,31 +212,51 @@ function menu_links_features_export($data, &$export, $module_name = '') {
|
||||
/**
|
||||
* Implements hook_features_export_render()
|
||||
*/
|
||||
function menu_links_features_export_render($module, $data) {
|
||||
function menu_links_features_export_render($module, $data, $export = NULL) {
|
||||
$code = array();
|
||||
$code[] = ' $menu_links = array();';
|
||||
$code[] = '';
|
||||
|
||||
$translatables = array();
|
||||
foreach ($data as $identifier) {
|
||||
|
||||
if ($link = features_menu_link_load($identifier)) {
|
||||
$new_identifier = menu_links_features_identifier($link, empty($export));
|
||||
|
||||
// Replace plid with a parent path.
|
||||
if (!empty($link['plid']) && $parent = menu_link_load($link['plid'])) {
|
||||
$link['parent_path'] = $parent['link_path'];
|
||||
// If the new identifier is different than the old, maintain
|
||||
// 'parent_path' for backwards compatibility.
|
||||
if ($new_identifier != menu_links_features_identifier($link)) {
|
||||
$link['parent_path'] = $parent['link_path'];
|
||||
}
|
||||
else {
|
||||
$clean_title = features_clean_title($parent['title']);
|
||||
$link['parent_identifier'] = "{$parent['menu_name']}_{$clean_title}:{$parent['link_path']}";
|
||||
}
|
||||
}
|
||||
unset($link['plid']);
|
||||
unset($link['mlid']);
|
||||
|
||||
$code[] = " // Exported menu link: {$identifier}";
|
||||
$code[] = " \$menu_links['{$identifier}'] = ". features_var_export($link, ' ') .";";
|
||||
if (isset($export)) {
|
||||
// Don't show new identifier unless we are actually exporting.
|
||||
$link['options']['identifier'] = $new_identifier;
|
||||
// identifiers are renewed, => that means we need to update them in the db
|
||||
$temp = $link;
|
||||
menu_link_save($temp);
|
||||
}
|
||||
|
||||
unset($link['plid']);
|
||||
unset($link['mlid']);
|
||||
|
||||
$code[] = " // Exported menu link: {$new_identifier}.";
|
||||
$code[] = " \$menu_links['{$new_identifier}'] = ". features_var_export($link, ' ') .";";
|
||||
$translatables[] = $link['link_title'];
|
||||
}
|
||||
}
|
||||
$code[] = '';
|
||||
if (!empty($translatables)) {
|
||||
$code[] = features_translatables_export($translatables, ' ');
|
||||
}
|
||||
|
||||
$code[] = '';
|
||||
$code[] = ' return $menu_links;';
|
||||
$code = implode("\n", $code);
|
||||
return array('menu_default_menu_links' => $code);
|
||||
@@ -253,26 +294,36 @@ function menu_links_features_rebuild_ordered($menu_links, $reset = FALSE) {
|
||||
$current = count($unordered);
|
||||
foreach ($unordered as $key => $link) {
|
||||
$identifier = menu_links_features_identifier($link);
|
||||
$parent = isset($link['parent_path']) ? "{$link['menu_name']}:{$link['parent_path']}" : '';
|
||||
if (empty($parent)) {
|
||||
$ordered[$identifier] = 0;
|
||||
$all_links[$identifier] = $link;
|
||||
unset($unordered[$key]);
|
||||
$parent = isset($link['parent_identifier']) ? $link['parent_identifier'] : '';
|
||||
$weight = 0;
|
||||
// Parent has been seen, so weigh this above parent.
|
||||
if (isset($ordered[$parent])) {
|
||||
$weight = $ordered[$parent] + 1;
|
||||
}
|
||||
elseif (isset($ordered[$parent])) {
|
||||
$ordered[$identifier] = $ordered[$parent] + 1;
|
||||
$all_links[$identifier] = $link;
|
||||
unset($unordered[$key]);
|
||||
// Next loop will try to find parent weight instead.
|
||||
elseif ($parent) {
|
||||
continue;
|
||||
}
|
||||
$ordered[$identifier] = $weight;
|
||||
$all_links[$identifier] = $link;
|
||||
unset($unordered[$key]);
|
||||
}
|
||||
// Exit out when the above does no changes this loop.
|
||||
} while (count($unordered) < $current);
|
||||
}
|
||||
// Add all remaining unordered items to the ordered list.
|
||||
foreach ($unordered as $link) {
|
||||
$identifier = menu_links_features_identifier($link);
|
||||
$ordered[$identifier] = 0;
|
||||
$all_links[$identifier] = $link;
|
||||
}
|
||||
asort($ordered);
|
||||
}
|
||||
|
||||
// Ensure any default menu items that do not exist are created.
|
||||
foreach (array_keys($ordered) as $identifier) {
|
||||
$link = $all_links[$identifier];
|
||||
|
||||
$existing = features_menu_link_load($identifier);
|
||||
if (!$existing || in_array($link, $menu_links)) {
|
||||
// Retrieve the mlid if this is an existing item.
|
||||
@@ -280,9 +331,13 @@ function menu_links_features_rebuild_ordered($menu_links, $reset = FALSE) {
|
||||
$link['mlid'] = $existing['mlid'];
|
||||
}
|
||||
// Retrieve the plid for a parent link.
|
||||
if (!empty($link['parent_path']) && $parent = features_menu_link_load("{$link['menu_name']}:{$link['parent_path']}")) {
|
||||
if (!empty($link['parent_identifier']) && $parent = features_menu_link_load($link['parent_identifier'])) {
|
||||
$link['plid'] = $parent['mlid'];
|
||||
}
|
||||
// This if for backwards compatibility.
|
||||
elseif (!empty($link['parent_path']) && $parent = features_menu_link_load("{$link['menu_name']}:{$link['parent_path']}")) {
|
||||
$link['plid'] = $parent['mlid'];
|
||||
}
|
||||
else {
|
||||
$link['plid'] = 0;
|
||||
}
|
||||
@@ -292,19 +347,81 @@ function menu_links_features_rebuild_ordered($menu_links, $reset = FALSE) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a menu link by its menu_name:link_path identifier.
|
||||
* Load a menu link by its menu_name_cleantitle:link_path identifier.
|
||||
* Also matches links with unique menu_name:link_path
|
||||
*/
|
||||
function features_menu_link_load($identifier) {
|
||||
list($menu_name, $link_path) = explode(':', $identifier, 2);
|
||||
$link = db_select('menu_links')
|
||||
->fields('menu_links', array('menu_name', 'mlid', 'plid', 'link_path', 'router_path', 'link_title', 'options', 'module', 'hidden', 'external', 'has_children', 'expanded', 'weight'))
|
||||
$menu_name = '';
|
||||
$link_path = '';
|
||||
// This gets variables for menu_name_cleantitle:link_path format.
|
||||
if (strstr($identifier, "_")) {
|
||||
$link_path = substr($identifier, strpos($identifier, ":") + 1);
|
||||
list($menu_name) = explode('_', $identifier, 2);
|
||||
$clean_title = substr($identifier, strpos($identifier, "_") + 1, strpos($identifier, ":") - strpos($identifier, "_") - 1);
|
||||
}
|
||||
// This gets variables for traditional identifier format.
|
||||
else {
|
||||
$clean_title = '';
|
||||
list($menu_name, $link_path) = explode(':', $identifier, 2);
|
||||
}
|
||||
$links = db_select('menu_links')
|
||||
->fields('menu_links', array('menu_name', 'mlid', 'plid', 'link_path', 'router_path', 'link_title', 'options', 'module', 'hidden', 'external', 'has_children', 'expanded', 'weight', 'customized'))
|
||||
->condition('menu_name', $menu_name)
|
||||
->condition('link_path', $link_path)
|
||||
->addTag('features_menu_link')
|
||||
->execute()
|
||||
->fetchAssoc();
|
||||
if ($link) {
|
||||
$link['options'] = unserialize($link['options']);
|
||||
return $link;
|
||||
->fetchAllAssoc('mlid');
|
||||
|
||||
foreach($links as $link) {
|
||||
$link->options = unserialize($link->options);
|
||||
|
||||
// Title or previous identifier matches.
|
||||
if ((isset($link->options['identifier']) && strcmp($link->options['identifier'], $identifier) == 0)
|
||||
|| (isset($clean_title) && strcmp(features_clean_title($link->link_title), $clean_title) == 0)) {
|
||||
|
||||
return (array)$link;
|
||||
}
|
||||
}
|
||||
|
||||
// Only one link with the requested menu_name and link_path does exists,
|
||||
// -- providing an upgrade possibility for links saved in a feature before the
|
||||
// new identifier-pattern was added.
|
||||
if (count($links) == 1 && empty($clean_title)) {
|
||||
$link = reset($links); // get the first item
|
||||
return (array)$link;
|
||||
}
|
||||
// If link_path was changed on an existing link, we need to find it by
|
||||
// searching for link_title.
|
||||
else if (isset($clean_title)) {
|
||||
$links = db_select('menu_links')
|
||||
->fields('menu_links', array('menu_name', 'mlid', 'plid', 'link_path', 'router_path', 'link_title', 'options', 'module', 'hidden', 'external', 'has_children', 'expanded', 'weight'))
|
||||
->condition('menu_name', $menu_name)
|
||||
->execute()
|
||||
->fetchAllAssoc('mlid');
|
||||
|
||||
foreach($links as $link) {
|
||||
$link->options = unserialize($link->options);
|
||||
// Links with a stored identifier must only be matched on that identifier,
|
||||
// to prevent cross over assumptions.
|
||||
if (isset($link->options['identifier'])) {
|
||||
if (strcmp($link->options['identifier'], $identifier) == 0) {
|
||||
return (array)$link;
|
||||
}
|
||||
}
|
||||
elseif ((strcmp(features_clean_title($link->link_title), $clean_title) == 0)) {
|
||||
return (array)$link;
|
||||
}
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a lowercase clean string with only letters, numbers and dashes
|
||||
*/
|
||||
function features_clean_title($str) {
|
||||
return strtolower(preg_replace_callback('/(\s)|([^a-zA-Z\-0-9])/i', create_function(
|
||||
'$matches',
|
||||
'return $matches[1]?"-":"";'
|
||||
), $str));
|
||||
}
|
||||
|
@@ -9,6 +9,7 @@ function node_features_api() {
|
||||
'name' => t('Content types'),
|
||||
'feature_source' => TRUE,
|
||||
'default_hook' => 'node_info',
|
||||
'alter_type' => FEATURES_ALTER_TYPE_INLINE,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -43,7 +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']}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,6 +87,7 @@ function node_features_export_render($module, $data, $export = NULL) {
|
||||
}
|
||||
}
|
||||
$output[] = ' );';
|
||||
$output[] = ' drupal_alter(\'node_info\', $items);';
|
||||
$output[] = ' return $items;';
|
||||
$output = implode("\n", $output);
|
||||
return array('node_info' => $output);
|
||||
@@ -115,7 +117,7 @@ function node_features_revert($module = NULL) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_features_disable().
|
||||
* Implements hook_features_disable_feature().
|
||||
*
|
||||
* When a features module is disabled, modify any node types it provides so
|
||||
* they can be deleted manually through the content types UI.
|
||||
@@ -123,7 +125,7 @@ function node_features_revert($module = NULL) {
|
||||
* @param $module
|
||||
* Name of module that has been disabled.
|
||||
*/
|
||||
function node_features_disable($module) {
|
||||
function node_features_disable_feature($module) {
|
||||
if ($default_types = features_get_default('node', $module)) {
|
||||
foreach ($default_types as $type_name => $type_info) {
|
||||
$type_info = node_type_load($type_name);
|
||||
@@ -131,22 +133,26 @@ function node_features_disable($module) {
|
||||
$type_info->custom = 1;
|
||||
$type_info->modified = 1;
|
||||
$type_info->locked = 0;
|
||||
$type_info->disabled = 0;
|
||||
node_type_save($type_info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_features_enable().
|
||||
* Implements hook_features_enable_feature().
|
||||
*
|
||||
* When a features module is enabled, modify any node types it provides so
|
||||
* they can no longer be deleted manually through the content types UI.
|
||||
*
|
||||
* Update the database cache of node types if needed.
|
||||
*
|
||||
* @param $module
|
||||
* Name of module that has been enabled.
|
||||
*/
|
||||
function node_features_enable($module) {
|
||||
function node_features_enable_feature($module) {
|
||||
if ($default_types = features_get_default('node', $module)) {
|
||||
$rebuild = FALSE;
|
||||
foreach ($default_types as $type_name => $type_info) {
|
||||
// Ensure the type exists.
|
||||
if ($type_info = node_type_load($type_name)) {
|
||||
@@ -154,8 +160,15 @@ function node_features_enable($module) {
|
||||
$type_info->custom = 0;
|
||||
$type_info->modified = 0;
|
||||
$type_info->locked = 1;
|
||||
$type_info->disabled = 0;
|
||||
node_type_save($type_info);
|
||||
}
|
||||
else {
|
||||
$rebuild = TRUE;
|
||||
}
|
||||
}
|
||||
if ($rebuild) {
|
||||
node_types_rebuild();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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']}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -29,8 +29,12 @@ function user_permission_features_export($data, &$export, $module_name = '') {
|
||||
// Ensure the modules that provide the given permissions are included as dependencies.
|
||||
$map = user_permission_get_modules();
|
||||
foreach ($data as $perm) {
|
||||
if (isset($map[$perm])) {
|
||||
$perm_module = $map[$perm];
|
||||
$perm_name = $perm;
|
||||
// Export vocabulary permissions using the machine name, instead of
|
||||
// vocabulary id.
|
||||
_user_features_change_term_permission($perm_name, 'machine_name');
|
||||
if (isset($map[$perm_name])) {
|
||||
$perm_module = $map[$perm_name];
|
||||
$export['dependencies'][$perm_module] = $perm_module;
|
||||
$export['features']['user_permission'][$perm] = $perm;
|
||||
}
|
||||
@@ -46,14 +50,17 @@ function user_permission_features_export_options() {
|
||||
$modules = array();
|
||||
$module_info = system_get_info('module');
|
||||
foreach (module_implements('permission') as $module) {
|
||||
$modules[$module_info[$module]['name']] = $module;
|
||||
$modules[$module] = $module_info[$module]['name'];
|
||||
}
|
||||
ksort($modules);
|
||||
|
||||
$options = array();
|
||||
foreach ($modules as $display_name => $module) {
|
||||
foreach ($modules as $module => $display_name) {
|
||||
if ($permissions = module_invoke($module, 'permission')) {
|
||||
foreach ($permissions as $perm => $perm_item) {
|
||||
// Export vocabulary permissions using the machine name, instead of
|
||||
// vocabulary id.
|
||||
_user_features_change_term_permission($perm);
|
||||
$options[$perm] = strip_tags("{$display_name}: {$perm_item['title']}");
|
||||
}
|
||||
}
|
||||
@@ -78,7 +85,11 @@ function user_permission_features_export_render($module, $data) {
|
||||
|
||||
foreach ($data as $perm_name) {
|
||||
$permission = array();
|
||||
$permission['name'] = $perm_name;
|
||||
// Export vocabulary permissions using the machine name, instead of
|
||||
// vocabulary id.
|
||||
$perm = $perm_name;
|
||||
_user_features_change_term_permission($perm_name, 'machine_name');
|
||||
$permission['name'] = $perm;
|
||||
if (!empty($permissions[$perm_name])) {
|
||||
sort($permissions[$perm_name]);
|
||||
$permission['roles'] = drupal_map_assoc($permissions[$perm_name]);
|
||||
@@ -89,9 +100,9 @@ function user_permission_features_export_render($module, $data) {
|
||||
if (isset($perm_modules[$perm_name])) {
|
||||
$permission['module'] = $perm_modules[$perm_name];
|
||||
}
|
||||
$perm_identifier = features_var_export($perm_name);
|
||||
$perm_identifier = features_var_export($perm);
|
||||
$perm_export = features_var_export($permission, ' ');
|
||||
$code[] = " // Exported permission: {$perm_name}.";
|
||||
$code[] = " // Exported permission: {$perm_identifier}.";
|
||||
$code[] = " \$permissions[{$perm_identifier}] = {$perm_export};";
|
||||
$code[] = "";
|
||||
}
|
||||
@@ -122,10 +133,20 @@ function user_permission_features_rebuild($module) {
|
||||
// or via drush.
|
||||
node_types_rebuild();
|
||||
|
||||
$modules = user_permission_get_modules();
|
||||
$roles = _user_features_get_roles();
|
||||
$permissions_by_role = _user_features_get_permissions(FALSE);
|
||||
foreach ($defaults as $permission) {
|
||||
$perm = $permission['name'];
|
||||
_user_features_change_term_permission($perm, 'machine_name');
|
||||
if (empty($modules[$perm])) {
|
||||
$args = array('!name' => $perm, '!module' => $module,);
|
||||
$msg = t('Warning in features rebuild of !module. No module defines permission "!name".', $args);
|
||||
drupal_set_message($msg, 'warning');
|
||||
continue;
|
||||
}
|
||||
// Export vocabulary permissions using the machine name, instead of
|
||||
// vocabulary id.
|
||||
foreach ($roles as $role) {
|
||||
if (in_array($role, $permission['roles'])) {
|
||||
$permissions_by_role[$role][$perm] = TRUE;
|
||||
|
@@ -45,7 +45,7 @@ class FeaturesUserTestCase extends DrupalWebTestCase {
|
||||
module_load_include('inc', 'features', 'features.export');
|
||||
|
||||
$components = array_filter(array(
|
||||
'field' => 'field',
|
||||
'field_instance' => 'field',
|
||||
'filter' => 'filter',
|
||||
'image' => 'image',
|
||||
'node' => 'node',
|
||||
@@ -85,7 +85,7 @@ class FeaturesUserTestCase extends DrupalWebTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
protected function _test_field($op = 'load') {
|
||||
protected function _test_field_instance($op = 'load') {
|
||||
switch ($op) {
|
||||
case 'load':
|
||||
return field_info_instance('node', 'field_features_test', 'features_test');
|
||||
@@ -218,7 +218,7 @@ class FeaturesEnableTestCase extends DrupalWebTestCase {
|
||||
|
||||
|
||||
/**
|
||||
* Tests intergration of ctools for features.
|
||||
* Tests integration of ctools for features.
|
||||
*/
|
||||
class FeaturesCtoolsIntegrationTest extends DrupalWebTestCase {
|
||||
protected $profile = 'testing';
|
||||
@@ -266,6 +266,13 @@ class FeaturesCtoolsIntegrationTest extends DrupalWebTestCase {
|
||||
$this->assertFalse(function_exists($function), 'Chaos tools functions for ' . $module . ' do not exist while it is disabled.');
|
||||
// Module enable will trigger declaring the new functions.
|
||||
module_enable(array($module));
|
||||
}
|
||||
|
||||
// CTools hooks only created when there is an actual feature exportable
|
||||
// enabled.
|
||||
module_enable(array('features_test'));
|
||||
|
||||
foreach ($try as $module) {
|
||||
if (module_exists($module)) {
|
||||
$function_exists = function_exists($function);
|
||||
if ($function_exists) {
|
||||
@@ -280,3 +287,43 @@ class FeaturesCtoolsIntegrationTest extends DrupalWebTestCase {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test detecting modules as features.
|
||||
*/
|
||||
class FeaturesDetectionTestCase extends DrupalWebTestCase {
|
||||
protected $profile = 'testing';
|
||||
|
||||
/**
|
||||
* Test info.
|
||||
*/
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => t('Feature Detection tests'),
|
||||
'description' => t('Run tests for detecting items as features.') ,
|
||||
'group' => t('Features'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up test.
|
||||
*/
|
||||
public function setUp() {
|
||||
parent::setUp(array(
|
||||
'features',
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Run test.
|
||||
*/
|
||||
public function test() {
|
||||
module_load_include('inc', 'features', 'features.export');
|
||||
// First test that features_populate inserts the features api key.
|
||||
$export = features_populate(array(), array(), 'features_test_empty_fake');
|
||||
$this->assertTrue(!empty($export['features']['features_api']) && key($export['features']['features_api']) == 'api:' . FEATURES_API, 'Features API key added to new export.');
|
||||
$this->assertTrue((bool)features_get_features('features_test'), 'Features test recognized as a feature.');
|
||||
$this->assertFalse((bool)features_get_features('features'), 'Features module not recognized as a feature.');
|
||||
}
|
||||
}
|
||||
|
@@ -1,104 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* features_test.features.field.inc
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_field_default_fields().
|
||||
*/
|
||||
function features_test_field_default_fields() {
|
||||
$fields = array();
|
||||
|
||||
// Exported field: 'node-features_test-field_features_test'
|
||||
$fields['node-features_test-field_features_test'] = array(
|
||||
'field_config' => array(
|
||||
'active' => '1',
|
||||
'cardinality' => '1',
|
||||
'deleted' => '0',
|
||||
'entity_types' => array(),
|
||||
'field_name' => 'field_features_test',
|
||||
'foreign keys' => array(
|
||||
'format' => array(
|
||||
'columns' => array(
|
||||
'format' => 'format',
|
||||
),
|
||||
'table' => 'filter_format',
|
||||
),
|
||||
),
|
||||
'indexes' => array(
|
||||
'format' => array(
|
||||
0 => 'format',
|
||||
),
|
||||
),
|
||||
'module' => 'text',
|
||||
'settings' => array(
|
||||
'max_length' => '255',
|
||||
),
|
||||
'translatable' => '1',
|
||||
'type' => 'text',
|
||||
),
|
||||
'field_instance' => array(
|
||||
'bundle' => 'features_test',
|
||||
'default_value' => NULL,
|
||||
'deleted' => '0',
|
||||
'description' => '',
|
||||
'display' => array(
|
||||
'default' => array(
|
||||
'label' => 'above',
|
||||
'module' => 'text',
|
||||
'settings' => array(),
|
||||
'type' => 'text_default',
|
||||
'weight' => 0,
|
||||
),
|
||||
'full' => array(
|
||||
'label' => 'above',
|
||||
'settings' => array(),
|
||||
'type' => 'hidden',
|
||||
'weight' => 0,
|
||||
),
|
||||
'print' => array(
|
||||
'label' => 'above',
|
||||
'settings' => array(),
|
||||
'type' => 'hidden',
|
||||
'weight' => 0,
|
||||
),
|
||||
'rss' => array(
|
||||
'label' => 'above',
|
||||
'settings' => array(),
|
||||
'type' => 'hidden',
|
||||
'weight' => 0,
|
||||
),
|
||||
'teaser' => array(
|
||||
'label' => 'above',
|
||||
'settings' => array(),
|
||||
'type' => 'hidden',
|
||||
'weight' => 0,
|
||||
),
|
||||
),
|
||||
'entity_type' => 'node',
|
||||
'field_name' => 'field_features_test',
|
||||
'label' => 'Test',
|
||||
'required' => 0,
|
||||
'settings' => array(
|
||||
'text_processing' => '0',
|
||||
'user_register_form' => FALSE,
|
||||
),
|
||||
'widget' => array(
|
||||
'active' => 1,
|
||||
'module' => 'text',
|
||||
'settings' => array(
|
||||
'size' => '60',
|
||||
),
|
||||
'type' => 'text_textfield',
|
||||
'weight' => '-4',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Translatables
|
||||
// Included for use with string extractors like potx.
|
||||
t('Test');
|
||||
|
||||
return $fields;
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* features_test.features.field_base.inc
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_field_default_field_bases().
|
||||
*/
|
||||
function features_test_field_default_field_bases() {
|
||||
$field_bases = array();
|
||||
|
||||
// Exported field_base: 'field_features_test'
|
||||
$field_bases['field_features_test'] = array(
|
||||
'active' => 1,
|
||||
'cardinality' => 1,
|
||||
'deleted' => 0,
|
||||
'entity_types' => array(),
|
||||
'field_name' => 'field_features_test',
|
||||
'foreign keys' => array(
|
||||
'format' => array(
|
||||
'columns' => array(
|
||||
'format' => 'format',
|
||||
),
|
||||
'table' => 'filter_format',
|
||||
),
|
||||
),
|
||||
'indexes' => array(
|
||||
'format' => array(
|
||||
0 => 'format',
|
||||
),
|
||||
),
|
||||
'locked' => 0,
|
||||
'module' => 'text',
|
||||
'settings' => array(
|
||||
'max_length' => 255,
|
||||
),
|
||||
'translatable' => 1,
|
||||
'type' => 'text',
|
||||
);
|
||||
|
||||
return $field_bases;
|
||||
}
|
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* features_test.features.field_instance.inc
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_field_default_field_instances().
|
||||
*/
|
||||
function features_test_field_default_field_instances() {
|
||||
$field_instances = array();
|
||||
|
||||
// Exported field_instance: 'node-features_test-field_features_test'
|
||||
$field_instances['node-features_test-field_features_test'] = array(
|
||||
'bundle' => 'features_test',
|
||||
'default_value' => NULL,
|
||||
'deleted' => 0,
|
||||
'description' => '',
|
||||
'display' => array(
|
||||
'default' => array(
|
||||
'label' => 'above',
|
||||
'module' => 'text',
|
||||
'settings' => array(),
|
||||
'type' => 'text_default',
|
||||
'weight' => 0,
|
||||
),
|
||||
'full' => array(
|
||||
'label' => 'above',
|
||||
'settings' => array(),
|
||||
'type' => 'hidden',
|
||||
'weight' => 0,
|
||||
),
|
||||
'print' => array(
|
||||
'label' => 'above',
|
||||
'settings' => array(),
|
||||
'type' => 'hidden',
|
||||
'weight' => 0,
|
||||
),
|
||||
'rss' => array(
|
||||
'label' => 'above',
|
||||
'settings' => array(),
|
||||
'type' => 'hidden',
|
||||
'weight' => 0,
|
||||
),
|
||||
'teaser' => array(
|
||||
'label' => 'above',
|
||||
'settings' => array(),
|
||||
'type' => 'hidden',
|
||||
'weight' => 0,
|
||||
),
|
||||
),
|
||||
'entity_type' => 'node',
|
||||
'field_name' => 'field_features_test',
|
||||
'label' => 'Test',
|
||||
'required' => 0,
|
||||
'settings' => array(
|
||||
'text_processing' => 0,
|
||||
'user_register_form' => FALSE,
|
||||
),
|
||||
'widget' => array(
|
||||
'active' => 1,
|
||||
'module' => 'text',
|
||||
'settings' => array(
|
||||
'size' => 60,
|
||||
),
|
||||
'type' => 'text_textfield',
|
||||
'weight' => -4,
|
||||
),
|
||||
);
|
||||
|
||||
// Translatables
|
||||
// Included for use with string extractors like potx.
|
||||
t('Test');
|
||||
|
||||
return $field_instances;
|
||||
}
|
@@ -10,22 +10,22 @@
|
||||
function features_test_filter_default_formats() {
|
||||
$formats = array();
|
||||
|
||||
// Exported format: features_test
|
||||
// Exported format: features_test.
|
||||
$formats['features_test'] = array(
|
||||
'format' => 'features_test',
|
||||
'name' => 'features_test',
|
||||
'cache' => '1',
|
||||
'status' => '1',
|
||||
'weight' => '0',
|
||||
'cache' => 1,
|
||||
'status' => 1,
|
||||
'weight' => 0,
|
||||
'filters' => array(
|
||||
'filter_autop' => array(
|
||||
'weight' => '10',
|
||||
'status' => '1',
|
||||
'weight' => 10,
|
||||
'status' => 1,
|
||||
'settings' => array(),
|
||||
),
|
||||
'filter_html' => array(
|
||||
'weight' => '10',
|
||||
'status' => '1',
|
||||
'weight' => 10,
|
||||
'status' => 1,
|
||||
'settings' => array(
|
||||
'allowed_html' => '<a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>',
|
||||
'filter_html_help' => 1,
|
||||
@@ -33,20 +33,20 @@ function features_test_filter_default_formats() {
|
||||
),
|
||||
),
|
||||
'filter_htmlcorrector' => array(
|
||||
'weight' => '10',
|
||||
'status' => '1',
|
||||
'weight' => 10,
|
||||
'status' => 1,
|
||||
'settings' => array(),
|
||||
),
|
||||
'filter_html_escape' => array(
|
||||
'weight' => '10',
|
||||
'status' => '1',
|
||||
'weight' => 10,
|
||||
'status' => 1,
|
||||
'settings' => array(),
|
||||
),
|
||||
'filter_url' => array(
|
||||
'weight' => '10',
|
||||
'status' => '1',
|
||||
'weight' => 10,
|
||||
'status' => 1,
|
||||
'settings' => array(
|
||||
'filter_url_length' => '72',
|
||||
'filter_url_length' => 72,
|
||||
),
|
||||
),
|
||||
),
|
@@ -18,7 +18,7 @@ function features_test_ctools_plugin_api() {
|
||||
* Implements hook_views_api().
|
||||
*/
|
||||
function features_test_views_api() {
|
||||
return array("version" => "3.0");
|
||||
return array("api" => "3.0");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -27,27 +27,20 @@ function features_test_views_api() {
|
||||
function features_test_image_default_styles() {
|
||||
$styles = array();
|
||||
|
||||
// Exported image style: features_test
|
||||
// Exported image style: features_test.
|
||||
$styles['features_test'] = array(
|
||||
'name' => 'features_test',
|
||||
'effects' => array(
|
||||
2 => array(
|
||||
'label' => 'Scale',
|
||||
'help' => 'Scaling will maintain the aspect-ratio of the original image. If only a single dimension is specified, the other dimension will be calculated.',
|
||||
'effect callback' => 'image_scale_effect',
|
||||
'dimensions callback' => 'image_scale_dimensions',
|
||||
'form callback' => 'image_scale_form',
|
||||
'summary theme' => 'image_scale_summary',
|
||||
'module' => 'image',
|
||||
'name' => 'image_scale',
|
||||
'data' => array(
|
||||
'width' => '100',
|
||||
'height' => '100',
|
||||
'width' => 100,
|
||||
'height' => 100,
|
||||
'upscale' => 0,
|
||||
),
|
||||
'weight' => '1',
|
||||
'weight' => 1,
|
||||
),
|
||||
),
|
||||
'label' => 'features_test',
|
||||
);
|
||||
|
||||
return $styles;
|
@@ -13,9 +13,9 @@ function features_test_taxonomy_default_vocabularies() {
|
||||
'name' => 'Taxonomy Features Test',
|
||||
'machine_name' => 'taxonomy_features_test',
|
||||
'description' => 'Taxonomy vocabulary',
|
||||
'hierarchy' => '0',
|
||||
'hierarchy' => 0,
|
||||
'module' => 'taxonomy',
|
||||
'weight' => '0',
|
||||
'weight' => 0,
|
||||
'rdf_mapping' => array(
|
||||
'rdftype' => array(
|
||||
0 => 'skos:ConceptScheme',
|
@@ -14,8 +14,8 @@ function features_test_user_default_permissions() {
|
||||
$permissions['create features_test content'] = array(
|
||||
'name' => 'create features_test content',
|
||||
'roles' => array(
|
||||
0 => 'anonymous user',
|
||||
1 => 'authenticated user',
|
||||
'anonymous user' => 'anonymous user',
|
||||
'authenticated user' => 'authenticated user',
|
||||
),
|
||||
'module' => 'node',
|
||||
);
|
@@ -10,19 +10,20 @@ dependencies[] = taxonomy
|
||||
dependencies[] = views
|
||||
features[ctools][] = strongarm:strongarm:1
|
||||
features[ctools][] = views:views_default:3.0
|
||||
features[features_api][] = api:1
|
||||
features[field][] = node-features_test-field_features_test
|
||||
features[features_api][] = api:2
|
||||
features[field_base][] = field_features_test
|
||||
features[field_instance][] = node-features_test-field_features_test
|
||||
features[filter][] = features_test
|
||||
features[image][] = features_test
|
||||
features[node][] = features_test
|
||||
features[taxonomy][] = taxonomy_features_test
|
||||
features[user_permission][] = create features_test content
|
||||
features[views_view][] = features_test
|
||||
hidden = TRUE
|
||||
hidden = 1
|
||||
|
||||
; Information added by drupal.org packaging script on 2012-12-08
|
||||
version = "7.x-2.0-beta1+5-dev"
|
||||
; Information added by Drupal.org packaging script on 2016-04-18
|
||||
version = "7.x-2.10"
|
||||
core = "7.x"
|
||||
project = "features"
|
||||
datestamp = "1354928180"
|
||||
datestamp = "1461011641"
|
||||
|
@@ -10,7 +10,7 @@
|
||||
function features_test_views_default_views() {
|
||||
$export = array();
|
||||
|
||||
$view = new view;
|
||||
$view = new view();
|
||||
$view->name = 'features_test';
|
||||
$view->description = 'Test view provided by Features testing module.';
|
||||
$view->tag = 'testing';
|
||||
@@ -23,6 +23,7 @@ function features_test_views_default_views() {
|
||||
/* Display: Defaults */
|
||||
$handler = $view->new_display('default', 'Defaults', 'default');
|
||||
$handler->display->display_options['title'] = 'Test';
|
||||
$handler->display->display_options['use_more_always'] = FALSE;
|
||||
$handler->display->display_options['access']['type'] = 'none';
|
||||
$handler->display->display_options['cache']['type'] = 'none';
|
||||
$handler->display->display_options['query']['type'] = 'views_query';
|
@@ -3,7 +3,7 @@
|
||||
<div class='clearfix features-components'>
|
||||
<div class='column'>
|
||||
<div class='info'>
|
||||
<h3><?php print $name ?></h3>
|
||||
<?php print $lock_feature ?><h3><?php print $name ?></h3>
|
||||
<div class='description'><?php print $description ?></div>
|
||||
<?php print $dependencies ?>
|
||||
</div>
|
||||
|
@@ -81,6 +81,7 @@ function template_preprocess_features_admin_components(&$vars) {
|
||||
// Other elements
|
||||
$vars['buttons'] = drupal_render($form['buttons']);
|
||||
$vars['form'] = $form;
|
||||
$vars['lock_feature'] = theme('features_lock_link', array('feature' => $form['#feature']->name));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,6 +110,36 @@ function theme_features_module_status($vars) {
|
||||
return "<span class=\"$class\">$text</span>";
|
||||
}
|
||||
|
||||
/**
|
||||
* Themes a lock link
|
||||
*/
|
||||
function theme_features_lock_link($vars) {
|
||||
drupal_add_library('system', 'ui');
|
||||
drupal_add_library('system', 'drupal.ajax');
|
||||
$component = $vars['component'] ? $vars['component'] : '';
|
||||
if ($component && features_component_is_locked($component)) {
|
||||
return l(t('Component locked'), 'admin/structure/features/settings', array(
|
||||
'attributes' => array(
|
||||
'class' => 'features-lock-icon ui-icon ui-icon-locked',
|
||||
'title' => t('This component is locked on a global level.'),
|
||||
),
|
||||
'fragment' => 'edit-lock-components',
|
||||
));
|
||||
}
|
||||
$feature = $vars['feature'];
|
||||
$is_locked = features_feature_is_locked($feature, $component);
|
||||
$options = array(
|
||||
'attributes' => array(
|
||||
'class' => array('use-ajax features-lock-icon ui-icon ' . ($is_locked ? ' ui-icon-locked' : ' ui-icon-unlocked')),
|
||||
'id' => 'features-lock-link-' . $feature . ($component ? '-' . $component : ''),
|
||||
'title' => $is_locked ? t('This item is locked and features will not be rebuilt or reverted.') : t('This item is unlocked and will be rebuilt/reverted as normal.'),
|
||||
),
|
||||
'query' => array('token' => drupal_get_token('features/' . $feature . '/' . $component)),
|
||||
);
|
||||
$path = "admin/structure/features/" . $feature . "/lock/nojs" . ($component ? '/' . $component: '');
|
||||
return l($is_locked ? t('UnLock') : t('Lock'), $path, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Themes a module status display.
|
||||
*/
|
||||
|
Reference in New Issue
Block a user