You are here

ed_readmore.module in Read More Link (Drupal 6 and earlier) 6.5

Same filename and directory in other branches
  1. 5 ed_readmore.module
  2. 6.2 ed_readmore.module
  3. 6.3 ed_readmore.module

Customize the "Read More" link shown in teasers.

File

ed_readmore.module
View source
<?php

/**
 * @file
 * Customize the "Read More" link shown in teasers.
 */
define('ED_READMORE_PLACEMENT_DEFAULT', 'inline');
define('ED_READMORE_TEXT_PREPEND_DEFAULT', '<strong>');
define('ED_READMORE_TEXT_DEFAULT', t('Read more'));
define('ED_READMORE_TEXT_APPEND_DEFAULT', ' &raquo;</strong>');

/**
 * Implementation of hook_menu().
 */
function ed_readmore_menu() {
  $items['admin/settings/ed_readmore'] = 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(
      'ed_readmore_admin_settings',
    ),
    'access arguments' => array(
      'administer site configuration',
    ),
    'type' => MENU_NORMAL_ITEM,
  );
  return $items;
}

/**
 * Displays the settings form.
 */
function ed_readmore_admin_settings() {
  $form = array();
  $elements = array(
    'address' => '<address>',
    'blockquote' => '<blockquote>',
    'cite' => '<cite>',
    'div' => '<div>',
    'h1' => '<h1>',
    'h2' => '<h2>',
    'h3' => '<h3>',
    'h4' => '<h4>',
    'h5' => '<h5>',
    'h6' => '<h6>',
    'p' => '<p>',
    'span' => '<span>',
  );
  $form['ed_readmore_behavior'] = array(
    '#type' => 'fieldset',
    '#title' => t('Link behavior'),
    '#collapsible' => FALSE,
  );
  $form['ed_readmore_behavior']['ed_readmore_placement'] = array(
    '#type' => 'radios',
    '#title' => t('Link placement'),
    '#options' => array(
      'inline' => t('Inline: Try to add the Read More link after the last word of the teaser. If this fails, add the link on a new line after the teaser.'),
      'after' => t('On a new line: Add the Read More link on a new line after the teaser.'),
      'disable' => t('Disable the link: Do not add a Read More link to the teaser.'),
    ),
    '#default_value' => variable_get('ed_readmore_placement', ED_READMORE_PLACEMENT_DEFAULT),
    '#description' => t('The inline option will attempt to add the Read More link after the last word of the teaser and before any CCK fields. The HTML elements into which the Read More link may be inserted can be chosen in the "Advanced options for inline placement" interface below.'),
  );
  $form['ed_readmore_behavior']['ed_readmore_placement_advanced'] = array(
    '#type' => 'fieldset',
    '#title' => t('Advanced options for inline placement'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
  );
  $form['ed_readmore_behavior']['ed_readmore_placement_advanced']['ed_readmore_elements'] = array(
    '#type' => 'select',
    '#title' => t('Elements eligible for inline placement'),
    '#description' => t('Select the elements into which the Read More link may be inserted. The "Inline" placement option must be selected above.'),
    '#multiple' => TRUE,
    '#options' => $elements,
    '#default_value' => variable_get('ed_readmore_elements', array(
      'p',
    )),
    '#size' => 10,
  );
  $form['ed_readmore_behavior']['ed_readmore_remove'] = array(
    '#type' => 'checkbox',
    '#title' => t('Remove Read More link from links section'),
    '#default_value' => variable_get('ed_readmore_remove', TRUE),
    '#description' => t('Enabling this option will remove Drupal\'s default Read More link from the node links (the <code>$links</code> output).'),
  );
  $form['ed_readmore_behavior']['ed_readmore_rss'] = array(
    '#type' => 'checkbox',
    '#title' => t('Replace Read More link in RSS feeds'),
    '#default_value' => variable_get('ed_readmore_rss', TRUE),
  );
  $form['ed_readmore_behavior']['ed_readmore_anchor'] = array(
    '#type' => 'checkbox',
    '#title' => t('Skip to unread content (SEE WARNING BELOW)'),
    '#default_value' => variable_get('ed_readmore_anchor', FALSE),
    '#description' => t('Enabling this option will add an anchor to the destination page so that the user skips past the content they already saw in the teaser. WARNING: This feature is still being tested! Please report any problems you experience in the <a href="@link">Read More issue queue</a>.', array(
      '@link' => url('http://drupal.org/project/issues/ed_readmore'),
    )),
  );
  $form['ed_readmore_formatting'] = array(
    '#type' => 'fieldset',
    '#title' => t('Link text and formatting'),
    '#description' => t('Here you can specify the wording of the Read More link, add special characters, and change its appearance by wrapping it in markup. Allowed HTML is listed below. Special characters should be encoded (like %raquo or %amp).', array(
      '%raquo' => '&raquo;',
      '%amp' => '&amp;',
    )),
    '#collapsible' => FALSE,
  );
  $form['ed_readmore_formatting']['ed_readmore_text'] = array(
    '#type' => 'textfield',
    '#title' => t('Link text'),
    '#default_value' => variable_get('ed_readmore_text', ED_READMORE_TEXT_DEFAULT),
    '#description' => t('Enter the text you wish to display in the Read More link.'),
    '#required' => TRUE,
  );
  $form['ed_readmore_formatting']['ed_readmore_text_prepend'] = array(
    '#type' => 'textfield',
    '#title' => t('Prepend link text'),
    '#default_value' => variable_get('ed_readmore_text_prepend', ED_READMORE_TEXT_PREPEND_DEFAULT),
    '#description' => t('Enter the text, markup, or characters you wish to add <em>before</em> the Read More link.'),
    '#required' => FALSE,
  );
  $form['ed_readmore_formatting']['ed_readmore_text_append'] = array(
    '#type' => 'textfield',
    '#title' => t('Append link text'),
    '#default_value' => variable_get('ed_readmore_text_append', ED_READMORE_TEXT_APPEND_DEFAULT),
    '#description' => t('Enter the text, markup, or characters you wish to add <em>after</em> to the Read More link.'),
    '#required' => FALSE,
  );
  $form['ed_readmore_formatting']['allowed_html'] = array(
    '#type' => 'fieldset',
    '#title' => t('Allowed HTML'),
    '#description' => t('The following HTML is allowed in the link text and separator fields above.'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
  );
  $form['ed_readmore_formatting']['allowed_html']['help'] = array(
    '#value' => t('<code>abbr</code>, <code>acronym</code>, <code>b</code>, <code>big</code>, <code>cite</code>, <code>code</code>, <code>del</code>, <code>em</code>, <code>font</code>, <code>i</code>, <code>img</code>, <code>ins</code>, <code>small</code>, <code>span</code>, <code>strong</code>, <code>sub</code>, <code>sup</code>'),
    '#prefix' => '<div>',
    '#suffix' => '</div>',
  );
  $form['ed_readmore_attributes'] = array(
    '#type' => 'fieldset',
    '#title' => t('Link attributes'),
    '#collapsible' => FALSE,
  );
  $form['ed_readmore_attributes']['ed_readmore_title'] = array(
    '#type' => 'textfield',
    '#title' => t('Override link title'),
    '#default_value' => variable_get('ed_readmore_title', ''),
    '#description' => t('Enter the text you wish to be used as the title for the Read More link (the value of the %title attribute). The link title is used for accessibility and search engine optimization and appears as a tooltip in some browsers. If no text is entered, Drupal\'s default link title will be used: "Read the rest of [post title]."', array(
      '%title' => 'title=""',
    )),
    '#required' => FALSE,
  );
  if (module_exists('token')) {
    $form['ed_readmore_attributes']['tokens'] = array(
      '#type' => 'fieldset',
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
      '#title' => t('Placeholder tokens'),
      '#description' => t('The following placeholder tokens can be used in the Read More link text and title. They will be replaced with the appropriate values.'),
    );
    $form['ed_readmore_attributes']['tokens']['ed_readmore_tokens'] = array(
      '#type' => 'checkbox',
      '#title' => t('Enable placeholder tokens'),
      '#default_value' => variable_get('ed_readmore_tokens', FALSE),
      '#description' => t('This option will allow you to use the placeholder tokens listed below.'),
    );
    $form['ed_readmore_attributes']['tokens']['help'] = array(
      '#value' => theme('token_help', 'node'),
    );

    // Add Token-specific help text.
    $token_description = t('<a href="@tokens">Tokens</a> are supported if you enable them below.', array(
      '@tokens' => url('http://drupal.org/project/token'),
    ));
    $form['ed_readmore_formatting']['ed_readmore_text']['#description'] .= ' ' . $token_description;
    $form['ed_readmore_formatting']['ed_readmore_title']['#description'] .= ' ' . $token_description;
  }
  $form['ed_readmore_attributes']['ed_readmore_nofollow'] = array(
    '#type' => 'checkbox',
    '#title' => t('Make link nofollow'),
    '#default_value' => variable_get('ed_readmore_nofollow', TRUE),
    '#description' => t('Adds %nofollow to the link\'s attributes. Often used for search engine optimization.', array(
      '%nofollow' => 'rel="nofollow"',
    )),
  );
  $form['ed_readmore_attributes']['ed_readmore_newwindow'] = array(
    '#type' => 'checkbox',
    '#title' => t('Make link open in a new window'),
    '#default_value' => variable_get('ed_readmore_newwindow', FALSE),
    '#description' => t('Adds %newwindow to the link\'s attributes.', array(
      '%newwindow' => 'target="_blank"',
    )),
  );
  return system_settings_form($form);
}

/**
 * Implementation of hook_link_alter().
 */
function ed_readmore_link_alter(&$links, $node) {

  // Remove the link from the node's $links output if the option is enabled.
  if (variable_get('ed_readmore_remove', TRUE)) {
    unset($links['node_read_more']);
  }
}

/**
 * Implementation of hook_nodeapi().
 */
function ed_readmore_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
  switch ($op) {
    case 'view':
      $anchor = variable_get('ed_readmore_anchor', FALSE);
      if ($teaser) {

        /* Teaser */
        $display = variable_get('ed_readmore_placement', ED_READMORE_PLACEMENT_DEFAULT);

        // Don't do anything if placing the link is disabled.
        if ($display == 'disable' || !$node->readmore) {
          break;
        }

        // Is this an RSS feed?
        if ($node->build_mode == NODE_BUILD_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('ed_readmore_rss', TRUE)) {
            $node->readmore = NULL;
          }
          else {
            break;
          }
        }

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

          // Get last position of the last closing marker in teaser.
          if (preg_match('!</?' . $elements . '[^>]*>\\s*$!i', $node->content['body']['#value'], $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 = strpos($teaser, $node->content['body']['#value']) + $match[0][1];

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

        // Append the link to the end of the teaser.
        if ($display == 'after') {
          $node->content['read_more'] = array(
            '#value' => ed_readmore_link_render($node, $display, $anchor),
            '#weight' => 10,
          );
        }
      }
      else {
        if ($anchor) {

          /* Full node and anchoring is enabled */
          $teaser_rendered = check_markup($node->teaser, $node->format, FALSE);
          $node->content['body']['#value'] = substr_replace($node->content['body']['#value'], $teaser_rendered . "\n" . '<a name="more"></a>', 0, strlen($teaser_rendered));
        }
      }
      break;
  }
}

/**
 * Prepares the link for theming and returns a rendered link.
 *
 * XSS checking and other safety measures are performed here to prevent
 * themers from omitting them.
 */
function ed_readmore_link_render($node, $display, $anchor) {

  // 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',
    'font',
    'i',
    'img',
    'ins',
    'small',
    'span',
    'strong',
    'sub',
    'sup',
  );

  // Filter link text for cross-site scripting (XSS).
  // We sanitize the link text here despite it being passed to l() later on
  // because the theme function that calls l() might be changed to allow HTML.
  $link_text = variable_get('ed_readmore_text_prepend', ED_READMORE_TEXT_PREPEND_DEFAULT);
  $link_text .= t(variable_get('ed_readmore_text', ED_READMORE_TEXT_DEFAULT));
  $link_text .= variable_get('ed_readmore_text_append', ED_READMORE_TEXT_APPEND_DEFAULT);
  $link_text = filter_xss($link_text, $allowed_tags);

  // We don't need to run check_plain() here because it's passed to l(),
  // which handles this for us.
  $link_title = t(variable_get('ed_readmore_title', ''));

  // Mimic node.module's default link title.
  if (empty($link_title)) {
    $link_title = t('Read the rest of !title.', array(
      '!title' => $node->title,
    ));
  }

  // Replace tokens with values if the Token module and the token options are enabled.
  if (module_exists('token') && variable_get('ed_readmore_tokens', FALSE)) {
    $link_text = token_replace($link_text, 'node', $node);
    $link_title = token_replace($link_title, 'node', $node);
  }

  // Build link options array.
  $link_options = array(
    'attributes' => array(
      'title' => $link_title,
    ),
    'html' => TRUE,
  );

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

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

  // Add target="blank" to link if the option is enabled.
  if (variable_get('ed_readmore_newwindow', FALSE)) {
    $link_options['attributes']['target'] = '_blank';
  }

  // Send prepared data to the theme function.
  return theme('ed_readmore_link', $node, $link_text, $link_options, $display);
}

/**
 * Implementation of hook_theme().
 */
function ed_readmore_theme($existing, $type, $theme, $path) {
  return array(
    'ed_readmore_link' => array(
      'arguments' => array(
        'link_text' => NULL,
        'link_destination' => NULL,
        'link_options' => NULL,
        'display' => NULL,
      ),
    ),
  );
}

/**
 * Theme function that wraps the rendered link.
 */
function theme_ed_readmore_link($node, $link_text, $link_options, $display) {

  // 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">' . l($link_text, 'node/' . $node->nid, $link_options) . '</' . $element . '>';
}

Functions

Namesort descending Description
ed_readmore_admin_settings Displays the settings form.
ed_readmore_link_alter Implementation of hook_link_alter().
ed_readmore_link_render Prepares the link for theming and returns a rendered link.
ed_readmore_menu Implementation of hook_menu().
ed_readmore_nodeapi Implementation of hook_nodeapi().
ed_readmore_theme Implementation of hook_theme().
theme_ed_readmore_link Theme function that wraps the rendered link.

Constants