Skip to content

Commit 7f79d26

Browse files
committed
Don't allow term meta to be added to shared taxonomy terms.
`add_term_meta()` and `update_term_meta()` identify terms by `$term_id`. In cases where a term is shared between taxonomies, `$term_id` is insufficient to distinguish where the metadata belongs. When attempting to add/update termmeta on a shared term, a `WP_Error` object is returned. This gives developers enough information to decide whether they'd like to force the term to be split and retry the save, or show an error in the UI, or whatever. Props boonebgorges, mboynes, DH-Shredder, jorbin, aaroncampbell. Fixes #34544. git-svn-id: https://develop.svn.wordpress.org/trunk@35515 602fd350-edb4-49c9-b593-d223f7449a82
1 parent dc3f5a0 commit 7f79d26

2 files changed

Lines changed: 121 additions & 2 deletions

File tree

src/wp-includes/taxonomy-functions.php

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1578,14 +1578,19 @@ function get_terms( $taxonomies, $args = '' ) {
15781578
* @param mixed $meta_value Metadata value.
15791579
* @param bool $unique Optional. Whether to bail if an entry with the same key is found for the term.
15801580
* Default false.
1581-
* @return int|bool Meta ID on success, false on failure.
1581+
* @return int|WP_Error|bool Meta ID on success. WP_Error when term_id is ambiguous between taxonomies.
1582+
* False on failure.
15821583
*/
15831584
function add_term_meta( $term_id, $meta_key, $meta_value, $unique = false ) {
15841585
// Bail if term meta table is not installed.
15851586
if ( get_option( 'db_version' ) < 34370 ) {
15861587
return false;
15871588
}
15881589

1590+
if ( wp_term_is_shared( $term_id ) ) {
1591+
return new WP_Error( 'ambiguous_term_id', __( 'Term meta cannot be added to terms that are shared between taxonomies.'), $term_id );
1592+
}
1593+
15891594
$added = add_metadata( 'term', $term_id, $meta_key, $meta_value, $unique );
15901595

15911596
// Bust term query cache.
@@ -1655,14 +1660,19 @@ function get_term_meta( $term_id, $key = '', $single = false ) {
16551660
* @param string $meta_key Metadata key.
16561661
* @param mixed $meta_value Metadata value.
16571662
* @param mixed $prev_value Optional. Previous value to check before removing.
1658-
* @return int|bool Meta ID if the key didn't previously exist. True on successful update. False on failure.
1663+
* @return int|WP_Error|bool Meta ID if the key didn't previously exist. True on successful update.
1664+
* WP_Error when term_id is ambiguous between taxonomies. False on failure.
16591665
*/
16601666
function update_term_meta( $term_id, $meta_key, $meta_value, $prev_value = '' ) {
16611667
// Bail if term meta table is not installed.
16621668
if ( get_option( 'db_version' ) < 34370 ) {
16631669
return false;
16641670
}
16651671

1672+
if ( wp_term_is_shared( $term_id ) ) {
1673+
return new WP_Error( 'ambiguous_term_id', __( 'Term meta cannot be added to terms that are shared between taxonomies.'), $term_id );
1674+
}
1675+
16661676
$updated = update_metadata( 'term', $term_id, $meta_key, $meta_value, $prev_value );
16671677

16681678
// Bust term query cache.
@@ -4007,6 +4017,18 @@ function _split_shared_term( $term_id, $term_taxonomy_id, $record = true ) {
40074017
update_option( '_split_terms', $split_term_data );
40084018
}
40094019

4020+
// If we've just split the final shared term, set the "finished" flag.
4021+
$shared_terms_exist = $wpdb->get_results(
4022+
"SELECT tt.term_id, t.*, count(*) as term_tt_count FROM {$wpdb->term_taxonomy} tt
4023+
LEFT JOIN {$wpdb->terms} t ON t.term_id = tt.term_id
4024+
GROUP BY t.term_id
4025+
HAVING term_tt_count > 1
4026+
LIMIT 1"
4027+
);
4028+
if ( ! $shared_terms_exist ) {
4029+
update_option( 'finished_splitting_shared_terms', true );
4030+
}
4031+
40104032
/**
40114033
* Fires after a previously shared taxonomy term is split into two separate terms.
40124034
*
@@ -4254,6 +4276,29 @@ function wp_get_split_term( $old_term_id, $taxonomy ) {
42544276
return $term_id;
42554277
}
42564278

4279+
/**
4280+
* Determine whether a term is shared between multiple taxonomies.
4281+
*
4282+
* Shared taxonomy terms began to be split in 4.3, but failed cron tasks or other delays in upgrade routines may cause
4283+
* shared terms to remain.
4284+
*
4285+
* @since 4.4.0
4286+
*
4287+
* @param int $term_id
4288+
* @return bool
4289+
*/
4290+
function wp_term_is_shared( $term_id ) {
4291+
global $wpdb;
4292+
4293+
if ( get_option( 'finished_splitting_shared_terms' ) ) {
4294+
return false;
4295+
}
4296+
4297+
$tt_count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_taxonomy WHERE term_id = %d", $term_id ) );
4298+
4299+
return $tt_count > 1;
4300+
}
4301+
42574302
/**
42584303
* Generate a permalink for a taxonomy term archive.
42594304
*

tests/phpunit/tests/term/meta.php

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,80 @@ public function test_deleting_term_meta_should_bust_get_terms_cache() {
307307
$this->assertEqualSets( array( $terms[0] ), $found );
308308
}
309309

310+
/**
311+
* @ticket 34544
312+
*/
313+
public function test_add_term_meta_should_return_error_when_term_id_is_shared() {
314+
global $wpdb;
315+
316+
update_option( 'finished_splitting_shared_terms', false );
317+
318+
register_taxonomy( 'wptests_tax', 'post' );
319+
register_taxonomy( 'wptests_tax_2', 'post' );
320+
register_taxonomy( 'wptests_tax_3', 'post' );
321+
322+
$t1 = wp_insert_term( 'Foo', 'wptests_tax' );
323+
$t2 = wp_insert_term( 'Foo', 'wptests_tax_2' );
324+
$t3 = wp_insert_term( 'Foo', 'wptests_tax_3' );
325+
326+
// Manually modify because shared terms shouldn't naturally occur.
327+
$wpdb->update( $wpdb->term_taxonomy,
328+
array( 'term_id' => $t1['term_id'] ),
329+
array( 'term_taxonomy_id' => $t2['term_taxonomy_id'] ),
330+
array( '%d' ),
331+
array( '%d' )
332+
);
333+
334+
$wpdb->update( $wpdb->term_taxonomy,
335+
array( 'term_id' => $t1['term_id'] ),
336+
array( 'term_taxonomy_id' => $t3['term_taxonomy_id'] ),
337+
array( '%d' ),
338+
array( '%d' )
339+
);
340+
341+
$found = add_term_meta( $t1['term_id'], 'bar', 'baz' );
342+
$this->assertWPError( $found );
343+
$this->assertSame( 'ambiguous_term_id', $found->get_error_code() );
344+
}
345+
346+
/**
347+
* @ticket 34544
348+
*/
349+
public function test_update_term_meta_should_return_error_when_term_id_is_shared() {
350+
global $wpdb;
351+
352+
update_option( 'finished_splitting_shared_terms', false );
353+
354+
register_taxonomy( 'wptests_tax', 'post' );
355+
$t1 = wp_insert_term( 'Foo', 'wptests_tax' );
356+
add_term_meta( $t1, 'foo', 'bar' );
357+
358+
register_taxonomy( 'wptests_tax_2', 'post' );
359+
register_taxonomy( 'wptests_tax_3', 'post' );
360+
361+
$t2 = wp_insert_term( 'Foo', 'wptests_tax_2' );
362+
$t3 = wp_insert_term( 'Foo', 'wptests_tax_3' );
363+
364+
// Manually modify because shared terms shouldn't naturally occur.
365+
$wpdb->update( $wpdb->term_taxonomy,
366+
array( 'term_id' => $t1['term_id'] ),
367+
array( 'term_taxonomy_id' => $t2['term_taxonomy_id'] ),
368+
array( '%d' ),
369+
array( '%d' )
370+
);
371+
372+
$wpdb->update( $wpdb->term_taxonomy,
373+
array( 'term_id' => $t1['term_id'] ),
374+
array( 'term_taxonomy_id' => $t3['term_taxonomy_id'] ),
375+
array( '%d' ),
376+
array( '%d' )
377+
);
378+
379+
$found = update_term_meta( $t1['term_id'], 'foo', 'baz' );
380+
$this->assertWPError( $found );
381+
$this->assertSame( 'ambiguous_term_id', $found->get_error_code() );
382+
}
383+
310384
public static function set_cache_results( $q ) {
311385
$q->set( 'cache_results', true );
312386
}

0 commit comments

Comments
 (0)