Opened 5 weeks ago
Last modified 5 weeks ago
#65805 new defect (bug)
wp_set_object_terms() casts numeric term IDs to strings via array_unique(), causing false matching by term name
| Reported by: | badwolfgr | Owned by: | |
|---|---|---|---|
| Priority: | normal | Milestone: | Awaiting Review |
| Component: | Taxonomy | Version: | 7.0.2 |
| Severity: | normal | Keywords: | reporter-feedback |
| Cc: | Focuses: |
Description
When passing an array of integer term IDs to wp_set_object_terms() where the term IDs represent terms that consist purely of numbers (e.g. term ID 725 for a color named "01"), WordPress incorrectly treats the term ID as a term name.
Steps to reproduce:
- Create a term in a taxonomy (e.g. 'pa_color') with the name "01". Assume it is assigned Term ID
725. - Create a product or post.
- Pass the integer term ID in an array to
wp_set_object_terms( $post_id, [ 725 ], 'pa_color' ).
Expected behavior:
WordPress should assign Term ID 725 (Name: "01") to the post, because passing integers to wp_set_object_terms should be treated as Term IDs.
Actual behavior:
Inside wp_set_object_terms(), the $terms array is passed through array_unique(). Because PHP's array_unique() defaults to SORT_STRING, it silently casts the integer 725 into the string "725".
Then, inside the foreach loop, $term is a string. When passed to term_exists( $term, $taxonomy ), WordPress searches for a term with the Name "725" instead of the ID 725.
If a term named "725" doesn't exist, it incorrectly creates a brand new term named "725".
Proposed Fix:
In wp-includes/taxonomy.php inside wp_set_object_terms(), the array_unique() call should use SORT_REGULAR or strictly preserve integers so numeric Term IDs are not cast to strings before being evaluated by term_exists().
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)
Hi @badwolfgr, thanks for the report.
I went looking for that array_unique() call and I can't find it. wp_set_object_terms() doesn't have one. Here's the relevant stretch on 7.0:
It goes straight from the is_array() check into the loop. taxonomy.php does use array_unique() in four spots (3567, 3699, 3850, 4160), but those are deferred term counts, taxonomy lists, cache priming and object types. None of them ever see $terms.
The other thing is array_unique() wouldn't cast anything even if it were there. SORT_STRING only affects how elements get compared; what comes back keeps its original types. So array_unique( array( 725 ) ) hands you back int 725, and SORT_REGULAR wouldn't change a thing.
You might be thinking of wp_set_post_terms(), which does call it:
but intval() runs first there and the ints come through fine. Core counts on that, actually. If array_unique() stringified its output, the is_int() guard at taxonomy.php:2861 would never fire for hierarchical taxonomiy ID would create a categorycalled 999. There's a test pinning that at tests/phpunit/tests/term/wpSetObjectTerms.php:428.
What I suspect you're actually hitting is a string. term_exists() checks is_int() at line 1637, and anything that isn't a real int gets looked up as a slug and then as a name. The
guard at 2861 only skips ints, hes nothing falls through towp_insert_term() and you get a brand new term. Which is what you're seeing.
These two do completely different things:
Could you var_dump() the array right before the call? print_r() renders 725 and '725' identically so it's easy to miss. Strings sneak in from $wpdb results (every column comes back as a string), $_POST, RESTs, array_keys().
The pa_color detail might not bt_post_terms() only does theintval() pass for hierarchical taxonomies (post.php:5735), and WooCommerce's pa_* attributes
aren't hierarchical, so nothing.
To unblock yourself: