'admin/build/custom_pagers',
'title' => t('Custom pagers'),
'description' => t('Add custom pagers for content types.'),
'callback' => 'custom_pagers_page',
'access' => $access
);
$items[] = array(
'path' => 'admin/build/custom_pagers/add',
'title' => t('Add custom pager'),
'type' => MENU_CALLBACK,
'callback' => 'drupal_get_form',
'callback arguments' => array('custom_pagers_form'),
'access' => $access
);
$items[] = array(
'path' => 'admin/build/custom_pagers/edit',
'title' => t('Edit custom pager'),
'type' => MENU_CALLBACK,
'callback' => 'drupal_get_form',
'callback arguments' => array('custom_pagers_form'),
'access' => $access
);
}
return $items;
}
function custom_pagers_perm() {
return array('administer custom pagers', 'use php in custom pagers');
}
function custom_pagers_nodeapi(&$node, $op, $teaser, $page) {
switch ($op) {
case 'view':
// We want to make sure we don't try to output when print.module is active.
// It's a bit of special casing but it doesn't do much harm.
if ($teaser == false && !$node->printing) {
$pagers = _custom_pagers_load_all_pagers();
foreach ($pagers as $pager) {
if ($pager->position != 'block' && _custom_pagers_visibility($pager, $node)) {
$nav_array = custom_pager_build_nav($pager, $node);
if ($nav_array['current_index'] != -1) {
$output = theme('custom_pager', $nav_array, $node, $pager);
switch ($pager->position) {
case 'top':
$node->content['custom_pager_top'][$pager->pid] = array('#value' => $output);
break;
case 'bottom':
$node->content['custom_pager_bottom'][$pager->pid] = array('#value' => $output);
break;
case 'both':
$node->content['custom_pager_top'][$pager->pid] = array('#value' => $output);
$node->content['custom_pager_bottom'][$pager->pid] = array('#value' => $output);
break;
}
}
}
}
if (isset($node->content['custom_pager_top'])) {
$node->content['custom_pager_top']['#weight'] = -100;
}
if (isset($node->content['custom_pager_bottom'])) {
$node->content['custom_pager_bottom']['#weight'] = 100;
}
}
break;
case 'update':
case 'insert':
case 'delete':
// If a user makes any changes to a node, we want to make sure that
// their pager cache is cleared. It's ugly, but it should prevent some
// of the nastier cache-went-stale issues.
unset($_SESSION['custom_pagers']);
break;
}
}
/**
* Implementation of hook_block().
*
* Generates a block with a pager for the current node.
*/
function custom_pagers_block($op = 'list', $delta = 0) {
if ($op == 'list') {
$pagers = _custom_pagers_load_all_pagers();
foreach ($pagers as $pager) {
if ($pager->position == 'block') {
$blocks[$pager->pid]['info'] = $pager->title;
}
}
return $blocks;
}
else if ($op == 'view' && arg(0) == 'node' && is_numeric(arg(1))) {
$node = node_load(arg(1));
$pagers = _custom_pagers_load_all_pagers();
if ($pager = $pagers[$delta]) {
if ($pager->position == 'block' && _custom_pagers_visibility($pager, $node)) {
$nav_array = custom_pager_build_nav($pager, $node);
if ($nav_array['current_index'] != -1) {
if (module_exists('token')) {
$block['subject'] = token_replace($pager->title, 'node', $node);
}
else {
$block['subject'] = $pager->title;
}
$block['content'] = theme('custom_pager', $nav_array, $node, $pager);
return $block;
}
}
}
}
}
// Lists all current custom pagers, and provides a link to the edit page.
function custom_pagers_page() {
$pagers = _custom_pagers_load_all_pagers(TRUE);
$header = array(t('Title'), t('Node list'), t('Visibility'), '');
$rows = array();
foreach ($pagers as $pager) {
$row = array();
$row[] = $pager->title;
$row[] = !empty($pager->list_php) ? t('PHP snippet') : $pager->view . t(' view');
$row[] = !empty($pager->visibility_php) ? t('PHP snippet') : $pager->node_type . t(' nodes');
$row[] = l(t('edit'), 'admin/build/custom_pagers/edit/' . $pager->pid);
$rows[] = $row;
}
if (count($rows) == 0) {
$rows[] = array(array('data' => t('No custom pagers have been defined.'), 'colspan' => 3));
}
$rows[] = array(array('data' => l(t('Add a new custom pager'), 'admin/build/custom_pagers/add'), 'colspan' => 2));
return theme('table', $header, $rows);
}
// Displays an edit form for a custom pager record.
function custom_pagers_form() {
$pid = arg(4);
if (isset($pid)) {
$pager = _custom_pagers_load_pager($pid);
$form['pid'] = array(
'#type' => 'hidden',
'#value' => $pid,
);
}
$form['title'] = array(
'#type' => 'textfield',
'#title' => t('Title'),
'#required' => TRUE,
'#default_value' => $pid ? $pager->title : '',
);
$form['position'] = array(
'#type' => 'select',
'#title' => t('Pager position'),
'#required' => TRUE,
'#options' => array(
'top' => t("Above the node's body"),
'bottom' => t("Below the node's body"),
'both' => t("Both above and below the node's body"),
'block' => t("In a sidebar block"),
),
'#description' => t('The node type(s) this custom pager will apply to.'),
'#default_value' => $pid ? $pager->position : NULL,
);
$form['visibility'] = array(
'#type' => 'fieldset',
'#collapsible' => FALSE,
'#collapsed' => FALSE,
'#title' => t('Pager visibility'),
'#description' => t('Determine what nodes this pager should be displayed on.'),
);
// Warn the user they're about to override custom code.
if ((!empty($pager->visibility_php)) && !user_access('use php in custom pagers')){
$form['visibility']['php'] = array(
'#type' => 'item',
'#title' => t('Note'),
'#value' => t('This pager uses custom PHP snippets to control visibility. You do not have permission to use PHP snippets, and the changes you make to this field will be ignored.'),
);
}
$form['visibility']['node_type'] = array(
'#type' => 'select',
'#title' => t('By node type'),
'#required' => TRUE,
'#multiple' => TRUE,
'#options' => node_get_types('names'),
'#description' => t('If the PHP field is filled in, this field will be ignored.'),
'#default_value' => $pid ? explode(',', $pager->node_type) : NULL,
);
$form['visibility']['visibility_php'] = array(
'#type' => 'textarea',
'#title' => t('By PHP snippet'),
'#access' => user_access('use php in custom pagers'),
'#description' => t('Use a snippet of PHP to return TRUE or FALSE. Note that this code has access to the $node variable, and can check its type or any other property. If this field is filled out, the By node type field will be ignored.'),
'#default_value' => $pid ? $pager->visibility_php : '',
);
$form['node_list'] = array(
'#type' => 'fieldset',
'#collapsible' => FALSE,
'#collapsed' => FALSE,
'#title' => t('Pager node list'),
'#description' => t('Determine how the list of nodes for this pager should be generated.'),
);
// Warn the user they're about to override custom code, or if Views
// is missing and PHP permissions haven't been granted.
if (!user_access('use php in custom pagers')) {
if (!empty($pager->list_php)) {
$form['node_list']['note'] = array(
'#type' => 'item',
'#title' => t('Note'),
'#value' => t('This pager uses custom PHP snippets to generate a list of nodes. You do not have permission to use PHP snippets, and the changes you make to this field will be ignored.'),
);
}
elseif (!module_exists('views')){
$form['node_list']['warning'] = array(
'#type' => 'item',
'#title' => t('Warning'),
'#value' => t('The Views module is not installed, and you do not have permission to use PHP snippets to configure the node. You can save this custom pager, but it will not appear until the node list is properly configured.'),
);
}
}
$form['node_list']['list_php'] = array(
'#type' => 'textarea',
'#title' => t('Use PHP snippet'),
'#access' => user_access('use php in custom pagers'),
'#default_value' => $pid ? $pager->list_php : '',
'#description' => t('Use a snippet of PHP to populate the pager. The snippet should return an array of node ids in the order they should be browsed. If this field is filled out, the Use a view and View arguments fields will be ignored.'),
);
if (module_exists('views')) {
$options = array();
include_once(drupal_get_path('module', 'views') . '/views_cache.inc');
$default_views = _views_get_default_views();
$res = db_query("SELECT name FROM {view_view} ORDER BY name");
while ($view = db_fetch_object($res)) {
$options[$view->name] = $view->name;
}
if(is_array($default_views)) {
foreach($default_views as $key => $view) {
$options[$key] = $view->name;
}
}
$form['node_list']['view'] = array(
'#type' => 'select',
'#title' => t('Use a view'),
'#required' => TRUE,
'#options' => $options,
'#description' => t('A view used to populate the pager. The nodes will appear in the pager in the same order they are displayed in the view. If the PHP field is populated, this will be ignored.'),
'#default_value' => $pid ? $pager->view : NULL
);
$form['node_list']['args'] = array(
'#type' => 'textarea',
'#title' => t('View arguments'),
'#required' => FALSE,
'#description' => t('A return-delimited list of arguments to pass into the selected view. If Token.module is enabled, placeholder tokens like [type] and [author] can be used.'),
'#default_value' => $pid ? $pager->args : NULL
);
$form['help'] = array(
'#type' => 'fieldset',
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#title' => t('Placeholder tokens'),
'#description' => t("The following placeholder tokens can be used when passing arguments into the view. Each will be replaced with the correct values at runtime."),
);
if (module_exists('token')) {
$form['help']['tokens'] = array(
'#value' => theme('token_help', 'node'),
);
}
else {
$form['help']['#description'] = t("To use dynamic placeholder tokens in your pager arguments (the ID of the current node or currently logged in user, for example), download and install the Token module from Drupal.org.", array('@token' => 'http://www.drupal.org/project/token'));
$form['help']['#collapsible'] = FALSE;
$form['help']['#collapsed'] = FALSE;
}
}
$form['node_list']['reverse_list'] = array(
'#type' => 'checkbox',
'#title' => t('Reverse the list of nodes'),
'#return_value' => 1,
'#description' => t("The natural list view ordering for an archive is the opposite of the natural 'previous/next' order for a pager. As such, reversing the pager list is useful when using a single view for paging and other sorted lists (pages, blocks, etc)."),
'#default_value' => $pid ? $pager->reverse_list : NULL
);
$form['node_list']['cache_list'] = array(
'#type' => 'checkbox',
'#title' => t('Cache the list of nodes'),
'#return_value' => 1,
'#default_value' => $pid ? $pager->cache_list : NULL
);
$form['buttons']['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
if ($pid) {
$form['buttons']['delete'] = array(
'#type' => 'submit',
'#value' => t('Delete'),
);
}
return $form;
}
function custom_pagers_form_submit($form_id, $form_values) {
if ($form_values['op'] == t('Delete')) {
_custom_pagers_delete_pager($form_values['pid']);
}
else {
$pager = (object)$form_values;
$pager->node_type = implode(',', $pager->node_type);
_custom_pagers_save_pager($pager);
}
return 'admin/build/custom_pagers';
}
function _custom_pagers_load_pager($pid) {
$sql = 'SELECT * FROM {custom_pager} WHERE pid = %d';
$result = db_query($sql, $pid);
$pager = db_fetch_object($result);
return $pager;
}
function _custom_pagers_load_all_pagers($refresh = FALSE) {
static $pagers;
if ($refresh || !isset($pagers)) {
$sql = 'SELECT * FROM {custom_pager}';
$result = db_query($sql);
$pagers = array();
while($pager = db_fetch_object($result)) {
$pagers[$pager->pid] = $pager;
}
}
return $pagers;
}
function _custom_pagers_save_pager($pager = NULL) {
if (isset($pager->pid)) {
$sql = "UPDATE {custom_pager} SET";
$sql .= " title = '%s', view = '%s', args = '%s', list_php = '%s', visibility_php = '%s', node_type = '%s', position = '%s', reverse_list = %d, cache_list = %d";
$sql .= " WHERE pid = %d";
db_query($sql, $pager->title, $pager->view, $pager->args, $pager->list_php, $pager->visibility_php, $pager->node_type, $pager->position, $pager->reverse_list, $pager->cache_list, $pager->pid);
}
else {
$sql = "INSERT INTO {custom_pager}";
$sql .= " (title, view, args, position, list_php, visibility_php, node_type, reverse_list, cache_list)";
$sql .= " VALUES ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s')";
db_query($sql, $pager->title, $pager->view, $pager->args, $pager->position, $pager->list_php, $pager->visibility_php, $pager->node_type, $pager->reverse_list, $pager->cache_list);
}
cache_clear_all('custom_pagers_', 'cache', TRUE);
}
function _custom_pagers_delete_pager($pid) {
$sql = 'DELETE FROM {custom_pager} WHERE pid = %d';
db_query($sql, $pid);
}
function _custom_pagers_visibility($pager, $node) {
$visible = FALSE;
if (!empty($pager->visibility_php)) {
// Use PHP code to generate the list.
ob_start();
$result = eval(trim($pager->visibility_php));
$visible = ($result == TRUE);
ob_end_clean();
} elseif (!empty($pager->node_type)) {
$visible = (strpos($pager->node_type, $node->type) !== FALSE);
}
return $visible;
}
function custom_pager_build_nav($pager, $node) {
static $pager_cache;
$list = array();
// First we check the static function cache for this pager.
// If it's already been built for this page-load, we'll use it.
if (isset($pager_cache[$pager->pid])) {
$list = explode(',', $pager_cache[$pager->pid]);
}
// If it doesn't give us a list, and the pager is set to cache its
// data, we'll try to load from the current user's session. We do it
// that way rather than via cache_set() because of the potential for
// node_access violations. Each user's node list *could* be different.
if (empty($list) && $pager->cache_list) {
if ($cache = $_SESSION['custom_pagers'][$pager->pid]) {
// We should probably set the pager cache lifetime to a configurable
// value. If any nodes drop through the cracks, users won't see the
// pager when they visit them. Five minutes should keep the pager from
// thrashing. In the future, we'll want to develop a better strategy
// for this.
if ($cache['timestamp'] < (time() - 300)) {
unset($_SESSION['custom_pagers'][$pager->pid]);
}
else {
$list = explode(',', $_SESSION['custom_pagers'][$pager->pid]['data']);
}
}
}
// If $list is still empty, neither caching strategy produced a list.
// Let's build it from scratch!
if (empty($list)) {
// If the pager uses PHP, execute the PHP and run with the list.
// Otherwise, use a view to get a list of node ids.
if (!empty($pager->list_php)) {
// Use PHP code to generate the list.
ob_start();
$result = eval(trim($pager->list_php));
if (is_array($result)) {
$list = $result;
}
ob_end_clean();
}
elseif (module_exists('views')) {
// Use a view to generate the list.
$args = explode("\n", $pager->args);
if (module_exists('token')) {
$args = token_replace($args, 'node', $node);
}
$view = views_get_view($pager->view);
$tmp = views_build_view('items', $view, $args);
if (count($tmp['items'])) {
foreach($tmp['items'] as $item) {
$list[] = $item->nid;
}
}
}
if ($pager->reverse_list) {
$list = array_reverse($list);
}
}
// If we get to this point, we want to cache what we've made.
if ($pager->cache_list) {
$_SESSION['custom_pagers'][$pager->pid]['data'] = implode(',', $list);
$_SESSION['custom_pagers'][$pager->pid]['timestamp'] = time();
}
$pager_cache[$pager->pid] = $list;
return pager_entries_by_val($node->nid, $list);
}
// Helper functions to pull proper entries from a list of nids.
function pager_entries_by_val($value, $list) {
$list = array_values($list);
foreach ($list as $k => $v) {
if ($v == $value) {
$key = $k;
}
}
if (!isset($key)) {
$key = -1;
}
return pager_entries_by_key($key, $list);
}
function pager_entries_by_key($key, $list, $increment = 1) {
$list = array_values($list);
$nav = array(
'first' => $list[0],
'prev' => $list[max($key - $increment, 0)],
'next' => $list[min($key + $increment, (count($list) - 1))],
'last' => $list[count($list) - 1],
'full_list' => $list
);
foreach($nav as $k => $v) {
if ($nav[$k] == $list[$key]) {
$nav[$k] = NULL;
}
}
$nav['current_index'] = $key;
return $nav;
}
function theme_custom_pager($nav_array, $node, $pager) {
drupal_add_css(drupal_get_path('module', 'custom_pagers') .'/custom_pagers.css');
$links = array();
$links['custom_pager_prev'] = array(
'title' => t('‹ previous'),
'href' => !empty($nav_array['prev']) ? 'node/'. $nav_array['prev'] : NULL,
);
$links['custom_pager_index'] = array(
'title' => t('@count of @count_total', array('@count' => ($nav_array['current_index'] + 1), '@count_total' => count($nav_array['full_list']))),
);
$links['custom_pager_next'] = array(
'title' => t('next ›'),
'href' => !empty($nav_array['next']) ? 'node/'. $nav_array['next'] : NULL,
);
return theme('links', $links, array('class' => "custom-pager custom-pager-$pager->pid custom-pager-$node->type"));
}