You are here

read_more.module in Read More Link 7

Customize the "Read More" link shown in teasers.

File

read_more.module
View source
<?php

// $Id$

/**
 * @file
 * Customize the "Read More" link shown in teasers.
 */
define('READ_MORE_PLACEMENT_DEFAULT', 'inline');
define('READ_MORE_TEXT_DEFAULT', '[node:read-more:link]');
define('READ_MORE_LINK_TEXT_DEFAULT', '<strong>Read more<span class="element-invisible"> about [node:title]</span></strong>');
define('READ_MORE_TITLE_DEFAULT', '[node:title]');

/**
 * Implements hook_menu().
 */
function read_more_menu() {
  $items['admin/config/content/read_more'] = array(
    'title' => 'Read More link',
    'description' => 'Configures the <strong>Read More</strong> link that appears in node teasers.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'read_more_admin_settings',
    ),
    'access arguments' => array(
      'administer site configuration',
    ),
    'file' => 'read_more.admin.inc',
    'type' => MENU_NORMAL_ITEM,
  );
  $items['admin/config/content/read_more/settings'] = array(
    'title' => 'Settings',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
  return $items;
}

/**
 * Implements template_preprocess_node().
 */
function read_more_preprocess_node(&$vars) {

  // Remove the link from the node's $links output if the option is enabled.
  if (variable_get('read_more_remove', TRUE)) {
    unset($vars['content']['links']['node']['#links']['node-readmore']);
  }
}

/**
 * Implements hook_node_view().
 */
function read_more_node_view($node, $view_mode, $langcode) {
  $attachments = variable_get('read_more_' . $node->type . '_view_modes', array());
  if (!empty($attachments[$view_mode])) {

    /* Teaser */
    $display = variable_get('read_more_placement', READ_MORE_PLACEMENT_DEFAULT);

    // Don't do anything if placing the link is disabled.
    if ($display == 'disable') {
      return;
    }

    // Is this an RSS feed?
    if ($view_mode == 'rss') {

      // If link replacement in RSS feeds is enabled, prevent core from
      // adding a second link by setting the readmore flag to NULL.
      if (variable_get('read_more_rss', TRUE)) {
        $node->readmore = NULL;
      }
      else {
        return;
      }
    }
    $read_more_link = read_more_link($node, $display, $view_mode);

    // Try to insert the link inline.
    if ($display == 'inline') {
      $elements_array = variable_get('read_more_elements', array(
        'p',
      ));
      $elements = '(?:' . implode('|', $elements_array) . ')';

      // Only add a link if there's a body field.
      if (!variable_get('read_more_require_body_field', FALSE) || isset($node->content['body'])) {

        // Get last position of the last closing marker in teaser, if the body
        // field exists.
        if (isset($node->content['body'])) {
          preg_match('!</?' . $elements . '[^>]*>\\s*$!i', $node->content['body'][0]['#markup'], $match, PREG_OFFSET_CAPTURE);

          // Recalculate the position in $teaser. We do this because there may be extra CCK fields appended to the teaser.
          $insert_point = empty($match) ? -1 : strpos($view_mode, $node->content['body'][0]['#markup']) + $match[0][1];

          // Insert the link.
          $node->content['body'][0]['#markup'] = substr_replace($node->content['body'][0]['#markup'], drupal_render($read_more_link), $insert_point, 0);
        }
        else {
          $display = 'after';
        }
      }
    }

    // Append the link to the end of the teaser.
    if ($display == 'after') {
      $node->content['read_more'] = read_more_link($node, $display, $view_mode) + array(
        '#weight' => 1,
      );
    }
  }
  if ($view_mode == 'full' && variable_get('read_more_anchor', FALSE)) {

    /* Full node and anchoring is enabled */
    $body = field_get_items('node', $node, 'body');
    $teaser = field_view_value('node', $node, 'body', $body[0], 'teaser');
    $teaser_rendered = $teaser['#markup'];
    $node->content['body'][0]['#markup'] = substr_replace($node->content['body'][0]['#markup'], $teaser_rendered . '<a name="more"></a>', 0, strlen($teaser_rendered));
  }
}

/**
 * Prepares the link for theming and returns a link.
 *
 * XSS checking and other safety measures are performed here to prevent
 * themers from omitting them.
 */
function read_more_link($node, $display, $view_mode) {
  $uri = entity_uri('node', $node);
  if (empty($uri)) {
    return array();
  }

  // Send prepared data to the theme function.
  return array(
    '#theme' => 'read_more_link',
    '#node' => $node,
    '#display' => $display,
    '#view_mode' => $view_mode,
  );
}

/* Link preprocessing */

/**
 * Add link options as prescribed in the Read More link config page.
 */
function _read_more_link_options($uri) {

  // Build link options array.
  $link_options = $uri['options'];
  $link_options['attributes']['title'] = _read_more_link_title($uri['options']['entity']);
  $link_options['html'] = TRUE;

  // Add anchor to link if the option is enabled.
  if (variable_get('read_more_anchor', FALSE)) {
    $link_options['fragment'] = 'more';
  }

  // Add rel="nofollow" to link if the option is enabled.
  if (variable_get('read_more_nofollow', TRUE)) {
    $link_options['attributes']['rel'] = 'nofollow';
  }

  // Add target="blank" to link if the option is enabled.
  if (variable_get('read_more_newwindow', FALSE)) {
    $link_options['attributes']['target'] = '_blank';
  }
  return $link_options;
}
function _read_more_link_uri($node, $options = array()) {
  $uri = entity_uri('node', $node);
  $uri['options'] = _read_more_link_options($uri);
  $uri['options'] = $options + $uri['options'];
  return $uri;
}

/**
 * @return The Read More URL, with any noted options.
 */
function _read_more_link_url($node, $options = array()) {
  $uri = _read_more_link_uri($node, $options);
  return url($uri['path'], $uri['options']);
}

/**
 * Limit the tags in the link text and link wrapper to a specific set.
 */
function _read_more_filter_xss($text) {

  // Allowed tags borrowed largely from filter_xss_admin().
  // See http://api.drupal.org/api/function/filter_xss_admin
  $allowed_tags = array(
    'abbr',
    'acronym',
    'b',
    'big',
    'cite',
    'code',
    'del',
    'em',
    'i',
    'img',
    'ins',
    'small',
    'span',
    'strong',
    'sub',
    'sup',
  );
  return filter_xss($text, $allowed_tags);
}

/* Token handlers */

/**
 * The 'read-more' token will probably include the node:read-more:link token.
 */
function _read_more_wrapper($node, $view_mode) {
  $link_wrapper = variable_get('read_more_text', READ_MORE_TEXT_DEFAULT);

  // Filter link text for cross-site scripting (XSS).
  // We sanitize the link text here because l() will be told to allow HTML.
  $link_wrapper = _read_more_filter_xss($link_wrapper);
  return token_replace($link_wrapper, array(
    'node' => $node,
  ), array(
    'read_more_view_mode' => $view_mode,
  ));
}

/**
 * The read-more:link token wraps the read-more:link-text in link.
 */
function _read_more_link($node, $view_mode) {
  $link_text = _read_more_link_text($node);
  $link_uri = _read_more_link_uri($node);
  $view_mode_settings = variable_get('read_more_absolute_link_modes', array());
  if (!empty($view_mode_settings[$view_mode])) {
    $link_uri['options']['absolute'] = TRUE;
  }
  return l($link_text, $link_uri['path'], $link_uri['options']);
}

/**
 * The read-more:link-text token contains the text that will be wrapped in a link,
 * but without the link.
 */
function _read_more_link_text($node) {
  $link_text = variable_get('read_more_link_text', READ_MORE_LINK_TEXT_DEFAULT);

  // Filter link text for cross-site scripting (XSS).
  // We sanitize the link text here because l() will be told to allow HTML.
  $link_text = _read_more_filter_xss($link_text);

  // Replace tokens with values if the Token module and the token options are enabled.
  $link_text = token_replace($link_text, array(
    'node' => $node,
  ));
  return $link_text;
}

/**
 * The read-more:link-title token will be included in the link as the title attribute.
 */
function _read_more_link_title($node) {

  // We don't need to sanitize the link title attribute because it's passed to l(), which runs strip_tags() for us.
  $link_title = variable_get('read_more_title', READ_MORE_TITLE_DEFAULT);
  $link_title = token_replace($link_title, array(
    'node' => $node,
  ));
  return $link_title;
}

/**
 * Implements hook_theme().
 */
function read_more_theme($existing, $type, $theme, $path) {
  return array(
    'read_more_link' => array(
      'variables' => array(
        'node' => NULL,
        'display' => NULL,
        'view_mode' => NULL,
      ),
    ),
  );
}

/**
 * Theme function that wraps the rendered link.
 */
function theme_read_more_link($vars) {
  $display = $vars['display'];
  $node = $vars['node'];
  $view_mode = $vars['view_mode'];

  // Use a <div> (block-level) element for links appended after the teaser.
  if ($display == 'after') {
    $element = 'div';
    $separator = '';
  }
  else {

    // Use a <span> (inline) element for links that appear inside the teaser.
    $element = 'span';
    $separator = ' ';
  }
  return $separator . '<' . $element . ' class="read-more">' . _read_more_wrapper($node, $view_mode) . '</' . $element . '>';
}
function read_more_view_modes_options_list() {
  $entity_info = entity_get_info('node');
  $view_modes = array();
  foreach ($entity_info['view modes'] as $key => $view_mode) {
    $view_modes[$key] = $view_mode['label'];
  }
  return $view_modes;
}

Functions

Namesort descending Description
read_more_link Prepares the link for theming and returns a link.
read_more_menu Implements hook_menu().
read_more_node_view Implements hook_node_view().
read_more_preprocess_node Implements template_preprocess_node().
read_more_theme Implements hook_theme().
read_more_view_modes_options_list
theme_read_more_link Theme function that wraps the rendered link.
_read_more_filter_xss Limit the tags in the link text and link wrapper to a specific set.
_read_more_link The read-more:link token wraps the read-more:link-text in link.
_read_more_link_options Add link options as prescribed in the Read More link config page.
_read_more_link_text The read-more:link-text token contains the text that will be wrapped in a link, but without the link.
_read_more_link_title The read-more:link-title token will be included in the link as the title attribute.
_read_more_link_uri
_read_more_link_url
_read_more_wrapper The 'read-more' token will probably include the node:read-more:link token.

Constants

Namesort descending Description
READ_MORE_LINK_TEXT_DEFAULT
READ_MORE_PLACEMENT_DEFAULT @file Customize the "Read More" link shown in teasers.
READ_MORE_TEXT_DEFAULT
READ_MORE_TITLE_DEFAULT