2

Site example: http://www.ianstuart-bride.com

I currently have this bit of code in header.php, but it does not work as desired:

<?php if (is_archive() && ($paged > 1) && ($paged < $wp_query->max_num_pages)) { ?>
    <link rel="prefetch" href="<?php echo get_next_posts_page_link(); ?>">
    <link rel="prerender" href="<?php echo get_next_posts_page_link(); ?>">
<?php } elseif (is_singular()) { ?>
    <link rel="prefetch" href="<?php bloginfo('home'); ?>">
    <link rel="prerender" href="<?php bloginfo('home'); ?>">
<?php } ?>

The homepage prefetch points to a non-existent page. I would like to disable prefetching on the homepage.

I want to enable prefetching on these pages, for the 'Next' dress.

This is the code for the single dress pages.

I tried this, but it linked to the current page instead of the next one:

    <link rel="prefetch" href="<?php echo get_permalink($next_dress) ?>">
    <link rel="prerender" href="<?php echo get_permalink($next_dress) ?>">
  • 2
    you're setting $next_dress after you try to use its value? – Milo Mar 24 '14 at 16:01
  • 2
    +1 to @Milo just because you're using WordPress doesn't mean that you can suspend cause and effect – Tom J Nowell Mar 25 '14 at 0:10
6
+50

If I understand correctly you are putting that code in header.php, that is required in the file single-dress.php using get_header(), and in the same file, after about 2 dozen lines, you define the variables you used 2 dozen lines before.

So, either your server has an embedded time machine, or your code can't work.

What you have to do is just define variables before you use them.

Probably, instead of messing up your code, and fill your header with a series of if / elseif, you can separate your logic from your templates.

I'll write a simple class that handles the prefetching. You can save it in a separate file and require it from your functions.php.

Please read the inline comments for more explanation.

<?php
class My_Theme_Prefetcher {

    public $next_url;
    public $prev_url;

    function setup() {
        global $wp_query;
        // for empty query there is nothing to prefetch 
        if ( ! $wp_query instanceof WP_Query || $wp_query->found_posts <= 0  ) return;
        if ( $wp_query->is_archive() ) {
            // next page URL for archives, that is empty if there is no next page URL
            $this->next_url = get_next_posts_page_link();
        } elseif ( $wp_query->is_singular( 'dress' ) ) {
            // if on a single page view for dress CPT, run a query for dress posts
            $this->set_dress_query( $wp_query->get_queried_object_id() );
        }
        // A filter to easily get class instance and access to instance methods and vars
        add_filter( 'my_theme_prefetcher', function() { return $this; } );
        // output the prefetch strings on wp_head
        add_action( 'wp_head', array( $this, 'output' ) );
    }

    /**
    * Perform a query to get all dresses in same collection,
    * save adjacent post URLs into instance properties
    */
    function set_dress_query( $dressid ) {
        $args =  static::dress_query_args( $dressid );
        if ( empty( $args ) ) return;
        $dress_query = new WP_Query( $args );
        if ( $dress_query->found_posts > 0 ) {
            // some dresses found, discover and save adjacent URLs
            $adjacents = static::get_adjacents( $dressid, $dress_query->posts );
            if ( $adjacents['next'] ) 
                $this->next_url = get_permalink( $adjacents['next'] );
            if ( $adjacents['prev'] ) 
                $this->prev_url = get_permalink( $adjacents['prev'] );
        }
    }

    /**
    * Given a current ID and a set of posts, discover adjacent posts
    */
    static function get_adjacents( $current, Array $all ) {
        $adjacents = array( 'prev' => FALSE, 'next' => FALSE );
        if ( is_numeric( $current ) && ! empty( $all ) ) {
            $ids = wp_list_pluck( $all, 'ID' );
            $this_posts = array_search( $current, $ids );
            if ( $this_posts !== FALSE ) {
                $prev_i = $this_posts > 0 ? $this_posts -1 : FALSE;
                $next_i = $this_posts < count( $ids ) - 1 ? $this_posts + 1 : FALSE;
                $previous = $prev_i !== FALSE ? $all[ $prev_i ] : FALSE;
                $next = $next_i !== FALSE ? $all[ $next_i ] : FALSE;
                $adjacents = array( 'prev' => $previous, 'next' => $next );
            }
        }
        return $adjacents;
    }

    /**
    * Output prefetch string on wp_head. Do nothing if no, or invalid, URL is set
    */
    function output() {
        if (
            empty( $this->next_url ) ||
            ! filter_var( $this->next_url, FILTER_VALIDATE_URL )
        ) return;
        $format = '<link rel="prefetch" href="%1$s"><link rel="prerender" href="%1$s">';
        printf( $format, esc_url( $this->next_url ) );
    }

    /**
    * Returns the args for dress query for a given dress id.
    */
    static function dress_query_args( $dressid ) {
        $collections = get_the_terms( $dressid, 'collections' );
        if ( is_wp_error( $collections ) || empty( $collections ) ) return;
        $term = array_shift( $collections );
        $args = array(
            'post_type' => 'dress',
            'posts_per_page' => -1,
            'tax_query' => array(
                array( 'taxonomy' => 'collections', 'terms' => array( $term->term_id ) )
            ),
            'order' => 'ASC',
            'orderby' => 'title'
        );
        return $args;
    }

}

All the logic is in the class: when setup() method is called, the class looks at the current query, and if it is an archive, then setup next URL to next archive page. If the page is for a single dress view, then it runs a query to get all dresses in the same collection, and if found, save adjacent post URLs in instance variables.

Now we need to launch the class, i.e. call the setup() method on a hook fired after the main query is set, but before wp_head() is called: 'template_include' will be perfect.

I don't use template_redirect here to allow faster redirect if needed (without triggering the additional query). However, 'template_include' is a filter, so we have to return the current template. Add to your functions.php:

add_filter( 'template_include', function( $template ) {
    $prefetcher = new My_Theme_Prefetcher;
    $prefetcher->setup();
    return $template;
} );

Now, you have only to be sure in your header.php there is the wp_head() call and the prefetch links will be added by the class.

There is another task to do. In template file (single-dress.php) we need to display adjacent posts links, but we don't need to run another query, because adjacent posts URLs are already saved in the My_Theme_Prefetcher instantiated on 'template_include'.

So we need to access that instance and the custom filter 'my_theme_prefetcher' that was created for the scope.

So in the template, you can:

<figure class="pinboard">
    <?php

    // here goes your thumbnail image stuff, I will not copy it...

    // get the prefetcher instance and check it
    $prefetcher = apply_filters( 'my_theme_prefetcher', NULL );

    if ( $prefetcher instanceof My_Theme_Prefetcher ) { 
        // we got prefetcher instance, get the urls
        $prev_url = $prefetcher->prev_url ? : FALSE;
        $next_url = $prefetcher->next_url ? : FALSE;
    } else {
        // something goes wrong, try to run the query
        // function that returns arguments is static, so we can access it
        $args = My_Theme_Prefetcher::dress_query_args( $post->ID );
        $dress_by_tax = ! empty( $args ) ? new WP_Query( $args ) : FALSE;
        if ( $dress_by_tax instanceof WP_Query && ! empty( $dress_by_tax->posts ) ) {
            // function that returns adjacent posts is static, so we can access it
            $adjacents = My_Theme_Prefetcher::get_adjacents( $post->ID, $dress_by_tax->posts );
            $next_url = $adjacents['next'] ? get_permalink( $adjacents['next'] ) : FALSE;
            $prev_url = $adjacents['prev'] ? get_permalink( $adjacents['prev'] ) : FALSE;
        } else {
            $next_url = $prev_url = FALSE;
        }
    }
    ?>

    <?php if ( $next_url ) : ?>
        <div class="nav-next arrow block button spaced ribbon">
            <a href="<?php echo $next_url ?>">Next</a>
        </div>
    <?php endif; ?>

    <?php if ( $prev_url ) : ?>
        <div class="nav-prev arrow-left block button spaced ribbon2 left">
            <a href="<?php echo $prev_url ?>">Previous</a>
        </div>
    <?php endif; ?>

    <div class="mask"><span>Click for larger size</span></div>

</figure>
  • Thanks for this. I am having trouble getting it to work. I have saved the class file as prefetch.php and placed it in includes`, as functions.php` looks like this. So I am unsure where to add what you said should be added to functions.php. – paradroid Mar 29 '14 at 3:04
  • 1 remove the code you posted from header.php 2 save the class in my answer (1st code snippet) in a file prefetch.php inside the folder /includes in you theme and then in functions.php write include 'includes/prefetch.php'; 3 in functions.php also copy and paste the secondo snippent in the answer, the one that start with add_filter( . 4 You are done. @paradroid – gmazzap Mar 29 '14 at 9:41
  • I did actually get that far, but it caused a load of code to appear in the wp-admin interface. As the syntax highlighting looks a bit weird in my text editor, I think there maybe errors in the class file. Also, what about the last code snippet? Thanks. – paradroid Mar 29 '14 at 15:34
  • The server has PHP 5.4.6. The garbled text only appears if signed into wp-admin (which also does not cache or minify in wtc total cache). Here is a screenshot. It also appears at the top of the wp-admin interface. – paradroid Mar 30 '14 at 7:22
  • @paradroid seems you have not <?php on top of the class file... – gmazzap Mar 30 '14 at 7:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.