wp_insert_term

The timeline below displays how wordpress function wp_insert_term has changed across different WordPress versions. If a version is not listed, refer to the next available version below.

WordPress Version: 6.5

/**
 * Adds a new term to the database.
 *
 * A non-existent term is inserted in the following sequence:
 * 1. The term is added to the term table, then related to the taxonomy.
 * 2. If everything is correct, several actions are fired.
 * 3. The 'term_id_filter' is evaluated.
 * 4. The term cache is cleaned.
 * 5. Several more actions are fired.
 * 6. An array is returned containing the `term_id` and `term_taxonomy_id`.
 *
 * If the 'slug' argument is not empty, then it is checked to see if the term
 * is invalid. If it is not a valid, existing term, it is added and the term_id
 * is given.
 *
 * If the taxonomy is hierarchical, and the 'parent' argument is not empty,
 * the term is inserted and the term_id will be given.
 *
 * Error handling:
 * If `$taxonomy` does not exist or `$term` is empty,
 * a WP_Error object will be returned.
 *
 * If the term already exists on the same hierarchical level,
 * or the term slug and name are not unique, a WP_Error object will be returned.
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @since 2.3.0
 *
 * @param string       $term     The term name to add.
 * @param string       $taxonomy The taxonomy to which to add the term.
 * @param array|string $args {
 *     Optional. Array or query string of arguments for inserting a term.
 *
 *     @type string $alias_of    Slug of the term to make this term an alias of.
 *                               Default empty string. Accepts a term slug.
 *     @type string $description The term description. Default empty string.
 *     @type int    $parent      The id of the parent term. Default 0.
 *     @type string $slug        The term slug to use. Default empty string.
 * }
 * @return array|WP_Error {
 *     An array of the new term data, WP_Error otherwise.
 *
 *     @type int        $term_id          The new term ID.
 *     @type int|string $term_taxonomy_id The new term taxonomy ID. Can be a numeric string.
 * }
 */
function wp_insert_term($term, $taxonomy, $args = array())
{
    global $wpdb;
    if (!taxonomy_exists($taxonomy)) {
        return new WP_Error('invalid_taxonomy', __('Invalid taxonomy.'));
    }
    /**
     * Filters a term before it is sanitized and inserted into the database.
     *
     * @since 3.0.0
     * @since 6.1.0 The `$args` parameter was added.
     *
     * @param string|WP_Error $term     The term name to add, or a WP_Error object if there's an error.
     * @param string          $taxonomy Taxonomy slug.
     * @param array|string    $args     Array or query string of arguments passed to wp_insert_term().
     */
    $term = apply_filters('pre_insert_term', $term, $taxonomy, $args);
    if (is_wp_error($term)) {
        return $term;
    }
    if (is_int($term) && 0 === $term) {
        return new WP_Error('invalid_term_id', __('Invalid term ID.'));
    }
    if ('' === trim($term)) {
        return new WP_Error('empty_term_name', __('A name is required for this term.'));
    }
    $defaults = array('alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => '');
    $args = wp_parse_args($args, $defaults);
    if ((int) $args['parent'] > 0 && !term_exists((int) $args['parent'])) {
        return new WP_Error('missing_parent', __('Parent term does not exist.'));
    }
    $args['name'] = $term;
    $args['taxonomy'] = $taxonomy;
    // Coerce null description to strings, to avoid database errors.
    $args['description'] = (string) $args['description'];
    $args = sanitize_term($args, $taxonomy, 'db');
    // expected_slashed ($name)
    $name = wp_unslash($args['name']);
    $description = wp_unslash($args['description']);
    $parent = (int) $args['parent'];
    // Sanitization could clean the name to an empty string that must be checked again.
    if ('' === $name) {
        return new WP_Error('invalid_term_name', __('Invalid term name.'));
    }
    $slug_provided = !empty($args['slug']);
    if (!$slug_provided) {
        $slug = sanitize_title($name);
    } else {
        $slug = $args['slug'];
    }
    $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));
        }
    }
    /*
     * Prevent the creation of terms with duplicate names at the same level of a taxonomy hierarchy,
     * unless a unique slug has been explicitly provided.
     */
    $name_matches = get_terms(array('taxonomy' => $taxonomy, 'name' => $name, 'hide_empty' => false, 'parent' => $args['parent'], 'update_term_meta_cache' => false));
    /*
     * The `name` match in `get_terms()` doesn't differentiate accented characters,
     * so we do a stricter comparison here.
     */
    $name_match = null;
    if ($name_matches) {
        foreach ($name_matches as $_match) {
            if (strtolower($name) === strtolower($_match->name)) {
                $name_match = $_match;
                break;
            }
        }
    }
    if ($name_match) {
        $slug_match = get_term_by('slug', $slug, $taxonomy);
        if (!$slug_provided || $name_match->slug === $slug || $slug_match) {
            if (is_taxonomy_hierarchical($taxonomy)) {
                $siblings = get_terms(array('taxonomy' => $taxonomy, 'get' => 'all', 'parent' => $parent, 'update_term_meta_cache' => false));
                $existing_term = null;
                $sibling_names = wp_list_pluck($siblings, 'name');
                $sibling_slugs = wp_list_pluck($siblings, 'slug');
                if ((!$slug_provided || $name_match->slug === $slug) && in_array($name, $sibling_names, true)) {
                    $existing_term = $name_match;
                } elseif ($slug_match && in_array($slug, $sibling_slugs, true)) {
                    $existing_term = $slug_match;
                }
                if ($existing_term) {
                    return new WP_Error('term_exists', __('A term with the name provided already exists with this parent.'), $existing_term->term_id);
                }
            } else {
                return new WP_Error('term_exists', __('A term with the name provided already exists in this taxonomy.'), $name_match->term_id);
            }
        }
    }
    $slug = wp_unique_term_slug($slug, (object) $args);
    $data = compact('name', 'slug', 'term_group');
    /**
     * Filters term data before it is inserted into the database.
     *
     * @since 4.7.0
     *
     * @param array  $data     Term data to be inserted.
     * @param string $taxonomy Taxonomy slug.
     * @param array  $args     Arguments passed to wp_insert_term().
     */
    $data = apply_filters('wp_insert_term_data', $data, $taxonomy, $args);
    if (false === $wpdb->insert($wpdb->terms, $data)) {
        return new WP_Error('db_insert_error', __('Could not insert term into the database.'), $wpdb->last_error);
    }
    $term_id = (int) $wpdb->insert_id;
    // Seems unreachable. However, is used in the case that a term name is provided, which sanitizes to an empty string.
    if (empty($slug)) {
        $slug = sanitize_title($slug, $term_id);
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edit_terms', $term_id, $taxonomy);
        $wpdb->update($wpdb->terms, compact('slug'), compact('term_id'));
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edited_terms', $term_id, $taxonomy);
    }
    $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));
    if (!empty($tt_id)) {
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    if (false === $wpdb->insert($wpdb->term_taxonomy, compact('term_id', 'taxonomy', 'description', 'parent') + array('count' => 0))) {
        return new WP_Error('db_insert_error', __('Could not insert term taxonomy into the database.'), $wpdb->last_error);
    }
    $tt_id = (int) $wpdb->insert_id;
    /*
     * Confidence check: if we just created a term with the same parent + taxonomy + slug but a higher term_id than
     * an existing term, then we have unwittingly created a duplicate term. Delete the dupe, and use the term_id
     * and term_taxonomy_id of the older term instead. Then return out of the function so that the "create" hooks
     * are not fired.
     */
    $duplicate_term = $wpdb->get_row($wpdb->prepare("SELECT t.term_id, t.slug, tt.term_taxonomy_id, tt.taxonomy FROM {$wpdb->terms} AS t INNER JOIN {$wpdb->term_taxonomy} AS tt ON ( tt.term_id = t.term_id ) WHERE t.slug = %s AND tt.parent = %d AND tt.taxonomy = %s AND t.term_id < %d AND tt.term_taxonomy_id != %d", $slug, $parent, $taxonomy, $term_id, $tt_id));
    /**
     * Filters the duplicate term check that takes place during term creation.
     *
     * Term parent + taxonomy + slug combinations are meant to be unique, and wp_insert_term()
     * performs a last-minute confirmation of this uniqueness before allowing a new term
     * to be created. Plugins with different uniqueness requirements may use this filter
     * to bypass or modify the duplicate-term check.
     *
     * @since 5.1.0
     *
     * @param object $duplicate_term Duplicate term row from terms table, if found.
     * @param string $term           Term being inserted.
     * @param string $taxonomy       Taxonomy name.
     * @param array  $args           Arguments passed to wp_insert_term().
     * @param int    $tt_id          term_taxonomy_id for the newly created term.
     */
    $duplicate_term = apply_filters('wp_insert_term_duplicate_term_check', $duplicate_term, $term, $taxonomy, $args, $tt_id);
    if ($duplicate_term) {
        $wpdb->delete($wpdb->terms, array('term_id' => $term_id));
        $wpdb->delete($wpdb->term_taxonomy, array('term_taxonomy_id' => $tt_id));
        $term_id = (int) $duplicate_term->term_id;
        $tt_id = (int) $duplicate_term->term_taxonomy_id;
        clean_term_cache($term_id, $taxonomy);
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    /**
     * Fires immediately after a new term is created, before the term cache is cleaned.
     *
     * The {@see 'create_$taxonomy'} hook is also available for targeting a specific
     * taxonomy.
     *
     * @since 2.3.0
     * @since 6.1.0 The `$args` parameter was added.
     *
     * @param int    $term_id  Term ID.
     * @param int    $tt_id    Term taxonomy ID.
     * @param string $taxonomy Taxonomy slug.
     * @param array  $args     Arguments passed to wp_insert_term().
     */
    do_action('create_term', $term_id, $tt_id, $taxonomy, $args);
    /**
     * Fires after a new term is created for a specific taxonomy.
     *
     * The dynamic portion of the hook name, `$taxonomy`, refers
     * to the slug of the taxonomy the term was created for.
     *
     * Possible hook names include:
     *
     *  - `create_category`
     *  - `create_post_tag`
     *
     * @since 2.3.0
     * @since 6.1.0 The `$args` parameter was added.
     *
     * @param int   $term_id Term ID.
     * @param int   $tt_id   Term taxonomy ID.
     * @param array $args    Arguments passed to wp_insert_term().
     */
    do_action("create_{$taxonomy}", $term_id, $tt_id, $args);
    /**
     * Filters the term ID after a new term is created.
     *
     * @since 2.3.0
     * @since 6.1.0 The `$args` parameter was added.
     *
     * @param int   $term_id Term ID.
     * @param int   $tt_id   Term taxonomy ID.
     * @param array $args    Arguments passed to wp_insert_term().
     */
    $term_id = apply_filters('term_id_filter', $term_id, $tt_id, $args);
    clean_term_cache($term_id, $taxonomy);
    /**
     * Fires after a new term is created, and after the term cache has been cleaned.
     *
     * The {@see 'created_$taxonomy'} hook is also available for targeting a specific
     * taxonomy.
     *
     * @since 2.3.0
     * @since 6.1.0 The `$args` parameter was added.
     *
     * @param int    $term_id  Term ID.
     * @param int    $tt_id    Term taxonomy ID.
     * @param string $taxonomy Taxonomy slug.
     * @param array  $args     Arguments passed to wp_insert_term().
     */
    do_action('created_term', $term_id, $tt_id, $taxonomy, $args);
    /**
     * Fires after a new term in a specific taxonomy is created, and after the term
     * cache has been cleaned.
     *
     * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
     *
     * Possible hook names include:
     *
     *  - `created_category`
     *  - `created_post_tag`
     *
     * @since 2.3.0
     * @since 6.1.0 The `$args` parameter was added.
     *
     * @param int   $term_id Term ID.
     * @param int   $tt_id   Term taxonomy ID.
     * @param array $args    Arguments passed to wp_insert_term().
     */
    do_action("created_{$taxonomy}", $term_id, $tt_id, $args);
    /**
     * Fires after a term has been saved, and the term cache has been cleared.
     *
     * The {@see 'saved_$taxonomy'} hook is also available for targeting a specific
     * taxonomy.
     *
     * @since 5.5.0
     * @since 6.1.0 The `$args` parameter was added.
     *
     * @param int    $term_id  Term ID.
     * @param int    $tt_id    Term taxonomy ID.
     * @param string $taxonomy Taxonomy slug.
     * @param bool   $update   Whether this is an existing term being updated.
     * @param array  $args     Arguments passed to wp_insert_term().
     */
    do_action('saved_term', $term_id, $tt_id, $taxonomy, false, $args);
    /**
     * Fires after a term in a specific taxonomy has been saved, and the term
     * cache has been cleared.
     *
     * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
     *
     * Possible hook names include:
     *
     *  - `saved_category`
     *  - `saved_post_tag`
     *
     * @since 5.5.0
     * @since 6.1.0 The `$args` parameter was added.
     *
     * @param int   $term_id Term ID.
     * @param int   $tt_id   Term taxonomy ID.
     * @param bool  $update  Whether this is an existing term being updated.
     * @param array $args    Arguments passed to wp_insert_term().
     */
    do_action("saved_{$taxonomy}", $term_id, $tt_id, false, $args);
    return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
}

WordPress Version: 6.1

/**
 * Adds a new term to the database.
 *
 * A non-existent term is inserted in the following sequence:
 * 1. The term is added to the term table, then related to the taxonomy.
 * 2. If everything is correct, several actions are fired.
 * 3. The 'term_id_filter' is evaluated.
 * 4. The term cache is cleaned.
 * 5. Several more actions are fired.
 * 6. An array is returned containing the `term_id` and `term_taxonomy_id`.
 *
 * If the 'slug' argument is not empty, then it is checked to see if the term
 * is invalid. If it is not a valid, existing term, it is added and the term_id
 * is given.
 *
 * If the taxonomy is hierarchical, and the 'parent' argument is not empty,
 * the term is inserted and the term_id will be given.
 *
 * Error handling:
 * If `$taxonomy` does not exist or `$term` is empty,
 * a WP_Error object will be returned.
 *
 * If the term already exists on the same hierarchical level,
 * or the term slug and name are not unique, a WP_Error object will be returned.
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @since 2.3.0
 *
 * @param string       $term     The term name to add.
 * @param string       $taxonomy The taxonomy to which to add the term.
 * @param array|string $args {
 *     Optional. Array or query string of arguments for inserting a term.
 *
 *     @type string $alias_of    Slug of the term to make this term an alias of.
 *                               Default empty string. Accepts a term slug.
 *     @type string $description The term description. Default empty string.
 *     @type int    $parent      The id of the parent term. Default 0.
 *     @type string $slug        The term slug to use. Default empty string.
 * }
 * @return array|WP_Error {
 *     An array of the new term data, WP_Error otherwise.
 *
 *     @type int        $term_id          The new term ID.
 *     @type int|string $term_taxonomy_id The new term taxonomy ID. Can be a numeric string.
 * }
 */
function wp_insert_term($term, $taxonomy, $args = array())
{
    global $wpdb;
    if (!taxonomy_exists($taxonomy)) {
        return new WP_Error('invalid_taxonomy', __('Invalid taxonomy.'));
    }
    /**
     * Filters a term before it is sanitized and inserted into the database.
     *
     * @since 3.0.0
     * @since 6.1.0 The `$args` parameter was added.
     *
     * @param string|WP_Error $term     The term name to add, or a WP_Error object if there's an error.
     * @param string          $taxonomy Taxonomy slug.
     * @param array|string    $args     Array or query string of arguments passed to wp_insert_term().
     */
    $term = apply_filters('pre_insert_term', $term, $taxonomy, $args);
    if (is_wp_error($term)) {
        return $term;
    }
    if (is_int($term) && 0 === $term) {
        return new WP_Error('invalid_term_id', __('Invalid term ID.'));
    }
    if ('' === trim($term)) {
        return new WP_Error('empty_term_name', __('A name is required for this term.'));
    }
    $defaults = array('alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => '');
    $args = wp_parse_args($args, $defaults);
    if ((int) $args['parent'] > 0 && !term_exists((int) $args['parent'])) {
        return new WP_Error('missing_parent', __('Parent term does not exist.'));
    }
    $args['name'] = $term;
    $args['taxonomy'] = $taxonomy;
    // Coerce null description to strings, to avoid database errors.
    $args['description'] = (string) $args['description'];
    $args = sanitize_term($args, $taxonomy, 'db');
    // expected_slashed ($name)
    $name = wp_unslash($args['name']);
    $description = wp_unslash($args['description']);
    $parent = (int) $args['parent'];
    $slug_provided = !empty($args['slug']);
    if (!$slug_provided) {
        $slug = sanitize_title($name);
    } else {
        $slug = $args['slug'];
    }
    $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));
        }
    }
    /*
     * Prevent the creation of terms with duplicate names at the same level of a taxonomy hierarchy,
     * unless a unique slug has been explicitly provided.
     */
    $name_matches = get_terms(array('taxonomy' => $taxonomy, 'name' => $name, 'hide_empty' => false, 'parent' => $args['parent'], 'update_term_meta_cache' => false));
    /*
     * The `name` match in `get_terms()` doesn't differentiate accented characters,
     * so we do a stricter comparison here.
     */
    $name_match = null;
    if ($name_matches) {
        foreach ($name_matches as $_match) {
            if (strtolower($name) === strtolower($_match->name)) {
                $name_match = $_match;
                break;
            }
        }
    }
    if ($name_match) {
        $slug_match = get_term_by('slug', $slug, $taxonomy);
        if (!$slug_provided || $name_match->slug === $slug || $slug_match) {
            if (is_taxonomy_hierarchical($taxonomy)) {
                $siblings = get_terms(array('taxonomy' => $taxonomy, 'get' => 'all', 'parent' => $parent, 'update_term_meta_cache' => false));
                $existing_term = null;
                $sibling_names = wp_list_pluck($siblings, 'name');
                $sibling_slugs = wp_list_pluck($siblings, 'slug');
                if ((!$slug_provided || $name_match->slug === $slug) && in_array($name, $sibling_names, true)) {
                    $existing_term = $name_match;
                } elseif ($slug_match && in_array($slug, $sibling_slugs, true)) {
                    $existing_term = $slug_match;
                }
                if ($existing_term) {
                    return new WP_Error('term_exists', __('A term with the name provided already exists with this parent.'), $existing_term->term_id);
                }
            } else {
                return new WP_Error('term_exists', __('A term with the name provided already exists in this taxonomy.'), $name_match->term_id);
            }
        }
    }
    $slug = wp_unique_term_slug($slug, (object) $args);
    $data = compact('name', 'slug', 'term_group');
    /**
     * Filters term data before it is inserted into the database.
     *
     * @since 4.7.0
     *
     * @param array  $data     Term data to be inserted.
     * @param string $taxonomy Taxonomy slug.
     * @param array  $args     Arguments passed to wp_insert_term().
     */
    $data = apply_filters('wp_insert_term_data', $data, $taxonomy, $args);
    if (false === $wpdb->insert($wpdb->terms, $data)) {
        return new WP_Error('db_insert_error', __('Could not insert term into the database.'), $wpdb->last_error);
    }
    $term_id = (int) $wpdb->insert_id;
    // Seems unreachable. However, is used in the case that a term name is provided, which sanitizes to an empty string.
    if (empty($slug)) {
        $slug = sanitize_title($slug, $term_id);
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edit_terms', $term_id, $taxonomy);
        $wpdb->update($wpdb->terms, compact('slug'), compact('term_id'));
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edited_terms', $term_id, $taxonomy);
    }
    $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));
    if (!empty($tt_id)) {
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    if (false === $wpdb->insert($wpdb->term_taxonomy, compact('term_id', 'taxonomy', 'description', 'parent') + array('count' => 0))) {
        return new WP_Error('db_insert_error', __('Could not insert term taxonomy into the database.'), $wpdb->last_error);
    }
    $tt_id = (int) $wpdb->insert_id;
    /*
     * Sanity check: if we just created a term with the same parent + taxonomy + slug but a higher term_id than
     * an existing term, then we have unwittingly created a duplicate term. Delete the dupe, and use the term_id
     * and term_taxonomy_id of the older term instead. Then return out of the function so that the "create" hooks
     * are not fired.
     */
    $duplicate_term = $wpdb->get_row($wpdb->prepare("SELECT t.term_id, t.slug, tt.term_taxonomy_id, tt.taxonomy FROM {$wpdb->terms} AS t INNER JOIN {$wpdb->term_taxonomy} AS tt ON ( tt.term_id = t.term_id ) WHERE t.slug = %s AND tt.parent = %d AND tt.taxonomy = %s AND t.term_id < %d AND tt.term_taxonomy_id != %d", $slug, $parent, $taxonomy, $term_id, $tt_id));
    /**
     * Filters the duplicate term check that takes place during term creation.
     *
     * Term parent + taxonomy + slug combinations are meant to be unique, and wp_insert_term()
     * performs a last-minute confirmation of this uniqueness before allowing a new term
     * to be created. Plugins with different uniqueness requirements may use this filter
     * to bypass or modify the duplicate-term check.
     *
     * @since 5.1.0
     *
     * @param object $duplicate_term Duplicate term row from terms table, if found.
     * @param string $term           Term being inserted.
     * @param string $taxonomy       Taxonomy name.
     * @param array  $args           Arguments passed to wp_insert_term().
     * @param int    $tt_id          term_taxonomy_id for the newly created term.
     */
    $duplicate_term = apply_filters('wp_insert_term_duplicate_term_check', $duplicate_term, $term, $taxonomy, $args, $tt_id);
    if ($duplicate_term) {
        $wpdb->delete($wpdb->terms, array('term_id' => $term_id));
        $wpdb->delete($wpdb->term_taxonomy, array('term_taxonomy_id' => $tt_id));
        $term_id = (int) $duplicate_term->term_id;
        $tt_id = (int) $duplicate_term->term_taxonomy_id;
        clean_term_cache($term_id, $taxonomy);
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    /**
     * Fires immediately after a new term is created, before the term cache is cleaned.
     *
     * The {@see 'create_$taxonomy'} hook is also available for targeting a specific
     * taxonomy.
     *
     * @since 2.3.0
     * @since 6.1.0 The `$args` parameter was added.
     *
     * @param int    $term_id  Term ID.
     * @param int    $tt_id    Term taxonomy ID.
     * @param string $taxonomy Taxonomy slug.
     * @param array  $args     Arguments passed to wp_insert_term().
     */
    do_action('create_term', $term_id, $tt_id, $taxonomy, $args);
    /**
     * Fires after a new term is created for a specific taxonomy.
     *
     * The dynamic portion of the hook name, `$taxonomy`, refers
     * to the slug of the taxonomy the term was created for.
     *
     * Possible hook names include:
     *
     *  - `create_category`
     *  - `create_post_tag`
     *
     * @since 2.3.0
     * @since 6.1.0 The `$args` parameter was added.
     *
     * @param int   $term_id Term ID.
     * @param int   $tt_id   Term taxonomy ID.
     * @param array $args    Arguments passed to wp_insert_term().
     */
    do_action("create_{$taxonomy}", $term_id, $tt_id, $args);
    /**
     * Filters the term ID after a new term is created.
     *
     * @since 2.3.0
     * @since 6.1.0 The `$args` parameter was added.
     *
     * @param int   $term_id Term ID.
     * @param int   $tt_id   Term taxonomy ID.
     * @param array $args    Arguments passed to wp_insert_term().
     */
    $term_id = apply_filters('term_id_filter', $term_id, $tt_id, $args);
    clean_term_cache($term_id, $taxonomy);
    /**
     * Fires after a new term is created, and after the term cache has been cleaned.
     *
     * The {@see 'created_$taxonomy'} hook is also available for targeting a specific
     * taxonomy.
     *
     * @since 2.3.0
     * @since 6.1.0 The `$args` parameter was added.
     *
     * @param int    $term_id  Term ID.
     * @param int    $tt_id    Term taxonomy ID.
     * @param string $taxonomy Taxonomy slug.
     * @param array  $args     Arguments passed to wp_insert_term().
     */
    do_action('created_term', $term_id, $tt_id, $taxonomy, $args);
    /**
     * Fires after a new term in a specific taxonomy is created, and after the term
     * cache has been cleaned.
     *
     * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
     *
     * Possible hook names include:
     *
     *  - `created_category`
     *  - `created_post_tag`
     *
     * @since 2.3.0
     * @since 6.1.0 The `$args` parameter was added.
     *
     * @param int   $term_id Term ID.
     * @param int   $tt_id   Term taxonomy ID.
     * @param array $args    Arguments passed to wp_insert_term().
     */
    do_action("created_{$taxonomy}", $term_id, $tt_id, $args);
    /**
     * Fires after a term has been saved, and the term cache has been cleared.
     *
     * The {@see 'saved_$taxonomy'} hook is also available for targeting a specific
     * taxonomy.
     *
     * @since 5.5.0
     * @since 6.1.0 The `$args` parameter was added.
     *
     * @param int    $term_id  Term ID.
     * @param int    $tt_id    Term taxonomy ID.
     * @param string $taxonomy Taxonomy slug.
     * @param bool   $update   Whether this is an existing term being updated.
     * @param array  $args     Arguments passed to wp_insert_term().
     */
    do_action('saved_term', $term_id, $tt_id, $taxonomy, false, $args);
    /**
     * Fires after a term in a specific taxonomy has been saved, and the term
     * cache has been cleared.
     *
     * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
     *
     * Possible hook names include:
     *
     *  - `saved_category`
     *  - `saved_post_tag`
     *
     * @since 5.5.0
     * @since 6.1.0 The `$args` parameter was added.
     *
     * @param int   $term_id Term ID.
     * @param int   $tt_id   Term taxonomy ID.
     * @param bool  $update  Whether this is an existing term being updated.
     * @param array $args    Arguments passed to wp_insert_term().
     */
    do_action("saved_{$taxonomy}", $term_id, $tt_id, false, $args);
    return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
}

WordPress Version: 5.8

/**
 * Add a new term to the database.
 *
 * A non-existent term is inserted in the following sequence:
 * 1. The term is added to the term table, then related to the taxonomy.
 * 2. If everything is correct, several actions are fired.
 * 3. The 'term_id_filter' is evaluated.
 * 4. The term cache is cleaned.
 * 5. Several more actions are fired.
 * 6. An array is returned containing the `term_id` and `term_taxonomy_id`.
 *
 * If the 'slug' argument is not empty, then it is checked to see if the term
 * is invalid. If it is not a valid, existing term, it is added and the term_id
 * is given.
 *
 * If the taxonomy is hierarchical, and the 'parent' argument is not empty,
 * the term is inserted and the term_id will be given.
 *
 * Error handling:
 * If `$taxonomy` does not exist or `$term` is empty,
 * a WP_Error object will be returned.
 *
 * If the term already exists on the same hierarchical level,
 * or the term slug and name are not unique, a WP_Error object will be returned.
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @since 2.3.0
 *
 * @param string       $term     The term name to add.
 * @param string       $taxonomy The taxonomy to which to add the term.
 * @param array|string $args {
 *     Optional. Array or query string of arguments for inserting a term.
 *
 *     @type string $alias_of    Slug of the term to make this term an alias of.
 *                               Default empty string. Accepts a term slug.
 *     @type string $description The term description. Default empty string.
 *     @type int    $parent      The id of the parent term. Default 0.
 *     @type string $slug        The term slug to use. Default empty string.
 * }
 * @return array|WP_Error {
 *     An array of the new term data, WP_Error otherwise.
 *
 *     @type int        $term_id          The new term ID.
 *     @type int|string $term_taxonomy_id The new term taxonomy ID. Can be a numeric string.
 * }
 */
function wp_insert_term($term, $taxonomy, $args = array())
{
    global $wpdb;
    if (!taxonomy_exists($taxonomy)) {
        return new WP_Error('invalid_taxonomy', __('Invalid taxonomy.'));
    }
    /**
     * Filters a term before it is sanitized and inserted into the database.
     *
     * @since 3.0.0
     *
     * @param string|WP_Error $term     The term name to add, or a WP_Error object if there's an error.
     * @param string          $taxonomy Taxonomy slug.
     */
    $term = apply_filters('pre_insert_term', $term, $taxonomy);
    if (is_wp_error($term)) {
        return $term;
    }
    if (is_int($term) && 0 === $term) {
        return new WP_Error('invalid_term_id', __('Invalid term ID.'));
    }
    if ('' === trim($term)) {
        return new WP_Error('empty_term_name', __('A name is required for this term.'));
    }
    $defaults = array('alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => '');
    $args = wp_parse_args($args, $defaults);
    if ((int) $args['parent'] > 0 && !term_exists((int) $args['parent'])) {
        return new WP_Error('missing_parent', __('Parent term does not exist.'));
    }
    $args['name'] = $term;
    $args['taxonomy'] = $taxonomy;
    // Coerce null description to strings, to avoid database errors.
    $args['description'] = (string) $args['description'];
    $args = sanitize_term($args, $taxonomy, 'db');
    // expected_slashed ($name)
    $name = wp_unslash($args['name']);
    $description = wp_unslash($args['description']);
    $parent = (int) $args['parent'];
    $slug_provided = !empty($args['slug']);
    if (!$slug_provided) {
        $slug = sanitize_title($name);
    } else {
        $slug = $args['slug'];
    }
    $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));
        }
    }
    /*
     * Prevent the creation of terms with duplicate names at the same level of a taxonomy hierarchy,
     * unless a unique slug has been explicitly provided.
     */
    $name_matches = get_terms(array('taxonomy' => $taxonomy, 'name' => $name, 'hide_empty' => false, 'parent' => $args['parent'], 'update_term_meta_cache' => false));
    /*
     * The `name` match in `get_terms()` doesn't differentiate accented characters,
     * so we do a stricter comparison here.
     */
    $name_match = null;
    if ($name_matches) {
        foreach ($name_matches as $_match) {
            if (strtolower($name) === strtolower($_match->name)) {
                $name_match = $_match;
                break;
            }
        }
    }
    if ($name_match) {
        $slug_match = get_term_by('slug', $slug, $taxonomy);
        if (!$slug_provided || $name_match->slug === $slug || $slug_match) {
            if (is_taxonomy_hierarchical($taxonomy)) {
                $siblings = get_terms(array('taxonomy' => $taxonomy, 'get' => 'all', 'parent' => $parent, 'update_term_meta_cache' => false));
                $existing_term = null;
                $sibling_names = wp_list_pluck($siblings, 'name');
                $sibling_slugs = wp_list_pluck($siblings, 'slug');
                if ((!$slug_provided || $name_match->slug === $slug) && in_array($name, $sibling_names, true)) {
                    $existing_term = $name_match;
                } elseif ($slug_match && in_array($slug, $sibling_slugs, true)) {
                    $existing_term = $slug_match;
                }
                if ($existing_term) {
                    return new WP_Error('term_exists', __('A term with the name provided already exists with this parent.'), $existing_term->term_id);
                }
            } else {
                return new WP_Error('term_exists', __('A term with the name provided already exists in this taxonomy.'), $name_match->term_id);
            }
        }
    }
    $slug = wp_unique_term_slug($slug, (object) $args);
    $data = compact('name', 'slug', 'term_group');
    /**
     * Filters term data before it is inserted into the database.
     *
     * @since 4.7.0
     *
     * @param array  $data     Term data to be inserted.
     * @param string $taxonomy Taxonomy slug.
     * @param array  $args     Arguments passed to wp_insert_term().
     */
    $data = apply_filters('wp_insert_term_data', $data, $taxonomy, $args);
    if (false === $wpdb->insert($wpdb->terms, $data)) {
        return new WP_Error('db_insert_error', __('Could not insert term into the database.'), $wpdb->last_error);
    }
    $term_id = (int) $wpdb->insert_id;
    // Seems unreachable. However, is used in the case that a term name is provided, which sanitizes to an empty string.
    if (empty($slug)) {
        $slug = sanitize_title($slug, $term_id);
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edit_terms', $term_id, $taxonomy);
        $wpdb->update($wpdb->terms, compact('slug'), compact('term_id'));
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edited_terms', $term_id, $taxonomy);
    }
    $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));
    if (!empty($tt_id)) {
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    if (false === $wpdb->insert($wpdb->term_taxonomy, compact('term_id', 'taxonomy', 'description', 'parent') + array('count' => 0))) {
        return new WP_Error('db_insert_error', __('Could not insert term taxonomy into the database.'), $wpdb->last_error);
    }
    $tt_id = (int) $wpdb->insert_id;
    /*
     * Sanity check: if we just created a term with the same parent + taxonomy + slug but a higher term_id than
     * an existing term, then we have unwittingly created a duplicate term. Delete the dupe, and use the term_id
     * and term_taxonomy_id of the older term instead. Then return out of the function so that the "create" hooks
     * are not fired.
     */
    $duplicate_term = $wpdb->get_row($wpdb->prepare("SELECT t.term_id, t.slug, tt.term_taxonomy_id, tt.taxonomy FROM {$wpdb->terms} t INNER JOIN {$wpdb->term_taxonomy} tt ON ( tt.term_id = t.term_id ) WHERE t.slug = %s AND tt.parent = %d AND tt.taxonomy = %s AND t.term_id < %d AND tt.term_taxonomy_id != %d", $slug, $parent, $taxonomy, $term_id, $tt_id));
    /**
     * Filters the duplicate term check that takes place during term creation.
     *
     * Term parent+taxonomy+slug combinations are meant to be unique, and wp_insert_term()
     * performs a last-minute confirmation of this uniqueness before allowing a new term
     * to be created. Plugins with different uniqueness requirements may use this filter
     * to bypass or modify the duplicate-term check.
     *
     * @since 5.1.0
     *
     * @param object $duplicate_term Duplicate term row from terms table, if found.
     * @param string $term           Term being inserted.
     * @param string $taxonomy       Taxonomy name.
     * @param array  $args           Term arguments passed to the function.
     * @param int    $tt_id          term_taxonomy_id for the newly created term.
     */
    $duplicate_term = apply_filters('wp_insert_term_duplicate_term_check', $duplicate_term, $term, $taxonomy, $args, $tt_id);
    if ($duplicate_term) {
        $wpdb->delete($wpdb->terms, array('term_id' => $term_id));
        $wpdb->delete($wpdb->term_taxonomy, array('term_taxonomy_id' => $tt_id));
        $term_id = (int) $duplicate_term->term_id;
        $tt_id = (int) $duplicate_term->term_taxonomy_id;
        clean_term_cache($term_id, $taxonomy);
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    /**
     * Fires immediately after a new term is created, before the term cache is cleaned.
     *
     * The {@see 'create_$taxonomy'} hook is also available for targeting a specific
     * taxonomy.
     *
     * @since 2.3.0
     *
     * @param int    $term_id  Term ID.
     * @param int    $tt_id    Term taxonomy ID.
     * @param string $taxonomy Taxonomy slug.
     */
    do_action('create_term', $term_id, $tt_id, $taxonomy);
    /**
     * Fires after a new term is created for a specific taxonomy.
     *
     * The dynamic portion of the hook name, `$taxonomy`, refers
     * to the slug of the taxonomy the term was created for.
     *
     * Possible hook names include:
     *
     *  - `create_category`
     *  - `create_post_tag`
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Term taxonomy ID.
     */
    do_action("create_{$taxonomy}", $term_id, $tt_id);
    /**
     * Filters the term ID after a new term is created.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Term taxonomy ID.
     */
    $term_id = apply_filters('term_id_filter', $term_id, $tt_id);
    clean_term_cache($term_id, $taxonomy);
    /**
     * Fires after a new term is created, and after the term cache has been cleaned.
     *
     * The {@see 'created_$taxonomy'} hook is also available for targeting a specific
     * taxonomy.
     *
     * @since 2.3.0
     *
     * @param int    $term_id  Term ID.
     * @param int    $tt_id    Term taxonomy ID.
     * @param string $taxonomy Taxonomy slug.
     */
    do_action('created_term', $term_id, $tt_id, $taxonomy);
    /**
     * Fires after a new term in a specific taxonomy is created, and after the term
     * cache has been cleaned.
     *
     * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
     *
     * Possible hook names include:
     *
     *  - `created_category`
     *  - `created_post_tag`
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Term taxonomy ID.
     */
    do_action("created_{$taxonomy}", $term_id, $tt_id);
    /**
     * Fires after a term has been saved, and the term cache has been cleared.
     *
     * The {@see 'saved_$taxonomy'} hook is also available for targeting a specific
     * taxonomy.
     *
     * @since 5.5.0
     *
     * @param int    $term_id  Term ID.
     * @param int    $tt_id    Term taxonomy ID.
     * @param string $taxonomy Taxonomy slug.
     * @param bool   $update   Whether this is an existing term being updated.
     */
    do_action('saved_term', $term_id, $tt_id, $taxonomy, false);
    /**
     * Fires after a term in a specific taxonomy has been saved, and the term
     * cache has been cleared.
     *
     * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
     *
     * Possible hook names include:
     *
     *  - `saved_category`
     *  - `saved_post_tag`
     *
     * @since 5.5.0
     *
     * @param int  $term_id Term ID.
     * @param int  $tt_id   Term taxonomy ID.
     * @param bool $update  Whether this is an existing term being updated.
     */
    do_action("saved_{$taxonomy}", $term_id, $tt_id, false);
    return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
}

WordPress Version: 5.7

/**
 * Add a new term to the database.
 *
 * A non-existent term is inserted in the following sequence:
 * 1. The term is added to the term table, then related to the taxonomy.
 * 2. If everything is correct, several actions are fired.
 * 3. The 'term_id_filter' is evaluated.
 * 4. The term cache is cleaned.
 * 5. Several more actions are fired.
 * 6. An array is returned containing the `term_id` and `term_taxonomy_id`.
 *
 * If the 'slug' argument is not empty, then it is checked to see if the term
 * is invalid. If it is not a valid, existing term, it is added and the term_id
 * is given.
 *
 * If the taxonomy is hierarchical, and the 'parent' argument is not empty,
 * the term is inserted and the term_id will be given.
 *
 * Error handling:
 * If `$taxonomy` does not exist or `$term` is empty,
 * a WP_Error object will be returned.
 *
 * If the term already exists on the same hierarchical level,
 * or the term slug and name are not unique, a WP_Error object will be returned.
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @since 2.3.0
 *
 * @param string       $term     The term name to add.
 * @param string       $taxonomy The taxonomy to which to add the term.
 * @param array|string $args {
 *     Optional. Array or query string of arguments for inserting a term.
 *
 *     @type string $alias_of    Slug of the term to make this term an alias of.
 *                               Default empty string. Accepts a term slug.
 *     @type string $description The term description. Default empty string.
 *     @type int    $parent      The id of the parent term. Default 0.
 *     @type string $slug        The term slug to use. Default empty string.
 * }
 * @return array|WP_Error {
 *     An array of the new term data, WP_Error otherwise.
 *
 *     @type int        $term_id          The new term ID.
 *     @type int|string $term_taxonomy_id The new term taxonomy ID. Can be a numeric string.
 * }
 */
function wp_insert_term($term, $taxonomy, $args = array())
{
    global $wpdb;
    if (!taxonomy_exists($taxonomy)) {
        return new WP_Error('invalid_taxonomy', __('Invalid taxonomy.'));
    }
    /**
     * Filters a term before it is sanitized and inserted into the database.
     *
     * @since 3.0.0
     *
     * @param string|WP_Error $term     The term name to add, or a WP_Error object if there's an error.
     * @param string          $taxonomy Taxonomy slug.
     */
    $term = apply_filters('pre_insert_term', $term, $taxonomy);
    if (is_wp_error($term)) {
        return $term;
    }
    if (is_int($term) && 0 === $term) {
        return new WP_Error('invalid_term_id', __('Invalid term ID.'));
    }
    if ('' === trim($term)) {
        return new WP_Error('empty_term_name', __('A name is required for this term.'));
    }
    $defaults = array('alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => '');
    $args = wp_parse_args($args, $defaults);
    if ((int) $args['parent'] > 0 && !term_exists((int) $args['parent'])) {
        return new WP_Error('missing_parent', __('Parent term does not exist.'));
    }
    $args['name'] = $term;
    $args['taxonomy'] = $taxonomy;
    // Coerce null description to strings, to avoid database errors.
    $args['description'] = (string) $args['description'];
    $args = sanitize_term($args, $taxonomy, 'db');
    // expected_slashed ($name)
    $name = wp_unslash($args['name']);
    $description = wp_unslash($args['description']);
    $parent = (int) $args['parent'];
    $slug_provided = !empty($args['slug']);
    if (!$slug_provided) {
        $slug = sanitize_title($name);
    } else {
        $slug = $args['slug'];
    }
    $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));
        }
    }
    /*
     * Prevent the creation of terms with duplicate names at the same level of a taxonomy hierarchy,
     * unless a unique slug has been explicitly provided.
     */
    $name_matches = get_terms(array('taxonomy' => $taxonomy, 'name' => $name, 'hide_empty' => false, 'parent' => $args['parent'], 'update_term_meta_cache' => false));
    /*
     * The `name` match in `get_terms()` doesn't differentiate accented characters,
     * so we do a stricter comparison here.
     */
    $name_match = null;
    if ($name_matches) {
        foreach ($name_matches as $_match) {
            if (strtolower($name) === strtolower($_match->name)) {
                $name_match = $_match;
                break;
            }
        }
    }
    if ($name_match) {
        $slug_match = get_term_by('slug', $slug, $taxonomy);
        if (!$slug_provided || $name_match->slug === $slug || $slug_match) {
            if (is_taxonomy_hierarchical($taxonomy)) {
                $siblings = get_terms(array('taxonomy' => $taxonomy, 'get' => 'all', 'parent' => $parent, 'update_term_meta_cache' => false));
                $existing_term = null;
                $sibling_names = wp_list_pluck($siblings, 'name');
                $sibling_slugs = wp_list_pluck($siblings, 'slug');
                if ((!$slug_provided || $name_match->slug === $slug) && in_array($name, $sibling_names, true)) {
                    $existing_term = $name_match;
                } elseif ($slug_match && in_array($slug, $sibling_slugs, true)) {
                    $existing_term = $slug_match;
                }
                if ($existing_term) {
                    return new WP_Error('term_exists', __('A term with the name provided already exists with this parent.'), $existing_term->term_id);
                }
            } else {
                return new WP_Error('term_exists', __('A term with the name provided already exists in this taxonomy.'), $name_match->term_id);
            }
        }
    }
    $slug = wp_unique_term_slug($slug, (object) $args);
    $data = compact('name', 'slug', 'term_group');
    /**
     * Filters term data before it is inserted into the database.
     *
     * @since 4.7.0
     *
     * @param array  $data     Term data to be inserted.
     * @param string $taxonomy Taxonomy slug.
     * @param array  $args     Arguments passed to wp_insert_term().
     */
    $data = apply_filters('wp_insert_term_data', $data, $taxonomy, $args);
    if (false === $wpdb->insert($wpdb->terms, $data)) {
        return new WP_Error('db_insert_error', __('Could not insert term into the database.'), $wpdb->last_error);
    }
    $term_id = (int) $wpdb->insert_id;
    // Seems unreachable. However, is used in the case that a term name is provided, which sanitizes to an empty string.
    if (empty($slug)) {
        $slug = sanitize_title($slug, $term_id);
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edit_terms', $term_id, $taxonomy);
        $wpdb->update($wpdb->terms, compact('slug'), compact('term_id'));
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edited_terms', $term_id, $taxonomy);
    }
    $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));
    if (!empty($tt_id)) {
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    if (false === $wpdb->insert($wpdb->term_taxonomy, compact('term_id', 'taxonomy', 'description', 'parent') + array('count' => 0))) {
        return new WP_Error('db_insert_error', __('Could not insert term taxonomy into the database.'), $wpdb->last_error);
    }
    $tt_id = (int) $wpdb->insert_id;
    /*
     * Sanity check: if we just created a term with the same parent + taxonomy + slug but a higher term_id than
     * an existing term, then we have unwittingly created a duplicate term. Delete the dupe, and use the term_id
     * and term_taxonomy_id of the older term instead. Then return out of the function so that the "create" hooks
     * are not fired.
     */
    $duplicate_term = $wpdb->get_row($wpdb->prepare("SELECT t.term_id, t.slug, tt.term_taxonomy_id, tt.taxonomy FROM {$wpdb->terms} t INNER JOIN {$wpdb->term_taxonomy} tt ON ( tt.term_id = t.term_id ) WHERE t.slug = %s AND tt.parent = %d AND tt.taxonomy = %s AND t.term_id < %d AND tt.term_taxonomy_id != %d", $slug, $parent, $taxonomy, $term_id, $tt_id));
    /**
     * Filters the duplicate term check that takes place during term creation.
     *
     * Term parent+taxonomy+slug combinations are meant to be unique, and wp_insert_term()
     * performs a last-minute confirmation of this uniqueness before allowing a new term
     * to be created. Plugins with different uniqueness requirements may use this filter
     * to bypass or modify the duplicate-term check.
     *
     * @since 5.1.0
     *
     * @param object $duplicate_term Duplicate term row from terms table, if found.
     * @param string $term           Term being inserted.
     * @param string $taxonomy       Taxonomy name.
     * @param array  $args           Term arguments passed to the function.
     * @param int    $tt_id          term_taxonomy_id for the newly created term.
     */
    $duplicate_term = apply_filters('wp_insert_term_duplicate_term_check', $duplicate_term, $term, $taxonomy, $args, $tt_id);
    if ($duplicate_term) {
        $wpdb->delete($wpdb->terms, array('term_id' => $term_id));
        $wpdb->delete($wpdb->term_taxonomy, array('term_taxonomy_id' => $tt_id));
        $term_id = (int) $duplicate_term->term_id;
        $tt_id = (int) $duplicate_term->term_taxonomy_id;
        clean_term_cache($term_id, $taxonomy);
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    /**
     * Fires immediately after a new term is created, before the term cache is cleaned.
     *
     * The {@see 'create_$taxonomy'} hook is also available for targeting a specific
     * taxonomy.
     *
     * @since 2.3.0
     *
     * @param int    $term_id  Term ID.
     * @param int    $tt_id    Term taxonomy ID.
     * @param string $taxonomy Taxonomy slug.
     */
    do_action('create_term', $term_id, $tt_id, $taxonomy);
    /**
     * Fires after a new term is created for a specific taxonomy.
     *
     * The dynamic portion of the hook name, `$taxonomy`, refers
     * to the slug of the taxonomy the term was created for.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Term taxonomy ID.
     */
    do_action("create_{$taxonomy}", $term_id, $tt_id);
    /**
     * Filters the term ID after a new term is created.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Term taxonomy ID.
     */
    $term_id = apply_filters('term_id_filter', $term_id, $tt_id);
    clean_term_cache($term_id, $taxonomy);
    /**
     * Fires after a new term is created, and after the term cache has been cleaned.
     *
     * The {@see 'created_$taxonomy'} hook is also available for targeting a specific
     * taxonomy.
     *
     * @since 2.3.0
     *
     * @param int    $term_id  Term ID.
     * @param int    $tt_id    Term taxonomy ID.
     * @param string $taxonomy Taxonomy slug.
     */
    do_action('created_term', $term_id, $tt_id, $taxonomy);
    /**
     * Fires after a new term in a specific taxonomy is created, and after 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("created_{$taxonomy}", $term_id, $tt_id);
    /**
     * Fires after a term has been saved, and the term cache has been cleared.
     *
     * The {@see 'saved_$taxonomy'} hook is also available for targeting a specific
     * taxonomy.
     *
     * @since 5.5.0
     *
     * @param int    $term_id  Term ID.
     * @param int    $tt_id    Term taxonomy ID.
     * @param string $taxonomy Taxonomy slug.
     * @param bool   $update   Whether this is an existing term being updated.
     */
    do_action('saved_term', $term_id, $tt_id, $taxonomy, false);
    /**
     * Fires after a term in a specific taxonomy has been saved, and the term
     * cache has been cleared.
     *
     * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
     *
     * @since 5.5.0
     *
     * @param int  $term_id Term ID.
     * @param int  $tt_id   Term taxonomy ID.
     * @param bool $update  Whether this is an existing term being updated.
     */
    do_action("saved_{$taxonomy}", $term_id, $tt_id, false);
    return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
}

WordPress Version: 5.6

/**
 * Add a new term to the database.
 *
 * A non-existent term is inserted in the following sequence:
 * 1. The term is added to the term table, then related to the taxonomy.
 * 2. If everything is correct, several actions are fired.
 * 3. The 'term_id_filter' is evaluated.
 * 4. The term cache is cleaned.
 * 5. Several more actions are fired.
 * 6. An array is returned containing the `term_id` and `term_taxonomy_id`.
 *
 * If the 'slug' argument is not empty, then it is checked to see if the term
 * is invalid. If it is not a valid, existing term, it is added and the term_id
 * is given.
 *
 * If the taxonomy is hierarchical, and the 'parent' argument is not empty,
 * the term is inserted and the term_id will be given.
 *
 * Error handling:
 * If `$taxonomy` does not exist or `$term` is empty,
 * a WP_Error object will be returned.
 *
 * If the term already exists on the same hierarchical level,
 * or the term slug and name are not unique, a WP_Error object will be returned.
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @since 2.3.0
 *
 * @param string       $term     The term name to add.
 * @param string       $taxonomy The taxonomy to which to add the term.
 * @param array|string $args {
 *     Optional. Array or query string of arguments for inserting a term.
 *
 *     @type string $alias_of    Slug of the term to make this term an alias of.
 *                               Default empty string. Accepts a term slug.
 *     @type string $description The term description. Default empty string.
 *     @type int    $parent      The id of the parent term. Default 0.
 *     @type string $slug        The term slug to use. Default empty string.
 * }
 * @return array|WP_Error An array containing the `term_id` and `term_taxonomy_id`,
 *                        WP_Error otherwise.
 */
function wp_insert_term($term, $taxonomy, $args = array())
{
    global $wpdb;
    if (!taxonomy_exists($taxonomy)) {
        return new WP_Error('invalid_taxonomy', __('Invalid taxonomy.'));
    }
    /**
     * Filters a term before it is sanitized and inserted into the database.
     *
     * @since 3.0.0
     *
     * @param string|WP_Error $term     The term name to add, or a WP_Error object if there's an error.
     * @param string          $taxonomy Taxonomy slug.
     */
    $term = apply_filters('pre_insert_term', $term, $taxonomy);
    if (is_wp_error($term)) {
        return $term;
    }
    if (is_int($term) && 0 === $term) {
        return new WP_Error('invalid_term_id', __('Invalid term ID.'));
    }
    if ('' === trim($term)) {
        return new WP_Error('empty_term_name', __('A name is required for this term.'));
    }
    $defaults = array('alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => '');
    $args = wp_parse_args($args, $defaults);
    if ((int) $args['parent'] > 0 && !term_exists((int) $args['parent'])) {
        return new WP_Error('missing_parent', __('Parent term does not exist.'));
    }
    $args['name'] = $term;
    $args['taxonomy'] = $taxonomy;
    // Coerce null description to strings, to avoid database errors.
    $args['description'] = (string) $args['description'];
    $args = sanitize_term($args, $taxonomy, 'db');
    // expected_slashed ($name)
    $name = wp_unslash($args['name']);
    $description = wp_unslash($args['description']);
    $parent = (int) $args['parent'];
    $slug_provided = !empty($args['slug']);
    if (!$slug_provided) {
        $slug = sanitize_title($name);
    } else {
        $slug = $args['slug'];
    }
    $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));
        }
    }
    /*
     * Prevent the creation of terms with duplicate names at the same level of a taxonomy hierarchy,
     * unless a unique slug has been explicitly provided.
     */
    $name_matches = get_terms(array('taxonomy' => $taxonomy, 'name' => $name, 'hide_empty' => false, 'parent' => $args['parent'], 'update_term_meta_cache' => false));
    /*
     * The `name` match in `get_terms()` doesn't differentiate accented characters,
     * so we do a stricter comparison here.
     */
    $name_match = null;
    if ($name_matches) {
        foreach ($name_matches as $_match) {
            if (strtolower($name) === strtolower($_match->name)) {
                $name_match = $_match;
                break;
            }
        }
    }
    if ($name_match) {
        $slug_match = get_term_by('slug', $slug, $taxonomy);
        if (!$slug_provided || $name_match->slug === $slug || $slug_match) {
            if (is_taxonomy_hierarchical($taxonomy)) {
                $siblings = get_terms(array('taxonomy' => $taxonomy, 'get' => 'all', 'parent' => $parent, 'update_term_meta_cache' => false));
                $existing_term = null;
                $sibling_names = wp_list_pluck($siblings, 'name');
                $sibling_slugs = wp_list_pluck($siblings, 'slug');
                if ((!$slug_provided || $name_match->slug === $slug) && in_array($name, $sibling_names, true)) {
                    $existing_term = $name_match;
                } elseif ($slug_match && in_array($slug, $sibling_slugs, true)) {
                    $existing_term = $slug_match;
                }
                if ($existing_term) {
                    return new WP_Error('term_exists', __('A term with the name provided already exists with this parent.'), $existing_term->term_id);
                }
            } else {
                return new WP_Error('term_exists', __('A term with the name provided already exists in this taxonomy.'), $name_match->term_id);
            }
        }
    }
    $slug = wp_unique_term_slug($slug, (object) $args);
    $data = compact('name', 'slug', 'term_group');
    /**
     * Filters term data before it is inserted into the database.
     *
     * @since 4.7.0
     *
     * @param array  $data     Term data to be inserted.
     * @param string $taxonomy Taxonomy slug.
     * @param array  $args     Arguments passed to wp_insert_term().
     */
    $data = apply_filters('wp_insert_term_data', $data, $taxonomy, $args);
    if (false === $wpdb->insert($wpdb->terms, $data)) {
        return new WP_Error('db_insert_error', __('Could not insert term into the database.'), $wpdb->last_error);
    }
    $term_id = (int) $wpdb->insert_id;
    // Seems unreachable. However, is used in the case that a term name is provided, which sanitizes to an empty string.
    if (empty($slug)) {
        $slug = sanitize_title($slug, $term_id);
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edit_terms', $term_id, $taxonomy);
        $wpdb->update($wpdb->terms, compact('slug'), compact('term_id'));
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edited_terms', $term_id, $taxonomy);
    }
    $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));
    if (!empty($tt_id)) {
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    if (false === $wpdb->insert($wpdb->term_taxonomy, compact('term_id', 'taxonomy', 'description', 'parent') + array('count' => 0))) {
        return new WP_Error('db_insert_error', __('Could not insert term taxonomy into the database.'), $wpdb->last_error);
    }
    $tt_id = (int) $wpdb->insert_id;
    /*
     * Sanity check: if we just created a term with the same parent + taxonomy + slug but a higher term_id than
     * an existing term, then we have unwittingly created a duplicate term. Delete the dupe, and use the term_id
     * and term_taxonomy_id of the older term instead. Then return out of the function so that the "create" hooks
     * are not fired.
     */
    $duplicate_term = $wpdb->get_row($wpdb->prepare("SELECT t.term_id, t.slug, tt.term_taxonomy_id, tt.taxonomy FROM {$wpdb->terms} t INNER JOIN {$wpdb->term_taxonomy} tt ON ( tt.term_id = t.term_id ) WHERE t.slug = %s AND tt.parent = %d AND tt.taxonomy = %s AND t.term_id < %d AND tt.term_taxonomy_id != %d", $slug, $parent, $taxonomy, $term_id, $tt_id));
    /**
     * Filters the duplicate term check that takes place during term creation.
     *
     * Term parent+taxonomy+slug combinations are meant to be unique, and wp_insert_term()
     * performs a last-minute confirmation of this uniqueness before allowing a new term
     * to be created. Plugins with different uniqueness requirements may use this filter
     * to bypass or modify the duplicate-term check.
     *
     * @since 5.1.0
     *
     * @param object $duplicate_term Duplicate term row from terms table, if found.
     * @param string $term           Term being inserted.
     * @param string $taxonomy       Taxonomy name.
     * @param array  $args           Term arguments passed to the function.
     * @param int    $tt_id          term_taxonomy_id for the newly created term.
     */
    $duplicate_term = apply_filters('wp_insert_term_duplicate_term_check', $duplicate_term, $term, $taxonomy, $args, $tt_id);
    if ($duplicate_term) {
        $wpdb->delete($wpdb->terms, array('term_id' => $term_id));
        $wpdb->delete($wpdb->term_taxonomy, array('term_taxonomy_id' => $tt_id));
        $term_id = (int) $duplicate_term->term_id;
        $tt_id = (int) $duplicate_term->term_taxonomy_id;
        clean_term_cache($term_id, $taxonomy);
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    /**
     * Fires immediately after a new term is created, before the term cache is cleaned.
     *
     * The {@see 'create_$taxonomy'} hook is also available for targeting a specific
     * taxonomy.
     *
     * @since 2.3.0
     *
     * @param int    $term_id  Term ID.
     * @param int    $tt_id    Term taxonomy ID.
     * @param string $taxonomy Taxonomy slug.
     */
    do_action('create_term', $term_id, $tt_id, $taxonomy);
    /**
     * Fires after a new term is created for a specific taxonomy.
     *
     * The dynamic portion of the hook name, `$taxonomy`, refers
     * to the slug of the taxonomy the term was created for.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Term taxonomy ID.
     */
    do_action("create_{$taxonomy}", $term_id, $tt_id);
    /**
     * Filters the term ID after a new term is created.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Term taxonomy ID.
     */
    $term_id = apply_filters('term_id_filter', $term_id, $tt_id);
    clean_term_cache($term_id, $taxonomy);
    /**
     * Fires after a new term is created, and after the term cache has been cleaned.
     *
     * The {@see 'created_$taxonomy'} hook is also available for targeting a specific
     * taxonomy.
     *
     * @since 2.3.0
     *
     * @param int    $term_id  Term ID.
     * @param int    $tt_id    Term taxonomy ID.
     * @param string $taxonomy Taxonomy slug.
     */
    do_action('created_term', $term_id, $tt_id, $taxonomy);
    /**
     * Fires after a new term in a specific taxonomy is created, and after 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("created_{$taxonomy}", $term_id, $tt_id);
    /**
     * Fires after a term has been saved, and the term cache has been cleared.
     *
     * The {@see 'saved_$taxonomy'} hook is also available for targeting a specific
     * taxonomy.
     *
     * @since 5.5.0
     *
     * @param int    $term_id  Term ID.
     * @param int    $tt_id    Term taxonomy ID.
     * @param string $taxonomy Taxonomy slug.
     * @param bool   $update   Whether this is an existing term being updated.
     */
    do_action('saved_term', $term_id, $tt_id, $taxonomy, false);
    /**
     * Fires after a term in a specific taxonomy has been saved, and the term
     * cache has been cleared.
     *
     * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
     *
     * @since 5.5.0
     *
     * @param int  $term_id Term ID.
     * @param int  $tt_id   Term taxonomy ID.
     * @param bool $update  Whether this is an existing term being updated.
     */
    do_action("saved_{$taxonomy}", $term_id, $tt_id, false);
    return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
}

WordPress Version: 5.5

/**
 * Add a new term to the database.
 *
 * A non-existent term is inserted in the following sequence:
 * 1. The term is added to the term table, then related to the taxonomy.
 * 2. If everything is correct, several actions are fired.
 * 3. The 'term_id_filter' is evaluated.
 * 4. The term cache is cleaned.
 * 5. Several more actions are fired.
 * 6. An array is returned containing the `term_id` and `term_taxonomy_id`.
 *
 * If the 'slug' argument is not empty, then it is checked to see if the term
 * is invalid. If it is not a valid, existing term, it is added and the term_id
 * is given.
 *
 * If the taxonomy is hierarchical, and the 'parent' argument is not empty,
 * the term is inserted and the term_id will be given.
 *
 * Error handling:
 * If `$taxonomy` does not exist or `$term` is empty,
 * a WP_Error object will be returned.
 *
 * If the term already exists on the same hierarchical level,
 * or the term slug and name are not unique, a WP_Error object will be returned.
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @since 2.3.0
 *
 * @param string       $term     The term name to add.
 * @param string       $taxonomy The taxonomy to which to add the term.
 * @param array|string $args {
 *     Optional. Array or string of arguments for inserting a term.
 *
 *     @type string $alias_of    Slug of the term to make this term an alias of.
 *                               Default empty string. Accepts a term slug.
 *     @type string $description The term description. Default empty string.
 *     @type int    $parent      The id of the parent term. Default 0.
 *     @type string $slug        The term slug to use. Default empty string.
 * }
 * @return array|WP_Error An array containing the `term_id` and `term_taxonomy_id`,
 *                        WP_Error otherwise.
 */
function wp_insert_term($term, $taxonomy, $args = array())
{
    global $wpdb;
    if (!taxonomy_exists($taxonomy)) {
        return new WP_Error('invalid_taxonomy', __('Invalid taxonomy.'));
    }
    /**
     * Filters a term before it is sanitized and inserted into the database.
     *
     * @since 3.0.0
     *
     * @param string|WP_Error $term     The term name to add, or a WP_Error object if there's an error.
     * @param string          $taxonomy Taxonomy slug.
     */
    $term = apply_filters('pre_insert_term', $term, $taxonomy);
    if (is_wp_error($term)) {
        return $term;
    }
    if (is_int($term) && 0 === $term) {
        return new WP_Error('invalid_term_id', __('Invalid term ID.'));
    }
    if ('' === trim($term)) {
        return new WP_Error('empty_term_name', __('A name is required for this term.'));
    }
    $defaults = array('alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => '');
    $args = wp_parse_args($args, $defaults);
    if ($args['parent'] > 0 && !term_exists((int) $args['parent'])) {
        return new WP_Error('missing_parent', __('Parent term does not exist.'));
    }
    $args['name'] = $term;
    $args['taxonomy'] = $taxonomy;
    // Coerce null description to strings, to avoid database errors.
    $args['description'] = (string) $args['description'];
    $args = sanitize_term($args, $taxonomy, 'db');
    // expected_slashed ($name)
    $name = wp_unslash($args['name']);
    $description = wp_unslash($args['description']);
    $parent = (int) $args['parent'];
    $slug_provided = !empty($args['slug']);
    if (!$slug_provided) {
        $slug = sanitize_title($name);
    } else {
        $slug = $args['slug'];
    }
    $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));
        }
    }
    /*
     * Prevent the creation of terms with duplicate names at the same level of a taxonomy hierarchy,
     * unless a unique slug has been explicitly provided.
     */
    $name_matches = get_terms(array('taxonomy' => $taxonomy, 'name' => $name, 'hide_empty' => false, 'parent' => $args['parent'], 'update_term_meta_cache' => false));
    /*
     * The `name` match in `get_terms()` doesn't differentiate accented characters,
     * so we do a stricter comparison here.
     */
    $name_match = null;
    if ($name_matches) {
        foreach ($name_matches as $_match) {
            if (strtolower($name) === strtolower($_match->name)) {
                $name_match = $_match;
                break;
            }
        }
    }
    if ($name_match) {
        $slug_match = get_term_by('slug', $slug, $taxonomy);
        if (!$slug_provided || $name_match->slug === $slug || $slug_match) {
            if (is_taxonomy_hierarchical($taxonomy)) {
                $siblings = get_terms(array('taxonomy' => $taxonomy, 'get' => 'all', 'parent' => $parent, 'update_term_meta_cache' => false));
                $existing_term = null;
                $sibling_names = wp_list_pluck($siblings, 'name');
                $sibling_slugs = wp_list_pluck($siblings, 'slug');
                if ((!$slug_provided || $name_match->slug === $slug) && in_array($name, $sibling_names, true)) {
                    $existing_term = $name_match;
                } elseif ($slug_match && in_array($slug, $sibling_slugs, true)) {
                    $existing_term = $slug_match;
                }
                if ($existing_term) {
                    return new WP_Error('term_exists', __('A term with the name provided already exists with this parent.'), $existing_term->term_id);
                }
            } else {
                return new WP_Error('term_exists', __('A term with the name provided already exists in this taxonomy.'), $name_match->term_id);
            }
        }
    }
    $slug = wp_unique_term_slug($slug, (object) $args);
    $data = compact('name', 'slug', 'term_group');
    /**
     * Filters term data before it is inserted into the database.
     *
     * @since 4.7.0
     *
     * @param array  $data     Term data to be inserted.
     * @param string $taxonomy Taxonomy slug.
     * @param array  $args     Arguments passed to wp_insert_term().
     */
    $data = apply_filters('wp_insert_term_data', $data, $taxonomy, $args);
    if (false === $wpdb->insert($wpdb->terms, $data)) {
        return new WP_Error('db_insert_error', __('Could not insert term into the database.'), $wpdb->last_error);
    }
    $term_id = (int) $wpdb->insert_id;
    // Seems unreachable. However, is used in the case that a term name is provided, which sanitizes to an empty string.
    if (empty($slug)) {
        $slug = sanitize_title($slug, $term_id);
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edit_terms', $term_id, $taxonomy);
        $wpdb->update($wpdb->terms, compact('slug'), compact('term_id'));
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edited_terms', $term_id, $taxonomy);
    }
    $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));
    if (!empty($tt_id)) {
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    if (false === $wpdb->insert($wpdb->term_taxonomy, compact('term_id', 'taxonomy', 'description', 'parent') + array('count' => 0))) {
        return new WP_Error('db_insert_error', __('Could not insert term taxonomy into the database.'), $wpdb->last_error);
    }
    $tt_id = (int) $wpdb->insert_id;
    /*
     * Sanity check: if we just created a term with the same parent + taxonomy + slug but a higher term_id than
     * an existing term, then we have unwittingly created a duplicate term. Delete the dupe, and use the term_id
     * and term_taxonomy_id of the older term instead. Then return out of the function so that the "create" hooks
     * are not fired.
     */
    $duplicate_term = $wpdb->get_row($wpdb->prepare("SELECT t.term_id, t.slug, tt.term_taxonomy_id, tt.taxonomy FROM {$wpdb->terms} t INNER JOIN {$wpdb->term_taxonomy} tt ON ( tt.term_id = t.term_id ) WHERE t.slug = %s AND tt.parent = %d AND tt.taxonomy = %s AND t.term_id < %d AND tt.term_taxonomy_id != %d", $slug, $parent, $taxonomy, $term_id, $tt_id));
    /**
     * Filters the duplicate term check that takes place during term creation.
     *
     * Term parent+taxonomy+slug combinations are meant to be unique, and wp_insert_term()
     * performs a last-minute confirmation of this uniqueness before allowing a new term
     * to be created. Plugins with different uniqueness requirements may use this filter
     * to bypass or modify the duplicate-term check.
     *
     * @since 5.1.0
     *
     * @param object $duplicate_term Duplicate term row from terms table, if found.
     * @param string $term           Term being inserted.
     * @param string $taxonomy       Taxonomy name.
     * @param array  $args           Term arguments passed to the function.
     * @param int    $tt_id          term_taxonomy_id for the newly created term.
     */
    $duplicate_term = apply_filters('wp_insert_term_duplicate_term_check', $duplicate_term, $term, $taxonomy, $args, $tt_id);
    if ($duplicate_term) {
        $wpdb->delete($wpdb->terms, array('term_id' => $term_id));
        $wpdb->delete($wpdb->term_taxonomy, array('term_taxonomy_id' => $tt_id));
        $term_id = (int) $duplicate_term->term_id;
        $tt_id = (int) $duplicate_term->term_taxonomy_id;
        clean_term_cache($term_id, $taxonomy);
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    /**
     * Fires immediately after a new term is created, before the term cache is 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('create_term', $term_id, $tt_id, $taxonomy);
    /**
     * Fires after a new term is created for a specific taxonomy.
     *
     * The dynamic portion of the hook name, `$taxonomy`, refers
     * to the slug of the taxonomy the term was created for.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Term taxonomy ID.
     */
    do_action("create_{$taxonomy}", $term_id, $tt_id);
    /**
     * Filters the term ID after a new term is created.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Term taxonomy ID.
     */
    $term_id = apply_filters('term_id_filter', $term_id, $tt_id);
    clean_term_cache($term_id, $taxonomy);
    /**
     * Fires after a new term is created, and after 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('created_term', $term_id, $tt_id, $taxonomy);
    /**
     * Fires after a new term in a specific taxonomy is created, and after 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("created_{$taxonomy}", $term_id, $tt_id);
    /**
     * Fires after a term has been saved, and the term cache has been cleared.
     *
     * @since 5.5.0
     *
     * @param int    $term_id  Term ID.
     * @param int    $tt_id    Term taxonomy ID.
     * @param string $taxonomy Taxonomy slug.
     * @param bool   $update   Whether this is an existing term being updated.
     */
    do_action('saved_term', $term_id, $tt_id, $taxonomy, false);
    /**
     * Fires after a term in a specific taxonomy has been saved, and the term
     * cache has been cleared.
     *
     * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
     *
     * @since 5.5.0
     *
     * @param int  $term_id Term ID.
     * @param int  $tt_id   Term taxonomy ID.
     * @param bool $update  Whether this is an existing term being updated.
     */
    do_action("saved_{$taxonomy}", $term_id, $tt_id, false);
    return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
}

WordPress Version: 5.4

/**
 * Add a new term to the database.
 *
 * A non-existent term is inserted in the following sequence:
 * 1. The term is added to the term table, then related to the taxonomy.
 * 2. If everything is correct, several actions are fired.
 * 3. The 'term_id_filter' is evaluated.
 * 4. The term cache is cleaned.
 * 5. Several more actions are fired.
 * 6. An array is returned containing the term_id and term_taxonomy_id.
 *
 * If the 'slug' argument is not empty, then it is checked to see if the term
 * is invalid. If it is not a valid, existing term, it is added and the term_id
 * is given.
 *
 * If the taxonomy is hierarchical, and the 'parent' argument is not empty,
 * the term is inserted and the term_id will be given.
 *
 * Error handling:
 * If $taxonomy does not exist or $term is empty,
 * a WP_Error object will be returned.
 *
 * If the term already exists on the same hierarchical level,
 * or the term slug and name are not unique, a WP_Error object will be returned.
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @since 2.3.0
 *
 * @param string       $term     The term name to add or update.
 * @param string       $taxonomy The taxonomy to which to add the term.
 * @param array|string $args {
 *     Optional. Array or string of arguments for inserting a term.
 *
 *     @type string $alias_of    Slug of the term to make this term an alias of.
 *                               Default empty string. Accepts a term slug.
 *     @type string $description The term description. Default empty string.
 *     @type int    $parent      The id of the parent term. Default 0.
 *     @type string $slug        The term slug to use. Default empty string.
 * }
 * @return array|WP_Error An array containing the `term_id` and `term_taxonomy_id`,
 *                        WP_Error otherwise.
 */
function wp_insert_term($term, $taxonomy, $args = array())
{
    global $wpdb;
    if (!taxonomy_exists($taxonomy)) {
        return new WP_Error('invalid_taxonomy', __('Invalid taxonomy.'));
    }
    /**
     * Filters a term before it is sanitized and inserted into the database.
     *
     * @since 3.0.0
     *
     * @param string|WP_Error $term     The term name to add or update, or a WP_Error object if there's an error.
     * @param string          $taxonomy Taxonomy slug.
     */
    $term = apply_filters('pre_insert_term', $term, $taxonomy);
    if (is_wp_error($term)) {
        return $term;
    }
    if (is_int($term) && 0 === $term) {
        return new WP_Error('invalid_term_id', __('Invalid term ID.'));
    }
    if ('' === trim($term)) {
        return new WP_Error('empty_term_name', __('A name is required for this term.'));
    }
    $defaults = array('alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => '');
    $args = wp_parse_args($args, $defaults);
    if ($args['parent'] > 0 && !term_exists((int) $args['parent'])) {
        return new WP_Error('missing_parent', __('Parent term does not exist.'));
    }
    $args['name'] = $term;
    $args['taxonomy'] = $taxonomy;
    // Coerce null description to strings, to avoid database errors.
    $args['description'] = (string) $args['description'];
    $args = sanitize_term($args, $taxonomy, 'db');
    // expected_slashed ($name)
    $name = wp_unslash($args['name']);
    $description = wp_unslash($args['description']);
    $parent = (int) $args['parent'];
    $slug_provided = !empty($args['slug']);
    if (!$slug_provided) {
        $slug = sanitize_title($name);
    } else {
        $slug = $args['slug'];
    }
    $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));
        }
    }
    /*
     * Prevent the creation of terms with duplicate names at the same level of a taxonomy hierarchy,
     * unless a unique slug has been explicitly provided.
     */
    $name_matches = get_terms(array('taxonomy' => $taxonomy, 'name' => $name, 'hide_empty' => false, 'parent' => $args['parent'], 'update_term_meta_cache' => false));
    /*
     * The `name` match in `get_terms()` doesn't differentiate accented characters,
     * so we do a stricter comparison here.
     */
    $name_match = null;
    if ($name_matches) {
        foreach ($name_matches as $_match) {
            if (strtolower($name) === strtolower($_match->name)) {
                $name_match = $_match;
                break;
            }
        }
    }
    if ($name_match) {
        $slug_match = get_term_by('slug', $slug, $taxonomy);
        if (!$slug_provided || $name_match->slug === $slug || $slug_match) {
            if (is_taxonomy_hierarchical($taxonomy)) {
                $siblings = get_terms(array('taxonomy' => $taxonomy, 'get' => 'all', 'parent' => $parent, 'update_term_meta_cache' => false));
                $existing_term = null;
                if ((!$slug_provided || $name_match->slug === $slug) && in_array($name, wp_list_pluck($siblings, 'name'))) {
                    $existing_term = $name_match;
                } elseif ($slug_match && in_array($slug, wp_list_pluck($siblings, 'slug'))) {
                    $existing_term = $slug_match;
                }
                if ($existing_term) {
                    return new WP_Error('term_exists', __('A term with the name provided already exists with this parent.'), $existing_term->term_id);
                }
            } else {
                return new WP_Error('term_exists', __('A term with the name provided already exists in this taxonomy.'), $name_match->term_id);
            }
        }
    }
    $slug = wp_unique_term_slug($slug, (object) $args);
    $data = compact('name', 'slug', 'term_group');
    /**
     * Filters term data before it is inserted into the database.
     *
     * @since 4.7.0
     *
     * @param array  $data     Term data to be inserted.
     * @param string $taxonomy Taxonomy slug.
     * @param array  $args     Arguments passed to wp_insert_term().
     */
    $data = apply_filters('wp_insert_term_data', $data, $taxonomy, $args);
    if (false === $wpdb->insert($wpdb->terms, $data)) {
        return new WP_Error('db_insert_error', __('Could not insert term into the database.'), $wpdb->last_error);
    }
    $term_id = (int) $wpdb->insert_id;
    // Seems unreachable. However, is used in the case that a term name is provided, which sanitizes to an empty string.
    if (empty($slug)) {
        $slug = sanitize_title($slug, $term_id);
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edit_terms', $term_id, $taxonomy);
        $wpdb->update($wpdb->terms, compact('slug'), compact('term_id'));
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edited_terms', $term_id, $taxonomy);
    }
    $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));
    if (!empty($tt_id)) {
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    if (false === $wpdb->insert($wpdb->term_taxonomy, compact('term_id', 'taxonomy', 'description', 'parent') + array('count' => 0))) {
        return new WP_Error('db_insert_error', __('Could not insert term taxonomy into the database.'), $wpdb->last_error);
    }
    $tt_id = (int) $wpdb->insert_id;
    /*
     * Sanity check: if we just created a term with the same parent + taxonomy + slug but a higher term_id than
     * an existing term, then we have unwittingly created a duplicate term. Delete the dupe, and use the term_id
     * and term_taxonomy_id of the older term instead. Then return out of the function so that the "create" hooks
     * are not fired.
     */
    $duplicate_term = $wpdb->get_row($wpdb->prepare("SELECT t.term_id, t.slug, tt.term_taxonomy_id, tt.taxonomy FROM {$wpdb->terms} t INNER JOIN {$wpdb->term_taxonomy} tt ON ( tt.term_id = t.term_id ) WHERE t.slug = %s AND tt.parent = %d AND tt.taxonomy = %s AND t.term_id < %d AND tt.term_taxonomy_id != %d", $slug, $parent, $taxonomy, $term_id, $tt_id));
    /**
     * Filters the duplicate term check that takes place during term creation.
     *
     * Term parent+taxonomy+slug combinations are meant to be unique, and wp_insert_term()
     * performs a last-minute confirmation of this uniqueness before allowing a new term
     * to be created. Plugins with different uniqueness requirements may use this filter
     * to bypass or modify the duplicate-term check.
     *
     * @since 5.1.0
     *
     * @param object $duplicate_term Duplicate term row from terms table, if found.
     * @param string $term           Term being inserted.
     * @param string $taxonomy       Taxonomy name.
     * @param array  $args           Term arguments passed to the function.
     * @param int    $tt_id          term_taxonomy_id for the newly created term.
     */
    $duplicate_term = apply_filters('wp_insert_term_duplicate_term_check', $duplicate_term, $term, $taxonomy, $args, $tt_id);
    if ($duplicate_term) {
        $wpdb->delete($wpdb->terms, array('term_id' => $term_id));
        $wpdb->delete($wpdb->term_taxonomy, array('term_taxonomy_id' => $tt_id));
        $term_id = (int) $duplicate_term->term_id;
        $tt_id = (int) $duplicate_term->term_taxonomy_id;
        clean_term_cache($term_id, $taxonomy);
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    /**
     * Fires immediately after a new term is created, before the term cache is 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('create_term', $term_id, $tt_id, $taxonomy);
    /**
     * Fires after a new term is created for a specific taxonomy.
     *
     * The dynamic portion of the hook name, `$taxonomy`, refers
     * to the slug of the taxonomy the term was created for.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Term taxonomy ID.
     */
    do_action("create_{$taxonomy}", $term_id, $tt_id);
    /**
     * Filters the term ID after a new term is created.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Taxonomy term ID.
     */
    $term_id = apply_filters('term_id_filter', $term_id, $tt_id);
    clean_term_cache($term_id, $taxonomy);
    /**
     * Fires after a new term is created, and after 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('created_term', $term_id, $tt_id, $taxonomy);
    /**
     * Fires after a new term in a specific taxonomy is created, and after 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("created_{$taxonomy}", $term_id, $tt_id);
    return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
}

WordPress Version: 5.3

/**
 * Add a new term to the database.
 *
 * A non-existent term is inserted in the following sequence:
 * 1. The term is added to the term table, then related to the taxonomy.
 * 2. If everything is correct, several actions are fired.
 * 3. The 'term_id_filter' is evaluated.
 * 4. The term cache is cleaned.
 * 5. Several more actions are fired.
 * 6. An array is returned containing the term_id and term_taxonomy_id.
 *
 * If the 'slug' argument is not empty, then it is checked to see if the term
 * is invalid. If it is not a valid, existing term, it is added and the term_id
 * is given.
 *
 * If the taxonomy is hierarchical, and the 'parent' argument is not empty,
 * the term is inserted and the term_id will be given.
 *
 * Error handling:
 * If $taxonomy does not exist or $term is empty,
 * a WP_Error object will be returned.
 *
 * If the term already exists on the same hierarchical level,
 * or the term slug and name are not unique, a WP_Error object will be returned.
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @since 2.3.0
 *
 * @param string       $term     The term name to add or update.
 * @param string       $taxonomy The taxonomy to which to add the term.
 * @param array|string $args {
 *     Optional. Array or string of arguments for inserting a term.
 *
 *     @type string $alias_of    Slug of the term to make this term an alias of.
 *                               Default empty string. Accepts a term slug.
 *     @type string $description The term description. Default empty string.
 *     @type int    $parent      The id of the parent term. Default 0.
 *     @type string $slug        The term slug to use. Default empty string.
 * }
 * @return array|WP_Error An array containing the `term_id` and `term_taxonomy_id`,
 *                        WP_Error otherwise.
 */
function wp_insert_term($term, $taxonomy, $args = array())
{
    global $wpdb;
    if (!taxonomy_exists($taxonomy)) {
        return new WP_Error('invalid_taxonomy', __('Invalid taxonomy.'));
    }
    /**
     * Filters a term before it is sanitized and inserted into the database.
     *
     * @since 3.0.0
     *
     * @param string|WP_Error $term     The term name to add or update, or a WP_Error object if there's an error.
     * @param string          $taxonomy Taxonomy slug.
     */
    $term = apply_filters('pre_insert_term', $term, $taxonomy);
    if (is_wp_error($term)) {
        return $term;
    }
    if (is_int($term) && 0 === $term) {
        return new WP_Error('invalid_term_id', __('Invalid term ID.'));
    }
    if ('' === trim($term)) {
        return new WP_Error('empty_term_name', __('A name is required for this term.'));
    }
    $defaults = array('alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => '');
    $args = wp_parse_args($args, $defaults);
    if ($args['parent'] > 0 && !term_exists((int) $args['parent'])) {
        return new WP_Error('missing_parent', __('Parent term does not exist.'));
    }
    $args['name'] = $term;
    $args['taxonomy'] = $taxonomy;
    // Coerce null description to strings, to avoid database errors.
    $args['description'] = (string) $args['description'];
    $args = sanitize_term($args, $taxonomy, 'db');
    // expected_slashed ($name)
    $name = wp_unslash($args['name']);
    $description = wp_unslash($args['description']);
    $parent = (int) $args['parent'];
    $slug_provided = !empty($args['slug']);
    if (!$slug_provided) {
        $slug = sanitize_title($name);
    } else {
        $slug = $args['slug'];
    }
    $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));
        }
    }
    /*
     * Prevent the creation of terms with duplicate names at the same level of a taxonomy hierarchy,
     * unless a unique slug has been explicitly provided.
     */
    $name_matches = get_terms(array('taxonomy' => $taxonomy, 'name' => $name, 'hide_empty' => false, 'parent' => $args['parent'], 'update_term_meta_cache' => false));
    /*
     * The `name` match in `get_terms()` doesn't differentiate accented characters,
     * so we do a stricter comparison here.
     */
    $name_match = null;
    if ($name_matches) {
        foreach ($name_matches as $_match) {
            if (strtolower($name) === strtolower($_match->name)) {
                $name_match = $_match;
                break;
            }
        }
    }
    if ($name_match) {
        $slug_match = get_term_by('slug', $slug, $taxonomy);
        if (!$slug_provided || $name_match->slug === $slug || $slug_match) {
            if (is_taxonomy_hierarchical($taxonomy)) {
                $siblings = get_terms(array('taxonomy' => $taxonomy, 'get' => 'all', 'parent' => $parent, 'update_term_meta_cache' => false));
                $existing_term = null;
                if ((!$slug_provided || $name_match->slug === $slug) && in_array($name, wp_list_pluck($siblings, 'name'))) {
                    $existing_term = $name_match;
                } elseif ($slug_match && in_array($slug, wp_list_pluck($siblings, 'slug'))) {
                    $existing_term = $slug_match;
                }
                if ($existing_term) {
                    return new WP_Error('term_exists', __('A term with the name provided already exists with this parent.'), $existing_term->term_id);
                }
            } else {
                return new WP_Error('term_exists', __('A term with the name provided already exists in this taxonomy.'), $name_match->term_id);
            }
        }
    }
    $slug = wp_unique_term_slug($slug, (object) $args);
    $data = compact('name', 'slug', 'term_group');
    /**
     * Filters term data before it is inserted into the database.
     *
     * @since 4.7.0
     *
     * @param array  $data     Term data to be inserted.
     * @param string $taxonomy Taxonomy slug.
     * @param array  $args     Arguments passed to wp_insert_term().
     */
    $data = apply_filters('wp_insert_term_data', $data, $taxonomy, $args);
    if (false === $wpdb->insert($wpdb->terms, $data)) {
        return new WP_Error('db_insert_error', __('Could not insert term into the database.'), $wpdb->last_error);
    }
    $term_id = (int) $wpdb->insert_id;
    // Seems unreachable, However, Is used in the case that a term name is provided, which sanitizes to an empty string.
    if (empty($slug)) {
        $slug = sanitize_title($slug, $term_id);
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edit_terms', $term_id, $taxonomy);
        $wpdb->update($wpdb->terms, compact('slug'), compact('term_id'));
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edited_terms', $term_id, $taxonomy);
    }
    $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));
    if (!empty($tt_id)) {
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    if (false === $wpdb->insert($wpdb->term_taxonomy, compact('term_id', 'taxonomy', 'description', 'parent') + array('count' => 0))) {
        return new WP_Error('db_insert_error', __('Could not insert term taxonomy into the database.'), $wpdb->last_error);
    }
    $tt_id = (int) $wpdb->insert_id;
    /*
     * Sanity check: if we just created a term with the same parent + taxonomy + slug but a higher term_id than
     * an existing term, then we have unwittingly created a duplicate term. Delete the dupe, and use the term_id
     * and term_taxonomy_id of the older term instead. Then return out of the function so that the "create" hooks
     * are not fired.
     */
    $duplicate_term = $wpdb->get_row($wpdb->prepare("SELECT t.term_id, t.slug, tt.term_taxonomy_id, tt.taxonomy FROM {$wpdb->terms} t INNER JOIN {$wpdb->term_taxonomy} tt ON ( tt.term_id = t.term_id ) WHERE t.slug = %s AND tt.parent = %d AND tt.taxonomy = %s AND t.term_id < %d AND tt.term_taxonomy_id != %d", $slug, $parent, $taxonomy, $term_id, $tt_id));
    /**
     * Filters the duplicate term check that takes place during term creation.
     *
     * Term parent+taxonomy+slug combinations are meant to be unique, and wp_insert_term()
     * performs a last-minute confirmation of this uniqueness before allowing a new term
     * to be created. Plugins with different uniqueness requirements may use this filter
     * to bypass or modify the duplicate-term check.
     *
     * @since 5.1.0
     *
     * @param object $duplicate_term Duplicate term row from terms table, if found.
     * @param string $term           Term being inserted.
     * @param string $taxonomy       Taxonomy name.
     * @param array  $args           Term arguments passed to the function.
     * @param int    $tt_id          term_taxonomy_id for the newly created term.
     */
    $duplicate_term = apply_filters('wp_insert_term_duplicate_term_check', $duplicate_term, $term, $taxonomy, $args, $tt_id);
    if ($duplicate_term) {
        $wpdb->delete($wpdb->terms, array('term_id' => $term_id));
        $wpdb->delete($wpdb->term_taxonomy, array('term_taxonomy_id' => $tt_id));
        $term_id = (int) $duplicate_term->term_id;
        $tt_id = (int) $duplicate_term->term_taxonomy_id;
        clean_term_cache($term_id, $taxonomy);
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    /**
     * Fires immediately after a new term is created, before the term cache is 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('create_term', $term_id, $tt_id, $taxonomy);
    /**
     * Fires after a new term is created for a specific taxonomy.
     *
     * The dynamic portion of the hook name, `$taxonomy`, refers
     * to the slug of the taxonomy the term was created for.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Term taxonomy ID.
     */
    do_action("create_{$taxonomy}", $term_id, $tt_id);
    /**
     * Filters the term ID after a new term is created.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Taxonomy term ID.
     */
    $term_id = apply_filters('term_id_filter', $term_id, $tt_id);
    clean_term_cache($term_id, $taxonomy);
    /**
     * Fires after a new term is created, and after 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('created_term', $term_id, $tt_id, $taxonomy);
    /**
     * Fires after a new term in a specific taxonomy is created, and after 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("created_{$taxonomy}", $term_id, $tt_id);
    return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
}

WordPress Version: 5.1

/**
 * Add a new term to the database.
 *
 * A non-existent term is inserted in the following sequence:
 * 1. The term is added to the term table, then related to the taxonomy.
 * 2. If everything is correct, several actions are fired.
 * 3. The 'term_id_filter' is evaluated.
 * 4. The term cache is cleaned.
 * 5. Several more actions are fired.
 * 6. An array is returned containing the term_id and term_taxonomy_id.
 *
 * If the 'slug' argument is not empty, then it is checked to see if the term
 * is invalid. If it is not a valid, existing term, it is added and the term_id
 * is given.
 *
 * If the taxonomy is hierarchical, and the 'parent' argument is not empty,
 * the term is inserted and the term_id will be given.
 *
 * Error handling:
 * If $taxonomy does not exist or $term is empty,
 * a WP_Error object will be returned.
 *
 * If the term already exists on the same hierarchical level,
 * or the term slug and name are not unique, a WP_Error object will be returned.
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @since 2.3.0
 *
 * @param string       $term     The term to add or update.
 * @param string       $taxonomy The taxonomy to which to add the term.
 * @param array|string $args {
 *     Optional. Array or string of arguments for inserting a term.
 *
 *     @type string $alias_of    Slug of the term to make this term an alias of.
 *                               Default empty string. Accepts a term slug.
 *     @type string $description The term description. Default empty string.
 *     @type int    $parent      The id of the parent term. Default 0.
 *     @type string $slug        The term slug to use. Default empty string.
 * }
 * @return array|WP_Error An array containing the `term_id` and `term_taxonomy_id`,
 *                        WP_Error otherwise.
 */
function wp_insert_term($term, $taxonomy, $args = array())
{
    global $wpdb;
    if (!taxonomy_exists($taxonomy)) {
        return new WP_Error('invalid_taxonomy', __('Invalid taxonomy.'));
    }
    /**
     * Filters a term before it is sanitized and inserted into the database.
     *
     * @since 3.0.0
     *
     * @param string $term     The term to add or update.
     * @param string $taxonomy Taxonomy slug.
     */
    $term = apply_filters('pre_insert_term', $term, $taxonomy);
    if (is_wp_error($term)) {
        return $term;
    }
    if (is_int($term) && 0 == $term) {
        return new WP_Error('invalid_term_id', __('Invalid term ID.'));
    }
    if ('' == trim($term)) {
        return new WP_Error('empty_term_name', __('A name is required for this term.'));
    }
    $defaults = array('alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => '');
    $args = wp_parse_args($args, $defaults);
    if ($args['parent'] > 0 && !term_exists((int) $args['parent'])) {
        return new WP_Error('missing_parent', __('Parent term does not exist.'));
    }
    $args['name'] = $term;
    $args['taxonomy'] = $taxonomy;
    // Coerce null description to strings, to avoid database errors.
    $args['description'] = (string) $args['description'];
    $args = sanitize_term($args, $taxonomy, 'db');
    // expected_slashed ($name)
    $name = wp_unslash($args['name']);
    $description = wp_unslash($args['description']);
    $parent = (int) $args['parent'];
    $slug_provided = !empty($args['slug']);
    if (!$slug_provided) {
        $slug = sanitize_title($name);
    } else {
        $slug = $args['slug'];
    }
    $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));
        }
    }
    /*
     * Prevent the creation of terms with duplicate names at the same level of a taxonomy hierarchy,
     * unless a unique slug has been explicitly provided.
     */
    $name_matches = get_terms($taxonomy, array('name' => $name, 'hide_empty' => false, 'parent' => $args['parent'], 'update_term_meta_cache' => false));
    /*
     * The `name` match in `get_terms()` doesn't differentiate accented characters,
     * so we do a stricter comparison here.
     */
    $name_match = null;
    if ($name_matches) {
        foreach ($name_matches as $_match) {
            if (strtolower($name) === strtolower($_match->name)) {
                $name_match = $_match;
                break;
            }
        }
    }
    if ($name_match) {
        $slug_match = get_term_by('slug', $slug, $taxonomy);
        if (!$slug_provided || $name_match->slug === $slug || $slug_match) {
            if (is_taxonomy_hierarchical($taxonomy)) {
                $siblings = get_terms($taxonomy, array('get' => 'all', 'parent' => $parent, 'update_term_meta_cache' => false));
                $existing_term = null;
                if ((!$slug_provided || $name_match->slug === $slug) && in_array($name, wp_list_pluck($siblings, 'name'))) {
                    $existing_term = $name_match;
                } elseif ($slug_match && in_array($slug, wp_list_pluck($siblings, 'slug'))) {
                    $existing_term = $slug_match;
                }
                if ($existing_term) {
                    return new WP_Error('term_exists', __('A term with the name provided already exists with this parent.'), $existing_term->term_id);
                }
            } else {
                return new WP_Error('term_exists', __('A term with the name provided already exists in this taxonomy.'), $name_match->term_id);
            }
        }
    }
    $slug = wp_unique_term_slug($slug, (object) $args);
    $data = compact('name', 'slug', 'term_group');
    /**
     * Filters term data before it is inserted into the database.
     *
     * @since 4.7.0
     *
     * @param array  $data     Term data to be inserted.
     * @param string $taxonomy Taxonomy slug.
     * @param array  $args     Arguments passed to wp_insert_term().
     */
    $data = apply_filters('wp_insert_term_data', $data, $taxonomy, $args);
    if (false === $wpdb->insert($wpdb->terms, $data)) {
        return new WP_Error('db_insert_error', __('Could not insert term into the database.'), $wpdb->last_error);
    }
    $term_id = (int) $wpdb->insert_id;
    // Seems unreachable, However, Is used in the case that a term name is provided, which sanitizes to an empty string.
    if (empty($slug)) {
        $slug = sanitize_title($slug, $term_id);
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edit_terms', $term_id, $taxonomy);
        $wpdb->update($wpdb->terms, compact('slug'), compact('term_id'));
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edited_terms', $term_id, $taxonomy);
    }
    $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));
    if (!empty($tt_id)) {
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    $wpdb->insert($wpdb->term_taxonomy, compact('term_id', 'taxonomy', 'description', 'parent') + array('count' => 0));
    $tt_id = (int) $wpdb->insert_id;
    /*
     * Sanity check: if we just created a term with the same parent + taxonomy + slug but a higher term_id than
     * an existing term, then we have unwittingly created a duplicate term. Delete the dupe, and use the term_id
     * and term_taxonomy_id of the older term instead. Then return out of the function so that the "create" hooks
     * are not fired.
     */
    $duplicate_term = $wpdb->get_row($wpdb->prepare("SELECT t.term_id, t.slug, tt.term_taxonomy_id, tt.taxonomy FROM {$wpdb->terms} t INNER JOIN {$wpdb->term_taxonomy} tt ON ( tt.term_id = t.term_id ) WHERE t.slug = %s AND tt.parent = %d AND tt.taxonomy = %s AND t.term_id < %d AND tt.term_taxonomy_id != %d", $slug, $parent, $taxonomy, $term_id, $tt_id));
    /**
     * Filters the duplicate term check that takes place during term creation.
     *
     * Term parent+taxonomy+slug combinations are meant to be unique, and wp_insert_term()
     * performs a last-minute confirmation of this uniqueness before allowing a new term
     * to be created. Plugins with different uniqueness requirements may use this filter
     * to bypass or modify the duplicate-term check.
     *
     * @since 5.1.0
     *
     * @param object $duplicate_term Duplicate term row from terms table, if found.
     * @param string $term           Term being inserted.
     * @param string $taxonomy       Taxonomy name.
     * @param array  $args           Term arguments passed to the function.
     * @param int    $tt_id          term_taxonomy_id for the newly created term.
     */
    $duplicate_term = apply_filters('wp_insert_term_duplicate_term_check', $duplicate_term, $term, $taxonomy, $args, $tt_id);
    if ($duplicate_term) {
        $wpdb->delete($wpdb->terms, array('term_id' => $term_id));
        $wpdb->delete($wpdb->term_taxonomy, array('term_taxonomy_id' => $tt_id));
        $term_id = (int) $duplicate_term->term_id;
        $tt_id = (int) $duplicate_term->term_taxonomy_id;
        clean_term_cache($term_id, $taxonomy);
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    /**
     * Fires immediately after a new term is created, before the term cache is 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('create_term', $term_id, $tt_id, $taxonomy);
    /**
     * Fires after a new term is created for a specific taxonomy.
     *
     * The dynamic portion of the hook name, `$taxonomy`, refers
     * to the slug of the taxonomy the term was created for.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Term taxonomy ID.
     */
    do_action("create_{$taxonomy}", $term_id, $tt_id);
    /**
     * Filters the term ID after a new term is created.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Taxonomy term ID.
     */
    $term_id = apply_filters('term_id_filter', $term_id, $tt_id);
    clean_term_cache($term_id, $taxonomy);
    /**
     * Fires after a new term is created, and after 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('created_term', $term_id, $tt_id, $taxonomy);
    /**
     * Fires after a new term in a specific taxonomy is created, and after 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("created_{$taxonomy}", $term_id, $tt_id);
    return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
}

WordPress Version: 4.9

/**
 * Add a new term to the database.
 *
 * A non-existent term is inserted in the following sequence:
 * 1. The term is added to the term table, then related to the taxonomy.
 * 2. If everything is correct, several actions are fired.
 * 3. The 'term_id_filter' is evaluated.
 * 4. The term cache is cleaned.
 * 5. Several more actions are fired.
 * 6. An array is returned containing the term_id and term_taxonomy_id.
 *
 * If the 'slug' argument is not empty, then it is checked to see if the term
 * is invalid. If it is not a valid, existing term, it is added and the term_id
 * is given.
 *
 * If the taxonomy is hierarchical, and the 'parent' argument is not empty,
 * the term is inserted and the term_id will be given.
 *
 * Error handling:
 * If $taxonomy does not exist or $term is empty,
 * a WP_Error object will be returned.
 *
 * If the term already exists on the same hierarchical level,
 * or the term slug and name are not unique, a WP_Error object will be returned.
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @since 2.3.0
 *
 * @param string       $term     The term to add or update.
 * @param string       $taxonomy The taxonomy to which to add the term.
 * @param array|string $args {
 *     Optional. Array or string of arguments for inserting a term.
 *
 *     @type string $alias_of    Slug of the term to make this term an alias of.
 *                               Default empty string. Accepts a term slug.
 *     @type string $description The term description. Default empty string.
 *     @type int    $parent      The id of the parent term. Default 0.
 *     @type string $slug        The term slug to use. Default empty string.
 * }
 * @return array|WP_Error An array containing the `term_id` and `term_taxonomy_id`,
 *                        WP_Error otherwise.
 */
function wp_insert_term($term, $taxonomy, $args = array())
{
    global $wpdb;
    if (!taxonomy_exists($taxonomy)) {
        return new WP_Error('invalid_taxonomy', __('Invalid taxonomy.'));
    }
    /**
     * Filters a term before it is sanitized and inserted into the database.
     *
     * @since 3.0.0
     *
     * @param string $term     The term to add or update.
     * @param string $taxonomy Taxonomy slug.
     */
    $term = apply_filters('pre_insert_term', $term, $taxonomy);
    if (is_wp_error($term)) {
        return $term;
    }
    if (is_int($term) && 0 == $term) {
        return new WP_Error('invalid_term_id', __('Invalid term ID.'));
    }
    if ('' == trim($term)) {
        return new WP_Error('empty_term_name', __('A name is required for this term.'));
    }
    $defaults = array('alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => '');
    $args = wp_parse_args($args, $defaults);
    if ($args['parent'] > 0 && !term_exists((int) $args['parent'])) {
        return new WP_Error('missing_parent', __('Parent term does not exist.'));
    }
    $args['name'] = $term;
    $args['taxonomy'] = $taxonomy;
    // Coerce null description to strings, to avoid database errors.
    $args['description'] = (string) $args['description'];
    $args = sanitize_term($args, $taxonomy, 'db');
    // expected_slashed ($name)
    $name = wp_unslash($args['name']);
    $description = wp_unslash($args['description']);
    $parent = (int) $args['parent'];
    $slug_provided = !empty($args['slug']);
    if (!$slug_provided) {
        $slug = sanitize_title($name);
    } else {
        $slug = $args['slug'];
    }
    $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));
        }
    }
    /*
     * Prevent the creation of terms with duplicate names at the same level of a taxonomy hierarchy,
     * unless a unique slug has been explicitly provided.
     */
    $name_matches = get_terms($taxonomy, array('name' => $name, 'hide_empty' => false, 'parent' => $args['parent']));
    /*
     * The `name` match in `get_terms()` doesn't differentiate accented characters,
     * so we do a stricter comparison here.
     */
    $name_match = null;
    if ($name_matches) {
        foreach ($name_matches as $_match) {
            if (strtolower($name) === strtolower($_match->name)) {
                $name_match = $_match;
                break;
            }
        }
    }
    if ($name_match) {
        $slug_match = get_term_by('slug', $slug, $taxonomy);
        if (!$slug_provided || $name_match->slug === $slug || $slug_match) {
            if (is_taxonomy_hierarchical($taxonomy)) {
                $siblings = get_terms($taxonomy, array('get' => 'all', 'parent' => $parent));
                $existing_term = null;
                if ((!$slug_provided || $name_match->slug === $slug) && in_array($name, wp_list_pluck($siblings, 'name'))) {
                    $existing_term = $name_match;
                } elseif ($slug_match && in_array($slug, wp_list_pluck($siblings, 'slug'))) {
                    $existing_term = $slug_match;
                }
                if ($existing_term) {
                    return new WP_Error('term_exists', __('A term with the name provided already exists with this parent.'), $existing_term->term_id);
                }
            } else {
                return new WP_Error('term_exists', __('A term with the name provided already exists in this taxonomy.'), $name_match->term_id);
            }
        }
    }
    $slug = wp_unique_term_slug($slug, (object) $args);
    $data = compact('name', 'slug', 'term_group');
    /**
     * Filters term data before it is inserted into the database.
     *
     * @since 4.7.0
     *
     * @param array  $data     Term data to be inserted.
     * @param string $taxonomy Taxonomy slug.
     * @param array  $args     Arguments passed to wp_insert_term().
     */
    $data = apply_filters('wp_insert_term_data', $data, $taxonomy, $args);
    if (false === $wpdb->insert($wpdb->terms, $data)) {
        return new WP_Error('db_insert_error', __('Could not insert term into the database.'), $wpdb->last_error);
    }
    $term_id = (int) $wpdb->insert_id;
    // Seems unreachable, However, Is used in the case that a term name is provided, which sanitizes to an empty string.
    if (empty($slug)) {
        $slug = sanitize_title($slug, $term_id);
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edit_terms', $term_id, $taxonomy);
        $wpdb->update($wpdb->terms, compact('slug'), compact('term_id'));
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edited_terms', $term_id, $taxonomy);
    }
    $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));
    if (!empty($tt_id)) {
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    $wpdb->insert($wpdb->term_taxonomy, compact('term_id', 'taxonomy', 'description', 'parent') + array('count' => 0));
    $tt_id = (int) $wpdb->insert_id;
    /*
     * Sanity check: if we just created a term with the same parent + taxonomy + slug but a higher term_id than
     * an existing term, then we have unwittingly created a duplicate term. Delete the dupe, and use the term_id
     * and term_taxonomy_id of the older term instead. Then return out of the function so that the "create" hooks
     * are not fired.
     */
    $duplicate_term = $wpdb->get_row($wpdb->prepare("SELECT t.term_id, tt.term_taxonomy_id FROM {$wpdb->terms} t INNER JOIN {$wpdb->term_taxonomy} tt ON ( tt.term_id = t.term_id ) WHERE t.slug = %s AND tt.parent = %d AND tt.taxonomy = %s AND t.term_id < %d AND tt.term_taxonomy_id != %d", $slug, $parent, $taxonomy, $term_id, $tt_id));
    if ($duplicate_term) {
        $wpdb->delete($wpdb->terms, array('term_id' => $term_id));
        $wpdb->delete($wpdb->term_taxonomy, array('term_taxonomy_id' => $tt_id));
        $term_id = (int) $duplicate_term->term_id;
        $tt_id = (int) $duplicate_term->term_taxonomy_id;
        clean_term_cache($term_id, $taxonomy);
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    /**
     * Fires immediately after a new term is created, before the term cache is 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("create_term", $term_id, $tt_id, $taxonomy);
    /**
     * Fires after a new term is created for a specific taxonomy.
     *
     * The dynamic portion of the hook name, `$taxonomy`, refers
     * to the slug of the taxonomy the term was created for.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Term taxonomy ID.
     */
    do_action("create_{$taxonomy}", $term_id, $tt_id);
    /**
     * Filters the term ID after a new term is created.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Taxonomy term ID.
     */
    $term_id = apply_filters('term_id_filter', $term_id, $tt_id);
    clean_term_cache($term_id, $taxonomy);
    /**
     * Fires after a new term is created, and after 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('created_term', $term_id, $tt_id, $taxonomy);
    /**
     * Fires after a new term in a specific taxonomy is created, and after 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("created_{$taxonomy}", $term_id, $tt_id);
    return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
}

WordPress Version: 4.8

/**
 * Add a new term to the database.
 *
 * A non-existent term is inserted in the following sequence:
 * 1. The term is added to the term table, then related to the taxonomy.
 * 2. If everything is correct, several actions are fired.
 * 3. The 'term_id_filter' is evaluated.
 * 4. The term cache is cleaned.
 * 5. Several more actions are fired.
 * 6. An array is returned containing the term_id and term_taxonomy_id.
 *
 * If the 'slug' argument is not empty, then it is checked to see if the term
 * is invalid. If it is not a valid, existing term, it is added and the term_id
 * is given.
 *
 * If the taxonomy is hierarchical, and the 'parent' argument is not empty,
 * the term is inserted and the term_id will be given.
 *
 * Error handling:
 * If $taxonomy does not exist or $term is empty,
 * a WP_Error object will be returned.
 *
 * If the term already exists on the same hierarchical level,
 * or the term slug and name are not unique, a WP_Error object will be returned.
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @since 2.3.0
 *
 * @param string       $term     The term to add or update.
 * @param string       $taxonomy The taxonomy to which to add the term.
 * @param array|string $args {
 *     Optional. Array or string of arguments for inserting a term.
 *
 *     @type string $alias_of    Slug of the term to make this term an alias of.
 *                               Default empty string. Accepts a term slug.
 *     @type string $description The term description. Default empty string.
 *     @type int    $parent      The id of the parent term. Default 0.
 *     @type string $slug        The term slug to use. Default empty string.
 * }
 * @return array|WP_Error An array containing the `term_id` and `term_taxonomy_id`,
 *                        WP_Error otherwise.
 */
function wp_insert_term($term, $taxonomy, $args = array())
{
    global $wpdb;
    if (!taxonomy_exists($taxonomy)) {
        return new WP_Error('invalid_taxonomy', __('Invalid taxonomy.'));
    }
    /**
     * Filters a term before it is sanitized and inserted into the database.
     *
     * @since 3.0.0
     *
     * @param string $term     The term to add or update.
     * @param string $taxonomy Taxonomy slug.
     */
    $term = apply_filters('pre_insert_term', $term, $taxonomy);
    if (is_wp_error($term)) {
        return $term;
    }
    if (is_int($term) && 0 == $term) {
        return new WP_Error('invalid_term_id', __('Invalid term ID.'));
    }
    if ('' == trim($term)) {
        return new WP_Error('empty_term_name', __('A name is required for this term.'));
    }
    $defaults = array('alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => '');
    $args = wp_parse_args($args, $defaults);
    if ($args['parent'] > 0 && !term_exists((int) $args['parent'])) {
        return new WP_Error('missing_parent', __('Parent term does not exist.'));
    }
    $args['name'] = $term;
    $args['taxonomy'] = $taxonomy;
    // Coerce null description to strings, to avoid database errors.
    $args['description'] = (string) $args['description'];
    $args = sanitize_term($args, $taxonomy, 'db');
    // expected_slashed ($name)
    $name = wp_unslash($args['name']);
    $description = wp_unslash($args['description']);
    $parent = (int) $args['parent'];
    $slug_provided = !empty($args['slug']);
    if (!$slug_provided) {
        $slug = sanitize_title($name);
    } else {
        $slug = $args['slug'];
    }
    $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));
        }
    }
    /*
     * Prevent the creation of terms with duplicate names at the same level of a taxonomy hierarchy,
     * unless a unique slug has been explicitly provided.
     */
    $name_matches = get_terms($taxonomy, array('name' => $name, 'hide_empty' => false, 'parent' => $args['parent']));
    /*
     * The `name` match in `get_terms()` doesn't differentiate accented characters,
     * so we do a stricter comparison here.
     */
    $name_match = null;
    if ($name_matches) {
        foreach ($name_matches as $_match) {
            if (strtolower($name) === strtolower($_match->name)) {
                $name_match = $_match;
                break;
            }
        }
    }
    if ($name_match) {
        $slug_match = get_term_by('slug', $slug, $taxonomy);
        if (!$slug_provided || $name_match->slug === $slug || $slug_match) {
            if (is_taxonomy_hierarchical($taxonomy)) {
                $siblings = get_terms($taxonomy, array('get' => 'all', 'parent' => $parent));
                $existing_term = null;
                if ((!$slug_provided || $name_match->slug === $slug) && in_array($name, wp_list_pluck($siblings, 'name'))) {
                    $existing_term = $name_match;
                } elseif ($slug_match && in_array($slug, wp_list_pluck($siblings, 'slug'))) {
                    $existing_term = $slug_match;
                }
                if ($existing_term) {
                    return new WP_Error('term_exists', __('A term with the name provided already exists with this parent.'), $existing_term->term_id);
                }
            } else {
                return new WP_Error('term_exists', __('A term with the name provided already exists in this taxonomy.'), $name_match->term_id);
            }
        }
    }
    $slug = wp_unique_term_slug($slug, (object) $args);
    $data = compact('name', 'slug', 'term_group');
    /**
     * Filters term data before it is inserted into the database.
     *
     * @since 4.7.0
     *
     * @param array  $data     Term data to be inserted.
     * @param string $taxonomy Taxonomy slug.
     * @param array  $args     Arguments passed to wp_insert_term().
     */
    $data = apply_filters('wp_insert_term_data', $data, $taxonomy, $args);
    if (false === $wpdb->insert($wpdb->terms, $data)) {
        return new WP_Error('db_insert_error', __('Could not insert term into the database'), $wpdb->last_error);
    }
    $term_id = (int) $wpdb->insert_id;
    // Seems unreachable, However, Is used in the case that a term name is provided, which sanitizes to an empty string.
    if (empty($slug)) {
        $slug = sanitize_title($slug, $term_id);
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edit_terms', $term_id, $taxonomy);
        $wpdb->update($wpdb->terms, compact('slug'), compact('term_id'));
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edited_terms', $term_id, $taxonomy);
    }
    $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));
    if (!empty($tt_id)) {
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    $wpdb->insert($wpdb->term_taxonomy, compact('term_id', 'taxonomy', 'description', 'parent') + array('count' => 0));
    $tt_id = (int) $wpdb->insert_id;
    /*
     * Sanity check: if we just created a term with the same parent + taxonomy + slug but a higher term_id than
     * an existing term, then we have unwittingly created a duplicate term. Delete the dupe, and use the term_id
     * and term_taxonomy_id of the older term instead. Then return out of the function so that the "create" hooks
     * are not fired.
     */
    $duplicate_term = $wpdb->get_row($wpdb->prepare("SELECT t.term_id, tt.term_taxonomy_id FROM {$wpdb->terms} t INNER JOIN {$wpdb->term_taxonomy} tt ON ( tt.term_id = t.term_id ) WHERE t.slug = %s AND tt.parent = %d AND tt.taxonomy = %s AND t.term_id < %d AND tt.term_taxonomy_id != %d", $slug, $parent, $taxonomy, $term_id, $tt_id));
    if ($duplicate_term) {
        $wpdb->delete($wpdb->terms, array('term_id' => $term_id));
        $wpdb->delete($wpdb->term_taxonomy, array('term_taxonomy_id' => $tt_id));
        $term_id = (int) $duplicate_term->term_id;
        $tt_id = (int) $duplicate_term->term_taxonomy_id;
        clean_term_cache($term_id, $taxonomy);
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    /**
     * Fires immediately after a new term is created, before the term cache is 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("create_term", $term_id, $tt_id, $taxonomy);
    /**
     * Fires after a new term is created for a specific taxonomy.
     *
     * The dynamic portion of the hook name, `$taxonomy`, refers
     * to the slug of the taxonomy the term was created for.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Term taxonomy ID.
     */
    do_action("create_{$taxonomy}", $term_id, $tt_id);
    /**
     * Filters the term ID after a new term is created.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Taxonomy term ID.
     */
    $term_id = apply_filters('term_id_filter', $term_id, $tt_id);
    clean_term_cache($term_id, $taxonomy);
    /**
     * Fires after a new term is created, and after 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('created_term', $term_id, $tt_id, $taxonomy);
    /**
     * Fires after a new term in a specific taxonomy is created, and after 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("created_{$taxonomy}", $term_id, $tt_id);
    return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
}

WordPress Version: 4.7

/**
 * Add a new term to the database.
 *
 * A non-existent term is inserted in the following sequence:
 * 1. The term is added to the term table, then related to the taxonomy.
 * 2. If everything is correct, several actions are fired.
 * 3. The 'term_id_filter' is evaluated.
 * 4. The term cache is cleaned.
 * 5. Several more actions are fired.
 * 6. An array is returned containing the term_id and term_taxonomy_id.
 *
 * If the 'slug' argument is not empty, then it is checked to see if the term
 * is invalid. If it is not a valid, existing term, it is added and the term_id
 * is given.
 *
 * If the taxonomy is hierarchical, and the 'parent' argument is not empty,
 * the term is inserted and the term_id will be given.
 *
 * Error handling:
 * If $taxonomy does not exist or $term is empty,
 * a WP_Error object will be returned.
 *
 * If the term already exists on the same hierarchical level,
 * or the term slug and name are not unique, a WP_Error object will be returned.
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @since 2.3.0
 *
 * @param string       $term     The term to add or update.
 * @param string       $taxonomy The taxonomy to which to add the term.
 * @param array|string $args {
 *     Optional. Array or string of arguments for inserting a term.
 *
 *     @type string $alias_of    Slug of the term to make this term an alias of.
 *                               Default empty string. Accepts a term slug.
 *     @type string $description The term description. Default empty string.
 *     @type int    $parent      The id of the parent term. Default 0.
 *     @type string $slug        The term slug to use. Default empty string.
 * }
 * @return array|WP_Error An array containing the `term_id` and `term_taxonomy_id`,
 *                        WP_Error otherwise.
 */
function wp_insert_term($term, $taxonomy, $args = array())
{
    global $wpdb;
    if (!taxonomy_exists($taxonomy)) {
        return new WP_Error('invalid_taxonomy', __('Invalid taxonomy.'));
    }
    /**
     * Filters a term before it is sanitized and inserted into the database.
     *
     * @since 3.0.0
     *
     * @param string $term     The term to add or update.
     * @param string $taxonomy Taxonomy slug.
     */
    $term = apply_filters('pre_insert_term', $term, $taxonomy);
    if (is_wp_error($term)) {
        return $term;
    }
    if (is_int($term) && 0 == $term) {
        return new WP_Error('invalid_term_id', __('Invalid term ID.'));
    }
    if ('' == trim($term)) {
        return new WP_Error('empty_term_name', __('A name is required for this term.'));
    }
    $defaults = array('alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => '');
    $args = wp_parse_args($args, $defaults);
    if ($args['parent'] > 0 && !term_exists((int) $args['parent'])) {
        return new WP_Error('missing_parent', __('Parent term does not exist.'));
    }
    $args['name'] = $term;
    $args['taxonomy'] = $taxonomy;
    // Coerce null description to strings, to avoid database errors.
    $args['description'] = (string) $args['description'];
    $args = sanitize_term($args, $taxonomy, 'db');
    // expected_slashed ($name)
    $name = wp_unslash($args['name']);
    $description = wp_unslash($args['description']);
    $parent = (int) $args['parent'];
    $slug_provided = !empty($args['slug']);
    if (!$slug_provided) {
        $slug = sanitize_title($name);
    } else {
        $slug = $args['slug'];
    }
    $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));
        }
    }
    /*
     * Prevent the creation of terms with duplicate names at the same level of a taxonomy hierarchy,
     * unless a unique slug has been explicitly provided.
     */
    $name_matches = get_terms($taxonomy, array('name' => $name, 'hide_empty' => false));
    /*
     * The `name` match in `get_terms()` doesn't differentiate accented characters,
     * so we do a stricter comparison here.
     */
    $name_match = null;
    if ($name_matches) {
        foreach ($name_matches as $_match) {
            if (strtolower($name) === strtolower($_match->name)) {
                $name_match = $_match;
                break;
            }
        }
    }
    if ($name_match) {
        $slug_match = get_term_by('slug', $slug, $taxonomy);
        if (!$slug_provided || $name_match->slug === $slug || $slug_match) {
            if (is_taxonomy_hierarchical($taxonomy)) {
                $siblings = get_terms($taxonomy, array('get' => 'all', 'parent' => $parent));
                $existing_term = null;
                if ($name_match->slug === $slug && in_array($name, wp_list_pluck($siblings, 'name'))) {
                    $existing_term = $name_match;
                } elseif ($slug_match && in_array($slug, wp_list_pluck($siblings, 'slug'))) {
                    $existing_term = $slug_match;
                }
                if ($existing_term) {
                    return new WP_Error('term_exists', __('A term with the name provided already exists with this parent.'), $existing_term->term_id);
                }
            } else {
                return new WP_Error('term_exists', __('A term with the name provided already exists in this taxonomy.'), $name_match->term_id);
            }
        }
    }
    $slug = wp_unique_term_slug($slug, (object) $args);
    $data = compact('name', 'slug', 'term_group');
    /**
     * Filters term data before it is inserted into the database.
     *
     * @since 4.7.0
     *
     * @param array  $data     Term data to be inserted.
     * @param string $taxonomy Taxonomy slug.
     * @param array  $args     Arguments passed to wp_insert_term().
     */
    $data = apply_filters('wp_insert_term_data', $data, $taxonomy, $args);
    if (false === $wpdb->insert($wpdb->terms, $data)) {
        return new WP_Error('db_insert_error', __('Could not insert term into the database'), $wpdb->last_error);
    }
    $term_id = (int) $wpdb->insert_id;
    // Seems unreachable, However, Is used in the case that a term name is provided, which sanitizes to an empty string.
    if (empty($slug)) {
        $slug = sanitize_title($slug, $term_id);
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edit_terms', $term_id, $taxonomy);
        $wpdb->update($wpdb->terms, compact('slug'), compact('term_id'));
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edited_terms', $term_id, $taxonomy);
    }
    $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));
    if (!empty($tt_id)) {
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    $wpdb->insert($wpdb->term_taxonomy, compact('term_id', 'taxonomy', 'description', 'parent') + array('count' => 0));
    $tt_id = (int) $wpdb->insert_id;
    /*
     * Sanity check: if we just created a term with the same parent + taxonomy + slug but a higher term_id than
     * an existing term, then we have unwittingly created a duplicate term. Delete the dupe, and use the term_id
     * and term_taxonomy_id of the older term instead. Then return out of the function so that the "create" hooks
     * are not fired.
     */
    $duplicate_term = $wpdb->get_row($wpdb->prepare("SELECT t.term_id, tt.term_taxonomy_id FROM {$wpdb->terms} t INNER JOIN {$wpdb->term_taxonomy} tt ON ( tt.term_id = t.term_id ) WHERE t.slug = %s AND tt.parent = %d AND tt.taxonomy = %s AND t.term_id < %d AND tt.term_taxonomy_id != %d", $slug, $parent, $taxonomy, $term_id, $tt_id));
    if ($duplicate_term) {
        $wpdb->delete($wpdb->terms, array('term_id' => $term_id));
        $wpdb->delete($wpdb->term_taxonomy, array('term_taxonomy_id' => $tt_id));
        $term_id = (int) $duplicate_term->term_id;
        $tt_id = (int) $duplicate_term->term_taxonomy_id;
        clean_term_cache($term_id, $taxonomy);
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    /**
     * Fires immediately after a new term is created, before the term cache is 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("create_term", $term_id, $tt_id, $taxonomy);
    /**
     * Fires after a new term is created for a specific taxonomy.
     *
     * The dynamic portion of the hook name, `$taxonomy`, refers
     * to the slug of the taxonomy the term was created for.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Term taxonomy ID.
     */
    do_action("create_{$taxonomy}", $term_id, $tt_id);
    /**
     * Filters the term ID after a new term is created.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Taxonomy term ID.
     */
    $term_id = apply_filters('term_id_filter', $term_id, $tt_id);
    clean_term_cache($term_id, $taxonomy);
    /**
     * Fires after a new term is created, and after 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('created_term', $term_id, $tt_id, $taxonomy);
    /**
     * Fires after a new term in a specific taxonomy is created, and after 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("created_{$taxonomy}", $term_id, $tt_id);
    return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
}

WordPress Version: 4.6

/**
 * Add a new term to the database.
 *
 * A non-existent term is inserted in the following sequence:
 * 1. The term is added to the term table, then related to the taxonomy.
 * 2. If everything is correct, several actions are fired.
 * 3. The 'term_id_filter' is evaluated.
 * 4. The term cache is cleaned.
 * 5. Several more actions are fired.
 * 6. An array is returned containing the term_id and term_taxonomy_id.
 *
 * If the 'slug' argument is not empty, then it is checked to see if the term
 * is invalid. If it is not a valid, existing term, it is added and the term_id
 * is given.
 *
 * If the taxonomy is hierarchical, and the 'parent' argument is not empty,
 * the term is inserted and the term_id will be given.
 *
 * Error handling:
 * If $taxonomy does not exist or $term is empty,
 * a WP_Error object will be returned.
 *
 * If the term already exists on the same hierarchical level,
 * or the term slug and name are not unique, a WP_Error object will be returned.
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @since 2.3.0
 *
 * @param string       $term     The term to add or update.
 * @param string       $taxonomy The taxonomy to which to add the term.
 * @param array|string $args {
 *     Optional. Array or string of arguments for inserting a term.
 *
 *     @type string $alias_of    Slug of the term to make this term an alias of.
 *                               Default empty string. Accepts a term slug.
 *     @type string $description The term description. Default empty string.
 *     @type int    $parent      The id of the parent term. Default 0.
 *     @type string $slug        The term slug to use. Default empty string.
 * }
 * @return array|WP_Error An array containing the `term_id` and `term_taxonomy_id`,
 *                        WP_Error otherwise.
 */
function wp_insert_term($term, $taxonomy, $args = array())
{
    global $wpdb;
    if (!taxonomy_exists($taxonomy)) {
        return new WP_Error('invalid_taxonomy', __('Invalid taxonomy.'));
    }
    /**
     * Filters a term before it is sanitized and inserted into the database.
     *
     * @since 3.0.0
     *
     * @param string $term     The term to add or update.
     * @param string $taxonomy Taxonomy slug.
     */
    $term = apply_filters('pre_insert_term', $term, $taxonomy);
    if (is_wp_error($term)) {
        return $term;
    }
    if (is_int($term) && 0 == $term) {
        return new WP_Error('invalid_term_id', __('Invalid term ID.'));
    }
    if ('' == trim($term)) {
        return new WP_Error('empty_term_name', __('A name is required for this term.'));
    }
    $defaults = array('alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => '');
    $args = wp_parse_args($args, $defaults);
    if ($args['parent'] > 0 && !term_exists((int) $args['parent'])) {
        return new WP_Error('missing_parent', __('Parent term does not exist.'));
    }
    $args['name'] = $term;
    $args['taxonomy'] = $taxonomy;
    // Coerce null description to strings, to avoid database errors.
    $args['description'] = (string) $args['description'];
    $args = sanitize_term($args, $taxonomy, 'db');
    // expected_slashed ($name)
    $name = wp_unslash($args['name']);
    $description = wp_unslash($args['description']);
    $parent = (int) $args['parent'];
    $slug_provided = !empty($args['slug']);
    if (!$slug_provided) {
        $slug = sanitize_title($name);
    } else {
        $slug = $args['slug'];
    }
    $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));
        }
    }
    /*
     * Prevent the creation of terms with duplicate names at the same level of a taxonomy hierarchy,
     * unless a unique slug has been explicitly provided.
     */
    $name_matches = get_terms($taxonomy, array('name' => $name, 'hide_empty' => false));
    /*
     * The `name` match in `get_terms()` doesn't differentiate accented characters,
     * so we do a stricter comparison here.
     */
    $name_match = null;
    if ($name_matches) {
        foreach ($name_matches as $_match) {
            if (strtolower($name) === strtolower($_match->name)) {
                $name_match = $_match;
                break;
            }
        }
    }
    if ($name_match) {
        $slug_match = get_term_by('slug', $slug, $taxonomy);
        if (!$slug_provided || $name_match->slug === $slug || $slug_match) {
            if (is_taxonomy_hierarchical($taxonomy)) {
                $siblings = get_terms($taxonomy, array('get' => 'all', 'parent' => $parent));
                $existing_term = null;
                if ($name_match->slug === $slug && in_array($name, wp_list_pluck($siblings, 'name'))) {
                    $existing_term = $name_match;
                } elseif ($slug_match && in_array($slug, wp_list_pluck($siblings, 'slug'))) {
                    $existing_term = $slug_match;
                }
                if ($existing_term) {
                    return new WP_Error('term_exists', __('A term with the name provided already exists with this parent.'), $existing_term->term_id);
                }
            } else {
                return new WP_Error('term_exists', __('A term with the name provided already exists in this taxonomy.'), $name_match->term_id);
            }
        }
    }
    $slug = wp_unique_term_slug($slug, (object) $args);
    if (false === $wpdb->insert($wpdb->terms, compact('name', 'slug', 'term_group'))) {
        return new WP_Error('db_insert_error', __('Could not insert term into the database'), $wpdb->last_error);
    }
    $term_id = (int) $wpdb->insert_id;
    // Seems unreachable, However, Is used in the case that a term name is provided, which sanitizes to an empty string.
    if (empty($slug)) {
        $slug = sanitize_title($slug, $term_id);
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edit_terms', $term_id, $taxonomy);
        $wpdb->update($wpdb->terms, compact('slug'), compact('term_id'));
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edited_terms', $term_id, $taxonomy);
    }
    $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));
    if (!empty($tt_id)) {
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    $wpdb->insert($wpdb->term_taxonomy, compact('term_id', 'taxonomy', 'description', 'parent') + array('count' => 0));
    $tt_id = (int) $wpdb->insert_id;
    /*
     * Sanity check: if we just created a term with the same parent + taxonomy + slug but a higher term_id than
     * an existing term, then we have unwittingly created a duplicate term. Delete the dupe, and use the term_id
     * and term_taxonomy_id of the older term instead. Then return out of the function so that the "create" hooks
     * are not fired.
     */
    $duplicate_term = $wpdb->get_row($wpdb->prepare("SELECT t.term_id, tt.term_taxonomy_id FROM {$wpdb->terms} t INNER JOIN {$wpdb->term_taxonomy} tt ON ( tt.term_id = t.term_id ) WHERE t.slug = %s AND tt.parent = %d AND tt.taxonomy = %s AND t.term_id < %d AND tt.term_taxonomy_id != %d", $slug, $parent, $taxonomy, $term_id, $tt_id));
    if ($duplicate_term) {
        $wpdb->delete($wpdb->terms, array('term_id' => $term_id));
        $wpdb->delete($wpdb->term_taxonomy, array('term_taxonomy_id' => $tt_id));
        $term_id = (int) $duplicate_term->term_id;
        $tt_id = (int) $duplicate_term->term_taxonomy_id;
        clean_term_cache($term_id, $taxonomy);
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    /**
     * Fires immediately after a new term is created, before the term cache is 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("create_term", $term_id, $tt_id, $taxonomy);
    /**
     * Fires after a new term is created for a specific taxonomy.
     *
     * The dynamic portion of the hook name, `$taxonomy`, refers
     * to the slug of the taxonomy the term was created for.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Term taxonomy ID.
     */
    do_action("create_{$taxonomy}", $term_id, $tt_id);
    /**
     * Filters the term ID after a new term is created.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Taxonomy term ID.
     */
    $term_id = apply_filters('term_id_filter', $term_id, $tt_id);
    clean_term_cache($term_id, $taxonomy);
    /**
     * Fires after a new term is created, and after 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('created_term', $term_id, $tt_id, $taxonomy);
    /**
     * Fires after a new term in a specific taxonomy is created, and after 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("created_{$taxonomy}", $term_id, $tt_id);
    return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
}

WordPress Version: 4.5

/**
 * Add a new term to the database.
 *
 * A non-existent term is inserted in the following sequence:
 * 1. The term is added to the term table, then related to the taxonomy.
 * 2. If everything is correct, several actions are fired.
 * 3. The 'term_id_filter' is evaluated.
 * 4. The term cache is cleaned.
 * 5. Several more actions are fired.
 * 6. An array is returned containing the term_id and term_taxonomy_id.
 *
 * If the 'slug' argument is not empty, then it is checked to see if the term
 * is invalid. If it is not a valid, existing term, it is added and the term_id
 * is given.
 *
 * If the taxonomy is hierarchical, and the 'parent' argument is not empty,
 * the term is inserted and the term_id will be given.
 *
 * Error handling:
 * If $taxonomy does not exist or $term is empty,
 * a WP_Error object will be returned.
 *
 * If the term already exists on the same hierarchical level,
 * or the term slug and name are not unique, a WP_Error object will be returned.
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @since 2.3.0
 *
 * @param string       $term     The term to add or update.
 * @param string       $taxonomy The taxonomy to which to add the term.
 * @param array|string $args {
 *     Optional. Array or string of arguments for inserting a term.
 *
 *     @type string $alias_of    Slug of the term to make this term an alias of.
 *                               Default empty string. Accepts a term slug.
 *     @type string $description The term description. Default empty string.
 *     @type int    $parent      The id of the parent term. Default 0.
 *     @type string $slug        The term slug to use. Default empty string.
 * }
 * @return array|WP_Error An array containing the `term_id` and `term_taxonomy_id`,
 *                        {@see WP_Error} otherwise.
 */
function wp_insert_term($term, $taxonomy, $args = array())
{
    global $wpdb;
    if (!taxonomy_exists($taxonomy)) {
        return new WP_Error('invalid_taxonomy', __('Invalid taxonomy'));
    }
    /**
     * Filter a term before it is sanitized and inserted into the database.
     *
     * @since 3.0.0
     *
     * @param string $term     The term to add or update.
     * @param string $taxonomy Taxonomy slug.
     */
    $term = apply_filters('pre_insert_term', $term, $taxonomy);
    if (is_wp_error($term)) {
        return $term;
    }
    if (is_int($term) && 0 == $term) {
        return new WP_Error('invalid_term_id', __('Invalid term ID'));
    }
    if ('' == trim($term)) {
        return new WP_Error('empty_term_name', __('A name is required for this term'));
    }
    $defaults = array('alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => '');
    $args = wp_parse_args($args, $defaults);
    if ($args['parent'] > 0 && !term_exists((int) $args['parent'])) {
        return new WP_Error('missing_parent', __('Parent term does not exist.'));
    }
    $args['name'] = $term;
    $args['taxonomy'] = $taxonomy;
    // Coerce null description to strings, to avoid database errors.
    $args['description'] = (string) $args['description'];
    $args = sanitize_term($args, $taxonomy, 'db');
    // expected_slashed ($name)
    $name = wp_unslash($args['name']);
    $description = wp_unslash($args['description']);
    $parent = (int) $args['parent'];
    $slug_provided = !empty($args['slug']);
    if (!$slug_provided) {
        $slug = sanitize_title($name);
    } else {
        $slug = $args['slug'];
    }
    $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));
        }
    }
    /*
     * Prevent the creation of terms with duplicate names at the same level of a taxonomy hierarchy,
     * unless a unique slug has been explicitly provided.
     */
    $name_matches = get_terms($taxonomy, array('name' => $name, 'hide_empty' => false));
    /*
     * The `name` match in `get_terms()` doesn't differentiate accented characters,
     * so we do a stricter comparison here.
     */
    $name_match = null;
    if ($name_matches) {
        foreach ($name_matches as $_match) {
            if (strtolower($name) === strtolower($_match->name)) {
                $name_match = $_match;
                break;
            }
        }
    }
    if ($name_match) {
        $slug_match = get_term_by('slug', $slug, $taxonomy);
        if (!$slug_provided || $name_match->slug === $slug || $slug_match) {
            if (is_taxonomy_hierarchical($taxonomy)) {
                $siblings = get_terms($taxonomy, array('get' => 'all', 'parent' => $parent));
                $existing_term = null;
                if ($name_match->slug === $slug && in_array($name, wp_list_pluck($siblings, 'name'))) {
                    $existing_term = $name_match;
                } elseif ($slug_match && in_array($slug, wp_list_pluck($siblings, 'slug'))) {
                    $existing_term = $slug_match;
                }
                if ($existing_term) {
                    return new WP_Error('term_exists', __('A term with the name provided already exists with this parent.'), $existing_term->term_id);
                }
            } else {
                return new WP_Error('term_exists', __('A term with the name provided already exists in this taxonomy.'), $name_match->term_id);
            }
        }
    }
    $slug = wp_unique_term_slug($slug, (object) $args);
    if (false === $wpdb->insert($wpdb->terms, compact('name', 'slug', 'term_group'))) {
        return new WP_Error('db_insert_error', __('Could not insert term into the database'), $wpdb->last_error);
    }
    $term_id = (int) $wpdb->insert_id;
    // Seems unreachable, However, Is used in the case that a term name is provided, which sanitizes to an empty string.
    if (empty($slug)) {
        $slug = sanitize_title($slug, $term_id);
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edit_terms', $term_id, $taxonomy);
        $wpdb->update($wpdb->terms, compact('slug'), compact('term_id'));
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edited_terms', $term_id, $taxonomy);
    }
    $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));
    if (!empty($tt_id)) {
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    $wpdb->insert($wpdb->term_taxonomy, compact('term_id', 'taxonomy', 'description', 'parent') + array('count' => 0));
    $tt_id = (int) $wpdb->insert_id;
    /*
     * Sanity check: if we just created a term with the same parent + taxonomy + slug but a higher term_id than
     * an existing term, then we have unwittingly created a duplicate term. Delete the dupe, and use the term_id
     * and term_taxonomy_id of the older term instead. Then return out of the function so that the "create" hooks
     * are not fired.
     */
    $duplicate_term = $wpdb->get_row($wpdb->prepare("SELECT t.term_id, tt.term_taxonomy_id FROM {$wpdb->terms} t INNER JOIN {$wpdb->term_taxonomy} tt ON ( tt.term_id = t.term_id ) WHERE t.slug = %s AND tt.parent = %d AND tt.taxonomy = %s AND t.term_id < %d AND tt.term_taxonomy_id != %d", $slug, $parent, $taxonomy, $term_id, $tt_id));
    if ($duplicate_term) {
        $wpdb->delete($wpdb->terms, array('term_id' => $term_id));
        $wpdb->delete($wpdb->term_taxonomy, array('term_taxonomy_id' => $tt_id));
        $term_id = (int) $duplicate_term->term_id;
        $tt_id = (int) $duplicate_term->term_taxonomy_id;
        clean_term_cache($term_id, $taxonomy);
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    /**
     * Fires immediately after a new term is created, before the term cache is 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("create_term", $term_id, $tt_id, $taxonomy);
    /**
     * Fires after a new term is created for a specific taxonomy.
     *
     * The dynamic portion of the hook name, `$taxonomy`, refers
     * to the slug of the taxonomy the term was created for.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Term taxonomy ID.
     */
    do_action("create_{$taxonomy}", $term_id, $tt_id);
    /**
     * Filter the term ID after a new term is created.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Taxonomy term ID.
     */
    $term_id = apply_filters('term_id_filter', $term_id, $tt_id);
    clean_term_cache($term_id, $taxonomy);
    /**
     * Fires after a new term is created, and after 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('created_term', $term_id, $tt_id, $taxonomy);
    /**
     * Fires after a new term in a specific taxonomy is created, and after 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("created_{$taxonomy}", $term_id, $tt_id);
    return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
}

WordPress Version: 4.4

/**
 * Add a new term to the database.
 *
 * A non-existent term is inserted in the following sequence:
 * 1. The term is added to the term table, then related to the taxonomy.
 * 2. If everything is correct, several actions are fired.
 * 3. The 'term_id_filter' is evaluated.
 * 4. The term cache is cleaned.
 * 5. Several more actions are fired.
 * 6. An array is returned containing the term_id and term_taxonomy_id.
 *
 * If the 'slug' argument is not empty, then it is checked to see if the term
 * is invalid. If it is not a valid, existing term, it is added and the term_id
 * is given.
 *
 * If the taxonomy is hierarchical, and the 'parent' argument is not empty,
 * the term is inserted and the term_id will be given.
 *
 * Error handling:
 * If $taxonomy does not exist or $term is empty,
 * a WP_Error object will be returned.
 *
 * If the term already exists on the same hierarchical level,
 * or the term slug and name are not unique, a WP_Error object will be returned.
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @since 2.3.0
 *
 * @param string       $term     The term to add or update.
 * @param string       $taxonomy The taxonomy to which to add the term.
 * @param array|string $args {
 *     Optional. Array or string of arguments for inserting a term.
 *
 *     @type string $alias_of    Slug of the term to make this term an alias of.
 *                               Default empty string. Accepts a term slug.
 *     @type string $description The term description. Default empty string.
 *     @type int    $parent      The id of the parent term. Default 0.
 *     @type string $slug        The term slug to use. Default empty string.
 * }
 * @return array|WP_Error An array containing the `term_id` and `term_taxonomy_id`,
 *                        {@see WP_Error} otherwise.
 */
function wp_insert_term($term, $taxonomy, $args = array())
{
    global $wpdb;
    if (!taxonomy_exists($taxonomy)) {
        return new WP_Error('invalid_taxonomy', __('Invalid taxonomy'));
    }
    /**
     * Filter a term before it is sanitized and inserted into the database.
     *
     * @since 3.0.0
     *
     * @param string $term     The term to add or update.
     * @param string $taxonomy Taxonomy slug.
     */
    $term = apply_filters('pre_insert_term', $term, $taxonomy);
    if (is_wp_error($term)) {
        return $term;
    }
    if (is_int($term) && 0 == $term) {
        return new WP_Error('invalid_term_id', __('Invalid term ID'));
    }
    if ('' == trim($term)) {
        return new WP_Error('empty_term_name', __('A name is required for this term'));
    }
    $defaults = array('alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => '');
    $args = wp_parse_args($args, $defaults);
    if ($args['parent'] > 0 && !term_exists((int) $args['parent'])) {
        return new WP_Error('missing_parent', __('Parent term does not exist.'));
    }
    $args['name'] = $term;
    $args['taxonomy'] = $taxonomy;
    $args = sanitize_term($args, $taxonomy, 'db');
    // expected_slashed ($name)
    $name = wp_unslash($args['name']);
    $description = wp_unslash($args['description']);
    $parent = (int) $args['parent'];
    $slug_provided = !empty($args['slug']);
    if (!$slug_provided) {
        $slug = sanitize_title($name);
    } else {
        $slug = $args['slug'];
    }
    $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));
        }
    }
    /*
     * Prevent the creation of terms with duplicate names at the same level of a taxonomy hierarchy,
     * unless a unique slug has been explicitly provided.
     */
    $name_matches = get_terms($taxonomy, array('name' => $name, 'hide_empty' => false));
    /*
     * The `name` match in `get_terms()` doesn't differentiate accented characters,
     * so we do a stricter comparison here.
     */
    $name_match = null;
    if ($name_matches) {
        foreach ($name_matches as $_match) {
            if (strtolower($name) === strtolower($_match->name)) {
                $name_match = $_match;
                break;
            }
        }
    }
    if ($name_match) {
        $slug_match = get_term_by('slug', $slug, $taxonomy);
        if (!$slug_provided || $name_match->slug === $slug || $slug_match) {
            if (is_taxonomy_hierarchical($taxonomy)) {
                $siblings = get_terms($taxonomy, array('get' => 'all', 'parent' => $parent));
                $existing_term = null;
                if ($name_match->slug === $slug && in_array($name, wp_list_pluck($siblings, 'name'))) {
                    $existing_term = $name_match;
                } elseif ($slug_match && in_array($slug, wp_list_pluck($siblings, 'slug'))) {
                    $existing_term = $slug_match;
                }
                if ($existing_term) {
                    return new WP_Error('term_exists', __('A term with the name provided already exists with this parent.'), $existing_term->term_id);
                }
            } else {
                return new WP_Error('term_exists', __('A term with the name provided already exists in this taxonomy.'), $name_match->term_id);
            }
        }
    }
    $slug = wp_unique_term_slug($slug, (object) $args);
    if (false === $wpdb->insert($wpdb->terms, compact('name', 'slug', 'term_group'))) {
        return new WP_Error('db_insert_error', __('Could not insert term into the database'), $wpdb->last_error);
    }
    $term_id = (int) $wpdb->insert_id;
    // Seems unreachable, However, Is used in the case that a term name is provided, which sanitizes to an empty string.
    if (empty($slug)) {
        $slug = sanitize_title($slug, $term_id);
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edit_terms', $term_id, $taxonomy);
        $wpdb->update($wpdb->terms, compact('slug'), compact('term_id'));
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edited_terms', $term_id, $taxonomy);
    }
    $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));
    if (!empty($tt_id)) {
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    $wpdb->insert($wpdb->term_taxonomy, compact('term_id', 'taxonomy', 'description', 'parent') + array('count' => 0));
    $tt_id = (int) $wpdb->insert_id;
    /*
     * Sanity check: if we just created a term with the same parent + taxonomy + slug but a higher term_id than
     * an existing term, then we have unwittingly created a duplicate term. Delete the dupe, and use the term_id
     * and term_taxonomy_id of the older term instead. Then return out of the function so that the "create" hooks
     * are not fired.
     */
    $duplicate_term = $wpdb->get_row($wpdb->prepare("SELECT t.term_id, tt.term_taxonomy_id FROM {$wpdb->terms} t INNER JOIN {$wpdb->term_taxonomy} tt ON ( tt.term_id = t.term_id ) WHERE t.slug = %s AND tt.parent = %d AND tt.taxonomy = %s AND t.term_id < %d AND tt.term_taxonomy_id != %d", $slug, $parent, $taxonomy, $term_id, $tt_id));
    if ($duplicate_term) {
        $wpdb->delete($wpdb->terms, array('term_id' => $term_id));
        $wpdb->delete($wpdb->term_taxonomy, array('term_taxonomy_id' => $tt_id));
        $term_id = (int) $duplicate_term->term_id;
        $tt_id = (int) $duplicate_term->term_taxonomy_id;
        clean_term_cache($term_id, $taxonomy);
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    /**
     * Fires immediately after a new term is created, before the term cache is 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("create_term", $term_id, $tt_id, $taxonomy);
    /**
     * Fires after a new term is created for a specific taxonomy.
     *
     * The dynamic portion of the hook name, `$taxonomy`, refers
     * to the slug of the taxonomy the term was created for.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Term taxonomy ID.
     */
    do_action("create_{$taxonomy}", $term_id, $tt_id);
    /**
     * Filter the term ID after a new term is created.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Taxonomy term ID.
     */
    $term_id = apply_filters('term_id_filter', $term_id, $tt_id);
    clean_term_cache($term_id, $taxonomy);
    /**
     * Fires after a new term is created, and after 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('created_term', $term_id, $tt_id, $taxonomy);
    /**
     * Fires after a new term in a specific taxonomy is created, and after 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("created_{$taxonomy}", $term_id, $tt_id);
    return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
}

WordPress Version: 4.3

/**
 * Add a new term to the database.
 *
 * A non-existent term is inserted in the following sequence:
 * 1. The term is added to the term table, then related to the taxonomy.
 * 2. If everything is correct, several actions are fired.
 * 3. The 'term_id_filter' is evaluated.
 * 4. The term cache is cleaned.
 * 5. Several more actions are fired.
 * 6. An array is returned containing the term_id and term_taxonomy_id.
 *
 * If the 'slug' argument is not empty, then it is checked to see if the term
 * is invalid. If it is not a valid, existing term, it is added and the term_id
 * is given.
 *
 * If the taxonomy is hierarchical, and the 'parent' argument is not empty,
 * the term is inserted and the term_id will be given.
 * Error handling:
 * If $taxonomy does not exist or $term is empty,
 * a WP_Error object will be returned.
 *
 * If the term already exists on the same hierarchical level,
 * or the term slug and name are not unique, a WP_Error object will be returned.
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 * @since 2.3.0
 *
 * @param string       $term     The term to add or update.
 * @param string       $taxonomy The taxonomy to which to add the term.
 * @param array|string $args {
 *     Optional. Array or string of arguments for inserting a term.
 *
 *     @type string $alias_of    Slug of the term to make this term an alias of.
 *                               Default empty string. Accepts a term slug.
 *     @type string $description The term description. Default empty string.
 *     @type int    $parent      The id of the parent term. Default 0.
 *     @type string $slug        The term slug to use. Default empty string.
 * }
 * @return array|WP_Error An array containing the `term_id` and `term_taxonomy_id`,
 *                        {@see WP_Error} otherwise.
 */
function wp_insert_term($term, $taxonomy, $args = array())
{
    global $wpdb;
    if (!taxonomy_exists($taxonomy)) {
        return new WP_Error('invalid_taxonomy', __('Invalid taxonomy'));
    }
    /**
     * Filter a term before it is sanitized and inserted into the database.
     *
     * @since 3.0.0
     *
     * @param string $term     The term to add or update.
     * @param string $taxonomy Taxonomy slug.
     */
    $term = apply_filters('pre_insert_term', $term, $taxonomy);
    if (is_wp_error($term)) {
        return $term;
    }
    if (is_int($term) && 0 == $term) {
        return new WP_Error('invalid_term_id', __('Invalid term ID'));
    }
    if ('' == trim($term)) {
        return new WP_Error('empty_term_name', __('A name is required for this term'));
    }
    $defaults = array('alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => '');
    $args = wp_parse_args($args, $defaults);
    if ($args['parent'] > 0 && !term_exists((int) $args['parent'])) {
        return new WP_Error('missing_parent', __('Parent term does not exist.'));
    }
    $args['name'] = $term;
    $args['taxonomy'] = $taxonomy;
    $args = sanitize_term($args, $taxonomy, 'db');
    // expected_slashed ($name)
    $name = wp_unslash($args['name']);
    $description = wp_unslash($args['description']);
    $parent = (int) $args['parent'];
    $slug_provided = !empty($args['slug']);
    if (!$slug_provided) {
        $slug = sanitize_title($name);
    } else {
        $slug = $args['slug'];
    }
    $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));
        }
    }
    /*
     * Prevent the creation of terms with duplicate names at the same level of a taxonomy hierarchy,
     * unless a unique slug has been explicitly provided.
     */
    if ($name_match = get_term_by('name', $name, $taxonomy)) {
        $slug_match = get_term_by('slug', $slug, $taxonomy);
        if (!$slug_provided || $name_match->slug === $slug || $slug_match) {
            if (is_taxonomy_hierarchical($taxonomy)) {
                $siblings = get_terms($taxonomy, array('get' => 'all', 'parent' => $parent));
                $existing_term = null;
                if ($name_match->slug === $slug && in_array($name, wp_list_pluck($siblings, 'name'))) {
                    $existing_term = $name_match;
                } elseif ($slug_match && in_array($slug, wp_list_pluck($siblings, 'slug'))) {
                    $existing_term = $slug_match;
                }
                if ($existing_term) {
                    return new WP_Error('term_exists', __('A term with the name provided already exists with this parent.'), $existing_term->term_id);
                }
            } else {
                return new WP_Error('term_exists', __('A term with the name provided already exists in this taxonomy.'), $name_match->term_id);
            }
        }
    }
    $slug = wp_unique_term_slug($slug, (object) $args);
    if (false === $wpdb->insert($wpdb->terms, compact('name', 'slug', 'term_group'))) {
        return new WP_Error('db_insert_error', __('Could not insert term into the database'), $wpdb->last_error);
    }
    $term_id = (int) $wpdb->insert_id;
    // Seems unreachable, However, Is used in the case that a term name is provided, which sanitizes to an empty string.
    if (empty($slug)) {
        $slug = sanitize_title($slug, $term_id);
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edit_terms', $term_id, $taxonomy);
        $wpdb->update($wpdb->terms, compact('slug'), compact('term_id'));
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edited_terms', $term_id, $taxonomy);
    }
    $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));
    if (!empty($tt_id)) {
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    $wpdb->insert($wpdb->term_taxonomy, compact('term_id', 'taxonomy', 'description', 'parent') + array('count' => 0));
    $tt_id = (int) $wpdb->insert_id;
    /*
     * Sanity check: if we just created a term with the same parent + taxonomy + slug but a higher term_id than
     * an existing term, then we have unwittingly created a duplicate term. Delete the dupe, and use the term_id
     * and term_taxonomy_id of the older term instead. Then return out of the function so that the "create" hooks
     * are not fired.
     */
    $duplicate_term = $wpdb->get_row($wpdb->prepare("SELECT t.term_id, tt.term_taxonomy_id FROM {$wpdb->terms} t INNER JOIN {$wpdb->term_taxonomy} tt ON ( tt.term_id = t.term_id ) WHERE t.slug = %s AND tt.parent = %d AND tt.taxonomy = %s AND t.term_id < %d AND tt.term_taxonomy_id != %d", $slug, $parent, $taxonomy, $term_id, $tt_id));
    if ($duplicate_term) {
        $wpdb->delete($wpdb->terms, array('term_id' => $term_id));
        $wpdb->delete($wpdb->term_taxonomy, array('term_taxonomy_id' => $tt_id));
        $term_id = (int) $duplicate_term->term_id;
        $tt_id = (int) $duplicate_term->term_taxonomy_id;
        clean_term_cache($term_id, $taxonomy);
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    /**
     * Fires immediately after a new term is created, before the term cache is 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("create_term", $term_id, $tt_id, $taxonomy);
    /**
     * Fires after a new term is created for a specific taxonomy.
     *
     * The dynamic portion of the hook name, `$taxonomy`, refers
     * to the slug of the taxonomy the term was created for.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Term taxonomy ID.
     */
    do_action("create_{$taxonomy}", $term_id, $tt_id);
    /**
     * Filter the term ID after a new term is created.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Taxonomy term ID.
     */
    $term_id = apply_filters('term_id_filter', $term_id, $tt_id);
    clean_term_cache($term_id, $taxonomy);
    /**
     * Fires after a new term is created, and after 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('created_term', $term_id, $tt_id, $taxonomy);
    /**
     * Fires after a new term in a specific taxonomy is created, and after 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("created_{$taxonomy}", $term_id, $tt_id);
    return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
}

WordPress Version: 4.2

/**
 * Add a new term to the database.
 *
 * A non-existent term is inserted in the following sequence:
 * 1. The term is added to the term table, then related to the taxonomy.
 * 2. If everything is correct, several actions are fired.
 * 3. The 'term_id_filter' is evaluated.
 * 4. The term cache is cleaned.
 * 5. Several more actions are fired.
 * 6. An array is returned containing the term_id and term_taxonomy_id.
 *
 * If the 'slug' argument is not empty, then it is checked to see if the term
 * is invalid. If it is not a valid, existing term, it is added and the term_id
 * is given.
 *
 * If the taxonomy is hierarchical, and the 'parent' argument is not empty,
 * the term is inserted and the term_id will be given.
 * Error handling:
 * If $taxonomy does not exist or $term is empty,
 * a WP_Error object will be returned.
 *
 * If the term already exists on the same hierarchical level,
 * or the term slug and name are not unique, a WP_Error object will be returned.
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 * @since 2.3.0
 *
 * @param string       $term     The term to add or update.
 * @param string       $taxonomy The taxonomy to which to add the term.
 * @param array|string $args {
 *     Optional. Array or string of arguments for inserting a term.
 *
 *     @type string $alias_of    Slug of the term to make this term an alias of.
 *                               Default empty string. Accepts a term slug.
 *     @type string $description The term description. Default empty string.
 *     @type int    $parent      The id of the parent term. Default 0.
 *     @type string $slug        The term slug to use. Default empty string.
 * }
 * @return array|WP_Error An array containing the `term_id` and `term_taxonomy_id`,
 *                        {@see WP_Error} otherwise.
 */
function wp_insert_term($term, $taxonomy, $args = array())
{
    global $wpdb;
    if (!taxonomy_exists($taxonomy)) {
        return new WP_Error('invalid_taxonomy', __('Invalid taxonomy'));
    }
    /**
     * Filter a term before it is sanitized and inserted into the database.
     *
     * @since 3.0.0
     *
     * @param string $term     The term to add or update.
     * @param string $taxonomy Taxonomy slug.
     */
    $term = apply_filters('pre_insert_term', $term, $taxonomy);
    if (is_wp_error($term)) {
        return $term;
    }
    if (is_int($term) && 0 == $term) {
        return new WP_Error('invalid_term_id', __('Invalid term ID'));
    }
    if ('' == trim($term)) {
        return new WP_Error('empty_term_name', __('A name is required for this term'));
    }
    $defaults = array('alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => '');
    $args = wp_parse_args($args, $defaults);
    if ($args['parent'] > 0 && !term_exists((int) $args['parent'])) {
        return new WP_Error('missing_parent', __('Parent term does not exist.'));
    }
    $args['name'] = $term;
    $args['taxonomy'] = $taxonomy;
    $args = sanitize_term($args, $taxonomy, 'db');
    // expected_slashed ($name)
    $name = wp_unslash($args['name']);
    $description = wp_unslash($args['description']);
    $parent = (int) $args['parent'];
    $slug_provided = !empty($args['slug']);
    if (!$slug_provided) {
        $slug = sanitize_title($name);
    } else {
        $slug = $args['slug'];
    }
    $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));
        }
    }
    /*
     * Prevent the creation of terms with duplicate names at the same level of a taxonomy hierarchy,
     * unless a unique slug has been explicitly provided.
     */
    if ($name_match = get_term_by('name', $name, $taxonomy)) {
        $slug_match = get_term_by('slug', $slug, $taxonomy);
        if (!$slug_provided || $name_match->slug === $slug || $slug_match) {
            if (is_taxonomy_hierarchical($taxonomy)) {
                $siblings = get_terms($taxonomy, array('get' => 'all', 'parent' => $parent));
                $existing_term = null;
                if ($name_match->slug === $slug && in_array($name, wp_list_pluck($siblings, 'name'))) {
                    $existing_term = $name_match;
                } elseif ($slug_match && in_array($slug, wp_list_pluck($siblings, 'slug'))) {
                    $existing_term = $slug_match;
                }
                if ($existing_term) {
                    return new WP_Error('term_exists', __('A term with the name already exists with this parent.'), $existing_term->term_id);
                }
            } else {
                return new WP_Error('term_exists', __('A term with the name already exists in this taxonomy.'), $name_match->term_id);
            }
        }
    }
    $slug = wp_unique_term_slug($slug, (object) $args);
    if (false === $wpdb->insert($wpdb->terms, compact('name', 'slug', 'term_group'))) {
        return new WP_Error('db_insert_error', __('Could not insert term into the database'), $wpdb->last_error);
    }
    $term_id = (int) $wpdb->insert_id;
    // Seems unreachable, However, Is used in the case that a term name is provided, which sanitizes to an empty string.
    if (empty($slug)) {
        $slug = sanitize_title($slug, $term_id);
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edit_terms', $term_id, $taxonomy);
        $wpdb->update($wpdb->terms, compact('slug'), compact('term_id'));
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edited_terms', $term_id, $taxonomy);
    }
    $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));
    if (!empty($tt_id)) {
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    $wpdb->insert($wpdb->term_taxonomy, compact('term_id', 'taxonomy', 'description', 'parent') + array('count' => 0));
    $tt_id = (int) $wpdb->insert_id;
    /*
     * Sanity check: if we just created a term with the same parent + taxonomy + slug but a higher term_id than
     * an existing term, then we have unwittingly created a duplicate term. Delete the dupe, and use the term_id
     * and term_taxonomy_id of the older term instead. Then return out of the function so that the "create" hooks
     * are not fired.
     */
    $duplicate_term = $wpdb->get_row($wpdb->prepare("SELECT t.term_id, tt.term_taxonomy_id FROM {$wpdb->terms} t INNER JOIN {$wpdb->term_taxonomy} tt ON ( tt.term_id = t.term_id ) WHERE t.slug = %s AND tt.parent = %d AND tt.taxonomy = %s AND t.term_id < %d AND tt.term_taxonomy_id != %d", $slug, $parent, $taxonomy, $term_id, $tt_id));
    if ($duplicate_term) {
        $wpdb->delete($wpdb->terms, array('term_id' => $term_id));
        $wpdb->delete($wpdb->term_taxonomy, array('term_taxonomy_id' => $tt_id));
        $term_id = (int) $duplicate_term->term_id;
        $tt_id = (int) $duplicate_term->term_taxonomy_id;
        clean_term_cache($term_id, $taxonomy);
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    /**
     * Fires immediately after a new term is created, before the term cache is 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("create_term", $term_id, $tt_id, $taxonomy);
    /**
     * Fires after a new term is created for a specific taxonomy.
     *
     * The dynamic portion of the hook name, `$taxonomy`, refers
     * to the slug of the taxonomy the term was created for.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Term taxonomy ID.
     */
    do_action("create_{$taxonomy}", $term_id, $tt_id);
    /**
     * Filter the term ID after a new term is created.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Taxonomy term ID.
     */
    $term_id = apply_filters('term_id_filter', $term_id, $tt_id);
    clean_term_cache($term_id, $taxonomy);
    /**
     * Fires after a new term is created, and after the term cache has been cleaned.
     *
     * @since 2.3.0
     */
    do_action("created_term", $term_id, $tt_id, $taxonomy);
    /**
     * Fires after a new term in a specific taxonomy is created, and after the term
     * cache has been cleaned.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Term taxonomy ID.
     */
    do_action("created_{$taxonomy}", $term_id, $tt_id);
    return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
}

WordPress Version: 4.1

/**
 * Add a new term to the database.
 *
 * A non-existent term is inserted in the following sequence:
 * 1. The term is added to the term table, then related to the taxonomy.
 * 2. If everything is correct, several actions are fired.
 * 3. The 'term_id_filter' is evaluated.
 * 4. The term cache is cleaned.
 * 5. Several more actions are fired.
 * 6. An array is returned containing the term_id and term_taxonomy_id.
 *
 * If the 'slug' argument is not empty, then it is checked to see if the term
 * is invalid. If it is not a valid, existing term, it is added and the term_id
 * is given.
 *
 * If the taxonomy is hierarchical, and the 'parent' argument is not empty,
 * the term is inserted and the term_id will be given.
 * Error handling:
 * If $taxonomy does not exist or $term is empty,
 * a WP_Error object will be returned.
 *
 * If the term already exists on the same hierarchical level,
 * or the term slug and name are not unique, a WP_Error object will be returned.
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 * @since 2.3.0
 *
 * @param string       $term     The term to add or update.
 * @param string       $taxonomy The taxonomy to which to add the term.
 * @param array|string $args {
 *     Optional. Array or string of arguments for inserting a term.
 *
 *     @type string $alias_of    Slug of the term to make this term an alias of.
 *                               Default empty string. Accepts a term slug.
 *     @type string $description The term description. Default empty string.
 *     @type int    $parent      The id of the parent term. Default 0.
 *     @type string $slug        The term slug to use. Default empty string.
 * }
 * @return array|WP_Error An array containing the `term_id` and `term_taxonomy_id`,
 *                        {@see WP_Error} otherwise.
 */
function wp_insert_term($term, $taxonomy, $args = array())
{
    global $wpdb;
    if (!taxonomy_exists($taxonomy)) {
        return new WP_Error('invalid_taxonomy', __('Invalid taxonomy'));
    }
    /**
     * Filter a term before it is sanitized and inserted into the database.
     *
     * @since 3.0.0
     *
     * @param string $term     The term to add or update.
     * @param string $taxonomy Taxonomy slug.
     */
    $term = apply_filters('pre_insert_term', $term, $taxonomy);
    if (is_wp_error($term)) {
        return $term;
    }
    if (is_int($term) && 0 == $term) {
        return new WP_Error('invalid_term_id', __('Invalid term ID'));
    }
    if ('' == trim($term)) {
        return new WP_Error('empty_term_name', __('A name is required for this term'));
    }
    $defaults = array('alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => '');
    $args = wp_parse_args($args, $defaults);
    if ($args['parent'] > 0 && !term_exists((int) $args['parent'])) {
        return new WP_Error('missing_parent', __('Parent term does not exist.'));
    }
    $args['name'] = $term;
    $args['taxonomy'] = $taxonomy;
    $args = sanitize_term($args, $taxonomy, 'db');
    // expected_slashed ($name)
    $name = wp_unslash($args['name']);
    $description = wp_unslash($args['description']);
    $parent = (int) $args['parent'];
    $slug_provided = !empty($args['slug']);
    if (!$slug_provided) {
        $_name = trim($name);
        $existing_term = get_term_by('name', $_name, $taxonomy);
        if ($existing_term) {
            $slug = $existing_term->slug;
        } else {
            $slug = sanitize_title($name);
        }
    } else {
        $slug = $args['slug'];
    }
    $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;
        } else if (!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));
        }
    }
    // Terms with duplicate names are not allowed at the same level of a taxonomy hierarchy.
    if ($exists = term_exists($slug, $taxonomy)) {
        $existing_term = get_term($exists['term_id'], $taxonomy);
        if ($name === $existing_term->name) {
            if (is_taxonomy_hierarchical($taxonomy)) {
                $siblings = get_terms($taxonomy, array('fields' => 'names', 'get' => 'all', 'parent' => $parent));
                if (in_array($name, $siblings)) {
                    return new WP_Error('term_exists', __('A term with the name and slug already exists with this parent.'), $exists['term_id']);
                }
            } else {
                return new WP_Error('term_exists', __('A term with the name and slug already exists in this taxonomy.'), $exists['term_id']);
            }
        }
    }
    $slug = wp_unique_term_slug($slug, (object) $args);
    if (false === $wpdb->insert($wpdb->terms, compact('name', 'slug', 'term_group'))) {
        return new WP_Error('db_insert_error', __('Could not insert term into the database'), $wpdb->last_error);
    }
    $term_id = (int) $wpdb->insert_id;
    // Seems unreachable, However, Is used in the case that a term name is provided, which sanitizes to an empty string.
    if (empty($slug)) {
        $slug = sanitize_title($slug, $term_id);
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edit_terms', $term_id, $taxonomy);
        $wpdb->update($wpdb->terms, compact('slug'), compact('term_id'));
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edited_terms', $term_id, $taxonomy);
    }
    $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));
    if (!empty($tt_id)) {
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    $wpdb->insert($wpdb->term_taxonomy, compact('term_id', 'taxonomy', 'description', 'parent') + array('count' => 0));
    $tt_id = (int) $wpdb->insert_id;
    /*
     * Sanity check: if we just created a term with the same parent + taxonomy + slug but a higher term_id than
     * an existing term, then we have unwittingly created a duplicate term. Delete the dupe, and use the term_id
     * and term_taxonomy_id of the older term instead. Then return out of the function so that the "create" hooks
     * are not fired.
     */
    $duplicate_term = $wpdb->get_row($wpdb->prepare("SELECT t.term_id, tt.term_taxonomy_id FROM {$wpdb->terms} t INNER JOIN {$wpdb->term_taxonomy} tt ON ( tt.term_id = t.term_id ) WHERE t.slug = %s AND tt.parent = %d AND tt.taxonomy = %s AND t.term_id < %d AND tt.term_taxonomy_id != %d", $slug, $parent, $taxonomy, $term_id, $tt_id));
    if ($duplicate_term) {
        $wpdb->delete($wpdb->terms, array('term_id' => $term_id));
        $wpdb->delete($wpdb->term_taxonomy, array('term_taxonomy_id' => $tt_id));
        $term_id = (int) $duplicate_term->term_id;
        $tt_id = (int) $duplicate_term->term_taxonomy_id;
        clean_term_cache($term_id, $taxonomy);
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    /**
     * Fires immediately after a new term is created, before the term cache is 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("create_term", $term_id, $tt_id, $taxonomy);
    /**
     * Fires after a new term is created for a specific taxonomy.
     *
     * The dynamic portion of the hook name, `$taxonomy`, refers
     * to the slug of the taxonomy the term was created for.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Term taxonomy ID.
     */
    do_action("create_{$taxonomy}", $term_id, $tt_id);
    /**
     * Filter the term ID after a new term is created.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Taxonomy term ID.
     */
    $term_id = apply_filters('term_id_filter', $term_id, $tt_id);
    clean_term_cache($term_id, $taxonomy);
    /**
     * Fires after a new term is created, and after the term cache has been cleaned.
     *
     * @since 2.3.0
     */
    do_action("created_term", $term_id, $tt_id, $taxonomy);
    /**
     * Fires after a new term in a specific taxonomy is created, and after the term
     * cache has been cleaned.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Term taxonomy ID.
     */
    do_action("created_{$taxonomy}", $term_id, $tt_id);
    return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
}

WordPress Version: 4.0

/**
 * Add a new term to the database.
 *
 * A non-existent term is inserted in the following sequence:
 * 1. The term is added to the term table, then related to the taxonomy.
 * 2. If everything is correct, several actions are fired.
 * 3. The 'term_id_filter' is evaluated.
 * 4. The term cache is cleaned.
 * 5. Several more actions are fired.
 * 6. An array is returned containing the term_id and term_taxonomy_id.
 *
 * If the 'slug' argument is not empty, then it is checked to see if the term
 * is invalid. If it is not a valid, existing term, it is added and the term_id
 * is given.
 *
 * If the taxonomy is hierarchical, and the 'parent' argument is not empty,
 * the term is inserted and the term_id will be given.
 * Error handling:
 * If $taxonomy does not exist or $term is empty,
 * a WP_Error object will be returned.
 *
 * If the term already exists on the same hierarchical level,
 * or the term slug and name are not unique, a WP_Error object will be returned.
 *
 * @global wpdb $wpdb The WordPress database object.
 * @since 2.3.0
 *
 * @param string       $term     The term to add or update.
 * @param string       $taxonomy The taxonomy to which to add the term
 * @param array|string $args {
 *     Arguments to change values of the inserted term.
 *
 *     @type string 'alias_of'    Slug of the term to make this term an alias of.
 *                                Default empty string. Accepts a term slug.
 *     @type string 'description' The term description.
 *                                Default empty string.
 *     @type int    'parent'      The id of the parent term.
 *                                Default 0.
 *     @type string 'slug'        The term slug to use.
 *                                Default empty string.
 * }
 * @return array|WP_Error An array containing the term_id and term_taxonomy_id, WP_Error otherwise.
 */
function wp_insert_term($term, $taxonomy, $args = array())
{
    global $wpdb;
    if (!taxonomy_exists($taxonomy)) {
        return new WP_Error('invalid_taxonomy', __('Invalid taxonomy'));
    }
    /**
     * Filter a term before it is sanitized and inserted into the database.
     *
     * @since 3.0.0
     *
     * @param string $term     The term to add or update.
     * @param string $taxonomy Taxonomy slug.
     */
    $term = apply_filters('pre_insert_term', $term, $taxonomy);
    if (is_wp_error($term)) {
        return $term;
    }
    if (is_int($term) && 0 == $term) {
        return new WP_Error('invalid_term_id', __('Invalid term ID'));
    }
    if ('' == trim($term)) {
        return new WP_Error('empty_term_name', __('A name is required for this term'));
    }
    $defaults = array('alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => '');
    $args = wp_parse_args($args, $defaults);
    if ($args['parent'] > 0 && !term_exists((int) $args['parent'])) {
        return new WP_Error('missing_parent', __('Parent term does not exist.'));
    }
    $args['name'] = $term;
    $args['taxonomy'] = $taxonomy;
    $args = sanitize_term($args, $taxonomy, 'db');
    // expected_slashed ($name)
    $name = wp_unslash($args['name']);
    $description = wp_unslash($args['description']);
    $parent = (int) $args['parent'];
    $slug_provided = !empty($args['slug']);
    if (!$slug_provided) {
        $_name = trim($name);
        $existing_term = get_term_by('name', $_name, $taxonomy);
        if ($existing_term) {
            $slug = $existing_term->slug;
        } else {
            $slug = sanitize_title($name);
        }
    } else {
        $slug = $args['slug'];
    }
    $term_group = 0;
    if ($args['alias_of']) {
        $alias = $wpdb->get_row($wpdb->prepare("SELECT term_id, term_group FROM {$wpdb->terms} WHERE slug = %s", $args['alias_of']));
        if ($alias->term_group) {
            // The alias we want is already in a group, so let's use that one.
            $term_group = $alias->term_group;
        } else {
            // The alias isn't in a group, so let's create a new one and firstly add the alias term to it.
            $term_group = $wpdb->get_var("SELECT MAX(term_group) FROM {$wpdb->terms}") + 1;
            /**
             * 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', $alias->term_id, $taxonomy);
            $wpdb->update($wpdb->terms, compact('term_group'), array('term_id' => $alias->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', $alias->term_id, $taxonomy);
        }
    }
    if ($term_id = term_exists($slug)) {
        $existing_term = $wpdb->get_row($wpdb->prepare("SELECT name FROM {$wpdb->terms} WHERE term_id = %d", $term_id), ARRAY_A);
        // We've got an existing term in the same taxonomy, which matches the name of the new term:
        if (is_taxonomy_hierarchical($taxonomy) && $existing_term['name'] == $name && $exists = term_exists((int) $term_id, $taxonomy)) {
            // Hierarchical, and it matches an existing term, Do not allow same "name" in the same level.
            $siblings = get_terms($taxonomy, array('fields' => 'names', 'get' => 'all', 'parent' => $parent));
            if (in_array($name, $siblings)) {
                if ($slug_provided) {
                    return new WP_Error('term_exists', __('A term with the name and slug provided already exists with this parent.'), $exists['term_id']);
                } else {
                    return new WP_Error('term_exists', __('A term with the name provided already exists with this parent.'), $exists['term_id']);
                }
            } else {
                $slug = wp_unique_term_slug($slug, (object) $args);
                if (false === $wpdb->insert($wpdb->terms, compact('name', 'slug', 'term_group'))) {
                    return new WP_Error('db_insert_error', __('Could not insert term into the database'), $wpdb->last_error);
                }
                $term_id = (int) $wpdb->insert_id;
            }
        } elseif ($existing_term['name'] != $name) {
            // We've got an existing term, with a different name, Create the new term.
            $slug = wp_unique_term_slug($slug, (object) $args);
            if (false === $wpdb->insert($wpdb->terms, compact('name', 'slug', 'term_group'))) {
                return new WP_Error('db_insert_error', __('Could not insert term into the database'), $wpdb->last_error);
            }
            $term_id = (int) $wpdb->insert_id;
        } elseif ($exists = term_exists((int) $term_id, $taxonomy)) {
            // Same name, same slug.
            return new WP_Error('term_exists', __('A term with the name and slug provided already exists.'), $exists['term_id']);
        }
    } else {
        // This term does not exist at all in the database, Create it.
        $slug = wp_unique_term_slug($slug, (object) $args);
        if (false === $wpdb->insert($wpdb->terms, compact('name', 'slug', 'term_group'))) {
            return new WP_Error('db_insert_error', __('Could not insert term into the database'), $wpdb->last_error);
        }
        $term_id = (int) $wpdb->insert_id;
    }
    // Seems unreachable, However, Is used in the case that a term name is provided, which sanitizes to an empty string.
    if (empty($slug)) {
        $slug = sanitize_title($slug, $term_id);
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edit_terms', $term_id, $taxonomy);
        $wpdb->update($wpdb->terms, compact('slug'), compact('term_id'));
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edited_terms', $term_id, $taxonomy);
    }
    $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));
    if (!empty($tt_id)) {
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    $wpdb->insert($wpdb->term_taxonomy, compact('term_id', 'taxonomy', 'description', 'parent') + array('count' => 0));
    $tt_id = (int) $wpdb->insert_id;
    /**
     * Fires immediately after a new term is created, before the term cache is 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("create_term", $term_id, $tt_id, $taxonomy);
    /**
     * Fires after a new term is created for a specific taxonomy.
     *
     * The dynamic portion of the hook name, $taxonomy, refers
     * to the slug of the taxonomy the term was created for.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Term taxonomy ID.
     */
    do_action("create_{$taxonomy}", $term_id, $tt_id);
    /**
     * Filter the term ID after a new term is created.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Taxonomy term ID.
     */
    $term_id = apply_filters('term_id_filter', $term_id, $tt_id);
    clean_term_cache($term_id, $taxonomy);
    /**
     * Fires after a new term is created, and after the term cache has been cleaned.
     *
     * @since 2.3.0
     */
    do_action("created_term", $term_id, $tt_id, $taxonomy);
    /**
     * Fires after a new term in a specific taxonomy is created, and after the term
     * cache has been cleaned.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Term taxonomy ID.
     */
    do_action("created_{$taxonomy}", $term_id, $tt_id);
    return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
}

WordPress Version: 3.9

/**
 * Add a new term to the database.
 *
 * A non-existent term is inserted in the following sequence:
 * 1. The term is added to the term table, then related to the taxonomy.
 * 2. If everything is correct, several actions are fired.
 * 3. The 'term_id_filter' is evaluated.
 * 4. The term cache is cleaned.
 * 5. Several more actions are fired.
 * 6. An array is returned containing the term_id and term_taxonomy_id.
 *
 * If the 'slug' argument is not empty, then it is checked to see if the term
 * is invalid. If it is not a valid, existing term, it is added and the term_id
 * is given.
 *
 * If the taxonomy is hierarchical, and the 'parent' argument is not empty,
 * the term is inserted and the term_id will be given.
 * Error handling:
 * If $taxonomy does not exist or $term is empty,
 * a WP_Error object will be returned.
 *
 * If the term already exists on the same hierarchical level,
 * or the term slug and name are not unique, a WP_Error object will be returned.
 *
 * @global wpdb $wpdb The WordPress database object.
 * @since 2.3.0
 *
 * @param string       $term     The term to add or update.
 * @param string       $taxonomy The taxonomy to which to add the term
 * @param array|string $args {
 *     Arguments to change values of the inserted term.
 *
 *     @type string 'alias_of'    Slug of the term to make this term an alias of.
 *                                Default empty string. Accepts a term slug.
 *     @type string 'description' The term description.
 *                                Default empty string.
 *     @type int    'parent'      The id of the parent term.
 *                                Default 0.
 *     @type string 'slug'        The term slug to use.
 *                                Default empty string.
 * }
 * @return array|WP_Error An array containing the term_id and term_taxonomy_id, WP_Error otherwise.
 */
function wp_insert_term($term, $taxonomy, $args = array())
{
    global $wpdb;
    if (!taxonomy_exists($taxonomy)) {
        return new WP_Error('invalid_taxonomy', __('Invalid taxonomy'));
    }
    /**
     * Filter a term before it is sanitized and inserted into the database.
     *
     * @since 3.0.0
     *
     * @param string $term     The term to add or update.
     * @param string $taxonomy Taxonomy slug.
     */
    $term = apply_filters('pre_insert_term', $term, $taxonomy);
    if (is_wp_error($term)) {
        return $term;
    }
    if (is_int($term) && 0 == $term) {
        return new WP_Error('invalid_term_id', __('Invalid term ID'));
    }
    if ('' == trim($term)) {
        return new WP_Error('empty_term_name', __('A name is required for this term'));
    }
    $defaults = array('alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => '');
    $args = wp_parse_args($args, $defaults);
    $args['name'] = $term;
    $args['taxonomy'] = $taxonomy;
    $args = sanitize_term($args, $taxonomy, 'db');
    extract($args, EXTR_SKIP);
    // expected_slashed ($name)
    $name = wp_unslash($name);
    $description = wp_unslash($description);
    $slug_provided = !empty($slug);
    if (!$slug_provided) {
        $slug = sanitize_title($name);
    }
    $term_group = 0;
    if ($alias_of) {
        $alias = $wpdb->get_row($wpdb->prepare("SELECT term_id, term_group FROM {$wpdb->terms} WHERE slug = %s", $alias_of));
        if ($alias->term_group) {
            // The alias we want is already in a group, so let's use that one.
            $term_group = $alias->term_group;
        } else {
            // The alias isn't in a group, so let's create a new one and firstly add the alias term to it.
            $term_group = $wpdb->get_var("SELECT MAX(term_group) FROM {$wpdb->terms}") + 1;
            /**
             * 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', $alias->term_id, $taxonomy);
            $wpdb->update($wpdb->terms, compact('term_group'), array('term_id' => $alias->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', $alias->term_id, $taxonomy);
        }
    }
    if ($term_id = term_exists($slug)) {
        $existing_term = $wpdb->get_row($wpdb->prepare("SELECT name FROM {$wpdb->terms} WHERE term_id = %d", $term_id), ARRAY_A);
        // We've got an existing term in the same taxonomy, which matches the name of the new term:
        if (is_taxonomy_hierarchical($taxonomy) && $existing_term['name'] == $name && $exists = term_exists((int) $term_id, $taxonomy)) {
            // Hierarchical, and it matches an existing term, Do not allow same "name" in the same level.
            $siblings = get_terms($taxonomy, array('fields' => 'names', 'get' => 'all', 'parent' => (int) $parent));
            if (in_array($name, $siblings)) {
                if ($slug_provided) {
                    return new WP_Error('term_exists', __('A term with the name and slug provided already exists with this parent.'), $exists['term_id']);
                } else {
                    return new WP_Error('term_exists', __('A term with the name provided already exists with this parent.'), $exists['term_id']);
                }
            } else {
                $slug = wp_unique_term_slug($slug, (object) $args);
                if (false === $wpdb->insert($wpdb->terms, compact('name', 'slug', 'term_group'))) {
                    return new WP_Error('db_insert_error', __('Could not insert term into the database'), $wpdb->last_error);
                }
                $term_id = (int) $wpdb->insert_id;
            }
        } elseif ($existing_term['name'] != $name) {
            // We've got an existing term, with a different name, Create the new term.
            $slug = wp_unique_term_slug($slug, (object) $args);
            if (false === $wpdb->insert($wpdb->terms, compact('name', 'slug', 'term_group'))) {
                return new WP_Error('db_insert_error', __('Could not insert term into the database'), $wpdb->last_error);
            }
            $term_id = (int) $wpdb->insert_id;
        } elseif ($exists = term_exists((int) $term_id, $taxonomy)) {
            // Same name, same slug.
            return new WP_Error('term_exists', __('A term with the name and slug provided already exists.'), $exists['term_id']);
        }
    } else {
        // This term does not exist at all in the database, Create it.
        $slug = wp_unique_term_slug($slug, (object) $args);
        if (false === $wpdb->insert($wpdb->terms, compact('name', 'slug', 'term_group'))) {
            return new WP_Error('db_insert_error', __('Could not insert term into the database'), $wpdb->last_error);
        }
        $term_id = (int) $wpdb->insert_id;
    }
    // Seems unreachable, However, Is used in the case that a term name is provided, which sanitizes to an empty string.
    if (empty($slug)) {
        $slug = sanitize_title($slug, $term_id);
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edit_terms', $term_id, $taxonomy);
        $wpdb->update($wpdb->terms, compact('slug'), compact('term_id'));
        /** This action is documented in wp-includes/taxonomy.php */
        do_action('edited_terms', $term_id, $taxonomy);
    }
    $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));
    if (!empty($tt_id)) {
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    $wpdb->insert($wpdb->term_taxonomy, compact('term_id', 'taxonomy', 'description', 'parent') + array('count' => 0));
    $tt_id = (int) $wpdb->insert_id;
    /**
     * Fires immediately after a new term is created, before the term cache is 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("create_term", $term_id, $tt_id, $taxonomy);
    /**
     * Fires after a new term is created for a specific taxonomy.
     *
     * The dynamic portion of the hook name, $taxonomy, refers
     * to the slug of the taxonomy the term was created for.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Term taxonomy ID.
     */
    do_action("create_{$taxonomy}", $term_id, $tt_id);
    /**
     * Filter the term ID after a new term is created.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Taxonomy term ID.
     */
    $term_id = apply_filters('term_id_filter', $term_id, $tt_id);
    clean_term_cache($term_id, $taxonomy);
    /**
     * Fires after a new term is created, and after the term cache has been cleaned.
     *
     * @since 2.3.0
     */
    do_action("created_term", $term_id, $tt_id, $taxonomy);
    /**
     * Fires after a new term in a specific taxonomy is created, and after the term
     * cache has been cleaned.
     *
     * @since 2.3.0
     *
     * @param int $term_id Term ID.
     * @param int $tt_id   Term taxonomy ID.
     */
    do_action("created_{$taxonomy}", $term_id, $tt_id);
    return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
}

WordPress Version: 3.8

/**
 * Add a new term to the database.
 *
 * A non-existent term is inserted in the following sequence:
 * 1. The term is added to the term table, then related to the taxonomy.
 * 2. If everything is correct, several actions are fired.
 * 3. The 'term_id_filter' is evaluated.
 * 4. The term cache is cleaned.
 * 5. Several more actions are fired.
 * 6. An array is returned containing the term_id and term_taxonomy_id.
 *
 * If the 'slug' argument is not empty, then it is checked to see if the term
 * is invalid. If it is not a valid, existing term, it is added and the term_id
 * is given.
 *
 * If the taxonomy is hierarchical, and the 'parent' argument is not empty,
 * the term is inserted and the term_id will be given.
 * Error handling:
 * If $taxonomy does not exist or $term is empty,
 * a WP_Error object will be returned.
 *
 * If the term already exists on the same hierarchical level,
 * or the term slug and name are not unique, a WP_Error object will be returned.
 *
 * @global wpdb $wpdb The WordPress database object.
 * @since 2.3.0
 *
 * @param string       $term     The term to add or update.
 * @param string       $taxonomy The taxonomy to which to add the term
 * @param array|string $args {
 *     Arguments to change values of the inserted term.
 *
 *     @type string 'alias_of'    Slug of the term to make this term an alias of.
 *                                Default empty string. Accepts a term slug.
 *     @type string 'description' The term description.
 *                                Default empty string.
 *     @type int    'parent'      The id of the parent term.
 *                                Default 0.
 *     @type string 'slug'        The term slug to use.
 *                                Default empty string.
 * }
 * @return array|WP_Error An array containing the term_id and term_taxonomy_id, WP_Error otherwise.
 */
function wp_insert_term($term, $taxonomy, $args = array())
{
    global $wpdb;
    if (!taxonomy_exists($taxonomy)) {
        return new WP_Error('invalid_taxonomy', __('Invalid taxonomy'));
    }
    $term = apply_filters('pre_insert_term', $term, $taxonomy);
    if (is_wp_error($term)) {
        return $term;
    }
    if (is_int($term) && 0 == $term) {
        return new WP_Error('invalid_term_id', __('Invalid term ID'));
    }
    if ('' == trim($term)) {
        return new WP_Error('empty_term_name', __('A name is required for this term'));
    }
    $defaults = array('alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => '');
    $args = wp_parse_args($args, $defaults);
    $args['name'] = $term;
    $args['taxonomy'] = $taxonomy;
    $args = sanitize_term($args, $taxonomy, 'db');
    extract($args, EXTR_SKIP);
    // expected_slashed ($name)
    $name = wp_unslash($name);
    $description = wp_unslash($description);
    $slug_provided = !empty($slug);
    if (!$slug_provided) {
        $slug = sanitize_title($name);
    }
    $term_group = 0;
    if ($alias_of) {
        $alias = $wpdb->get_row($wpdb->prepare("SELECT term_id, term_group FROM {$wpdb->terms} WHERE slug = %s", $alias_of));
        if ($alias->term_group) {
            // The alias we want is already in a group, so let's use that one.
            $term_group = $alias->term_group;
        } else {
            // The alias isn't in a group, so let's create a new one and firstly add the alias term to it.
            $term_group = $wpdb->get_var("SELECT MAX(term_group) FROM {$wpdb->terms}") + 1;
            do_action('edit_terms', $alias->term_id, $taxonomy);
            $wpdb->update($wpdb->terms, compact('term_group'), array('term_id' => $alias->term_id));
            do_action('edited_terms', $alias->term_id, $taxonomy);
        }
    }
    if ($term_id = term_exists($slug)) {
        $existing_term = $wpdb->get_row($wpdb->prepare("SELECT name FROM {$wpdb->terms} WHERE term_id = %d", $term_id), ARRAY_A);
        // We've got an existing term in the same taxonomy, which matches the name of the new term:
        if (is_taxonomy_hierarchical($taxonomy) && $existing_term['name'] == $name && $exists = term_exists((int) $term_id, $taxonomy)) {
            // Hierarchical, and it matches an existing term, Do not allow same "name" in the same level.
            $siblings = get_terms($taxonomy, array('fields' => 'names', 'get' => 'all', 'parent' => (int) $parent));
            if (in_array($name, $siblings)) {
                if ($slug_provided) {
                    return new WP_Error('term_exists', __('A term with the name and slug provided already exists with this parent.'), $exists['term_id']);
                } else {
                    return new WP_Error('term_exists', __('A term with the name provided already exists with this parent.'), $exists['term_id']);
                }
            } else {
                $slug = wp_unique_term_slug($slug, (object) $args);
                if (false === $wpdb->insert($wpdb->terms, compact('name', 'slug', 'term_group'))) {
                    return new WP_Error('db_insert_error', __('Could not insert term into the database'), $wpdb->last_error);
                }
                $term_id = (int) $wpdb->insert_id;
            }
        } elseif ($existing_term['name'] != $name) {
            // We've got an existing term, with a different name, Create the new term.
            $slug = wp_unique_term_slug($slug, (object) $args);
            if (false === $wpdb->insert($wpdb->terms, compact('name', 'slug', 'term_group'))) {
                return new WP_Error('db_insert_error', __('Could not insert term into the database'), $wpdb->last_error);
            }
            $term_id = (int) $wpdb->insert_id;
        } elseif ($exists = term_exists((int) $term_id, $taxonomy)) {
            // Same name, same slug.
            return new WP_Error('term_exists', __('A term with the name and slug provided already exists.'), $exists['term_id']);
        }
    } else {
        // This term does not exist at all in the database, Create it.
        $slug = wp_unique_term_slug($slug, (object) $args);
        if (false === $wpdb->insert($wpdb->terms, compact('name', 'slug', 'term_group'))) {
            return new WP_Error('db_insert_error', __('Could not insert term into the database'), $wpdb->last_error);
        }
        $term_id = (int) $wpdb->insert_id;
    }
    // Seems unreachable, However, Is used in the case that a term name is provided, which sanitizes to an empty string.
    if (empty($slug)) {
        $slug = sanitize_title($slug, $term_id);
        do_action('edit_terms', $term_id, $taxonomy);
        $wpdb->update($wpdb->terms, compact('slug'), compact('term_id'));
        do_action('edited_terms', $term_id, $taxonomy);
    }
    $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));
    if (!empty($tt_id)) {
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    $wpdb->insert($wpdb->term_taxonomy, compact('term_id', 'taxonomy', 'description', 'parent') + array('count' => 0));
    $tt_id = (int) $wpdb->insert_id;
    do_action("create_term", $term_id, $tt_id, $taxonomy);
    do_action("create_{$taxonomy}", $term_id, $tt_id);
    $term_id = apply_filters('term_id_filter', $term_id, $tt_id);
    clean_term_cache($term_id, $taxonomy);
    do_action("created_term", $term_id, $tt_id, $taxonomy);
    do_action("created_{$taxonomy}", $term_id, $tt_id);
    return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
}

WordPress Version: 3.7

/**
 * Add a new term to the database.
 *
 * A non-existent term is inserted in the following sequence:
 * 1. The term is added to the term table, then related to the taxonomy.
 * 2. If everything is correct, several actions are fired.
 * 3. The 'term_id_filter' is evaluated.
 * 4. The term cache is cleaned.
 * 5. Several more actions are fired.
 * 6. An array is returned containing the term_id and term_taxonomy_id.
 *
 * If the 'slug' argument is not empty, then it is checked to see if the term
 * is invalid. If it is not a valid, existing term, it is added and the term_id
 * is given.
 *
 * If the taxonomy is hierarchical, and the 'parent' argument is not empty,
 * the term is inserted and the term_id will be given.
 * Error handling:
 * If $taxonomy does not exist or $term is empty,
 * a WP_Error object will be returned.
 *
 * If the term already exists on the same hierarchical level,
 * or the term slug and name are not unique, a WP_Error object will be returned.
 *
 * @global wpdb $wpdb The WordPress database object.
 * @since 2.3.0
 *
 * @param string       $term     The term to add or update.
 * @param string       $taxonomy The taxonomy to which to add the term
 * @param array|string $args {
 *     Arguments to change values of the inserted term.
 *
 *     @type string 'alias_of'    Slug of the term to make this term an alias of.
 *                                Default empty string. Accepts a term slug.
 *     @type string 'description' The term description.
 *                                Default empty string.
 *     @type int    'parent'      The id of the parent term.
 *                                Default 0.
 *     @type string 'slug'        The term slug to use.
 *                                Default empty string.
 * }
 * @return array|WP_Error An array containing the term_id and term_taxonomy_id, WP_Error otherwise.
 */
function wp_insert_term($term, $taxonomy, $args = array())
{
    global $wpdb;
    if (!taxonomy_exists($taxonomy)) {
        return new WP_Error('invalid_taxonomy', __('Invalid taxonomy'));
    }
    $term = apply_filters('pre_insert_term', $term, $taxonomy);
    if (is_wp_error($term)) {
        return $term;
    }
    if (is_int($term) && 0 == $term) {
        return new WP_Error('invalid_term_id', __('Invalid term ID'));
    }
    if ('' == trim($term)) {
        return new WP_Error('empty_term_name', __('A name is required for this term'));
    }
    $defaults = array('alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => '');
    $args = wp_parse_args($args, $defaults);
    $args['name'] = $term;
    $args['taxonomy'] = $taxonomy;
    $args = sanitize_term($args, $taxonomy, 'db');
    extract($args, EXTR_SKIP);
    // expected_slashed ($name)
    $name = wp_unslash($name);
    $description = wp_unslash($description);
    if (empty($slug)) {
        $slug = sanitize_title($name);
    }
    $term_group = 0;
    if ($alias_of) {
        $alias = $wpdb->get_row($wpdb->prepare("SELECT term_id, term_group FROM {$wpdb->terms} WHERE slug = %s", $alias_of));
        if ($alias->term_group) {
            // The alias we want is already in a group, so let's use that one.
            $term_group = $alias->term_group;
        } else {
            // The alias isn't in a group, so let's create a new one and firstly add the alias term to it.
            $term_group = $wpdb->get_var("SELECT MAX(term_group) FROM {$wpdb->terms}") + 1;
            do_action('edit_terms', $alias->term_id, $taxonomy);
            $wpdb->update($wpdb->terms, compact('term_group'), array('term_id' => $alias->term_id));
            do_action('edited_terms', $alias->term_id, $taxonomy);
        }
    }
    if ($term_id = term_exists($slug)) {
        $existing_term = $wpdb->get_row($wpdb->prepare("SELECT name FROM {$wpdb->terms} WHERE term_id = %d", $term_id), ARRAY_A);
        // We've got an existing term in the same taxonomy, which matches the name of the new term:
        if (is_taxonomy_hierarchical($taxonomy) && $existing_term['name'] == $name && $exists = term_exists((int) $term_id, $taxonomy)) {
            // Hierarchical, and it matches an existing term, Do not allow same "name" in the same level.
            $siblings = get_terms($taxonomy, array('fields' => 'names', 'get' => 'all', 'parent' => (int) $parent));
            if (in_array($name, $siblings)) {
                return new WP_Error('term_exists', __('A term with the name provided already exists with this parent.'), $exists['term_id']);
            } else {
                $slug = wp_unique_term_slug($slug, (object) $args);
                if (false === $wpdb->insert($wpdb->terms, compact('name', 'slug', 'term_group'))) {
                    return new WP_Error('db_insert_error', __('Could not insert term into the database'), $wpdb->last_error);
                }
                $term_id = (int) $wpdb->insert_id;
            }
        } elseif ($existing_term['name'] != $name) {
            // We've got an existing term, with a different name, Create the new term.
            $slug = wp_unique_term_slug($slug, (object) $args);
            if (false === $wpdb->insert($wpdb->terms, compact('name', 'slug', 'term_group'))) {
                return new WP_Error('db_insert_error', __('Could not insert term into the database'), $wpdb->last_error);
            }
            $term_id = (int) $wpdb->insert_id;
        } elseif ($exists = term_exists((int) $term_id, $taxonomy)) {
            // Same name, same slug.
            return new WP_Error('term_exists', __('A term with the name provided already exists.'), $exists['term_id']);
        }
    } else {
        // This term does not exist at all in the database, Create it.
        $slug = wp_unique_term_slug($slug, (object) $args);
        if (false === $wpdb->insert($wpdb->terms, compact('name', 'slug', 'term_group'))) {
            return new WP_Error('db_insert_error', __('Could not insert term into the database'), $wpdb->last_error);
        }
        $term_id = (int) $wpdb->insert_id;
    }
    // Seems unreachable, However, Is used in the case that a term name is provided, which sanitizes to an empty string.
    if (empty($slug)) {
        $slug = sanitize_title($slug, $term_id);
        do_action('edit_terms', $term_id, $taxonomy);
        $wpdb->update($wpdb->terms, compact('slug'), compact('term_id'));
        do_action('edited_terms', $term_id, $taxonomy);
    }
    $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));
    if (!empty($tt_id)) {
        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    }
    $wpdb->insert($wpdb->term_taxonomy, compact('term_id', 'taxonomy', 'description', 'parent') + array('count' => 0));
    $tt_id = (int) $wpdb->insert_id;
    do_action("create_term", $term_id, $tt_id, $taxonomy);
    do_action("create_{$taxonomy}", $term_id, $tt_id);
    $term_id = apply_filters('term_id_filter', $term_id, $tt_id);
    clean_term_cache($term_id, $taxonomy);
    do_action("created_term", $term_id, $tt_id, $taxonomy);
    do_action("created_{$taxonomy}", $term_id, $tt_id);
    return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
}