features.export.inc in Features 7.2
Same filename and directory in other branches
Contains functions that export configuration into feature modules.
File
features.export.incView source
<?php
/**
* @file
* Contains functions that export configuration into feature modules.
*/
/**
* Populates an export array with additional keys.
*
* @param array $info
* Feature info array
* Format:
* $['features'][$component][$name] = $name
* $['dependencies'][$dependency] = $dependency.
* @param string $module_name
* Module name of the feature being generated.
*
* @return array
* Fully populated export array.
* Format:
* $['features']['features_api']['api:' . FEATURES_API] = TRUE
* $['features'][$component][$name] = $name
* $['dependencies'][$dependency] = $dependency
* $['conflicts'] = [..]
* $['features_exclude'][$component][$name] = $name
*/
function features_populate($info, $module_name) {
// Sanitize items.
$items = !empty($info['features']) ? array_filter($info['features']) : array();
$items['dependencies'] = !empty($info['dependencies']) ? drupal_map_assoc(array_filter($info['dependencies'])) : array();
// Populate stub.
$stub = array(
'features' => array(),
'dependencies' => array(),
'conflicts' => array(),
) + $info + array(
'features_exclude' => array(),
);
$export = _features_populate($items, $stub, $module_name, TRUE);
// Add Features API version. Any module with this entry in the .info file
// will be treated as a Feature and included in the admin/build/features UI.
$export['features']['features_api']['api:' . FEATURES_API] = TRUE;
// Allow other modules to alter the export.
/* @see \hook_features_export_alter() */
drupal_alter('features_export', $export, $module_name);
// Clean up and standardize order.
foreach (array_keys($export['features']) as $k) {
ksort($export['features'][$k]);
}
ksort($export['features']);
ksort($export['dependencies']);
ksort($export['features_exclude']);
return $export;
}
/**
* Populates an export array with additional components from the "pipe".
*
* The mechanism allows each component to add objects from other components as
* dependencies, using hook_features_export().
*
* @param string[][] $pipe
* Format: $[$component][] = $name
* Original list of components.
* @param array $export
* Associative array of items, and module dependencies which define a feature.
* Passed by reference.
* Format:
* $['features'][$component][$name] = $name
* $['dependencies'][$dependency] = $dependency
* $['conflicts'] = [..]
* $['features_exclude'][$component][$name] = $name.
* During the recursive calls to this function, the export array will be
* filled up with additional components and module dependencies.
* @param string $module_name
* Module name of the feature being generated.
* @param bool $reset
* TRUE, to reset the static cache for this function.
*
* @return array
* Fully populated $export array.
* Format:
* $['features'][$component][$name] = $name
* $['dependencies'][$dependency] = $dependency
* $['conflicts'] = [..]
* $['features_exclude'][$component][$name] = $name
* The return value is the same as the value of the by-reference $export
* parameter after the function was executed.
*
* @see \hook_features_export()
* @see \hook_features_pipe_alter()
* @see \hook_features_pipe_COMPONENT_alter()
*
* @todo The return value is pointless, see #3074424.
*/
function _features_populate($pipe, &$export, $module_name = '', $reset = FALSE) {
// Ensure that the export will be created in the english language.
$language = _features_export_language();
if ($reset) {
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);
// Remove any excluded items.
if (!empty($export['features_exclude'][$component])) {
$data = array_diff($data, $export['features_exclude'][$component]);
if ($component == 'dependencies' && !empty($export['dependencies'])) {
$export['dependencies'] = array_diff($export['dependencies'], $export['features_exclude'][$component]);
}
}
if (!empty($data) && ($function = features_hook($component, 'features_export'))) {
// Pass module-specific data and export array.
// We don't use features_invoke() here since we need to pass $export by
// reference.
$more = $function($data, $export, $module_name, $component);
// Add the context information.
$export['component'] = $component;
$export['module_name'] = $module_name;
// Allow other modules to manipulate the pipe to add in additional
// modules.
drupal_alter(array(
'features_pipe',
'features_pipe_' . $component,
), $more, $data, $export);
// Remove the component information.
unset($export['component']);
unset($export['module_name']);
// Allow for export functions to request additional exports, but avoid
// circular references on already processed components.
$processed[$component] = isset($processed[$component]) ? array_merge($processed[$component], $data) : $data;
if (!empty($more)) {
// Remove already processed components.
foreach ($more as $component_name => $component_data) {
if (isset($processed[$component_name])) {
$more[$component_name] = array_diff($component_data, $processed[$component_name]);
}
}
if ($more = array_filter($more)) {
_features_populate($more, $export, $module_name);
}
}
}
}
_features_export_language($language);
return $export;
}
/**
* Iterates over data and convert to dependencies if already defined elsewhere.
*
* @param string[] $data
* Names of component items/objects, e.g. "node-article-body".
* @param array $export
* Export array.
* @param string $module_name
* Module name.
* @param string $component
* E.g. "field_instance".
*/
function _features_resolve_dependencies(&$data, &$export, $module_name, $component) {
if ($map = features_get_default_map($component)) {
foreach ($data as $key => $item) {
// If this node type is provided by a different module, add it as a
// dependency.
if (isset($map[$item]) && $map[$item] != $module_name) {
$export['dependencies'][$map[$item]] = $map[$item];
unset($data[$key]);
}
}
}
}
/**
* Minimizes a list of dependencies.
*
* Iterates over a list of dependencies and kills modules that are
* captured by other modules 'higher up'.
*
* @param string[] $dependencies
* List of module names required by the given module.
* Format: $[$dependency] = $dependency.
* @param string $module_name
* Module name whose dependencies are being processed.
*
* @return string[]
* Format: $[$dependency] = $dependency
*/
function _features_export_minimize_dependencies($dependencies, $module_name = '') {
// Ensure that the module doesn't depend upon itself.
if (!empty($module_name) && !empty($dependencies[$module_name])) {
unset($dependencies[$module_name]);
}
// Do some cleanup:
// - Remove modules required by Drupal core.
// - Protect against direct circular dependencies.
// - Remove "intermediate" dependencies.
$required = drupal_required_modules();
foreach ($dependencies as $k => $v) {
if (empty($v) || in_array($v, $required)) {
unset($dependencies[$k]);
}
else {
$module = features_get_modules($v);
if ($module && !empty($module->info['dependencies'])) {
// If this dependency depends on the module itself, we have a circular
// dependency.
// Don't let it happen. Only you can prevent forest fires.
if (in_array($module_name, $module->info['dependencies'])) {
unset($dependencies[$k]);
}
else {
foreach ($module->info['dependencies'] as $j => $dependency) {
if (array_search($dependency, $dependencies) !== FALSE) {
$position = array_search($dependency, $dependencies);
unset($dependencies[$position]);
}
}
}
}
}
}
return drupal_map_assoc(array_unique($dependencies));
}
/**
* Completes a list of dependencies by adding all indirect dependencies as well.
*
* Mathematically this would be called a 'transitive closure'.
*
* This function is recursive, some of its parameters are only meant to be used
* in recursive calls.
*
* @param string[] $dependencies
* Original list of dependencies.
* Format: $[*] = $module
* The array keys will be ignored, which means this has the same result for
* serial or associative arrays.
* @param string $module_name
* (obsolete) Name of the module whose dependencies are being processed.
* This has no effect whatsoever, so it can be safely omitted.
* @param string[] $maximized
* (recursive) List of modules that were already processed in previous
* recursion levels. Omit in non-recursive call.
* @param bool $first
* (recursive) TRUE, if this is not a recursive call.
*
* @return string[]
* Complete list of direct and indirect dependencies.
* Format: $[] = $module
*
* @see _module_build_dependencies()
*/
function _features_export_maximize_dependencies($dependencies, $module_name = '', $maximized = array(), $first = TRUE) {
foreach ($dependencies as $k => $v) {
$parsed_dependency = drupal_parse_dependency($v);
$name = $parsed_dependency['name'];
if (!in_array($name, $maximized)) {
$maximized[] = $name;
$module = features_get_modules($name);
if ($module && !empty($module->info['dependencies'])) {
$maximized = array_merge($maximized, _features_export_maximize_dependencies($module->info['dependencies'], $module_name, $maximized, FALSE));
}
}
}
return array_unique($maximized);
}
/**
* Prepares a feature export array into a finalized info array.
*
* @param array $export
* An exported feature definition.
* This has the same structure as a module *.info array, but it may be missing
* some keys, or have the keys in the wrong order.
* @param string $module_name
* The name of the module to be exported.
* @param bool $reset
* TRUE to reset the module cache. Only set to true when
* doing a final export for delivery.
* @param bool $add_deprecated
* TRUE to also add deprecated components.
*
* @return array
* Complete *.info array with all required keys added and standardized order.
*/
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.
$copy_list = array(
'scripts',
'stylesheets',
);
foreach ($copy_list as $item) {
if (isset($export[$item])) {
$existing->info[$item] = $export[$item];
}
}
// Prepare info string -- if module exists, merge into its existing info file.
$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) {
// 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']);
}
if (isset($export['conflicts'])) {
unset($export['conflicts']);
}
// Order info array.
$standard_info = array();
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 = features_array_diff_assoc_recursive($export, $standard_info);
ksort($export);
return array_merge($standard_info, $export);
}
/**
* Generate an array of hooks and their raw code.
*
* @param array $export
* Export array.
* @param string $module_name
* Feature module name.
* @param bool $reset
* TRUE to reset the cache.
*
* @return string[][]
* Format: $[$component][$hook_name] = $function_body_php
* E.g. $['node']['node_info'] = '$items = array(..); [..] return $items;'
*/
function features_export_render_hooks($export, $module_name, $reset = FALSE) {
features_include();
$code = array();
// Sort components to keep exported code consistent.
ksort($export['features']);
foreach ($export['features'] as $component => $data) {
if (!empty($data)) {
// Sort the items so that we don't generate different exports based on
// order.
asort($data);
/* @see \hook_features_export_render() */
if (features_hook($component, 'features_export_render')) {
$hooks = features_invoke($component, 'features_export_render', $module_name, $data, $export);
$code[$component] = $hooks;
}
}
}
return $code;
}
/**
* Render feature export into an array representing its files.
*
* @param array $export
* An exported feature definition.
* @param string $module_name
* The name of the module to be exported.
* @param bool $reset
* Boolean flag for resetting the module cache. Only set to true when
* doing a final export for delivery.
*
* @return array
* Array of info file and module file contents.
*/
function features_export_render($export, $module_name, $reset = FALSE) {
$code = array();
// 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']) {
case FEATURES_DEFAULTS_INCLUDED:
$file['name'] = "features.{$component}";
break;
case FEATURES_DEFAULTS_CUSTOM:
$file['name'] = $components[$component]['default_filename'];
break;
}
}
if (!isset($code[$file['name']])) {
$code[$file['name']] = array();
}
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'];
$code[$hook_file][$hook_name] = features_export_render_defaults($module_name, $hook_name, $hook_code, $hook_args);
}
}
// Finalize strings to be written to files.
$code = array_filter($code);
foreach ($code as $filename => $contents) {
if ($filename != '_files') {
$code[$filename] = "<?php\n\n/**\n * @file\n * {$module_name}.{$filename}.inc\n */\n\n" . implode("\n\n", $contents) . "\n";
}
}
// Allow extra files to be added to the feature via ['_files'].
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.
$export = features_export_prepare($export, $module_name, $reset);
$code['info'] = features_export_info($export);
// Used to create or manipulate the generated .module for features.inc.
$modulefile_features_inc = "<?php\n\n/**\n * @file\n * Code for the {$export['name']} feature.\n */\n\ninclude_once '{$module_name}.features.inc';\n";
$modulefile_blank = "<?php\n\n/**\n * @file\n * Drupal needs this blank file.\n */\n";
// Prepare the module.
// If module exists, let it be and include it in the files.
if ($existing = features_get_modules($module_name, TRUE)) {
$code['module'] = file_get_contents($existing->filename);
// If the current module file does not reference the features.inc include,.
// @TODO this way of checking does not account for the possibility of inclusion instruction being commented out.
if (isset($code['features']) && strpos($code['module'], "{$module_name}.features.inc") === FALSE) {
// If .module does not begin with <?php\n, just add a warning.
if (strpos($code['module'], "<?php\n") !== 0) {
features_log(t('@module does not appear to include the @include file.', array(
'@module' => "{$module_name}.module",
'@include' => "{$module_name}.features.inc",
)), 'warning');
}
else {
// Remove the old message if it exists, else just remove the <?php.
$length = strpos($code['module'], $modulefile_blank) === 0 ? strlen($modulefile_blank) : 6;
$code['module'] = $modulefile_features_inc . substr($code['module'], $length);
}
}
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}";
}
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');
}
elseif ($file->name === "{$module_name}.features" && empty($code['features'])) {
// Attempt to remove the "include_once '*.features.inc';" statement in
// the *.module file.
if (strpos($code['module'], "{$module_name}.features.inc")) {
$code['module'] = str_replace($modulefile_features_inc, $modulefile_blank, $code['module']);
}
// Check if the removal was successful.
if (strpos($code['module'], "{$module_name}.features.inc")) {
// The removal of the "include_once" was not successful, perhaps due
// to custom modifications in the *.module file.
// Leave a comment in *.features.inc to clean this up manually.
$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 {
// The "include_once" was removed successfully.
// However, the *.features.inc file still needs to be removed
// manually.
$code['features'] = "<?php\n\n// This file is deprecated and can be removed.\n";
}
}
}
}
}
elseif (!empty($code['features'])) {
$code['module'] = $modulefile_features_inc;
}
else {
$code['module'] = $modulefile_blank;
}
return $code;
}
/**
* Detect differences between DB and code components of a feature.
*
* @param \stdClass $module
* A module info object.
*
* @return string[][]
* Format: $[$component] = ['normal' => '..', 'default' => '..']
*/
function features_detect_overrides($module) {
$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, FALSE, FALSE);
$overridden = array();
// Compare feature info.
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']) {
$overridden['info'] = $compare;
}
// Collect differences at a per-component level.
$states = features_get_component_states(array(
$module->name,
), FALSE);
foreach ($states[$module->name] as $component => $state) {
if ($state != FEATURES_DEFAULT) {
$normal = features_get_normal($component, $module->name);
$default = features_get_default($component, $module->name);
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'])) {
$overridden[$component] = $compare;
}
}
}
$cache[$module->name] = $overridden;
}
return $cache[$module->name];
}
/**
* Gets the available default hooks keyed by components.
*
* @param string|null $component
* A component name, e.g. 'field_instance', or NULL to list all components.
* @param bool $reset
* (optional) If TRUE, the components cache will be cleared.
*
* @return string[]|string|null
* Return value depending on parameters:
* - If $component is NULL:
* The default hook for each component.
* Format: $[$component] = $default_hook
* - If $component is provided:
* The default hook (string), or NULL.
*/
function features_get_default_hooks($component = NULL, $reset = FALSE) {
return features_get_components($component, 'default_hook', $reset);
}
/**
* Gets the available default hooks keyed by components.
*
* @param string $component
* Component name.
*
* @return string|false
* The alter hook name, or FALSE if no altering should happen.
*/
function features_get_default_alter_hook($component) {
$default_hook = features_get_components($component, 'default_hook');
$alter_hook = features_get_components($component, 'alter_hook');
$alter_type = features_get_components($component, 'alter_type');
return empty($alter_type) || $alter_type != 'none' ? $alter_hook ? $alter_hook : $default_hook : FALSE;
}
/**
* Return a code string representing an implementation of a defaults module hook.
*
* @param string $module
* The module name or component name.
* @param string $hook
* The hook name.
* @param string $code
* Function body.
* @param string $args
* (optional) A parameter list.
*
* @return string
* The PHP code for a function declaration with doc comment.
*/
function features_export_render_defaults($module, $hook, $code, $args = '') {
$output = array();
$output[] = "/**";
$output[] = " * Implements hook_{$hook}().";
$output[] = " */";
$output[] = "function {$module}_{$hook}(" . $args . ") {";
$output[] = $code;
$output[] = "}";
return implode("\n", $output);
}
/**
* Generate code friendly to the Drupal .info format from a structured array.
*
* @param mixed $info
* An array or single value to put in a module's .info file.
* @param string[] $parents
* Array of parent keys (internal use only).
*
* @return string
* A code string ready to be written to a module's .info file.
*
* @todo It would be faster to pass a string for $parents.
*/
function features_export_info($info, $parents = array()) {
$output = '';
if (is_array($info)) {
foreach ($info as $k => $v) {
$child = $parents;
$child[] = $k;
$output .= features_export_info($v, $child);
}
}
elseif (!empty($info) && count($parents)) {
$line = array_shift($parents);
foreach ($parents as $key) {
$line .= is_numeric($key) ? "[]" : "[{$key}]";
}
$line .= " = {$info}\n";
return $line;
}
return $output;
}
/**
* Tar creation function. Written by dmitrig01.
*
* @param string $name
* Filename of the file to be tarred.
* @param string $contents
* File contents to be written as tar.
*
* @return string
* The raw tar file contents.
*/
function features_tar_create($name, $contents) {
/* http://www.mkssoftware.com/docs/man4/tar.4.asp */
/* http://www.phpclasses.org/browse/file/21200.html */
$tar = '';
$bigheader = $header = '';
if (strlen($name) > 100) {
$bigheader = pack("a100a8a8a8a12a12a8a1a100a6a2a32a32a8a8a155a12", '././@LongLink', '0000000', '0000000', '0000000', sprintf("%011o", strlen($name)), '00000000000', ' ', 'L', '', 'ustar ', '0', '', '', '', '', '', '');
$bigheader .= str_pad($name, floor((strlen($name) + 512 - 1) / 512) * 512, "\0");
$checksum = 0;
for ($i = 0; $i < 512; $i++) {
$checksum += ord(substr($bigheader, $i, 1));
}
$bigheader = substr_replace($bigheader, sprintf("%06o", $checksum) . "\0 ", 148, 8);
}
$header = pack("a100a8a8a8a12a12a8a1a100a6a2a32a32a8a8a155a12", substr($name, 0, 100), '100644 ', ' 765 ', ' 765 ', sprintf("%11s ", decoct(strlen($contents))), sprintf("%11s", decoct(REQUEST_TIME)), ' ', '', '', 'ustar ', ' ', '', '', '', '', '', '');
// 500 12 ??
$checksum = 0;
for ($i = 0; $i < 512; $i++) {
$checksum += ord(substr($header, $i, 1));
}
$header = substr_replace($header, sprintf("%06o", $checksum) . "\0 ", 148, 8);
$tar = $bigheader . $header;
$buffer = str_split($contents, 512);
foreach ($buffer as $item) {
$tar .= pack("a512", $item);
}
return $tar;
}
/**
* Alternative to var_export(), with a more pleasant output format.
*
* The function is recursive, some parameters should only be provided on
* recursive calls.
*
* The function is inspired/adapted from views_var_export().
*
* @param mixed $var
* The value to export.
* @param string $prefix
* (recursive) Prefix for indentation.
* @param bool $init
* (recursive) TRUE, if this is the first level of recursion.
* @param int $count
* (recursive) The recursion depth. Starts with 0.
*
* @return string
* A php statement whose return value is $var.
*
* @see \views_var_export()
* @see \var_export()
*/
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, $count + 1);
}
elseif (is_array($var)) {
if (empty($var)) {
$output = 'array()';
}
else {
$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, $count + 1) . ",\n";
}
$output .= ')';
}
}
elseif (is_bool($var)) {
$output = $var ? 'TRUE' : 'FALSE';
}
elseif (is_int($var)) {
$output = intval($var);
}
elseif (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;
}
}
elseif (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 unintentional indentation.
$var = str_replace("\n", "***BREAK***", $var);
$output = var_export($var, TRUE);
}
else {
$output = var_export($var, TRUE);
}
if ($prefix) {
$output = str_replace("\n", "\n{$prefix}", $output);
}
if ($init) {
$output = str_replace("***BREAK***", "\n", $output);
}
return $output;
}
/**
* Creates PHP code with t() statements for given translatable strings.
*
* This PHP code can be added to generated code, to allow string extractors like
* potx to extract a list of translatables from the exported module.
*
* This is typically called from hook_features_export_render() implementations.
*
* @param string[] $translatables
* List of translatable strings.
* Format: $[] = $string.
* @param string $indent
* Indentation to prepend to each line of code.
*
* @return string
* PHP code with t() statements for each translatable string.
*/
function features_translatables_export($translatables, $indent = '') {
$output = '';
$translatables = array_filter(array_unique($translatables));
if (!empty($translatables)) {
$output .= "{$indent}// Translatables\n";
$output .= "{$indent}// Included for use with string extractors like potx.\n";
sort($translatables);
foreach ($translatables as $string) {
$output .= "{$indent}t(" . features_var_export($string) . ");\n";
}
}
return $output;
}
/**
* Gets a summary storage state for a feature.
*
* @param string $module_name
* Module name.
*
* @return int
* Value of one of the component state constants listed below.
* The number is the maximum of all individual component states for the given
* module, or FEATURES_DEFAULT if no component states found in the module.
*
* @see FEATURES_REBUILDABLE
* @see FEATURES_DEFAULT
* @see FEATURES_OVERRIDDEN
* @see FEATURES_NEEDS_REVIEW
* @see FEATURES_REBUILDING
*/
function features_get_storage($module_name) {
// Get component states, and array_diff against array(FEATURES_DEFAULT).
// If the returned array has any states that don't match FEATURES_DEFAULT,
// return the highest state.
$states = features_get_component_states(array(
$module_name,
), FALSE);
$states = array_diff($states[$module_name], array(
FEATURES_DEFAULT,
));
$storage = !empty($states) ? max($states) : FEATURES_DEFAULT;
return $storage;
}
/**
* Gets an md5 signature for a the state of an object in code or database.
*
* Wrapper around features_get_[storage]() to return an md5 hash of a normalized
* defaults/normal object array. Can be used to compare normal/default states
* of a module's component.
*
* @param string $state
* One of 'cache', 'default' or 'normal'.
* @param string $module_name
* A module name.
* @param string $component
* A component name, e.g. 'field_instance'.
* @param bool $reset
* If TRUE, the static cache for features_get_default() or
* features_get_normal() will be reset.
*
* @return string|false
* An md5 signature, or FALSE if not found.
*/
function features_get_signature($state = 'default', $module_name, $component, $reset = FALSE) {
switch ($state) {
case 'cache':
// Load the last known stored signature from the database.
switch (_features_get_signature_storage_type()) {
case 'table':
// The database is fully updated.
// All signatures are stored in a dedicated database table.
$qr = db_select('features_signature', 'fs')
->fields('fs', array(
'signature',
))
->condition('module', $module_name)
->condition('component', $component)
->execute();
return $qr ? $qr
->fetchField() : FALSE;
case 'cache':
// The database is not fully updated, only to schema version 7201.
// Signatures are stored in a cache table.
$cache = cache_get('features_codecache', 'cache_featurestate');
if (isset($cache->data[$module_name][$component])) {
return $cache->data[$module_name][$component];
}
// No stored signature for this component.
return FALSE;
case 'variable':
default:
// The database is not fully updated, schema version before 7201.
// Signatures are stored in a variable.
$signaturess = variable_get('features_codecache', array());
if (isset($signaturess[$module_name][$component])) {
return $signaturess[$module_name][$component];
}
// No stored signature for this component.
return FALSE;
}
case 'default':
// Get the component data as currently in code.
$objects = features_get_default($component, $module_name, TRUE, $reset);
break;
case 'normal':
// Get the component data as currently in the database.
$objects = features_get_normal($component, $module_name, $reset);
break;
}
if (!empty($objects)) {
// Build a signature hash from the component data.
features_sanitize($objects, $component);
return md5(_features_linetrim(features_var_export($objects)));
}
return FALSE;
}
/**
* Updates a module/component signature in the database.
*
* The signature stored in the database reflects the last known state of the
* component in code.
*
* @param string $module
* A feature module name.
* @param string $component
* A component name, e.g. 'field_instance'.
* @param string|null|false $signature
* An md5 signature, or NULL to generate one from the current state in code,
* or FALSE to delete the signature.
* @param string|null $message
* (optional) Message to store along with the updated signature.
*/
function features_set_signature($module, $component, $signature = NULL, $message = NULL) {
if ($signature === NULL) {
// Build signature from current state in code.
$signature = features_get_signature('default', $module, $component, TRUE);
}
// Support un-updated databases.
switch (_features_get_signature_storage_type()) {
case 'table':
// The database is fully updated.
// All signatures are stored in a dedicated database table.
if ($signature === FALSE) {
// Delete the signature.
db_delete('features_signature')
->condition('module', $module)
->condition('component', $component)
->execute();
}
else {
// Insert or update the signature.
db_merge('features_signature')
->key(array(
'module' => $module,
'component' => $component,
))
->fields(array(
'signature' => $signature,
'updated' => time(),
'message' => $message,
))
->execute();
}
break;
case 'cache':
// The database is not fully updated, only to schema version 7201.
// Signatures are stored in a cache table.
$cache = cache_get('features_codecache', 'cache_featurestate');
if (!empty($cache->data)) {
$signaturess = $cache->data;
}
$signaturess[$module][$component] = $signature;
cache_set('features_codecache', $signaturess, 'cache_featurestate');
break;
case 'variable':
default:
// The database is not fully updated, schema version before 7201.
// Signatures are stored in a variable.
$signaturess = variable_get('features_codecache', array());
$signaturess[$module][$component] = $signature;
variable_set('features_codecache', $signaturess);
break;
}
}
/**
* Gets the current storage type for features component signatures.
*
* This is needed to prevent breakage in a database that is not fully updated
* yet, e.g. in deployment operations that run before the database update.
*
* The signatures used to be stored in a variable.
* Since #1325288, it was stored in a cache table. This only applies to projects
* that were using a -dev branch after 7.x-2.11.
* Since #3162854, it is stored in a dedicated non-cache table.
*
* @return string
* One of 'table', 'cache' or 'type'.
* On a fully updated database, this value will be 'table'.
*
* @see \features_get_signature()
* @see \features_set_signature()
* @see \features_update_7202()
*/
function _features_get_signature_storage_type() {
$type =& drupal_static(__FUNCTION__);
if ($type !== NULL) {
return $type;
}
if (db_table_exists('features_signature')) {
$type = 'table';
}
elseif (db_table_exists('cache_featurestate')) {
$type = 'cache';
}
else {
$type = 'variable';
}
return $type;
}
/**
* Gets, sets or deletes a semaphore for a given component.
*
* @param string $op
* One of 'get', 'set' or 'del'.
* @param string $component
* A component name, e.g. 'field_instance'.
*
* @return int|false|void
* If $op is 'get', the semaphore, or FALSE if none found for the component.
* If $op is 'set' or 'del', nothing is returned.
*/
function features_semaphore($op, $component) {
// Note: we don't use variable_get() here as the inited variable
// static cache may be stale. Retrieving directly from the DB narrows
// the possibility of collision.
$semaphore = db_query("SELECT value FROM {variable} WHERE name = :name", array(
':name' => 'features_semaphore',
))
->fetchField();
$semaphore = !empty($semaphore) ? unserialize($semaphore) : array();
switch ($op) {
case 'get':
return isset($semaphore[$component]) ? $semaphore[$component] : FALSE;
case 'set':
$semaphore[$component] = REQUEST_TIME;
variable_set('features_semaphore', $semaphore);
break;
case 'del':
if (isset($semaphore[$component])) {
unset($semaphore[$component]);
variable_set('features_semaphore', $semaphore);
}
break;
}
}
/**
* Get normal objects for a given module/component pair.
*
* @param string $component
* The component name, e.g. 'field_instance'.
* @param string $module_name
* The name of the exported module.
* @param bool $reset
* If TRUE, the static cache will be reset.
*
* @return mixed|false
* The normal objects that would be written into the feature on update.
*/
function features_get_normal($component, $module_name, $reset = FALSE) {
if ($reset) {
drupal_static_reset(__FUNCTION__);
}
$cache =& drupal_static(__FUNCTION__, array());
if (!isset($cache[$module_name][$component])) {
features_include();
$code = NULL;
$module = features_get_features($module_name);
// Special handling for dependencies component.
if ($component === 'dependencies') {
$cache[$module_name][$component] = isset($module->info['dependencies']) ? array_filter($module->info['dependencies'], '_features_module_exists') : array();
}
else {
$default_hook = features_get_default_hooks($component);
if ($module && $default_hook && isset($module->info['features'][$component]) && features_hook($component, 'features_export_render')) {
$code = features_invoke($component, 'features_export_render', $module_name, $module->info['features'][$component], NULL);
$cache[$module_name][$component] = isset($code[$default_hook]) ? eval($code[$default_hook]) : FALSE;
}
}
// Clear out vars for memory's sake.
unset($code);
unset($module);
}
return isset($cache[$module_name][$component]) ? $cache[$module_name][$component] : FALSE;
}
/**
* Helper function to determine if a module is enabled.
*
* @param string $module
* This module name comes from the .info file and can have version info in it.
*
* @return bool
* TRUE, if the module is enabled.
*/
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.
*
* @param string $component
* A component name, e.g. 'field_instance'.
* @param string|null $module_name
* (optional) If specified, only return defaults for this module.
* @param bool $alter
* (optional) If TRUE, the defaults will be passed through an alter hook.
* @param bool $reset
* If TRUE, the static cache will be reset.
*
* @return array[]|object[]|mixed[]|false
* Format: $[$name] = $item_export_data
* Object data as defined in code. For most components, this is the data
* returned from the component hook implementation(s).
* For some components these are actual objects, for others they are arrays or
* perhaps just strings.
*/
function features_get_default($component, $module_name = NULL, $alter = TRUE, $reset = FALSE) {
$cache =& drupal_static(__FUNCTION__, array());
// Ensure $alter is a true/false boolean.
$alter = !empty($alter);
features_include();
features_include_defaults($component);
$default_hook = features_get_default_hooks($component);
$components = features_get_components();
// Collect defaults for all modules if no module name was specified.
if (isset($module_name)) {
$modules = array(
$module_name,
);
}
else {
if ($component === 'dependencies') {
$modules = array_keys(features_get_features());
}
else {
$modules = array();
foreach (features_get_component_map($component) as $component_modules) {
$modules = array_merge($modules, $component_modules);
}
$modules = array_unique($modules);
}
}
// Collect and cache information for each specified module.
foreach ($modules as $m) {
if (!isset($cache[$component][$alter][$m]) || $reset) {
// Special handling for dependencies component.
if ($component === 'dependencies') {
$module = features_get_features($m);
$cache[$component][$alter][$m] = isset($module->info['dependencies']) ? $module->info['dependencies'] : array();
unset($module);
}
else {
if ($default_hook && module_hook($m, $default_hook)) {
$cache[$component][$alter][$m] = call_user_func("{$m}_{$default_hook}");
if (is_array($cache[$component][$alter][$m])) {
$alter_type = features_get_components('alter_type', $component);
if ($alter && (!isset($alter_type) || $alter_type == FEATURES_ALTER_TYPE_NORMAL)) {
if ($alter_hook = features_get_default_alter_hook($component)) {
drupal_alter($alter_hook, $cache[$component][$alter][$m]);
}
}
}
else {
$cache[$component][$alter][$m] = FALSE;
}
}
else {
$cache[$component][$alter][$m] = FALSE;
}
}
}
}
// A specific module was specified. Retrieve only its components.
if (isset($module_name)) {
return isset($cache[$component][$alter][$module_name]) ? $cache[$component][$alter][$module_name] : FALSE;
}
// No module was specified. Retrieve all components.
$all_defaults = array();
if (isset($cache[$component][$alter])) {
foreach (array_filter($cache[$component][$alter]) as $module_components) {
$all_defaults = array_merge($all_defaults, $module_components);
}
}
return $all_defaults;
}
/**
* Gets a map of components to their providing modules.
*
* This function only cares what is returned from the (generated) default hooks,
* not what is in the info file.
*
* @param string $component
* The component name, e.g. 'field_instance'.
* @param string|null $attribute
* Object property name or array key to get the id from exported object data.
* These ids will be used as array keys in the return value.
* @param callable|null $callback
* Callback to get the object id from exported object data. These ids will be
* used as array keys in the return value.
* @param bool $reset
* If TRUE, the static cache entry for this specific component is reset.
*
* @return string[]|false
* Format: $[$name] = $module
* E.g. $['node-article-field_body'] = 'mysite_ct_article'
* The module where each item is exported.
* If the $GLOBALS['features_ignore_conflicts'] is TRUE(-ish), this function
* will return FALSE.
*/
function features_get_default_map($component, $attribute = NULL, $callback = NULL, $reset = FALSE) {
$map =& drupal_static(__FUNCTION__, array());
global $features_ignore_conflicts;
if ($features_ignore_conflicts) {
return FALSE;
}
features_include();
features_include_defaults($component);
if ((!isset($map[$component]) || $reset) && ($default_hook = features_get_default_hooks($component))) {
$map[$component] = array();
foreach (module_implements($default_hook) as $module) {
if ($defaults = features_get_default($component, $module)) {
foreach ($defaults as $key => $object) {
if (isset($callback)) {
if ($object_key = $callback($object)) {
$map[$component][$object_key] = $module;
}
}
elseif (isset($attribute)) {
if (is_object($object) && isset($object->{$attribute})) {
$map[$component][$object->{$attribute}] = $module;
}
elseif (is_array($object) && isset($object[$attribute])) {
$map[$component][$object[$attribute]] = $module;
}
}
elseif (!isset($attribute) && !isset($callback)) {
if (!is_numeric($key)) {
$map[$component][$key] = $module;
}
}
else {
return FALSE;
}
}
}
}
}
return isset($map[$component]) ? $map[$component] : FALSE;
}
/**
* Retrieve an array of features/components and their current states.
*
* @param string[] $features
* (optional) Machine names of feature modules.
* If empty, states for all features will be returned.
* @param bool $rebuild_only
* (optional) If TRUE, only rebuildable components are returned.
* @param bool $reset
* (optional) If TRUE, relevant caches will be reset before fetching.
*
* @return int[][]
* Component states by module and component.
* Format: $[$module][$component] = $state
* where $state will be one of the states listed below.
*
* @see FEATURES_REBUILDABLE
* @see FEATURES_DEFAULT
* @see FEATURES_OVERRIDDEN
* @see FEATURES_NEEDS_REVIEW
* @see FEATURES_REBUILDING
*/
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) {
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(NULL, NULL, $reset));
if ($rebuild_only) {
foreach ($components as $k => $component) {
if (!features_hook($component, 'features_rebuild')) {
unset($components[$k]);
}
}
}
foreach ($features as $feature) {
$cache[$feature] = isset($cache[$feature]) ? $cache[$feature] : array();
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);
$codecache = features_get_signature('cache', $feature, $component, $reset);
$semaphore = features_semaphore('get', $component);
// DB and code states match, there is nothing more to check.
if ($normal == $default) {
$cache[$feature][$component] = FEATURES_DEFAULT;
// Stale semaphores can be deleted.
features_semaphore('del', $component);
// Update code cache if it is stale, clear out semaphore if it is
// stale.
if ($default != $codecache) {
features_set_signature($feature, $component, $default, __FUNCTION__ . '(): $normal === $default');
}
}
elseif (!features_hook($component, 'features_rebuild')) {
$cache[$feature][$component] = FEATURES_OVERRIDDEN;
}
else {
if (empty($semaphore)) {
// Exception for dependencies. Dependencies are always
// rebuildable.
if ($component === 'dependencies') {
$cache[$feature][$component] = FEATURES_REBUILDABLE;
}
else {
// Code has not changed, but DB does not match. User has DB
// overrides.
if ($codecache == $default) {
$cache[$feature][$component] = FEATURES_OVERRIDDEN;
}
elseif (empty($codecache) || $codecache == $normal) {
$cache[$feature][$component] = FEATURES_REBUILDABLE;
}
elseif ($codecache != $default) {
$cache[$feature][$component] = FEATURES_NEEDS_REVIEW;
}
}
}
else {
// Semaphore is still within processing horizon. Do nothing.
if (REQUEST_TIME - $semaphore < FEATURES_SEMAPHORE_TIMEOUT) {
$cache[$feature][$component] = FEATURES_REBUILDING;
}
else {
$cache[$feature][$component] = FEATURES_REBUILDABLE;
}
}
}
}
}
}
}
// Filter cached components on the way out to ensure that even if we have
// cached more data than has been requested, the return value only reflects
// the requested features/components.
$return = $cache;
$return = array_intersect_key($return, array_flip($features));
foreach ($return as $k => $v) {
$return[$k] = array_intersect_key($return[$k], array_flip($components));
}
_features_export_language($language);
return $return;
}
/**
* Helper function to eliminate whitespace differences in code.
*
* @param string $code
* Original (multi-line) piece of code.
*
* @return string
* Modified piece of code with no leading or trailing whitespace per line.
*/
function _features_linetrim($code) {
$code = explode("\n", $code);
foreach ($code as $k => $line) {
$code[$k] = trim($line);
}
return implode("\n", $code);
}
/**
* Helper function to "sanitize" an array or object.
*
* Converts everything to an array, sorts the keys, removes recursion.
*
* @param array|object $array
* The object or array to "sanitize".
* After this call, this variable will be an array.
* @param string $component
* (optional) Name of a component type, e.g. "field_instance".
* @param bool $remove_empty
* (optional) If TRUE, remove null or empty values for assoc arrays.
*
* @todo Needs unit tests.
* @todo A better name might be "normalize", not "sanitize".
*/
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);
}
/**
* Internal. "Sanitizes" a (nested) array (or object) recursively.
*
* The following operations are performed on every recursion level:
* - Objects are converted to array via get_object_vars().
* - "Associative" arrays are sorted by key.
* - Non-associative/serial arrays are sorted by value.
* - Empty values and empty sub-trees are removed if $remove_empty is TRUE.
*
* @param array|object|mixed $array
* Array or object to be sanitized.
* If $array is an object, it will be converted to array via get_object_vars().
* Any nested objects will be converted to array in the same way.
* If $array is not an array or object, it will be left as-is.
* @param bool $remove_empty
* If TRUE, remove null or empty values for assoc arrays.
* This will also remove empty arrays or objects nested in the hierarchy.
*
* @internal
* This function is designed for internal use within @see features_sanitize().
*
* @todo Needs unit tests.
* @todo A better name might be "normalize", not "sanitize".
*/
function _features_sanitize(&$array, $remove_empty = TRUE) {
if (is_object($array)) {
$array = get_object_vars($array);
}
if (is_array($array)) {
$is_assoc = _features_is_assoc($array);
if ($is_assoc) {
ksort($array, SORT_STRING);
if ($remove_empty) {
$array = array_filter($array);
}
}
else {
sort($array);
}
foreach ($array as $k => $v) {
if (is_array($v) or is_object($v)) {
_features_sanitize($array[$k]);
if ($remove_empty && $is_assoc && empty($array[$k])) {
unset($array[$k]);
}
}
}
}
}
/**
* Internal. Checks whether the array is "associative".
*
* An array is considered "associative" by this function, if:
* - it is empty (no values), or
* - it has any non-integer keys, or
* - some numeric indices are missing, e,g, [0 => $v, 1 => $v, 3 => $v].
*
* Note: The order of numeric indices is irrelevant.
* E.g. [1 => $v, 0 => $v] is considered NOT associative.
*
* Borrowed from: http://www.php.net/manual/en/function.is-array.php#96724
*
* @param array $array
* The array to be tested.
*
* @return bool
* TRUE if the array is considered "associative".
*
* @internal
* This function is designed for internal use within @see _features_sanitize().
*
* @todo Needs unit tests.
*/
function _features_is_assoc($array) {
return is_array($array) && (0 !== count(array_diff_key($array, array_keys(array_keys($array)))) || count($array) == 0);
}
/**
* Returns a deep copy of the object or array without recursion.
*
* Properties or array values with recursive references are replaced with dummy
* string values.
*
* The algorithm operates on a serialized version of the data. It was introduced
* in features in #2543306, mainly for performance reasons. It is taken from
* https://code.google.com/p/formaldehyde/source/browse/trunk/formaldehyde.php.
* The algorithm is also used in the node_export module.
*
* @param object|array|mixed $o
* Original object or array, or an arbitrary value.
*
* @return object|array|mixed
* A copy of the object or array with recursion removed.
* If the original value was not an object or array, it is returned unaltered.
*
* @todo Needs unit tests.
*/
function features_remove_recursion($o) {
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, '_features_remove_recursion', $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, '_features_remove_recursion', substr($serialize, $last));
$o = unserialize($serialize);
}
}
return $o;
}
/**
* Callback function for preg_replace_callback() to remove recursion.
*
* The regular expression in calling code is '#(r|R):([0-9]+);#'. The source
* string is a serialized array or object. The modified source string will be
* sent back to unserialize(), so all replacements must be compatible with that.
*
* @param string[] $m
* Match parts from preg_replace_callback().
* $m[0] contains the complete matching fragment for "(r|R):([0-9]+);".
* $m[1] contains the sub-fragment for "(r|R)".
* $m[2] contains the sub-fragment for "([0-9]+)".
*
* @return string
* Replacement string fragment.
*
* @internal
* Designed for internal use within @see features_remove_recursion().
*
* @see preg_replace_callback()
*/
function _features_remove_recursion($m) {
$r = "\0{$m[1]}ecursion_features";
return 's:' . strlen($r . $m[2]) . ':"' . $r . $m[2] . '";';
}
/**
* Helper to removes a set of keys an object/array.
*
* @param object|array $item
* An object or array passed by reference.
* @param int[] $ignore_keys
* Array of keys to be ignored. Values are the level of the key.
* Format: $[$key] = $level.
* @param int $level
* Level of key to remove. Up to 2 levels deep because $item can still be
* recursive.
*
* @internal
* Designed for internal use within @see features_sanitize().
*/
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 string $component
* Name of a component type, e.g. "field_instance".
*
* @return int[]
* Map of keys to ignore at specific recursion levels.
* Format: $[$key] = $level
*
* @internal
* Designed for use within @see features_sanitize().
*
* @see features_features_ignore()
*
* @todo Add hook_features_ignore() in features.api.php.
*/
function _features_get_ignore_keys($component) {
static $cache;
if (!isset($cache[$component])) {
$cache[$component] = module_invoke_all('features_ignore', $component);
}
return $cache[$component];
}
Functions
Name | Description |
---|---|
features_detect_overrides | Detect differences between DB and code components of a feature. |
features_export_info | Generate code friendly to the Drupal .info format from a structured array. |
features_export_prepare | Prepares a feature export array into a finalized info array. |
features_export_render | Render feature export into an array representing its files. |
features_export_render_defaults | Return a code string representing an implementation of a defaults module hook. |
features_export_render_hooks | Generate an array of hooks and their raw code. |
features_get_component_states | Retrieve an array of features/components and their current states. |
features_get_default | Get defaults for a given module/component pair. |
features_get_default_alter_hook | Gets the available default hooks keyed by components. |
features_get_default_hooks | Gets the available default hooks keyed by components. |
features_get_default_map | Gets a map of components to their providing modules. |
features_get_normal | Get normal objects for a given module/component pair. |
features_get_signature | Gets an md5 signature for a the state of an object in code or database. |
features_get_storage | Gets a summary storage state for a feature. |
features_populate | Populates an export array with additional keys. |
features_remove_recursion | Returns a deep copy of the object or array without recursion. |
features_sanitize | Helper function to "sanitize" an array or object. |
features_semaphore | Gets, sets or deletes a semaphore for a given component. |
features_set_signature | Updates a module/component signature in the database. |
features_tar_create | Tar creation function. Written by dmitrig01. |
features_translatables_export | Creates PHP code with t() statements for given translatable strings. |
features_var_export | Alternative to var_export(), with a more pleasant output format. |
_features_export_maximize_dependencies | Completes a list of dependencies by adding all indirect dependencies as well. |
_features_export_minimize_dependencies | Minimizes a list of dependencies. |
_features_get_ignore_keys | Returns an array of keys to be ignored for various exportables. |
_features_get_signature_storage_type | Gets the current storage type for features component signatures. |
_features_is_assoc | Internal. Checks whether the array is "associative". |
_features_linetrim | Helper function to eliminate whitespace differences in code. |
_features_module_exists | Helper function to determine if a module is enabled. |
_features_populate | Populates an export array with additional components from the "pipe". |
_features_remove_ignores | Helper to removes a set of keys an object/array. |
_features_remove_recursion | Callback function for preg_replace_callback() to remove recursion. |
_features_resolve_dependencies | Iterates over data and convert to dependencies if already defined elsewhere. |
_features_sanitize | Internal. "Sanitizes" a (nested) array (or object) recursively. |