4

I need to add a category to about 2500 posts, and either need to use an SQL query or wp_set_object_terms with PHP. I'm looking at wp_set_object_terms so I can add a category and not impact the currently assigned categories.

The function below that uses wp_set_object_terms' (from the WordPress Codex) adds one or more categories from an array (in the case of the example, categories 6 and 8) to the post with the ID of 42 (in the example).

But I need to be able to add an array of post IDs ($object_id), not an array of categories.

According to https://codex.wordpress.org/Function_Reference/wp_set_object_terms, $object_id has to be a single integer.

How can I use an array of ~2500 post IDs for $object_id? Do I need to a for each loop and run through the array and process each category addition to each post ID one at a time?

From https://codex.wordpress.org/Function_Reference/wp_set_object_terms :

// An array of IDs of categories we to add to this post. (Not using an array)
$cat_ids = array( 6, 8 );

// Add these categories, note the last argument is true.
// (42 is the $object_id; I will hardcode the category ID)
$term_taxonomy_ids = wp_set_object_terms( 42, $cat_ids, 'category', true );

if ( is_wp_error( $term_taxonomy_ids ) ) {
    // There was an error somewhere and the terms couldn't be set.
} else {
    // Success! These categories were added to the post.
}
4

I doubt it would justify the effort of writing custom SQL queries for only 2500 posts.

Within the wp_set_object_terms( $object_id, ... ) function we have:

$object_id = (int) $object_id;

so it's correct that it only takes a single post id as input.

So you would need to loop over your $post_ids array but it might be sufficient to use the wp_defer_term_counting() trick1):

/**
 * Add terms to posts (with debug display)
 */
function wpse_terms_to_posts( $terms, $taxonomy, $append, &$post_ids )
{
    wp_defer_term_counting( true );

    print '<ul>';
    foreach( (array) $post_ids as $post_id )
    {
        $tt_ids = wp_set_object_terms( $post_id, $terms, $taxonomy, $append );

        print '<li>';
        if( is_wp_error( $tt_ids ) )
            printf(
                'Problem adding terms to post_id: %d - Error: %s',
                $post_id,
                $tt_ids->get_error_message()
            );
    else
            print 'Success adding terms to post_id: ' . $post_id ;
        print '</li>';
    }
    print '</ul>';

    wp_defer_term_counting( false );
}

Here we pass $post_ids by reference in case it's very large.

Usage (PHP 5.4+) :

wpse_terms_to_posts(
    $terms     = [ 6, 8 ],
    $taxonomy  = 'category',
    $append    = true,
    $post_ids  = [ 123, 234, 345 ]
);

We could even slice the $post_ids array, if 2500 is too much for our system in a single run.

Here's an example how we can run the function with admin-ajax.php with such a slicing feature:

We create our own action with:

/**
 * Custom wp ajax action to activate our wpse_terms_to_posts() function
 * 
 * Example: /wp-admin/admin-ajax.php
 *          ?action=wpse-terms-to-posts&wpse_offset=0&wpse_length=500
 */
add_action( 'wp_ajax_wpse-terms-to-posts', function()
{
    $offset = filter_input( INPUT_GET, 'wpse_offset', FILTER_SANITIZE_NUMBER_INT );
    $length = filter_input( INPUT_GET, 'wpse_length', FILTER_SANITIZE_NUMBER_INT );

    // Check for user capability and non empty user input:
    if( current_user_can( 'manage_options' ) && '' !== $offset && '' !== $length  )
    {
        print '<h3>Start:</h3>';      
        $start = microtime( true );

        // Settings - Edit to your needs:
        $terms     = [ 6, 8 ];
        $taxonomy  = 'category';
        $append    = true;
        $post_ids  = [ 123, 234, 345, 456, 567, 678, 789 ];

        // Add terms to posts:
        wpse_terms_to_posts (
            $terms,
            $taxonomy,
            $append,
            array_slice( $post_ids, $offset, $length )
        );

        printf( '<h3>Done in %f seconds</h3>', microtime( true ) - $start );         
        exit();
    }

} );

where we use the wpse_offset and wpse_length GET parameters to slice the posts array.

If we want to slice the post ids array into chunks of 500, we can activate it manually with:

/wp-admin/admin-ajax.php?action=wpse-terms-to-posts&wpse_offset=0&wpse_length=500

/wp-admin/admin-ajax.php?action=wpse-terms-to-posts&wpse_offset=500&wpse_length=500

/wp-admin/admin-ajax.php?action=wpse-terms-to-posts&wpse_offset=1000&wpse_length=500

/wp-admin/admin-ajax.php?action=wpse-terms-to-posts&wpse_offset=1500&wpse_length=500

/wp-admin/admin-ajax.php?action=wpse-terms-to-posts&wpse_offset=2000&wpse_length=500

That way we can check for any term errors.

Hope you can modify this to your needs.

1) wp_defer_term_counting() was first mentioned on WPSE in this answer by @JanFabry

  • Thanks for the effort! Looks to be exactly what I need. Sorry, just learning here: where does the "usage" wpse_terms_to_posts ( $terms... go? Inside the function? I get a parse error on it. – markratledge Jun 22 '15 at 3:30
  • I assume PHP 5.4+ by using the [] array notation, that could be the parse error you got? I updated the answer with an example how you can activate the wpse_terms_to_posts function via admin-ajax.php. @markratledge – birgire Jun 22 '15 at 10:01
  • I love this approach. I had a foreach loop in mind, but with the huge amount of posts, the actual operation can bom out due to memory or time. :-). Your approach caters well for this issues – Pieter Goosen Jun 22 '15 at 10:03
  • It's handy if we want to watch out for errors (then I usually don't print out the successful inserts ). One could also log the errors into a file instead. If the number of manual "slice" calls is too large, then I sometimes use a linux cron script with curl or wget. Then we need a no_priv type of action. We could also check out the upgrade process from the multisite with auto refresh from javascript, where the browser page must be open during the process. @PieterGoosen – birgire Jun 22 '15 at 10:10
  • Very nice; let me check this out later today. – markratledge Jun 22 '15 at 19:29

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.