skip to Main Content

I have a shortcode that queries some custom posts and also has pagination added. Everything works, but I want to randomize the order. However when I set order to ‘rand’ in my args, every page in my pagination is totally random – meaning, the same post can show up multiple times on different pages. How can I randomize the order of all my posts but not on a per-page level? Here’s my code:

function slaProductsArchive( $atts ){
    global $paged;
    $paged = (get_query_var('paged')) ? get_query_var('paged') : 1;
    $numposts = intval($atts['num']);
    $cat = $atts['cat'];
     $args = array(  
        'post_type' => 'product',
        'post_status' => 'publish',
        'posts_per_page' => $numposts, 
        'orderby' => 'rand',
        'product-category' => $cat,
        'paged' => $paged,
    );

    $loop = new WP_Query( $args ); 
    while ( $loop->have_posts() ) : $loop->the_post(); 

// Display my posts

    endwhile;
    $output .= '</div>';
    $output .= '<div class="slaPagination"><span class="prev-posts-links">' . get_previous_posts_link('<i class="fas fa-arrow-left"></i> Previous') . '</span> ';
    $output .= '<span class="next-posts-links">' . get_next_posts_link('Next <i class="fas fa-arrow-right"></i>', $loop->max_num_pages) . '</span></div>';
    
    wp_reset_postdata(); 
    return $output;
}
add_shortcode('slaProductsArchive', 'slaProductsArchive');

2

Answers


  1. There are a few things you can do, based on how you’d like to handle it. A few ideas:

    1. Seed the random order with something that changes
    2. Store the order the "already shown" posts, and exclude them
    3. Grab all the posts and shuffle them with a seed

    There are other ways of course, and numbers 2 and 3 aren’t particularly graceful outside of very specific uses. So let’s focus on number 1, seeding the random.

    The posts_orderby filter is the one you’re looking for. It allows you to, well… filter what the posts are ordered by.

    add_filter( 'posts_orderby', 'so_69214414_edit_orderby' );
    function so_69214414_edit_orderby( $orderby ){
        $seed = ''; // We need to figure out what to seed with
        return " RAND({$seed}) ";
    }
    

    Figured out what the define $seed as is really up to you, and how often you want the random order to change.

    How it works is that if you RAND(1) – it will seed the random with 1, so as long as the posts are unchanged, they’ll always be in that order. RAND(2) will produce an entirely different order than RAND(1) – but every time you use RAND(2) it’s the same. For instance:

    RAND(1) : [1, 3, 6, 2, 4, 5]
    RAND(2) : [3, 5, 2, 1, 4, 6]
    RAND(2) : [3, 5, 2, 1, 4, 6]
    RAND(2) : [3, 5, 2, 1, 4, 6]
    RAND(1) : [1, 3, 6, 2, 4, 5]
    RAND(3) : [5, 1, 4, 3, 6, 2]
    

    What I use for one of my WP sites is seed in the day: $seed = date('M d, Y'). That way, the posts always "randomize" at midnight every night, but page 1, page 2, page 3, etc will always be in order. For example:

    Day 1: p1[1, 3] p2[6, 2] p3[5, 4]
    Day 2: p1[3, 5] p2[4, 1] p3[2, 6]
    

    If that doesn’t work for your usecase, you can use PHP sessions and store a random number in there, or you can store a random number in a cookie – and refresh that every time the user visits "page 1" so page 1 is always random, but page 2, 3, 4 always pull from that cookied-seed, etc.

    Just figure out how often you want the seed to change, and then drop that in the posts_orderby filter, and you’ll be good to go.

    Just make sure that you use the add_filter before you call new WP_Query(), and then remove_filter afterwards if you don’t want it to apply anywhere else!

    Login or Signup to reply.
  2. For this, I used the WC-Session, since you’re using products, I’m assuming you are using WooCommerce. Each page stores in a session variable, so if you go back to a previous page, it’s the same as before. It also writes a separate array of posts that were done so you can use post__not_in.

    So while this generates a random order, once a page is created, it’s that order on refresh.

    I also updated your usage of the shortcode_atts and made a condition in case there is no product category set.

    function slaProductsArchive( $atts ){
        $atts = shortcode_atts(
            array(
                'num' => 10,
                'cat' => ''
                ),
            $atts, 'slaProductsArchive' );
    
        // Retrieve Previous WC Session Vars
        $done = WC()->session->get('viewed_randoms', array());
        $my_page = WC()->session->get('my_page', array());
    
    
        global $paged;
        $paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;
        if (!array_key_exists($paged, $my_page)) {
            if ( !empty( $atts['cat'] ) ) {
                $args = array(
                    'post_type'      => 'product',
                    'post_status'    => 'publish',
                    'posts_per_page' => absint( $atts['num'] ),
                    'orderby'        => 'rand',
                    'paged'          => $paged,
                    'product_cat'    => $atts['cat'],
                    'post__not_in'   => $done
                );
            } else {
                $args = array(
                    'post_type'      => 'product',
                    'post_status'    => 'publish',
                    'posts_per_page' => absint( $atts['num'] ),
                    'orderby'        => 'rand',
                    'paged'          => $paged,
                    'post__not_in'   => $done
                );
            }
        } else {
            $args = array(
                'post_type'      => 'product',
                'post_status'    => 'publish',
                'post__in' => $my_page[$paged]
            );
        }
        // Initialize $output ** This wasn't here before ** 
        $output = '<div>';
        // A variable to store the POST ID's of this loop
        $this_loop = array();
        
        $loop = new WP_Query( $args );
        while ( $loop->have_posts() ) : $loop->the_post();
        
        // just to show something from the loop
        // You can remove this and put your own loop here 
        $output .= '<p>' . get_the_title() . ' ' . get_the_ID(). '</p>';
    
        // Set two array of the POST ID's
        $done[] = get_the_ID();
        $this_loop[] = get_the_ID();
        
        endwhile;
        $output .= '</div>';
        $output .= '<div class="slaPagination"><span class="prev-posts-links">' . get_previous_posts_link( '<i class="fas fa-arrow-left"></i> Previous' ) . '</span> ';
        $output .= '<span class="next-posts-links">' . get_next_posts_link( 'Next <i class="fas fa-arrow-right"></i>', $loop->max_num_pages ) . '</span></div>';
    
        // Set My Page Key =  Page Number - Value = each post ID
        $my_page[$paged] = $this_loop;
        // Store WC Session Var
        WC()->session->set('viewed_randoms', $done);
        WC()->session->set('my_page', $my_page);
        wp_reset_postdata();
        return $output;
    }
    add_shortcode( 'slaProductsArchive', 'slaProductsArchive' );
    

    Alternate Method Without WooCommerce

    In this method, I initialize a PHP Session, and use the cookie of the session ID to store a transient, since all of the rest is being done after headers are sent. So the session is only for a session ID. You could also simply set a cookie with whatever name you want and use it for a made up session, purely for using transients as this session.

    function dd_register_session(){
        if(!session_id() && !headers_sent()) {
            session_start();
            session_write_close();
        }
    }
    add_action('init','dd_register_session', 1);
    
    add_action('wp_logout', 'end_session');
    add_action('wp_login', 'end_session');
    add_action('end_session_action', 'end_session');
    
    function end_session() {
        session_destroy ();
    }
    
    function slaProductsArchive( $atts ){
        $atts = shortcode_atts(
            array(
                'num' => 10,
                'cat' => ''
            ),
            $atts, 'slaProductsArchive' );
    
        // Retrieve Previous Session Data
        $done = $my_page = array();
        if (isset($_COOKIE['PHPSESSID'])) {
            if ( false !== ( $session_data = get_transient( $_COOKIE['PHPSESSID'] ) ) ) {
                $done = $session_data['done'];
                $my_page = $session_data['my_page'];
            }
        }
        global $paged;
        $paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;
        if (!array_key_exists($paged, $my_page)) {
            if ( !empty( $atts['cat'] ) ) {
                $args = array(
                    'post_type'      => 'product',
                    'post_status'    => 'publish',
                    'posts_per_page' => absint( $atts['num'] ),
                    'orderby'        => 'rand',
                    'paged'          => $paged,
                    'product_cat'    => $atts['cat'],
                    'post__not_in'   => $done
                );
            } else {
                $args = array(
                    'post_type'      => 'product',
                    'post_status'    => 'publish',
                    'posts_per_page' => absint( $atts['num'] ),
                    'orderby'        => 'rand',
                    'paged'          => $paged,
                    'post__not_in'   => $done
                );
            }
        } else {
            $args = array(
                'post_type'   => 'product',
                'post_status' => 'publish',
                'post__in'    => $my_page[$paged],
                'orderby'     => 'post__in'
            );
        }
        // Initialize $output ** This wasn't here before **
        $output = '<div>';
        // A variable to store the POST ID's of this loop
        $this_loop = array();
    
        $loop = new WP_Query( $args );
        while ( $loop->have_posts() ) : $loop->the_post();
    
            // just to show something from the loop
            // You can remove this and put your own loop here
            $output .= '<p>' . get_the_title() . ' ' . get_the_ID(). '</p>';
    
            // Set two array of the POST ID's
            $done[] = get_the_ID();
            $this_loop[] = get_the_ID();
    
        endwhile;
        $output .= '</div>';
        $output .= '<div class="slaPagination"><span class="prev-posts-links">' . get_previous_posts_link( '<i class="fas fa-arrow-left"></i> Previous' ) . '</span> ';
        $output .= '<span class="next-posts-links">' . get_next_posts_link( 'Next <i class="fas fa-arrow-right"></i>', $loop->max_num_pages ) . '</span></div>';
    
        // Set My Page Key =  Page Number - Value = each post ID
        $my_page[$paged] = $this_loop;
        if (isset($_COOKIE['PHPSESSID'])) {
            set_transient( $_COOKIE['PHPSESSID'] , array('done' => $done, 'my_page' => $my_page), 10 * MINUTE_IN_SECONDS );
        }
        wp_reset_postdata();
        return $output;
    }
    add_shortcode( 'slaProductsArchive', 'slaProductsArchive' );
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search