You are here

paging.module in Paging 7

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

Allows a node to be broken into multiple pages via a tag.

Original module written by Marco Scutari. Rewritten, considerably shortened & made more Drupal-friendly by Earl Miles. Yet again rewritten, extended, and maintained by Gurpartap Singh. Upgraded to Drupal 7 and maintained by Jen Lampton.

File

paging.module
View source
<?php

/**
 * @file
 * Allows a node to be broken into multiple pages via a tag.
 *
 * Original module written by Marco Scutari.
 * Rewritten, considerably shortened & made more Drupal-friendly by Earl Miles.
 * Yet again rewritten, extended, and maintained by Gurpartap Singh.
 * Upgraded to Drupal 7 and maintained by Jen Lampton.
 */

/**
 * Implements hook_help().
 */
function paging_help($path, $arg) {
  switch ($path) {

    // Main module help for the block module
    case 'admin/help#paging':
      return '<p>' . t('Break long pages into smaller ones by means of a page break tag (e.g. %separator):</p>
<pre>First page here.
%separator
Second page here.
%separator
More pages here.</pre>', array(
        '%separator' => variable_get('paging_separator', '<!--pagebreak-->'),
      )) . '<p>' . t('Automatic page breaking based on character or word count is also supported.') . '</p>';
  }
}

/**
 * Implements hook_menu().
 */
function paging_menu() {
  $items = array();
  $items['admin/config/content/paging'] = array(
    'title' => 'Paging',
    'description' => 'Enable or disable paging, configure separator string, toggle automatic paging and more for each content types.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'paging_settings',
    ),
    'access arguments' => array(
      'administer site configuration',
    ),
    'file' => 'paging.admin.inc',
  );
  return $items;
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function paging_form_node_type_form_alter(&$form, &$form_state, $form_id) {
  $type = $form['#node_type']->type;
  $form['paging'] = array(
    '#type' => 'fieldset',
    '#title' => t('Pagination Settings'),
    '#group' => 'additional_settings',
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
  );
  $form['paging']['paging_enabled'] = array(
    '#type' => 'checkbox',
    '#title' => 'Enable pagination',
    '#default_value' => variable_get('paging_enabled_' . $type, 0),
    '#attributes' => array(
      'class' => array(
        'paging-enabled',
      ),
    ),
  );

  // Get all valid fields.
  $fields = field_info_fields();
  $field_options = array();

  // Remove fields that are not on nodes.
  $valid_bundles = array(
    'node',
  );

  // TODO make this work for other entity types?
  // Remove fields that are not lontext, or longtext and sumamry from the list.
  $valid_fields = array(
    'text_long',
    'text_with_summary',
  );

  // @todo - remove fields with multiple values?
  foreach ($fields as $fieldname => $field) {
    $option = TRUE;
    if (!in_array($field['type'], $valid_fields)) {
      unset($fields[$fieldname]);
      $option = FALSE;
    }
    else {
      foreach ($valid_bundles as $bundle_name) {
        if (!array_key_exists($bundle_name, $field['bundles'])) {
          unset($fields[$fieldname]);
          $option = FALSE;
        }
      }
    }
    if ($option) {
      $field_options[$fieldname] = $fieldname;
    }
  }

  // @todo - replace this with formatter?
  $default_field = isset($field_options['body']) ? 'body' : reset(array_keys($field_options));
  $form['paging']['paging_field'] = array(
    '#type' => 'radios',
    '#title' => 'Select field to use for page breaks',
    '#options' => $field_options,
    '#default_value' => variable_get('paging_field_' . $type, $default_field),
    '#attributes' => array(
      'class' => array(
        'paging-enabled',
      ),
    ),
    '#states' => array(
      'visible' => array(
        // action to take.
        ':input[name="paging_enabled"]' => array(
          'checked' => TRUE,
        ),
      ),
    ),
  );

  // Optional automatic paging method.
  // Each option opens the corresponding character/word length select list.
  // Accompanied by paging.admin.js.
  // @TODO this will need an upgrade path. (now specifying keys for options)
  $form['paging']['paging_automatic_method'] = array(
    '#type' => 'radios',
    '#title' => t('Automatic paging'),
    '#options' => array(
      'disabled' => t('Disabled'),
      'chars' => t('Limit by characters <small>(recommended)</small>'),
      'words' => t('Limit by words'),
    ),
    '#required' => TRUE,
    '#description' => t('This setting is ignored when a paging separator like %pagebreak is found in the text.', array(
      '%pagebreak' => variable_get('paging_separator', '<!--pagebreak-->'),
    )),
    '#default_value' => variable_get('paging_automatic_method_' . $type, 'disabled'),
    '#attributes' => array(
      'class' => array(
        'paging-method',
      ),
    ),
    '#states' => array(
      'visible' => array(
        // action to take.
        ':input[name="paging_enabled"]' => array(
          'checked' => TRUE,
        ),
      ),
    ),
  );

  // Get the length options.
  // @TODO: Do we really need 750?
  $char_len_options = array(
    -1 => 750,
  ) + range(500, 7500, 500);
  asort($char_len_options);
  $char_len_options = drupal_map_assoc($char_len_options);

  // Automatic paging method. Select list to choose the number of characters per page.
  $form['paging']['paging_automatic_chars'] = array(
    '#type' => 'select',
    '#title' => t('Number of characters to display per page'),
    '#options' => $char_len_options,
    '#field_suffix' => t('characters'),
    '#required' => TRUE,
    '#default_value' => variable_get('paging_automatic_chars_' . $type, 4000),
    '#states' => array(
      'visible' => array(
        // action to take.
        ':input[name="paging_enabled"]' => array(
          'checked' => TRUE,
        ),
        ':input[name="paging_automatic_method"]' => array(
          'value' => 'chars',
        ),
      ),
    ),
  );

  // Automatic paging method. Text box to choose orphan size.
  $form['paging']['paging_automatic_chars_orphan'] = array(
    '#type' => 'textfield',
    '#title' => t('Minumum character length for final page'),
    '#size' => 6,
    '#field_suffix' => t('characters'),
    '#required' => TRUE,
    '#description' => t('If automatic paging causes the final page to have fewer characters, that text will be appended to the previous page.'),
    '#default_value' => variable_get('paging_automatic_chars_orphan_' . $type, 100),
    '#attached' => array(
      'css' => array(
        drupal_get_path('module', 'paging') . '/css/paging.admin.css',
      ),
    ),
    '#states' => array(
      'visible' => array(
        // action to take.
        ':input[name="paging_enabled"]' => array(
          'checked' => TRUE,
        ),
        ':input[name="paging_automatic_method"]' => array(
          'value' => 'chars',
        ),
      ),
    ),
  );

  // Automatic paging method. Select list to choose the number of words per page.
  $form['paging']['paging_automatic_words'] = array(
    '#type' => 'select',
    '#title' => t('Number of words to display per page'),
    '#options' => drupal_map_assoc(range(100, 1000, 50)),
    '#field_suffix' => t('words'),
    '#required' => TRUE,
    '#default_value' => variable_get('paging_automatic_words_' . $type, 400),
    '#states' => array(
      'visible' => array(
        // action to take.
        ':input[name="paging_enabled"]' => array(
          'checked' => TRUE,
        ),
        ':input[name="paging_automatic_method"]' => array(
          'value' => 'words',
        ),
      ),
    ),
  );

  // Automatic paging method. Text box to set orphan page size.
  $form['paging']['paging_automatic_words_orphan'] = array(
    '#type' => 'textfield',
    '#title' => t('Minumum word length for final page'),
    '#size' => 6,
    '#field_suffix' => t('words'),
    '#required' => TRUE,
    '#description' => t('If automatic paging causes the final page to have fewer words, that text will be appended to the previous page.'),
    '#default_value' => variable_get('paging_automatic_words_orphan_' . $type, 200),
    '#attached' => array(
      'css' => array(
        drupal_get_path('module', 'paging') . '/css/paging.admin.css',
      ),
    ),
    '#states' => array(
      'visible' => array(
        // action to take.
        ':input[name="paging_enabled"]' => array(
          'checked' => TRUE,
        ),
        ':input[name="paging_automatic_method"]' => array(
          'value' => 'words',
        ),
      ),
    ),
  );
}

/**
 * Implements hook_node_load().
 */
function paging_node_load($nodes, $types) {

  // We can use $types to figure out if we need to process any of these nodes.
  $our_types = array();
  foreach ($types as $type) {
    if (variable_get('paging_enabled_' . $type, FALSE)) {
      $our_types[] = $type;
    }
  }

  // Now $our_types contains all the types from $types that we want
  // to deal with. If it's empty, we can safely return.
  if (!count($our_types)) {
    return;
  }
  foreach ($nodes as $node) {

    // Load the separator for the content type (backwards compatability w 1.0)
    $paging_separator = variable_get('paging_separator_' . $node->type, FALSE);

    // Load the global separator.
    if (!$paging_separator) {
      $paging_separator = variable_get('paging_separator', '<!--pagebreak-->');
    }
    $field = variable_get('paging_field_' . $node->type, 0);
    if (!empty($node->{$field})) {
      foreach ($node->{$field} as $language => $item) {
        $body = $item[0]['value'];

        // Check if manual page separators were used.
        if (strpos($body, $paging_separator) !== FALSE) {
          $node->paging[$language]['pages'] = explode($paging_separator, $body);
          $node->paging[$language]['page_count'] = count($node->paging[$language]['pages']);
        }
        else {
          $body_parts = $body;

          // Automatic paging based on character count.
          if (variable_get('paging_automatic_method_' . $node->type, 0) == 'chars' && ($max_chars = variable_get('paging_automatic_chars_' . $node->type, 4000)) != 0) {
            $orphan_size = variable_get('paging_automatic_chars_orphan_' . $type, 100);
            $total_chars = drupal_strlen($body);

            // Check if pagination is possible.
            if ($total_chars > $max_chars) {
              $body = $body;
              $breaks = (int) ($total_chars / $max_chars);
              $bodypart = array();
              for ($i = 0; $i <= $breaks; $i++) {

                // Pick off the next body part.
                $bodypart[$i] = truncate_utf8($body, $max_chars, TRUE);

                // Now pull that off the body.
                $bodycount = drupal_strlen($bodypart[$i]);
                $body = drupal_substr($body, $bodycount);

                // Check for orphans.
                if (drupal_strlen($body) < $orphan_size) {
                  $bodypart[$i] .= $body;
                  break;
                }
              }
              $body_parts = implode($paging_separator, $bodypart);
            }
          }
          elseif (variable_get('paging_automatic_method_' . $node->type, 0) == 'words' && ($max_words = variable_get('paging_automatic_words_' . $node->type, 400)) != 0) {
            $orphan_size = variable_get('paging_automatic_words_orphan_' . $type, 100);
            $words = explode(' ', $body);
            $total_words = count($words);
            $words_remaining = $total_words - $max_words;

            // Check if pagination is possible.
            if ($total_words > $max_words) {
              $breaks = (int) ($total_words / $max_words);
              for ($i = 1; $i < $breaks; $i++) {
                $index = $i * $max_words;
                $words_remaining -= $max_words;

                // Orphan check.
                if ($words_remaining >= $orphan_size) {

                  // Not an orphan, treat normally.
                  $words[$index] .= $paging_separator;
                }
              }
            }
            $body_parts = implode(' ', $words);
          }
          $node->paging[$language]['pages'] = explode($paging_separator, $body_parts);
          $node->paging[$language]['page_count'] = count($node->paging[$language]['pages']);
        }
      }
    }
  }
}

/**
 * Implements hook_node_view().
 */
function paging_node_view($node, $view_mode, $langcode) {

  // If paging is enabled for this node type.
  if (variable_get('paging_enabled_' . $node->type, 0) == TRUE) {

    // Get the paging field name.
    $field = variable_get('paging_field_' . $node->type, 0);

    // Fall back to default language if translation doesn't exist.
    if (empty($node->{$field}[$langcode])) {
      $langcode = $node->language;
    }
    if (isset($node->paging[$langcode]['page_count'])) {

      // Get the field to use as the body.
      $body = paging_fetch_body($node, TRUE);

      // Get the summary version of the field to use as the body.
      $summary = paging_fetch_body_summary($node, TRUE);

      // Fetch a structured array containing page names.
      $node->paging[$langcode]['page_names'] = paging_fetch_names($body);

      // Check if view_mode is teaser.
      if ($view_mode != 'full') {

        // Check to see if the summary is longer than our first page.
        if ($node->paging[$langcode]['page_count'] > 1 && strlen($summary) > strlen($node->paging[$langcode]['pages'][0])) {
          $node->paging[$langcode]['pagemore'] = TRUE;
        }
      }

      // Set an element value for this pager.
      $element = 0;

      // Pull page from the URL query string.
      $page = isset($_GET['page']) ? $_GET['page'] : '';

      // Only do paging
      // a) if not in teaser view mode;
      // b) if there is more than one page;
      // c) if a printable version is not being requested; or
      // d) if a non-paged version is not being explicitly requested
      //    e.g. http://www.example.com/node/1?page=full or node/1/full.
      if ($view_mode == 'full' && $node->paging[$langcode]['page_count'] > 1 && arg(2) != 'print' && arg(2) != 'full' && $page != 'full') {
        pager_default_initialize($node->paging[$langcode]['page_count'], 1, $element);

        // Store the page in here, for safe keeping.
        $current_page = explode(',', $page);

        // Clean up page number for use later on.
        $page = $current_page[$element] != '' ? $current_page[$element] : 0;

        // Put the current page contents into the body.
        $lang = isset($node->{$field}[$node->language]) ? $node->language : LANGUAGE_NONE;
        $format = $node->{$field}[$lang][0]['format'];
        $node->content[$field][0]['#markup'] = check_markup($node->paging[$langcode]['pages'][$page], $format, FALSE);

        // Mapping the pages in $node->paging[$langcode]['page_names'] and $node->paging[$langcode]['page_count'] to set number of pages as the array length.
        $fake = array_fill(0, $node->paging[$langcode]['page_count'] - 1 + 1, '');
        $length = count($fake) > count($node->paging[$langcode]['page_names']) ? count($fake) : count($node->paging[$langcode]['page_names']);
        for ($i = 0; $i < $length; ++$i) {
          $merged[$i] = array_key_exists($i, $node->paging[$langcode]['page_names']) ? $node->paging[$langcode]['page_names'][$i] : '';
        }

        // Fill the empty names with node title and page number.
        $node->paging[$langcode]['page_names'] = _paging_populate_empty_names($merged, $node->title);

        // Generate the pager.
        $pager = theme('pager', array(
          'element' => $element,
        ));

        // Add pager to node content.
        $node->content['paging']['#markup'] = $pager;

        // Add the second pager if requested.
        $setting = variable_get('paging_pager_count', 'one');
        if ($setting == 'two') {
          $node->content['paging_above']['#markup'] = $pager;
        }
        if (variable_get('paging_name_title_' . $node->type, 0) && !empty($page)) {

          // Set the browser title to page's name.
          drupal_set_title(t($node->paging[$langcode]['page_names'][$page]));
        }
      }
    }
  }
}

/**
 * Implements hook_field_extra_fields().
 */
function paging_field_extra_fields() {
  $setting = variable_get('paging_pager_count', 'one');
  $extra = array();
  foreach (entity_get_info() as $entity_type => $entity_info) {
    foreach (array_keys($entity_info['bundles']) as $bundle) {
      $extra[$entity_type][$bundle]['display']['paging'] = array(
        'label' => t('Pager'),
        'description' => t('Pager for paging module.'),
        'weight' => 20,
      );
      if ($setting == 'two') {
        $extra[$entity_type][$bundle]['display']['paging_above'] = array(
          'label' => t('Pager (top)'),
          'description' => t('A second pager for paging module, useful when you want one at both the top and bottom.'),
          'weight' => -20,
        );
        $extra[$entity_type][$bundle]['display']['paging']['label'] = t('Pager (bottom)');
      }
    }
  }
  return $extra;
}

/**
 * Implements hook_wysiwyg_include_directory().
 */
function paging_wysiwyg_include_directory($type) {
  switch ($type) {
    case 'plugins':
      return 'wysiwyg';
  }
}

/**
 * Implements hook_block_info().
 */

// @TODO need an update hook for this.
function paging_block_info() {
  $blocks['paging_pager'] = array(
    'info' => t('Page Navigation (Paging)'),
    'cache' => DRUPAL_NO_CACHE,
  );
  return $blocks;
}

/**
 * Implements hook_block_view().
 */
function paging_block_view($delta = '') {

  // This example is adapted from node.module.
  $block = array();
  switch ($delta) {
    case 'paging_pager':
      $block['subject'] = t('Page navigation');
      $block['content'] = array(
        '#markup' => paging_build_names(),
      );
      break;
  }
  return $block;
}

/**
 * Returns a rendered list of page links.
 *
 * @param $nid
 *   Node ID to render page links for.
 *
 * @return
 *   An array of page names linked to the pages of the post.
 */
function paging_build_names($nid = NULL) {
  global $pager_page_array;
  global $language;

  // Load node ID form URL, if none was supplied.
  $nid = $nid ? $nid : arg(1);

  // Fetch a structured array containing page names.
  $names = paging_fetch_names($nid);

  // Load the node object to counting total number of expected pages.
  $node = node_load($nid);

  // Invoke 'load' operation in hook_nodeapi() implementation to calculate the actual number of pages in the node body.
  paging_node_load(array(
    $node,
  ), array(
    $node->type,
  ));

  // Comparing and mapping the number of pages in $names and $node->page_count.
  $fake = array_fill(0, ($node->paging[$language->language]['page_count'] - 1 < 1 ? 1 : $node->paging[$language->language]['page_count'] - 1) + 1, '');
  $length = count($fake) > count($names) ? count($fake) : count($names);
  $merged = array();
  for ($i = 0; $i < $length; ++$i) {
    $merged[$i] = array_key_exists($i, $names) ? $names[$i] : '';
  }

  // Fill the empty names with node title and page number.
  $names = _paging_populate_empty_names($merged, $node->title);
  $rendered_links = array();

  // Element value to distinguish between multiple pagers on one page.
  $element = 1;

  // Convert the names into links.
  foreach ($names as $key => $name) {
    $page_new = pager_load_array($key, $element, $pager_page_array);
    $rendered_links[] = theme('pager_link', array(
      'text' => $name,
      'page_new' => $page_new,
      'element' => $element,
    ));
  }
  return theme('item_list', array(
    'items' => $rendered_links,
  ));
}

/**
 * Return an array of page names for a node.
 *
 * @param $nid
 *   Either the nid of the node or the node object itself.
 *
 * @return
 *   An array of page names found in the node body.
 */
function paging_fetch_names($nid) {
  if (is_numeric($nid)) {
    $node = node_load($nid);
    $body = paging_fetch_body($node);
    preg_match("/<!--pagenames:(.*?)-->/", $body, $matches);
    if (count($matches) > 0) {
      return explode('||', $matches[1]);
    }
  }
  return array();
}

/**
 * Return the contents of the body that will be split by breaks.
 *
 * @param $node
 *   A fully loaded node object.
 * @param $safe
 *   Weather requesting the safe value or not.
 *
 * @return
 *   The complete text from the body, or main field of that node.
 */
function paging_fetch_body($node, $safe = FALSE) {
  global $language;
  $body = '';
  $field = variable_get('paging_field_' . $node->type, 0);
  if ($field && isset($node->{$field})) {
    if (!empty($node->{$field}[$language->language])) {
      $lang = $language->language;
    }
    else {
      $lang = $node->language;
    }

    // Some body fields appear not to have a 'safe_value'.
    if ($safe && !empty($node->{$field}[$lang][0]['safe_value'])) {
      $body = $node->{$field}[$lang][0]['safe_value'];
    }
    else {
      $body = $node->{$field}[$lang][0]['value'];
    }
  }
  return $body;
}

/**
 * Return the summary view of the body that will be split by breaks.
 *
 * @param $node
 *   A fully loaded node object.
 * @param $safe
 *   Weather requesting the safe value or not.
 *
 * @return
 *   The summary text from the body, or main field of that node.
 */
function paging_fetch_body_summary($node, $safe = FALSE) {
  $summary = '';
  $field = variable_get('paging_field_' . $node->type, 0);
  if ($field && isset($node->{$field}) && $node->{$field}) {
    $lang = isset($node->{$field}[$node->language]) ? $node->language : LANGUAGE_NONE;

    // Some body fields appear not to have a 'safe_summary'.
    if ($safe && !empty($node->{$field}[$lang][0]['safe_summary'])) {
      $body = $node->{$field}[$lang][0]['safe_summary'];
    }
    else {
      $summary = $node->{$field}[$lang][0]['summary'];
    }
  }
  return $summary;
}

/**
 * Helper function to populate empty page names.
 *
 * @param $names
 *   An array of page names found in the node body.
 * @param $title
 *   The title of the node.
 *
 * @return
 *   A complete array of page names, even if not provided.
 */
function _paging_populate_empty_names($names, $title) {
  foreach ($names as $key => $name) {
    trim($names[$key]);
    if (empty($names[$key])) {
      $names[$key] = $title . ' - Page ' . ($key + 1);
    }
  }
  return $names;
}

/**
 * Implements hook_field_formatter_info().
 */
function paging_field_formatter_info() {
  return array(
    'paging_paged' => array(
      'label' => t('Paginated'),
      'field types' => array(
        'text_long',
        'text_with_summary',
      ),
      'settings' => array(),
    ),
  );
}

/**
 * Implements hook_field_formatter_view().
 */
function paging_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  $element = array();
  switch ($display['type']) {
    case 'paging_paged':
      foreach ($items as $delta => $item) {
        $markup = _paging_split_content($item, $entity, $langcode, $delta);
        $element[$delta] = array(
          '#data' => $item['value'],
          '#markup' => $markup,
        );
      }
      break;
  }
  return $element;
}

/**
 * Function splits field value into chunks for pagination.
 */
function _paging_split_content($item, $node, $lang, $delta) {

  // If paging is enabled for this node type.
  if (variable_get('paging_enabled_' . $node->type, 0) == TRUE && isset($node->paging[$lang]['page_count'])) {

    // Get the paging field name.
    $field = variable_get('paging_field_' . $node->type, 0);

    // Set an element value for this pager.
    $element = 0;

    // Pull page from the URL query string.
    $page = isset($_GET['page']) ? $_GET['page'] : '';

    // Put the current page contents into the body.
    $format = $node->{$field}[$lang][$delta]['format'];
    if ($node->paging[$lang]['page_count'] > 1 && arg(2) != 'print' && arg(2) != 'full' && $page != 'full') {

      // Store the page in here, for safe keeping.
      $current_page = explode(',', $page);

      // Clean up page number for use later on.
      $page = $current_page[$element] != '' ? $current_page[$element] : 0;
      $output = check_markup($node->paging[$lang]['pages'][$page], $format, FALSE);
    }
    elseif ($node->paging[$lang]['page_count'] == 1 || arg(2) != 'full' || $page != 'full') {
      $value = isset($item['safe_value']) ? $item['safe_value'] : $item['value'];
      $output = check_markup($value, $format, FALSE);
    }
    return $output;
  }
}

Functions

Namesort descending Description
paging_block_info
paging_block_view Implements hook_block_view().
paging_build_names Returns a rendered list of page links.
paging_fetch_body Return the contents of the body that will be split by breaks.
paging_fetch_body_summary Return the summary view of the body that will be split by breaks.
paging_fetch_names Return an array of page names for a node.
paging_field_extra_fields Implements hook_field_extra_fields().
paging_field_formatter_info Implements hook_field_formatter_info().
paging_field_formatter_view Implements hook_field_formatter_view().
paging_form_node_type_form_alter Implements hook_form_FORM_ID_alter().
paging_help Implements hook_help().
paging_menu Implements hook_menu().
paging_node_load Implements hook_node_load().
paging_node_view Implements hook_node_view().
paging_wysiwyg_include_directory Implements hook_wysiwyg_include_directory().
_paging_populate_empty_names Helper function to populate empty page names.
_paging_split_content Function splits field value into chunks for pagination.