+/**
+ * Splits a batch of shared taxonomy terms.
+ *
+ * @since 4.3.0
+ *
+ * @global wpdb $wpdb WordPress database abstraction object.
+ */
+function _wp_batch_split_terms() {
+ global $wpdb;
+
+ $lock_name = 'term_split.lock';
+
+ // Try to lock.
+ $lock_result = $wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO `$wpdb->options` ( `option_name`, `option_value`, `autoload` ) VALUES (%s, %s, 'no') /* LOCK */", $lock_name, time() ) );
+
+ if ( ! $lock_result ) {
+ $lock_result = get_option( $lock_name );
+
+ // Bail if we were unable to create a lock, or if the existing lock is still valid.
+ if ( ! $lock_result || ( $lock_result > ( time() - HOUR_IN_SECONDS ) ) ) {
+ wp_schedule_single_event( time() + ( 5 * MINUTE_IN_SECONDS ), 'wp_split_shared_term_batch' );
+ return;
+ }
+ }
+
+ // Update the lock, as by this point we've definitely got a lock, just need to fire the actions.
+ update_option( $lock_name, time() );
+
+ // Get a list of shared terms (those with more than one associated row in term_taxonomy).
+ $shared_terms = $wpdb->get_results(
+ "SELECT tt.term_id, t.*, count(*) as term_tt_count FROM {$wpdb->term_taxonomy} tt
+ LEFT JOIN {$wpdb->terms} t ON t.term_id = tt.term_id
+ GROUP BY t.term_id
+ HAVING term_tt_count > 1
+ LIMIT 10"
+ );
+
+ // No more terms, we're done here.
+ if ( ! $shared_terms ) {
+ update_option( 'finished_splitting_shared_terms', true );
+ delete_option( $lock_name );
+ return;
+ }
+
+ // Shared terms found? We'll need to run this script again.
+ wp_schedule_single_event( time() + ( 2 * MINUTE_IN_SECONDS ), 'wp_split_shared_term_batch' );
+
+ // Rekey shared term array for faster lookups.
+ $_shared_terms = array();
+ foreach ( $shared_terms as $shared_term ) {
+ $term_id = intval( $shared_term->term_id );
+ $_shared_terms[ $term_id ] = $shared_term;
+ }
+ $shared_terms = $_shared_terms;
+
+ // Get term taxonomy data for all shared terms.
+ $shared_term_ids = implode( ',', array_keys( $shared_terms ) );
+ $shared_tts = $wpdb->get_results( "SELECT * FROM {$wpdb->term_taxonomy} WHERE `term_id` IN ({$shared_term_ids})" );
+
+ // Split term data recording is slow, so we do it just once, outside the loop.
+ $split_term_data = get_option( '_split_terms', array() );
+ $skipped_first_term = $taxonomies = array();
+ foreach ( $shared_tts as $shared_tt ) {
+ $term_id = intval( $shared_tt->term_id );
+
+ // Don't split the first tt belonging to a given term_id.
+ if ( ! isset( $skipped_first_term[ $term_id ] ) ) {
+ $skipped_first_term[ $term_id ] = 1;
+ continue;
+ }
+
+ if ( ! isset( $split_term_data[ $term_id ] ) ) {
+ $split_term_data[ $term_id ] = array();
+ }
+
+ // Keep track of taxonomies whose hierarchies need flushing.
+ if ( ! isset( $taxonomies[ $shared_tt->taxonomy ] ) ) {
+ $taxonomies[ $shared_tt->taxonomy ] = 1;
+ }
+
+ // Split the term.
+ $split_term_data[ $term_id ][ $shared_tt->taxonomy ] = _split_shared_term( $shared_terms[ $term_id ], $shared_tt, false );
+ }
+
+ // Rebuild the cached hierarchy for each affected taxonomy.
+ foreach ( array_keys( $taxonomies ) as $tax ) {
+ delete_option( "{$tax}_children" );
+ _get_term_hierarchy( $tax );
+ }
+
+ update_option( '_split_terms', $split_term_data );
+
+ delete_option( $lock_name );
+}
+
+/**
+ * In order to avoid the _wp_batch_split_terms() job being accidentally removed,
+ * check that it's still scheduled while we haven't finished splitting terms.
+ *
+ * @ignore
+ * @since 4.3.0
+ */
+function _wp_check_for_scheduled_split_terms() {
+ if ( ! get_option( 'finished_splitting_shared_terms' ) && ! wp_next_scheduled( 'wp_split_shared_term_batch' ) ) {
+ wp_schedule_single_event( time() + MINUTE_IN_SECONDS, 'wp_split_shared_term_batch' );
+ }
+}
+