diff --git a/core/includes/common.inc b/core/includes/common.inc index b97cc5e..0a3c96a 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -1926,18 +1926,7 @@ function drupal_html_id($id) { } $seen_ids = &drupal_static(__FUNCTION__, $seen_ids_init); - $id = strtr(drupal_strtolower($id), array(' ' => '-', '_' => '-', '[' => '-', ']' => '')); - - // As defined in http://www.w3.org/TR/html4/types.html#type-name, HTML IDs can - // only contain letters, digits ([0-9]), hyphens ("-"), underscores ("_"), - // colons (":"), and periods ("."). We strip out any character not in that - // list. Note that the CSS spec doesn't allow colons or periods in identifiers - // (http://www.w3.org/TR/CSS21/syndata.html#characters), so we strip those two - // characters as well. - $id = preg_replace('/[^A-Za-z0-9\-_]/', '', $id); - - // Removing multiple consecutive hyphens. - $id = preg_replace('/\-+/', '-', $id); + $id = drupal_clean_id_identifier($id); // Ensure IDs are unique by appending a counter after the first occurrence. // The counter needs to be appended with a delimiter that does not exist in // the base ID. Requiring a unique delimiter helps ensure that we really do @@ -1954,6 +1943,36 @@ function drupal_html_id($id) { } /** + * Prepares a string for use as a valid HTML ID. + * + * Only use this function when you want to intentionally skip the uniqueness + * guarantee of drupal_html_id(). + * + * @param string $id + * The ID to clean. + * + * @return string + * The cleaned ID. + * + * @see drupal_html_id() + */ +function drupal_clean_id_identifier($id) { + $id = strtr(drupal_strtolower($id), array(' ' => '-', '_' => '-', '[' => '-', ']' => '')); + + // As defined in http://www.w3.org/TR/html4/types.html#type-name, HTML IDs can + // only contain letters, digits ([0-9]), hyphens ("-"), underscores ("_"), + // colons (":"), and periods ("."). We strip out any character not in that + // list. Note that the CSS spec doesn't allow colons or periods in identifiers + // (http://www.w3.org/TR/CSS21/syndata.html#characters), so we strip those two + // characters as well. + $id = preg_replace('/[^A-Za-z0-9\-_]/', '', $id); + + // Removing multiple consecutive hyphens. + $id = preg_replace('/\-+/', '-', $id); + return $id; +} + +/** * Adds a JavaScript file, setting, or inline code to the page. * * The behavior of this function depends on the parameters it is called with. diff --git a/core/includes/form.inc b/core/includes/form.inc index 5d4a943..65c15f2 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -1697,52 +1697,81 @@ function form_process_table($element, &$form_state) { // tableselect element behaves as if it had been of #type checkboxes or // radios. foreach (element_children($element) as $key) { + $row = &$element[$key]; + // Prepare the element #parents for the tableselect form element. + // Their values have to be located in child keys (#tree is ignored), since + // form_validate_table() has to be able to validate whether input (for the + // parent #type 'table' element) has been submitted. + $element_parents = array_merge($element['#parents'], array($key)); + + // Since the #parents of the tableselect form element will equal the + // #parents of the row element, prevent FormBuilder from auto-generating + // an #id for the row element, since drupal_html_id() would automatically + // append a suffix to the tableselect form element's #id otherwise. + $row['#id'] = drupal_html_id('edit-' . implode('-', $element_parents) . '-row'); + // Do not overwrite manually created children. - if (!isset($element[$key]['select'])) { + if (!isset($row['select'])) { // Determine option label; either an assumed 'title' column, or the // first available column containing a #title or #markup. // @todo Consider to add an optional $element[$key]['#title_key'] // defaulting to 'title'? - $title = ''; - if (!empty($element[$key]['title']['#title'])) { - $title = $element[$key]['title']['#title']; + unset($label_element); + $title = NULL; + if (isset($row['title']['#type']) && $row['title']['#type'] == 'label') { + $label_element = &$row['title']; } else { - foreach (element_children($element[$key]) as $column) { - if (isset($element[$key][$column]['#title'])) { - $title = $element[$key][$column]['#title']; - break; - } - if (isset($element[$key][$column]['#markup'])) { - $title = $element[$key][$column]['#markup']; - break; + if (!empty($row['title']['#title'])) { + $title = $row['title']['#title']; + } + else { + foreach (element_children($row) as $column) { + if (isset($row[$column]['#title'])) { + $title = $row[$column]['#title']; + break; + } + if (isset($row[$column]['#markup'])) { + $title = $row[$column]['#markup']; + break; + } } } - } - if ($title !== '') { - $title = t('Update !title', array('!title' => $title)); + if (isset($title) && $title !== '') { + $title = t('Update !title', array('!title' => $title)); + } } // Prepend the select column to existing columns. - $element[$key] = array('select' => array()) + $element[$key]; - $element[$key]['select'] += array( + $row = array('select' => array()) + $row; + $row['select'] += array( '#type' => $element['#multiple'] ? 'checkbox' : 'radio', - '#title' => $title, - '#title_display' => 'invisible', + '#id' => drupal_html_id('edit-' . implode('-', $element_parents)), // @todo If rows happen to use numeric indexes instead of string keys, // this results in a first row with $key === 0, which is always FALSE. '#return_value' => $key, '#attributes' => $element['#attributes'], + '#wrapper_attributes' => array( + 'class' => array('table-select'), + ), ); - $element_parents = array_merge($element['#parents'], array($key)); if ($element['#multiple']) { - $element[$key]['select']['#default_value'] = isset($value[$key]) ? $key : NULL; - $element[$key]['select']['#parents'] = $element_parents; + $row['select']['#default_value'] = isset($value[$key]) ? $key : NULL; + $row['select']['#parents'] = $element_parents; } else { - $element[$key]['select']['#default_value'] = ($element['#default_value'] == $key ? $key : NULL); - $element[$key]['select']['#parents'] = $element['#parents']; - $element[$key]['select']['#id'] = drupal_html_id('edit-' . implode('-', $element_parents)); + $row['select']['#default_value'] = ($element['#default_value'] == $key ? $key : NULL); + $row['select']['#parents'] = $element['#parents']; + } + if ($label_element) { + $label_element['#id'] = $row['select']['#id'] . '--label'; + $label_element['#for'] = $row['select']['#id']; + $row['select']['#attributes']['aria-labelledby'] = $label_element['#id']; + $row['select']['#title_display'] = 'none'; + } + else { + $row['select']['#title'] = $title; + $row['select']['#title_display'] = 'invisible'; } } } @@ -2866,7 +2895,17 @@ function theme_form_element_label($variables) { $attributes['class'] = 'visually-hidden'; } - if (!empty($element['#id'])) { + // A #for property of a dedicated #type 'label' element as precedence. + if (!empty($element['#for'])) { + $attributes['for'] = $element['#for']; + // A custom #id allows the referenced form input element to refer back to + // the label element; e.g., in the 'aria-labelledby' attribute. + if (!empty($element['#id'])) { + $attributes['id'] = $element['#id']; + } + } + // Otherwise, point to the #id of the form input element. + elseif (!empty($element['#id'])) { $attributes['for'] = $element['#id']; } diff --git a/core/modules/simpletest/css/simpletest.module.css b/core/modules/simpletest/css/simpletest.module.css index 86bd04b..611cc4a 100644 --- a/core/modules/simpletest/css/simpletest.module.css +++ b/core/modules/simpletest/css/simpletest.module.css @@ -3,7 +3,7 @@ #simpletest-form-table th.select-all { width: 1em; } -th.simpletest_test { +th.simpletest-test-label { width: 16em; } diff --git a/core/modules/simpletest/lib/Drupal/simpletest/Form/SimpletestTestForm.php b/core/modules/simpletest/lib/Drupal/simpletest/Form/SimpletestTestForm.php index c469b52..57e1a20 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/Form/SimpletestTestForm.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/Form/SimpletestTestForm.php @@ -7,6 +7,8 @@ namespace Drupal\simpletest\Form; +use Drupal\Component\Utility\SortArray; +use Drupal\Component\Utility\String; use Drupal\Core\Form\FormBase; /** @@ -51,8 +53,46 @@ public function buildForm(array $form, array &$form_state) { '#description' => $this->t('Select the test(s) or test group(s) you would like to run, and click Run tests.'), ); - $form['tests']['table'] = array( - '#theme' => 'simpletest_test_table', + $form['tests'] = array( + '#type' => 'table', + '#id' => 'simpletest-form-table', + '#tableselect' => TRUE, + '#header' => array( + array('data' => $this->t('Test'), 'class' => array('simpletest-test-label')), + array('data' => $this->t('Description'), 'class' => array('simpletest-test-description')), + ), + '#empty' => $this->t('No tests to display.'), + '#attached' => array( + 'library' => array( + array('simpletest', 'drupal.simpletest'), + ), + ), + ); + + // Define the images used to expand/collapse the test groups. + $image_collapsed = array( + '#theme' => 'image', + '#uri' => 'core/misc/menu-collapsed.png', + '#width' => '7', + '#height' => '7', + '#alt' => $this->t('Expand'), + '#title' => $this->t('Expand'), + '#suffix' => '(' . $this->t('Expand') . ')', + ); + $image_extended = array( + '#theme' => 'image', + '#uri' => 'core/misc/menu-expanded.png', + '#width' => '7', + '#height' => '7', + '#alt' => $this->t('Collapse'), + '#title' => $this->t('Collapse'), + '#suffix' => '(' . $this->t('Collapse') . ')', + ); + $js = array( + 'images' => array( + drupal_render($image_collapsed), + drupal_render($image_extended), + ), ); // Generate the list of tests arranged by group. @@ -61,28 +101,103 @@ public function buildForm(array $form, array &$form_state) { $form_state['storage']['PHPUnit'] = $groups['PHPUnit']; foreach ($groups as $group => $tests) { - $form['tests']['table'][$group] = array( - '#collapsed' => TRUE, + $form['tests'][$group] = array( + '#attributes' => array('class' => array('simpletest-group')), + ); + + // Make the class name safe for output on the page by replacing all + // non-word/decimal characters with a dash (-). + $group_class = 'module-' . strtolower(trim(preg_replace("/[^\w\d]/", "-", $group))); + + // Override tableselect column with custom selector for this group. + // This group-select-all checkbox is injected via JavaScript. + $form['tests'][$group]['select'] = array( + '#wrapper_attributes' => array( + 'id' => $group_class, + 'class' => array('simpletest-select-all'), + ), + ); + $form['tests'][$group]['title'] = array( + // Expand/collapse image. + '#prefix' => '
', + '#markup' => '', + '#wrapper_attributes' => array( + 'class' => array('simpletest-group-label'), + ), + ); + $form['tests'][$group]['description'] = array( + '#markup' => ' ', + '#wrapper_attributes' => array( + 'class' => array('simpletest-group-description'), + ), + ); + + // Add individual tests to group. + $current_js = array( + 'testClass' => $group_class . '-test', + 'testNames' => array(), + // imageDirection maps to the 'images' index in the $js array. + 'imageDirection' => 0, + 'clickActive' => FALSE, ); + // Sort test classes within group alphabetically by name/label. + uasort($tests, function ($a, $b) { + return SortArray::sortByKeyString($a, $b, 'name'); + }); + + // Cycle through each test within the current group. foreach ($tests as $class => $info) { - $form['tests']['table'][$group][$class] = array( - '#type' => 'checkbox', + $test_id = drupal_clean_id_identifier($class); + $test_checkbox_id = 'edit-tests-' . $test_id; + $current_js['testNames'][] = $test_checkbox_id; + + $form['tests'][$class] = array( + '#attributes' => array('class' => array($group_class . '-test', 'js-hide')), + ); + $form['tests'][$class]['title'] = array( + '#type' => 'label', '#title' => $info['name'], - '#description' => $info['description'], + '#wrapper_attributes' => array( + 'class' => array('simpletest-test-label', 'table-filter-text-source'), + ), + ); + $form['tests'][$class]['description'] = array( + '#prefix' => '
', + '#markup' => String::format('@description (@class)', array( + '@description' => $info['description'], + '@class' => $class, + )), + '#suffix' => '
', + '#wrapper_attributes' => array( + 'class' => array('simpletest-test-description', 'table-filter-text-source'), + ), ); } + + $js['simpletest-test-group-' . $group_class] = $current_js; } + // Add JavaScript array of settings. + $form['tests']['#attached']['js'][] = array( + 'type' => 'setting', + 'data' => array('simpleTest' => $js), + ); + // Action buttons. - $form['tests']['op'] = array( + $form['actions'] = array('#type' => 'actions'); + $form['actions']['submit'] = array( '#type' => 'submit', '#value' => $this->t('Run tests'), + '#tableselect' => TRUE, + '#button_type' => 'primary', ); + $form['clean'] = array( '#type' => 'fieldset', '#title' => $this->t('Clean test environment'), '#description' => $this->t('Remove tables with the prefix "simpletest" and temporary directories that are left over from tests that crashed. This is intended for developers when creating tests.'), + '#weight' => 200, ); $form['clean']['op'] = array( '#type' => 'submit', @@ -97,21 +212,20 @@ public function buildForm(array $form, array &$form_state) { * {@inheritdoc} */ public function submitForm(array &$form, array &$form_state) { - // Get list of tests. - $tests_list = array(); simpletest_classloader_register(); $phpunit_all = array_keys($form_state['storage']['PHPUnit']); - foreach ($form_state['values'] as $class_name => $value) { + $tests_list = array(); + foreach ($form_state['values']['tests'] as $class_name => $value) { // Since class_exists() will likely trigger an autoload lookup, // we do the fast check first. - if ($value === 1 && class_exists($class_name)) { + if ($value === $class_name && class_exists($class_name)) { $test_type = in_array($class_name, $phpunit_all) ? 'UnitTest' : 'WebTest'; $tests_list[$test_type][] = $class_name; } } - if (count($tests_list) > 0 ) { + if (!empty($tests_list)) { $test_id = simpletest_run_tests($tests_list, 'drupal'); $form_state['redirect_route'] = array( 'route_name' => 'simpletest.result_form', @@ -120,9 +234,6 @@ public function submitForm(array &$form, array &$form_state) { ), ); } - else { - drupal_set_message($this->t('No test(s) selected.'), 'error'); - } } } diff --git a/core/modules/simpletest/lib/Drupal/simpletest/Tests/BrokenSetUpTest.php b/core/modules/simpletest/lib/Drupal/simpletest/Tests/BrokenSetUpTest.php index 42b786a..6b13160 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/Tests/BrokenSetUpTest.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/Tests/BrokenSetUpTest.php @@ -77,7 +77,7 @@ function testMethod() { if (!drupal_valid_test_ua()) { // Verify that a broken setUp() method is caught. file_put_contents($this->originalFileDirectory . '/simpletest/trigger', 'setup'); - $edit['Drupal\simpletest\Tests\BrokenSetUpTest'] = TRUE; + $edit['tests[Drupal\simpletest\Tests\BrokenSetUpTest]'] = TRUE; $this->drupalPostForm('admin/config/development/testing', $edit, t('Run tests')); $this->assertRaw('Broken setup'); $this->assertNoRaw('The setUp() method has run.'); @@ -88,7 +88,7 @@ function testMethod() { // Verify that a broken tearDown() method is caught. file_put_contents($this->originalFileDirectory . '/simpletest/trigger', 'teardown'); - $edit['Drupal\simpletest\Tests\BrokenSetUpTest'] = TRUE; + $edit['tests[Drupal\simpletest\Tests\BrokenSetUpTest]'] = TRUE; $this->drupalPostForm('admin/config/development/testing', $edit, t('Run tests')); $this->assertNoRaw('Broken setup'); $this->assertRaw('The setUp() method has run.'); @@ -99,7 +99,7 @@ function testMethod() { // Verify that a broken test method is caught. file_put_contents($this->originalFileDirectory . '/simpletest/trigger', 'test'); - $edit['Drupal\simpletest\Tests\BrokenSetUpTest'] = TRUE; + $edit['tests[Drupal\simpletest\Tests\BrokenSetUpTest]'] = TRUE; $this->drupalPostForm('admin/config/development/testing', $edit, t('Run tests')); $this->assertNoRaw('Broken setup'); $this->assertRaw('The setUp() method has run.'); diff --git a/core/modules/simpletest/lib/Drupal/simpletest/Tests/InstallationProfileModuleTestsTest.php b/core/modules/simpletest/lib/Drupal/simpletest/Tests/InstallationProfileModuleTestsTest.php index 911c824..994fb36 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/Tests/InstallationProfileModuleTestsTest.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/Tests/InstallationProfileModuleTestsTest.php @@ -57,7 +57,7 @@ function testInstallationProfileTests() { $this->drupalGet('admin/config/development/testing'); $this->assertText('Installation profile module tests helper'); $edit = array( - 'Drupal\drupal_system_listing_compatible_test\Tests\SystemListingCompatibleTest' => TRUE, + 'tests[Drupal\drupal_system_listing_compatible_test\Tests\SystemListingCompatibleTest]' => TRUE, ); $this->drupalPostForm(NULL, $edit, t('Run tests')); $this->assertText('SystemListingCompatibleTest test executed.'); diff --git a/core/modules/simpletest/lib/Drupal/simpletest/Tests/MissingCheckedRequirementsTest.php b/core/modules/simpletest/lib/Drupal/simpletest/Tests/MissingCheckedRequirementsTest.php index 61d6401..8b15c29 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/Tests/MissingCheckedRequirementsTest.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/Tests/MissingCheckedRequirementsTest.php @@ -55,7 +55,7 @@ protected function testCheckRequirements() { // that the child tests did not run. if (!drupal_valid_test_ua()) { // Run this test from web interface. - $edit['Drupal\simpletest\Tests\MissingCheckedRequirementsTest'] = TRUE; + $edit['tests[Drupal\simpletest\Tests\MissingCheckedRequirementsTest]'] = TRUE; $this->drupalPostForm('admin/config/development/testing', $edit, t('Run tests')); $this->assertRaw('Test is not allowed to run.', 'Test check for requirements came up.'); $this->assertNoText('Test ran when it failed requirements check.', 'Test requirements stopped test from running.'); diff --git a/core/modules/simpletest/lib/Drupal/simpletest/Tests/SimpleTestTest.php b/core/modules/simpletest/lib/Drupal/simpletest/Tests/SimpleTestTest.php index 2a19a5c1..fa985ed 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/Tests/SimpleTestTest.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/Tests/SimpleTestTest.php @@ -160,7 +160,7 @@ function testWebTestRunner() { $this->drupalGet('admin/config/development/testing'); $edit = array(); - $edit['Drupal\simpletest\Tests\SimpleTestTest'] = TRUE; + $edit['tests[Drupal\simpletest\Tests\SimpleTestTest]'] = TRUE; $this->drupalPostForm(NULL, $edit, t('Run tests')); // Parse results and confirm that they are correct. diff --git a/core/modules/simpletest/simpletest.module b/core/modules/simpletest/simpletest.module index c6b53bf..a3d722a 100644 --- a/core/modules/simpletest/simpletest.module +++ b/core/modules/simpletest/simpletest.module @@ -79,10 +79,6 @@ function simpletest_permission() { */ function simpletest_theme() { return array( - 'simpletest_test_table' => array( - 'render element' => 'table', - 'file' => 'simpletest.theme.inc', - ), 'simpletest_result_summary' => array( 'render element' => 'form', 'file' => 'simpletest.theme.inc', diff --git a/core/modules/simpletest/simpletest.theme.inc b/core/modules/simpletest/simpletest.theme.inc index 1743b74..9890c62 100644 --- a/core/modules/simpletest/simpletest.theme.inc +++ b/core/modules/simpletest/simpletest.theme.inc @@ -6,154 +6,6 @@ */ /** - * Returns an HTML table for a test list generated by simpletest_test_form(). - * - * @param $variables - * An associative array containing: - * - table: A render element representing the table. - * - * @ingroup themeable - */ -function theme_simpletest_test_table($variables) { - $table = $variables['table']; - - drupal_add_library('simpletest', 'drupal.simpletest'); - - // Create header for test selection table. - $header = array( - array('class' => array('select-all')), - array('data' => t('Test'), 'class' => array('simpletest_test')), - array('data' => t('Description'), 'class' => array('simpletest_description')), - ); - - // Define the images used to expand/collapse the test groups. - $image_collapsed = array( - '#theme' => 'image', - '#uri' => 'core/misc/menu-collapsed.png', - '#width' => '7', - '#height' => '7', - '#alt' => t('Expand'), - '#title' => t('Expand'), - '#suffix' => '(' . t('Expand') . ')', - ); - $image_extended = array( - '#theme' => 'image', - '#uri' => 'core/misc/menu-expanded.png', - '#width' => '7', - '#height' => '7', - '#alt' => t('Collapse'), - '#title' => t('Collapse'), - '#suffix' => ' (' . t('Collapse') . ')', - ); - $js = array( - 'images' => array( - drupal_render($image_collapsed), - drupal_render($image_extended), - ), - ); - - // Cycle through each test group and create a row. - $rows = array(); - foreach (element_children($table) as $key) { - $element = &$table[$key]; - $row = array(); - - // Make the class name safe for output on the page by replacing all - // non-word/decimal characters with a dash (-). - $test_class = 'module-' . strtolower(trim(preg_replace("/[^\w\d]/", "-", $key))); - - // Select the right "expand"/"collapse" image, depending on whether the - // category is expanded (at least one test selected) or not. - $collapsed = !empty($element['#collapsed']); - $image_index = $collapsed ? 0 : 1; - - // Place-holder for checkboxes to select group of tests. - $row[] = array('id' => $test_class, 'class' => array('simpletest-select-all')); - - // Expand/collapse image and group title. - $row[] = array( - 'data' => '
' . - '', - 'class' => array('simpletest-group-label'), - ); - - $row[] = array( - 'data' => ' ', - 'class' => array('simpletest-group-description'), - ); - - $rows[] = array('data' => $row, 'class' => array('simpletest-group')); - - // Add individual tests to group. - $current_js = array( - 'testClass' => $test_class . '-test', - 'testNames' => array(), - 'imageDirection' => $image_index, - 'clickActive' => FALSE, - ); - - // Sorting $element by children's #title attribute instead of by class name. - uasort($element, 'element_sort_by_title'); - - // Cycle through each test within the current group. - foreach (element_children($element) as $test_name) { - $test = $element[$test_name]; - $row = array(); - - $current_js['testNames'][] = $test['#id']; - - // Store test title and description so that checkbox won't render them. - $title = $test['#title']; - $description = $test['#description']; - - $test['#title_display'] = 'invisible'; - unset($test['#description']); - - // Test name is used to determine what tests to run. - $test['#name'] = $test_name; - - $row[] = array( - 'data' => drupal_render($test), - 'class' => array('simpletest-test-select'), - ); - $row[] = array( - 'data' => '', - 'class' => array('simpletest-test-label', 'table-filter-text-source'), - ); - $row[] = array( - 'data' => '
' . format_string('@description (@class)', array('@description' => $description, '@class' => $test_name)) . '
', - 'class' => array('simpletest-test-description', 'table-filter-text-source'), - ); - - $rows[] = array('data' => $row, 'class' => array($test_class . '-test', ($collapsed ? 'js-hide' : ''))); - } - $js['simpletest-test-group-' . $test_class] = $current_js; - unset($table[$key]); - } - - // Add js array of settings. - $attached = array(); - $attached['#attached']['js'][] = array( - 'data' => array('simpleTest' => $js), - 'type' => 'setting', - ); - drupal_render($attached); - - if (empty($rows)) { - return '' . t('No tests to display.') . ''; - } - else { - $simpletest_form_table = array( - '#theme' => 'table', - '#header' => $header, - '#rows' => $rows, - '#attributes' => array('id' => 'simpletest-form-table'), - ); - return drupal_render($simpletest_form_table); - } -} - -/** * Returns HTML for the summary status of a simpletest result. * * @param $variables diff --git a/core/modules/system/system.module b/core/modules/system/system.module index cc647a2..9d237f2 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -517,6 +517,9 @@ function system_element_info() { ); // Form structure. + $types['label'] = array( + '#theme' => 'form_element_label', + ); $types['item'] = array( // Forms that show author fields to both anonymous and authenticated users // need to dynamically switch between #type 'textfield' and #type 'item' to