Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.1042 diff -u -p -r1.1042 common.inc --- includes/common.inc 7 Nov 2009 14:02:31 -0000 1.1042 +++ includes/common.inc 7 Nov 2009 16:19:55 -0000 @@ -5516,9 +5516,6 @@ function drupal_common_theme() { 'form_required_marker' => array( 'arguments' => array('element' => NULL), ), - 'text_format_wrapper' => array( - 'render element' => 'element', - ), 'vertical_tabs' => array( 'render element' => 'element', ), Index: includes/form.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/form.inc,v retrieving revision 1.394 diff -u -p -r1.394 form.inc --- includes/form.inc 7 Nov 2009 14:44:04 -0000 1.394 +++ includes/form.inc 7 Nov 2009 22:52:51 -0000 @@ -1083,10 +1083,11 @@ function form_builder($form_id, $element // Check to see if a tree of child elements is present. If so, // continue down the tree if required. $element[$key]['#parents'] = $element[$key]['#tree'] && $element['#tree'] ? array_merge($element['#parents'], array($key)) : array($key); - $array_parents = isset($element['#array_parents']) ? $element['#array_parents'] : array(); - $array_parents[] = $key; - $element[$key]['#array_parents'] = $array_parents; } + // Ensure #array_parents matches the actual internal parents in the form. + $array_parents = isset($element['#array_parents']) ? $element['#array_parents'] : array(); + $array_parents[] = $key; + $element[$key]['#array_parents'] = $array_parents; // Assign a decimal placeholder weight to preserve original array order. if (!isset($element[$key]['#weight'])) { @@ -1992,110 +1993,6 @@ function form_process_radios($element) { } /** - * Add text format selector to text elements with the #text_format property. - * - * The #text_format property should be the ID of an text format, found in - * {filter_format}.format, which gets passed to filter_form(). - * - * If the property #text_format is set, the form element will be expanded into - * two separate form elements, one holding the content of the element, and the - * other holding the text format selector. The original element is shifted into - * a child element, but is otherwise unaltered, so that the format selector is - * at the same level as the text field which it affects. - * - * For example: - * @code - * // A simple textarea, such as a node body. - * $form['body'] = array( - * '#type' => 'textarea', - * '#title' => t('Body'), - * '#text_format' => isset($node->format) ? $node->format : filter_default_format(), - * ); - * @endcode - * - * Becomes: - * @code - * $form['body'] = array( - * // Type switches to 'markup', as we're only interested in submitting the child elements. - * '#type' => 'markup', - * // 'value' holds the original element. - * 'value' => array( - * '#type' => 'textarea', - * '#title' => t('Body'), - * '#parents' => array('body'), - * ), - * // 'format' holds the text format selector. - * 'format' => array( - * '#parents' => array('body_format'), - * ... - * ), - * ); - * @endcode - * - * And would result in: - * @code - * // Original, unaltered form element value. - * $form_state['values']['body'] = 'Example content'; - * // Chosen text format. - * $form_state['values']['body_format'] = 1; - * @endcode - * - * @see system_element_info(), filter_form() - */ -function form_process_text_format($element) { - if (isset($element['#text_format'])) { - // Determine the form element parents and element name to use for the input - // format widget. This simulates the 'element' and 'element_format' pair of - // parents that filter_form() expects. - $element_parents = $element['#parents']; - $element_name = array_pop($element_parents); - $element_parents[] = $element_name . '_format'; - - // We need to break references, otherwise form_builder recurses infinitely. - $element['value'] = (array)$element; - $element['value']['#weight'] = 0; - unset($element['value']['#description']); - $element['#type'] = 'markup'; - $element['#theme'] = NULL; - $element['#theme_wrappers'] = array('text_format_wrapper'); - $element['format'] = filter_form($element['#text_format'], 1, $element_parents); - - // We need to clear the #text_format from the new child otherwise we - // would get into an infinite loop. - unset($element['value']['#text_format']); - } - return $element; -} - -/** - * Theme a text format form element. - * - * @param $variables - * An associative array containing: - * - element: An associative array containing the properties of the element. - * Properties used: #children, #description - * - * @return - * A string representing the form element. - * - * @ingroup themeable - */ -function theme_text_format_wrapper($variables) { - $element = $variables['element']; - $output = '
' . "\n"; - - $output .= $element['#children'] . "\n"; - - if (!empty($element['#description'])) { - $output .= '
' . $element['#description'] . "
\n"; - } - - $output .= "
\n"; - - return $output; -} - -/** * Theme a checkbox form element. * * @param $variables @@ -2210,7 +2107,7 @@ function form_process_container($element * @param $element * An associative array containing the properties and children of the * group. - * Properties used: #children. + * Properties used: #id, #attributes, #children. * @return * A themed HTML string representing the form element. * @@ -2218,7 +2115,12 @@ function form_process_container($element */ function theme_container($variables) { $element = $variables['element']; - return '
' . $element['#children'] . '
'; + if (!isset($element['#attributes']['id'])) { + $element['#attributes']['id'] = $element['#id']; + } + // Force the 'form-wrapper' class. + $element['#attributes']['class'][] = 'form-wrapper'; + return '' . $element['#children'] . ''; } /** Index: misc/form.js =================================================================== RCS file: /cvs/drupal/drupal/misc/form.js,v retrieving revision 1.12 diff -u -p -r1.12 form.js --- misc/form.js 16 Oct 2009 16:37:00 -0000 1.12 +++ misc/form.js 7 Nov 2009 22:47:19 -0000 @@ -69,23 +69,6 @@ Drupal.behaviors.multiselectSelector = { }; /** - * Automatically display the guidelines of the selected text format. - */ -Drupal.behaviors.filterGuidelines = { - attach: function (context) { - $('.filter-guidelines', context).once('filter-guidelines') - .find('label').hide() - .parents('.filter-wrapper').find('select.filter-list') - .bind('change', function () { - $(this).parents('.filter-wrapper') - .find('.filter-guidelines-item').hide() - .siblings('#filter-guidelines-' + this.value).show(); - }) - .change(); - } -}; - -/** * Prepopulate form fields with information from the visitor cookie. */ Drupal.behaviors.fillUserInfoFromCookie = { Index: modules/block/block.admin.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/block/block.admin.inc,v retrieving revision 1.61 diff -u -p -r1.61 block.admin.inc --- modules/block/block.admin.inc 3 Nov 2009 05:27:18 -0000 1.61 +++ modules/block/block.admin.inc 6 Nov 2009 23:58:56 -0000 @@ -446,9 +446,9 @@ function block_add_block_form_validate($ function block_add_block_form_submit($form, &$form_state) { $delta = db_insert('block_custom') ->fields(array( - 'body' => $form_state['values']['body'], + 'body' => $form_state['values']['body']['value'], 'info' => $form_state['values']['info'], - 'format' => $form_state['values']['body_format'], + 'format' => $form_state['values']['body']['format'], )) ->execute(); Index: modules/block/block.module =================================================================== RCS file: /cvs/drupal/drupal/modules/block/block.module,v retrieving revision 1.395 diff -u -p -r1.395 block.module --- modules/block/block.module 7 Nov 2009 13:35:20 -0000 1.395 +++ modules/block/block.module 7 Nov 2009 23:27:19 -0000 @@ -423,10 +423,10 @@ function block_custom_block_form($edit = ); $form['body_field']['#weight'] = -17; $form['body_field']['body'] = array( - '#type' => 'textarea', + '#type' => 'textarea_format', '#title' => t('Block body'), '#default_value' => $edit['body'], - '#text_format' => isset($edit['format']) ? $edit['format'] : filter_default_format(), + '#format' => isset($edit['format']) ? $edit['format'] : NULL, '#rows' => 15, '#description' => t('The content of the block as shown to the user.'), '#required' => TRUE, @@ -453,9 +453,9 @@ function block_custom_block_form($edit = function block_custom_block_save($edit, $delta) { db_update('block_custom') ->fields(array( - 'body' => $edit['body'], + 'body' => $edit['body']['value'], 'info' => $edit['info'], - 'format' => $edit['body_format'], + 'format' => $edit['body']['format'], )) ->condition('bid', $delta) ->execute(); Index: modules/block/block.test =================================================================== RCS file: /cvs/drupal/drupal/modules/block/block.test,v retrieving revision 1.30 diff -u -p -r1.30 block.test --- modules/block/block.test 16 Oct 2009 23:48:37 -0000 1.30 +++ modules/block/block.test 21 Oct 2009 05:38:08 -0000 @@ -78,8 +78,8 @@ class BlockTestCase extends DrupalWebTes $custom_block = array(); $custom_block['info'] = $this->randomName(8); $custom_block['title'] = $this->randomName(8); - $custom_block['body'] = '

Full HTML

'; - $custom_block['body_format'] = 2; + $custom_block['body[value]'] = '

Full HTML

'; + $custom_block['body[format]'] = 2; $this->drupalPost('admin/structure/block/add', $custom_block, t('Save block')); // Set the created custom block to a specific region. @@ -119,7 +119,7 @@ class BlockTestCase extends DrupalWebTes $custom_block = array(); $custom_block['info'] = $this->randomName(8); $custom_block['title'] = $title; - $custom_block['body'] = $this->randomName(32); + $custom_block['body[value]'] = $this->randomName(32); $this->drupalPost('admin/structure/block/add', $custom_block, t('Save block')); $bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField(); Index: modules/comment/comment.module =================================================================== RCS file: /cvs/drupal/drupal/modules/comment/comment.module,v retrieving revision 1.802 diff -u -p -r1.802 comment.module --- modules/comment/comment.module 7 Nov 2009 13:35:20 -0000 1.802 +++ modules/comment/comment.module 7 Nov 2009 16:19:56 -0000 @@ -1288,7 +1288,7 @@ function comment_save($comment) { 'changed' => $comment->changed, 'subject' => $comment->subject, 'comment' => $comment->comment, - 'format' => $comment->comment_format, + 'format' => $comment->format, 'uid' => $comment->uid, 'name' => $comment->name, 'mail' => $comment->mail, @@ -1364,7 +1364,7 @@ function comment_save($comment) { 'uid' => $comment->uid, 'subject' => $comment->subject, 'comment' => $comment->comment, - 'format' => $comment->comment_format, + 'format' => $comment->format, 'hostname' => ip_address(), 'created' => $comment->created, 'changed' => $comment->changed, @@ -1841,11 +1841,11 @@ function comment_form($form, &$form_stat } $form['comment'] = array( - '#type' => 'textarea', + '#type' => 'textarea_format', '#title' => t('Comment'), '#rows' => 15, '#default_value' => $default, - '#text_format' => isset($comment->format) ? $comment->format : filter_default_format(), + '#format' => isset($comment->format) ? $comment->format : NULL, '#required' => TRUE, ); @@ -1923,7 +1923,8 @@ function comment_preview($comment) { $node = node_load($comment->nid); if (!form_get_errors()) { - $comment->format = $comment->comment_format; + $comment->format = $comment->comment['format']; + $comment->comment = $comment->comment['value']; // Attach the user and time information. if (!empty($comment->author)) { @@ -2053,7 +2054,7 @@ function comment_submit($comment) { // 1) Filter it into HTML // 2) Strip out all HTML tags // 3) Convert entities back to plain-text. - $comment['subject'] = truncate_utf8(trim(decode_entities(strip_tags(check_markup($comment['comment'], $comment['comment_format'])))), 29, TRUE); + $comment['subject'] = truncate_utf8(trim(decode_entities(strip_tags(check_markup($comment['comment']['value'], $comment['comment']['format'])))), 29, TRUE); // Edge cases where the comment body is populated only by HTML tags will // require a default subject. if ($comment['subject'] == '') { Index: modules/comment/comment.pages.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/comment/comment.pages.inc,v retrieving revision 1.28 diff -u -p -r1.28 comment.pages.inc --- modules/comment/comment.pages.inc 1 Nov 2009 12:11:10 -0000 1.28 +++ modules/comment/comment.pages.inc 6 Nov 2009 23:58:57 -0000 @@ -108,7 +108,8 @@ function comment_reply(stdClass $node, $ */ function comment_approve($comment) { $comment->status = COMMENT_PUBLISHED; - $comment->comment_format = $comment->format; + $comment->format = $comment->comment['format']; + $comment->comment = $comment->comment['value']; comment_save($comment); drupal_set_message(t('Comment approved.')); Index: modules/comment/comment.test =================================================================== RCS file: /cvs/drupal/drupal/modules/comment/comment.test,v retrieving revision 1.52 diff -u -p -r1.52 comment.test --- modules/comment/comment.test 20 Oct 2009 17:33:42 -0000 1.52 +++ modules/comment/comment.test 21 Oct 2009 05:42:01 -0000 @@ -24,7 +24,7 @@ class CommentHelperCase extends DrupalWe */ function postComment($node, $comment, $subject = '', $contact = NULL) { $edit = array(); - $edit['comment'] = $comment; + $edit['comment[value]'] = $comment; $preview_mode = variable_get('comment_preview_article', DRUPAL_OPTIONAL); $subject_mode = variable_get('comment_subject_field_article', 1); @@ -413,17 +413,17 @@ class CommentPreviewTest extends Comment $this->drupalLogin($this->web_user); $edit = array(); $edit['subject'] = $this->randomName(8); - $edit['comment'] = $this->randomName(16); + $edit['comment[value]'] = $this->randomName(16); $this->drupalPost('node/' . $this->node->nid, $edit, t('Preview')); // Check that the preview is displaying the title and body. $this->assertTitle(t('Preview comment | Drupal'), t('Page title is "Preview comment".')); $this->assertText($edit['subject'], t('Subject displayed.')); - $this->assertText($edit['comment'], t('Comment displayed.')); + $this->assertText($edit['comment[value]'], t('Comment displayed.')); // Check that the title and body fields are displayed with the correct values. $this->assertFieldByName('subject', $edit['subject'], t('Subject field displayed.')); - $this->assertFieldByName('comment', $edit['comment'], t('Comment field displayed.')); + $this->assertFieldByName('comment', $edit['comment[value]'], t('Comment field displayed.')); } /** @@ -439,23 +439,23 @@ class CommentPreviewTest extends Comment $edit = array(); $edit['subject'] = $this->randomName(8); - $edit['comment'] = $this->randomName(16); + $edit['comment[value]'] = $this->randomName(16); $edit['author'] = $web_user->name; $edit['date'] = '2008-03-02 17:23 +0300'; $expected_date = format_date(strtotime($edit['date'])); - $comment = $this->postComment($this->node, $edit['subject'], $edit['comment'], TRUE); + $comment = $this->postComment($this->node, $edit['subject'], $edit['comment[value]'], TRUE); $this->drupalPost('comment/' . $comment->id . '/edit', $edit, t('Preview')); // Check that the preview is displaying the subject, comment, author and date correctly. $this->assertTitle(t('Preview comment | Drupal'), t('Page title is "Preview comment".')); $this->assertText($edit['subject'], t('Subject displayed.')); - $this->assertText($edit['comment'], t('Comment displayed.')); + $this->assertText($edit['comment[value]'], t('Comment displayed.')); $this->assertText($edit['author'], t('Author displayed.')); $this->assertText($expected_date, t('Date displayed.')); // Check that the title and body fields are displayed with the correct values. $this->assertFieldByName('subject', $edit['subject'], t('Subject field displayed.')); - $this->assertFieldByName('comment', $edit['comment'], t('Comment field displayed.')); + $this->assertFieldByName('comment', $edit['comment[value]'], t('Comment field displayed.')); $this->assertFieldByName('author', $edit['author'], t('Author field displayed.')); $this->assertFieldByName('date', $edit['date'], t('Date field displayed.')); } Index: modules/field/modules/text/text.js =================================================================== RCS file: /cvs/drupal/drupal/modules/field/modules/text/text.js,v retrieving revision 1.1 diff -u -p -r1.1 text.js --- modules/field/modules/text/text.js 11 Sep 2009 13:30:49 -0000 1.1 +++ modules/field/modules/text/text.js 20 Oct 2009 22:58:37 -0000 @@ -5,9 +5,9 @@ /** * Auto-hide summary textarea if empty and show hide and unhide links. */ -Drupal.behaviors.textTextareaSummary = { +Drupal.behaviors.textSummary = { attach: function (context, settings) { - $('textarea.text-textarea-summary:not(.text-textarea-summary-processed)', context).addClass('text-textarea-summary-processed').each(function () { + $('textarea.text-summary', context).once('text-summary', function () { var $fieldset = $(this).closest('#body-wrapper'); var $summary = $fieldset.find('div.text-summary-wrapper'); var $summaryLabel = $summary.find('div.form-type-textarea label'); Index: modules/field/modules/text/text.module =================================================================== RCS file: /cvs/drupal/drupal/modules/field/modules/text/text.module,v retrieving revision 1.35 diff -u -p -r1.35 text.module --- modules/field/modules/text/text.module 1 Nov 2009 14:05:31 -0000 1.35 +++ modules/field/modules/text/text.module 7 Nov 2009 02:28:49 -0000 @@ -146,7 +146,7 @@ function text_field_instance_settings_fo '#default_value' => $settings['text_processing'], '#options' => array( t('Plain text'), - t('Filtered text (user selects input format)'), + t('Filtered text (user selects text format)'), ), ); if ($field['type'] == 'text_with_summary') { @@ -154,7 +154,7 @@ function text_field_instance_settings_fo '#type' => 'checkbox', '#title' => t('Summary input'), '#default_value' => $settings['display_summary'], - '#description' => t('This allows authors to input an explicit summary, to be displayed instead of the automatically trimmed text when using the "Summary or trimmed" display format.'), + '#description' => t('This allows authors to input an explicit summary, to be displayed instead of the automatically trimmed text when using the "Summary or trimmed" display type.'), ); } @@ -170,13 +170,16 @@ function text_field_instance_settings_fo */ function text_field_validate($obj_type, $object, $field, $instance, $langcode, $items, &$errors) { foreach ($items as $delta => $item) { - foreach (array('value' => t('full text'), 'summary' => t('summary')) as $column => $desc) { + // @todo Length is counted separately for summary and value, so the maximum + // length can be exceeded very easily. + foreach (array('value', 'summary') as $column) { if (!empty($item[$column])) { if (!empty($field['settings']['max_length']) && drupal_strlen($item[$column]) > $field['settings']['max_length']) { switch ($column) { case 'value': $message = t('%name: the text may not be longer than %max characters.', array('%name' => $instance['label'], '%max' => $field['settings']['max_length'])); break; + case 'summary': $message = t('%name: the summary may not be longer than %max characters.', array('%name' => $instance['label'], '%max' => $field['settings']['max_length'])); break; @@ -205,11 +208,11 @@ function text_field_load($obj_type, $obj if (!empty($instances[$id]['settings']['text_processing'])) { // Only process items with a cacheable format, the rest will be // handled by text_field_sanitize(). - $format = $item['format']; - if (filter_format_allowcache($format)) { - $items[$id][$delta]['safe'] = isset($item['value']) ? check_markup($item['value'], $format, $langcode) : ''; + $format_id = $item['format']; + if (filter_format_allowcache($format_id)) { + $items[$id][$delta]['safe'] = isset($item['value']) ? check_markup($item['value'], $format_id, $langcode) : ''; if ($field['type'] == 'text_with_summary') { - $items[$id][$delta]['safe_summary'] = isset($item['summary']) ? check_markup($item['summary'], $format, $langcode) : ''; + $items[$id][$delta]['safe_summary'] = isset($item['summary']) ? check_markup($item['summary'], $format_id, $langcode) : ''; } } } @@ -235,10 +238,10 @@ function text_field_sanitize($obj_type, // from a form preview. if (!isset($items[$delta]['safe'])) { if (!empty($instance['settings']['text_processing'])) { - $format = $item['format']; - $items[$delta]['safe'] = isset($item['value']) ? check_markup($item['value'], $format, $langcode, TRUE) : ''; + $format_id = $item['format']; + $items[$delta]['safe'] = isset($item['value']) ? check_markup($item['value'], $format_id, $langcode, TRUE) : ''; if ($field['type'] == 'text_with_summary') { - $items[$delta]['safe_summary'] = isset($item['summary']) ? check_markup($item['summary'], $format, $langcode, TRUE) : ''; + $items[$delta]['safe_summary'] = isset($item['summary']) ? check_markup($item['summary'], $format_id, $langcode, TRUE) : ''; } } else { @@ -469,14 +472,6 @@ function text_summary($text, $format = N /** * Implement hook_field_widget_info(). - * - * Here we indicate that the field module will handle - * the default value and multiple values for these widgets. - * - * Callbacks can be omitted if default handing is used. - * They're included here just so this module can be used - * as an example for custom modules that might do things - * differently. */ function text_field_widget_info() { return array( @@ -528,40 +523,6 @@ function text_field_widget_settings_form } /** - * Implement hook_element_info(). - * - * Autocomplete_path is not used by text_field_widget but other - * widgets can use it (see nodereference and userreference). - */ -function text_element_info() { - $types['text_textfield'] = array( - '#input' => TRUE, - '#columns' => array('value'), - '#delta' => 0, - '#process' => array('text_textfield_elements_process'), - '#theme_wrappers' => array('text_textfield'), - '#autocomplete_path' => FALSE, - ); - $types['text_textarea'] = array( - '#input' => TRUE, - '#columns' => array('value', 'format'), - '#delta' => 0, - '#process' => array('text_textarea_elements_process'), - '#theme_wrappers' => array('text_textarea'), - '#filter_value' => filter_default_format(), - ); - $types['text_textarea_with_summary'] = array( - '#input' => TRUE, - '#columns' => array('value', 'format', 'summary'), - '#delta' => 0, - '#process' => array('text_textarea_with_summary_process'), - '#theme_wrappers' => array('text_textarea'), - '#filter_value' => filter_default_format(), - ); - return $types; -} - -/** * Implement hook_field_widget(). */ function text_field_widget(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) { @@ -569,8 +530,58 @@ function text_field_widget(&$form, &$for '#type' => $instance['widget']['type'], '#default_value' => isset($items[$delta]) ? $items[$delta] : '', ); + + if (isset($element['#columns'][1]) && $element['#columns'][1] == 'summary') { + $field_key = $element['#columns'][1]; + $display = !empty($items[$delta][$field_key]) || !empty($instance['settings']['display_summary']); + $element[$field_key] = array( + '#title' => t('Summary'), + '#type' => $display ? 'textarea' : 'value', + '#default_value' => isset($items[$delta][$field_key]) ? $items[$delta][$field_key] : NULL, + '#rows' => $instance['widget']['settings']['summary_rows'], + '#title' => t('Summary'), + '#description' => t('Leave blank to use trimmed value of full text as the summary.'), + '#attached' => array('js' => array(drupal_get_path('module', 'text') . '/text.js')), + '#attributes' => array('class' => array('text-summary')), + '#prefix' => '
', + '#suffix' => '
', + ); + } + + $field_key = $element['#columns'][0]; + switch ($instance['widget']['type']) { + case 'text_textfield': + $element[$field_key] = array( + '#type' => 'textfield', + '#default_value' => isset($items[$delta][$field_key]) ? $items[$delta][$field_key] : NULL, + '#size' => $instance['widget']['settings']['size'], + '#title' => $instance['label'], + '#description' => $instance['description'], + '#required' => $instance['required'], + '#attributes' => array('class' => array('text-textfield')), + ); + break; + + default: + $element[$field_key] = array( + '#type' => 'textarea', + '#default_value' => isset($items[$delta][$field_key]) ? $items[$delta][$field_key] : NULL, + '#rows' => $instance['widget']['settings']['rows'], + '#title' => $instance['label'], + '#description' => $instance['description'], + '#required' => $instance['required'], + '#attributes' => array('class' => array('text-textarea')), + '#prefix' => '
', + '#suffix' => '
', + ); + break; + } + + // Conditionally alter the form element's type if text processing is enabled. if (!empty($instance['settings']['text_processing'])) { - $element['#value_callback'] = 'text_field_widget_formatted_text_value'; + $element[$field_key]['#type'] .= '_format'; + $element[$field_key]['#format'] = isset($items[$delta]['format']) ? $items[$delta]['format'] : NULL; + $element[$field_key]['#format_tree'] = FALSE; } return $element; @@ -593,170 +604,3 @@ function text_field_widget_error($elemen form_error($error_element, $error['message']); } -/** - * Process an individual element. - * - * Build the form element. When creating a form using FAPI #process, - * note that $element['#value'] is already set. - * - * The $field and $instance arrays are in $form['#fields'][$element['#field_name']]. - * - * TODO: For widgets to be actual FAPI 'elements', reusable outside of a - * 'field' context, they shoudn't rely on $field and $instance. The bits of - * information needed to adjust the behavior of the 'element' should be - * extracted in hook_field_widget() above. - */ -function text_textfield_elements_process($element, $form_state, $form) { - $field = $form['#fields'][$element['#field_name']]['field']; - $instance = $form['#fields'][$element['#field_name']]['instance']; - $field_key = $element['#columns'][0]; - $delta = $element['#delta']; - - $element[$field_key] = array( - '#type' => 'textfield', - '#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : NULL, - '#autocomplete_path' => $element['#autocomplete_path'], - '#size' => $instance['widget']['settings']['size'], - '#attributes' => array('class' => array('text')), - '#title' => $element['#title'], - '#description' => $element['#description'], - '#required' => $element['#required'], - ); - - $element[$field_key]['#maxlength'] = !empty($field['settings']['max_length']) ? $field['settings']['max_length'] : NULL; - - if (!empty($instance['settings']['text_processing'])) { - $filter_key = (count($element['#columns']) == 2) ? $element['#columns'][1] : 'format'; - $format = isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : filter_default_format(); - $element[$field_key]['#text_format'] = $format; - } - - return $element; -} - -/** - * Process an individual element. - * - * Build the form element. When creating a form using FAPI #process, - * note that $element['#value'] is already set. - * - * The $field and $instance arrays are in $form['#fields'][$element['#field_name']]. - */ -function text_textarea_elements_process($element, $form_state, $form) { - $field = $form['#fields'][$element['#field_name']]['field']; - $instance = $form['#fields'][$element['#field_name']]['instance']; - $field_key = $element['#columns'][0]; - $delta = $element['#delta']; - - $element[$field_key] = array( - '#type' => 'textarea', - '#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : NULL, - '#rows' => $instance['widget']['settings']['rows'], - '#weight' => 0, - '#title' => $element['#title'], - '#description' => $element['#description'], - '#required' => $element['#required'], - ); - - if (!empty($instance['settings']['text_processing'])) { - $filter_key = (count($element['#columns']) == 2) ? $element['#columns'][1] : 'format'; - $format = isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : filter_default_format(); - $element[$field_key]['#text_format'] = $format; - } - - return $element; -} - -/** - * Process an individual element. - * - * Build the form element. When creating a form using FAPI #process, - * note that $element['#value'] is already set. - * - * The $field and $instance arrays are in $form['#fields'][$element['#field_name']]. - */ -function text_textarea_with_summary_process($element, $form_state, $form) { - $field = $form['#fields'][$element['#field_name']]['field']; - $instance = $form['#fields'][$element['#field_name']]['instance']; - $delta = $element['#delta']; - - $field_key = $element['#columns'][1]; - $display = !empty($element['#value'][$field_key]) || !empty($instance['settings']['display_summary']); - $element[$field_key] = array( - '#title' => t('Summary'), - '#type' => $display ? 'textarea' : 'value', - '#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : NULL, - '#rows' => $instance['widget']['settings']['summary_rows'], - '#weight' => 0, - '#title' => t('Summary'), - '#description' => t('Leave blank to use trimmed value of full text as the summary.'), - '#display' => $display, - '#attached' => array('js' => array(drupal_get_path('module', 'text') . '/text.js')), - '#attributes' => array('class' => array('text-textarea-summary')), - '#prefix' => '
', - '#suffix' => '
', - ); - - $field_key = $element['#columns'][0]; - $element[$field_key] = array( - '#type' => 'textarea', - '#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : NULL, - '#rows' => $instance['widget']['settings']['rows'], - '#weight' => 1, - '#title' => $display ? t('Full text') : $element['#title'], - '#description' => $element['#description'], - '#required' => $element['#required'], - '#attributes' => array('class' => array('text-full-textarea')), - '#prefix' => '
', - '#suffix' => '
', - ); - - if (!empty($instance['settings']['text_processing'])) { - $filter_key = (count($element['#columns']) == 2) ? $element['#columns'][1] : 'format'; - $format = isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : filter_default_format(); - $element[$field_key]['#text_format'] = $format; - } - - return $element; -} - -/** - * Helper function to determine the value for a formatted text widget. - * - * '#text_format' puts the format in '[column 0]_format' in incoming values, - * while we need it in '[column 1]'. - */ -function text_field_widget_formatted_text_value($form, $edit = FALSE) { - if ($edit !== FALSE) { - $field_key = $form['#columns'][0]; - $filter_key = (count($form['#columns']) == 2) ? $form['#columns'][1] : 'format'; - $default_key = $field_key . '_format'; - // The format selector uses #access = FALSE if only one format is - // available. In this case, we don't receive its value, and need to - // manually set it. - $edit['format'] = !empty($edit[$default_key]) ? $edit[$default_key] : filter_default_format(); - unset($edit[$default_key]); - return $edit; - } -} - -/** - * FAPI theme for an individual text elements. - * - * The textfield or textarea is already rendered by the - * textfield or textarea themes and the html output - * lives in $variables['element']['#children']. Override this theme to - * make custom changes to the output. - * - * $variables['element']['#field_name'] contains the field name - * $variables['element']['#delta] is the position of this element in the group - */ -function theme_text_textfield($variables) { - $element = $variables['element']; - return $element['#children']; -} - -function theme_text_textarea($variables) { - $element = $variables['element']; - return $element['#children']; -} Index: modules/filter/filter.css =================================================================== RCS file: /cvs/drupal/drupal/modules/filter/filter.css,v retrieving revision 1.1 diff -u -p -r1.1 filter.css --- modules/filter/filter.css 30 Mar 2009 03:15:40 -0000 1.1 +++ modules/filter/filter.css 7 Nov 2009 22:55:41 -0000 @@ -11,7 +11,6 @@ } .filter-wrapper .form-item { float: left; - margin: 0; padding: 0 0 0.5em 1.5em; } .filter-wrapper .form-item label { Index: modules/filter/filter.js =================================================================== RCS file: modules/filter/filter.js diff -N modules/filter/filter.js --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/filter/filter.js 7 Nov 2009 22:46:56 -0000 @@ -0,0 +1,21 @@ +// $Id: form.js,v 1.12 2009/10/16 16:37:00 dries Exp $ +(function ($) { + +/** + * Automatically display the guidelines of the selected text format. + */ +Drupal.behaviors.filterGuidelines = { + attach: function (context) { + $('.filter-guidelines', context).once('filter-guidelines') + .find('label').hide() + .parents('.filter-wrapper').find('select.filter-list') + .bind('change', function () { + $(this).parents('.filter-wrapper') + .find('.filter-guidelines-item').hide() + .siblings('#filter-guidelines-' + this.value).show(); + }) + .change(); + } +}; + +})(jQuery); Index: modules/filter/filter.module =================================================================== RCS file: /cvs/drupal/drupal/modules/filter/filter.module,v retrieving revision 1.300 diff -u -p -r1.300 filter.module --- modules/filter/filter.module 23 Oct 2009 22:24:14 -0000 1.300 +++ modules/filter/filter.module 7 Nov 2009 23:27:29 -0000 @@ -49,6 +49,9 @@ function filter_theme() { 'variables' => array('tips' => NULL, 'long' => FALSE), 'file' => 'filter.pages.inc', ), + 'filter_format_wrapper' => array( + 'render element' => 'element', + ), 'filter_tips_more_info' => array( 'variables' => array(), ), @@ -59,6 +62,24 @@ function filter_theme() { } /** + * Implement of hook_elements(). + * + * Elements defined by Filter module are wrappers around the respective, + * non-prefixed elements and are expanded by filter_process_format(). + * + * @see filter_process_format() + */ +function filter_element_info() { + $type['textfield_format'] = array( + '#process' => array('filter_process_format'), + ); + $type['textarea_format'] = array( + '#process' => array('filter_process_format'), + ); + return $type; +} + +/** * Implement hook_menu(). */ function filter_menu() { @@ -617,69 +638,187 @@ function check_markup($text, $format_id /** * Generates a selector for choosing a format in a form. * - * @param $selected_format - * The ID of the format that is currently selected; uses the default format - * for the current user if not provided. - * @param $weight - * The weight of the form element within the form. - * @param $parents - * The parents array of the element. Required when defining multiple text - * formats on a single form or having a different parent than 'format'. + * The form element will be expanded into two separate form elements, one + * holding the content of the element, and the other holding the text format + * selector. The original element is shifted into a child element, but is + * otherwise unaltered, so that the format selector is at the same level as the + * text field which it affects. + * + * The optional #format property should be the ID of an text format, found in + * {filter_format}.format, or NULL to use the default format for the current + * user. For example: + * @code + * $form['body'] = array( + * '#type' => 'textarea_format', + * '#title' => t('Body'), + * '#format' => isset($node->format) ? $node->format : NULL, + * ); + * @endcode + * + * Becomes: + * @code + * $form['body'] = array( + * '#type' => 'markup', + * // 'value' holds the original element. + * 'value' => array( + * '#type' => 'textarea', + * '#title' => t('Body'), + * '#parents' => array('body', 'value'), + * ), + * // 'format' holds the text format widget. + * 'format' => array( + * '#parents' => array('body', 'format'), + * ), + * ); + * @endcode + * + * Which results in the submitted form values: + * @code + * // Original, unaltered form element value. + * $form_state['values']['body']['value'] = 'Example content'; + * // Chosen text format. + * $form_state['values']['body']['format'] = 1; + * @endcode + * + * @param $element + * The form element to process. Properties used: + * - #type: A form element #type suffixed with '_format'. The form element for + * 'value' will use this #type without the '_format' suffix. For example, + * if the to be processed form element specifies 'textarea_format', then the + * new child element in 'value' will use the #type 'textarea'. By default, + * Filter module registers 'textarea_format' and 'textfield_format' in + * filter_element_info(). Custom implementations needs to register the + * suffixed '_format' types in hook_element_info(). + * - #format: (optional) The text format id to preselect. If 0, NULL, or not + * defined, the default format for the current user will be used. + * - #format_tree: (optional) If set to FALSE, then the new born child element + * 'format' will appear on the same array level as the original $element. * * @return - * Form API array for the form element. - * - * @ingroup forms + * The expanded element. */ -function filter_form($selected_format = NULL, $weight = NULL, $parents = array('format')) { +function filter_process_format($element) { global $user; - // Use the default format for this user if none was selected. - if (empty($selected_format)) { - $selected_format = filter_default_format($user); + // By default, the element will be expanded into two new child elements + // 'value' and 'format', which should end up as sub-keys of the original + // element in the submitted form values (equaling Form API's #tree behavior). + // However, in certain situations, form validation and submit handlers may + // expect the original element's value at its original location, and the + // 'format' value next to it on the same level (e.g. Text field module). If + // #format_tree was explicitly set to FALSE, then we mimic #tree = FALSE to + // some extent, but having the difference that the resulting values will not + // end up on the top-level of the form; the #parents property of the new child + // elements take over the #parents of the original element, and the 'format' + // element is placed next to the original element. + $tree = !isset($element['#format_tree']) || $element['#format_tree']; + + $value_parents = $element['#parents']; + $format_parents = $element['#parents']; + // If #format_tree is FALSE, make the 'format' element appear on the same + // level as the passed in element. + if (!$tree) { + array_pop($format_parents); } + else { + $value_parents[] = 'value'; + } + $format_parents[] = 'format'; + // Move this element into sub-element 'value'. Break references and remove + // this #process function to prevent form_builder() to recurse infinitely. + $element['value'] = (array) $element; + $element['value']['#process'] = array_diff($element['value']['#process'], array('filter_process_format')); + + // Turn this element into a text format wrapper. + $element['#type'] = 'markup'; + unset($element['#theme']); + $element['#theme_wrappers'] = array('filter_format_wrapper'); + $path = drupal_get_path('module', 'filter'); + $element['#attached']['js'][] = $path . '/filter.js'; + $element['#attached']['css'][] = $path . '/filter.css'; + + // Setup child element for value. + // The #type without '_format' suffix is expected to be a valid #type. This + // allows to enhance further (and custom) form elements with text formats. + $element['value']['#type'] = strtr($element['value']['#type'], array('_format' => '')); + $element['value'] += element_info($element['value']['#type']); + $element['value']['#parents'] = $value_parents; + // Description is handled by theme_filter_format_wrapper() and weight needs + // to be reset to ensure proper ordering. + unset($element['value']['#description'], $element['value']['#weight']); + // Unset properties that will be processed for the original element already. + unset($element['value']['#prefix'], $element['value']['#suffix'], $element['value']['#attached']); + + // Setup child element for text format widget. + $element['format'] = element_info('fieldset'); + $element['format']['#defaults_loaded'] = TRUE; + $element['format']['#attributes']['class'][] = 'filter-wrapper'; + + // Prepare text format guidelines. + $element['format']['guidelines'] = array( + '#type' => 'container', + '#attributes' => array('class' => array('filter-guidelines')), + '#weight' => 20, + ); // Get a list of formats that the current user has access to. $formats = filter_formats($user); - - drupal_add_js('misc/form.js'); - drupal_add_css(drupal_get_path('module', 'filter') . '/filter.css'); - $element_id = drupal_html_id('edit-' . implode('-', $parents)); - - $form = array( - '#type' => 'fieldset', - '#weight' => $weight, - '#attributes' => array('class' => array('filter-wrapper')), - ); - $form['format_guidelines'] = array( - '#prefix' => '
', - '#suffix' => '
', - '#weight' => 2, - ); foreach ($formats as $format) { $options[$format->format] = $format->name; - $form['format_guidelines'][$format->format] = array( - '#markup' => theme('filter_guidelines', array('format' => $format)), + $element['format']['guidelines'][$format->format] = array( + '#theme' => 'filter_guidelines', + '#format' => $format, ); } - $form['format'] = array( + + // Use the default format for this user if none was selected. + if (empty($element['#format'])) { + $element['#format'] = filter_default_format($user); + } + $element['format']['format'] = array( '#type' => 'select', '#title' => t('Text format'), '#options' => $options, - '#default_value' => $selected_format, - '#parents' => $parents, + '#default_value' => $element['#format'], '#access' => count($formats) > 1, - '#id' => $element_id, + '#weight' => 10, '#attributes' => array('class' => array('filter-list')), + '#parents' => $format_parents, ); - $form['format_help'] = array( - '#prefix' => '
', - '#markup' => theme('filter_tips_more_info'), - '#suffix' => '
', - '#weight' => 1, + + $element['format']['help'] = array( + '#type' => 'container', + '#theme' => 'filter_tips_more_info', + '#attributes' => array('class' => array('filter-help')), + '#weight' => 0, ); - return $form; + return $element; +} + +/** + * Theme a text format form element. + * + * @param $variables + * An associative array containing: + * - element: An associative array containing the properties of the element. + * Properties used: #children, #description + * + * @return + * A string representing the form element. + * + * @ingroup themeable + */ +function theme_filter_format_wrapper($variables) { + $element = $variables['element']; + $output = '
'; + $output .= $element['#children']; + if (!empty($element['#description'])) { + $output .= '
' . $element['#description'] . '
'; + } + $output .= "
\n"; + + return $output; } /** Index: modules/system/system.module =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.module,v retrieving revision 1.837 diff -u -p -r1.837 system.module --- modules/system/system.module 7 Nov 2009 13:35:21 -0000 1.837 +++ modules/system/system.module 7 Nov 2009 16:19:57 -0000 @@ -354,7 +354,7 @@ function system_element_info() { '#size' => 60, '#maxlength' => 128, '#autocomplete_path' => FALSE, - '#process' => array('form_process_text_format', 'ajax_process_form'), + '#process' => array('ajax_process_form'), '#theme' => 'textfield', '#theme_wrappers' => array('form_element'), ); @@ -376,7 +376,7 @@ function system_element_info() { '#cols' => 60, '#rows' => 5, '#resizable' => TRUE, - '#process' => array('form_process_text_format', 'ajax_process_form'), + '#process' => array('ajax_process_form'), '#theme' => 'textarea', '#theme_wrappers' => array('form_element'), );