### Eclipse Workspace Patch 1.0 #P drupal_test_7 Index: modules/field/field.test =================================================================== RCS file: /cvs/drupal/drupal/modules/field/field.test,v retrieving revision 1.22 diff -u -r1.22 field.test --- modules/field/field.test 26 May 2009 09:12:29 -0000 1.22 +++ modules/field/field.test 27 May 2009 00:15:04 -0000 @@ -722,10 +722,9 @@ $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. @@ -735,7 +734,7 @@ ); 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')); Index: modules/trigger/trigger.test =================================================================== RCS file: /cvs/drupal/drupal/modules/trigger/trigger.test,v retrieving revision 1.8 diff -u -r1.8 trigger.test --- modules/trigger/trigger.test 24 May 2009 17:39:35 -0000 1.8 +++ modules/trigger/trigger.test 27 May 2009 00:15:13 -0000 @@ -37,8 +37,8 @@ $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[NODE_BODY_FIELD . '[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. Index: modules/field/theme/field.css =================================================================== RCS file: /cvs/drupal/drupal/modules/field/theme/field.css,v retrieving revision 1.4 diff -u -r1.4 field.css --- modules/field/theme/field.css 30 Mar 2009 05:28:41 -0000 1.4 +++ modules/field/theme/field.css 27 May 2009 00:15:05 -0000 @@ -32,7 +32,11 @@ 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; +} Index: modules/php/php.test =================================================================== RCS file: /cvs/drupal/drupal/modules/php/php.test,v retrieving revision 1.10 diff -u -r1.10 php.test --- modules/php/php.test 24 May 2009 17:39:33 -0000 1.10 +++ modules/php/php.test 27 May 2009 00:15:08 -0000 @@ -23,7 +23,7 @@ * @return stdObject Node object. */ function createNodeWithCode() { - return $this->drupalCreateNode(array('body' => '')); + return $this->drupalCreateNode(array(NODE_BODY_FIELD => array('value' => ''))); } } @@ -60,7 +60,7 @@ // Change filter to PHP filter and see that PHP code is evaluated. $edit = array(); - $edit['body_format'] = 3; + $edit[NODE_BODY_FIELD . '[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.')); Index: modules/node/node.install =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.install,v retrieving revision 1.17 diff -u -r1.17 node.install --- modules/node/node.install 13 May 2009 19:42:15 -0000 1.17 +++ modules/node/node.install 27 May 2009 00:15:06 -0000 @@ -208,18 +208,6 @@ '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', @@ -232,12 +220,6 @@ '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'), @@ -295,7 +277,7 @@ '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, @@ -398,6 +380,125 @@ } /** + * Convert body and teaser from node properties to fields. + */ +function node_update_7004(&$context) { + $ret = array('#finished' => 0); + + // OMG, do not frickin' tell me that in D7 I *still* can't assume + // that enabled core modules are fully loaded at this point... + field_init(); + + // Get node type info for every invocation. + $node_types = node_get_types('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 NODE_BODY_FIELD + // 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. + // + // We have to use node_load() and field_attach_update() to avoid + // making assumptions about field storage. We cannot use + // node_load_multiple() because we are loading by vid, not nid. + // + // TODO: Actually, we probably can assume default field storage + // since this is a core update and no contrib modules are + // enabled yet, and it would be WAY faster. + 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->{NODE_BODY_FIELD}[0]['summary'] = $revision->teaser; + } + // Do this after text_summary() above. + $break = ''; + if (substr($revision->body, 0, strlen($break)) == $break) { + $revision->body = substr($revision->body, strlen($break)); + } + $node->{NODE_BODY_FIELD}[0]['value'] = $revision->body; + $node->{NODE_BODY_FIELD}[0]['format'] = $revision->format; + field_attach_update('node', $node); + } + + $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 " . NODE_BODY_FIELD . " 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. */ Index: modules/node/node.test =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.test,v retrieving revision 1.26 diff -u -r1.26 node.test --- modules/node/node.test 24 May 2009 17:39:32 -0000 1.26 +++ modules/node/node.test 27 May 2009 00:15:08 -0000 @@ -141,7 +141,7 @@ // 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->{NODE_BODY_FIELD}[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 @@ 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]->{NODE_BODY_FIELD}[0]['value'] == $reverted_node->{NODE_BODY_FIELD}[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 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 = "

\nHi\n

\n

\nfolks\n
\n!\n

"; - - // 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( - "

\nHi\n

\n

\nfolks\n
\n!\n

", - "<", - "", - "

\n", - "

\nH", - "

\nHi", - "

\nHi\n", - "

\nHi\n<", - "

\nHi\n\nHi\n\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

\n

\nfolks\n
\n!\n

", - "

\nHi\n

\n

\nfolks\n
\n!\n

", - "

\nHi\n

\n

\nfolks\n
\n!\n

", - ); - - // And Using an text format WITH the line-break filter. - $teasers_lb = array( - "

\nHi\n

\n

\nfolks\n
\n!\n

", - "<", - "", - "

", - "

", - "

", - "

\nHi", - "

\nHi", - "

\nHi", - "

\nHi", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

", - "

\nHi\n

\n

\nfolks\n
\n!\n

", - "

\nHi\n

\n

\nfolks\n
\n!\n

", - "

\nHi\n

\n

\nfolks\n
\n!\n

", - ); - - // 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 @@ * Check node edit functionality. */ function testPageEdit() { + $body_key = NODE_BODY_FIELD . '[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 @@ // 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', '' . $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,46 @@ * Check the node preview functionality. */ function testPagePreview() { + $body_key = NODE_BODY_FIELD . '[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', '' . $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 = NODE_BODY_FIELD . '[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', '' . $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 +310,7 @@ // Create a node. $edit = array(); $edit['title'] = $this->randomName(8); - $edit['body'] = $this->randomName(16); + $edit[NODE_BODY_FIELD . '[0][value]'] = $this->randomName(16); $this->drupalPost('node/add/page', $edit, t('Save')); // Check that the page has been created. @@ -599,7 +457,7 @@ // Create a node. $edit = array(); $edit['title'] = $this->randomName(8); - $edit['body'] = $this->randomName(16); + $edit[NODE_BODY_FIELD . '[0][value]'] = $this->randomName(16); $this->drupalPost('node/add/page', $edit, t('Save')); // Check that the post information is displayed. @@ -620,7 +478,7 @@ // Create a node. $edit = array(); $edit['title'] = $this->randomName(8); - $edit['body'] = $this->randomName(16); + $edit[NODE_BODY_FIELD . '[0][value]'] = $this->randomName(16); $this->drupalPost('node/add/page', $edit, t('Save')); // Check that the post information is displayed. Index: modules/node/node.module =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.module,v retrieving revision 1.1058 diff -u -r1.1058 node.module --- modules/node/node.module 26 May 2009 09:13:47 -0000 1.1058 +++ modules/node/node.module 27 May 2009 00:15:07 -0000 @@ -46,6 +46,11 @@ define('NODE_BUILD_PRINT', 5); /** + * Name of the default body field. + */ +define('NODE_BODY_FIELD', 'node_body'); + +/** * Implementation of hook_help(). */ function node_help($path, $arg) { @@ -291,186 +296,6 @@ } /** - * 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\r\n" . trim($form_state['values']['body']); - } - else { - // Empty teaser, no spaces. - $body = '' . $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'], '') === 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'], '') === 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 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, ''); - - // 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, '' => 0); - - // If no complete paragraph then treat line breaks as paragraphs. - $line_breaks = array('
' => 6, '
' => 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; -} - -/** * Builds a list of available node types, and returns all of part of this list * in the specified format. * @@ -587,6 +412,7 @@ 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; } @@ -597,13 +423,67 @@ ->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. + */ +function node_configure_fields($type) { + // Add or remove the body field, as needed. + $field = field_info_field(NODE_BODY_FIELD); + $instance = field_info_instance(NODE_BODY_FIELD, $type->type); + if ($type->has_body) { + if (empty($field)) { + $field = array( + 'field_name' => NODE_BODY_FIELD, + 'type' => 'text_with_summary', + ); + $field = field_create_field($field); + } + if (empty($instance)) { + $instance = array( + 'field_name' => NODE_BODY_FIELD, + '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 @@ -961,7 +841,8 @@ // 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->{NODE_BODY_FIELD}[0]['value']) && count(explode(' ', $node->{NODE_BODY_FIELD}[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))); } @@ -998,25 +879,6 @@ // 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); - // 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)) { @@ -1062,16 +924,6 @@ 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; @@ -1231,30 +1083,6 @@ } /** - * 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 @@ -1279,30 +1107,41 @@ * * @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('', '', $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); @@ -1587,16 +1426,16 @@ // 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); @@ -1609,7 +1448,7 @@ '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; @@ -1739,7 +1578,7 @@ $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", @@ -2193,12 +2032,12 @@ // 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 = '

' . check_plain($node->title) . '

' . $node->body; + $text = '

' . check_plain($node->title) . '

' . $node->rendered; // Fetch extra data normally not visible $extra = module_invoke_all('node_update_index', $node); @@ -2403,10 +2242,6 @@ 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; @@ -2912,8 +2747,6 @@ ); } - $form['body_field'] = node_body_field($node, $type->body_label, $type->min_word_count); - return $form; } Index: modules/node/node.pages.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.pages.inc,v retrieving revision 1.66 diff -u -r1.66 node.pages.inc --- modules/node/node.pages.inc 25 May 2009 15:39:12 -0000 1.66 +++ modules/node/node.pages.inc 27 May 2009 00:15:08 -0000 @@ -114,7 +114,7 @@ $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; } @@ -286,52 +286,6 @@ } /** - * 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_get_types('type', $node)->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'] = '
'; - $form['#suffix'] = '
'; - - $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' => '
', - '#suffix' => '
', - ); - - $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) { @@ -392,16 +346,6 @@ $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); - // 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()) { @@ -427,28 +371,20 @@ $output = '
'; $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('', '', $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, '') === 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. You can insert the delimiter "<!--break-->" (without the quotes) to fine-tune where your post gets split.')); $output .= '

' . t('Preview trimmed version') . '

'; - $output .= drupal_render(node_build(clone $node, TRUE)); + $output .= $trimmed; $output .= '

' . t('Preview full version') . '

'; - $output .= drupal_render(node_build($node, FALSE)); + $output .= $full; } else { - $output .= drupal_render(node_build($node, FALSE)); + $output .= $full; } $output .= "
\n"; Index: modules/field/modules/text/text.test =================================================================== RCS file: /cvs/drupal/drupal/modules/field/modules/text/text.test,v retrieving revision 1.6 diff -u -r1.6 text.test --- modules/field/modules/text/text.test 29 Apr 2009 12:08:28 -0000 1.6 +++ modules/field/modules/text/text.test 27 May 2009 00:15:05 -0000 @@ -13,7 +13,7 @@ } 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 @@ // 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() . '
' . $this->randomName(); @@ -175,11 +175,11 @@ // 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 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 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() { + $text = 'A question? A sentence. Another sentence.'; + $expected = 'A question? A sentence.'; + $this->callTextSummary($text, $expected, NULL, 30); + } + + /** + * Test teaser 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 teaser length edge cases. + */ + function testLength() { + // This body string tests a number of edge cases. + $text = "

\nHi\n

\n

\nfolks\n
\n!\n

"; + + // 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: + $expected = array( + "

\nHi\n

\n

\nfolks\n
\n!\n

", + "<", + "", + "

\n", + "

\nH", + "

\nHi", + "

\nHi\n", + "

\nHi\n<", + "

\nHi\n\nHi\n\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

\n

\nfolks\n
\n!\n

", + "

\nHi\n

\n

\nfolks\n
\n!\n

", + "

\nHi\n

\n

\nfolks\n
\n!\n

", + ); + + // And Using an text format WITH the line-break filter. + $expected_lb = array( + "

\nHi\n

\n

\nfolks\n
\n!\n

", + "<", + "", + "

", + "

", + "

", + "

\nHi", + "

\nHi", + "

\nHi", + "

\nHi", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

", + "

\nHi\n

\n

\nfolks\n
\n!\n

", + "

\nHi\n

\n

\nfolks\n
\n!\n

", + "

\nHi\n

\n

\nfolks\n
\n!\n

", + ); + + // Test node_teaser() 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 node_teaser() 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 summary.', array('@summary' => $summary))); + } +} Index: modules/field/modules/text/text.info =================================================================== RCS file: /cvs/drupal/drupal/modules/field/modules/text/text.info,v retrieving revision 1.3 diff -u -r1.3 text.info --- modules/field/modules/text/text.info 10 May 2009 18:56:57 -0000 1.3 +++ modules/field/modules/text/text.info 27 May 2009 00:15:04 -0000 @@ -5,3 +5,4 @@ version = VERSION core = 7.x files[]=text.module +required = TRUE Index: modules/field/modules/text/text.module =================================================================== RCS file: /cvs/drupal/drupal/modules/field/modules/text/text.module,v retrieving revision 1.7 diff -u -r1.7 text.module --- modules/field/modules/text/text.module 20 May 2009 09:48:47 -0000 1.7 +++ modules/field/modules/text/text.module 27 May 2009 00:15:04 -0000 @@ -14,6 +14,9 @@ '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,23 @@ 'field_formatter_text_trimmed' => array( 'arguments' => array('element' => NULL), ), + 'field_formatter_text_summary_or_trimmed' => array( + 'arguments' => array('element' => NULL), + ), ); } /** * Implementation of hook_field_info(). + * + * @param $max_length + * The maximum length for a varchar field. + * @param $text_processing + * Whether text input filters should be used. + * @param $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 +54,25 @@ '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/teaser 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 +80,39 @@ * Implementation of 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 +133,27 @@ * Implementation of 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, + ); + } } } } @@ -114,14 +163,22 @@ global $language; foreach ($items as $delta => $item) { // TODO D7 : this code is really node-related. + $format = $item['format']; if (!empty($instance['settings']['text_processing'])) { $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) : ''; + $text = isset($item['value']) ? check_markup($item['value'], $format, isset($object->language) ? $object->language : $language->language, $check) : ''; + if ($field['type'] == 'text_with_summary') { + $summary = isset($item['summary']) ? check_markup($item['summary'], $format, isset($object->language) ? $object->language : $language->language, $check) : ''; + } } else { $text = check_plain($item['value']); + if ($field['type'] == 'text_with_summary') { + $summary = check_plain($item['summary']); + } } $items[$delta]['safe'] = $text; + $items[$delta]['safe_summary'] = isset($summary) ? $summary : ''; } } @@ -142,21 +199,39 @@ 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, ), @@ -184,7 +259,141 @@ 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 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 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 summary. If omitted, the default + * value will be used. Ignored if the special delimiter is present + * in $body. + * @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, ''); + + // 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, '' => 0); + + // If no complete paragraph then treat line breaks as paragraphs. + $line_breaks = array('
' => 6, '
' => 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 return the summary. + if ($min_rpos !== $max_rpos) { + // Don't slice with length 0. Length must be <0 to slice from RHS. + return ($min_rpos === 0) ? $summary : substr($summary, 0, 0 - $min_rpos); + } + } + + // If a break point was not found, still return a summary. + return $summary; } /** @@ -218,6 +427,15 @@ '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, + ), + ), ); } @@ -239,12 +457,21 @@ '#input' => TRUE, '#columns' => array('value'), '#delta' => 0, '#process' => array('text_textfield_process'), + '#theme_wrapper' => 'text_textfield', '#autocomplete_path' => FALSE, ), 'text_textarea' => array( '#input' => TRUE, '#columns' => array('value', 'format'), '#delta' => 0, '#process' => array('text_textarea_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, ), ); @@ -287,6 +514,10 @@ '#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; } @@ -294,7 +525,17 @@ * Implementation of 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']); } /** @@ -322,24 +563,17 @@ '#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; @@ -358,33 +592,94 @@ $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'; + if (!empty($edit[$default_key])) { + $edit['format'] = $edit[$default_key]; + unset($edit[$default_key]); + } + return $edit; + } + } + +/** * FAPI theme for an individual text elements. * * The textfield or textarea is already rendered by the @@ -402,3 +697,21 @@ 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']; + } +} Index: modules/field/modules/options/options.info =================================================================== RCS file: /cvs/drupal/drupal/modules/field/modules/options/options.info,v retrieving revision 1.2 diff -u -r1.2 options.info --- modules/field/modules/options/options.info 10 May 2009 18:56:57 -0000 1.2 +++ modules/field/modules/options/options.info 27 May 2009 00:15:04 -0000 @@ -5,3 +5,4 @@ version = VERSION core = 7.x files[]=options.module +required = TRUE Index: modules/dblog/dblog.test =================================================================== RCS file: /cvs/drupal/drupal/modules/dblog/dblog.test,v retrieving revision 1.20 diff -u -r1.20 dblog.test --- modules/dblog/dblog.test 24 May 2009 17:39:32 -0000 1.20 +++ modules/dblog/dblog.test 27 May 2009 00:15:03 -0000 @@ -327,7 +327,7 @@ default: $content = array( 'title' => $this->randomName(8), - 'body' => $this->randomName(32), + NODE_BODY_FIELD . '[0][value]' => $this->randomName(32), ); break; } @@ -351,7 +351,7 @@ default: $content = array( - 'body' => $this->randomName(32), + NODE_BODY_FIELD . '[0][value]' => $this->randomName(32), ); break; } Index: modules/simpletest/tests/common.test =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/tests/common.test,v retrieving revision 1.43 diff -u -r1.43 common.test --- modules/simpletest/tests/common.test 25 May 2009 05:20:16 -0000 1.43 +++ modules/simpletest/tests/common.test 27 May 2009 00:15:10 -0000 @@ -261,9 +261,7 @@ // 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!') . "", + NODE_BODY_FIELD => array(array('value' => t('This tests the inline CSS!') . "", 'format' => 3)), // PHP filter. 'promote' => 1, ); $node = $this->drupalCreateNode($settings); Index: modules/simpletest/tests/module.test =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/tests/module.test,v retrieving revision 1.3 diff -u -r1.3 module.test --- modules/simpletest/tests/module.test 26 Apr 2009 15:14:55 -0000 1.3 +++ modules/simpletest/tests/module.test 27 May 2009 00:15:10 -0000 @@ -22,19 +22,24 @@ * 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_query("UPDATE {system} SET weight = 20 WHERE name = 'path' AND type = 'module'"); @@ -42,8 +47,9 @@ 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( @@ -56,7 +62,7 @@ // Reset the module list. module_list(TRUE); - $this->assertModuleList($base_module_list, t('After reset')); + $this->assertModuleList($module_list, t('After reset')); } /** @@ -66,6 +72,7 @@ * 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))); Index: modules/filter/filter.admin.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/filter/filter.admin.inc,v retrieving revision 1.27 diff -u -r1.27 filter.admin.inc --- modules/filter/filter.admin.inc 26 Apr 2009 06:00:31 -0000 1.27 +++ modules/filter/filter.admin.inc 27 May 2009 00:15:05 -0000 @@ -302,10 +302,6 @@ $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)) Index: modules/filter/filter.test =================================================================== RCS file: /cvs/drupal/drupal/modules/filter/filter.test,v retrieving revision 1.21 diff -u -r1.21 filter.test --- modules/filter/filter.test 24 May 2009 19:12:12 -0000 1.21 +++ modules/filter/filter.test 27 May 2009 00:15:05 -0000 @@ -109,8 +109,8 @@ $edit = array(); $edit['title'] = $this->randomName(); - $edit['body'] = $body . '' . $extra_text . ''; - $edit['body_format'] = $filtered; + $edit[NODE_BODY_FIELD . '[0][value]'] = $body . '' . $extra_text . ''; + $edit[NODE_BODY_FIELD . '[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.')); Index: modules/path/path.test =================================================================== RCS file: /cvs/drupal/drupal/modules/path/path.test,v retrieving revision 1.12 diff -u -r1.12 path.test --- modules/path/path.test 16 May 2009 19:07:02 -0000 1.12 +++ modules/path/path.test 27 May 2009 00:15:08 -0000 @@ -212,7 +212,7 @@ $this->clickLink(t('add translation')); $edit = array(); $edit['title'] = $this->randomName(); - $edit['body'] = $this->randomName(); + $edit[NODE_BODY_FIELD . '[0][value]'] = $this->randomName(); $edit['path'] = $this->randomName(); $this->drupalPost(NULL, $edit, t('Save')); Index: modules/book/book.module =================================================================== RCS file: /cvs/drupal/drupal/modules/book/book.module,v retrieving revision 1.493 diff -u -r1.493 book.module --- modules/book/book.module 9 May 2009 18:28:11 -0000 1.493 +++ modules/book/book.module 27 May 2009 00:15:03 -0000 @@ -1037,7 +1037,7 @@ 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 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; } /** Index: modules/book/book.test =================================================================== RCS file: /cvs/drupal/drupal/modules/book/book.test,v retrieving revision 1.9 diff -u -r1.9 book.test --- modules/book/book.test 25 Apr 2009 23:01:43 -0000 1.9 +++ modules/book/book.test 27 May 2009 00:15:03 -0000 @@ -139,8 +139,7 @@ // Check printer friendly version. $this->drupalGet('book/export/html/' . $node->nid); $this->assertText($node->title, t('Printer friendly title found.')); - $node->body = str_replace('', '', $node->body); - $this->assertRaw(check_markup($node->body, $node->format), t('Printer friendly body found.')); + $this->assertRaw(check_markup($node->{NODE_BODY_FIELD}[0]['value'], $node->{NODE_BODY_FIELD}[0]['format']), t('Printer friendly body found.')); $number++; } @@ -170,7 +169,7 @@ $edit = array(); $edit['title'] = $number . ' - SimpleTest test node ' . $this->randomName(10); - $edit['body'] = 'SimpleTest test body ' . $this->randomName(32) . ' ' . $this->randomName(32); + $edit[NODE_BODY_FIELD .'[0][value]'] = 'SimpleTest test body ' . $this->randomName(32) . ' ' . $this->randomName(32); $edit['book[bid]'] = $book_nid; if ($parent !== NULL) { Index: modules/translation/translation.module =================================================================== RCS file: /cvs/drupal/drupal/modules/translation/translation.module,v retrieving revision 1.45 diff -u -r1.45 translation.module --- modules/translation/translation.module 18 May 2009 09:45:01 -0000 1.45 +++ modules/translation/translation.module 27 May 2009 00:15:13 -0000 @@ -213,7 +213,6 @@ $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); } Index: modules/translation/translation.test =================================================================== RCS file: /cvs/drupal/drupal/modules/translation/translation.test,v retrieving revision 1.11 diff -u -r1.11 translation.test --- modules/translation/translation.test 2 Apr 2009 20:39:45 -0000 1.11 +++ modules/translation/translation.test 27 May 2009 00:15:13 -0000 @@ -60,14 +60,14 @@ // to return to the page then resubmitting the form without a refresh. $edit = array(); $edit['title'] = $this->randomName(); - $edit['body'] = $this->randomName(); + $edit[NODE_BODY_FIELD . '[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[NODE_BODY_FIELD . '[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 @@ // Update translation and mark as updated. $edit = array(); - $edit['body'] = $this->randomName(); + $edit[NODE_BODY_FIELD . '[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 @@ function createPage($title, $body, $language) { $edit = array(); $edit['title'] = $title; - $edit['body'] = $body; + $edit[NODE_BODY_FIELD . '[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 @@ $edit = array(); $edit['title'] = $title; - $edit['body'] = $body; + $edit[NODE_BODY_FIELD . '[0][value]'] = $body; $this->drupalPost(NULL, $edit, t('Save')); $this->assertRaw(t('Page %title has been created.', array('%title' => $edit['title'])), t('Translation created.')); Index: modules/system/system.install =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.install,v retrieving revision 1.331 diff -u -r1.331 system.install --- modules/system/system.install 25 May 2009 13:42:56 -0000 1.331 +++ modules/system/system.install 27 May 2009 00:15:12 -0000 @@ -3496,6 +3496,17 @@ } /** + * Enable field type modules. + */ +function system_update_7024() { + $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. */ Index: modules/system/system.test =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.test,v retrieving revision 1.44 diff -u -r1.44 system.test --- modules/system/system.test 24 May 2009 17:39:34 -0000 1.44 +++ modules/system/system.test 27 May 2009 00:15:12 -0000 @@ -494,7 +494,7 @@ $edit = array( 'title' => $this->randomName(10), - 'body' => $this->randomName(100) + NODE_BODY_FIELD => array(array('value' => $this->randomName(100))), ); $node = $this->drupalCreateNode($edit); @@ -555,7 +555,7 @@ $edit = array( 'title' => $this->randomName(10), - 'body' => $this->randomName(100) + NODE_BODY_FIELD => array(array('value' => $this->randomName(100))), ); $node = $this->drupalCreateNode($edit); @@ -681,7 +681,7 @@ // Generate node content. $edit = array( 'title' => '!SimpleTest! ' . $title . $this->randomName(20), - 'body' => '!SimpleTest! test body' . $this->randomName(200), + NODE_BODY_FIELD . '[0][value]' => '!SimpleTest! test body' . $this->randomName(200), ); // Create the node with HTML in the title. $this->drupalPost('node/add/page', $edit, t('Save')); Index: modules/field/modules/number/number.info =================================================================== RCS file: /cvs/drupal/drupal/modules/field/modules/number/number.info,v retrieving revision 1.3 diff -u -r1.3 number.info --- modules/field/modules/number/number.info 10 May 2009 18:56:56 -0000 1.3 +++ modules/field/modules/number/number.info 27 May 2009 00:15:04 -0000 @@ -5,3 +5,4 @@ version = VERSION core = 7.x files[]=number.module +required = TRUE Index: modules/blog/blog.module =================================================================== RCS file: /cvs/drupal/drupal/modules/blog/blog.module,v retrieving revision 1.320 diff -u -r1.320 blog.module --- modules/blog/blog.module 26 May 2009 09:13:46 -0000 1.320 +++ modules/blog/blog.module 27 May 2009 00:15:02 -0000 @@ -10,6 +10,7 @@ * Implementation of hook_node_info(). */ function blog_node_info() { + $a = 1; return array( 'blog' => array( 'name' => t('Blog entry'), Index: modules/blog/blog.test =================================================================== RCS file: /cvs/drupal/drupal/modules/blog/blog.test,v retrieving revision 1.12 diff -u -r1.12 blog.test --- modules/blog/blog.test 24 May 2009 17:39:31 -0000 1.12 +++ modules/blog/blog.test 27 May 2009 00:15:02 -0000 @@ -154,7 +154,7 @@ // Edit blog node. $edit = array(); $edit['title'] = 'node/' . $node->nid; - $edit['body'] = $this->randomName(256); + $edit[NODE_BODY_FIELD . '[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')); Index: modules/aggregator/aggregator.test =================================================================== RCS file: /cvs/drupal/drupal/modules/aggregator/aggregator.test,v retrieving revision 1.24 diff -u -r1.24 aggregator.test --- modules/aggregator/aggregator.test 24 May 2009 17:39:30 -0000 1.24 +++ modules/aggregator/aggregator.test 27 May 2009 00:15:02 -0000 @@ -245,7 +245,7 @@ for ($i = 0; $i < 5; $i++) { $edit = array(); $edit['title'] = $this->randomName(); - $edit['body'] = $this->randomName(); + $edit[NODE_BODY_FIELD . '[0][value]'] = $this->randomName(); $this->drupalPost('node/add/article', $edit, t('Save')); } } Index: modules/field/modules/list/list.info =================================================================== RCS file: /cvs/drupal/drupal/modules/field/modules/list/list.info,v retrieving revision 1.3 diff -u -r1.3 list.info --- modules/field/modules/list/list.info 10 May 2009 18:56:56 -0000 1.3 +++ modules/field/modules/list/list.info 27 May 2009 00:15:04 -0000 @@ -5,3 +5,4 @@ version = VERSION core = 7.x files[]=list.module +required = TRUE Index: modules/locale/locale.test =================================================================== RCS file: /cvs/drupal/drupal/modules/locale/locale.test,v retrieving revision 1.23 diff -u -r1.23 locale.test --- modules/locale/locale.test 20 Apr 2009 02:23:16 -0000 1.23 +++ modules/locale/locale.test 27 May 2009 00:15:06 -0000 @@ -1347,7 +1347,7 @@ $edit = array( 'type' => 'page', 'title' => $node_title, - 'body' => $node_body, + NODE_BODY_FIELD => array(array('value' => $node_body)), 'language' => $langcode, ); $node = $this->drupalCreateNode($edit); Index: modules/forum/forum.test =================================================================== RCS file: /cvs/drupal/drupal/modules/forum/forum.test,v retrieving revision 1.19 diff -u -r1.19 forum.test --- modules/forum/forum.test 24 May 2009 17:39:32 -0000 1.19 +++ modules/forum/forum.test 27 May 2009 00:15:05 -0000 @@ -233,7 +233,7 @@ $edit = array( 'title' => $title, - 'body' => $body, + NODE_BODY_FIELD . '[0][value]' => $body, 'taxonomy[1]' => $tid ); @@ -322,7 +322,7 @@ // Edit forum node (including moving it to another forum). $edit = array(); $edit['title'] = 'node/' . $node->nid; - $edit['body'] = $this->randomName(256); + $edit[NODE_BODY_FIELD . '[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')); Index: modules/search/search.test =================================================================== RCS file: /cvs/drupal/drupal/modules/search/search.test,v retrieving revision 1.21 diff -u -r1.21 search.test --- modules/search/search.test 25 May 2009 15:43:38 -0000 1.21 +++ modules/search/search.test 27 May 2009 00:15:08 -0000 @@ -312,7 +312,7 @@ // 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', NODE_BODY_FIELD => array(array('value' => "Drupal's search rocks"))); foreach (array(0, 1) as $num) { if ($num == 1) { switch ($node_rank) { @@ -321,7 +321,7 @@ $settings[$node_rank] = 1; break; case 'relevance': - $settings['body'] .= " really rocks"; + $settings[NODE_BODY_FIELD][0]['value'] .= " really rocks"; break; case 'recent': $settings['created'] = REQUEST_TIME + 3600; Index: modules/help/help.test =================================================================== RCS file: /cvs/drupal/drupal/modules/help/help.test,v retrieving revision 1.6 diff -u -r1.6 help.test --- modules/help/help.test 31 Mar 2009 01:49:52 -0000 1.6 +++ modules/help/help.test 27 May 2009 00:15:05 -0000 @@ -51,17 +51,19 @@ $crumb = '›'; foreach ($this->modules as $module => $name) { - // 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('

' . t($name) . '

', 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('

' . t($name) . '

', t('[' . $module . '] Heading was displayed')); + $this->assertText(t('Home ' . $crumb . ' Administer ' . $crumb . ' Help'), t('[' . $module . '] Breadcrumbs were displayed')); + } } } } Index: modules/simpletest/drupal_web_test_case.php =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/drupal_web_test_case.php,v retrieving revision 1.108 diff -u -r1.108 drupal_web_test_case.php --- modules/simpletest/drupal_web_test_case.php 26 May 2009 08:46:03 -0000 1.108 +++ modules/simpletest/drupal_web_test_case.php 27 May 2009 00:15:09 -0000 @@ -620,27 +620,26 @@ * * @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), - 'title' => $this->randomName(8), - 'comment' => 2, - 'changed' => REQUEST_TIME, - 'format' => FILTER_FORMAT_DEFAULT, - 'moderate' => 0, - 'promote' => 0, - 'revision' => 1, - 'log' => '', - 'status' => 1, - 'sticky' => 0, - 'type' => 'page', - 'revisions' => NULL, - 'taxonomy' => NULL, + NODE_BODY_FIELD => array(array()), + 'title' => $this->randomName(8), + 'comment' => 2, + 'changed' => REQUEST_TIME, + 'moderate' => 0, + 'promote' => 0, + 'revision' => 1, + 'log' => '', + 'status' => 1, + 'sticky' => 0, + 'type' => 'page', + 'revisions' => NULL, + 'taxonomy' => NULL, ); // Use the original node's created time for existing nodes. @@ -648,11 +647,6 @@ $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'])) { @@ -665,6 +659,13 @@ } } + // Merge body field value and format separately. + $body = array( + 'value' => $this->randomName(32), + 'format' => FILTER_FORMAT_DEFAULT + ); + $settings[NODE_BODY_FIELD][0] += $body; + $node = (object) $settings; node_save($node); Index: modules/taxonomy/taxonomy.test =================================================================== RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.test,v retrieving revision 1.31 diff -u -r1.31 taxonomy.test --- modules/taxonomy/taxonomy.test 24 May 2009 17:39:35 -0000 1.31 +++ modules/taxonomy/taxonomy.test 27 May 2009 00:15:12 -0000 @@ -460,7 +460,7 @@ // Post an article. $edit = array(); $edit['title'] = $this->randomName(); - $edit['body'] = $this->randomName(); + $edit[NODE_BODY_FIELD . '[0][value]'] = $this->randomName(); $edit['taxonomy[' . $this->vocabulary->vid . ']'] = $term1->tid; $this->drupalPost('node/add/article', $edit, t('Save')); @@ -502,7 +502,7 @@ // 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[NODE_BODY_FIELD . '[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) {