From 4ef74ecd987abba6ab276e82bcbf190297e435ed Mon Sep 17 00:00:00 2001
From: webchick <webchick@24967.no-reply.drupal.org>
Date: Wed, 25 Apr 2012 15:00:46 -0700
Subject: [PATCH] Issue #1541792 by tim.plunkett, chx, Amitaibu: Enable dynamic
 allowed list values function with additional context

---
 core/modules/field/field.form.inc             |  4 +
 core/modules/field/modules/list/list.module   | 29 +++++--
 .../field/modules/list/tests/list.test        | 81 +++++++++++++++++++
 .../field/modules/list/tests/list_test.module |  9 +++
 .../field/modules/options/options.api.php     |  7 +-
 .../field/modules/options/options.module      |  9 ++-
 .../field/modules/options/options.test        | 35 ++++++++
 core/modules/taxonomy/taxonomy.module         |  2 +-
 8 files changed, 164 insertions(+), 12 deletions(-)

diff --git a/core/modules/field/field.form.inc b/core/modules/field/field.form.inc
index 8faa22bf84e5..2ced5b02a598 100644
--- a/core/modules/field/field.form.inc
+++ b/core/modules/field/field.form.inc
@@ -53,6 +53,8 @@ function field_default_form($entity_type, $entity, $field, $instance, $langcode,
   // If field module handles multiple values for this form element, and we are
   // displaying an individual element, process the multiple value form.
   if (!isset($get_delta) && field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_DEFAULT) {
+    // Store the entity in the form.
+    $form['#entity'] = $entity;
     $elements = field_multiple_value_form($field, $instance, $langcode, $items, $form, $form_state);
   }
   // If the widget is handling multiple values (e.g Options), or if we are
@@ -64,6 +66,7 @@ function field_default_form($entity_type, $entity, $field, $instance, $langcode,
     if (function_exists($function)) {
       $element = array(
         '#entity_type' => $instance['entity_type'],
+        '#entity' => $entity,
         '#bundle' => $instance['bundle'],
         '#field_name' => $field_name,
         '#language' => $langcode,
@@ -173,6 +176,7 @@ function field_multiple_value_form($field, $instance, $langcode, $items, &$form,
       $multiple = $field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED;
       $element = array(
         '#entity_type' => $instance['entity_type'],
+        '#entity' => $form['#entity'],
         '#bundle' => $instance['bundle'],
         '#field_name' => $field_name,
         '#language' => $langcode,
diff --git a/core/modules/field/modules/list/list.module b/core/modules/field/modules/list/list.module
index c2bff2a9acd2..f0b1b80f972f 100644
--- a/core/modules/field/modules/list/list.module
+++ b/core/modules/field/modules/list/list.module
@@ -221,24 +221,39 @@ function list_field_update_field($field, $prior_field, $has_data) {
  *
  * @param $field
  *   The field definition.
+ * @param $instance
+ *   (optional) A field instance array. Defaults to NULL.
+ * @param $entity_type
+ *   (optional) The type of entity; e.g. 'node' or 'user'. Defaults to NULL.
+ * @param $entity
+ *   (optional) The entity object. Defaults to NULL.
  *
  * @return
  *   The array of allowed values. Keys of the array are the raw stored values
  *   (number or text), values of the array are the display labels.
  */
-function list_allowed_values($field) {
+function list_allowed_values($field, $instance = NULL, $entity_type = NULL, $entity = NULL) {
   $allowed_values = &drupal_static(__FUNCTION__, array());
 
   if (!isset($allowed_values[$field['id']])) {
     $function = $field['settings']['allowed_values_function'];
+    // If $cacheable is FALSE, then the allowed values are not statically
+    // cached. See list_test_dynamic_values_callback() for an example of
+    // generating dynamic and uncached values.
+    $cacheable = TRUE;
     if (!empty($function)) {
-      $values = $function($field);
+      $values = $function($field, $instance, $entity_type, $entity, $cacheable);
     }
     else {
       $values = $field['settings']['allowed_values'];
     }
 
-    $allowed_values[$field['id']] = $values;
+    if ($cacheable) {
+      $allowed_values[$field['id']] = $values;
+    }
+    else {
+      return $values;
+    }
   }
 
   return $allowed_values[$field['id']];
@@ -373,7 +388,7 @@ function _list_values_in_use($field, $values) {
  * - 'list_illegal_value': The value is not part of the list of allowed values.
  */
 function list_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
-  $allowed_values = list_allowed_values($field);
+  $allowed_values = list_allowed_values($field, $instance, $entity_type, $entity);
   foreach ($items as $delta => $item) {
     if (!empty($item['value'])) {
       if (!empty($allowed_values) && !isset($allowed_values[$item['value']])) {
@@ -419,8 +434,8 @@ function list_field_widget_info_alter(&$info) {
 /**
  * Implements hook_options_list().
  */
-function list_options_list($field, $instance) {
-  return list_allowed_values($field);
+function list_options_list($field, $instance, $entity_type, $entity) {
+  return list_allowed_values($field, $instance, $entity_type, $entity);
 }
 
 /**
@@ -447,7 +462,7 @@ function list_field_formatter_view($entity_type, $entity, $field, $instance, $la
 
   switch ($display['type']) {
     case 'list_default':
-      $allowed_values = list_allowed_values($field);
+      $allowed_values = list_allowed_values($field, $instance, $entity_type, $entity);
       foreach ($items as $delta => $item) {
         if (isset($allowed_values[$item['value']])) {
           $output = field_filter_xss($allowed_values[$item['value']]);
diff --git a/core/modules/field/modules/list/tests/list.test b/core/modules/field/modules/list/tests/list.test
index 988f6a3d1294..1ad52a62ad27 100644
--- a/core/modules/field/modules/list/tests/list.test
+++ b/core/modules/field/modules/list/tests/list.test
@@ -113,6 +113,87 @@ class ListFieldTestCase extends FieldTestCase {
   }
 }
 
+/**
+ * Sets up a List field for testing allowed values functions.
+ */
+class ListDynamicValuesTestCase extends FieldTestCase {
+  function setUp() {
+    parent::setUp(array('list', 'field_test', 'list_test'));
+
+    $this->field_name = 'test_list';
+    $this->field = array(
+      'field_name' => $this->field_name,
+      'type' => 'list_text',
+      'cardinality' => 1,
+      'settings' => array(
+        'allowed_values_function' => 'list_test_dynamic_values_callback',
+      ),
+    );
+    $this->field = field_create_field($this->field);
+
+    $this->instance = array(
+      'field_name' => $this->field_name,
+      'entity_type' => 'test_entity',
+      'bundle' => 'test_bundle',
+      'required' => TRUE,
+      'widget' => array(
+        'type' => 'options_select',
+      ),
+    );
+    $this->instance = field_create_instance($this->instance);
+    $this->test = array(
+      'id' => mt_rand(1, 10),
+      'vid' => mt_rand(1, 10),
+      'bundle' => 'test_bundle',
+      'label' => $this->randomString(),
+    );
+    $this->entity = call_user_func_array('field_test_create_stub_entity', $this->test);
+  }
+}
+
+/**
+ * Tests the List field allowed values function.
+ */
+class ListDynamicValuesValidationTestCase extends ListDynamicValuesTestCase {
+  public static function getInfo() {
+    return array(
+      'name' => 'List field dynamic values',
+      'description' => 'Test the List field allowed values function.',
+      'group' => 'Field types',
+    );
+  }
+
+  /**
+   * Test that allowed values function gets the entity.
+   */
+  function testDynamicAllowedValues() {
+    // Verify that the test passes against every value we had.
+    foreach ($this->test as $key => $value) {
+      $this->entity->test_list[LANGUAGE_NOT_SPECIFIED][0]['value'] = $value;
+      try {
+        field_attach_validate('test_entity', $this->entity);
+        $this->pass("$key should pass");
+      }
+      catch (FieldValidationException $e) {
+        // This will display as an exception, no need for a separate error.
+        throw($e);
+      }
+    }
+    // Now verify that the test does not pass against anything else.
+    foreach ($this->test as $key => $value) {
+      $this->entity->test_list[LANGUAGE_NOT_SPECIFIED][0]['value'] = is_numeric($value) ? (100 - $value) : ('X' . $value);
+      $pass = FALSE;
+      try {
+        field_attach_validate('test_entity', $this->entity);
+      }
+      catch (FieldValidationException $e) {
+        $pass = TRUE;
+      }
+      $this->assertTrue($pass, $key . ' should not pass');
+    }
+  }
+}
+
 /**
  * List module UI tests.
  */
diff --git a/core/modules/field/modules/list/tests/list_test.module b/core/modules/field/modules/list/tests/list_test.module
index 8d5340412dac..aa5333779e82 100644
--- a/core/modules/field/modules/list/tests/list_test.module
+++ b/core/modules/field/modules/list/tests/list_test.module
@@ -21,3 +21,12 @@ function list_test_allowed_values_callback($field) {
 
   return $values;
 }
+
+/**
+ * An entity-bound allowed values callback.
+ */
+function list_test_dynamic_values_callback($field, $instance, $entity_type, $entity, &$cacheable) {
+  $cacheable = FALSE;
+  // We need the values of the entity as keys.
+  return drupal_map_assoc(array_merge(array($entity->ftlabel), entity_extract_ids($entity_type, $entity)));
+}
diff --git a/core/modules/field/modules/options/options.api.php b/core/modules/field/modules/options/options.api.php
index d1ac0db1e4ef..ce2ec621eb5c 100644
--- a/core/modules/field/modules/options/options.api.php
+++ b/core/modules/field/modules/options/options.api.php
@@ -19,6 +19,11 @@
  *   The instance definition. It is recommended to only use instance level
  *   properties to filter out values from a list defined by field level
  *   properties.
+ * @param $entity_type
+ *   The entity type the field is attached to.
+ * @param $entity
+ *   The entity object the field is attached to, or NULL if no entity
+ *   exists (e.g. in field settings page).
  *
  * @return
  *   The array of options for the field. Array keys are the values to be
@@ -29,7 +34,7 @@
  *   widget. The HTML tags defined in _field_filter_xss_allowed_tags() are
  *   allowed, other tags will be filtered.
  */
-function hook_options_list($field, $instance) {
+function hook_options_list($field, $instance, $entity_type, $entity) {
   // Sample structure.
   $options = array(
     0 => t('Zero'),
diff --git a/core/modules/field/modules/options/options.module b/core/modules/field/modules/options/options.module
index 15e1714000e7..3862ba778748 100644
--- a/core/modules/field/modules/options/options.module
+++ b/core/modules/field/modules/options/options.module
@@ -79,8 +79,11 @@ function options_field_widget_form(&$form, &$form_state, $field, $instance, $lan
   $has_value = isset($items[0][$value_key]);
   $properties = _options_properties($type, $multiple, $required, $has_value);
 
+  $entity_type = $element['#entity_type'];
+  $entity = $element['#entity'];
+
   // Prepare the list of options.
-  $options = _options_get_options($field, $instance, $properties);
+  $options = _options_get_options($field, $instance, $properties, $entity_type, $entity);
 
   // Put current field values in shape.
   $default_value = _options_storage_to_form($items, $options, $value_key, $properties);
@@ -237,9 +240,9 @@ function _options_properties($type, $multiple, $required, $has_value) {
 /**
  * Collects the options for a field.
  */
-function _options_get_options($field, $instance, $properties) {
+function _options_get_options($field, $instance, $properties, $entity_type, $entity) {
   // Get the list of options.
-  $options = (array) module_invoke($field['module'], 'options_list', $field, $instance);
+  $options = (array) module_invoke($field['module'], 'options_list', $field, $instance, $entity_type, $entity);
 
   // Sanitize the options.
   _options_prepare_options($options, $properties);
diff --git a/core/modules/field/modules/options/options.test b/core/modules/field/modules/options/options.test
index b945949ffdbf..be446cdf7b4a 100644
--- a/core/modules/field/modules/options/options.test
+++ b/core/modules/field/modules/options/options.test
@@ -519,3 +519,38 @@ class OptionsWidgetsTestCase extends FieldTestCase {
   }
 }
 
+/**
+ * Test an options select on a list field with a dynamic allowed values function.
+ */
+class OptionsSelectDynamicValuesTestCase extends ListDynamicValuesTestCase {
+  public static function getInfo() {
+    return array(
+      'name' => 'Options select dynamic values',
+      'description' => 'Test an options select on a list field with a dynamic allowed values function.',
+      'group' => 'Field types',
+    );
+  }
+
+  /**
+   * Tests the 'options_select' widget (single select).
+   */
+  function testSelectListDynamic() {
+    // Create an entity.
+    $this->entity->is_new = TRUE;
+    field_test_entity_save($this->entity);
+    // Create a web user.
+    $web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content'));
+    $this->drupalLogin($web_user);
+
+    // Display form.
+    $this->drupalGet('test-entity/manage/' . $this->entity->ftid . '/edit');
+    $options = $this->xpath('//select[@id="edit-test-list-und"]/option');
+    $this->assertEqual(count($options), count($this->test) + 1);
+    foreach ($options as $option) {
+      $value = (string) $option['value'];
+      if ($value != '_none') {
+        $this->assertTrue(array_search($value, $this->test));
+      }
+    }
+  }
+}
diff --git a/core/modules/taxonomy/taxonomy.module b/core/modules/taxonomy/taxonomy.module
index 81bda1d39857..6f7bc1e659f8 100644
--- a/core/modules/taxonomy/taxonomy.module
+++ b/core/modules/taxonomy/taxonomy.module
@@ -1124,7 +1124,7 @@ function taxonomy_field_widget_info_alter(&$info) {
 /**
  * Implements hook_options_list().
  */
-function taxonomy_options_list($field, $instance) {
+function taxonomy_options_list($field, $instance, $entity_type, $entity) {
   $function = !empty($field['settings']['options_list_callback']) ? $field['settings']['options_list_callback'] : 'taxonomy_allowed_values';
   return $function($field);
 }
-- 
GitLab