PageRenderTime 71ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/sites/all/modules/contrib/conditional_fields/conditional_fields.module

https://bitbucket.org/antisocnet/drupal
Unknown | 1993 lines | 1760 code | 233 blank | 0 comment | 0 complexity | aa76a8d242b83c6a6dc0deb1b9ed14e4 MD5 | raw file
Possible License(s): GPL-2.0, AGPL-1.0, LGPL-2.1
  1. <?php
  2. /**
  3. * @file
  4. * Define dependencies between fields based on their states and values.
  5. *
  6. * Conditional Fields for Drupal 7 is basically an user interface for the States
  7. * API, plus the ability to hide fields on certain conditions when viewing
  8. * content.
  9. */
  10. /**
  11. * Dependency is triggered if the dependee has a certain value.
  12. */
  13. define('CONDITIONAL_FIELDS_DEPENDENCY_VALUES_WIDGET', 1);
  14. /**
  15. * Dependency is triggered if the dependee has all values.
  16. */
  17. define('CONDITIONAL_FIELDS_DEPENDENCY_VALUES_AND', 2);
  18. /**
  19. * Dependency is triggered if the dependee has any of the values.
  20. */
  21. define('CONDITIONAL_FIELDS_DEPENDENCY_VALUES_OR', 3);
  22. /**
  23. * Dependency is triggered if the dependee has only one of the values.
  24. */
  25. define('CONDITIONAL_FIELDS_DEPENDENCY_VALUES_XOR', 4);
  26. /**
  27. * Dependency is triggered if the dependee does not have any of the values.
  28. */
  29. define('CONDITIONAL_FIELDS_DEPENDENCY_VALUES_NOT', 5);
  30. /**
  31. * Dependency is triggered if the dependee values match a regular expression.
  32. */
  33. define('CONDITIONAL_FIELDS_DEPENDENCY_VALUES_REGEX', 6);
  34. /**
  35. * Field view setting. Dependent is shown only if the dependency is triggered.
  36. */
  37. define('CONDITIONAL_FIELDS_FIELD_VIEW_EVALUATE', 1);
  38. /**
  39. * Field view setting. Dependent is shown only if the dependee is shown as well.
  40. */
  41. define('CONDITIONAL_FIELDS_FIELD_VIEW_HIDE_ORPHAN', 2);
  42. /**
  43. * Field view setting. Dependent is highlighted if the dependency is not
  44. * triggered.
  45. */
  46. define('CONDITIONAL_FIELDS_FIELD_VIEW_HIGHLIGHT', 3);
  47. /**
  48. * Field view setting. Dependent has a textual description of the dependency.
  49. */
  50. define('CONDITIONAL_FIELDS_FIELD_VIEW_DESCRIBE', 4);
  51. /**
  52. * Field view setting. Dependent is shown only if the dependee is shown as well
  53. * and the dependency evaluates to TRUE.
  54. */
  55. define('CONDITIONAL_FIELDS_FIELD_VIEW_HIDE_UNTRIGGERED_ORPHAN', 5);
  56. /**
  57. * Field edit setting. Dependent is shown only if the dependee is shown as well.
  58. */
  59. define('CONDITIONAL_FIELDS_FIELD_EDIT_HIDE_ORPHAN', 1);
  60. /**
  61. * Field edit setting. Dependent is shown only if the dependee is shown as well
  62. * and the dependency evaluates to TRUE.
  63. */
  64. define('CONDITIONAL_FIELDS_FIELD_EDIT_HIDE_UNTRIGGERED_ORPHAN', 2);
  65. /**
  66. * Field edit setting. Dependent is reset to its default values if the
  67. * dependency was not triggered when the form is submitted.
  68. */
  69. define('CONDITIONAL_FIELDS_FIELD_EDIT_RESET_UNTRIGGERED', 3);
  70. /**
  71. * Implements hook_permission().
  72. */
  73. function conditional_fields_permission() {
  74. return array(
  75. 'administer dependencies' => array(
  76. 'title' => t('Administer dependencies'),
  77. 'description' => t('View, edit and delete field dependencies.'),
  78. ),
  79. );
  80. }
  81. /**
  82. * Implements hook_menu().
  83. */
  84. function conditional_fields_menu() {
  85. $items = array();
  86. // Ensure the following is not executed until field_bundles is working and
  87. // tables are updated. Needed to avoid errors on initial installation.
  88. if (defined('MAINTENANCE_MODE')) {
  89. return $items;
  90. }
  91. $items['admin/structure/dependencies'] = array(
  92. 'title' => 'Field dependencies',
  93. 'description' => 'Administer field dependencies for the site.',
  94. 'page callback' => 'conditional_fields_dependencies_overview_page',
  95. 'access arguments' => array('administer dependencies'),
  96. 'file' => 'includes/conditional_fields.admin.inc',
  97. );
  98. $items['admin/structure/dependencies/overview'] = array(
  99. 'title' => 'Overview',
  100. 'type' => MENU_DEFAULT_LOCAL_TASK,
  101. 'weight' => 1,
  102. );
  103. $items['admin/structure/dependencies/edit/%conditional_fields_dependency'] = array(
  104. 'title' => 'Edit dependency',
  105. 'page callback' => 'drupal_get_form',
  106. 'page arguments' => array('conditional_fields_dependency_edit_form', 4),
  107. 'access arguments' => array('administer dependencies'),
  108. 'file' => 'includes/conditional_fields.admin.inc',
  109. );
  110. $items['admin/structure/dependencies/delete/%conditional_fields_dependency'] = array(
  111. 'title' => 'Delete dependency',
  112. 'page callback' => 'drupal_get_form',
  113. 'page arguments' => array('conditional_fields_dependency_delete_form', 4),
  114. 'access arguments' => array('administer dependencies'),
  115. 'file' => 'includes/conditional_fields.admin.inc',
  116. );
  117. // Some of the following code is copied from field_ui_menu().
  118. // Create tabs for all possible bundles.
  119. foreach (entity_get_info() as $entity_type => $entity_info) {
  120. if ($entity_info['fieldable']) {
  121. $items["admin/structure/dependencies/$entity_type"] = array(
  122. 'title' => $entity_info['label'],
  123. 'page arguments' => array(NULL, 3),
  124. 'access arguments' => array('administer dependencies'),
  125. 'type' => MENU_LOCAL_TASK,
  126. 'weight' => 2,
  127. );
  128. foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) {
  129. if (module_exists('field_ui') && isset($bundle_info['admin'])) {
  130. // Extract path information from the bundle and replace any "magic"
  131. // wildcard with a normal one.
  132. $path = preg_replace('/(%[a-z0-9_]*)/', '%', $bundle_info['admin']['path']);
  133. if (isset($bundle_info['admin']['bundle argument'])) {
  134. $bundle_pos = $bundle_info['admin']['bundle argument'];
  135. }
  136. else {
  137. $bundle_pos = $bundle_name;
  138. }
  139. $items["$path/dependencies"] = array(
  140. 'title' => $entity_type == 'comment' ? 'Comment dependencies' : 'Manage dependencies',
  141. 'page callback' => 'conditional_fields_dependencies_overview_page',
  142. 'page arguments' => array($bundle_pos, $entity_type),
  143. 'type' => MENU_LOCAL_TASK,
  144. 'weight' => $entity_type == 'comment' ? 4 : 2,
  145. 'file' => 'includes/conditional_fields.admin.inc',
  146. 'access arguments' => array('administer dependencies'),
  147. );
  148. }
  149. }
  150. }
  151. }
  152. return $items;
  153. }
  154. /**
  155. * Implements hook_forms().
  156. *
  157. * Maps all dependency add forms to the same callback.
  158. */
  159. function conditional_fields_forms() {
  160. foreach (entity_get_info() as $entity_type => $entity_info) {
  161. if ($entity_info['fieldable']) {
  162. foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) {
  163. $forms['conditional_fields_dependency_add_form_' . $entity_type . '_' . $bundle_name] = array(
  164. 'callback' => 'conditional_fields_dependency_add_form',
  165. 'callback arguments' => array($entity_type, $bundle_name),
  166. );
  167. }
  168. }
  169. }
  170. return $forms;
  171. }
  172. /**
  173. * Implements hook_js_alter().
  174. *
  175. * Overrides core states API with a patched version that allows multiple
  176. * conditions and OR/XOR logic.
  177. */
  178. function conditional_fields_js_alter(&$javascript) {
  179. // Since Drupal 7.14, states.js includes the OR/XOR patch.
  180. if (isset($javascript['misc/states.js']) && version_compare(VERSION, '7.14', '<')) {
  181. $javascript['misc/states.js']['data'] = drupal_get_path('module', 'conditional_fields') . '/js/states.js';
  182. }
  183. }
  184. /**
  185. * Implements hook_element_info_alter().
  186. * Adds an #after_build function to all form elements.
  187. */
  188. function conditional_fields_element_info_alter(&$types) {
  189. foreach ($types as $type => $info) {
  190. $types[$type]['#after_build'][] = 'conditional_fields_element_after_build';
  191. }
  192. }
  193. /**
  194. * Processes form elements with dependencies.
  195. *
  196. * Just adds a #conditional_fields property to the form with the needed
  197. * data, which is used later in conditional_fields_form_after_build():
  198. * - The fields #parents property.
  199. * - Field dependencies data.
  200. */
  201. function conditional_fields_element_after_build($element, &$form_state) {
  202. // Ensure that the element is a field.
  203. if (isset($element['#field_name'])) {
  204. $field = $element;
  205. }
  206. elseif (isset($element['#language'], $element[$element['#language']], $element[$element['#language']]['#field_name'])) {
  207. // Some fields are wrapped in containers before processing.
  208. $field = $element[$element['#language']];
  209. }
  210. else {
  211. return $element;
  212. }
  213. $form = &$form_state['complete form'];
  214. // Avoid processing fields in fields_ui administration pages.
  215. if (drupal_substr($form['#form_id'], 0, 9) == 'field_ui_') {
  216. return $element;
  217. }
  218. // Some fields do not have entity type and bundle properties. In this case we
  219. // try to use the properties from the form. This is not an optimal solution,
  220. // since in case of fields in entities within entities they might not correspond,
  221. // and their dependencies will not be loaded.
  222. if (isset($field['#entity_type'], $field['#bundle'])) {
  223. $entity_type = $field['#entity_type'];
  224. $bundle = $field['#bundle'];
  225. }
  226. elseif (isset($form['#entity_type'], $form['#bundle'])) {
  227. $entity_type = $form['#entity_type'];
  228. $bundle = $form['#bundle'];
  229. }
  230. else {
  231. return $element;
  232. }
  233. $dependencies = conditional_fields_load_dependencies($entity_type, $bundle);
  234. if (!$dependencies) {
  235. return $element;
  236. }
  237. // Attach dependent.
  238. if (isset($dependencies['dependents'][$field['#field_name']])) {
  239. foreach ($dependencies['dependents'][$field['#field_name']] as $id => $dependency) {
  240. if (!isset($form['#conditional_fields'][$field['#field_name']]['dependees'][$id])) {
  241. conditional_fields_attach_dependency($form, array('#field_name' => $dependency['dependee']), $field, $dependency['options'], $id);
  242. }
  243. }
  244. }
  245. // Attach dependee.
  246. // TODO: collect information about every element of the dependee widget, not
  247. // just the first encountered. This bottom-up approach would allow us to
  248. // define per-element sets of dependency values.
  249. if (isset($dependencies['dependees'][$field['#field_name']])) {
  250. foreach ($dependencies['dependees'][$field['#field_name']] as $id => $dependency) {
  251. if (!isset($form['#conditional_fields'][$field['#field_name']]['dependents'][$id])) {
  252. conditional_fields_attach_dependency($form, $field, array('#field_name' => $dependency['dependent']), $dependency['options'], $id);
  253. }
  254. }
  255. }
  256. return $element;
  257. }
  258. /**
  259. * Attaches a single dependency to a form.
  260. *
  261. * Call this function when defining or altering a form to create dependencies
  262. * dynamically.
  263. *
  264. * @param $form
  265. * The form where the dependency is attached.
  266. * @param $dependee
  267. * The dependee field form element. Either a string identifying the element
  268. * key in the form, or a fully built field array. Actually used properties of
  269. * the array are #field_name and #parents.
  270. * @param $dependent
  271. * The dependent field form element. Either a string identifying the element
  272. * key in the form, or a fully built field array. Actually used properties of
  273. * the array are #field_name and #field_parents.
  274. * @param $options
  275. * An array of dependency options with the following key/value pairs:
  276. * - state: The state applied to the dependent when the dependency is
  277. * triggered. See conditional_fields_states() for available states.
  278. * - condition: The condition for the dependency to be triggered. See
  279. * conditional_fields_conditions() for available conditions.
  280. * - values_set: One of the following constants:
  281. * - CONDITIONAL_FIELDS_DEPENDENCY_VALUES_WIDGET: Dependency is
  282. * triggered if the dependee has a certain value defined in 'value'.
  283. * - CONDITIONAL_FIELDS_DEPENDENCY_VALUES_AND: Dependency is triggered if
  284. * the dependee has all the values defined in 'values'.
  285. * - CONDITIONAL_FIELDS_DEPENDENCY_VALUES_OR: Dependency is triggered if the
  286. * dependee has any of the values defined in 'values'.
  287. * - CONDITIONAL_FIELDS_DEPENDENCY_VALUES_XOR: Dependency is triggered if
  288. * the dependee has only one of the values defined in 'values'.
  289. * - CONDITIONAL_FIELDS_DEPENDENCY_VALUES_NOT: Dependency is triggered if
  290. * the dependee does not have any of the values defined in 'values'.
  291. * - value: The value to be tested when 'values_set' is
  292. * CONDITIONAL_FIELDS_DEPENDENCY_VALUES_WIDGET. An associative array with
  293. * the same structure of the dependee field values as found in
  294. * $form_states['values] when the form is submitted. You can use
  295. * field_default_extract_form_values() to extract this array.
  296. * - values: The array of values to be tested when 'values_set' is not
  297. * CONDITIONAL_FIELDS_DEPENDENCY_VALUES_WIDGET.
  298. * - value_form: An associative array with the same structure of the dependee
  299. * field values as found in $form_state['input']['value']['field'] when the
  300. * form is submitted.
  301. * - effect: The jQuery effect associated to the state change. See
  302. * conditional_fields_effects() for available effects and options.
  303. * - effect_options: The options for the active effect.
  304. * - element_view: An associative array of field view behaviors with
  305. * CONDITIONAL_FIELDS_FIELD_VIEW_* constants as keys and the same constants
  306. * as values for enabled behaviors and 0 for disabled behaviors.
  307. * See conditional_fields_behaviors() for descriptions.
  308. * - element_view_per_role: Set to 1 to activate field view settings per role.
  309. * - element_view_roles: An associative array of field view settings per role
  310. * where the keys are role ids and the values are arrays with the same
  311. * structure of 'element_view'.
  312. * - element_edit: An associative array of field edit behaviors with
  313. * CONDITIONAL_FIELDS_FIELD_EDIT_* constants as keys and the same constants
  314. * as values for enabled behaviors and 0 for disabled behaviors.
  315. * See conditional_fields_behaviors() for descriptions.
  316. * - element_edit_per_role: Set to 1 to activate field edit settings per role.
  317. * - element_edit_roles: An associative array of field edit settings per role
  318. * where the keys are role ids and the values are arrays with the same
  319. * structure of 'element_edit'.
  320. * - selector: (optional) Custom jQuery selector for the dependee.
  321. * @param $id
  322. * (internal use) The identifier for the dependency. Omit this parameter when
  323. * attaching a custom dependency.
  324. *
  325. * Note that you don't need to manually set all these options, since default
  326. * settings are always provided.
  327. */
  328. function conditional_fields_attach_dependency(&$form, $dependee, $dependent, $options, $id = 0) {
  329. $options += conditional_fields_dependency_default_options();
  330. // The absence of the $id parameter identifies a custom dependency.
  331. if (!$id) {
  332. // String values are accepted to simplify usage of this function with custom
  333. // forms.
  334. if (is_string($dependee) && is_string($dependent)) {
  335. $dependee = array(
  336. '#field_name' => $dependee,
  337. '#parents' => array($dependee),
  338. );
  339. $dependent = array(
  340. '#field_name' => $dependent,
  341. '#field_parents' => array($dependent),
  342. );
  343. // Custom dependencies have automatically assigned a progressive id.
  344. static $current_id;
  345. if (!$current_id) {
  346. $current_id = 1;
  347. }
  348. $id = $current_id;
  349. $current_id++;
  350. }
  351. }
  352. // Attach dependee.
  353. // Use the #parents property of the dependee instead of #field_parents since
  354. // we will need access to the full structure of the widget.
  355. if (isset($dependee['#parents'])) {
  356. $form['#conditional_fields'][$dependee['#field_name']]['parents'] = $dependee['#parents'];
  357. $form['#conditional_fields'][$dependee['#field_name']]['dependents'][$id] = array(
  358. 'dependent' => $dependent['#field_name'],
  359. 'options' => $options,
  360. );
  361. }
  362. // Attach dependent.
  363. if (isset($dependent['#field_parents'])) {
  364. $dependent_parents = $dependent['#field_parents'];
  365. }
  366. elseif (isset($dependent['#parents'])) {
  367. $dependent_parents = $dependent['#parents'];
  368. }
  369. if (isset($dependent_parents)) {
  370. $form['#conditional_fields'][$dependent['#field_name']]['field_parents'] = $dependent_parents;
  371. $form['#conditional_fields'][$dependent['#field_name']]['dependees'][$id] = array(
  372. 'dependee' => $dependee['#field_name'],
  373. 'options' => $options,
  374. );
  375. }
  376. // Actual processing is done in conditional_fields_form_after_build().
  377. // Append the property so the callback runs last.
  378. _conditional_fields_element_add_property($form, '#after_build', 'conditional_fields_form_after_build', 'append');
  379. }
  380. /**
  381. * after_build callback for forms with dependencies.
  382. *
  383. * Builds and attaches #states properties to dependent fields, adds additional
  384. * visual effects handling to the States API and attaches a validation callback
  385. * to the form that handles validation of dependent fields.
  386. */
  387. function conditional_fields_form_after_build($form, &$form_state) {
  388. // Dependencies data is attached in conditional_fields_element_after_build().
  389. if (empty($form['#conditional_fields'])) {
  390. return $form;
  391. }
  392. $effects = array();
  393. $state_handlers = conditional_fields_states_handlers();
  394. // Cycle all dependents.
  395. foreach ($form['#conditional_fields'] as $dependent => $dependent_info) {
  396. $states = array();
  397. if (empty($dependent_info['dependees'])) {
  398. continue;
  399. }
  400. $dependent_location = array_merge($dependent_info['field_parents'], array($dependent));
  401. $dependent_form_field = drupal_array_get_nested_value($form, $dependent_location);
  402. // Cycle the dependant's dependees.
  403. foreach ($dependent_info['dependees'] as $dependency) {
  404. $dependee = $dependency['dependee'];
  405. if (empty($form['#conditional_fields'][$dependee])) {
  406. continue;
  407. }
  408. $dependee_info = $form['#conditional_fields'][$dependee];
  409. $dependee_form_field = drupal_array_get_nested_value($form, $dependee_info['parents']);
  410. $options = $dependency['options'];
  411. // Load field edit behaviors.
  412. // If this dependent has multiple dependees, only the logic of the first
  413. // dependency will be taken into account.
  414. if (!isset($behaviors)) {
  415. $behaviors = conditional_fields_field_behaviors('edit', $options);
  416. }
  417. // Determine if the dependee is in the form.
  418. if (empty($dependee_form_field) || (isset($dependee_form_field['#access']) && $dependee_form_field['#access'] == FALSE)) {
  419. // Apply orphan dependent behaviors.
  420. /*
  421. if (in_array(CONDITIONAL_FIELDS_FIELD_EDIT_HIDE_UNTRIGGERED_ORPHAN, $behaviors)) {
  422. // TODO
  423. $is_triggered = TRUE;
  424. if ($is_orphan && !$is_triggered) {
  425. $form[$dependent]['#access'] = FALSE;
  426. }
  427. }
  428. */
  429. if (in_array(CONDITIONAL_FIELDS_FIELD_EDIT_HIDE_ORPHAN, $behaviors)) {
  430. $dependent_form_field['#access'] = FALSE;
  431. }
  432. unset($behaviors[CONDITIONAL_FIELDS_FIELD_EDIT_HIDE_UNTRIGGERED_ORPHAN]);
  433. unset($behaviors[CONDITIONAL_FIELDS_FIELD_EDIT_HIDE_ORPHAN]);
  434. continue;
  435. }
  436. unset($behaviors[CONDITIONAL_FIELDS_FIELD_EDIT_HIDE_UNTRIGGERED_ORPHAN]);
  437. unset($behaviors[CONDITIONAL_FIELDS_FIELD_EDIT_HIDE_ORPHAN]);
  438. // Build a jQuery selector if it was not overridden by a custom value.
  439. // Note that this may be overridden later by a state handler.
  440. if (!$options['selector']) {
  441. $options['selector'] = conditional_fields_field_selector($dependee_form_field);
  442. }
  443. else {
  444. // Replace the language placeholder in the selector with current language.
  445. $options['selector'] = str_replace('%lang', $dependee_form_field['#language'], $options['selector']);
  446. }
  447. if ($options['condition'] != 'value') {
  448. // Conditions different than "value" are always evaluated against TRUE.
  449. $state = array($options['state'] => array($options['selector'] => array($options['condition'] => TRUE)));
  450. }
  451. else {
  452. // Build the values that trigger the dependency.
  453. $values = array();
  454. if ($options['values_set'] == CONDITIONAL_FIELDS_DEPENDENCY_VALUES_WIDGET) {
  455. $values[$options['condition']] = $options['value_form'];
  456. }
  457. elseif ($options['values_set'] == CONDITIONAL_FIELDS_DEPENDENCY_VALUES_REGEX) {
  458. $values[$options['condition']] = $options['value'];
  459. }
  460. elseif ($options['values_set'] == CONDITIONAL_FIELDS_DEPENDENCY_VALUES_AND) {
  461. $values[$options['condition']] = count($options['values']) == 1 ? $options['values'][0] : $options['values'];
  462. }
  463. else {
  464. if ($options['values_set'] == CONDITIONAL_FIELDS_DEPENDENCY_VALUES_XOR) {
  465. // XOR behaves like OR with added 'xor' element.
  466. $values[] = 'xor';
  467. }
  468. elseif ($options['values_set'] == CONDITIONAL_FIELDS_DEPENDENCY_VALUES_NOT) {
  469. // NOT behaves like OR with switched state.
  470. $options['state'] = strpos($options['state'], '!') === 0 ? drupal_substr($options['state'], 1) : '!' . $options['state'];
  471. }
  472. // OR, NOT and XOR conditions are obtained with a nested array.
  473. foreach ($options['values'] as $value) {
  474. $values[] = array($options['condition'] => $value);
  475. }
  476. }
  477. $state = array($options['state'] => array($options['selector'] => $values));
  478. $dependee_form_state = isset($dependee_form_field['#field_parents'], $dependee_form_field['#field_name'], $dependee_form_field['#language']) ? field_form_get_state($dependee_form_field['#field_parents'], $dependee_form_field['#field_name'], $dependee_form_field['#language'], $form_state) : NULL;
  479. // Execute special handler for fields that need further processing.
  480. // The handler has no return value. Modify the $state parameter by
  481. // reference if needed.
  482. foreach ($state_handlers as $handler => $handler_conditions) {
  483. if (array_intersect_assoc($handler_conditions, $dependee_form_field) == $handler_conditions) {
  484. $handler($dependee_form_field, $dependee_form_state, $options, $state);
  485. }
  486. }
  487. // Add validation callback to element.
  488. _conditional_fields_element_add_property($dependent_form_field, '#element_validate', 'conditional_fields_dependent_validate', 'append');
  489. }
  490. // Add the $state into the correct logic group in $states.
  491. foreach ($state as $key => $constraints) {
  492. if (empty($states[$key][$options['grouping']])) {
  493. $states[$key][$options['grouping']] = $constraints;
  494. }
  495. else {
  496. $states[$key][$options['grouping']] = array_merge($states[$key][$options['grouping']], $constraints);
  497. }
  498. }
  499. // Build effect settings for effects with options.
  500. // TODO: add dependee key to allow different effects on the same selector.
  501. if ($options['effect'] && $options['effect'] != 'show') {
  502. $selector = conditional_fields_field_selector(drupal_array_get_nested_value($form, array($dependent_location[0])));
  503. // Convert numeric strings to numbers.
  504. foreach ($options['effect_options'] as &$effect_option) {
  505. if (is_numeric($effect_option)) {
  506. $effect_option += 0;
  507. }
  508. }
  509. $effects[$selector] = array(
  510. 'effect' => $options['effect'],
  511. 'options' => $options['effect_options'],
  512. );
  513. }
  514. // Apply reset dependent to default if untriggered behavior.
  515. if (in_array(CONDITIONAL_FIELDS_FIELD_EDIT_RESET_UNTRIGGERED, $behaviors)) {
  516. // Add property to element so conditional_fields_dependent_validate() can
  517. // pick it up.
  518. $dependent_form_field['#conditional_fields_reset_if_untriggered'] = TRUE;
  519. unset($behaviors[CONDITIONAL_FIELDS_FIELD_EDIT_RESET_UNTRIGGERED]);
  520. }
  521. }
  522. // Execute custom behaviors.
  523. if (!empty($behaviors)) {
  524. foreach ($behaviors as $behavior) {
  525. // Custom behaviors are callbacks.
  526. if (function_exists($$behavior)) {
  527. $$behavior('edit', $form, $form_state, $dependent, $dependencies);
  528. }
  529. }
  530. }
  531. unset($behaviors);
  532. if (empty($states)) {
  533. continue;
  534. }
  535. // Save the modified field back into the form.
  536. drupal_array_set_nested_value($form, $dependent_location, $dependent_form_field);
  537. // Map the states based on the conjunctions.
  538. $states_new = array();
  539. foreach ($states as $state_key => $value) {
  540. // As the main object is ANDed together we can add the AND items directly.
  541. if (!empty($states[$state_key]['AND'])) {
  542. $states_new[$state_key] = $states[$state_key]['AND'];
  543. }
  544. // The OR and XOR groups are moved into a sub-array that has numeric keys
  545. // so that we get a JSON array and not an object, as required by the States
  546. // API for OR and XOR groupings.
  547. if (!empty($states[$state_key]['OR'])) {
  548. $or = array();
  549. foreach ($states[$state_key]['OR'] as $constraint_key => $constraint_value) {
  550. $or[] = array($constraint_key => $constraint_value);
  551. }
  552. // '1' as a string so that we get an object (which means logic groups
  553. // are ANDed together).
  554. $states_new[$state_key]['1'] = $or;
  555. }
  556. if (!empty($states[$state_key]['XOR'])) {
  557. $xor = array('xor');
  558. foreach ($states[$state_key]['XOR'] as $constraint_key => $constraint_value) {
  559. $xor[] = array($constraint_key => $constraint_value);
  560. }
  561. // '2' as a string so that we get an object.
  562. $states_new[$state_key]['2'] = $xor;
  563. }
  564. }
  565. $states = $states_new;
  566. // Add the #states property to the dependent field.
  567. drupal_array_set_nested_value($form, array_merge($dependent_location, array('#states')), $states);
  568. $has_states = TRUE;
  569. }
  570. if (empty($has_states)) {
  571. return $form;
  572. }
  573. $form['#attached']['js'][] = drupal_get_path('module', 'conditional_fields') . '/js/conditional_fields.js';
  574. // Add effect settings to the form.
  575. if ($effects) {
  576. $form['#attached']['js'][] = array(
  577. 'data' => array(
  578. 'conditionalFields' => array(
  579. 'effects' => $effects,
  580. ),
  581. ),
  582. 'type' => 'setting',
  583. );
  584. }
  585. // Validation callback to manage dependent fields validation.
  586. $form['#validate'][] = 'conditional_fields_form_validate';
  587. // Initialize validation information every time the form is rendered to avoid
  588. // stale data after a failed submission.
  589. $form_state['conditional_fields_untriggered_dependents'] = array();
  590. return $form;
  591. }
  592. /**
  593. * Dependent field validation callback.
  594. *
  595. * If the dependencies of a dependent field are not triggered, the validation
  596. * errors that it might have thrown must be removed, together with its submitted
  597. * values. This will simulate the field not being present in the form at all.
  598. * In this field-level callback we just collect needed information and store it
  599. * in $form_state. Values and errors will be removed in a single sweep in
  600. * conditional_fields_form_validate(), which runs at the end of the validation
  601. * cycle.
  602. *
  603. * @see conditional_fields_form_validate()
  604. */
  605. function conditional_fields_dependent_validate($element, &$form_state, $form) {
  606. $dependent = $element[$element['#language']];
  607. // Check if this field's dependencies were triggered.
  608. if (conditional_fields_evaluate_dependencies($dependent, $form, $form_state)) {
  609. return;
  610. }
  611. // Mark submitted values for removal. We have to remove them after all fields
  612. // have been validated to avoid collision between dependencies.
  613. $form_state_addition['parents'] = $dependent['#array_parents'];
  614. // Optional behavior: reset the field to its default values.
  615. // Default values are always valid, so it's safe to skip validation.
  616. if (!empty($element['#conditional_fields_reset_if_untriggered'])) {
  617. $form_state_addition['reset'] = TRUE;
  618. }
  619. // Tag validation errors previously set on this field for removal in
  620. // conditional_fields_form_validate().
  621. $errors = form_get_errors();
  622. if ($errors) {
  623. $error_key = implode('][', $dependent['#parents']);
  624. foreach ($errors as $name => $error) {
  625. // An error triggered by this field might have been set on a descendant
  626. // element. This also means that so there can be multiple errors on the
  627. // same field (even though Drupal doesn't support multiple errors on the
  628. // same element).
  629. if (strpos($name, $error_key) === 0) {
  630. $field_errors[$name] = $error;
  631. }
  632. }
  633. }
  634. if (!empty($field_errors)) {
  635. $form_state_addition['errors'] = $field_errors;
  636. }
  637. $form_state['conditional_fields_untriggered_dependents'][] = $form_state_addition;
  638. }
  639. /**
  640. * Extracts submitted field values during form validation.
  641. *
  642. * @return
  643. * The requested field values parent. Actual field vales are stored under the
  644. * key $element['#field_name'].
  645. */
  646. function conditional_fields_form_field_get_values($element, $form_state) {
  647. // Fall back to #parents to support custom dependencies.
  648. $parents = isset($element['#field_parents']) ? $element['#field_parents'] : $element['#parents'];
  649. return drupal_array_get_nested_value($form_state['values'], $parents);
  650. }
  651. /**
  652. * Validation callback for any form with conditional fields.
  653. *
  654. * This validation callback is added to all forms that contain fields with
  655. * dependencies. It removes all validation errors from dependent fields whose
  656. * dependencies are not triggered, which were collected at field-level
  657. * validation in conditional_fields_dependent_validate().
  658. *
  659. * @see conditional_fields_dependent_validate()
  660. */
  661. function conditional_fields_form_validate($form, &$form_state) {
  662. if (empty($form_state['conditional_fields_untriggered_dependents'])) {
  663. return;
  664. }
  665. $untriggered_dependents_errors = array();
  666. foreach ($form_state['conditional_fields_untriggered_dependents'] as $field) {
  667. $dependent = drupal_array_get_nested_value($form, $field['parents']);
  668. $field_values_location = conditional_fields_form_field_get_values($dependent, $form_state);
  669. // If we couldn't find a location for the field's submitted values, let the
  670. // validation errors pass through to avoid security holes.
  671. if (!isset($field_values_location[$dependent['#field_name']])) {
  672. continue;
  673. }
  674. if (empty($field['reset'])) {
  675. unset($field_values_location[$dependent['#field_name']]);
  676. }
  677. else {
  678. $dependent_info = field_form_get_state($dependent['#field_parents'], $dependent['#field_name'], $dependent['#language'], $form_state);
  679. $field_values_location[$dependent['#field_name']][$dependent['#language']] = field_get_default_value($dependent_info['instance']['entity_type'], NULL, $dependent_info['field'], $dependent_info['instance'], $dependent['#language']);
  680. }
  681. // Save the changed array back in place.
  682. // Do not use form_set_value() since it assumes that the values are located at
  683. // $form_state['values'][ ... $element['#parents'] ... ], while the
  684. // documentation of hook_field_widget_form() states that field values are
  685. // $form_state['values'][ ... $element['#field_parents'] ... ].
  686. drupal_array_set_nested_value($form_state['values'], $dependent['#field_parents'], $field_values_location);
  687. if (!empty($field['errors'])) {
  688. $untriggered_dependents_errors = array_merge($untriggered_dependents_errors, $field['errors']);
  689. }
  690. }
  691. if (!empty($untriggered_dependents_errors)) {
  692. // Since Drupal provides no clean way to selectively remove error messages,
  693. // we have to store all current form errors and error messages, clear them,
  694. // filter out from our stored values the errors originating from untriggered
  695. // dependent fields, and then reinstate remaining errors and messages.
  696. $errors = array_diff_assoc((array) form_get_errors(), $untriggered_dependents_errors);
  697. form_clear_error();
  698. $error_messages = drupal_get_messages('error');
  699. $removed_messages = array_values($untriggered_dependents_errors);
  700. // Reinstate remaining errors.
  701. foreach ($errors as $name => $error) {
  702. form_set_error($name, $error);
  703. // form_set_error() calls drupal_set_message(), so we have to filter out
  704. // these from the messages to avoid duplicates.
  705. $removed_messages[] = $error;
  706. }
  707. // Reinstate remaining error messages (which, at this point, are messages that
  708. // were originated outside of the validation process).
  709. foreach (array_diff($error_messages['error'], $removed_messages) as $message) {
  710. drupal_set_message($message, 'error');
  711. }
  712. }
  713. }
  714. /**
  715. * Helper function to add a property/value pair to a render array safely without
  716. * overriding any pre-existing value.
  717. *
  718. * @param $position
  719. * 'append' if $value should be inserted at the end of the $element[$property]
  720. * array, any other value to insert it at the beginning.
  721. *
  722. */
  723. function _conditional_fields_element_add_property(&$element, $property, $value, $position = 'prepend') {
  724. // Avoid overriding default element properties that might not yet be set.
  725. if (!isset($element[$property])) {
  726. $element[$property] = isset($element['#type']) ? element_info_property($element['#type'], $property, array()) : array();
  727. }
  728. if (in_array($value, $element[$property])) {
  729. return;
  730. }
  731. switch ($position) {
  732. case 'append':
  733. $element[$property] = array_merge($element[$property], (array) $value);
  734. break;
  735. case 'prepend':
  736. default:
  737. $element[$property] = array_merge((array) $value, $element[$property]);
  738. break;
  739. }
  740. }
  741. /**
  742. * Implements hook_entity_view_alter().
  743. *
  744. * Applies entity view logic to conditional fields.
  745. */
  746. function conditional_fields_entity_view_alter(&$build, $type) {
  747. if (!(isset($build['#entity_type'], $build['#bundle']) && $dependencies = conditional_fields_load_dependencies($build['#entity_type'], $build['#bundle']))) {
  748. return;
  749. }
  750. $evaluated_dependents = array();
  751. foreach ($dependencies['dependents'] as $dependent => $dependency) {
  752. if (empty($build[$dependent]['#access'])) {
  753. continue;
  754. }
  755. foreach ($dependency as $dependency_options) {
  756. $dependee = $dependency_options['dependee'];
  757. $options = $dependency_options['options'];
  758. // We can interface with the States API only through the Value condition.
  759. if ($options['condition'] != 'value') {
  760. continue;
  761. }
  762. // Determine field view behaviors.
  763. // If this dependent has multiple dependencies, only the logic of the
  764. // first dependency will be taken into account.
  765. if (!isset($behaviors)) {
  766. $behaviors = conditional_fields_field_behaviors('view', $options);
  767. }
  768. // Manage orphan fields (dependents with no dependees).
  769. $evaluate = in_array(CONDITIONAL_FIELDS_FIELD_VIEW_EVALUATE, $behaviors);
  770. $hide_orphan = in_array(CONDITIONAL_FIELDS_FIELD_VIEW_HIDE_ORPHAN, $behaviors);
  771. $hide_untriggered_orphan = in_array(CONDITIONAL_FIELDS_FIELD_VIEW_HIDE_UNTRIGGERED_ORPHAN, $behaviors);
  772. $is_orphan = empty($build[$dependee]['#access']);
  773. if ($is_orphan) {
  774. // Hide the dependent. No need to evaluate the dependency.
  775. if ($hide_orphan) {
  776. $build[$dependent]['#access'] = FALSE;
  777. continue;
  778. }
  779. if ($hide_untriggered_orphan) {
  780. $evaluate = TRUE;
  781. }
  782. if ($evaluate) {
  783. // We have to look for the dependee in the entity object.
  784. // TODO: Is it possible to avoid hardcoding this?
  785. switch ($type) {
  786. case 'node':
  787. $entity_property = '#node';
  788. break;
  789. case 'user':
  790. $entity_property = '#account';
  791. break;
  792. case 'term':
  793. $entity_property = '#term';
  794. break;
  795. case 'field_collection_item':
  796. case 'profile2':
  797. default:
  798. $entity_property = '#entity';
  799. }
  800. // If we didn't find the field, there is nothing more we can do.
  801. if (!isset($build[$entity_property]->$dependee)) {
  802. continue;
  803. }
  804. $items = $build[$entity_property]->$dependee;
  805. // Values are keyed by language here, remove it.
  806. $items = array_shift($items);
  807. }
  808. }
  809. else {
  810. $items = $build[$dependee]['#items'];
  811. }
  812. if ($evaluate) {
  813. $evaluated_dependents[$dependent][$options['grouping']][] = conditional_fields_evaluate_dependency('view', $items, $options);
  814. }
  815. }
  816. if (isset($evaluated_dependents[$dependent])) {
  817. $is_triggered = conditional_fields_evaluate_grouping($evaluated_dependents[$dependent]);
  818. foreach ($behaviors as $behavior) {
  819. switch ($behavior) {
  820. case CONDITIONAL_FIELDS_FIELD_VIEW_EVALUATE:
  821. // Hide the dependent if it is not triggered.
  822. if (!$is_triggered) {
  823. $build[$dependent]['#access'] = FALSE;
  824. }
  825. break;
  826. case CONDITIONAL_FIELDS_FIELD_VIEW_HIDE_ORPHAN:
  827. // This case was handled while looking for the field.
  828. break;
  829. case CONDITIONAL_FIELDS_FIELD_VIEW_HIDE_UNTRIGGERED_ORPHAN:
  830. // Hide the dependent if the dependee is not viewable and the dependency is not triggered.
  831. if ($is_orphan && !$is_triggered) {
  832. $build[$dependent]['#access'] = FALSE;
  833. }
  834. break;
  835. case CONDITIONAL_FIELDS_FIELD_VIEW_HIGHLIGHT:
  836. // Show the dependent themed like an error message if it is not triggered.
  837. if (!$is_triggered) {
  838. $build[$dependent]['#prefix'] = isset($build[$dependent]['#prefix']) ? '<div class="messages error">' . $build[$dependent]['#prefix'] : '<div class="messages error">';
  839. $build[$dependent]['#suffix'] = isset($build[$dependent]['#suffix']) ? $build[$dependent]['#suffix'] . '</div>' : '</div>';
  840. }
  841. break;
  842. case CONDITIONAL_FIELDS_FIELD_VIEW_DESCRIBE:
  843. // Show a textual description of the dependency under the dependent field.
  844. if ($build[$dependent]['#access']) {
  845. $dependee_title = isset($build[$dependee]['#title']) ? $build[$dependee]['#title'] : $dependee;
  846. $dependent_title = isset($build[$dependent]['#title']) ? $build[$dependent]['#title'] : $dependent;
  847. $description = conditional_fields_dependency_description($dependee_title, $dependent_title, $options);
  848. if (isset($build[$dependent]['#suffix'])) {
  849. $description = $build[$dependent]['#suffix'] . $description;
  850. }
  851. $build[$dependent]['#suffix'] = $description;
  852. }
  853. break;
  854. default:
  855. // Custom behaviors are callbacks.
  856. $$behavior('view', $dependee, $dependent, $is_triggered, $dependencies, $build, $type);
  857. break;
  858. }
  859. if (empty($build[$dependent]['#access'])) {
  860. break;
  861. }
  862. }
  863. }
  864. unset($behaviors);
  865. }
  866. }
  867. /**
  868. * Evaluates an array with 'AND', 'OR' and 'XOR' groupings,
  869. * each containing a list of boolean values.
  870. */
  871. function conditional_fields_evaluate_grouping($groups) {
  872. $or = $and = $xor = TRUE;
  873. if (!empty($groups['OR'])) {
  874. $or = in_array(TRUE, $groups['OR']);
  875. }
  876. if (!empty($groups['AND'])) {
  877. $and = !in_array(FALSE, $groups['AND']);
  878. }
  879. if (!empty($groups['XOR'])) {
  880. $xor = array_sum($groups['XOR']) == 1;
  881. }
  882. return $or && $and && $xor;
  883. }
  884. /**
  885. * Evaluate a set of dependencies for a dependent field.
  886. *
  887. * @param $dependent
  888. * The field form element in the current language.
  889. */
  890. function conditional_fields_evaluate_dependencies($dependent, $form, $form_state) {
  891. $dependencies = $form['#conditional_fields'][$dependent['#field_name']]['dependees'];
  892. $evaluated_dependees = array();
  893. foreach ($dependencies as $dependency_id => $dependency) {
  894. // Extract field values from submitted values.
  895. $dependee = $dependency['dependee'];
  896. $dependee_parents = $form['#conditional_fields'][$dependee]['parents'];
  897. // We have the parents of the field, but depending on the entity type and
  898. // the widget type, they may include additional elements that are actually
  899. // part of the value. So we find the depth of the field inside the form
  900. // structure and use the parents only up to that depth.
  901. $dependee_parents_keys = array_flip($dependee_parents);
  902. $dependee_parent = drupal_array_get_nested_value($form, array_slice($dependee_parents, 0, $dependee_parents_keys[$dependee]));
  903. $values = conditional_fields_form_field_get_values($dependee_parent[$dependee], $form_state);
  904. // Remove the language key.
  905. if (isset($dependee_parent[$dependee]['#language'], $values[$dependee_parent[$dependee]['#language']])) {
  906. $values = $values[$dependee_parent[$dependee]['#language']];
  907. }
  908. $evaluated_dependees[$dependent['#field_name']][$dependency['options']['grouping']][] = conditional_fields_evaluate_dependency('edit', $values, $dependency['options']);
  909. }
  910. return conditional_fields_evaluate_grouping($evaluated_dependees[$dependent['#field_name']]);
  911. }
  912. /**
  913. * Evaluate if a dependency meets the requirements to be triggered.
  914. *
  915. * @param $context
  916. * 'edit' if $values are extracted from $form_state or 'view' if
  917. * $values are extracted from an entity.
  918. */
  919. function conditional_fields_evaluate_dependency($context, $values, $options) {
  920. if ($options['values_set'] == CONDITIONAL_FIELDS_DEPENDENCY_VALUES_WIDGET) {
  921. $dependency_values = $context == 'view' ? $options['value'] : $options['value_form'];
  922. // Simple case: both values are strings or integers. Should never happen in
  923. // view context, but does no harm to check anyway.
  924. if (!is_array($values)) {
  925. // Options elements consider "_none" value same as empty.
  926. $values = $values === '_none' ? '' : $values;
  927. if (!is_array($dependency_values)) {
  928. // Some widgets store integers, but values saved in $dependency_values
  929. // are always strings. Convert them to integers because we want to do a
  930. // strict equality check to differentiate empty strings from '0'.
  931. if (is_int($values) && is_numeric($dependency_values)) {
  932. $dependency_values = (int) $dependency_values;
  933. }
  934. return $dependency_values === $values;
  935. }
  936. // If $values is a string and $dependency_values an array, convert $values
  937. // to the standard field array form format. This covers cases like single
  938. // value textfields.
  939. $values = array(array('value' => $values));
  940. }
  941. // If we are in form context, we are almost done.
  942. if ($context == 'edit') {
  943. // If $dependency_values is not an array, we can only assume that it
  944. // should map to the first key of the first value of $values.
  945. if (!is_array($dependency_values)) {
  946. $key = current(array_keys((array) current($values)));
  947. $dependency_values = array(array($key => $dependency_values));
  948. }
  949. // Compare arrays recursively ignoring keys, since multiple select widgets
  950. // values have numeric keys in form format and string keys in storage
  951. // format.
  952. return array_values($dependency_values) == array_values($values);
  953. }
  954. // $values, when viewing fields, may contain all sort of additional
  955. // information, so filter out from $values the keys that are not present in
  956. // $dependency_values.
  957. // Values here are alway keyed by delta (regardless of multiple value
  958. // settings).
  959. foreach ($values as $delta => &$value) {
  960. if (isset($dependency_values[$delta])) {
  961. $value = array_intersect_key($value, $dependency_values[$delta]);
  962. foreach ($value as $key => &$element_value) {
  963. if (isset($dependency_values[$delta][$key]) && is_int($dependency_values[$delta][$key]) && is_numeric($element_value)) {
  964. $element_value = (int) $element_value;
  965. }
  966. }
  967. }
  968. }
  969. // Compare values.
  970. foreach ($dependency_values as $delta => $dependency_value) {
  971. if (!isset($values[$delta])) {
  972. return FALSE;
  973. }
  974. foreach ($dependency_value as $key => $dependency_element_value) {
  975. // Ignore keys set in the field and not in the dependency.
  976. if (isset($values[$delta][$key]) && $values[$delta][$key] !== $dependency_element_value) {
  977. return FALSE;
  978. }
  979. }
  980. }
  981. return TRUE;
  982. }
  983. // Flatten array of values.
  984. $reference_values = array();
  985. foreach ((array) $values as $value) {
  986. // TODO: support multiple values.
  987. $reference_values[] = is_array($value) ? array_shift($value) : $value;
  988. }
  989. // Regular expression method.
  990. if ($options['values_set'] == CONDITIONAL_FIELDS_DEPENDENCY_VALUES_REGEX) {
  991. foreach ($reference_values as $reference_value) {
  992. if (!preg_match('/' . $options['value']['RegExp'] . '/', $reference_value)) {
  993. return FALSE;
  994. }
  995. }
  996. return TRUE;
  997. }
  998. switch ($options['values_set']) {
  999. case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_AND:
  1000. $diff = array_diff($options['values'], $reference_values);
  1001. return empty($diff);
  1002. case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_OR:
  1003. $intersect = array_intersect($options['values'], $reference_values);
  1004. return !empty($intersect);
  1005. case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_XOR:
  1006. $intersect = array_intersect($options['values'], $reference_values);
  1007. return count($intersect) == 1;
  1008. case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_NOT:
  1009. $intersect = array_intersect($options['values'], $reference_values);
  1010. return empty($intersect);
  1011. }
  1012. }
  1013. /**
  1014. * Determine which dependency behaviors should be used in forms and content
  1015. * display, depending on dependency options and user roles.
  1016. *
  1017. * @param $op
  1018. * 'view' or 'edit'.
  1019. * @param $options
  1020. * Dependency options.
  1021. *
  1022. * @return
  1023. * An array of behaviors.
  1024. *
  1025. */
  1026. function conditional_fields_field_behaviors($op, $options) {
  1027. global $user;
  1028. if ($options['element_' . $op . '_per_role']) {
  1029. foreach ($options['element_' . $op . '_roles'] as $rid => $role_behaviors) {
  1030. if (isset($user->roles[$rid])) {
  1031. $behaviors = $role_behaviors;
  1032. break;
  1033. }
  1034. }
  1035. }
  1036. else {
  1037. $behaviors = $options['element_' . $op];
  1038. }
  1039. // Filter out inactive behaviors.
  1040. $behaviors = array_filter($behaviors);
  1041. return $behaviors;
  1042. }
  1043. /**
  1044. * Builds a jQuery selector from the name attribute of a field.
  1045. * TODO: support custom selectors with %lang and %key placeholders.
  1046. */
  1047. function conditional_fields_field_selector($field) {
  1048. if (isset($field['#attributes']['name'])) {
  1049. return '[name="' . $field['#attributes']['name'] . '"]';
  1050. }
  1051. if (isset($field['#name'])) {
  1052. return '[name="' . $field['#name'] . '"]';
  1053. }
  1054. // Try with id if name is not found.
  1055. if (isset($field['#attributes']['id'])) {
  1056. return '#' . $field['#attributes']['id'];
  1057. }
  1058. if (isset($field['#id'])) {
  1059. return '#' . $field['#id'];
  1060. }
  1061. return FALSE;
  1062. }
  1063. /**
  1064. * Provides default options for a dependency.
  1065. *
  1066. * For an explanation of available options,
  1067. * @see conditional_fields_field_attach_dependency()
  1068. */
  1069. function conditional_fields_dependency_default_options() {
  1070. return array(
  1071. 'state' => 'visible',
  1072. 'condition' => 'value',
  1073. 'grouping' => 'AND',
  1074. 'values_set' => CONDITIONAL_FIELDS_DEPENDENCY_VALUES_WIDGET,
  1075. 'value' => array(),
  1076. 'values' => array(),
  1077. 'value_form' => array(),
  1078. 'effect' => 'show',
  1079. 'effect_options' => array(),
  1080. 'element_view' => array(
  1081. CONDITIONAL_FIELDS_FIELD_VIEW_EVALUATE => CONDITIONAL_FIELDS_FIELD_VIEW_EVALUATE,
  1082. CONDITIONAL_FIELDS_FIELD_VIEW_HIDE_ORPHAN => CONDITIONAL_FIELDS_FIELD_VIEW_HIDE_ORPHAN,
  1083. CONDITIONAL_FIELDS_FIELD_VIEW_HIDE_UNTRIGGERED_ORPHAN => 0,
  1084. CONDITIONAL_FIELDS_FIELD_VIEW_HIGHLIGHT => 0,
  1085. CONDITIONAL_FIELDS_FIELD_VIEW_DESCRIBE => 0,
  1086. ),
  1087. 'element_view_per_role' => 0,
  1088. 'element_view_roles' => array(),
  1089. 'element_edit' => array(
  1090. CONDITIONAL_FIELDS_FIELD_EDIT_HIDE_ORPHAN => CONDITIONAL_FIELDS_FIELD_EDIT_HIDE_ORPHAN,
  1091. CONDITIONAL_FIELDS_FIELD_EDIT_HIDE_UNTRIGGERED_ORPHAN => 0,
  1092. CONDITIONAL_FIELDS_FIELD_EDIT_RESET_UNTRIGGERED => 0,
  1093. ),
  1094. 'element_edit_per_role' => 0,
  1095. 'element_edit_roles' => array(),
  1096. 'selector' => '',
  1097. );
  1098. }
  1099. /**
  1100. * Loads all dependencies from the database.
  1101. *
  1102. * The result can be filtered by providing an entity type and a bundle name.
  1103. */
  1104. function conditional_fields_load_dependencies($entity_type = NULL, $bundle = NULL) {
  1105. // Use the advanced drupal_static() pattern.
  1106. static $dependencies;
  1107. if (!isset($dependencies)) {
  1108. $dependencies = &drupal_static(__FUNCTION__);
  1109. }
  1110. if (!$dependencies) {
  1111. $dependencies = array();
  1112. }
  1113. if (!isset($dependencies[$entity_type][$bundle])) {
  1114. if (!empty($entity_type) && !empty($bundle)) {
  1115. $dependencies[$entity_type][$bundle] = array();
  1116. }
  1117. $default_options = conditional_fields_dependency_default_options();
  1118. $select = db_select('conditional_fields', 'cf')
  1119. ->fields('cf', array('id', 'options'))
  1120. ->orderBy('cf.dependent');
  1121. $fci_depende = $select->join('field_config_instance', 'fci_dependee', 'cf.dependee = fci_dependee.id');
  1122. $fci_dependent = $select->join('field_config_instance', 'fci_dependent', 'cf.dependent = fci_dependent.id');
  1123. $select->addField($fci_depende, 'field_name', 'dependee');
  1124. $select->addField($fci_dependent, 'field_name', 'dependent');
  1125. $select->addField($fci_depende, 'entity_type');
  1126. $select->addField($fci_depende, 'bundle');
  1127. if ($entity_type) {
  1128. $select->condition(
  1129. db_and()
  1130. ->condition('fci_dependee.entity_type', $entity_type)
  1131. ->condition('fci_dependent.entity_type', $entity_type)
  1132. );
  1133. }
  1134. if ($bundle) {
  1135. $select->condition(
  1136. db_and()
  1137. ->condition('fci_dependee.bundle', $bundle)
  1138. ->condition('fci_dependent.bundle', $bundle)
  1139. );
  1140. }
  1141. $result = $select->execute();
  1142. foreach ($result as $dependency) {
  1143. $result_entity_type = $entity_type ? $entity_type : $dependency->entity_type;
  1144. $result_bundle = $bundle ? $bundle : $dependency->bundle;
  1145. $options = unserialize($dependency->options);
  1146. $options += $default_options;
  1147. $dependencies[$result_entity_type][$result_bundle]['dependents'][$dependency->dependent][$dependency->id] = array(
  1148. 'dependee' => $dependency->dependee,
  1149. 'options' => $options,
  1150. );
  1151. $dependencies[$result_entity_type][$result_bundle]['dependees'][$dependency->dependee][$dependency->id] = array(
  1152. 'dependent' => $dependency->dependent,
  1153. 'options' => $options,
  1154. );
  1155. }
  1156. }
  1157. if ($entity_type && isset($dependencies[$entity_type])) {
  1158. if ($bundle && isset($dependencies[$entity_type][$bundle])) {
  1159. return $dependencies[$entity_type][$bundle];
  1160. }
  1161. return $dependencies[$entity_type];
  1162. }
  1163. return $dependencies;
  1164. }
  1165. /**
  1166. * Menu argument loader: loads a dependency from the database.
  1167. *
  1168. * @param $id
  1169. * The dependency ID.
  1170. *
  1171. * @return
  1172. * A fully populated dependency array, complete with its conditions, or FALSE
  1173. * if the dependency is not found.
  1174. */
  1175. function conditional_fields_dependency_load($id) {
  1176. $result = db_select('conditional_fields', 'cf')
  1177. ->fields('cf', array('id', 'dependee', 'dependent', 'options'))
  1178. ->condition('id', $id)
  1179. ->execute()
  1180. ->fetchAssoc();
  1181. if (!$result) {
  1182. return FALSE;
  1183. }
  1184. $result['options'] = unserialize($result['options']) + conditional_fields_dependency_default_options();
  1185. return $result;
  1186. }
  1187. /**
  1188. * Inserts a new dependency in the database.
  1189. * For the format of $options,
  1190. * @see conditional_fields_dependency_default_options()
  1191. */
  1192. function conditional_fields_dependency_insert($dependee_id, $dependent_id, $options = array()) {
  1193. $options += conditional_fields_dependency_default_options();
  1194. $dependency = array(
  1195. 'dependee' => $dependee_id,
  1196. 'dependent' => $dependent_id,
  1197. 'options' => $options,
  1198. );
  1199. if (drupal_write_record('conditional_fields', $dependency)) {
  1200. return $dependency['id'];
  1201. }
  1202. }
  1203. /**
  1204. * Updates a dependency.
  1205. */
  1206. function conditional_fields_dependency_update($dependency) {
  1207. drupal_write_record('conditional_fields', $dependency, 'id');
  1208. }
  1209. /**
  1210. * Deletes dependencies.
  1211. */
  1212. function conditional_fields_dependency_delete($dependency_ids) {
  1213. $or = db_or();
  1214. foreach ($dependency_ids as $id) {
  1215. $or = $or->condition('id', $id);
  1216. }
  1217. return db_delete('conditional_fields')
  1218. ->condition($or)
  1219. ->execute();
  1220. }
  1221. /**
  1222. * Implements hook_field_delete_instance().
  1223. *
  1224. * Delete any dependency associated with the deleted instance.
  1225. */
  1226. function conditional_fields_field_delete_instance($instance) {
  1227. db_delete('conditional_fields')
  1228. ->condition(
  1229. db_or()
  1230. ->condition('dependee', $instance['id'])
  1231. ->condition('dependent', $instance['id']))
  1232. ->execute();
  1233. }
  1234. /**
  1235. * Implements hook_theme().
  1236. */
  1237. function conditional_fields_theme() {
  1238. return array(
  1239. 'conditional_fields_table' => array(
  1240. 'render element' => 'elements',
  1241. ),
  1242. );
  1243. }
  1244. /**
  1245. * Implements hook_element_info().
  1246. */
  1247. function conditional_fields_element_info() {
  1248. return array(
  1249. 'conditional_fields_table' => array(
  1250. '#theme' => 'conditional_fields_table',
  1251. ),
  1252. );
  1253. }
  1254. /**
  1255. * Builds a list of supported states that may be applied to a dependent field.
  1256. */
  1257. function conditional_fields_states() {
  1258. $states = array(
  1259. // Supported by States API
  1260. 'visible' => t('Visible'),
  1261. '!visible' => t('Invisible'),
  1262. '!empty' => t('Filled with a value'),
  1263. 'empty' => t('Emptied'),
  1264. '!disabled' => t('Enabled'),
  1265. 'disabled' => t('Disabled'),
  1266. 'checked' => t('Checked'),
  1267. '!checked' => t('Unchecked'),
  1268. 'required' => t('Required'),
  1269. '!required' => t('Optional'),
  1270. '!collapsed' => t('Expanded'),
  1271. 'collapsed' => t('Collapsed'),
  1272. // Supported by Conditional Fields
  1273. 'unchanged' => t('Unchanged (no state)'),
  1274. // TODO: Add support to these states:
  1275. /*
  1276. 'relevant' => t('Relevant'),
  1277. '!relevant' => t('Irrelevant'),
  1278. 'valid' => t('Valid'),
  1279. '!valid' => t('Invalid'),
  1280. 'touched' => t('Touched'),
  1281. '!touched' => t('Untouched'),
  1282. '!readonly' => t('Read/Write'),
  1283. 'readonly' => t('Read Only'),
  1284. */
  1285. );
  1286. // Allow other modules to modify the states
  1287. drupal_alter('conditional_fields_states', $states);
  1288. return $states;
  1289. }
  1290. /**
  1291. * Builds a list of supported effects that may be applied to a dependent field
  1292. * when it changes from visible to invisible and viceversa. The effects may
  1293. * have options that will be passed as Javascript settings and used by
  1294. * conditional_fields.js.
  1295. *
  1296. * @return
  1297. * An associative array of effects. Each key is an unique name for the effect.
  1298. * The value is an associative array:
  1299. * - label: The human readable name of the effect.
  1300. * - states: The states that can be associated with this effect.
  1301. * - options: An associative array of effect options names, field types,
  1302. * descriptions and default values.
  1303. */
  1304. function conditional_fields_effects() {
  1305. $effects = array(
  1306. 'show' => array(
  1307. 'label' => t('Show/Hide'),
  1308. 'states' => array('visible', '!visible'),
  1309. ),
  1310. 'fade' => array(
  1311. 'label' => t('Fade in/Fade out'),
  1312. 'states' => array('visible', '!visible'),
  1313. 'options' => array(
  1314. 'speed' => array(
  1315. '#type' => 'textfield',
  1316. '#description' => t('The speed at which the animation is performed, in milliseconds.'),
  1317. '#default_value' => 400,
  1318. ),
  1319. ),
  1320. ),
  1321. 'slide' => array(
  1322. 'label' => t('Slide down/Slide up'),
  1323. 'states' => array('visible', '!visible'),
  1324. 'options' => array(
  1325. 'speed' => array(
  1326. '#type' => 'textfield',
  1327. '#description' => t('The speed at which the animation is performed, in milliseconds.'),
  1328. '#default_value' => 400,
  1329. ),
  1330. ),
  1331. ),
  1332. 'fill' => array(
  1333. 'label' => t('Fill field with a value'),
  1334. 'states' => array('!empty'),
  1335. 'options' => array(
  1336. 'value' => array(
  1337. '#type' => 'textfield',
  1338. '#description' => t('The value that should be given to the field when automatically filled.'),
  1339. '#default_value' => '',
  1340. ),
  1341. 'reset' => array(
  1342. '#type' => 'checkbox',
  1343. '#title' => t('Restore previous value when untriggered'),
  1344. '#default_value' => 1,
  1345. ),
  1346. ),
  1347. ),
  1348. 'empty' => array(
  1349. 'label' => t('Empty field'),
  1350. 'states' => array('empty'),
  1351. 'options' => array(
  1352. 'value' => array(
  1353. '#type' => 'hidden',
  1354. '#description' => t('The value that should be given to the field when automatically emptied.'),
  1355. '#value' => '',
  1356. '#default_value' => '',
  1357. ),
  1358. 'reset' => array(
  1359. '#type' => 'checkbox',
  1360. '#title' => t('Restore previous value when untriggered'),
  1361. '#default_value' => 1,
  1362. ),
  1363. ),
  1364. ),
  1365. );
  1366. // Allow other modules to modify the effects.
  1367. drupal_alter('conditional_fields_effects', $effects);
  1368. return $effects;
  1369. }
  1370. /**
  1371. * List of states of a dependee field that may be used to evaluate a condition.
  1372. */
  1373. function conditional_fields_conditions($checkboxes = TRUE) {
  1374. // Supported by States API
  1375. $conditions = array(
  1376. '!empty' => t('Filled'),
  1377. 'empty' => t('Empty'),
  1378. 'touched' => t('Touched'),
  1379. '!touched' => t('Untouched'),
  1380. 'focused' => t('Focused'),
  1381. '!focused' => t('Unfocused'),
  1382. );
  1383. if ($checkboxes) {
  1384. // Relevant only if dependee is a list of checkboxes
  1385. $conditions['checked'] = t('Checked');
  1386. $conditions['!checked'] = t('Unchecked');
  1387. }
  1388. $conditions['value'] = t('Value');
  1389. // TODO: Add support from Conditional Fields to these conditions
  1390. /*
  1391. '!disabled' => t('Enabled'),
  1392. 'disabled' => t('Disabled'),
  1393. 'required' => t('Required'),
  1394. '!required' => t('Optional'),
  1395. 'relevant' => t('Relevant'),
  1396. '!relevant' => t('Irrelevant'),
  1397. 'valid' => t('Valid'),
  1398. '!valid' => t('Invalid'),
  1399. '!readonly' => t('Read/Write'),
  1400. 'readonly' => t('Read Only'),
  1401. '!collapsed' => t('Expanded'),
  1402. 'collapsed' => t('Collapsed'),
  1403. */
  1404. // Allow other modules to modify the conditions
  1405. drupal_alter('conditional_fields_conditions', $conditions);
  1406. return $conditions;
  1407. }
  1408. /**
  1409. * List of behaviors that can be applied when editing forms and viewing content
  1410. * with dependencies.
  1411. */
  1412. function conditional_fields_behaviors($op = NULL) {
  1413. $behaviors = array(
  1414. 'edit' => array(
  1415. CONDITIONAL_FIELDS_FIELD_EDIT_HIDE_ORPHAN => t('Hide the dependent if the dependee is not in the form'),
  1416. CONDITIONAL_FIELDS_FIELD_EDIT_RESET_UNTRIGGERED => t('Reset the dependent to its default values when the form is submitted if the dependency is not triggered.') . '<br /><em>' . t('Note: This setting only applies if the condition is "Value" and may not work with some field types. Also, ensure that the default values are valid, since they will not be validated.') . '</em>',
  1417. // TODO: Implement. Settings are imported from D6 though, they just do nothing for now.
  1418. /*
  1419. CONDITIONAL_FIELDS_FIELD_EDIT_HIDE_UNTRIGGERED_ORPHAN => t('Hide the dependent if the dependee is not in the form and the dependency is not triggered.') . '<br /><em>' . t('Note: This setting is not currently not implemented and has no effect.') . '</em>',
  1420. */
  1421. ),
  1422. 'view' => array(
  1423. CONDITIONAL_FIELDS_FIELD_VIEW_EVALUATE => t('Hide the dependent if the dependency is not triggered'),
  1424. CONDITIONAL_FIELDS_FIELD_VIEW_HIDE_ORPHAN => t('Hide the dependent if the dependee is not viewable by the user'),
  1425. CONDITIONAL_FIELDS_FIELD_VIEW_HIDE_UNTRIGGERED_ORPHAN => t('Hide the dependent if the dependee is not viewable by the user and the dependency is not triggered'),
  1426. CONDITIONAL_FIELDS_FIELD_VIEW_HIGHLIGHT => t('Theme the dependent like an error message if the dependency is not triggered'),
  1427. CONDITIONAL_FIELDS_FIELD_VIEW_DESCRIBE => t('Show a textual description of the dependency under the dependent'),
  1428. ),
  1429. );
  1430. // Allow other modules to modify the options.
  1431. drupal_alter('conditional_fields_behaviors', $op, $behaviors);
  1432. if (isset($behaviors[$op])) {
  1433. return $behaviors[$op];
  1434. }
  1435. return $behaviors;
  1436. }
  1437. /**
  1438. * Builds a list of special fields handlers to be executed when building the
  1439. * #states array. The keys are handler function names and the key/value pairs
  1440. * are field properties and their values that trigger the execution of the handler.
  1441. *
  1442. * The handlers themselves must accept the parameters $field, $field_info,
  1443. * $options and $state.
  1444. *
  1445. * @see conditional_fields_field_attach_form()
  1446. * @see conditional_fields_states_handler_select()
  1447. */
  1448. function conditional_fields_states_handlers() {
  1449. $handlers = array(
  1450. 'conditional_fields_states_handler_select' => array(
  1451. '#type' => 'select',
  1452. '#multiple' => TRUE,
  1453. ),
  1454. 'conditional_fields_states_handler_checkbox' => array(
  1455. '#type' => 'checkbox',
  1456. ),
  1457. 'conditional_fields_states_handler_checkboxes' => array(
  1458. '#type' => 'checkboxes',
  1459. ),
  1460. 'conditional_fields_states_handler_text' => array(
  1461. '#type' => 'textfield',
  1462. ),
  1463. 'conditional_fields_states_handler_textarea' => array(
  1464. '#type' => 'textarea',
  1465. ),
  1466. 'conditional_fields_states_handler_date_combo' => array(
  1467. '#type' => 'date_combo',
  1468. ),
  1469. 'conditional_fields_states_handler_link_field' => array(
  1470. '#type' => 'link_field',
  1471. ),
  1472. 'conditional_fields_states_handler_link_addressfield' => array(
  1473. '#addressfield' => 1,
  1474. ),
  1475. );
  1476. // Allow other modules to modify the handlers
  1477. drupal_alter('conditional_fields_states_handlers', $handlers);
  1478. return $handlers;
  1479. }
  1480. /**
  1481. * States handler for multiple select lists.
  1482. *
  1483. * Multiple select fields always require an array as value.
  1484. * In addition, since our modified States API triggers a dependency only if all
  1485. * reference values of type Array are selected, a different selector must be
  1486. * added for each value of a set for OR, XOR and NOT evaluations.
  1487. */
  1488. function conditional_fields_states_handler_select($field, $field_info, $options, &$state) {
  1489. switch ($options['values_set']) {
  1490. case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_WIDGET:
  1491. case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_AND:
  1492. $state[$options['state']][$options['selector']]['value'] = (array) $state[$options['state']][$options['selector']]['value'];
  1493. return;
  1494. case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_XOR:
  1495. $select_states[$options['state']][] = 'xor';
  1496. case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_REGEX:
  1497. $regex = TRUE;
  1498. case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_NOT:
  1499. case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_OR:
  1500. foreach ($options['values'] as $value) {
  1501. $select_states[$options['state']][] = array(
  1502. $options['selector'] => array(
  1503. $options['condition'] => empty($regex) ? array($value) : $options['value'],
  1504. ),
  1505. );
  1506. }
  1507. break;
  1508. }
  1509. $state = $select_states;
  1510. }
  1511. /**
  1512. * States handler for single on/off checkbox.
  1513. */
  1514. function conditional_fields_states_handler_checkbox($field, $field_info, $options, &$state) {
  1515. switch ($options['values_set']) {
  1516. case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_WIDGET:
  1517. $checked = $options['value'][0]['value'] == $field['#on_value'] ? TRUE : FALSE;
  1518. break;
  1519. case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_REGEX:
  1520. $checked = preg_match('/' . $options['value']['RegExp'] . '/', $field['#on_value']) ? TRUE : FALSE;
  1521. break;
  1522. case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_AND:
  1523. // ANDing values of a single checkbox doesn't make sense: just use the first value.
  1524. $checked = $options['values'][0] == $field['#on_value'] ? TRUE : FALSE;
  1525. break;
  1526. case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_XOR:
  1527. case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_OR:
  1528. case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_NOT:
  1529. $checked = in_array($field['#on_value'], $options['values']) ? TRUE : FALSE;
  1530. break;
  1531. }
  1532. $state[$options['state']][$options['selector']] = array('checked' => $checked);
  1533. }
  1534. /**
  1535. * States handler for checkboxes.
  1536. */
  1537. function conditional_fields_states_handler_checkboxes($field, $field_info, $options, &$state) {
  1538. // Checkboxes are actually different form fields, so the #states property
  1539. // has to include a state for each checkbox.
  1540. $checkboxes_selectors = array();
  1541. switch ($options['values_set']) {
  1542. case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_WIDGET:
  1543. foreach ($options['value'] as $value) {
  1544. $checkboxes_selectors[conditional_fields_field_selector($field[current($value)])] = array('checked' => TRUE);
  1545. }
  1546. break;
  1547. case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_REGEX:
  1548. // We interpret this as: checkboxes whose values match the regular
  1549. // expression should be checked.
  1550. foreach ($field['#options'] as $key => $label) {
  1551. if (preg_match('/' . $options['value']['RegExp'] . '/', $key)) {
  1552. $checkboxes_selectors[conditional_fields_field_selector($field[$key])] = array('checked' => TRUE);
  1553. }
  1554. }
  1555. break;
  1556. case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_AND:
  1557. foreach ($options['values'] as $value) {
  1558. $checkboxes_selectors[conditional_fields_field_selector($field[$value])] = array('checked' => TRUE);
  1559. }
  1560. break;
  1561. case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_XOR:
  1562. $checkboxes_selectors[] = 'xor';
  1563. case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_OR:
  1564. case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_NOT:
  1565. foreach ($options['values'] as $value) {
  1566. $checkboxes_selectors[] = array(conditional_fields_field_selector($field[$value]) => array('checked' => TRUE));
  1567. }
  1568. break;
  1569. }
  1570. $state = array($options['state'] => $checkboxes_selectors);
  1571. }
  1572. /**
  1573. * States handler for text fields.
  1574. */
  1575. function conditional_fields_states_handler_text($field, $field_info, $options, &$state) {
  1576. // Text fields values are keyed by cardinality, so we have to flatten them.
  1577. // TODO: support multiple values.
  1578. if ($options['values_set'] == CONDITIONAL_FIELDS_DEPENDENCY_VALUES_WIDGET) {
  1579. // Cast as array to handle the exception of autocomplete text fields.
  1580. $value = (array) $state[$options['state']][$options['selector']]['value'][0];
  1581. $state[$options['state']][$options['selector']]['value'] = array_shift($value);
  1582. }
  1583. }
  1584. /**
  1585. * States handler for text areas.
  1586. */
  1587. function conditional_fields_states_handler_textarea($field, $field_info, $options, &$state) {
  1588. conditional_fields_states_handler_text($field, $field_info, $options, $state);
  1589. }
  1590. /**
  1591. * States handler for date combos.
  1592. */
  1593. function conditional_fields_states_handler_date_combo($field, $field_info, $options, &$state) {
  1594. // Date text.
  1595. if ($field_info['instance']['widget']['type'] == 'date_text') {
  1596. if ($options['values_set'] == CONDITIONAL_FIELDS_DEPENDENCY_VALUES_WIDGET) {
  1597. $state[$options['state']][$options['selector']]['value'] = $state[$options['state']][$options['selector']]['value'][0]['value']['date'];
  1598. }
  1599. return;
  1600. }
  1601. // Add a condition for each date part.
  1602. $date_selectors = array();
  1603. $regex = $options['values_set'] == CONDITIONAL_FIELDS_DEPENDENCY_VALUES_REGEX;
  1604. // Date popup.
  1605. if ($field_info['instance']['widget']['type'] == 'date_popup') {
  1606. $date_selectors[conditional_fields_field_selector($field['value']['date'])] = array(
  1607. 'value' => $regex ? $options['value'] : $options['value_form'][0]['value']['date'],
  1608. );
  1609. if ($field_info['field']['settings']['granularity']['hour'] || $field_info['field']['settings']['granularity']['minute'] || $field_info['field']['settings']['granularity']['second']) {
  1610. $date_selectors[conditional_fields_field_selector($field['value']['time'])] = array(
  1611. 'value' => $regex ? $options['value'] : $options['value_form'][0]['value']['time'],
  1612. );
  1613. }
  1614. }
  1615. // Date select.
  1616. else {
  1617. foreach ($field_info['field']['settings']['granularity'] as $date_part) {
  1618. if ($date_part) {
  1619. $date_selectors[conditional_fields_field_selector($field['value'][$date_part])] = array(
  1620. 'value' => $regex ? $options['value'] : $options['value_form'][0]['value'][$date_part],
  1621. );
  1622. }
  1623. }
  1624. }
  1625. $state = array($options['state'] => $date_selectors);
  1626. }
  1627. /**
  1628. * States handler for links provided by the Link module.
  1629. */
  1630. function conditional_fields_states_handler_link_field($field, $field_info, $options, &$state) {
  1631. $link_selectors = array();
  1632. $regex = $options['values_set'] == CONDITIONAL_FIELDS_DEPENDENCY_VALUES_REGEX;
  1633. // Add a condition for each link part (Title and URL)
  1634. if ($field_info['instance']['settings']['title'] == 'optional' || $field_info['instance']['settings']['title'] == 'required') {
  1635. $link_selectors[conditional_fields_field_selector($field['title'])] = array('value' => $regex ? $options['value'] : $options['value_form'][0]['title']);
  1636. }
  1637. $link_selectors[conditional_fields_field_selector($field['url'])] = array('value' => $regex ? $options['value'] : $options['value_form'][0]['url']);
  1638. $state = array($options['state'] => $link_selectors);
  1639. }
  1640. /**
  1641. * States handler for links provided by the Addressfield module.
  1642. */
  1643. function conditional_fields_states_handler_link_addressfield($field, $field_info, $options, &$state) {
  1644. if ($options['values_set'] != CONDITIONAL_FIELDS_DEPENDENCY_VALUES_WIDGET) {
  1645. return;
  1646. }
  1647. $regex = $options['values_set'] == CONDITIONAL_FIELDS_DEPENDENCY_VALUES_REGEX;
  1648. $keys = array();
  1649. if ($field['#handlers']['address']) {
  1650. $keys += array('country', 'thoroughfare', 'premise', 'postal_code', 'locality', 'administrative_area');
  1651. }
  1652. if ($field['#handlers']['organisation']) {
  1653. $keys += array('organisation_name');
  1654. }
  1655. if ($field['#handlers']['name-oneline']) {
  1656. $keys += array('name_line');
  1657. }
  1658. elseif ($field['#handlers']['name-full']) {
  1659. $keys += array('first_name', 'last_name');
  1660. }
  1661. $addressfield_selectors = array();
  1662. foreach ($keys as $key) {
  1663. $addressfield_selectors[str_replace('%key', $key, $options['selector'])] = array('value' => $regex ? $options['value'] : $options['value'][0][$key]);
  1664. }
  1665. $state = array($options['state'] => $addressfield_selectors);
  1666. }
  1667. /**
  1668. * Build a textual description of a dependency
  1669. */
  1670. function conditional_fields_dependency_description($dependee_name, $dependent_name, $options) {
  1671. $states = conditional_fields_states();
  1672. if ($options['condition'] == 'value') {
  1673. $values = implode(', ', $options['values']);
  1674. switch ($options['values_set']) {
  1675. case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_WIDGET:
  1676. if (count($options['value']) == 1) {
  1677. return t('%dependent_name is !state when %dependee_name has value "@value".', array(
  1678. '%dependent_name' => $dependent_name,
  1679. '!state' => drupal_strtolower($states[$options['state']]),
  1680. '%dependee_name' => $dependee_name,
  1681. '@value' => current($options['value'][0]),
  1682. ));
  1683. }
  1684. // "Single values" of multiple value fields like checkboxes are not
  1685. // actually single. Such fields will be ANDed.
  1686. $value_array = array();
  1687. foreach ($options['value'] as $value) {
  1688. $value_array[] = current($value);
  1689. }
  1690. $values = implode(', ', $value_array);
  1691. case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_AND:
  1692. $description = '%dependent_name is !state when %dependee_name has all the values: @values.';
  1693. break;
  1694. case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_OR:
  1695. $description = '%dependent_name is !state when %dependee_name has at least one of the values: @values.';
  1696. break;
  1697. case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_XOR:
  1698. $description = '%dependent_name is !state when %dependee_name has only one of the values: @values.';
  1699. break;
  1700. case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_NOT:
  1701. $description = '%dependent_name is !state when %dependee_name has none of the values: @values.';
  1702. break;
  1703. case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_REGEX:
  1704. return t('%dependent_name is !state when %dependee_name values match the regular expression: @regex.', array(
  1705. '%dependent_name' => $dependent_name,
  1706. '!state' => drupal_strtolower($states[$options['state']]),
  1707. '%dependee_name' => $dependee_name,
  1708. '@regex' => $options['value']['RegExp'],
  1709. ));
  1710. break;
  1711. }
  1712. return t($description, array(
  1713. '%dependent_name' => $dependent_name,
  1714. '!state' => drupal_strtolower($states[$options['state']]),
  1715. '%dependee_name' => $dependee_name,
  1716. '@values' => $values,
  1717. ));
  1718. }
  1719. else {
  1720. $conditions = conditional_fields_conditions();
  1721. return t('%dependent_name is !state when %dependee_name is !condition.', array(
  1722. '%dependent_name' => $dependent_name,
  1723. '!state' => drupal_strtolower($states[$options['state']]),
  1724. '%dependee_name' => $dependee_name,
  1725. '!condition' => drupal_strtolower($conditions[$options['condition']]),
  1726. ));
  1727. }
  1728. }
  1729. /**
  1730. * Implements hook_module_implements_alter().
  1731. *
  1732. * Ensures that some Conditional Fields hooks are processed last.
  1733. */
  1734. function conditional_fields_module_implements_alter(&$implementations, $hook) {
  1735. if (isset($implementations['conditional_fields'])) {
  1736. // The element_info_alter hook should attempt to add its #after_build
  1737. // callback last to ensure that conditional_fields_form_validate runs after
  1738. // all other validation routines.
  1739. // The Features hook must be processed when the fields actually exist.
  1740. if ($hook == 'element_info_alter' || $hook = 'features_api') {
  1741. $group = $implementations['conditional_fields'];
  1742. unset($implementations['conditional_fields']);
  1743. $implementations['conditional_fields'] = $group;
  1744. }
  1745. }
  1746. }
  1747. /**
  1748. * Implements hook_features_api().
  1749. */
  1750. function conditional_fields_features_api() {
  1751. return array(
  1752. 'conditional_fields' => array(
  1753. 'name' => t('Conditional Fields'),
  1754. 'default_hook' => 'conditional_fields_default_fields',
  1755. 'default_file' => FEATURES_DEFAULTS_INCLUDED,
  1756. 'feature_source' => TRUE,
  1757. 'file' => drupal_get_path('module', 'conditional_fields') . '/includes/conditional_fields.features.inc',
  1758. ),
  1759. );
  1760. }