2 Loops on one page – pagination of 2nd loop ignore array of excluded posts

Because this is suppose to be the main loop on your home page, there is a far better way to achieve this without running any custom query at all. You should never replace the main query with a custom one. I’m not going to go into details as I have already done a quite extensive answer on this which you must go and read.

We can use the main query and modify it to our free will to adjust it to what we need throught the filters and actions available

THE MASTER PLAN – The lego parts

You need 22 posts on page one, and from there on just 11. The tricky parts are

  • Page 1 where we need the first 11 posts in random order, then the next 11 in normal order.

  • Recalculating pagination as we will have the last page 404’ing in most cases depending on post count

What we need to do is:

  • Most important, remove the custom queries and go back to the main loop on index.php. Your index.php should look like the following: (Adjust to your exact needs, this is the bare minimum)

    if ( have_posts() ) {
        while ( have_posts() ) {
            the_post();
    
            // Your markup and template tags to display posts
    
        } 
        // Your paging function
    } else {
        // Your message to display when there is no posts
    } 
    
  • Set posts per page to 11. We can do that manually or use the setting in the backend

  • Through pre_get_posts we will add an extra 11 posts to page 11. At this stage you should have 22 posts on page one, sorted by date. The other pages should have 11 posts with most probably the last page 404’ing.

  • To sort out pagination, we will need to filter the amount of found posts through the found_posts filter. The $max_num_pages property in the query object, which is responsible for calulating pagination, uses the amount of posts per page and the amount of found posts to calculate how many pages there will be. After this is sorted, everything will work, except the first 11 posts on page one which will not be random

  • To randomize the first 11 posts on page one, we will rearrange the post order of the posts through the the_posts filter. This filter executes before the loop is actually run to display the posts. After this is done, you should have the result you are after

THE CODE – Putting our lego blocks to use

Few notes

  • All code is untested and but might still be buggy. Please let me know if you have any issues

  • The code requires at least PHP 5.4

  • Adjust as needed. All code will go into a custom plugin (recommended) or in your theme’s function file

PART ONE – The helping hands

Because we will have repetitive code, lets create filterable helpers

/**
 * Return the amount of posts per page, which by default is 11
 */
function wpse_219896_ppp ()
{
    return apply_filters( 'wpse_219896_ppp', 11 );
}

/**
 * Returns the offset to use, which by default is 11
 */
function wpse_219896_offset ()
{
    return apply_filters( 'wpse_219896_offset', 11 );
}

We can simply adjust any of these function’s output with the following example:

add_filter( 'wpse_219895_ppp', function ( $ppp )
{
    return 10;
}

This is just done to keep the code maintainable

PART TWO – pre_get_posts

We will now set our posts per page and offsets throught the pre_get_posts action

add_action( 'pre_get_posts', function ( $q )
{
    if (    $q->is_home()       // Only target the home page
         && $q->is_main_query() // Only target the main query
    ) {
        // First set our variables
        $ppp          = wpse_219896_ppp();
        $offset       = wpse_21896_offset();
        $current_page = get_query_var( 'paged', 1 );

        // Target the first page first
        if ( !$q->is_paged() ) {
            // Add our offset to $ppp
            $q->set( 'posts_per_page', ( $ppp + $offset ) );
        } else { 
            /**
             * Lets target all the paged pages now.
             *
             * Because we are breaking the internal calculation for post offset and pagination,
             * we must manually recalculate and set it
             */
            $paged_offset = $offset + ( ( $current_page -1 ) * $ppp );

            $q->set( 'posts_per_page', $ppp          );
            $q->set( 'offset',         $paged_offset );
        }
    }
});

We have now taken care of the posts per page issue, page one will have 22 posts, the rest will have 11

PART THREE – found_posts filter

We now need to adjust for pagination to work correctly

add_filter( 'found_posts', function ( $found_posts, $q )
{
    if (    $q->is_home()       // Only target the home page
         && $q->is_main_query() // Only target the main query
    ) {    
        $offset = wpse_219896_offset();

        // Make sure we have atleat more posts found than the offset
        if ( $offset >= $found_posts ) 
            return $found_posts;

        $found_posts = $found_posts - $offset;
    }
    return $found_posts;
}, 10, 2 );

PART FOUR – the_posts filter

All we now need to do is to sort the first 11 posts on page one randomly

add_filter( 'the_posts', function ( $posts, $q )
{
    if (     $q->is_home()       // Only target the home page
         &&  $q->is_main_query() // Only target the main query
         && !$q->is_paged()      // Only target page one
    ) {        
        $offset = wpse_219896_offset();

        // Make sure that have more than 11 posts, if not, randomly sort all
        if ( $offset >= $q->post_count ) {
            shuffle( $posts );
            return $posts;
        }

        /**
         * We have more posts than our offset, lets split the array and 
         * randomize the first set of posts
         */
        $array_1 = array_slice( $posts, 0, $offset );
        $array_2 = array_slice( $posts, $offset, '', true );

        // Randomize array_1
        shuffle( $array_1 );

        // Merge the two slices into one array again
        $posts = array_merge( $array_1, $array_2 );
    }
    return $posts;
}, 10, 2 );

This should take care of everything.

ALL TOGETHER NOW

We can simplify this into a plugin. Here is how the final code could look like

<?php
/*
Plugin Name: Random posts per page
Plugin URI:  https://wordpress.stackexchange.com/q/219896/31545
Description: Different amount of posts  per page with x amount shown randomly
Version:     1.0.0
Author:      Pieter Goosen
Author URI:  https://wordpress.stackexchange.com/users/31545/pieter-goosen
License:     GPL2
License URI: https://www.gnu.org/licenses/gpl-2.0.html
*/

/**
 * Return the amount of posts per page, which by default is 11
 */
function wpse_219896_ppp ()
{
    // Filter the amount of posts per page
    return apply_filters( 'wpse_219896_ppp', 11 );
}

/**
 * Returns the offset to use, which by default is 11
 */
function wpse_219896_offset ()
{
    // Filter the offset of posts
    return apply_filters( 'wpse_219896_offset', 11 );
}

add_action( 'pre_get_posts', function ( $q )
{
    if (    $q->is_home()       // Only target the home page
         && $q->is_main_query() // Only target the main query
    ) {
        // First set our variables
        $ppp          = wpse_219896_ppp();
        $offset       = wpse_219896_offset();
        $current_page = get_query_var( 'paged', 1 );

        // Target the first page first
        if ( !$q->is_paged() ) {

            // Add our offset to $ppp
            $q->set( 'posts_per_page', ( $ppp + $offset ) );

            // Filter the_posts to randomize first x amount of posts
            add_filter( 'the_posts', function ( $posts, $q ) use ( $ppp, $offset )
            {
                remove_filter( current_filter(), __FUNCTION__ );

                if ( !$q->is_main_query() ) 
                    return $posts;

                // Make sure that have more than $offset posts, if not, randomly sort all
                if ( $offset >= count( $q->posts ) ) {
                    shuffle( $posts );
                    return $posts;
                }

                /**
                 * We have more posts than our offset, lets split the array and 
                 * randomize the first set of posts
                 */
                $array_1 = array_slice( $posts, 0, $offset );
                $array_2 = array_slice( $posts, $offset, $ppp, true );

                // Randomize array_1
                shuffle( $array_1 );

                // Merge the two slices into one array again
                $posts = array_merge( $array_1, $array_2 );

                return $posts;
            }, 10, 2 );

        } else { 
            /**
             * Lets target all the paged pages now.
             *
             * Because we are breaking the internal calculation for post offset and pagination,
             * we must manually recalculate and set it
             */
            $paged_offset = $offset + ( ( $current_page -1 ) * $ppp );

            $q->set( 'posts_per_page', $ppp          );
            $q->set( 'offset',         $paged_offset );
        }

        // Filter the found_posts property of the query
        add_filter( 'found_posts', function ( $found_posts, $q ) use ( $offset )
        {
            remove_filter( current_filter(), __FUNCTION__ );

            if ( !$q->is_main_query() )
                return $found_posts;

            // Make sure we have at leat more posts found than the offset
            if ( $offset >= $found_posts ) 
                return $found_posts;

            return $found_posts - $offset;
        }, 10, 2 );
    }
});