diff --git a/src/classes/class-tainacan-bulk-edit.php b/src/classes/class-tainacan-bulk-edit.php index 8a1222aba..04cd155db 100644 --- a/src/classes/class-tainacan-bulk-edit.php +++ b/src/classes/class-tainacan-bulk-edit.php @@ -439,26 +439,32 @@ class Bulk_Edit { private function _add_value(Entities\Metadatum $metadatum, $value) { global $wpdb; $type = $metadatum->get_metadata_type_object(); + $taxRepo = Repositories\Taxonomies::get_instance(); if ($type->get_primitive_type() == 'term') { $options = $metadatum->get_metadata_type_options(); $taxonomy_id = $options['taxonomy_id']; - $tax = Repositories\Taxonomies::get_instance()->fetch($taxonomy_id); + $tax = $taxRepo->fetch($taxonomy_id); if ($tax instanceof Entities\Taxonomy) { - $term = term_exists($value, $tax->get_db_identifier()); + $term = $taxRepo->term_exists($tax, $value, 0, true); + $term_id = false; - if (!is_array($term)) { + if (false === $term) { $term = wp_insert_term($value, $tax->get_db_identifier()); + if (is_WP_Error($term) || !isset($term['term_taxonomy_id'])) { + return new \WP_Error( 'error', __( 'Error adding term', 'tainacan' ) ); + } + $term_id = $term['term_taxonomy_id']; + } else { + $term_id = $term->term_taxonomy_id; } - if (is_WP_Error($term) || !isset($term['term_taxonomy_id'])) { - return new \WP_Error( 'error', __( 'Error adding term', 'tainacan' ) ); - } + - $insert_q = $this->_build_select( $wpdb->prepare("post_id, %d", $term['term_taxonomy_id']) ); + $insert_q = $this->_build_select( $wpdb->prepare("post_id, %d", $term_id) ); $query = "INSERT IGNORE INTO $wpdb->term_relationships (object_id, term_taxonomy_id) $insert_q"; @@ -506,28 +512,29 @@ class Bulk_Edit { private function _remove_value(Entities\Metadatum $metadatum, $value) { global $wpdb; $type = $metadatum->get_metadata_type_object(); + $taxRepo = Repositories\Taxonomies::get_instance(); if ($type->get_primitive_type() == 'term') { $options = $metadatum->get_metadata_type_options(); $taxonomy_id = $options['taxonomy_id']; - $tax = Repositories\Taxonomies::get_instance()->fetch($taxonomy_id); + $tax = $taxRepo->fetch($taxonomy_id); if ($tax instanceof Entities\Taxonomy) { - $term = term_exists($value, $tax->get_db_identifier()); + $term = $taxRepo->term_exists($tax, $value, null, true); if (!$term) { return 0; } - if (is_WP_Error($term) || !isset($term['term_taxonomy_id'])) { + if ( !isset($term->term_taxonomy_id) ) { return new \WP_Error( 'error', __( 'Term not found', 'tainacan' ) ); } $delete_q = $this->_build_select( "post_id" ); - $query = $wpdb->prepare( "DELETE FROM $wpdb->term_relationships WHERE term_taxonomy_id = %d AND object_id IN ($delete_q)", $term['term_taxonomy_id'] ); + $query = $wpdb->prepare( "DELETE FROM $wpdb->term_relationships WHERE term_taxonomy_id = %d AND object_id IN ($delete_q)", $term->term_taxonomy_id ); return $wpdb->query($query); @@ -566,42 +573,48 @@ class Bulk_Edit { return new \WP_Error( 'error', __( 'New value and old value can not be the same', 'tainacan' ) ); } + $taxRepo = Repositories\Taxonomies::get_instance(); $type = $metadatum->get_metadata_type_object(); if ($type->get_primitive_type() == 'term') { $options = $metadatum->get_metadata_type_options(); $taxonomy_id = $options['taxonomy_id']; - $tax = Repositories\Taxonomies::get_instance()->fetch($taxonomy_id); + $tax = $taxRepo->fetch($taxonomy_id); if ($tax instanceof Entities\Taxonomy) { // check old term - $term = term_exists($value, $tax->get_db_identifier()); + $term = $taxRepo->term_exists($tax, $value, null, true); if (!$term) { return 0; } - if (is_WP_Error($term) || !isset($term['term_taxonomy_id'])) { + if (is_WP_Error($term) || !isset($term->term_taxonomy_id)) { return new \WP_Error( 'error', __( 'Term not found', 'tainacan' ) ); } // check new term - $newterm = term_exists($newvalue, $tax->get_db_identifier()); + $newterm = $taxRepo->term_exists($tax, $newvalue, 0, true); + - if (!is_array($newterm)) { + if (false === $newterm) { $newterm = wp_insert_term($newvalue, $tax->get_db_identifier()); + if (is_WP_Error($newterm) || !isset($newterm['term_taxonomy_id'])) { + return new \WP_Error( 'error', __( 'Error adding term', 'tainacan' ) ); + } + $newtermid = $newterm['term_taxonomy_id']; + } else { + $newtermid = $newterm->term_taxonomy_id; } - if (is_WP_Error($newterm) || !isset($newterm['term_taxonomy_id'])) { - return new \WP_Error( 'error', __( 'Error adding term', 'tainacan' ) ); - } + - $insert_q = $this->_build_select( $wpdb->prepare("post_id, %d", $newterm['term_taxonomy_id']) ); + $insert_q = $this->_build_select( $wpdb->prepare("post_id, %d", $newtermid) ); // only where old_value is present (this is what this method have different from the _add_value()) - $insert_q .= $wpdb->prepare( " AND post_id IN(SELECT object_id FROM $wpdb->term_relationships WHERE term_taxonomy_id = %d)", $term['term_taxonomy_id'] ); + $insert_q .= $wpdb->prepare( " AND post_id IN(SELECT object_id FROM $wpdb->term_relationships WHERE term_taxonomy_id = %d)", $term->term_taxonomy_id ); $query = "INSERT IGNORE INTO $wpdb->term_relationships (object_id, term_taxonomy_id) $insert_q "; diff --git a/src/classes/entities/class-tainacan-taxonomy.php b/src/classes/entities/class-tainacan-taxonomy.php index 8adc445b5..0340334d9 100644 --- a/src/classes/entities/class-tainacan-taxonomy.php +++ b/src/classes/entities/class-tainacan-taxonomy.php @@ -196,5 +196,19 @@ class Taxonomy extends Entity { return parent::validate(); } + + /** + * Check if a term already exists + * + * @param string $term_name The term name + * @param int|null $parent The ID of the parent term to look for children or null to look for terms in any hierarchical position. Default is null + * @param bool $return_term wether to return the term object if it exists. default is to false + * + * @return bool|WP_Term return boolean indicating if term exists. If $return_term is true and term exists, return WP_Term object + */ + function term_exists($term_name, $parent = null, $return_term = false) { + $repo = $this->get_repository(); + return $repo->term_exists($this, $term_name, $parent, $return_term); + } } \ No newline at end of file diff --git a/src/classes/entities/class-tainacan-term.php b/src/classes/entities/class-tainacan-term.php index 57ba881c9..2f7b4c3b5 100644 --- a/src/classes/entities/class-tainacan-term.php +++ b/src/classes/entities/class-tainacan-term.php @@ -217,41 +217,18 @@ class Term extends Entity { $parent = $this->get_parent(); $name = $this->get_name(); $taxonomy = $this->get_taxonomy(); - - /** - * Code from WordPress Core, taxonomy.php#2070 - */ - - /* - * Prevent the creation of terms with duplicate names at the same level of a taxonomy hierarchy, - * unless a unique slug has been explicitly provided. - */ - $name_matches = get_terms( $taxonomy, array( - 'name' => $name, - 'hide_empty' => false, - 'parent' => $parent, - 'exclude' => $this->get_id() - ) ); - - /* - * The `name` match in `get_terms()` doesn't differentiate accented characters, - * so we do a stricter comparison here. - */ - $name_match = null; - if ( $name_matches ) { - foreach ( $name_matches as $_match ) { - if ( is_object($_match) && isset($_match) && strtolower( $name ) === strtolower( $_match->name ) ) { - $name_match = $_match; - break; - } + + $repo = $this->get_repository(); + + $term_exists = $repo->term_exists($name, $taxonomy, $parent, true); + + if (false !== $term_exists) { + if ($this->get_id() != $term_exists->term_taxonomy_id) { + $this->add_error( 'repeated', __('You can not have two terms with the same name at the same level', 'tainacan') ); + return false; } } - if ($name_match) { - $this->add_error( 'repeated', __('You can not have two terms with the same name at the same level', 'tainacan') ); - return false; - } - $this->set_as_valid(); return true; diff --git a/src/classes/metadata-types/taxonomy/class-tainacan-taxonomy.php b/src/classes/metadata-types/taxonomy/class-tainacan-taxonomy.php index 56b20a667..d77c827f6 100644 --- a/src/classes/metadata-types/taxonomy/class-tainacan-taxonomy.php +++ b/src/classes/metadata-types/taxonomy/class-tainacan-taxonomy.php @@ -126,6 +126,7 @@ class Taxonomy extends Metadata_Type { $term = $term->get_id(); } + // TODO term_exists is not fully reliable. Use $terms_repository->term_exists. see issue #159 if (!term_exists($term)) { $valid = false; break; diff --git a/src/classes/repositories/class-tainacan-taxonomies.php b/src/classes/repositories/class-tainacan-taxonomies.php index 79844c133..3e83e0973 100644 --- a/src/classes/repositories/class-tainacan-taxonomies.php +++ b/src/classes/repositories/class-tainacan-taxonomies.php @@ -343,6 +343,21 @@ class Taxonomies extends Repository { $prefix = Entities\Taxonomy::$db_identifier_prefix; return $prefix . $id; } + + /** + * Check if a term already exists + * + * @param Entities\Taxonomy $taxonomy The taxonomy object where to look for terms + * @param string $term_name The term name + * @param int|null $parent The ID of the parent term to look for children or null to look for terms in any hierarchical position. Default is null + * @param bool $return_term wether to return the term object if it exists. default is to false + * + * @return bool|WP_Term return boolean indicating if term exists. If $return_term is true and term exists, return WP_Term object + */ + public function term_exists(Entities\Taxonomy $taxonomy, $term_name, $parent = null, $return_term = false) { + $TermsRepo = Terms::get_instance(); + return $TermsRepo->term_exists($term_name, $taxonomy, $parent, $return_term); + } } \ No newline at end of file diff --git a/src/classes/repositories/class-tainacan-terms.php b/src/classes/repositories/class-tainacan-terms.php index b3680fd4a..70bbbc946 100644 --- a/src/classes/repositories/class-tainacan-terms.php +++ b/src/classes/repositories/class-tainacan-terms.php @@ -265,6 +265,54 @@ class Terms extends Repository { return $deleted; } + + /** + * Check if a term already exists + * + * @param string $term_name The term name + * @param mixed $taxonomy The taxonomy ID, slug or Entity. + * @param int $parent The ID of the parent term to look for children or null to look for terms in any hierarchical position. Default is null + * @param bool $return_term wether to return the term object if it exists. default is to false + * + * @return bool|WP_Term return boolean indicating if term exists. If $return_term is true and term exists, return WP_Term object + */ + public function term_exists($name, $taxonomy, $parent = null, $return_term = false) { + + $Tainacan_Taxonomies = \Tainacan\Repositories\Taxonomies::get_instance(); + + if ( is_numeric( $taxonomy ) ) { + $taxonomy_slug = $Tainacan_Taxonomies->get_db_identifier_by_id( $taxonomy ); + } elseif (is_string($taxonomy)) { + $taxonomy_slug = $taxonomy; + } elseif ( $taxonomy instanceof Entities\Taxonomy ) { + $taxonomy_slug = $taxonomy->get_db_identifier(); + } + + $args = [ + 'name' => $name, + 'taxonomy' => $taxonomy_slug, + 'parent' => $parent, + 'hide_empty' => 0, + 'suppress_filter' => true + ]; + + if (is_null($parent)) { + unset($args['parent']); + } + + $terms = get_terms($args); + + if (empty($terms)) { + return false; + } + + if ($return_term) { + return $terms[0]; + } + + return true; + + } /** * @param $term_id diff --git a/src/importer/class-tainacan-csv.php b/src/importer/class-tainacan-csv.php index 621c296ff..dbc83721c 100644 --- a/src/importer/class-tainacan-csv.php +++ b/src/importer/class-tainacan-csv.php @@ -663,9 +663,9 @@ class CSV extends Importer { $this->add_error_log('Malformed term hierarchy for Item ' . $this->get_current_collection_item() . '. Term skipped. Value: ' . $values); return false; } - $exists = term_exists( $value ,$taxonomy->get_db_identifier(), $parent ); - if (0 !== $exists && null !== $exists && isset($exists['term_id'])) { - $parent = $exists['term_id']; + $exists = $Tainacan_Terms->term_exists( $value ,$taxonomy->get_db_identifier(), $parent, true ); + if (false !== $exists && isset($exists->term_taxonomy_id)) { + $parent = $exists->term_taxonomy_id; } else { $this->add_log('New term created: ' . $value . ' in tax_id: ' . $taxonomy->get_db_identifier() . '; parent: ' . $parent); $term = new Entities\Term(); diff --git a/tests/test-taxonomies.php b/tests/test-taxonomies.php index 939cbae70..2a0d6fb40 100644 --- a/tests/test-taxonomies.php +++ b/tests/test-taxonomies.php @@ -105,5 +105,151 @@ class Taxonomies extends TAINACAN_UnitTestCase { $this->assertEquals(1, sizeof($terms), 'you should be able to create a term even if the taxonomy is still auto-draft'); + } + + function test_term_exists() { + + $taxonomy = $this->tainacan_entity_factory->create_entity( + 'taxonomy', + array( + 'name' => 'genero', + 'description' => 'tipos de musica', + 'allow_insert' => 'yes', + 'status' => 'publish' + ), + true + ); + + $Tainacan_Taxonomies = \Tainacan\Repositories\Taxonomies::get_instance(); + $Tainacan_Terms = \Tainacan\Repositories\Terms::get_instance(); + + $term = $this->tainacan_entity_factory->create_entity( + 'term', + array( + 'taxonomy' => $taxonomy->get_db_identifier(), + 'name' => 'Rock', + ), + true + ); + + $parent = $this->tainacan_entity_factory->create_entity( + 'term', + array( + 'taxonomy' => $taxonomy->get_db_identifier(), + 'name' => 'Parent', + ), + true + ); + + $child = $this->tainacan_entity_factory->create_entity( + 'term', + array( + 'taxonomy' => $taxonomy->get_db_identifier(), + 'name' => 'Child', + 'parent' => $parent->get_id() + ), + true + ); + + $this->assertFalse( $Tainacan_Terms->term_exists('Reggae', $taxonomy->get_db_identifier()) ); + $this->assertTrue( $Tainacan_Terms->term_exists('Rock', $taxonomy->get_db_identifier()) ); + + //var_dump( $Tainacan_Terms->term_exists('Rock', $taxonomy->get_db_identifier(), 0, true) ); + + // test extreme case + + $term_2 = $this->tainacan_entity_factory->create_entity( + 'term', + array( + 'taxonomy' => $taxonomy->get_db_identifier(), + 'name' => 'test 123', + 'parent' => $term->get_id() + ), + true + ); + + $this->assertFalse( $Tainacan_Terms->term_exists('test 123', $taxonomy->get_db_identifier(), 0) ); // parent 0 + $this->assertTrue( $Tainacan_Terms->term_exists('test 123', $taxonomy->get_db_identifier(), $term->get_id()) ); // spaces in between + + // testing passing taxonomy object + $this->assertTrue( $Tainacan_Terms->term_exists('Rock', $taxonomy) ); + + // testing passing ID + $this->assertTrue( $Tainacan_Terms->term_exists('Rock', $taxonomy->get_id()) ); + + // testing via Taxonomy object + $this->assertTrue( $taxonomy->term_exists('Rock') ); + + // testing retrieving the term + $this->assertTrue( $taxonomy->term_exists('Rock', 0, true) instanceof \WP_Term ); + $this->assertEquals( $term->get_id(), $taxonomy->term_exists('Rock', 0, true)->term_taxonomy_id ); + + // test parent + $this->assertTrue( $Tainacan_Terms->term_exists('Child', $taxonomy->get_db_identifier()) ); // parent null + $this->assertFalse( $Tainacan_Terms->term_exists('Child', $taxonomy->get_db_identifier(), 0) ); // parent 0 + $this->assertTrue( $Tainacan_Terms->term_exists('Child', $taxonomy->get_db_identifier(), $parent->get_id()) ); // parent + + } + + function test_term_validation() { + + $taxonomy = $this->tainacan_entity_factory->create_entity( + 'taxonomy', + array( + 'name' => 'genero', + 'description' => 'tipos de musica', + 'allow_insert' => 'yes', + 'status' => 'publish' + ), + true + ); + + $Tainacan_Taxonomies = \Tainacan\Repositories\Taxonomies::get_instance(); + $Tainacan_Terms = \Tainacan\Repositories\Terms::get_instance(); + + $term = $this->tainacan_entity_factory->create_entity( + 'term', + array( + 'taxonomy' => $taxonomy->get_db_identifier(), + 'name' => 'Rock', + ), + true + ); + + $parent = $this->tainacan_entity_factory->create_entity( + 'term', + array( + 'taxonomy' => $taxonomy->get_db_identifier(), + 'name' => 'Parent', + ), + true + ); + + $child = $this->tainacan_entity_factory->create_entity( + 'term', + array( + 'taxonomy' => $taxonomy->get_db_identifier(), + 'name' => 'Child', + 'parent' => $parent->get_id() + ), + true + ); + + $newTerm = new \Tainacan\Entities\Term(); + $newTerm->set_name('Child'); + $newTerm->set_taxonomy($taxonomy->get_db_identifier()); + + $this->assertTrue( $newTerm->validate() ); + + $newTerm->set_parent($parent->get_id()); + + $this->assertFalse( $newTerm->validate(), 'term should not validate because it has a duplicate in the same level' ); + + $child->set_description('changed'); + + $this->assertTrue( $child->validate(), 'child should validate'); + + + } } \ No newline at end of file