paging.module in Paging 7
Same filename and directory in other branches
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.moduleView 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;
}
}