Commit b94bb8d8 authored by webchick's avatar webchick
Browse files

Issue #1821906 by Wim Leers: Allow more Field API public API functions to act...

Issue #1821906 by Wim Leers: Allow more Field API public API functions to act on a single field within an entity.
parent a755ea6b
......@@ -832,16 +832,19 @@ function _field_invoke_formatter_target($display) {
* @param $langcode
* The language the field values are going to be entered, if no language
* is provided the default site language will be used.
* @param array $options
* An associative array of additional options. See field_invoke_method() for
* details.
*
* @see field_form_get_state()
* @see field_form_set_state()
*/
function field_attach_form($entity_type, EntityInterface $entity, &$form, &$form_state, $langcode = NULL) {
function field_attach_form($entity_type, EntityInterface $entity, &$form, &$form_state, $langcode = NULL, array $options = array()) {
// Set #parents to 'top-level' by default.
$form += array('#parents' => array());
// If no language is provided use the default site language.
$options = array('langcode' => field_valid_language($langcode));
$options['langcode'] = field_valid_language($langcode);
$form += (array) field_invoke_method('form', _field_invoke_widget_target(), $entity, $form, $form_state, $options);
// Add custom weight handling.
......@@ -1041,13 +1044,17 @@ function field_attach_load_revision($entity_type, $entities, $options = array())
* If validation errors are found, a FieldValidationException is thrown. The
* 'errors' property contains the array of errors, keyed by field name,
* language and delta.
* @param array $options
* An associative array of additional options. See field_invoke_method() for
* details.
*/
function field_attach_validate($entity_type, $entity) {
function field_attach_validate($entity_type, $entity, array $options = array()) {
$errors = array();
// Check generic, field-type-agnostic errors first.
_field_invoke_default('validate', $entity_type, $entity, $errors);
$null = NULL;
_field_invoke_default('validate', $entity_type, $entity, $errors, $null, $options);
// Check field-type specific errors.
_field_invoke('validate', $entity_type, $entity, $errors);
_field_invoke('validate', $entity_type, $entity, $errors, $null, $options);
// Let other modules validate the entity.
// Avoid module_invoke_all() to let $errors be taken by reference.
......@@ -1089,11 +1096,14 @@ function field_attach_validate($entity_type, $entity) {
* full form structure, or a sub-element of a larger form.
* @param $form_state
* An associative array containing the current state of the form.
* @param array $options
* An associative array of additional options. See field_invoke_method() for
* details.
*/
function field_attach_form_validate($entity_type, EntityInterface $entity, $form, &$form_state) {
function field_attach_form_validate($entity_type, EntityInterface $entity, $form, &$form_state, array $options = array()) {
// Perform field_level validation.
try {
field_attach_validate($entity_type, $entity);
field_attach_validate($entity_type, $entity, $options);
}
catch (FieldValidationException $e) {
// Pass field-level validation errors back to widgets for accurate error
......@@ -1105,7 +1115,7 @@ function field_attach_form_validate($entity_type, EntityInterface $entity, $form
field_form_set_state($form['#parents'], $field_name, $langcode, $form_state, $field_state);
}
}
field_invoke_method('flagErrors', _field_invoke_widget_target(), $entity, $form, $form_state);
field_invoke_method('flagErrors', _field_invoke_widget_target(), $entity, $form, $form_state, $options);
}
}
......@@ -1126,10 +1136,13 @@ function field_attach_form_validate($entity_type, EntityInterface $entity, $form
* full form structure, or a sub-element of a larger form.
* @param $form_state
* An associative array containing the current state of the form.
* @param array $options
* An associative array of additional options. See field_invoke_method() for
* details.
*/
function field_attach_submit($entity_type, EntityInterface $entity, $form, &$form_state) {
function field_attach_submit($entity_type, EntityInterface $entity, $form, &$form_state, array $options = array()) {
// Extract field values from submitted values.
field_invoke_method('submit', _field_invoke_widget_target(), $entity, $form, $form_state);
field_invoke_method('submit', _field_invoke_widget_target(), $entity, $form, $form_state, $options);
// Let other modules act on submitting the entity.
// Avoid module_invoke_all() to let $form_state be taken by reference.
......@@ -1351,9 +1364,12 @@ function field_attach_delete_revision($entity_type, $entity) {
* @param $langcode
* (Optional) The language the field values are to be shown in. If no language
* is provided the current language is used.
* @param array $options
* An associative array of additional options. See field_invoke_method() for
* details.
*/
function field_attach_prepare_view($entity_type, $entities, $view_mode, $langcode = NULL) {
$options = array('langcode' => array());
function field_attach_prepare_view($entity_type, $entities, $view_mode, $langcode = NULL, array $options = array()) {
$options['langcode'] = array();
// To ensure hooks are only run once per entity, only process items without
// the _field_view_prepared flag.
......@@ -1422,14 +1438,16 @@ function field_attach_prepare_view($entity_type, $entities, $view_mode, $langcod
* @param $langcode
* The language the field values are to be shown in. If no language is
* provided the current language is used.
* @param array $options
* An associative array of additional options. See field_invoke_method() for
* details.
* @return
* A renderable array for the field values.
*/
function field_attach_view($entity_type, EntityInterface $entity, $view_mode, $langcode = NULL) {
function field_attach_view($entity_type, EntityInterface $entity, $view_mode, $langcode = NULL, array $options = array()) {
// Determine the actual language code to display for each field, given the
// language codes available in the field data.
$display_langcode = field_language($entity_type, $entity, NULL, $langcode);
$options = array('langcode' => $display_langcode);
$options['langcode'] = field_language($entity_type, $entity, NULL, $langcode);
// Invoke field_default_view().
$null = NULL;
......
......@@ -25,13 +25,18 @@ public static function getInfo() {
* Test field_attach_view() and field_attach_prepare_view().
*/
function testFieldAttachView() {
$this->createFieldWithInstance('_2');
$entity_type = 'test_entity';
$entity_init = field_test_create_entity();
$langcode = LANGUAGE_NOT_SPECIFIED;
$options = array('field_name' => $this->field_name_2);
// Populate values to be displayed.
$values = $this->_generateTestFieldValues($this->field['cardinality']);
$entity_init->{$this->field_name}[$langcode] = $values;
$values_2 = $this->_generateTestFieldValues($this->field_2['cardinality']);
$entity_init->{$this->field_name_2}[$langcode] = $values_2;
// Simple formatter, label displayed.
$entity = clone($entity_init);
......@@ -46,15 +51,47 @@ function testFieldAttachView() {
),
);
field_update_instance($this->instance);
$formatter_setting_2 = $this->randomName();
$this->instance_2['display'] = array(
'full' => array(
'label' => 'above',
'type' => 'field_test_default',
'settings' => array(
'test_formatter_setting' => $formatter_setting_2,
)
),
);
field_update_instance($this->instance_2);
// View all fields.
field_attach_prepare_view($entity_type, array($entity->ftid => $entity), 'full');
$entity->content = field_attach_view($entity_type, $entity, 'full');
$output = drupal_render($entity->content);
$this->content = $output;
$this->assertRaw($this->instance['label'], "Label is displayed.");
$this->assertRaw($this->instance['label'], "First field's label is displayed.");
foreach ($values as $delta => $value) {
$this->content = $output;
$this->assertRaw("$formatter_setting|{$value['value']}", "Value $delta is displayed, formatter settings are applied.");
}
$this->assertRaw($this->instance_2['label'], "Second field's label is displayed.");
foreach ($values_2 as $delta => $value) {
$this->content = $output;
$this->assertRaw("$formatter_setting_2|{$value['value']}", "Value $delta is displayed, formatter settings are applied.");
}
// View single field (the second field).
field_attach_prepare_view($entity_type, array($entity->ftid => $entity), 'full', $langcode, $options);
$entity->content = field_attach_view($entity_type, $entity, 'full', $langcode, $options);
$output = drupal_render($entity->content);
$this->content = $output;
$this->assertNoRaw($this->instance['label'], "First field's label is not displayed.");
foreach ($values as $delta => $value) {
$this->content = $output;
$this->assertNoRaw("$formatter_setting|{$value['value']}", "Value $delta is displayed, formatter settings are applied.");
}
$this->assertRaw($this->instance_2['label'], "Second field's label is displayed.");
foreach ($values_2 as $delta => $value) {
$this->content = $output;
$this->assertRaw("$formatter_setting_2|{$value['value']}", "Value $delta is displayed, formatter settings are applied.");
}
// Label hidden.
$entity = clone($entity_init);
......@@ -81,7 +118,7 @@ function testFieldAttachView() {
$this->content = $output;
$this->assertNoRaw($this->instance['label'], "Hidden field: label is not displayed.");
foreach ($values as $delta => $value) {
$this->assertNoRaw($value['value'], "Hidden field: value $delta is not displayed.");
$this->assertNoRaw("$formatter_setting|{$value['value']}", "Hidden field: value $delta is not displayed.");
}
// Multiple formatter.
......@@ -291,19 +328,29 @@ function testFieldAttachCache() {
* hook_field_validate.
*/
function testFieldAttachValidate() {
$this->createFieldWithInstance('_2');
$entity_type = 'test_entity';
$entity = field_test_create_entity(0, 0, $this->instance['bundle']);
$langcode = LANGUAGE_NOT_SPECIFIED;
// Set up values to generate errors
// Set up all but one values of the first field to generate errors.
$values = array();
for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
$values[$delta]['value'] = -1;
}
// Arrange for item 1 not to generate an error
// Arrange for item 1 not to generate an error.
$values[1]['value'] = 1;
$entity->{$this->field_name}[$langcode] = $values;
// Set up all values of the second field to generate errors.
$values_2 = array();
for ($delta = 0; $delta < $this->field_2['cardinality']; $delta++) {
$values_2[$delta]['value'] = -1;
}
$entity->{$this->field_name_2}[$langcode] = $values_2;
// Validate all fields.
try {
field_attach_validate($entity_type, $entity);
}
......@@ -313,26 +360,57 @@ function testFieldAttachValidate() {
foreach ($values as $delta => $value) {
if ($value['value'] != 1) {
$this->assertIdentical($errors[$this->field_name][$langcode][$delta][0]['error'], 'field_test_invalid', "Error set on value $delta");
$this->assertEqual(count($errors[$this->field_name][$langcode][$delta]), 1, "Only one error set on value $delta");
$this->assertIdentical($errors[$this->field_name][$langcode][$delta][0]['error'], 'field_test_invalid', "Error set on first field's value $delta");
$this->assertEqual(count($errors[$this->field_name][$langcode][$delta]), 1, "Only one error set on first field's value $delta");
unset($errors[$this->field_name][$langcode][$delta]);
}
else {
$this->assertFalse(isset($errors[$this->field_name][$langcode][$delta]), "No error set on value $delta");
$this->assertFalse(isset($errors[$this->field_name][$langcode][$delta]), "No error set on first field's value $delta");
}
}
$this->assertEqual(count($errors[$this->field_name][$langcode]), 0, 'No extraneous errors set');
foreach ($values_2 as $delta => $value) {
$this->assertIdentical($errors[$this->field_name_2][$langcode][$delta][0]['error'], 'field_test_invalid', "Error set on second field's value $delta");
$this->assertEqual(count($errors[$this->field_name_2][$langcode][$delta]), 1, "Only one error set on second field's value $delta");
unset($errors[$this->field_name_2][$langcode][$delta]);
}
$this->assertEqual(count($errors[$this->field_name][$langcode]), 0, 'No extraneous errors set for first field');
$this->assertEqual(count($errors[$this->field_name_2][$langcode]), 0, 'No extraneous errors set for second field');
// Validate a single field.
$options = array('field_name' => $this->field_name_2);
try {
field_attach_validate($entity_type, $entity, $options);
}
catch (FieldValidationException $e) {
$errors = $e->errors;
}
foreach ($values_2 as $delta => $value) {
$this->assertIdentical($errors[$this->field_name_2][$langcode][$delta][0]['error'], 'field_test_invalid', "Error set on second field's value $delta");
$this->assertEqual(count($errors[$this->field_name_2][$langcode][$delta]), 1, "Only one error set on second field's value $delta");
unset($errors[$this->field_name_2][$langcode][$delta]);
}
$this->assertFalse(isset($errors[$this->field_name]), 'No validation errors are set for the first field, despite it having errors');
$this->assertEqual(count($errors[$this->field_name_2][$langcode]), 0, 'No extraneous errors set for second field');
// Check that cardinality is validated.
$entity->{$this->field_name}[$langcode] = $this->_generateTestFieldValues($this->field['cardinality'] + 1);
$entity->{$this->field_name_2}[$langcode] = $this->_generateTestFieldValues($this->field_2['cardinality'] + 1);
// When validating all fields.
try {
field_attach_validate($entity_type, $entity);
}
catch (FieldValidationException $e) {
$errors = $e->errors;
}
$this->assertEqual($errors[$this->field_name][$langcode][0][0]['error'], 'field_cardinality', 'Cardinality validation failed.');
$this->assertEqual($errors[$this->field_name_2][$langcode][0][0]['error'], 'field_cardinality', 'Cardinality validation failed.');
// When validating a single field (the second field).
try {
field_attach_validate($entity_type, $entity, $options);
}
catch (FieldValidationException $e) {
$errors = $e->errors;
}
$this->assertEqual($errors[$this->field_name_2][$langcode][0][0]['error'], 'field_cardinality', 'Cardinality validation failed.');
}
/**
......@@ -342,18 +420,39 @@ function testFieldAttachValidate() {
* widgets show up.
*/
function testFieldAttachForm() {
$this->createFieldWithInstance('_2');
$entity_type = 'test_entity';
$entity = field_test_create_entity(0, 0, $this->instance['bundle']);
$langcode = LANGUAGE_NOT_SPECIFIED;
// When generating form for all fields.
$form = array();
$form_state = form_state_defaults();
field_attach_form($entity_type, $entity, $form, $form_state);
$langcode = LANGUAGE_NOT_SPECIFIED;
$this->assertEqual($form[$this->field_name][$langcode]['#title'], $this->instance['label'], "Form title is {$this->instance['label']}");
$this->assertEqual($form[$this->field_name][$langcode]['#title'], $this->instance['label'], "First field's form title is {$this->instance['label']}");
$this->assertEqual($form[$this->field_name_2][$langcode]['#title'], $this->instance_2['label'], "Second field's form title is {$this->instance_2['label']}");
for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
// field_test_widget uses 'textfield'
$this->assertEqual($form[$this->field_name][$langcode][$delta]['value']['#type'], 'textfield', "Form delta $delta widget is textfield");
$this->assertEqual($form[$this->field_name][$langcode][$delta]['value']['#type'], 'textfield', "First field's form delta $delta widget is textfield");
}
for ($delta = 0; $delta < $this->field_2['cardinality']; $delta++) {
// field_test_widget uses 'textfield'
$this->assertEqual($form[$this->field_name_2][$langcode][$delta]['value']['#type'], 'textfield', "Second field's form delta $delta widget is textfield");
}
// When generating form for a single field (the second field).
$options = array('field_name' => $this->field_name_2);
$form = array();
$form_state = form_state_defaults();
field_attach_form($entity_type, $entity, $form, $form_state, NULL, $options);
$this->assertFalse(isset($form[$this->field_name]), 'The first field does not exist in the form');
$this->assertEqual($form[$this->field_name_2][$langcode]['#title'], $this->instance_2['label'], "Second field's form title is {$this->instance_2['label']}");
for ($delta = 0; $delta < $this->field_2['cardinality']; $delta++) {
// field_test_widget uses 'textfield'
$this->assertEqual($form[$this->field_name_2][$langcode][$delta]['value']['#type'], 'textfield', "Second field's form delta $delta widget is textfield");
}
}
......@@ -361,15 +460,19 @@ function testFieldAttachForm() {
* Test field_attach_submit().
*/
function testFieldAttachSubmit() {
$this->createFieldWithInstance('_2');
$entity_type = 'test_entity';
$entity = field_test_create_entity(0, 0, $this->instance['bundle']);
$entity_init = field_test_create_entity(0, 0, $this->instance['bundle']);
$langcode = LANGUAGE_NOT_SPECIFIED;
// Build the form.
// Build the form for all fields.
$form = array();
$form_state = form_state_defaults();
field_attach_form($entity_type, $entity, $form, $form_state);
field_attach_form($entity_type, $entity_init, $form, $form_state);
// Simulate incoming values.
// First field.
$values = array();
$weights = array();
for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
......@@ -383,21 +486,59 @@ function testFieldAttachSubmit() {
}
// Leave an empty value. 'field_test' fields are empty if empty().
$values[1]['value'] = 0;
// Second field.
$values_2 = array();
$weights_2 = array();
for ($delta = 0; $delta < $this->field_2['cardinality']; $delta++) {
$values_2[$delta]['value'] = mt_rand(1, 127);
// Assign random weight.
do {
$weight = mt_rand(0, $this->field_2['cardinality']);
} while (in_array($weight, $weights_2));
$weights_2[$delta] = $weight;
$values_2[$delta]['_weight'] = $weight;
}
// Leave an empty value. 'field_test' fields are empty if empty().
$values_2[1]['value'] = 0;
$langcode = LANGUAGE_NOT_SPECIFIED;
// Pretend the form has been built.
drupal_prepare_form('field_test_entity_form', $form, $form_state);
drupal_process_form('field_test_entity_form', $form, $form_state);
$form_state['values'][$this->field_name][$langcode] = $values;
$form_state['values'][$this->field_name_2][$langcode] = $values_2;
// Call field_attach_submit() for all fields.
$entity = clone($entity_init);
field_attach_submit($entity_type, $entity, $form, $form_state);
asort($weights);
asort($weights_2);
$expected_values = array();
$expected_values_2 = array();
foreach ($weights as $key => $value) {
if ($key != 1) {
$expected_values[] = array('value' => $values[$key]['value']);
}
}
$this->assertIdentical($entity->{$this->field_name}[$langcode], $expected_values, 'Submit filters empty values');
foreach ($weights_2 as $key => $value) {
if ($key != 1) {
$expected_values_2[] = array('value' => $values_2[$key]['value']);
}
}
$this->assertIdentical($entity->{$this->field_name_2}[$langcode], $expected_values_2, 'Submit filters empty values');
// Call field_attach_submit() for a single field (the second field).
$options = array('field_name' => $this->field_name_2);
$entity = clone($entity_init);
field_attach_submit($entity_type, $entity, $form, $form_state, $options);
$expected_values_2 = array();
foreach ($weights_2 as $key => $value) {
if ($key != 1) {
$expected_values_2[] = array('value' => $values_2[$key]['value']);
}
}
$this->assertFalse(isset($entity->{$this->field_name}), 'The first field does not exist in the entity object');
$this->assertIdentical($entity->{$this->field_name_2}[$langcode], $expected_values_2, 'Submit filters empty values');
}
}
......@@ -19,12 +19,28 @@ abstract class FieldAttachTestBase extends FieldTestBase {
function setUp() {
parent::setUp();
$this->field_name = drupal_strtolower($this->randomName() . '_field_name');
$this->field = array('field_name' => $this->field_name, 'type' => 'test_field', 'cardinality' => 4);
$this->field = field_create_field($this->field);
$this->field_id = $this->field['id'];
$this->instance = array(
'field_name' => $this->field_name,
$this->createFieldWithInstance();
}
/**
* Create a field and an instance of it.
*
* @param string $suffix
* (optional) A string that should only contain characters that are valid in
* PHP variable names as well.
*/
function createFieldWithInstance($suffix = '') {
$field_name = 'field_name' . $suffix;
$field = 'field' . $suffix;
$field_id = 'field_id' . $suffix;
$instance = 'instance' . $suffix;
$this->$field_name = drupal_strtolower($this->randomName() . '_field_name' . $suffix);
$this->$field = array('field_name' => $this->$field_name, 'type' => 'test_field', 'cardinality' => 4);
$this->$field = field_create_field($this->$field);
$this->$field_id = $this->{$field}['id'];
$this->$instance = array(
'field_name' => $this->$field_name,
'entity_type' => 'test_entity',
'bundle' => 'test_bundle',
'label' => $this->randomName() . '_label',
......@@ -41,6 +57,6 @@ function setUp() {
)
)
);
field_create_instance($this->instance);
field_create_instance($this->$instance);
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment