Index: modules/node/content_types.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/content_types.inc,v
retrieving revision 1.91
diff -u -r1.91 content_types.inc
--- modules/node/content_types.inc	24 Aug 2009 19:26:46 -0000	1.91
+++ modules/node/content_types.inc	26 Aug 2009 09:33:15 -0000
@@ -16,7 +16,7 @@
   $rows = array();
 
   foreach ($names as $key => $name) {
-    $type = $types[$key]; 
+    $type = $types[$key];
     if (node_hook($type->type, 'form')) {
       $type_url_str = str_replace('_', '-', $type->type);
       $row = array(theme('node_admin_overview', $name, $type));
@@ -37,7 +37,7 @@
   if (empty($rows)) {
     $rows[] = array(array('data' => t('No content types available. <a href="@link">Add content type</a>.', array('@link' => url('admin/structure/types/add'))), 'colspan' => '5', 'class' => array('message')));
   }
-  
+
   $build['node_table'] = array(
     '#theme' => 'table',
     '#header' => $header,
@@ -194,12 +194,22 @@
     '#description' => t('Enable the <em>submitted by Username on date</em> text.'),
   );
   $form['display']['teaser_length'] = array(
-    '#type' => 'select', 
+    '#type' => 'select',
     '#title' => t('Length of trimmed posts'),
     '#default_value' => variable_get('teaser_length_' . $type->type, 600),
     '#options' => drupal_map_assoc(array(0, 200, 400, 600, 800, 1000, 1200, 1400, 1600, 1800, 2000), '_node_characters'),
     '#description' => t("The maximum number of characters used in the trimmed version of content.")
   );
+  $form['display']['teaser_read_more_location'] = array(
+    '#type' => 'checkboxes',
+    '#title' => t('<em>Read more</em> link'),
+    '#default_value' => variable_get('teaser_read_more_location_' . $type->type, array('inline')),
+    '#options' => array(
+      'inline' => t('Append to summary'),
+      'link' => t('Display in links section'),
+    ),
+    '#description' => t('Placement of <em>Read more</em> link(s) on the trimmed post, for linking to the full post.'),
+  );
   $form['old_type'] = array(
     '#type' => 'value',
     '#value' => $type->type,
@@ -318,8 +328,6 @@
     return;
   }
 
-  $status = node_type_save($type);
-
   $variables = $form_state['values'];
 
   // Remove everything that's been saved already - whatever's left is assumed
@@ -347,6 +355,8 @@
     }
   }
 
+  $status = node_type_save($type);
+
   node_types_rebuild();
   $t_args = array('%name' => $type->name);
 
@@ -436,6 +446,7 @@
   node_type_delete($form_state['values']['type']);
 
   variable_del('teaser_length_' . $form_state['values']['type']);
+  variable_del('teaser_read_more_location_' . $form_state['values']['type']);
   variable_del('node_preview_' . $form_state['values']['type']);
   $t_args = array('%name' => $form_state['values']['name']);
   drupal_set_message(t('The content type %name has been deleted.', $t_args));
Index: modules/node/node.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.test,v
retrieving revision 1.42
diff -u -r1.42 node.test
--- modules/node/node.test	25 Aug 2009 02:48:16 -0000	1.42
+++ modules/node/node.test	26 Aug 2009 09:33:17 -0000
@@ -870,6 +870,103 @@
   }
 }
 
+class SummaryReadMoreTestCase extends DrupalWebTestCase {
+  public static function getInfo() {
+    return array(
+      'name' => 'Summary read more',
+      'description' => 'Check the presence and placement of read more links in summaries',
+      'group' => 'Node',
+    );
+  }
+
+  function setUp() {
+    parent::setUp();
+
+    $web_user = $this->drupalCreateUser(array('create article content','administer content types'));
+    $this->drupalLogin($web_user);
+    $this->web_user = $web_user;
+  }
+
+  function testSummaryReadMore() {
+    $langcode = FIELD_LANGUAGE_NONE;
+    // Create a regular article.
+    $edit = array(
+      'title' => $this->randomName(8),
+      "body[$langcode][0][value]" => $this->randomName(16),
+    );
+    $this->drupalPost('node/add/article', $edit, t('Save'));
+
+    // Create an article containing just an unordered list. Since
+    // the read more link will only be added inline to paragraphs, this
+    // allows us to test how the read more link is inserted when the
+    // summary does not end with a paragraph tag.
+    $edit = array();
+    $edit["body[$langcode][0][value]"] = '<ul>';
+    for ($index = 0; $index < 10; $index++) {
+      $edit["body[$langcode][0][value]"] .= '<li>' . $this->randomString(10) . '</li>';
+    }
+    $edit["body[$langcode][0][value]"] .= '</ul>';
+
+    $edit['title'] = $this->randomName(8);
+    $this->drupalPost('node/add/article', $edit, t('Save'));
+
+    // Change the read more link placement to "inline".
+    $this->drupalGet('admin/structure/node-type/article');
+    $edit = array();
+    $edit['teaser_read_more_location'] = array('inline');
+    $this->drupalPost(NULL, $edit, t('Save content type'));
+
+    // Verify that the link was added at the end of the summary, either as a span,
+    // in case the summary ends with a paragraph, or as a div, in case the summary
+    // ends with any other element.
+    $this->drupalGet('node');
+    $read_more_node = $this->xpath('//div[@id="node-1"]//p[last()]/span[@class="read-more"]');
+    $this->assertTrue(is_array($read_more_node) && count($read_more_node) == 1, t('Read more link was added inline as a span to the summary.'));
+    $read_more_node = $this->xpath('//div[@id="node-2"]//div[@class="read-more"]');
+    $this->assertTrue(is_array($read_more_node) && count($read_more_node) == 1, t('Read more link was added inline as a div to the summary.'));
+
+    // Change the read more link placement to "links" instead of "inline".
+    $this->drupalGet('admin/structure/node-type/article');
+    $edit = array();
+    $edit['teaser_read_more_location'] = array('link');
+    $this->drupalPost('admin/structure/node-type/article', $edit, t('Save content type'));
+
+    // Verify that the read more link is included in the links section.
+    $this->drupalGet('node');
+    $this->assertLink(t('Read more'));
+    $read_more_node = $this->xpath('//div[@id="node-1"]//div[@class="links"]//ul/li/a[text()="'. t('Read more').'"]');
+    $this->assertTrue(is_array($read_more_node) && count($read_more_node) == 1, t('Read more link was added to the links section.'));
+
+    // Change the read more link placement to both "links"and "inline".
+    $this->drupalGet('admin/structure/node-type/article');
+    $edit = array();
+    $edit['teaser_read_more_location'] = array('inline','link');
+    $this->drupalPost('admin/structure/node-type/article', $edit, t('Save content type'));
+
+    // Verify that the read more link is included in the links section, and
+    // verify that the link was added at the end of the summary, either as a span,
+    // in case the summary ends with a paragraph, or as a div, in case the summary
+    // ends with any other element.
+    $this->drupalGet('node');
+    $this->assertLink(t('Read more'));
+    $read_more_node = $this->xpath('//div[@id="node-1"]//div[@class="links"]//ul/li/a[text()="'. t('Read more').'"]');
+    $this->assertTrue(is_array($read_more_node) && count($read_more_node) == 1, t('Read more link was added to the links section.'));
+    $read_more_node = $this->xpath('//div[@id="node-1"]//p[last()]/span[@class="read-more"]');
+    $this->assertTrue(is_array($read_more_node) && count($read_more_node) == 1, t('Read more link was added inline as a span to the summary.'));
+    $read_more_node = $this->xpath('//div[@id="node-2"]//div[@class="read-more"]');
+    $this->assertTrue(is_array($read_more_node) && count($read_more_node) == 1, t('Read more link was added inline as a div to the summary.'));
+
+    // Remove the read more links entirely
+    $this->drupalGet('admin/structure/node-type/article');
+    $edit = array();
+    $edit['teaser_read_more_location'] = array();
+    $this->drupalPost(NULL, $edit, t('Save content type'));
+
+    // Verify that the read more links is not displayed.
+    $this->drupalGet('node');
+    $this->assertNoLink(t('Read more'));
+  }
+}
 /**
  * Test node administration page functionality.
  */
Index: modules/node/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.module,v
retrieving revision 1.1112
diff -u -r1.1112 node.module
--- modules/node/node.module	25 Aug 2009 21:53:47 -0000	1.1112
+++ modules/node/node.module	26 Aug 2009 09:33:17 -0000
@@ -520,13 +520,16 @@
       );
       $field = field_create_field($field);
     }
+    $summary_link = in_array('inline', variable_get('teaser_read_more_location_' . $type->type, array('inline')));
+    
     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),
+        'settings' => array('display_summary' => TRUE,
+          'read_more' => ($summary_link ? t('Read more') : '')),
 
         // With no UI in core, we have to define default
         // formatters for the teaser and full view.
@@ -543,11 +546,13 @@
           ),
         ),
       );
+
       field_create_instance($instance);
     }
     else {
       $instance['label'] = $type->body_label;
       $instance['settings']['display_summary'] = TRUE;
+      $instance['settings']['read_more'] = ($summary_link ? t('Read more') : '');
       field_update_instance($instance);
     }
   }
@@ -1060,11 +1065,13 @@
   // to know when a teaser view is different than a full view.
   $links = array();
   if ($build_mode == 'teaser') {
-    $links['node_readmore'] = array(
-      'title' => t('Read more'),
-      'href' => 'node/' . $node->nid,
-      'attributes' => array('rel' => 'tag', 'title' => strip_tags($node->title))
-    );
+    if (in_array('link', variable_get('teaser_read_more_location_' . $node->type, array('inline')))) {
+      $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(
     '#theme' => 'links',
Index: modules/field/modules/text/text.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/modules/text/text.module,v
retrieving revision 1.24
diff -u -r1.24 text.module
--- modules/field/modules/text/text.module	25 Aug 2009 10:35:32 -0000	1.24
+++ modules/field/modules/text/text.module	26 Aug 2009 09:33:15 -0000
@@ -29,6 +29,9 @@
     'field_formatter_text_summary_or_trimmed' => array(
       'arguments' => array('element' => NULL),
     ),
+    'read_more' => array(
+      'arguments' => array('link' => NULL, 'inline' => FALSE),
+    ),
   );
 }
 
@@ -65,7 +68,7 @@
       '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),
+      'instance_settings' => array('text_processing' => 1, 'display_summary' => 0, 'read_more' => ''),
       'default_widget' => 'text_textarea_with_summary',
       'default_formatter' => 'text_summary_or_trimmed',
     ),
@@ -339,12 +342,64 @@
   $instance = field_info_instance($element['#field_name'], $element['#bundle']);
 
   if (!empty($element['#item']['safe_summary'])) {
-    return $element['#item']['safe_summary'];
+    $summary =  $element['#item']['safe_summary'];
   }
   else {
     $size = variable_get('teaser_length_' . $element['#bundle'], 600);
-    return text_summary($element['#item']['safe'], $instance['settings']['text_processing'] ? $element['#item']['format'] : NULL, $size);
+    $summary = text_summary($element['#item']['safe'], $instance['settings']['text_processing'] ? $element['#item']['format'] : NULL, $size);
   }
+
+  // If enabled, add a "Read more" link to the end of the summary.
+  if (!empty($instance['settings']['read_more'])) {
+    list($id, $vid, $bundle) = field_attach_extract_ids($element['#object_type'], $element['#object']);
+
+    if ($summary) {
+      // Define the tags that allow the read more link to be inserted right before their
+      // closing tag. If the summary ends with any other tag, the read more link will be
+      // appended at the end of the summary instead.
+      $inline_tags = array('p');
+
+      $htmlDom = DOMDocument::loadHTML($summary);
+
+      // The result of DOMDocument::saveXML($bodyNode) is a partial (X)HTML document.
+      // We only need what is inside the body tag.
+      $bodyNode = $htmlDom->getElementsByTagName('body')->item(0);
+
+      // Fetch the last element in the summary.
+      $lastNode = $bodyNode->lastChild;
+
+      // Determine how to insert the read more link into the summary. Inline means the
+      // the link will be inserted before the ending tag of the last element in the summary,
+      // otherwise the link will be appended to the summary as a stand-alone element.
+      $inline = in_array($lastNode->tagName, $inline_tags);
+
+      // Create and theme the read more link.
+      // @todo: Once #525622 lands, use the new API to build the link to the object.
+      $link = $htmlDom->createDocumentFragment();
+      $link->appendXml(theme('read_more', l($instance['settings']['read_more'], "{$element['#object_type']}/$id"), $inline));
+
+      // Add the read more link to the summary.
+      if ($inline && $lastNode) {
+        $lastNode->appendChild($link);
+      }
+      else {
+        $bodyNode->appendChild($link);
+      }
+
+      // DOM manipulation treats our fragment as a full document. Extract the contents of
+      // the <body> element, as we are only interested in that.
+      if (preg_match("|^<body[^>]*>(.*)</body>$|s", $htmlDom->saveXML($bodyNode), $matches)) {
+        $summary = $matches[1];
+      }
+    }
+    else {
+      // The summary is empty, return the read more link.
+      // @todo: Once #525622 lands, use the new API to build the link to the object.
+      return theme('read_more', l($instance['settings']['read_more'], "{$element['#object_type']}/$id"), FALSE);
+    }
+  }
+
+  return $summary;
 }
 
 /**
@@ -773,6 +828,11 @@
   }
 }
 
+function theme_read_more($link, $inline = FALSE) {
+  $element = ($inline ? 'span' : 'div');
+  return ' <'. $element .' class="read-more">' . $link . '</'. $element .'>';
+}
+
 /**
  * FAPI theme for an individual text elements.
  *