PageRenderTime 52ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/sites/all/modules/features/features.admin.inc

https://bitbucket.org/hjain/trinet
Pascal | 877 lines | 435 code | 49 blank | 393 comment | 63 complexity | 1fa94421433de7e299c9ee85c4b61de2 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-3.0, AGPL-1.0
  1. <?php
  2. /**
  3. * Form callback for features export form. Acts as a router based on the form_state.
  4. */
  5. function features_export_form($form, $form_state, $feature = NULL) {
  6. module_load_include('inc', 'features', 'features.export');
  7. features_include();
  8. $form = array(
  9. '#attributes' => array('class' => array('features-export-form')),
  10. '#feature' => isset($feature) ? $feature : NULL,
  11. );
  12. $form['info'] = array(
  13. '#type' => 'fieldset',
  14. '#tree' => FALSE,
  15. );
  16. $form['info']['name'] = array(
  17. '#title' => t('Name'),
  18. '#description' => t('Example: Image gallery') . ' (' . t('Do not begin name with numbers.') . ')',
  19. '#type' => 'textfield',
  20. '#default_value' => !empty($feature->info['name']) ? $feature->info['name'] : '',
  21. '#attributes' => array('class' => array('feature-name')),
  22. );
  23. $form['info']['module_name'] = array(
  24. '#type' => 'textfield',
  25. '#title' => t('Machine-readable name'),
  26. '#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>'),
  27. '#required' => TRUE,
  28. '#default_value' => !empty($feature->name) ? $feature->name : '',
  29. '#attributes' => array('class' => array('feature-module-name')),
  30. '#element_validate' => array('features_export_form_validate_field'),
  31. );
  32. // If recreating this feature, disable machine name field and blank out
  33. // js-attachment classes to ensure the machine name cannot be changed.
  34. if (isset($feature)) {
  35. $form['info']['module_name']['#value'] = $feature->name;
  36. $form['info']['module_name']['#disabled'] = TRUE;
  37. $form['info']['name']['#attributes'] = array();
  38. }
  39. $form['info']['description'] = array(
  40. '#title' => t('Description'),
  41. '#description' => t('Provide a short description of what users should expect when they enable your feature.'),
  42. '#type' => 'textfield',
  43. '#default_value' => !empty($feature->info['description']) ? $feature->info['description'] : '',
  44. );
  45. $form['info']['package'] = array(
  46. '#title' => t('Package'),
  47. '#description' => t('Organize your features in groups.'),
  48. '#type' => 'textfield',
  49. '#autocomplete_path' => 'features/autocomplete/packages',
  50. '#default_value' => !empty($feature->info['package']) ? $feature->info['package'] : 'Features',
  51. );
  52. $form['info']['version'] = array(
  53. '#title' => t('Version'),
  54. '#description' => t('Examples: 7.x-1.0, 7.x-1.0-beta1'),
  55. '#type' => 'textfield',
  56. '#required' => FALSE,
  57. '#default_value' => !empty($feature->info['version']) ? $feature->info['version'] : '',
  58. '#size' => 30,
  59. '#element_validate' => array('features_export_form_validate_field'),
  60. );
  61. $form['info']['project_status_url'] = array(
  62. '#title' => t('URL of update XML'),
  63. '#description' => t('Example: http://mywebsite.com/fserver'),
  64. '#type' => 'textfield',
  65. '#required' => FALSE,
  66. '#default_value' => !empty($feature->info['project status url']) ? $feature->info['project status url'] : '',
  67. '#size' => 30,
  68. '#element_validate' => array('features_export_form_validate_field'),
  69. );
  70. // User-selected feature source components.
  71. $components = features_get_components();
  72. uasort($components, 'features_compare_component_name');
  73. $form['export'] = array(
  74. '#type' => 'fieldset',
  75. '#tree' => FALSE,
  76. '#theme' => 'features_form_export',
  77. );
  78. $form['export']['components'] = array(
  79. '#title' => t('Edit components'),
  80. '#type' => 'select',
  81. '#options' => array('------'),
  82. '#attributes' => array('class' => array('features-select-components')),
  83. );
  84. $form['export']['sources'] = array(
  85. '#tree' => TRUE,
  86. '#theme' => 'features_form_components',
  87. );
  88. foreach ($components as $component => $component_info) {
  89. $options = features_invoke($component, 'features_export_options');
  90. if ($component === 'dependencies') {
  91. $default_value = !empty($feature->info['dependencies']) ? $feature->info['dependencies'] : array();
  92. }
  93. else {
  94. $default_value = !empty($feature->info['features'][$component]) ? $feature->info['features'][$component] : array();
  95. }
  96. if ($options) {
  97. // Find all default components that are not provided by this feature and
  98. // strip them out of the possible options.
  99. if ($map = features_get_default_map($component)) {
  100. foreach ($map as $k => $v) {
  101. if (isset($options[$k]) && (!isset($feature->name) || $v !== $feature->name)) {
  102. unset($options[$k]);
  103. }
  104. }
  105. }
  106. // Ensure all options are stripped of potentially bad values.
  107. foreach ($options as $k => $v) {
  108. $options[$k] = check_plain($v);
  109. }
  110. $label = (isset($component_info['name']) ? $component_info['name'] . ": " . $component : $component);
  111. $form['export']['components']['#options'][$component] = $label;
  112. if (!empty($options)) {
  113. $form['export']['sources'][$component] = array(
  114. '#type' => 'checkboxes',
  115. '#options' => features_dom_encode_options($options),
  116. '#title' => $component,
  117. '#default_value' => features_dom_encode_options($default_value, FALSE),
  118. '#ajax' => array(
  119. 'callback' => 'features_export_build_form_populate',
  120. 'wrapper' => 'features-export-contents',
  121. ),
  122. );
  123. }
  124. else {
  125. $form['export']['sources'][$component] = array(
  126. '#type' => 'item',
  127. '#title' => $component,
  128. '#value' => t('All components of this type are exported by other features or modules.'),
  129. );
  130. }
  131. }
  132. }
  133. $form['export']['features'] = array(
  134. '#tree' => TRUE,
  135. '#prefix' => "<div id='features-export-populated'><div id='features-export-contents'>",
  136. '#suffix' => "</div></div>",
  137. '#markup' => !empty($feature->info) ? theme('features_components', array('info' => $feature->info, 'sources' => $feature->info['features'])) : "<div class='placeholder'></div>",
  138. );
  139. $form['buttons'] = array('#theme' => 'features_form_buttons', '#tree' => FALSE);
  140. $form['buttons']['submit'] = array(
  141. '#type' => 'submit',
  142. '#value' => t('Download feature'),
  143. '#weight' => 10,
  144. '#submit' => array('features_export_build_form_submit'),
  145. );
  146. return $form;
  147. }
  148. /**
  149. * Validation for project field.
  150. */
  151. function features_export_form_validate_field($element, &$form_state) {
  152. switch ($element['#name']) {
  153. case 'module_name':
  154. if (!preg_match('!^[a-z0-9_]+$!', $element['#value'])) {
  155. form_error($element, t('The machine-readable name must contain only lowercase letters, numbers, and underscores.'));
  156. }
  157. // If user is filling out the feature name for the first time and uses
  158. // the name of an existing module throw an error.
  159. else if (empty($element['#default_value']) && features_get_info('module', $element['#value'])) {
  160. form_error($element, t('A module by the name @name already exists on your site. Please choose a different name.', array('@name' => $element['#value'])));
  161. }
  162. break;
  163. case 'project_status_url':
  164. if (!empty($element['#value']) && !valid_url($element['#value'])) {
  165. 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'])));
  166. }
  167. break;
  168. case 'version':
  169. preg_match('/^(?P<core>\d+\.x)-(?P<major>\d+)\.(?P<patch>\d+)-?(?P<extra>\w+)?$/', $element['#value'], $matches);
  170. if (!empty($element['#value']) && !isset($matches['core'], $matches['major'])) {
  171. form_error($element, t('Please enter a valid version with core and major version number. Example: !example', array('!example' => '6.x-1.0')));
  172. };
  173. break;
  174. }
  175. }
  176. /**
  177. * Submit handler for features_export_form_build().
  178. */
  179. function features_export_build_form_submit($form, &$form_state) {
  180. module_load_include('inc', 'features', 'features.export');
  181. features_include();
  182. // Assemble the combined component list
  183. $stub = array();
  184. $components = array_keys(features_get_components());
  185. foreach ($components as $component) {
  186. // User-selected components take precedence.
  187. if (!empty($form_state['values']['sources'][$component])) {
  188. $stub[$component] = features_dom_decode_options(array_filter($form_state['values']['sources'][$component]));
  189. }
  190. // Only fallback to an existing feature's values if there are no export options for the component.
  191. else if (!empty($form['#feature']->info['features'][$component])) {
  192. $stub[$component] = $form['#feature']->info['features'][$component];
  193. }
  194. }
  195. // Generate populated feature
  196. $module_name = $form_state['values']['module_name'];
  197. $export = features_populate($stub, $form_state['values']['sources']['dependencies'], $module_name);
  198. // Directly copy the following attributes
  199. $attr = array('name', 'description', 'package');
  200. foreach ($attr as $key) {
  201. $export[$key] = isset($form_state['values'][$key]) ? $form_state['values'][$key] : NULL;
  202. }
  203. // If either update status-related keys are provided, add a project key
  204. // corresponding to the module name.
  205. if (!empty($form_state['values']['version']) || !empty($form_state['values']['project_status_url'])) {
  206. $export['project'] = $form_state['values']['module_name'];
  207. }
  208. if (!empty($form_state['values']['version'])) {
  209. $export['version'] = $form_state['values']['version'];
  210. }
  211. if (!empty($form_state['values']['project_status_url'])) {
  212. $export['project status url'] = $form_state['values']['project_status_url'];
  213. }
  214. // Generate download
  215. if ($files = features_export_render($export, $module_name, TRUE)) {
  216. $filename = (!empty($export['version']) ? "{$module_name}-{$export['version']}" : $module_name) . '.tar';
  217. // Clear out output buffer to remove any garbage from tar output.
  218. if (ob_get_level()) {
  219. ob_end_clean();
  220. }
  221. drupal_add_http_header('Content-type', 'application/x-tar');
  222. drupal_add_http_header('Content-Disposition', 'attachment; filename="'. $filename .'"');
  223. drupal_send_headers();
  224. $tar = array();
  225. $filenames = array();
  226. foreach ($files as $extension => $file_contents) {
  227. if (!in_array($extension, array('module', 'info'))) {
  228. $extension .= '.inc';
  229. }
  230. $filenames[] = "{$module_name}.$extension";
  231. print features_tar_create("{$module_name}/{$module_name}.$extension", $file_contents);
  232. }
  233. if (features_get_modules($module_name, TRUE)) {
  234. $module_path = drupal_get_path('module', $module_name);
  235. // file_scan_directory() can throw warnings when using PHP 5.3, messing
  236. // up the output of our file stream. Suppress errors in this one case in
  237. // order to produce valid output.
  238. foreach (@file_scan_directory($module_path, '/.*/') as $file) {
  239. $filename = substr($file->uri, strlen($module_path) + 1);
  240. if (!in_array($filename, $filenames)) {
  241. // Add this file.
  242. $contents = file_get_contents($file->uri);
  243. print features_tar_create("{$module_name}/{$filename}", $contents);
  244. unset($contents);
  245. }
  246. }
  247. }
  248. print pack("a1024","");
  249. exit;
  250. }
  251. }
  252. /**
  253. * AHAH handler for features_export_form_build().
  254. */
  255. function features_export_build_form_populate($form, $form_state) {
  256. module_load_include('inc', 'features', 'features.export');
  257. features_include();
  258. $stub = array();
  259. $submitted = $form_state['values'];
  260. // Assemble the combined component list
  261. $components = array_keys(features_get_components());
  262. foreach ($components as $component) {
  263. // User-selected components take precedence.
  264. if (!empty($submitted['sources'][$component])) {
  265. // Validate and set the default value for each selected option. This
  266. foreach ($submitted['sources'][$component] as $key => $value) {
  267. if (isset($form['export']['sources'][$component]['#options'][$key])) {
  268. $form['export']['sources'][$component]['#default_value'][$key] = $value;
  269. }
  270. }
  271. $stub[$component] = features_dom_decode_options(array_filter($submitted['sources'][$component]));
  272. }
  273. // Only fallback to an existing feature's values if there are no export options for the component.
  274. else if (!empty($form['export']['sources'][$component]) && !empty($form['#feature']->info['features'][$component])) {
  275. $stub[$component] = $form['#feature']->info['features'][$component];
  276. }
  277. }
  278. // Assemble dependencies
  279. $dependencies = isset($submitted['sources']['dependencies']) ? $submitted['sources']['dependencies'] : array();
  280. // Generate populated feature
  281. $module_name = isset($form['#feature'], $form['#feature']->name) ? $form['#feature']->name : '';
  282. $export = features_populate($stub, $dependencies, $module_name);
  283. // Render component display
  284. $components_rendered = theme('features_components', array('info' => $export, 'sources' => $stub));
  285. $form['export']['features']['#markup'] = $components_rendered;
  286. // @TODO: Reimplement this for D7.
  287. // Re-cache form. This ensures that if the form fails to validate, selected
  288. // values are preserved for the user.
  289. // form_set_cache($submitted['form_build_id'], $form, $form_state);
  290. return $form['export']['features'];
  291. }
  292. /**
  293. * array_filter() callback for excluding hidden modules.
  294. */
  295. function features_filter_hidden($module) {
  296. return empty($module->info['hidden']);
  297. }
  298. /**
  299. * admin/build/features page callback.
  300. */
  301. function features_admin_form($form, $form_state) {
  302. // Load export functions to use in comparison.
  303. module_load_include('inc', 'features', 'features.export');
  304. // Clear & rebuild key caches
  305. features_get_info(NULL, NULL, TRUE);
  306. features_rebuild();
  307. $modules = array_filter(features_get_modules(), 'features_filter_hidden');
  308. $features = array_filter(features_get_features(), 'features_filter_hidden');
  309. $conflicts = features_get_conflicts();
  310. foreach ($modules as $key => $module) {
  311. if ($module->status && !empty($module->info['dependencies'])) {
  312. foreach ($module->info['dependencies'] as $dependent) {
  313. if (isset($features[$dependent])) {
  314. $features[$dependent]->dependents[$key] = $module->info['name'];
  315. }
  316. }
  317. }
  318. }
  319. if ( empty($features) ) {
  320. $form['no_features'] = array(
  321. '#markup' => t('No Features were found. Please use the !create_link link to create
  322. a new Feature module, or upload an existing Feature to your modules directory.',
  323. array('!create_link' => l(t('Create Feature'), 'admin/structure/features/create'))),
  324. );
  325. return $form ;
  326. }
  327. $form = array('#features' => $features);
  328. // Generate features form. Features are sorted by dependencies, resort alpha
  329. ksort($features);
  330. foreach ($features as $name => $module) {
  331. $package_title = !empty($module->info['package']) ? $module->info['package'] : t('Other');
  332. $package = strtolower(preg_replace('/[^a-zA-Z0-9-]+/', '-', $package_title));
  333. // Set up package elements
  334. if (!isset($form[$package])) {
  335. $form[$package] = array(
  336. '#tree' => FALSE,
  337. '#title' => $package_title,
  338. '#theme' => 'features_form_package',
  339. '#type' => 'fieldset',
  340. '#group' => 'packages',
  341. );
  342. $form[$package]['links'] =
  343. $form[$package]['version'] =
  344. $form[$package]['weight'] =
  345. $form[$package]['status'] =
  346. $form[$package]['action'] = array('#tree' => TRUE);
  347. }
  348. $disabled = FALSE;
  349. $description = isset($module->info['description']) ? $module->info['description'] : '';
  350. // Detect unmet dependencies
  351. if (!empty($module->info['dependencies'])) {
  352. $unmet_dependencies = array();
  353. $dependencies = _features_export_maximize_dependencies($module->info['dependencies']);
  354. foreach ($dependencies as $dependency) {
  355. if (empty($modules[$dependency])) {
  356. $unmet_dependencies[] = theme('features_module_status', array('status' => FEATURES_MODULE_MISSING, 'module' => $dependency));
  357. }
  358. }
  359. if (!empty($unmet_dependencies)) {
  360. $description .= "<div class='dependencies'>" . t('Unmet dependencies: !dependencies', array('!dependencies' => implode(', ', $unmet_dependencies))) . "</div>";
  361. $disabled = TRUE;
  362. }
  363. }
  364. if (!empty($module->dependents)) {
  365. $disabled = TRUE;
  366. $description .= "<div class='requirements'>". t('Required by: !dependents', array('!dependents' => implode(', ', $module->dependents))) ."</div>";
  367. }
  368. // Detect potential conflicts
  369. if (!empty($conflicts[$name])) {
  370. $module_conflicts = array();
  371. foreach (array_keys($conflicts[$name]) as $conflict) {
  372. // If conflicting module is disabled, indicate so in feature listing
  373. $status = !module_exists($conflict) ? FEATURES_MODULE_DISABLED : FEATURES_MODULE_CONFLICT;
  374. $module_conflicts[] = theme('features_module_status', array('status' => $status, 'module' => $conflict));
  375. // Only disable modules with conflicts if they are not already enabled.
  376. // If they are already enabled, somehow the user got themselves into a
  377. // bad situation and they need to be able to disable a conflicted module.
  378. if (module_exists($conflict) && !module_exists($name)) {
  379. $disabled = TRUE;
  380. }
  381. }
  382. $description .= "<div class='conflicts'>". t('Conflicts with: !conflicts', array('!conflicts' => implode(', ', $module_conflicts))) ."</div>";
  383. }
  384. $href = "admin/structure/features/{$name}";
  385. $module_name = (user_access('administer features')) ? l($module->info['name'], $href) : $module->info['name'];
  386. $form[$package]['status'][$name] = array(
  387. '#type' => 'checkbox',
  388. '#title' => $module_name,
  389. '#description' => $description,
  390. '#default_value' => $module->status,
  391. '#disabled' => $disabled,
  392. );
  393. if (!empty($module->info['project status url'])) {
  394. $uri = l(truncate_utf8($module->info['project status url'], 35, TRUE, TRUE), $module->info['project status url']);
  395. }
  396. else if (isset($module->info['project'], $module->info['version'], $module->info['datestamp'])) {
  397. $uri = l('http://drupal.org', 'http://drupal.org/project/' . $module->info['project']);
  398. }
  399. else {
  400. $uri = t('Unavailable');
  401. }
  402. $version = !empty($module->info['version']) ? $module->info['version'] : '';
  403. $version = !empty($version) ? "<div class='description'>$version</div>" : '';
  404. $form[$package]['sign'][$name] = array('#markup' => "{$uri} {$version}");
  405. if (user_access('administer features')) {
  406. // Add status link
  407. if ($module->status) {
  408. $state = theme('features_storage_link', array('storage' => FEATURES_CHECKING, 'path' => $href));
  409. $state .= l(t('Check'), "admin/structure/features/{$name}/status", array('attributes' => array('class' => array('admin-check'))));
  410. $state .= theme('features_storage_link', array('storage' => FEATURES_REBUILDING, 'path' => $href));
  411. $state .= theme('features_storage_link', array('storage' => FEATURES_NEEDS_REVIEW, 'path' => $href));
  412. $state .= theme('features_storage_link', array('storage' => FEATURES_OVERRIDDEN, 'path' => $href));
  413. $state .= theme('features_storage_link', array('storage' => FEATURES_DEFAULT, 'path' => $href));
  414. }
  415. elseif (!empty($conflicts[$name])) {
  416. $state = theme('features_storage_link', array('storage' => FEATURES_CONFLICT, 'path' => $href));
  417. }
  418. else {
  419. $state = theme('features_storage_link', array('storage' => FEATURES_DISABLED, 'path' => $href));
  420. }
  421. $form[$package]['state'][$name] = array(
  422. '#markup' => !empty($state) ? $state : '',
  423. );
  424. // Add in recreate link
  425. $form[$package]['actions'][$name] = array(
  426. '#markup' => l(t('Recreate'), "admin/structure/features/{$name}/recreate", array('attributes' => array('class' => array('admin-update')))),
  427. );
  428. }
  429. }
  430. ksort($form);
  431. // As of 7.0 beta 2 it matters where the "vertical_tabs" element lives on the
  432. // the array. We add it late, but at the beginning of the array because that
  433. // keeps us away from trouble.
  434. $form = array('packages' => array('#type' => 'vertical_tabs')) + $form;
  435. $form['buttons'] = array(
  436. '#theme' => 'features_form_buttons',
  437. );
  438. $form['buttons']['submit'] = array(
  439. '#type' => 'submit',
  440. '#value' => t('Save settings'),
  441. '#submit' => array('features_form_submit'),
  442. '#validate' => array('features_form_validate'),
  443. );
  444. return $form;
  445. }
  446. /**
  447. * Display the components of a feature.
  448. */
  449. function features_admin_components($form, $form_state, $feature) {
  450. // Breadcrumb navigation
  451. $breadcrumb[] = l(t('Home'), NULL);
  452. $breadcrumb[] = l(t('Features'), 'admin/structure/features');
  453. drupal_set_breadcrumb($breadcrumb);
  454. module_load_include('inc', 'features', 'features.export');
  455. $form = array();
  456. // Store feature info for theme layer.
  457. $form['module'] = array('#type' => 'value', '#value' => $feature->name);
  458. $form['#info'] = $feature->info;
  459. $form['#dependencies'] = array();
  460. if (!empty($feature->info['dependencies'])) {
  461. foreach ($feature->info['dependencies'] as $dependency) {
  462. $parsed_dependency = drupal_parse_dependency($dependency);
  463. $dependency = $parsed_dependency['name'];
  464. $status = features_get_module_status($dependency);
  465. $form['#dependencies'][$dependency] = $status;
  466. }
  467. }
  468. $conflicts = features_get_conflicts();
  469. if (!module_exists($form['module']['#value']) && isset($form['module']['#value']) && !empty($conflicts[$form['module']['#value']])) {
  470. $module_conflicts = $conflicts[$form['module']['#value']];
  471. $conflicts = array();
  472. foreach ($module_conflicts as $conflict) {
  473. $conflicts = array_merge_recursive($conflict, $conflicts);
  474. }
  475. }
  476. else {
  477. $conflicts = array();
  478. }
  479. $form['#conflicts'] = $conflicts;
  480. $review = $revert = FALSE;
  481. // Iterate over components and retrieve status for display
  482. $states = features_get_component_states(array($feature->name), FALSE);
  483. $form['revert']['#tree'] = TRUE;
  484. foreach ($feature->info['features'] as $component => $items) {
  485. if (user_access('administer features') && array_key_exists($component, $states[$feature->name]) && in_array($states[$feature->name][$component], array(FEATURES_OVERRIDDEN, FEATURES_NEEDS_REVIEW))) {
  486. switch ($states[$feature->name][$component]) {
  487. case FEATURES_OVERRIDDEN:
  488. $revert = TRUE;
  489. break;
  490. case FEATURES_NEEDS_REVIEW:
  491. $review = TRUE;
  492. break;
  493. }
  494. $form['revert'][$component] = array(
  495. '#type' => 'checkbox',
  496. '#default_value' => FALSE,
  497. );
  498. }
  499. if (module_exists('diff')) {
  500. $item = menu_get_item("admin/structure/features/{$feature->name}/diff/{$component}");
  501. $path = ($item && $item['access']) ? $item['href'] : NULL;
  502. }
  503. else {
  504. $path = NULL;
  505. }
  506. $storage = FEATURES_DEFAULT;
  507. if (array_key_exists($component, $states[$feature->name])) {
  508. $storage = $states[$feature->name][$component];
  509. }
  510. else if (array_key_exists($component, $conflicts)) {
  511. $storage = FEATURES_CONFLICT;
  512. }
  513. $form['components'][$component] = array(
  514. '#markup' => theme('features_storage_link', array('storage' => $storage, 'path' => $path)),
  515. );
  516. }
  517. if ($review || $revert) {
  518. $form['buttons'] = array('#theme' => 'features_form_buttons', '#tree' => TRUE);
  519. if ($revert || $review) {
  520. $form['buttons']['revert'] = array(
  521. '#type' => 'submit',
  522. '#value' => t('Revert components'),
  523. '#submit' => array('features_admin_components_revert'),
  524. );
  525. }
  526. if ($review) {
  527. $form['buttons']['review'] = array(
  528. '#type' => 'submit',
  529. '#value' => t('Mark as reviewed'),
  530. '#submit' => array('features_admin_components_review'),
  531. );
  532. }
  533. }
  534. return $form;
  535. }
  536. /**
  537. * Submit handler for revert form.
  538. */
  539. function features_admin_components_revert(&$form, &$form_state) {
  540. module_load_include('inc', 'features', 'features.export');
  541. features_include();
  542. $module = $form_state['values']['module'];
  543. $revert = array();
  544. foreach (array_filter($form_state['values']['revert']) as $component => $status) {
  545. $revert[$module][] = $component;
  546. drupal_set_message(t('Reverted all <strong>!component</strong> components for <strong>!module</strong>.', array('!component' => $component, '!module' => $module)));
  547. }
  548. features_revert($revert);
  549. $form_state['redirect'] = 'admin/structure/features/' . $module;
  550. }
  551. /**
  552. * Submit handler for revert form.
  553. */
  554. function features_admin_components_review(&$form, &$form_state) {
  555. module_load_include('inc', 'features', 'features.export');
  556. features_include();
  557. $module = $form_state['values']['module'];
  558. $revert = array();
  559. foreach (array_filter($form_state['values']['revert']) as $component => $status) {
  560. features_set_signature($module, $component);
  561. drupal_set_message(t('All <strong>!component</strong> components for <strong>!module</strong> reviewed.', array('!component' => $component, '!module' => $module)));
  562. }
  563. $form_state['redirect'] = 'admin/structure/features/' . $module;
  564. }
  565. /**
  566. * Validate handler for the 'manage features' form.
  567. */
  568. function features_form_validate(&$form, &$form_state) {
  569. include_once './includes/install.inc';
  570. $conflicts = features_get_conflicts();
  571. foreach ($form_state['values']['status'] as $module => $status) {
  572. if ($status) {
  573. if (!empty($conflicts[$module])) {
  574. foreach (array_keys($conflicts[$module]) as $conflict) {
  575. if (!empty($form_state['values']['status'][$conflict])) {
  576. form_set_error('status', t('The feature !module cannot be enabled because it conflicts with !conflict.', array('!module' => $module, '!conflict' => $conflict)));
  577. }
  578. }
  579. }
  580. if (!drupal_check_module($module)) {
  581. form_set_error('status', t('The feature !module cannot be enabled because it has unmet requirements.', array('!module' => $module, '!conflict' => $conflict)));
  582. }
  583. }
  584. }
  585. }
  586. /**
  587. * Submit handler for the 'manage features' form
  588. */
  589. function features_form_submit(&$form, &$form_state) {
  590. // Clear drupal caches after enabling a feature. We do this in a separate
  591. // page callback rather than as part of the submit handler as some modules
  592. // have includes/other directives of importance in hooks that have already
  593. // been called in this page load.
  594. $form_state['redirect'] = 'admin/structure/features/cleanup/clear';
  595. $features = $form['#features'];
  596. if (!empty($features)) {
  597. $status = $form_state['values']['status'];
  598. $install = array_keys(array_filter($status));
  599. $disable = array_diff(array_keys($status), $install);
  600. // Disable first. If there are any features that are disabled that are
  601. // dependencies of features that have been queued for install, they will
  602. // be re-enabled.
  603. module_disable($disable);
  604. features_install_modules($install);
  605. }
  606. }
  607. /**
  608. * Form for disabling orphaned dependencies.
  609. */
  610. function features_cleanup_form($form, $form_state, $cache_clear = FALSE) {
  611. $form = array();
  612. // Clear caches if we're getting a post-submit redirect that requests it.
  613. if ($cache_clear) {
  614. drupal_flush_all_caches();
  615. // The following functions need to be run because drupal_flush_all_caches()
  616. // runs rebuilds in the wrong order. The node type cache is rebuilt *after*
  617. // the menu is rebuilt, meaning that the menu tree is stale in certain
  618. // circumstances after drupal_flush_all_caches(). We rebuild again.
  619. menu_rebuild();
  620. }
  621. // Retrieve orphaned modules and provide them as optional modules to be disabled.
  622. // Exclude any modules that have been added to the 'ignored' list.
  623. $options = array();
  624. $orphans = features_get_orphans();
  625. $ignored = variable_get('features_ignored_orphans', array());
  626. if (!empty($orphans)) {
  627. foreach ($orphans as $module) {
  628. if (!in_array($module->name, $ignored, TRUE)) {
  629. $options[$module->name] = check_plain($module->info['name']);
  630. }
  631. }
  632. }
  633. if (!empty($options)) {
  634. $form['orphans'] = array(
  635. '#title' => t('Orphaned dependencies'),
  636. '#description' => t('These modules are dependencies of features that have been disabled. They may be disabled without affecting other components of your website.'),
  637. '#type' => 'checkboxes',
  638. '#options' => $options,
  639. '#default_value' => array_keys($options),
  640. );
  641. $form['buttons'] = array('#tree' => TRUE, '#theme' => 'features_form_buttons');
  642. $form['buttons']['disable'] = array(
  643. '#type' => 'submit',
  644. '#value' => t('Disable selected modules'),
  645. '#submit' => array('features_cleanup_form_disable'),
  646. );
  647. $form['buttons']['ignore'] = array(
  648. '#type' => 'submit',
  649. '#value' => t('Leave enabled'),
  650. '#submit' => array('features_cleanup_form_ignore'),
  651. );
  652. }
  653. else {
  654. drupal_goto('admin/structure/features');
  655. }
  656. return $form;
  657. }
  658. /**
  659. * Submit handler for disable action on features_cleanup_form().
  660. */
  661. function features_cleanup_form_disable(&$form, &$form_state) {
  662. if (!empty($form_state['values']['orphans'])) {
  663. $disable = array_keys(array_filter($form_state['values']['orphans']));
  664. $ignored = array_diff(array_keys($form_state['values']['orphans']), $disable);
  665. // Disable any orphans that have been selected.
  666. module_disable($disable);
  667. drupal_flush_all_caches();
  668. // Add enabled modules to ignored orphans list.
  669. $ignored_orphans = variable_get('features_ignored_orphans', array());
  670. foreach ($ignored as $module) {
  671. $ignored_orphans[$module] = $module;
  672. }
  673. variable_set('features_ignored_orphans', $ignored_orphans);
  674. }
  675. $form_state['redirect'] = 'admin/structure/features/cleanup';
  676. }
  677. /**
  678. * Submit handler for ignore action on features_cleanup_form().
  679. */
  680. function features_cleanup_form_ignore(&$form, &$form_state) {
  681. if (!empty($form_state['values']['orphans'])) {
  682. $ignored = array_keys($form_state['values']['orphans']);
  683. $ignored_orphans = variable_get('features_ignored_orphans', array());
  684. foreach ($ignored as $module) {
  685. $ignored_orphans[$module] = $module;
  686. }
  687. variable_set('features_ignored_orphans', $ignored_orphans);
  688. }
  689. $form_state['redirect'] = 'admin/structure/features/cleanup';
  690. }
  691. /**
  692. * Page callback to display the differences between what's in code and
  693. * what is in the db.
  694. *
  695. * @param $feature
  696. * A loaded feature object to display differences for.
  697. * @param $component
  698. * Optional: specific component to display differences for. If excluded, all components are used.
  699. *
  700. * @return Themed display of what is different.
  701. */
  702. function features_feature_diff($feature, $component = NULL) {
  703. drupal_add_css(drupal_get_path('module', 'features') . '/features.css');
  704. module_load_include('inc', 'features', 'features.export');
  705. $overrides = features_detect_overrides($feature);
  706. $output = '';
  707. if (!empty($overrides)) {
  708. // Filter overrides down to specified component.
  709. if (isset($component) && isset($overrides[$component])) {
  710. $overrides = array($component => $overrides[$component]);
  711. }
  712. module_load_include('inc', 'diff', 'diff.engine');
  713. $formatter = new DrupalDiffFormatter();
  714. $rows = array();
  715. foreach ($overrides as $component => $items) {
  716. $rows[] = array(array('data' => $component, 'colspan' => 4, 'header' => TRUE));
  717. $diff = new Diff(explode("\n", $items['default']), explode("\n", $items['normal']));
  718. $rows = array_merge($rows, $formatter->format($diff));
  719. }
  720. $header = array(
  721. array('data' => t('Default'), 'colspan' => 2),
  722. array('data' => t('Overrides'), 'colspan' => 2),
  723. );
  724. $output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('class' => array('diff', 'features-diff'))));
  725. }
  726. else {
  727. $output = "<div class='features-empty'>" . t('No changes have been made to this feature.') . "</div>";
  728. }
  729. $output = array('page' => array('#markup' => "<div class='features-comparison'>{$output}</div>"));
  730. return $output;
  731. }
  732. /**
  733. * Compare the component names. Used to sort alphabetically.
  734. */
  735. function features_compare_component_name($a, $b) {
  736. return strcasecmp($a['name'], $b['name']);
  737. }
  738. /**
  739. * Javascript call back that returns the status of a feature.
  740. */
  741. function features_feature_status($feature) {
  742. module_load_include('inc', 'features', 'features.export');
  743. return drupal_json_output(array('storage' => features_get_storage($feature->name)));
  744. }
  745. /**
  746. * Make a Drupal options array safe for usage with jQuery DOM selectors.
  747. * Encodes known bad characters into __[ordinal]__ so that they may be
  748. * safely referenced by JS behaviors.
  749. */
  750. function features_dom_encode_options($options = array(), $keys_only = TRUE) {
  751. $replacements = features_dom_encode_map();
  752. $encoded = array();
  753. foreach ($options as $key => $value) {
  754. $encoded[strtr($key, $replacements)] = $keys_only ? $value : strtr($value, $replacements);
  755. }
  756. return $encoded;
  757. }
  758. /**
  759. * Decode an array of option values that have been encoded by
  760. * features_dom_encode_options().
  761. */
  762. function features_dom_decode_options($options, $keys_only = FALSE) {
  763. $replacements = array_flip(features_dom_encode_map());
  764. $encoded = array();
  765. foreach ($options as $key => $value) {
  766. $encoded[strtr($key, $replacements)] = $keys_only ? $value : strtr($value, $replacements);
  767. }
  768. return $encoded;
  769. }
  770. /**
  771. * Returns encoding map for decode and encode options.
  772. */
  773. function features_dom_encode_map() {
  774. return array(
  775. ':' => '__' . ord(':') . '__',
  776. '/' => '__' . ord('/') . '__',
  777. ',' => '__' . ord(',') . '__',
  778. '.' => '__' . ord('.') . '__',
  779. '<' => '__' . ord('<') . '__',
  780. '>' => '__' . ord('>') . '__',
  781. );
  782. }
  783. /**
  784. * Page callback: Autocomplete field for features package.
  785. *
  786. * @param $search_string
  787. * The char or string that user have written in autocomplete field,
  788. * this is the string this function uses for filter.
  789. *
  790. * @see features_menu()
  791. */
  792. function features_autocomplete_packages($search_string) {
  793. $matched_packages = array();
  794. //fetch all modules that are features and copy the package name into a new array.
  795. foreach (features_get_features(NULL, TRUE) as $value) {
  796. if(preg_match('/' . $search_string . '/', $value->info['package'])) {
  797. $matched_packages[$value->info['package']] = $value->info['package'];
  798. }
  799. }
  800. //removes duplicated package, we wont a list of all unique packages.
  801. $matched_packages = array_unique($matched_packages);
  802. drupal_json_output($matched_packages);
  803. }