6

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.

  • Good question that have me hooked. I wonder how posts_per_page=-1 influence offset as offset is used to calculate pagination in queries. get_posts also legally breaks pagination, so it would be interesting to see how this all influence the offset parameter ;-) – Pieter Goosen Sep 7 '15 at 10:44
  • Yeh I have been trying to work on it from last couple of hours but could not find any way. Although it sounds very easy to achieve. :-P I kind of knew offset won't work but tried it anyway. – Robert hue Sep 7 '15 at 11:37
  • I have an idea, just need to test it out – Pieter Goosen Sep 7 '15 at 11:44
  • The fact that get_posts() of query.php strictly defines the relationhip between posts_per_page and nopaging. I suggest a good solution here would be to overwrite/filter post_clauses. What do you think? – Ignat B. Sep 7 '15 at 12:17
  • @IgnatB. posts_per_page needed to be overwritten and below solution (of setting this to higher number) by Pieter works perfectly. Although thank you for trying out and for your answer. :-) – Robert hue Sep 7 '15 at 14:14
6

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

| improve this answer | |
  • +1 for setting the posts_per_page to some high number. Actually assuming the site starts with zero posts it can even be 2 as there will never be more then 11 posts. – Mark Kaplun Sep 7 '15 at 12:54
  • @MarkKaplun the assumption here is, what happens if the site starts of with a 1000 posts. After an initial run or two, you are correct in the assumption that there will never be more than 11 posts :-) – Pieter Goosen Sep 7 '15 at 13:03
  • My assumption is that it is not hard to manually delete X posts ;) and it is easier then writing the code to delete such a big amount as at some point you might run out of PHP memory, MYSQL memory or php execution time limit. – Mark Kaplun Sep 7 '15 at 13:24
  • WOW @Pieter, 'posts_per_page' => '1000' works great. I didn't tried out other solution you posted but I am sure it will work too. Thanks, – Robert hue Sep 7 '15 at 14:08
  • 1
    @Roberthue glad it worked. I actually posted the first solution as an alternative, I only realized after posting that I forgot to post the easy solution whereby you just define posts_per_page as an unlikely integer. Enjoy ;-) – Pieter Goosen Sep 7 '15 at 14:15
0

The fact that 'nopaging' => true and 'posts_per_page' => -1 behaves the same. This parameters are cancelling the LIMIT clause of MySQL request made by your WP.

offset (int) - number of post to displace or pass over. Warning: Setting the offset parameter overrides/ignores the paged parameter and breaks pagination (Click here for a workaround). The 'offset' parameter is ignored when 'posts_per_page'=>-1 (show all posts) is used.

Considering the MySQL docs and their recommendation, you can use a large number for posts_per_page parameter, leaving offset as it is.

To retrieve all rows from a certain offset up to the end of the result set, you can use some large number for the second parameter. This statement retrieves all rows from the 96th row to the last:

SELECT * FROM tbl LIMIT 95,18446744073709551615;

| improve this answer | |
  • I want to select all posts except latest 10. Not the other way around. – Robert hue Sep 7 '15 at 8:28
  • Please check. Sorry for missunderstanding, Hope this would helps. – Ignat B. Sep 7 '15 at 9:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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