Example #1
0
/**
 * Update term based on arguments provided.
 *
 * The $args will indiscriminately override all values with the same field name.
 * Care must be taken to not override important information need to update or
 * update will fail (or perhaps create a new term, neither would be acceptable).
 *
 * Defaults will set 'alias_of', 'description', 'parent', and 'slug' if not
 * defined in $args already.
 *
 * 'alias_of' will create a term group, if it doesn't already exist, and update
 * it for the $term.
 *
 * If the 'slug' argument in $args is missing, then the 'name' in $args will be
 * used. It should also be noted that if you set 'slug' and it isn't unique then
 * a WP_Error will be passed back. If you don't pass any slug, then a unique one
 * will be created for you.
 *
 * For what can be overrode in `$args`, check the term scheme can contain and stay
 * away from the term keys.
 *
 * @since 2.3.0
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @param int          $term_id  The ID of the term
 * @param string       $taxonomy The context in which to relate the term to the object.
 * @param array|string $args     Optional. Array of get_terms() arguments. Default empty array.
 * @return array|WP_Error Returns Term ID and Taxonomy Term ID
 */
function wp_update_term($term_id, $taxonomy, $args = array())
{
    global $wpdb;
    if (!taxonomy_exists($taxonomy)) {
        return new WP_Error('invalid_taxonomy', __('Invalid taxonomy'));
    }
    $term_id = (int) $term_id;
    // First, get all of the original args
    $term = get_term($term_id, $taxonomy, ARRAY_A);
    if (is_wp_error($term)) {
        return $term;
    }
    if (!$term) {
        return new WP_Error('invalid_term', __('Empty Term'));
    }
    // Escape data pulled from DB.
    $term = wp_slash($term);
    // Merge old and new args with new args overwriting old ones.
    $args = array_merge($term, $args);
    $defaults = array('alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => '');
    $args = wp_parse_args($args, $defaults);
    $args = sanitize_term($args, $taxonomy, 'db');
    $parsed_args = $args;
    // expected_slashed ($name)
    $name = wp_unslash($args['name']);
    $description = wp_unslash($args['description']);
    $parsed_args['name'] = $name;
    $parsed_args['description'] = $description;
    if ('' == trim($name)) {
        return new WP_Error('empty_term_name', __('A name is required for this term'));
    }
    if ($parsed_args['parent'] > 0 && !term_exists((int) $parsed_args['parent'])) {
        return new WP_Error('missing_parent', __('Parent term does not exist.'));
    }
    $empty_slug = false;
    if (empty($args['slug'])) {
        $empty_slug = true;
        $slug = sanitize_title($name);
    } else {
        $slug = $args['slug'];
    }
    $parsed_args['slug'] = $slug;
    $term_group = isset($parsed_args['term_group']) ? $parsed_args['term_group'] : 0;
    if ($args['alias_of']) {
        $alias = get_term_by('slug', $args['alias_of'], $taxonomy);
        if (!empty($alias->term_group)) {
            // The alias we want is already in a group, so let's use that one.
            $term_group = $alias->term_group;
        } elseif (!empty($alias->term_id)) {
            /*
             * The alias is not in a group, so we create a new one
             * and add the alias to it.
             */
            $term_group = $wpdb->get_var("SELECT MAX(term_group) FROM {$wpdb->terms}") + 1;
            wp_update_term($alias->term_id, $taxonomy, array('term_group' => $term_group));
        }
        $parsed_args['term_group'] = $term_group;
    }
    /**
     * Filter the term parent.
     *
     * Hook to this filter to see if it will cause a hierarchy loop.
     *
     * @since 3.1.0
     *
     * @param int    $parent      ID of the parent term.
     * @param int    $term_id     Term ID.
     * @param string $taxonomy    Taxonomy slug.
     * @param array  $parsed_args An array of potentially altered update arguments for the given term.
     * @param array  $args        An array of update arguments for the given term.
     */
    $parent = apply_filters('wp_update_term_parent', $args['parent'], $term_id, $taxonomy, $parsed_args, $args);
    // Check for duplicate slug
    $duplicate = get_term_by('slug', $slug, $taxonomy);
    if ($duplicate && $duplicate->term_id != $term_id) {
        // If an empty slug was passed or the parent changed, reset the slug to something unique.
        // Otherwise, bail.
        if ($empty_slug || $parent != $term['parent']) {
            $slug = wp_unique_term_slug($slug, (object) $args);
        } else {
            return new WP_Error('duplicate_term_slug', sprintf(__('The slug “%s” is already in use by another term'), $slug));
        }
    }
    $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));
    // 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;
    }
    /**
     * Fires immediately before the given terms are edited.
     *
     * @since 2.9.0
     *
     * @param int    $term_id  Term ID.
     * @param string $taxonomy Taxonomy slug.
     */
    do_action('edit_terms', $term_id, $taxonomy);
    $wpdb->update($wpdb->terms, compact('name', 'slug', 'term_group'), compact('term_id'));
    if (empty($slug)) {
        $slug = sanitize_title($name, $term_id);
        $wpdb->update($wpdb->terms, compact('slug'), compact('term_id'));
    }
    /**
     * Fires immediately after the given terms are edited.
     *
     * @since 2.9.0
     *
     * @param int    $term_id  Term ID
     * @param string $taxonomy Taxonomy slug.
     */
    do_action('edited_terms', $term_id, $taxonomy);
    /**
     * Fires immediate before a term-taxonomy relationship is updated.
     *
     * @since 2.9.0
     *
     * @param int    $tt_id    Term taxonomy ID.
     * @param string $taxonomy Taxonomy slug.
     */
    do_action('edit_term_taxonomy', $tt_id, $taxonomy);
    $wpdb->update($wpdb->term_taxonomy, compact('term_id', 'taxonomy', 'description', 'parent'), array('term_taxonomy_id' => $tt_id));
    /**
     * Fires immediately after a term-taxonomy relationship is updated.
     *
     * @since 2.9.0
     *
     * @param int    $tt_id    Term taxonomy ID.
     * @param string $taxonomy Taxonomy slug.
     */
    do_action('edited_term_taxonomy', $tt_id, $taxonomy);
    // Clean the relationship caches for all object types using this term.
    $objects = $wpdb->get_col($wpdb->prepare("SELECT object_id FROM {$wpdb->term_relationships} WHERE term_taxonomy_id = %d", $tt_id));
    $tax_object = get_taxonomy($taxonomy);
    foreach ($tax_object->object_type as $object_type) {
        clean_object_term_cache($objects, $object_type);
    }
    /**
     * Fires after a term has been updated, but before the term cache has been cleaned.
     *
     * @since 2.3.0
     *
     * @param int    $term_id  Term ID.
     * @param int    $tt_id    Term taxonomy ID.
     * @param string $taxonomy Taxonomy slug.
     */
    do_action("edit_term", $term_id, $tt_id, $taxonomy);
    /**
     * Fires after a term in a specific taxonomy has been updated, but before the term
     * cache has been cleaned.
     *
     * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Term taxonomy ID.
     */
    do_action("edit_{$taxonomy}", $term_id, $tt_id);
    /** This filter is documented in wp-includes/taxonomy.php */
    $term_id = apply_filters('term_id_filter', $term_id, $tt_id);
    clean_term_cache($term_id, $taxonomy);
    /**
     * Fires after a term has been updated, and the term cache has been cleaned.
     *
     * @since 2.3.0
     *
     * @param int    $term_id  Term ID.
     * @param int    $tt_id    Term taxonomy ID.
     * @param string $taxonomy Taxonomy slug.
     */
    do_action("edited_term", $term_id, $tt_id, $taxonomy);
    /**
     * Fires after a term for a specific taxonomy has been updated, and the term
     * cache has been cleaned.
     *
     * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Term taxonomy ID.
     */
    do_action("edited_{$taxonomy}", $term_id, $tt_id);
    return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_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\n\t\t LEFT JOIN {$wpdb->terms} t ON t.term_id = tt.term_id\n\t\t GROUP BY t.term_id\n\t\t HAVING term_tt_count > 1\n\t\t 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);
}
Example #3
0
/**
 * Split all shared taxonomy terms.
 *
 * @since 4.3.0
 */
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\n\t\t LEFT JOIN {$wpdb->terms} t ON t.term_id = tt.term_id\n\t\t GROUP BY t.term_id\n\t\t 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);
}
 public function split_shared_term($term_id, $new_term_id, $term_taxonomy_id, $taxonomy)
 {
     // avoid recursion
     static $avoid_recursion = false;
     if ($avoid_recursion) {
         return;
     }
     $avoid_recursion = true;
     $lang = $this->model->get_term_language($term_id);
     foreach ($this->model->get_translations('term', $term_id) as $key => $tr_id) {
         if ($lang->slug == $key) {
             $translations[$key] = $new_term_id;
         } else {
             $tr_term = get_term($tr_id, $taxonomy);
             $translations[$key] = _split_shared_term($tr_id, $tr_term->term_taxonomy_id);
             // hack translation ids sent by the form to avoid overwrite in PLL_Admin_Filters_Term::save_translations
             if (isset($_POST['term_tr_lang'][$key]) && $_POST['term_tr_lang'][$key] == $tr_id) {
                 $_POST['term_tr_lang'][$key] = $translations[$key];
             }
         }
         $this->model->set_term_language($translations[$key], $key);
     }
     $this->model->save_translations('term', $new_term_id, $translations);
     $avoid_recursion = false;
 }
 /**
  * @todo: account for wp 4.2 term splitting (https://developer.wordpress.org/plugins/taxonomy/working-with-split-terms-in-wp-4-2/)
  */
 function test_save_data_array_to_db__from_this_site__term_split()
 {
     //create term and term taxonomy
     $term = $this->new_model_obj_with_dependencies('Term', array('name' => 'Jaguar', 'slug' => 'jag'));
     $ttcar = $this->new_model_obj_with_dependencies('Term_Taxonomy', array('term_id' => $term->ID(), 'taxonomy' => 'cars', 'description' => 'A fast car'));
     $ttcat = $this->new_model_obj_with_dependencies('Term_Taxonomy', array('term_id' => $term->ID(), 'taxonomy' => 'cats', 'description' => 'A large black cat that likes to swim'));
     //create "csv" data for it (pretend exported)
     $csv_data = array('Term' => array($term->model_field_array()), 'Term_Taxonomy' => array($ttcar->model_field_array(), $ttcat->model_field_array()));
     $this->assertEquals($ttcat->get('term_id'), $ttcar->get('term_id'));
     //split the term in the "wp" way. Our model objet $ttcar will NOT get updated on its own
     $new_term_id_for_car = _split_shared_term($term->ID(), $ttcar->ID());
     $ttcar = EEM_Term_Taxonomy::instance()->refresh_entity_map_from_db($ttcar->ID());
     //		echo "updated term taxonomy:";var_dump($ttcar->model_field_array());
     $this->assertNotEquals($ttcat->get('term_id'), $ttcar->get('term_id'));
     //import it
     $new_mapping = EE_Import::instance()->save_data_rows_to_db($csv_data, false, array());
     $ttcar = EEM_Term_Taxonomy::instance()->refresh_entity_map_from_db($ttcar->ID());
     //when it's done importing, we should have saved a term-taxonomy for the new term, not re-inserted a term-taxonomy to the old term
     //and because it used the models, the model objects we have in scope should already be up-to-date
     $this->assertEquals($new_term_id_for_car, $ttcar->get('term_id'));
 }
Example #6
0
 /**
  * @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 shared 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));
 }
 /**
  * @ticket 33187
  * @group navmenus
  */
 public function test_nav_menu_locations_should_be_updated_on_split()
 {
     global $wpdb;
     $cat_term = wp_insert_term('Foo Menu', 'category');
     $shared_term_id = $cat_term['term_id'];
     $nav_term_id = wp_create_nav_menu('Foo Menu');
     $nav_term = get_term($nav_term_id, 'nav_menu');
     // Manually modify because shared terms shouldn't naturally occur.
     $wpdb->update($wpdb->term_taxonomy, array('term_id' => $shared_term_id), array('term_taxonomy_id' => $nav_term->term_taxonomy_id));
     set_theme_mod('nav_menu_locations', array('foo' => $shared_term_id));
     // Splitsville.
     $new_term_id = _split_shared_term($shared_term_id, $nav_term->term_taxonomy_id);
     $locations = get_nav_menu_locations();
     $this->assertEquals($new_term_id, $locations['foo']);
 }