Make WordPress Core


Ignore:
Timestamp:
08/26/2015 12:48:11 PM (11 years ago)
Author:
wonderboymusic
Message:

Taxonomy: move WP_Tax_Query into its own file. taxonomy.php loads the new files, so this is 100% BC if someone is loading taxonomy.php directly. New files created using svn cp.

Creates:
class-wp-tax-query.php
taxonomy-functions.php

taxonomy.php contains only top-level code. Class file only contains the class. Functions file only contains functions.

See #33413.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/taxonomy.php

    r33727 r33760  
    99 */
    1010
    11 //
    12 // Taxonomy Registration
    13 //
    14 
    15 /**
    16  * Creates the initial taxonomies.
    17  *
    18  * This function fires twice: in wp-settings.php before plugins are loaded (for
    19  * backwards compatibility reasons), and again on the {@see 'init'} action. We must
    20  * avoid registering rewrite rules before the {@see 'init'} action.
    21  *
    22  * @since 2.8.0
    23  *
    24  * @global WP_Rewrite $wp_rewrite The WordPress rewrite class.
    25  */
    26 function create_initial_taxonomies() {
    27         global $wp_rewrite;
    28 
    29         if ( ! did_action( 'init' ) ) {
    30                 $rewrite = array( 'category' => false, 'post_tag' => false, 'post_format' => false );
    31         } else {
    32 
    33                 /**
    34                  * Filter the post formats rewrite base.
    35                  *
    36                  * @since 3.1.0
    37                  *
    38                  * @param string $context Context of the rewrite base. Default 'type'.
    39                  */
    40                 $post_format_base = apply_filters( 'post_format_rewrite_base', 'type' );
    41                 $rewrite = array(
    42                         'category' => array(
    43                                 'hierarchical' => true,
    44                                 'slug' => get_option('category_base') ? get_option('category_base') : 'category',
    45                                 'with_front' => ! get_option('category_base') || $wp_rewrite->using_index_permalinks(),
    46                                 'ep_mask' => EP_CATEGORIES,
    47                         ),
    48                         'post_tag' => array(
    49                                 'hierarchical' => false,
    50                                 'slug' => get_option('tag_base') ? get_option('tag_base') : 'tag',
    51                                 'with_front' => ! get_option('tag_base') || $wp_rewrite->using_index_permalinks(),
    52                                 'ep_mask' => EP_TAGS,
    53                         ),
    54                         'post_format' => $post_format_base ? array( 'slug' => $post_format_base ) : false,
    55                 );
    56         }
    57 
    58         register_taxonomy( 'category', 'post', array(
    59                 'hierarchical' => true,
    60                 'query_var' => 'category_name',
    61                 'rewrite' => $rewrite['category'],
    62                 'public' => true,
    63                 'show_ui' => true,
    64                 'show_admin_column' => true,
    65                 '_builtin' => true,
    66         ) );
    67 
    68         register_taxonomy( 'post_tag', 'post', array(
    69                 'hierarchical' => false,
    70                 'query_var' => 'tag',
    71                 'rewrite' => $rewrite['post_tag'],
    72                 'public' => true,
    73                 'show_ui' => true,
    74                 'show_admin_column' => true,
    75                 '_builtin' => true,
    76         ) );
    77 
    78         register_taxonomy( 'nav_menu', 'nav_menu_item', array(
    79                 'public' => false,
    80                 'hierarchical' => false,
    81                 'labels' => array(
    82                         'name' => __( 'Navigation Menus' ),
    83                         'singular_name' => __( 'Navigation Menu' ),
    84                 ),
    85                 'query_var' => false,
    86                 'rewrite' => false,
    87                 'show_ui' => false,
    88                 '_builtin' => true,
    89                 'show_in_nav_menus' => false,
    90         ) );
    91 
    92         register_taxonomy( 'link_category', 'link', array(
    93                 'hierarchical' => false,
    94                 'labels' => array(
    95                         'name' => __( 'Link Categories' ),
    96                         'singular_name' => __( 'Link Category' ),
    97                         'search_items' => __( 'Search Link Categories' ),
    98                         'popular_items' => null,
    99                         'all_items' => __( 'All Link Categories' ),
    100                         'edit_item' => __( 'Edit Link Category' ),
    101                         'update_item' => __( 'Update Link Category' ),
    102                         'add_new_item' => __( 'Add New Link Category' ),
    103                         'new_item_name' => __( 'New Link Category Name' ),
    104                         'separate_items_with_commas' => null,
    105                         'add_or_remove_items' => null,
    106                         'choose_from_most_used' => null,
    107                 ),
    108                 'capabilities' => array(
    109                         'manage_terms' => 'manage_links',
    110                         'edit_terms'   => 'manage_links',
    111                         'delete_terms' => 'manage_links',
    112                         'assign_terms' => 'manage_links',
    113                 ),
    114                 'query_var' => false,
    115                 'rewrite' => false,
    116                 'public' => false,
    117                 'show_ui' => false,
    118                 '_builtin' => true,
    119         ) );
    120 
    121         register_taxonomy( 'post_format', 'post', array(
    122                 'public' => true,
    123                 'hierarchical' => false,
    124                 'labels' => array(
    125                         'name' => _x( 'Format', 'post format' ),
    126                         'singular_name' => _x( 'Format', 'post format' ),
    127                 ),
    128                 'query_var' => true,
    129                 'rewrite' => $rewrite['post_format'],
    130                 'show_ui' => false,
    131                 '_builtin' => true,
    132                 'show_in_nav_menus' => current_theme_supports( 'post-formats' ),
    133         ) );
    134 }
    135 
    136 /**
    137  * Retrieves a list of registered taxonomy names or objects.
    138  *
    139  * @since 3.0.0
    140  *
    141  * @global array $wp_taxonomies The registered taxonomies.
    142  *
    143  * @param array  $args     Optional. An array of `key => value` arguments to match against the taxonomy objects.
    144  *                         Default empty array.
    145  * @param string $output   Optional. The type of output to return in the array. Accepts either taxonomy 'names'
    146  *                         or 'objects'. Default 'names'.
    147  * @param string $operator Optional. The logical operation to perform. Accepts 'and' or 'or'. 'or' means only
    148  *                         one element from the array needs to match; 'and' means all elements must match.
    149  *                         Default 'and'.
    150  * @return array A list of taxonomy names or objects.
    151  */
    152 function get_taxonomies( $args = array(), $output = 'names', $operator = 'and' ) {
    153         global $wp_taxonomies;
    154 
    155         $field = ('names' == $output) ? 'name' : false;
    156 
    157         return wp_filter_object_list($wp_taxonomies, $args, $operator, $field);
    158 }
    159 
    160 /**
    161  * Return all of the taxonomy names that are of $object_type.
    162  *
    163  * It appears that this function can be used to find all of the names inside of
    164  * $wp_taxonomies global variable.
    165  *
    166  * `<?php $taxonomies = get_object_taxonomies('post'); ?>` Should
    167  * result in `Array( 'category', 'post_tag' )`
    168  *
    169  * @since 2.3.0
    170  *
    171  * @global array $wp_taxonomies The registered taxonomies.
    172  *
    173  * @param array|string|WP_Post $object Name of the type of taxonomy object, or an object (row from posts)
    174  * @param string               $output Optional. The type of output to return in the array. Accepts either
    175  *                                     taxonomy 'names' or 'objects'. Default 'names'.
    176  * @return array The names of all taxonomy of $object_type.
    177  */
    178 function get_object_taxonomies( $object, $output = 'names' ) {
    179         global $wp_taxonomies;
    180 
    181         if ( is_object($object) ) {
    182                 if ( $object->post_type == 'attachment' )
    183                         return get_attachment_taxonomies($object);
    184                 $object = $object->post_type;
    185         }
    186 
    187         $object = (array) $object;
    188 
    189         $taxonomies = array();
    190         foreach ( (array) $wp_taxonomies as $tax_name => $tax_obj ) {
    191                 if ( array_intersect($object, (array) $tax_obj->object_type) ) {
    192                         if ( 'names' == $output )
    193                                 $taxonomies[] = $tax_name;
    194                         else
    195                                 $taxonomies[ $tax_name ] = $tax_obj;
    196                 }
    197         }
    198 
    199         return $taxonomies;
    200 }
    201 
    202 /**
    203  * Retrieves the taxonomy object of $taxonomy.
    204  *
    205  * The get_taxonomy function will first check that the parameter string given
    206  * is a taxonomy object and if it is, it will return it.
    207  *
    208  * @since 2.3.0
    209  *
    210  * @global array $wp_taxonomies The registered taxonomies.
    211  *
    212  * @param string $taxonomy Name of taxonomy object to return.
    213  * @return object|false The Taxonomy Object or false if $taxonomy doesn't exist.
    214  */
    215 function get_taxonomy( $taxonomy ) {
    216         global $wp_taxonomies;
    217 
    218         if ( ! taxonomy_exists( $taxonomy ) )
    219                 return false;
    220 
    221         return $wp_taxonomies[$taxonomy];
    222 }
    223 
    224 /**
    225  * Checks that the taxonomy name exists.
    226  *
    227  * Formerly is_taxonomy(), introduced in 2.3.0.
    228  *
    229  * @since 3.0.0
    230  *
    231  * @global array $wp_taxonomies The registered taxonomies.
    232  *
    233  * @param string $taxonomy Name of taxonomy object.
    234  * @return bool Whether the taxonomy exists.
    235  */
    236 function taxonomy_exists( $taxonomy ) {
    237         global $wp_taxonomies;
    238 
    239         return isset( $wp_taxonomies[$taxonomy] );
    240 }
    241 
    242 /**
    243  * Whether the taxonomy object is hierarchical.
    244  *
    245  * Checks to make sure that the taxonomy is an object first. Then Gets the
    246  * object, and finally returns the hierarchical value in the object.
    247  *
    248  * A false return value might also mean that the taxonomy does not exist.
    249  *
    250  * @since 2.3.0
    251  *
    252  * @param string $taxonomy Name of taxonomy object.
    253  * @return bool Whether the taxonomy is hierarchical.
    254  */
    255 function is_taxonomy_hierarchical($taxonomy) {
    256         if ( ! taxonomy_exists($taxonomy) )
    257                 return false;
    258 
    259         $taxonomy = get_taxonomy($taxonomy);
    260         return $taxonomy->hierarchical;
    261 }
    262 
    263 /**
    264  * Create or modify a taxonomy object. Do not use before init.
    265  *
    266  * A simple function for creating or modifying a taxonomy object based on the
    267  * parameters given. The function will accept an array (third optional
    268  * parameter), along with strings for the taxonomy name and another string for
    269  * the object type.
    270  *
    271  * Nothing is returned, so expect error maybe or use taxonomy_exists() to check
    272  * whether taxonomy exists.
    273  *
    274  * Optional $args contents:
    275  *
    276  * - label - Name of the taxonomy shown in the menu. Usually plural. If not set, labels['name'] will be used.
    277  * - labels - An array of labels for this taxonomy.
    278  *     * By default tag labels are used for non-hierarchical types and category labels for hierarchical ones.
    279  *     * You can see accepted values in {@link get_taxonomy_labels()}.
    280  * - description - A short descriptive summary of what the taxonomy is for. Defaults to blank.
    281  * - public - If the taxonomy should be publicly queryable; //@TODO not implemented.
    282  *     * Defaults to true.
    283  * - hierarchical - Whether the taxonomy is hierarchical (e.g. category). Defaults to false.
    284  * - show_ui - Whether to generate a default UI for managing this taxonomy in the admin.
    285  *     * If not set, the default is inherited from public.
    286  * - show_in_menu - Whether to show the taxonomy in the admin menu.
    287  *     * If true, the taxonomy is shown as a submenu of the object type menu.
    288  *     * If false, no menu is shown.
    289  *     * show_ui must be true.
    290  *     * If not set, the default is inherited from show_ui.
    291  * - show_in_nav_menus - Makes this taxonomy available for selection in navigation menus.
    292  *     * If not set, the default is inherited from public.
    293  * - show_tagcloud - Whether to list the taxonomy in the Tag Cloud Widget.
    294  *     * If not set, the default is inherited from show_ui.
    295  * - show_in_quick_edit - Whether to show the taxonomy in the quick/bulk edit panel.
    296  *     * It not set, the default is inherited from show_ui.
    297  * - show_admin_column - Whether to display a column for the taxonomy on its post type listing screens.
    298  *     * Defaults to false.
    299  * - meta_box_cb - Provide a callback function for the meta box display.
    300  *     * If not set, defaults to post_categories_meta_box for hierarchical taxonomies
    301  *     and post_tags_meta_box for non-hierarchical.
    302  *     * If false, no meta box is shown.
    303  * - capabilities - Array of capabilities for this taxonomy.
    304  *     * You can see accepted values in this function.
    305  * - rewrite - Triggers the handling of rewrites for this taxonomy. Defaults to true, using $taxonomy as slug.
    306  *     * To prevent rewrite, set to false.
    307  *     * To specify rewrite rules, an array can be passed with any of these keys
    308  *         * 'slug' => string Customize the permastruct slug. Defaults to $taxonomy key
    309  *         * 'with_front' => bool Should the permastruct be prepended with WP_Rewrite::$front. Defaults to true.
    310  *         * 'hierarchical' => bool Either hierarchical rewrite tag or not. Defaults to false.
    311  *         * 'ep_mask' => const Assign an endpoint mask.
    312  *             * If not specified, defaults to EP_NONE.
    313  * - query_var - Sets the query_var key for this taxonomy. Defaults to $taxonomy key
    314  *     * If false, a taxonomy cannot be loaded at ?{query_var}={term_slug}
    315  *     * If specified as a string, the query ?{query_var_string}={term_slug} will be valid.
    316  * - update_count_callback - Works much like a hook, in that it will be called when the count is updated.
    317  *     * Defaults to _update_post_term_count() for taxonomies attached to post types, which then confirms
    318  *       that the objects are published before counting them.
    319  *     * Defaults to _update_generic_term_count() for taxonomies attached to other object types, such as links.
    320  * - _builtin - true if this taxonomy is a native or "built-in" taxonomy. THIS IS FOR INTERNAL USE ONLY!
    321  *
    322  * @todo Document $args as a hash notation.
    323  *
    324  * @since 2.3.0
    325  * @since 4.2.0 Introduced `show_in_quick_edit` argument.
    326  *
    327  * @global array $wp_taxonomies Registered taxonomies.
    328  * @global WP    $wp            WP instance.
    329  *
    330  * @param string       $taxonomy    Taxonomy key, must not exceed 32 characters.
    331  * @param array|string $object_type Name of the object type for the taxonomy object.
    332  * @param array|string $args        See optional args description above.
    333  * @return WP_Error|void WP_Error, if errors.
    334  */
    335 function register_taxonomy( $taxonomy, $object_type, $args = array() ) {
    336         global $wp_taxonomies, $wp;
    337 
    338         if ( ! is_array( $wp_taxonomies ) )
    339                 $wp_taxonomies = array();
    340 
    341         $defaults = array(
    342                 'labels'                => array(),
    343                 'description'           => '',
    344                 'public'                => true,
    345                 'hierarchical'          => false,
    346                 'show_ui'               => null,
    347                 'show_in_menu'          => null,
    348                 'show_in_nav_menus'     => null,
    349                 'show_tagcloud'         => null,
    350                 'show_in_quick_edit'    => null,
    351                 'show_admin_column'     => false,
    352                 'meta_box_cb'           => null,
    353                 'capabilities'          => array(),
    354                 'rewrite'               => true,
    355                 'query_var'             => $taxonomy,
    356                 'update_count_callback' => '',
    357                 '_builtin'              => false,
    358         );
    359         $args = wp_parse_args( $args, $defaults );
    360 
    361         if ( empty( $taxonomy ) || strlen( $taxonomy ) > 32 ) {
    362                 _doing_it_wrong( __FUNCTION__, __( 'Taxonomy names must be between 1 and 32 characters in length.' ), '4.2' );
    363                 return new WP_Error( 'taxonomy_length_invalid', __( 'Taxonomy names must be between 1 and 32 characters in length.' ) );
    364         }
    365 
    366         if ( false !== $args['query_var'] && ! empty( $wp ) ) {
    367                 if ( true === $args['query_var'] )
    368                         $args['query_var'] = $taxonomy;
    369                 else
    370                         $args['query_var'] = sanitize_title_with_dashes( $args['query_var'] );
    371                 $wp->add_query_var( $args['query_var'] );
    372         }
    373 
    374         if ( false !== $args['rewrite'] && ( is_admin() || '' != get_option( 'permalink_structure' ) ) ) {
    375                 $args['rewrite'] = wp_parse_args( $args['rewrite'], array(
    376                         'with_front' => true,
    377                         'hierarchical' => false,
    378                         'ep_mask' => EP_NONE,
    379                 ) );
    380 
    381                 if ( empty( $args['rewrite']['slug'] ) )
    382                         $args['rewrite']['slug'] = sanitize_title_with_dashes( $taxonomy );
    383 
    384                 if ( $args['hierarchical'] && $args['rewrite']['hierarchical'] )
    385                         $tag = '(.+?)';
    386                 else
    387                         $tag = '([^/]+)';
    388 
    389                 add_rewrite_tag( "%$taxonomy%", $tag, $args['query_var'] ? "{$args['query_var']}=" : "taxonomy=$taxonomy&term=" );
    390                 add_permastruct( $taxonomy, "{$args['rewrite']['slug']}/%$taxonomy%", $args['rewrite'] );
    391         }
    392 
    393         // If not set, default to the setting for public.
    394         if ( null === $args['show_ui'] )
    395                 $args['show_ui'] = $args['public'];
    396 
    397         // If not set, default to the setting for show_ui.
    398         if ( null === $args['show_in_menu' ] || ! $args['show_ui'] )
    399                 $args['show_in_menu' ] = $args['show_ui'];
    400 
    401         // If not set, default to the setting for public.
    402         if ( null === $args['show_in_nav_menus'] )
    403                 $args['show_in_nav_menus'] = $args['public'];
    404 
    405         // If not set, default to the setting for show_ui.
    406         if ( null === $args['show_tagcloud'] )
    407                 $args['show_tagcloud'] = $args['show_ui'];
    408 
    409         // If not set, default to the setting for show_ui.
    410         if ( null === $args['show_in_quick_edit'] ) {
    411                 $args['show_in_quick_edit'] = $args['show_ui'];
    412         }
    413 
    414         $default_caps = array(
    415                 'manage_terms' => 'manage_categories',
    416                 'edit_terms'   => 'manage_categories',
    417                 'delete_terms' => 'manage_categories',
    418                 'assign_terms' => 'edit_posts',
    419         );
    420         $args['cap'] = (object) array_merge( $default_caps, $args['capabilities'] );
    421         unset( $args['capabilities'] );
    422 
    423         $args['name'] = $taxonomy;
    424         $args['object_type'] = array_unique( (array) $object_type );
    425 
    426         $args['labels'] = get_taxonomy_labels( (object) $args );
    427         $args['label'] = $args['labels']->name;
    428 
    429         // If not set, use the default meta box
    430         if ( null === $args['meta_box_cb'] ) {
    431                 if ( $args['hierarchical'] )
    432                         $args['meta_box_cb'] = 'post_categories_meta_box';
    433                 else
    434                         $args['meta_box_cb'] = 'post_tags_meta_box';
    435         }
    436 
    437         $wp_taxonomies[ $taxonomy ] = (object) $args;
    438 
    439         // register callback handling for metabox
    440         add_filter( 'wp_ajax_add-' . $taxonomy, '_wp_ajax_add_hierarchical_term' );
    441 
    442         /**
    443          * Fires after a taxonomy is registered.
    444          *
    445          * @since 3.3.0
    446          *
    447          * @param string       $taxonomy    Taxonomy slug.
    448          * @param array|string $object_type Object type or array of object types.
    449          * @param array        $args        Array of taxonomy registration arguments.
    450          */
    451         do_action( 'registered_taxonomy', $taxonomy, $object_type, $args );
    452 }
    453 
    454 /**
    455  * Builds an object with all taxonomy labels out of a taxonomy object
    456  *
    457  * Accepted keys of the label array in the taxonomy object:
    458  *
    459  * - name - general name for the taxonomy, usually plural. The same as and overridden by $tax->label. Default is Tags/Categories
    460  * - singular_name - name for one object of this taxonomy. Default is Tag/Category
    461  * - search_items - Default is Search Tags/Search Categories
    462  * - popular_items - This string isn't used on hierarchical taxonomies. Default is Popular Tags
    463  * - all_items - Default is All Tags/All Categories
    464  * - parent_item - This string isn't used on non-hierarchical taxonomies. In hierarchical ones the default is Parent Category
    465  * - parent_item_colon - The same as `parent_item`, but with colon `:` in the end
    466  * - edit_item - Default is Edit Tag/Edit Category
    467  * - view_item - Default is View Tag/View Category
    468  * - update_item - Default is Update Tag/Update Category
    469  * - add_new_item - Default is Add New Tag/Add New Category
    470  * - new_item_name - Default is New Tag Name/New Category Name
    471  * - separate_items_with_commas - This string isn't used on hierarchical taxonomies. Default is "Separate tags with commas", used in the meta box.
    472  * - add_or_remove_items - This string isn't used on hierarchical taxonomies. Default is "Add or remove tags", used in the meta box when JavaScript is disabled.
    473  * - choose_from_most_used - This string isn't used on hierarchical taxonomies. Default is "Choose from the most used tags", used in the meta box.
    474  * - not_found - Default is "No tags found"/"No categories found", used in the meta box and taxonomy list table.
    475  * - no_terms - Default is "No tags"/"No categories", used in the posts and media list tables.
    476  *
    477  * Above, the first default value is for non-hierarchical taxonomies (like tags) and the second one is for hierarchical taxonomies (like categories).
    478  *
    479  * @todo Better documentation for the labels array.
    480  *
    481  * @since 3.0.0
    482  * @since 4.3.0 Added the `no_terms` label.
    483  *
    484  * @param object $tax Taxonomy object.
    485  * @return object object with all the labels as member variables.
    486  */
    487 function get_taxonomy_labels( $tax ) {
    488         $tax->labels = (array) $tax->labels;
    489 
    490         if ( isset( $tax->helps ) && empty( $tax->labels['separate_items_with_commas'] ) )
    491                 $tax->labels['separate_items_with_commas'] = $tax->helps;
    492 
    493         if ( isset( $tax->no_tagcloud ) && empty( $tax->labels['not_found'] ) )
    494                 $tax->labels['not_found'] = $tax->no_tagcloud;
    495 
    496         $nohier_vs_hier_defaults = array(
    497                 'name' => array( _x( 'Tags', 'taxonomy general name' ), _x( 'Categories', 'taxonomy general name' ) ),
    498                 'singular_name' => array( _x( 'Tag', 'taxonomy singular name' ), _x( 'Category', 'taxonomy singular name' ) ),
    499                 'search_items' => array( __( 'Search Tags' ), __( 'Search Categories' ) ),
    500                 'popular_items' => array( __( 'Popular Tags' ), null ),
    501                 'all_items' => array( __( 'All Tags' ), __( 'All Categories' ) ),
    502                 'parent_item' => array( null, __( 'Parent Category' ) ),
    503                 'parent_item_colon' => array( null, __( 'Parent Category:' ) ),
    504                 'edit_item' => array( __( 'Edit Tag' ), __( 'Edit Category' ) ),
    505                 'view_item' => array( __( 'View Tag' ), __( 'View Category' ) ),
    506                 'update_item' => array( __( 'Update Tag' ), __( 'Update Category' ) ),
    507                 'add_new_item' => array( __( 'Add New Tag' ), __( 'Add New Category' ) ),
    508                 'new_item_name' => array( __( 'New Tag Name' ), __( 'New Category Name' ) ),
    509                 'separate_items_with_commas' => array( __( 'Separate tags with commas' ), null ),
    510                 'add_or_remove_items' => array( __( 'Add or remove tags' ), null ),
    511                 'choose_from_most_used' => array( __( 'Choose from the most used tags' ), null ),
    512                 'not_found' => array( __( 'No tags found.' ), __( 'No categories found.' ) ),
    513                 'no_terms' => array( __( 'No tags' ), __( 'No categories' ) ),
    514         );
    515         $nohier_vs_hier_defaults['menu_name'] = $nohier_vs_hier_defaults['name'];
    516 
    517         return _get_custom_object_labels( $tax, $nohier_vs_hier_defaults );
    518 }
    519 
    520 /**
    521  * Add an already registered taxonomy to an object type.
    522  *
    523  * @since 3.0.0
    524  *
    525  * @global array $wp_taxonomies The registered taxonomies.
    526  *
    527  * @param string $taxonomy    Name of taxonomy object.
    528  * @param string $object_type Name of the object type.
    529  * @return bool True if successful, false if not.
    530  */
    531 function register_taxonomy_for_object_type( $taxonomy, $object_type) {
    532         global $wp_taxonomies;
    533 
    534         if ( !isset($wp_taxonomies[$taxonomy]) )
    535                 return false;
    536 
    537         if ( ! get_post_type_object($object_type) )
    538                 return false;
    539 
    540         if ( ! in_array( $object_type, $wp_taxonomies[$taxonomy]->object_type ) )
    541                 $wp_taxonomies[$taxonomy]->object_type[] = $object_type;
    542 
    543         // Filter out empties.
    544         $wp_taxonomies[ $taxonomy ]->object_type = array_filter( $wp_taxonomies[ $taxonomy ]->object_type );
    545 
    546         return true;
    547 }
    548 
    549 /**
    550  * Remove an already registered taxonomy from an object type.
    551  *
    552  * @since 3.7.0
    553  *
    554  * @global array $wp_taxonomies The registered taxonomies.
    555  *
    556  * @param string $taxonomy    Name of taxonomy object.
    557  * @param string $object_type Name of the object type.
    558  * @return bool True if successful, false if not.
    559  */
    560 function unregister_taxonomy_for_object_type( $taxonomy, $object_type ) {
    561         global $wp_taxonomies;
    562 
    563         if ( ! isset( $wp_taxonomies[ $taxonomy ] ) )
    564                 return false;
    565 
    566         if ( ! get_post_type_object( $object_type ) )
    567                 return false;
    568 
    569         $key = array_search( $object_type, $wp_taxonomies[ $taxonomy ]->object_type, true );
    570         if ( false === $key )
    571                 return false;
    572 
    573         unset( $wp_taxonomies[ $taxonomy ]->object_type[ $key ] );
    574         return true;
    575 }
    576 
    577 //
    578 // Term API
    579 //
    580 
    581 /**
    582  * Retrieve object_ids of valid taxonomy and term.
    583  *
    584  * The strings of $taxonomies must exist before this function will continue. On
    585  * failure of finding a valid taxonomy, it will return an WP_Error class, kind
    586  * of like Exceptions in PHP 5, except you can't catch them. Even so, you can
    587  * still test for the WP_Error class and get the error message.
    588  *
    589  * The $terms aren't checked the same as $taxonomies, but still need to exist
    590  * for $object_ids to be returned.
    591  *
    592  * It is possible to change the order that object_ids is returned by either
    593  * using PHP sort family functions or using the database by using $args with
    594  * either ASC or DESC array. The value should be in the key named 'order'.
    595  *
    596  * @since 2.3.0
    597  *
    598  * @global wpdb $wpdb WordPress database abstraction object.
    599  *
    600  * @param int|array    $term_ids   Term id or array of term ids of terms that will be used.
    601  * @param string|array $taxonomies String of taxonomy name or Array of string values of taxonomy names.
    602  * @param array|string $args       Change the order of the object_ids, either ASC or DESC.
    603  * @return WP_Error|array If the taxonomy does not exist, then WP_Error will be returned. On success.
    604  *      the array can be empty meaning that there are no $object_ids found or it will return the $object_ids found.
    605  */
    606 function get_objects_in_term( $term_ids, $taxonomies, $args = array() ) {
    607         global $wpdb;
    608 
    609         if ( ! is_array( $term_ids ) ) {
    610                 $term_ids = array( $term_ids );
    611         }
    612         if ( ! is_array( $taxonomies ) ) {
    613                 $taxonomies = array( $taxonomies );
    614         }
    615         foreach ( (array) $taxonomies as $taxonomy ) {
    616                 if ( ! taxonomy_exists( $taxonomy ) ) {
    617                         return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy' ) );
    618                 }
    619         }
    620 
    621         $defaults = array( 'order' => 'ASC' );
    622         $args = wp_parse_args( $args, $defaults );
    623 
    624         $order = ( 'desc' == strtolower( $args['order'] ) ) ? 'DESC' : 'ASC';
    625 
    626         $term_ids = array_map('intval', $term_ids );
    627 
    628         $taxonomies = "'" . implode( "', '", $taxonomies ) . "'";
    629         $term_ids = "'" . implode( "', '", $term_ids ) . "'";
    630 
    631         $object_ids = $wpdb->get_col("SELECT tr.object_id FROM $wpdb->term_relationships AS tr INNER JOIN $wpdb->term_taxonomy AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id WHERE tt.taxonomy IN ($taxonomies) AND tt.term_id IN ($term_ids) ORDER BY tr.object_id $order");
    632 
    633         if ( ! $object_ids ){
    634                 return array();
    635         }
    636         return $object_ids;
    637 }
    638 
    639 /**
    640  * Given a taxonomy query, generates SQL to be appended to a main query.
    641  *
    642  * @since 3.1.0
    643  *
    644  * @see WP_Tax_Query
    645  *
    646  * @param array  $tax_query         A compact tax query
    647  * @param string $primary_table
    648  * @param string $primary_id_column
    649  * @return array
    650  */
    651 function get_tax_sql( $tax_query, $primary_table, $primary_id_column ) {
    652         $tax_query_obj = new WP_Tax_Query( $tax_query );
    653         return $tax_query_obj->get_sql( $primary_table, $primary_id_column );
    654 }
    655 
    656 /**
    657  * Class for generating SQL clauses that filter a primary query according to object taxonomy terms.
    658  *
    659  * `WP_Tax_Query` is a helper that allows primary query classes, such as WP_Query, to filter
    660  * their results by object metadata, by generating `JOIN` and `WHERE` subclauses to be attached
    661  * to the primary SQL query string.
    662  *
    663  * @since 3.1.0
    664  */
    665 class WP_Tax_Query {
    666 
    667         /**
    668          * Array of taxonomy queries.
    669          *
    670          * See {@see WP_Tax_Query::__construct()} for information on tax query arguments.
    671          *
    672          * @since 3.1.0
    673          * @access public
    674          * @var array
    675          */
    676         public $queries = array();
    677 
    678         /**
    679          * The relation between the queries. Can be one of 'AND' or 'OR'.
    680          *
    681          * @since 3.1.0
    682          * @access public
    683          * @var string
    684          */
    685         public $relation;
    686 
    687         /**
    688          * Standard response when the query should not return any rows.
    689          *
    690          * @since 3.2.0
    691          *
    692          * @static
    693          * @access private
    694          * @var string
    695          */
    696         private static $no_results = array( 'join' => array( '' ), 'where' => array( '0 = 1' ) );
    697 
    698         /**
    699          * A flat list of table aliases used in the JOIN clauses.
    700          *
    701          * @since 4.1.0
    702          * @access protected
    703          * @var array
    704          */
    705         protected $table_aliases = array();
    706 
    707         /**
    708          * Terms and taxonomies fetched by this query.
    709          *
    710          * We store this data in a flat array because they are referenced in a
    711          * number of places by WP_Query.
    712          *
    713          * @since 4.1.0
    714          * @access public
    715          * @var array
    716          */
    717         public $queried_terms = array();
    718 
    719         /**
    720          * Database table that where the metadata's objects are stored (eg $wpdb->users).
    721          *
    722          * @since 4.1.0
    723          * @access public
    724          * @var string
    725          */
    726         public $primary_table;
    727 
    728         /**
    729          * Column in 'primary_table' that represents the ID of the object.
    730          *
    731          * @since 4.1.0
    732          * @access public
    733          * @var string
    734          */
    735         public $primary_id_column;
    736 
    737         /**
    738          * Constructor.
    739          *
    740          * @since 3.1.0
    741          * @since 4.1.0 Added support for `$operator` 'NOT EXISTS' and 'EXISTS' values.
    742          * @access public
    743          *
    744          * @param array $tax_query {
    745          *     Array of taxonomy query clauses.
    746          *
    747          *     @type string $relation Optional. The MySQL keyword used to join
    748          *                            the clauses of the query. Accepts 'AND', or 'OR'. Default 'AND'.
    749          *     @type array {
    750          *         Optional. An array of first-order clause parameters, or another fully-formed tax query.
    751          *
    752          *         @type string           $taxonomy         Taxonomy being queried. Optional when field=term_taxonomy_id.
    753          *         @type string|int|array $terms            Term or terms to filter by.
    754          *         @type string           $field            Field to match $terms against. Accepts 'term_id', 'slug',
    755          *                                                 'name', or 'term_taxonomy_id'. Default: 'term_id'.
    756          *         @type string           $operator         MySQL operator to be used with $terms in the WHERE clause.
    757          *                                                  Accepts 'AND', 'IN', 'NOT IN', 'EXISTS', 'NOT EXISTS'.
    758          *                                                  Default: 'IN'.
    759          *         @type bool             $include_children Optional. Whether to include child terms.
    760          *                                                  Requires a $taxonomy. Default: true.
    761          *     }
    762          * }
    763          */
    764         public function __construct( $tax_query ) {
    765                 if ( isset( $tax_query['relation'] ) ) {
    766                         $this->relation = $this->sanitize_relation( $tax_query['relation'] );
    767                 } else {
    768                         $this->relation = 'AND';
    769                 }
    770 
    771                 $this->queries = $this->sanitize_query( $tax_query );
    772         }
    773 
    774         /**
    775          * Ensure the 'tax_query' argument passed to the class constructor is well-formed.
    776          *
    777          * Ensures that each query-level clause has a 'relation' key, and that
    778          * each first-order clause contains all the necessary keys from `$defaults`.
    779          *
    780          * @since 4.1.0
    781          * @access public
    782          *
    783          * @param array $queries Array of queries clauses.
    784          * @return array Sanitized array of query clauses.
    785          */
    786         public function sanitize_query( $queries ) {
    787                 $cleaned_query = array();
    788 
    789                 $defaults = array(
    790                         'taxonomy' => '',
    791                         'terms' => array(),
    792                         'field' => 'term_id',
    793                         'operator' => 'IN',
    794                         'include_children' => true,
    795                 );
    796 
    797                 foreach ( $queries as $key => $query ) {
    798                         if ( 'relation' === $key ) {
    799                                 $cleaned_query['relation'] = $this->sanitize_relation( $query );
    800 
    801                         // First-order clause.
    802                         } elseif ( self::is_first_order_clause( $query ) ) {
    803 
    804                                 $cleaned_clause = array_merge( $defaults, $query );
    805                                 $cleaned_clause['terms'] = (array) $cleaned_clause['terms'];
    806                                 $cleaned_query[] = $cleaned_clause;
    807 
    808                                 /*
    809                                  * Keep a copy of the clause in the flate
    810                                  * $queried_terms array, for use in WP_Query.
    811                                  */
    812                                 if ( ! empty( $cleaned_clause['taxonomy'] ) && 'NOT IN' !== $cleaned_clause['operator'] ) {
    813                                         $taxonomy = $cleaned_clause['taxonomy'];
    814                                         if ( ! isset( $this->queried_terms[ $taxonomy ] ) ) {
    815                                                 $this->queried_terms[ $taxonomy ] = array();
    816                                         }
    817 
    818                                         /*
    819                                          * Backward compatibility: Only store the first
    820                                          * 'terms' and 'field' found for a given taxonomy.
    821                                          */
    822                                         if ( ! empty( $cleaned_clause['terms'] ) && ! isset( $this->queried_terms[ $taxonomy ]['terms'] ) ) {
    823                                                 $this->queried_terms[ $taxonomy ]['terms'] = $cleaned_clause['terms'];
    824                                         }
    825 
    826                                         if ( ! empty( $cleaned_clause['field'] ) && ! isset( $this->queried_terms[ $taxonomy ]['field'] ) ) {
    827                                                 $this->queried_terms[ $taxonomy ]['field'] = $cleaned_clause['field'];
    828                                         }
    829                                 }
    830 
    831                         // Otherwise, it's a nested query, so we recurse.
    832                         } elseif ( is_array( $query ) ) {
    833                                 $cleaned_subquery = $this->sanitize_query( $query );
    834 
    835                                 if ( ! empty( $cleaned_subquery ) ) {
    836                                         // All queries with children must have a relation.
    837                                         if ( ! isset( $cleaned_subquery['relation'] ) ) {
    838                                                 $cleaned_subquery['relation'] = 'AND';
    839                                         }
    840 
    841                                         $cleaned_query[] = $cleaned_subquery;
    842                                 }
    843                         }
    844                 }
    845 
    846                 return $cleaned_query;
    847         }
    848 
    849         /**
    850          * Sanitize a 'relation' operator.
    851          *
    852          * @since 4.1.0
    853          * @access public
    854          *
    855          * @param string $relation Raw relation key from the query argument.
    856          * @return string Sanitized relation ('AND' or 'OR').
    857          */
    858         public function sanitize_relation( $relation ) {
    859                 if ( 'OR' === strtoupper( $relation ) ) {
    860                         return 'OR';
    861                 } else {
    862                         return 'AND';
    863                 }
    864         }
    865 
    866         /**
    867          * Determine whether a clause is first-order.
    868          *
    869          * A "first-order" clause is one that contains any of the first-order
    870          * clause keys ('terms', 'taxonomy', 'include_children', 'field',
    871          * 'operator'). An empty clause also counts as a first-order clause,
    872          * for backward compatibility. Any clause that doesn't meet this is
    873          * determined, by process of elimination, to be a higher-order query.
    874          *
    875          * @since 4.1.0
    876          *
    877          * @static
    878          * @access protected
    879          *
    880          * @param array $query Tax query arguments.
    881          * @return bool Whether the query clause is a first-order clause.
    882          */
    883         protected static function is_first_order_clause( $query ) {
    884                 return is_array( $query ) && ( empty( $query ) || array_key_exists( 'terms', $query ) || array_key_exists( 'taxonomy', $query ) || array_key_exists( 'include_children', $query ) || array_key_exists( 'field', $query ) || array_key_exists( 'operator', $query ) );
    885         }
    886 
    887         /**
    888          * Generates SQL clauses to be appended to a main query.
    889          *
    890          * @since 3.1.0
    891          *
    892          * @static
    893          * @access public
    894          *
    895          * @param string $primary_table     Database table where the object being filtered is stored (eg wp_users).
    896          * @param string $primary_id_column ID column for the filtered object in $primary_table.
    897          * @return array {
    898          *     Array containing JOIN and WHERE SQL clauses to append to the main query.
    899          *
    900          *     @type string $join  SQL fragment to append to the main JOIN clause.
    901          *     @type string $where SQL fragment to append to the main WHERE clause.
    902          * }
    903          */
    904         public function get_sql( $primary_table, $primary_id_column ) {
    905                 $this->primary_table = $primary_table;
    906                 $this->primary_id_column = $primary_id_column;
    907 
    908                 return $this->get_sql_clauses();
    909         }
    910 
    911         /**
    912          * Generate SQL clauses to be appended to a main query.
    913          *
    914          * Called by the public WP_Tax_Query::get_sql(), this method
    915          * is abstracted out to maintain parity with the other Query classes.
    916          *
    917          * @since 4.1.0
    918          * @access protected
    919          *
    920          * @return array {
    921          *     Array containing JOIN and WHERE SQL clauses to append to the main query.
    922          *
    923          *     @type string $join  SQL fragment to append to the main JOIN clause.
    924          *     @type string $where SQL fragment to append to the main WHERE clause.
    925          * }
    926          */
    927         protected function get_sql_clauses() {
    928                 /*
    929                  * $queries are passed by reference to get_sql_for_query() for recursion.
    930                  * To keep $this->queries unaltered, pass a copy.
    931                  */
    932                 $queries = $this->queries;
    933                 $sql = $this->get_sql_for_query( $queries );
    934 
    935                 if ( ! empty( $sql['where'] ) ) {
    936                         $sql['where'] = ' AND ' . $sql['where'];
    937                 }
    938 
    939                 return $sql;
    940         }
    941 
    942         /**
    943          * Generate SQL clauses for a single query array.
    944          *
    945          * If nested subqueries are found, this method recurses the tree to
    946          * produce the properly nested SQL.
    947          *
    948          * @since 4.1.0
    949          * @access protected
    950          *
    951          * @param array $query Query to parse, passed by reference.
    952          * @param int   $depth Optional. Number of tree levels deep we currently are.
    953          *                     Used to calculate indentation. Default 0.
    954          * @return array {
    955          *     Array containing JOIN and WHERE SQL clauses to append to a single query array.
    956          *
    957          *     @type string $join  SQL fragment to append to the main JOIN clause.
    958          *     @type string $where SQL fragment to append to the main WHERE clause.
    959          * }
    960          */
    961         protected function get_sql_for_query( &$query, $depth = 0 ) {
    962                 $sql_chunks = array(
    963                         'join'  => array(),
    964                         'where' => array(),
    965                 );
    966 
    967                 $sql = array(
    968                         'join'  => '',
    969                         'where' => '',
    970                 );
    971 
    972                 $indent = '';
    973                 for ( $i = 0; $i < $depth; $i++ ) {
    974                         $indent .= "  ";
    975                 }
    976 
    977                 foreach ( $query as $key => &$clause ) {
    978                         if ( 'relation' === $key ) {
    979                                 $relation = $query['relation'];
    980                         } elseif ( is_array( $clause ) ) {
    981 
    982                                 // This is a first-order clause.
    983                                 if ( $this->is_first_order_clause( $clause ) ) {
    984                                         $clause_sql = $this->get_sql_for_clause( $clause, $query );
    985 
    986                                         $where_count = count( $clause_sql['where'] );
    987                                         if ( ! $where_count ) {
    988                                                 $sql_chunks['where'][] = '';
    989                                         } elseif ( 1 === $where_count ) {
    990                                                 $sql_chunks['where'][] = $clause_sql['where'][0];
    991                                         } else {
    992                                                 $sql_chunks['where'][] = '( ' . implode( ' AND ', $clause_sql['where'] ) . ' )';
    993                                         }
    994 
    995                                         $sql_chunks['join'] = array_merge( $sql_chunks['join'], $clause_sql['join'] );
    996                                 // This is a subquery, so we recurse.
    997                                 } else {
    998                                         $clause_sql = $this->get_sql_for_query( $clause, $depth + 1 );
    999 
    1000                                         $sql_chunks['where'][] = $clause_sql['where'];
    1001                                         $sql_chunks['join'][]  = $clause_sql['join'];
    1002                                 }
    1003                         }
    1004                 }
    1005 
    1006                 // Filter to remove empties.
    1007                 $sql_chunks['join']  = array_filter( $sql_chunks['join'] );
    1008                 $sql_chunks['where'] = array_filter( $sql_chunks['where'] );
    1009 
    1010                 if ( empty( $relation ) ) {
    1011                         $relation = 'AND';
    1012                 }
    1013 
    1014                 // Filter duplicate JOIN clauses and combine into a single string.
    1015                 if ( ! empty( $sql_chunks['join'] ) ) {
    1016                         $sql['join'] = implode( ' ', array_unique( $sql_chunks['join'] ) );
    1017                 }
    1018 
    1019                 // Generate a single WHERE clause with proper brackets and indentation.
    1020                 if ( ! empty( $sql_chunks['where'] ) ) {
    1021                         $sql['where'] = '( ' . "\n  " . $indent . implode( ' ' . "\n  " . $indent . $relation . ' ' . "\n  " . $indent, $sql_chunks['where'] ) . "\n" . $indent . ')';
    1022                 }
    1023 
    1024                 return $sql;
    1025         }
    1026 
    1027         /**
    1028          * Generate SQL JOIN and WHERE clauses for a "first-order" query clause.
    1029          *
    1030          * @since 4.1.0
    1031          * @access public
    1032          *
    1033          * @global wpdb $wpdb The WordPress database abstraction object.
    1034          *
    1035          * @param array $clause       Query clause, passed by reference.
    1036          * @param array $parent_query Parent query array.
    1037          * @return array {
    1038          *     Array containing JOIN and WHERE SQL clauses to append to a first-order query.
    1039          *
    1040          *     @type string $join  SQL fragment to append to the main JOIN clause.
    1041          *     @type string $where SQL fragment to append to the main WHERE clause.
    1042          * }
    1043          */
    1044         public function get_sql_for_clause( &$clause, $parent_query ) {
    1045                 global $wpdb;
    1046 
    1047                 $sql = array(
    1048                         'where' => array(),
    1049                         'join'  => array(),
    1050                 );
    1051 
    1052                 $join = $where = '';
    1053 
    1054                 $this->clean_query( $clause );
    1055 
    1056                 if ( is_wp_error( $clause ) ) {
    1057                         return self::$no_results;
    1058                 }
    1059 
    1060                 $terms = $clause['terms'];
    1061                 $operator = strtoupper( $clause['operator'] );
    1062 
    1063                 if ( 'IN' == $operator ) {
    1064 
    1065                         if ( empty( $terms ) ) {
    1066                                 return self::$no_results;
    1067                         }
    1068 
    1069                         $terms = implode( ',', $terms );
    1070 
    1071                         /*
    1072                          * Before creating another table join, see if this clause has a
    1073                          * sibling with an existing join that can be shared.
    1074                          */
    1075                         $alias = $this->find_compatible_table_alias( $clause, $parent_query );
    1076                         if ( false === $alias ) {
    1077                                 $i = count( $this->table_aliases );
    1078                                 $alias = $i ? 'tt' . $i : $wpdb->term_relationships;
    1079 
    1080                                 // Store the alias as part of a flat array to build future iterators.
    1081                                 $this->table_aliases[] = $alias;
    1082 
    1083                                 // Store the alias with this clause, so later siblings can use it.
    1084                                 $clause['alias'] = $alias;
    1085 
    1086                                 $join .= " INNER JOIN $wpdb->term_relationships";
    1087                                 $join .= $i ? " AS $alias" : '';
    1088                                 $join .= " ON ($this->primary_table.$this->primary_id_column = $alias.object_id)";
    1089                         }
    1090 
    1091 
    1092                         $where = "$alias.term_taxonomy_id $operator ($terms)";
    1093 
    1094                 } elseif ( 'NOT IN' == $operator ) {
    1095 
    1096                         if ( empty( $terms ) ) {
    1097                                 return $sql;
    1098                         }
    1099 
    1100                         $terms = implode( ',', $terms );
    1101 
    1102                         $where = "$this->primary_table.$this->primary_id_column NOT IN (
    1103                                 SELECT object_id
    1104                                 FROM $wpdb->term_relationships
    1105                                 WHERE term_taxonomy_id IN ($terms)
    1106                         )";
    1107 
    1108                 } elseif ( 'AND' == $operator ) {
    1109 
    1110                         if ( empty( $terms ) ) {
    1111                                 return $sql;
    1112                         }
    1113 
    1114                         $num_terms = count( $terms );
    1115 
    1116                         $terms = implode( ',', $terms );
    1117 
    1118                         $where = "(
    1119                                 SELECT COUNT(1)
    1120                                 FROM $wpdb->term_relationships
    1121                                 WHERE term_taxonomy_id IN ($terms)
    1122                                 AND object_id = $this->primary_table.$this->primary_id_column
    1123                         ) = $num_terms";
    1124 
    1125                 } elseif ( 'NOT EXISTS' === $operator || 'EXISTS' === $operator ) {
    1126 
    1127                         $where = $wpdb->prepare( "$operator (
    1128                                 SELECT 1
    1129                                 FROM $wpdb->term_relationships
    1130                                 INNER JOIN $wpdb->term_taxonomy
    1131                                 ON $wpdb->term_taxonomy.term_taxonomy_id = $wpdb->term_relationships.term_taxonomy_id
    1132                                 WHERE $wpdb->term_taxonomy.taxonomy = %s
    1133                                 AND $wpdb->term_relationships.object_id = $this->primary_table.$this->primary_id_column
    1134                         )", $clause['taxonomy'] );
    1135 
    1136                 }
    1137 
    1138                 $sql['join'][]  = $join;
    1139                 $sql['where'][] = $where;
    1140                 return $sql;
    1141         }
    1142 
    1143         /**
    1144          * Identify an existing table alias that is compatible with the current query clause.
    1145          *
    1146          * We avoid unnecessary table joins by allowing each clause to look for
    1147          * an existing table alias that is compatible with the query that it
    1148          * needs to perform.
    1149          *
    1150          * An existing alias is compatible if (a) it is a sibling of `$clause`
    1151          * (ie, it's under the scope of the same relation), and (b) the combination
    1152          * of operator and relation between the clauses allows for a shared table
    1153          * join. In the case of WP_Tax_Query, this only applies to 'IN'
    1154          * clauses that are connected by the relation 'OR'.
    1155          *
    1156          * @since 4.1.0
    1157          * @access protected
    1158          *
    1159          * @param array       $clause       Query clause.
    1160          * @param array       $parent_query Parent query of $clause.
    1161          * @return string|false Table alias if found, otherwise false.
    1162          */
    1163         protected function find_compatible_table_alias( $clause, $parent_query ) {
    1164                 $alias = false;
    1165 
    1166                 // Sanity check. Only IN queries use the JOIN syntax .
    1167                 if ( ! isset( $clause['operator'] ) || 'IN' !== $clause['operator'] ) {
    1168                         return $alias;
    1169                 }
    1170 
    1171                 // Since we're only checking IN queries, we're only concerned with OR relations.
    1172                 if ( ! isset( $parent_query['relation'] ) || 'OR' !== $parent_query['relation'] ) {
    1173                         return $alias;
    1174                 }
    1175 
    1176                 $compatible_operators = array( 'IN' );
    1177 
    1178                 foreach ( $parent_query as $sibling ) {
    1179                         if ( ! is_array( $sibling ) || ! $this->is_first_order_clause( $sibling ) ) {
    1180                                 continue;
    1181                         }
    1182 
    1183                         if ( empty( $sibling['alias'] ) || empty( $sibling['operator'] ) ) {
    1184                                 continue;
    1185                         }
    1186 
    1187                         // The sibling must both have compatible operator to share its alias.
    1188                         if ( in_array( strtoupper( $sibling['operator'] ), $compatible_operators ) ) {
    1189                                 $alias = $sibling['alias'];
    1190                                 break;
    1191                         }
    1192                 }
    1193 
    1194                 return $alias;
    1195         }
    1196 
    1197         /**
    1198          * Validates a single query.
    1199          *
    1200          * @since 3.2.0
    1201          * @access private
    1202          *
    1203          * @param array &$query The single query.
    1204          */
    1205         private function clean_query( &$query ) {
    1206                 if ( empty( $query['taxonomy'] ) ) {
    1207                         if ( 'term_taxonomy_id' !== $query['field'] ) {
    1208                                 $query = new WP_Error( 'Invalid taxonomy' );
    1209                                 return;
    1210                         }
    1211 
    1212                         // so long as there are shared terms, include_children requires that a taxonomy is set
    1213                         $query['include_children'] = false;
    1214                 } elseif ( ! taxonomy_exists( $query['taxonomy'] ) ) {
    1215                         $query = new WP_Error( 'Invalid taxonomy' );
    1216                         return;
    1217                 }
    1218 
    1219                 $query['terms'] = array_unique( (array) $query['terms'] );
    1220 
    1221                 if ( is_taxonomy_hierarchical( $query['taxonomy'] ) && $query['include_children'] ) {
    1222                         $this->transform_query( $query, 'term_id' );
    1223 
    1224                         if ( is_wp_error( $query ) )
    1225                                 return;
    1226 
    1227                         $children = array();
    1228                         foreach ( $query['terms'] as $term ) {
    1229                                 $children = array_merge( $children, get_term_children( $term, $query['taxonomy'] ) );
    1230                                 $children[] = $term;
    1231                         }
    1232                         $query['terms'] = $children;
    1233                 }
    1234 
    1235                 $this->transform_query( $query, 'term_taxonomy_id' );
    1236         }
    1237 
    1238         /**
    1239          * Transforms a single query, from one field to another.
    1240          *
    1241          * @since 3.2.0
    1242          *
    1243          * @global wpdb $wpdb The WordPress database abstraction object.
    1244          *
    1245          * @param array  &$query          The single query.
    1246          * @param string $resulting_field The resulting field. Accepts 'slug', 'name', 'term_taxonomy_id',
    1247          *                                or 'term_id'. Default 'term_id'.
    1248          */
    1249         public function transform_query( &$query, $resulting_field ) {
    1250                 global $wpdb;
    1251 
    1252                 if ( empty( $query['terms'] ) )
    1253                         return;
    1254 
    1255                 if ( $query['field'] == $resulting_field )
    1256                         return;
    1257 
    1258                 $resulting_field = sanitize_key( $resulting_field );
    1259 
    1260                 switch ( $query['field'] ) {
    1261                         case 'slug':
    1262                         case 'name':
    1263                                 foreach ( $query['terms'] as &$term ) {
    1264                                         /*
    1265                                          * 0 is the $term_id parameter. We don't have a term ID yet, but it doesn't
    1266                                          * matter because `sanitize_term_field()` ignores the $term_id param when the
    1267                                          * context is 'db'.
    1268                                          */
    1269                                         $term = "'" . esc_sql( sanitize_term_field( $query['field'], $term, 0, $query['taxonomy'], 'db' ) ) . "'";
    1270                                 }
    1271 
    1272                                 $terms = implode( ",", $query['terms'] );
    1273 
    1274                                 $terms = $wpdb->get_col( "
    1275                                         SELECT $wpdb->term_taxonomy.$resulting_field
    1276                                         FROM $wpdb->term_taxonomy
    1277                                         INNER JOIN $wpdb->terms USING (term_id)
    1278                                         WHERE taxonomy = '{$query['taxonomy']}'
    1279                                         AND $wpdb->terms.{$query['field']} IN ($terms)
    1280                                 " );
    1281                                 break;
    1282                         case 'term_taxonomy_id':
    1283                                 $terms = implode( ',', array_map( 'intval', $query['terms'] ) );
    1284                                 $terms = $wpdb->get_col( "
    1285                                         SELECT $resulting_field
    1286                                         FROM $wpdb->term_taxonomy
    1287                                         WHERE term_taxonomy_id IN ($terms)
    1288                                 " );
    1289                                 break;
    1290                         default:
    1291                                 $terms = implode( ',', array_map( 'intval', $query['terms'] ) );
    1292                                 $terms = $wpdb->get_col( "
    1293                                         SELECT $resulting_field
    1294                                         FROM $wpdb->term_taxonomy
    1295                                         WHERE taxonomy = '{$query['taxonomy']}'
    1296                                         AND term_id IN ($terms)
    1297                                 " );
    1298                 }
    1299 
    1300                 if ( 'AND' == $query['operator'] && count( $terms ) < count( $query['terms'] ) ) {
    1301                         $query = new WP_Error( 'Inexistent terms' );
    1302                         return;
    1303                 }
    1304 
    1305                 $query['terms'] = $terms;
    1306                 $query['field'] = $resulting_field;
    1307         }
    1308 }
    1309 
    1310 /**
    1311  * Get all Term data from database by Term ID.
    1312  *
    1313  * The usage of the get_term function is to apply filters to a term object. It
    1314  * is possible to get a term object from the database before applying the
    1315  * filters.
    1316  *
    1317  * $term ID must be part of $taxonomy, to get from the database. Failure, might
    1318  * be able to be captured by the hooks. Failure would be the same value as $wpdb
    1319  * returns for the get_row method.
    1320  *
    1321  * There are two hooks, one is specifically for each term, named 'get_term', and
    1322  * the second is for the taxonomy name, 'term_$taxonomy'. Both hooks gets the
    1323  * term object, and the taxonomy name as parameters. Both hooks are expected to
    1324  * return a Term object.
    1325  *
    1326  * {@see 'get_term'} hook - Takes two parameters the term Object and the taxonomy name.
    1327  * Must return term object. Used in get_term() as a catch-all filter for every
    1328  * $term.
    1329  *
    1330  * {@see 'get_$taxonomy'} hook - Takes two parameters the term Object and the taxonomy
    1331  * name. Must return term object. $taxonomy will be the taxonomy name, so for
    1332  * example, if 'category', it would be 'get_category' as the filter name. Useful
    1333  * for custom taxonomies or plugging into default taxonomies.
    1334  *
    1335  * @todo Better formatting for DocBlock
    1336  *
    1337  * @since 2.3.0
    1338  *
    1339  * @global wpdb $wpdb WordPress database abstraction object.
    1340  * @see sanitize_term_field() The $context param lists the available values for get_term_by() $filter param.
    1341  *
    1342  * @param int|object $term     If integer, will get from database. If object will apply filters and return $term.
    1343  * @param string     $taxonomy Taxonomy name that $term is part of.
    1344  * @param string     $output   Constant OBJECT, ARRAY_A, or ARRAY_N
    1345  * @param string     $filter   Optional, default is raw or no WordPress defined filter will applied.
    1346  * @return object|array|null|WP_Error Term Row from database. Will return null if $term is empty. If taxonomy does not
    1347  * exist then WP_Error will be returned.
    1348  */
    1349 function get_term($term, $taxonomy, $output = OBJECT, $filter = 'raw') {
    1350         global $wpdb;
    1351 
    1352         if ( empty( $term ) ) {
    1353                 return new WP_Error( 'invalid_term', __( 'Empty Term' ) );
    1354         }
    1355 
    1356         if ( ! taxonomy_exists( $taxonomy ) ) {
    1357                 return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy' ) );
    1358         }
    1359 
    1360         if ( is_object($term) && empty($term->filter) ) {
    1361                 wp_cache_add( $term->term_id, $term, $taxonomy );
    1362                 $_term = $term;
    1363         } else {
    1364                 if ( is_object($term) )
    1365                         $term = $term->term_id;
    1366                 if ( !$term = (int) $term )
    1367                         return null;
    1368                 if ( ! $_term = wp_cache_get( $term, $taxonomy ) ) {
    1369                         $_term = $wpdb->get_row( $wpdb->prepare( "SELECT t.*, tt.* FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id WHERE tt.taxonomy = %s AND t.term_id = %d LIMIT 1", $taxonomy, $term) );
    1370                         if ( ! $_term )
    1371                                 return null;
    1372                         wp_cache_add( $term, $_term, $taxonomy );
    1373                 }
    1374         }
    1375 
    1376         /**
    1377          * Filter a term.
    1378          *
    1379          * @since 2.3.0
    1380          *
    1381          * @param int|object $_term    Term object or ID.
    1382          * @param string     $taxonomy The taxonomy slug.
    1383          */
    1384         $_term = apply_filters( 'get_term', $_term, $taxonomy );
    1385 
    1386         /**
    1387          * Filter a taxonomy.
    1388          *
    1389          * The dynamic portion of the filter name, `$taxonomy`, refers
    1390          * to the taxonomy slug.
    1391          *
    1392          * @since 2.3.0
    1393          *
    1394          * @param int|object $_term    Term object or ID.
    1395          * @param string     $taxonomy The taxonomy slug.
    1396          */
    1397         $_term = apply_filters( "get_$taxonomy", $_term, $taxonomy );
    1398         $_term = sanitize_term($_term, $taxonomy, $filter);
    1399 
    1400         if ( $output == OBJECT ) {
    1401                 return $_term;
    1402         } elseif ( $output == ARRAY_A ) {
    1403                 $__term = get_object_vars($_term);
    1404                 return $__term;
    1405         } elseif ( $output == ARRAY_N ) {
    1406                 $__term = array_values(get_object_vars($_term));
    1407                 return $__term;
    1408         } else {
    1409                 return $_term;
    1410         }
    1411 }
    1412 
    1413 /**
    1414  * Get all Term data from database by Term field and data.
    1415  *
    1416  * Warning: $value is not escaped for 'name' $field. You must do it yourself, if
    1417  * required.
    1418  *
    1419  * The default $field is 'id', therefore it is possible to also use null for
    1420  * field, but not recommended that you do so.
    1421  *
    1422  * If $value does not exist, the return value will be false. If $taxonomy exists
    1423  * and $field and $value combinations exist, the Term will be returned.
    1424  *
    1425  * @todo Better formatting for DocBlock.
    1426  *
    1427  * @since 2.3.0
    1428  *
    1429  * @global wpdb $wpdb WordPress database abstraction object.
    1430  * @see sanitize_term_field() The $context param lists the available values for get_term_by() $filter param.
    1431  *
    1432  * @param string     $field    Either 'slug', 'name', 'id' (term_id), or 'term_taxonomy_id'
    1433  * @param string|int $value    Search for this term value
    1434  * @param string     $taxonomy Taxonomy Name
    1435  * @param string     $output   Constant OBJECT, ARRAY_A, or ARRAY_N
    1436  * @param string     $filter   Optional, default is raw or no WordPress defined filter will applied.
    1437  * @return object|array|null|WP_Error|false Term Row from database.
    1438  *                                          Will return false if $taxonomy does not exist or $term was not found.
    1439  */
    1440 function get_term_by($field, $value, $taxonomy, $output = OBJECT, $filter = 'raw') {
    1441         global $wpdb;
    1442 
    1443         if ( ! taxonomy_exists($taxonomy) )
    1444                 return false;
    1445 
    1446         if ( 'slug' == $field ) {
    1447                 $field = 't.slug';
    1448                 $value = sanitize_title($value);
    1449                 if ( empty($value) )
    1450                         return false;
    1451         } elseif ( 'name' == $field ) {
    1452                 // Assume already escaped
    1453                 $value = wp_unslash($value);
    1454                 $field = 't.name';
    1455         } elseif ( 'term_taxonomy_id' == $field ) {
    1456                 $value = (int) $value;
    1457                 $field = 'tt.term_taxonomy_id';
    1458         } else {
    1459                 $term = get_term( (int) $value, $taxonomy, $output, $filter );
    1460                 if ( is_wp_error( $term ) )
    1461                         $term = false;
    1462                 return $term;
    1463         }
    1464 
    1465         $term = $wpdb->get_row( $wpdb->prepare( "SELECT t.*, tt.* FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id WHERE tt.taxonomy = %s AND $field = %s LIMIT 1", $taxonomy, $value ) );
    1466         if ( ! $term )
    1467                 return false;
    1468 
    1469         wp_cache_add( $term->term_id, $term, $taxonomy );
    1470 
    1471         /** This filter is documented in wp-includes/taxonomy.php */
    1472         $term = apply_filters( 'get_term', $term, $taxonomy );
    1473 
    1474         /** This filter is documented in wp-includes/taxonomy.php */
    1475         $term = apply_filters( "get_$taxonomy", $term, $taxonomy );
    1476 
    1477         $term = sanitize_term($term, $taxonomy, $filter);
    1478 
    1479         if ( $output == OBJECT ) {
    1480                 return $term;
    1481         } elseif ( $output == ARRAY_A ) {
    1482                 return get_object_vars($term);
    1483         } elseif ( $output == ARRAY_N ) {
    1484                 return array_values(get_object_vars($term));
    1485         } else {
    1486                 return $term;
    1487         }
    1488 }
    1489 
    1490 /**
    1491  * Merge all term children into a single array of their IDs.
    1492  *
    1493  * This recursive function will merge all of the children of $term into the same
    1494  * array of term IDs. Only useful for taxonomies which are hierarchical.
    1495  *
    1496  * Will return an empty array if $term does not exist in $taxonomy.
    1497  *
    1498  * @since 2.3.0
    1499  *
    1500  * @param string $term_id  ID of Term to get children.
    1501  * @param string $taxonomy Taxonomy Name.
    1502  * @return array|WP_Error List of Term IDs. WP_Error returned if `$taxonomy` does not exist.
    1503  */
    1504 function get_term_children( $term_id, $taxonomy ) {
    1505         if ( ! taxonomy_exists($taxonomy) )
    1506                 return new WP_Error('invalid_taxonomy', __('Invalid taxonomy'));
    1507 
    1508         $term_id = intval( $term_id );
    1509 
    1510         $terms = _get_term_hierarchy($taxonomy);
    1511 
    1512         if ( ! isset($terms[$term_id]) )
    1513                 return array();
    1514 
    1515         $children = $terms[$term_id];
    1516 
    1517         foreach ( (array) $terms[$term_id] as $child ) {
    1518                 if ( $term_id == $child ) {
    1519                         continue;
    1520                 }
    1521 
    1522                 if ( isset($terms[$child]) )
    1523                         $children = array_merge($children, get_term_children($child, $taxonomy));
    1524         }
    1525 
    1526         return $children;
    1527 }
    1528 
    1529 /**
    1530  * Get sanitized Term field.
    1531  *
    1532  * Does checks for $term, based on the $taxonomy. The function is for contextual
    1533  * reasons and for simplicity of usage. See sanitize_term_field() for more
    1534  * information.
    1535  *
    1536  * @since 2.3.0
    1537  *
    1538  * @param string $field    Term field to fetch.
    1539  * @param int    $term     Term ID.
    1540  * @param string $taxonomy Taxonomy Name.
    1541  * @param string $context  Optional, default is display. Look at sanitize_term_field() for available options.
    1542  * @return string|int|null|WP_Error Will return an empty string if $term is not an object or if $field is not set in $term.
    1543  */
    1544 function get_term_field( $field, $term, $taxonomy, $context = 'display' ) {
    1545         $term = (int) $term;
    1546         $term = get_term( $term, $taxonomy );
    1547         if ( is_wp_error($term) )
    1548                 return $term;
    1549 
    1550         if ( !is_object($term) )
    1551                 return '';
    1552 
    1553         if ( !isset($term->$field) )
    1554                 return '';
    1555 
    1556         return sanitize_term_field($field, $term->$field, $term->term_id, $taxonomy, $context);
    1557 }
    1558 
    1559 /**
    1560  * Sanitizes Term for editing.
    1561  *
    1562  * Return value is sanitize_term() and usage is for sanitizing the term for
    1563  * editing. Function is for contextual and simplicity.
    1564  *
    1565  * @since 2.3.0
    1566  *
    1567  * @param int|object $id       Term ID or object.
    1568  * @param string     $taxonomy Taxonomy name.
    1569  * @return string|int|null|WP_Error Will return empty string if $term is not an object.
    1570  */
    1571 function get_term_to_edit( $id, $taxonomy ) {
    1572         $term = get_term( $id, $taxonomy );
    1573 
    1574         if ( is_wp_error($term) )
    1575                 return $term;
    1576 
    1577         if ( !is_object($term) )
    1578                 return '';
    1579 
    1580         return sanitize_term($term, $taxonomy, 'edit');
    1581 }
    1582 
    1583 /**
    1584  * Retrieve the terms in a given taxonomy or list of taxonomies.
    1585  *
    1586  * You can fully inject any customizations to the query before it is sent, as
    1587  * well as control the output with a filter.
    1588  *
    1589  * The {@see 'get_terms'} filter will be called when the cache has the term and will
    1590  * pass the found term along with the array of $taxonomies and array of $args.
    1591  * This filter is also called before the array of terms is passed and will pass
    1592  * the array of terms, along with the $taxonomies and $args.
    1593  *
    1594  * The {@see 'list_terms_exclusions'} filter passes the compiled exclusions along with
    1595  * the $args.
    1596  *
    1597  * The {@see 'get_terms_orderby'} filter passes the `ORDER BY` clause for the query
    1598  * along with the $args array.
    1599  *
    1600  * @since 2.3.0
    1601  * @since 4.2.0 Introduced 'name' and 'childless' parameters.
    1602  *
    1603  * @global wpdb  $wpdb WordPress database abstraction object.
    1604  * @global array $wp_filter
    1605  *
    1606  * @param string|array $taxonomies Taxonomy name or list of Taxonomy names.
    1607  * @param array|string $args {
    1608  *     Optional. Array or string of arguments to get terms.
    1609  *
    1610  *     @type string       $orderby           Field(s) to order terms by. Accepts term fields ('name', 'slug',
    1611  *                                           'term_group', 'term_id', 'id', 'description'), 'count' for term
    1612  *                                           taxonomy count, 'include' to match the 'order' of the $include param,
    1613  *                                           or 'none' to skip ORDER BY. Defaults to 'name'.
    1614  *     @type string       $order             Whether to order terms in ascending or descending order.
    1615  *                                           Accepts 'ASC' (ascending) or 'DESC' (descending).
    1616  *                                           Default 'ASC'.
    1617  *     @type bool|int     $hide_empty        Whether to hide terms not assigned to any posts. Accepts
    1618  *                                           1|true or 0|false. Default 1|true.
    1619  *     @type array|string $include           Array or comma/space-separated string of term ids to include.
    1620  *                                           Default empty array.
    1621  *     @type array|string $exclude           Array or comma/space-separated string of term ids to exclude.
    1622  *                                           If $include is non-empty, $exclude is ignored.
    1623  *                                           Default empty array.
    1624  *     @type array|string $exclude_tree      Array or comma/space-separated string of term ids to exclude
    1625  *                                           along with all of their descendant terms. If $include is
    1626  *                                           non-empty, $exclude_tree is ignored. Default empty array.
    1627  *     @type int|string   $number            Maximum number of terms to return. Accepts ''|0 (all) or any
    1628  *                                           positive number. Default ''|0 (all).
    1629  *     @type int          $offset            The number by which to offset the terms query. Default empty.
    1630  *     @type string       $fields            Term fields to query for. Accepts 'all' (returns an array of complete
    1631  *                                           term objects), 'ids' (returns an array of ids), 'id=>parent' (returns
    1632  *                                           an associative array with ids as keys, parent term IDs as values),
    1633  *                                           'names' (returns an array of term names), 'count' (returns the number
    1634  *                                           of matching terms), 'id=>name' (returns an associative array with ids
    1635  *                                           as keys, term names as values), or 'id=>slug' (returns an associative
    1636  *                                           array with ids as keys, term slugs as values). Default 'all'.
    1637  *     @type string|array $name              Optional. Name or array of names to return term(s) for. Default empty.
    1638  *     @type string|array $slug              Optional. Slug or array of slugs to return term(s) for. Default empty.
    1639  *     @type bool         $hierarchical      Whether to include terms that have non-empty descendants (even
    1640  *                                           if $hide_empty is set to true). Default true.
    1641  *     @type string       $search            Search criteria to match terms. Will be SQL-formatted with
    1642  *                                           wildcards before and after. Default empty.
    1643  *     @type string       $name__like        Retrieve terms with criteria by which a term is LIKE $name__like.
    1644  *                                           Default empty.
    1645  *     @type string       $description__like Retrieve terms where the description is LIKE $description__like.
    1646  *                                           Default empty.
    1647  *     @type bool         $pad_counts        Whether to pad the quantity of a term's children in the quantity
    1648  *                                           of each term's "count" object variable. Default false.
    1649  *     @type string       $get               Whether to return terms regardless of ancestry or whether the terms
    1650  *                                           are empty. Accepts 'all' or empty (disabled). Default empty.
    1651  *     @type int          $child_of          Term ID to retrieve child terms of. If multiple taxonomies
    1652  *                                           are passed, $child_of is ignored. Default 0.
    1653  *     @type int|string   $parent            Parent term ID to retrieve direct-child terms of. Default empty.
    1654  *     @type bool         $childless         True to limit results to terms that have no children. This parameter has
    1655  *                                           no effect on non-hierarchical taxonomies. Default false.
    1656  *     @type string       $cache_domain      Unique cache key to be produced when this query is stored in an
    1657  *                                           object cache. Default is 'core'.
    1658  * }
    1659  * @return array|int|WP_Error List of Term Objects and their children. Will return WP_Error, if any of $taxonomies
    1660  *                        do not exist.
    1661  */
    1662 function get_terms( $taxonomies, $args = '' ) {
    1663         global $wpdb;
    1664         $empty_array = array();
    1665 
    1666         $single_taxonomy = ! is_array( $taxonomies ) || 1 === count( $taxonomies );
    1667         if ( ! is_array( $taxonomies ) ) {
    1668                 $taxonomies = array( $taxonomies );
    1669         }
    1670 
    1671         foreach ( $taxonomies as $taxonomy ) {
    1672                 if ( ! taxonomy_exists($taxonomy) ) {
    1673                         return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy' ) );
    1674                 }
    1675         }
    1676 
    1677         $defaults = array('orderby' => 'name', 'order' => 'ASC',
    1678                 'hide_empty' => true, 'exclude' => array(), 'exclude_tree' => array(), 'include' => array(),
    1679                 'number' => '', 'fields' => 'all', 'name' => '', 'slug' => '', 'parent' => '', 'childless' => false,
    1680                 'hierarchical' => true, 'child_of' => 0, 'get' => '', 'name__like' => '', 'description__like' => '',
    1681                 'pad_counts' => false, 'offset' => '', 'search' => '', 'cache_domain' => 'core' );
    1682         $args = wp_parse_args( $args, $defaults );
    1683         $args['number'] = absint( $args['number'] );
    1684         $args['offset'] = absint( $args['offset'] );
    1685 
    1686         // Save queries by not crawling the tree in the case of multiple taxes or a flat tax.
    1687         $has_hierarchical_tax = false;
    1688         foreach ( $taxonomies as $_tax ) {
    1689                 if ( is_taxonomy_hierarchical( $_tax ) ) {
    1690                         $has_hierarchical_tax = true;
    1691                 }
    1692         }
    1693 
    1694         if ( ! $has_hierarchical_tax ) {
    1695                 $args['hierarchical'] = false;
    1696                 $args['pad_counts'] = false;
    1697         }
    1698 
    1699         // 'parent' overrides 'child_of'.
    1700         if ( 0 < intval( $args['parent'] ) ) {
    1701                 $args['child_of'] = false;
    1702         }
    1703 
    1704         if ( 'all' == $args['get'] ) {
    1705                 $args['childless'] = false;
    1706                 $args['child_of'] = 0;
    1707                 $args['hide_empty'] = 0;
    1708                 $args['hierarchical'] = false;
    1709                 $args['pad_counts'] = false;
    1710         }
    1711 
    1712         /**
    1713          * Filter the terms query arguments.
    1714          *
    1715          * @since 3.1.0
    1716          *
    1717          * @param array $args       An array of get_term() arguments.
    1718          * @param array $taxonomies An array of taxonomies.
    1719          */
    1720         $args = apply_filters( 'get_terms_args', $args, $taxonomies );
    1721 
    1722         // Avoid the query if the queried parent/child_of term has no descendants.
    1723         $child_of = $args['child_of'];
    1724         $parent   = $args['parent'];
    1725 
    1726         if ( $child_of ) {
    1727                 $_parent = $child_of;
    1728         } elseif ( $parent ) {
    1729                 $_parent = $parent;
    1730         } else {
    1731                 $_parent = false;
    1732         }
    1733 
    1734         if ( $_parent ) {
    1735                 $in_hierarchy = false;
    1736                 foreach ( $taxonomies as $_tax ) {
    1737                         $hierarchy = _get_term_hierarchy( $_tax );
    1738 
    1739                         if ( isset( $hierarchy[ $_parent ] ) ) {
    1740                                 $in_hierarchy = true;
    1741                         }
    1742                 }
    1743 
    1744                 if ( ! $in_hierarchy ) {
    1745                         return $empty_array;
    1746                 }
    1747         }
    1748 
    1749         // $args can be whatever, only use the args defined in defaults to compute the key.
    1750         $filter_key = ( has_filter('list_terms_exclusions') ) ? serialize($GLOBALS['wp_filter']['list_terms_exclusions']) : '';
    1751         $key = md5( serialize( wp_array_slice_assoc( $args, array_keys( $defaults ) ) ) . serialize( $taxonomies ) . $filter_key );
    1752         $last_changed = wp_cache_get( 'last_changed', 'terms' );
    1753         if ( ! $last_changed ) {
    1754                 $last_changed = microtime();
    1755                 wp_cache_set( 'last_changed', $last_changed, 'terms' );
    1756         }
    1757         $cache_key = "get_terms:$key:$last_changed";
    1758         $cache = wp_cache_get( $cache_key, 'terms' );
    1759         if ( false !== $cache ) {
    1760 
    1761                 /**
    1762                  * Filter the given taxonomy's terms cache.
    1763                  *
    1764                  * @since 2.3.0
    1765                  *
    1766                  * @param array $cache      Cached array of terms for the given taxonomy.
    1767                  * @param array $taxonomies An array of taxonomies.
    1768                  * @param array $args       An array of get_terms() arguments.
    1769                  */
    1770                 return apply_filters( 'get_terms', $cache, $taxonomies, $args );
    1771         }
    1772 
    1773         $_orderby = strtolower( $args['orderby'] );
    1774         if ( 'count' == $_orderby ) {
    1775                 $orderby = 'tt.count';
    1776         } elseif ( 'name' == $_orderby ) {
    1777                 $orderby = 't.name';
    1778         } elseif ( 'slug' == $_orderby ) {
    1779                 $orderby = 't.slug';
    1780         } elseif ( 'include' == $_orderby && ! empty( $args['include'] ) ) {
    1781                 $include = implode( ',', array_map( 'absint', $args['include'] ) );
    1782                 $orderby = "FIELD( t.term_id, $include )";
    1783         } elseif ( 'term_group' == $_orderby ) {
    1784                 $orderby = 't.term_group';
    1785         } elseif ( 'description' == $_orderby ) {
    1786                 $orderby = 'tt.description';
    1787         } elseif ( 'none' == $_orderby ) {
    1788                 $orderby = '';
    1789         } elseif ( empty($_orderby) || 'id' == $_orderby ) {
    1790                 $orderby = 't.term_id';
    1791         } else {
    1792                 $orderby = 't.name';
    1793         }
    1794 
    1795         /**
    1796          * Filter the ORDERBY clause of the terms query.
    1797          *
    1798          * @since 2.8.0
    1799          *
    1800          * @param string $orderby    `ORDERBY` clause of the terms query.
    1801          * @param array  $args       An array of terms query arguments.
    1802          * @param array  $taxonomies An array of taxonomies.
    1803          */
    1804         $orderby = apply_filters( 'get_terms_orderby', $orderby, $args, $taxonomies );
    1805 
    1806         $order = strtoupper( $args['order'] );
    1807         if ( ! empty( $orderby ) ) {
    1808                 $orderby = "ORDER BY $orderby";
    1809         } else {
    1810                 $order = '';
    1811         }
    1812 
    1813         if ( '' !== $order && ! in_array( $order, array( 'ASC', 'DESC' ) ) ) {
    1814                 $order = 'ASC';
    1815         }
    1816 
    1817         $where = "tt.taxonomy IN ('" . implode("', '", $taxonomies) . "')";
    1818 
    1819         $exclude = $args['exclude'];
    1820         $exclude_tree = $args['exclude_tree'];
    1821         $include = $args['include'];
    1822 
    1823         $inclusions = '';
    1824         if ( ! empty( $include ) ) {
    1825                 $exclude = '';
    1826                 $exclude_tree = '';
    1827                 $inclusions = implode( ',', wp_parse_id_list( $include ) );
    1828         }
    1829 
    1830         if ( ! empty( $inclusions ) ) {
    1831                 $inclusions = ' AND t.term_id IN ( ' . $inclusions . ' )';
    1832                 $where .= $inclusions;
    1833         }
    1834 
    1835         $exclusions = array();
    1836         if ( ! empty( $exclude_tree ) ) {
    1837                 $exclude_tree = wp_parse_id_list( $exclude_tree );
    1838                 $excluded_children = $exclude_tree;
    1839                 foreach ( $exclude_tree as $extrunk ) {
    1840                         $excluded_children = array_merge(
    1841                                 $excluded_children,
    1842                                 (array) get_terms( $taxonomies[0], array( 'child_of' => intval( $extrunk ), 'fields' => 'ids', 'hide_empty' => 0 ) )
    1843                         );
    1844                 }
    1845                 $exclusions = array_merge( $excluded_children, $exclusions );
    1846         }
    1847 
    1848         if ( ! empty( $exclude ) ) {
    1849                 $exclusions = array_merge( wp_parse_id_list( $exclude ), $exclusions );
    1850         }
    1851 
    1852         // 'childless' terms are those without an entry in the flattened term hierarchy.
    1853         $childless = (bool) $args['childless'];
    1854         if ( $childless ) {
    1855                 foreach ( $taxonomies as $_tax ) {
    1856                         $term_hierarchy = _get_term_hierarchy( $_tax );
    1857                         $exclusions = array_merge( array_keys( $term_hierarchy ), $exclusions );
    1858                 }
    1859         }
    1860 
    1861         if ( ! empty( $exclusions ) ) {
    1862                 $exclusions = ' AND t.term_id NOT IN (' . implode( ',', array_map( 'intval', $exclusions ) ) . ')';
    1863         } else {
    1864                 $exclusions = '';
    1865         }
    1866 
    1867         /**
    1868          * Filter the terms to exclude from the terms query.
    1869          *
    1870          * @since 2.3.0
    1871          *
    1872          * @param string $exclusions `NOT IN` clause of the terms query.
    1873          * @param array  $args       An array of terms query arguments.
    1874          * @param array  $taxonomies An array of taxonomies.
    1875          */
    1876         $exclusions = apply_filters( 'list_terms_exclusions', $exclusions, $args, $taxonomies );
    1877 
    1878         if ( ! empty( $exclusions ) ) {
    1879                 $where .= $exclusions;
    1880         }
    1881 
    1882         if ( ! empty( $args['name'] ) ) {
    1883                 $names = (array) $args['name'];
    1884                 foreach ( $names as &$_name ) {
    1885                         $_name = sanitize_term_field( 'name', $_name, 0, reset( $taxonomies ), 'db' );
    1886                 }
    1887 
    1888                 $where .= " AND t.name IN ('" . implode( "', '", array_map( 'esc_sql', $names ) ) . "')";
    1889         }
    1890 
    1891         if ( ! empty( $args['slug'] ) ) {
    1892                 if ( is_array( $args['slug'] ) ) {
    1893                         $slug = array_map( 'sanitize_title', $args['slug'] );
    1894                         $where .= " AND t.slug IN ('" . implode( "', '", $slug ) . "')";
    1895                 } else {
    1896                         $slug = sanitize_title( $args['slug'] );
    1897                         $where .= " AND t.slug = '$slug'";
    1898                 }
    1899         }
    1900 
    1901         if ( ! empty( $args['name__like'] ) ) {
    1902                 $where .= $wpdb->prepare( " AND t.name LIKE %s", '%' . $wpdb->esc_like( $args['name__like'] ) . '%' );
    1903         }
    1904 
    1905         if ( ! empty( $args['description__like'] ) ) {
    1906                 $where .= $wpdb->prepare( " AND tt.description LIKE %s", '%' . $wpdb->esc_like( $args['description__like'] ) . '%' );
    1907         }
    1908 
    1909         if ( '' !== $parent ) {
    1910                 $parent = (int) $parent;
    1911                 $where .= " AND tt.parent = '$parent'";
    1912         }
    1913 
    1914         $hierarchical = $args['hierarchical'];
    1915         if ( 'count' == $args['fields'] ) {
    1916                 $hierarchical = false;
    1917         }
    1918         if ( $args['hide_empty'] && !$hierarchical ) {
    1919                 $where .= ' AND tt.count > 0';
    1920         }
    1921 
    1922         $number = $args['number'];
    1923         $offset = $args['offset'];
    1924 
    1925         // Don't limit the query results when we have to descend the family tree.
    1926         if ( $number && ! $hierarchical && ! $child_of && '' === $parent ) {
    1927                 if ( $offset ) {
    1928                         $limits = 'LIMIT ' . $offset . ',' . $number;
    1929                 } else {
    1930                         $limits = 'LIMIT ' . $number;
    1931                 }
    1932         } else {
    1933                 $limits = '';
    1934         }
    1935 
    1936         if ( ! empty( $args['search'] ) ) {
    1937                 $like = '%' . $wpdb->esc_like( $args['search'] ) . '%';
    1938                 $where .= $wpdb->prepare( ' AND ((t.name LIKE %s) OR (t.slug LIKE %s))', $like, $like );
    1939         }
    1940 
    1941         $selects = array();
    1942         switch ( $args['fields'] ) {
    1943                 case 'all':
    1944                         $selects = array( 't.*', 'tt.*' );
    1945                         break;
    1946                 case 'ids':
    1947                 case 'id=>parent':
    1948                         $selects = array( 't.term_id', 'tt.parent', 'tt.count', 'tt.taxonomy' );
    1949                         break;
    1950                 case 'names':
    1951                         $selects = array( 't.term_id', 'tt.parent', 'tt.count', 't.name', 'tt.taxonomy' );
    1952                         break;
    1953                 case 'count':
    1954                         $orderby = '';
    1955                         $order = '';
    1956                         $selects = array( 'COUNT(*)' );
    1957                         break;
    1958                 case 'id=>name':
    1959                         $selects = array( 't.term_id', 't.name', 'tt.count', 'tt.taxonomy' );
    1960                         break;
    1961                 case 'id=>slug':
    1962                         $selects = array( 't.term_id', 't.slug', 'tt.count', 'tt.taxonomy' );
    1963                         break;
    1964         }
    1965 
    1966         $_fields = $args['fields'];
    1967 
    1968         /**
    1969          * Filter the fields to select in the terms query.
    1970          *
    1971          * Field lists modified using this filter will only modify the term fields returned
    1972          * by the function when the `$fields` parameter set to 'count' or 'all'. In all other
    1973          * cases, the term fields in the results array will be determined by the `$fields`
    1974          * parameter alone.
    1975          *
    1976          * Use of this filter can result in unpredictable behavior, and is not recommended.
    1977          *
    1978          * @since 2.8.0
    1979          *
    1980          * @param array $selects    An array of fields to select for the terms query.
    1981          * @param array $args       An array of term query arguments.
    1982          * @param array $taxonomies An array of taxonomies.
    1983          */
    1984         $fields = implode( ', ', apply_filters( 'get_terms_fields', $selects, $args, $taxonomies ) );
    1985 
    1986         $join = "INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id";
    1987 
    1988         $pieces = array( 'fields', 'join', 'where', 'orderby', 'order', 'limits' );
    1989 
    1990         /**
    1991          * Filter the terms query SQL clauses.
    1992          *
    1993          * @since 3.1.0
    1994          *
    1995          * @param array $pieces     Terms query SQL clauses.
    1996          * @param array $taxonomies An array of taxonomies.
    1997          * @param array $args       An array of terms query arguments.
    1998          */
    1999         $clauses = apply_filters( 'terms_clauses', compact( $pieces ), $taxonomies, $args );
    2000 
    2001         $fields = isset( $clauses[ 'fields' ] ) ? $clauses[ 'fields' ] : '';
    2002         $join = isset( $clauses[ 'join' ] ) ? $clauses[ 'join' ] : '';
    2003         $where = isset( $clauses[ 'where' ] ) ? $clauses[ 'where' ] : '';
    2004         $orderby = isset( $clauses[ 'orderby' ] ) ? $clauses[ 'orderby' ] : '';
    2005         $order = isset( $clauses[ 'order' ] ) ? $clauses[ 'order' ] : '';
    2006         $limits = isset( $clauses[ 'limits' ] ) ? $clauses[ 'limits' ] : '';
    2007 
    2008         $query = "SELECT $fields FROM $wpdb->terms AS t $join WHERE $where $orderby $order $limits";
    2009 
    2010         if ( 'count' == $_fields ) {
    2011                 return $wpdb->get_var( $query );
    2012         }
    2013 
    2014         $terms = $wpdb->get_results($query);
    2015         if ( 'all' == $_fields ) {
    2016                 update_term_cache( $terms );
    2017         }
    2018 
    2019         if ( empty($terms) ) {
    2020                 wp_cache_add( $cache_key, array(), 'terms', DAY_IN_SECONDS );
    2021 
    2022                 /** This filter is documented in wp-includes/taxonomy.php */
    2023                 return apply_filters( 'get_terms', array(), $taxonomies, $args );
    2024         }
    2025 
    2026         if ( $child_of ) {
    2027                 foreach ( $taxonomies as $_tax ) {
    2028                         $children = _get_term_hierarchy( $_tax );
    2029                         if ( ! empty( $children ) ) {
    2030                                 $terms = _get_term_children( $child_of, $terms, $_tax );
    2031                         }
    2032                 }
    2033         }
    2034 
    2035         // Update term counts to include children.
    2036         if ( $args['pad_counts'] && 'all' == $_fields ) {
    2037                 foreach ( $taxonomies as $_tax ) {
    2038                         _pad_term_counts( $terms, $_tax );
    2039                 }
    2040         }
    2041 
    2042         // Make sure we show empty categories that have children.
    2043         if ( $hierarchical && $args['hide_empty'] && is_array( $terms ) ) {
    2044                 foreach ( $terms as $k => $term ) {
    2045                         if ( ! $term->count ) {
    2046                                 $children = get_term_children( $term->term_id, $term->taxonomy );
    2047                                 if ( is_array( $children ) ) {
    2048                                         foreach ( $children as $child_id ) {
    2049                                                 $child = get_term( $child_id, $term->taxonomy );
    2050                                                 if ( $child->count ) {
    2051                                                         continue 2;
    2052                                                 }
    2053                                         }
    2054                                 }
    2055 
    2056                                 // It really is empty.
    2057                                 unset($terms[$k]);
    2058                         }
    2059                 }
    2060         }
    2061 
    2062         $_terms = array();
    2063         if ( 'id=>parent' == $_fields ) {
    2064                 foreach ( $terms as $term ) {
    2065                         $_terms[ $term->term_id ] = $term->parent;
    2066                 }
    2067         } elseif ( 'ids' == $_fields ) {
    2068                 foreach ( $terms as $term ) {
    2069                         $_terms[] = $term->term_id;
    2070                 }
    2071         } elseif ( 'names' == $_fields ) {
    2072                 foreach ( $terms as $term ) {
    2073                         $_terms[] = $term->name;
    2074                 }
    2075         } elseif ( 'id=>name' == $_fields ) {
    2076                 foreach ( $terms as $term ) {
    2077                         $_terms[ $term->term_id ] = $term->name;
    2078                 }
    2079         } elseif ( 'id=>slug' == $_fields ) {
    2080                 foreach ( $terms as $term ) {
    2081                         $_terms[ $term->term_id ] = $term->slug;
    2082                 }
    2083         }
    2084 
    2085         if ( ! empty( $_terms ) ) {
    2086                 $terms = $_terms;
    2087         }
    2088 
    2089         if ( $number && is_array( $terms ) && count( $terms ) > $number ) {
    2090                 $terms = array_slice( $terms, $offset, $number );
    2091         }
    2092 
    2093         wp_cache_add( $cache_key, $terms, 'terms', DAY_IN_SECONDS );
    2094 
    2095         /** This filter is documented in wp-includes/taxonomy */
    2096         return apply_filters( 'get_terms', $terms, $taxonomies, $args );
    2097 }
    2098 
    2099 /**
    2100  * Check if Term exists.
    2101  *
    2102  * Formerly is_term(), introduced in 2.3.0.
    2103  *
    2104  * @since 3.0.0
    2105  *
    2106  * @global wpdb $wpdb WordPress database abstraction object.
    2107  *
    2108  * @param int|string $term     The term to check
    2109  * @param string     $taxonomy The taxonomy name to use
    2110  * @param int        $parent   Optional. ID of parent term under which to confine the exists search.
    2111  * @return mixed Returns null if the term does not exist. Returns the term ID
    2112  *               if no taxonomy is specified and the term ID exists. Returns
    2113  *               an array of the term ID and the term taxonomy ID the taxonomy
    2114  *               is specified and the pairing exists.
    2115  */
    2116 function term_exists( $term, $taxonomy = '', $parent = null ) {
    2117         global $wpdb;
    2118 
    2119         $select = "SELECT term_id FROM $wpdb->terms as t WHERE ";
    2120         $tax_select = "SELECT tt.term_id, tt.term_taxonomy_id FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy as tt ON tt.term_id = t.term_id WHERE ";
    2121 
    2122         if ( is_int($term) ) {
    2123                 if ( 0 == $term )
    2124                         return 0;
    2125                 $where = 't.term_id = %d';
    2126                 if ( !empty($taxonomy) )
    2127                         return $wpdb->get_row( $wpdb->prepare( $tax_select . $where . " AND tt.taxonomy = %s", $term, $taxonomy ), ARRAY_A );
    2128                 else
    2129                         return $wpdb->get_var( $wpdb->prepare( $select . $where, $term ) );
    2130         }
    2131 
    2132         $term = trim( wp_unslash( $term ) );
    2133         $slug = sanitize_title( $term );
    2134 
    2135         $where = 't.slug = %s';
    2136         $else_where = 't.name = %s';
    2137         $where_fields = array($slug);
    2138         $else_where_fields = array($term);
    2139         $orderby = 'ORDER BY t.term_id ASC';
    2140         $limit = 'LIMIT 1';
    2141         if ( !empty($taxonomy) ) {
    2142                 if ( is_numeric( $parent ) ) {
    2143                         $parent = (int) $parent;
    2144                         $where_fields[] = $parent;
    2145                         $else_where_fields[] = $parent;
    2146                         $where .= ' AND tt.parent = %d';
    2147                         $else_where .= ' AND tt.parent = %d';
    2148                 }
    2149 
    2150                 $where_fields[] = $taxonomy;
    2151                 $else_where_fields[] = $taxonomy;
    2152 
    2153                 if ( $result = $wpdb->get_row( $wpdb->prepare("SELECT tt.term_id, tt.term_taxonomy_id FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy as tt ON tt.term_id = t.term_id WHERE $where AND tt.taxonomy = %s $orderby $limit", $where_fields), ARRAY_A) )
    2154                         return $result;
    2155 
    2156                 return $wpdb->get_row( $wpdb->prepare("SELECT tt.term_id, tt.term_taxonomy_id FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy as tt ON tt.term_id = t.term_id WHERE $else_where AND tt.taxonomy = %s $orderby $limit", $else_where_fields), ARRAY_A);
    2157         }
    2158 
    2159         if ( $result = $wpdb->get_var( $wpdb->prepare("SELECT term_id FROM $wpdb->terms as t WHERE $where $orderby $limit", $where_fields) ) )
    2160                 return $result;
    2161 
    2162         return $wpdb->get_var( $wpdb->prepare("SELECT term_id FROM $wpdb->terms as t WHERE $else_where $orderby $limit", $else_where_fields) );
    2163 }
    2164 
    2165 /**
    2166  * Check if a term is an ancestor of another term.
    2167  *
    2168  * You can use either an id or the term object for both parameters.
    2169  *
    2170  * @since 3.4.0
    2171  *
    2172  * @param int|object $term1    ID or object to check if this is the parent term.
    2173  * @param int|object $term2    The child term.
    2174  * @param string     $taxonomy Taxonomy name that $term1 and `$term2` belong to.
    2175  * @return bool Whether `$term2` is a child of `$term1`.
    2176  */
    2177 function term_is_ancestor_of( $term1, $term2, $taxonomy ) {
    2178         if ( ! isset( $term1->term_id ) )
    2179                 $term1 = get_term( $term1, $taxonomy );
    2180         if ( ! isset( $term2->parent ) )
    2181                 $term2 = get_term( $term2, $taxonomy );
    2182 
    2183         if ( empty( $term1->term_id ) || empty( $term2->parent ) )
    2184                 return false;
    2185         if ( $term2->parent == $term1->term_id )
    2186                 return true;
    2187 
    2188         return term_is_ancestor_of( $term1, get_term( $term2->parent, $taxonomy ), $taxonomy );
    2189 }
    2190 
    2191 /**
    2192  * Sanitize Term all fields.
    2193  *
    2194  * Relies on sanitize_term_field() to sanitize the term. The difference is that
    2195  * this function will sanitize <strong>all</strong> fields. The context is based
    2196  * on sanitize_term_field().
    2197  *
    2198  * The $term is expected to be either an array or an object.
    2199  *
    2200  * @since 2.3.0
    2201  *
    2202  * @param array|object $term     The term to check.
    2203  * @param string       $taxonomy The taxonomy name to use.
    2204  * @param string       $context  Optional. Context in which to sanitize the term. Accepts 'edit', 'db',
    2205  *                               'display', 'attribute', or 'js'. Default 'display'.
    2206  * @return array|object Term with all fields sanitized.
    2207  */
    2208 function sanitize_term($term, $taxonomy, $context = 'display') {
    2209         $fields = array( 'term_id', 'name', 'description', 'slug', 'count', 'parent', 'term_group', 'term_taxonomy_id', 'object_id' );
    2210 
    2211         $do_object = is_object( $term );
    2212 
    2213         $term_id = $do_object ? $term->term_id : (isset($term['term_id']) ? $term['term_id'] : 0);
    2214 
    2215         foreach ( (array) $fields as $field ) {
    2216                 if ( $do_object ) {
    2217                         if ( isset($term->$field) )
    2218                                 $term->$field = sanitize_term_field($field, $term->$field, $term_id, $taxonomy, $context);
    2219                 } else {
    2220                         if ( isset($term[$field]) )
    2221                                 $term[$field] = sanitize_term_field($field, $term[$field], $term_id, $taxonomy, $context);
    2222                 }
    2223         }
    2224 
    2225         if ( $do_object )
    2226                 $term->filter = $context;
    2227         else
    2228                 $term['filter'] = $context;
    2229 
    2230         return $term;
    2231 }
    2232 
    2233 /**
    2234  * Cleanse the field value in the term based on the context.
    2235  *
    2236  * Passing a term field value through the function should be assumed to have
    2237  * cleansed the value for whatever context the term field is going to be used.
    2238  *
    2239  * If no context or an unsupported context is given, then default filters will
    2240  * be applied.
    2241  *
    2242  * There are enough filters for each context to support a custom filtering
    2243  * without creating your own filter function. Simply create a function that
    2244  * hooks into the filter you need.
    2245  *
    2246  * @since 2.3.0
    2247  *
    2248  * @param string $field    Term field to sanitize.
    2249  * @param string $value    Search for this term value.
    2250  * @param int    $term_id  Term ID.
    2251  * @param string $taxonomy Taxonomy Name.
    2252  * @param string $context  Context in which to sanitize the term field. Accepts 'edit', 'db', 'display',
    2253  *                         'attribute', or 'js'.
    2254  * @return mixed Sanitized field.
    2255  */
    2256 function sanitize_term_field($field, $value, $term_id, $taxonomy, $context) {
    2257         $int_fields = array( 'parent', 'term_id', 'count', 'term_group', 'term_taxonomy_id', 'object_id' );
    2258         if ( in_array( $field, $int_fields ) ) {
    2259                 $value = (int) $value;
    2260                 if ( $value < 0 )
    2261                         $value = 0;
    2262         }
    2263 
    2264         if ( 'raw' == $context )
    2265                 return $value;
    2266 
    2267         if ( 'edit' == $context ) {
    2268 
    2269                 /**
    2270                  * Filter a term field to edit before it is sanitized.
    2271                  *
    2272                  * The dynamic portion of the filter name, `$field`, refers to the term field.
    2273                  *
    2274                  * @since 2.3.0
    2275                  *
    2276                  * @param mixed $value     Value of the term field.
    2277                  * @param int   $term_id   Term ID.
    2278                  * @param string $taxonomy Taxonomy slug.
    2279                  */
    2280                 $value = apply_filters( "edit_term_{$field}", $value, $term_id, $taxonomy );
    2281 
    2282                 /**
    2283                  * Filter the taxonomy field to edit before it is sanitized.
    2284                  *
    2285                  * The dynamic portions of the filter name, `$taxonomy` and `$field`, refer
    2286                  * to the taxonomy slug and taxonomy field, respectively.
    2287                  *
    2288                  * @since 2.3.0
    2289                  *
    2290                  * @param mixed $value   Value of the taxonomy field to edit.
    2291                  * @param int   $term_id Term ID.
    2292                  */
    2293                 $value = apply_filters( "edit_{$taxonomy}_{$field}", $value, $term_id );
    2294 
    2295                 if ( 'description' == $field )
    2296                         $value = esc_html($value); // textarea_escaped
    2297                 else
    2298                         $value = esc_attr($value);
    2299         } elseif ( 'db' == $context ) {
    2300 
    2301                 /**
    2302                  * Filter a term field value before it is sanitized.
    2303                  *
    2304                  * The dynamic portion of the filter name, `$field`, refers to the term field.
    2305                  *
    2306                  * @since 2.3.0
    2307                  *
    2308                  * @param mixed  $value    Value of the term field.
    2309                  * @param string $taxonomy Taxonomy slug.
    2310                  */
    2311                 $value = apply_filters( "pre_term_{$field}", $value, $taxonomy );
    2312 
    2313                 /**
    2314                  * Filter a taxonomy field before it is sanitized.
    2315                  *
    2316                  * The dynamic portions of the filter name, `$taxonomy` and `$field`, refer
    2317                  * to the taxonomy slug and field name, respectively.
    2318                  *
    2319                  * @since 2.3.0
    2320                  *
    2321                  * @param mixed $value Value of the taxonomy field.
    2322                  */
    2323                 $value = apply_filters( "pre_{$taxonomy}_{$field}", $value );
    2324 
    2325                 // Back compat filters
    2326                 if ( 'slug' == $field ) {
    2327                         /**
    2328                          * Filter the category nicename before it is sanitized.
    2329                          *
    2330                          * Use the pre_{$taxonomy}_{$field} hook instead.
    2331                          *
    2332                          * @since 2.0.3
    2333                          *
    2334                          * @param string $value The category nicename.
    2335                          */
    2336                         $value = apply_filters( 'pre_category_nicename', $value );
    2337                 }
    2338 
    2339         } elseif ( 'rss' == $context ) {
    2340 
    2341                 /**
    2342                  * Filter the term field for use in RSS.
    2343                  *
    2344                  * The dynamic portion of the filter name, `$field`, refers to the term field.
    2345                  *
    2346                  * @since 2.3.0
    2347                  *
    2348                  * @param mixed  $value    Value of the term field.
    2349                  * @param string $taxonomy Taxonomy slug.
    2350                  */
    2351                 $value = apply_filters( "term_{$field}_rss", $value, $taxonomy );
    2352 
    2353                 /**
    2354                  * Filter the taxonomy field for use in RSS.
    2355                  *
    2356                  * The dynamic portions of the hook name, `$taxonomy`, and `$field`, refer
    2357                  * to the taxonomy slug and field name, respectively.
    2358                  *
    2359                  * @since 2.3.0
    2360                  *
    2361                  * @param mixed $value Value of the taxonomy field.
    2362                  */
    2363                 $value = apply_filters( "{$taxonomy}_{$field}_rss", $value );
    2364         } else {
    2365                 // Use display filters by default.
    2366 
    2367                 /**
    2368                  * Filter the term field sanitized for display.
    2369                  *
    2370                  * The dynamic portion of the filter name, `$field`, refers to the term field name.
    2371                  *
    2372                  * @since 2.3.0
    2373                  *
    2374                  * @param mixed  $value    Value of the term field.
    2375                  * @param int    $term_id  Term ID.
    2376                  * @param string $taxonomy Taxonomy slug.
    2377                  * @param string $context  Context to retrieve the term field value.
    2378                  */
    2379                 $value = apply_filters( "term_{$field}", $value, $term_id, $taxonomy, $context );
    2380 
    2381                 /**
    2382                  * Filter the taxonomy field sanitized for display.
    2383                  *
    2384                  * The dynamic portions of the filter name, `$taxonomy`, and `$field`, refer
    2385                  * to the taxonomy slug and taxonomy field, respectively.
    2386                  *
    2387                  * @since 2.3.0
    2388                  *
    2389                  * @param mixed  $value   Value of the taxonomy field.
    2390                  * @param int    $term_id Term ID.
    2391                  * @param string $context Context to retrieve the taxonomy field value.
    2392                  */
    2393                 $value = apply_filters( "{$taxonomy}_{$field}", $value, $term_id, $context );
    2394         }
    2395 
    2396         if ( 'attribute' == $context ) {
    2397                 $value = esc_attr($value);
    2398         } elseif ( 'js' == $context ) {
    2399                 $value = esc_js($value);
    2400         }
    2401         return $value;
    2402 }
    2403 
    2404 /**
    2405  * Count how many terms are in Taxonomy.
    2406  *
    2407  * Default $args is 'hide_empty' which can be 'hide_empty=true' or array('hide_empty' => true).
    2408  *
    2409  * @todo Document $args as a hash notation.
    2410  *
    2411  * @since 2.3.0
    2412  *
    2413  * @param string       $taxonomy Taxonomy name
    2414  * @param array|string $args     Overwrite defaults. See get_terms()
    2415  * @return array|int|WP_Error How many terms are in $taxonomy. WP_Error if $taxonomy does not exist.
    2416  */
    2417 function wp_count_terms( $taxonomy, $args = array() ) {
    2418         $defaults = array('hide_empty' => false);
    2419         $args = wp_parse_args($args, $defaults);
    2420 
    2421         // backwards compatibility
    2422         if ( isset($args['ignore_empty']) ) {
    2423                 $args['hide_empty'] = $args['ignore_empty'];
    2424                 unset($args['ignore_empty']);
    2425         }
    2426 
    2427         $args['fields'] = 'count';
    2428 
    2429         return get_terms($taxonomy, $args);
    2430 }
    2431 
    2432 /**
    2433  * Will unlink the object from the taxonomy or taxonomies.
    2434  *
    2435  * Will remove all relationships between the object and any terms in
    2436  * a particular taxonomy or taxonomies. Does not remove the term or
    2437  * taxonomy itself.
    2438  *
    2439  * @since 2.3.0
    2440  *
    2441  * @param int          $object_id  The term Object Id that refers to the term.
    2442  * @param string|array $taxonomies List of Taxonomy Names or single Taxonomy name.
    2443  */
    2444 function wp_delete_object_term_relationships( $object_id, $taxonomies ) {
    2445         $object_id = (int) $object_id;
    2446 
    2447         if ( !is_array($taxonomies) )
    2448                 $taxonomies = array($taxonomies);
    2449 
    2450         foreach ( (array) $taxonomies as $taxonomy ) {
    2451                 $term_ids = wp_get_object_terms( $object_id, $taxonomy, array( 'fields' => 'ids' ) );
    2452                 $term_ids = array_map( 'intval', $term_ids );
    2453                 wp_remove_object_terms( $object_id, $term_ids, $taxonomy );
    2454         }
    2455 }
    2456 
    2457 /**
    2458  * Removes a term from the database.
    2459  *
    2460  * If the term is a parent of other terms, then the children will be updated to
    2461  * that term's parent.
    2462  *
    2463  * The `$args` 'default' will only override the terms found, if there is only one
    2464  * term found. Any other and the found terms are used.
    2465  *
    2466  * The $args 'force_default' will force the term supplied as default to be
    2467  * assigned even if the object was not going to be termless
    2468  *
    2469  * @todo Document $args as a hash notation.
    2470  *
    2471  * @since 2.3.0
    2472  *
    2473  * @global wpdb $wpdb WordPress database abstraction object.
    2474  *
    2475  * @param int          $term     Term ID.
    2476  * @param string       $taxonomy Taxonomy Name.
    2477  * @param array|string $args     Optional. Change 'default' term id and override found term ids.
    2478  * @return bool|int|WP_Error Returns false if not term; true if completes delete action.
    2479  */
    2480 function wp_delete_term( $term, $taxonomy, $args = array() ) {
    2481         global $wpdb;
    2482 
    2483         $term = (int) $term;
    2484 
    2485         if ( ! $ids = term_exists($term, $taxonomy) )
    2486                 return false;
    2487         if ( is_wp_error( $ids ) )
    2488                 return $ids;
    2489 
    2490         $tt_id = $ids['term_taxonomy_id'];
    2491 
    2492         $defaults = array();
    2493 
    2494         if ( 'category' == $taxonomy ) {
    2495                 $defaults['default'] = get_option( 'default_category' );
    2496                 if ( $defaults['default'] == $term )
    2497                         return 0; // Don't delete the default category
    2498         }
    2499 
    2500         $args = wp_parse_args($args, $defaults);
    2501 
    2502         if ( isset( $args['default'] ) ) {
    2503                 $default = (int) $args['default'];
    2504                 if ( ! term_exists( $default, $taxonomy ) ) {
    2505                         unset( $default );
    2506                 }
    2507         }
    2508 
    2509         if ( isset( $args['force_default'] ) ) {
    2510                 $force_default = $args['force_default'];
    2511         }
    2512 
    2513         /**
    2514          * Fires when deleting a term, before any modifications are made to posts or terms.
    2515          *
    2516          * @since 4.1.0
    2517          *
    2518          * @param int    $term     Term ID.
    2519          * @param string $taxonomy Taxonomy Name.
    2520          */
    2521         do_action( 'pre_delete_term', $term, $taxonomy );
    2522 
    2523         // Update children to point to new parent
    2524         if ( is_taxonomy_hierarchical($taxonomy) ) {
    2525                 $term_obj = get_term($term, $taxonomy);
    2526                 if ( is_wp_error( $term_obj ) )
    2527                         return $term_obj;
    2528                 $parent = $term_obj->parent;
    2529 
    2530                 $edit_ids = $wpdb->get_results( "SELECT term_id, term_taxonomy_id FROM $wpdb->term_taxonomy WHERE `parent` = " . (int)$term_obj->term_id );
    2531                 $edit_tt_ids = wp_list_pluck( $edit_ids, 'term_taxonomy_id' );
    2532 
    2533                 /**
    2534                  * Fires immediately before a term to delete's children are reassigned a parent.
    2535                  *
    2536                  * @since 2.9.0
    2537                  *
    2538                  * @param array $edit_tt_ids An array of term taxonomy IDs for the given term.
    2539                  */
    2540                 do_action( 'edit_term_taxonomies', $edit_tt_ids );
    2541 
    2542                 $wpdb->update( $wpdb->term_taxonomy, compact( 'parent' ), array( 'parent' => $term_obj->term_id) + compact( 'taxonomy' ) );
    2543 
    2544                 // Clean the cache for all child terms.
    2545                 $edit_term_ids = wp_list_pluck( $edit_ids, 'term_id' );
    2546                 clean_term_cache( $edit_term_ids, $taxonomy );
    2547 
    2548                 /**
    2549                  * Fires immediately after a term to delete's children are reassigned a parent.
    2550                  *
    2551                  * @since 2.9.0
    2552                  *
    2553                  * @param array $edit_tt_ids An array of term taxonomy IDs for the given term.
    2554                  */
    2555                 do_action( 'edited_term_taxonomies', $edit_tt_ids );
    2556         }
    2557 
    2558         // Get the term before deleting it or its term relationships so we can pass to actions below.
    2559         $deleted_term = get_term( $term, $taxonomy );
    2560 
    2561         $objects = $wpdb->get_col( $wpdb->prepare( "SELECT object_id FROM $wpdb->term_relationships WHERE term_taxonomy_id = %d", $tt_id ) );
    2562 
    2563         foreach ( (array) $objects as $object ) {
    2564                 $terms = wp_get_object_terms($object, $taxonomy, array('fields' => 'ids', 'orderby' => 'none'));
    2565                 if ( 1 == count($terms) && isset($default) ) {
    2566                         $terms = array($default);
    2567                 } else {
    2568                         $terms = array_diff($terms, array($term));
    2569                         if (isset($default) && isset($force_default) && $force_default)
    2570                                 $terms = array_merge($terms, array($default));
    2571                 }
    2572                 $terms = array_map('intval', $terms);
    2573                 wp_set_object_terms($object, $terms, $taxonomy);
    2574         }
    2575 
    2576         // Clean the relationship caches for all object types using this term.
    2577         $tax_object = get_taxonomy( $taxonomy );
    2578         foreach ( $tax_object->object_type as $object_type )
    2579                 clean_object_term_cache( $objects, $object_type );
    2580 
    2581         /**
    2582          * Fires immediately before a term taxonomy ID is deleted.
    2583          *
    2584          * @since 2.9.0
    2585          *
    2586          * @param int $tt_id Term taxonomy ID.
    2587          */
    2588         do_action( 'delete_term_taxonomy', $tt_id );
    2589         $wpdb->delete( $wpdb->term_taxonomy, array( 'term_taxonomy_id' => $tt_id ) );
    2590 
    2591         /**
    2592          * Fires immediately after a term taxonomy ID is deleted.
    2593          *
    2594          * @since 2.9.0
    2595          *
    2596          * @param int $tt_id Term taxonomy ID.
    2597          */
    2598         do_action( 'deleted_term_taxonomy', $tt_id );
    2599 
    2600         // Delete the term if no taxonomies use it.
    2601         if ( !$wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_taxonomy WHERE term_id = %d", $term) ) )
    2602                 $wpdb->delete( $wpdb->terms, array( 'term_id' => $term ) );
    2603 
    2604         clean_term_cache($term, $taxonomy);
    2605 
    2606         /**
    2607          * Fires after a term is deleted from the database and the cache is cleaned.
    2608          *
    2609          * @since 2.5.0
    2610          *
    2611          * @param int     $term         Term ID.
    2612          * @param int     $tt_id        Term taxonomy ID.
    2613          * @param string  $taxonomy     Taxonomy slug.
    2614          * @param mixed   $deleted_term Copy of the already-deleted term, in the form specified
    2615          *                              by the parent function. WP_Error otherwise.
    2616          */
    2617         do_action( 'delete_term', $term, $tt_id, $taxonomy, $deleted_term );
    2618 
    2619         /**
    2620          * Fires after a term in a specific taxonomy is deleted.
    2621          *
    2622          * The dynamic portion of the hook name, `$taxonomy`, refers to the specific
    2623          * taxonomy the term belonged to.
    2624          *
    2625          * @since 2.3.0
    2626          *
    2627          * @param int     $term         Term ID.
    2628          * @param int     $tt_id        Term taxonomy ID.
    2629          * @param mixed   $deleted_term Copy of the already-deleted term, in the form specified
    2630          *                              by the parent function. WP_Error otherwise.
    2631          */
    2632         do_action( "delete_$taxonomy", $term, $tt_id, $deleted_term );
    2633 
    2634         return true;
    2635 }
    2636 
    2637 /**
    2638  * Deletes one existing category.
    2639  *
    2640  * @since 2.0.0
    2641  *
    2642  * @param int $cat_ID
    2643  * @return bool|int|WP_Error Returns true if completes delete action; false if term doesn't exist;
    2644  *      Zero on attempted deletion of default Category; WP_Error object is also a possibility.
    2645  */
    2646 function wp_delete_category( $cat_ID ) {
    2647         return wp_delete_term( $cat_ID, 'category' );
    2648 }
    2649 
    2650 /**
    2651  * Retrieves the terms associated with the given object(s), in the supplied taxonomies.
    2652  *
    2653  * @since 2.3.0
    2654  * @since 4.2.0 Added support for 'taxonomy', 'parent', and 'term_taxonomy_id' values of `$orderby`.
    2655  *              Introduced `$parent` argument.
    2656  *
    2657  * @global wpdb $wpdb WordPress database abstraction object.
    2658  *
    2659  * @param int|array    $object_ids The ID(s) of the object(s) to retrieve.
    2660  * @param string|array $taxonomies The taxonomies to retrieve terms from.
    2661  * @param array|string $args {
    2662  *     Array of arguments.
    2663  *     @type string $orderby Field by which results should be sorted. Accepts 'name', 'count', 'slug', 'term_group',
    2664  *                           'term_order', 'taxonomy', 'parent', or 'term_taxonomy_id'. Default 'name'.
    2665  *     @type string $order   Sort order. Accepts 'ASC' or 'DESC'. Default 'ASC'.
    2666  *     @type string $fields  Fields to return for matched terms. Accepts 'all', 'ids', 'names', and
    2667  *                           'all_with_object_id'. Note that 'all' or 'all_with_object_id' will result in an array of
    2668  *                           term objects being returned, 'ids' will return an array of integers, and 'names' an array
    2669  *                           of strings.
    2670  *     @type int    $parent  Optional. Limit results to the direct children of a given term ID.
    2671  * }
    2672  * @return array|WP_Error The requested term data or empty array if no terms found.
    2673  *                        WP_Error if any of the $taxonomies don't exist.
    2674  */
    2675 function wp_get_object_terms($object_ids, $taxonomies, $args = array()) {
    2676         global $wpdb;
    2677 
    2678         if ( empty( $object_ids ) || empty( $taxonomies ) )
    2679                 return array();
    2680 
    2681         if ( !is_array($taxonomies) )
    2682                 $taxonomies = array($taxonomies);
    2683 
    2684         foreach ( $taxonomies as $taxonomy ) {
    2685                 if ( ! taxonomy_exists($taxonomy) )
    2686                         return new WP_Error('invalid_taxonomy', __('Invalid taxonomy'));
    2687         }
    2688 
    2689         if ( !is_array($object_ids) )
    2690                 $object_ids = array($object_ids);
    2691         $object_ids = array_map('intval', $object_ids);
    2692 
    2693         $defaults = array(
    2694                 'orderby' => 'name',
    2695                 'order'   => 'ASC',
    2696                 'fields'  => 'all',
    2697                 'parent'  => '',
    2698         );
    2699         $args = wp_parse_args( $args, $defaults );
    2700 
    2701         $terms = array();
    2702         if ( count($taxonomies) > 1 ) {
    2703                 foreach ( $taxonomies as $index => $taxonomy ) {
    2704                         $t = get_taxonomy($taxonomy);
    2705                         if ( isset($t->args) && is_array($t->args) && $args != array_merge($args, $t->args) ) {
    2706                                 unset($taxonomies[$index]);
    2707                                 $terms = array_merge($terms, wp_get_object_terms($object_ids, $taxonomy, array_merge($args, $t->args)));
    2708                         }
    2709                 }
    2710         } else {
    2711                 $t = get_taxonomy($taxonomies[0]);
    2712                 if ( isset($t->args) && is_array($t->args) )
    2713                         $args = array_merge($args, $t->args);
    2714         }
    2715 
    2716         $orderby = $args['orderby'];
    2717         $order = $args['order'];
    2718         $fields = $args['fields'];
    2719 
    2720         if ( in_array( $orderby, array( 'term_id', 'name', 'slug', 'term_group' ) ) ) {
    2721                 $orderby = "t.$orderby";
    2722         } elseif ( in_array( $orderby, array( 'count', 'parent', 'taxonomy', 'term_taxonomy_id' ) ) ) {
    2723                 $orderby = "tt.$orderby";
    2724         } elseif ( 'term_order' === $orderby ) {
    2725                 $orderby = 'tr.term_order';
    2726         } elseif ( 'none' === $orderby ) {
    2727                 $orderby = '';
    2728                 $order = '';
    2729         } else {
    2730                 $orderby = 't.term_id';
    2731         }
    2732 
    2733         // tt_ids queries can only be none or tr.term_taxonomy_id
    2734         if ( ('tt_ids' == $fields) && !empty($orderby) )
    2735                 $orderby = 'tr.term_taxonomy_id';
    2736 
    2737         if ( !empty($orderby) )
    2738                 $orderby = "ORDER BY $orderby";
    2739 
    2740         $order = strtoupper( $order );
    2741         if ( '' !== $order && ! in_array( $order, array( 'ASC', 'DESC' ) ) )
    2742                 $order = 'ASC';
    2743 
    2744         $taxonomy_array = $taxonomies;
    2745         $object_id_array = $object_ids;
    2746         $taxonomies = "'" . implode("', '", $taxonomies) . "'";
    2747         $object_ids = implode(', ', $object_ids);
    2748 
    2749         $select_this = '';
    2750         if ( 'all' == $fields ) {
    2751                 $select_this = 't.*, tt.*';
    2752         } elseif ( 'ids' == $fields ) {
    2753                 $select_this = 't.term_id';
    2754         } elseif ( 'names' == $fields ) {
    2755                 $select_this = 't.name';
    2756         } elseif ( 'slugs' == $fields ) {
    2757                 $select_this = 't.slug';
    2758         } elseif ( 'all_with_object_id' == $fields ) {
    2759                 $select_this = 't.*, tt.*, tr.object_id';
    2760         }
    2761 
    2762         $where = array(
    2763                 "tt.taxonomy IN ($taxonomies)",
    2764                 "tr.object_id IN ($object_ids)",
    2765         );
    2766 
    2767         if ( '' !== $args['parent'] ) {
    2768                 $where[] = $wpdb->prepare( 'tt.parent = %d', $args['parent'] );
    2769         }
    2770 
    2771         $where = implode( ' AND ', $where );
    2772 
    2773         $query = "SELECT $select_this FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS tt ON tt.term_id = t.term_id INNER JOIN $wpdb->term_relationships AS tr ON tr.term_taxonomy_id = tt.term_taxonomy_id WHERE $where $orderby $order";
    2774 
    2775         $objects = false;
    2776         if ( 'all' == $fields || 'all_with_object_id' == $fields ) {
    2777                 $_terms = $wpdb->get_results( $query );
    2778                 foreach ( $_terms as $key => $term ) {
    2779                         $_terms[$key] = sanitize_term( $term, $taxonomy, 'raw' );
    2780                 }
    2781                 $terms = array_merge( $terms, $_terms );
    2782                 update_term_cache( $terms );
    2783                 $objects = true;
    2784         } elseif ( 'ids' == $fields || 'names' == $fields || 'slugs' == $fields ) {
    2785                 $_terms = $wpdb->get_col( $query );
    2786                 $_field = ( 'ids' == $fields ) ? 'term_id' : 'name';
    2787                 foreach ( $_terms as $key => $term ) {
    2788                         $_terms[$key] = sanitize_term_field( $_field, $term, $term, $taxonomy, 'raw' );
    2789                 }
    2790                 $terms = array_merge( $terms, $_terms );
    2791         } elseif ( 'tt_ids' == $fields ) {
    2792                 $terms = $wpdb->get_col("SELECT tr.term_taxonomy_id FROM $wpdb->term_relationships AS tr INNER JOIN $wpdb->term_taxonomy AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id WHERE tr.object_id IN ($object_ids) AND tt.taxonomy IN ($taxonomies) $orderby $order");
    2793                 foreach ( $terms as $key => $tt_id ) {
    2794                         $terms[$key] = sanitize_term_field( 'term_taxonomy_id', $tt_id, 0, $taxonomy, 'raw' ); // 0 should be the term id, however is not needed when using raw context.
    2795                 }
    2796         }
    2797 
    2798         if ( ! $terms ) {
    2799                 $terms = array();
    2800         } elseif ( $objects && 'all_with_object_id' !== $fields ) {
    2801                 $_tt_ids = array();
    2802                 $_terms = array();
    2803                 foreach ( $terms as $term ) {
    2804                         if ( in_array( $term->term_taxonomy_id, $_tt_ids ) ) {
    2805                                 continue;
    2806                         }
    2807 
    2808                         $_tt_ids[] = $term->term_taxonomy_id;
    2809                         $_terms[] = $term;
    2810                 }
    2811                 $terms = $_terms;
    2812         } elseif ( ! $objects ) {
    2813                 $terms = array_values( array_unique( $terms ) );
    2814         }
    2815 
    2816         /**
    2817          * Filter the terms for a given object or objects.
    2818          *
    2819          * @since 4.2.0
    2820          *
    2821          * @param array $terms           An array of terms for the given object or objects.
    2822          * @param array $object_id_array Array of object IDs for which `$terms` were retrieved.
    2823          * @param array $taxonomy_array  Array of taxonomies from which `$terms` were retrieved.
    2824          * @param array $args            An array of arguments for retrieving terms for the given
    2825          *                               object(s). See wp_get_object_terms() for details.
    2826          */
    2827         $terms = apply_filters( 'get_object_terms', $terms, $object_id_array, $taxonomy_array, $args );
    2828 
    2829         /**
    2830          * Filter the terms for a given object or objects.
    2831          *
    2832          * The `$taxonomies` parameter passed to this filter is formatted as a SQL fragment. The
    2833          * {@see 'get_object_terms'} filter is recommended as an alternative.
    2834          *
    2835          * @since 2.8.0
    2836          *
    2837          * @param array     $terms      An array of terms for the given object or objects.
    2838          * @param int|array $object_ids Object ID or array of IDs.
    2839          * @param string    $taxonomies SQL-formatted (comma-separated and quoted) list of taxonomy names.
    2840          * @param array     $args       An array of arguments for retrieving terms for the given object(s).
    2841          *                              See {@see wp_get_object_terms()} for details.
    2842          */
    2843         return apply_filters( 'wp_get_object_terms', $terms, $object_ids, $taxonomies, $args );
    2844 }
    2845 
    2846 /**
    2847  * Add a new term to the database.
    2848  *
    2849  * A non-existent term is inserted in the following sequence:
    2850  * 1. The term is added to the term table, then related to the taxonomy.
    2851  * 2. If everything is correct, several actions are fired.
    2852  * 3. The 'term_id_filter' is evaluated.
    2853  * 4. The term cache is cleaned.
    2854  * 5. Several more actions are fired.
    2855  * 6. An array is returned containing the term_id and term_taxonomy_id.
    2856  *
    2857  * If the 'slug' argument is not empty, then it is checked to see if the term
    2858  * is invalid. If it is not a valid, existing term, it is added and the term_id
    2859  * is given.
    2860  *
    2861  * If the taxonomy is hierarchical, and the 'parent' argument is not empty,
    2862  * the term is inserted and the term_id will be given.
    2863 
    2864  * Error handling:
    2865  * If $taxonomy does not exist or $term is empty,
    2866  * a WP_Error object will be returned.
    2867  *
    2868  * If the term already exists on the same hierarchical level,
    2869  * or the term slug and name are not unique, a WP_Error object will be returned.
    2870  *
    2871  * @global wpdb $wpdb WordPress database abstraction object.
    2872 
    2873  * @since 2.3.0
    2874  *
    2875  * @param string       $term     The term to add or update.
    2876  * @param string       $taxonomy The taxonomy to which to add the term.
    2877  * @param array|string $args {
    2878  *     Optional. Array or string of arguments for inserting a term.
    2879  *
    2880  *     @type string $alias_of    Slug of the term to make this term an alias of.
    2881  *                               Default empty string. Accepts a term slug.
    2882  *     @type string $description The term description. Default empty string.
    2883  *     @type int    $parent      The id of the parent term. Default 0.
    2884  *     @type string $slug        The term slug to use. Default empty string.
    2885  * }
    2886  * @return array|WP_Error An array containing the `term_id` and `term_taxonomy_id`,
    2887  *                        {@see WP_Error} otherwise.
    2888  */
    2889 function wp_insert_term( $term, $taxonomy, $args = array() ) {
    2890         global $wpdb;
    2891 
    2892         if ( ! taxonomy_exists($taxonomy) ) {
    2893                 return new WP_Error('invalid_taxonomy', __('Invalid taxonomy'));
    2894         }
    2895         /**
    2896          * Filter a term before it is sanitized and inserted into the database.
    2897          *
    2898          * @since 3.0.0
    2899          *
    2900          * @param string $term     The term to add or update.
    2901          * @param string $taxonomy Taxonomy slug.
    2902          */
    2903         $term = apply_filters( 'pre_insert_term', $term, $taxonomy );
    2904         if ( is_wp_error( $term ) ) {
    2905                 return $term;
    2906         }
    2907         if ( is_int($term) && 0 == $term ) {
    2908                 return new WP_Error('invalid_term_id', __('Invalid term ID'));
    2909         }
    2910         if ( '' == trim($term) ) {
    2911                 return new WP_Error('empty_term_name', __('A name is required for this term'));
    2912         }
    2913         $defaults = array( 'alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => '');
    2914         $args = wp_parse_args( $args, $defaults );
    2915 
    2916         if ( $args['parent'] > 0 && ! term_exists( (int) $args['parent'] ) ) {
    2917                 return new WP_Error( 'missing_parent', __( 'Parent term does not exist.' ) );
    2918         }
    2919         $args['name'] = $term;
    2920         $args['taxonomy'] = $taxonomy;
    2921         $args = sanitize_term($args, $taxonomy, 'db');
    2922 
    2923         // expected_slashed ($name)
    2924         $name = wp_unslash( $args['name'] );
    2925         $description = wp_unslash( $args['description'] );
    2926         $parent = (int) $args['parent'];
    2927 
    2928         $slug_provided = ! empty( $args['slug'] );
    2929         if ( ! $slug_provided ) {
    2930                 $slug = sanitize_title( $name );
    2931         } else {
    2932                 $slug = $args['slug'];
    2933         }
    2934 
    2935         $term_group = 0;
    2936         if ( $args['alias_of'] ) {
    2937                 $alias = get_term_by( 'slug', $args['alias_of'], $taxonomy );
    2938                 if ( ! empty( $alias->term_group ) ) {
    2939                         // The alias we want is already in a group, so let's use that one.
    2940                         $term_group = $alias->term_group;
    2941                 } elseif ( ! empty( $alias->term_id ) ) {
    2942                         /*
    2943                          * The alias is not in a group, so we create a new one
    2944                          * and add the alias to it.
    2945                          */
    2946                         $term_group = $wpdb->get_var("SELECT MAX(term_group) FROM $wpdb->terms") + 1;
    2947 
    2948                         wp_update_term( $alias->term_id, $taxonomy, array(
    2949                                 'term_group' => $term_group,
    2950                         ) );
    2951                 }
    2952         }
    2953 
    2954         /*
    2955          * Prevent the creation of terms with duplicate names at the same level of a taxonomy hierarchy,
    2956          * unless a unique slug has been explicitly provided.
    2957          */
    2958         if ( $name_match = get_term_by( 'name', $name, $taxonomy ) ) {
    2959                 $slug_match = get_term_by( 'slug', $slug, $taxonomy );
    2960                 if ( ! $slug_provided || $name_match->slug === $slug || $slug_match ) {
    2961                         if ( is_taxonomy_hierarchical( $taxonomy ) ) {
    2962                                 $siblings = get_terms( $taxonomy, array( 'get' => 'all', 'parent' => $parent ) );
    2963 
    2964                                 $existing_term = null;
    2965                                 if ( $name_match->slug === $slug && in_array( $name, wp_list_pluck( $siblings, 'name' ) ) ) {
    2966                                         $existing_term = $name_match;
    2967                                 } elseif ( $slug_match && in_array( $slug, wp_list_pluck( $siblings, 'slug' ) ) ) {
    2968                                         $existing_term = $slug_match;
    2969                                 }
    2970 
    2971                                 if ( $existing_term ) {
    2972                                         return new WP_Error( 'term_exists', __( 'A term with the name provided already exists with this parent.' ), $existing_term->term_id );
    2973                                 }
    2974                         } else {
    2975                                 return new WP_Error( 'term_exists', __( 'A term with the name provided already exists in this taxonomy.' ), $name_match->term_id );
    2976                         }
    2977                 }
    2978         }
    2979 
    2980         $slug = wp_unique_term_slug( $slug, (object) $args );
    2981 
    2982         if ( false === $wpdb->insert( $wpdb->terms, compact( 'name', 'slug', 'term_group' ) ) ) {
    2983                 return new WP_Error( 'db_insert_error', __( 'Could not insert term into the database' ), $wpdb->last_error );
    2984         }
    2985 
    2986         $term_id = (int) $wpdb->insert_id;
    2987 
    2988         // Seems unreachable, However, Is used in the case that a term name is provided, which sanitizes to an empty string.
    2989         if ( empty($slug) ) {
    2990                 $slug = sanitize_title($slug, $term_id);
    2991 
    2992                 /** This action is documented in wp-includes/taxonomy.php */
    2993                 do_action( 'edit_terms', $term_id, $taxonomy );
    2994                 $wpdb->update( $wpdb->terms, compact( 'slug' ), compact( 'term_id' ) );
    2995 
    2996                 /** This action is documented in wp-includes/taxonomy.php */
    2997                 do_action( 'edited_terms', $term_id, $taxonomy );
    2998         }
    2999 
    3000         $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 ) );
    3001 
    3002         if ( !empty($tt_id) ) {
    3003                 return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    3004         }
    3005         $wpdb->insert( $wpdb->term_taxonomy, compact( 'term_id', 'taxonomy', 'description', 'parent') + array( 'count' => 0 ) );
    3006         $tt_id = (int) $wpdb->insert_id;
    3007 
    3008         /*
    3009          * Sanity check: if we just created a term with the same parent + taxonomy + slug but a higher term_id than
    3010          * an existing term, then we have unwittingly created a duplicate term. Delete the dupe, and use the term_id
    3011          * and term_taxonomy_id of the older term instead. Then return out of the function so that the "create" hooks
    3012          * are not fired.
    3013          */
    3014         $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 ) );
    3015         if ( $duplicate_term ) {
    3016                 $wpdb->delete( $wpdb->terms, array( 'term_id' => $term_id ) );
    3017                 $wpdb->delete( $wpdb->term_taxonomy, array( 'term_taxonomy_id' => $tt_id ) );
    3018 
    3019                 $term_id = (int) $duplicate_term->term_id;
    3020                 $tt_id   = (int) $duplicate_term->term_taxonomy_id;
    3021 
    3022                 clean_term_cache( $term_id, $taxonomy );
    3023                 return array( 'term_id' => $term_id, 'term_taxonomy_id' => $tt_id );
    3024         }
    3025 
    3026         /**
    3027          * Fires immediately after a new term is created, before the term cache is cleaned.
    3028          *
    3029          * @since 2.3.0
    3030          *
    3031          * @param int    $term_id  Term ID.
    3032          * @param int    $tt_id    Term taxonomy ID.
    3033          * @param string $taxonomy Taxonomy slug.
    3034          */
    3035         do_action( "create_term", $term_id, $tt_id, $taxonomy );
    3036 
    3037         /**
    3038          * Fires after a new term is created for a specific taxonomy.
    3039          *
    3040          * The dynamic portion of the hook name, `$taxonomy`, refers
    3041          * to the slug of the taxonomy the term was created for.
    3042          *
    3043          * @since 2.3.0
    3044          *
    3045          * @param int $term_id Term ID.
    3046          * @param int $tt_id   Term taxonomy ID.
    3047          */
    3048         do_action( "create_$taxonomy", $term_id, $tt_id );
    3049 
    3050         /**
    3051          * Filter the term ID after a new term is created.
    3052          *
    3053          * @since 2.3.0
    3054          *
    3055          * @param int $term_id Term ID.
    3056          * @param int $tt_id   Taxonomy term ID.
    3057          */
    3058         $term_id = apply_filters( 'term_id_filter', $term_id, $tt_id );
    3059 
    3060         clean_term_cache($term_id, $taxonomy);
    3061 
    3062         /**
    3063          * Fires after a new term is created, and after the term cache has been cleaned.
    3064          *
    3065          * @since 2.3.0
    3066          *
    3067          * @param int    $term_id  Term ID.
    3068          * @param int    $tt_id    Term taxonomy ID.
    3069          * @param string $taxonomy Taxonomy slug.
    3070          */
    3071         do_action( 'created_term', $term_id, $tt_id, $taxonomy );
    3072 
    3073         /**
    3074          * Fires after a new term in a specific taxonomy is created, and after the term
    3075          * cache has been cleaned.
    3076          *
    3077          * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
    3078          *
    3079          * @since 2.3.0
    3080          *
    3081          * @param int $term_id Term ID.
    3082          * @param int $tt_id   Term taxonomy ID.
    3083          */
    3084         do_action( "created_$taxonomy", $term_id, $tt_id );
    3085 
    3086         return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    3087 }
    3088 
    3089 /**
    3090  * Create Term and Taxonomy Relationships.
    3091  *
    3092  * Relates an object (post, link etc) to a term and taxonomy type. Creates the
    3093  * term and taxonomy relationship if it doesn't already exist. Creates a term if
    3094  * it doesn't exist (using the slug).
    3095  *
    3096  * A relationship means that the term is grouped in or belongs to the taxonomy.
    3097  * A term has no meaning until it is given context by defining which taxonomy it
    3098  * exists under.
    3099  *
    3100  * @since 2.3.0
    3101  *
    3102  * @global wpdb $wpdb The WordPress database abstraction object.
    3103  *
    3104  * @param int              $object_id The object to relate to.
    3105  * @param array|int|string $terms     A single term slug, single term id, or array of either term slugs or ids.
    3106  *                                    Will replace all existing related terms in this taxonomy.
    3107  * @param string           $taxonomy  The context in which to relate the term to the object.
    3108  * @param bool             $append    Optional. If false will delete difference of terms. Default false.
    3109  * @return array|WP_Error Affected Term IDs.
    3110  */
    3111 function wp_set_object_terms( $object_id, $terms, $taxonomy, $append = false ) {
    3112         global $wpdb;
    3113 
    3114         $object_id = (int) $object_id;
    3115 
    3116         if ( ! taxonomy_exists($taxonomy) )
    3117                 return new WP_Error('invalid_taxonomy', __('Invalid taxonomy'));
    3118 
    3119         if ( !is_array($terms) )
    3120                 $terms = array($terms);
    3121 
    3122         if ( ! $append )
    3123                 $old_tt_ids =  wp_get_object_terms($object_id, $taxonomy, array('fields' => 'tt_ids', 'orderby' => 'none'));
    3124         else
    3125                 $old_tt_ids = array();
    3126 
    3127         $tt_ids = array();
    3128         $term_ids = array();
    3129         $new_tt_ids = array();
    3130 
    3131         foreach ( (array) $terms as $term) {
    3132                 if ( !strlen(trim($term)) )
    3133                         continue;
    3134 
    3135                 if ( !$term_info = term_exists($term, $taxonomy) ) {
    3136                         // Skip if a non-existent term ID is passed.
    3137                         if ( is_int($term) )
    3138                                 continue;
    3139                         $term_info = wp_insert_term($term, $taxonomy);
    3140                 }
    3141                 if ( is_wp_error($term_info) )
    3142                         return $term_info;
    3143                 $term_ids[] = $term_info['term_id'];
    3144                 $tt_id = $term_info['term_taxonomy_id'];
    3145                 $tt_ids[] = $tt_id;
    3146 
    3147                 if ( $wpdb->get_var( $wpdb->prepare( "SELECT term_taxonomy_id FROM $wpdb->term_relationships WHERE object_id = %d AND term_taxonomy_id = %d", $object_id, $tt_id ) ) )
    3148                         continue;
    3149 
    3150                 /**
    3151                  * Fires immediately before an object-term relationship is added.
    3152                  *
    3153                  * @since 2.9.0
    3154                  *
    3155                  * @param int $object_id Object ID.
    3156                  * @param int $tt_id     Term taxonomy ID.
    3157                  */
    3158                 do_action( 'add_term_relationship', $object_id, $tt_id );
    3159                 $wpdb->insert( $wpdb->term_relationships, array( 'object_id' => $object_id, 'term_taxonomy_id' => $tt_id ) );
    3160 
    3161                 /**
    3162                  * Fires immediately after an object-term relationship is added.
    3163                  *
    3164                  * @since 2.9.0
    3165                  *
    3166                  * @param int $object_id Object ID.
    3167                  * @param int $tt_id     Term taxonomy ID.
    3168                  */
    3169                 do_action( 'added_term_relationship', $object_id, $tt_id );
    3170                 $new_tt_ids[] = $tt_id;
    3171         }
    3172 
    3173         if ( $new_tt_ids )
    3174                 wp_update_term_count( $new_tt_ids, $taxonomy );
    3175 
    3176         if ( ! $append ) {
    3177                 $delete_tt_ids = array_diff( $old_tt_ids, $tt_ids );
    3178 
    3179                 if ( $delete_tt_ids ) {
    3180                         $in_delete_tt_ids = "'" . implode( "', '", $delete_tt_ids ) . "'";
    3181                         $delete_term_ids = $wpdb->get_col( $wpdb->prepare( "SELECT tt.term_id FROM $wpdb->term_taxonomy AS tt WHERE tt.taxonomy = %s AND tt.term_taxonomy_id IN ($in_delete_tt_ids)", $taxonomy ) );
    3182                         $delete_term_ids = array_map( 'intval', $delete_term_ids );
    3183 
    3184                         $remove = wp_remove_object_terms( $object_id, $delete_term_ids, $taxonomy );
    3185                         if ( is_wp_error( $remove ) ) {
    3186                                 return $remove;
    3187                         }
    3188                 }
    3189         }
    3190 
    3191         $t = get_taxonomy($taxonomy);
    3192         if ( ! $append && isset($t->sort) && $t->sort ) {
    3193                 $values = array();
    3194                 $term_order = 0;
    3195                 $final_tt_ids = wp_get_object_terms($object_id, $taxonomy, array('fields' => 'tt_ids'));
    3196                 foreach ( $tt_ids as $tt_id )
    3197                         if ( in_array($tt_id, $final_tt_ids) )
    3198                                 $values[] = $wpdb->prepare( "(%d, %d, %d)", $object_id, $tt_id, ++$term_order);
    3199                 if ( $values )
    3200                         if ( false === $wpdb->query( "INSERT INTO $wpdb->term_relationships (object_id, term_taxonomy_id, term_order) VALUES " . join( ',', $values ) . " ON DUPLICATE KEY UPDATE term_order = VALUES(term_order)" ) )
    3201                                 return new WP_Error( 'db_insert_error', __( 'Could not insert term relationship into the database' ), $wpdb->last_error );
    3202         }
    3203 
    3204         wp_cache_delete( $object_id, $taxonomy . '_relationships' );
    3205 
    3206         /**
    3207          * Fires after an object's terms have been set.
    3208          *
    3209          * @since 2.8.0
    3210          *
    3211          * @param int    $object_id  Object ID.
    3212          * @param array  $terms      An array of object terms.
    3213          * @param array  $tt_ids     An array of term taxonomy IDs.
    3214          * @param string $taxonomy   Taxonomy slug.
    3215          * @param bool   $append     Whether to append new terms to the old terms.
    3216          * @param array  $old_tt_ids Old array of term taxonomy IDs.
    3217          */
    3218         do_action( 'set_object_terms', $object_id, $terms, $tt_ids, $taxonomy, $append, $old_tt_ids );
    3219         return $tt_ids;
    3220 }
    3221 
    3222 /**
    3223  * Add term(s) associated with a given object.
    3224  *
    3225  * @since 3.6.0
    3226  *
    3227  * @param int              $object_id The ID of the object to which the terms will be added.
    3228  * @param array|int|string $terms     The slug(s) or ID(s) of the term(s) to add.
    3229  * @param array|string     $taxonomy  Taxonomy name.
    3230  * @return array|WP_Error Affected Term IDs
    3231  */
    3232 function wp_add_object_terms( $object_id, $terms, $taxonomy ) {
    3233         return wp_set_object_terms( $object_id, $terms, $taxonomy, true );
    3234 }
    3235 
    3236 /**
    3237  * Remove term(s) associated with a given object.
    3238  *
    3239  * @since 3.6.0
    3240  *
    3241  * @global wpdb $wpdb WordPress database abstraction object.
    3242  *
    3243  * @param int              $object_id The ID of the object from which the terms will be removed.
    3244  * @param array|int|string $terms     The slug(s) or ID(s) of the term(s) to remove.
    3245  * @param array|string     $taxonomy  Taxonomy name.
    3246  * @return bool|WP_Error True on success, false or WP_Error on failure.
    3247  */
    3248 function wp_remove_object_terms( $object_id, $terms, $taxonomy ) {
    3249         global $wpdb;
    3250 
    3251         $object_id = (int) $object_id;
    3252 
    3253         if ( ! taxonomy_exists( $taxonomy ) ) {
    3254                 return new WP_Error( 'invalid_taxonomy', __( 'Invalid Taxonomy' ) );
    3255         }
    3256 
    3257         if ( ! is_array( $terms ) ) {
    3258                 $terms = array( $terms );
    3259         }
    3260 
    3261         $tt_ids = array();
    3262 
    3263         foreach ( (array) $terms as $term ) {
    3264                 if ( ! strlen( trim( $term ) ) ) {
    3265                         continue;
    3266                 }
    3267 
    3268                 if ( ! $term_info = term_exists( $term, $taxonomy ) ) {
    3269                         // Skip if a non-existent term ID is passed.
    3270                         if ( is_int( $term ) ) {
    3271                                 continue;
    3272                         }
    3273                 }
    3274 
    3275                 if ( is_wp_error( $term_info ) ) {
    3276                         return $term_info;
    3277                 }
    3278 
    3279                 $tt_ids[] = $term_info['term_taxonomy_id'];
    3280         }
    3281 
    3282         if ( $tt_ids ) {
    3283                 $in_tt_ids = "'" . implode( "', '", $tt_ids ) . "'";
    3284 
    3285                 /**
    3286                  * Fires immediately before an object-term relationship is deleted.
    3287                  *
    3288                  * @since 2.9.0
    3289                  *
    3290                  * @param int   $object_id Object ID.
    3291                  * @param array $tt_ids    An array of term taxonomy IDs.
    3292                  */
    3293                 do_action( 'delete_term_relationships', $object_id, $tt_ids );
    3294                 $deleted = $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->term_relationships WHERE object_id = %d AND term_taxonomy_id IN ($in_tt_ids)", $object_id ) );
    3295 
    3296                 /**
    3297                  * Fires immediately after an object-term relationship is deleted.
    3298                  *
    3299                  * @since 2.9.0
    3300                  *
    3301                  * @param int   $object_id Object ID.
    3302                  * @param array $tt_ids    An array of term taxonomy IDs.
    3303                  */
    3304                 do_action( 'deleted_term_relationships', $object_id, $tt_ids );
    3305 
    3306                 wp_update_term_count( $tt_ids, $taxonomy );
    3307 
    3308                 return (bool) $deleted;
    3309         }
    3310 
    3311         return false;
    3312 }
    3313 
    3314 /**
    3315  * Will make slug unique, if it isn't already.
    3316  *
    3317  * The `$slug` has to be unique global to every taxonomy, meaning that one
    3318  * taxonomy term can't have a matching slug with another taxonomy term. Each
    3319  * slug has to be globally unique for every taxonomy.
    3320  *
    3321  * The way this works is that if the taxonomy that the term belongs to is
    3322  * hierarchical and has a parent, it will append that parent to the $slug.
    3323  *
    3324  * If that still doesn't return an unique slug, then it try to append a number
    3325  * until it finds a number that is truly unique.
    3326  *
    3327  * The only purpose for `$term` is for appending a parent, if one exists.
    3328  *
    3329  * @since 2.3.0
    3330  *
    3331  * @global wpdb $wpdb WordPress database abstraction object.
    3332  *
    3333  * @param string $slug The string that will be tried for a unique slug.
    3334  * @param object $term The term object that the `$slug` will belong to.
    3335  * @return string Will return a true unique slug.
    3336  */
    3337 function wp_unique_term_slug( $slug, $term ) {
    3338         global $wpdb;
    3339 
    3340         $needs_suffix = true;
    3341         $original_slug = $slug;
    3342 
    3343         // As of 4.1, duplicate slugs are allowed as long as they're in different taxonomies.
    3344         if ( ! term_exists( $slug ) || get_option( 'db_version' ) >= 30133 && ! get_term_by( 'slug', $slug, $term->taxonomy ) ) {
    3345                 $needs_suffix = false;
    3346         }
    3347 
    3348         /*
    3349          * If the taxonomy supports hierarchy and the term has a parent, make the slug unique
    3350          * by incorporating parent slugs.
    3351          */
    3352         $parent_suffix = '';
    3353         if ( $needs_suffix && is_taxonomy_hierarchical( $term->taxonomy ) && ! empty( $term->parent ) ) {
    3354                 $the_parent = $term->parent;
    3355                 while ( ! empty($the_parent) ) {
    3356                         $parent_term = get_term($the_parent, $term->taxonomy);
    3357                         if ( is_wp_error($parent_term) || empty($parent_term) )
    3358                                 break;
    3359                         $parent_suffix .= '-' . $parent_term->slug;
    3360                         if ( ! term_exists( $slug . $parent_suffix ) ) {
    3361                                 break;
    3362                         }
    3363 
    3364                         if ( empty($parent_term->parent) )
    3365                                 break;
    3366                         $the_parent = $parent_term->parent;
    3367                 }
    3368         }
    3369 
    3370         // If we didn't get a unique slug, try appending a number to make it unique.
    3371 
    3372         /**
    3373          * Filter whether the proposed unique term slug is bad.
    3374          *
    3375          * @since 4.3.0
    3376          *
    3377          * @param bool   $needs_suffix Whether the slug needs to be made unique with a suffix.
    3378          * @param string $slug         The slug.
    3379          * @param object $term         Term object.
    3380          */
    3381         if ( apply_filters( 'wp_unique_term_slug_is_bad_slug', $needs_suffix, $slug, $term ) ) {
    3382                 if ( $parent_suffix ) {
    3383                         $slug .= $parent_suffix;
    3384                 } else {
    3385                         if ( ! empty( $term->term_id ) )
    3386                                 $query = $wpdb->prepare( "SELECT slug FROM $wpdb->terms WHERE slug = %s AND term_id != %d", $slug, $term->term_id );
    3387                         else
    3388                                 $query = $wpdb->prepare( "SELECT slug FROM $wpdb->terms WHERE slug = %s", $slug );
    3389 
    3390                         if ( $wpdb->get_var( $query ) ) {
    3391                                 $num = 2;
    3392                                 do {
    3393                                         $alt_slug = $slug . "-$num";
    3394                                         $num++;
    3395                                         $slug_check = $wpdb->get_var( $wpdb->prepare( "SELECT slug FROM $wpdb->terms WHERE slug = %s", $alt_slug ) );
    3396                                 } while ( $slug_check );
    3397                                 $slug = $alt_slug;
    3398                         }
    3399                 }
    3400         }
    3401 
    3402         /**
    3403          * Filter the unique term slug.
    3404          *
    3405          * @since 4.3.0
    3406          *
    3407          * @param string $slug          Unique term slug.
    3408          * @param object $term          Term object.
    3409          * @param string $original_slug Slug originally passed to the function for testing.
    3410          */
    3411         return apply_filters( 'wp_unique_term_slug', $slug, $term, $original_slug );
    3412 }
    3413 
    3414 /**
    3415  * Update term based on arguments provided.
    3416  *
    3417  * The $args will indiscriminately override all values with the same field name.
    3418  * Care must be taken to not override important information need to update or
    3419  * update will fail (or perhaps create a new term, neither would be acceptable).
    3420  *
    3421  * Defaults will set 'alias_of', 'description', 'parent', and 'slug' if not
    3422  * defined in $args already.
    3423  *
    3424  * 'alias_of' will create a term group, if it doesn't already exist, and update
    3425  * it for the $term.
    3426  *
    3427  * If the 'slug' argument in $args is missing, then the 'name' in $args will be
    3428  * used. It should also be noted that if you set 'slug' and it isn't unique then
    3429  * a WP_Error will be passed back. If you don't pass any slug, then a unique one
    3430  * will be created for you.
    3431  *
    3432  * For what can be overrode in `$args`, check the term scheme can contain and stay
    3433  * away from the term keys.
    3434  *
    3435  * @since 2.3.0
    3436  *
    3437  * @global wpdb $wpdb WordPress database abstraction object.
    3438  *
    3439  * @param int          $term_id  The ID of the term
    3440  * @param string       $taxonomy The context in which to relate the term to the object.
    3441  * @param array|string $args     Optional. Array of get_terms() arguments. Default empty array.
    3442  * @return array|WP_Error Returns Term ID and Taxonomy Term ID
    3443  */
    3444 function wp_update_term( $term_id, $taxonomy, $args = array() ) {
    3445         global $wpdb;
    3446 
    3447         if ( ! taxonomy_exists( $taxonomy ) ) {
    3448                 return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy' ) );
    3449         }
    3450 
    3451         $term_id = (int) $term_id;
    3452 
    3453         // First, get all of the original args
    3454         $term = get_term( $term_id, $taxonomy, ARRAY_A );
    3455 
    3456         if ( is_wp_error( $term ) ) {
    3457                 return $term;
    3458         }
    3459 
    3460         if ( ! $term ) {
    3461                 return new WP_Error( 'invalid_term', __( 'Empty Term' ) );
    3462         }
    3463 
    3464         // Escape data pulled from DB.
    3465         $term = wp_slash($term);
    3466 
    3467         // Merge old and new args with new args overwriting old ones.
    3468         $args = array_merge($term, $args);
    3469 
    3470         $defaults = array( 'alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => '');
    3471         $args = wp_parse_args($args, $defaults);
    3472         $args = sanitize_term($args, $taxonomy, 'db');
    3473         $parsed_args = $args;
    3474 
    3475         // expected_slashed ($name)
    3476         $name = wp_unslash( $args['name'] );
    3477         $description = wp_unslash( $args['description'] );
    3478 
    3479         $parsed_args['name'] = $name;
    3480         $parsed_args['description'] = $description;
    3481 
    3482         if ( '' == trim($name) )
    3483                 return new WP_Error('empty_term_name', __('A name is required for this term'));
    3484 
    3485         if ( $parsed_args['parent'] > 0 && ! term_exists( (int) $parsed_args['parent'] ) ) {
    3486                 return new WP_Error( 'missing_parent', __( 'Parent term does not exist.' ) );
    3487         }
    3488 
    3489         $empty_slug = false;
    3490         if ( empty( $args['slug'] ) ) {
    3491                 $empty_slug = true;
    3492                 $slug = sanitize_title($name);
    3493         } else {
    3494                 $slug = $args['slug'];
    3495         }
    3496 
    3497         $parsed_args['slug'] = $slug;
    3498 
    3499         $term_group = isset( $parsed_args['term_group'] ) ? $parsed_args['term_group'] : 0;
    3500         if ( $args['alias_of'] ) {
    3501                 $alias = get_term_by( 'slug', $args['alias_of'], $taxonomy );
    3502                 if ( ! empty( $alias->term_group ) ) {
    3503                         // The alias we want is already in a group, so let's use that one.
    3504                         $term_group = $alias->term_group;
    3505                 } elseif ( ! empty( $alias->term_id ) ) {
    3506                         /*
    3507                          * The alias is not in a group, so we create a new one
    3508                          * and add the alias to it.
    3509                          */
    3510                         $term_group = $wpdb->get_var("SELECT MAX(term_group) FROM $wpdb->terms") + 1;
    3511 
    3512                         wp_update_term( $alias->term_id, $taxonomy, array(
    3513                                 'term_group' => $term_group,
    3514                         ) );
    3515                 }
    3516 
    3517                 $parsed_args['term_group'] = $term_group;
    3518         }
    3519 
    3520         /**
    3521          * Filter the term parent.
    3522          *
    3523          * Hook to this filter to see if it will cause a hierarchy loop.
    3524          *
    3525          * @since 3.1.0
    3526          *
    3527          * @param int    $parent      ID of the parent term.
    3528          * @param int    $term_id     Term ID.
    3529          * @param string $taxonomy    Taxonomy slug.
    3530          * @param array  $parsed_args An array of potentially altered update arguments for the given term.
    3531          * @param array  $args        An array of update arguments for the given term.
    3532          */
    3533         $parent = apply_filters( 'wp_update_term_parent', $args['parent'], $term_id, $taxonomy, $parsed_args, $args );
    3534 
    3535         // Check for duplicate slug
    3536         $duplicate = get_term_by( 'slug', $slug, $taxonomy );
    3537         if ( $duplicate && $duplicate->term_id != $term_id ) {
    3538                 // If an empty slug was passed or the parent changed, reset the slug to something unique.
    3539                 // Otherwise, bail.
    3540                 if ( $empty_slug || ( $parent != $term['parent']) )
    3541                         $slug = wp_unique_term_slug($slug, (object) $args);
    3542                 else
    3543                         return new WP_Error('duplicate_term_slug', sprintf(__('The slug &#8220;%s&#8221; is already in use by another term'), $slug));
    3544         }
    3545 
    3546         $tt_id = (int) $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) );
    3547 
    3548         // Check whether this is a shared term that needs splitting.
    3549         $_term_id = _split_shared_term( $term_id, $tt_id );
    3550         if ( ! is_wp_error( $_term_id ) ) {
    3551                 $term_id = $_term_id;
    3552         }
    3553 
    3554         /**
    3555          * Fires immediately before the given terms are edited.
    3556          *
    3557          * @since 2.9.0
    3558          *
    3559          * @param int    $term_id  Term ID.
    3560          * @param string $taxonomy Taxonomy slug.
    3561          */
    3562         do_action( 'edit_terms', $term_id, $taxonomy );
    3563         $wpdb->update($wpdb->terms, compact( 'name', 'slug', 'term_group' ), compact( 'term_id' ) );
    3564         if ( empty($slug) ) {
    3565                 $slug = sanitize_title($name, $term_id);
    3566                 $wpdb->update( $wpdb->terms, compact( 'slug' ), compact( 'term_id' ) );
    3567         }
    3568 
    3569         /**
    3570          * Fires immediately after the given terms are edited.
    3571          *
    3572          * @since 2.9.0
    3573          *
    3574          * @param int    $term_id  Term ID
    3575          * @param string $taxonomy Taxonomy slug.
    3576          */
    3577         do_action( 'edited_terms', $term_id, $taxonomy );
    3578 
    3579         /**
    3580          * Fires immediate before a term-taxonomy relationship is updated.
    3581          *
    3582          * @since 2.9.0
    3583          *
    3584          * @param int    $tt_id    Term taxonomy ID.
    3585          * @param string $taxonomy Taxonomy slug.
    3586          */
    3587         do_action( 'edit_term_taxonomy', $tt_id, $taxonomy );
    3588 
    3589         $wpdb->update( $wpdb->term_taxonomy, compact( 'term_id', 'taxonomy', 'description', 'parent' ), array( 'term_taxonomy_id' => $tt_id ) );
    3590 
    3591         /**
    3592          * Fires immediately after a term-taxonomy relationship is updated.
    3593          *
    3594          * @since 2.9.0
    3595          *
    3596          * @param int    $tt_id    Term taxonomy ID.
    3597          * @param string $taxonomy Taxonomy slug.
    3598          */
    3599         do_action( 'edited_term_taxonomy', $tt_id, $taxonomy );
    3600 
    3601         // Clean the relationship caches for all object types using this term.
    3602         $objects = $wpdb->get_col( $wpdb->prepare( "SELECT object_id FROM $wpdb->term_relationships WHERE term_taxonomy_id = %d", $tt_id ) );
    3603         $tax_object = get_taxonomy( $taxonomy );
    3604         foreach ( $tax_object->object_type as $object_type ) {
    3605                 clean_object_term_cache( $objects, $object_type );
    3606         }
    3607 
    3608         /**
    3609          * Fires after a term has been updated, but before the term cache has been cleaned.
    3610          *
    3611          * @since 2.3.0
    3612          *
    3613          * @param int    $term_id  Term ID.
    3614          * @param int    $tt_id    Term taxonomy ID.
    3615          * @param string $taxonomy Taxonomy slug.
    3616          */
    3617         do_action( "edit_term", $term_id, $tt_id, $taxonomy );
    3618 
    3619         /**
    3620          * Fires after a term in a specific taxonomy has been updated, but before the term
    3621          * cache has been cleaned.
    3622          *
    3623          * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
    3624          *
    3625          * @since 2.3.0
    3626          *
    3627          * @param int $term_id Term ID.
    3628          * @param int $tt_id   Term taxonomy ID.
    3629          */
    3630         do_action( "edit_$taxonomy", $term_id, $tt_id );
    3631 
    3632         /** This filter is documented in wp-includes/taxonomy.php */
    3633         $term_id = apply_filters( 'term_id_filter', $term_id, $tt_id );
    3634 
    3635         clean_term_cache($term_id, $taxonomy);
    3636 
    3637         /**
    3638          * Fires after a term has been updated, and the term cache has been cleaned.
    3639          *
    3640          * @since 2.3.0
    3641          *
    3642          * @param int    $term_id  Term ID.
    3643          * @param int    $tt_id    Term taxonomy ID.
    3644          * @param string $taxonomy Taxonomy slug.
    3645          */
    3646         do_action( "edited_term", $term_id, $tt_id, $taxonomy );
    3647 
    3648         /**
    3649          * Fires after a term for a specific taxonomy has been updated, and the term
    3650          * cache has been cleaned.
    3651          *
    3652          * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
    3653          *
    3654          * @since 2.3.0
    3655          *
    3656          * @param int $term_id Term ID.
    3657          * @param int $tt_id   Term taxonomy ID.
    3658          */
    3659         do_action( "edited_$taxonomy", $term_id, $tt_id );
    3660 
    3661         return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
    3662 }
    3663 
    3664 /**
    3665  * Enable or disable term counting.
    3666  *
    3667  * @since 2.5.0
    3668  *
    3669  * @staticvar bool $_defer
    3670  *
    3671  * @param bool $defer Optional. Enable if true, disable if false.
    3672  * @return bool Whether term counting is enabled or disabled.
    3673  */
    3674 function wp_defer_term_counting($defer=null) {
    3675         static $_defer = false;
    3676 
    3677         if ( is_bool($defer) ) {
    3678                 $_defer = $defer;
    3679                 // flush any deferred counts
    3680                 if ( !$defer )
    3681                         wp_update_term_count( null, null, true );
    3682         }
    3683 
    3684         return $_defer;
    3685 }
    3686 
    3687 /**
    3688  * Updates the amount of terms in taxonomy.
    3689  *
    3690  * If there is a taxonomy callback applied, then it will be called for updating
    3691  * the count.
    3692  *
    3693  * The default action is to count what the amount of terms have the relationship
    3694  * of term ID. Once that is done, then update the database.
    3695  *
    3696  * @since 2.3.0
    3697  *
    3698  * @staticvar array $_deferred
    3699  *
    3700  * @param int|array $terms    The term_taxonomy_id of the terms.
    3701  * @param string    $taxonomy The context of the term.
    3702  * @return bool If no terms will return false, and if successful will return true.
    3703  */
    3704 function wp_update_term_count( $terms, $taxonomy, $do_deferred=false ) {
    3705         static $_deferred = array();
    3706 
    3707         if ( $do_deferred ) {
    3708                 foreach ( (array) array_keys($_deferred) as $tax ) {
    3709                         wp_update_term_count_now( $_deferred[$tax], $tax );
    3710                         unset( $_deferred[$tax] );
    3711                 }
    3712         }
    3713 
    3714         if ( empty($terms) )
    3715                 return false;
    3716 
    3717         if ( !is_array($terms) )
    3718                 $terms = array($terms);
    3719 
    3720         if ( wp_defer_term_counting() ) {
    3721                 if ( !isset($_deferred[$taxonomy]) )
    3722                         $_deferred[$taxonomy] = array();
    3723                 $_deferred[$taxonomy] = array_unique( array_merge($_deferred[$taxonomy], $terms) );
    3724                 return true;
    3725         }
    3726 
    3727         return wp_update_term_count_now( $terms, $taxonomy );
    3728 }
    3729 
    3730 /**
    3731  * Perform term count update immediately.
    3732  *
    3733  * @since 2.5.0
    3734  *
    3735  * @param array  $terms    The term_taxonomy_id of terms to update.
    3736  * @param string $taxonomy The context of the term.
    3737  * @return true Always true when complete.
    3738  */
    3739 function wp_update_term_count_now( $terms, $taxonomy ) {
    3740         $terms = array_map('intval', $terms);
    3741 
    3742         $taxonomy = get_taxonomy($taxonomy);
    3743         if ( !empty($taxonomy->update_count_callback) ) {
    3744                 call_user_func($taxonomy->update_count_callback, $terms, $taxonomy);
    3745         } else {
    3746                 $object_types = (array) $taxonomy->object_type;
    3747                 foreach ( $object_types as &$object_type ) {
    3748                         if ( 0 === strpos( $object_type, 'attachment:' ) )
    3749                                 list( $object_type ) = explode( ':', $object_type );
    3750                 }
    3751 
    3752                 if ( $object_types == array_filter( $object_types, 'post_type_exists' ) ) {
    3753                         // Only post types are attached to this taxonomy
    3754                         _update_post_term_count( $terms, $taxonomy );
    3755                 } else {
    3756                         // Default count updater
    3757                         _update_generic_term_count( $terms, $taxonomy );
    3758                 }
    3759         }
    3760 
    3761         clean_term_cache($terms, '', false);
    3762 
    3763         return true;
    3764 }
    3765 
    3766 //
    3767 // Cache
    3768 //
    3769 
    3770 /**
    3771  * Removes the taxonomy relationship to terms from the cache.
    3772  *
    3773  * Will remove the entire taxonomy relationship containing term `$object_id`. The
    3774  * term IDs have to exist within the taxonomy `$object_type` for the deletion to
    3775  * take place.
    3776  *
    3777  * @since 2.3.0
    3778  *
    3779  * @see get_object_taxonomies() for more on $object_type.
    3780  *
    3781  * @param int|array    $object_ids  Single or list of term object ID(s).
    3782  * @param array|string $object_type The taxonomy object type.
    3783  */
    3784 function clean_object_term_cache($object_ids, $object_type) {
    3785         if ( !is_array($object_ids) )
    3786                 $object_ids = array($object_ids);
    3787 
    3788         $taxonomies = get_object_taxonomies( $object_type );
    3789 
    3790         foreach ( $object_ids as $id ) {
    3791                 foreach ( $taxonomies as $taxonomy ) {
    3792                         wp_cache_delete($id, "{$taxonomy}_relationships");
    3793                 }
    3794         }
    3795 
    3796         /**
    3797          * Fires after the object term cache has been cleaned.
    3798          *
    3799          * @since 2.5.0
    3800          *
    3801          * @param array  $object_ids An array of object IDs.
    3802          * @param string $objet_type Object type.
    3803          */
    3804         do_action( 'clean_object_term_cache', $object_ids, $object_type );
    3805 }
    3806 
    3807 /**
    3808  * Will remove all of the term ids from the cache.
    3809  *
    3810  * @since 2.3.0
    3811  *
    3812  * @global wpdb $wpdb WordPress database abstraction object.
    3813  * @global bool $_wp_suspend_cache_invalidation
    3814  *
    3815  * @param int|array $ids            Single or list of Term IDs.
    3816  * @param string    $taxonomy       Optional. Can be empty and will assume `tt_ids`, else will use for context.
    3817  *                                  Default empty.
    3818  * @param bool      $clean_taxonomy Optional. Whether to clean taxonomy wide caches (true), or just individual
    3819  *                                  term object caches (false). Default true.
    3820  */
    3821 function clean_term_cache($ids, $taxonomy = '', $clean_taxonomy = true) {
    3822         global $wpdb, $_wp_suspend_cache_invalidation;
    3823 
    3824         if ( ! empty( $_wp_suspend_cache_invalidation ) ) {
    3825                 return;
    3826         }
    3827 
    3828         if ( !is_array($ids) )
    3829                 $ids = array($ids);
    3830 
    3831         $taxonomies = array();
    3832         // If no taxonomy, assume tt_ids.
    3833         if ( empty($taxonomy) ) {
    3834                 $tt_ids = array_map('intval', $ids);
    3835                 $tt_ids = implode(', ', $tt_ids);
    3836                 $terms = $wpdb->get_results("SELECT term_id, taxonomy FROM $wpdb->term_taxonomy WHERE term_taxonomy_id IN ($tt_ids)");
    3837                 $ids = array();
    3838                 foreach ( (array) $terms as $term ) {
    3839                         $taxonomies[] = $term->taxonomy;
    3840                         $ids[] = $term->term_id;
    3841                         wp_cache_delete($term->term_id, $term->taxonomy);
    3842                 }
    3843                 $taxonomies = array_unique($taxonomies);
    3844         } else {
    3845                 $taxonomies = array($taxonomy);
    3846                 foreach ( $taxonomies as $taxonomy ) {
    3847                         foreach ( $ids as $id ) {
    3848                                 wp_cache_delete($id, $taxonomy);
    3849                         }
    3850                 }
    3851         }
    3852 
    3853         foreach ( $taxonomies as $taxonomy ) {
    3854                 if ( $clean_taxonomy ) {
    3855                         wp_cache_delete('all_ids', $taxonomy);
    3856                         wp_cache_delete('get', $taxonomy);
    3857                         delete_option("{$taxonomy}_children");
    3858                         // Regenerate {$taxonomy}_children
    3859                         _get_term_hierarchy($taxonomy);
    3860                 }
    3861 
    3862                 /**
    3863                  * Fires once after each taxonomy's term cache has been cleaned.
    3864                  *
    3865                  * @since 2.5.0
    3866                  *
    3867                  * @param array  $ids      An array of term IDs.
    3868                  * @param string $taxonomy Taxonomy slug.
    3869                  */
    3870                 do_action( 'clean_term_cache', $ids, $taxonomy );
    3871         }
    3872 
    3873         wp_cache_set( 'last_changed', microtime(), 'terms' );
    3874 }
    3875 
    3876 /**
    3877  * Retrieves the taxonomy relationship to the term object id.
    3878  *
    3879  * @since 2.3.0
    3880  *
    3881  * @param int    $id       Term object ID.
    3882  * @param string $taxonomy Taxonomy name.
    3883  * @return bool|mixed Empty array if $terms found, but not `$taxonomy`. False if nothing is in cache
    3884  *                    for `$taxonomy` and `$id`.
    3885  */
    3886 function get_object_term_cache( $id, $taxonomy ) {
    3887         return wp_cache_get( $id, "{$taxonomy}_relationships" );
    3888 }
    3889 
    3890 /**
    3891  * Updates the cache for the given term object ID(s).
    3892  *
    3893  * Note: Due to performance concerns, great care should be taken to only update
    3894  * term caches when necessary. Processing time can increase exponentially depending
    3895  * on both the number of passed term IDs and the number of taxonomies those terms
    3896  * belong to.
    3897  *
    3898  * Caches will only be updated for terms not already cached.
    3899  *
    3900  * @since 2.3.0
    3901  *
    3902  * @param string|array $object_ids  Comma-separated list or array of term object IDs.
    3903  * @param array|string $object_type The taxonomy object type.
    3904  * @return void|false False if all of the terms in `$object_ids` are already cached.
    3905  */
    3906 function update_object_term_cache($object_ids, $object_type) {
    3907         if ( empty($object_ids) )
    3908                 return;
    3909 
    3910         if ( !is_array($object_ids) )
    3911                 $object_ids = explode(',', $object_ids);
    3912 
    3913         $object_ids = array_map('intval', $object_ids);
    3914 
    3915         $taxonomies = get_object_taxonomies($object_type);
    3916 
    3917         $ids = array();
    3918         foreach ( (array) $object_ids as $id ) {
    3919                 foreach ( $taxonomies as $taxonomy ) {
    3920                         if ( false === wp_cache_get($id, "{$taxonomy}_relationships") ) {
    3921                                 $ids[] = $id;
    3922                                 break;
    3923                         }
    3924                 }
    3925         }
    3926 
    3927         if ( empty( $ids ) )
    3928                 return false;
    3929 
    3930         $terms = wp_get_object_terms($ids, $taxonomies, array('fields' => 'all_with_object_id'));
    3931 
    3932         $object_terms = array();
    3933         foreach ( (array) $terms as $term )
    3934                 $object_terms[$term->object_id][$term->taxonomy][] = $term;
    3935 
    3936         foreach ( $ids as $id ) {
    3937                 foreach ( $taxonomies as $taxonomy ) {
    3938                         if ( ! isset($object_terms[$id][$taxonomy]) ) {
    3939                                 if ( !isset($object_terms[$id]) )
    3940                                         $object_terms[$id] = array();
    3941                                 $object_terms[$id][$taxonomy] = array();
    3942                         }
    3943                 }
    3944         }
    3945 
    3946         foreach ( $object_terms as $id => $value ) {
    3947                 foreach ( $value as $taxonomy => $terms ) {
    3948                         wp_cache_add( $id, $terms, "{$taxonomy}_relationships" );
    3949                 }
    3950         }
    3951 }
    3952 
    3953 /**
    3954  * Updates Terms to Taxonomy in cache.
    3955  *
    3956  * @since 2.3.0
    3957  *
    3958  * @param array  $terms    List of term objects to change.
    3959  * @param string $taxonomy Optional. Update Term to this taxonomy in cache. Default empty.
    3960  */
    3961 function update_term_cache( $terms, $taxonomy = '' ) {
    3962         foreach ( (array) $terms as $term ) {
    3963                 $term_taxonomy = $taxonomy;
    3964                 if ( empty($term_taxonomy) )
    3965                         $term_taxonomy = $term->taxonomy;
    3966 
    3967                 wp_cache_add( $term->term_id, $term, $term_taxonomy );
    3968         }
    3969 }
    3970 
    3971 //
    3972 // Private
    3973 //
    3974 
    3975 /**
    3976  * Retrieves children of taxonomy as Term IDs.
    3977  *
    3978  * @ignore
    3979  * @since 2.3.0
    3980  *
    3981  * @param string $taxonomy Taxonomy name.
    3982  * @return array Empty if $taxonomy isn't hierarchical or returns children as Term IDs.
    3983  */
    3984 function _get_term_hierarchy( $taxonomy ) {
    3985         if ( !is_taxonomy_hierarchical($taxonomy) )
    3986                 return array();
    3987         $children = get_option("{$taxonomy}_children");
    3988 
    3989         if ( is_array($children) )
    3990                 return $children;
    3991         $children = array();
    3992         $terms = get_terms($taxonomy, array('get' => 'all', 'orderby' => 'id', 'fields' => 'id=>parent'));
    3993         foreach ( $terms as $term_id => $parent ) {
    3994                 if ( $parent > 0 )
    3995                         $children[$parent][] = $term_id;
    3996         }
    3997         update_option("{$taxonomy}_children", $children);
    3998 
    3999         return $children;
    4000 }
    4001 
    4002 /**
    4003  * Get the subset of $terms that are descendants of $term_id.
    4004  *
    4005  * If `$terms` is an array of objects, then _get_term_children() returns an array of objects.
    4006  * If `$terms` is an array of IDs, then _get_term_children() returns an array of IDs.
    4007  *
    4008  * @access private
    4009  * @since 2.3.0
    4010  *
    4011  * @param int    $term_id   The ancestor term: all returned terms should be descendants of `$term_id`.
    4012  * @param array  $terms     The set of terms - either an array of term objects or term IDs - from which those that
    4013  *                          are descendants of $term_id will be chosen.
    4014  * @param string $taxonomy  The taxonomy which determines the hierarchy of the terms.
    4015  * @param array  $ancestors Optional. Term ancestors that have already been identified. Passed by reference, to keep
    4016  *                          track of found terms when recursing the hierarchy. The array of located ancestors is used
    4017  *                          to prevent infinite recursion loops. For performance, `term_ids` are used as array keys,
    4018  *                          with 1 as value. Default empty array.
    4019  * @return array|WP_Error The subset of $terms that are descendants of $term_id.
    4020  */
    4021 function _get_term_children( $term_id, $terms, $taxonomy, &$ancestors = array() ) {
    4022         $empty_array = array();
    4023         if ( empty($terms) )
    4024                 return $empty_array;
    4025 
    4026         $term_list = array();
    4027         $has_children = _get_term_hierarchy($taxonomy);
    4028 
    4029         if  ( ( 0 != $term_id ) && ! isset($has_children[$term_id]) )
    4030                 return $empty_array;
    4031 
    4032         // Include the term itself in the ancestors array, so we can properly detect when a loop has occurred.
    4033         if ( empty( $ancestors ) ) {
    4034                 $ancestors[ $term_id ] = 1;
    4035         }
    4036 
    4037         foreach ( (array) $terms as $term ) {
    4038                 $use_id = false;
    4039                 if ( !is_object($term) ) {
    4040                         $term = get_term($term, $taxonomy);
    4041                         if ( is_wp_error( $term ) )
    4042                                 return $term;
    4043                         $use_id = true;
    4044                 }
    4045 
    4046                 // Don't recurse if we've already identified the term as a child - this indicates a loop.
    4047                 if ( isset( $ancestors[ $term->term_id ] ) ) {
    4048                         continue;
    4049                 }
    4050 
    4051                 if ( $term->parent == $term_id ) {
    4052                         if ( $use_id )
    4053                                 $term_list[] = $term->term_id;
    4054                         else
    4055                                 $term_list[] = $term;
    4056 
    4057                         if ( !isset($has_children[$term->term_id]) )
    4058                                 continue;
    4059 
    4060                         $ancestors[ $term->term_id ] = 1;
    4061 
    4062                         if ( $children = _get_term_children( $term->term_id, $terms, $taxonomy, $ancestors) )
    4063                                 $term_list = array_merge($term_list, $children);
    4064                 }
    4065         }
    4066 
    4067         return $term_list;
    4068 }
    4069 
    4070 /**
    4071  * Add count of children to parent count.
    4072  *
    4073  * Recalculates term counts by including items from child terms. Assumes all
    4074  * relevant children are already in the $terms argument.
    4075  *
    4076  * @access private
    4077  * @since 2.3.0
    4078  *
    4079  * @global wpdb $wpdb WordPress database abstraction object.
    4080  *
    4081  * @param array  $terms    List of term IDs, passed by reference.
    4082  * @param string $taxonomy Term context.
    4083  */
    4084 function _pad_term_counts( &$terms, $taxonomy ) {
    4085         global $wpdb;
    4086 
    4087         // This function only works for hierarchical taxonomies like post categories.
    4088         if ( !is_taxonomy_hierarchical( $taxonomy ) )
    4089                 return;
    4090 
    4091         $term_hier = _get_term_hierarchy($taxonomy);
    4092 
    4093         if ( empty($term_hier) )
    4094                 return;
    4095 
    4096         $term_items = array();
    4097         $terms_by_id = array();
    4098         $term_ids = array();
    4099 
    4100         foreach ( (array) $terms as $key => $term ) {
    4101                 $terms_by_id[$term->term_id] = & $terms[$key];
    4102                 $term_ids[$term->term_taxonomy_id] = $term->term_id;
    4103         }
    4104 
    4105         // Get the object and term ids and stick them in a lookup table.
    4106         $tax_obj = get_taxonomy($taxonomy);
    4107         $object_types = esc_sql($tax_obj->object_type);
    4108         $results = $wpdb->get_results("SELECT object_id, term_taxonomy_id FROM $wpdb->term_relationships INNER JOIN $wpdb->posts ON object_id = ID WHERE term_taxonomy_id IN (" . implode(',', array_keys($term_ids)) . ") AND post_type IN ('" . implode("', '", $object_types) . "') AND post_status = 'publish'");
    4109         foreach ( $results as $row ) {
    4110                 $id = $term_ids[$row->term_taxonomy_id];
    4111                 $term_items[$id][$row->object_id] = isset($term_items[$id][$row->object_id]) ? ++$term_items[$id][$row->object_id] : 1;
    4112         }
    4113 
    4114         // Touch every ancestor's lookup row for each post in each term.
    4115         foreach ( $term_ids as $term_id ) {
    4116                 $child = $term_id;
    4117                 $ancestors = array();
    4118                 while ( !empty( $terms_by_id[$child] ) && $parent = $terms_by_id[$child]->parent ) {
    4119                         $ancestors[] = $child;
    4120                         if ( !empty( $term_items[$term_id] ) )
    4121                                 foreach ( $term_items[$term_id] as $item_id => $touches ) {
    4122                                         $term_items[$parent][$item_id] = isset($term_items[$parent][$item_id]) ? ++$term_items[$parent][$item_id]: 1;
    4123                                 }
    4124                         $child = $parent;
    4125 
    4126                         if ( in_array( $parent, $ancestors ) ) {
    4127                                 break;
    4128                         }
    4129                 }
    4130         }
    4131 
    4132         // Transfer the touched cells.
    4133         foreach ( (array) $term_items as $id => $items )
    4134                 if ( isset($terms_by_id[$id]) )
    4135                         $terms_by_id[$id]->count = count($items);
    4136 }
    4137 
    4138 //
    4139 // Default callbacks
    4140 //
    4141 
    4142 /**
    4143  * Will update term count based on object types of the current taxonomy.
    4144  *
    4145  * Private function for the default callback for post_tag and category
    4146  * taxonomies.
    4147  *
    4148  * @access private
    4149  * @since 2.3.0
    4150  *
    4151  * @global wpdb $wpdb WordPress database abstraction object.
    4152  *
    4153  * @param array  $terms    List of Term taxonomy IDs.
    4154  * @param object $taxonomy Current taxonomy object of terms.
    4155  */
    4156 function _update_post_term_count( $terms, $taxonomy ) {
    4157         global $wpdb;
    4158 
    4159         $object_types = (array) $taxonomy->object_type;
    4160 
    4161         foreach ( $object_types as &$object_type )
    4162                 list( $object_type ) = explode( ':', $object_type );
    4163 
    4164         $object_types = array_unique( $object_types );
    4165 
    4166         if ( false !== ( $check_attachments = array_search( 'attachment', $object_types ) ) ) {
    4167                 unset( $object_types[ $check_attachments ] );
    4168                 $check_attachments = true;
    4169         }
    4170 
    4171         if ( $object_types )
    4172                 $object_types = esc_sql( array_filter( $object_types, 'post_type_exists' ) );
    4173 
    4174         foreach ( (array) $terms as $term ) {
    4175                 $count = 0;
    4176 
    4177                 // Attachments can be 'inherit' status, we need to base count off the parent's status if so.
    4178                 if ( $check_attachments )
    4179                         $count += (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_relationships, $wpdb->posts p1 WHERE p1.ID = $wpdb->term_relationships.object_id AND ( post_status = 'publish' OR ( post_status = 'inherit' AND post_parent > 0 AND ( SELECT post_status FROM $wpdb->posts WHERE ID = p1.post_parent ) = 'publish' ) ) AND post_type = 'attachment' AND term_taxonomy_id = %d", $term ) );
    4180 
    4181                 if ( $object_types )
    4182                         $count += (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_relationships, $wpdb->posts WHERE $wpdb->posts.ID = $wpdb->term_relationships.object_id AND post_status = 'publish' AND post_type IN ('" . implode("', '", $object_types ) . "') AND term_taxonomy_id = %d", $term ) );
    4183 
    4184                 /** This action is documented in wp-includes/taxonomy.php */
    4185                 do_action( 'edit_term_taxonomy', $term, $taxonomy->name );
    4186                 $wpdb->update( $wpdb->term_taxonomy, compact( 'count' ), array( 'term_taxonomy_id' => $term ) );
    4187 
    4188                 /** This action is documented in wp-includes/taxonomy.php */
    4189                 do_action( 'edited_term_taxonomy', $term, $taxonomy->name );
    4190         }
    4191 }
    4192 
    4193 /**
    4194  * Will update term count based on number of objects.
    4195  *
    4196  * Default callback for the 'link_category' taxonomy.
    4197  *
    4198  * @since 3.3.0
    4199  *
    4200  * @global wpdb $wpdb WordPress database abstraction object.
    4201  *
    4202  * @param array  $terms    List of term taxonomy IDs.
    4203  * @param object $taxonomy Current taxonomy object of terms.
    4204  */
    4205 function _update_generic_term_count( $terms, $taxonomy ) {
    4206         global $wpdb;
    4207 
    4208         foreach ( (array) $terms as $term ) {
    4209                 $count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_relationships WHERE term_taxonomy_id = %d", $term ) );
    4210 
    4211                 /** This action is documented in wp-includes/taxonomy.php */
    4212                 do_action( 'edit_term_taxonomy', $term, $taxonomy->name );
    4213                 $wpdb->update( $wpdb->term_taxonomy, compact( 'count' ), array( 'term_taxonomy_id' => $term ) );
    4214 
    4215                 /** This action is documented in wp-includes/taxonomy.php */
    4216                 do_action( 'edited_term_taxonomy', $term, $taxonomy->name );
    4217         }
    4218 }
    4219 
    4220 /**
    4221  * Create a new term for a term_taxonomy item that currently shares its term
    4222  * with another term_taxonomy.
    4223  *
    4224  * @ignore
    4225  * @since 4.2.0
    4226  * @since 4.3.0 Introduced `$record` parameter. Also, `$term_id` and
    4227  *              `$term_taxonomy_id` can now accept objects.
    4228  *
    4229  * @global wpdb $wpdb
    4230  *
    4231  * @param int|object $term_id          ID of the shared term, or the shared term object.
    4232  * @param int|object $term_taxonomy_id ID of the term_taxonomy item to receive a new term, or the term_taxonomy object
    4233  *                                     (corresponding to a row from the term_taxonomy table).
    4234  * @param bool       $record           Whether to record data about the split term in the options table. The recording
    4235  *                                     process has the potential to be resource-intensive, so during batch operations
    4236  *                                     it can be beneficial to skip inline recording and do it just once, after the
    4237  *                                     batch is processed. Only set this to `false` if you know what you are doing.
    4238  *                                     Default: true.
    4239  * @return int|WP_Error When the current term does not need to be split (or cannot be split on the current
    4240  *                      database schema), `$term_id` is returned. When the term is successfully split, the
    4241  *                      new term_id is returned. A WP_Error is returned for miscellaneous errors.
    4242  */
    4243 function _split_shared_term( $term_id, $term_taxonomy_id, $record = true ) {
    4244         global $wpdb;
    4245 
    4246         if ( is_object( $term_id ) ) {
    4247                 $shared_term = $term_id;
    4248                 $term_id = intval( $shared_term->term_id );
    4249         }
    4250 
    4251         if ( is_object( $term_taxonomy_id ) ) {
    4252                 $term_taxonomy = $term_taxonomy_id;
    4253                 $term_taxonomy_id = intval( $term_taxonomy->term_taxonomy_id );
    4254         }
    4255 
    4256         // If there are no shared term_taxonomy rows, there's nothing to do here.
    4257         $shared_tt_count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_taxonomy tt WHERE tt.term_id = %d AND tt.term_taxonomy_id != %d", $term_id, $term_taxonomy_id ) );
    4258 
    4259         if ( ! $shared_tt_count ) {
    4260                 return $term_id;
    4261         }
    4262 
    4263         /*
    4264          * Verify that the term_taxonomy_id passed to the function is actually associated with the term_id.
    4265          * If there's a mismatch, it may mean that the term is already split. Return the actual term_id from the db.
    4266          */
    4267         $check_term_id = $wpdb->get_var( $wpdb->prepare( "SELECT term_id FROM $wpdb->term_taxonomy WHERE term_taxonomy_id = %d", $term_taxonomy_id ) );
    4268         if ( $check_term_id != $term_id ) {
    4269                 return $check_term_id;
    4270         }
    4271 
    4272         // Pull up data about the currently shared slug, which we'll use to populate the new one.
    4273         if ( empty( $shared_term ) ) {
    4274                 $shared_term = $wpdb->get_row( $wpdb->prepare( "SELECT t.* FROM $wpdb->terms t WHERE t.term_id = %d", $term_id ) );
    4275         }
    4276 
    4277         $new_term_data = array(
    4278                 'name' => $shared_term->name,
    4279                 'slug' => $shared_term->slug,
    4280                 'term_group' => $shared_term->term_group,
    4281         );
    4282 
    4283         if ( false === $wpdb->insert( $wpdb->terms, $new_term_data ) ) {
    4284                 return new WP_Error( 'db_insert_error', __( 'Could not split shared term.' ), $wpdb->last_error );
    4285         }
    4286 
    4287         $new_term_id = (int) $wpdb->insert_id;
    4288 
    4289         // Update the existing term_taxonomy to point to the newly created term.
    4290         $wpdb->update( $wpdb->term_taxonomy,
    4291                 array( 'term_id' => $new_term_id ),
    4292                 array( 'term_taxonomy_id' => $term_taxonomy_id )
    4293         );
    4294 
    4295         // Reassign child terms to the new parent.
    4296         if ( empty( $term_taxonomy ) ) {
    4297                 $term_taxonomy = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->term_taxonomy WHERE term_taxonomy_id = %d", $term_taxonomy_id ) );
    4298         }
    4299 
    4300         $children_tt_ids = $wpdb->get_col( $wpdb->prepare( "SELECT term_taxonomy_id FROM $wpdb->term_taxonomy WHERE parent = %d AND taxonomy = %s", $term_id, $term_taxonomy->taxonomy ) );
    4301         if ( ! empty( $children_tt_ids ) ) {
    4302                 foreach ( $children_tt_ids as $child_tt_id ) {
    4303                         $wpdb->update( $wpdb->term_taxonomy,
    4304                                 array( 'parent' => $new_term_id ),
    4305                                 array( 'term_taxonomy_id' => $child_tt_id )
    4306                         );
    4307                         clean_term_cache( $term_id, $term_taxonomy->taxonomy );
    4308                 }
    4309         } else {
    4310                 // If the term has no children, we must force its taxonomy cache to be rebuilt separately.
    4311                 clean_term_cache( $new_term_id, $term_taxonomy->taxonomy );
    4312         }
    4313 
    4314         // Clean the cache for term taxonomies formerly shared with the current term.
    4315         $shared_term_taxonomies = $wpdb->get_row( $wpdb->prepare( "SELECT taxonomy FROM $wpdb->term_taxonomy WHERE term_id = %d", $term_id ) );
    4316         if ( $shared_term_taxonomies ) {
    4317                 foreach ( $shared_term_taxonomies as $shared_term_taxonomy ) {
    4318                         clean_term_cache( $term_id, $shared_term_taxonomy );
    4319                 }
    4320         }
    4321 
    4322         // Keep a record of term_ids that have been split, keyed by old term_id. See {@see wp_get_split_term()}.
    4323         if ( $record ) {
    4324                 $split_term_data = get_option( '_split_terms', array() );
    4325                 if ( ! isset( $split_term_data[ $term_id ] ) ) {
    4326                         $split_term_data[ $term_id ] = array();
    4327                 }
    4328 
    4329                 $split_term_data[ $term_id ][ $term_taxonomy->taxonomy ] = $new_term_id;
    4330                 update_option( '_split_terms', $split_term_data );
    4331         }
    4332 
    4333         /**
    4334          * Fires after a previously shared taxonomy term is split into two separate terms.
    4335          *
    4336          * @since 4.2.0
    4337          *
    4338          * @param int    $term_id          ID of the formerly shared term.
    4339          * @param int    $new_term_id      ID of the new term created for the $term_taxonomy_id.
    4340          * @param int    $term_taxonomy_id ID for the term_taxonomy row affected by the split.
    4341          * @param string $taxonomy         Taxonomy for the split term.
    4342          */
    4343         do_action( 'split_shared_term', $term_id, $new_term_id, $term_taxonomy_id, $term_taxonomy->taxonomy );
    4344 
    4345         return $new_term_id;
    4346 }
    4347 
    4348 /**
    4349  * Splits a batch of shared taxonomy terms.
    4350  *
    4351  * @since 4.3.0
    4352  *
    4353  * @global wpdb $wpdb WordPress database abstraction object.
    4354  */
    4355 function _wp_batch_split_terms() {
    4356         global $wpdb;
    4357 
    4358         $lock_name = 'term_split.lock';
    4359 
    4360         // Try to lock.
    4361         $lock_result = $wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO `$wpdb->options` ( `option_name`, `option_value`, `autoload` ) VALUES (%s, %s, 'no') /* LOCK */", $lock_name, time() ) );
    4362 
    4363         if ( ! $lock_result ) {
    4364                 $lock_result = get_option( $lock_name );
    4365 
    4366                 // Bail if we were unable to create a lock, or if the existing lock is still valid.
    4367                 if ( ! $lock_result || ( $lock_result > ( time() - HOUR_IN_SECONDS ) ) ) {
    4368                         wp_schedule_single_event( time() + ( 5 * MINUTE_IN_SECONDS ), 'wp_split_shared_term_batch' );
    4369                         return;
    4370                 }
    4371         }
    4372 
    4373         // Update the lock, as by this point we've definitely got a lock, just need to fire the actions.
    4374         update_option( $lock_name, time() );
    4375 
    4376         // Get a list of shared terms (those with more than one associated row in term_taxonomy).
    4377         $shared_terms = $wpdb->get_results(
    4378                 "SELECT tt.term_id, t.*, count(*) as term_tt_count FROM {$wpdb->term_taxonomy} tt
    4379                  LEFT JOIN {$wpdb->terms} t ON t.term_id = tt.term_id
    4380                  GROUP BY t.term_id
    4381                  HAVING term_tt_count > 1
    4382                  LIMIT 10"
    4383         );
    4384 
    4385         // No more terms, we're done here.
    4386         if ( ! $shared_terms ) {
    4387                 update_option( 'finished_splitting_shared_terms', true );
    4388                 delete_option( $lock_name );
    4389                 return;
    4390         }
    4391 
    4392         // Shared terms found? We'll need to run this script again.
    4393         wp_schedule_single_event( time() + ( 2 * MINUTE_IN_SECONDS ), 'wp_split_shared_term_batch' );
    4394 
    4395         // Rekey shared term array for faster lookups.
    4396         $_shared_terms = array();
    4397         foreach ( $shared_terms as $shared_term ) {
    4398                 $term_id = intval( $shared_term->term_id );
    4399                 $_shared_terms[ $term_id ] = $shared_term;
    4400         }
    4401         $shared_terms = $_shared_terms;
    4402 
    4403         // Get term taxonomy data for all shared terms.
    4404         $shared_term_ids = implode( ',', array_keys( $shared_terms ) );
    4405         $shared_tts = $wpdb->get_results( "SELECT * FROM {$wpdb->term_taxonomy} WHERE `term_id` IN ({$shared_term_ids})" );
    4406 
    4407         // Split term data recording is slow, so we do it just once, outside the loop.
    4408         $split_term_data = get_option( '_split_terms', array() );
    4409         $skipped_first_term = $taxonomies = array();
    4410         foreach ( $shared_tts as $shared_tt ) {
    4411                 $term_id = intval( $shared_tt->term_id );
    4412 
    4413                 // Don't split the first tt belonging to a given term_id.
    4414                 if ( ! isset( $skipped_first_term[ $term_id ] ) ) {
    4415                         $skipped_first_term[ $term_id ] = 1;
    4416                         continue;
    4417                 }
    4418 
    4419                 if ( ! isset( $split_term_data[ $term_id ] ) ) {
    4420                         $split_term_data[ $term_id ] = array();
    4421                 }
    4422 
    4423                 // Keep track of taxonomies whose hierarchies need flushing.
    4424                 if ( ! isset( $taxonomies[ $shared_tt->taxonomy ] ) ) {
    4425                         $taxonomies[ $shared_tt->taxonomy ] = 1;
    4426                 }
    4427 
    4428                 // Split the term.
    4429                 $split_term_data[ $term_id ][ $shared_tt->taxonomy ] = _split_shared_term( $shared_terms[ $term_id ], $shared_tt, false );
    4430         }
    4431 
    4432         // Rebuild the cached hierarchy for each affected taxonomy.
    4433         foreach ( array_keys( $taxonomies ) as $tax ) {
    4434                 delete_option( "{$tax}_children" );
    4435                 _get_term_hierarchy( $tax );
    4436         }
    4437 
    4438         update_option( '_split_terms', $split_term_data );
    4439 
    4440         delete_option( $lock_name );
    4441 }
    4442 
    4443 /**
    4444  * In order to avoid the _wp_batch_split_terms() job being accidentally removed,
    4445  * check that it's still scheduled while we haven't finished splitting terms.
    4446  *
    4447  * @ignore
    4448  * @since 4.3.0
    4449  */
    4450 function _wp_check_for_scheduled_split_terms() {
    4451         if ( ! get_option( 'finished_splitting_shared_terms' ) && ! wp_next_scheduled( 'wp_split_shared_term_batch' ) ) {
    4452                 wp_schedule_single_event( time() + MINUTE_IN_SECONDS, 'wp_split_shared_term_batch' );
    4453         }
    4454 }
    4455 
    4456 /**
    4457  * Check default categories when a term gets split to see if any of them need to be updated.
    4458  *
    4459  * @ignore
    4460  * @since 4.2.0
    4461  *
    4462  * @param int    $term_id          ID of the formerly shared term.
    4463  * @param int    $new_term_id      ID of the new term created for the $term_taxonomy_id.
    4464  * @param int    $term_taxonomy_id ID for the term_taxonomy row affected by the split.
    4465  * @param string $taxonomy         Taxonomy for the split term.
    4466  */
    4467 function _wp_check_split_default_terms( $term_id, $new_term_id, $term_taxonomy_id, $taxonomy ) {
    4468         if ( 'category' != $taxonomy ) {
    4469                 return;
    4470         }
    4471 
    4472         foreach ( array( 'default_category', 'default_link_category', 'default_email_category' ) as $option ) {
    4473                 if ( $term_id == get_option( $option, -1 ) ) {
    4474                         update_option( $option, $new_term_id );
    4475                 }
    4476         }
    4477 }
    4478 
    4479 /**
    4480  * Check menu items when a term gets split to see if any of them need to be updated.
    4481  *
    4482  * @ignore
    4483  * @since 4.2.0
    4484  *
    4485  * @global wpdb $wpdb
    4486  *
    4487  * @param int    $term_id          ID of the formerly shared term.
    4488  * @param int    $new_term_id      ID of the new term created for the $term_taxonomy_id.
    4489  * @param int    $term_taxonomy_id ID for the term_taxonomy row affected by the split.
    4490  * @param string $taxonomy         Taxonomy for the split term.
    4491  */
    4492 function _wp_check_split_terms_in_menus( $term_id, $new_term_id, $term_taxonomy_id, $taxonomy ) {
    4493         global $wpdb;
    4494         $post_ids = $wpdb->get_col( $wpdb->prepare(
    4495                 "SELECT m1.post_id
    4496                 FROM {$wpdb->postmeta} AS m1
    4497                         INNER JOIN {$wpdb->postmeta} AS m2 ON ( m2.post_id = m1.post_id )
    4498                         INNER JOIN {$wpdb->postmeta} AS m3 ON ( m3.post_id = m1.post_id )
    4499                 WHERE ( m1.meta_key = '_menu_item_type' AND m1.meta_value = 'taxonomy' )
    4500                         AND ( m2.meta_key = '_menu_item_object' AND m2.meta_value = '%s' )
    4501                         AND ( m3.meta_key = '_menu_item_object_id' AND m3.meta_value = %d )",
    4502                 $taxonomy,
    4503                 $term_id
    4504         ) );
    4505 
    4506         if ( $post_ids ) {
    4507                 foreach ( $post_ids as $post_id ) {
    4508                         update_post_meta( $post_id, '_menu_item_object_id', $new_term_id, $term_id );
    4509                 }
    4510         }
    4511 }
    4512 
    4513 /**
    4514  * If the term being split is a nav_menu, change associations.
    4515  *
    4516  * @ignore
    4517  * @since 4.3.0
    4518  *
    4519  * @global wpdb $wpdb
    4520  *
    4521  * @param int    $term_id          ID of the formerly shared term.
    4522  * @param int    $new_term_id      ID of the new term created for the $term_taxonomy_id.
    4523  * @param int    $term_taxonomy_id ID for the term_taxonomy row affected by the split.
    4524  * @param string $taxonomy         Taxonomy for the split term.
    4525  */
    4526 function _wp_check_split_nav_menu_terms( $term_id, $new_term_id, $term_taxonomy_id, $taxonomy ) {
    4527         if ( 'nav_menu' !== $taxonomy ) {
    4528                 return;
    4529         }
    4530 
    4531         // Update menu locations.
    4532         $locations = get_nav_menu_locations();
    4533         foreach ( $locations as $location => $menu_id ) {
    4534                 if ( $term_id == $menu_id ) {
    4535                         $locations[ $location ] = $new_term_id;
    4536                 }
    4537         }
    4538         set_theme_mod( 'nav_menu_locations', $locations );
    4539 }
    4540 
    4541 /**
    4542  * Get data about terms that previously shared a single term_id, but have since been split.
    4543  *
    4544  * @since 4.2.0
    4545  *
    4546  * @param int $old_term_id Term ID. This is the old, pre-split term ID.
    4547  * @return array Array of new term IDs, keyed by taxonomy.
    4548  */
    4549 function wp_get_split_terms( $old_term_id ) {
    4550         $split_terms = get_option( '_split_terms', array() );
    4551 
    4552         $terms = array();
    4553         if ( isset( $split_terms[ $old_term_id ] ) ) {
    4554                 $terms = $split_terms[ $old_term_id ];
    4555         }
    4556 
    4557         return $terms;
    4558 }
    4559 
    4560 /**
    4561  * Get the new term ID corresponding to a previously split term.
    4562  *
    4563  * @since 4.2.0
    4564  *
    4565  * @param int    $old_term_id Term ID. This is the old, pre-split term ID.
    4566  * @param string $taxonomy    Taxonomy that the term belongs to.
    4567  * @return int|false If a previously split term is found corresponding to the old term_id and taxonomy,
    4568  *                   the new term_id will be returned. If no previously split term is found matching
    4569  *                   the parameters, returns false.
    4570  */
    4571 function wp_get_split_term( $old_term_id, $taxonomy ) {
    4572         $split_terms = wp_get_split_terms( $old_term_id );
    4573 
    4574         $term_id = false;
    4575         if ( isset( $split_terms[ $taxonomy ] ) ) {
    4576                 $term_id = (int) $split_terms[ $taxonomy ];
    4577         }
    4578 
    4579         return $term_id;
    4580 }
    4581 
    4582 /**
    4583  * Generate a permalink for a taxonomy term archive.
    4584  *
    4585  * @since 2.5.0
    4586  *
    4587  * @global WP_Rewrite $wp_rewrite
    4588  *
    4589  * @param object|int|string $term     The term object, ID, or slug whose link will be retrieved.
    4590  * @param string            $taxonomy Optional. Taxonomy. Default empty.
    4591  * @return string|WP_Error HTML link to taxonomy term archive on success, WP_Error if term does not exist.
    4592  */
    4593 function get_term_link( $term, $taxonomy = '' ) {
    4594         global $wp_rewrite;
    4595 
    4596         if ( !is_object($term) ) {
    4597                 if ( is_int( $term ) ) {
    4598                         $term = get_term( $term, $taxonomy );
    4599                 } else {
    4600                         $term = get_term_by( 'slug', $term, $taxonomy );
    4601                 }
    4602         }
    4603 
    4604         if ( !is_object($term) )
    4605                 $term = new WP_Error('invalid_term', __('Empty Term'));
    4606 
    4607         if ( is_wp_error( $term ) )
    4608                 return $term;
    4609 
    4610         $taxonomy = $term->taxonomy;
    4611 
    4612         $termlink = $wp_rewrite->get_extra_permastruct($taxonomy);
    4613 
    4614         $slug = $term->slug;
    4615         $t = get_taxonomy($taxonomy);
    4616 
    4617         if ( empty($termlink) ) {
    4618                 if ( 'category' == $taxonomy )
    4619                         $termlink = '?cat=' . $term->term_id;
    4620                 elseif ( $t->query_var )
    4621                         $termlink = "?$t->query_var=$slug";
    4622                 else
    4623                         $termlink = "?taxonomy=$taxonomy&term=$slug";
    4624                 $termlink = home_url($termlink);
    4625         } else {
    4626                 if ( $t->rewrite['hierarchical'] ) {
    4627                         $hierarchical_slugs = array();
    4628                         $ancestors = get_ancestors( $term->term_id, $taxonomy, 'taxonomy' );
    4629                         foreach ( (array)$ancestors as $ancestor ) {
    4630                                 $ancestor_term = get_term($ancestor, $taxonomy);
    4631                                 $hierarchical_slugs[] = $ancestor_term->slug;
    4632                         }
    4633                         $hierarchical_slugs = array_reverse($hierarchical_slugs);
    4634                         $hierarchical_slugs[] = $slug;
    4635                         $termlink = str_replace("%$taxonomy%", implode('/', $hierarchical_slugs), $termlink);
    4636                 } else {
    4637                         $termlink = str_replace("%$taxonomy%", $slug, $termlink);
    4638                 }
    4639                 $termlink = home_url( user_trailingslashit($termlink, 'category') );
    4640         }
    4641         // Back Compat filters.
    4642         if ( 'post_tag' == $taxonomy ) {
    4643 
    4644                 /**
    4645                  * Filter the tag link.
    4646                  *
    4647                  * @since 2.3.0
    4648                  * @deprecated 2.5.0 Use 'term_link' instead.
    4649                  *
    4650                  * @param string $termlink Tag link URL.
    4651                  * @param int    $term_id  Term ID.
    4652                  */
    4653                 $termlink = apply_filters( 'tag_link', $termlink, $term->term_id );
    4654         } elseif ( 'category' == $taxonomy ) {
    4655 
    4656                 /**
    4657                  * Filter the category link.
    4658                  *
    4659                  * @since 1.5.0
    4660                  * @deprecated 2.5.0 Use 'term_link' instead.
    4661                  *
    4662                  * @param string $termlink Category link URL.
    4663                  * @param int    $term_id  Term ID.
    4664                  */
    4665                 $termlink = apply_filters( 'category_link', $termlink, $term->term_id );
    4666         }
    4667 
    4668         /**
    4669          * Filter the term link.
    4670          *
    4671          * @since 2.5.0
    4672          *
    4673          * @param string $termlink Term link URL.
    4674          * @param object $term     Term object.
    4675          * @param string $taxonomy Taxonomy slug.
    4676          */
    4677         return apply_filters( 'term_link', $termlink, $term, $taxonomy );
    4678 }
    4679 
    4680 /**
    4681  * Display the taxonomies of a post with available options.
    4682  *
    4683  * This function can be used within the loop to display the taxonomies for a
    4684  * post without specifying the Post ID. You can also use it outside the Loop to
    4685  * display the taxonomies for a specific post.
    4686  *
    4687  * @since 2.5.0
    4688  *
    4689  * @param array $args {
    4690  *     Arguments about which post to use and how to format the output. Shares all of the arguments
    4691  *     supported by get_the_taxonomies(), in addition to the following.
    4692  *
    4693  *     @type  int|WP_Post $post   Post ID or object to get taxonomies of. Default current post.
    4694  *     @type  string      $before Displays before the taxonomies. Default empty string.
    4695  *     @type  string      $sep    Separates each taxonomy. Default is a space.
    4696  *     @type  string      $after  Displays after the taxonomies. Default empty string.
    4697  * }
    4698  * @param array $args See {@link get_the_taxonomies()} for a description of arguments and their defaults.
    4699  */
    4700 function the_taxonomies( $args = array() ) {
    4701         $defaults = array(
    4702                 'post' => 0,
    4703                 'before' => '',
    4704                 'sep' => ' ',
    4705                 'after' => '',
    4706         );
    4707 
    4708         $r = wp_parse_args( $args, $defaults );
    4709 
    4710         echo $r['before'] . join( $r['sep'], get_the_taxonomies( $r['post'], $r ) ) . $r['after'];
    4711 }
    4712 
    4713 /**
    4714  * Retrieve all taxonomies associated with a post.
    4715  *
    4716  * This function can be used within the loop. It will also return an array of
    4717  * the taxonomies with links to the taxonomy and name.
    4718  *
    4719  * @since 2.5.0
    4720  *
    4721  * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global $post.
    4722  * @param array $args {
    4723  *     Optional. Arguments about how to format the list of taxonomies. Default empty array.
    4724  *
    4725  *     @type string $template      Template for displaying a taxonomy label and list of terms.
    4726  *                                 Default is "Label: Terms."
    4727  *     @type string $term_template Template for displaying a single term in the list. Default is the term name
    4728  *                                 linked to its archive.
    4729  * }
    4730  * @return array List of taxonomies.
    4731  */
    4732 function get_the_taxonomies( $post = 0, $args = array() ) {
    4733         $post = get_post( $post );
    4734 
    4735         $args = wp_parse_args( $args, array(
    4736                 /* translators: %s: taxonomy label, %l: list of terms formatted as per $term_template */
    4737                 'template' => __( '%s: %l.' ),
    4738                 'term_template' => '<a href="%1$s">%2$s</a>',
    4739         ) );
    4740 
    4741         $taxonomies = array();
    4742 
    4743         if ( ! $post ) {
    4744                 return $taxonomies;
    4745         }
    4746 
    4747         foreach ( get_object_taxonomies( $post ) as $taxonomy ) {
    4748                 $t = (array) get_taxonomy( $taxonomy );
    4749                 if ( empty( $t['label'] ) ) {
    4750                         $t['label'] = $taxonomy;
    4751                 }
    4752                 if ( empty( $t['args'] ) ) {
    4753                         $t['args'] = array();
    4754                 }
    4755                 if ( empty( $t['template'] ) ) {
    4756                         $t['template'] = $args['template'];
    4757                 }
    4758                 if ( empty( $t['term_template'] ) ) {
    4759                         $t['term_template'] = $args['term_template'];
    4760                 }
    4761 
    4762                 $terms = get_object_term_cache( $post->ID, $taxonomy );
    4763                 if ( false === $terms ) {
    4764                         $terms = wp_get_object_terms( $post->ID, $taxonomy, $t['args'] );
    4765                 }
    4766                 $links = array();
    4767 
    4768                 foreach ( $terms as $term ) {
    4769                         $links[] = wp_sprintf( $t['term_template'], esc_attr( get_term_link( $term ) ), $term->name );
    4770                 }
    4771                 if ( $links ) {
    4772                         $taxonomies[$taxonomy] = wp_sprintf( $t['template'], $t['label'], $links, $terms );
    4773                 }
    4774         }
    4775         return $taxonomies;
    4776 }
    4777 
    4778 /**
    4779  * Retrieve all taxonomies of a post with just the names.
    4780  *
    4781  * @since 2.5.0
    4782  *
    4783  * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global $post.
    4784  * @return array
    4785  */
    4786 function get_post_taxonomies( $post = 0 ) {
    4787         $post = get_post( $post );
    4788 
    4789         return get_object_taxonomies($post);
    4790 }
    4791 
    4792 /**
    4793  * Determine if the given object is associated with any of the given terms.
    4794  *
    4795  * The given terms are checked against the object's terms' term_ids, names and slugs.
    4796  * Terms given as integers will only be checked against the object's terms' term_ids.
    4797  * If no terms are given, determines if object is associated with any terms in the given taxonomy.
    4798  *
    4799  * @since 2.7.0
    4800  *
    4801  * @param int              $object_id ID of the object (post ID, link ID, ...).
    4802  * @param string           $taxonomy  Single taxonomy name.
    4803  * @param int|string|array $terms     Optional. Term term_id, name, slug or array of said. Default null.
    4804  * @return bool|WP_Error WP_Error on input error.
    4805  */
    4806 function is_object_in_term( $object_id, $taxonomy, $terms = null ) {
    4807         if ( !$object_id = (int) $object_id )
    4808                 return new WP_Error( 'invalid_object', __( 'Invalid object ID' ) );
    4809 
    4810         $object_terms = get_object_term_cache( $object_id, $taxonomy );
    4811         if ( false === $object_terms )
    4812                  $object_terms = wp_get_object_terms( $object_id, $taxonomy );
    4813 
    4814         if ( is_wp_error( $object_terms ) )
    4815                 return $object_terms;
    4816         if ( empty( $object_terms ) )
    4817                 return false;
    4818         if ( empty( $terms ) )
    4819                 return ( !empty( $object_terms ) );
    4820 
    4821         $terms = (array) $terms;
    4822 
    4823         if ( $ints = array_filter( $terms, 'is_int' ) )
    4824                 $strs = array_diff( $terms, $ints );
    4825         else
    4826                 $strs =& $terms;
    4827 
    4828         foreach ( $object_terms as $object_term ) {
    4829                 // If term is an int, check against term_ids only.
    4830                 if ( $ints && in_array( $object_term->term_id, $ints ) ) {
    4831                         return true;
    4832                 }
    4833 
    4834                 if ( $strs ) {
    4835                         // Only check numeric strings against term_id, to avoid false matches due to type juggling.
    4836                         $numeric_strs = array_map( 'intval', array_filter( $strs, 'is_numeric' ) );
    4837                         if ( in_array( $object_term->term_id, $numeric_strs, true ) ) {
    4838                                 return true;
    4839                         }
    4840 
    4841                         if ( in_array( $object_term->name, $strs ) ) return true;
    4842                         if ( in_array( $object_term->slug, $strs ) ) return true;
    4843                 }
    4844         }
    4845 
    4846         return false;
    4847 }
    4848 
    4849 /**
    4850  * Determine if the given object type is associated with the given taxonomy.
    4851  *
    4852  * @since 3.0.0
    4853  *
    4854  * @param string $object_type Object type string.
    4855  * @param string $taxonomy    Single taxonomy name.
    4856  * @return bool True if object is associated with the taxonomy, otherwise false.
    4857  */
    4858 function is_object_in_taxonomy( $object_type, $taxonomy ) {
    4859         $taxonomies = get_object_taxonomies( $object_type );
    4860         if ( empty( $taxonomies ) ) {
    4861                 return false;
    4862         }
    4863         return in_array( $taxonomy, $taxonomies );
    4864 }
    4865 
    4866 /**
    4867  * Get an array of ancestor IDs for a given object.
    4868  *
    4869  * @since 3.1.0
    4870  * @since 4.1.0 Introduced the `$resource_type` argument.
    4871  *
    4872  * @param int    $object_id     Optional. The ID of the object. Default 0.
    4873  * @param string $object_type   Optional. The type of object for which we'll be retrieving
    4874  *                              ancestors. Accepts a post type or a taxonomy name. Default empty.
    4875  * @param string $resource_type Optional. Type of resource $object_type is. Accepts 'post_type'
    4876  *                              or 'taxonomy'. Default empty.
    4877  * @return array An array of ancestors from lowest to highest in the hierarchy.
    4878  */
    4879 function get_ancestors( $object_id = 0, $object_type = '', $resource_type = '' ) {
    4880         $object_id = (int) $object_id;
    4881 
    4882         $ancestors = array();
    4883 
    4884         if ( empty( $object_id ) ) {
    4885 
    4886                 /** This filter is documented in wp-includes/taxonomy.php */
    4887                 return apply_filters( 'get_ancestors', $ancestors, $object_id, $object_type, $resource_type );
    4888         }
    4889 
    4890         if ( ! $resource_type ) {
    4891                 if ( is_taxonomy_hierarchical( $object_type ) ) {
    4892                         $resource_type = 'taxonomy';
    4893                 } elseif ( post_type_exists( $object_type ) ) {
    4894                         $resource_type = 'post_type';
    4895                 }
    4896         }
    4897 
    4898         if ( 'taxonomy' === $resource_type ) {
    4899                 $term = get_term($object_id, $object_type);
    4900                 while ( ! is_wp_error($term) && ! empty( $term->parent ) && ! in_array( $term->parent, $ancestors ) ) {
    4901                         $ancestors[] = (int) $term->parent;
    4902                         $term = get_term($term->parent, $object_type);
    4903                 }
    4904         } elseif ( 'post_type' === $resource_type ) {
    4905                 $ancestors = get_post_ancestors($object_id);
    4906         }
    4907 
    4908         /**
    4909          * Filter a given object's ancestors.
    4910          *
    4911          * @since 3.1.0
    4912          * @since 4.1.1 Introduced the `$resource_type` parameter.
    4913          *
    4914          * @param array  $ancestors     An array of object ancestors.
    4915          * @param int    $object_id     Object ID.
    4916          * @param string $object_type   Type of object.
    4917          * @param string $resource_type Type of resource $object_type is.
    4918          */
    4919         return apply_filters( 'get_ancestors', $ancestors, $object_id, $object_type, $resource_type );
    4920 }
    4921 
    4922 /**
    4923  * Returns the term's parent's term_ID.
    4924  *
    4925  * @since 3.1.0
    4926  *
    4927  * @param int    $term_id  Term ID.
    4928  * @param string $taxonomy Taxonomy name.
    4929  * @return int|false False on error.
    4930  */
    4931 function wp_get_term_taxonomy_parent_id( $term_id, $taxonomy ) {
    4932         $term = get_term( $term_id, $taxonomy );
    4933         if ( ! $term || is_wp_error( $term ) ) {
    4934                 return false;
    4935         }
    4936         return (int) $term->parent;
    4937 }
    4938 
    4939 /**
    4940  * Checks the given subset of the term hierarchy for hierarchy loops.
    4941  * Prevents loops from forming and breaks those that it finds.
    4942  *
    4943  * Attached to the {@see 'wp_update_term_parent'} filter.
    4944  *
    4945  * @since 3.1.0
    4946  *
    4947  * @param int    $parent   `term_id` of the parent for the term we're checking.
    4948  * @param int    $term_id  The term we're checking.
    4949  * @param string $taxonomy The taxonomy of the term we're checking.
    4950  *
    4951  * @return int The new parent for the term.
    4952  */
    4953 function wp_check_term_hierarchy_for_loops( $parent, $term_id, $taxonomy ) {
    4954         // Nothing fancy here - bail
    4955         if ( !$parent )
    4956                 return 0;
    4957 
    4958         // Can't be its own parent.
    4959         if ( $parent == $term_id )
    4960                 return 0;
    4961 
    4962         // Now look for larger loops.
    4963         if ( !$loop = wp_find_hierarchy_loop( 'wp_get_term_taxonomy_parent_id', $term_id, $parent, array( $taxonomy ) ) )
    4964                 return $parent; // No loop
    4965 
    4966         // Setting $parent to the given value causes a loop.
    4967         if ( isset( $loop[$term_id] ) )
    4968                 return 0;
    4969 
    4970         // There's a loop, but it doesn't contain $term_id. Break the loop.
    4971         foreach ( array_keys( $loop ) as $loop_member )
    4972                 wp_update_term( $loop_member, $taxonomy, array( 'parent' => 0 ) );
    4973 
    4974         return $parent;
    4975 }
     11require_once( ABSPATH . WPINC . '/taxonomy-functions.php' );
     12require_once( ABSPATH . WPINC . '/class-wp-tax-query.php' );
Note: See TracChangeset for help on using the changeset viewer.