diff --git a/core/modules/field/field.form.inc b/core/modules/field/field.form.inc index 8faa22b..2ced5b0 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 c2bff2a..bccddff 100644 --- a/core/modules/field/modules/list/list.module +++ b/core/modules/field/modules/list/list.module @@ -221,24 +221,37 @@ function list_field_update_field($field, $prior_field, $has_data) { * * @param $field * The field definition. + * @param $instance + * A field instance array. + * @param $entity_type + * The type of entity; e.g. 'node' or 'user'. + * @param $entity + * The entity object. * * @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, $entity_type, $entity) { $allowed_values = &drupal_static(__FUNCTION__, array()); if (!isset($allowed_values[$field['id']])) { + // See list_test_dynamic_values_callback() for a dynamic example. $function = $field['settings']['allowed_values_function']; + $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 +386,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 +432,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 +460,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 988f6a3..935d061 100644 --- a/core/modules/field/modules/list/tests/list.test +++ b/core/modules/field/modules/list/tests/list.test @@ -113,6 +113,81 @@ class ListFieldTestCase extends FieldTestCase { } } +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); + } +} + +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) { + $entity->test_list[LANGUAGE_NOT_SPECIFIED][0]['value'] = $value; + try { + field_attach_validate('test_entity', $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) { + $entity->test_list[LANGUAGE_NOT_SPECIFIED][0]['value'] = is_numeric($value) ? (100 - $value) : ('X' . $value); + $pass = FALSE; + try { + field_attach_validate('test_entity', $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 8d53404..a60e369 100644 --- a/core/modules/field/modules/list/tests/list_test.module +++ b/core/modules/field/modules/list/tests/list_test.module @@ -21,3 +21,9 @@ function list_test_allowed_values_callback($field) { return $values; } + +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 d1ac0db..ce2ec62 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 15e1714..3862ba7 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 b945949..b3fb8f1 100644 --- a/core/modules/field/modules/options/options.test +++ b/core/modules/field/modules/options/options.test @@ -519,3 +519,35 @@ class OptionsWidgetsTestCase extends FieldTestCase { } } +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 an 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 81bda1d..6f7bc1e 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); }