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