Make WordPress Core

Changeset 33760


Ignore:
Timestamp:
08/26/2015 12:48:11 PM (9 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.

Location:
trunk/src/wp-includes
Files:
1 edited
2 copied

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/class-wp-tax-query.php

    r33758 r33760  
    11<?php
    2 /**
    3  * Taxonomy API
    4  *
    5  * @since 2.3.0
    6  *
    7  * @package WordPress
    8  * @subpackage Taxonomy
    9  */
    10 
    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 
    6562/**
    6573 * Class for generating SQL clauses that filter a primary query according to object taxonomy terms.
     
    6628 *
    6639 * @since 3.1.0
     10 * @package WordPress
     11 * @subpackage Taxonomy
    66412 */
    66513class WP_Tax_Query {
     
    1307655    }
    1308656}
    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 }
  • trunk/src/wp-includes/taxonomy-functions.php

    r33758 r33760  
    652652    $tax_query_obj = new WP_Tax_Query( $tax_query );
    653653    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     }
    1308654}
    1309655
  • 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