Delete expired posts after a number of days after they expired

I use the below code to delete custom posts with status ‘expired’ (thanks to Jamie Keefer). Posts are set as ‘expired’ by a 3rd party plugin. Users have only a frontend access to their posts (adverts).

My question is: how to delete them after a number of days after they expired if post authors don’t republish them? Also, I will appreciate any suggestions about how to improve this code.

// expired_post_delete hook fires when the Cron is executed
add_action( 'expired_post_delete', 'delete_expired_posts' );

// This function will run once the 'expired_post_delete' is called
function delete_expired_posts() {

    $todays_date = current_time('mysql');

    $args = array(
        'post_type' => 'advert',
        'post_status' => 'expired',
        'posts_per_page' => -1
    );

    $posts = new WP_Query( $args );

    // The Loop
    if ( $posts->have_posts() ) {

        while ( $posts->have_posts() ) {
            $posts->the_post();
            wp_delete_post(get_the_ID());
        }

    } else {
            // no posts found
    }

    /* Restore original Post Data */
    wp_reset_postdata();
}

// Add function to register event to WordPress init
add_action( 'init', 'register_daily_post_delete_event');

// Function which will register the event
function register_daily_post_delete_event() {
    // Make sure this event hasn't been scheduled
    if( !wp_next_scheduled( 'expired_post_delete' ) ) {
        // Schedule the event
        wp_schedule_event( time(), 'daily', 'expired_post_delete' );
    }
}

UPDATE

This is how posts are set as ‘expired’:

add_action( 'adverts_event_expire_ads', 'adverts_event_expire_ads' );

/**
 * Expires ads
 * 
 * Function finds Adverts that already expired (value in _expiration_date
 * meta field is lower then current timestamp) and changes their status to 'expired'.
 * 
 * @since 0.1
 * @return void
 */
function adverts_event_expire_ads() {

    // find adverts with status 'publish' which exceeded expiration date
    // (_expiration_date is a timestamp)
    $posts = new WP_Query( array( 
        "post_type" => "advert",
        "post_status" => "publish",
        "meta_query" => array(
            array(
                "key" => "_expiration_date",
                "value" => current_time( 'timestamp' ),
                "compare" => "<="
            )
        )
    ) );


    if( $posts->post_count ) {
        foreach($posts->posts as $post) {
            // change post status to expired.
            $update = wp_update_post( array( 
                "ID" => $post->ID,
                "post_status" => "expired"
            ) );
        } // endforeach
    } // endif

}

2 Answers
2

WP Cron jobs are not reliable as it needs someone to visit the site at the time the event should fire. If you need precise timing, you should use server cron jobs.

Anyways, lets look at your code and what is wrong and we can fix it

  • wp is a better hook to use to hook your scheduled event, this is the earliest that postdata available. init is way to early. I probably think for safety, you can also try the template_redirect hook.

  • Your code are very very expensive to run which unnecessarily wastes server resources. We need to look at the following:

    • We do not need any postdata except the post ID. Any other postdata are useless, and querying it waste a lot of resources. On large sites, this can actually lead to fatal errors due to timing out

    • We only need to query posts which has expired and the expiry date has reached a certain timeframe.

Lets put everything in code: (NOTE: This all untested, I have also copied and pasted some of your code, so I might have missed something, and also, the code requires PHP 5.4+)

function get_exired_posts_to_delete()
{
    /**
     * If you need posts that expired more than a week ago, we would need to
     * get the unix time stamp of the day a week ago. You can adjust the relative 
     * date and time formats as needed. 
     * @see http://php.net/manual/en/function.strtotime.php
     * @see http://php.net/manual/en/datetime.formats.php
     */
    // As example, we need to get posts that has expired more than 7days ago
    $past = strtotime( "- 1 week" );

    // Set our query arguments
    $args = [
        'fields'         => 'ids', // Only get post ID's to improve performance
        'post_type'      => 'advert',
        'post_status'    => 'expired',
        'posts_per_page' => -1,
        'meta_query'     => [
            [
                'key'     => '_expiration_date',
                'value'   => $past,
                'compare' => '<='
            ]
        ]
    ];
    $q = get_posts( $args );

    // Check if we have posts to delete, if not, return false
    if ( !$q )
        return false;

    // OK, we have posts to delete, lets delete them
    foreach ( $q as $id )
        wp_delete_post( $id );
}

Now we can create our custom hook to hook our function

// expired_post_delete hook fires when the Cron is executed
add_action( 'expired_post_delete', 'get_exired_posts_to_delete' );

Lastly, schedule our event

// Add function to register event to wp
add_action( 'wp', 'register_daily_post_delete_event');
function register_daily_post_delete_event() {
    // Make sure this event hasn't been scheduled
    if( !wp_next_scheduled( 'expired_post_delete' ) ) {
        // Schedule the event
        wp_schedule_event( time(), 'daily', 'expired_post_delete' );
    }
}

This should about do it, just remember to set the correct time frame inside the function

Leave a Comment