From ec49827b0baf58ccdab1ad207c0df6ea604711ee Mon Sep 17 00:00:00 2001 From: Boone Gorges Date: Mon, 16 Mar 2015 11:15:34 +0000 Subject: [PATCH] In `wp_insert_term()`, allow a term with an existing name if a unique `$slug` has been provided. `wp_insert_term()` protects against the creation of terms with duplicate names at the same level of a taxonomy hierarchy. However, it's historically been possible to override this protection by explicitly providing a value of `$slug` that is unique at the hierarchy tier. This ability was broken in [31734], and the current changeset restores the original behavior. A number of unit tests are added and refactored in support of these changes. See #17689 for discussion of a fix that was superceded by [31734]. This commit retains the fix for the underlying bug described in that ticket. See #31328. git-svn-id: https://develop.svn.wordpress.org/trunk@31792 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/taxonomy.php | 30 ++-- tests/phpunit/tests/term.php | 272 +++++++++++++++++++++++++---------- 2 files changed, 220 insertions(+), 82 deletions(-) diff --git a/src/wp-includes/taxonomy.php b/src/wp-includes/taxonomy.php index c7a923e8da..92ec413728 100644 --- a/src/wp-includes/taxonomy.php +++ b/src/wp-includes/taxonomy.php @@ -2903,15 +2903,29 @@ function wp_insert_term( $term, $taxonomy, $args = array() ) { } } - // Terms with duplicate names are not allowed at the same level of a taxonomy hierarchy. - if ( $existing_term = get_term_by( 'name', $name, $taxonomy ) ) { - if ( is_taxonomy_hierarchical( $taxonomy ) ) { - $siblings = get_terms( $taxonomy, array( 'fields' => 'names', 'get' => 'all', 'parent' => $parent ) ); - if ( in_array( $name, $siblings ) ) { - return new WP_Error( 'term_exists', __( 'A term with the name already exists with this parent.' ), $existing_term->term_id ); + /* + * Prevent the creation of terms with duplicate names at the same level of a taxonomy hierarchy, + * unless a unique slug has been explicitly provided. + */ + if ( $name_match = get_term_by( 'name', $name, $taxonomy ) ) { + $slug_match = get_term_by( 'slug', $slug, $taxonomy ); + if ( ! $slug_provided || $name_match->slug === $slug || $slug_match ) { + if ( is_taxonomy_hierarchical( $taxonomy ) ) { + $siblings = get_terms( $taxonomy, array( 'get' => 'all', 'parent' => $parent ) ); + + $existing_term = null; + if ( $name_match->slug === $slug && in_array( $name, wp_list_pluck( $siblings, 'name' ) ) ) { + $existing_term = $name_match; + } elseif ( $slug_match && in_array( $slug, wp_list_pluck( $siblings, 'slug' ) ) ) { + $existing_term = $slug_match; + } + + if ( $existing_term ) { + return new WP_Error( 'term_exists', __( 'A term with the name already exists with this parent.' ), $existing_term->term_id ); + } + } else { + return new WP_Error( 'term_exists', __( 'A term with the name already exists in this taxonomy.' ), $name_match->term_id ); } - } else { - return new WP_Error( 'term_exists', __( 'A term with the name already exists in this taxonomy.' ), $existing_term->term_id ); } } diff --git a/tests/phpunit/tests/term.php b/tests/phpunit/tests/term.php index 712663efab..dd7e3730e0 100644 --- a/tests/phpunit/tests/term.php +++ b/tests/phpunit/tests/term.php @@ -174,9 +174,7 @@ class Tests_Term extends WP_UnitTestCase { // Test existing term name with unique slug $term1 = $this->factory->tag->create( array( 'name' => 'Bozo', 'slug' => 'bozo1' ) ); - $this->assertTrue( is_wp_error( $term1 ) ); - $this->assertSame( 'term_exists', $term1->get_error_code() ); - $this->assertEquals( $term->term_id, $term1->get_error_data() ); + $this->assertFalse( is_wp_error( $term1 ) ); // Test an existing term name $term2 = $this->factory->tag->create( array( 'name' => 'Bozo' ) ); @@ -227,6 +225,203 @@ class Tests_Term extends WP_UnitTestCase { $this->assertFalse( is_wp_error( $term20 ) ); } + /** + * @ticket 31328 + */ + public function test_wp_insert_term_should_not_allow_duplicate_names_when_slug_is_a_duplicate_of_the_same_term_in_non_hierarchical_taxonomy() { + register_taxonomy( 'wptests_tax', 'post' ); + $t1 = $this->factory->term->create( array( + 'name' => 'Foo', + 'slug' => 'foo', + 'taxonomy' => 'wptests_tax', + ) ); + + $t2 = wp_insert_term( 'Foo', 'wptests_tax', array( + 'slug' => 'foo', + ) ); + + $this->assertWPError( $t2 ); + $this->assertSame( 'term_exists', $t2->get_error_code() ); + } + + /** + * @ticket 31328 + */ + public function test_wp_insert_term_should_not_allow_duplicate_names_when_slug_is_a_duplicate_of_a_different_term_in_non_hierarchical_taxonomy() { + register_taxonomy( 'wptests_tax', 'post' ); + $t1 = $this->factory->term->create( array( + 'name' => 'Foo', + 'slug' => 'foo', + 'taxonomy' => 'wptests_tax', + ) ); + + $t2 = $this->factory->term->create( array( + 'name' => 'Bar', + 'slug' => 'bar', + 'taxonomy' => 'wptests_tax', + ) ); + + $t3 = wp_insert_term( 'Foo', 'wptests_tax', array( + 'slug' => 'bar', + ) ); + + $this->assertWPError( $t3 ); + $this->assertSame( 'term_exists', $t3->get_error_code() ); + } + + /** + * @ticket 31328 + */ + public function test_wp_insert_term_should_allow_duplicate_names_when_a_unique_slug_has_been_provided_in_non_hierarchical_taxonomy() { + register_taxonomy( 'wptests_tax', 'post' ); + $t1 = $this->factory->term->create( array( + 'name' => 'Foo', + 'slug' => 'foo', + 'taxonomy' => 'wptests_tax', + ) ); + + $t2 = wp_insert_term( 'Foo', 'wptests_tax', array( + 'slug' => 'foo-unique', + ) ); + + $this->assertFalse( is_wp_error( $t2 ) ); + + $t2_term = get_term( $t2['term_id'], 'wptests_tax' ); + $this->assertSame( 'foo-unique', $t2_term->slug ); + $this->assertSame( 'Foo', $t2_term->name ); + } + + /** + * @ticket 31328 + */ + public function test_wp_insert_term_should_not_allow_duplicate_names_when_the_slug_is_not_provided_in_non_hierarchical_taxonomy() { + register_taxonomy( 'wptests_tax', 'post' ); + $t1 = $this->factory->term->create( array( + 'name' => 'Foo', + 'slug' => 'foo', + 'taxonomy' => 'wptests_tax', + ) ); + + $t2 = wp_insert_term( 'Foo', 'wptests_tax' ); + + $this->assertWPError( $t2 ); + $this->assertSame( 'term_exists', $t2->get_error_code() ); + } + + /** + * @ticket 31328 + */ + public function test_wp_insert_term_should_not_allow_duplicate_names_when_slug_is_a_duplicate_of_the_same_term_in_hierarchical_taxonomy() { + register_taxonomy( 'wptests_tax', 'post', array( 'hierarchical' => true ) ); + $t1 = $this->factory->term->create( array( + 'name' => 'Foo', + 'slug' => 'foo', + 'taxonomy' => 'wptests_tax', + ) ); + + $t2 = wp_insert_term( 'Foo', 'wptests_tax', array( + 'slug' => 'foo', + ) ); + + $this->assertWPError( $t2 ); + $this->assertSame( 'term_exists', $t2->get_error_code() ); + } + + /** + * @ticket 31328 + */ + public function test_wp_insert_term_should_not_allow_duplicate_names_when_slug_is_a_duplicate_of_a_different_term_at_same_hierarchy_level_in_hierarchical_taxonomy() { + register_taxonomy( 'wptests_tax', 'post', array( 'hierarchical' => true ) ); + $t1 = $this->factory->term->create( array( + 'name' => 'Foo', + 'slug' => 'foo', + 'taxonomy' => 'wptests_tax', + ) ); + + $t2 = $this->factory->term->create( array( + 'name' => 'Bar', + 'slug' => 'bar', + 'taxonomy' => 'wptests_tax', + ) ); + + $t3 = wp_insert_term( 'Foo', 'wptests_tax', array( + 'slug' => 'bar', + ) ); + + $this->assertWPError( $t3 ); + $this->assertSame( 'term_exists', $t3->get_error_code() ); + } + + /** + * @ticket 31328 + */ + public function test_wp_insert_term_should_allow_duplicate_names_when_slug_is_a_duplicate_of_a_term_at_different_hierarchy_level_in_hierarchical_taxonomy() { + register_taxonomy( 'wptests_tax', 'post', array( 'hierarchical' => true ) ); + $t1 = $this->factory->term->create( array( + 'name' => 'Foo', + 'slug' => 'foo', + 'taxonomy' => 'wptests_tax', + ) ); + + $t2 = $this->factory->term->create(); + + $t3 = $this->factory->term->create( array( + 'name' => 'Bar', + 'slug' => 'bar', + 'parent' => $t2, + 'taxonomy' => 'wptests_tax', + ) ); + + $t4 = wp_insert_term( 'Foo', 'wptests_tax', array( + 'slug' => 'bar', + ) ); + + $this->assertFalse( is_wp_error( $t4 ) ); + $t4_term = get_term( $t4['term_id'], 'wptests_tax' ); + + // `wp_unique_term_slug()` allows term creation but iterates the slug. + $this->assertSame( 'bar-2', $t4_term->slug ); + $this->assertSame( 'Foo', $t4_term->name ); + } + + /** + * @ticket 31328 + */ + public function test_wp_insert_term_should_allow_duplicate_names_when_a_unique_slug_has_been_provided_in_hierarchical_taxonomy() { + register_taxonomy( 'wptests_tax', 'post', array( 'hierarchical' => true ) ); + $t1 = $this->factory->term->create( array( + 'name' => 'Foo', + 'slug' => 'foo', + 'taxonomy' => 'wptests_tax', + ) ); + + $t2 = wp_insert_term( 'Foo', 'wptests_tax', array( + 'slug' => 'foo-unique', + ) ); + + $this->assertFalse( is_wp_error( $t2 ) ); + + $t2_term = get_term( $t2['term_id'], 'wptests_tax' ); + $this->assertSame( 'foo-unique', $t2_term->slug ); + $this->assertSame( 'Foo', $t2_term->name ); + } + + /** + * @ticket 31328 + */ + public function test_wp_insert_term_should_not_allow_duplicate_names_when_the_slug_is_not_provided_in_hierarchical_taxonomy() { + register_taxonomy( 'wptests_tax', 'post', array( 'hierarchical' => true ) ); + $t1 = $this->factory->term->create( array( + 'name' => 'Foo', + 'slug' => 'foo', + 'taxonomy' => 'wptests_tax', + ) ); + + $t2 = wp_insert_term( 'Foo', 'wptests_tax' ); + + $this->assertWPError( $t2 ); + $this->assertSame( 'term_exists', $t2->get_error_code() ); + } /** * @ticket 5809 */ @@ -370,77 +565,6 @@ class Tests_Term extends WP_UnitTestCase { $this->assertSame( 0, $created_term->term_group ); } - public function test_wp_insert_term_duplicate_name_slug_non_hierarchical() { - register_taxonomy( 'foo', 'post', array() ); - - $existing_term = $this->factory->term->create( array( - 'slug' => 'new-term', - 'name' => 'New Term', - 'taxonomy' => 'foo', - ) ); - - $found = wp_insert_term( 'New Term', 'foo', array( - 'slug' => 'new-term', - ) ); - - _unregister_taxonomy( 'foo' ); - - $this->assertTrue( is_wp_error( $found ) ); - $this->assertEquals( $existing_term, $found->get_error_data() ); - } - - public function test_wp_insert_term_duplicate_name_hierarchical() { - register_taxonomy( 'foo', 'post', array( - 'hierarchical' => true, - ) ); - - $parent_term = $this->factory->term->create( array( - 'taxonomy' => 'foo', - ) ); - - $existing_term = $this->factory->term->create( array( - 'name' => 'New Term', - 'taxonomy' => 'foo', - 'parent' => $parent_term, - ) ); - - $found = wp_insert_term( 'New Term', 'foo', array( - 'parent' => $parent_term, - ) ); - - _unregister_taxonomy( 'foo' ); - - $this->assertTrue( is_wp_error( $found ) ); - $this->assertEquals( $existing_term, $found->get_error_data() ); - } - - public function test_wp_insert_term_duplicate_name_slug_hierarchical() { - register_taxonomy( 'foo', 'post', array( - 'hierarchical' => true, - ) ); - - $parent_term = $this->factory->term->create( array( - 'taxonomy' => 'foo', - ) ); - - $existing_term = $this->factory->term->create( array( - 'name' => 'New Term', - 'slug' => 'new-term-slug', - 'taxonomy' => 'foo', - 'parent' => $parent_term, - ) ); - - $found = wp_insert_term( 'New Term', 'foo', array( - 'parent' => $parent_term, - 'slug' => 'new-term-slug', - ) ); - - _unregister_taxonomy( 'foo' ); - - $this->assertTrue( is_wp_error( $found ) ); - $this->assertEquals( $existing_term, $found->get_error_data() ); - } - /** * @ticket 5809 */