4

After reading several posts regarding post category manipulation with a given time I wanted to create a plugin that would manipulate posts after a date. My original post reference is from six years ago: "Plugin for changing a post's category based on it's post date?" but I've referenced several posts regarding manipulation:

However it isn't removing the category and adding the new one:

Code:

function check_if_cat_has_reached_time() {
    // test if term exists and if doesn't return
    $test_term1 = term_exists('Uncategorized', 'category');
    if ($test_term1 !== 0 && $test_term1 !== null) :
        // $default_cat = get_term_by('id', 'Uncategorized', 'category');
        $default_cat = get_cat_ID('uncategorized');
    else :
        return;
    endif;

    // test if term exists and if doesn't return
    $test_term2 = term_exists('failed', 'category');
    if ($test_term2 !== 0 && $test_term2 !== null) :
        $new_cat = get_cat_ID('failed');
    else :
        return;
    endif;

    global $post;

    // the Post ID
    $the_ID = $post->ID;

    // Post creation time in epoch
    // $post_creation   = get_the_time('U',$the_ID);
    // current time in epoch
    $current_time = time();

    // two years in epoch
    $two_years = 31556926*2;

    // post time plus two years
    $post_plus_two = (int)$two_years + (int)get_the_time('U',$the_ID);

    if ($current_time >= $post_plus_two && has_category($default_cat,$the_ID)) :
        wp_remove_object_terms($the_ID, $default_cat, 'category');
        $update_post_cat = array(
            'post_category' => array($new_cat),
        );
        wp_insert_post($update_post_cat);
    endif;
}
add_action('save_post', 'check_if_cat_has_reached_time');

Is there a reason why wp_remove_object_terms() doesn't work? I used this after reading Remove specific category from a post.

So my questions are:

  • Am I removing the category correctly?
  • Am I updating the category correctly?
  • Is there better hook for manipulating posts in a plugin?
  • Would there be any performance issues if this was implemented on a site that had several thousand posts?

EDIT:

The below answer I've tried to implement in my theme's function.php but it still doesn't work. I'm using a default theme with WP Test. Before the function I make sure to create the categories with:

function create_foo_category() {
    wp_insert_term(
        'Foo',
        'category',
        array(
            'description'   => 'This is the Foo',
            'slug'          => 'foo'
        )
    );
}
add_action('after_setup_theme', 'create_foo_category');

then for bar:

function create_bar_category() {
    wp_insert_term(
        'Bar',
        'category',
        array(
            'description'   => 'This is the bar',
            'slug'          => 'bar'
        )
    );
}
add_action('after_setup_theme', 'create_bar_category');

I tried to manipulate the provided answer with:

function check_if_cat_has_reached_time() {
    global $post; 

    $the_ID = $post->ID;

    if (!wp_is_post_revision($the_ID)) {

    // unhook this function so it doesn't loop infinitely
    remove_action('save_post', 'check_if_cat_has_reached_time');

    // test if term exists and if doesn't return
    $check_default_cat = term_exists('Foo', 'category');
    if ($check_default_cat !== 0 && $check_default_cat !== null) {
        $default_cat = get_cat_ID('foo');
    } else {
        return;
    }

    // test if term exists and if doesn't return
    $check_new_cat = term_exists('Bar', 'category');
    if ($check_new_cat !== 0 && $check_new_cat !== null) {
        $new_cat = get_cat_ID('bar');
    } else {
        return;
    }

    // current time in epoch
    $current_time = time();

    // two years in epoch
    $two_years = 31556926*2;

    // post time plus two years
    $post_plus_two = (int)$two_years + (int)get_the_time('U',$the_ID);

    if ($current_time >= $post_plus_two && has_category($default_cat,$the_ID)) {
        wp_remove_object_terms($the_ID, $default_cat, 'category');
        $args = array(
            'ID'            => $the_ID,
            'post_category' => array($new_cat),
        );
        wp_update_post($args);
    }

    // re-hook this function
    add_action('save_post', 'check_if_cat_has_reached_time');
    }
}
add_action('publish_post', 'check_if_cat_has_reached_time');

EDIT:

After talking to a few people I think it was somewhat unclear what the intended purpose of the above was for. I'm looking to modify the posts without actually going into them with something like an event daily. After discussing this issue further I was made aware of wp_schedule_event() that I'm going to test out and edit.

  • At first glance, you may need to use wp_update_post instead of wp_insert_post - this is inserting a brand-new one rather than updating the existing one. – WebElaine Jun 8 '17 at 15:32
1

Your current function works pretty well, but it's hooked on to a post update hook so you need to actually edit/update a post for it to run. If you want the posts to be modified automatically after the specified time then you'll need to set up a scheduled event. I can think of two ways to set up your schedule...

1. A recurring event that checks all (relevant) posts

We can use wp_schedule_event() to run at a hook at specified intervals:

// a custom hook to schedule
add_action( 'wpse_269529_check_posts', 'wpse_269529_check_posts_cats' );

// make sure the event hasn't been scheduled
if( !wp_next_scheduled( 'wpse_269529_check_posts' ) ) {

    // Schedule the event
    wp_schedule_event( time(), 'daily', 'wpse_269529_check_posts' );
}

The paramaters for wp_schedule_event() are (in order); when the first event should run (we can just pass time() to run it from now), how often they should run (one of either "hourly", "twicedaily" or "daily"), and the hook to run. You can also pass some arguments but we don't need that.

We also use wp_next_scheduled() to check that the event hasn't already been scheduled.

Then we need the function that runs on that hook. We can use that to loop through all the posts with the category you want to replace, then update those with the new category:

function wpse_269529_check_posts_cats() {

    //categories
    $old_cat = get_cat_ID( 'foo' );
    $new_cat = get_cat_ID( 'bar' );

    // get all posts with the required category
    $args = array( 'posts_per_page' => -1, 'category' => $old_cat );
    $myposts = get_posts( $args );

    // loop through all posts and update with new category
    foreach ( $myposts as $mypost ) {
        $args = array(
            'ID'            => $mypost->ID,
            'post_category' => array( $new_cat ),
        );
        wp_update_post($args);
    }
}

You mention performance, and this runs a loop through all posts with the category, so maybe it's not the best option...

2. A single scheduled event per post

You can do that with wp_schedule_single_event(), and create the schedule on post creation (note that'll only work on new posts and not existing ones though). You hook a function to publish_post that'll set the schedule:

// runs when a post is published
add_action( 'publish_post', 'wpse_269529_schedule_post_check' );

function wpse_269529_schedule_post_check( $post_id ) {

    // set the time when the event should be scheduled
    $timestamp = strtotime( '+2 years' );

    // Schedule the event
    wp_schedule_single_event( $timestamp, 'wpse_269529_check_post', array( $post_id ) );    
}

Now, because we hooked that on to publish_post, we can pass the post ID to the scheduled event and update that post based on that (remember to set the number of parameters on the action hook to "1" for our ID):

// a custom hook to schedule
add_action( 'wpse_269529_check_post', 'wpse_269529_check_post_cats', 10, 1 );

// replace post categories
function wpse_269529_check_post_cats( $post_id ) {

    //categories
    $old_cat = get_cat_ID( 'foo' );
    $new_cat = get_cat_ID( 'bar' );

    // check for the old category
    if ( has_category( $old_cat, $post_id ) ) {
        // update post with new category
        $args = array(
            'ID'            => $post_id,
            'post_category' => array( $new_cat ),
        );
        wp_update_post($args);
    }
}

You could use a combination of the two and loop through all existing posts on plugin or theme activation (or something else depending on when, why and how you're doing this), then schedule an update per post for all new posts.

1

The save_post hook passes the ID of the post to its callback function, so there's no need to use get_the_ID().

If you use wp_insert_post(), a new post will be inserted to database each time you save a post. Instead, use wp_update_post():

function check_if_cat_has_reached_time($post_id) {
    if ( ! wp_is_post_revision( $post_id ) ){

        // unhook this function so it doesn't loop infinitely
        remove_action('save_post', 'check_if_cat_has_reached_time');

        // test if term exists and if doesn't return
        $check_default_cat = term_exists('Uncategorized', 'category');
        if ($check_default_cat !== 0 && $check_default_cat !== null) {
            $default_cat = get_cat_ID('uncategorized');
        } else {
            return;
        }

        // test if term exists and if doesn't return
        $check_new_cat = term_exists('failed', 'category');
        if ($check_new_cat !== 0 && $check_new_cat !== null) {
            $new_cat = get_cat_ID('failed');
        } else {
            return;
        }

        // post time plus two years
        $post_plus_two = strtotime('+2 years') + strtotime(get_the_date('Y-M-d',$post_id));

        if (strototime(date('Y-M-d')) >= $post_plus_two && has_category($default_cat,$post_id)) {
            wp_remove_object_terms($post_id, $default_cat, 'category');
            $args = array(
                'ID' => $post_id,
                'post_category' => array($new_cat),
            );
            wp_update_post($args);
        }

        // re-hook this function
        add_action('save_post', 'check_if_cat_has_reached_time');
    }
}
add_action('publish_post', 'check_if_cat_has_reached_time');

The remove_action part is important, since wp_update_post fires save_post hook every time it's called, therefore it's possible to create an infinite loop.

  • interesting approach but it still will not remove the category and add a new one. I've used WP Test on two posts that were created in 2013 with the above code except changing the uncategorized to foobar and making sure foobar existed in the category but it wont fire in functions.php – DᴀʀᴛʜVᴀᴅᴇʀ Jun 19 '17 at 19:40
  • @DᴀʀᴛʜVᴀᴅᴇʀ The only possible thing i can think of is, that the conditional doesn't work. Since there is no reason for wp_remove_object_terms not to work. You can try and echo something inside the conditional to see if you can pass through it. Also, do you have another category? A post can not be left without any category, and if you don't assign any, or even have no other category except uncategorized, the code won't work. – Jack Johansson Jun 19 '17 at 19:48
  • bummer.. guess I'll be spending the evening breaking it down.. Guess back to the drawing board. I'll let you know what I find. – DᴀʀᴛʜVᴀᴅᴇʀ Jun 19 '17 at 19:50

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.