<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[31418] trunk: Split shared taxonomy terms on term update.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/31418">31418</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/31418","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>boonebgorges</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2015-02-11 19:41:54 +0000 (Wed, 11 Feb 2015)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Split shared taxonomy terms on term update.

When updating an existing taxonomy term that shares its `term_id` with
another term, we generate a new row in `wp_terms` and associate the updated
term_taxonomy_id with the new term. This separates the terms, such that
updating the name of one term does not change the name of any others.

In cases where a plugin or theme stores term IDs in the database, term splitting
can cause backward compatibility issues. The current changeset introduces
two utilities to aid developers with the transition. The `'split_shared_term'`
action fires when the split takes place, and should be used to catch changes in
term_id. In cases where `'split_shared_term'` cannot be used, the
`wp_get_split_term()` function gives developers access to data about terms
that have previously been split. Documentation for these functions, with
examples, can be found in the Plugin Developer Handbook. WordPress itself
stores term IDs in this way in two places; `_wp_check_split_default_terms()`
and `_wp_check_split_terms_in_menus()` are hooked to `'split_shared_term'` to
perform the necessary cleanup.

See <a href="https://core.trac.wordpress.org/changeset/30241">[30241]</a> for a previous attempt at the split. It was reverted in <a href="https://core.trac.wordpress.org/changeset/30585">[30585]</a>
for 4.1.0.

Props boonebgorges, mboynes.
See <a href="https://core.trac.wordpress.org/ticket/5809">#5809</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpincludesdefaultfiltersphp">trunk/src/wp-includes/default-filters.php</a></li>
<li><a href="#trunksrcwpincludestaxonomyphp">trunk/src/wp-includes/taxonomy.php</a></li>
<li><a href="#trunktestsphpunitteststermphp">trunk/tests/phpunit/tests/term.php</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunktestsphpunitteststermsplitSharedTermphp">trunk/tests/phpunit/tests/term/splitSharedTerm.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpincludesdefaultfiltersphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/default-filters.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/default-filters.php 2015-02-11 19:18:19 UTC (rev 31417)
+++ trunk/src/wp-includes/default-filters.php   2015-02-11 19:41:54 UTC (rev 31418)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -306,6 +306,10 @@
</span><span class="cx" style="display: block; padding: 0 10px"> add_filter( 'determine_current_user', 'wp_validate_auth_cookie'          );
</span><span class="cx" style="display: block; padding: 0 10px"> add_filter( 'determine_current_user', 'wp_validate_logged_in_cookie', 20 );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+// Split term updates.
+add_action( 'split_shared_term', '_wp_check_split_default_terms',  10, 4 );
+add_action( 'split_shared_term', '_wp_check_split_terms_in_menus', 10, 4 );
+
</ins><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="cx" style="display: block; padding: 0 10px">  * Filters formerly mixed into wp-includes
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span></span></pre></div>
<a id="trunksrcwpincludestaxonomyphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/taxonomy.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/taxonomy.php        2015-02-11 19:18:19 UTC (rev 31417)
+++ trunk/src/wp-includes/taxonomy.php  2015-02-11 19:41:54 UTC (rev 31418)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3430,6 +3430,12 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        $tt_id = $wpdb->get_var( $wpdb->prepare( "SELECT tt.term_taxonomy_id FROM $wpdb->term_taxonomy AS tt INNER JOIN $wpdb->terms AS t ON tt.term_id = t.term_id WHERE tt.taxonomy = %s AND t.term_id = %d", $taxonomy, $term_id) );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        // Check whether this is a shared term that needs splitting.
+       $_term_id = _split_shared_term( $term_id, $tt_id );
+       if ( ! is_wp_error( $_term_id ) ) {
+               $term_id = $_term_id;
+       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">         /**
</span><span class="cx" style="display: block; padding: 0 10px">         * Fires immediately before the given terms are edited.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -4092,6 +4098,199 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Create a new term for a term_taxonomy item that currently shares its term with another term_taxonomy.
+ *
+ * @since 4.2.0
+ * @access private
+ *
+ * @param int  $term_id          ID of the shared term.
+ * @param int  $term_taxonomy_id ID of the term_taxonomy item to receive a new term.
+ * @return int|WP_Error When the current term does not need to be split (or cannot be split on the current database
+ *                      schema), `$term_id` is returned. When the term is successfully split, the new term_id is
+ *                      returned. A `WP_Error` is returned for miscellaneous errors.
+ */
+function _split_shared_term( $term_id, $term_taxonomy_id ) {
+       global $wpdb;
+
+       // Don't try to split terms if database schema does not support shared slugs.
+       $current_db_version = get_option( 'db_version' );
+       if ( $current_db_version < 30133 ) {
+               return $term_id;
+       }
+
+       // If there are no shared term_taxonomy rows, there's nothing to do here.
+       $shared_tt_count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_taxonomy tt WHERE tt.term_id = %d AND tt.term_taxonomy_id != %d", $term_id, $term_taxonomy_id ) );
+       if ( ! $shared_tt_count ) {
+               return $term_id;
+       }
+
+       // Pull up data about the currently shared slug, which we'll use to populate the new one.
+       $shared_term = $wpdb->get_row( $wpdb->prepare( "SELECT t.* FROM $wpdb->terms t WHERE t.term_id = %d", $term_id ) );
+
+       $new_term_data = array(
+               'name' => $shared_term->name,
+               'slug' => $shared_term->slug,
+               'term_group' => $shared_term->term_group,
+       );
+
+       if ( false === $wpdb->insert( $wpdb->terms, $new_term_data ) ) {
+               return new WP_Error( 'db_insert_error', __( 'Could not split shared term.' ), $wpdb->last_error );
+       }
+
+       $new_term_id = (int) $wpdb->insert_id;
+
+       // Update the existing term_taxonomy to point to the newly created term.
+       $wpdb->update( $wpdb->term_taxonomy,
+               array( 'term_id' => $new_term_id ),
+               array( 'term_taxonomy_id' => $term_taxonomy_id )
+       );
+
+       // Reassign child terms to the new parent.
+       $term_taxonomy = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->term_taxonomy WHERE term_taxonomy_id = %d", $term_taxonomy_id ) );
+       $children_tt_ids = $wpdb->get_col( $wpdb->prepare( "SELECT term_taxonomy_id FROM $wpdb->term_taxonomy WHERE taxonomy = %s AND parent = %d", $term_taxonomy->taxonomy, $term_id ) );
+
+       if ( ! empty( $children_tt_ids ) ) {
+               foreach ( $children_tt_ids as $child_tt_id ) {
+                       $wpdb->update( $wpdb->term_taxonomy,
+                               array( 'parent' => $new_term_id ),
+                               array( 'term_taxonomy_id' => $child_tt_id )
+                       );
+                       clean_term_cache( $term_id, $term_taxonomy->taxonomy );
+               }
+       } else {
+               // If the term has no children, we must force its taxonomy cache to be rebuilt separately.
+               clean_term_cache( $new_term_id, $term_taxonomy->taxonomy );
+       }
+
+       // Clean the cache for term taxonomies formerly shared with the current term.
+       $shared_term_taxonomies = $wpdb->get_row( $wpdb->prepare( "SELECT taxonomy FROM $wpdb->term_taxonomy WHERE term_id = %d", $term_id ) );
+       if ( $shared_term_taxonomies ) {
+               foreach ( $shared_term_taxonomies as $shared_term_taxonomy ) {
+                       clean_term_cache( $term_id, $shared_term_taxonomy );
+               }
+       }
+
+       // Keep a record of term_ids that have been split, keyed by old term_id. See {@see wp_get_split_term()}.
+       $split_term_data = get_option( '_split_terms', array() );
+       if ( ! isset( $split_term_data[ $term_id ] ) ) {
+               $split_term_data[ $term_id ] = array();
+       }
+
+       $split_term_data[ $term_id ][ $term_taxonomy->taxonomy ] = $new_term_id;
+
+       update_option( '_split_terms', $split_term_data );
+
+       /**
+        * Fires after a previously shared taxonomy term is split into two separate terms.
+        *
+        * @since 4.2.0
+        *
+        * @param int    $term_id          ID of the formerly shared term.
+        * @param int    $new_term_id      ID of the new term created for the $term_taxonomy_id.
+        * @param int    $term_taxonomy_id ID for the term_taxonomy row affected by the split.
+        * @param string $taxonomy         Taxonomy for the split term.
+        */
+       do_action( 'split_shared_term', $term_id, $new_term_id, $term_taxonomy_id, $term_taxonomy->taxonomy );
+
+       return $new_term_id;
+}
+
+/**
+ * Check default categories when a term gets split to see if any of them need to be updated.
+ *
+ * @since 4.2.0
+ * @access private
+ *
+ * @param int    $term_id          ID of the formerly shared term.
+ * @param int    $new_term_id      ID of the new term created for the $term_taxonomy_id.
+ * @param int    $term_taxonomy_id ID for the term_taxonomy row affected by the split.
+ * @param string $taxonomy         Taxonomy for the split term.
+ */
+function _wp_check_split_default_terms( $term_id, $new_term_id, $term_taxonomy_id, $taxonomy ) {
+       if ( 'category' != $taxonomy ) {
+               return;
+       }
+
+       foreach ( array( 'default_category', 'default_link_category', 'default_email_category' ) as $option ) {
+               if ( $term_id == get_option( $option, -1 ) ) {
+                       update_option( $option, $new_term_id );
+               }
+       }
+}
+
+/**
+ * Check menu items when a term gets split to see if any of them need to be updated.
+ *
+ * @since 4.2.0
+ * @access private
+ *
+ * @param int    $term_id          ID of the formerly shared term.
+ * @param int    $new_term_id      ID of the new term created for the $term_taxonomy_id.
+ * @param int    $term_taxonomy_id ID for the term_taxonomy row affected by the split.
+ * @param string $taxonomy         Taxonomy for the split term.
+ */
+function _wp_check_split_terms_in_menus( $term_id, $new_term_id, $term_taxonomy_id, $taxonomy ) {
+       global $wpdb;
+       $post_ids = $wpdb->get_col( $wpdb->prepare(
+               "SELECT m1.post_id
+               FROM {$wpdb->postmeta} AS m1
+                       INNER JOIN {$wpdb->postmeta} AS m2 ON ( m2.post_id = m1.post_id )
+                       INNER JOIN {$wpdb->postmeta} AS m3 ON ( m3.post_id = m1.post_id )
+               WHERE ( m1.meta_key = '_menu_item_type' AND m1.meta_value = 'taxonomy' )
+                       AND ( m2.meta_key = '_menu_item_object' AND m2.meta_value = '%s' )
+                       AND ( m3.meta_key = '_menu_item_object_id' AND m3.meta_value = %d )",
+               $taxonomy,
+               $term_id
+       ) );
+
+       if ( $post_ids ) {
+               foreach ( $post_ids as $post_id ) {
+                       update_post_meta( $post_id, '_menu_item_object_id', $new_term_id, $term_id );
+               }
+       }
+}
+
+/**
+ * Get data about terms that previously shared a single term_id, but have since been split.
+ *
+ * @since 4.2.0
+ *
+ * @param int $old_term_id Term ID. This is the old, pre-split term ID.
+ * @return array Array of new term IDs, keyed by taxonomy.
+ */
+function wp_get_split_terms( $old_term_id ) {
+       $split_terms = get_option( '_split_terms', array() );
+
+       $terms = array();
+       if ( isset( $split_terms[ $old_term_id ] ) ) {
+               $terms = $split_terms[ $old_term_id ];
+       }
+
+       return $terms;
+}
+
+/**
+ * Get the new term ID corresponding to a previously split term.
+ *
+ * @since 4.2.0
+ *
+ * @param int    $old_term_id Term ID. This is the old, pre-split term ID.
+ * @param string $taxonomy    Taxonomy that the term belongs to.
+ * @return bool|int If a previously split term is found corresponding to the old term_id and taxonomy, the new term_id
+ *                  will be returned. If no previously split term is found matching the parameters, returns false.
+ */
+function wp_get_split_term( $old_term_id, $taxonomy ) {
+       $split_terms = wp_get_split_terms( $old_term_id );
+
+       $term_id = false;
+       if ( isset( $split_terms[ $taxonomy ] ) ) {
+               $term_id = (int) $split_terms[ $taxonomy ];
+       }
+
+       return $term_id;
+}
+
+/**
</ins><span class="cx" style="display: block; padding: 0 10px">  * Generate a permalink for a taxonomy term archive.
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @since 2.5.0
</span></span></pre></div>
<a id="trunktestsphpunitteststermsplitSharedTermphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/tests/term/splitSharedTerm.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/term/splitSharedTerm.php                                (rev 0)
+++ trunk/tests/phpunit/tests/term/splitSharedTerm.php  2015-02-11 19:41:54 UTC (rev 31418)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,213 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+/**
+ * @group taxonomy
+ */
+class Tests_Term_SplitSharedTerm extends WP_UnitTestCase {
+       protected $terms = array();
+
+       /**
+        * Sets up a number of split terms for testing. Terms are as follows.
+        *
+        * - `$this->terms['t1']` is an array of the 'term_id' and 'term_taxonomy_id' of a term in the 'wptests_tax'
+        *   taxonomy. Pre-split, the term_id of t1 (`$this->terms['t1']['term_id']`) was shared by t1, t2, and t3.
+        * - `$this->terms['t2']` is an array of the 'term_id' and 'term_taxonomy_id' of a term in the 'wptests_tax_2'
+        *   taxonomy. Pre-split, the term_id of t2 was `$this->terms['t1']['term_id']`.
+        * - `$this->terms['t3']` is an array of the 'term_id' and 'term_taxonomy_id' of a term in the 'wptests_tax_3'
+        *   taxonomy. Pre-split, the term_id of t2 was `$this->terms['t1']['term_id']`.
+        * - `$this->terms['t2_child']` is an array of the 'term_id' and 'term_taxonomy_id' of a term in the
+        *   'wptests_tax_2' taxonomy. This term is a child of t2, and is used to test parent/child relationships
+        *   after term splitting.
+        */
+       public function setUp() {
+               global $wpdb;
+
+               parent::setUp();
+
+               register_taxonomy( 'wptests_tax', 'post' );
+               register_taxonomy( 'wptests_tax_2', 'post', array(
+                       'hierarchical' => true,
+               ) );
+               register_taxonomy( 'wptests_tax_3', 'post' );
+
+               $t1 = wp_insert_term( 'Foo', 'wptests_tax' );
+               $t2 = wp_insert_term( 'Foo', 'wptests_tax_2' );
+               $t3 = wp_insert_term( 'Foo', 'wptests_tax_3' );
+
+               // Manually modify because split terms shouldn't naturally occur.
+               $wpdb->update( $wpdb->term_taxonomy,
+                       array( 'term_id' => $t1['term_id'] ),
+                       array( 'term_taxonomy_id' => $t2['term_taxonomy_id'] ),
+                       array( '%d' ),
+                       array( '%d' )
+               );
+
+               $wpdb->update( $wpdb->term_taxonomy,
+                       array( 'term_id' => $t1['term_id'] ),
+                       array( 'term_taxonomy_id' => $t3['term_taxonomy_id'] ),
+                       array( '%d' ),
+                       array( '%d' )
+               );
+
+               $t2_child = wp_insert_term( 'Foo Child', 'wptests_tax_2', array(
+                       'parent' => $t1['term_id'],
+               ) );
+
+               // Split the terms and store the new term IDs.
+               $t2['term_id'] = _split_shared_term( $t1['term_id'], $t2['term_taxonomy_id'] );
+               $t3['term_id'] = _split_shared_term( $t1['term_id'], $t3['term_taxonomy_id'] );
+
+               $this->terms = array(
+                       't1' => $t1,
+                       't2' => $t2,
+                       't3' => $t3,
+                       't2_child' => $t2_child,
+               );
+       }
+
+       /**
+        * @ticket 5809
+        */
+       public function test_should_create_new_term_ids() {
+               $t1_term = get_term_by( 'term_taxonomy_id', $this->terms['t1']['term_taxonomy_id'], 'wptests_tax' );
+               $t2_term = get_term_by( 'term_taxonomy_id', $this->terms['t2']['term_taxonomy_id'], 'wptests_tax_2' );
+               $t3_term = get_term_by( 'term_taxonomy_id', $this->terms['t3']['term_taxonomy_id'], 'wptests_tax_3' );
+
+               $this->assertNotEquals( $t1_term->term_id, $t2_term->term_id );
+               $this->assertNotEquals( $t1_term->term_id, $t3_term->term_id );
+               $this->assertNotEquals( $t2_term->term_id, $t3_term->term_id );
+       }
+
+       /**
+        * @ticket 5809
+        */
+       public function test_should_retain_child_terms_when_using_get_terms_parent() {
+               $children = get_terms( 'wptests_tax_2', array(
+                       'parent' => $this->terms['t2']['term_id'],
+                       'hide_empty' => false,
+               ) );
+
+               $this->assertEquals( $this->terms['t2_child']['term_taxonomy_id'], $children[0]->term_taxonomy_id );
+       }
+
+       /**
+        * @ticket 5809
+        */
+       public function test_should_retain_child_terms_when_using_get_terms_child_of() {
+               $children = get_terms( 'wptests_tax_2', array(
+                       'child_of' => $this->terms['t2']['term_id'],
+                       'hide_empty' => false,
+               ) );
+
+               $this->assertEquals( $this->terms['t2_child']['term_taxonomy_id'], $children[0]->term_taxonomy_id );
+       }
+
+       /**
+        * @ticket 30335
+        */
+       public function test_should_rebuild_split_term_taxonomy_hierarchy() {
+               global $wpdb;
+
+               register_taxonomy( 'wptests_tax_3', 'post' );
+               register_taxonomy( 'wptests_tax_4', 'post', array(
+                       'hierarchical' => true,
+               ) );
+
+               $t1 = wp_insert_term( 'Foo1', 'wptests_tax_3' );
+               $t2 = wp_insert_term( 'Foo1 Parent', 'wptests_tax_4' );
+               $t3 = wp_insert_term( 'Foo1', 'wptests_tax_4', array(
+                       'parent' => $t2['term_id'],
+               ) );
+
+               // Manually modify because split terms shouldn't naturally occur.
+               $wpdb->update( $wpdb->term_taxonomy,
+                       array( 'term_id' => $t1['term_id'] ),
+                       array( 'term_taxonomy_id' => $t3['term_taxonomy_id'] ),
+                       array( '%d' ),
+                       array( '%d' )
+               );
+               $th = _get_term_hierarchy( 'wptests_tax_4' );
+
+               $new_term_id = _split_shared_term( $t1['term_id'], $t3['term_taxonomy_id'] );
+
+               $t2_children = get_term_children( $t2['term_id'], 'wptests_tax_4' );
+               $this->assertEquals( array( $new_term_id ), $t2_children );
+       }
+
+       /**
+        * @ticket 30335
+        */
+       public function test_should_update_default_category_on_term_split() {
+               global $wpdb;
+               $t1 = wp_insert_term( 'Foo Default', 'category' );
+
+               update_option( 'default_category', $t1['term_id'] );
+
+               register_taxonomy( 'wptests_tax_5', 'post' );
+               $t2 = wp_insert_term( 'Foo Default', 'wptests_tax_5' );
+
+               // Manually modify because split terms shouldn't naturally occur.
+               $wpdb->update( $wpdb->term_taxonomy,
+                       array( 'term_id' => $t1['term_id'] ),
+                       array( 'term_taxonomy_id' => $t2['term_taxonomy_id'] ),
+                       array( '%d' ),
+                       array( '%d' )
+               );
+
+               $this->assertEquals( $t1['term_id'], get_option( 'default_category', -1 ) );
+
+               $new_term_id = _split_shared_term( $t1['term_id'], $t1['term_taxonomy_id'] );
+
+               $this->assertNotEquals( $new_term_id, $t1['term_id'] );
+               $this->assertEquals( $new_term_id, get_option( 'default_category', -1 ) );
+       }
+
+       /**
+        * @ticket 30335
+        */
+       public function test_should_update_menus_on_term_split() {
+               global $wpdb;
+
+               $t1 = wp_insert_term( 'Foo Menu', 'category' );
+
+               register_taxonomy( 'wptests_tax_6', 'post' );
+               $t2 = wp_insert_term( 'Foo Menu', 'wptests_tax_6' );
+
+               // Manually modify because split terms shouldn't naturally occur.
+               $wpdb->update( $wpdb->term_taxonomy,
+                       array( 'term_id' => $t1['term_id'] ),
+                       array( 'term_taxonomy_id' => $t2['term_taxonomy_id'] ),
+                       array( '%d' ),
+                       array( '%d' )
+               );
+
+               $menu_id = wp_create_nav_menu( rand_str() );
+               $cat_menu_item = wp_update_nav_menu_item( $menu_id, 0, array(
+                       'menu-item-type' => 'taxonomy',
+                       'menu-item-object' => 'category',
+                       'menu-item-object-id' => $t1['term_id'],
+                       'menu-item-status' => 'publish'
+               ) );
+               $this->assertEquals( $t1['term_id'], get_post_meta( $cat_menu_item, '_menu_item_object_id', true ) );
+
+               $new_term_id = _split_shared_term( $t1['term_id'], $t1['term_taxonomy_id'] );
+               $this->assertNotEquals( $new_term_id, $t1['term_id'] );
+               $this->assertEquals( $new_term_id, get_post_meta( $cat_menu_item, '_menu_item_object_id', true ) );
+       }
+
+       public function test_wp_get_split_terms() {
+               $found = wp_get_split_terms( $this->terms['t1']['term_id'] );
+
+               $expected = array(
+                       'wptests_tax_2' => $this->terms['t2']['term_id'],
+                       'wptests_tax_3' => $this->terms['t3']['term_id'],
+               );
+
+               $this->assertEqualSets( $expected, $found );
+       }
+
+       public function test_wp_get_split_term() {
+               $found = wp_get_split_term( $this->terms['t1']['term_id'], 'wptests_tax_3' );
+               $this->assertEquals( $this->terms['t3']['term_id'], $found );
+       }
+}
</ins></span></pre></div>
<a id="trunktestsphpunitteststermphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/term.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/term.php        2015-02-11 19:18:19 UTC (rev 31417)
+++ trunk/tests/phpunit/tests/term.php  2015-02-11 19:41:54 UTC (rev 31418)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -760,6 +760,88 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertSame( 'Bar', $t3_term->name );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        /**
+        * @ticket 5809
+        */
+       public function test_wp_update_term_should_split_shared_term() {
+               global $wpdb;
+
+               register_taxonomy( 'wptests_tax', 'post' );
+               register_taxonomy( 'wptests_tax_2', 'post' );
+
+               $t1 = wp_insert_term( 'Foo', 'wptests_tax' );
+               $t2 = wp_insert_term( 'Foo', 'wptests_tax_2' );
+
+               // Manually modify because split terms shouldn't naturally occur.
+               $wpdb->update( $wpdb->term_taxonomy,
+                       array( 'term_id' => $t1['term_id'] ),
+                       array( 'term_taxonomy_id' => $t2['term_taxonomy_id'] ),
+                       array( '%d' ),
+                       array( '%d' )
+               );
+
+               $posts = $this->factory->post->create_many( 2 );
+               wp_set_object_terms( $posts[0], array( 'Foo' ), 'wptests_tax' );
+               wp_set_object_terms( $posts[1], array( 'Foo' ), 'wptests_tax_2' );
+
+               // Verify that the terms are shared.
+               $t1_terms = wp_get_object_terms( $posts[0], 'wptests_tax' );
+               $t2_terms = wp_get_object_terms( $posts[1], 'wptests_tax_2' );
+               $this->assertSame( $t1_terms[0]->term_id, $t2_terms[0]->term_id );
+
+               wp_update_term( $t2_terms[0]->term_id, 'wptests_tax_2', array(
+                       'name' => 'New Foo',
+               ) );
+
+               $t1_terms = wp_get_object_terms( $posts[0], 'wptests_tax' );
+               $t2_terms = wp_get_object_terms( $posts[1], 'wptests_tax_2' );
+               $this->assertNotEquals( $t1_terms[0]->term_id, $t2_terms[0]->term_id );
+       }
+
+       /**
+        * @ticket 5809
+        */
+       public function test_wp_update_term_should_not_split_shared_term_before_410_schema_change() {
+               global $wpdb;
+
+               $db_version = get_option( 'db_version' );
+               update_option( 'db_version', 30055 );
+
+               register_taxonomy( 'wptests_tax', 'post' );
+               register_taxonomy( 'wptests_tax_2', 'post' );
+
+               $t1 = wp_insert_term( 'Foo', 'wptests_tax' );
+               $t2 = wp_insert_term( 'Foo', 'wptests_tax_2' );
+
+               // Manually modify because split terms shouldn't naturally occur.
+               $wpdb->update( $wpdb->term_taxonomy,
+                       array( 'term_id' => $t1['term_id'] ),
+                       array( 'term_taxonomy_id' => $t2['term_taxonomy_id'] ),
+                       array( '%d' ),
+                       array( '%d' )
+               );
+
+               $posts = $this->factory->post->create_many( 2 );
+               wp_set_object_terms( $posts[0], array( 'Foo' ), 'wptests_tax' );
+               wp_set_object_terms( $posts[1], array( 'Foo' ), 'wptests_tax_2' );
+
+               // Verify that the term is shared.
+               $t1_terms = wp_get_object_terms( $posts[0], 'wptests_tax' );
+               $t2_terms = wp_get_object_terms( $posts[1], 'wptests_tax_2' );
+               $this->assertSame( $t1_terms[0]->term_id, $t2_terms[0]->term_id );
+
+               wp_update_term( $t2_terms[0]->term_id, 'wptests_tax_2', array(
+                       'name' => 'New Foo',
+               ) );
+
+               // Term should still be shared.
+               $t1_terms = wp_get_object_terms( $posts[0], 'wptests_tax' );
+               $t2_terms = wp_get_object_terms( $posts[1], 'wptests_tax_2' );
+               $this->assertSame( $t1_terms[0]->term_id, $t2_terms[0]->term_id );
+
+               update_option( 'db_version', $db_version );
+       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">         public function test_wp_update_term_alias_of_no_term_group() {
</span><span class="cx" style="display: block; padding: 0 10px">                register_taxonomy( 'wptests_tax', 'post' );
</span><span class="cx" style="display: block; padding: 0 10px">                $t1 = $this->factory->term->create( array(
</span></span></pre>
</div>
</div>

</body>
</html>