How do I reorder (pop and push) items from WP_Query?

You could use the Metadata API to retrieve the rw_advertising_position metadata for each post, seperate the ads from the content, and then re-insert the ads at the proper locations:

/**
 * Extracts from an array posts with positional metadata and re-inserts them at the proper
 * indices. See https://wordpress.stackexchange.com/questions/210493
 **/
function wpse_210493_apply_advertising_position( &$posts, $return = false ) {
    $ad_posts      = array();
    $content_posts = array();

    // Seperate $posts into "Ads" and "Content" arrays based on whether or not they have 'rw_adversiting_position' meta-data    
    foreach( $posts as $post ) {
        $position = get_post_meta( $post->ID, 'rw_adversiting_position', true );

        if( ! empty( $position ) )
            $ad_posts[ intval( $position ) ] = $post;
        else
            $content_posts[] = $post;  
    }

    // Sort the ads from smallest position index to greatest such that re-insertion properly factors in all ads
    ksort( $ad_posts );

    // Add the ads back into the content at their specified positions
    foreach( $ad_posts as $position => $ad ) {
        array_splice( $content_posts, $position, 0, array( $ad ) );
    }

    // If $return is true, return the resulting array. Otherwise replace the original $posts array with it.
    if( $return )
        return $content_posts;
    else
        $posts = $content_posts;
}

DISCLAIMER

In the example above, I specify a function parameter &$posts which instructs PHP to use a pass-by-reference evaluation strategy for the argument passed to the function as $posts. This means that instead of referring to a locally-scoped copy of the data passed as the first argument, the $posts variable will refer to the data at it’s original place in memory.

Here I’ve used this mechanism to provide the (default) option to directly re-arrange an array of post objects without needing to handle a return value. The function itself merely sorting an array, I choose to pass the array argument by reference in order to provide behavior more consistent with all 12 of PHP’s array sorting functions.

As @Andrei Gheorghiu points out in the comments, passing by reference can produce unexpected results if you’re unfamiliar with the practice. In such a scenario you may wish to steer clear of it which can be done by setting the $return argument in the example to true, or to be completely safe remove the option entirely as Andrei has.

In your template:

// [...]
$normal_query = new WP_Query( $normal_args );

wpse_210493_apply_advertising_position( $normal_query->posts );

if ( $normal_query->have_posts() ) {
// [...]

I haven’t tested that code – it’s just for illustrative purposes.

Alternately, using a second query to retrieve the ads alone might work a little better.

Leave a Comment