Index: modules/node/content_types.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/node/content_types.inc,v retrieving revision 1.93 diff -u -r1.93 content_types.inc --- modules/node/content_types.inc 10 Sep 2009 22:10:10 -0000 1.93 +++ modules/node/content_types.inc 17 Sep 2009 16:54:11 -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. Add content type.', array('@link' => url('admin/structure/types/add'))), 'colspan' => '5', 'class' => array('message'))); } - + $build['node_table'] = array( '#theme' => 'table', '#header' => $header, @@ -196,12 +196,22 @@ '#description' => t('Enable the submitted by Username on date 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('Read more 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 Read more link(s) on the trimmed post, for linking to the full post.'), + ); $form['old_type'] = array( '#type' => 'value', '#value' => $type->type, @@ -320,8 +330,6 @@ return; } - $status = node_type_save($type); - $variables = $form_state['values']; // Remove everything that's been saved already - whatever's left is assumed @@ -349,6 +357,8 @@ } } + $status = node_type_save($type); + node_types_rebuild(); menu_rebuild(); $t_args = array('%name' => $type->name); @@ -439,6 +449,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.module =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.module,v retrieving revision 1.1124 diff -u -r1.1124 node.module --- modules/node/node.module 17 Sep 2009 03:12:40 -0000 1.1124 +++ modules/node/node.module 17 Sep 2009 16:54:12 -0000 @@ -541,13 +541,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. @@ -564,11 +567,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); } } @@ -1085,11 +1090,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/node/node.test =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.test,v retrieving revision 1.43 diff -u -r1.43 node.test --- modules/node/node.test 29 Aug 2009 04:16:15 -0000 1.43 +++ modules/node/node.test 17 Sep 2009 16:54:13 -0000 @@ -870,6 +870,111 @@ } } +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]"] = ''; + + $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( + 'teaser_read_more_location[inline]' => TRUE, + 'teaser_read_more_location[link]' => FALSE, + ); + $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( + 'teaser_read_more_location[inline]' => FALSE, + 'teaser_read_more_location[link]' => TRUE, + ); + $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( + 'teaser_read_more_location[inline]' => TRUE, + 'teaser_read_more_location[link]' => TRUE + ); + $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( + 'teaser_read_more_location[inline]' => FALSE, + 'teaser_read_more_location[link]' => FALSE + ); + $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/field/modules/text/text.module =================================================================== RCS file: /cvs/drupal/drupal/modules/field/modules/text/text.module,v retrieving revision 1.28 diff -u -r1.28 text.module --- modules/field/modules/text/text.module 11 Sep 2009 13:30:49 -0000 1.28 +++ modules/field/modules/text/text.module 17 Sep 2009 16:54:10 -0000 @@ -17,6 +17,9 @@ 'text_textfield' => array( 'arguments' => array('element' => NULL), ), + 'read_more' => array( + 'arguments' => array('link' => NULL, 'inline' => FALSE), + ), ); } @@ -53,7 +56,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', ), @@ -327,12 +330,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 element, as we are only interested in that. + if (preg_match("|^]*>(.*)$|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; } /** @@ -767,6 +822,14 @@ } /** + * Theme function for 'read more' link. + */ +function theme_read_more($link, $inline = FALSE) { + $element = ($inline ? 'span' : 'div'); + return ' <'. $element .' class="read-more">' . $link . ''; +} + +/** * FAPI theme for an individual text elements. * * The textfield or textarea is already rendered by the