Extending Now Reading – Part 1

Filed Under Coding &Plugins | Comments (5) | Page Tools

One of the main parts of this blog is the Now Reading Reloaded plugin for managing the books I’m reading and have read. The plugin is great providing library, page, author, tag and search views. It also has a review/rating system where you can rate a book and write a review. However it does not provide a template to display favourites; like a “top 10″ list of highest-rated books. So I thought I’d write one. It was a bit of a challenge figuring out the URL structure, but once I did that, the template implementation was reasonably simple; the functions I wanted were already implemented in the plugin just not exposed in the provided templates.

This post is a summary of how I implemented the favourites page, including the URL structure required for Now Reading, how I added the new page functionality to the plugin code, and how I implemented the new template page…

Note: The changes described below have been included in Now Reading Reloaded 5.1.0.0.

Now Reading URL Structure

It took me some time to figure out how the individual php pages were referenced through the URL, and how this changes with mod_rewrite.

Normal URL Structure

The plugin refers to each function through a URL query string of the form blog/index.php?now_reading_<type>=<value>. For example the library page is referred to as blog/index.php?now_reading_library=1 (or true), a tag page is referred to a blog/index.php?now_reading_tag=Doctor.

When wordpress parses the query string, it stores the query field names (field) and field values (value) in a data structure that can be used by the plugin code to drive page displays. For example there are checks to see if the current URL is referring to a Now Reading page (is_now_reading_page() looks for one of the defined now_reading_* fields) or one of the specific fields (e.g. the main now-reading.php module has a check for the different fields and loads the appropriate page).

Structure with mod_rewrite

A lot of wordpress implementations use the url mod rewrite function to provide simpler URL namespace. The Now Reading plugin supports this. It uses the following simplified structure:

  • blog/library – root of the Now Reading files and the “library” page
  • blog/library/<author> – for all books belonging to a specific author; written as firstname-surname.
  • blog/library/<author>/<title> – for a specific; written with dashes joining the words.
  • blog/library/tag/<tag> – for all books matching a tag; written with a plus sign joining the words such as “10th+Doctor”.
  • blog/library/search/?q=<search arg> – for all books matching a search argument.

There are a few others, but that will give you an idea of the structure. These are not real directories, just a way of passing information to the now-reading.php module so it knows what template file to load.

The url.php module contains a set of mod rewrite rules that convert the above structure into the normal query string structure that now-reading.php is expecting.

function nr_mod_rewrite( $rules ) {
    $options = get_option('nowReadingOptions');
    add_rewrite_rule(preg_quote($options['permalinkBase']) . '([0-9]+)/?$', 'index.php?now_reading_id=$matches[1]', 'top');
    add_rewrite_rule(preg_quote($options['permalinkBase']) . 'tag/([^/]+)/?$', 'index.php?now_reading_tag=$matches[1]', 'top');
    add_rewrite_rule(preg_quote($options['permalinkBase']) . 'search/?$', 'index.php?now_reading_search=true', 'top');
    add_rewrite_rule(preg_quote($options['permalinkBase']) . 'reader/([^/]+)/?$', 'index.php?now_reading_library=1&now_reading_reader=$matches[1]', 'top');
    add_rewrite_rule(preg_quote($options['permalinkBase']) . '([^/]+)/([^/]+)/?$', 'index.php?now_reading_author=$matches[1]&now_reading_title=$matches[2]', 'top');
    add_rewrite_rule(preg_quote($options['permalinkBase']) . '([^/]+)/?$', 'index.php?now_reading_author=$matches[1]', 'top');
    add_rewrite_rule(preg_quote($options['permalinkBase']) . '?$', 'index.php?now_reading_library=1', 'top');
}

Each line uses a regular expression to match a URL and convert it to the URL query string. For example the second rewrite rule looks for a URL containing “tag” followed by a slash and a string (not containing a slash) followed by a slash at the end (such as blog/tag/10th+Doctor/) and converts this to index.php?now_reading_tag=<tag> (such as index.php?now_reading_tag=10th+Doctor) which now-reading.php can consume (to load the tag.php template).

Note that there is a rewrite rule somewhere (base wordpress I assume) that will put a trailing slash on the URL if it’s not there.

Now that we understand the structure, we can go through and add a custom template to the plugin.

Adding a Custom Template to the Plugin

I had two options; code in the specific template name where I needed to, or code in some generic code so multiple templates could be added later without repeating the plugin changes. I decided to go the latter option.

I decided to implement the following:

  • Introduce a new query string structure that included the specific template name; index.php?now_reading_page=<page_name> (for example index.php?now_reading_page=favourites.php).
  • Support the mod rewrite structure with; library/page/<page_name> (for example library/page/favourites.php).
  • Introduce a new function to generate the appropriate URL (query string or mod rewrite) passing it the template name; library_page_url( <page_name> ) (for example library_page_url(favourites.php) ).

This required modification of:

  1. url.php – the module that processes the URL’s.
  2. now-reading.php – the core Now Reading (Reloaded) module that loads the template files (amongst other functions).
  3. template-functions.php – the module that provides the functions that the template files can use.

The changes to these are detailed in the following sections.

Changes to url.php

There were three changes to be made to this file:

#1. Add the new query string field to the list of known ones:

function nr_query_vars( $vars ) {
    $vars[] = 'now_reading_library';
    $vars[] = 'now_reading_id';
    $vars[] = 'now_reading_tag';
    $vars[] = 'now_reading_page';
    $vars[] = 'now_reading_search';
    $vars[] = 'now_reading_title';
    $vars[] = 'now_reading_author';
    $vars[] = 'now_reading_reader'; //in order to filter books by reader
    return $vars;

The new entry is the ‘now_reading_page’ line.

#2. Add the new mod rewrite rule to the existing ones:

function nr_mod_rewrite( $rules ) {
    $options = get_option('nowReadingOptions');
    add_rewrite_rule(preg_quote($options['permalinkBase']) . '([0-9]+)/?$', 'index.php?now_reading_id=$matches[1]', 'top');
    add_rewrite_rule(preg_quote($options['permalinkBase']) . 'tag/([^/]+)/?$', 'index.php?now_reading_tag=$matches[1]', 'top');
    add_rewrite_rule(preg_quote($options['permalinkBase']) . 'page/([^/]+)/?$', 'index.php?now_reading_page=$matches[1]', 'top');
    add_rewrite_rule(preg_quote($options['permalinkBase']) . 'search/?$', 'index.php?now_reading_search=true', 'top');
    add_rewrite_rule(preg_quote($options['permalinkBase']) . 'reader/([^/]+)/?$', 'index.php?now_reading_library=1&now_reading_reader=$matches[1]', 'top');
    add_rewrite_rule(preg_quote($options['permalinkBase']) . '([^/]+)/([^/]+)/?$', 'index.php?now_reading_author=$matches[1]&now_reading_title=$matches[2]', 'top');
    add_rewrite_rule(preg_quote($options['permalinkBase']) . '([^/]+)/?$', 'index.php?now_reading_author=$matches[1]', 'top');
    add_rewrite_rule(preg_quote($options['permalinkBase']) . '?$', 'index.php?now_reading_library=1', 'top');
}

Order is important here. The new entry needs to go before the more generic entries in the last three rules (lines).

#3. Add the new query field to the list of ones that identify this as a now_reading page:

function is_now_reading_page() {
    global $wp;
    $wp->parse_request();

    return (
    get_query_var('now_reading_library') ||
        get_query_var('now_reading_search')  ||
        get_query_var('now_reading_id')      ||
        get_query_var('now_reading_tag')     ||
        get_query_var('now_reading_page')    ||
        get_query_var('now_reading_title')   ||
        get_query_var('now_reading_author')
    );
}

This completes the changes to url.php.

Changes to now-reading.php

This file contains logic to determine what templates are loaded and what arguments or global variables to define for the template. The following code was added to the library_init() function, with the code before/after shown for context:

    if ( get_query_var('now_reading_tag') ) {
    // Tag permalink:
        $GLOBALS['nr_tag'] = get_query_var('now_reading_tag');

        $load = nr_load_template('tag.php');
        if ( is_wp_error($load) )
            echo $load->get_error_message();

        die;
    }

    if ( get_query_var('now_reading_page') ) {
    // get page name from query string:
        $nrr_page = get_query_var('now_reading_page');

        $load = nr_load_template($nrr_page);
        if ( is_wp_error($load) )
            echo $load->get_error_message();

        die;
    }

    if ( get_query_var('now_reading_search') ) {
    // Search page:
        $GLOBALS['query'] = $_GET['q'];
        unset($_GET['q']); // Just in case

        $load = nr_load_template('search.php');
        if ( is_wp_error($load) )
            echo $load->get_error_message();

        die;
    }

The central if statement checks to see if the now_reading_page query string field has been specified and if so, it gets the page name passed as the query string field value (e.g. favourites.php), defines this in a variable (nrr_page) and then loads this page. The nr_load_template() function will look across the different template directories for Now Reading and load the appropriate file.

There is another function, nr_page_title(), that defines the page title based on the query string field. I did not modify this.

Changes to template-functions.php

This module defines the functions that can be called from template files. There was one change to make; a new function to build the custom template URL:

/**
 * Returns a URL to the permalink for a given (custom) page.
 * @param string $page Page name (e.g. custom.php) to create URL for.
 * @param bool $echo Whether or not to echo the results.
 */
function library_page_url( $page, $echo = true ) {
    $options = get_option('nowReadingOptions');

    if ( $options['useModRewrite'] )
        $url = get_option('home') . "/" . preg_replace("/^\/|\/+$/", "", $options['permalinkBase']) . "/page/" . urlencode($page);
    else
        $url = get_option('home') . '/index.php?now_reading_page=' . urlencode($page);

    $url = apply_filters('library_page_url', $url);

    if ( $echo )
        echo $url;
    return $url;
}

This function is passed a page name (e.g. favourites.php), determines if mod rewrite is enabled for the blog and then builds the appropriate URL which is returned to the calling template.

So that’s it for the background coding changes. The next section looks at how the favourites page was implemented.

Implementing a Favourites Page

There were two pieces of work; building the page to display my favourite books and creating the links to the new page.

New favourites.php

I created a new file in my theme/now-reading-reloaded directory called favourites.php.

This file contains a lot of code relating to my overall theme. The core functionality was implemented as follows:

<?php if( have_books('orderby=rating&order=desc&num=10') ) : ?>
  <div id="nrr-book-list1">
    <ul>
      <?php while( have_books('orderby=rating&order=desc&num=10') ) : the_book(); ?>
        <div class="nrr-book-cover">
          <p><a href="<?php book_permalink() ?>">
            <img class="nr-coversingle" src="<?php book_image() ?>" alt="Go to <?php book_title() ?> at Amazon" />
          </a></p>
        </div>
        <div class="nrr-book-info">
          <h3><?php book_title() ?></h3>
          <p>By <a href="<?php book_author_permalink() ?>"><?php book_author() ?></a></p>
          <p><a href="<?php book_permalink() ?>">See more info</a>
		    <?php if( can_now_reading_admin() ) : ?>
               | <a href="<?php book_edit_url() ?>">Edit book</a>
            <?php endif; ?>
            <?php if( !is_custom_book() ): ?>
               | <a href="<?php book_url() ?>">See this book on Amazon.</a>
            <?php endif; ?>
          </p>
          <p><strong>Rating:</strong> <?php book_rating() ?></p>
          <p><?php book_review() ?></p>
        </div>
      <?php endwhile; ?>
    </ul>
  </div>
<?php else : ?>
  <p>None</p>
<?php endif; ?>

The two important bits of code are the have_books() functions on lines 1 and 4. Both tell Now Reading to go get ten (10) books ordered (sorted) by the rating, descending. This is not a new function I’ve added here, it is standard Now Reading functionality. This template, favourites.php, does not use anything new just existing functions.

Linking to favourites.php

This is where the new functionality comes in. I’ve got a few sidebar modules that contain my library links, one for the main blog page and one for the library pages. Both were updated to include the following code:

<a href="<?php library_page_url('favourites.php') ?>">Favourite books</a>

This calls the new library_page_url function I defined in template-functions.php (see above) passing it the name of the file I want to load. The result can be seen by going to the home page or one of the library pages for this blog.

That’s it, that’s all the template changes I had to make.

Conclusion

Now that the hard work has been done to extend Now Reading Reloaded’s core code, I can easily add new template pages using the existing and new functions. Say I wanted to add a new template to show the highest rated book in the last year, all I’d need to do is define a new template file (say highest-rated.php) in my theme now-reading-reloaded directory, add the function calls to get the book info, and then add another link call to my sidebars (e.g. <a href=”<?php library_page_url(‘highest-rated.php’) ?>”>Highest-rated this year</a>).

This has been an interesting exercise to understand how plugins are put together.

Page Tools

  • RSS
  • Email this Post
  • Print This Post

Tags: , , , , ,

Related posts:

  1. Tweaking the Now Reading Plugin
  2. Got Now-Reading working
  3. A Custom WP Category Template

Comments

5 Responses

  1. Nice job!

    While I don’t wish to get as complex as you did…can you tell me, how can I incorporate my Now Reading into a page, rather than my sidebar? Is there just some code that I can enter into a page to make it appear?

    Thanks!

    Comment by Jason at August 27th, 2009 at 3:22 pm

  2. Thanks Jason.

    It depends on what you want to incorporate into your page and how you want to incorporate it.

    If you’re working at the code level (i.e. modifying the php files):
    1. You can create custom template files (*.php) in your {theme}/now-reading-reloaded directory as listed above.
    2. If you have a specific php file (such as a specific category file) you can include the “<?php nr_display() ?>” command that will put the sidebar display into the body. This just includes the {theme}/now-reading-reloaded/sidebar.php file.

    However, I suspect you want to embed the Now Reading functionality into the page content, not the template. This is where you would use a shortcode (something like [NowReading], but as far as I know there isn’t one.

    Comment by David at August 28th, 2009 at 1:37 pm

  3. Im looking for exactly the functionality described at the end of your last post David. It would be great to be able to embed NRR directly into a page.

    Comment by Josh Dinsdale at September 7th, 2009 at 7:08 am

  4. I agree Josh. I expect it wouldn’t be too hard to do, but I’ve not done any coding like that before and I’m a bit short on free time at the moment. It may be worth suggesting this to Ben Gunnink (http://heliologue.com/) for his next update to NRR.

    Comment by David at September 12th, 2009 at 12:38 pm

  5. [...] http://davidedwardsphotos.com/blog/2009/08/11/extending-now-reading-part-1 [...]

    Comment by Now Reading Reloaded Plugin Customization | KeganBall.net at March 27th, 2010 at 6:39 am

Leave a Comment

Please note: Comment moderation is enabled and may delay your comment. There is no need to resubmit your comment.