summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--includes/common.inc24
-rw-r--r--includes/form.inc148
-rw-r--r--modules/field_ui/field_ui.admin.inc15
-rw-r--r--modules/simpletest/tests/common.test132
4 files changed, 234 insertions, 85 deletions
diff --git a/includes/common.inc b/includes/common.inc
index 07422d08d..bebe2ad52 100644
--- a/includes/common.inc
+++ b/includes/common.inc
@@ -5602,6 +5602,30 @@ function element_get_visible_children(array $elements) {
}
/**
+ * Sets HTML attributes based on element properties.
+ *
+ * @param $element
+ * The renderable element to process.
+ * @param $map
+ * An associative array whose keys are element property names and whose values
+ * are the HTML attribute names to set for corresponding the property; e.g.,
+ * array('#propertyname' => 'attributename'). If both names are identical
+ * except for the leading '#', then an attribute name value is sufficient and
+ * no property name needs to be specified.
+ */
+function element_set_attributes(array &$element, array $map) {
+ foreach ($map as $property => $attribute) {
+ // If the key is numeric, the attribute name needs to be taken over.
+ if (is_int($property)) {
+ $property = '#' . $attribute;
+ }
+ if (isset($element[$property])) {
+ $element['#attributes'][$attribute] = $element[$property];
+ }
+ }
+}
+
+/**
* Sets a value in a nested array with variable depth.
*
* This helper function should be used when the depth of the array element you
diff --git a/includes/form.inc b/includes/form.inc
index 59ed6517a..e42beea73 100644
--- a/includes/form.inc
+++ b/includes/form.inc
@@ -1414,17 +1414,20 @@ function form_get_errors() {
}
/**
- * Return the error message filed against the form with the specified name.
+ * Returns the error message filed against the given form element.
+ *
+ * Form errors higher up in the form structure override deeper errors as well as
+ * errors on the element itself.
*/
function form_get_error($element) {
$form = form_set_error();
- $key = $element['#parents'][0];
- if (isset($form[$key])) {
- return $form[$key];
- }
- $key = implode('][', $element['#parents']);
- if (isset($form[$key])) {
- return $form[$key];
+ $parents = array();
+ foreach ($element['#parents'] as $parent) {
+ $parents[] = $parent;
+ $key = implode('][', $parents);
+ if (isset($form[$key])) {
+ return $form[$key];
+ }
}
}
@@ -2251,10 +2254,15 @@ function _form_options_flatten($array) {
*/
function theme_select($variables) {
$element = $variables['element'];
- $size = $element['#size'] ? ' size="' . $element['#size'] . '"' : '';
+ element_set_attributes($element, array('id', 'name', 'size'));
_form_set_class($element, array('form-select'));
- $multiple = $element['#multiple'];
- return '<select name="' . $element['#name'] . '' . ($multiple ? '[]' : '') . '"' . ($multiple ? ' multiple="multiple" ' : '') . drupal_attributes($element['#attributes']) . ' id="' . $element['#id'] . '" ' . $size . '>' . form_select_options($element) . '</select>';
+
+ if (!empty($element['#multiple'])) {
+ $element['#attributes']['multiple'] = 'multiple';
+ $element['#attributes']['name'] .= '[]';
+ }
+
+ return '<select' . drupal_attributes($element['#attributes']) . '>' . form_select_options($element) . '</select>';
}
/**
@@ -2276,7 +2284,7 @@ function form_select_options($element, $choices = NULL) {
// array_key_exists() accommodates the rare event where $element['#value'] is NULL.
// isset() fails in this situation.
$value_valid = isset($element['#value']) || array_key_exists('#value', $element);
- $value_is_array = is_array($element['#value']);
+ $value_is_array = $value_valid && is_array($element['#value']);
$options = '';
foreach ($choices as $key => $choice) {
if (is_array($choice)) {
@@ -2364,6 +2372,8 @@ function form_get_options($element, $key) {
*/
function theme_fieldset($variables) {
$element = $variables['element'];
+ element_set_attributes($element, array('id'));
+ _form_set_class($element, array('form-wrapper'));
$output = '<fieldset' . drupal_attributes($element['#attributes']) . '>';
if (!empty($element['#title'])) {
@@ -2397,10 +2407,9 @@ function theme_fieldset($variables) {
function theme_radio($variables) {
$element = $variables['element'];
$element['#attributes']['type'] = 'radio';
- $element['#attributes']['name'] = $element['#name'];
- $element['#attributes']['id'] = $element['#id'];
- $element['#attributes']['value'] = $element['#return_value'];
- if (check_plain($element['#value']) == $element['#return_value']) {
+ element_set_attributes($element, array('id', 'name', '#return_value' => 'value'));
+
+ if (isset($element['#return_value']) && check_plain($element['#value']) == $element['#return_value']) {
$element['#attributes']['checked'] = 'checked';
}
_form_set_class($element, array('form-radio'));
@@ -2422,7 +2431,7 @@ function theme_radio($variables) {
function theme_radios($variables) {
$element = $variables['element'];
$attributes = array();
- if (!empty($element['#id'])) {
+ if (isset($element['#id'])) {
$attributes['id'] = $element['#id'];
}
$attributes['class'] = 'form-radios';
@@ -2507,9 +2516,11 @@ function theme_date($variables) {
function form_process_date($element) {
// Default to current date
if (empty($element['#value'])) {
- $element['#value'] = array('day' => format_date(REQUEST_TIME, 'custom', 'j'),
- 'month' => format_date(REQUEST_TIME, 'custom', 'n'),
- 'year' => format_date(REQUEST_TIME, 'custom', 'Y'));
+ $element['#value'] = array(
+ 'day' => format_date(REQUEST_TIME, 'custom', 'j'),
+ 'month' => format_date(REQUEST_TIME, 'custom', 'n'),
+ 'year' => format_date(REQUEST_TIME, 'custom', 'Y'),
+ );
}
$element['#tree'] = TRUE;
@@ -2529,9 +2540,11 @@ function form_process_date($element) {
case 'day':
$options = drupal_map_assoc(range(1, 31));
break;
+
case 'month':
$options = drupal_map_assoc(range(1, 12), 'map_month');
break;
+
case 'year':
$options = drupal_map_assoc(range(1900, 2050));
break;
@@ -2632,11 +2645,10 @@ function theme_checkbox($variables) {
$element = $variables['element'];
$t = get_t();
$element['#attributes']['type'] = 'checkbox';
- $element['#attributes']['name'] = $element['#name'];
- $element['#attributes']['id'] = $element['#id'];
- $element['#attributes']['value'] = $element['#return_value'];
+ element_set_attributes($element, array('id', 'name', '#return_value' => 'value'));
+
// Unchecked checkbox has #value of integer 0.
- if ($element['#value'] !== 0 && $element['#value'] == $element['#return_value']) {
+ if (isset($element['#return_value']) && isset($element['#value']) && $element['#value'] !== 0 && $element['#value'] == $element['#return_value']) {
$element['#attributes']['checked'] = 'checked';
}
_form_set_class($element, array('form-checkbox'));
@@ -2657,12 +2669,12 @@ function theme_checkbox($variables) {
function theme_checkboxes($variables) {
$element = $variables['element'];
$attributes = array();
- if (!empty($element['#id'])) {
+ if (isset($element['#id'])) {
$attributes['id'] = $element['#id'];
}
- $attributes['class'] = 'form-checkboxes';
+ $attributes['class'][] = 'form-checkboxes';
if (!empty($element['#attributes']['class'])) {
- $attributes['class'] .= ' ' . implode(' ', $element['#attributes']['class']);
+ $attributes['class'] = array_merge($attributes['class'], $element['#attributes']['class']);
}
return '<div' . drupal_attributes($attributes) . '>' . (!empty($element['#children']) ? $element['#children'] : '') . '</div>';
}
@@ -2938,7 +2950,6 @@ function form_process_fieldset(&$element, &$form_state) {
if (!isset($element['#attributes']['class'])) {
$element['#attributes']['class'] = array();
}
- $element['#attributes']['class'][] = 'form-wrapper';
// Collapsible fieldsets
if (!empty($element['#collapsible'])) {
@@ -2948,7 +2959,6 @@ function form_process_fieldset(&$element, &$form_state) {
$element['#attributes']['class'][] = 'collapsed';
}
}
- $element['#attributes']['id'] = $element['#id'];
return $element;
}
@@ -2964,6 +2974,10 @@ function form_process_fieldset(&$element, &$form_state) {
* The modified element with all group members.
*/
function form_pre_render_fieldset($element) {
+ // Fieldsets may be rendered outside of a Form API context.
+ if (!isset($element['#parents']) || !isset($element['#groups'])) {
+ return $element;
+ }
// Inject group member elements belonging to this group.
$parents = implode('][', $element['#parents']);
$children = element_children($element['#groups'][$parents]);
@@ -3073,8 +3087,7 @@ function theme_vertical_tabs($variables) {
* @ingroup themeable
*/
function theme_submit($variables) {
- $element = $variables['element'];
- return theme('button', $element);
+ return theme('button', $variables['element']);
}
/**
@@ -3090,11 +3103,8 @@ function theme_submit($variables) {
function theme_button($variables) {
$element = $variables['element'];
$element['#attributes']['type'] = 'submit';
- if (!empty($element['#name'])) {
- $element['#attributes']['name'] = $element['#name'];
- }
- $element['#attributes']['id'] = $element['#id'];
- $element['#attributes']['value'] = $element['#value'];
+ element_set_attributes($element, array('id', 'name', 'value'));
+
$element['#attributes']['class'][] = 'form-' . $element['#button_type'];
if (!empty($element['#attributes']['disabled'])) {
$element['#attributes']['class'][] = 'form-button-disabled';
@@ -3116,11 +3126,8 @@ function theme_button($variables) {
function theme_image_button($variables) {
$element = $variables['element'];
$element['#attributes']['type'] = 'image';
- $element['#attributes']['name'] = $element['#name'];
- if (!empty($element['#value'])) {
- $element['#attributes']['value'] = $element['#value'];
- }
- $element['#attributes']['id'] = $element['#id'];
+ element_set_attributes($element, array('id', 'name', 'value'));
+
$element['#attributes']['src'] = file_create_url($element['#src']);
if (!empty($element['#title'])) {
$element['#attributes']['alt'] = $element['#title'];
@@ -3148,9 +3155,7 @@ function theme_image_button($variables) {
function theme_hidden($variables) {
$element = $variables['element'];
$element['#attributes']['type'] = 'hidden';
- $element['#attributes']['name'] = $element['#name'];
- $element['#attributes']['id'] = $element['#id'];
- $element['#attributes']['value'] = $element['#value'];
+ element_set_attributes($element, array('id', 'name', 'value'));
return '<input' . drupal_attributes($element['#attributes']) . " />\n";
}
@@ -3168,15 +3173,7 @@ function theme_hidden($variables) {
function theme_textfield($variables) {
$element = $variables['element'];
$element['#attributes']['type'] = 'text';
- $element['#attributes']['name'] = $element['#name'];
- $element['#attributes']['id'] = $element['#id'];
- $element['#attributes']['value'] = $element['#value'];
- if (!empty($element['#size'])) {
- $element['#attributes']['size'] = $element['#size'];
- }
- if (!empty($element['#maxlength'])) {
- $element['#attributes']['maxlength'] = $element['#maxlength'];
- }
+ element_set_attributes($element, array('id', 'name', 'value', 'size', 'maxlength'));
_form_set_class($element, array('form-text'));
$extra = '';
@@ -3186,7 +3183,7 @@ function theme_textfield($variables) {
$attributes = array();
$attributes['type'] = 'hidden';
- $attributes['id'] = $element['#id'] . '-autocomplete';
+ $attributes['id'] = $element['#attributes']['id'] . '-autocomplete';
$attributes['value'] = url($element['#autocomplete_path'], array('absolute' => TRUE));
$attributes['disabled'] = 'disabled';
$attributes['class'][] = 'autocomplete';
@@ -3210,14 +3207,13 @@ function theme_textfield($variables) {
*/
function theme_form($variables) {
$element = $variables['element'];
- if (!empty($element['#action'])) {
+ if (isset($element['#action'])) {
$element['#attributes']['action'] = drupal_strip_dangerous_protocols($element['#action']);
}
- $element['#attributes']['method'] = $element['#method'];
+ element_set_attributes($element, array('method', 'id'));
if (empty($element['#attributes']['accept-charset'])) {
$element['#attributes']['accept-charset'] = "UTF-8";
}
- $element['#attributes']['id'] = $element['#id'];
// Anonymous DIV to satisfy XHTML compliance.
return '<form' . drupal_attributes($element['#attributes']) . '><div>' . $element['#children'] . '</div></form>';
}
@@ -3235,10 +3231,7 @@ function theme_form($variables) {
*/
function theme_textarea($variables) {
$element = $variables['element'];
- $element['#attributes']['name'] = $element['#name'];
- $element['#attributes']['id'] = $element['#id'];
- $element['#attributes']['cols'] = $element['#cols'];
- $element['#attributes']['rows'] = $element['#rows'];
+ element_set_attributes($element, array('id', 'name', 'cols', 'rows'));
_form_set_class($element, array('form-textarea'));
$wrapper_attributes = array(
@@ -3271,15 +3264,7 @@ function theme_textarea($variables) {
function theme_password($variables) {
$element = $variables['element'];
$element['#attributes']['type'] = 'password';
- $element['#attributes']['name'] = $element['#name'];
- $element['#attributes']['id'] = $element['#id'];
- $element['#attributes']['value'] = $element['#value'];
- if (!empty($element['#size'])) {
- $element['#attributes']['size'] = $element['#size'];
- }
- if (!empty($element['#maxlength'])) {
- $element['#attributes']['maxlength'] = $element['#maxlength'];
- }
+ element_set_attributes($element, array('id', 'name', 'value', 'size', 'maxlength'));
_form_set_class($element, array('form-text'));
return '<input' . drupal_attributes($element['#attributes']) . ' />';
@@ -3316,11 +3301,7 @@ function form_process_weight($element) {
function theme_file($variables) {
$element = $variables['element'];
$element['#attributes']['type'] = 'file';
- $element['#attributes']['name'] = $element['#name'];
- $element['#attributes']['id'] = $element['#id'];
- if (!empty($element['#size'])) {
- $element['#attributes']['size'] = $element['#size'];
- }
+ element_set_attributes($element, array('id', 'name', 'size'));
_form_set_class($element, array('form-file'));
return '<input' . drupal_attributes($element['#attributes']) . ' />';
@@ -3373,10 +3354,16 @@ function theme_file($variables) {
* @ingroup themeable
*/
function theme_form_element($variables) {
- $element = $variables['element'];
+ $element = &$variables['element'];
// This is also used in the installer, pre-database setup.
$t = get_t();
+ // This function is invoked as theme wrapper, but the rendered form element
+ // may not necessarily have been processed by form_builder().
+ $element += array(
+ '#title_display' => 'before',
+ );
+
// Add element #id for #type 'item'.
if (isset($element['#markup']) && !empty($element['#id'])) {
$attributes['id'] = $element['#id'];
@@ -3422,7 +3409,7 @@ function theme_form_element($variables) {
}
if (!empty($element['#description'])) {
- $output .= ' <div class="description">' . $element['#description'] . "</div>\n";
+ $output .= '<div class="description">' . $element['#description'] . "</div>\n";
}
$output .= "</div>\n";
@@ -3521,10 +3508,13 @@ function _form_set_class(&$element, $class = array()) {
}
$element['#attributes']['class'] = array_merge($element['#attributes']['class'], $class);
}
- if ($element['#required']) {
+ // This function is invoked from form element theme functions, but the
+ // rendered form element may not necessarily have been processed by
+ // form_builder().
+ if (!empty($element['#required'])) {
$element['#attributes']['class'][] = 'required';
}
- if (form_get_error($element)) {
+ if (isset($element['#parents']) && form_get_error($element)) {
$element['#attributes']['class'][] = 'error';
}
}
diff --git a/modules/field_ui/field_ui.admin.inc b/modules/field_ui/field_ui.admin.inc
index cf3e8c3e9..dcfd7eca4 100644
--- a/modules/field_ui/field_ui.admin.inc
+++ b/modules/field_ui/field_ui.admin.inc
@@ -1648,13 +1648,22 @@ function field_ui_field_edit_form($form, &$form_state, $instance) {
$bundles = field_info_bundles();
// Create a form structure for the instance values.
- $form['instance'] = array(
+ // @todo Fieldset element info needs to be merged in order to not skip the
+ // default element definition for #pre_render. While the current default
+ // value could simply be hard-coded, we'd possibly forget this location
+ // when system_element_info() is updated. See also form_builder(). This
+ // particular #pre_render, field_ui_field_edit_instance_pre_render(), might
+ // as well be entirely needless though.
+ $form['instance'] = array_merge(element_info('fieldset'), array(
'#tree' => TRUE,
'#type' => 'fieldset',
'#title' => t('%type settings', array('%type' => $bundles[$entity_type][$bundle]['label'])),
- '#description' => t('These settings apply only to the %field field when used in the %type type.', array('%field' => $instance['label'], '%type' => $bundles[$entity_type][$bundle]['label'])),
+ '#description' => t('These settings apply only to the %field field when used in the %type type.', array(
+ '%field' => $instance['label'],
+ '%type' => $bundles[$entity_type][$bundle]['label'],
+ )),
'#pre_render' => array('field_ui_field_edit_instance_pre_render'),
- );
+ ));
// Build the non-configurable instance values.
$form['instance']['field_name'] = array(
diff --git a/modules/simpletest/tests/common.test b/modules/simpletest/tests/common.test
index bcec70f0c..c90a59004 100644
--- a/modules/simpletest/tests/common.test
+++ b/modules/simpletest/tests/common.test
@@ -1389,11 +1389,11 @@ class JavaScriptTestCase extends DrupalWebTestCase {
/**
* Tests for drupal_render().
*/
-class DrupalRenderUnitTestCase extends DrupalWebTestCase {
+class DrupalRenderTestCase extends DrupalWebTestCase {
public static function getInfo() {
return array(
- 'name' => 'Drupal render',
- 'description' => 'Performs unit tests on drupal_render().',
+ 'name' => 'drupal_render()',
+ 'description' => 'Performs functional tests on drupal_render().',
'group' => 'System',
);
}
@@ -1470,6 +1470,132 @@ class DrupalRenderUnitTestCase extends DrupalWebTestCase {
// Test that passing arguments to the theme function works.
$this->assertEqual(drupal_render($element), $element['#foo'] . $element['#bar'], 'Passing arguments to theme functions works');
}
+
+ /**
+ * Test rendering form elements without passing through form_builder().
+ */
+ function testDrupalRenderFormElements() {
+ // Define a series of form elements.
+ $element = array(
+ '#type' => 'button',
+ '#value' => $this->randomName(),
+ );
+ $this->assertRenderedElement($element, '//input[@type=:type]', array(':type' => 'submit'));
+
+ $element = array(
+ '#type' => 'textfield',
+ '#title' => $this->randomName(),
+ '#value' => $this->randomName(),
+ );
+ $this->assertRenderedElement($element, '//input[@type=:type]', array(':type' => 'text'));
+
+ $element = array(
+ '#type' => 'password',
+ '#title' => $this->randomName(),
+ );
+ $this->assertRenderedElement($element, '//input[@type=:type]', array(':type' => 'password'));
+
+ $element = array(
+ '#type' => 'textarea',
+ '#title' => $this->randomName(),
+ '#value' => $this->randomName(),
+ );
+ $this->assertRenderedElement($element, '//textarea');
+
+ $element = array(
+ '#type' => 'radio',
+ '#title' => $this->randomName(),
+ '#value' => FALSE,
+ );
+ $this->assertRenderedElement($element, '//input[@type=:type]', array(':type' => 'radio'));
+
+ $element = array(
+ '#type' => 'checkbox',
+ '#title' => $this->randomName(),
+ );
+ $this->assertRenderedElement($element, '//input[@type=:type]', array(':type' => 'checkbox'));
+
+ $element = array(
+ '#type' => 'select',
+ '#title' => $this->randomName(),
+ '#options' => array(
+ 0 => $this->randomName(),
+ 1 => $this->randomName(),
+ ),
+ );
+ $this->assertRenderedElement($element, '//select');
+
+ $element = array(
+ '#type' => 'file',
+ '#title' => $this->randomName(),
+ );
+ $this->assertRenderedElement($element, '//input[@type=:type]', array(':type' => 'file'));
+
+ $element = array(
+ '#type' => 'item',
+ '#title' => $this->randomName(),
+ '#markup' => $this->randomName(),
+ );
+ $this->assertRenderedElement($element, '//div[contains(@class, :class) and contains(., :markup)]/label[contains(., :label)]', array(
+ ':class' => 'form-type-item',
+ ':markup' => $element['#markup'],
+ ':label' => $element['#title'],
+ ));
+
+ $element = array(
+ '#type' => 'hidden',
+ '#title' => $this->randomName(),
+ '#value' => $this->randomName(),
+ );
+ $this->assertRenderedElement($element, '//input[@type=:type]', array(':type' => 'hidden'));
+
+ $element = array(
+ '#type' => 'link',
+ '#title' => $this->randomName(),
+ '#href' => $this->randomName(),
+ '#options' => array(
+ 'absolute' => TRUE,
+ ),
+ );
+ $this->assertRenderedElement($element, '//a[@href=:href and contains(., :title)]', array(
+ ':href' => url($element['#href'], array('absolute' => TRUE)),
+ ':title' => $element['#title'],
+ ));
+
+ $element = array(
+ '#type' => 'fieldset',
+ '#title' => $this->randomName(),
+ );
+ $this->assertRenderedElement($element, '//fieldset/legend[contains(., :title)]', array(
+ ':title' => $element['#title'],
+ ));
+
+ $element['item'] = array(
+ '#type' => 'item',
+ '#title' => $this->randomName(),
+ '#markup' => $this->randomName(),
+ );
+ $this->assertRenderedElement($element, '//fieldset/div/div[contains(@class, :class) and contains(., :markup)]', array(
+ ':class' => 'form-type-item',
+ ':markup' => $element['item']['#markup'],
+ ));
+ }
+
+ protected function assertRenderedElement(array $element, $xpath, array $xpath_args = array()) {
+ $original_element = $element;
+ $this->drupalSetContent(drupal_render($element));
+ $this->verbose('<pre>' . check_plain(var_export($original_element, TRUE)) . '</pre>'
+ . '<pre>' . check_plain(var_export($element, TRUE)) . '</pre>'
+ . '<hr />' . $this->drupalGetContent()
+ );
+
+ // @see DrupalWebTestCase::xpath()
+ $xpath = $this->buildXPathQuery($xpath, $xpath_args);
+ $element += array('#value' => NULL);
+ $this->assertFieldByXPath($xpath, $element['#value'], t('#type @type was properly rendered.', array(
+ '@type' => var_export($element['#type'], TRUE),
+ )));
+ }
}
/**