wp_set_object_terms and arrays

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

Leave a Comment

Hata!: SQLSTATE[HY000] [1045] Access denied for user 'divattrend_liink'@'localhost' (using password: YES)