Delete all posts from WordPress except latest X posts

On a project I require to keep only 10 latest posts on website and auto delete all other posts. So I came up with this function which triggers on post publish/update and I used offset parameter to exclude latest 10 items.

function wpse_auto_delete_posts( $post_ID ) {
  global $post;

  $delete_posts = get_posts( array( 'post_type' => 'post', 'offset' => 10, 'posts_per_page' => -1 ) );
  foreach ( $delete_posts as $delete_post ) : setup_postdata( $delete_post );
    wp_delete_post( $delete_post->ID, true );
  endforeach;
  wp_reset_postdata();

}
add_action( 'publish_post', 'wpse_auto_delete_posts' );

But it appears, offset didn’t work here with 'posts_per_page' => -1 and it keeps deleting all posts. I thought of using Date query but it will also not work too because new posts are created very frequently.

Is there any way to select all posts except latest 10 or 5. So WordPress can process them and keep latest X posts.

2 s
2

The offset parameter is ignored with posts_per_page set to -1 in WP_Query. If you look at the source code in the WP_Query class, posts_per_page=-1 sets nopaging to true.

if ( !isset($q['nopaging']) ) {
    if ( $q['posts_per_page'] == -1 ) {
        $q['nopaging'] = true;
    } else {
        $q['nopaging'] = false;
    }
}

This in turn will not append the LIMIT to the SQL query (empty($q['nopaging'] === false which fail the conditional statement) meaning that the whole pagination/offset is ignored and all posts are returned regardless

if ( empty($q['nopaging']) && !$this->is_singular ) {
    $page = absint($q['paged']);
    if ( !$page )
        $page = 1;

    if ( empty($q['offset']) ) {
        $pgstrt = absint( ( $page - 1 ) * $q['posts_per_page'] ) . ', ';
    } else { // we're ignoring $page and using 'offset'
        $q['offset'] = absint($q['offset']);
        $pgstrt = $q['offset'] . ', ';
    }
    $limits="LIMIT " . $pgstrt . $q['posts_per_page'];
}

I think the best workaround here is to make use of normal PHP (array_slice())after getting all the posts. You want to get only post ID’s here as wp_delete_post is quite expensive to run and only really need the post ID, so we don’t need any other post info.

In short, your query will look like this: (NOTE: This is all untested and needs PHP 5.4+)

add_action( 'publish_post', function ( $post_ID ) 
{
    $args = [
        'nopaging' => true,
        'fields'   => 'ids'
    ];
    $posts_array = get_posts( $args );
    // Make sure that we have post ID's returned
    if ( $posts_array ) {
        // Get all post ids after the 10th post using array_slice
        $delete_posts = array_slice( $posts_array, 10 );
        // Make sure that we actually have post ID's in the $delete_posts array
        if ( $delete_posts ) {
            foreach ( $delete_posts as $delete_post )
                wp_delete_post( $delete_post, true );
        }
    }
});

EDIT

Just before I forget, you can also define posts_per_page as an unlikely integer value instead of -1. This will also work with your code with the offset parameter set

Leave a Comment