/** * 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. * * NOTE: This is the Connections equivalent of @see wp_insert_term() in WordPress core ../wp-includes/taxonomy.php * * Actions: * cn_edit_terms * Passes: (int) $term_id, (string) $taxonomy * * cn_edited_terms * Passes: (int) $term_id, (string) $taxonomy * * cn_create_term * Passes: (int) $term_id, (int) $taxonomy_term_id, (string) $taxonomy * * cn_create_$taxonomy * Passes: (int) $term_id, (int) $taxonomy_term_id * * cn_created_term * Passes: (int) $term_id, (int) $taxonomy_term_id, (string) $taxonomy * * cn_created_$taxonomy * Passes: (int) $term_id, (int) $taxonomy_term_id * * Filters: * cn_pre_insert_term * Passes: (string) $term, (string) $taxonomy * Return: $term * * cn_term_id_filter * Passes: (int) $term_id, (int) $taxonomy_term_id * Return: $term_id * * @global wpdb $wpdb The WordPress database object. * * @access public * @since 8.1.6 * @static * * @param string $term The term to add or update. * @param string $taxonomy The taxonomy to which to add the term * @param array|object $args { * Optional. 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. */ public static function insert($term, $taxonomy, $args = array()) { /** @var $wpdb wpdb */ global $wpdb; // @todo Implement taxonomy check. //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 8.1.6 * * @param string $term The term to add or update. * @param string $taxonomy Taxonomy slug. */ $term = apply_filters('cn_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', 'connections')); } if ('' == trim($term)) { return new WP_Error('empty_term_name', __('A name is required for this term', 'connections')); } $defaults = array('alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => ''); $args = wp_parse_args($args, $defaults); if (0 < $args['parent'] && !self::exists((int) $args['parent'])) { return new WP_Error('missing_parent', __('Parent term does not exist.', 'connections')); } $args['name'] = $term; $args['taxonomy'] = $taxonomy; $args = sanitize_term($args, 'cn_' . $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 = cnTerm::getBy('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; cnTerm::update($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 = self::getTaxonomyTerms($taxonomy, array('name' => $name, 'hide_empty' => false)); /* * The `name` match in `self::getTaxonomyTerms()` 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)) { /** @var cnTerm_Object $name_match */ $name_match = $_match; break; } } } if ($name_match) { $slug_match = cnTerm::getBy('slug', $slug, $taxonomy); if (!$slug_provided || $name_match->slug === $slug || $slug_match) { if (is_taxonomy_hierarchical($taxonomy)) { $siblings = self::getTaxonomyTerms($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.', 'connections'), $existing_term->term_id); } } else { return new WP_Error('term_exists', __('A term with the name provided already exists in this taxonomy.', 'connections'), $name_match->term_id); } } } $slug = cnTerm::unique_slug($slug, (object) $args); if (FALSE === $wpdb->insert(CN_TERMS_TABLE, compact('name', 'slug', 'term_group'))) { return new WP_Error('db_insert_error', __('Could not insert term into the database', 'connections'), $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); /** @see cnTerm::insert() */ do_action('cn_edit_terms', $term_id, $taxonomy); $wpdb->update(CN_TERMS_TABLE, compact('slug'), compact('term_id')); /** @see cnTerm::insert() */ do_action('cn_edited_terms', $term_id, $taxonomy); } $tt_id = $wpdb->get_var($wpdb->prepare("SELECT tt.term_taxonomy_id FROM " . CN_TERM_TAXONOMY_TABLE . " AS tt INNER JOIN " . CN_TERMS_TABLE . " 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(CN_TERM_TAXONOMY_TABLE, 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 " . CN_TERMS_TABLE . " t INNER JOIN " . CN_TERM_TAXONOMY_TABLE . " 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; cnTerm::cleanCache($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 8.1.6 * * @param int $term_id Term ID. * @param int $tt_id Term taxonomy ID. * @param string $taxonomy Taxonomy slug. */ do_action("cn_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 8.1.6 * * @param int $term_id Term ID. * @param int $tt_id Term taxonomy ID. */ do_action("cn_create_{$taxonomy}", $term_id, $tt_id); /** * Filter the term ID after a new term is created. * * @since 8.1.6 * * @param int $term_id Term ID. * @param int $tt_id Taxonomy term ID. */ $term_id = apply_filters('cn_term_id_filter', $term_id, $tt_id); self::cleanCache($term_id, $taxonomy); /** * Fires after a new term is created, and after the term cache has been cleaned. * * @since 8.1.6 */ do_action("cn_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 8.1.6 * * @param int $term_id Term ID. * @param int $tt_id Term taxonomy ID. */ do_action("cn_created_{$taxonomy}", $term_id, $tt_id); return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id); }