features.export.inc 42 KB

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