Clean URLs for nodes paginated using Paging module

A limitation of Drupal's pager system, that comes as a disappointment to some SEO "freaks" Tongue is the separate "page" query in the URLs to browse multiple pages. Say, a third page on a paginated node would have node/1?page=0,2 in the URL. Instead here's a method to accomplish a better looking version (like node/1/page/3). "Paging Sweet Urls" is a module that converts the "page/X" part in the URL into the actual "page" query that the pager system understands. We also need to override theme_pager_links() to set proper hyperlinks in pager navigation. Still this implementation has a limitation that it cannot handle the functionality with aliased URLs (see below). I believe, it'll need a mod_rewrite apache directive to be able to handle URL aliases. But I'll take up with it some other time.

Also, there's Clean Pagination module providing similar functionality, but not for node pages. It can, however, handle views, etc. URLs. Find the code and instructions ahead.


Download "Paging Sweet Urls" module

Contents of paging_sweet_urls.info:

name = Paging Sweet Urls
description = Clean URLs for paging module.
core = 6.x

Contents of paging_sweet_urls.module:

<?php
 
/**
 * Implementation of hook_init().
 */
function paging_sweet_urls_init() {
  $args = arg();
  // We use array_pop() instead of arg(x), because the url query can be
  // node/X/page/2 or node/X/view/page/2 (Default local task URL for nodes).
  $page_no = array_pop($args) - 1;
  $page = array_pop($args);
  // Check if we are on the node/X or node/X/page/X path or node/X/view/page/X paths.
  if ($args[0] == 'node' && is_numeric($args[1]) && ($args[2] != 'view' || $args[2] != 'page') && $page == 'page') {
    $_GET['q'] = implode('/', $args);
    $pages = explode(',', $_GET['page']);
    $pages[0] = !empty($pages[0]) ? $pages[0] : 0;
    $pages[1] = $page_no < 0 ? 0 : $page_no;
    $_GET['page'] = implode(',', $pages);
  }
}

Append this function to the theme's template.php file.

function phptemplate_pager_link($text, $page_new, $element, $parameters = array(), $attributes = array()) {
  $args = arg();
  $page_no = array_pop($args);
  $page = array_pop($args);
  // Check if we are not on the node/X or node/X/page/X path or node/X/view/page/X paths.
  // Still using arg(x) for some checks to avoid popped $args for certain conditions.
  if ((arg(0) != 'node' || !is_numeric(arg(1))) && (arg(2) != 'view' || (!empty($page) && $page != 'page')) && $element != 1) {
    // Don't mingle when the required module is absent.
    if (!module_exists('paging_sweet_urls')) {
      return theme_pager_link($text, $page_new, $element, $parameters, $attributes);
    }
  }
 
  $page = isset($_GET['page']) ? $_GET['page'] : '';
  if ($new_page = implode(',', pager_load_array($page_new[$element], $element, explode(',', $page)))) {
    // $parameters['page'] = $new_page;
  }
 
  $query = array();
  if (count($parameters)) {
    $query[] = drupal_query_string_encode($parameters, array());
  }
  $querystring = pager_get_querystring();
  if ($querystring != '') {
    $query[] = $querystring;
  }
 
  // Set each pager link title
  if (!isset($attributes['title'])) {
    static $titles = NULL;
    if (!isset($titles)) {
      $titles = array(
        t('« first') => t('Go to first page'),
        t('‹ previous') => t('Go to previous page'),
        t('next ›') => t('Go to next page'),
        t('last »') => t('Go to last page'),
      );
    }
    if (isset($titles[$text])) {
      $attributes['title'] = $titles[$text];
    }
    else if (is_numeric($text)) {
      $attributes['title'] = t('Go to page @number', array('@number' => $text));
    }
  }
 
  $nid = arg(1);
  $page_no = $page_new[1] + 1;
 
  return l($text, "node/$nid/page/$page_no", array('attributes' => $attributes, 'query' => count($query) ? implode('&', $query) : NULL));
}

Download

27 Comments

improvement

you can override the theme function in the module, no need to modify the theme to 'complete' the module's work.

http://www.lullabot.com/articles/overriding-theme-functions-in-modules

Good idea but I believe

Good idea but I believe this wouldn't let the theme override the same function. I could even package the phptemplate_pager_link() function in the module itself, but that won't be the best.

Above all this, there are a very few cases when one would need to override that theme function. So, we can assume it safe to package the override in the module itself by either of the method.

Drupal 5 version

Hi Gurpartap,
thanks for the nice module. Since I'm still using Drupal 5, I changed some lines of your module code to make it work there.
Therefore, I only had to include a copy of the drupal 6 version of arg() and to change the last line of your template function.

Unfortunately, I couldn't attach the files to my post – so here are the changes in clear text:

/**
 * Implementation of hook_init().
 */
function paging_sweet_urls_init() {
  //CHANGED for drupal 5
  $args = arg_d6();
  // We use array_pop() instead of arg(x), because the url query can be
  // node/X/page/2 or node/X/view/page/2 (Default local task URL for nodes).
  $page_no = array_pop($args) - 1;
  $page = array_pop($args);
  // Check if we are on the node/X or node/X/page/X path or node/X/view/page/X paths.
  if ($args[0] == 'node' && is_numeric($args[1]) && ($args[2] != 'view' || $args[2] != 'page') && $page == 'page') {
    $_GET['q'] = implode('/', $args);
    $pages = explode(',', $_GET['page']);
    $pages[0] = !empty($pages[0]) ? $pages[0] : 0;
    $pages[1] = $page_no < 0 ? 0 : $page_no;
    $_GET['page'] = implode(',', $pages);
  }
}
 
 
/**
* Copy of the arg() function of drupal 6. The arg() function in drupal 5 requires an argument, this one not.
*/
function arg_d6($index = NULL, $path = NULL) {
  static $arguments;
 
  if (!isset($path)) {
    $path = $_GET['q'];
  }
  if (!isset($arguments[$path])) {
    $arguments[$path] = explode('/', $path);
  }
  if (!isset($index)) {
    return $arguments[$path];
  }
  if (isset($arguments[$path][$index])) {
    return $arguments[$path][$index];
  }
}
 
 
function phptemplate_pager_link($text, $page_new, $element, $parameters = array(), $attributes = array()) {
  // CHANGED for Drupal 5
  $args = arg_d6();
  $page_no = array_pop($args);
  $page = array_pop($args);
  // Check if we are not on the node/X or node/X/page/X path or node/X/view/page/X paths.
  // Still using arg(x) for some checks to avoid popped $args for certain conditions.
  if ((arg(0) != 'node' || !is_numeric(arg(1))) && (arg(2) != 'view' || (!empty($page) && $page != 'page')) && $element != 1) {
    // Don't mingle when the required module is absent.
    if (!module_exists('paging_sweet_urls')) {
      return theme_pager_link($text, $page_new, $element, $parameters, $attributes);
    }
  }
 
  $page = isset($_GET['page']) ? $_GET['page'] : '';
  if ($new_page = implode(',', pager_load_array($page_new[$element], $element, explode(',', $page)))) {
    // $parameters['page'] = $new_page;
  }
 
  $query = array();
  if (count($parameters)) {
    $query[] = drupal_query_string_encode($parameters, array());
  }
  $querystring = pager_get_querystring();
  if ($querystring != '') {
    $query[] = $querystring;
  }
 
  // Set each pager link title
  if (!isset($attributes['title'])) {
    static $titles = NULL;
    if (!isset($titles)) {
      $titles = array(
        t('« first') => t('Go to first page'),
        t('‹ previous') => t('Go to previous page'),
        t('next ›') => t('Go to next page'),
        t('last »') => t('Go to last page'),
      );
    }
    if (isset($titles[$text])) {
      $attributes['title'] = $titles[$text];
    }
    else if (is_numeric($text)) {
      $attributes['title'] = t('Go to page @number', array('@number' => $text));
    }
  }
 
  $nid = arg(1);
  $page_no = $page_new[1] + 1;
 
  // CHANGED for drupal 5
  return l($text, "node/$nid/page/$page_no", $attributes, count($query) ? implode('&', $query) : NULL);
}

Thanks and best wishes,
Oli

small fix for template function

Hi,
me again. I faced an issue on admin page that uses paging (e.g. 'admin/content/node'). In case the module exists AND arg(0) != 'node', the function continues happily.
The fix:
Always return with the standard theme_pager_link() if arg != 'node' etc. So the function should check for this no matter if the module 'paging_sweet_urls' exists or not.

function phptemplate_pager_link($text, $page_new, $element, $parameters = array(), $attributes = array()) {
  $page_no = array_pop($args);
  $page = array_pop($args);
  // Check if we are not on the node/X or node/X/page/X path or node/X/view/page/X paths.
  // Still using arg(x) for some checks to avoid popped $args for certain conditions.
	// Don't mingle when the required module is absent.
  if (!module_exists('paging_sweet_urls')) {
    return theme_pager_link($text, $page_new, $element, $parameters, $attributes);
  }
  if ((arg(0) != 'node' || !is_numeric(arg(1))) && (arg(2) != 'view' || (!empty($page) && $page != 'page')) && $element != 1) {
    return theme_pager_link($text, $page_new, $element, $parameters, $attributes);
  }
  ...

Thanks for your

Thanks for your contribution. I've added this module along with paging module but it seems like the code snippet added to template.php file makes other pager not function properly. For example in the url aliases admin page, i have 3 pages but each time i click to go to 2nd page it gives me a page not found whereas it was working before. Seems like it breaks paging used on other section of the site. This was tested on Drupal 6.9

Thanks,

Interesting

The filter logic there should not allow it to happen on that page, however, I shall test it out. Thanks for reporting!

Thanks for this fix, it

Thanks for this fix, it definitely cleans up the URL. What's the best way to tweak the line below so that we display the PATHAUTO URL instead of the raw NODE url?

I'd prefer to keep using PATHAUTO URL for consistency. Thanks!

return l($text, "node/$nid/page/$page_no", array('attributes' => $attributes, 'query' => count($query) ?

Actually not this way

Handling URL aliases for this is quite tricky and may require the use of Apache directives in the .htaccess files, as Drupal in no way is aware of the aliased path (because it is converted into system path upon bootstrap).

If time permits, I'll work on it.

Will look into this

I have a Lyrics site that is all php templates. been thing about moving over to drupal and have Drupal manage all the content. This pagination module will definately help as my site must be SEO friendly Smile

Will look into this

I have a lyrics site that is currently using php templates. I want to move over to drupal to manage the CMS. This paging module is important as the site must be url friendly.

paging

how ro create paging functionality without node value directlu created function and without query

Can't get D5 version to work

Hiya!
I tried the above change(s) to the D6 module to work with D5 and all I got was a page full of php errors upon module install conformation.
Amy suggestions?
Thanks!
-Brian

Hey! Must say a nice module,

Hey!

Must say a nice module, thanks a lot for this. I can use it on few of my sites but most of my sites have clean URLs Sad

error on index

Your module seems to have problems with paging on the index of a drupal site e.g. if you habe 20 posts on www.domain.tld and want paging to break after 10 posts the modules generates garbage.

If $nid is NULL in "phptemplate_pager_link" you get

http://www.domain.tld/node/%252Fpage/1

it would be very handy to get

http://www.domain.tld/page/1

Any suggestions?

regards!

I must say great website. I

I must say great website. I have just googled it nice info out there.

Drupal 5 Alias Fix

We recently implemented this on a site but did however discover that the aliases were needed. So we went ahead and appended some code to the Drupal 5 version to get the aliases working. Figured it would only be fair to share the code as it might be useful to others as well as potentially useful for getting the aliases to work under Drupal 6 if they don't already.

The modified paging_sweet_urls.module file:

/**
 * Implementation of hook_init().
 */
function paging_sweet_urls_init() {
  //CHANGED for drupal 5
  $args = arg_d6();
  // We use array_pop() instead of arg(x), because the url query can be
  // node/X/page/2 or node/X/view/page/2 (Default local task URL for nodes).
  $page_no = array_pop($args) - 1;
  $page = array_pop($args);
  // Check if we are on the node/X or node/X/page/X path or node/X/view/page/X paths.
  if ($args[0] == 'node' && is_numeric($args[1]) && ($args[2] != 'view' || $args[2] != 'page') && $page == 'page') {
    $_GET['q'] = implode('/', $args);
    $pages = explode(',', $_GET['page']);
    $pages[0] = !empty($pages[0]) ? $pages[0] : 0;
    $pages[1] = $page_no < 0 ? 0 : $page_no;
    $_GET['page'] = implode(',', $pages);
  }
}
 
 
/**
* Copy of the arg() function of drupal 6. The arg() function in drupal 5 requires an argument, this one not.
*/
function arg_d6($index = NULL, $path = NULL) {
  static $arguments;
 
  if (!isset($path)) {
    $path = $_GET['q'];
    if (preg_match('/(.*)\/page\/([0-9]+)/is', $path)) {
      if ($rpath = get_url_src(substr($path, 0, strrpos($path, '/page')))) $path = $rpath.substr($path, strrpos($path, '/page'));
    }
  }
  if (!isset($arguments[$path])) {
    $arguments[$path] = explode('/', $path);
  }
  if (!isset($index)) {
    return $arguments[$path];
  }
  if (isset($arguments[$path][$index])) {
    return $arguments[$path][$index];
  }
}
 
// Determine the url alias for a node
function get_url_alias($src) {
  if ($query = db_query("SELECT * FROM {url_alias} WHERE src='%s'", $src)) {
    if ($rs = db_fetch_object($query)) return $rs->dst;
  }
  return $src;
}
 
// Determine the url src for a node
function get_url_src($dst) {
  if ($query = db_query("SELECT * FROM {url_alias} WHERE dst='%s'", $dst)) {
    if ($rs = db_fetch_object($query)) return $rs->src;
  }
  return false;
}
 
function phptemplate_pager_link($text, $page_new, $element, $parameters = array(), $attributes = array()) {
  // CHANGED for Drupal 5
  $args = arg_d6();
  $page_no = array_pop($args);
  $page = array_pop($args);
  // Check if we are not on the node/X or node/X/page/X path or node/X/view/page/X paths.
  // Still using arg(x) for some checks to avoid popped $args for certain conditions.
  if ((arg(0) != 'node' || !is_numeric(arg(1))) && (arg(2) != 'view' || (!empty($page) && $page != 'page')) && $element != 1) {
    // Don't mingle when the required module is absent.
    if (!module_exists('paging_sweet_urls')) {
      return theme_pager_link($text, $page_new, $element, $parameters, $attributes);
    }
  }
 
  $page = isset($_GET['page']) ? $_GET['page'] : '';
  if ($new_page = implode(',', pager_load_array($page_new[$element], $element, explode(',', $page)))) {
    // $parameters['page'] = $new_page;
  }
 
  $query = array();
  if (count($parameters)) {
    $query[] = drupal_query_string_encode($parameters, array());
  }
  $querystring = pager_get_querystring();
  if ($querystring != '') {
    $query[] = $querystring;
  }
 
  // Set each pager link title
  if (!isset($attributes['title'])) {
    static $titles = NULL;
    if (!isset($titles)) {
      $titles = array(
        t('´ first') => t('Go to first page'),
        t('ã previous') => t('Go to previous page'),
        t('next õ') => t('Go to next page'),
        t('last ª') => t('Go to last page'),
      );
    }
    if (isset($titles[$text])) {
      $attributes['title'] = $titles[$text];
    }
    else if (is_numeric($text)) {
      $attributes['title'] = t('Go to page @number', array('@number' => $text));
    }
  }
 
  $nid = arg(1);
  $page_no = $page_new[1] + 1;
 
  $alias = get_url_alias("node/$nid");
 
  // CHANGED for drupal 5
  return l($text, "$alias/page/$page_no", $attributes, count($query) ? implode('&', $query) : NULL);
}

A packaged version is available here.

Hope this helps...

paging_sweet_urls.module not working

i have installed this module but the pagination url for pages get same link as and it shows the page not found when clicked on the page link. I am working drupal 5 and i need this urgent for my site kinldy help.

Awesome

The improvements looks really good. i like they structure of the code. I will keep these as reference, thanks.

Remove page

How to remove that "page"?
At this moment url looks like this: /something/page/3
My goal is to look like this: /something/3

Try removing the /page from

Try removing the /page from the return statement in the phptemplate theme function.

No teme

Hello from Russia!
Can I quote a post in your blog with the link to you?

No problem

No problem as long as it maintains the correct reference and link.

help on the layout an d emoticons

hullo:

Very useful site for these specific Drupal use cases

On a separate topic though, I notice that you have a very interesting layout for your "Post new comment " area as well as a gr8 set of large Emoticons.

Can you plz provide a run down on how to achieve something similar? I'm starting out with Drupal and don't have much idea about how to get this type of Emoticons and also how to design the the layout of the Post Comment area.

Any help is very much appreciated.

Thanks and cheers.

You need the paging module for this to work

Hey, thanks a lot for this code, it works like a charm on one site I administer.

One important detail that you should mention is that your module works on top of the paging module. You should even modify the .info file for having that module as a requirement.

Why don't you add this to drupal.org? It will be easier to find and track improvements over there.

Cheers!

how to call

hi,
which function is need to call to apply paging. this is code work for custom modules
best refards,
Kamran Sohail

hi

ssa bai ji good 2 c a nice site from a sikh

URL alias function is must

Thanks for a nice article. I must say that ur contribution of Paging module is very valuable as it fills a big void in Drupal in terms of its innate inability to provide page breaks. For some websites it is must as it breaks big articles into more readable parts. However, in its present form, the paging module is not SEO friendly, as you urself admitted, due to that URL thing. This so called sweet URL rather makes it worse as it adds nameless meaningless words like Node and number which make no sense from SEO point of view. I wish you would rather have utilized ur efforts to somehow add URL alias aspects in paging module itself instead of this sweet URL. We are really eagerly looking forward to getting a really good tool from you. Thanks

Post new comment

Smileys
:);):P*JOKINGLY*8)8-|:(:O(pl)]:&lt;O:)&lt;:o):thinking(Y)
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <del> <code> <ul> <ol> <li> <small> <br> <img> <h2> <h3> <h4>
  • Lines and paragraphs break automatically.
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>. The supported tag styles are: <foo>, [foo]. PHP source code can also be enclosed in <?php ... ?> or <% ... %>.
  • Textual smileys will be replaced with graphical ones.

More information about formatting options