Mercurial > hg > rr-repo
comparison sites/all/modules/features/features.admin.inc @ 4:ce11bbd8f642
added modules
author | danieleb <danielebarchiesi@me.com> |
---|---|
date | Thu, 19 Sep 2013 10:38:44 +0100 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
3:b28be78d8160 | 4:ce11bbd8f642 |
---|---|
1 <?php | |
2 | |
3 /** | |
4 * @file | |
5 * Forms for Features admin screens | |
6 */ | |
7 | |
8 | |
9 /** | |
10 * Settings form for features | |
11 */ | |
12 function features_settings_form($form, $form_state) { | |
13 $form = array(); | |
14 | |
15 $components = features_get_components(); | |
16 uasort($components, 'features_compare_component_name'); | |
17 $form['show_components'] = array( | |
18 '#type' => 'fieldset', | |
19 '#title' => t('Show components on create/edit feature form.'), | |
20 '#description' => t('Components with no options will not be shown no matter the setting below. Disabled components cannot be used with admin form.') | |
21 ); | |
22 foreach ($components as $compontent => $info) { | |
23 if (empty($info['feature_source']) && empty($info['features_source'])) { | |
24 continue; | |
25 } | |
26 $form['show_components']['features_admin_show_component_' . $compontent] = array( | |
27 '#title' => t('@name (@machine)', array('@name' => $info['name'], '@machine' => $compontent)), | |
28 '#type' => 'checkbox', | |
29 '#default_value' => variable_get('features_admin_show_component_' . $compontent, TRUE), | |
30 ); | |
31 if ($compontent == 'menu_links' && ($menus = menu_get_menus())) { | |
32 $form['show_components']['features_admin_menu_links'] = array( | |
33 '#title' => t('Advanced Menu Link Settings'), | |
34 '#type' => 'fieldset', | |
35 '#collapsed' => TRUE, | |
36 '#collapsible' => TRUE, | |
37 '#states' => array( | |
38 'invisible' => array( | |
39 'input[name="features_admin_show_component_menu_links"]' => array('checked' => FALSE), | |
40 ), | |
41 ), | |
42 ); | |
43 $form['show_components']['features_admin_menu_links']['features_admin_menu_links_menus'] = array( | |
44 '#title' => t('Allowed menus for menu links'), | |
45 '#type' => 'checkboxes', | |
46 '#options' => array_map('check_plain', $menus), | |
47 '#default_value' => variable_get('features_admin_menu_links_menus', array_keys(menu_get_menus())), | |
48 ); | |
49 } | |
50 } | |
51 | |
52 $form['features_rebuild_on_flush'] = array( | |
53 '#type' => 'checkbox', | |
54 '#title' => t('Rebuild features on cache clear'), | |
55 '#default_value' => variable_get('features_rebuild_on_flush', TRUE), | |
56 '#description' => t('If you have a large site with many features, you may experience lag on full cache clear. If disabled, features will rebuild only when viewing the features list or saving the modules list.'), | |
57 ); | |
58 | |
59 return system_settings_form($form); | |
60 } | |
61 | |
62 /** | |
63 * Form constructor for features export form. | |
64 * | |
65 * Acts as a router based on the form_state. | |
66 * | |
67 * @param object|null $feature | |
68 * The feature object, if available. NULL by default. | |
69 * | |
70 * @see features_export_build_form_submit() | |
71 * @ingroup forms | |
72 */ | |
73 function features_export_form($form, $form_state, $feature = NULL) { | |
74 module_load_include('inc', 'features', 'features.export'); | |
75 features_include(); | |
76 | |
77 $feature_name = !empty($feature->name) ? $feature->name : ''; | |
78 $form = array( | |
79 '#attributes' => array('class' => array('features-export-form')), | |
80 '#feature' => isset($feature) ? $feature : NULL, | |
81 ); | |
82 $form['info'] = array( | |
83 '#type' => 'fieldset', | |
84 '#title' => t('General Information'), | |
85 '#tree' => FALSE, | |
86 '#weight' => 2, | |
87 '#collapsible' => FALSE, | |
88 '#collapsed' => FALSE, | |
89 '#prefix' => "<div id='features-export-info'>", | |
90 '#suffix' => '</div>', | |
91 ); | |
92 $form['info']['name'] = array( | |
93 '#title' => t('Name'), | |
94 '#description' => t('Example: Image gallery') . ' (' . t('Do not begin name with numbers.') . ')', | |
95 '#type' => 'textfield', | |
96 '#default_value' => !empty($feature->info['name']) ? $feature->info['name'] : '', | |
97 '#attributes' => array('class' => array('feature-name')), | |
98 ); | |
99 $form['info']['module_name'] = array( | |
100 '#type' => 'textfield', | |
101 '#title' => t('Machine-readable name'), | |
102 '#description' => t('Example: image_gallery') . '<br/>' . t('May only contain lowercase letters, numbers and underscores. <strong>Try to avoid conflicts with the names of existing Drupal projects.</strong>'), | |
103 '#required' => TRUE, | |
104 '#default_value' => $feature_name, | |
105 '#attributes' => array('class' => array('feature-module-name')), | |
106 '#element_validate' => array('features_export_form_validate_field'), | |
107 ); | |
108 // If recreating this feature, disable machine name field and blank out | |
109 // js-attachment classes to ensure the machine name cannot be changed. | |
110 if (isset($feature)) { | |
111 $form['info']['module_name']['#value'] = $feature_name; | |
112 $form['info']['module_name']['#disabled'] = TRUE; | |
113 $form['info']['name']['#attributes'] = array(); | |
114 } | |
115 $form['info']['description'] = array( | |
116 '#title' => t('Description'), | |
117 '#description' => t('Provide a short description of what users should expect when they enable your feature.'), | |
118 '#type' => 'textfield', | |
119 '#default_value' => !empty($feature->info['description']) ? $feature->info['description'] : '', | |
120 ); | |
121 $form['info']['package'] = array( | |
122 '#title' => t('Package'), | |
123 '#description' => t('Organize your features in groups.'), | |
124 '#type' => 'textfield', | |
125 '#autocomplete_path' => 'features/autocomplete/packages', | |
126 '#default_value' => !empty($feature->info['package']) ? $feature->info['package'] : 'Features', | |
127 ); | |
128 $form['info']['version'] = array( | |
129 '#title' => t('Version'), | |
130 '#description' => t('Examples: 7.x-1.0, 7.x-1.0-beta1'), | |
131 '#type' => 'textfield', | |
132 '#required' => FALSE, | |
133 '#default_value' => !empty($feature->info['version']) ? $feature->info['version'] : '', | |
134 '#size' => 30, | |
135 '#element_validate' => array('features_export_form_validate_field'), | |
136 ); | |
137 $form['advanced'] = array( | |
138 '#type' => 'fieldset', | |
139 '#title' => t('Advanced Options'), | |
140 '#tree' => FALSE, | |
141 '#collapsible' => TRUE, | |
142 '#collapsed' => TRUE, | |
143 '#weight' => 10, | |
144 '#prefix' => "<div id='features-export-advanced'>", | |
145 '#suffix' => '</div>', | |
146 ); | |
147 $form['advanced']['project_status_url'] = array( | |
148 '#title' => t('URL of update XML'), | |
149 '#description' => t('URL of Feature Server. For Example: http://mywebsite.com/fserver'), | |
150 '#type' => 'textfield', | |
151 '#required' => FALSE, | |
152 '#default_value' => !empty($feature->info['project status url']) ? $feature->info['project status url'] : '', | |
153 '#element_validate' => array('features_export_form_validate_field'), | |
154 ); | |
155 $directory = (!empty($feature->filename)) ? dirname($feature->filename) : 'sites/all/modules/features'; | |
156 if (!empty($feature_name) && substr_compare($directory, $feature_name, strlen($directory)-strlen($feature_name), strlen($feature_name)) === 0) { | |
157 // if path ends with module_name, strip it | |
158 $directory = dirname($directory); | |
159 } | |
160 if (user_access('generate features')) { | |
161 $form['advanced']['generate_path'] = array( | |
162 '#title' => t('Path to Generate feature module'), | |
163 '#description' => t('File path for feature module. For Example: sites/all/modules/features or /tmp. ' . | |
164 t('Leave blank for <strong>@path</strong>', array('@path' => $directory))), | |
165 '#type' => 'textfield', | |
166 '#required' => FALSE, | |
167 '#default_value' => !empty($feature->info['project path']) ? $feature->info['project path'] : '', | |
168 ); | |
169 $form['advanced']['generate'] = array( | |
170 '#type' => 'submit', | |
171 '#value' => t('Generate feature'), | |
172 '#submit' => array('features_export_build_form_submit'), | |
173 ); | |
174 } | |
175 // build the Component Listing panel on the right | |
176 _features_export_form_components($form, $form_state); | |
177 | |
178 $form['advanced']['info-preview'] = array( | |
179 '#type' => 'button', | |
180 '#value' => t('Preview .info file'), | |
181 '#ajax' => array( | |
182 'callback' => 'features_info_file_preview', | |
183 'wrapper' => 'features-export-wrapper', | |
184 ), | |
185 ); | |
186 //Info dialog | |
187 $form['advanced']['info-file'] = array( | |
188 '#prefix' => '<div id="features-info-file" title="Export .info file preview">', | |
189 'text' => array( | |
190 '#type' => 'textarea', | |
191 '#default_value' => '', | |
192 '#resizable' => FALSE, | |
193 ), | |
194 '#suffix' => '</div>', | |
195 ); | |
196 | |
197 $form['buttons'] = array( | |
198 '#theme' => 'features_form_buttons', | |
199 '#tree' => FALSE, | |
200 '#weight' => 99, | |
201 '#prefix' => "<div id='features-export-buttons'>", | |
202 '#suffix' => '</div>', | |
203 ); | |
204 $form['buttons']['submit'] = array( | |
205 '#type' => 'submit', | |
206 '#value' => t('Download feature'), | |
207 '#weight' => 10, | |
208 '#submit' => array('features_export_build_form_submit'), | |
209 ); | |
210 | |
211 $form['#attached']['library'][] = array('system', 'ui.dialog'); | |
212 | |
213 return $form; | |
214 } | |
215 | |
216 /** | |
217 * Return the render array elements for the Components selection on the Export form | |
218 * @param array $feature - feature associative array | |
219 * @param array $components - array of components in feature | |
220 */ | |
221 function _features_export_form_components(&$form, &$form_state) { | |
222 global $features_ignore_conflicts; | |
223 drupal_add_css(drupal_get_path('module', 'features') . '/features.css'); | |
224 drupal_add_js(drupal_get_path('module', 'features') . '/features.js'); | |
225 | |
226 $feature = $form['#feature']; | |
227 | |
228 // keep the allow_conflict variable around in the session | |
229 if (isset($form_state['values']['features_allow_conflicts'])) { | |
230 $_SESSION['features_allow_conflicts'] = $form_state['values']['features_allow_conflicts']; | |
231 $features_ignore_conflicts = $_SESSION['features_allow_conflicts']; | |
232 } | |
233 | |
234 $form['export'] = array( | |
235 '#type' => 'fieldset', | |
236 '#title' => t('Components'), | |
237 '#description' => t('Expand each component section and select which items should be included in this feature export.'), | |
238 '#tree' => FALSE, | |
239 '#prefix' => "<div id='features-export-wrapper'>", | |
240 '#suffix' => '</div>', | |
241 '#collapsible' => FALSE, | |
242 '#collapsed' => FALSE, | |
243 '#weight' => 1, | |
244 ); | |
245 | |
246 // filter field used in javascript, so javascript will unhide it | |
247 $form['export']['features_filter_wrapper'] = array( | |
248 '#type' => 'fieldset', | |
249 '#title' => t('Filters'), | |
250 '#tree' => FALSE, | |
251 '#prefix' => "<div id='features-filter' class='element-invisible'>", | |
252 '#suffix' => '</div>', | |
253 '#collapsible' => FALSE, | |
254 '#collapsed' => FALSE, | |
255 '#weight' => -10, | |
256 ); | |
257 $form['export']['features_filter_wrapper']['features_filter'] = array( | |
258 '#type' => 'textfield', | |
259 '#title' => t('Search'), | |
260 '#hidden' => TRUE, | |
261 '#default_value' => '', | |
262 '#suffix' => "<span class='features-filter-clear'>". t('Clear') ."</span>", | |
263 ); | |
264 $form['export']['features_filter_wrapper']['checkall'] = array( | |
265 '#type' => 'checkbox', | |
266 '#default_value' => FALSE, | |
267 '#hidden' => TRUE, | |
268 '#title' => t('Select all'), | |
269 '#attributes' => array( | |
270 'class' => array('features-checkall'), | |
271 ) | |
272 ); | |
273 | |
274 $form['advanced']['features_autodetect_wrapper'] = array( | |
275 '#type' => 'fieldset', | |
276 '#tree' => FALSE, | |
277 '#prefix' => "<div id='features-autodetect'>", | |
278 '#suffix' => '</div>', | |
279 '#collapsible' => FALSE, | |
280 '#collapsed' => FALSE, | |
281 ); | |
282 $form['advanced']['features_autodetect_wrapper']['autodetect'] = array( | |
283 '#title' => t('Add auto-detected dependencies'), | |
284 '#type' => 'checkbox', | |
285 '#default_value' => !empty($feature->info['no autodetect']) ? FALSE : TRUE, | |
286 ); | |
287 | |
288 // this refresh button will rebuild the form. | |
289 // this button is hidden by javascript since it is only needed when | |
290 // javascript is not available | |
291 $form['advanced']['features_autodetect_wrapper']['features_refresh'] = array( | |
292 '#type' => 'submit', | |
293 '#value' => t('Refresh'), | |
294 '#name' => 'features-refresh', | |
295 '#attributes' => array( | |
296 'title' => t("Refresh the list of auto-detected items."), | |
297 'class' => array('features-refresh-button'), | |
298 ), | |
299 '#submit' => array('features_export_form_rebuild'), | |
300 '#prefix' => "<div class='features-refresh-wrapper'>", | |
301 '#suffix' => "</div>", | |
302 '#ajax' => array( | |
303 'callback' => 'features_export_form_ajax', | |
304 'wrapper' => 'features-export-wrapper', | |
305 ), | |
306 ); | |
307 | |
308 // generate the export array for the current feature and user selections | |
309 $export = _features_export_build($feature, $form_state); | |
310 | |
311 $form['advanced']['features_allow_conflicts'] = array( | |
312 '#title' => t('Allow conflicts to be added'), | |
313 '#type' => 'checkbox', | |
314 '#default_value' => $features_ignore_conflicts, | |
315 '#ajax' => array( | |
316 'callback' => 'features_export_form_ajax', | |
317 'wrapper' => 'features-export-wrapper', | |
318 ), | |
319 ); | |
320 | |
321 if (isset($form_state['values']['op']) && ($form_state['values']['op'] == $form_state['values']['info-preview'])) { | |
322 // handle clicking Preview button | |
323 module_load_include('inc', 'features', 'features.export'); | |
324 | |
325 $feature_export = _features_export_generate($export, $form_state, $feature); | |
326 $feature_export = features_export_prepare($feature_export, $feature->name, TRUE); | |
327 $info = features_export_info($feature_export); | |
328 | |
329 drupal_add_js(array('features' => array('info' => $info)), 'setting'); | |
330 } | |
331 | |
332 // determine any components that are deprecated | |
333 $deprecated = features_get_deprecated($export['components']); | |
334 | |
335 $sections = array('included', 'detected', 'added'); | |
336 foreach ($export['components'] as $component => $component_info) { | |
337 if (!variable_get('features_admin_show_component_' . $component, TRUE)) { | |
338 continue; | |
339 } | |
340 $label = (isset($component_info['name']) ? | |
341 $component_info['name'] . " <span>(" . check_plain($component) . ")</span>" : check_plain($component)); | |
342 | |
343 $count = 0; | |
344 foreach ($sections as $section) { | |
345 $count += count($component_info['options'][$section]); | |
346 } | |
347 $extra_class = ($count == 0) ? 'features-export-empty' : ''; | |
348 $component_name = str_replace('_', '-', check_plain($component)); | |
349 | |
350 if ($count + count($component_info['options']['sources']) > 0) { | |
351 | |
352 if (!empty($deprecated[$component])) { | |
353 // only show deprecated component if it has some exports | |
354 if (!empty($component_info['options']['included'])) { | |
355 $form['export'][$component] = array( | |
356 '#markup' => '', | |
357 '#tree' => TRUE, | |
358 ); | |
359 | |
360 $form['export'][$component]['deprecated'] = array( | |
361 '#type' => 'fieldset', | |
362 '#title' => $label . "<span class='features-conflict'> (" . t('DEPRECATED') . ")</span>", | |
363 '#tree' => TRUE, | |
364 '#collapsible' => TRUE, | |
365 '#collapsed' => TRUE, | |
366 '#attributes' => array('class' => array('features-export-component')), | |
367 ); | |
368 $list = ' '; | |
369 foreach ($component_info['options']['included'] as $key) { | |
370 $list .= "<span class='form-type-checkbox features-conflict'>$key</span>"; | |
371 } | |
372 $form['export'][$component]['deprecated']['selected'] = array( | |
373 '#prefix' => "<div class='component-detected'>", | |
374 '#markup' => $list, | |
375 '#suffix' => "</div>", | |
376 ); | |
377 } | |
378 } | |
379 else { | |
380 $form['export'][$component] = array( | |
381 '#markup' => '', | |
382 '#tree' => TRUE, | |
383 ); | |
384 | |
385 $form['export'][$component]['sources'] = array( | |
386 '#type' => 'fieldset', | |
387 '#title' => $label, | |
388 '#tree' => TRUE, | |
389 '#collapsible' => TRUE, | |
390 '#collapsed' => TRUE, | |
391 '#attributes' => array('class' => array('features-export-component')), | |
392 '#prefix' => "<div class='features-export-parent component-$component'>", | |
393 ); | |
394 $form['export'][$component]['sources']['selected'] = array( | |
395 '#type' => 'checkboxes', | |
396 '#id' => "edit-sources-$component_name", | |
397 '#options' => features_dom_encode_options($component_info['options']['sources']), | |
398 '#default_value' => features_dom_encode_options($component_info['selected']['sources'], FALSE), | |
399 '#attributes' => array( | |
400 'class' => array('component-select'), | |
401 ), | |
402 ); | |
403 | |
404 foreach ($sections as $section) { | |
405 $form['export'][$component][$section] = array( | |
406 '#type' => 'checkboxes', | |
407 '#options' => !empty($component_info['options'][$section]) ? | |
408 features_dom_encode_options($component_info['options'][$section]) : array(), | |
409 '#default_value' => !empty($component_info['selected'][$section]) ? | |
410 features_dom_encode_options($component_info['selected'][$section], FALSE) : array(), | |
411 '#attributes' => array('class' => array('component-' . $section)), | |
412 ); | |
413 } | |
414 $form['export'][$component][$sections[0]]['#prefix'] = | |
415 "<div class='component-list features-export-list $extra_class'>"; | |
416 $form['export'][$component][$sections[count($sections)-1]]['#suffix'] = '</div></div>'; | |
417 } | |
418 } | |
419 } | |
420 $form['export']['features_legend'] = array( | |
421 '#type' => 'fieldset', | |
422 '#title' => t('Legend'), | |
423 '#tree' => FALSE, | |
424 '#prefix' => "<div id='features-legend'>", | |
425 '#suffix' => '</div>', | |
426 '#collapsible' => FALSE, | |
427 '#collapsed' => FALSE, | |
428 ); | |
429 $form['export']['features_legend']['legend'] = array( | |
430 '#markup' => | |
431 "<span class='component-included'>Normal</span> " . | |
432 "<span class='component-added'>Changed</span> " . | |
433 "<span class='component-detected'>Auto detected</span> " . | |
434 "<span class='features-conflict'>Conflict</span> ", | |
435 ); | |
436 } | |
437 | |
438 /** | |
439 * Return the full feature export array based upon user selections in form_state | |
440 * @param array $feature Feature array to be exported | |
441 * @param array $form_state Optional form_state information for user selections | |
442 * can be updated to reflect new selection status | |
443 * @return array New export array to be exported | |
444 * array['components'][$component_name] = $component_info | |
445 * $component_info['options'][$section] is list of available options | |
446 * $component_info['selected'][$section] is option state TRUE/FALSE | |
447 * $section = array('sources', included', 'detected', 'added') | |
448 * sources - options that are available to be added to the feature | |
449 * included - options that have been previously exported to the feature | |
450 * detected - options that have been auto-detected | |
451 * added - newly added options to the feature | |
452 * | |
453 * NOTE: This routine gets a bit complex to handle all of the different possible | |
454 * user checkbox selections and de-selections. | |
455 * Cases to test: | |
456 * 1a) uncheck Included item -> mark as Added but unchecked | |
457 * 1b) re-check unchecked Added item -> return it to Included check item | |
458 * 2a) check Sources item -> mark as Added and checked | |
459 * 2b) uncheck Added item -> return it to Sources as unchecked | |
460 * 3a) uncheck Included item that still exists as auto-detect -> mark as Detected but unchecked | |
461 * 3b) re-check Detected item -> return it to Included and checked | |
462 * 4a) check Sources item should also add any auto-detect items as Detected and checked | |
463 * 4b) uncheck Sources item with auto-detect and auto-detect items should return to Sources and unchecked | |
464 * 5a) uncheck a Detected item -> refreshing page should keep it as unchecked Detected | |
465 * 6) when nothing changes, refresh should not change any state | |
466 * 7) should never see an unchecked Included item | |
467 */ | |
468 function _features_export_build($feature, &$form_state) { | |
469 global $features_ignore_conflicts; | |
470 // set a global to effect features_get_component_map when building feature | |
471 // hate to use a global, but it's just for an admin screen so probably ok | |
472 if (isset($_SESSION['features_allow_conflicts'])) { | |
473 $features_ignore_conflicts = $_SESSION['features_allow_conflicts']; | |
474 } | |
475 | |
476 $feature_name = isset($feature->name) ? $feature->name : NULL; | |
477 $conflicts = _features_get_used($feature_name); | |
478 $reset = FALSE; | |
479 if (isset($form_state['triggering_element']['#name']) && ($form_state['triggering_element']['#name'] == 'features_allow_conflicts')) { | |
480 // when clicking the Allow Conflicts button, reset the feature back to it's original state | |
481 $reset = TRUE; | |
482 } | |
483 | |
484 module_load_include('inc', 'features', 'features.export'); | |
485 features_include(); | |
486 | |
487 $components = features_get_components(); | |
488 uasort($components, 'features_compare_component_name'); | |
489 | |
490 // Assemble the combined component list | |
491 $stub = array(); | |
492 $sections = array('sources', 'included', 'detected', 'added'); | |
493 | |
494 // create a new feature "stub" to populate | |
495 | |
496 $stub_count = array(); | |
497 foreach ($components as $component => $component_info) { | |
498 if ($reset) { | |
499 unset($form_state['values'][$component]); | |
500 } | |
501 if (!variable_get('features_admin_show_component_' . $component, TRUE)) { | |
502 unset($components[$component]); | |
503 continue; | |
504 } | |
505 // User-selected components take precedence. | |
506 $stub[$component] = array(); | |
507 $stub_count[$component] = 0; | |
508 // add selected items from Sources checkboxes | |
509 if (!empty($form_state['values'][$component]['sources']['selected'])) { | |
510 $stub[$component] = array_merge($stub[$component], features_dom_decode_options(array_filter($form_state['values'][$component]['sources']['selected']))); | |
511 $stub_count[$component]++; | |
512 } | |
513 // add selected items from already Included and newly Added checkboxes | |
514 foreach (array('included', 'added') as $section) { | |
515 if (!empty($form_state['values'][$component][$section])) { | |
516 $stub[$component] = array_merge($stub[$component], features_dom_decode_options(array_filter($form_state['values'][$component][$section]))); | |
517 $stub_count[$component]++; | |
518 } | |
519 } | |
520 // count any detected items | |
521 if (!empty($form_state['values'][$component]['detected'])) { | |
522 $stub_count[$component]++; | |
523 } | |
524 // Only fallback to an existing feature's values if there are no export options for the component. | |
525 if ($component == 'dependencies') { | |
526 if (($stub_count[$component] == 0) && !empty($feature->info['dependencies'])) { | |
527 $stub[$component] = drupal_map_assoc($feature->info['dependencies']); | |
528 } | |
529 } | |
530 elseif (($stub_count[$component] == 0) && !empty($feature->info['features'][$component])) { | |
531 $stub[$component] = drupal_map_assoc($feature->info['features'][$component]); | |
532 } | |
533 } | |
534 // Generate new populated feature | |
535 $export = features_populate(array('features' => $stub, 'dependencies' => $stub['dependencies']), $feature_name); | |
536 | |
537 // Components that are already exported to feature | |
538 $exported_features_info = !empty($feature->info['features']) ? $feature->info['features'] : array(); | |
539 $exported_features_info['dependencies'] = !empty($feature->info['dependencies']) ? $feature->info['dependencies'] : array(); | |
540 // Components that should be exported | |
541 $new_features_info = !empty($export['features']) ? $export['features'] : array(); | |
542 $new_features_info['dependencies'] = !empty($export['dependencies']) ? $export['dependencies'] : array(); | |
543 $excluded = !empty($feature->info['features_exclude']) ? $feature->info['features_exclude'] : array(); | |
544 | |
545 // now fill the $export with categorized sections of component options | |
546 // based upon user selections and de-selections | |
547 | |
548 foreach ($components as $component => $component_info) { | |
549 $component_export = $component_info; | |
550 foreach ($sections as $section) { | |
551 $component_export['options'][$section] = array(); | |
552 $component_export['selected'][$section] = array(); | |
553 } | |
554 $options = features_invoke($component, 'features_export_options'); | |
555 if (!empty($options)) { | |
556 $exported_components = !empty($exported_features_info[$component]) ? $exported_features_info[$component] : array(); | |
557 $new_components = !empty($new_features_info[$component]) ? $new_features_info[$component] : array(); | |
558 | |
559 // Find all default components that are not provided by this feature and | |
560 // strip them out of the possible options. | |
561 if ($map = features_get_default_map($component)) { | |
562 foreach ($map as $k => $v) { | |
563 if (isset($options[$k]) && (!isset($feature->name) || $v !== $feature->name)) { | |
564 unset($options[$k]); | |
565 } | |
566 } | |
567 } | |
568 foreach ($options as $key => $value) { | |
569 // use the $clean_key when accessing $form_state | |
570 $clean_key = features_dom_encode($key); | |
571 // if checkbox in Sources is checked, move it to Added section | |
572 if (!empty($form_state['values'][$component]['sources']['selected'][$clean_key])) { | |
573 unset($form_state['input'][$component]['sources']['selected'][$clean_key]); | |
574 $form_state['values'][$component]['sources']['selected'][$clean_key] = FALSE; | |
575 $form_state['values'][$component]['added'][$clean_key] = 1; | |
576 $form_state['input'][$component]['added'][$clean_key] = $clean_key; | |
577 $component_export['options']['added'][$key] = check_plain($value); | |
578 $component_export['selected']['added'][$key] = $key; | |
579 } | |
580 elseif (in_array($key, $new_components)) { | |
581 // option is in the New exported array | |
582 if (in_array($key, $exported_components)) { | |
583 // option was already previously exported | |
584 // so it's part of the Included checkboxes | |
585 $section = 'included'; | |
586 $default_value = $key; | |
587 if ($reset) { | |
588 // leave it included | |
589 } | |
590 // if Included item was un-selected (removed from export $stub) | |
591 // but was re-detected in the $new_components | |
592 // means it was an auto-detect that was previously part of the export | |
593 // and is now de-selected in UI | |
594 elseif (!empty($form_state['values']) && | |
595 (isset($form_state['values'][$component]['included'][$clean_key]) || | |
596 empty($form_state['values'][$component]['detected'][$clean_key])) && | |
597 empty($stub[$component][$key])) { | |
598 $section = 'detected'; | |
599 $default_value = FALSE; | |
600 } | |
601 // unless it's unchecked in the form, then move it to Newly disabled item | |
602 elseif (!empty($form_state['values']) && | |
603 empty($form_state['values'][$component]['added'][$clean_key]) && | |
604 empty($form_state['values'][$component]['detected'][$clean_key]) && | |
605 empty($form_state['values'][$component]['included'][$clean_key])) { | |
606 $section = 'added'; | |
607 $default_value = FALSE; | |
608 } | |
609 } | |
610 else { | |
611 // option was in New exported array, but NOT in already exported | |
612 // so it's a user-selected or an auto-detect item | |
613 $section = 'detected'; | |
614 // check for item explicity excluded | |
615 if (isset($excluded[$component][$key]) && !isset($form_state['values'][$component]['detected'][$clean_key])) { | |
616 $default_value = FALSE; | |
617 } | |
618 else { | |
619 $default_value = $key; | |
620 } | |
621 // if it's already checked in Added or Sources, leave it in Added as checked | |
622 if (!empty($form_state['values']) && | |
623 (!empty($form_state['values'][$component]['added'][$clean_key]) || | |
624 !empty($form_state['values'][$component]['sources']['selected'][$clean_key]))) { | |
625 $section = 'added'; | |
626 $default_value = $key; | |
627 } | |
628 // if it's already been unchecked, leave it unchecked | |
629 elseif (!empty($form_state['values']) && | |
630 empty($form_state['values'][$component]['sources']['selected'][$clean_key]) && | |
631 empty($form_state['values'][$component]['detected'][$clean_key]) && | |
632 !isset($form_state['values'][$component]['added'][$clean_key])) { | |
633 $section = 'detected'; | |
634 $default_value = FALSE; | |
635 } | |
636 } | |
637 $component_export['options'][$section][$key] = check_plain($value); | |
638 $component_export['selected'][$section][$key] = $default_value; | |
639 // save which dependencies are specifically excluded from auto-detection | |
640 if (($section == 'detected') && ($default_value === FALSE)) { | |
641 $excluded[$component][$key] = $key; | |
642 // remove excluded item from export | |
643 if ($component == 'dependencies') { | |
644 unset($export['dependencies'][$key]); | |
645 } | |
646 else { | |
647 unset($export['features'][$component][$key]); | |
648 } | |
649 } | |
650 else { | |
651 unset($excluded[$component][$key]); | |
652 } | |
653 // remove the 'input' and set the 'values' so Drupal stops looking at 'input' | |
654 if (isset($form_state['values'])) { | |
655 if (!$default_value) { | |
656 unset($form_state['input'][$component][$section][$clean_key]); | |
657 $form_state['values'][$component][$section][$clean_key] = FALSE; | |
658 } | |
659 else { | |
660 $form_state['input'][$component][$section][$clean_key] = $clean_key; | |
661 $form_state['values'][$component][$section][$clean_key] = 1; | |
662 } | |
663 } | |
664 } | |
665 else { | |
666 // option was not part of the new export | |
667 $added = FALSE; | |
668 foreach (array('included', 'added') as $section) { | |
669 // restore any user-selected checkboxes | |
670 if (!empty($form_state['values'][$component][$section][$clean_key])) { | |
671 $component_export['options'][$section][$key] = check_plain($value); | |
672 $component_export['selected'][$section][$key] = $key; | |
673 $added = TRUE; | |
674 } | |
675 } | |
676 if (!$added) { | |
677 // if not Included or Added, then put it back in the unchecked Sources checkboxes | |
678 $component_export['options']['sources'][$key] = check_plain($value); | |
679 $component_export['selected']['sources'][$key] = FALSE; | |
680 } | |
681 } | |
682 } | |
683 } | |
684 $export['components'][$component] = $component_export; | |
685 } | |
686 $export['features_exclude'] = $excluded; | |
687 | |
688 // make excluded list and conflicts available for javascript to pass to our ajax callback | |
689 drupal_add_js(array('features' => array( | |
690 'excluded' => $excluded, | |
691 'conflicts' => $conflicts, | |
692 )), 'setting'); | |
693 | |
694 return $export; | |
695 } | |
696 | |
697 /** | |
698 * AJAX callback for features_export_form. | |
699 */ | |
700 function features_export_form_ajax($form, &$form_state) { | |
701 return $form['export']; | |
702 } | |
703 | |
704 /** | |
705 * Tells the ajax form submission to rebuild form state. | |
706 */ | |
707 function features_export_form_rebuild($form, &$form_state) { | |
708 $form_state['rebuild'] = TRUE; | |
709 } | |
710 | |
711 function features_export_components_json($feature_name) { | |
712 module_load_include('inc', 'features', 'features.export'); | |
713 $export = array(); | |
714 if (!empty($_POST['items'])) { | |
715 $excluded = (!empty($_POST['excluded'])) ? $_POST['excluded'] : array(); | |
716 $stub = array(); | |
717 foreach ($_POST['items'] as $key) { | |
718 preg_match('/^([^\[]+)(\[.+\])?\[(.+)\]\[(.+)\]$/', $key, $matches); | |
719 if (!empty($matches[1]) && !empty($matches[4])) { | |
720 $component = $matches[1]; | |
721 $item = features_dom_decode($matches[4]); | |
722 if (empty($stub[$component])) { | |
723 $stub[$component] = array($item); | |
724 } | |
725 else { | |
726 $stub[$component] = array_merge($stub[$component], array($item)); | |
727 } | |
728 } | |
729 } | |
730 | |
731 $stub['dependencies'] = isset($stub['dependencies']) ? $stub['dependencies'] : array(); | |
732 $export = features_populate(array('features' => $stub, 'dependencies' => $stub['dependencies']), $feature_name); | |
733 $export['features']['dependencies'] = $export['dependencies']; | |
734 | |
735 // uncheck any detected item that is in the excluded list | |
736 foreach ($export['features'] as $component => $value) { | |
737 foreach ($value as $key => $item) { | |
738 $clean_key = features_dom_encode($key); | |
739 if ($key != $clean_key) { | |
740 // need to move key to a cleankey for javascript | |
741 $export['features'][$component][$clean_key] = $export['features'][$component][$key]; | |
742 unset($export['features'][$component][$key]); | |
743 } | |
744 if (isset($excluded[$component][$key])) { | |
745 $export['features'][$component][$clean_key] = FALSE; | |
746 } | |
747 } | |
748 } | |
749 } | |
750 print drupal_json_encode($export['features']); | |
751 } | |
752 | |
753 /** | |
754 * AJAX callback to get .info file preview. | |
755 */ | |
756 function features_info_file_preview($form, &$form_state){ | |
757 return $form['export']; | |
758 } | |
759 | |
760 /** | |
761 * Render API callback: Validates a project field. | |
762 * | |
763 * This function is assigned as an #element_validate callback in | |
764 * features_export_form(). | |
765 */ | |
766 function features_export_form_validate_field($element, &$form_state) { | |
767 switch ($element['#name']) { | |
768 case 'module_name': | |
769 if (!preg_match('!^[a-z0-9_]+$!', $element['#value'])) { | |
770 form_error($element, t('The machine-readable name must contain only lowercase letters, numbers, and underscores.')); | |
771 } | |
772 // If user is filling out the feature name for the first time and uses | |
773 // the name of an existing module throw an error. | |
774 else if (empty($element['#default_value']) && features_get_info('module', $element['#value'])) { | |
775 form_error($element, t('A module by the name @name already exists on your site. Please choose a different name.', array('@name' => $element['#value']))); | |
776 } | |
777 break; | |
778 case 'project_status_url': | |
779 if (!empty($element['#value']) && !valid_url($element['#value'])) { | |
780 form_error($element, t('The URL %url is invalid. Please enter a fully-qualified URL, such as http://www.example.com/feed.xml.', array('%url' => $element['#value']))); | |
781 } | |
782 break; | |
783 case 'version': | |
784 preg_match('/^(?P<core>\d+\.x)-(?P<major>\d+)\.(?P<patch>\d+)-?(?P<extra>\w+)?$/', $element['#value'], $matches); | |
785 if (!empty($element['#value']) && !isset($matches['core'], $matches['major'])) { | |
786 form_error($element, t('Please enter a valid version with core and major version number. Example: @example', array('@example' => '7.x-1.0'))); | |
787 }; | |
788 break; | |
789 } | |
790 } | |
791 | |
792 /** | |
793 * Return the $export array to be rendered for the feature export | |
794 */ | |
795 function _features_export_generate($export, $form_state, $feature = NULL) { | |
796 unset($export['components']); // remove the UI data that we are not saving to disk | |
797 | |
798 $module_name = $form_state['values']['module_name']; | |
799 // Directly copy the following attributes from form_state | |
800 $attr = array('name', 'description', 'package', 'project path'); | |
801 foreach ($attr as $key) { | |
802 $export[$key] = isset($form_state['values'][$key]) ? $form_state['values'][$key] : NULL; | |
803 } | |
804 // Directly copy the following attributes from the original feature | |
805 $attr = array('scripts' , 'stylesheets'); | |
806 foreach ($attr as $key) { | |
807 $export[$key] = isset($feature->info[$key]) ? $feature->info[$key] : NULL; | |
808 } | |
809 // If either update status-related keys are provided, add a project key | |
810 // corresponding to the module name. | |
811 if (!empty($form_state['values']['version']) || !empty($form_state['values']['project_status_url'])) { | |
812 $export['project'] = $form_state['values']['module_name']; | |
813 } | |
814 if (!empty($form_state['values']['version'])) { | |
815 $export['version'] = $form_state['values']['version']; | |
816 } | |
817 if (!empty($form_state['values']['project_status_url'])) { | |
818 $export['project status url'] = $form_state['values']['project_status_url']; | |
819 } | |
820 $export['no autodetect'] = empty($form_state['values']['autodetect']) ? 1 : NULL; | |
821 $export['project path'] = !empty($form_state['values']['generate_path']) ? $form_state['values']['generate_path'] : NULL; | |
822 return $export; | |
823 } | |
824 | |
825 /** | |
826 * Form submission handler for features_export_form(). | |
827 */ | |
828 function features_export_build_form_submit($form, &$form_state) { | |
829 $feature = $form['#feature']; | |
830 $export = _features_export_build($feature, $form_state); | |
831 $export = _features_export_generate($export, $form_state, $feature); | |
832 $generate = ($form_state['values']['op'] == $form_state['values']['generate']); | |
833 $module_name = $form_state['values']['module_name']; | |
834 | |
835 if ($generate && !user_access('generate features')) { | |
836 drupal_set_message(t("No permission for generating features.")); | |
837 return; | |
838 } | |
839 | |
840 // Generate download | |
841 if ($files = features_export_render($export, $module_name, TRUE)) { | |
842 $filename = (!empty($export['version']) ? "{$module_name}-{$export['version']}" : $module_name) . '.tar'; | |
843 | |
844 if ($generate) { | |
845 $success = TRUE; | |
846 $destination = 'sites/all/modules/features'; | |
847 $directory = (!empty($export['project path'])) ? $export['project path'] . '/' . $module_name : | |
848 (isset($feature->filename) ? dirname($feature->filename) : $destination . '/' . $module_name); | |
849 if (!is_dir($directory)) { | |
850 if (mkdir($directory, 0777, true) === FALSE) { | |
851 $success = FALSE; | |
852 } | |
853 } | |
854 } | |
855 else { | |
856 // Clear out output buffer to remove any garbage from tar output. | |
857 if (ob_get_level()) { | |
858 ob_end_clean(); | |
859 } | |
860 | |
861 drupal_add_http_header('Content-type', 'application/x-tar'); | |
862 drupal_add_http_header('Content-Disposition', 'attachment; filename="'. $filename .'"'); | |
863 drupal_send_headers(); | |
864 } | |
865 | |
866 $tar = array(); | |
867 $filenames = array(); | |
868 foreach ($files as $extension => $file_contents) { | |
869 if (!in_array($extension, array('module', 'info'))) { | |
870 $extension .= '.inc'; | |
871 } | |
872 $filenames[] = "{$module_name}.$extension"; | |
873 if ($generate) { | |
874 if (file_put_contents("{$directory}/{$module_name}.$extension", $file_contents) === FALSE) { | |
875 $success = FALSE; | |
876 } | |
877 } | |
878 else { | |
879 print features_tar_create("{$module_name}/{$module_name}.$extension", $file_contents); | |
880 } | |
881 } | |
882 if (features_get_modules($module_name, TRUE)) { | |
883 // prevent deprecated component files from being included in download | |
884 $deprecated = features_get_deprecated(); | |
885 foreach ($deprecated as $component) { | |
886 $info = features_get_components($component); | |
887 $filename = isset($info['default_file']) && $info['default_file'] == FEATURES_DEFAULTS_CUSTOM ? $info['default_filename'] : "features.{$component}"; | |
888 $filename .= '.inc'; | |
889 $filenames[] = "{$module_name}.$filename"; | |
890 } | |
891 $module_path = drupal_get_path('module', $module_name); | |
892 // file_scan_directory() can throw warnings when using PHP 5.3, messing | |
893 // up the output of our file stream. Suppress errors in this one case in | |
894 // order to produce valid output. | |
895 foreach (@file_scan_directory($module_path, '/.*/') as $file) { | |
896 $filename = substr($file->uri, strlen($module_path) + 1); | |
897 if (!in_array($filename, $filenames)) { | |
898 // Add this file. | |
899 $contents = file_get_contents($file->uri); | |
900 if ($generate) { | |
901 if (file_put_contents("{$directory}/{$filename}", $contents) === FALSE) { | |
902 $success = FALSE; | |
903 } | |
904 } | |
905 else { | |
906 print features_tar_create("{$module_name}/{$filename}", $contents); | |
907 } | |
908 unset($contents); | |
909 } | |
910 } | |
911 } | |
912 if ($generate) { | |
913 if ($success) { | |
914 drupal_set_message(t("Module @name written to @directory", | |
915 array('@name' => $export['name'], '@directory' => $directory))); | |
916 } | |
917 else { | |
918 drupal_set_message( | |
919 t("Could not write module to @path. ", array('@path' => $directory)) . | |
920 t("Ensure your file permissions allow the web server to write to that directory."), "error"); | |
921 } | |
922 } | |
923 else { | |
924 print pack("a1024",""); | |
925 exit; | |
926 } | |
927 } | |
928 } | |
929 | |
930 /** | |
931 * array_filter() callback for excluding hidden modules. | |
932 */ | |
933 function features_filter_hidden($module) { | |
934 return empty($module->info['hidden']); | |
935 } | |
936 | |
937 /** | |
938 * Form constructor for the features configuration form. | |
939 */ | |
940 function features_admin_form($form, $form_state) { | |
941 // Load export functions to use in comparison. | |
942 module_load_include('inc', 'features', 'features.export'); | |
943 | |
944 // Clear & rebuild key caches | |
945 features_get_info(NULL, NULL, TRUE); | |
946 features_rebuild(); | |
947 | |
948 $modules = array_filter(features_get_modules(), 'features_filter_hidden'); | |
949 $features = array_filter(features_get_features(), 'features_filter_hidden'); | |
950 $conflicts = features_get_conflicts(); | |
951 | |
952 foreach ($modules as $key => $module) { | |
953 if ($module->status && !empty($module->info['dependencies'])) { | |
954 foreach ($module->info['dependencies'] as $dependent) { | |
955 if (isset($features[$dependent])) { | |
956 $features[$dependent]->dependents[$key] = $module->info['name']; | |
957 } | |
958 } | |
959 } | |
960 } | |
961 | |
962 if ( empty($features) ) { | |
963 $form['no_features'] = array( | |
964 '#markup' => t('No Features were found. Please use the !create_link link to create | |
965 a new Feature module, or upload an existing Feature to your modules directory.', | |
966 array('!create_link' => l(t('Create Feature'), 'admin/structure/features/create'))), | |
967 ); | |
968 return $form ; | |
969 } | |
970 | |
971 $form = array('#features' => $features); | |
972 | |
973 // Generate features form. Features are sorted by dependencies, resort alpha | |
974 ksort($features); | |
975 foreach ($features as $name => $module) { | |
976 $package_title = !empty($module->info['package']) ? $module->info['package'] : t('Other'); | |
977 $package = strtolower(preg_replace('/[^a-zA-Z0-9-]+/', '-', $package_title)); | |
978 | |
979 // Set up package elements | |
980 if (!isset($form[$package])) { | |
981 $form[$package] = array( | |
982 '#tree' => FALSE, | |
983 '#title' => check_plain($package_title), | |
984 '#theme' => 'features_form_package', | |
985 '#type' => 'fieldset', | |
986 '#group' => 'packages', | |
987 ); | |
988 $form[$package]['links'] = | |
989 $form[$package]['version'] = | |
990 $form[$package]['weight'] = | |
991 $form[$package]['status'] = | |
992 $form[$package]['action'] = array('#tree' => TRUE); | |
993 } | |
994 | |
995 $disabled = FALSE; | |
996 $description = isset($module->info['description']) ? check_plain($module->info['description']) : ''; | |
997 | |
998 // Detect unmet dependencies | |
999 if (!empty($module->info['dependencies'])) { | |
1000 $unmet_dependencies = array(); | |
1001 $dependencies = _features_export_maximize_dependencies($module->info['dependencies']); | |
1002 foreach ($dependencies as $dependency) { | |
1003 if (empty($modules[$dependency])) { | |
1004 $unmet_dependencies[] = theme('features_module_status', array('status' => FEATURES_MODULE_MISSING, 'module' => $dependency)); | |
1005 } | |
1006 } | |
1007 if (!empty($unmet_dependencies)) { | |
1008 $description .= "<div class='dependencies'>" . t('Unmet dependencies: !dependencies', array('!dependencies' => implode(', ', $unmet_dependencies))) . "</div>"; | |
1009 $disabled = TRUE; | |
1010 } | |
1011 } | |
1012 | |
1013 if (!empty($module->dependents)) { | |
1014 $disabled = TRUE; | |
1015 $description .= "<div class='requirements'>". t('Required by: !dependents', array('!dependents' => implode(', ', $module->dependents))) ."</div>"; | |
1016 } | |
1017 | |
1018 // Detect potential conflicts | |
1019 if (!empty($conflicts[$name])) { | |
1020 $module_conflicts = array(); | |
1021 foreach ($conflicts[$name] as $conflict => $components) { | |
1022 $component_strings = array(); | |
1023 foreach ($components as $component => $component_conflicts) { | |
1024 $component_strings[] = t('@component [@items]', array('@component' => $component, '@items' => implode(', ', $component_conflicts))); | |
1025 } | |
1026 $component_strings = implode(', ', $component_strings); | |
1027 // If conflicting module is disabled, indicate so in feature listing | |
1028 $status = !module_exists($conflict) ? FEATURES_MODULE_DISABLED : FEATURES_MODULE_CONFLICT; | |
1029 $module_conflicts[] = theme('features_module_status', array('status' => $status, 'module' => $conflict)) . t(' in ') . $component_strings; | |
1030 // Only disable modules with conflicts if they are not already enabled. | |
1031 // If they are already enabled, somehow the user got themselves into a | |
1032 // bad situation and they need to be able to disable a conflicted module. | |
1033 if (module_exists($conflict) && !module_exists($name)) { | |
1034 $disabled = TRUE; | |
1035 } | |
1036 } | |
1037 $description .= "<div class='conflicts'>". t('Conflicts with: !conflicts', array('!conflicts' => implode(', ', $module_conflicts))) ."</div>"; | |
1038 } | |
1039 | |
1040 $href = "admin/structure/features/{$name}"; | |
1041 $module_name = (user_access('administer features')) ? l($module->info['name'], $href) : $module->info['name']; | |
1042 $form[$package]['status'][$name] = array( | |
1043 '#type' => 'checkbox', | |
1044 '#title' => $module_name, | |
1045 '#description' => $description, | |
1046 '#default_value' => $module->status, | |
1047 '#disabled' => $disabled, | |
1048 ); | |
1049 | |
1050 if (!empty($module->info['project status url'])) { | |
1051 $uri = l(truncate_utf8($module->info['project status url'], 35, TRUE, TRUE), $module->info['project status url']); | |
1052 } | |
1053 else if (isset($module->info['project'], $module->info['version'], $module->info['datestamp'])) { | |
1054 $uri = l('http://drupal.org', 'http://drupal.org/project/' . $module->info['project']); | |
1055 } | |
1056 else { | |
1057 $uri = t('Unavailable'); | |
1058 } | |
1059 $version = !empty($module->info['version']) ? $module->info['version'] : ''; | |
1060 $version = !empty($version) ? "<div class='description'>$version</div>" : ''; | |
1061 $form[$package]['sign'][$name] = array('#markup' => "{$uri} {$version}"); | |
1062 | |
1063 if (user_access('administer features')) { | |
1064 // Add status link | |
1065 if ($module->status) { | |
1066 $state = theme('features_storage_link', array('storage' => FEATURES_CHECKING, 'path' => $href)); | |
1067 $state .= l(t('Check'), "admin/structure/features/{$name}/status", array('attributes' => array('class' => array('admin-check')))); | |
1068 $state .= theme('features_storage_link', array('storage' => FEATURES_REBUILDING, 'path' => $href)); | |
1069 $state .= theme('features_storage_link', array('storage' => FEATURES_NEEDS_REVIEW, 'path' => $href)); | |
1070 $state .= theme('features_storage_link', array('storage' => FEATURES_OVERRIDDEN, 'path' => $href)); | |
1071 $state .= theme('features_storage_link', array('storage' => FEATURES_DEFAULT, 'path' => $href)); | |
1072 } | |
1073 elseif (!empty($conflicts[$name])) { | |
1074 $state = theme('features_storage_link', array('storage' => FEATURES_CONFLICT, 'path' => $href)); | |
1075 } | |
1076 else { | |
1077 $state = theme('features_storage_link', array('storage' => FEATURES_DISABLED, 'path' => $href)); | |
1078 } | |
1079 $form[$package]['state'][$name] = array( | |
1080 '#markup' => !empty($state) ? $state : '', | |
1081 ); | |
1082 | |
1083 // Add in recreate link | |
1084 $form[$package]['actions'][$name] = array( | |
1085 '#markup' => l(t('Recreate'), "admin/structure/features/{$name}/recreate", array('attributes' => array('class' => array('admin-update')))), | |
1086 ); | |
1087 } | |
1088 } | |
1089 ksort($form); | |
1090 | |
1091 // As of 7.0 beta 2 it matters where the "vertical_tabs" element lives on the | |
1092 // the array. We add it late, but at the beginning of the array because that | |
1093 // keeps us away from trouble. | |
1094 $form = array('packages' => array('#type' => 'vertical_tabs')) + $form; | |
1095 | |
1096 $form['buttons'] = array( | |
1097 '#theme' => 'features_form_buttons', | |
1098 ); | |
1099 $form['buttons']['submit'] = array( | |
1100 '#type' => 'submit', | |
1101 '#value' => t('Save settings'), | |
1102 '#submit' => array('features_form_submit'), | |
1103 '#validate' => array('features_form_validate'), | |
1104 ); | |
1105 return $form; | |
1106 } | |
1107 | |
1108 /** | |
1109 * Display the components of a feature. | |
1110 */ | |
1111 function features_admin_components($form, $form_state, $feature) { | |
1112 // Breadcrumb navigation | |
1113 $breadcrumb[] = l(t('Home'), NULL); | |
1114 $breadcrumb[] = l(t('Administration'), 'admin'); | |
1115 $breadcrumb[] = l(t('Structure'), 'admin/structure'); | |
1116 $breadcrumb[] = l(t('Features'), 'admin/structure/features'); | |
1117 drupal_set_breadcrumb($breadcrumb); | |
1118 | |
1119 module_load_include('inc', 'features', 'features.export'); | |
1120 $form = array(); | |
1121 | |
1122 // Store feature info for theme layer. | |
1123 $form['module'] = array('#type' => 'value', '#value' => $feature->name); | |
1124 $form['#info'] = $feature->info; | |
1125 $form['#dependencies'] = array(); | |
1126 if (!empty($feature->info['dependencies'])) { | |
1127 foreach ($feature->info['dependencies'] as $dependency) { | |
1128 $parsed_dependency = drupal_parse_dependency($dependency); | |
1129 $dependency = $parsed_dependency['name']; | |
1130 $status = features_get_module_status($dependency); | |
1131 $form['#dependencies'][$dependency] = $status; | |
1132 } | |
1133 } | |
1134 | |
1135 $conflicts = features_get_conflicts(); | |
1136 if (!module_exists($form['module']['#value']) && isset($form['module']['#value']) && !empty($conflicts[$form['module']['#value']])) { | |
1137 $module_conflicts = $conflicts[$form['module']['#value']]; | |
1138 $conflicts = array(); | |
1139 foreach ($module_conflicts as $conflict) { | |
1140 $conflicts = array_merge_recursive($conflict, $conflicts); | |
1141 } | |
1142 } | |
1143 else { | |
1144 $conflicts = array(); | |
1145 } | |
1146 $form['#conflicts'] = $conflicts; | |
1147 | |
1148 $review = $revert = FALSE; | |
1149 | |
1150 // Iterate over components and retrieve status for display | |
1151 $states = features_get_component_states(array($feature->name), FALSE); | |
1152 $form['revert']['#tree'] = TRUE; | |
1153 foreach ($feature->info['features'] as $component => $items) { | |
1154 if (user_access('administer features') && array_key_exists($component, $states[$feature->name]) && in_array($states[$feature->name][$component], array(FEATURES_OVERRIDDEN, FEATURES_NEEDS_REVIEW))) { | |
1155 switch ($states[$feature->name][$component]) { | |
1156 case FEATURES_OVERRIDDEN: | |
1157 $revert = TRUE; | |
1158 break; | |
1159 case FEATURES_NEEDS_REVIEW: | |
1160 $review = TRUE; | |
1161 break; | |
1162 } | |
1163 $form['revert'][$component] = array( | |
1164 '#type' => 'checkbox', | |
1165 '#default_value' => FALSE, | |
1166 ); | |
1167 } | |
1168 if (module_exists('diff')) { | |
1169 $diffpath = "admin/structure/features/{$feature->name}/diff/{$component}"; | |
1170 $item = menu_get_item($diffpath); | |
1171 $path = ($item && $item['access']) ? $diffpath : NULL; | |
1172 } | |
1173 else { | |
1174 $path = NULL; | |
1175 } | |
1176 | |
1177 $storage = FEATURES_DEFAULT; | |
1178 if (array_key_exists($component, $states[$feature->name])) { | |
1179 $storage = $states[$feature->name][$component]; | |
1180 } | |
1181 else if (array_key_exists($component, $conflicts)) { | |
1182 $storage = FEATURES_CONFLICT; | |
1183 } | |
1184 $form['components'][$component] = array( | |
1185 '#markup' => theme('features_storage_link', array('storage' => $storage, 'path' => $path)), | |
1186 ); | |
1187 } | |
1188 | |
1189 if ($review || $revert) { | |
1190 $form['buttons'] = array('#theme' => 'features_form_buttons', '#tree' => TRUE); | |
1191 if ($revert || $review) { | |
1192 $form['buttons']['revert'] = array( | |
1193 '#type' => 'submit', | |
1194 '#value' => t('Revert components'), | |
1195 '#submit' => array('features_admin_components_revert'), | |
1196 ); | |
1197 } | |
1198 if ($review) { | |
1199 $form['buttons']['review'] = array( | |
1200 '#type' => 'submit', | |
1201 '#value' => t('Mark as reviewed'), | |
1202 '#submit' => array('features_admin_components_review'), | |
1203 ); | |
1204 } | |
1205 } | |
1206 return $form; | |
1207 } | |
1208 | |
1209 /** | |
1210 * Submit handler for revert form. | |
1211 */ | |
1212 function features_admin_components_revert(&$form, &$form_state) { | |
1213 module_load_include('inc', 'features', 'features.export'); | |
1214 features_include(); | |
1215 $module = $form_state['values']['module']; | |
1216 $revert = array($module => array()); | |
1217 foreach (array_filter($form_state['values']['revert']) as $component => $status) { | |
1218 $revert[$module][] = $component; | |
1219 drupal_set_message(t('Reverted all <strong>@component</strong> components for <strong>@module</strong>.', array('@component' => $component, '@module' => $module))); | |
1220 } | |
1221 if (empty($revert[$module])) { | |
1222 drupal_set_message(t('Please select which components to revert.'), 'warning'); | |
1223 } | |
1224 features_revert($revert); | |
1225 $form_state['redirect'] = 'admin/structure/features/' . $module; | |
1226 } | |
1227 | |
1228 /** | |
1229 * Submit handler for revert form. | |
1230 */ | |
1231 function features_admin_components_review(&$form, &$form_state) { | |
1232 module_load_include('inc', 'features', 'features.export'); | |
1233 features_include(); | |
1234 $module = $form_state['values']['module']; | |
1235 $revert = array(); | |
1236 foreach (array_filter($form_state['values']['revert']) as $component => $status) { | |
1237 features_set_signature($module, $component); | |
1238 drupal_set_message(t('All <strong>@component</strong> components for <strong>@module</strong> reviewed.', array('@component' => $component, '@module' => $module))); | |
1239 } | |
1240 $form_state['redirect'] = 'admin/structure/features/' . $module; | |
1241 } | |
1242 | |
1243 /** | |
1244 * Validate handler for the 'manage features' form. | |
1245 */ | |
1246 function features_form_validate(&$form, &$form_state) { | |
1247 include_once './includes/install.inc'; | |
1248 $conflicts = features_get_conflicts(); | |
1249 foreach ($form_state['values']['status'] as $module => $status) { | |
1250 if ($status) { | |
1251 if (!empty($conflicts[$module])) { | |
1252 foreach (array_keys($conflicts[$module]) as $conflict) { | |
1253 if (!empty($form_state['values']['status'][$conflict])) { | |
1254 form_set_error('status', t('The feature @module cannot be enabled because it conflicts with @conflict.', array('@module' => $module, '@conflict' => $conflict))); | |
1255 } | |
1256 } | |
1257 } | |
1258 if (!drupal_check_module($module)) { | |
1259 form_set_error('status', t('The feature @module cannot be enabled because it has unmet requirements.', array('@module' => $module))); | |
1260 } | |
1261 } | |
1262 } | |
1263 } | |
1264 | |
1265 /** | |
1266 * Submit handler for the 'manage features' form | |
1267 */ | |
1268 function features_form_submit(&$form, &$form_state) { | |
1269 // Clear drupal caches after enabling a feature. We do this in a separate | |
1270 // page callback rather than as part of the submit handler as some modules | |
1271 // have includes/other directives of importance in hooks that have already | |
1272 // been called in this page load. | |
1273 $form_state['redirect'] = 'admin/structure/features/cleanup/clear'; | |
1274 | |
1275 $features = $form['#features']; | |
1276 if (!empty($features)) { | |
1277 $status = $form_state['values']['status']; | |
1278 $install = array_keys(array_filter($status)); | |
1279 $disable = array_diff(array_keys($status), $install); | |
1280 | |
1281 // Disable first. If there are any features that are disabled that are | |
1282 // dependencies of features that have been queued for install, they will | |
1283 // be re-enabled. | |
1284 module_disable($disable); | |
1285 features_install_modules($install); | |
1286 } | |
1287 } | |
1288 | |
1289 /** | |
1290 * Form for clearing cache after enabling a feature. | |
1291 */ | |
1292 function features_cleanup_form($form, $form_state, $cache_clear = FALSE) { | |
1293 // Clear caches if we're getting a post-submit redirect that requests it. | |
1294 if ($cache_clear) { | |
1295 drupal_flush_all_caches(); | |
1296 | |
1297 // The following functions need to be run because drupal_flush_all_caches() | |
1298 // runs rebuilds in the wrong order. The node type cache is rebuilt *after* | |
1299 // the menu is rebuilt, meaning that the menu tree is stale in certain | |
1300 // circumstances after drupal_flush_all_caches(). We rebuild again. | |
1301 menu_rebuild(); | |
1302 } | |
1303 | |
1304 drupal_goto('admin/structure/features'); | |
1305 } | |
1306 | |
1307 /** | |
1308 * Page callback to display the differences between what's in code and | |
1309 * what is in the db. | |
1310 * | |
1311 * @param $feature | |
1312 * A loaded feature object to display differences for. | |
1313 * @param $component | |
1314 * (optional) Specific component to display differences for. If excluded, all | |
1315 * components are used. | |
1316 * | |
1317 * @return | |
1318 * Themed display of what is different. | |
1319 */ | |
1320 function features_feature_diff($feature, $component = NULL) { | |
1321 drupal_add_css(drupal_get_path('module', 'features') . '/features.css'); | |
1322 module_load_include('inc', 'features', 'features.export'); | |
1323 drupal_set_title($feature->info['name']); | |
1324 | |
1325 $overrides = features_detect_overrides($feature); | |
1326 | |
1327 $output = ''; | |
1328 if (!empty($overrides)) { | |
1329 // Filter overrides down to specified component. | |
1330 if (isset($component) && isset($overrides[$component])) { | |
1331 $overrides = array($component => $overrides[$component]); | |
1332 } | |
1333 | |
1334 module_load_include('inc', 'diff', 'diff.engine'); | |
1335 $formatter = new DrupalDiffFormatter(); | |
1336 | |
1337 $rows = array(); | |
1338 foreach ($overrides as $component => $items) { | |
1339 $rows[] = array(array('data' => $component, 'colspan' => 4, 'header' => TRUE)); | |
1340 $diff = new Diff(explode("\n", $items['default']), explode("\n", $items['normal'])); | |
1341 $rows = array_merge($rows, $formatter->format($diff)); | |
1342 } | |
1343 $header = array( | |
1344 array('data' => t('Default'), 'colspan' => 2), | |
1345 array('data' => t('Overrides'), 'colspan' => 2), | |
1346 ); | |
1347 $output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('class' => array('diff', 'features-diff')))); | |
1348 } | |
1349 else { | |
1350 $output = "<div class='features-empty'>" . t('No changes have been made to this feature.') . "</div>"; | |
1351 } | |
1352 $output = array('page' => array('#markup' => "<div class='features-comparison'>{$output}</div>")); | |
1353 return $output; | |
1354 } | |
1355 | |
1356 /** | |
1357 * Compare the component names. Used to sort alphabetically. | |
1358 */ | |
1359 function features_compare_component_name($a, $b) { | |
1360 return strcasecmp($a['name'], $b['name']); | |
1361 } | |
1362 | |
1363 /** | |
1364 * Javascript callback that returns the status of a feature. | |
1365 */ | |
1366 function features_feature_status($feature) { | |
1367 module_load_include('inc', 'features', 'features.export'); | |
1368 return drupal_json_output(array('storage' => features_get_storage($feature->name))); | |
1369 } | |
1370 | |
1371 /** | |
1372 * Make a Drupal options array safe for usage with jQuery DOM selectors. | |
1373 * Encodes known bad characters into __[ordinal]__ so that they may be | |
1374 * safely referenced by JS behaviors. | |
1375 */ | |
1376 function features_dom_encode_options($options = array(), $keys_only = TRUE) { | |
1377 $replacements = features_dom_encode_map(); | |
1378 $encoded = array(); | |
1379 foreach ($options as $key => $value) { | |
1380 $encoded[strtr($key, $replacements)] = $keys_only ? $value : strtr($value, $replacements); | |
1381 } | |
1382 return $encoded; | |
1383 } | |
1384 | |
1385 function features_dom_encode($key) { | |
1386 $replacements = features_dom_encode_map(); | |
1387 return strtr($key, $replacements); | |
1388 } | |
1389 | |
1390 function features_dom_decode($key) { | |
1391 $replacements = array_flip(features_dom_encode_map()); | |
1392 return strtr($key, $replacements); | |
1393 } | |
1394 | |
1395 /** | |
1396 * Decode an array of option values that have been encoded by | |
1397 * features_dom_encode_options(). | |
1398 */ | |
1399 function features_dom_decode_options($options, $keys_only = FALSE) { | |
1400 $replacements = array_flip(features_dom_encode_map()); | |
1401 $encoded = array(); | |
1402 foreach ($options as $key => $value) { | |
1403 $encoded[strtr($key, $replacements)] = $keys_only ? $value : strtr($value, $replacements); | |
1404 } | |
1405 return $encoded; | |
1406 } | |
1407 | |
1408 /** | |
1409 * Returns encoding map for decode and encode options. | |
1410 */ | |
1411 function features_dom_encode_map() { | |
1412 return array( | |
1413 ':' => '__' . ord(':') . '__', | |
1414 '/' => '__' . ord('/') . '__', | |
1415 ',' => '__' . ord(',') . '__', | |
1416 '.' => '__' . ord('.') . '__', | |
1417 '<' => '__' . ord('<') . '__', | |
1418 '>' => '__' . ord('>') . '__', | |
1419 '%' => '__' . ord('%') . '__', | |
1420 ')' => '__' . ord(')') . '__', | |
1421 '(' => '__' . ord('(') . '__', | |
1422 ); | |
1423 } | |
1424 | |
1425 /** | |
1426 * Page callback: Autocomplete field for features package. | |
1427 * | |
1428 * @param $search_string | |
1429 * The char or string that user have written in autocomplete field, | |
1430 * this is the string this function uses for filter. | |
1431 * | |
1432 * @see features_menu() | |
1433 */ | |
1434 function features_autocomplete_packages($search_string) { | |
1435 $matched_packages = array(); | |
1436 //fetch all modules that are features and copy the package name into a new array. | |
1437 foreach (features_get_features(NULL, TRUE) as $value) { | |
1438 if (preg_match('/' . $search_string . '/i', $value->info['package'])) { | |
1439 $matched_packages[$value->info['package']] = $value->info['package']; | |
1440 } | |
1441 } | |
1442 //removes duplicated package, we wont a list of all unique packages. | |
1443 $matched_packages = array_unique($matched_packages); | |
1444 drupal_json_output($matched_packages); | |
1445 } | |
1446 | |
1447 /** | |
1448 * Return a list of all used components/items not matching a given feature module | |
1449 * similar to features_get_conflicts but returns all component items "in use" | |
1450 */ | |
1451 function _features_get_used($module_name = NULL) { | |
1452 | |
1453 global $features_ignore_conflicts; | |
1454 // make sure we turn off the ignore_conflicts global to get full list of used components | |
1455 // hate to use global, but since this is just for an admin screen it's not a real problem | |
1456 $old_value = $features_ignore_conflicts; | |
1457 $features_ignore_conflicts = FALSE; | |
1458 | |
1459 $conflicts = array(); | |
1460 $component_info = features_get_components(); | |
1461 $map = features_get_component_map(); | |
1462 | |
1463 foreach ($map as $type => $components) { | |
1464 // Only check conflicts for components we know about. | |
1465 if (isset($component_info[$type])) { | |
1466 foreach ($components as $component => $modules) { | |
1467 foreach ($modules as $module) { | |
1468 // only for enabled modules | |
1469 if (module_exists($module) && (empty($module_name) || ($module_name != $module))) { | |
1470 if (!isset($conflicts[$module])) { | |
1471 $conflicts[$module] = array(); | |
1472 } | |
1473 $conflicts[$module][$type][] = $component; | |
1474 } | |
1475 } | |
1476 } | |
1477 } | |
1478 } | |
1479 | |
1480 // restore previous value of global | |
1481 $features_ignore_conflicts = $old_value; | |
1482 return $conflicts; | |
1483 } |