You are here

paging.module in Paging 5

Same filename and directory in other branches
  1. 6 paging.module
  2. 7 paging.module

File

paging.module
View source
<?php

// Original module written by Marco Scutari.
// Rewritten and considerably shortened and made more Drupal-friendly
// by Earl Miles.
define('PAGING_SEPARATOR', variable_get('paging_separator', '<!--pagebreak-->'));

/**
 * Implementation of hook_help().
 */
function paging_help($section) {
  switch ($section) {
    case 'admin/help#paging':
      return t('<p>Break long pages into smaller ones by means of a page tag:</p>
<pre>First page here.
%separator
Second page here.
%separator
More pages here.</pre>', array(
        '%separator' => PAGING_SEPARATOR,
      ));
  }
}

/**
 * Implementation of hook_menu().
 */
function paging_menu($maycache) {
  if ($maycache) {
    $items[] = array(
      'path' => 'admin/settings/paging',
      'title' => t('Paging'),
      'description' => t('Enable or disable paging for certain node types and set other preferences.'),
      'callback' => 'drupal_get_form',
      'callback arguments' => 'paging_settings',
      'access' => user_access('administer site configuration'),
      'type' => MENU_NORMAL_ITEM,
    );
  }
  return $items;
}

/**
 * Menu callback; display module settings form.
 */
function paging_settings() {
  $form = array();
  $form['paging_config'] = array(
    '#type' => 'fieldset',
    '#title' => t('Basic preferences'),
  );
  $form['paging_config']['paging_node_types_enabled'] = array(
    '#type' => 'checkboxes',
    '#title' => t('Node types'),
    '#description' => t('Select the node types you want to enable paging for.'),
    '#default_value' => variable_get('paging_node_types_enabled', array()),
    '#options' => node_get_types('names'),
  );
  $form['paging_config']['paging_separator'] = array(
    '#type' => 'textfield',
    '#title' => t('Separator'),
    '#size' => 20,
    '#maxlength' => 255,
    '#default_value' => PAGING_SEPARATOR,
    '#required' => TRUE,
    '#description' => t('Page separator string. You should use an HTML tag that will render reasonably when paging is not enabled, such as <em>&lt;!--pagebreak--&gt;</em> or <em>&lt;HR /&gt;</em>.'),
  );
  $form['paging_config']['paging_read_more_enabled'] = array(
    '#type' => 'checkbox',
    '#title' => t('Link "Read more" to the second page'),
    '#description' => t('If enabled, the "Read more" link under teasers will link to the 2nd page of the node. Implemented only when the teaser and the first page content is same.'),
    '#default_value' => variable_get('paging_read_more_enabled', 0),
  );
  $form['paging_config']['paging_pager_widget_position'] = array(
    '#type' => 'radios',
    '#title' => t('Pager position'),
    '#options' => array(
      'below' => t('Below content'),
      'above' => t('Above content'),
      'both' => t('Below and above content'),
      'manual' => t('None (No output)'),
    ),
    '#required' => TRUE,
    '#description' => t('Choose the position of page navigation. If set to %none, <code>@paging</code> can be used to place it at a customizable location.', array(
      '%none' => t('None'),
      '@paging' => '$node->paging',
    )),
    '#default_value' => variable_get('paging_pager_widget_position', 'below'),
  );
  $form['paging_automatic'] = array(
    '#type' => 'fieldset',
    '#title' => t('Automatic Paging'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
    '#description' => t('Automatic paging is disabled when both methods below are disabled, or when the node already contains the page break separator. Automatic paging by line break is preferred over the method of automatic paging by words.'),
  );
  $form['paging_automatic']['paging_paragraph'] = array(
    '#type' => 'fieldset',
    '#title' => t('By line break'),
    '#collapsed' => FALSE,
    '#prefix' => '<div class="container-inline">',
  );
  $form['paging_automatic']['paging_paragraph']['paging_automatic_chars'] = array(
    '#type' => 'select',
    '#title' => t('Length of each page'),
    '#options' => array(
      0 => t('Disabled'),
      500 => t('500 characters'),
      750 => t('750 characters'),
      1000 => t('1000 characters'),
      1500 => t('1500 characters'),
      2000 => t('2000 characters'),
      2500 => t('2500 characters'),
      3000 => t('3000 characters'),
      3500 => t('3500 characters'),
      4000 => t('4000 characters'),
      4500 => t('4500 characters'),
    ),
    '#required' => TRUE,
    '#description' => '<br />' . t('Set the number of characters that should be displayed per page. <strong>This is the recommended method</strong> for automatic paging.'),
    '#default_value' => variable_get('paging_automatic_chars', 0),
  );
  $form['paging_automatic']['paging_words'] = array(
    '#type' => 'fieldset',
    '#title' => t('By words'),
    '#collapsed' => FALSE,
  );
  $form['paging_automatic']['paging_words']['paging_automatic_words'] = array(
    '#type' => 'select',
    '#title' => t('Length of each page'),
    '#options' => array(
      0 => t('Disabled'),
      100 => t('100 words'),
      150 => t('150 words'),
      200 => t('200 words'),
      250 => t('250 words'),
      300 => t('300 words'),
      350 => t('350 words'),
      400 => t('400 words'),
      450 => t('450 words'),
      500 => t('500 words'),
      550 => t('550 words'),
      600 => t('600 words'),
      650 => t('650 words'),
      700 => t('700 words'),
      750 => t('750 words'),
    ),
    '#required' => TRUE,
    '#description' => '<br />' . t('Set the number of words that should be displayed per page. This may break your sentences at page breaks.'),
    '#default_value' => variable_get('paging_automatic_words', 0),
    '#suffix' => '</div>',
  );
  return system_settings_form($form);
}

/**
 * Implementation of hook_nodeapi().
 */
function paging_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
  if (in_array($node->type, variable_get('paging_node_types_enabled', array()), TRUE)) {
    switch ($op) {
      case 'load':
      case 'view':
      case 'alter':
        if (isset($node->field_body[0]['view'])) {

          // support for CCK
          _paging_nodeapi($node, $node->field_body[0]['view'], $node->field_teaser[0]['view'], $op, $teaser, $page);
        }
        elseif (isset($node->field_body[0]['value'])) {

          // support for CCK
          _paging_nodeapi($node, $node->field_body[0]['value'], $node->field_teaser[0]['value'], $op, $teaser, $page);
        }
        else {
          _paging_nodeapi($node, $node->body, $node->teaser, $op, $teaser, $page);
        }
        break;
    }
  }
}

/**
 * Helper function for paging_nodeapi().
 */
function _paging_nodeapi(&$node, &$node_body, &$node_teaser, $op, $teaser, $page) {
  switch ($op) {
    case 'load':
      if (strpos($teaser ? $node_teaser : $node_body, PAGING_SEPARATOR) !== FALSE) {
        $pages = explode(PAGING_SEPARATOR, $node_body);
      }
      else {
        $body_parts = $node_body;
        if (($max_chars = variable_get('paging_automatic_chars', 0)) != 0) {
          $total_chars = strlen($node_body);
          $body = $node_body;
          if ($total_chars > $max_chars) {
            $breaks = (int) ($total_chars / $max_chars);
            $bodypart = array();
            for ($i = 0; $i <= $breaks; $i++) {
              $bodypart[$i] = paging_paragraph_split(trim($body), NULL, $max_chars);
              $bodycount = strlen($bodypart[$i]);
              $body = substr($body, $bodycount);
            }
            $body_parts = implode(PAGING_SEPARATOR, $bodypart);
          }
        }
        elseif (($max_words = variable_get('paging_automatic_words', 0)) != 0) {
          $words = explode(' ', $node_body);
          $total_words = count($words);
          if ($total_words > $max_words) {
            $breaks = (int) ($total_words / $max_words);
            for ($i = 1; $i < $breaks; $i++) {
              $index = $i * $max_words;
              $words[$index] .= PAGING_SEPARATOR;
            }
          }
          $body_parts = implode(' ', $words);
        }
        $pages = explode(PAGING_SEPARATOR, $body_parts);
      }
      foreach ($pages as $key => $page) {
        if (is_null($page) || $page == "") {
          unset($pages[$key]);
        }
      }
      $node->pages = array_values($pages);
      $node->page_count = count($node->pages);
      break;
    case 'view':
      if ($teaser && !$node->in_preview && strpos($node_teaser, '<!--paging_filter-->') !== FALSE) {

        // Check to see if the teaser is longer than our first page.
        if ($node->page_count > 1 && strlen($node->teaser) > strlen($node->pages[0])) {
          $node->content['body']['#value'] = check_markup($node->pages[0], $node->format, false);
          $node->pagemore = true;
        }
      }
      break;
    case 'alter':
      if (!$node->in_preview && strpos($teaser ? $node_teaser : $node_body, '<!--paging_filter-->') !== FALSE) {
        $element = 1;
        $page = isset($_GET['page']) ? $_GET['page'] : '';

        // Remove internal <!--paging_filter--> tag from final output.
        $node->teaser = str_replace('<!--paging_filter-->', '', $node_teaser);
        if (!$teaser && $node->page_count > 1 && arg(2) != 'print' && arg(2) != 'full' && $page != 'full' && arg(0) != 'book' && arg(1) != 'export') {
          global $pager_page_array, $pager_total;
          $pager_page_array = explode(',', $page);
          $pager_total[$element] = $node->page_count;
          $page = isset($pager_page_array[$element]) ? $pager_page_array[$element] : 0;
          $node->body = check_markup($node->pages[$page], $node->format, false);
          $position = variable_get('paging_pager_widget_position', 'below');
          $node->paging = theme('pager', NULL, 1, $element);
          if ($position == 'above' || $position == 'both') {
            $node->body = $node->paging . $node->body;
          }
          if ($position == 'below' || $position == 'both') {
            $node->body .= $node->paging;
          }

          // Remove internal <!--paging_filter--> tag from final output.
          $node->body = str_replace('<!--paging_filter-->', '', $node->body);
          $node->content['body']['#value'] = $node->body;
        }
      }
      break;
  }
}

/**
 * Split the page at appropriate paragraph/line ending.
 * This is a copy of node_teaser() from Drupal 6.
 */
function paging_paragraph_split($body, $format = NULL, $size = NULL) {
  if (!isset($size)) {
    $size = variable_get('teaser_length', 600);
  }

  // Find where the delimiter is in the body
  $delimiter = strpos($body, '<!--break-->');

  // If the size is zero, and there is no delimiter, the entire body is the teaser.
  if ($size == 0 && $delimiter === FALSE) {
    return $body;
  }

  // If a valid delimiter has been specified, use it to chop off the teaser.
  if ($delimiter !== FALSE) {
    return substr($body, 0, $delimiter);
  }

  // We check for the presence of the PHP evaluator filter in the current
  // format. If the body contains PHP code, we do not split it up to prevent
  // parse errors.
  if (isset($format)) {
    $filters = filter_list_format($format);
    if (isset($filters['php/0']) && strpos($body, '<?') !== FALSE) {
      return $body;
    }
  }

  // If we have a short body, the entire body is the teaser.
  if (drupal_strlen($body) <= $size) {
    return $body;
  }

  // If the delimiter has not been specified, try to split at paragraph or
  // sentence boundaries.
  // The teaser may not be longer than maximum length specified. Initial slice.
  $teaser = truncate_utf8($body, $size);

  // Store the actual length of the UTF8 string -- which might not be the same
  // as $size.
  $max_rpos = strlen($teaser);

  // How much to cut off the end of the teaser so that it doesn't end in the
  // middle of a paragraph, sentence, or word.
  // Initialize it to maximum in order to find the minimum.
  $min_rpos = $max_rpos;

  // Store the reverse of the teaser.  We use strpos on the reversed needle and
  // haystack for speed and convenience.
  $reversed = strrev($teaser);

  // Build an array of arrays of break points grouped by preference.
  $break_points = array();

  // A paragraph near the end of sliced teaser is most preferable.
  $break_points[] = array(
    '</p>' => 0,
  );

  // If no complete paragraph then treat line breaks as paragraphs.
  $line_breaks = array(
    '<br />' => 6,
    '<br>' => 4,
  );

  // Newline only indicates a line break if line break converter
  // filter is present.
  if (isset($filters['filter/1'])) {
    $line_breaks["\n"] = 1;
  }
  $break_points[] = $line_breaks;

  // If the first paragraph is too long, split at the end of a sentence.
  $break_points[] = array(
    '. ' => 1,
    '! ' => 1,
    '? ' => 1,
    '。' => 0,
    '؟ ' => 1,
  );

  // Iterate over the groups of break points until a break point is found.
  foreach ($break_points as $points) {

    // Look for each break point, starting at the end of the teaser.
    foreach ($points as $point => $offset) {

      // The teaser is already reversed, but the break point isn't.
      $rpos = strpos($reversed, strrev($point));
      if ($rpos !== FALSE) {
        $min_rpos = min($rpos + $offset, $min_rpos);
      }
    }

    // If a break point was found in this group, slice and return the teaser.
    if ($min_rpos !== $max_rpos) {

      // Don't slice with length 0.  Length must be <0 to slice from RHS.
      return $min_rpos === 0 ? $teaser : substr($teaser, 0, 0 - $min_rpos);
    }
  }

  // If a break point was not found, still return a teaser.
  return $teaser;
}

/**
 * Implementation of hook_link().
 */
function paging_link($type, $node = 0, $main = 0) {
  $links = array();
  if ($type == 'node') {
    if (array_key_exists('links', $node)) {
      $links = $node->links;
    }
    if ($main == 1 && $node->teaser && $node->pagemore && variable_get('paging_read_more_enabled', 0)) {
      $links['node_read_more'] = array(
        'title' => t('Read more'),
        'href' => drupal_get_path_alias("node/{$node->nid}"),
        'attributes' => array(
          'title' => t('Read the rest of this posting.'),
          'class' => 'read-more-paging',
        ),
        'query' => 'page=0,1',
      );
    }
  }
  return $links;
}

/**
 * Implementation of hook_filter().
 */
function paging_filter($in_op, $in_delta = 0, $in_format = -1, $in_text = '') {
  switch ($in_op) {
    case 'list':
      return array(
        t("Paging"),
      );
    case 'description':
      return t('Allows content to be broken up into pages, using the %separator tag, configurable <a href="!url">here</a>.', array(
        '%separator' => PAGING_SEPARATOR,
        '!url' => url('admin/settings/paging'),
      ));

    // the filter gets called before the nodeapi 'view' so,
    // add a comment to the body to inform the nodapi to apply the filter
    case 'process':
      return '<!--paging_filter-->' . $in_text;
    default:
      return $in_text;
  }
}

/**
 * Implementation of hook_filter_tips().
 */
function paging_filter_tips($in_delta, $in_format, $in_is_long = FALSE) {
  if ($in_is_long) {
    return '<h1>' . t('Paging Help') . '</h1>' . paging_help('admin/help#paging');
  }
  else {
    return t('Use %separator to create page breaks.', array(
      '%separator' => PAGING_SEPARATOR,
    ));
  }
}

Functions

Namesort descending Description
paging_filter Implementation of hook_filter().
paging_filter_tips Implementation of hook_filter_tips().
paging_help Implementation of hook_help().
paging_link Implementation of hook_link().
paging_menu Implementation of hook_menu().
paging_nodeapi Implementation of hook_nodeapi().
paging_paragraph_split Split the page at appropriate paragraph/line ending. This is a copy of node_teaser() from Drupal 6.
paging_settings Menu callback; display module settings form.
_paging_nodeapi Helper function for paging_nodeapi().

Constants

Namesort descending Description
PAGING_SEPARATOR