updated features

This commit is contained in:
Bachir Soussi Chiadmi 2016-04-19 16:32:54 +02:00
parent fb0666538c
commit e2fde76aff
13 changed files with 295 additions and 190 deletions

View File

@ -88,6 +88,12 @@ function features_settings_form($form, $form_state) {
'#default_value' => variable_get('features_rebuild_on_flush', TRUE), '#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.'), '#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); return system_settings_form($form);
} }
@ -109,7 +115,7 @@ function features_export_form($form, $form_state, $feature = NULL) {
$feature_name = !empty($feature->name) ? $feature->name : ''; $feature_name = !empty($feature->name) ? $feature->name : '';
$form = array( $form = array(
'#attributes' => array('class' => array('features-export-form')), '#attributes' => array('class' => array('features-export-form', 'clearfix')),
'#feature' => isset($feature) ? $feature : NULL, '#feature' => isset($feature) ? $feature : NULL,
); );
$form['info'] = array( $form['info'] = array(
@ -756,7 +762,7 @@ function features_export_form_rebuild($form, &$form_state) {
function features_export_components_json($feature_name) { function features_export_components_json($feature_name) {
module_load_include('inc', 'features', 'features.export'); module_load_include('inc', 'features', 'features.export');
$export = array(); $export = array('features' => array());
if (!empty($_POST['items'])) { if (!empty($_POST['items'])) {
$excluded = (!empty($_POST['excluded'])) ? $_POST['excluded'] : array(); $excluded = (!empty($_POST['excluded'])) ? $_POST['excluded'] : array();
$stub = array(); $stub = array();
@ -1143,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 // 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 // the array. We add it late, but at the beginning of the array because that
// keeps us away from trouble. // 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( $form['buttons'] = array(
'#theme' => 'features_form_buttons', '#theme' => 'features_form_buttons',
@ -1328,7 +1334,7 @@ function features_form_submit(&$form, &$form_state) {
// page callback rather than as part of the submit handler as some modules // page callback rather than as part of the submit handler as some modules
// have includes/other directives of importance in hooks that have already // have includes/other directives of importance in hooks that have already
// been called in this page load. // 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']; $features = $form['#features'];
if (!empty($features)) { if (!empty($features)) {
@ -1352,22 +1358,20 @@ function features_form_rebuild() {
} }
/** /**
* Form for clearing cache after enabling a feature. * Callback for clearing cache after enabling a feature.
*/ */
function features_cleanup_form($form, $form_state, $cache_clear = FALSE) { function features_cleanup() {
// Clear caches if we're getting a post-submit redirect that requests it. if (!empty($_GET['token']) && drupal_valid_token($_GET['token'])) {
if ($cache_clear) {
drupal_flush_all_caches(); drupal_flush_all_caches();
// The following functions need to be run because 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* // 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 // the menu is rebuilt, meaning that the menu tree is stale in certain
// circumstances after drupal_flush_all_caches(). We rebuild again. // circumstances after drupal_flush_all_caches(). We rebuild again.
menu_rebuild(); menu_rebuild();
}
drupal_goto('admin/structure/features'); drupal_goto('admin/structure/features');
} }
return MENU_NOT_FOUND;
}
/** /**
* Page callback to display the differences between what's in code and * Page callback to display the differences between what's in code and

View File

@ -42,7 +42,7 @@
* are declared "dynamically" or are part of a family of components. * are declared "dynamically" or are part of a family of components.
* *
* 'alter_type': What type of alter hook this hook uses. 'normal' is called * '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. * and may not be implemented by some default hooks.
* 'none' is no alter hook exists. Defaults to 'normal' * 'none' is no alter hook exists. Defaults to 'normal'
* *
@ -310,7 +310,7 @@ function hook_features_pipe_COMPONENT_alter(&$pipe, $data, $export) {
* The module being exported contained in $export['module_name']. * The module being exported contained in $export['module_name'].
*/ */
function hook_features_pipe_alter(&$pipe, $data, $export) { 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'; $pipe['dependencies'][] = 'mymodule';
} }
} }

View File

@ -32,6 +32,12 @@ function features_drush_command() {
), ),
'drupal dependencies' => array('features'), 'drupal dependencies' => array('features'),
'aliases' => array('fl', '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( $items['features-export'] = array(
'description' => "Export a feature from your site into a module.", 'description' => "Export a feature from your site into a module.",
@ -207,12 +213,12 @@ function drush_features_list() {
} }
module_load_include('inc', 'features', 'features.export'); module_load_include('inc', 'features', 'features.export');
$rows = array(array(dt('Name'), dt('Feature'), dt('Status'), dt('Version'), dt('State')));
// Sort the Features list before compiling the output. // Sort the Features list before compiling the output.
$features = features_get_features(NULL, TRUE); $features = features_get_features(NULL, TRUE);
ksort($features); ksort($features);
$rows = array();
foreach ($features as $k => $m) { foreach ($features as $k => $m) {
switch (features_get_storage($m->name)) { switch (features_get_storage($m->name)) {
case FEATURES_DEFAULT: case FEATURES_DEFAULT:
@ -230,17 +236,20 @@ function drush_features_list() {
($m->status == 0 && ($status == 'all' || $status == 'disabled')) || ($m->status == 0 && ($status == 'all' || $status == 'disabled')) ||
($m->status == 1 && ($status == 'all' || $status == 'enabled')) ($m->status == 1 && ($status == 'all' || $status == 'enabled'))
) { ) {
$rows[] = array( $rows[$k] = array(
$m->info['name'], 'name' => $m->info['name'],
$m->name, 'feature' => $m->name,
$m->status ? dt('Enabled') : dt('Disabled'), 'status' => $m->status ? dt('Enabled') : dt('Disabled'),
$m->info['version'], 'version' => $m->info['version'],
$storage 'state' => $storage
); );
} }
} }
if (version_compare(DRUSH_VERSION, '6.0', '<')) {
drush_print_table($rows, TRUE); drush_print_table($rows, TRUE);
} }
return $rows;
}
/** /**
* List components, with pattern matching. * List components, with pattern matching.
@ -337,8 +346,13 @@ function _drush_features_component_filter($all_components, $patterns = array(),
// Rewrite * to %. Let users use both as wildcard. // Rewrite * to %. Let users use both as wildcard.
$pattern = strtr($pattern, array('*' => '%')); $pattern = strtr($pattern, array('*' => '%'));
$sources = array(); $sources = array();
$source_pattern = strtok($pattern, ':'); if (strpos($pattern, ':') !== FALSE) {
$component_pattern = strtok(':'); list($source_pattern, $component_pattern) = explode(':', $pattern, 2);
}
else {
$source_pattern = $pattern;
$component_pattern = '';
}
// If source is empty, use a pattern. // If source is empty, use a pattern.
if ($source_pattern == '') { if ($source_pattern == '') {
$source_pattern = '%'; $source_pattern = '%';
@ -607,9 +621,18 @@ function _drush_features_export($info, $module_name = NULL, $directory = NULL) {
drush_op('mkdir', $directory); drush_op('mkdir', $directory);
} }
if (is_dir($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(); drupal_flush_all_caches();
$export = _drush_features_generate_export($info, $module_name); $export = _drush_features_generate_export($info, $module_name);
$files = features_export_render($export, $module_name, TRUE); $files = features_export_render($export, $module_name, TRUE);
// Restore the language
_features_export_language($language);
// Copy any files if _files key is there. // Copy any files if _files key is there.
if (!empty($files['_files'])) { if (!empty($files['_files'])) {
foreach ($files['_files'] as $file_name => $file_info) { foreach ($files['_files'] as $file_name => $file_info) {
@ -685,7 +708,7 @@ function _drush_features_generate_export(&$info, &$module_name) {
} }
else { else {
// Split version number parts. // Split version number parts.
$pattern = '/([0-9]-[a-z]+([0-9])+)/'; $pattern = '/([0-9]-[a-z]+([0-9]+))/';
$matches = array(); $matches = array();
preg_match($pattern, $version_minor, $matches); preg_match($pattern, $version_minor, $matches);
$number = array_pop($matches); $number = array_pop($matches);
@ -797,7 +820,7 @@ function drush_features_revert() {
} }
} }
else { else {
drush_features_list(); drush_print_table(drush_features_list());
return; return;
} }
} }
@ -851,7 +874,7 @@ function drush_features_revert_all() {
*/ */
function drush_features_diff() { function drush_features_diff() {
if (!$args = func_get_args()) { if (!$args = func_get_args()) {
drush_features_list(); drush_print_table(drush_features_list());
return; return;
} }
$module = $args[0]; $module = $args[0];

View File

@ -46,7 +46,7 @@ function features_populate($info, $module_name) {
*/ */
function _features_populate($pipe, &$export, $module_name = '', $reset = FALSE) { function _features_populate($pipe, &$export, $module_name = '', $reset = FALSE) {
// Ensure that the export will be created in the english language. // Ensure that the export will be created in the english language.
_features_set_export_language(); $language = _features_export_language();
if ($reset) { if ($reset) {
drupal_static_reset(__FUNCTION__); drupal_static_reset(__FUNCTION__);
@ -92,6 +92,7 @@ function _features_populate($pipe, &$export, $module_name = '', $reset = FALSE)
} }
} }
} }
_features_export_language($language);
return $export; return $export;
} }
@ -843,6 +844,13 @@ function features_get_default($component, $module_name = NULL, $alter = TRUE, $r
/** /**
* Get a map of components to their providing modules. * 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) { function features_get_default_map($component, $attribute = NULL, $callback = NULL, $reset = FALSE) {
$map = &drupal_static(__FUNCTION__, array()); $map = &drupal_static(__FUNCTION__, array());
@ -891,6 +899,9 @@ function features_get_default_map($component, $attribute = NULL, $callback = NUL
* Retrieve an array of features/components and their current states. * Retrieve an array of features/components and their current states.
*/ */
function features_get_component_states($features = array(), $rebuild_only = TRUE, $reset = FALSE) { function features_get_component_states($features = array(), $rebuild_only = TRUE, $reset = FALSE) {
// Ensure that the export will be created in the English language.
$language = _features_export_language();
if ($reset) { if ($reset) {
drupal_static_reset(__FUNCTION__); drupal_static_reset(__FUNCTION__);
} }
@ -901,7 +912,7 @@ function features_get_component_states($features = array(), $rebuild_only = TRUE
// Retrieve only rebuildable components if requested. // Retrieve only rebuildable components if requested.
features_include(); features_include();
$components = array_keys(features_get_components()); $components = array_keys(features_get_components(NULL, NULL, $reset));
if ($rebuild_only) { if ($rebuild_only) {
foreach ($components as $k => $component) { foreach ($components as $k => $component) {
if (!features_hook($component, 'features_rebuild')) { if (!features_hook($component, 'features_rebuild')) {
@ -984,6 +995,9 @@ function features_get_component_states($features = array(), $rebuild_only = TRUE
foreach ($return as $k => $v) { foreach ($return as $k => $v) {
$return[$k] = array_intersect_key($return[$k], array_flip($components)); $return[$k] = array_intersect_key($return[$k], array_flip($components));
} }
_features_export_language($language);
return $return; return $return;
} }
@ -1006,17 +1020,14 @@ function _features_linetrim($code) {
* @param bool $remove_empty if set, remove null or empty values for assoc arrays. * @param bool $remove_empty if set, remove null or empty values for assoc arrays.
*/ */
function features_sanitize(&$array, $component = NULL, $remove_empty = TRUE) { function features_sanitize(&$array, $component = NULL, $remove_empty = TRUE) {
// make a deep copy of data to prevent problems when removing recursion later. $array = features_remove_recursion($array);
$array = unserialize(serialize($array));
if (isset($component)) { if (isset($component)) {
$ignore_keys = _features_get_ignore_keys($component); $ignore_keys = _features_get_ignore_keys($component);
// remove keys to be ignored // remove keys to be ignored
// doing this now allows us to better control which recursive parts are removed
if (count($ignore_keys)) { if (count($ignore_keys)) {
_features_remove_ignores($array, $ignore_keys); _features_remove_ignores($array, $ignore_keys);
} }
} }
features_remove_recursion($array);
_features_sanitize($array, $remove_empty); _features_sanitize($array, $remove_empty);
} }
@ -1069,81 +1080,45 @@ function _features_is_assoc($array) {
/** /**
* Removes recursion from an object or array. * Removes recursion from an object or array.
* *
* @param $item * Taken from https://code.google.com/p/formaldehyde/source/browse/trunk/formaldehyde.php
* An object or array passed by reference. * 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(&$item) { function features_remove_recursion($o) {
$uniqid = __FUNCTION__ . mt_rand(); // use of uniqid() here impacts performance static $replace;
$stack = array(); if (!isset($replace)) {
return _features_remove_recursion($item, $stack, $uniqid); $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]+);#';
* Helper to removes recursion from an object/array. $serialize = serialize($o);
* if (preg_match($re, $serialize)) {
* @param $item $last = $pos = 0;
* An object or array passed by reference. while (false !== ($pos = strpos($serialize, 's:', $pos))) {
*/ $chunk = substr($serialize, $last, $pos - $last);
function _features_remove_recursion(&$object, &$stack = array(), $uniqid) { if (preg_match($re, $chunk)) {
if ((is_object($object) || is_array($object)) && $object) { $length = strlen($chunk);
$in_stack = FALSE; $chunk = preg_replace_callback($re, $replace, $chunk);
foreach ($stack as &$item) { $serialize = substr($serialize, 0, $last) . $chunk . substr($serialize, $last + ($pos - $last));
if (_features_is_ref_to($object, $item, $uniqid)) { $pos += strlen($chunk) - $length;
$in_stack = TRUE; }
break; $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);
} }
} }
unset($item); return $o;
if (!$in_stack) {
$stack[] = $object;
foreach ($object as $key => &$subobject) {
if (_features_remove_recursion($subobject, $stack, $uniqid)) {
if (is_object($object)) {
unset($object->$key);
}
else {
unset($object[$key]);
}
}
}
unset($subobject);
}
else {
return TRUE;
}
}
return FALSE;
}
/**
* Helper function in determining equality of arrays. Credit to http://stackoverflow.com/a/4263181
*
* @see _features_remove_recursion()
*
* @param $a
* object a
* @param $b
* object b
* @return bool
*
*/
function _features_is_ref_to(&$a, &$b, $uniqid) {
if (is_object($a) && is_object($b)) {
return ($a === $b);
}
$temp_a = $a;
$temp_b = $b;
$b = $uniqid;
if ($a === $uniqid) $return = true;
else $return = false;
$a = $temp_a;
$b = $temp_b;
return $return;
} }
/** /**
@ -1162,7 +1137,7 @@ function _features_remove_ignores(&$item, $ignore_keys, $level = -1) {
if (!is_array($item) && !is_object($item)) { if (!is_array($item) && !is_object($item)) {
return; return;
} }
foreach ($item as $key => $value) { foreach ($item as $key => &$value) {
if (isset($ignore_keys[$key]) && ($ignore_keys[$key] == $level)) { if (isset($ignore_keys[$key]) && ($ignore_keys[$key] == $level)) {
if ($is_object) { if ($is_object) {
unset($item->$key); unset($item->$key);
@ -1175,6 +1150,7 @@ function _features_remove_ignores(&$item, $ignore_keys, $level = -1) {
_features_remove_ignores($value, $ignore_keys, $level+1); _features_remove_ignores($value, $ignore_keys, $level+1);
} }
} }
unset($value);
} }
/** /**

View File

@ -3,12 +3,16 @@ description = "Provides feature management for Drupal."
core = 7.x core = 7.x
package = "Features" package = "Features"
files[] = tests/features.test files[] = tests/features.test
test_dependencies[] = image
test_dependencies[] = strongarm
test_dependencies[] = taxonomy
test_dependencies[] = views
configure = admin/structure/features/settings configure = admin/structure/features/settings
; Information added by Drupal.org packaging script on 2015-06-24 ; Information added by Drupal.org packaging script on 2016-04-18
version = "7.x-2.6" version = "7.x-2.10"
core = "7.x" core = "7.x"
project = "features" project = "features"
datestamp = "1435165997" datestamp = "1461011641"

View File

@ -84,8 +84,7 @@ function features_menu() {
$items['admin/structure/features/cleanup'] = array( $items['admin/structure/features/cleanup'] = array(
'title' => 'Cleanup', 'title' => 'Cleanup',
'description' => 'Clear cache after enabling/disabling a feature.', 'description' => 'Clear cache after enabling/disabling a feature.',
'page callback' => 'drupal_get_form', 'page callback' => 'features_cleanup',
'page arguments' => array('features_cleanup_form', 4),
'type' => MENU_CALLBACK, 'type' => MENU_CALLBACK,
'file' => 'features.admin.inc', 'file' => 'features.admin.inc',
'weight' => 1, 'weight' => 1,
@ -128,7 +127,7 @@ function features_menu() {
'description' => 'Display components of a feature.', 'description' => 'Display components of a feature.',
'page callback' => 'drupal_get_form', 'page callback' => 'drupal_get_form',
'page arguments' => array('features_admin_components', 3), 'page arguments' => array('features_admin_components', 3),
'load arguments' => array(3, TRUE), 'load arguments' => array(TRUE),
'access callback' => 'user_access', 'access callback' => 'user_access',
'access arguments' => array('administer features'), 'access arguments' => array('administer features'),
'type' => MENU_CALLBACK, 'type' => MENU_CALLBACK,
@ -147,7 +146,7 @@ function features_menu() {
'description' => 'Recreate an existing feature.', 'description' => 'Recreate an existing feature.',
'page callback' => 'drupal_get_form', 'page callback' => 'drupal_get_form',
'page arguments' => array('features_export_form', 3), 'page arguments' => array('features_export_form', 3),
'load arguments' => array(3, TRUE), 'load arguments' => array(TRUE),
'access callback' => 'user_access', 'access callback' => 'user_access',
'access arguments' => array('administer features'), 'access arguments' => array('administer features'),
'type' => MENU_LOCAL_TASK, 'type' => MENU_LOCAL_TASK,
@ -160,7 +159,7 @@ function features_menu() {
'description' => 'Compare default and current feature.', 'description' => 'Compare default and current feature.',
'page callback' => 'features_feature_diff', 'page callback' => 'features_feature_diff',
'page arguments' => array(3, 5), 'page arguments' => array(3, 5),
'load arguments' => array(3, TRUE), 'load arguments' => array(TRUE),
'access callback' => 'features_access_override_actions', 'access callback' => 'features_access_override_actions',
'access arguments' => array(3), 'access arguments' => array(3),
'type' => MENU_LOCAL_TASK, 'type' => MENU_LOCAL_TASK,
@ -173,7 +172,7 @@ function features_menu() {
'description' => 'Lock a feature or components.', 'description' => 'Lock a feature or components.',
'page callback' => 'features_admin_lock', 'page callback' => 'features_admin_lock',
'page arguments' => array(3, 5, 6), 'page arguments' => array(3, 5, 6),
'load arguments' => array(3, TRUE, TRUE), 'load arguments' => array(TRUE),
'access arguments' => array('administer features'), 'access arguments' => array('administer features'),
'type' => MENU_CALLBACK, 'type' => MENU_CALLBACK,
'file' => 'features.admin.inc', 'file' => 'features.admin.inc',
@ -183,7 +182,7 @@ function features_menu() {
'description' => 'Javascript status call back.', 'description' => 'Javascript status call back.',
'page callback' => 'features_feature_status', 'page callback' => 'features_feature_status',
'page arguments' => array(3), 'page arguments' => array(3),
'load arguments' => array(3, TRUE), 'load arguments' => array(TRUE),
'access callback' => 'user_access', 'access callback' => 'user_access',
'access arguments' => array('administer features'), 'access arguments' => array('administer features'),
'type' => MENU_CALLBACK, 'type' => MENU_CALLBACK,
@ -278,8 +277,12 @@ function features_flush_caches() {
features_get_modules(NULL, TRUE); features_get_modules(NULL, TRUE);
} }
} }
if (db_table_exists('cache_features')) {
return array('cache_features'); return array('cache_features');
} }
return array();
}
/** /**
* Implements hook_form(). * Implements hook_form().
@ -515,7 +518,7 @@ function features_load_feature($name, $reset = FALSE) {
* Return a module 'object' including .info information. * Return a module 'object' including .info information.
* *
* @param $name * @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. * an array of all available modules will be returned.
* @param $reset * @param $reset
* Whether to reset the cache. * Whether to reset the cache.
@ -757,9 +760,9 @@ function features_get_info($type = 'module', $name = NULL, $reset = FALSE) {
$cache->data = $data; $cache->data = $data;
} }
if (!empty($name)) { 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;
} }
/** /**
@ -906,7 +909,7 @@ function features_access_override_actions($feature) {
features_include(); features_include();
module_load_include('inc', 'features', 'features.export'); 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]; return $access[$feature->name];
} }
@ -917,8 +920,10 @@ function features_access_override_actions($feature) {
* Implements hook_form_alter() for system_modules form(). * Implements hook_form_alter() for system_modules form().
*/ */
function features_form_system_modules_alter(&$form) { function features_form_system_modules_alter(&$form) {
if (variable_get('features_rebuild_modules_page', FALSE)) {
features_rebuild(); features_rebuild();
} }
}
/** /**
* Restore the specified modules to the default state. * Restore the specified modules to the default state.
@ -1219,12 +1224,14 @@ function features_feature_unlock($feature, $component = NULL) {
} }
/** /**
* Sets the current language to english to ensure a proper export. * Sets/Returns the current language to english to ensure a proper export.
*/ */
function _features_set_export_language() { function _features_export_language($language = NULL) {
// Ensure this is only done if the language isn't already en. $current = $GLOBALS['language'];
// This can be called multiple times - ensure the handling is done just once. if (isset($language)) {
if ($GLOBALS['language']->language != 'en' && !drupal_static(__FUNCTION__)) { $GLOBALS['language'] = $language;
}
elseif ($GLOBALS['language']->language != 'en') {
// Create the language object as language_default() does. // Create the language object as language_default() does.
$GLOBALS['language'] = (object) array( $GLOBALS['language'] = (object) array(
'language' => 'en', 'language' => 'en',
@ -1239,57 +1246,8 @@ function _features_set_export_language() {
'weight' => 0, 'weight' => 0,
'javascript' => '', 'javascript' => '',
); );
// Ensure that static caches are cleared, as they might contain language
// specific information. But keep some important ones. The call below
// accesses a non existing key and requests to reset it. In such cases the
// whole caching data array is returned.
$static = drupal_static(uniqid('', TRUE), NULL, TRUE);
drupal_static_reset();
// Restore some of the language independent, runtime state information to
// keep everything working and avoid unnecessary double processing.
$static_caches_to_keep = array(
'conf_path',
'system_list',
'ip_address',
'drupal_page_is_cacheable',
'list_themes',
'drupal_page_header',
'drupal_send_headers',
'drupal_http_headers',
'language_list',
'module_implements',
'drupal_alter',
'path_is_admin',
'path_get_admin_paths',
'drupal_match_path',
'menu_get_custom_theme',
'menu_get_item',
'arg',
'drupal_system_listing',
'drupal_parse_info_file',
'libraries_get_path',
'module_hook_info',
'drupal_add_js',
'drupal_add_js:jquery_added',
'drupal_add_library',
'drupal_get_library',
'drupal_add_css',
'menu_set_active_trail',
'menu_link_get_preferred',
'menu_set_active_menu_names',
'theme_get_registry',
'features_get_components',
'features_get_components_by_key',
);
foreach ($static_caches_to_keep as $cid) {
if (isset($static[$cid])) {
$data = &drupal_static($cid);
$data = $static[$cid];
}
}
$called = &drupal_static(__FUNCTION__);
$called = TRUE;
} }
return $current;
} }
/** /**
@ -1326,6 +1284,11 @@ function features_features_ignore($component) {
case 'field': case 'field':
$ignores['locked'] = 1; $ignores['locked'] = 1;
break; break;
case 'field_base':
$ignores['indexes'] = 0;
break;
case 'taxonomy':
$ignores['hierarchy'] = 0;
} }
return $ignores; return $ignores;
} }

View 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();
}
}
}
}

View File

@ -170,10 +170,10 @@ function field_base_features_export_render($module, $data, $export = NULL) {
$field_identifier = features_var_export($identifier); $field_identifier = features_var_export($identifier);
if (features_field_export_needs_wrap($field_prefix, $field_identifier)) { if (features_field_export_needs_wrap($field_prefix, $field_identifier)) {
$code[] = rtrim($field_prefix); $code[] = rtrim($field_prefix);
$code[] = " // {$field_identifier}"; $code[] = " // {$field_identifier}.";
} }
else { else {
$code[] = $field_prefix . $field_identifier; $code[] = $field_prefix . $field_identifier . '.';
} }
$code[] = " \$field_bases[{$field_identifier}] = {$field_export};"; $code[] = " \$field_bases[{$field_identifier}] = {$field_export};";
$code[] = ""; $code[] = "";
@ -201,10 +201,10 @@ function field_instance_features_export_render($module, $data, $export = NULL) {
$instance_identifier = features_var_export($identifier); $instance_identifier = features_var_export($identifier);
if (features_field_export_needs_wrap($instance_prefix, $instance_identifier)) { if (features_field_export_needs_wrap($instance_prefix, $instance_identifier)) {
$code[] = rtrim($instance_prefix); $code[] = rtrim($instance_prefix);
$code[] = " // {$instance_identifier}"; $code[] = " // {$instance_identifier}.";
} }
else { else {
$code[] = $instance_prefix . $instance_identifier; $code[] = $instance_prefix . $instance_identifier . '.';
} }
$code[] = " \$field_instances[{$instance_identifier}] = {$field_export};"; $code[] = " \$field_instances[{$instance_identifier}] = {$field_export};";
$code[] = ""; $code[] = "";
@ -276,11 +276,21 @@ function field_base_features_rebuild($module) {
$existing_field = $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); $array_diff_result = drupal_array_diff_assoc_recursive($field + $existing_field, $existing_field);
if (!empty($array_diff_result)) { if (!empty($array_diff_result)) {
try {
field_update_field($field); 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 { else {
try {
field_create_field($field); 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; $existing_fields[$field['field_name']] = $field;
} }
variable_set('menu_rebuild_needed', TRUE); variable_set('menu_rebuild_needed', TRUE);
@ -562,5 +572,6 @@ function features_field_load($identifier) {
* @see https://www.drupal.org/node/1354 * @see https://www.drupal.org/node/1354
*/ */
function features_field_export_needs_wrap($prefix, $identifier) { function features_field_export_needs_wrap($prefix, $identifier) {
return (strlen($prefix) + strlen($identifier) > 80); // Check for 79 characters, since the comment ends with a full stop.
return (strlen($prefix) + strlen($identifier) > 79);
} }

View File

@ -141,8 +141,8 @@ function _features_language_save($language) {
} }
// Update Existing language. // Update Existing language.
else { else {
// @TODO: get properties from schema. // Get field list from table schema.
$properties = array('language', 'name', 'native', 'direction', 'enabled', 'plurals', 'formula', 'domain', 'prefix', 'weight', 'javascript'); $properties = drupal_schema_fields_sql('languages');
// The javascript hash is not in the imported data but should be empty // The javascript hash is not in the imported data but should be empty
if (!isset($language->javascript)) { if (!isset($language->javascript)) {
$language->javascript = ''; $language->javascript = '';

View File

@ -103,7 +103,6 @@ function menu_custom_features_export_render($module, $data) {
$code[] = features_translatables_export($translatables, ' '); $code[] = features_translatables_export($translatables, ' ');
} }
$code[] = '';
$code[] = ' return $menus;'; $code[] = ' return $menus;';
$code = implode("\n", $code); $code = implode("\n", $code);
return array('menu_default_menu_custom' => $code); return array('menu_default_menu_custom' => $code);
@ -248,7 +247,7 @@ function menu_links_features_export_render($module, $data, $export = NULL) {
unset($link['plid']); unset($link['plid']);
unset($link['mlid']); unset($link['mlid']);
$code[] = " // Exported menu link: {$new_identifier}"; $code[] = " // Exported menu link: {$new_identifier}.";
$code[] = " \$menu_links['{$new_identifier}'] = ". features_var_export($link, ' ') .";"; $code[] = " \$menu_links['{$new_identifier}'] = ". features_var_export($link, ' ') .";";
$translatables[] = $link['link_title']; $translatables[] = $link['link_title'];
} }
@ -316,6 +315,7 @@ function menu_links_features_rebuild_ordered($menu_links, $reset = FALSE) {
foreach ($unordered as $link) { foreach ($unordered as $link) {
$identifier = menu_links_features_identifier($link); $identifier = menu_links_features_identifier($link);
$ordered[$identifier] = 0; $ordered[$identifier] = 0;
$all_links[$identifier] = $link;
} }
asort($ordered); asort($ordered);
} }

View File

@ -145,11 +145,14 @@ function node_features_disable_feature($module) {
* When a features module is enabled, modify any node types it provides so * 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. * they can no longer be deleted manually through the content types UI.
* *
* Update the database cache of node types if needed.
*
* @param $module * @param $module
* Name of module that has been enabled. * Name of module that has been enabled.
*/ */
function node_features_enable_feature($module) { function node_features_enable_feature($module) {
if ($default_types = features_get_default('node', $module)) { if ($default_types = features_get_default('node', $module)) {
$rebuild = FALSE;
foreach ($default_types as $type_name => $type_info) { foreach ($default_types as $type_name => $type_info) {
// Ensure the type exists. // Ensure the type exists.
if ($type_info = node_type_load($type_name)) { if ($type_info = node_type_load($type_name)) {
@ -160,6 +163,12 @@ function node_features_enable_feature($module) {
$type_info->disabled = 0; $type_info->disabled = 0;
node_type_save($type_info); node_type_save($type_info);
} }
else {
$rebuild = TRUE;
}
}
if ($rebuild) {
node_types_rebuild();
} }
} }
} }

View File

@ -218,7 +218,7 @@ class FeaturesEnableTestCase extends DrupalWebTestCase {
/** /**
* Tests intergration of ctools for features. * Tests integration of ctools for features.
*/ */
class FeaturesCtoolsIntegrationTest extends DrupalWebTestCase { class FeaturesCtoolsIntegrationTest extends DrupalWebTestCase {
protected $profile = 'testing'; protected $profile = 'testing';

View File

@ -21,9 +21,9 @@ features[user_permission][] = create features_test content
features[views_view][] = features_test features[views_view][] = features_test
hidden = 1 hidden = 1
; Information added by Drupal.org packaging script on 2015-06-24 ; Information added by Drupal.org packaging script on 2016-04-18
version = "7.x-2.6" version = "7.x-2.10"
core = "7.x" core = "7.x"
project = "features" project = "features"
datestamp = "1435165997" datestamp = "1461011641"