2

I use this function to insert at the beginning of the loop two post at my convenience (with meta key and value)

add_filter( 'posts_results', 'insert_post_wpse_96347', 10, 2 );
function insert_post_wpse_96347( $posts, \WP_Query $q ) {
    remove_filter( current_filter(), __FUNCTION__ );

    if ( $q->is_main_query() && $q->is_home() && 0 == get_query_var( 'paged' ) ) {

        $args = [
            'meta_key'         => 'caja', 
            'meta_value'       => ['uno','dos'], 
            'post__not_in'     => get_option( "sticky_posts" ), 
            'posts_per_page'   => '2',
            'suppress_filters' => true
        ];

        $p2insert = new WP_Query($args);
        $insert_at = 0;
        if ( !empty( $p2insert->posts ) ) {
            array_splice( $posts, $insert_at, 0, $p2insert->posts );
        }
    }
  return $posts;
}

But these posts still appear in the loop, they would have to hide to not look twice.

How can I do this?

2
  • Are you just trying to put two specific posts at the front of your query results/loop? Also do any of the other posts in the loop have meta_key caja and any values?
    – Adam
    Mar 8, 2016 at 13:35
  • @userabuser Yes, it does. But these two post are still displayed in the rest of the loop and may confuse the user.
    – nicogaldo
    Mar 8, 2016 at 13:42

2 Answers 2

1

We can try the following alternative way:

  • Remove the two posts we select via our custom query from the main query via the pre_get_posts action

  • Return the two posts on top of page one via the the_posts filter

Lets look at possible code:

add_action( 'pre_get_posts', function ( $q )
{
    remove_filter( current_filter(), __FUNCTION__ );

    if (    $q->is_home()       // Only target the home page     
         && $q->is_main_query() // Only target the main query
    ) {
        // Set our query args and run our query to get the required post ID's
        $args = [
            'meta_key'         => 'caja', 
            'meta_value'       => ['uno','dos'], 
            'posts_per_page'   => '2',
            'fields'           => 'ids', // Get only post ID's
        ];      
        $ids = get_posts( $args );

        // Make sure we have ID's, if not, bail
        if ( !$ids )
            return;

        // We have id's, lets remove them from the main query
        $q->set( 'post__not_in', $ids );

        // Lets add the two posts in front on page one
        if ( $q->is_paged() )
            return;

        add_filter( 'the_posts', function ( $posts, $q ) use ( $args )
        {
            if ( !$q->is_main_query() )
                return $posts;

            // Lets run our query to get the posts to add
            $args['fields'] = 'all';
            $posts_to_add   = get_posts( $args );

            $stickies = get_option( 'sticky_posts' );
            if ( $stickies ) {
                $sticky_count = count( $stickies );
                array_splice( $posts, $sticky_count, 0, $posts_to_add );

                return $posts;
            }

            // Add these two posts in front
            $posts = array_merge( $posts_to_add, $posts );

            return $posts;
        }, 10, 2 );
    }
}); 

This should replace the current code that you have posted in your question

5
  • Copy and paste and it works! But using array_merge put the two post before the post sticky. Before array_splice had chosen the option. Any way to resolve this?
    – nicogaldo
    Mar 8, 2016 at 19:26
  • I've solved so with this code. What do you think?
    – nicogaldo
    Mar 8, 2016 at 19:37
  • Good Morning. Glad the first set of code worked, even though just halfway. We can do what you did in your link much faster which will also safe on resources and db calls. I have updated my code, so be sure to check it out Mar 9, 2016 at 4:24
  • Thanks Pieter, it works! I've just changed some parameters to get_post to respect an order 'meta_value' => [1,2], 'orderby' => 'meta_value_num', 'order' => 'asc'
    – nicogaldo
    Mar 9, 2016 at 14:00
  • My pleasure, glad it worked. The code is always there for you to modify at will, just as long as the biger picture is what you are looking for. ;-) Enjoy nd good luck on your project Mar 9, 2016 at 14:11
0

Try this instead:

NOTE: make sure to add any extra conditional logic you need for your usecase, such as a check for is_home() and the value for get_query_var( 'paged' ). I have intentionally left those out just for brevity.

function custom_pre_get_posts_meta_query( $query ) {

    if ( is_admin() || ! $query->is_main_query() )
        return;


    $meta_key = 'caja'; //meta_key to query

    $query->set( 
        'orderby', 
        array(
            'meta_value' => 'DESC',
            'date'       => 'DESC'
        ) 
    );

    $query->set( 
        'meta_query', 
        array(
            array(
                'key'     => $meta_key,
                'value'   => 'IGNORE THIS VALUE', //this just needs to be something random
                'compare' => 'NOT EXISTS'
            )
        )
    );

    add_filter( 'posts_where', 'custom_where_meta_query' );

}   

add_action( 'pre_get_posts', 'custom_pre_get_posts_meta_query', 1 );


function custom_where_meta_query( $where = '' ){

    global $wpdb;

    $meta_key    = 'caja'; //meta_key
    $meta_values = array('uno', 'dos'); //meta_values

    $sql = "'" . implode( "', '", array_map( 'esc_sql', $meta_values ) ) . "'";

    $where .= $wpdb->prepare( 
        " OR (( {$wpdb->postmeta}.meta_key = %s AND {$wpdb->postmeta}.meta_value IN ( {$sql} ) ))", 
        $meta_key
    );

    remove_filter( 'posts_where', 'custom_where_meta_query' );

    return $where;

}

The first callback function on pre_get_posts will initiate WP_Meta_Query which builds the neccessary SQL that we then filter in the second callback function.

The results are first ordered by meta_value DESC then by date DESC. This will group the two matched posts for the meta key caja at the top and the remaining posts will be sorted by date thereafter.

This avoids you having to re-query the database for two specific posts you want, then splice the result set and finally purge the result set of the duplicates.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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