From 18e6ae8ca50915d4ff6516f55e1ef0e556778fb1 Mon Sep 17 00:00:00 2001
From: Bob Vincent <bobvin@pillars.net>
Date: Thu, 19 May 2011 12:04:39 -0400
Subject: [PATCH] Issue #823380 by AlexisWilke, NancyDru, greg.harvey,
 pillarsdotnet, AES2, vito_swat, scito, joachim,
 intoxination: Better read_more handling.

---
 modules/field/field.module             |   35 ++++++++
 modules/field/modules/text/text.module |   19 ++++-
 modules/node/node.module               |    6 ++
 modules/node/node.test                 |  136 ++++++++++++++++++++++++++++++++
 4 files changed, 192 insertions(+), 4 deletions(-)

diff --git a/modules/field/field.module b/modules/field/field.module
index f04c39c0cab95cf1205c4deacb77f05af85e166d..656bd91484ae8cf73045af24bbc2ce2cb9fc6ceb 100644
--- a/modules/field/field.module
+++ b/modules/field/field.module
@@ -1045,6 +1045,41 @@ function field_extract_bundle($entity_type, $bundle) {
 }
 
 /**
+ * Check if any fields should trigger a readmore.
+ *
+ * This will parse through all renderable fields in the renederable array
+ * and check for #readmore property. If #readmore is TRUE, then we will
+ * return TRUE to signify that the caller should include a readmore link.
+ *
+ * @param $elements
+ *   An array of renderable fields
+ * @return
+ *   True if a single element has #readmore set to TRUE, otherwise FALSE.
+ */
+function field_has_read_more($elements) {
+  // Early-return if the user does not have access.
+  if (empty($elements) || (isset($elements['#access']) && !$elements['#access'])) {
+    return FALSE;
+  }
+
+  // Return if #read_more is set on this element.
+  if (!empty($elements['#read_more']) && $elements['#read_more']) {
+    return TRUE;
+  }
+
+  // Iterate through children.
+  foreach (element_children($elements) as $key) {
+    if (field_has_read_more($elements[$key])) {
+      // A child element has #read_more set to TRUE.
+      return TRUE;
+    }
+  }
+
+  // Neither this element, nor any child elements had #read_more set.
+  return FALSE;
+}
+
+/**
  * Theme preprocess function for theme_field() and field.tpl.php.
  *
  * @see theme_field()
diff --git a/modules/field/modules/text/text.module b/modules/field/modules/text/text.module
index d73814faaafa007f2f13499616ecd17edcf24b7f..033f4c71409348c44406d56ea319e6860139ba3d 100644
--- a/modules/field/modules/text/text.module
+++ b/modules/field/modules/text/text.module
@@ -262,23 +262,34 @@ function text_field_formatter_view($entity_type, $entity, $field, $instance, $la
     case 'text_trimmed':
       foreach ($items as $delta => $item) {
         $output = _text_sanitize($instance, $langcode, $item, 'value');
+        $readmore = FALSE;
         if ($display['type'] == 'text_trimmed') {
-          $output = text_summary($output, $instance['settings']['text_processing'] ? $item['format'] : NULL, $display['settings']['trim_length']);
+          $trimmed_output = text_summary($output, $instance['settings']['text_processing'] ? $item['format'] : NULL, $display['settings']['trim_length']);
+          if ($trimmed_output != $output){
+            $readmore = TRUE;
+          }
+          $output = $trimmed_output;
         }
-        $element[$delta] = array('#markup' => $output);
+        $element[$delta] = array('#markup' => $output, '#read_more' => $readmore);
       }
       break;
 
     case 'text_summary_or_trimmed':
       foreach ($items as $delta => $item) {
+        $readmore = FALSE;
         if (!empty($item['summary'])) {
+          $readmore = TRUE;
           $output = _text_sanitize($instance, $langcode, $item, 'summary');
         }
         else {
           $output = _text_sanitize($instance, $langcode, $item, 'value');
-          $output = text_summary($output, $instance['settings']['text_processing'] ? $item['format'] : NULL, $display['settings']['trim_length']);
+          $trimmed_output = text_summary($output, $instance['settings']['text_processing'] ? $item['format'] : NULL, $display['settings']['trim_length']);
+          if ($trimmed_output != $output){
+            $readmore = TRUE;
+          }
+          $output = $trimmed_output;
         }
-        $element[$delta] = array('#markup' => $output);
+        $element[$delta] = array('#markup' => $output, '#read_more' => $readmore);
       }
       break;
 
diff --git a/modules/node/node.module b/modules/node/node.module
index 0c3cfb7a006279b66b78ff03b76ba42e03477077..9d6e4dc60778cd4d41476723c1c9c6deb3678777 100644
--- a/modules/node/node.module
+++ b/modules/node/node.module
@@ -1376,6 +1376,12 @@ function node_build_content($node, $view_mode = 'full', $langcode = NULL) {
       'html' => TRUE,
       'attributes' => array('rel' => 'tag', 'title' => $node_title_stripped),
     );
+    // If no field has the '#read_more' property set to TRUE, then hide the
+    // "Read more" link from view. It cannot be removed altogether because
+    // too many tests have been written which require it.
+    if (!field_has_read_more($node->content)) {
+      $links['node-readmore']['attributes']['class'] = 'element-hidden';
+    }
   }
   $node->content['links']['node'] = array(
     '#theme' => 'links__node__node',
diff --git a/modules/node/node.test b/modules/node/node.test
index 817f3908e01b957a01fd48013a0c97a90235e77f..d9c3cf148634ec09606fa2f0f12f1a785848ea8c 100644
--- a/modules/node/node.test
+++ b/modules/node/node.test
@@ -2291,3 +2291,139 @@ class NodeTokenReplaceTestCase extends DrupalWebTestCase {
     }
   }
 }
+
+/**
+ * Test the "Read more" link for teasers.
+ */
+class NodeTeaserReadMoreTest extends DrupalWebTestCase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Teaser read more',
+      'description' => 'Test the "Read more" link for teasers.',
+      'group' => 'Node',
+    );
+  }
+
+  function setUp() {
+    parent::setUp();
+
+    $web_user = $this->drupalCreateUser(array(
+      'create article content',
+      'create page content',
+      'administer filters',
+      filter_permission_name(filter_format_load('filtered_html')),
+      filter_permission_name(filter_format_load('full_html')),
+      'administer content types',
+      'access administration pages',
+      'bypass node access',
+      'administer taxonomy',
+      'administer nodes',
+    ));
+    $this->drupalLogin($web_user);
+  }
+
+  /**
+   * Create a node where teaser and full view are equal. The "Read more" link
+   * must not be visible.
+   */
+  function testTeaserIsComplete() {
+    $node1 = $this->drupalCreateNode(array(
+      'type' => 'article',
+      'promote' => 1,
+      'body' => array(
+        LANGUAGE_NONE => array(
+          '0' => array(
+            'value' => 'body_' . $this->randomName(32),
+          ),
+        ),
+      ),
+    ) );
+
+    $this->drupalGet('node');
+    $this->assertText($node1->title, t('Node title appears on the default listing.'));
+    $this->assertText($node1->body['und'][0]['value'], t('Node body appears on the default listing.'));
+    // Confirm that the read more link for the promoted node exists but is
+    // invisible.
+    $this->assertFieldByXPath("//a[text()='Read more' and (contains(concat(' ', normalize-space(@class), ' '), ' element-hidden ')]", NULL, "Read more link has the 'element-hidden' class.");
+  }
+
+  /**
+   * Create a node with a tag where teaser and full view are equal. The
+   * "Read more" link must not appear.
+   */
+  function testTeaserIsCompleteWithTag() {
+    // Post an article with a taxonomy term.
+    $langcode = LANGUAGE_NONE;
+    $tag = 'tag_' . $this->randomName(8);
+    $edit = array();
+    $edit['title'] = 'title_' . $this->randomName(8);
+    $edit["body[$langcode][0][value]"] = 'body_' . $this->randomName(32);
+    $edit["field_tags[$langcode]"] = $tag;
+    $edit['promote'] = 1;
+    $this->drupalPost('node/add/article', $edit, t('Save'));
+    $node1 = $this->drupalGetNodeByTitle($edit['title']);
+
+    $this->drupalGet('node');
+    $this->assertText($node1->title, t('Node title appears on the default listing.'));
+    $this->assertText($node1->body['und'][0]['value'], t('Node body appears on the default listing.'));
+    $this->assertText($tag, t('Tag appears on the default listing.'));
+    // Confirm that the read more link for the promoted node exists but is
+    // invisible.
+    $this->assertFieldByXPath("//a[text()='Read more' and contains(concat(' ', normalize-space(@class), ' '), ' element-hidden ')]", NULL, "Read more link has the 'element-hidden' class.");
+  }
+
+  /**
+   * Create a node with a summary. The "Read more" link must be set.
+   */
+  function testTeaserSummary() {
+    $body = 'body_' . $this->randomName(32);
+    $summary = 'summary_' . $this->randomName(32);
+    $node1 = $this->drupalCreateNode(array(
+      'type' => 'article',
+      'promote' => 1,
+      'body' => array(
+        LANGUAGE_NONE => array(
+          '0' => array(
+            'value' => $body,
+            'summary' => $summary,
+          ),
+        ),
+      ),
+    ) );
+
+    $this->drupalGet('node');
+    $this->assertText($node1->title, t('Node title appears on the default listing.'));
+    $this->assertText($summary, t('The summary text appears in the default listing.'));
+    $this->assertNoText($body, t('The body text does not appear in the default listing.'));
+    // Confirm that the read more link for the promoted node is not hidden.
+    $this->assertFieldByXPath("//a[text()='Read more' and not(contains(normalize-space(@class), ' element-hidden '))]", NULL, "Read more link does not have the 'element-hidden' class.");
+  }
+
+  /**
+   * Create a node with trimmed body. The "Read more" link must be set.
+   */
+  function testTeaserTrimmed() {
+    $node1 = $this->drupalCreateNode(array(
+      'type' => 'article',
+      'promote' => 1,
+      'body' => array(
+        LANGUAGE_NONE => array(
+          '0' => array(
+            'value' => 'teaser<!--break-->body',
+            // Set text format to Full HTML due to bug http://drupal.org/node/881006
+            'format' => 'full_html',
+          ),
+        ),
+      ),
+    ) );
+
+    $this->drupalGet('node');
+    $this->assertText($node1->title, t('Node title appears on the default listing.'));
+    $this->assertText('teaser', t('The teaser text appears in the default listing.'));
+    $this->assertNoText('body', t('The body text does not appear in the default listing.'));
+    // Confirm that the read more link for the promoted node is not hidden.
+    $this->assertFieldByXPath("//a[text()='Read more' and not(contains(normalize-space(@class), ' element-hidden '))]", NULL, "Read more link does not have the 'element-hidden' class.");
+  }
+
+}
-- 
1.7.5.4

