diff options
author | Dries Buytaert <dries@buytaert.net> | 2009-06-12 08:39:40 +0000 |
---|---|---|
committer | Dries Buytaert <dries@buytaert.net> | 2009-06-12 08:39:40 +0000 |
commit | 3d64cb5ecae7c0d093e1343f87901769dc7d819e (patch) | |
tree | 765b3104ae2bbdf96ac677f8deab9b5457ffa4bf | |
parent | bfdea95337376b00e60049b640c076e8ab32293f (diff) | |
download | brdo-3d64cb5ecae7c0d093e1343f87901769dc7d819e.tar.gz brdo-3d64cb5ecae7c0d093e1343f87901769dc7d819e.tar.bz2 |
- Patch #372743 by bjaspan, yched, KarenS, catch et al: node body and teasers as fields. Oh, my.
42 files changed, 920 insertions, 703 deletions
diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 798c55977..5ce66c7c9 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -110,6 +110,8 @@ Drupal 7.0, xxxx-xx-xx (development version) for RDFa support. - Field API: * Custom data fields may be attached to nodes and users in Drupal. + * Node bodies and teasers are now Field API fields instead of + being a hard-coded property of node objects. * In addition, any other object type may register with Field API and allow custom data fields to be attached to itself. * Provides a subset of the features of the Content Construction diff --git a/modules/aggregator/aggregator.pages.inc b/modules/aggregator/aggregator.pages.inc index 5c5d0c70c..3884f509f 100644 --- a/modules/aggregator/aggregator.pages.inc +++ b/modules/aggregator/aggregator.pages.inc @@ -378,11 +378,11 @@ function theme_aggregator_page_rss($feeds, $category = NULL) { foreach ($feeds as $feed) { switch ($feed_length) { case 'teaser': - $teaser = node_teaser($feed->description, NULL, variable_get('aggregator_teaser_length', 600)); - if ($teaser != $feed->description) { - $teaser .= '<p><a href="' . check_url($feed->link) . '">' . t('read more') . "</a></p>\n"; + $summary = text_summary($feed->description, NULL, variable_get('aggregator_teaser_length', 600)); + if ($summary != $feed->description) { + $summary .= '<p><a href="' . check_url($feed->link) . '">' . t('read more') . "</a></p>\n"; } - $feed->description = $teaser; + $feed->description = $summary; break; case 'title': $feed->description = ''; diff --git a/modules/aggregator/aggregator.test b/modules/aggregator/aggregator.test index aa9e1db59..4abef3721 100644 --- a/modules/aggregator/aggregator.test +++ b/modules/aggregator/aggregator.test @@ -252,7 +252,7 @@ EOF; for ($i = 0; $i < 5; $i++) { $edit = array(); $edit['title'] = $this->randomName(); - $edit['body'] = $this->randomName(); + $edit['body[0][value]'] = $this->randomName(); $this->drupalPost('node/add/article', $edit, t('Save')); } } diff --git a/modules/blog/blog.module b/modules/blog/blog.module index d4e1171b1..900e0930a 100644 --- a/modules/blog/blog.module +++ b/modules/blog/blog.module @@ -73,12 +73,7 @@ function blog_help($path, $arg) { * Implement hook_form(). */ function blog_form($node, $form_state) { - global $nid; - $type = node_type_get_type($node); - - $form['title'] = array('#type' => 'textfield', '#title' => check_plain($type->title_label), '#required' => TRUE, '#default_value' => !empty($node->title) ? $node->title : NULL, '#weight' => -5); - $form['body_field'] = node_body_field($node, $type->body_label, $type->min_word_count); - return $form; + return node_content_form($node, $form_state); } /** @@ -89,8 +84,7 @@ function blog_view($node, $teaser) { // Breadcrumb navigation. drupal_set_breadcrumb(array(l(t('Home'), NULL), l(t('Blogs'), 'blog'), l(t("!name's blog", array('!name' => $node->name)), 'blog/' . $node->uid))); } - - return node_prepare($node, $teaser); + return $node; } /** diff --git a/modules/blog/blog.test b/modules/blog/blog.test index 8b231d878..a59a63cf7 100644 --- a/modules/blog/blog.test +++ b/modules/blog/blog.test @@ -154,7 +154,7 @@ class BlogTestCase extends DrupalWebTestCase { // Edit blog node. $edit = array(); $edit['title'] = 'node/' . $node->nid; - $edit['body'] = $this->randomName(256); + $edit['body[0][value]'] = $this->randomName(256); $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); $this->assertRaw(t('Blog entry %title has been updated.', array('%title' => $edit['title'])), t('Blog node was edited')); diff --git a/modules/blogapi/blogapi.module b/modules/blogapi/blogapi.module index 52ebbb0eb..ef8e22693 100644 --- a/modules/blogapi/blogapi.module +++ b/modules/blogapi/blogapi.module @@ -213,7 +213,7 @@ function blogapi_blogger_new_post($appkey, $blogid, $username, $password, $conte } else { $edit['title'] = blogapi_blogger_title($content); - $edit['body'] = $content; + $edit['body'][0]['value'] = $content; } if (!node_access('create', $edit['type'])) { @@ -274,12 +274,12 @@ function blogapi_blogger_edit_post($appkey, $postid, $username, $password, $cont // Check for bloggerAPI vs. metaWeblogAPI. if (is_array($content)) { $node->title = $content['title']; - $node->body = $content['description']; + $node->body[0]['value'] = $content['description']; _blogapi_mt_extra($node, $content); } else { $node->title = blogapi_blogger_title($content); - $node->body = $content; + $node->body[0]['value'] = $content; } module_invoke_all('node_blogapi_edit', $node); @@ -379,7 +379,7 @@ function blogapi_blogger_get_recent_posts($appkey, $blogid, $username, $password } if ($bodies) { - $result = db_query_range("SELECT n.nid, n.title, r.body, r.format, n.comment, n.created, u.name FROM {node} n, {node_revision} r, {users} u WHERE n.uid = u.uid AND n.vid = r.vid AND n.type = :type AND n.uid = :uid ORDER BY n.created DESC", array( + $result = db_query_range("SELECT n.nid, n.title, n.comment, n.created, u.name FROM {node} n, {node_revision} r, {users} u WHERE n.uid = u.uid AND n.vid = r.vid AND n.type = :type AND n.uid = :uid ORDER BY n.created DESC", array( ':type' => $blogid, ':uid' => $user->uid ), 0, $number_of_posts); @@ -892,14 +892,14 @@ function _blogapi_mt_extra($node, $struct) { // Merge the 3 body sections (description, mt_excerpt, mt_text_more) into one body. if ($struct['mt_excerpt']) { - $node->body = $struct['mt_excerpt'] . '<!--break-->' . $node->body; + $node->body[0]['value'] = $struct['mt_excerpt'] . '<!--break-->' . $node->body[0]['value']; } if ($struct['mt_text_more']) { - $node->body = $node->body . '<!--extended-->' . $struct['mt_text_more']; + $node->body[0]['value'] = $node->body[0]['value'] . '<!--extended-->' . $struct['mt_text_more']; } if ($struct['mt_convert_breaks']) { - $node->format = $struct['mt_convert_breaks']; + $node->body[0]['format'] = $struct['mt_convert_breaks']; } if ($struct['dateCreated']) { @@ -922,17 +922,19 @@ function _blogapi_get_post($node, $bodies = TRUE) { ); if ($bodies) { + $body = $node->body[0]['value']; + $format = $node->body[0]['format']; if ($node->comment == 1) { $comment = 2; } elseif ($node->comment == 2) { $comment = 1; } - $xmlrpcval['content'] = "<title>$node->title</title>$node->body"; - $xmlrpcval['description'] = $node->body; + $xmlrpcval['content'] = "<title>$node->title</title>$body"; + $xmlrpcval['description'] = $body; // Add MT specific fields $xmlrpcval['mt_allow_comments'] = (int) $comment; - $xmlrpcval['mt_convert_breaks'] = $node->format; + $xmlrpcval['mt_convert_breaks'] = $format; } return $xmlrpcval; diff --git a/modules/book/book.module b/modules/book/book.module index e70b824a2..c17e45199 100644 --- a/modules/book/book.module +++ b/modules/book/book.module @@ -1037,7 +1037,7 @@ function book_export_traverse($tree, $visit_func) { function book_node_export($node, $children = '') { $node->build_mode = NODE_BUILD_PRINT; $node = node_build_content($node, FALSE, FALSE); - $node->body = drupal_render($node->content); + $node->rendered = drupal_render($node->content); return theme('book_node_export_html', $node, $children); } @@ -1054,7 +1054,7 @@ function book_node_export($node, $children = '') { function template_preprocess_book_node_export_html(&$variables) { $variables['depth'] = $variables['node']->book['depth']; $variables['title'] = check_plain($variables['node']->title); - $variables['content'] = $variables['node']->body; + $variables['content'] = $variables['node']->rendered; } /** diff --git a/modules/book/book.test b/modules/book/book.test index 8290f6f17..1c8ac0c32 100644 --- a/modules/book/book.test +++ b/modules/book/book.test @@ -141,8 +141,7 @@ class BookTestCase extends DrupalWebTestCase { // Check printer friendly version. $this->drupalGet('book/export/html/' . $node->nid); $this->assertText($node->title, t('Printer friendly title found.')); - $node->body = str_replace('<!--break-->', '', $node->body); - $this->assertRaw(check_markup($node->body, $node->format), t('Printer friendly body found.')); + $this->assertRaw(check_markup($node->body[0]['value'], $node->body[0]['format']), t('Printer friendly body found.')); $number++; } @@ -174,7 +173,7 @@ class BookTestCase extends DrupalWebTestCase { $edit = array(); $edit['title'] = $number . ' - SimpleTest test node ' . $this->randomName(10); - $edit['body'] = 'SimpleTest test body ' . $this->randomName(32) . ' ' . $this->randomName(32); + $edit['body[0][value]'] = 'SimpleTest test body ' . $this->randomName(32) . ' ' . $this->randomName(32); $edit['book[bid]'] = $book_nid; if ($parent !== NULL) { diff --git a/modules/dblog/dblog.test b/modules/dblog/dblog.test index 644ae63c7..d41147804 100644 --- a/modules/dblog/dblog.test +++ b/modules/dblog/dblog.test @@ -327,7 +327,7 @@ class DBLogTestCase extends DrupalWebTestCase { default: $content = array( 'title' => $this->randomName(8), - 'body' => $this->randomName(32), + 'body[0][value]' => $this->randomName(32), ); break; } @@ -351,7 +351,7 @@ class DBLogTestCase extends DrupalWebTestCase { default: $content = array( - 'body' => $this->randomName(32), + 'body[0][value]' => $this->randomName(32), ); break; } diff --git a/modules/field/field.crud.inc b/modules/field/field.crud.inc index 97b3d8a70..956122ecf 100644 --- a/modules/field/field.crud.inc +++ b/modules/field/field.crud.inc @@ -425,7 +425,9 @@ function field_create_instance($instance) { // Set the field id. $instance['field_id'] = $field['id']; - // TODO: Check that the specifed bundle exists. + // Note that we do *not* prevent creating a field on non-existing bundles, + // because that would break the 'Body as field' upgrade for contrib + // node types. // TODO: Check that the widget type is known and can handle the field type ? // TODO: Check that the formatters are known and can handle the field type ? diff --git a/modules/field/field.test b/modules/field/field.test index c4aee219b..07d047f06 100644 --- a/modules/field/field.test +++ b/modules/field/field.test @@ -930,10 +930,9 @@ class FieldInfoTestCase extends DrupalWebTestCase { $this->assertEqual($info[$w_key]['module'], 'field_test', t("Widget type field_test module appears")); } - // Verify that no fields or instances exist - $fields = field_info_fields(); + // Verify that no unexpected instances exist. + $core_fields = field_info_fields(); $instances = field_info_instances(FIELD_TEST_BUNDLE); - $this->assertTrue(empty($fields), t('With no fields, info fields is empty.')); $this->assertTrue(empty($instances), t('With no instances, info bundles is empty.')); // Create a field, verify it shows up. @@ -943,7 +942,7 @@ class FieldInfoTestCase extends DrupalWebTestCase { ); field_create_field($field); $fields = field_info_fields(); - $this->assertEqual(count($fields), 1, t('One field exists')); + $this->assertEqual(count($fields), count($core_fields) + 1, t('One new field exists')); $this->assertEqual($fields[$field['field_name']]['field_name'], $field['field_name'], t('info fields contains field name')); $this->assertEqual($fields[$field['field_name']]['type'], $field['type'], t('info fields contains field type')); $this->assertEqual($fields[$field['field_name']]['module'], 'field_test', t('info fields contains field module')); diff --git a/modules/field/modules/list/list.info b/modules/field/modules/list/list.info index 1e201a386..96a98d500 100644 --- a/modules/field/modules/list/list.info +++ b/modules/field/modules/list/list.info @@ -5,3 +5,4 @@ package = Core - fields version = VERSION core = 7.x files[]=list.module +required = TRUE diff --git a/modules/field/modules/number/number.info b/modules/field/modules/number/number.info index e77fa7959..314506807 100644 --- a/modules/field/modules/number/number.info +++ b/modules/field/modules/number/number.info @@ -5,3 +5,4 @@ package = Core - fields version = VERSION core = 7.x files[]=number.module +required = TRUE diff --git a/modules/field/modules/options/options.info b/modules/field/modules/options/options.info index d61d108b8..04d53c422 100644 --- a/modules/field/modules/options/options.info +++ b/modules/field/modules/options/options.info @@ -5,3 +5,4 @@ package = Core - fields version = VERSION core = 7.x files[]=options.module +required = TRUE diff --git a/modules/field/modules/text/text.info b/modules/field/modules/text/text.info index 17a676025..6e8bb24ee 100644 --- a/modules/field/modules/text/text.info +++ b/modules/field/modules/text/text.info @@ -6,3 +6,4 @@ version = VERSION core = 7.x files[] = text.module files[] = text.test +required = TRUE
\ No newline at end of file diff --git a/modules/field/modules/text/text.module b/modules/field/modules/text/text.module index 7c31d54cb..00ba52017 100644 --- a/modules/field/modules/text/text.module +++ b/modules/field/modules/text/text.module @@ -14,6 +14,9 @@ function text_theme() { 'text_textarea' => array( 'arguments' => array('element' => NULL), ), + 'text_textarea_with_summary' => array( + 'arguments' => array('element' => NULL), + ), 'text_textfield' => array( 'arguments' => array('element' => NULL), ), @@ -26,11 +29,22 @@ function text_theme() { 'field_formatter_text_trimmed' => array( 'arguments' => array('element' => NULL), ), + 'field_formatter_text_summary_or_trimmed' => array( + 'arguments' => array('element' => NULL), + ), ); } /** * Implement hook_field_info(). + * + * Field settings: + * - max_length: the maximum length for a varchar field. + * Instance settings: + * - text_processing: whether text input filters should be used. + * - display_summary: whether the summary field should be displayed. + * When empty and not displayed the summary will take its value from the + * trimmed value of the main text field. */ function text_field_info() { return array( @@ -39,18 +53,25 @@ function text_field_info() { 'description' => t('This field stores varchar text in the database.'), 'settings' => array('max_length' => 255), 'instance_settings' => array('text_processing' => 0), - 'widget_settings' => array('size' => 60), 'default_widget' => 'text_textfield', 'default_formatter' => 'text_default', ), 'text_long' => array( 'label' => t('Long text'), 'description' => t('This field stores long text in the database.'), + 'settings' => array('max_length' => ''), 'instance_settings' => array('text_processing' => 0), - 'widget_settings' => array('rows' => 5), 'default_widget' => 'text_textarea', 'default_formatter' => 'text_default', ), + 'text_with_summary' => array( + 'label' => t('Long text and summary'), + 'description' => t('This field stores long text in the database along with optional summary text.'), + 'settings' => array('max_length' => ''), + 'instance_settings' => array('text_processing' => 1, 'display_summary' => 0), + 'default_widget' => 'text_textarea_with_summary', + 'default_formatter' => 'text_summary_or_trimmed', + ), ); } @@ -58,23 +79,39 @@ function text_field_info() { * Implement hook_field_schema(). */ function text_field_schema($field) { - if ($field['type'] == 'text_long') { - $columns = array( - 'value' => array( - 'type' => 'text', - 'size' => 'big', - 'not null' => FALSE, - ), - ); - } - else { - $columns = array( - 'value' => array( - 'type' => 'varchar', - 'length' => $field['settings']['max_length'], - 'not null' => FALSE, - ), - ); + switch ($field['type']) { + case 'text': + $columns = array( + 'value' => array( + 'type' => 'varchar', + 'length' => $field['settings']['max_length'], + 'not null' => FALSE, + ), + ); + break; + case 'text_long': + $columns = array( + 'value' => array( + 'type' => 'text', + 'size' => 'big', + 'not null' => FALSE, + ), + ); + break; + case 'text_with_summary': + $columns = array( + 'value' => array( + 'type' => 'text', + 'size' => 'big', + 'not null' => FALSE, + ), + 'summary' => array( + 'type' => 'text', + 'size' => 'big', + 'not null' => FALSE, + ), + ); + break; } $columns += array( 'format' => array( @@ -95,16 +132,27 @@ function text_field_schema($field) { * Implement hook_field_validate(). * * Possible error codes: - * - 'text_max_length': The value exceeds the maximum length. + * - 'text_value_max_length': The value exceeds the maximum length. + * - 'text_summary_max_length': The summary exceeds the maximum length. */ function text_field_validate($obj_type, $object, $field, $instance, $items, &$errors) { foreach ($items as $delta => $item) { - if (!empty($item['value'])) { - if (!empty($field['settings']['max_length']) && drupal_strlen($item['value']) > $field['settings']['max_length']) { - $errors[$field['field_name']][$delta][] = array( - 'error' => 'text_max_length', - 'message' => t('%name: the value may not be longer than %max characters.', array('%name' => $instance['label'], '%max' => $field['settings']['max_length'])), - ); + foreach (array('value' => t('full text'), 'summary' => t('summary')) as $column => $desc) { + 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; + } + $errors[$field['field_name']][$delta][] = array( + 'error' => "text_{$column}_length", + 'message' => $message, + ); + } } } } @@ -126,17 +174,22 @@ function text_field_load($obj_type, $objects, $field, $instances, &$items) { if (!empty($instances[$id]['settings']['text_processing'])) { // Only process items with a cacheable format, the rest will be // handled by text_field_sanitize(). - if (filter_format_allowcache($item['format'])) { + $format = $item['format']; + if (filter_format_allowcache($format)) { // TODO D7 : this code is really node-related. $check = is_null($object) || (isset($object->build_mode) && $object->build_mode == NODE_BUILD_PREVIEW); - $text = isset($item['value']) ? check_markup($item['value'], $item['format'], isset($object->language) ? $object->language : $language->language, $check, FALSE) : ''; + $lang = isset($object->language) ? $object->language : $language->language; + $items[$id][$delta]['safe'] = isset($item['value']) ? check_markup($item['value'], $format, $lang, $check, FALSE) : ''; + if ($field['type'] == 'text_with_summary') { + $items[$id][$delta]['safe_summary'] = isset($item['summary']) ? check_markup($item['summary'], $format, $lang, $check, FALSE) : ''; + } } } else { - $text = check_plain($item['value']); - } - if (isset($text)) { - $items[$id][$delta]['safe'] = $text; + $items[$id][$delta]['safe'] = check_plain($item['value']); + if ($field['type'] == 'text_with_summary') { + $items[$id][$delta]['safe_summary'] = check_plain($item['summary']); + } } } } @@ -155,14 +208,21 @@ function text_field_sanitize($obj_type, $object, $field, $instance, &$items) { // from a form preview. if (!isset($items[$delta]['safe'])) { if (!empty($instance['settings']['text_processing'])) { + $format = $item['format']; // TODO D7 : this code is really node-related. $check = is_null($object) || (isset($object->build_mode) && $object->build_mode == NODE_BUILD_PREVIEW); - $text = isset($item['value']) ? check_markup($item['value'], $item['format'], isset($object->language) ? $object->language : $language->language, $check) : ''; + $lang = isset($object->language) ? $object->language : $language->language; + $items[$delta]['safe'] = isset($item['value']) ? check_markup($item['value'], $format, $lang, $check) : ''; + if ($field['type'] == 'text_with_summary') { + $items[$delta]['safe_summary'] = isset($item['summary']) ? check_markup($item['summary'], $format, $lang, $check) : ''; + } } else { - $text = check_plain($item['value']); + $item[$delta]['safe'] = check_plain($item['value']); + if ($field['type'] == 'text_with_summary') { + $item[$delta]['safe_summary'] = check_plain($item['summary']); + } } - $items[$delta]['safe'] = $text; } } } @@ -184,21 +244,39 @@ function text_field_formatter_info() { return array( 'text_default' => array( 'label' => t('Default'), - 'field types' => array('text', 'text_long'), + 'field types' => array('text', 'text_long', 'text_with_summary'), 'behaviors' => array( 'multiple values' => FIELD_BEHAVIOR_DEFAULT, ), ), 'text_plain' => array( 'label' => t('Plain text'), - 'field types' => array('text', 'text_long'), + 'field types' => array('text', 'text_long', 'text_with_summary'), 'behaviors' => array( 'multiple values' => FIELD_BEHAVIOR_DEFAULT, ), ), + + // The text_trimmed formatter displays the trimmed version of the + // full element of the field. It is intended to be used with text + // and text_long fields. It also works with text_with_summary + // fields though the text_summary_or_trimmed formatter makes more + // sense for that field type. 'text_trimmed' => array( 'label' => t('Trimmed'), - 'field types' => array('text', 'text_long'), + 'field types' => array('text', 'text_long', 'text_with_summary'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + ), + ), + + // The 'summary or trimmed' field formatter for text_with_summary + // fields displays returns the summary element of the field or, if + // the summary is empty, the trimmed version of the full element + // of the field. + 'text_summary_or_trimmed' => array( + 'label' => t('Summary or trimmed'), + 'field types' => array('text_with_summary'), 'behaviors' => array( 'multiple values' => FIELD_BEHAVIOR_DEFAULT, ), @@ -226,7 +304,150 @@ function theme_field_formatter_text_plain($element) { function theme_field_formatter_text_trimmed($element) { $field = field_info_field($element['#field_name']); $instance = field_info_instance($element['#field_name'], $element['#bundle']); - return $instance['settings']['text_processing'] ? $element['#item']['format'] : NULL; + return text_summary($element['#item']['safe'], $instance['settings']['text_processing'] ? $element['#item']['format'] : NULL); +} + +/** + * Theme function for 'summary or trimmed' field formatter for + * text_with_summary fields. This formatter returns the summary + * element of the field or, if the summary is empty, the trimmed + * version of the full element of the field. + */ +function theme_field_formatter_text_summary_or_trimmed($element) { + $field = field_info_field($element['#field_name']); + $instance = field_info_instance($element['#field_name'], $element['#bundle']); + + if (!empty($element['#item']['safe_summary'])) { + return $element['#item']['safe_summary']; + } + else { + return text_summary($element['#item']['safe'], $instance['settings']['text_processing'] ? $element['#item']['format'] : NULL); + } +} + +/** + * Generate a trimmed, formatted version of a text field value. + * + * If the end of the summary is not indicated using the <!--break--> delimiter + * then we generate the summary automatically, trying to end it at a sensible + * place such as the end of a paragraph, a line break, or the end of a + * sentence (in that order of preference). + * + * @param $text + * The content for which a summary will be generated. + * @param $format + * The format of the content. + * If the PHP filter is present and $text contains PHP code, we do not + * split it up to prevent parse errors. + * If the line break filter is present then we treat newlines embedded in + * $text as line breaks. + * If the htmlcorrector filter is present, it will be run on the generated + * summary (if different from the incoming $text). + * @param $size + * The desired character length of the summary. If omitted, the default + * value will be used. Ignored if the special delimiter is present + * in $text. + * @return + * The generated summary. + */ +function text_summary($text, $format = NULL, $size = NULL) { + + if (!isset($size)) { + // What used to be called 'teaser' is now called 'summary', but + // the variable 'teaser_length' is preserved for backwards compatibility. + $size = variable_get('teaser_length', 600); + } + + // Find where the delimiter is in the body + $delimiter = strpos($text, '<!--break-->'); + + // If the size is zero, and there is no delimiter, the entire body is the summary. + if ($size == 0 && $delimiter === FALSE) { + return $text; + } + + // If a valid delimiter has been specified, use it to chop off the summary. + if ($delimiter !== FALSE) { + return substr($text, 0, $delimiter); + } + + // We check for the presence of the PHP evaluator filter in the current + // format. If the body contains PHP code, we do not split it up to prevent + // parse errors. + if (isset($format)) { + $filters = filter_list_format($format); + if (isset($filters['php/0']) && strpos($text, '<?') !== FALSE) { + return $text; + } + } + + // If we have a short body, the entire body is the summary. + if (drupal_strlen($text) <= $size) { + return $text; + } + + // If the delimiter has not been specified, try to split at paragraph or + // sentence boundaries. + + // The summary may not be longer than maximum length specified. Initial slice. + $summary = truncate_utf8($text, $size); + + // Store the actual length of the UTF8 string -- which might not be the same + // as $size. + $max_rpos = strlen($summary); + + // How much to cut off the end of the summary so that it doesn't end in the + // middle of a paragraph, sentence, or word. + // Initialize it to maximum in order to find the minimum. + $min_rpos = $max_rpos; + + // Store the reverse of the summary. We use strpos on the reversed needle and + // haystack for speed and convenience. + $reversed = strrev($summary); + + // Build an array of arrays of break points grouped by preference. + $break_points = array(); + + // A paragraph near the end of sliced summary is most preferable. + $break_points[] = array('</p>' => 0); + + // If no complete paragraph then treat line breaks as paragraphs. + $line_breaks = array('<br />' => 6, '<br>' => 4); + // Newline only indicates a line break if line break converter + // filter is present. + if (isset($filters['filter/1'])) { + $line_breaks["\n"] = 1; + } + $break_points[] = $line_breaks; + + // If the first paragraph is too long, split at the end of a sentence. + $break_points[] = array('. ' => 1, '! ' => 1, '? ' => 1, '。' => 0, '؟ ' => 1); + + // Iterate over the groups of break points until a break point is found. + foreach ($break_points as $points) { + // Look for each break point, starting at the end of the summary. + foreach ($points as $point => $offset) { + // The summary is already reversed, but the break point isn't. + $rpos = strpos($reversed, strrev($point)); + if ($rpos !== FALSE) { + $min_rpos = min($rpos + $offset, $min_rpos); + } + } + + // If a break point was found in this group, slice and stop searching. + if ($min_rpos !== $max_rpos) { + // Don't slice with length 0. Length must be <0 to slice from RHS. + $summary = ($min_rpos === 0) ? $summary : substr($summary, 0, 0 - $min_rpos); + break; + } + } + + // If the htmlcorrector filter is present, apply it to the generated summary. + if (isset($filters['filter/3'])) { + $summary = _filter_htmlcorrector($summary); + } + + return $summary; } /** @@ -260,6 +481,15 @@ function text_field_widget_info() { 'default value' => FIELD_BEHAVIOR_DEFAULT, ), ), + 'text_textarea_with_summary' => array( + 'label' => t('Text area with a summary'), + 'field types' => array('text_with_summary'), + 'settings' => array('rows' => 20, 'summary_rows' => 5), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + 'default value' => FIELD_BEHAVIOR_DEFAULT, + ), + ), ); } @@ -281,14 +511,23 @@ function text_elements() { '#input' => TRUE, '#columns' => array('value'), '#delta' => 0, '#process' => array('text_textfield_elements_process'), + '#theme_wrapper' => 'text_textfield', '#autocomplete_path' => FALSE, - ), + ), 'text_textarea' => array( '#input' => TRUE, '#columns' => array('value', 'format'), '#delta' => 0, '#process' => array('text_textarea_elements_process'), + '#theme_wrapper' => 'text_textarea', '#filter_value' => FILTER_FORMAT_DEFAULT, - ), + ), + 'text_textarea_with_summary' => array( + '#input' => TRUE, + '#columns' => array('value', 'format', 'summary'), '#delta' => 0, + '#process' => array('text_textarea_with_summary_process'), + '#theme_wrapper' => 'text_textarea_with_summary', + '#filter_value' => FILTER_FORMAT_DEFAULT, + ), ); } @@ -329,6 +568,10 @@ function text_field_widget(&$form, &$form_state, $field, $instance, $items, $del '#type' => $instance['widget']['type'], '#default_value' => isset($items[$delta]) ? $items[$delta] : '', ); + if (!empty($instance['settings']['text_processing'])) { + $element['#value_callback'] = 'text_field_widget_formatted_text_value'; + } + return $element; } @@ -336,7 +579,17 @@ function text_field_widget(&$form, &$form_state, $field, $instance, $items, $del * Implement hook_field_widget_error(). */ function text_field_widget_error($element, $error) { - form_error($element['value'], $error['message']); + switch ($error['error']) { + case 'text_summary_max_length': + $error_element = $element[$element['#columns'][1]]; + break; + + default: + $error_element = $element[$element['#columns'][0]]; + break; + } + + form_error($error_element, $error['message']); } /** @@ -364,24 +617,17 @@ function text_textfield_elements_process($element, $form_state, $form) { '#autocomplete_path' => $element['#autocomplete_path'], '#size' => $instance['widget']['settings']['size'], '#attributes' => array('class' => 'text'), - // The following values were set by the field module and need - // to be passed down to the nested element. '#title' => $element['#title'], '#description' => $element['#description'], '#required' => $element['#required'], - '#field_name' => $element['#field_name'], - '#bundle' => $element['#bundle'], - '#delta' => $element['#delta'], - '#columns' => $element['#columns'], ); $element[$field_key]['#maxlength'] = !empty($field['settings']['max_length']) ? $field['settings']['max_length'] : NULL; if (!empty($instance['settings']['text_processing'])) { - $filter_key = $element['#columns'][1]; + $filter_key = (count($element['#columns']) == 2) ? $element['#columns'][1] : 'format'; $format = isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : FILTER_FORMAT_DEFAULT; - $parents = array_merge($element['#parents'] , array($filter_key)); - $element[$filter_key] = filter_form($format, 1, $parents); + $element[$field_key]['#text_format'] = $format; } return $element; @@ -400,33 +646,95 @@ function text_textarea_elements_process($element, $form_state, $form) { $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, - // The following values were set by the field module and need - // to be passed down to the nested element. '#title' => $element['#title'], '#description' => $element['#description'], '#required' => $element['#required'], - '#field_name' => $element['#field_name'], - '#bundle' => $element['#bundle'], - '#delta' => $element['#delta'], - '#columns' => $element['#columns'], ); 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_FORMAT_DEFAULT; - $parents = array_merge($element['#parents'] , array($filter_key)); - $element[$filter_key] = filter_form($format, 1, $parents); + $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.'), + '#required' => $element['#required'], + '#display' => $display, + ); + + $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'], + '#required' => $instance['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_FORMAT_DEFAULT; + $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_resolve_format(FILTER_FORMAT_DEFAULT); + unset($edit[$default_key]); + return $edit; + } +} + +/** * FAPI theme for an individual text elements. * * The textfield or textarea is already rendered by the @@ -444,3 +752,21 @@ function theme_text_textfield($element) { function theme_text_textarea($element) { return $element['#children']; } + +function theme_text_textarea_with_summary($element) { + // If displaying both a textarea and a summary field, wrap them + // in a fieldset to make it clear they belong together. + $field_key = $element['#columns'][1]; + if (!empty($element[$field_key]['#display'])) { + $fieldset = array( + '#title' => $element['#title'], + '#value' => $element['#children'], + '#attributes' => array('class' => 'text-textarea'), + '#id' => str_replace('_', '-', $element['#field_name']) . '-summary-wrapper', + ); + return theme('fieldset', $fieldset); + } + else { + return $element['#children']; + } +} diff --git a/modules/field/modules/text/text.test b/modules/field/modules/text/text.test index cb240b6c9..3c2d706b9 100644 --- a/modules/field/modules/text/text.test +++ b/modules/field/modules/text/text.test @@ -13,7 +13,7 @@ class TextFieldTestCase extends DrupalWebTestCase { } function setUp() { - parent::setUp('field', 'text', 'field_test'); + parent::setUp('field_test'); $web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content')); $this->drupalLogin($web_user); @@ -149,7 +149,7 @@ class TextFieldTestCase extends DrupalWebTestCase { // selector is displayed $this->drupalGet('test-entity/add/test-bundle'); $this->assertFieldByName($this->field_name . '[0][value]', '', t('Widget is displayed')); - $this->assertNoFieldByName($this->field_name . '[0][format]', '1', t('Format selector is not displayed')); + $this->assertNoFieldByName($this->field_name . '[0][value_format]', '1', t('Format selector is not displayed')); // Submit with data that should be filtered. $value = $this->randomName() . '<br />' . $this->randomName(); @@ -175,11 +175,11 @@ class TextFieldTestCase extends DrupalWebTestCase { // We should now have a 'text format' selector. $this->drupalGet('test-entity/' . $id . '/edit'); $this->assertFieldByName($this->field_name . '[0][value]', '', t('Widget is displayed')); - $this->assertFieldByName($this->field_name . '[0][format]', '1', t('Format selector is displayed')); + $this->assertFieldByName($this->field_name . '[0][value_format]', '1', t('Format selector is displayed')); // Edit and change the format to 'Full HTML'. $edit = array( - $this->field_name . '[0][format]' => 2, + $this->field_name . '[0][value_format]' => 2, ); $this->drupalPost(NULL, $edit, t('Save')); $this->assertRaw(t('test_entity @id has been updated.', array('@id' => $id)), t('Entity was updated')); @@ -196,3 +196,148 @@ class TextFieldTestCase extends DrupalWebTestCase { * */ } + +class TextSummaryTestCase extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => t('Text summary'), + 'description' => t('Test text_summary() with different strings and lengths.'), + 'group' => t('Field'), + ); + } + + /** + * Tests an edge case where the first sentence is a question and + * subsequent sentences are not. This edge case is documented at + * http://drupal.org/node/180425. + */ + function testFirstSentenceQuestion() { + $text = 'A question? A sentence. Another sentence.'; + $expected = 'A question? A sentence.'; + $this->callTextSummary($text, $expected, NULL, 30); + } + + /** + * Test summary with long example. + */ + function testLongSentence() { + $text = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ' . // 125 + 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. ' . // 108 + 'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. ' . // 103 + 'Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'; // 110 + $expected = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ' . + 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. ' . + 'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.'; + // First three sentences add up to: 336, so add one for space and then 3 to get half-way into next word. + $this->callTextSummary($text, $expected, NULL, 340); + } + + /** + * Test various summary length edge cases. + */ + function testLength() { + // This string tests a number of edge cases. + $text = "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>"; + + // The summaries we expect text_summary() to return when $size is the index + // of each array item. + // Using no text format: + $expected = array( + "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>", + "<", + "<p", + "<p>", + "<p>\n", + "<p>\nH", + "<p>\nHi", + "<p>\nHi\n", + "<p>\nHi\n<", + "<p>\nHi\n</", + "<p>\nHi\n</p", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>", + "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>", + "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>", + ); + + // And using a text format WITH the line-break and htmlcorrector filters. + $expected_lb = array( + "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>", + "<", + "<p", + "<p></p>", + "<p></p>", + "<p></p>", + "<p></p>", + "<p>\nHi</p>", + "<p>\nHi</p>", + "<p>\nHi</p>", + "<p>\nHi</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>", + "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>", + "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>", + "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>", + ); + + // Test text_summary() for different sizes. + for ($i = 0; $i <= 37; $i++) { + $this->callTextSummary($text, $expected[$i], NULL, $i); + $this->callTextSummary($text, $expected_lb[$i], 1, $i); + $this->callTextSummary($text, $expected_lb[$i], 2, $i); + } + } + + /** + * Calls text_summary() and asserts that the expected teaser is returned. + */ + function callTextSummary($text, $expected, $format = NULL, $size = NULL) { + $summary = text_summary($text, $format, $size); + $this->assertIdentical($summary, $expected, t('Generated summary "@summary" matches expected "@expected".', array('@summary' => $summary, '@expected' => $expected))); + } +} diff --git a/modules/field/theme/field.css b/modules/field/theme/field.css index 17bf18915..93dcbcb08 100644 --- a/modules/field/theme/field.css +++ b/modules/field/theme/field.css @@ -32,7 +32,11 @@ form .field-add-more-submit { margin: .5em 0 0; } -.form-item .number { +form .form-item .text { display: inline; width: auto; -}
\ No newline at end of file +} +form .form-item .number { + display: inline; + width: auto; +} diff --git a/modules/filter/filter.admin.inc b/modules/filter/filter.admin.inc index 4b58e4fc1..aa9bc8b7a 100644 --- a/modules/filter/filter.admin.inc +++ b/modules/filter/filter.admin.inc @@ -311,10 +311,6 @@ function filter_admin_delete_submit($form, &$form_state) { $default = variable_get('filter_default_format', 1); // Replace existing instances of the deleted format with the default format. - db_update('node_revision') - ->fields(array('format' => $default)) - ->condition('format', $form_state['values']['format']) - ->execute(); if (db_table_exists('comment')) { db_update('comment') ->fields(array('format' => $default)) diff --git a/modules/filter/filter.test b/modules/filter/filter.test index e50e08538..53fc98518 100644 --- a/modules/filter/filter.test +++ b/modules/filter/filter.test @@ -109,8 +109,8 @@ class FilterAdminTestCase extends DrupalWebTestCase { $edit = array(); $edit['title'] = $this->randomName(); - $edit['body'] = $body . '<random>' . $extra_text . '</random>'; - $edit['body_format'] = $filtered; + $edit['body[0][value]'] = $body . '<random>' . $extra_text . '</random>'; + $edit['body[0][value_format]'] = $filtered; $this->drupalPost('node/add/page', $edit, t('Save')); $this->assertRaw(t('Page %title has been created.', array('%title' => $edit['title'])), t('Filtered node created.')); diff --git a/modules/forum/forum.module b/modules/forum/forum.module index eac3c58c8..8579ca519 100644 --- a/modules/forum/forum.module +++ b/modules/forum/forum.module @@ -549,8 +549,6 @@ function forum_form($node, $form_state) { $form['shadow'] = array('#type' => 'checkbox', '#title' => t('Leave shadow copy'), '#default_value' => $shadow, '#description' => t('If you move this topic, you can leave a link in the old forum to the new forum.')); } - $form['body_field'] = node_body_field($node, $type->body_label, 1); - $form['#submit'][] = 'forum_submit'; // Assign the forum topic submit handler. diff --git a/modules/forum/forum.test b/modules/forum/forum.test index 4941734bf..0b2978800 100644 --- a/modules/forum/forum.test +++ b/modules/forum/forum.test @@ -233,7 +233,7 @@ class ForumTestCase extends DrupalWebTestCase { $edit = array( 'title' => $title, - 'body' => $body, + 'body[0][value]' => $body, 'taxonomy[1]' => $tid ); @@ -322,7 +322,7 @@ class ForumTestCase extends DrupalWebTestCase { // Edit forum node (including moving it to another forum). $edit = array(); $edit['title'] = 'node/' . $node->nid; - $edit['body'] = $this->randomName(256); + $edit['body[0][value]'] = $this->randomName(256); $edit['taxonomy[1]'] = $this->root_forum['tid']; // Assumes the topic is initially associated with $forum. $edit['shadow'] = TRUE; $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); diff --git a/modules/help/help.test b/modules/help/help.test index 089bd4862..9bdd41d19 100644 --- a/modules/help/help.test +++ b/modules/help/help.test @@ -54,14 +54,19 @@ class HelpTestCase extends DrupalWebTestCase { // View module help node. $this->drupalGet('admin/help/' . $module); $this->assertResponse($response); - if ($response == 200) { - // NOTE: The asserts fail on blog and poll because the get returns the 'admin/help' node instead of the indicated node??? -// if ($module == 'blog' || $module == 'poll') { -// continue; -// } - $this->assertTitle($name . ' | Drupal', t('[' . $module . '] Title was displayed')); - $this->assertRaw('<h2>' . t($name) . '</h2>', t('[' . $module . '] Heading was displayed')); - $this->assertText(t('Home ' . $crumb . ' Administer ' . $crumb . ' Help'), t('[' . $module . '] Breadcrumbs were displayed')); + if (drupal_function_exists($module . '_help')) { + // View module help node. + $this->drupalGet('admin/help/' . $module); + $this->assertResponse($response); + if ($response == 200) { + // NOTE: The asserts fail on blog and poll because the get returns the 'admin/help' node instead of the indicated node??? +// if ($module == 'blog' || $module == 'poll') { +// continue; +// } + $this->assertTitle($name . ' | Drupal', t('[' . $module . '] Title was displayed')); + $this->assertRaw('<h2>' . t($name) . '</h2>', t('[' . $module . '] Heading was displayed')); + $this->assertText(t('Home ' . $crumb . ' Administer ' . $crumb . ' Help'), t('[' . $module . '] Breadcrumbs were displayed')); + } } } } diff --git a/modules/locale/locale.test b/modules/locale/locale.test index 95ab2d998..a2546e0be 100644 --- a/modules/locale/locale.test +++ b/modules/locale/locale.test @@ -1395,7 +1395,7 @@ class LocaleContentFunctionalTest extends DrupalWebTestCase { $edit = array( 'type' => 'page', 'title' => $node_title, - 'body' => $node_body, + 'body' => array(array('value' => $node_body)), 'language' => $langcode, ); $node = $this->drupalCreateNode($edit); diff --git a/modules/node/node.api.php b/modules/node/node.api.php index d3058a57d..66630b7d0 100644 --- a/modules/node/node.api.php +++ b/modules/node/node.api.php @@ -204,7 +204,7 @@ function hook_node_grants_alter(&$grants, $account, $op) { // Get our list of banned roles. $restricted = variable_get('example_restricted_roles', array()); - + if ($op != 'view' && !empty($restricted)) { // Now check the roles for this account against the restrictions. foreach ($restricted as $role_id) { @@ -904,7 +904,6 @@ function hook_view($node, $teaser = FALSE) { menu_set_location($breadcrumb); } - $node = node_prepare($node, $teaser); $node->content['myfield'] = array( '#value' => theme('mymodule_myfield', $node->myfield), '#weight' => 1, diff --git a/modules/node/node.install b/modules/node/node.install index 9ec782dea..72c82481a 100644 --- a/modules/node/node.install +++ b/modules/node/node.install @@ -213,18 +213,6 @@ function node_schema() { 'not null' => TRUE, 'default' => '', ), - 'body' => array( - 'description' => 'The body of this version.', - 'type' => 'text', - 'not null' => TRUE, - 'size' => 'big', - ), - 'teaser' => array( - 'description' => 'The teaser of this version.', - 'type' => 'text', - 'not null' => TRUE, - 'size' => 'big', - ), 'log' => array( 'description' => 'The log entry explaining the changes in this version.', 'type' => 'text', @@ -237,12 +225,6 @@ function node_schema() { 'not null' => TRUE, 'default' => 0, ), - 'format' => array( - 'description' => "The text format used by this version's body.", - 'type' => 'int', - 'not null' => TRUE, - 'default' => 0, - ), ), 'indexes' => array( 'nid' => array('nid'), @@ -304,7 +286,7 @@ function node_schema() { 'default' => '', ), 'has_body' => array( - 'description' => 'Boolean indicating whether this type uses the {node_revision}.body field.', + 'description' => 'Boolean indicating whether this type has the body field attached.', 'type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, @@ -416,6 +398,7 @@ function node_update_7004() { // Map old preview setting to new values order. $original_preview ? $original_preview = 2 : $original_preview = 1; + $node_types_clear(); $type_list = node_type_get_types(); // Apply original settings to all types. @@ -430,6 +413,119 @@ function node_update_7004() { } /** + * Convert body and teaser from node properties to fields. + */ +function node_update_7005(&$context) { + $ret = array('#finished' => 0); + + // Get node type info for every invocation. + node_type_clear(); + $node_types = node_type_get_types(); + $body_types = array(); + foreach ($node_types as $type => $info) { + if ($info->has_body) { + $body_types[] = $type; + } + } + + if (!isset($context['total'])) { + // Initial invocation. + + // Re-save node types to create body field instances. + foreach ($node_types as $type => $info) { + if ($info->has_body) { + node_type_save($info); + } + } + + // Initialize state for future calls. + $context['last'] = 0; + $context['count'] = 0; + + $query = db_select('node', 'n'); + $query->join('node_revision', 'nr', 'n.vid = nr.vid'); + $query->condition('n.type', $body_types, 'IN'); + $context['total'] = $query->countQuery()->execute()->fetchField(); + } + else { + // Subsequent invocations. + + $found = FALSE; + if ($context['total']) { + // Operate on every revision of every node (whee!), in batches. + $batch_size = 50; + $query = db_select('node', 'n'); + $nr = $query->innerJoin('node_revision', 'nr', 'n.vid = nr.vid'); + $revisions = $query + ->fields('n', array('type')) + ->fields($nr) + ->condition('nr.vid', $context['last'], '>') + ->condition('n.type', $body_types, 'IN') + ->orderBy('nr.vid', 'ASC') + ->execute(); + + // Load each reversion of each node, set up 'body' + // appropriately, and save the node's field data. Note that + // node_load() will not return the body or teaser values from + // {node_revision} because those columns have been removed from the + // schema structure in memory (but not yet from the database), + // so we get the values from the explicit query of the table + // instead. + foreach ($revisions as $revision) { + $found = TRUE; + + if ($node_types[$revision->type]->has_body) { + $node = (object) array( + 'nid' => $revision->nid, + 'vid' => $revision->vid, + 'type' => $revision->type, + ); + if (!empty($revision->teaser) && $revision->teaser != text_summary($revision->body)) { + $node->body[0]['summary'] = $revision->teaser; + } + // Do this after text_summary() above. + $break = '<!--break-->'; + if (substr($revision->body, 0, strlen($break)) == $break) { + $revision->body = substr($revision->body, strlen($break)); + } + $node->body[0]['value'] = $revision->body; + $node->body[0]['format'] = $revision->format; + // This is a core update and no contrib modules are enabled yet, so + // we can assume default field storage for a faster update. + field_sql_storage_field_storage_write('node', $node, FIELD_STORAGE_INSERT, array()); + } + + $context['last'] = $revision->vid; + $context['count'] += 1; + + if (--$batch_size == 0) { + break; + } + } + + $ret['#finished'] = min(0.99, $context['count'] / $context['total']); + } + + if (!$found) { + // All nodes are processed. + $ret[] = array('success' => TRUE, 'query' => "{$context['total']} node body and teaser properties migrated to the 'body' field."); + + // Remove the now-obsolete body info from node_revision. + db_drop_field($ret, 'node_revision', 'body'); + db_drop_field($ret, 'node_revision', 'teaser'); + db_drop_field($ret, 'node_revision', 'format'); + + // We're done. + $ret['#finished'] = 1; + } + } + + return $ret; +} + + + +/** * @} End of "defgroup updates-6.x-to-7.x" * The next series of updates should start at 8000. */ diff --git a/modules/node/node.module b/modules/node/node.module index d853da33f..85bcceb36 100644 --- a/modules/node/node.module +++ b/modules/node/node.module @@ -291,186 +291,6 @@ function node_mark($nid, $timestamp) { } /** - * See if the user used JS to submit a teaser. - */ -function node_teaser_js(&$form, &$form_state) { - if (isset($form_state['input']['teaser_js'])) { - // Glue the teaser to the body. - if (trim($form_state['values']['teaser_js'])) { - // Space the teaser from the body - $body = trim($form_state['values']['teaser_js']) . "\r\n<!--break-->\r\n" . trim($form_state['values']['body']); - } - else { - // Empty teaser, no spaces. - $body = '<!--break-->' . $form_state['values']['body']; - } - // Pass updated body value on to preview/submit form processing. - form_set_value($form['body'], $body, $form_state); - // Pass updated body value back onto form for those cases - // in which the form is redisplayed. - $form['body']['#value'] = $body; - } - return $form; -} - -/** - * Ensure value of "teaser_include" checkbox is consistent with other form data. - * - * This handles two situations in which an unchecked checkbox is rejected: - * - * 1. The user defines a teaser (summary) but it is empty; - * 2. The user does not define a teaser (summary) (in this case an - * unchecked checkbox would cause the body to be empty, or missing - * the auto-generated teaser). - * - * If JavaScript is active then it is used to force the checkbox to be - * checked when hidden, and so the second case will not arise. - * - * In either case a warning message is output. - */ -function node_teaser_include_verify(&$form, &$form_state) { - $message = ''; - - // $form_state['input'] is set only when the form is built for preview/submit. - if (isset($form_state['input']['body']) && isset($form_state['values']['teaser_include']) && !$form_state['values']['teaser_include']) { - // "teaser_include" checkbox is present and unchecked. - if (strpos($form_state['values']['body'], '<!--break-->') === 0) { - // Teaser is empty string. - $message = t('You specified that the summary should not be shown when this post is displayed in full view. This setting is ignored when the summary is empty.'); - } - elseif (strpos($form_state['values']['body'], '<!--break-->') === FALSE) { - // Teaser delimiter is not present in the body. - $message = t('You specified that the summary should not be shown when this post is displayed in full view. This setting has been ignored since you have not defined a summary for the post. (To define a summary, insert the delimiter "<!--break-->" (without the quotes) in the Body of the post to indicate the end of the summary and the start of the main content.)'); - } - - if (!empty($message)) { - drupal_set_message($message, 'warning'); - // Pass new checkbox value on to preview/submit form processing. - form_set_value($form['teaser_include'], 1, $form_state); - // Pass new checkbox value back onto form for those cases - // in which form is redisplayed. - $form['teaser_include']['#value'] = 1; - } - } - - return $form; -} - -/** - * Generate a teaser for a node body. - * - * If the end of the teaser is not indicated using the <!--break--> delimiter - * then we generate the teaser automatically, trying to end it at a sensible - * place such as the end of a paragraph, a line break, or the end of a - * sentence (in that order of preference). - * - * @param $body - * The content for which a teaser will be generated. - * @param $format - * The format of the content. If the content contains PHP code, we do not - * split it up to prevent parse errors. If the line break filter is present - * then we treat newlines embedded in $body as line breaks. - * @param $size - * The desired character length of the teaser. If omitted, the default - * value will be used. Ignored if the special delimiter is present - * in $body. - * @return - * The generated teaser. - */ -function node_teaser($body, $format = NULL, $size = NULL) { - - if (!isset($size)) { - $size = variable_get('teaser_length', 600); - } - - // Find where the delimiter is in the body - $delimiter = strpos($body, '<!--break-->'); - - // If the size is zero, and there is no delimiter, the entire body is the teaser. - if ($size == 0 && $delimiter === FALSE) { - return $body; - } - - // If a valid delimiter has been specified, use it to chop off the teaser. - if ($delimiter !== FALSE) { - return substr($body, 0, $delimiter); - } - - // We check for the presence of the PHP evaluator filter in the current - // format. If the body contains PHP code, we do not split it up to prevent - // parse errors. - if (isset($format)) { - $filters = filter_list_format($format); - if (isset($filters['php/0']) && strpos($body, '<?') !== FALSE) { - return $body; - } - } - - // If we have a short body, the entire body is the teaser. - if (drupal_strlen($body) <= $size) { - return $body; - } - - // If the delimiter has not been specified, try to split at paragraph or - // sentence boundaries. - - // The teaser may not be longer than maximum length specified. Initial slice. - $teaser = truncate_utf8($body, $size); - - // Store the actual length of the UTF8 string -- which might not be the same - // as $size. - $max_rpos = strlen($teaser); - - // How much to cut off the end of the teaser so that it doesn't end in the - // middle of a paragraph, sentence, or word. - // Initialize it to maximum in order to find the minimum. - $min_rpos = $max_rpos; - - // Store the reverse of the teaser. We use strpos on the reversed needle and - // haystack for speed and convenience. - $reversed = strrev($teaser); - - // Build an array of arrays of break points grouped by preference. - $break_points = array(); - - // A paragraph near the end of sliced teaser is most preferable. - $break_points[] = array('</p>' => 0); - - // If no complete paragraph then treat line breaks as paragraphs. - $line_breaks = array('<br />' => 6, '<br>' => 4); - // Newline only indicates a line break if line break converter - // filter is present. - if (isset($filters['filter/1'])) { - $line_breaks["\n"] = 1; - } - $break_points[] = $line_breaks; - - // If the first paragraph is too long, split at the end of a sentence. - $break_points[] = array('. ' => 1, '! ' => 1, '? ' => 1, '。' => 0, '؟ ' => 1); - - // Iterate over the groups of break points until a break point is found. - foreach ($break_points as $points) { - // Look for each break point, starting at the end of the teaser. - foreach ($points as $point => $offset) { - // The teaser is already reversed, but the break point isn't. - $rpos = strpos($reversed, strrev($point)); - if ($rpos !== FALSE) { - $min_rpos = min($rpos + $offset, $min_rpos); - } - } - - // If a break point was found in this group, slice and return the teaser. - if ($min_rpos !== $max_rpos) { - // Don't slice with length 0. Length must be <0 to slice from RHS. - return ($min_rpos === 0) ? $teaser : substr($teaser, 0, 0 - $min_rpos); - } - } - - // If a break point was not found, still return a teaser. - return $teaser; -} - -/** * Extract the type name. * * @param $node @@ -632,6 +452,7 @@ function node_type_save($info) { if (!empty($type->old_type) && $type->old_type != $type->type) { field_attach_rename_bundle($type->old_type, $type->type); } + node_configure_fields($type); module_invoke_all('node_type', 'update', $type); return SAVED_UPDATED; } @@ -642,13 +463,74 @@ function node_type_save($info) { ->execute(); field_attach_create_bundle($type->type); - + node_configure_fields($type); module_invoke_all('node_type', 'insert', $type); return SAVED_NEW; } } /** + * Manage the field(s) for a node type. + * + * Currently, the node module manages a single Field API field, + * 'body'. If $type->has_body is true, this function ensures the + * 'body' field exists and creates an instance of it for the bundle + * $type->type (e.g. 'page', 'story', ...). If $type->has_body is + * false, this function removes the instance (if it exists) for the + * 'body' field on $type->type. + */ +function node_configure_fields($type) { + // Add or remove the body field, as needed. + $field = field_info_field('body'); + $instance = field_info_instance('body', $type->type); + if ($type->has_body) { + if (empty($field)) { + $field = array( + 'field_name' => 'body', + 'type' => 'text_with_summary', + ); + $field = field_create_field($field); + } + if (empty($instance)) { + $instance = array( + 'field_name' => 'body', + 'bundle' => $type->type, + 'label' => $type->body_label, + 'widget_type' => 'text_textarea_with_summary', + 'settings' => array('display_summary' => TRUE), + + // With no UI in core, we have to define default + // formatters for the teaser and full view. + // This may change if the method of handling displays + // is changed or if a UI gets into core. + 'display' => array( + 'full' => array( + 'label' => 'hidden', + 'type' => 'text_default', + 'exclude' => 0, + ), + 'teaser' => array( + 'label' => 'hidden', + 'type' => 'text_summary_or_trimmed', + 'exclude' => 0, + ), + ), + ); + field_create_instance($instance); + } + else { + $instance['label'] = $type->body_label; + $instance['settings']['display_summary'] = TRUE; + field_update_instance($instance); + } + } + elseif (!empty($instance)) { + field_delete_instance($instance); + } + +} + +/** * Deletes a node type from the database. * * @param $type @@ -1004,7 +886,8 @@ function node_validate($node, $form = array()) { // Make sure the body has the minimum number of words. // TODO : use a better word counting algorithm that will work in other languages - if (!empty($type->min_word_count) && isset($node->body) && count(explode(' ', $node->body)) < $type->min_word_count) { + if (!empty($type->min_word_count) && isset($node->body[0]['value']) && count(explode(' ', $node->body[0]['value'])) < $type->min_word_count) { + // TODO: Use Field API to set this error. form_set_error('body', t('The body of your @type is too short. You need at least %words words.', array('%words' => $type->min_word_count, '@type' => $type->name))); } @@ -1041,25 +924,6 @@ function node_submit($node) { // Convert the node to an object, if necessary. $node = (object)$node; - // Generate the teaser, but only if it hasn't been set (e.g. by a - // module-provided 'teaser' form item). - if (!isset($node->teaser)) { - if (isset($node->body)) { - $node->format = (!empty($node->body_format) ? $node->body_format : FILTER_FORMAT_DEFAULT); - $node->teaser = node_teaser($node->body, isset($node->format) ? $node->format : NULL, variable_get('teaser_length_' . $node->type, 600)); - // Chop off the teaser from the body if needed. The teaser_include - // property might not be set (eg. in Blog API postings), so only act on - // it, if it was set with a given value. - if (isset($node->teaser_include) && !$node->teaser_include && $node->teaser == substr($node->body, 0, strlen($node->teaser))) { - $node->body = substr($node->body, strlen($node->teaser)); - } - } - else { - $node->teaser = ''; - $node->format = 0; - } - } - if (user_access('administer nodes')) { // Populate the "authored by" field. if ($account = user_load_by_name($node->name)) { @@ -1105,16 +969,6 @@ function node_save($node) { if (!isset($node->log)) { $node->log = ''; } - - // For the same reasons, make sure we have $node->teaser and - // $node->body. We should consider making these fields nullable - // in a future version since node types are not required to use them. - if (!isset($node->teaser)) { - $node->teaser = ''; - } - if (!isset($node->body)) { - $node->body = ''; - } } elseif (!empty($node->revision)) { $node->old_vid = $node->vid; @@ -1274,30 +1128,6 @@ function node_build($node, $teaser = FALSE) { } /** - * Apply filters and build the node's standard elements. - */ -function node_prepare($node, $teaser = FALSE) { - // First we'll overwrite the existing node teaser and body with - // the filtered copies! Then, we'll stick those into the content - // array and set the read more flag if appropriate. - $node->readmore = (strlen($node->teaser) < strlen($node->body)); - - if ($teaser == FALSE) { - $node->body = check_markup($node->body, $node->format, $node->language, FALSE); - } - else { - $node->teaser = check_markup($node->teaser, $node->format, $node->language, FALSE); - } - - $node->content['body'] = array( - '#markup' => $teaser ? $node->teaser : $node->body, - '#weight' => 0, - ); - - return $node; -} - -/** * Builds a structured array representing the node's content. * * The content built for the node will vary depending on the $node->build_mode @@ -1322,30 +1152,41 @@ function node_prepare($node, $teaser = FALSE) { * * @return * An structured array containing the individual elements - * of the node's body. + * of the node's content. */ function node_build_content($node, $teaser = FALSE) { - // The build mode identifies the target for which the node is built. if (!isset($node->build_mode)) { $node->build_mode = NODE_BUILD_NORMAL; } - // Remove the delimiter (if any) that separates the teaser from the body. - $node->body = isset($node->body) ? str_replace('<!--break-->', '', $node->body) : ''; - // The 'view' hook can be implemented to overwrite the default function // to display nodes. if (node_hook($node, 'view')) { $node = node_invoke($node, 'view', $teaser); } - else { - $node = node_prepare($node, $teaser); - } // Build fields content. + if (empty($node->content)) { + $node->content = array(); + }; $node->content += field_attach_view('node', $node, $teaser); + // Always display a read more link on teasers because we have no way + // to know when a teaser view is different than a full view. + $links = array(); + if ($teaser) { + $links['node_readmore'] = array( + 'title' => t('Read more'), + 'href' => 'node/' . $node->nid, + 'attributes' => array('rel' => 'tag', 'title' => strip_tags($node->title)) + ); + } + $node->content['links']['node'] = array( + '#type' => 'node_links', + '#value' => $links + ); + // Allow modules to make their own additions to the node. module_invoke_all('node_view', $node, $teaser); @@ -1649,16 +1490,16 @@ function node_search($op = 'search', $keys = NULL) { // Load results. $results = array(); foreach ($find as $item) { - // Build the node body. + // Render the node. $node = node_load($item->sid); $node->build_mode = NODE_BUILD_SEARCH_RESULT; $node = node_build_content($node, FALSE, FALSE); - $node->body = drupal_render($node->content); + $node->rendered = drupal_render($node->content); // Fetch comments for snippet. - $node->body .= module_invoke('comment', 'node_update_index', $node); + $node->rendered .= module_invoke('comment', 'node_update_index', $node); // Fetch terms for snippet. - $node->body .= module_invoke('taxonomy', 'node_update_index', $node); + $node->rendered .= module_invoke('taxonomy', 'node_update_index', $node); $extra = module_invoke_all('node_search_result', $node); @@ -1671,7 +1512,7 @@ function node_search($op = 'search', $keys = NULL) { 'node' => $node, 'extra' => $extra, 'score' => $total ? ($item->calculated_score / $total) : 0, - 'snippet' => search_excerpt($keys, $node->body), + 'snippet' => search_excerpt($keys, $node->rendered), ); } return $results; @@ -1801,7 +1642,7 @@ function node_link($type, $node = NULL, $teaser = FALSE) { $links = array(); if ($type == 'node') { - if ($teaser == 1 && $node->teaser && !empty($node->readmore)) { + if ($teaser == 1) { $links['node_read_more'] = array( 'title' => t('Read more'), 'href' => "node/$node->nid", @@ -2254,12 +2095,12 @@ function _node_index_node($node) { // results half-life calculation. variable_set('node_cron_last', $node->changed); - // Build the node body. + // Render the node. $node->build_mode = NODE_BUILD_SEARCH_INDEX; $node = node_build_content($node, FALSE, FALSE); - $node->body = drupal_render($node->content); + $node->rendered = drupal_render($node->content); - $text = '<h1>' . check_plain($node->title) . '</h1>' . $node->body; + $text = '<h1>' . check_plain($node->title) . '</h1>' . $node->rendered; // Fetch extra data normally not visible $extra = module_invoke_all('node_update_index', $node); @@ -2464,10 +2305,6 @@ function node_access($op, $node, $account = NULL) { if (empty($account)) { $account = $user; } - // If the node is in a restricted format, disallow editing. - if ($op == 'update' && !filter_access($node->format)) { - return FALSE; - } if (user_access('bypass node access', $account)) { return TRUE; @@ -2977,9 +2814,9 @@ function node_content_access($op, $node, $account) { * Implement hook_form(). */ function node_content_form($node, $form_state) { - + $type = node_type_get_type($node); - + $form = array(); if ($type->has_title) { @@ -2993,8 +2830,6 @@ function node_content_form($node, $form_state) { ); } - $form['body_field'] = node_body_field($node, $type->body_label, $type->min_word_count); - return $form; } diff --git a/modules/node/node.pages.inc b/modules/node/node.pages.inc index 729a991db..ee362cbf6 100644 --- a/modules/node/node.pages.inc +++ b/modules/node/node.pages.inc @@ -114,7 +114,7 @@ function node_form(&$form_state, $node) { $form['#prefix'] = $form_state['node_preview']; } $node = (object)$node; - foreach (array('body', 'title', 'format') as $key) { + foreach (array('title') as $key) { if (!isset($node->$key)) { $node->$key = NULL; } @@ -287,52 +287,6 @@ function node_form(&$form_state, $node) { } /** - * Return a node body field, with format and teaser. - */ -function node_body_field($node, $label, $word_count) { - // Do not generate a body field if the type does not specify one. - if (!node_type_get_type($node->type)->has_body) { - return array(); - } - - // Check if we need to restore the teaser at the beginning of the body. - $include = !isset($node->teaser) || ($node->teaser == substr($node->body, 0, strlen($node->teaser))); - - $form = array( - '#after_build' => array('node_teaser_js', 'node_teaser_include_verify')); - - $form['#prefix'] = '<div class="body-field-wrapper clearfix">'; - $form['#suffix'] = '</div>'; - - $form['teaser_js'] = array( - '#type' => 'textarea', - '#rows' => 10, - '#teaser' => 'edit-body', - '#teaser_checkbox' => 'edit-teaser-include', - '#disabled' => TRUE, - ); - - $form['teaser_include'] = array( - '#type' => 'checkbox', - '#title' => t('Show summary in full view'), - '#default_value' => $include, - '#prefix' => '<div class="teaser-checkbox">', - '#suffix' => '</div>', - ); - - $form['body'] = array( - '#type' => 'textarea', - '#title' => check_plain($label), - '#default_value' => $include ? $node->body : ($node->teaser . $node->body), - '#rows' => 20, - '#required' => ($word_count > 0), - '#text_format' => isset($node->format) ? $node->format : FILTER_FORMAT_DEFAULT, - ); - - return $form; -} - -/** * Button submit function: handle the 'Delete' button on the node form. */ function node_form_delete_submit($form, &$form_state) { @@ -393,16 +347,6 @@ function node_preview($node) { $node->changed = REQUEST_TIME; - // Extract a teaser, if it hasn't been set (e.g. by a module-provided - // 'teaser' form item). - if (!isset($node->teaser)) { - $node->teaser = empty($node->body) ? '' : node_teaser($node->body, $node->format, variable_get('teaser_length_' . $type, 600)); - // Chop off the teaser from the body if needed. - if (!$node->teaser_include && $node->teaser == substr($node->body, 0, strlen($node->teaser))) { - $node->body = substr($node->body, strlen($node->teaser)); - } - } - // Display a preview of the node. // Previewing alters $node so it needs to be cloned. if (!form_get_errors()) { @@ -428,28 +372,20 @@ function theme_node_preview($node) { $output = '<div class="preview">'; $preview_trimmed_version = FALSE; - // Do we need to preview trimmed version of post as well as full version? - if (isset($node->teaser) && isset($node->body)) { - $teaser = trim($node->teaser); - $body = trim(str_replace('<!--break-->', '', $node->body)); - - // Preview trimmed version if teaser and body will appear different; - // also (edge case) if both teaser and body have been specified by the user - // and are actually the same. - if ($teaser != $body || ($body && strpos($node->body, '<!--break-->') === 0)) { - $preview_trimmed_version = TRUE; - } - } - if ($preview_trimmed_version) { + $trimmed = drupal_render(node_build(clone $node, TRUE)); + $full = drupal_render(node_build($node, FALSE)); + + // Do we need to preview trimmed version of post as well as full version? + if ($trimmed != $full) { drupal_set_message(t('The trimmed version of your post shows what your post looks like when promoted to the main page or when exported for syndication.<span class="no-js"> You can insert the delimiter "<!--break-->" (without the quotes) to fine-tune where your post gets split.</span>')); $output .= '<h3>' . t('Preview trimmed version') . '</h3>'; - $output .= drupal_render(node_build(clone $node, TRUE)); + $output .= $trimmed; $output .= '<h3>' . t('Preview full version') . '</h3>'; - $output .= drupal_render(node_build($node, FALSE)); + $output .= $full; } else { - $output .= drupal_render(node_build($node, FALSE)); + $output .= $full; } $output .= "</div>\n"; diff --git a/modules/node/node.test b/modules/node/node.test index 252ebbd3c..152a1353b 100644 --- a/modules/node/node.test +++ b/modules/node/node.test @@ -141,7 +141,7 @@ class NodeRevisionsTestCase extends DrupalWebTestCase { // Confirm the correct revision text appears on "view revisions" page. $this->drupalGet("node/$node->nid/revisions/$node->vid/view"); - $this->assertText($node->body, t('Correct text displays for version.')); + $this->assertText($node->body[0]['value'], t('Correct text displays for version.')); // Confirm the correct log message appears on "revisions overview" page. $this->drupalGet("node/$node->nid/revisions"); @@ -155,7 +155,7 @@ class NodeRevisionsTestCase extends DrupalWebTestCase { array('@type' => 'Page', '%title' => $nodes[1]->title, '%revision-date' => format_date($nodes[1]->revision_timestamp))), t('Revision reverted.')); $reverted_node = node_load($node->nid); - $this->assertTrue(($nodes[1]->body == $reverted_node->body), t('Node reverted correctly.')); + $this->assertTrue(($nodes[1]->body[0]['value'] == $reverted_node->body[0]['value']), t('Node reverted correctly.')); // Confirm revisions delete properly. $this->drupalPost("node/$node->nid/revisions/{$nodes[1]->vid}/delete", array(), t('Delete')); @@ -166,151 +166,6 @@ class NodeRevisionsTestCase extends DrupalWebTestCase { } } -class NodeTeaserTestCase extends DrupalWebTestCase { - public static function getInfo() { - return array( - 'name' => t('Node teaser'), - 'description' => t('Test node_teaser() with different strings and lengths.'), - 'group' => t('Node'), - ); - } - - /** - * Tests an edge case where if the first sentence is a question and - * subsequent sentences are not. This is edge case is documented at - * http://drupal.org/node/180425. - */ - function testFirstSentenceQuestion() { - $body = 'A question? A sentence. Another sentence.'; - $expected = 'A question? A sentence.'; - $this->callNodeTeaser($body, $expected, NULL, 30); - } - - /** - * Test teaser with long example. - */ - function testLongSentence() { - $body = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ' . // 125 - 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. ' . // 108 - 'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. ' . // 103 - 'Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'; // 110 - $expected = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ' . - 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. ' . - 'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.'; - // First three sentences add up to: 336, so add one for space and then 3 to get half-way into next word. - $this->callNodeTeaser($body, $expected, NULL, 340); - } - - /** - * Test various teaser length edge cases. - */ - function testLength() { - // This body string tests a number of edge cases. - $body = "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>"; - - // The teasers we expect node_teaser() to return when $size is the index - // of each array item. - // Using an text format with no line-break filter: - $teasers = array( - "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>", - "<", - "<p", - "<p>", - "<p>\n", - "<p>\nH", - "<p>\nHi", - "<p>\nHi\n", - "<p>\nHi\n<", - "<p>\nHi\n</", - "<p>\nHi\n</p", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>", - "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>", - "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>", - ); - - // And Using an text format WITH the line-break filter. - $teasers_lb = array( - "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>", - "<", - "<p", - "<p>", - "<p>", - "<p>", - "<p>", - "<p>\nHi", - "<p>\nHi", - "<p>\nHi", - "<p>\nHi", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>", - "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>", - "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>", - "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>", - ); - - // Test node_teaser() for different sizes. - for ($i = 0; $i <= 37; $i++) { - $this->callNodeTeaser($body, $teasers[$i], NULL, $i); - $this->callNodeTeaser($body, $teasers_lb[$i], 1, $i); - $this->callNodeTeaser($body, $teasers_lb[$i], 2, $i); - } - } - - /** - * Calls node_teaser() and asserts that the expected teaser is returned. - */ - function callNodeTeaser($body, $expected, $format = NULL, $size = NULL) { - $teaser = node_teaser($body, $format, $size); - $this->assertIdentical($teaser, $expected, t('Generated teaser "@teaser" matches expected teaser.', array('@teaser' => $teaser))); - } -} - class PageEditTestCase extends DrupalWebTestCase { public static function getInfo() { return array( @@ -331,10 +186,11 @@ class PageEditTestCase extends DrupalWebTestCase { * Check node edit functionality. */ function testPageEdit() { + $body_key = 'body[0][value]'; // Create node to edit. $edit = array(); $edit['title'] = $this->randomName(8); - $edit['body'] = $this->randomName(16); + $edit[$body_key] = $this->randomName(16); $this->drupalPost('node/add/page', $edit, t('Save')); // Check that the node exists in the database. @@ -350,18 +206,18 @@ class PageEditTestCase extends DrupalWebTestCase { // Check that the title and body fields are displayed with the correct values. $this->assertLink(t('Edit'), 0, t('Edit tab found.')); $this->assertFieldByName('title', $edit['title'], t('Title field displayed.')); - $this->assertFieldByName('body', '<!--break-->' . $edit['body'], t('Body field displayed.')); + $this->assertFieldByName($body_key, $edit[$body_key], t('Body field displayed.')); // Edit the content of the node. $edit = array(); $edit['title'] = $this->randomName(8); - $edit['body'] = $this->randomName(16); + $edit[$body_key] = $this->randomName(16); // Stay on the current page, without reloading. $this->drupalPost(NULL, $edit, t('Save')); // Check that the title and body fields are displayed with the updated values. $this->assertText($edit['title'], t('Title displayed.')); - $this->assertText($edit['body'], t('Body displayed.')); + $this->assertText($edit[$body_key], t('Body displayed.')); } } @@ -385,44 +241,47 @@ class PagePreviewTestCase extends DrupalWebTestCase { * Check the node preview functionality. */ function testPagePreview() { + $body_key = 'body[0][value]'; + // Fill in node creation form and preview node. $edit = array(); $edit['title'] = $this->randomName(8); - $edit['body'] = $this->randomName(16); + $edit[$body_key] = $this->randomName(16); $this->drupalPost('node/add/page', $edit, t('Preview')); // Check that the preview is displaying the title and body. $this->assertTitle(t('Preview | Drupal'), t('Page title is preview.')); $this->assertText($edit['title'], t('Title displayed.')); - $this->assertText($edit['body'], t('Body displayed.')); + $this->assertText($edit[$body_key], t('Body displayed.')); // Check that the title and body fields are displayed with the correct values. $this->assertFieldByName('title', $edit['title'], t('Title field displayed.')); - $this->assertFieldByName('body', '<!--break-->' . $edit['body'], t('Body field displayed.')); + $this->assertFieldByName($body_key, $edit[$body_key], t('Body field displayed.')); } /** * Check the node preview functionality, when using revisions. */ function testPagePreviewWithRevisions() { + $body_key = 'body[0][value]'; // Force revision on page content. variable_set('node_options_page', array('status', 'revision')); // Fill in node creation form and preview node. $edit = array(); $edit['title'] = $this->randomName(8); - $edit['body'] = $this->randomName(16); + $edit[$body_key] = $this->randomName(16); $edit['log'] = $this->randomName(32); $this->drupalPost('node/add/page', $edit, t('Preview')); // Check that the preview is displaying the title and body. $this->assertTitle(t('Preview | Drupal'), t('Page title is preview.')); $this->assertText($edit['title'], t('Title displayed.')); - $this->assertText($edit['body'], t('Body displayed.')); + $this->assertText($edit[$body_key], t('Body displayed.')); // Check that the title and body fields are displayed with the correct values. $this->assertFieldByName('title', $edit['title'], t('Title field displayed.')); - $this->assertFieldByName('body', '<!--break-->' . $edit['body'], t('Body field displayed.')); + $this->assertFieldByName($body_key, $edit[$body_key], t('Body field displayed.')); // Check that the log field has the correct value. $this->assertFieldByName('log', $edit['log'], t('Log field displayed.')); @@ -452,7 +311,7 @@ class PageCreationTestCase extends DrupalWebTestCase { // Create a node. $edit = array(); $edit['title'] = $this->randomName(8); - $edit['body'] = $this->randomName(16); + $edit['body[0][value]'] = $this->randomName(16); $this->drupalPost('node/add/page', $edit, t('Save')); // Check that the page has been created. @@ -599,7 +458,7 @@ class NodePostSettingsTestCase extends DrupalWebTestCase { // Create a node. $edit = array(); $edit['title'] = $this->randomName(8); - $edit['body'] = $this->randomName(16); + $edit['body[0][value]'] = $this->randomName(16); $this->drupalPost('node/add/page', $edit, t('Save')); // Check that the post information is displayed. @@ -620,7 +479,7 @@ class NodePostSettingsTestCase extends DrupalWebTestCase { // Create a node. $edit = array(); $edit['title'] = $this->randomName(8); - $edit['body'] = $this->randomName(16); + $edit['body[0][value]'] = $this->randomName(16); $this->drupalPost('node/add/page', $edit, t('Save')); // Check that the post information is displayed. @@ -726,7 +585,7 @@ class NodeAccessRecordsAlterUnitTest extends DrupalWebTestCase { $this->assertEqual(count($records), 1, t('Returned the correct number of rows.')); $this->assertEqual($records[0]->realm, 'test_page_realm', t('Grant with page_realm acquired for node without alteration.')); $this->assertEqual($records[0]->gid, 1, t('Grant with gid = 1 acquired for node without alteration.')); - + // Create a promoted page node. $node3 = $this->drupalCreateNode(array('type' => 'page', 'promote' => 1)); $this->assertTrue(node_load($node3->nid), t('Promoted page node created.')); @@ -785,7 +644,7 @@ class NodeSaveTestCase extends DrupalWebTestCase { $title = $this->randomName(8); $node = array( 'title' => $title, - 'body' => $this->randomName(32), + 'body' => array(array('value' => $this->randomName(32))), 'uid' => $this->web_user->uid, 'type' => 'article', 'nid' => $test_nid, @@ -845,7 +704,7 @@ class NodeAccessRebuildTestCase extends DrupalWebTestCase { 'group' => t('Node'), ); } - + function setUp() { parent::setUp(); @@ -853,7 +712,7 @@ class NodeAccessRebuildTestCase extends DrupalWebTestCase { $this->drupalLogin($web_user); $this->web_user = $web_user; } - + function testNodeAccessRebuild() { $this->drupalGet('admin/reports/status'); $this->clickLink(t('Rebuild permissions')); diff --git a/modules/path/path.test b/modules/path/path.test index 421f58247..2475a5382 100644 --- a/modules/path/path.test +++ b/modules/path/path.test @@ -212,7 +212,7 @@ class PathLanguageTestCase extends DrupalWebTestCase { $this->clickLink(t('add translation')); $edit = array(); $edit['title'] = $this->randomName(); - $edit['body'] = $this->randomName(); + $edit['body[0][value]'] = $this->randomName(); $edit['path'] = $this->randomName(); $this->drupalPost(NULL, $edit, t('Save')); diff --git a/modules/php/php.test b/modules/php/php.test index 1d6d317f4..441563e3a 100644 --- a/modules/php/php.test +++ b/modules/php/php.test @@ -23,7 +23,7 @@ class PHPTestCase extends DrupalWebTestCase { * @return stdObject Node object. */ function createNodeWithCode() { - return $this->drupalCreateNode(array('body' => '<?php print "SimpleTest PHP was executed!"; ?>')); + return $this->drupalCreateNode(array('body' => array('value' => '<?php print "SimpleTest PHP was executed!"; ?>'))); } } @@ -60,7 +60,7 @@ class PHPFilterTestCase extends PHPTestCase { // Change filter to PHP filter and see that PHP code is evaluated. $edit = array(); - $edit['body_format'] = 3; + $edit['body[0][value_format]'] = 3; $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); $this->assertRaw(t('Page %title has been updated.', array('%title' => $node->title)), t('PHP code filter turned on.')); diff --git a/modules/search/search.api.php b/modules/search/search.api.php index a31162fb8..417ea7d0e 100644 --- a/modules/search/search.api.php +++ b/modules/search/search.api.php @@ -245,32 +245,23 @@ function hook_search_preprocess($text) { * @ingroup search */ function hook_update_index() { - $last = variable_get('node_cron_last', 0); $limit = (int)variable_get('search_cron_limit', 100); - $result = db_query_range('SELECT n.nid, c.last_comment_timestamp FROM {node} n LEFT JOIN {node_comment_statistics} c ON n.nid = c.nid WHERE n.status = 1 AND n.moderate = 0 AND (n.created > %d OR n.changed > %d OR c.last_comment_timestamp > %d) ORDER BY GREATEST(n.created, n.changed, c.last_comment_timestamp) ASC', $last, $last, $last, 0, $limit); + $result = db_query_range("SELECT n.nid FROM {node} n LEFT JOIN {search_dataset} d ON d.type = 'node' AND d.sid = n.nid WHERE d.sid IS NULL OR d.reindex <> 0 ORDER BY d.reindex ASC, n.nid ASC", 0, $limit); - while ($node = db_fetch_object($result)) { - $last_comment = $node->last_comment_timestamp; - $node = node_load(array('nid' => $node->nid)); + foreach ($result as $node) { + $node = node_load($node->nid); - // We update this variable per node in case cron times out, or if the node - // cannot be indexed (PHP nodes which call drupal_goto, for example). - // In rare cases this can mean a node is only partially indexed, but the - // chances of this happening are very small. - variable_set('node_cron_last', max($last_comment, $node->changed, $node->created)); + // Save the changed time of the most recent indexed node, for the search + // results half-life calculation. + variable_set('node_cron_last', $node->changed); - // Get node output (filtered and with module-specific fields). - if (node_hook($node, 'view')) { - node_invoke($node, 'view', FALSE, FALSE); - } - else { - $node = node_prepare($node, FALSE); - } - // Allow modules to change $node->body before viewing. - module_invoke_all('node_view', $node, FALSE, FALSE); + // Render the node. + $node->build_mode = NODE_BUILD_SEARCH_INDEX; + $node = node_build_content($node, FALSE, FALSE); + $node->rendered = drupal_render($node->content); - $text = '<h1>' . drupal_specialchars($node->title) . '</h1>' . $node->body; + $text = '<h1>' . check_plain($node->title) . '</h1>' . $node->rendered; // Fetch extra data normally not visible $extra = module_invoke_all('node_update_index', $node); diff --git a/modules/search/search.test b/modules/search/search.test index fcde2c67e..6788744c1 100644 --- a/modules/search/search.test +++ b/modules/search/search.test @@ -312,7 +312,7 @@ class SearchRankingTestCase extends DrupalWebTestCase { // Create nodes for testing. foreach ($node_ranks as $node_rank) { - $settings = array('type' => 'page', 'title' => 'Drupal rocks', 'body' => "Drupal's search rocks"); + $settings = array('type' => 'page', 'title' => 'Drupal rocks', 'body' => array(array('value' => "Drupal's search rocks"))); foreach (array(0, 1) as $num) { if ($num == 1) { switch ($node_rank) { @@ -321,7 +321,7 @@ class SearchRankingTestCase extends DrupalWebTestCase { $settings[$node_rank] = 1; break; case 'relevance': - $settings['body'] .= " really rocks"; + $settings['body'][0]['value'] .= " really rocks"; break; case 'recent': $settings['created'] = REQUEST_TIME + 3600; diff --git a/modules/simpletest/drupal_web_test_case.php b/modules/simpletest/drupal_web_test_case.php index b2d5af76d..535e18b4c 100644 --- a/modules/simpletest/drupal_web_test_case.php +++ b/modules/simpletest/drupal_web_test_case.php @@ -632,18 +632,17 @@ class DrupalWebTestCase extends DrupalTestCase { * * @param $settings * An associative array of settings to change from the defaults, keys are - * node properties, for example 'body' => 'Hello, world!'. + * node properties, for example 'title' => 'Hello, world!'. * @return * Created node object. */ protected function drupalCreateNode($settings = array()) { // Populate defaults array. $settings += array( - 'body' => $this->randomName(32), + 'body' => array(array()), 'title' => $this->randomName(8), 'comment' => 2, 'changed' => REQUEST_TIME, - 'format' => FILTER_FORMAT_DEFAULT, 'moderate' => 0, 'promote' => 0, 'revision' => 1, @@ -660,11 +659,6 @@ class DrupalWebTestCase extends DrupalTestCase { $settings['date'] = format_date($settings['created'], 'custom', 'Y-m-d H:i:s O'); } - // Add the default teaser. - if (!isset($settings['teaser'])) { - $settings['teaser'] = $settings['body']; - } - // If the node's user uid is not specified manually, use the currently // logged in user if available, or else the user running the test. if (!isset($settings['uid'])) { @@ -677,6 +671,13 @@ class DrupalWebTestCase extends DrupalTestCase { } } + // Merge body field value and format separately. + $body = array( + 'value' => $this->randomName(32), + 'format' => FILTER_FORMAT_DEFAULT + ); + $settings['body'][0] += $body; + $node = (object) $settings; node_save($node); @@ -989,9 +990,18 @@ class DrupalWebTestCase extends DrupalTestCase { $this->preloadRegistry(); // Add the specified modules to the list of modules in the default profile. - $args = func_get_args(); - $modules = array_unique(array_merge(drupal_get_profile_modules('default', 'en'), $args)); - drupal_install_modules($modules, TRUE); + // Install the modules specified by the default profile. + $core_modules = drupal_get_profile_modules('default', 'en'); + drupal_install_modules($core_modules, TRUE); + + node_type_clear(); + + // Install additional modules one at a time in order to make sure that the + // list of modules is updated between each module's installation. + $modules = func_get_args(); + foreach ($modules as $module) { + drupal_install_modules(array($module), TRUE); + } // Because the schema is static cached, we need to flush // it between each run. If we don't, then it will contain diff --git a/modules/simpletest/tests/common.test b/modules/simpletest/tests/common.test index 6b767a527..4ea5cb4ce 100644 --- a/modules/simpletest/tests/common.test +++ b/modules/simpletest/tests/common.test @@ -261,9 +261,7 @@ class CascadingStylesheetsTestCase extends DrupalWebTestCase { // Create a node, using the PHP filter that tests drupal_add_css(). $settings = array( 'type' => 'page', - 'format' => 3, // PHP filter. - 'body_format' => 3, - 'body' => t('This tests the inline CSS!') . "<?php drupal_add_css('$css', 'inline'); ?>", + 'body' => array(array('value' => t('This tests the inline CSS!') . "<?php drupal_add_css('$css', 'inline'); ?>", 'format' => 3)), // PHP filter. 'promote' => 1, ); $node = $this->drupalCreateNode($settings); diff --git a/modules/simpletest/tests/module.test b/modules/simpletest/tests/module.test index 87f1a1544..f42753f58 100644 --- a/modules/simpletest/tests/module.test +++ b/modules/simpletest/tests/module.test @@ -22,19 +22,24 @@ class ModuleUnitTest extends DrupalWebTestCase { * The basic functionality of module_list(). */ function testModuleList() { - $base_module_list = drupal_get_profile_modules('default', 'en'); - // Key the list by module name. - $base_module_list = array_combine($base_module_list, $base_module_list); - // All default profile modules have a weight equal to 0, the default sort - // order is thus simply alphabetical. - ksort($base_module_list); - $this->assertModuleList($base_module_list, t('Default profile')); + // Build a list of modules filenames. + $base_module_list = array(); + foreach (drupal_get_profile_modules('default', 'en') as $module) { + $base_module_list[$module] = drupal_get_path('module', $module); + } + asort($base_module_list); + // Build a list of module names based on that order. Since all default + // profile modules have a weight equal to 0, the default sort order is + // simply alphabetical. + $module_list = array_keys($base_module_list); + $this->assertModuleList($module_list, t('Default profile')); // Try to install a new module. drupal_install_modules(array('path')); - $base_module_list['path'] = 'path'; - ksort($base_module_list); - $this->assertModuleList($base_module_list, t('After adding a module')); + $base_module_list['path'] = drupal_get_path('module', 'path'); + asort($base_module_list); + $module_list = array_keys($base_module_list); + $this->assertModuleList($module_list, t('After adding a module')); // Try to mess with the module weights. db_update('system') @@ -46,8 +51,9 @@ class ModuleUnitTest extends DrupalWebTestCase { module_list(TRUE); // Move path at the end of the array. unset($base_module_list['path']); - $base_module_list['path'] = 'path'; - $this->assertModuleList($base_module_list, t('After changing weights')); + $base_module_list['path'] = drupal_get_path('module', 'path'); + $module_list = array_keys($base_module_list); + $this->assertModuleList($module_list, t('After changing weights')); // Test the fixed list feature. $fixed_list = array( @@ -60,7 +66,7 @@ class ModuleUnitTest extends DrupalWebTestCase { // Reset the module list. module_list(TRUE); - $this->assertModuleList($base_module_list, t('After reset')); + $this->assertModuleList($module_list, t('After reset')); } /** @@ -70,6 +76,7 @@ class ModuleUnitTest extends DrupalWebTestCase { * The expected values, sorted by weight and file name. */ protected function assertModuleList(Array $expected_values, $condition) { + $expected_values = array_combine($expected_values, $expected_values); $this->assertIdentical($expected_values, module_list(), t('@condition: module_list() returns correct results', array('@condition' => $condition))); ksort($expected_values); $this->assertIdentical($expected_values, module_list(FALSE, TRUE), t('@condition: module_list() returns correctly sorted results', array('@condition' => $condition))); diff --git a/modules/system/system.install b/modules/system/system.install index af694203e..1c787f13f 100644 --- a/modules/system/system.install +++ b/modules/system/system.install @@ -3549,6 +3549,17 @@ function system_update_7026() { } /** + * Enable field type modules. + */ +function system_update_7027() { + $ret = array(); + $module_list = array('text', 'number', 'list', 'options'); + drupal_install_modules($module_list); + module_enable($module_list); + return $ret; +} + +/** * @} End of "defgroup updates-6.x-to-7.x" * The next series of updates should start at 8000. */ diff --git a/modules/system/system.test b/modules/system/system.test index 533a3cdc8..46cafc001 100644 --- a/modules/system/system.test +++ b/modules/system/system.test @@ -518,7 +518,7 @@ class AccessDeniedTestCase extends DrupalWebTestCase { $edit = array( 'title' => $this->randomName(10), - 'body' => $this->randomName(100) + 'body' => array(array('value' => $this->randomName(100))), ); $node = $this->drupalCreateNode($edit); @@ -579,7 +579,7 @@ class PageNotFoundTestCase extends DrupalWebTestCase { $edit = array( 'title' => $this->randomName(10), - 'body' => $this->randomName(100) + 'body' => array(array('value' => $this->randomName(100))), ); $node = $this->drupalCreateNode($edit); @@ -705,7 +705,7 @@ class PageTitleFiltering extends DrupalWebTestCase { // Generate node content. $edit = array( 'title' => '!SimpleTest! ' . $title . $this->randomName(20), - 'body' => '!SimpleTest! test body' . $this->randomName(200), + 'body[0][value]' => '!SimpleTest! test body' . $this->randomName(200), ); // Create the node with HTML in the title. $this->drupalPost('node/add/page', $edit, t('Save')); diff --git a/modules/taxonomy/taxonomy.test b/modules/taxonomy/taxonomy.test index 87123bc39..ba91e6de2 100644 --- a/modules/taxonomy/taxonomy.test +++ b/modules/taxonomy/taxonomy.test @@ -474,7 +474,7 @@ class TaxonomyTermTestCase extends TaxonomyWebTestCase { // Post an article. $edit = array(); $edit['title'] = $this->randomName(); - $edit['body'] = $this->randomName(); + $edit['body[0][value]'] = $this->randomName(); $edit['taxonomy[' . $this->vocabulary->vid . ']'] = $term1->tid; $this->drupalPost('node/add/article', $edit, t('Save')); @@ -516,7 +516,7 @@ class TaxonomyTermTestCase extends TaxonomyWebTestCase { // Insert the terms in a comma separated list. Vocabulary 1 is a // free-tagging field created by the default profile. $edit['taxonomy[tags][' . $this->vocabulary->vid . ']'] = implode(', ', $terms); - $edit['body'] = $this->randomName(); + $edit['body[0][value]'] = $this->randomName(); $this->drupalPost('node/add/article', $edit, t('Save')); $this->assertRaw(t('@type %title has been created.', array('@type' => t('Article'), '%title' => $edit['title'])), t('The node was created successfully')); foreach ($terms as $term) { diff --git a/modules/translation/translation.module b/modules/translation/translation.module index 58045f396..4b9dbecd7 100644 --- a/modules/translation/translation.module +++ b/modules/translation/translation.module @@ -213,7 +213,6 @@ function translation_node_prepare($node) { $node->language = $language; $node->translation_source = $source_node; $node->title = $node->translation_source->title; - $node->body = $node->translation_source->body; // Let every module add custom translated fields. module_invoke_all('node_prepare_translation', $node); } diff --git a/modules/translation/translation.test b/modules/translation/translation.test index 73afe8409..6dc2ce456 100644 --- a/modules/translation/translation.test +++ b/modules/translation/translation.test @@ -60,14 +60,14 @@ class TranslationTestCase extends DrupalWebTestCase { // to return to the page then resubmitting the form without a refresh. $edit = array(); $edit['title'] = $this->randomName(); - $edit['body'] = $this->randomName(); + $edit['body[0][value]'] = $this->randomName(); $this->drupalPost('node/add/page', $edit, t('Save'), array('query' => array('translation' => $node->nid, 'language' => 'es'))); $duplicate = $this->drupalGetNodeByTitle($edit['title']); $this->assertEqual($duplicate->tnid, 0, t('The node does not have a tnid.')); // Update original and mark translation as outdated. $edit = array(); - $edit['body'] = $this->randomName(); + $edit['body[0][value]'] = $this->randomName(); $edit['translation[retranslate]'] = TRUE; $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); $this->assertRaw(t('Page %title has been updated.', array('%title' => $node_title)), t('Original node updated.')); @@ -78,7 +78,7 @@ class TranslationTestCase extends DrupalWebTestCase { // Update translation and mark as updated. $edit = array(); - $edit['body'] = $this->randomName(); + $edit['body[0][value]'] = $this->randomName(); $edit['translation[status]'] = FALSE; $this->drupalPost('node/' . $node_translation->nid . '/edit', $edit, t('Save')); $this->assertRaw(t('Page %title has been updated.', array('%title' => $node_translation_title)), t('Translated node updated.')); @@ -128,7 +128,7 @@ class TranslationTestCase extends DrupalWebTestCase { function createPage($title, $body, $language) { $edit = array(); $edit['title'] = $title; - $edit['body'] = $body; + $edit['body[0][value]'] = $body; $edit['language'] = $language; $this->drupalPost('node/add/page', $edit, t('Save')); $this->assertRaw(t('Page %title has been created.', array('%title' => $edit['title'])), t('Page created.')); @@ -153,7 +153,7 @@ class TranslationTestCase extends DrupalWebTestCase { $edit = array(); $edit['title'] = $title; - $edit['body'] = $body; + $edit['body[0][value]'] = $body; $this->drupalPost(NULL, $edit, t('Save')); $this->assertRaw(t('Page %title has been created.', array('%title' => $edit['title'])), t('Translation created.')); diff --git a/modules/trigger/trigger.test b/modules/trigger/trigger.test index 32f056cff..5e9d7a286 100644 --- a/modules/trigger/trigger.test +++ b/modules/trigger/trigger.test @@ -37,8 +37,8 @@ class TriggerContentTestCase extends DrupalWebTestCase { $web_user = $this->drupalCreateUser(array('create page content', 'access content', 'administer nodes')); $this->drupalLogin($web_user); $edit = array(); - $edit['title'] = '!SimpleTest test node! ' . $this->randomName(10); - $edit['body'] = '!SimpleTest test body! ' . $this->randomName(32) . ' ' . $this->randomName(32); + $edit['title'] = '!SimpleTest test node! ' . $this->randomName(10); + $edit['body[0][value]'] = '!SimpleTest test body! ' . $this->randomName(32) . ' ' . $this->randomName(32); $edit[$info['property']] = !$info['expected']; $this->drupalPost('node/add/page', $edit, t('Save')); // Make sure the text we want appears. |