features.export.inc 42 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168
  1. <?php
  2. /**
  3. * @param $info - feature info array
  4. * @param $module_name
  5. * @return fully populated export array
  6. */
  7. function features_populate($info, $module_name) {
  8. // Sanitize items.
  9. $items = !empty($info['features']) ? array_filter($info['features']) : array();
  10. $items['dependencies'] = !empty($info['dependencies']) ? drupal_map_assoc(array_filter($info['dependencies'])) : array();
  11. // Populate stub
  12. $stub = array('features' => array(), 'dependencies' => array(), 'conflicts' => array()) + $info + array('features_exclude' => array());
  13. $export = _features_populate($items, $stub, $module_name, TRUE);
  14. // Add Features API version. Any module with this entry in the .info file
  15. // will be treated as a Feature and included in the admin/build/features UI.
  16. $export['features']['features_api']['api:' . FEATURES_API] = TRUE;
  17. // Allow other modules to alter the export.
  18. drupal_alter('features_export', $export, $module_name);
  19. // Clean up and standardize order
  20. foreach (array_keys($export['features']) as $k) {
  21. ksort($export['features'][$k]);
  22. }
  23. ksort($export['features']);
  24. ksort($export['dependencies']);
  25. ksort($export['features_exclude']);
  26. return $export;
  27. }
  28. /**
  29. * Iterate and descend into a feature definition to extract module
  30. * dependencies and feature definition. Calls hook_features_export for modules
  31. * that implement it.
  32. *
  33. * @param $pipe
  34. * Associative of array of module => info-for-module
  35. * @param $export
  36. * Associative array of items, and module dependencies which define a feature.
  37. * Passed by reference.
  38. *
  39. * @return fully populated $export array.
  40. */
  41. function _features_populate($pipe, &$export, $module_name = '', $reset = FALSE) {
  42. // Ensure that the export will be created in the english language.
  43. $language = _features_export_language();
  44. if ($reset) {
  45. drupal_static_reset(__FUNCTION__);
  46. }
  47. $processed = &drupal_static(__FUNCTION__, array());
  48. features_include();
  49. foreach ($pipe as $component => $data) {
  50. // Convert already defined items to dependencies.
  51. // _features_resolve_dependencies($data, $export, $module_name, $component);
  52. // Remove any excluded items.
  53. if (!empty($export['features_exclude'][$component])) {
  54. $data = array_diff($data, $export['features_exclude'][$component]);
  55. if ($component == 'dependencies' && !empty($export['dependencies'])) {
  56. $export['dependencies'] = array_diff($export['dependencies'], $export['features_exclude'][$component]);
  57. }
  58. }
  59. if (!empty($data) && $function = features_hook($component, 'features_export')) {
  60. // Pass module-specific data and export array.
  61. // We don't use features_invoke() here since we need to pass $export by reference.
  62. $more = $function($data, $export, $module_name, $component);
  63. // Add the context information.
  64. $export['component'] = $component;
  65. $export['module_name'] = $module_name;
  66. // Allow other modules to manipulate the pipe to add in additional modules.
  67. drupal_alter(array('features_pipe', 'features_pipe_' . $component), $more, $data, $export);
  68. // Remove the component information.
  69. unset($export['component']);
  70. unset($export['module_name']);
  71. // Allow for export functions to request additional exports, but avoid
  72. // circular references on already processed components.
  73. $processed[$component] = isset($processed[$component]) ? array_merge($processed[$component], $data) : $data;
  74. if (!empty($more)) {
  75. // Remove already processed components.
  76. foreach ($more as $component_name => $component_data) {
  77. if (isset($processed[$component_name])) {
  78. $more[$component_name] = array_diff($component_data, $processed[$component_name]);
  79. }
  80. }
  81. if ($more = array_filter($more)) {
  82. _features_populate($more, $export, $module_name);
  83. }
  84. }
  85. }
  86. }
  87. _features_export_language($language);
  88. return $export;
  89. }
  90. /**
  91. * Iterates over data and convert to dependencies if already defined elsewhere.
  92. */
  93. function _features_resolve_dependencies(&$data, &$export, $module_name, $component) {
  94. if ($map = features_get_default_map($component)) {
  95. foreach ($data as $key => $item) {
  96. // If this node type is provided by a different module, add it as a dependency
  97. if (isset($map[$item]) && $map[$item] != $module_name) {
  98. $export['dependencies'][$map[$item]] = $map[$item];
  99. unset($data[$key]);
  100. }
  101. }
  102. }
  103. }
  104. /**
  105. * Iterates over a list of dependencies and kills modules that are
  106. * captured by other modules 'higher up'.
  107. */
  108. function _features_export_minimize_dependencies($dependencies, $module_name = '') {
  109. // Ensure that the module doesn't depend upon itself
  110. if (!empty($module_name) && !empty($dependencies[$module_name])) {
  111. unset($dependencies[$module_name]);
  112. }
  113. // Do some cleanup:
  114. // - Remove modules required by Drupal core.
  115. // - Protect against direct circular dependencies.
  116. // - Remove "intermediate" dependencies.
  117. $required = drupal_required_modules();
  118. foreach ($dependencies as $k => $v) {
  119. if (empty($v) || in_array($v, $required)) {
  120. unset($dependencies[$k]);
  121. }
  122. else {
  123. $module = features_get_modules($v);
  124. if ($module && !empty($module->info['dependencies'])) {
  125. // If this dependency depends on the module itself, we have a circular dependency.
  126. // Don't let it happen. Only you can prevent forest fires.
  127. if (in_array($module_name, $module->info['dependencies'])) {
  128. unset($dependencies[$k]);
  129. }
  130. // Iterate through the dependency's dependencies and remove any dependencies
  131. // that are captured by it.
  132. else {
  133. foreach ($module->info['dependencies'] as $j => $dependency) {
  134. if (array_search($dependency, $dependencies) !== FALSE) {
  135. $position = array_search($dependency, $dependencies);
  136. unset($dependencies[$position]);
  137. }
  138. }
  139. }
  140. }
  141. }
  142. }
  143. return drupal_map_assoc(array_unique($dependencies));
  144. }
  145. /**
  146. * Iterates over a list of dependencies and maximize the list of modules.
  147. */
  148. function _features_export_maximize_dependencies($dependencies, $module_name = '', $maximized = array(), $first = TRUE) {
  149. foreach ($dependencies as $k => $v) {
  150. $parsed_dependency = drupal_parse_dependency($v);
  151. $name = $parsed_dependency['name'];
  152. if (!in_array($name, $maximized)) {
  153. $maximized[] = $name;
  154. $module = features_get_modules($name);
  155. if ($module && !empty($module->info['dependencies'])) {
  156. $maximized = array_merge($maximized, _features_export_maximize_dependencies($module->info['dependencies'], $module_name, $maximized, FALSE));
  157. }
  158. }
  159. }
  160. return array_unique($maximized);
  161. }
  162. /**
  163. * Prepare a feature export array into a finalized info array.
  164. * @param $export
  165. * An exported feature definition.
  166. * @param $module_name
  167. * The name of the module to be exported.
  168. * @param $reset
  169. * Boolean flag for resetting the module cache. Only set to true when
  170. * doing a final export for delivery.
  171. */
  172. function features_export_prepare($export, $module_name, $reset = FALSE, $add_deprecated = TRUE) {
  173. $existing = features_get_modules($module_name, $reset);
  174. // copy certain exports directly into info
  175. $copy_list = array('scripts', 'stylesheets');
  176. foreach ($copy_list as $item) {
  177. if(isset($export[$item])) {
  178. $existing->info[$item] = $export[$item];
  179. }
  180. }
  181. // Prepare info string -- if module exists, merge into its existing info file
  182. $defaults = !empty($existing->info) ? $existing->info : array('core' => '7.x', 'package' => 'Features');
  183. $export = array_merge($defaults, $export);
  184. $deprecated = features_get_deprecated();
  185. // Cleanup info array
  186. foreach ($export['features'] as $component => $data) {
  187. // if performing the final export, do not export deprecated components
  188. if (($reset || !$add_deprecated) && !empty($deprecated[$component])) {
  189. unset($export['features'][$component]);
  190. }
  191. else {
  192. $export['features'][$component] = array_keys($data);
  193. }
  194. }
  195. if (isset($export['dependencies'])) {
  196. $export['dependencies'] = array_values($export['dependencies']);
  197. }
  198. if (isset($export['conflicts'])) {
  199. unset($export['conflicts']);
  200. }
  201. // Order info array.
  202. $standard_info = array();
  203. foreach (array_merge(array('name', 'description', 'core', 'package', 'version', 'project', 'dependencies'), $copy_list) as $item) {
  204. if (isset($export[$item])) {
  205. $standard_info[$item] = $export[$item];
  206. }
  207. }
  208. if (isset($export['php']) && ($export['php'] != DRUPAL_MINIMUM_PHP)) {
  209. $standard_info['php'] = $export['php'];
  210. }
  211. unset($export['php']);
  212. $export = features_array_diff_assoc_recursive($export, $standard_info);
  213. ksort($export);
  214. return array_merge($standard_info, $export);
  215. }
  216. /**
  217. * Generate an array of hooks and their raw code.
  218. */
  219. function features_export_render_hooks($export, $module_name, $reset = FALSE) {
  220. features_include();
  221. $code = array();
  222. // Sort components to keep exported code consistent
  223. ksort($export['features']);
  224. foreach ($export['features'] as $component => $data) {
  225. if (!empty($data)) {
  226. // Sort the items so that we don't generate different exports based on order
  227. asort($data);
  228. if (features_hook($component, 'features_export_render')) {
  229. $hooks = features_invoke($component, 'features_export_render', $module_name, $data, $export);
  230. $code[$component] = $hooks;
  231. }
  232. }
  233. }
  234. return $code;
  235. }
  236. /**
  237. * Render feature export into an array representing its files.
  238. *
  239. * @param $export
  240. * An exported feature definition.
  241. * @param $module_name
  242. * The name of the module to be exported.
  243. * @param $reset
  244. * Boolean flag for resetting the module cache. Only set to true when
  245. * doing a final export for delivery.
  246. *
  247. * @return array of info file and module file contents.
  248. */
  249. function features_export_render($export, $module_name, $reset = FALSE) {
  250. $code = array();
  251. // Generate hook code
  252. $component_hooks = features_export_render_hooks($export, $module_name, $reset);
  253. $components = features_get_components();
  254. $deprecated = features_get_deprecated($components);
  255. // Group component code into their respective files
  256. foreach ($component_hooks as $component => $hooks) {
  257. if ($reset && !empty($deprecated[$component])) {
  258. // skip deprecated components on final export
  259. continue;
  260. }
  261. $file = array('name' => 'features');
  262. if (isset($components[$component]['default_file'])) {
  263. switch ($components[$component]['default_file']) {
  264. case FEATURES_DEFAULTS_INCLUDED:
  265. $file['name'] = "features.$component";
  266. break;
  267. case FEATURES_DEFAULTS_CUSTOM:
  268. $file['name'] = $components[$component]['default_filename'];
  269. break;
  270. }
  271. }
  272. if (!isset($code[$file['name']])) {
  273. $code[$file['name']] = array();
  274. }
  275. foreach ($hooks as $hook_name => $hook_info) {
  276. // These are purely files that will be copied over.
  277. if (is_array($hook_info) && (!empty($hook_info['file_path']) || !empty($hook_info['file_content']))) {
  278. $code['_files'][$hook_name] = $hook_info;
  279. continue;
  280. }
  281. $hook_code = is_array($hook_info) ? $hook_info['code'] : $hook_info;
  282. $hook_args = is_array($hook_info) && !empty($hook_info['args']) ? $hook_info['args'] : '';
  283. $hook_file = is_array($hook_info) && !empty($hook_info['file']) ? $hook_info['file'] : $file['name'];
  284. $code[$hook_file][$hook_name] = features_export_render_defaults($module_name, $hook_name, $hook_code, $hook_args);
  285. }
  286. }
  287. // Finalize strings to be written to files
  288. $code = array_filter($code);
  289. foreach ($code as $filename => $contents) {
  290. if ($filename != '_files') {
  291. $code[$filename] = "<?php\n\n/**\n * @file\n * {$module_name}.{$filename}.inc\n */\n\n". implode("\n\n", $contents) ."\n";
  292. }
  293. }
  294. // Allow extra files be added to feature.
  295. if ($files = module_invoke_all('features_export_files', $module_name, $export)) {
  296. $code['_files'] = !empty($code['_files']) ? $code['_files'] + $files : $files;
  297. }
  298. if (!empty($code['_files'])) {
  299. drupal_alter('features_export_files', $code['_files'], $module_name, $export);
  300. }
  301. // Generate info file output
  302. $export = features_export_prepare($export, $module_name, $reset);
  303. $code['info'] = features_export_info($export);
  304. // Used to create or manipulate the generated .module for features.inc.
  305. $modulefile_features_inc = "<?php\n\n/**\n * @file\n * Code for the {$export['name']} feature.\n */\n\ninclude_once '{$module_name}.features.inc';\n";
  306. $modulefile_blank = "<?php\n\n/**\n * @file\n * Drupal needs this blank file.\n */\n";
  307. // Prepare the module
  308. // If module exists, let it be and include it in the files
  309. if ($existing = features_get_modules($module_name, TRUE)) {
  310. $code['module'] = file_get_contents($existing->filename);
  311. // If the current module file does not reference the features.inc include,
  312. // @TODO this way of checking does not account for the possibility of inclusion instruction being commented out.
  313. if (isset($code['features']) && strpos($code['module'], "{$module_name}.features.inc") === FALSE) {
  314. // If .module does not begin with <?php\n, just add a warning.
  315. if (strpos($code['module'], "<?php\n") !== 0) {
  316. features_log(t('@module does not appear to include the @include file.', array('@module' => "{$module_name}.module", '@include' => "{$module_name}.features.inc")), 'warning');
  317. }
  318. else {
  319. // Remove the old message if it exists, else just remove the <?php
  320. $length = strpos($code['module'], $modulefile_blank) === 0 ? strlen($modulefile_blank) : 6;
  321. $code['module'] = $modulefile_features_inc . substr($code['module'], $length);
  322. }
  323. }
  324. if ($reset) {
  325. // only check for deprecated files on final export
  326. // Deprecated files. Display a message for any of these files letting the
  327. // user know that they may be removed.
  328. $deprecated_files = array(
  329. "{$module_name}.defaults",
  330. "{$module_name}.features.views",
  331. "{$module_name}.features.node"
  332. );
  333. // add deprecated components
  334. foreach ($deprecated as $component) {
  335. $info = features_get_components($component);
  336. $filename = isset($info['default_file']) && $info['default_file'] == FEATURES_DEFAULTS_CUSTOM ? $info['default_filename'] : "features.{$component}";
  337. $deprecated_files[] = "{$module_name}.$filename";
  338. }
  339. foreach (file_scan_directory(drupal_get_path('module', $module_name), '/.*/') as $file) {
  340. if (in_array($file->name, $deprecated_files, TRUE)) {
  341. features_log(t('The file @filename has been deprecated and can be removed.', array('@filename' => $file->filename)), 'status');
  342. }
  343. elseif ($file->name === "{$module_name}.features" && empty($code['features'])) {
  344. // Try and remove features.inc include.
  345. if (strpos($code['module'], "{$module_name}.features.inc")) {
  346. $code['module'] = str_replace($modulefile_features_inc, $modulefile_blank, $code['module']);
  347. }
  348. // If unable to remove the include, add a message to remove.
  349. if (strpos($code['module'], "{$module_name}.features.inc")) {
  350. $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";
  351. }
  352. else {
  353. $code['features'] = "<?php\n\n// This file is deprecated and can be removed.\n";
  354. }
  355. }
  356. }
  357. }
  358. }
  359. // Add a stub module to include the defaults
  360. else if (!empty($code['features'])) {
  361. $code['module'] = $modulefile_features_inc;
  362. }
  363. else {
  364. $code['module'] = $modulefile_blank;
  365. }
  366. return $code;
  367. }
  368. /**
  369. * Detect differences between DB and code components of a feature.
  370. */
  371. function features_detect_overrides($module) {
  372. $cache = &drupal_static(__FUNCTION__, array());
  373. if (!isset($cache[$module->name])) {
  374. // Rebuild feature from .info file description and prepare an export from current DB state.
  375. $export = features_populate($module->info, $module->name);
  376. $export = features_export_prepare($export, $module->name, FALSE, FALSE);
  377. $overridden = array();
  378. // Compare feature info
  379. features_sanitize($module->info);
  380. features_sanitize($export);
  381. $compare = array('normal' => features_export_info($export), 'default' => features_export_info($module->info));
  382. if ($compare['normal'] !== $compare['default']) {
  383. $overridden['info'] = $compare;
  384. }
  385. // Collect differences at a per-component level
  386. $states = features_get_component_states(array($module->name), FALSE);
  387. foreach ($states[$module->name] as $component => $state) {
  388. if ($state != FEATURES_DEFAULT) {
  389. $normal = features_get_normal($component, $module->name);
  390. $default = features_get_default($component, $module->name);
  391. features_sanitize($normal, $component);
  392. features_sanitize($default, $component);
  393. $compare = array('normal' => features_var_export($normal), 'default' => features_var_export($default));
  394. if (_features_linetrim($compare['normal']) !== _features_linetrim($compare['default'])) {
  395. $overridden[$component] = $compare;
  396. }
  397. }
  398. }
  399. $cache[$module->name] = $overridden;
  400. }
  401. return $cache[$module->name];
  402. }
  403. /**
  404. * Gets the available default hooks keyed by components.
  405. */
  406. function features_get_default_hooks($component = NULL, $reset = FALSE) {
  407. return features_get_components($component, 'default_hook', $reset);
  408. }
  409. /**
  410. * Gets the available default hooks keyed by components.
  411. */
  412. function features_get_default_alter_hook($component) {
  413. $default_hook = features_get_components($component, 'default_hook');
  414. $alter_hook = features_get_components($component, 'alter_hook');
  415. $alter_type = features_get_components($component, 'alter_type');
  416. return empty($alter_type) || $alter_type != 'none' ? ($alter_hook ? $alter_hook : $default_hook) : FALSE;
  417. }
  418. /**
  419. * Return a code string representing an implementation of a defaults module hook.
  420. */
  421. function features_export_render_defaults($module, $hook, $code, $args = '') {
  422. $output = array();
  423. $output[] = "/**";
  424. $output[] = " * Implements hook_{$hook}().";
  425. $output[] = " */";
  426. $output[] = "function {$module}_{$hook}(" . $args . ") {";
  427. $output[] = $code;
  428. $output[] = "}";
  429. return implode("\n", $output);
  430. }
  431. /**
  432. * Generate code friendly to the Drupal .info format from a structured array.
  433. *
  434. * @param $info
  435. * An array or single value to put in a module's .info file.
  436. * @param $parents
  437. * Array of parent keys (internal use only).
  438. *
  439. * @return
  440. * A code string ready to be written to a module's .info file.
  441. */
  442. function features_export_info($info, $parents = array()) {
  443. $output = '';
  444. if (is_array($info)) {
  445. foreach ($info as $k => $v) {
  446. $child = $parents;
  447. $child[] = $k;
  448. $output .= features_export_info($v, $child);
  449. }
  450. }
  451. else if (!empty($info) && count($parents)) {
  452. $line = array_shift($parents);
  453. foreach ($parents as $key) {
  454. $line .= is_numeric($key) ? "[]" : "[{$key}]";
  455. }
  456. $line .= " = {$info}\n";
  457. return $line;
  458. }
  459. return $output;
  460. }
  461. /**
  462. * Tar creation function. Written by dmitrig01.
  463. *
  464. * @param $name
  465. * Filename of the file to be tarred.
  466. * @param $contents
  467. * String contents of the file.
  468. *
  469. * @return
  470. * A string of the tar file contents.
  471. */
  472. function features_tar_create($name, $contents) {
  473. /* http://www.mkssoftware.com/docs/man4/tar.4.asp */
  474. /* http://www.phpclasses.org/browse/file/21200.html */
  475. $tar = '';
  476. $bigheader = $header = '';
  477. if (strlen($name) > 100) {
  478. $bigheader = pack("a100a8a8a8a12a12a8a1a100a6a2a32a32a8a8a155a12",
  479. '././@LongLink', '0000000', '0000000', '0000000',
  480. sprintf("%011o", strlen($name)), '00000000000',
  481. ' ', 'L', '', 'ustar ', '0',
  482. '', '', '', '', '', '');
  483. $bigheader .= str_pad($name, floor((strlen($name) + 512 - 1) / 512) * 512, "\0");
  484. $checksum = 0;
  485. for ($i = 0; $i < 512; $i++) {
  486. $checksum += ord(substr($bigheader, $i, 1));
  487. }
  488. $bigheader = substr_replace($bigheader, sprintf("%06o", $checksum)."\0 ", 148, 8);
  489. }
  490. $header = pack("a100a8a8a8a12a12a8a1a100a6a2a32a32a8a8a155a12", // book the memorie area
  491. substr($name,0,100), // 0 100 File name
  492. '100644 ', // File permissions
  493. ' 765 ', // UID,
  494. ' 765 ', // GID,
  495. sprintf("%11s ", decoct(strlen($contents))), // Filesize,
  496. sprintf("%11s", decoct(REQUEST_TIME)), // Creation time
  497. ' ', // 148 8 Check sum for header block
  498. '', // 156 1 Link indicator / ustar Type flag
  499. '', // 157 100 Name of linked file
  500. 'ustar ', // 257 6 USTAR indicator "ustar"
  501. ' ', // 263 2 USTAR version "00"
  502. '', // 265 32 Owner user name
  503. '', // 297 32 Owner group name
  504. '', // 329 8 Device major number
  505. '', // 337 8 Device minor number
  506. '', // 345 155 Filename prefix
  507. ''); // 500 12 ??
  508. $checksum = 0;
  509. for ($i = 0; $i < 512; $i++) {
  510. $checksum += ord(substr($header, $i, 1));
  511. }
  512. $header = substr_replace($header, sprintf("%06o", $checksum)."\0 ", 148, 8);
  513. $tar = $bigheader.$header;
  514. $buffer = str_split($contents, 512);
  515. foreach ($buffer as $item) {
  516. $tar .= pack("a512", $item);
  517. }
  518. return $tar;
  519. }
  520. /**
  521. * Export var function -- from Views.
  522. */
  523. function features_var_export($var, $prefix = '', $init = TRUE, $count = 0) {
  524. if ($count > 50) {
  525. watchdog('features', 'Recursion depth reached in features_var_export', array());
  526. return '';
  527. }
  528. if (is_object($var)) {
  529. $output = method_exists($var, 'export') ? $var->export() : features_var_export((array) $var, '', FALSE, $count+1);
  530. }
  531. else if (is_array($var)) {
  532. if (empty($var)) {
  533. $output = 'array()';
  534. }
  535. else {
  536. $output = "array(\n";
  537. foreach ($var as $key => $value) {
  538. // Using normal var_export on the key to ensure correct quoting.
  539. $output .= " " . var_export($key, TRUE) . " => " . features_var_export($value, ' ', FALSE, $count+1) . ",\n";
  540. }
  541. $output .= ')';
  542. }
  543. }
  544. else if (is_bool($var)) {
  545. $output = $var ? 'TRUE' : 'FALSE';
  546. }
  547. else if (is_int($var)) {
  548. $output = intval($var);
  549. }
  550. else if (is_numeric($var)) {
  551. $floatval = floatval($var);
  552. if (is_string($var) && ((string) $floatval !== $var)) {
  553. // Do not convert a string to a number if the string
  554. // representation of that number is not identical to the
  555. // original value.
  556. $output = var_export($var, TRUE);
  557. }
  558. else {
  559. $output = $floatval;
  560. }
  561. }
  562. else if (is_string($var) && strpos($var, "\n") !== FALSE) {
  563. // Replace line breaks in strings with a token for replacement
  564. // at the very end. This protects whitespace in strings from
  565. // unintentional indentation.
  566. $var = str_replace("\n", "***BREAK***", $var);
  567. $output = var_export($var, TRUE);
  568. }
  569. else {
  570. $output = var_export($var, TRUE);
  571. }
  572. if ($prefix) {
  573. $output = str_replace("\n", "\n$prefix", $output);
  574. }
  575. if ($init) {
  576. $output = str_replace("***BREAK***", "\n", $output);
  577. }
  578. return $output;
  579. }
  580. /**
  581. * Helper function to return an array of t()'d translatables strings.
  582. * Useful for providing a separate array of translatables with your
  583. * export so that string extractors like potx can detect them.
  584. */
  585. function features_translatables_export($translatables, $indent = '') {
  586. $output = '';
  587. $translatables = array_filter(array_unique($translatables));
  588. if (!empty($translatables)) {
  589. $output .= "{$indent}// Translatables\n";
  590. $output .= "{$indent}// Included for use with string extractors like potx.\n";
  591. sort($translatables);
  592. foreach ($translatables as $string) {
  593. $output .= "{$indent}t(" . features_var_export($string) . ");\n";
  594. }
  595. }
  596. return $output;
  597. }
  598. /**
  599. * Get a summary storage state for a feature.
  600. */
  601. function features_get_storage($module_name) {
  602. // Get component states, and array_diff against array(FEATURES_DEFAULT).
  603. // If the returned array has any states that don't match FEATURES_DEFAULT,
  604. // return the highest state.
  605. $states = features_get_component_states(array($module_name), FALSE);
  606. $states = array_diff($states[$module_name], array(FEATURES_DEFAULT));
  607. $storage = !empty($states) ? max($states) : FEATURES_DEFAULT;
  608. return $storage;
  609. }
  610. /**
  611. * Wrapper around features_get_[storage] to return an md5hash of a normalized
  612. * defaults/normal object array. Can be used to compare normal/default states
  613. * of a module's component.
  614. */
  615. function features_get_signature($state = 'default', $module_name, $component, $reset = FALSE) {
  616. switch ($state) {
  617. case 'cache':
  618. $codecache = variable_get('features_codecache', array());
  619. return isset($codecache[$module_name][$component]) ? $codecache[$module_name][$component] : FALSE;
  620. case 'default':
  621. $objects = features_get_default($component, $module_name, TRUE, $reset);
  622. break;
  623. case 'normal':
  624. $objects = features_get_normal($component, $module_name, $reset);
  625. break;
  626. }
  627. if (!empty($objects)) {
  628. features_sanitize($objects, $component);
  629. return md5(_features_linetrim(features_var_export($objects)));
  630. }
  631. return FALSE;
  632. }
  633. /**
  634. * Set the signature of a module/component pair in the codecache.
  635. */
  636. function features_set_signature($module, $component, $signature = NULL) {
  637. $var_codecache = variable_get('features_codecache', array());
  638. $signature = isset($signature) ? $signature : features_get_signature('default', $module, $component, TRUE);
  639. $var_codecache[$module][$component] = $signature;
  640. variable_set('features_codecache', $var_codecache);
  641. }
  642. /**
  643. * Processing semaphore operations.
  644. */
  645. function features_semaphore($op, $component) {
  646. // Note: we don't use variable_get() here as the inited variable
  647. // static cache may be stale. Retrieving directly from the DB narrows
  648. // the possibility of collision.
  649. $semaphore = db_query("SELECT value FROM {variable} WHERE name = :name", array(':name' => 'features_semaphore'))->fetchField();
  650. $semaphore = !empty($semaphore) ? unserialize($semaphore) : array();
  651. switch ($op) {
  652. case 'get':
  653. return isset($semaphore[$component]) ? $semaphore[$component] : FALSE;
  654. case 'set':
  655. $semaphore[$component] = REQUEST_TIME;
  656. variable_set('features_semaphore', $semaphore);
  657. break;
  658. case 'del':
  659. if (isset($semaphore[$component])) {
  660. unset($semaphore[$component]);
  661. variable_set('features_semaphore', $semaphore);
  662. }
  663. break;
  664. }
  665. }
  666. /**
  667. * Get normal objects for a given module/component pair.
  668. */
  669. function features_get_normal($component, $module_name, $reset = FALSE) {
  670. if ($reset) {
  671. drupal_static_reset(__FUNCTION__);
  672. }
  673. $cache = &drupal_static(__FUNCTION__, array());
  674. if (!isset($cache[$module_name][$component])) {
  675. features_include();
  676. $code = NULL;
  677. $module = features_get_features($module_name);
  678. // Special handling for dependencies component.
  679. if ($component === 'dependencies') {
  680. $cache[$module_name][$component] = isset($module->info['dependencies']) ? array_filter($module->info['dependencies'], '_features_module_exists') : array();
  681. }
  682. // All other components.
  683. else {
  684. $default_hook = features_get_default_hooks($component);
  685. if ($module && $default_hook && isset($module->info['features'][$component]) && features_hook($component, 'features_export_render')) {
  686. $code = features_invoke($component, 'features_export_render', $module_name, $module->info['features'][$component], NULL);
  687. $cache[$module_name][$component] = isset($code[$default_hook]) ? eval($code[$default_hook]) : FALSE;
  688. }
  689. }
  690. // Clear out vars for memory's sake.
  691. unset($code);
  692. unset($module);
  693. }
  694. return isset($cache[$module_name][$component]) ? $cache[$module_name][$component] : FALSE;
  695. }
  696. /**
  697. * Helper function to determine if a module is enabled
  698. * @param $module
  699. * This module name comes from the .info file and can have version info in it.
  700. */
  701. function _features_module_exists($module) {
  702. $parsed_dependency = drupal_parse_dependency($module);
  703. $name = $parsed_dependency['name'];
  704. return module_exists($name);
  705. }
  706. /**
  707. * Get defaults for a given module/component pair.
  708. */
  709. function features_get_default($component, $module_name = NULL, $alter = TRUE, $reset = FALSE) {
  710. $cache = &drupal_static(__FUNCTION__, array());
  711. $alter = !empty($alter); // ensure $alter is a true/false boolean
  712. features_include();
  713. features_include_defaults($component);
  714. $default_hook = features_get_default_hooks($component);
  715. $components = features_get_components();
  716. // Collect defaults for all modules if no module name was specified.
  717. if (isset($module_name)) {
  718. $modules = array($module_name);
  719. }
  720. else {
  721. if ($component === 'dependencies') {
  722. $modules = array_keys(features_get_features());
  723. }
  724. else {
  725. $modules = array();
  726. foreach (features_get_component_map($component) as $component_modules) {
  727. $modules = array_merge($modules, $component_modules);
  728. }
  729. $modules = array_unique($modules);
  730. }
  731. }
  732. // Collect and cache information for each specified module.
  733. foreach ($modules as $m) {
  734. if (!isset($cache[$component][$alter][$m]) || $reset) {
  735. // Special handling for dependencies component.
  736. if ($component === 'dependencies') {
  737. $module = features_get_features($m);
  738. $cache[$component][$alter][$m] = isset($module->info['dependencies']) ? $module->info['dependencies'] : array();
  739. unset($module);
  740. }
  741. // All other components
  742. else {
  743. if ($default_hook && module_hook($m, $default_hook)) {
  744. $cache[$component][$alter][$m] = call_user_func("{$m}_{$default_hook}");
  745. if (is_array($cache[$component][$alter][$m])) {
  746. $alter_type = features_get_components('alter_type', $component);
  747. if ($alter && (!isset($alter_type) || $alter_type == FEATURES_ALTER_TYPE_NORMAL)) {
  748. if ($alter_hook = features_get_default_alter_hook($component)) {
  749. drupal_alter($alter_hook, $cache[$component][$alter][$m]);
  750. }
  751. }
  752. }
  753. else {
  754. $cache[$component][$alter][$m] = FALSE;
  755. }
  756. }
  757. else {
  758. $cache[$component][$alter][$m] = FALSE;
  759. }
  760. }
  761. }
  762. }
  763. // A specific module was specified. Retrieve only its components.
  764. if (isset($module_name)) {
  765. return isset($cache[$component][$alter][$module_name]) ? $cache[$component][$alter][$module_name] : FALSE;
  766. }
  767. // No module was specified. Retrieve all components.
  768. $all_defaults = array();
  769. if (isset($cache[$component][$alter])) {
  770. foreach (array_filter($cache[$component][$alter]) as $module_components) {
  771. $all_defaults = array_merge($all_defaults, $module_components);
  772. }
  773. }
  774. return $all_defaults;
  775. }
  776. /**
  777. * Get a map of components to their providing modules.
  778. *
  779. * @param string $component
  780. * @param string $attribute
  781. * @param callable $callback
  782. * @param bool $reset
  783. *
  784. * @return array|bool
  785. */
  786. function features_get_default_map($component, $attribute = NULL, $callback = NULL, $reset = FALSE) {
  787. $map = &drupal_static(__FUNCTION__, array());
  788. global $features_ignore_conflicts;
  789. if ($features_ignore_conflicts) {
  790. return FALSE;
  791. }
  792. features_include();
  793. features_include_defaults($component);
  794. if ((!isset($map[$component]) || $reset) && $default_hook = features_get_default_hooks($component)) {
  795. $map[$component] = array();
  796. foreach (module_implements($default_hook) as $module) {
  797. if ($defaults = features_get_default($component, $module)) {
  798. foreach ($defaults as $key => $object) {
  799. if (isset($callback)) {
  800. if ($object_key = $callback($object)) {
  801. $map[$component][$object_key] = $module;
  802. }
  803. }
  804. elseif (isset($attribute)) {
  805. if (is_object($object) && isset($object->{$attribute})) {
  806. $map[$component][$object->{$attribute}] = $module;
  807. }
  808. elseif (is_array($object) && isset($object[$attribute])) {
  809. $map[$component][$object[$attribute]] = $module;
  810. }
  811. }
  812. elseif (!isset($attribute) && !isset($callback)) {
  813. if (!is_numeric($key)) {
  814. $map[$component][$key] = $module;
  815. }
  816. }
  817. else {
  818. return FALSE;
  819. }
  820. }
  821. }
  822. }
  823. }
  824. return isset($map[$component]) ? $map[$component] : FALSE;
  825. }
  826. /**
  827. * Retrieve an array of features/components and their current states.
  828. */
  829. function features_get_component_states($features = array(), $rebuild_only = TRUE, $reset = FALSE) {
  830. // Ensure that the export will be created in the English language.
  831. $language = _features_export_language();
  832. if ($reset) {
  833. drupal_static_reset(__FUNCTION__);
  834. }
  835. $cache = &drupal_static(__FUNCTION__, array());
  836. $all_features = features_get_features();
  837. $features = !empty($features) ? $features : array_keys($all_features);
  838. // Retrieve only rebuildable components if requested.
  839. features_include();
  840. $components = array_keys(features_get_components(NULL, NULL, $reset));
  841. if ($rebuild_only) {
  842. foreach ($components as $k => $component) {
  843. if (!features_hook($component, 'features_rebuild')) {
  844. unset($components[$k]);
  845. }
  846. }
  847. }
  848. foreach ($features as $feature) {
  849. $cache[$feature] = isset($cache[$feature]) ? $cache[$feature] : array();
  850. if (module_exists($feature) && !empty($all_features[$feature]->components)) {
  851. foreach (array_intersect($all_features[$feature]->components, $components) as $component) {
  852. if (!isset($cache[$feature][$component])) {
  853. $normal = features_get_signature('normal', $feature, $component, $reset);
  854. $default = features_get_signature('default', $feature, $component, $reset);
  855. $codecache = features_get_signature('cache', $feature, $component, $reset);
  856. $semaphore = features_semaphore('get', $component);
  857. // DB and code states match, there is nothing more to check.
  858. if ($normal == $default) {
  859. $cache[$feature][$component] = FEATURES_DEFAULT;
  860. // Stale semaphores can be deleted.
  861. features_semaphore('del', $component);
  862. // Update code cache if it is stale, clear out semaphore if it stale.
  863. if ($default != $codecache) {
  864. features_set_signature($feature, $component, $default);
  865. }
  866. }
  867. // Component properly implements exportables.
  868. else if (!features_hook($component, 'features_rebuild')) {
  869. $cache[$feature][$component] = FEATURES_OVERRIDDEN;
  870. }
  871. // Component does not implement exportables.
  872. else {
  873. if (empty($semaphore)) {
  874. // Exception for dependencies. Dependencies are always rebuildable.
  875. if ($component === 'dependencies') {
  876. $cache[$feature][$component] = FEATURES_REBUILDABLE;
  877. }
  878. // All other rebuildable components require comparison.
  879. else {
  880. // Code has not changed, but DB does not match. User has DB overrides.
  881. if ($codecache == $default) {
  882. $cache[$feature][$component] = FEATURES_OVERRIDDEN;
  883. }
  884. // DB has no modifications to prior code state (or this is initial install).
  885. else if (empty($codecache) || $codecache == $normal) {
  886. $cache[$feature][$component] = FEATURES_REBUILDABLE;
  887. }
  888. // None of the states match. Requires user intervention.
  889. else if ($codecache != $default) {
  890. $cache[$feature][$component] = FEATURES_NEEDS_REVIEW;
  891. }
  892. }
  893. }
  894. else {
  895. // Semaphore is still within processing horizon. Do nothing.
  896. if ((REQUEST_TIME - $semaphore) < FEATURES_SEMAPHORE_TIMEOUT) {
  897. $cache[$feature][$component] = FEATURES_REBUILDING;
  898. }
  899. // A stale semaphore means a previous rebuild attempt did not complete.
  900. // Attempt to complete the rebuild.
  901. else {
  902. $cache[$feature][$component] = FEATURES_REBUILDABLE;
  903. }
  904. }
  905. }
  906. }
  907. }
  908. }
  909. }
  910. // Filter cached components on the way out to ensure that even if we have
  911. // cached more data than has been requested, the return value only reflects
  912. // the requested features/components.
  913. $return = $cache;
  914. $return = array_intersect_key($return, array_flip($features));
  915. foreach ($return as $k => $v) {
  916. $return[$k] = array_intersect_key($return[$k], array_flip($components));
  917. }
  918. _features_export_language($language);
  919. return $return;
  920. }
  921. /**
  922. * Helper function to eliminate whitespace differences in code.
  923. */
  924. function _features_linetrim($code) {
  925. $code = explode("\n", $code);
  926. foreach ($code as $k => $line) {
  927. $code[$k] = trim($line);
  928. }
  929. return implode("\n", $code);
  930. }
  931. /**
  932. * Helper function to "sanitize" an array or object.
  933. * Converts everything to an array, sorts the keys, removes recursion.
  934. * @param $array
  935. * @param $component string name of component
  936. * @param bool $remove_empty if set, remove null or empty values for assoc arrays.
  937. */
  938. function features_sanitize(&$array, $component = NULL, $remove_empty = TRUE) {
  939. $array = features_remove_recursion($array);
  940. if (isset($component)) {
  941. $ignore_keys = _features_get_ignore_keys($component);
  942. // remove keys to be ignored
  943. if (count($ignore_keys)) {
  944. _features_remove_ignores($array, $ignore_keys);
  945. }
  946. }
  947. _features_sanitize($array, $remove_empty);
  948. }
  949. /**
  950. * "Sanitizes" an array recursively, performing two key operations:
  951. * - Sort an array by its keys (assoc) or values (non-assoc)
  952. * @param bool $remove_empty if set, remove null or empty values for assoc arrays.
  953. */
  954. function _features_sanitize(&$array, $remove_empty = TRUE) {
  955. if (is_object($array)) {
  956. $array = get_object_vars($array);
  957. }
  958. if (is_array($array)) {
  959. $is_assoc = _features_is_assoc($array);
  960. if ($is_assoc) {
  961. ksort($array, SORT_STRING);
  962. if ($remove_empty) {
  963. $array = array_filter($array);
  964. }
  965. }
  966. else {
  967. sort($array);
  968. }
  969. foreach ($array as $k => $v) {
  970. if (is_array($v) or is_object($v)) {
  971. _features_sanitize($array[$k]);
  972. if ($remove_empty && $is_assoc && empty($array[$k])) {
  973. unset($array[$k]);
  974. }
  975. }
  976. }
  977. }
  978. }
  979. /**
  980. * Is the given array an associative array. This basically extracts the keys twice to get the
  981. * numerically ordered keys. It then does a diff with the original array and if there is no
  982. * key diff then the original array is not associative.
  983. *
  984. * NOTE: If you have non-sequential numerical keys, this will identify the array as assoc.
  985. *
  986. * Borrowed from: http://www.php.net/manual/en/function.is-array.php#96724
  987. *
  988. * @return True is the array is an associative array, false otherwise
  989. */
  990. function _features_is_assoc($array) {
  991. return (is_array($array) && (0 !== count(array_diff_key($array, array_keys(array_keys($array)))) || count($array)==0));
  992. }
  993. /**
  994. * Removes recursion from an object or array.
  995. *
  996. * Taken from https://code.google.com/p/formaldehyde/source/browse/trunk/formaldehyde.php
  997. * Also used in node_export module
  998. *
  999. * @param $o mixed
  1000. * @return mixed
  1001. * returns a copy of the object or array with recursion removed
  1002. */
  1003. function features_remove_recursion($o) {
  1004. if (is_array($o) || is_object($o)) {
  1005. $re = '#(r|R):([0-9]+);#';
  1006. $serialize = serialize($o);
  1007. if (preg_match($re, $serialize)) {
  1008. $last = $pos = 0;
  1009. while (false !== ($pos = strpos($serialize, 's:', $pos))) {
  1010. $chunk = substr($serialize, $last, $pos - $last);
  1011. if (preg_match($re, $chunk)) {
  1012. $length = strlen($chunk);
  1013. $chunk = preg_replace_callback($re, '_features_remove_recursion', $chunk);
  1014. $serialize = substr($serialize, 0, $last) . $chunk . substr($serialize, $last + ($pos - $last));
  1015. $pos += strlen($chunk) - $length;
  1016. }
  1017. $pos += 2;
  1018. $last = strpos($serialize, ':', $pos);
  1019. $length = substr($serialize, $pos, $last - $pos);
  1020. $last += 4 + $length;
  1021. $pos = $last;
  1022. }
  1023. $serialize = substr($serialize, 0, $last) . preg_replace_callback($re, '_features_remove_recursion', substr($serialize, $last));
  1024. $o = unserialize($serialize);
  1025. }
  1026. }
  1027. return $o;
  1028. }
  1029. /**
  1030. * Callback function for preg_replace_callback() to remove recursion.
  1031. */
  1032. function _features_remove_recursion($m) {
  1033. $r = "\x00{$m[1]}ecursion_features";
  1034. return 's:' . strlen($r . $m[2]) . ':"' . $r . $m[2] . '";';
  1035. }
  1036. /**
  1037. * Helper to removes a set of keys an object/array.
  1038. *
  1039. * @param $item
  1040. * An object or array passed by reference.
  1041. * @param $ignore_keys
  1042. * Array of keys to be ignored. Values are the level of the key.
  1043. * @param $level
  1044. * Level of key to remove. Up to 2 levels deep because $item can still be
  1045. * recursive
  1046. */
  1047. function _features_remove_ignores(&$item, $ignore_keys, $level = -1) {
  1048. $is_object = is_object($item);
  1049. if (!is_array($item) && !is_object($item)) {
  1050. return;
  1051. }
  1052. foreach ($item as $key => &$value) {
  1053. if (isset($ignore_keys[$key]) && ($ignore_keys[$key] == $level)) {
  1054. if ($is_object) {
  1055. unset($item->$key);
  1056. }
  1057. else {
  1058. unset($item[$key]);
  1059. }
  1060. }
  1061. elseif (($level < 2) && (is_array($value) || is_object($value))) {
  1062. _features_remove_ignores($value, $ignore_keys, $level+1);
  1063. }
  1064. }
  1065. unset($value);
  1066. }
  1067. /**
  1068. * Returns an array of keys to be ignored for various exportables
  1069. * @param $component
  1070. * The component to retrieve ignore_keys from.
  1071. */
  1072. function _features_get_ignore_keys($component) {
  1073. static $cache;
  1074. if (!isset($cache[$component])) {
  1075. $cache[$component] = module_invoke_all('features_ignore', $component);
  1076. }
  1077. return $cache[$component];
  1078. }