Term splitting routine should be run in a separate process, triggered via wp-cron.

[32814] introduced a routine to split shared terms, which was run during the
regular WP database upgrade. This turned out to be problematic because plugins
are not loaded during the db upgrade (due to `WP_INSTALLING`), with the result
that plugins were not able to hook into the 'split_shared_term' action during
the bulk split. We work around this limitation by moving the term splitting
routine to a separate process, triggered by a wp-cron hook.

Props boonebgorges, Chouby, peterwilsoncc, pento, dd32.
Fixes #30261.
Built from https://develop.svn.wordpress.org/trunk@33615


git-svn-id: http://core.svn.wordpress.org/trunk@33582 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
Boone Gorges 2015-08-14 03:59:26 +00:00
parent f74a907eac
commit 151d27369d
4 changed files with 124 additions and 78 deletions

View File

@ -1505,8 +1505,9 @@ function upgrade_430() {
upgrade_430_fix_comments();
}
// Shared terms are split in a separate process.
if ( $wp_current_db_version < 32814 ) {
split_all_shared_terms();
wp_schedule_single_event( time() + ( 1 * MINUTE_IN_SECONDS ), 'wp_split_shared_term_batch' );
}
if ( $wp_current_db_version < 33055 && 'utf8mb4' === $wpdb->charset ) {
@ -1880,76 +1881,6 @@ function maybe_convert_table_to_utf8mb4( $table ) {
return $wpdb->query( "ALTER TABLE $table CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci" );
}
/**
* Splits all shared taxonomy terms.
*
* @since 4.3.0
*
* @global wpdb $wpdb WordPress database abstraction object.
*/
function split_all_shared_terms() {
global $wpdb;
// 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"
);
if ( empty( $shared_terms ) ) {
return;
}
// 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.
$suspend = wp_suspend_cache_invalidation( true );
$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 );
}
wp_suspend_cache_invalidation( $suspend );
update_option( '_split_terms', $split_term_data );
}
/**
* Retrieve all options as it was for 1.2.
*

View File

@ -328,9 +328,11 @@ add_filter( 'determine_current_user', 'wp_validate_auth_cookie' );
add_filter( 'determine_current_user', 'wp_validate_logged_in_cookie', 20 );
// Split term updates.
add_action( 'admin_init', '_wp_check_for_scheduled_split_terms' );
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 );
add_action( 'split_shared_term', '_wp_check_split_nav_menu_terms', 10, 4 );
add_action( 'wp_split_shared_term_batch', '_wp_batch_split_terms' );
/**
* Filters formerly mixed into wp-includes

View File

@ -4249,12 +4249,6 @@ function _split_shared_term( $term_id, $term_taxonomy_id, $record = true ) {
$term_taxonomy_id = intval( $term_taxonomy->term_taxonomy_id );
}
// 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 ) );
@ -4262,6 +4256,15 @@ function _split_shared_term( $term_id, $term_taxonomy_id, $record = true ) {
return $term_id;
}
/*
* Verify that the term_taxonomy_id passed to the function is actually associated with the term_id.
* If there's a mismatch, it may mean that the term is already split. Return the actual term_id from the db.
*/
$check_term_id = $wpdb->get_var( $wpdb->prepare( "SELECT term_id FROM $wpdb->term_taxonomy WHERE term_taxonomy_id = %d", $term_taxonomy_id ) );
if ( $check_term_id != $term_id ) {
return $check_term_id;
}
// Pull up data about the currently shared slug, which we'll use to populate the new one.
if ( empty( $shared_term ) ) {
$shared_term = $wpdb->get_row( $wpdb->prepare( "SELECT t.* FROM $wpdb->terms t WHERE t.term_id = %d", $term_id ) );
@ -4338,6 +4341,116 @@ function _split_shared_term( $term_id, $term_taxonomy_id, $record = true ) {
return $new_term_id;
}
/**
* 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 20"
);
// 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.
$suspend = wp_suspend_cache_invalidation( true );
$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 );
}
wp_suspend_cache_invalidation( $suspend );
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_batch_split_terms' ) ) {
wp_schedule_single_event( 'wp_batch_split_terms', time() + MINUTE_IN_SECONDS );
}
}
/**
* Check default categories when a term gets split to see if any of them need to be updated.
*

View File

@ -4,7 +4,7 @@
*
* @global string $wp_version
*/
$wp_version = '4.3-RC2-33614';
$wp_version = '4.3-RC2-33615';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.