[wp-trac] [WordPress Trac] #47351: term_exists() needs to apply_filters('pre_term_name') to match wp_insert/update_term()

WordPress Trac noreply at wordpress.org
Wed May 22 20:00:53 UTC 2019


#47351: term_exists() needs to apply_filters('pre_term_name') to match
wp_insert/update_term()
---------------------------+-----------------------------
 Reporter:  piklistuser23  |      Owner:  (none)
     Type:  defect (bug)   |     Status:  new
 Priority:  normal         |  Milestone:  Awaiting Review
Component:  Taxonomy       |    Version:  5.2.1
 Severity:  normal         |   Keywords:
  Focuses:                 |
---------------------------+-----------------------------
 term_exists() first searches via slug, then failing that searches by name.

 When wp_insert_term()/wp_update_term() format their data they invoke the
 pre_term_name filter which in turn eventually calls esc_html(). This
 causes, eg, any '&' to become '&' in the term name saved in the db.
 (which is horrible practice, but that's beside the point)

 In a hierarchical taxonomy, if there are multiple terms with the same name
 under different parents, the insert/update functions will enforce unique
 slugs. Thus the second and subsequent terms added with the same name will
 no longer be found in term_exists() via the default slug comparison.

 The second test term_exists() does is by comparing the name. However,
 currently term_exists() does not invoke the pre_term_name filter before
 submitting the name for comparison.

 The result is that in the case where multiple terms exist with the same
 name, which name includes contents altered by the pre_term_name filter
 hook, term_exists() will fail to find them even though they do exist.

 Subsequent attempts in code to wp_insert_term() the alleged missing term
 (a common pattern) will of course fail and report a duplicate collision.

 How to fix:
 in wp-includes/taxonomy.php, in the function term_exists(), near the
 beginning,

 replacing:
         $term = trim( wp_unslash( $term ) );
         $slug = sanitize_title( $term );
 with:
         $term = trim( wp_unslash( $term ) );
         $slug = sanitize_title( $term );
         $term = apply_filters( "pre_term_name", $term, $taxonomy );

 solves this.


 Example:

 taxonomy 'tax':
 id parent term
 1 0 Foo
 2 1 a & b  -> slug generated: a-b
 3 0 Bar
 4 3 a & b  -> slug generated: a-b-bar

 the db will have name = 'a & b'
 term_exists('a & b', 'tax', 1):
 will test via slug 'a-b' and succeed and return the result

 term_exists('a & b', 'tax', 3):
 will test via slug 'a-b' and fail to match 'a-b-bar'
 will then test name 'a & b' and fail to match 'a & b'
 will return no result!

 With the fix,
 term_exists('a & b', 'tax', 3):
 will still fail the slug comparison
 will then test name using the correct value 'a & b' and find the match
 as it should.


 Another potential issue I haven't tested yet is with the slug naming.
 Given the above example, what happens if you added a top-level term id 5,
 name 'a & b'? Since id 2 has used the 'a-b' slug, how would it make the
 new slug unique--it has no parent slug to append. Perhaps making the slug
 always append the parent slug is something else that may need to be
 considered--if that were the behavior already I would never have noticed
 this bug since my case would not have failed the slug comparison to expose
 the issue with the name comparison.

 In addition, I don't know why it's testing slug first anyways or at all
 even, since the function signature as documented is accepting 'name' it
 should start and end there, especially since users can edit slugs
 arbitrarily. If I swapped the slugs between terms 2 and 4 in the above
 example, it would be a perfectly valid way to use the system, yet
 term_exists() would now fail its first slug comparison query every time
 and have to do the name query anyways--an extra query every time rather
 than just testing name first and only.

 Another possible improvement, since the slug test is probably relied upon
 in existing code and can't be removed now, would be to look at the $term
 value and see if it looks like a slug format. If so, do the slug query
 first, if not, do the name query first. A simple strpos($term, '-') test
 and/or $term == $slug test could potentially save a lot of extra queries.

 The better/correct solution to the above would be to actually remove the
 excessive default pre_term_name filters in wp_insert|update_term() and
 only filter the content to minimal SQL grammar safety--leaving the user
 content opaque and unmangled in the database. But, this would require
 updating the db contents when deploying the change, and could adversely
 affect existing client code that deals with this very issue.

-- 
Ticket URL: <https://core.trac.wordpress.org/ticket/47351>
WordPress Trac <https://core.trac.wordpress.org/>
WordPress publishing platform


More information about the wp-trac mailing list