PageRenderTime 24ms CodeModel.GetById 31ms RepoModel.GetById 1ms app.codeStats 0ms

/sites/all/modules/features/features.export.inc

https://bitbucket.org/hjain/trinet
Pascal | 881 lines | 512 code | 35 blank | 334 comment | 49 complexity | 7767bc52c22bacab18dcdc319347e886 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-3.0, AGPL-1.0
  1. <?php
  2. /**
  3. * @param $items
  4. * @param $module_name
  5. * @return
  6. */
  7. function features_populate($items, $dependencies, $module_name) {
  8. // Sanitize items.
  9. $items = array_filter($items);
  10. $items['dependencies'] = drupal_map_assoc(array_filter($dependencies));
  11. // Populate stub
  12. $stub = array('features' => array(), 'dependencies' => array(), 'conflicts' => array());
  13. $export = _features_populate($items, $stub, $module_name);
  14. // Allow other modules to alter the export.
  15. drupal_alter('features_export', $export, $module_name);
  16. // Clean up and standardize order
  17. foreach (array_keys($export['features']) as $k) {
  18. ksort($export['features'][$k]);
  19. }
  20. ksort($export['features']);
  21. ksort($export['dependencies']);
  22. return $export;
  23. }
  24. /**
  25. * Iterate and descend into a feature definition to extract module
  26. * dependencies and feature definition. Calls hook_features_export for modules
  27. * that implement it.
  28. *
  29. * @param $pipe
  30. * Associative of array of module => info-for-module
  31. * @param $export
  32. * Associative array of items, and module dependencies which define a feature.
  33. * Passed by reference.
  34. *
  35. * @return fully populated $export array.
  36. */
  37. function _features_populate($pipe, &$export, $module_name = '') {
  38. features_include();
  39. foreach ($pipe as $component => $data) {
  40. static $processed = array();
  41. // Convert already defined items to dependencies.
  42. _features_resolve_dependencies($data, $export, $module_name, $component);
  43. if (!empty($data) && $function = features_hook($component, 'features_export')) {
  44. // Pass module-specific data and export array.
  45. // We don't use features_invoke() here since we need to pass $export by reference.
  46. $more = $function($data, $export, $module_name, $component);
  47. // Add the context information.
  48. $export['component'] = $component;
  49. // Allow other modules to manipulate the pipe to add in additional modules.
  50. drupal_alter(array('features_pipe', 'features_pipe_' . $component), $more, $data, $export);
  51. // Remove the component information.
  52. unset($export['component']);
  53. // Allow for export functions to request additional exports, but avoid
  54. // circular references on already processed components.
  55. $processed[$component] = isset($processed[$component]) ? array_merge($processed[$component], $data) : $data;
  56. if (!empty($more)) {
  57. // Remove already processed components.
  58. foreach ($more as $component_name => $component_data) {
  59. if (isset($processed[$component_name])) {
  60. $more[$component_name] = array_diff($component_data, $processed[$component_name]);
  61. }
  62. }
  63. if ($more = array_filter($more)) {
  64. _features_populate($more, $export, $module_name);
  65. }
  66. }
  67. }
  68. }
  69. return $export;
  70. }
  71. /**
  72. * Iterates over data and convert to dependencies if already defined elsewhere.
  73. */
  74. function _features_resolve_dependencies(&$data, &$export, $module_name, $component) {
  75. if ($map = features_get_default_map($component)) {
  76. foreach ($data as $key => $item) {
  77. // If this node type is provided by a different module, add it as a dependency
  78. if (isset($map[$item]) && $map[$item] != $module_name) {
  79. $export['dependencies'][$map[$item]] = $map[$item];
  80. unset($data[$key]);
  81. }
  82. }
  83. }
  84. }
  85. /**
  86. * Iterates over a list of dependencies and kills modules that are
  87. * captured by other modules 'higher up'.
  88. */
  89. function _features_export_minimize_dependencies($dependencies, $module_name = '') {
  90. // Ensure that the module doesn't depend upon itself
  91. if (!empty($module_name) && !empty($dependencies[$module_name])) {
  92. unset($dependencies[$module_name]);
  93. }
  94. // Do some cleanup:
  95. // - Remove modules required by Drupal core.
  96. // - Protect against direct circular dependencies.
  97. // - Remove "intermediate" dependencies.
  98. $required = drupal_required_modules();
  99. foreach ($dependencies as $k => $v) {
  100. if (empty($v) || in_array($v, $required)) {
  101. unset($dependencies[$k]);
  102. }
  103. else {
  104. $module = features_get_modules($v);
  105. if ($module && !empty($module->info['dependencies'])) {
  106. // If this dependency depends on the module itself, we have a circular dependency.
  107. // Don't let it happen. Only you can prevent forest fires.
  108. if (in_array($module_name, $module->info['dependencies'])) {
  109. unset($dependencies[$k]);
  110. }
  111. // Iterate through the dependency's dependencies and remove any dependencies
  112. // that are captured by it.
  113. else {
  114. foreach ($module->info['dependencies'] as $j => $dependency) {
  115. if (array_search($dependency, $dependencies) !== FALSE) {
  116. $position = array_search($dependency, $dependencies);
  117. unset($dependencies[$position]);
  118. }
  119. }
  120. }
  121. }
  122. }
  123. }
  124. return drupal_map_assoc(array_unique($dependencies));
  125. }
  126. /**
  127. * Iterates over a list of dependencies and maximize the list of modules.
  128. */
  129. function _features_export_maximize_dependencies($dependencies, $module_name = '', $maximized = array(), $first = TRUE) {
  130. foreach ($dependencies as $k => $v) {
  131. $parsed_dependency = drupal_parse_dependency($v);
  132. $name = $parsed_dependency['name'];
  133. if (!in_array($name, $maximized)) {
  134. $maximized[] = $name;
  135. $module = features_get_modules($name);
  136. if ($module && !empty($module->info['dependencies'])) {
  137. $maximized = array_merge($maximized, _features_export_maximize_dependencies($module->info['dependencies'], $module_name, $maximized, FALSE));
  138. }
  139. }
  140. }
  141. return array_unique($maximized);
  142. }
  143. /**
  144. * Prepare a feature export array into a finalized info array.
  145. */
  146. function features_export_prepare($export, $module_name, $reset = FALSE) {
  147. $existing = features_get_modules($module_name, $reset);
  148. // Prepare info string -- if module exists, merge into its existing info file
  149. $defaults = $existing ? $existing->info : array('core' => '7.x', 'package' => 'Features');
  150. $export = array_merge($defaults, $export);
  151. // Cleanup info array
  152. foreach ($export['features'] as $component => $data) {
  153. $export['features'][$component] = array_keys($data);
  154. }
  155. if (isset($export['dependencies'])) {
  156. $export['dependencies'] = array_values($export['dependencies']);
  157. }
  158. if (isset($export['conflicts'])) {
  159. unset($export['conflicts']);
  160. }
  161. // Order info array.
  162. $standard_info = array();
  163. foreach (array('name', 'description', 'core', 'package', 'php', 'version', 'project', 'dependencies') as $item) {
  164. if (isset($export[$item])) {
  165. $standard_info[$item] = $export[$item];
  166. }
  167. }
  168. $export = array_diff_assoc($export, $standard_info);
  169. ksort($export);
  170. return array_merge($standard_info, $export);
  171. }
  172. /**
  173. * Generate an array of hooks and their raw code.
  174. */
  175. function features_export_render_hooks($export, $module_name, $reset = FALSE) {
  176. features_include();
  177. $code = array();
  178. // Sort components to keep exported code consistent
  179. ksort($export['features']);
  180. foreach ($export['features'] as $component => $data) {
  181. if (!empty($data)) {
  182. // Sort the items so that we don't generate different exports based on order
  183. asort($data);
  184. if (features_hook($component, 'features_export_render')) {
  185. $hooks = features_invoke($component, 'features_export_render', $module_name, $data, $export);
  186. $code[$component] = $hooks;
  187. }
  188. }
  189. }
  190. return $code;
  191. }
  192. /**
  193. * Render feature export into an array representing its files.
  194. *
  195. * @param $export
  196. * An exported feature definition.
  197. * @param $module_name
  198. * The name of the module to be exported.
  199. * @param $reset
  200. * Boolean flag for resetting the module cache. Only set to true when
  201. * doing a final export for delivery.
  202. *
  203. * @return array of info file and module file contents.
  204. */
  205. function features_export_render($export, $module_name, $reset = FALSE) {
  206. $code = array();
  207. // Generate hook code
  208. $component_hooks = features_export_render_hooks($export, $module_name, $reset);
  209. $components = features_get_components();
  210. // Group component code into their respective files
  211. foreach ($component_hooks as $component => $hooks) {
  212. $file = array('name' => 'features');
  213. if (isset($components[$component]['default_file'])) {
  214. switch ($components[$component]['default_file']) {
  215. case FEATURES_DEFAULTS_INCLUDED:
  216. $file['name'] = "features.$component";
  217. break;
  218. case FEATURES_DEFAULTS_CUSTOM:
  219. $file['name'] = $components[$component]['default_filename'];
  220. break;
  221. }
  222. }
  223. if (!isset($code[$file['name']])) {
  224. $code[$file['name']] = array();
  225. }
  226. foreach ($hooks as $hook_name => $hook_info) {
  227. $hook_code = is_array($hook_info) ? $hook_info['code'] : $hook_info;
  228. $hook_args = is_array($hook_info) && !empty($hook_info['args']) ? $hook_info['args'] : '';
  229. $hook_file = is_array($hook_info) && !empty($hook_info['file']) ? $hook_info['file'] : $file['name'];
  230. $code[$hook_file][$hook_name] = features_export_render_defaults($module_name, $hook_name, $hook_code, $hook_args);
  231. }
  232. }
  233. // Finalize strings to be written to files
  234. $code = array_filter($code);
  235. foreach ($code as $filename => $contents) {
  236. $code[$filename] = "<?php\n/**\n * @file\n * {$module_name}.{$filename}.inc\n */\n\n". implode("\n\n", $contents) ."\n";
  237. }
  238. // Generate info file output
  239. $export = features_export_prepare($export, $module_name, $reset);
  240. $code['info'] = features_export_info($export);
  241. // Prepare the module
  242. // If module exists, let it be and include it in the files
  243. if ($existing = features_get_modules($module_name, TRUE)) {
  244. $code['module'] = file_get_contents($existing->filename);
  245. // If the current module file does not reference the features.inc include,
  246. // set a warning message.
  247. // @TODO this way of checking does not account for the possibility of inclusion instruction being commented out.
  248. if (isset($code['features']) && strpos($code['module'], "{$module_name}.features.inc") === FALSE) {
  249. features_log(t('@module does not appear to include the @include file.', array('@module' => "{$module_name}.module", '@include' => "{$module_name}.features.inc")), 'warning');
  250. }
  251. // Deprecated files. Display a message for any of these files letting the
  252. // user know that they may be removed.
  253. $deprecated = array(
  254. "{$module_name}.defaults",
  255. "{$module_name}.features.views",
  256. "{$module_name}.features.node"
  257. );
  258. foreach (file_scan_directory(drupal_get_path('module', $module_name), '/.*/') as $file) {
  259. if (in_array($file->name, $deprecated, TRUE)) {
  260. features_log(t('The file @filename has been deprecated and can be removed.', array('@filename' => $file->filename)), 'status');
  261. }
  262. elseif ($file->name === "{$module_name}.features" && empty($code['features'])) {
  263. $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";
  264. }
  265. }
  266. }
  267. // Add a stub module to include the defaults
  268. else if (!empty($code['features'])) {
  269. $code['module'] = "<?php\n/**\n * @file\n * Code for the {$export['name']} feature.\n */\n\ninclude_once '{$module_name}.features.inc';\n";
  270. }
  271. else {
  272. $code['module'] = "<?php\n/**\n * @file\n */\n\n// Drupal needs this blank file.\n";
  273. }
  274. return $code;
  275. }
  276. /**
  277. * Detect differences between DB and code components of a feature.
  278. */
  279. function features_detect_overrides($module) {
  280. static $cache;
  281. if (!isset($cache)) {
  282. $cache = array();
  283. }
  284. if (!isset($cache[$module->name])) {
  285. // Rebuild feature from .info file description and prepare an export from current DB state.
  286. $export = features_populate($module->info['features'], $module->info['dependencies'], $module->name);
  287. $export = features_export_prepare($export, $module->name);
  288. $overridden = array();
  289. // Compare feature info
  290. _features_sanitize($module->info);
  291. _features_sanitize($export);
  292. $compare = array('normal' => features_export_info($export), 'default' => features_export_info($module->info));
  293. if ($compare['normal'] !== $compare['default']) {
  294. $overridden['info'] = $compare;
  295. }
  296. // Collect differences at a per-component level
  297. $states = features_get_component_states(array($module->name), FALSE);
  298. foreach ($states[$module->name] as $component => $state) {
  299. if ($state != FEATURES_DEFAULT) {
  300. $normal = features_get_normal($component, $module->name);
  301. $default = features_get_default($component, $module->name);
  302. _features_sanitize($normal);
  303. _features_sanitize($default);
  304. $compare = array('normal' => features_var_export($normal), 'default' => features_var_export($default));
  305. if (_features_linetrim($compare['normal']) !== _features_linetrim($compare['default'])) {
  306. $overridden[$component] = $compare;
  307. }
  308. }
  309. }
  310. $cache[$module->name] = $overridden;
  311. }
  312. return $cache[$module->name];
  313. }
  314. /**
  315. * Gets the available default hooks keyed by components.
  316. */
  317. function features_get_default_hooks($component = NULL, $reset = FALSE) {
  318. return features_get_components($component, 'default_hook', $reset);
  319. }
  320. /**
  321. * Gets the available default hooks keyed by components.
  322. */
  323. function features_get_default_alter_hook($component) {
  324. $default_hook = features_get_components($component, 'default_hook');
  325. $alter_hook = features_get_components($component, 'alter_hook');
  326. $alter_type = features_get_components($component, 'alter_type');
  327. return empty($alter_type) || $alter_type != 'none' ? ($alter_hook ? $alter_hook : $default_hook) : FALSE;
  328. }
  329. /**
  330. * Return a code string representing an implementation of a defaults module hook.
  331. */
  332. function features_export_render_defaults($module, $hook, $code, $args = '') {
  333. $output = array();
  334. $output[] = "/**";
  335. $output[] = " * Implements hook_{$hook}().";
  336. $output[] = " */";
  337. $output[] = "function {$module}_{$hook}(" . $args . ") {";
  338. $output[] = $code;
  339. $output[] = "}";
  340. return implode("\n", $output);
  341. }
  342. /**
  343. * Generate code friendly to the Drupal .info format from a structured array.
  344. *
  345. * @param $info
  346. * An array or single value to put in a module's .info file.
  347. * @param $parents
  348. * Array of parent keys (internal use only).
  349. *
  350. * @return
  351. * A code string ready to be written to a module's .info file.
  352. */
  353. function features_export_info($info, $parents = array()) {
  354. $output = '';
  355. if (is_array($info)) {
  356. foreach ($info as $k => $v) {
  357. $child = $parents;
  358. $child[] = $k;
  359. $output .= features_export_info($v, $child);
  360. }
  361. }
  362. else if (!empty($info) && count($parents)) {
  363. $line = array_shift($parents);
  364. foreach ($parents as $key) {
  365. $line .= is_numeric($key) ? "[]" : "[{$key}]";
  366. }
  367. $line .= " = \"{$info}\"\n";
  368. return $line;
  369. }
  370. return $output;
  371. }
  372. /**
  373. * Tar creation function. Written by dmitrig01.
  374. *
  375. * @param $name
  376. * Filename of the file to be tarred.
  377. * @param $contents
  378. * String contents of the file.
  379. *
  380. * @return
  381. * A string of the tar file contents.
  382. */
  383. function features_tar_create($name, $contents) {
  384. $tar = '';
  385. $binary_data_first = pack("a100a8a8a8a12A12",
  386. $name,
  387. '100644 ', // File permissions
  388. ' 765 ', // UID,
  389. ' 765 ', // GID,
  390. sprintf("%11s ", decoct(strlen($contents))), // Filesize,
  391. sprintf("%11s", decoct(REQUEST_TIME)) // Creation time
  392. );
  393. $binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12", '', '', '', '', '', '', '', '', '', '');
  394. $checksum = 0;
  395. for ($i = 0; $i < 148; $i++) {
  396. $checksum += ord(substr($binary_data_first, $i, 1));
  397. }
  398. for ($i = 148; $i < 156; $i++) {
  399. $checksum += ord(' ');
  400. }
  401. for ($i = 156, $j = 0; $i < 512; $i++, $j++) {
  402. $checksum += ord(substr($binary_data_last, $j, 1));
  403. }
  404. $tar .= $binary_data_first;
  405. $tar .= pack("a8", sprintf("%6s ", decoct($checksum)));
  406. $tar .= $binary_data_last;
  407. $buffer = str_split($contents, 512);
  408. foreach ($buffer as $item) {
  409. $tar .= pack("a512", $item);
  410. }
  411. return $tar;
  412. }
  413. /**
  414. * Export var function -- from Views.
  415. */
  416. function features_var_export($var, $prefix = '', $init = TRUE) {
  417. if (is_object($var)) {
  418. $output = method_exists($var, 'export') ? $var->export() : features_var_export((array) $var);
  419. }
  420. else if (is_array($var)) {
  421. if (empty($var)) {
  422. $output = 'array()';
  423. }
  424. else {
  425. $output = "array(\n";
  426. foreach ($var as $key => $value) {
  427. // Using normal var_export on the key to ensure correct quoting.
  428. $output .= " " . var_export($key, TRUE) . " => " . features_var_export($value, ' ', FALSE) . ",\n";
  429. }
  430. $output .= ')';
  431. }
  432. }
  433. else if (is_bool($var)) {
  434. $output = $var ? 'TRUE' : 'FALSE';
  435. }
  436. else if (is_string($var) && strpos($var, "\n") !== FALSE) {
  437. // Replace line breaks in strings with a token for replacement
  438. // at the very end. This protects whitespace in strings from
  439. // unintentional indentation.
  440. $var = str_replace("\n", "***BREAK***", $var);
  441. $output = var_export($var, TRUE);
  442. }
  443. else {
  444. $output = var_export($var, TRUE);
  445. }
  446. if ($prefix) {
  447. $output = str_replace("\n", "\n$prefix", $output);
  448. }
  449. if ($init) {
  450. $output = str_replace("***BREAK***", "\n", $output);
  451. }
  452. return $output;
  453. }
  454. /**
  455. * Helper function to return an array of t()'d translatables strings.
  456. * Useful for providing a separate array of translatables with your
  457. * export so that string extractors like potx can detect them.
  458. */
  459. function features_translatables_export($translatables, $indent = '') {
  460. $output = '';
  461. $translatables = array_filter(array_unique($translatables));
  462. if (!empty($translatables)) {
  463. $output .= "{$indent}// Translatables\n";
  464. $output .= "{$indent}// Included for use with string extractors like potx.\n";
  465. sort($translatables);
  466. foreach ($translatables as $string) {
  467. $output .= "{$indent}t(" . features_var_export($string) . ");\n";
  468. }
  469. }
  470. return $output;
  471. }
  472. /**
  473. * Get a summary storage state for a feature.
  474. */
  475. function features_get_storage($module_name) {
  476. // Get component states, and array_diff against array(FEATURES_DEFAULT).
  477. // If the returned array has any states that don't match FEATURES_DEFAULT,
  478. // return the highest state.
  479. $states = features_get_component_states(array($module_name), FALSE);
  480. $states = array_diff($states[$module_name], array(FEATURES_DEFAULT));
  481. $storage = !empty($states) ? max($states) : FEATURES_DEFAULT;
  482. return $storage;
  483. }
  484. /**
  485. * Wrapper around features_get_[storage] to return an md5hash of a normalized
  486. * defaults/normal object array. Can be used to compare normal/default states
  487. * of a module's component.
  488. */
  489. function features_get_signature($state = 'default', $module_name, $component, $reset = FALSE) {
  490. switch ($state) {
  491. case 'cache':
  492. $codecache = variable_get('features_codecache', array());
  493. return isset($codecache[$module_name][$component]) ? $codecache[$module_name][$component] : FALSE;
  494. case 'default':
  495. $objects = features_get_default($component, $module_name, TRUE, $reset);
  496. break;
  497. case 'normal':
  498. $objects = features_get_normal($component, $module_name, $reset);
  499. break;
  500. }
  501. if (!empty($objects)) {
  502. $objects = (array) $objects;
  503. _features_sanitize($objects);
  504. return md5(_features_linetrim(features_var_export($objects)));
  505. }
  506. return FALSE;
  507. }
  508. /**
  509. * Set the signature of a module/component pair in the codecache.
  510. */
  511. function features_set_signature($module, $component, $signature = NULL) {
  512. $var_codecache = variable_get('features_codecache', array());
  513. $signature = isset($signature) ? $signature : features_get_signature('default', $module, $component, TRUE);
  514. $var_codecache[$module][$component] = $signature;
  515. variable_set('features_codecache', $var_codecache);
  516. }
  517. /**
  518. * Processing semaphore operations.
  519. */
  520. function features_semaphore($op, $component) {
  521. // Note: we don't use variable_get() here as the inited variable
  522. // static cache may be stale. Retrieving directly from the DB narrows
  523. // the possibility of collision.
  524. $semaphore = db_query("SELECT value FROM {variable} WHERE name = :name", array(':name' => 'features_semaphore'))->fetchField();
  525. $semaphore = !empty($semaphore) ? unserialize($semaphore) : array();
  526. switch ($op) {
  527. case 'get':
  528. return isset($semaphore[$component]) ? $semaphore[$component] : FALSE;
  529. case 'set':
  530. $semaphore[$component] = REQUEST_TIME;
  531. variable_set('features_semaphore', $semaphore);
  532. break;
  533. case 'del':
  534. if (isset($semaphore[$component])) {
  535. unset($semaphore[$component]);
  536. variable_set('features_semaphore', $semaphore);
  537. }
  538. break;
  539. }
  540. }
  541. /**
  542. * Get normal objects for a given module/component pair.
  543. */
  544. function features_get_normal($component, $module_name, $reset = FALSE) {
  545. static $cache;
  546. if (!isset($cache) || $reset) {
  547. $cache = array();
  548. }
  549. if (!isset($cache[$module_name][$component])) {
  550. features_include();
  551. $code = NULL;
  552. $module = features_get_features($module_name);
  553. // Special handling for dependencies component.
  554. if ($component === 'dependencies') {
  555. $cache[$module_name][$component] = isset($module->info['dependencies']) ? array_filter($module->info['dependencies'], 'module_exists') : array();
  556. }
  557. // All other components.
  558. else {
  559. $default_hook = features_get_default_hooks($component);
  560. if ($module && $default_hook && isset($module->info['features'][$component]) && features_hook($component, 'features_export_render')) {
  561. $code = features_invoke($component, 'features_export_render', $module_name, $module->info['features'][$component], NULL);
  562. $cache[$module_name][$component] = isset($code[$default_hook]) ? eval($code[$default_hook]) : FALSE;
  563. }
  564. }
  565. // Clear out vars for memory's sake.
  566. unset($code);
  567. unset($module);
  568. }
  569. return isset($cache[$module_name][$component]) ? $cache[$module_name][$component] : FALSE;
  570. }
  571. /**
  572. * Get defaults for a given module/component pair.
  573. */
  574. function features_get_default($component, $module_name = NULL, $alter = TRUE, $reset = FALSE) {
  575. static $cache = array();
  576. features_include();
  577. features_include_defaults($component);
  578. $default_hook = features_get_default_hooks($component);
  579. $components = features_get_components();
  580. // Collect defaults for all modules if no module name was specified.
  581. if (isset($module_name)) {
  582. $modules = array($module_name);
  583. }
  584. else {
  585. if ($component === 'dependencies') {
  586. $modules = array_keys(features_get_features());
  587. }
  588. else {
  589. $modules = array();
  590. foreach (features_get_component_map($component) as $component_modules) {
  591. $modules = array_merge($modules, $component_modules);
  592. }
  593. $modules = array_unique($modules);
  594. }
  595. }
  596. // Collect and cache information for each specified module.
  597. foreach ($modules as $m) {
  598. if (!isset($cache[$component][$m]) || $reset) {
  599. // Special handling for dependencies component.
  600. if ($component === 'dependencies') {
  601. $module = features_get_features($m);
  602. $cache[$component][$m] = isset($module->info['dependencies']) ? $module->info['dependencies'] : array();
  603. unset($module);
  604. }
  605. // All other components
  606. else {
  607. if ($default_hook && module_hook($m, $default_hook)) {
  608. $cache[$component][$m] = call_user_func("{$m}_{$default_hook}");
  609. $alter_type = features_get_components('alter_type', $component);
  610. if ($alter && (!isset($alter_type) || $alter_type == FEATURES_ALTER_TYPE_NORMAL)) {
  611. if ($alter_hook = features_get_default_alter_hook($component)) {
  612. drupal_alter($alter_hook, $cache[$component][$m]);
  613. }
  614. }
  615. }
  616. else {
  617. $cache[$component][$m] = FALSE;
  618. }
  619. }
  620. }
  621. }
  622. // A specific module was specified. Retrieve only its components.
  623. if (isset($module_name)) {
  624. return isset($cache[$component][$module_name]) ? $cache[$component][$module_name] : FALSE;
  625. }
  626. // No module was specified. Retrieve all components.
  627. $all_defaults = array();
  628. if (isset($cache[$component])) {
  629. foreach (array_filter($cache[$component]) as $module_components) {
  630. $all_defaults = array_merge($all_defaults, $module_components);
  631. }
  632. }
  633. return $all_defaults;
  634. }
  635. /**
  636. * Get a map of components to their providing modules.
  637. */
  638. function features_get_default_map($component, $attribute = NULL, $callback = NULL, $reset = FALSE) {
  639. static $map = array();
  640. features_include();
  641. features_include_defaults($component);
  642. if ((!isset($map[$component]) || $reset) && $default_hook = features_get_default_hooks($component)) {
  643. $map[$component] = array();
  644. foreach (module_implements($default_hook) as $module) {
  645. if ($defaults = features_get_default($component, $module)) {
  646. foreach ($defaults as $key => $object) {
  647. if (isset($callback)) {
  648. if ($object_key = $callback($object)) {
  649. $map[$component][$object_key] = $module;
  650. }
  651. }
  652. elseif (isset($attribute)) {
  653. if (is_object($object) && isset($object->{$attribute})) {
  654. $map[$component][$object->{$attribute}] = $module;
  655. }
  656. elseif (is_array($object) && isset($object[$attribute])) {
  657. $map[$component][$object[$attribute]] = $module;
  658. }
  659. }
  660. elseif (!isset($attribute) && !isset($callback)) {
  661. if (!is_numeric($key)) {
  662. $map[$component][$key] = $module;
  663. }
  664. }
  665. else {
  666. return FALSE;
  667. }
  668. }
  669. }
  670. }
  671. }
  672. return isset($map[$component]) ? $map[$component] : FALSE;
  673. }
  674. /**
  675. * Retrieve an array of features/components and their current states.
  676. */
  677. function features_get_component_states($features = array(), $rebuild_only = TRUE, $reset = FALSE) {
  678. static $cache;
  679. if (!isset($cache) || $reset) {
  680. $cache = array();
  681. }
  682. $features = !empty($features) ? $features : array_keys(features_get_features());
  683. // Retrieve only rebuildable components if requested.
  684. features_include();
  685. $components = array_keys(features_get_components());
  686. if ($rebuild_only) {
  687. foreach ($components as $k => $component) {
  688. if (!features_hook($component, 'features_rebuild')) {
  689. unset($components[$k]);
  690. }
  691. }
  692. }
  693. foreach ($features as $feature) {
  694. $cache[$feature] = isset($cache[$feature]) ? $cache[$feature] : array();
  695. if (module_exists($feature)) {
  696. foreach ($components as $component) {
  697. if (!isset($cache[$feature][$component])) {
  698. $normal = features_get_signature('normal', $feature, $component, $reset);
  699. $default = features_get_signature('default', $feature, $component, $reset);
  700. $codecache = features_get_signature('cache', $feature, $component, $reset);
  701. $semaphore = features_semaphore('get', $component);
  702. // DB and code states match, there is nothing more to check.
  703. if ($normal == $default) {
  704. $cache[$feature][$component] = FEATURES_DEFAULT;
  705. // Stale semaphores can be deleted.
  706. features_semaphore('del', $component);
  707. // Update code cache if it is stale, clear out semaphore if it stale.
  708. if ($default != $codecache) {
  709. features_set_signature($feature, $component, $default);
  710. }
  711. }
  712. // Component properly implements exportables.
  713. else if (!features_hook($component, 'features_rebuild')) {
  714. $cache[$feature][$component] = FEATURES_OVERRIDDEN;
  715. }
  716. // Component does not implement exportables.
  717. else {
  718. if (empty($semaphore)) {
  719. // Exception for dependencies. Dependencies are always rebuildable.
  720. if ($component === 'dependencies') {
  721. $cache[$feature][$component] = FEATURES_REBUILDABLE;
  722. }
  723. // All other rebuildable components require comparison.
  724. else {
  725. // Code has not changed, but DB does not match. User has DB overrides.
  726. if ($codecache == $default) {
  727. $cache[$feature][$component] = FEATURES_OVERRIDDEN;
  728. }
  729. // DB has no modifications to prior code state (or this is initial install).
  730. else if (empty($codecache) || $codecache == $normal) {
  731. $cache[$feature][$component] = FEATURES_REBUILDABLE;
  732. }
  733. // None of the states match. Requires user intervention.
  734. else if ($codecache != $default) {
  735. $cache[$feature][$component] = FEATURES_NEEDS_REVIEW;
  736. }
  737. }
  738. }
  739. else {
  740. // Semaphore is still within processing horizon. Do nothing.
  741. if ((REQUEST_TIME - $semaphore) < FEATURES_SEMAPHORE_TIMEOUT) {
  742. $cache[$feature][$component] = FEATURES_REBUILDING;
  743. }
  744. // A stale semaphore means a previous rebuild attempt did not complete.
  745. // Attempt to complete the rebuild.
  746. else {
  747. $cache[$feature][$component] = FEATURES_REBUILDABLE;
  748. }
  749. }
  750. }
  751. }
  752. }
  753. }
  754. }
  755. // Filter cached components on the way out to ensure that even if we have
  756. // cached more data than has been requested, the return value only reflects
  757. // the requested features/components.
  758. $return = $cache;
  759. $return = array_intersect_key($return, array_flip($features));
  760. foreach ($return as $k => $v) {
  761. $return[$k] = array_intersect_key($return[$k], array_flip($components));
  762. }
  763. return $return;
  764. }
  765. /**
  766. * Helper function to eliminate whitespace differences in code.
  767. */
  768. function _features_linetrim($code) {
  769. $code = explode("\n", $code);
  770. foreach ($code as $k => $line) {
  771. $code[$k] = trim($line);
  772. }
  773. return implode("\n", $code);
  774. }
  775. /**
  776. * "Sanitizes" an array recursively, performing two key operations:
  777. * - Sort an array by its keys (assoc) or values (non-assoc)
  778. * - Remove any null or empty values for associative arrays (array_filter()).
  779. */
  780. function _features_sanitize(&$array) {
  781. if (is_array($array)) {
  782. if (_features_is_assoc($array)) {
  783. ksort($array);
  784. $array = array_filter($array);
  785. }
  786. else {
  787. sort($array);
  788. }
  789. foreach ($array as $k => $v) {
  790. if (is_array($v)) {
  791. _features_sanitize($array[$k]);
  792. }
  793. }
  794. }
  795. }
  796. /**
  797. * Is the given array an associative array. This basically extracts the keys twice to get the
  798. * numerically ordered keys. It then does a diff with the original array and if there is no
  799. * key diff then the original array is not associative.
  800. *
  801. * NOTE: If you have non-sequential numerical keys, this will identify the array as assoc.
  802. *
  803. * Borrowed from: http://www.php.net/manual/en/function.is-array.php#96724
  804. *
  805. * @return True is the array is an associative array, false otherwise
  806. */
  807. function _features_is_assoc($array) {
  808. return (is_array($array) && (0 !== count(array_diff_key($array, array_keys(array_keys($array)))) || count($array)==0));
  809. }