Skip to:
Content

Ticket #7502: 7502.01.patch

File 7502.01.patch, 88.2 KB (added by r-a-y, 14 months ago)
  • src/bp-forums/bp-forums-bbpress-sa.php

    diff --git src/bp-forums/bp-forums-bbpress-sa.php src/bp-forums/bp-forums-bbpress-sa.php
    index cefec1eb..020550ef 100644
    function bp_forums_load_bbpress() { 
    3737        define( 'BB_INC', 'bb-includes/' );
    3838
    3939        require( BB_PATH . BB_INC . 'class.bb-query.php' );
    40         require( BB_PATH . BB_INC . 'class.bb-walker.php' );
     40        @require( BB_PATH . BB_INC . 'class.bb-walker.php' );
    4141
    4242        require( BB_PATH . BB_INC . 'functions.bb-core.php' );
    4343        require( BB_PATH . BB_INC . 'functions.bb-forums.php' );
    function bp_forums_load_bbpress() { 
    5050        require( BB_PATH . BB_INC . 'functions.bb-formatting.php' );
    5151        require( BB_PATH . BB_INC . 'functions.bb-template.php' );
    5252
    53         require( BACKPRESS_PATH . 'class.wp-taxonomy.php' );
    54         require( BB_PATH . BB_INC . 'class.bb-taxonomy.php' );
     53        require( $bp->plugin_dir . '/bp-forums/class.backpress-taxonomy.php' );
     54        require( $bp->plugin_dir . '/bp-forums/class.bb-taxonomy.php' );
    5555
    56         require( BB_PATH . 'bb-admin/includes/functions.bb-admin.php' );
     56        @require( BB_PATH . 'bb-admin/includes/functions.bb-admin.php' );
    5757
    5858        $bb = new stdClass();
    5959        require( bp_get_option( 'bb-config-location' ) );
  • new file src/bp-forums/class.backpress-taxonomy.php

    diff --git src/bp-forums/class.backpress-taxonomy.php src/bp-forums/class.backpress-taxonomy.php
    new file mode 100644
    index 00000000..f02fe194
    - +  
     1<?php
     2// Last sync [WP11537] - Refactored into a class based on wp-includes/taxonomy.php
     3
     4/**
     5 * Taxonomy API
     6 *
     7 * @package WordPress
     8 * @subpackage Taxonomy
     9 * @since 2.3.0
     10 */
     11
     12/**
     13 * WordPress Taxonomy based off of WordPress revision 8782.
     14 *
     15 * @since 2.3.0
     16 */
     17class BackPress_Taxonomy {
     18        /**
     19         * Stores the database.
     20         *
     21         * @var unknown_type
     22         */
     23        var $db;
     24        var $taxonomies = array();
     25
     26        function WP_Taxonomy( &$db ) {
     27                $this->__construct( $db );
     28                register_shutdown_function( array(&$this, '__destruct') );
     29        }
     30
     31        /**
     32         * PHP5 constructor - Assigns the database to an attribute of the class.
     33         *
     34         * @param unknown_type $db
     35         */
     36        function __construct( &$db ) {
     37                $this->db =& $db;
     38        }
     39
     40        /**
     41         * Does nothing.
     42         *
     43         * @package BackPress
     44         * @subpackage Taxonomy
     45         */
     46        function __destruct() {
     47        }
     48
     49        /**
     50         * Return all of the taxonomy names that are of $object_type.
     51         *
     52         * It appears that this function can be used to find all of the names inside of
     53         * $this->taxonomies variable.
     54         *
     55         * <code><?php $taxonomies = $this->get_object_taxonomies('post'); ?></code> Should
     56         * result in <code>Array('category', 'post_tag')</code>
     57         *
     58         * @package WordPress
     59         * @subpackage Taxonomy
     60         * @since 2.3.0
     61         *
     62         * @uses $this->taxonomies
     63         *
     64         * @param array|string|object $object_type Name of the type of taxonomy object, or an object (row from posts)
     65         * @return array The names of all taxonomy of $object_type.
     66         */
     67        function get_object_taxonomies($object_type) {
     68                $object_type = (array) $object_type;
     69
     70                // WP DIFF
     71                $taxonomies = array();
     72                foreach ( (array) $this->taxonomies as $taxonomy ) {
     73                        if ( array_intersect($object_type, (array) $taxonomy->object_type) )
     74                                $taxonomies[] = $taxonomy->name;
     75                }
     76
     77                return $taxonomies;
     78        }
     79
     80        /**
     81         * Retrieves the taxonomy object of $taxonomy.
     82         *
     83         * The get_taxonomy function will first check that the parameter string given
     84         * is a taxonomy object and if it is, it will return it.
     85         *
     86         * @package WordPress
     87         * @subpackage Taxonomy
     88         * @since 2.3.0
     89         *
     90         * @uses $this->taxonomies
     91         * @uses $this->is_taxonomy() Checks whether taxonomy exists
     92         *
     93         * @param string $taxonomy Name of taxonomy object to return
     94         * @return object|bool The Taxonomy Object or false if $taxonomy doesn't exist
     95         */
     96        function get_taxonomy( $taxonomy ) {
     97                if ( !$this->is_taxonomy($taxonomy) )
     98                        return false;
     99
     100                return $this->taxonomies[$taxonomy];
     101        }
     102
     103        /**
     104         * Checks that the taxonomy name exists.
     105         *
     106         * @package WordPress
     107         * @subpackage Taxonomy
     108         * @since 2.3.0
     109         *
     110         * @uses $this->taxonomies
     111         *
     112         * @param string $taxonomy Name of taxonomy object
     113         * @return bool Whether the taxonomy exists or not.
     114         */
     115        function is_taxonomy( $taxonomy ) {
     116                return isset($this->taxonomies[$taxonomy]);
     117        }
     118
     119        /**
     120         * Whether the taxonomy object is hierarchical.
     121         *
     122         * Checks to make sure that the taxonomy is an object first. Then Gets the
     123         * object, and finally returns the hierarchical value in the object.
     124         *
     125         * A false return value might also mean that the taxonomy does not exist.
     126         *
     127         * @package WordPress
     128         * @subpackage Taxonomy
     129         * @since 2.3.0
     130         *
     131         * @uses $this->is_taxonomy() Checks whether taxonomy exists
     132         * @uses $this->get_taxonomy() Used to get the taxonomy object
     133         *
     134         * @param string $taxonomy Name of taxonomy object
     135         * @return bool Whether the taxonomy is hierarchical
     136         */
     137        function is_taxonomy_hierarchical($taxonomy) {
     138                if ( !$this->is_taxonomy($taxonomy) )
     139                        return false;
     140
     141                $taxonomy = $this->get_taxonomy($taxonomy);
     142                return $taxonomy->hierarchical;
     143        }
     144
     145        /**
     146         * Create or modify a taxonomy object. Do not use before init.
     147         *
     148         * A simple function for creating or modifying a taxonomy object based on the
     149         * parameters given. The function will accept an array (third optional
     150         * parameter), along with strings for the taxonomy name and another string for
     151         * the object type.
     152         *
     153         * The function keeps a default set, allowing for the $args to be optional but
     154         * allow the other functions to still work. It is possible to overwrite the
     155         * default set, which contains two keys: hierarchical and update_count_callback.
     156         *
     157         * Nothing is returned, so expect error maybe or use is_taxonomy() to check
     158         * whether taxonomy exists.
     159         *
     160         * Optional $args contents:
     161         *
     162         * hierarachical - has some defined purpose at other parts of the API and is a
     163         * boolean value.
     164         *
     165         * update_count_callback - works much like a hook, in that it will be called
     166         * when the count is updated.
     167         *
     168         * @package WordPress
     169         * @subpackage Taxonomy
     170         * @since 2.3.0
     171         * @uses $this->taxonomies Inserts new taxonomy object into the list
     172         *
     173         * @param string $taxonomy Name of taxonomy object
     174         * @param string $object_type Name of the object type for the taxonomy object.
     175         * @param array|string $args See above description for the two keys values.
     176         */
     177        function register_taxonomy( $taxonomy, $object_type, $args = array() ) {
     178                $defaults = array('hierarchical' => false, 'update_count_callback' => '');
     179                $args = wp_parse_args($args, $defaults);
     180
     181                $args['name'] = $taxonomy;
     182                $args['object_type'] = $object_type;
     183                $this->taxonomies[$taxonomy] = (object) $args;
     184        }
     185
     186        //
     187        // Term API
     188        //
     189
     190        /**
     191         * Retrieve object_ids of valid taxonomy and term.
     192         *
     193         * The strings of $taxonomies must exist before this function will continue. On
     194         * failure of finding a valid taxonomy, it will return an WP_Error class, kind
     195         * of like Exceptions in PHP 5, except you can't catch them. Even so, you can
     196         * still test for the WP_Error class and get the error message.
     197         *
     198         * The $terms aren't checked the same as $taxonomies, but still need to exist
     199         * for $object_ids to be returned.
     200         *
     201         * It is possible to change the order that object_ids is returned by either
     202         * using PHP sort family functions or using the database by using $args with
     203         * either ASC or DESC array. The value should be in the key named 'order'.
     204         *
     205         * @package WordPress
     206         * @subpackage Taxonomy
     207         * @since 2.3.0
     208         *
     209         * @uses wp_parse_args() Creates an array from string $args.
     210         *
     211         * @param string|array $terms String of term or array of string values of terms that will be used
     212         * @param string|array $taxonomies String of taxonomy name or Array of string values of taxonomy names
     213         * @param array|string $args Change the order of the object_ids, either ASC or DESC
     214         * @return WP_Error|array If the taxonomy does not exist, then WP_Error will be returned. On success
     215         *      the array can be empty meaning that there are no $object_ids found or it will return the $object_ids found.
     216         */
     217        function get_objects_in_term( $terms, $taxonomies, $args = null ) {
     218                if ( !is_array($terms) )
     219                        $terms = array($terms);
     220
     221                if ( !is_array($taxonomies) )
     222                        $taxonomies = array($taxonomies);
     223
     224                foreach ( (array) $taxonomies as $taxonomy ) {
     225                        if ( !$this->is_taxonomy($taxonomy) )
     226                                return new WP_Error('invalid_taxonomy', __('Invalid Taxonomy'));
     227                }
     228
     229                $defaults = array('order' => 'ASC', 'field' => 'term_id');
     230                $args = wp_parse_args( $args, $defaults );
     231                extract($args, EXTR_SKIP);
     232
     233                if ( 'tt_id' == $field )
     234                        $field = 'tt.term_taxonomy_id';
     235                else
     236                        $field = 'tt.term_id';
     237
     238                $order = ( 'desc' == strtolower($order) ) ? 'DESC' : 'ASC';
     239
     240                $terms = array_map('intval', $terms);
     241
     242                $taxonomies = "'" . implode("', '", $taxonomies) . "'";
     243                $terms = "'" . implode("', '", $terms) . "'";
     244
     245                $object_ids = $this->db->get_col("SELECT tr.object_id FROM {$this->db->term_relationships} AS tr INNER JOIN {$this->db->term_taxonomy} AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id WHERE tt.taxonomy IN ($taxonomies) AND $field IN ($terms) ORDER BY tr.object_id $order");
     246
     247                if ( ! $object_ids )
     248                        return array();
     249
     250                return $object_ids;
     251        }
     252
     253        /**
     254         * Get all Term data from database by Term ID.
     255         *
     256         * The usage of the get_term function is to apply filters to a term object. It
     257         * is possible to get a term object from the database before applying the
     258         * filters.
     259         *
     260         * $term ID must be part of $taxonomy, to get from the database. Failure, might
     261         * be able to be captured by the hooks. Failure would be the same value as $this->db
     262         * returns for the get_row method.
     263         *
     264         * There are two hooks, one is specifically for each term, named 'get_term', and
     265         * the second is for the taxonomy name, 'term_$taxonomy'. Both hooks gets the
     266         * term object, and the taxonomy name as parameters. Both hooks are expected to
     267         * return a Term object.
     268         *
     269         * 'get_term' hook - Takes two parameters the term Object and the taxonomy name.
     270         * Must return term object. Used in get_term() as a catch-all filter for every
     271         * $term.
     272         *
     273         * 'get_$taxonomy' hook - Takes two parameters the term Object and the taxonomy
     274         * name. Must return term object. $taxonomy will be the taxonomy name, so for
     275         * example, if 'category', it would be 'get_category' as the filter name. Useful
     276         * for custom taxonomies or plugging into default taxonomies.
     277         *
     278         * @package WordPress
     279         * @subpackage Taxonomy
     280         * @since 2.3.0
     281         *
     282         * @uses $this->sanitize_term() Cleanses the term based on $filter context before returning.
     283         * @see $this->sanitize_term_field() The $context param lists the available values for get_term_by() $filter param.
     284         *
     285         * @param int|object $term If integer, will get from database. If object will apply filters and return $term.
     286         * @param string $taxonomy Taxonomy name that $term is part of.
     287         * @param string $output Constant OBJECT, ARRAY_A, or ARRAY_N
     288         * @param string $filter Optional, default is raw or no WordPress defined filter will applied.
     289         * @return mixed|null|WP_Error Term Row from database. Will return null if $term is empty. If taxonomy does not
     290         * exist then WP_Error will be returned.
     291         */
     292        function &get_term($term, $taxonomy, $output = OBJECT, $filter = 'raw') {
     293                if ( empty($term) ) {
     294                        $error = new WP_Error('invalid_term', __('Empty Term'));
     295                        return $error;
     296                }
     297
     298                if ( !$this->is_taxonomy($taxonomy) ) {
     299                        $error = new WP_Error('invalid_taxonomy', __('Invalid Taxonomy'));
     300                        return $error;
     301                }
     302
     303                if ( is_object($term) ) {
     304                        wp_cache_add($term->term_id, $term, $taxonomy);
     305                        wp_cache_add($term->term_taxonomy_id, $term->term_id, "$taxonomy:tt_id" );
     306                        $_term = $term;
     307                } else {
     308                        $term = (int) $term;
     309                        if ( ! $_term = wp_cache_get($term, $taxonomy) ) {
     310                                $_term = $this->db->get_row( $this->db->prepare( "SELECT t.*, tt.* FROM {$this->db->terms} AS t INNER JOIN {$this->db->term_taxonomy} AS tt ON t.term_id = tt.term_id WHERE tt.taxonomy = %s AND t.term_id = %s LIMIT 1", $taxonomy, $term) );
     311                                wp_cache_add($term, $_term, $taxonomy);
     312                                wp_cache_add($_term->term_taxonomy_id, $_term->term_id, "$taxonomy:tt_id" );
     313                        }
     314                }
     315
     316                $_term = apply_filters('get_term', $_term, $taxonomy);
     317                $_term = apply_filters("get_$taxonomy", $_term, $taxonomy);
     318                $_term = $this->sanitize_term($_term, $taxonomy, $filter);
     319
     320                backpress_convert_object( $_term, $output );
     321
     322                return $_term;
     323        }
     324
     325        /**
     326         * Get all Term data from database by Term field and data.
     327         *
     328         * Warning: $value is not escaped for 'name' $field. You must do it yourself, if
     329         * required.
     330         *
     331         * The default $field is 'id', therefore it is possible to also use null for
     332         * field, but not recommended that you do so.
     333         *
     334         * If $value does not exist, the return value will be false. If $taxonomy exists
     335         * and $field and $value combinations exist, the Term will be returned.
     336         *
     337         * @package WordPress
     338         * @subpackage Taxonomy
     339         * @since 2.3.0
     340         *
     341         * @uses $this->sanitize_term() Cleanses the term based on $filter context before returning.
     342         * @see $this->sanitize_term_field() The $context param lists the available values for get_term_by() $filter param.
     343         *
     344         * @param string $field Either 'slug', 'name', 'id', or 'tt_id'
     345         * @param string|int $value Search for this term value
     346         * @param string $taxonomy Taxonomy Name
     347         * @param string $output Constant OBJECT, ARRAY_A, or ARRAY_N
     348         * @param string $filter Optional, default is raw or no WordPress defined filter will applied.
     349         * @return mixed Term Row from database. Will return false if $taxonomy does not exist or $term was not found.
     350         */
     351        function get_term_by($field, $value, $taxonomy, $output = OBJECT, $filter = 'raw') {
     352                if ( !$this->is_taxonomy($taxonomy) )
     353                        return false;
     354
     355                if ( 'slug' == $field ) {
     356                        $field = 't.slug';
     357                        $value = $this->sanitize_term_slug($value, $taxonomy);
     358                        if ( empty($value) )
     359                                return false;
     360                } else if ( 'name' == $field ) {
     361                        // Assume already escaped
     362                        $field = 't.name';
     363                } else if ( 'tt_id' == $field ) {
     364                        $field = 'tt.term_taxonomy_id';
     365                        $value = (int) $value;
     366                        if ( $_term_id = wp_cache_get( $value, "$taxonomy:tt_id" ) )
     367                                return $this->get_term( $_term_id, $taxonomy, $output, $filter );
     368                } else {
     369                        $field = 't.term_id';
     370                        $value = (int) $value;
     371                }
     372
     373                $term = $this->db->get_row( $this->db->prepare( "SELECT t.*, tt.* FROM {$this->db->terms} AS t INNER JOIN {$this->db->term_taxonomy} AS tt ON t.term_id = tt.term_id WHERE tt.taxonomy = %s AND $field = %s LIMIT 1", $taxonomy, $value) );
     374                if ( !$term )
     375                        return false;
     376
     377                wp_cache_add($term->term_id, $term, $taxonomy);
     378                wp_cache_add($term->term_taxonomy_id, $term->term_id, "$taxonomy:tt_id" );
     379
     380                $term = $this->sanitize_term($term, $taxonomy, $filter);
     381
     382                backpress_convert_object( $term, $output );
     383
     384                return $term;
     385        }
     386
     387        /**
     388         * Merge all term children into a single array of their IDs.
     389         *
     390         * This recursive function will merge all of the children of $term into the same
     391         * array of term IDs. Only useful for taxonomies which are hierarchical.
     392         *
     393         * Will return an empty array if $term does not exist in $taxonomy.
     394         *
     395         * @package WordPress
     396         * @subpackage Taxonomy
     397         * @since 2.3.0
     398         *
     399         * @uses $this->_get_term_hierarchy()
     400         * @uses $this->get_term_children() Used to get the children of both $taxonomy and the parent $term
     401         *
     402         * @param string $term ID of Term to get children
     403         * @param string $taxonomy Taxonomy Name
     404         * @return array|WP_Error List of Term Objects. WP_Error returned if $taxonomy does not exist
     405         */
     406        function get_term_children( $term_id, $taxonomy ) {
     407                if ( !$this->is_taxonomy($taxonomy) )
     408                        return new WP_Error('invalid_taxonomy', __('Invalid Taxonomy'));
     409
     410                $term_id = intval( $term_id );
     411
     412                $terms = $this->_get_term_hierarchy($taxonomy);
     413
     414                if ( ! isset($terms[$term_id]) )
     415                        return array();
     416
     417                $children = $terms[$term_id];
     418
     419                foreach ( (array) $terms[$term_id] as $child ) {
     420                        if ( isset($terms[$child]) )
     421                                $children = array_merge($children, $this->get_term_children($child, $taxonomy));
     422                }
     423
     424                return $children;
     425        }
     426
     427        /**
     428         * Get sanitized Term field.
     429         *
     430         * Does checks for $term, based on the $taxonomy. The function is for contextual
     431         * reasons and for simplicity of usage. See sanitize_term_field() for more
     432         * information.
     433         *
     434         * @package WordPress
     435         * @subpackage Taxonomy
     436         * @since 2.3.0
     437         *
     438         * @uses $this->sanitize_term_field() Passes the return value in sanitize_term_field on success.
     439         *
     440         * @param string $field Term field to fetch
     441         * @param int $term Term ID
     442         * @param string $taxonomy Taxonomy Name
     443         * @param string $context Optional, default is display. Look at sanitize_term_field() for available options.
     444         * @return mixed Will return an empty string if $term is not an object or if $field is not set in $term.
     445         */
     446        function get_term_field( $field, $term, $taxonomy, $context = 'display' ) {
     447                $term = (int) $term;
     448                $term = $this->get_term( $term, $taxonomy );
     449                if ( is_wp_error($term) )
     450                        return $term;
     451
     452                if ( !is_object($term) )
     453                        return '';
     454
     455                if ( !isset($term->$field) )
     456                        return '';
     457
     458                return $this->sanitize_term_field($field, $term->$field, $term->term_id, $taxonomy, $context);
     459        }
     460
     461        /**
     462         * Sanitizes Term for editing.
     463         *
     464         * Return value is sanitize_term() and usage is for sanitizing the term for
     465         * editing. Function is for contextual and simplicity.
     466         *
     467         * @package WordPress
     468         * @subpackage Taxonomy
     469         * @since 2.3.0
     470         *
     471         * @uses $this->sanitize_term() Passes the return value on success
     472         *
     473         * @param int|object $id Term ID or Object
     474         * @param string $taxonomy Taxonomy Name
     475         * @return mixed|null|WP_Error Will return empty string if $term is not an object.
     476         */
     477        function get_term_to_edit( $id, $taxonomy ) {
     478                $term = $this->get_term( $id, $taxonomy );
     479
     480                if ( is_wp_error($term) )
     481                        return $term;
     482
     483                if ( !is_object($term) )
     484                        return '';
     485
     486                return $this->sanitize_term($term, $taxonomy, 'edit');
     487        }
     488
     489        /**
     490         * Retrieve the terms in a given taxonomy or list of taxonomies.
     491         *
     492         * You can fully inject any customizations to the query before it is sent, as
     493         * well as control the output with a filter.
     494         *
     495         * The 'get_terms' filter will be called when the cache has the term and will
     496         * pass the found term along with the array of $taxonomies and array of $args.
     497         * This filter is also called before the array of terms is passed and will pass
     498         * the array of terms, along with the $taxonomies and $args.
     499         *
     500         * The 'list_terms_exclusions' filter passes the compiled exclusions along with
     501         * the $args.
     502         *
     503         * The 'get_terms_orderby' filter passes the ORDER BY clause for the query
     504         * along with the $args array.
     505         *
     506         * The 'get_terms_fields' filter passes the fields for the SELECT query
     507         * along with the $args array.
     508         *
     509         * The list of arguments that $args can contain, which will overwrite the defaults:
     510         *
     511         * orderby - Default is 'name'. Can be name, count, term_group, slug or nothing
     512         * (will use term_id), Passing a custom value other than these will cause it to
     513         * order based on the custom value.
     514         *
     515         * order - Default is ASC. Can use DESC.
     516         *
     517         * hide_empty - Default is true. Will not return empty terms, which means
     518         * terms whose count is 0 according to the given taxonomy.
     519         *
     520         * exclude - Default is an empty string.  A comma- or space-delimited string
     521         * of term ids to exclude from the return array.  If 'include' is non-empty,
     522         * 'exclude' is ignored.
     523         *
     524         * include - Default is an empty string.  A comma- or space-delimited string
     525         * of term ids to include in the return array.
     526         *
     527         * number - The maximum number of terms to return.  Default is empty.
     528         *
     529         * offset - The number by which to offset the terms query.
     530         *
     531         * fields - Default is 'all', which returns an array of term objects.
     532         * If 'fields' is 'ids' or 'names', returns an array of
     533         * integers or strings, respectively.
     534         *
     535         * slug - Returns terms whose "slug" matches this value. Default is empty string.
     536         *
     537         * hierarchical - Whether to include terms that have non-empty descendants
     538         * (even if 'hide_empty' is set to true).
     539         *
     540         * search - Returned terms' names will contain the value of 'search',
     541         * case-insensitive.  Default is an empty string.
     542         *
     543         * name__like - Returned terms' names will begin with the value of 'name__like',
     544         * case-insensitive. Default is empty string.
     545         *
     546         * The argument 'pad_counts', if set to true will include the quantity of a term's
     547         * children in the quantity of each term's "count" object variable.
     548         *
     549         * The 'get' argument, if set to 'all' instead of its default empty string,
     550         * returns terms regardless of ancestry or whether the terms are empty.
     551         *
     552         * The 'child_of' argument, when used, should be set to the integer of a term ID.  Its default
     553         * is 0.  If set to a non-zero value, all returned terms will be descendants
     554         * of that term according to the given taxonomy.  Hence 'child_of' is set to 0
     555         * if more than one taxonomy is passed in $taxonomies, because multiple taxonomies
     556         * make term ancestry ambiguous.
     557         *
     558         * The 'parent' argument, when used, should be set to the integer of a term ID.  Its default is
     559         * the empty string '', which has a different meaning from the integer 0.
     560         * If set to an integer value, all returned terms will have as an immediate
     561         * ancestor the term whose ID is specified by that integer according to the given taxonomy.
     562         * The 'parent' argument is different from 'child_of' in that a term X is considered a 'parent'
     563         * of term Y only if term X is the father of term Y, not its grandfather or great-grandfather, etc.
     564         *
     565         * @package WordPress
     566         * @subpackage Taxonomy
     567         * @since 2.3.0
     568         *
     569         * @uses wp_parse_args() Merges the defaults with those defined by $args and allows for strings.
     570         *
     571         * @param string|array Taxonomy name or list of Taxonomy names
     572         * @param string|array $args The values of what to search for when returning terms
     573         * @return array|WP_Error List of Term Objects and their children. Will return WP_Error, if any of $taxonomies do not exist.
     574         */
     575        function &get_terms($taxonomies, $args = '') {
     576                $empty_array = array();
     577
     578                $single_taxonomy = false;
     579                if ( !is_array($taxonomies) ) {
     580                        $single_taxonomy = true;
     581                        $taxonomies = array($taxonomies);
     582                }
     583
     584                foreach ( (array) $taxonomies as $taxonomy ) {
     585                        if ( ! $this->is_taxonomy($taxonomy) ) {
     586                                $error = new WP_Error('invalid_taxonomy', __('Invalid Taxonomy'));
     587                                return $error;
     588                        }
     589                }
     590
     591                $in_taxonomies = "'" . implode("', '", $taxonomies) . "'";
     592
     593                $defaults = array('orderby' => 'name', 'order' => 'ASC',
     594                        'hide_empty' => true, 'exclude' => '', 'exclude_tree' => '', 'include' => '',
     595                        'number' => '', 'fields' => 'all', 'slug' => '', 'parent' => '',
     596                        'hierarchical' => true, 'child_of' => 0, 'get' => '', 'name__like' => '',
     597                        'pad_counts' => false, 'offset' => '', 'search' => '');
     598                $args = wp_parse_args( $args, $defaults );
     599                $args['number'] = absint( $args['number'] );
     600                $args['offset'] = absint( $args['offset'] );
     601                if ( !$single_taxonomy || !$this->is_taxonomy_hierarchical($taxonomies[0]) ||
     602                        '' !== $args['parent'] ) {
     603                        $args['child_of'] = 0;
     604                        $args['hierarchical'] = false;
     605                        $args['pad_counts'] = false;
     606                }
     607
     608                if ( 'all' == $args['get'] ) {
     609                        $args['child_of'] = 0;
     610                        $args['hide_empty'] = 0;
     611                        $args['hierarchical'] = false;
     612                        $args['pad_counts'] = false;
     613                }
     614                extract($args, EXTR_SKIP);
     615
     616                if ( $child_of ) {
     617                        $hierarchy = $this->_get_term_hierarchy($taxonomies[0]);
     618                        if ( !isset($hierarchy[$child_of]) )
     619                                return $empty_array;
     620                }
     621
     622                if ( $parent ) {
     623                        $hierarchy = $this->_get_term_hierarchy($taxonomies[0]);
     624                        if ( !isset($hierarchy[$parent]) )
     625                                return $empty_array;
     626                }
     627
     628                // $args can be whatever, only use the args defined in defaults to compute the key
     629                $filter_key = ( has_filter('list_terms_exclusions') ) ? serialize($GLOBALS['wp_filter']['list_terms_exclusions']) : '';
     630                $key = md5( serialize( compact(array_keys($defaults)) ) . serialize( $taxonomies ) . $filter_key );
     631                $last_changed = wp_cache_get('last_changed', 'terms');
     632                if ( !$last_changed ) {
     633                        $last_changed = time();
     634                        wp_cache_set('last_changed', $last_changed, 'terms');
     635                }
     636                $cache_key = "get_terms:$key:$last_changed";
     637                $cache = wp_cache_get( $cache_key, 'terms' );
     638                if ( false !== $cache ) {
     639                        $cache = apply_filters('get_terms', $cache, $taxonomies, $args);
     640                        return $cache;
     641                }
     642
     643                $_orderby = strtolower($orderby);
     644                if ( 'count' == $_orderby )
     645                        $orderby = 'tt.count';
     646                else if ( 'name' == $_orderby )
     647                        $orderby = 't.name';
     648                else if ( 'slug' == $_orderby )
     649                        $orderby = 't.slug';
     650                else if ( 'term_group' == $_orderby )
     651                        $orderby = 't.term_group';
     652                elseif ( empty($_orderby) || 'id' == $_orderby )
     653                        $orderby = 't.term_id';
     654
     655                $orderby = apply_filters( 'get_terms_orderby', $orderby, $args );
     656
     657                $where = '';
     658                $inclusions = '';
     659                if ( !empty($include) ) {
     660                        $exclude = '';
     661                        $exclude_tree = '';
     662                        $interms = preg_split('/[\s,]+/',$include);
     663                        if ( count($interms) ) {
     664                                foreach ( (array) $interms as $interm ) {
     665                                        if (empty($inclusions))
     666                                                $inclusions = ' AND ( t.term_id = ' . intval($interm) . ' ';
     667                                        else
     668                                                $inclusions .= ' OR t.term_id = ' . intval($interm) . ' ';
     669                                }
     670                        }
     671                }
     672
     673                if ( !empty($inclusions) )
     674                        $inclusions .= ')';
     675                $where .= $inclusions;
     676
     677                $exclusions = '';
     678                if ( ! empty( $exclude_tree ) ) {
     679                        $excluded_trunks = preg_split('/[\s,]+/',$exclude_tree);
     680                        foreach( (array) $excluded_trunks as $extrunk ) {
     681                                $excluded_children = (array) $this->get_terms($taxonomies[0], array('child_of' => intval($extrunk), 'fields' => 'ids'));
     682                                $excluded_children[] = $extrunk;
     683                                foreach( (array) $excluded_children as $exterm ) {
     684                                        if ( empty($exclusions) )
     685                                                $exclusions = ' AND ( t.term_id <> ' . intval($exterm) . ' ';
     686                                        else
     687                                                $exclusions .= ' AND t.term_id <> ' . intval($exterm) . ' ';
     688
     689                                }
     690                        }
     691                }
     692                if ( !empty($exclude) ) {
     693                        $exterms = preg_split('/[\s,]+/',$exclude);
     694                        if ( count($exterms) ) {
     695                                foreach ( (array) $exterms as $exterm ) {
     696                                        if ( empty($exclusions) )
     697                                                $exclusions = ' AND ( t.term_id <> ' . intval($exterm) . ' ';
     698                                        else
     699                                                $exclusions .= ' AND t.term_id <> ' . intval($exterm) . ' ';
     700                                }
     701                        }
     702                }
     703
     704                if ( !empty($exclusions) )
     705                        $exclusions .= ')';
     706                $exclusions = apply_filters('list_terms_exclusions', $exclusions, $args );
     707                $where .= $exclusions;
     708
     709                if ( !empty($slug) ) {
     710                        $slug =  $this->sanitize_term_slug($slug);
     711                        $where .= " AND t.slug = '$slug'";
     712                }
     713
     714                if ( !empty($name__like) )
     715                        $where .= " AND t.name LIKE '{$name__like}%'";
     716
     717                if ( '' !== $parent ) {
     718                        $parent = (int) $parent;
     719                        $where .= " AND tt.parent = '$parent'";
     720                }
     721
     722                if ( $hide_empty && !$hierarchical )
     723                        $where .= ' AND tt.count > 0';
     724
     725                // don't limit the query results when we have to descend the family tree
     726                if ( ! empty($number) && ! $hierarchical && empty( $child_of ) && '' === $parent ) {
     727                        if( $offset )
     728                                $limit = 'LIMIT ' . $offset . ',' . $number;
     729                        else
     730                                $limit = 'LIMIT ' . $number;
     731
     732                } else
     733                        $limit = '';
     734
     735                if ( !empty($search) ) {
     736                        $search = like_escape($search);
     737                        $where .= " AND (t.name LIKE '%$search%')";
     738                }
     739
     740                if ( !in_array( $fields, array( 'all', 'ids', 'names', 'tt_ids' ) ) )
     741                        $fields = 'all';
     742
     743                $selects = array();
     744                if ( 'all' == $fields )
     745                        $selects = array('t.*', 'tt.*');
     746                else if ( 'ids' == $fields )
     747                        $selects = array('t.term_id', 'tt.parent', 'tt.count');
     748                else if ( 'names' == $fields )
     749                        $selects = array('t.term_id', 'tt.parent', 'tt.count', 't.name');
     750                $select_this = implode(', ', apply_filters( 'get_terms_fields', $selects, $args ));
     751
     752                $query = "SELECT $select_this FROM {$this->db->terms} AS t INNER JOIN {$this->db->term_taxonomy} AS tt ON t.term_id = tt.term_id WHERE tt.taxonomy IN ($in_taxonomies) $where ORDER BY $orderby $order $limit";
     753
     754                $terms = $this->db->get_results($query);
     755                if ( 'all' == $fields ) {
     756                        $this->update_term_cache($terms);
     757                }
     758
     759                if ( empty($terms) ) {
     760                        wp_cache_add( $cache_key, array(), 'terms' );
     761                        $terms = apply_filters('get_terms', array(), $taxonomies, $args);
     762                        return $terms;
     763                }
     764
     765                if ( $child_of || $hierarchical ) {
     766                        $children = $this->_get_term_hierarchy($taxonomies[0]);
     767                        if ( ! empty($children) )
     768                                $terms = & $this->_get_term_children($child_of, $terms, $taxonomies[0]);
     769                }
     770
     771                // Update term counts to include children.
     772                if ( $pad_counts )
     773                        $this->_pad_term_counts($terms, $taxonomies[0]);
     774
     775                // Make sure we show empty categories that have children.
     776                if ( $hierarchical && $hide_empty && is_array($terms) ) {
     777                        foreach ( $terms as $k => $term ) {
     778                                if ( ! $term->count ) {
     779                                        $children = $this->_get_term_children($term->term_id, $terms, $taxonomies[0]);
     780                                        if( is_array($children) )
     781                                                foreach ( $children as $child )
     782                                                        if ( $child->count )
     783                                                                continue 2;
     784
     785                                        // It really is empty
     786                                        unset($terms[$k]);
     787                                }
     788                        }
     789                }
     790                reset ( $terms );
     791
     792                $_terms = array();
     793                if ( 'ids' == $fields ) {
     794                        while ( $term = array_shift($terms) )
     795                                $_terms[] = $term->term_id;
     796                        $terms = $_terms;
     797                } elseif ( 'names' == $fields ) {
     798                        while ( $term = array_shift($terms) )
     799                                $_terms[] = $term->name;
     800                        $terms = $_terms;
     801                }
     802
     803                if ( 0 < $number && intval(@count($terms)) > $number ) {
     804                        $terms = array_slice($terms, $offset, $number);
     805                }
     806
     807                wp_cache_add( $cache_key, $terms, 'terms' );
     808
     809                $terms = apply_filters('get_terms', $terms, $taxonomies, $args);
     810                return $terms;
     811        }
     812
     813        /**
     814         * Check if Term exists.
     815         *
     816         * Returns the index of a defined term, or 0 (false) if the term doesn't exist.
     817         *
     818         * @package WordPress
     819         * @subpackage Taxonomy
     820         * @since 2.3.0
     821         *
     822         * @param int|string $term The term to check
     823         * @param string $taxonomy The taxonomy name to use
     824         * @param int $parent ID of parent term under which to confine the exists search.
     825         * @return mixed Get the term id or Term Object, if exists.
     826         */
     827        function is_term($term, $taxonomy = '', $parent = 0) {
     828                $select = "SELECT term_id FROM {$this->db->terms} as t WHERE ";
     829                $tax_select = "SELECT tt.term_id, tt.term_taxonomy_id FROM {$this->db->terms} AS t INNER JOIN {$this->db->term_taxonomy} as tt ON tt.term_id = t.term_id WHERE ";
     830               
     831                if ( is_int($term) ) {
     832                        if ( 0 == $term )
     833                                return 0;
     834                        $where = 't.term_id = %d';
     835                        if ( !empty($taxonomy) )
     836                                return $this->db->get_row( $this->db->prepare( $tax_select . $where . " AND tt.taxonomy = %s", $term, $taxonomy ), ARRAY_A );
     837                        else
     838                                return $this->db->get_var( $this->db->prepare( $select . $where, $term ) );
     839                }
     840
     841                $term = trim( stripslashes( $term ) );
     842
     843                if ( '' === $slug = $this->sanitize_term_slug($term) )
     844                        return 0;
     845
     846                $where = 't.slug = %s';
     847                $else_where = 't.name = %s';
     848                $where_fields = array($slug);
     849                $else_where_fields = array($term);
     850                if ( !empty($taxonomy) ) {
     851                        $parent = (int) $parent;
     852                        if ( $parent > 0 ) {
     853                                $where_fields[] = $parent;
     854                                $else_where_fields[] = $parent;
     855                                $where .= ' AND tt.parent = %d';
     856                                $else_where .= ' AND tt.parent = %d';
     857                        }
     858
     859                        $where_fields[] = $taxonomy;
     860                        $else_where_fields[] = $taxonomy;
     861
     862                        if ( $result = $this->db->get_row( $this->db->prepare("SELECT tt.term_id, tt.term_taxonomy_id FROM {$this->db->terms} AS t INNER JOIN {$this->db->term_taxonomy} as tt ON tt.term_id = t.term_id WHERE $where AND tt.taxonomy = %s", $where_fields), ARRAY_A) )
     863                                return $result;
     864
     865                        return $this->db->get_row( $this->db->prepare("SELECT tt.term_id, tt.term_taxonomy_id FROM {$this->db->terms} AS t INNER JOIN {$this->db->term_taxonomy} as tt ON tt.term_id = t.term_id WHERE $else_where AND tt.taxonomy = %s", $else_where_fields), ARRAY_A);
     866                }
     867
     868                if ( $result = $this->db->get_var( $this->db->prepare("SELECT term_id FROM {$this->db->terms} as t WHERE $where", $where_fields) ) )
     869                        return $result;
     870
     871                return $this->db->get_var( $this->db->prepare("SELECT term_id FROM {$this->db->terms} as t WHERE $else_where", $else_where_fields) );
     872        }
     873
     874        function sanitize_term_slug( $title, $taxonomy = '', $term_id = 0 ) {
     875                return apply_filters( 'pre_term_slug', $title, $taxonomy, $term_id );
     876        }
     877
     878        function format_to_edit( $text ) {
     879                return format_to_edit( $text );
     880        }
     881
     882        /**
     883         * Sanitize Term all fields
     884         *
     885         * Relies on sanitize_term_field() to sanitize the term. The difference
     886         * is that this function will sanitize <strong>all</strong> fields. The
     887         * context is based on sanitize_term_field().
     888         *
     889         * The $term is expected to be either an array or an object.
     890         *
     891         * @package WordPress
     892         * @subpackage Taxonomy
     893         * @since 2.3.0
     894         *
     895         * @uses $this->sanitize_term_field Used to sanitize all fields in a term
     896         *
     897         * @param array|object $term The term to check
     898         * @param string $taxonomy The taxonomy name to use
     899         * @param string $context Default is 'display'.
     900         * @return array|object Term with all fields sanitized
     901         */
     902        function sanitize_term($term, $taxonomy, $context = 'display') {
     903
     904                if ( 'raw' == $context )
     905                        return $term;
     906
     907                $fields = array('term_id', 'name', 'description', 'slug', 'count', 'parent', 'term_group');
     908
     909                $do_object = false;
     910                if ( is_object($term) )
     911                        $do_object = true;
     912
     913                $term_id = $do_object ? $term->term_id : (isset($term['term_id']) ? $term['term_id'] : 0);
     914
     915                foreach ( (array) $fields as $field ) {
     916                        if ( $do_object ) {
     917                                if ( isset($term->$field) )
     918                                        $term->$field = $this->sanitize_term_field($field, $term->$field, $term_id, $taxonomy, $context);
     919                        } else {
     920                                if ( isset($term[$field]) )
     921                                        $term[$field] = $this->sanitize_term_field($field, $term[$field], $term_id, $taxonomy, $context);
     922                        }
     923                }
     924
     925                if ( $do_object )
     926                        $term->filter = $context;
     927                else
     928                        $term['filter'] = $context;
     929
     930                return $term;
     931        }
     932
     933        /**
     934         * Cleanse the field value in the term based on the context.
     935         *
     936         * Passing a term field value through the function should be assumed to have
     937         * cleansed the value for whatever context the term field is going to be used.
     938         *
     939         * If no context or an unsupported context is given, then default filters will
     940         * be applied.
     941         *
     942         * There are enough filters for each context to support a custom filtering
     943         * without creating your own filter function. Simply create a function that
     944         * hooks into the filter you need.
     945         *
     946         * @package WordPress
     947         * @subpackage Taxonomy
     948         * @since 2.3.0
     949         *
     950         * @param string $field Term field to sanitize
     951         * @param string $value Search for this term value
     952         * @param int $term_id Term ID
     953         * @param string $taxonomy Taxonomy Name
     954         * @param string $context Either edit, db, display, attribute, or js.
     955         * @return mixed sanitized field
     956         */
     957        function sanitize_term_field($field, $value, $term_id, $taxonomy, $context) {
     958                if ( 'parent' == $field  || 'term_id' == $field || 'count' == $field || 'term_group' == $field ) {
     959                        $value = (int) $value;
     960                        if ( $value < 0 )
     961                                $value = 0;
     962                }
     963
     964                if ( 'raw' == $context )
     965                        return $value;
     966
     967                if ( 'edit' == $context ) {
     968                        $value = apply_filters("edit_term_$field", $value, $term_id, $taxonomy);
     969                        $value = apply_filters("edit_${taxonomy}_$field", $value, $term_id);
     970                        if ( 'description' == $field )
     971                                $value = $this->format_to_edit($value);
     972                        else
     973                                $value = esc_attr($value);
     974                } else if ( 'db' == $context ) {
     975                        $value = apply_filters("pre_term_$field", $value, $taxonomy);
     976                        $value = apply_filters("pre_${taxonomy}_$field", $value);
     977                        // WP DIFF
     978                } else if ( 'rss' == $context ) {
     979                        $value = apply_filters("term_${field}_rss", $value, $taxonomy);
     980                        $value = apply_filters("${taxonomy}_${field}_rss", $value);
     981                } else {
     982                        // Use display filters by default.
     983                        $value = apply_filters("term_$field", $value, $term_id, $taxonomy, $context);
     984                        $value = apply_filters("${taxonomy}_$field", $value, $term_id, $context);
     985                }
     986
     987                if ( 'attribute' == $context )
     988                        $value = esc_attr($value);
     989                else if ( 'js' == $context )
     990                        $value = esc_js($value);
     991
     992                return $value;
     993        }
     994
     995        /**
     996         * Count how many terms are in Taxonomy.
     997         *
     998         * Default $args is 'ignore_empty' which can be <code>'ignore_empty=true'</code>
     999         * or <code>array('ignore_empty' => true);</code>.
     1000         *
     1001         * @package WordPress
     1002         * @subpackage Taxonomy
     1003         * @since 2.3.0
     1004         *
     1005         * @uses wp_parse_args() Turns strings into arrays and merges defaults into an array.
     1006         *
     1007         * @param string $taxonomy Taxonomy name
     1008         * @param array|string $args Overwrite defaults
     1009         * @return int How many terms are in $taxonomy
     1010         */
     1011        function count_terms( $taxonomy, $args = array() ) {
     1012                $defaults = array('ignore_empty' => false);
     1013                $args = wp_parse_args($args, $defaults);
     1014                extract($args, EXTR_SKIP);
     1015
     1016                $where = '';
     1017                if ( $ignore_empty )
     1018                        $where = 'AND count > 0';
     1019
     1020                return $this->db->get_var( $this->db->prepare( "SELECT COUNT(*) FROM {$this->db->term_taxonomy} WHERE taxonomy = %s $where", $taxonomy ) );
     1021        }
     1022
     1023        /**
     1024         * Will unlink the term from the taxonomy.
     1025         *
     1026         * Will remove the term's relationship to the taxonomy, not the term or taxonomy
     1027         * itself. The term and taxonomy will still exist. Will require the term's
     1028         * object ID to perform the operation.
     1029         *
     1030         * @package WordPress
     1031         * @subpackage Taxonomy
     1032         * @since 2.3.0
     1033         *
     1034         * @param int $object_id The term Object Id that refers to the term
     1035         * @param string|array $taxonomy List of Taxonomy Names or single Taxonomy name.
     1036         */
     1037        function delete_object_term_relationships( $object_id, $taxonomies ) {
     1038                $object_id = (int) $object_id;
     1039
     1040                if ( !is_array($taxonomies) )
     1041                        $taxonomies = array($taxonomies);
     1042
     1043                foreach ( (array) $taxonomies as $taxonomy ) {
     1044                        $terms = $this->get_object_terms($object_id, $taxonomy, array('fields' => 'tt_ids'));
     1045                        $in_terms = "'" . implode("', '", $terms) . "'";
     1046                        $this->db->query( $this->db->prepare( "DELETE FROM {$this->db->term_relationships} WHERE object_id = %d AND term_taxonomy_id IN ($in_terms)", $object_id ) );
     1047                        $this->update_term_count($terms, $taxonomy);
     1048                }
     1049        }
     1050
     1051        /**
     1052         * Removes a term from the database.
     1053         *
     1054         * If the term is a parent of other terms, then the children will be updated to
     1055         * that term's parent.
     1056         *
     1057         * The $args 'default' will only override the terms found, if there is only one
     1058         * term found. Any other and the found terms are used.
     1059         *
     1060         * The $args 'force_default' will force the term supplied as default to be
     1061         * assigned even if the object was not going to be termless
     1062         * @package WordPress
     1063         * @subpackage Taxonomy
     1064         * @since 2.3.0
     1065         *
     1066         * @uses do_action() Calls both 'delete_term' and 'delete_$taxonomy' action
     1067         *  hooks, passing term object, term id. 'delete_term' gets an additional
     1068         *  parameter with the $taxonomy parameter.
     1069         *
     1070         * @param int $term Term ID
     1071         * @param string $taxonomy Taxonomy Name
     1072         * @param array|string $args Optional. Change 'default' term id and override found term ids.
     1073         * @return bool|WP_Error Returns false if not term; true if completes delete action.
     1074         */
     1075        function delete_term( $term, $taxonomy, $args = array() ) {
     1076                $term = (int) $term;
     1077
     1078                if ( ! $ids = $this->is_term($term, $taxonomy) )
     1079                        return false;
     1080                if ( is_wp_error( $ids ) )
     1081                        return $ids;
     1082
     1083                $tt_id = $ids['term_taxonomy_id'];
     1084
     1085                $defaults = array();
     1086                $args = wp_parse_args($args, $defaults);
     1087                extract($args, EXTR_SKIP);
     1088
     1089                if ( isset($default) ) {
     1090                        $default = (int) $default;
     1091                        if ( !$this->is_term($default, $taxonomy) )
     1092                                unset($default);
     1093                }
     1094
     1095                // Update children to point to new parent
     1096                if ( $this->is_taxonomy_hierarchical($taxonomy) ) {
     1097                        $term_obj = $this->get_term($term, $taxonomy);
     1098                        if ( is_wp_error( $term_obj ) )
     1099                                return $term_obj;
     1100                        $parent = $term_obj->parent;
     1101
     1102                        $this->db->update( $this->db->term_taxonomy, compact( 'parent' ), array( 'parent' => $term_obj->term_id ) + compact( 'taxonomy' ) );
     1103                }
     1104
     1105                $objects = $this->db->get_col( $this->db->prepare( "SELECT object_id FROM {$this->db->term_relationships} WHERE term_taxonomy_id = %d", $tt_id ) );
     1106
     1107                foreach ( (array) $objects as $object ) {
     1108                        $terms = $this->get_object_terms($object, $taxonomy, array('fields' => 'ids', 'orderby' => 'none'));
     1109                        if ( 1 == count($terms) && isset($default) ) {
     1110                                $terms = array($default);
     1111                        } else {
     1112                                $terms = array_diff($terms, array($term));
     1113                                if (isset($default) && isset($force_default) && $force_default)
     1114                                        $terms = array_merge($terms, array($default));
     1115                        }
     1116                        $terms = array_map('intval', $terms);
     1117                        $this->set_object_terms($object, $terms, $taxonomy);
     1118                }
     1119
     1120                $this->db->query( $this->db->prepare( "DELETE FROM {$this->db->term_taxonomy} WHERE term_taxonomy_id = %d", $tt_id ) );
     1121
     1122                // Delete the term if no taxonomies use it.
     1123                if ( !$this->db->get_var( $this->db->prepare( "SELECT COUNT(*) FROM {$this->db->term_taxonomy} WHERE term_id = %d", $term) ) )
     1124                        $this->db->query( $this->db->prepare( "DELETE FROM {$this->db->terms} WHERE term_id = %d", $term) );
     1125
     1126                $this->clean_term_cache($term, $taxonomy);
     1127
     1128                do_action('delete_term', $term, $tt_id, $taxonomy);
     1129                do_action("delete_$taxonomy", $term, $tt_id);
     1130
     1131                return true;
     1132        }
     1133
     1134        /**
     1135         * Retrieves the terms associated with the given object(s), in the supplied taxonomies.
     1136         *
     1137         * The following information has to do the $args parameter and for what can be
     1138         * contained in the string or array of that parameter, if it exists.
     1139         *
     1140         * The first argument is called, 'orderby' and has the default value of 'name'.
     1141         * The other value that is supported is 'count'.
     1142         *
     1143         * The second argument is called, 'order' and has the default value of 'ASC'.
     1144         * The only other value that will be acceptable is 'DESC'.
     1145         *
     1146         * The final argument supported is called, 'fields' and has the default value of
     1147         * 'all'. There are multiple other options that can be used instead. Supported
     1148         * values are as follows: 'all', 'ids', 'names', and finally
     1149         * 'all_with_object_id'.
     1150         *
     1151         * The fields argument also decides what will be returned. If 'all' or
     1152         * 'all_with_object_id' is choosen or the default kept intact, then all matching
     1153         * terms objects will be returned. If either 'ids' or 'names' is used, then an
     1154         * array of all matching term ids or term names will be returned respectively.
     1155         *
     1156         * @package WordPress
     1157         * @subpackage Taxonomy
     1158         * @since 2.3.0
     1159         *
     1160         * @param int|array $object_id The id of the object(s) to retrieve.
     1161         * @param string|array $taxonomies The taxonomies to retrieve terms from.
     1162         * @param array|string $args Change what is returned
     1163         * @return array|WP_Error The requested term data or empty array if no terms found. WP_Error if $taxonomy does not exist.
     1164         */
     1165        function get_object_terms($object_ids, $taxonomies, $args = array()) {
     1166                if ( !is_array($taxonomies) )
     1167                        $taxonomies = array($taxonomies);
     1168
     1169                foreach ( (array) $taxonomies as $taxonomy ) {
     1170                        if ( !$this->is_taxonomy($taxonomy) )
     1171                                return new WP_Error('invalid_taxonomy', __('Invalid Taxonomy'));
     1172                }
     1173
     1174                if ( !is_array($object_ids) )
     1175                        $object_ids = array($object_ids);
     1176                $object_ids = array_map('intval', $object_ids);
     1177
     1178                $defaults = array('orderby' => 'name', 'order' => 'ASC', 'fields' => 'all');
     1179                $args = wp_parse_args( $args, $defaults );
     1180
     1181                $terms = array();
     1182                if ( count($taxonomies) > 1 ) {
     1183                        foreach ( $taxonomies as $index => $taxonomy ) {
     1184                                $t = $this->get_taxonomy($taxonomy);
     1185                                if ( isset($t->args) && is_array($t->args) && $args != array_merge($args, $t->args) ) {
     1186                                        unset($taxonomies[$index]);
     1187                                        $terms = array_merge($terms, $this->get_object_terms($object_ids, $taxonomy, array_merge($args, $t->args)));
     1188                                }
     1189                        }
     1190                } else {
     1191                        $t = $this->get_taxonomy($taxonomies[0]);
     1192                        if ( isset($t->args) && is_array($t->args) )
     1193                                $args = array_merge($args, $t->args);
     1194                }
     1195
     1196                extract($args, EXTR_SKIP);
     1197
     1198                if ( 'count' == $orderby )
     1199                        $orderby = 'tt.count';
     1200                else if ( 'name' == $orderby )
     1201                        $orderby = 't.name';
     1202                else if ( 'slug' == $orderby )
     1203                        $orderby = 't.slug';
     1204                else if ( 'term_group' == $orderby )
     1205                        $orderby = 't.term_group';
     1206                else if ( 'term_order' == $orderby )
     1207                        $orderby = 'tr.term_order';
     1208                else if ( 'none' == $orderby ) {
     1209                        $orderby = '';
     1210                        $order = '';
     1211                } else {
     1212                        $orderby = 't.term_id';
     1213                }
     1214
     1215                // tt_ids queries can only be none or tr.term_taxonomy_id
     1216                if ( ('tt_ids' == $fields) && !empty($orderby) )
     1217                        $orderby = 'tr.term_taxonomy_id';
     1218
     1219                if ( !empty($orderby) )
     1220                        $orderby = "ORDER BY $orderby";
     1221
     1222                $taxonomies = "'" . implode("', '", $taxonomies) . "'";
     1223                $object_ids = implode(', ', $object_ids);
     1224
     1225                $select_this = '';
     1226                if ( 'all' == $fields )
     1227                        $select_this = 't.*, tt.*';
     1228                else if ( 'ids' == $fields )
     1229                        $select_this = 't.term_id';
     1230                else if ( 'names' == $fields )
     1231                        $select_this = 't.name';
     1232                else if ( 'all_with_object_id' == $fields )
     1233                        $select_this = 't.*, tt.*, tr.object_id';
     1234
     1235                $query = "SELECT $select_this FROM {$this->db->terms} AS t INNER JOIN {$this->db->term_taxonomy} AS tt ON tt.term_id = t.term_id INNER JOIN {$this->db->term_relationships} AS tr ON tr.term_taxonomy_id = tt.term_taxonomy_id WHERE tt.taxonomy IN ($taxonomies) AND tr.object_id IN ($object_ids) $orderby $order";
     1236
     1237                if ( 'all' == $fields || 'all_with_object_id' == $fields ) {
     1238                        $terms = array_merge($terms, $this->db->get_results($query));
     1239                        $this->update_term_cache($terms);
     1240                } else if ( 'ids' == $fields || 'names' == $fields ) {
     1241                        $terms = array_merge($terms, $this->db->get_col($query));
     1242                } else if ( 'tt_ids' == $fields ) {
     1243                        $terms = $this->db->get_col("SELECT tr.term_taxonomy_id FROM {$this->db->term_relationships} AS tr INNER JOIN {$this->db->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");
     1244                }
     1245
     1246                if ( ! $terms )
     1247                        $terms = array();
     1248
     1249                return apply_filters('wp_get_object_terms', $terms, $object_ids, $taxonomies, $args);
     1250        }
     1251
     1252        /**
     1253         * Adds a new term to the database. Optionally marks it as an alias of an existing term.
     1254         *
     1255         * Error handling is assigned for the nonexistance of the $taxonomy and $term
     1256         * parameters before inserting. If both the term id and taxonomy exist
     1257         * previously, then an array will be returned that contains the term id and the
     1258         * contents of what is returned. The keys of the array are 'term_id' and
     1259         * 'term_taxonomy_id' containing numeric values.
     1260         *
     1261         * It is assumed that the term does not yet exist or the above will apply. The
     1262         * term will be first added to the term table and then related to the taxonomy
     1263         * if everything is well. If everything is correct, then several actions will be
     1264         * run prior to a filter and then several actions will be run after the filter
     1265         * is run.
     1266         *
     1267         * The arguments decide how the term is handled based on the $args parameter.
     1268         * The following is a list of the available overrides and the defaults.
     1269         *
     1270         * 'alias_of'. There is no default, but if added, expected is the slug that the
     1271         * term will be an alias of. Expected to be a string.
     1272         *
     1273         * 'description'. There is no default. If exists, will be added to the database
     1274         * along with the term. Expected to be a string.
     1275         *
     1276         * 'parent'. Expected to be numeric and default is 0 (zero). Will assign value
     1277         * of 'parent' to the term.
     1278         *
     1279         * 'slug'. Expected to be a string. There is no default.
     1280         *
     1281         * If 'slug' argument exists then the slug will be checked to see if it is not
     1282         * a valid term. If that check succeeds (it is not a valid term), then it is
     1283         * added and the term id is given. If it fails, then a check is made to whether
     1284         * the taxonomy is hierarchical and the parent argument is not empty. If the
     1285         * second check succeeds, the term will be inserted and the term id will be
     1286         * given.
     1287         *
     1288         * @package WordPress
     1289         * @subpackage Taxonomy
     1290         * @since 2.3.0
     1291         *
     1292         * @uses do_action() Calls 'create_term' hook with the term id and taxonomy id as parameters.
     1293         * @uses do_action() Calls 'create_$taxonomy' hook with term id and taxonomy id as parameters.
     1294         * @uses apply_filters() Calls 'term_id_filter' hook with term id and taxonomy id as parameters.
     1295         * @uses do_action() Calls 'created_term' hook with the term id and taxonomy id as parameters.
     1296         * @uses do_action() Calls 'created_$taxonomy' hook with term id and taxonomy id as parameters.
     1297         *
     1298         * @param int|string $term The term to add or update.
     1299         * @param string $taxonomy The taxonomy to which to add the term
     1300         * @param array|string $args Change the values of the inserted term
     1301         * @return array|WP_Error The Term ID and Term Taxonomy ID
     1302         */
     1303        function insert_term( $term, $taxonomy, $args = array() ) {
     1304                if ( !$this->is_taxonomy($taxonomy) )
     1305                        return new WP_Error('invalid_taxonomy', __('Invalid taxonomy'));
     1306
     1307                if ( is_int($term) && 0 == $term )
     1308                        return new WP_Error('invalid_term_id', __('Invalid term ID'));
     1309
     1310                if ( '' == trim($term) )
     1311                        return new WP_Error('empty_term_name', __('A name is required for this term'));
     1312
     1313                $defaults = array( 'alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => '');
     1314                $args = wp_parse_args($args, $defaults);
     1315                $args['name'] = $term;
     1316                $args['taxonomy'] = $taxonomy;
     1317                $args = $this->sanitize_term($args, $taxonomy, 'db');
     1318                extract($args, EXTR_SKIP);
     1319
     1320                // expected_slashed ($name)
     1321                $name = stripslashes($name);
     1322                $description = stripslashes($description);
     1323
     1324                if ( empty($slug) )
     1325                        $slug = $this->sanitize_term_slug($name, $taxonomy);
     1326
     1327                $term_group = 0;
     1328                if ( $alias_of ) {
     1329                        $alias = $this->db->get_row( $this->db->prepare( "SELECT term_id, term_group FROM {$this->db->terms} WHERE slug = %s", $alias_of) );
     1330                        if ( $alias->term_group ) {
     1331                                // The alias we want is already in a group, so let's use that one.
     1332                                $term_group = $alias->term_group;
     1333                        } else {
     1334                                // The alias isn't in a group, so let's create a new one and firstly add the alias term to it.
     1335                                $term_group = $this->db->get_var("SELECT MAX(term_group) FROM {$this->db->terms}") + 1;
     1336                                $this->db->query( $this->db->prepare( "UPDATE {$this->db->terms} SET term_group = %d WHERE term_id = %d", $term_group, $alias->term_id ) );
     1337                        }
     1338                }
     1339
     1340                if ( ! $term_id = $this->is_term($slug) ) {
     1341                        if ( false === $this->db->insert( $this->db->terms, compact( 'name', 'slug', 'term_group' ) ) )
     1342                                return new WP_Error('db_insert_error', __('Could not insert term into the database'), $this->db->last_error);
     1343                        $term_id = (int) $this->db->insert_id;
     1344                } else if ( $this->is_taxonomy_hierarchical($taxonomy) && !empty($parent) ) {
     1345                        // If the taxonomy supports hierarchy and the term has a parent, make the slug unique
     1346                        // by incorporating parent slugs.
     1347                        $slug = $this->unique_term_slug($slug, (object) $args);
     1348                        if ( false === $this->db->insert( $this->db->terms, compact( 'name', 'slug', 'term_group' ) ) )
     1349                                return new WP_Error('db_insert_error', __('Could not insert term into the database'), $this->db->last_error);
     1350                        $term_id = (int) $this->db->insert_id;
     1351                }
     1352
     1353                if ( empty($slug) ) {
     1354                        $slug = $this->sanitize_term_slug($slug, $taxonomy, $term_id);
     1355                        $this->db->update( $this->db->terms, compact( 'slug' ), compact( 'term_id' ) );
     1356                }
     1357
     1358                $tt_id = $this->db->get_var( $this->db->prepare( "SELECT tt.term_taxonomy_id FROM {$this->db->term_taxonomy} AS tt INNER JOIN {$this->db->terms} AS t ON tt.term_id = t.term_id WHERE tt.taxonomy = %s AND t.term_id = %d", $taxonomy, $term_id ) );
     1359
     1360                if ( !empty($tt_id) )
     1361                        return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
     1362
     1363                $this->db->insert( $this->db->term_taxonomy, compact( 'term_id', 'taxonomy', 'description', 'parent') + array( 'count' => 0 ) );
     1364                $tt_id = (int) $this->db->insert_id;
     1365
     1366                do_action("create_term", $term_id, $tt_id);
     1367                do_action("create_$taxonomy", $term_id, $tt_id);
     1368
     1369                $term_id = apply_filters('term_id_filter', $term_id, $tt_id);
     1370
     1371                $this->clean_term_cache($term_id, $taxonomy);
     1372
     1373                do_action("created_term", $term_id, $tt_id);
     1374                do_action("created_$taxonomy", $term_id, $tt_id);
     1375
     1376                return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
     1377        }
     1378
     1379        /**
     1380         * Create Term and Taxonomy Relationships.
     1381         *
     1382         * Relates an object (post, link etc) to a term and taxonomy type. Creates the
     1383         * term and taxonomy relationship if it doesn't already exist. Creates a term if
     1384         * it doesn't exist (using the slug).
     1385         *
     1386         * A relationship means that the term is grouped in or belongs to the taxonomy.
     1387         * A term has no meaning until it is given context by defining which taxonomy it
     1388         * exists under.
     1389         *
     1390         * @package WordPress
     1391         * @subpackage Taxonomy
     1392         * @since 2.3.0
     1393         *
     1394         * @param int $object_id The object to relate to.
     1395         * @param array|int|string $term The slug or id of the term, will replace all existing
     1396         * related terms in this taxonomy.
     1397         * @param array|string $taxonomy The context in which to relate the term to the object.
     1398         * @param bool $append If false will delete difference of terms.
     1399         * @return array|WP_Error Affected Term IDs
     1400         */
     1401        function set_object_terms($object_id, $terms, $taxonomy, $append = false) {
     1402                $object_id = (int) $object_id;
     1403
     1404                if ( !$this->is_taxonomy($taxonomy) )
     1405                        return new WP_Error('invalid_taxonomy', __('Invalid Taxonomy'));
     1406
     1407                if ( !is_array($terms) )
     1408                        $terms = array($terms);
     1409
     1410                if ( ! $append )
     1411                        $old_tt_ids = $this->get_object_terms($object_id, $taxonomy, array('fields' => 'tt_ids', 'orderby' => 'none'));
     1412
     1413                $tt_ids = array();
     1414                $term_ids = array();
     1415
     1416                foreach ( (array) $terms as $term ) {
     1417                        if ( !strlen(trim($term)) )
     1418                                continue;
     1419
     1420                        if ( !$id = $this->is_term($term, $taxonomy) )
     1421                                $id = $this->insert_term($term, $taxonomy);
     1422                        if ( is_wp_error($id) )
     1423                                return $id;
     1424                        $term_ids[] = $id['term_id'];
     1425                        $id = $id['term_taxonomy_id'];
     1426                        $tt_ids[] = $id;
     1427
     1428                        if ( $this->db->get_var( $this->db->prepare( "SELECT term_taxonomy_id FROM {$this->db->term_relationships} WHERE object_id = %d AND term_taxonomy_id = %d", $object_id, $id ) ) )
     1429                                continue;
     1430                        $this->db->insert( $this->db->term_relationships, array( 'object_id' => $object_id, 'term_taxonomy_id' => $id ) );
     1431                }
     1432
     1433                $this->update_term_count($tt_ids, $taxonomy);
     1434
     1435                if ( ! $append ) {
     1436                        $delete_terms = array_diff($old_tt_ids, $tt_ids);
     1437                        if ( $delete_terms ) {
     1438                                $in_delete_terms = "'" . implode("', '", $delete_terms) . "'";
     1439                                $this->db->query( $this->db->prepare("DELETE FROM {$this->db->term_relationships} WHERE object_id = %d AND term_taxonomy_id IN ($in_delete_terms)", $object_id) );
     1440                                $this->update_term_count($delete_terms, $taxonomy);
     1441                        }
     1442                }
     1443
     1444                $t = $this->get_taxonomy($taxonomy);
     1445                if ( ! $append && isset($t->sort) && $t->sort ) {
     1446                        $values = array();
     1447                        $term_order = 0;
     1448                        $final_tt_ids = $this->get_object_terms($object_id, $taxonomy, 'fields=tt_ids');
     1449                        foreach ( $tt_ids as $tt_id )
     1450                                if ( in_array($tt_id, $final_tt_ids) )
     1451                                        $values[] = $this->db->prepare( "(%d, %d, %d)", $object_id, $tt_id, ++$term_order);
     1452                        if ( $values )
     1453                                $this->db->query("INSERT INTO {$this->db->term_relationships} (object_id, term_taxonomy_id, term_order) VALUES " . join(',', $values) . " ON DUPLICATE KEY UPDATE term_order = VALUES(term_order)");
     1454                }
     1455
     1456                do_action('set_object_terms', $object_id, $terms, $tt_ids, $taxonomy, $append);
     1457                return $tt_ids;
     1458        }
     1459
     1460        /**
     1461         * Will make slug unique, if it isn't already.
     1462         *
     1463         * The $slug has to be unique global to every taxonomy, meaning that one
     1464         * taxonomy term can't have a matching slug with another taxonomy term. Each
     1465         * slug has to be globally unique for every taxonomy.
     1466         *
     1467         * The way this works is that if the taxonomy that the term belongs to is
     1468         * heirarchical and has a parent, it will append that parent to the $slug.
     1469         *
     1470         * If that still doesn't return an unique slug, then it try to append a number
     1471         * until it finds a number that is truely unique.
     1472         *
     1473         * The only purpose for $term is for appending a parent, if one exists.
     1474         *
     1475         * @package WordPress
     1476         * @subpackage Taxonomy
     1477         * @since 2.3.0
     1478         *
     1479         * @param string $slug The string that will be tried for a unique slug
     1480         * @param object $term The term object that the $slug will belong too
     1481         * @return string Will return a true unique slug.
     1482         */
     1483        function unique_term_slug($slug, $term) {
     1484                // If the taxonomy supports hierarchy and the term has a parent, make the slug unique
     1485                // by incorporating parent slugs.
     1486                if ( $this->is_taxonomy_hierarchical($term->taxonomy) && !empty($term->parent) ) {
     1487                        $the_parent = $term->parent;
     1488                        while ( ! empty($the_parent) ) {
     1489                                $parent_term = $this->get_term($the_parent, $term->taxonomy);
     1490                                if ( is_wp_error($parent_term) || empty($parent_term) )
     1491                                        break;
     1492                                        $slug .= '-' . $parent_term->slug;
     1493                                if ( empty($parent_term->parent) )
     1494                                        break;
     1495                                $the_parent = $parent_term->parent;
     1496                        }
     1497                }
     1498
     1499                // If we didn't get a unique slug, try appending a number to make it unique.
     1500                if ( !empty($args['term_id']) )
     1501                        $query = $this->db->prepare( "SELECT slug FROM {$this->db->terms} WHERE slug = %s AND term_id != %d", $slug, $args['term_id'] );
     1502                else
     1503                        $query = $this->db->prepare( "SELECT slug FROM {$this->db->terms} WHERE slug = %s", $slug );
     1504
     1505                if ( $this->db->get_var( $query ) ) {
     1506                        $num = 2;
     1507                        do {
     1508                                $alt_slug = $slug . "-$num";
     1509                                $num++;
     1510                                $slug_check = $this->db->get_var( $this->db->prepare( "SELECT slug FROM {$this->db->terms} WHERE slug = %s", $alt_slug ) );
     1511                        } while ( $slug_check );
     1512                        $slug = $alt_slug;
     1513                }
     1514
     1515                return $slug;
     1516        }
     1517
     1518        /**
     1519         * Update term based on arguments provided.
     1520         *
     1521         * The $args will indiscriminately override all values with the same field name.
     1522         * Care must be taken to not override important information need to update or
     1523         * update will fail (or perhaps create a new term, neither would be acceptable).
     1524         *
     1525         * Defaults will set 'alias_of', 'description', 'parent', and 'slug' if not
     1526         * defined in $args already.
     1527         *
     1528         * 'alias_of' will create a term group, if it doesn't already exist, and update
     1529         * it for the $term.
     1530         *
     1531         * If the 'slug' argument in $args is missing, then the 'name' in $args will be
     1532         * used. It should also be noted that if you set 'slug' and it isn't unique then
     1533         * a WP_Error will be passed back. If you don't pass any slug, then a unique one
     1534         * will be created for you.
     1535         *
     1536         * For what can be overrode in $args, check the term scheme can contain and stay
     1537         * away from the term keys.
     1538         *
     1539         * @package WordPress
     1540         * @subpackage Taxonomy
     1541         * @since 2.3.0
     1542         *
     1543         * @uses do_action() Will call both 'edit_term' and 'edit_$taxonomy' twice.
     1544         * @uses apply_filters() Will call the 'term_id_filter' filter and pass the term
     1545         *  id and taxonomy id.
     1546         *
     1547         * @param int $term_id The ID of the term
     1548         * @param string $taxonomy The context in which to relate the term to the object.
     1549         * @param array|string $args Overwrite term field values
     1550         * @return array|WP_Error Returns Term ID and Taxonomy Term ID
     1551         */
     1552        function update_term( $term_id, $taxonomy, $args = array() ) {
     1553                if ( !$this->is_taxonomy($taxonomy) )
     1554                        return new WP_Error('invalid_taxonomy', __('Invalid taxonomy'));
     1555
     1556                $term_id = (int) $term_id;
     1557
     1558                // First, get all of the original args
     1559                $term = $this->get_term($term_id, $taxonomy, ARRAY_A);
     1560
     1561                if ( is_wp_error( $term ) )
     1562                        return $term;
     1563
     1564                // Merge old and new args with new args overwriting old ones.
     1565                $args = array_merge($term, $args);
     1566
     1567                $defaults = array( 'alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => '');
     1568                $args = wp_parse_args($args, $defaults);
     1569                $args = $this->sanitize_term($args, $taxonomy, 'db');
     1570                extract($args, EXTR_SKIP);
     1571
     1572                // expected_slashed ($name)
     1573                $name = stripslashes($name);
     1574                $description = stripslashes($description);
     1575
     1576                if ( '' == trim($name) )
     1577                        return new WP_Error('empty_term_name', __('A name is required for this term'));
     1578
     1579                $empty_slug = false;
     1580                if ( empty($slug) ) {
     1581                        $empty_slug = true;
     1582                        $slug = $this->sanitize_term_slug($name, $taxonomy, $term_id);
     1583                }
     1584
     1585                if ( $alias_of ) {
     1586                        $alias = $this->db->get_row( $this->db->prepare( "SELECT term_id, term_group FROM {$this->db->terms} WHERE slug = %s", $alias_of) );
     1587                        if ( $alias->term_group ) {
     1588                                // The alias we want is already in a group, so let's use that one.
     1589                                $term_group = $alias->term_group;
     1590                        } else {
     1591                                // The alias isn't in a group, so let's create a new one and firstly add the alias term to it.
     1592                                $term_group = $this->db->get_var("SELECT MAX(term_group) FROM {$this->db->terms}") + 1;
     1593                                $this->db->update( $this->db->terms, compact('term_group'), array( 'term_id' => $alias->term_id ) );
     1594                        }
     1595                }
     1596
     1597                // Check for duplicate slug
     1598                $id = $this->db->get_var( $this->db->prepare( "SELECT term_id FROM {$this->db->terms} WHERE slug = %s", $slug ) );
     1599                if ( $id && ($id != $term_id) ) {
     1600                        // If an empty slug was passed or the parent changed, reset the slug to something unique.
     1601                        // Otherwise, bail.
     1602                        if ( $empty_slug || ( $parent != $term->parent) )
     1603                                $slug = $this->unique_term_slug($slug, (object) $args);
     1604                        else
     1605                                return new WP_Error('duplicate_term_slug', sprintf(__('The slug &#8220;%s&#8221; is already in use by another term'), $slug));
     1606                }
     1607
     1608                $this->db->update($this->db->terms, compact( 'name', 'slug', 'term_group' ), compact( 'term_id' ) );
     1609
     1610                if ( empty($slug) ) {
     1611                        $slug = $this->sanitize_term_slug($name, $taxonomy, $term_id);
     1612                        $this->db->update( $this->db->terms, compact( 'slug' ), compact( 'term_id' ) );
     1613                }
     1614
     1615                $tt_id = $this->db->get_var( $this->db->prepare( "SELECT tt.term_taxonomy_id FROM {$this->db->term_taxonomy} AS tt INNER JOIN {$this->db->terms} AS t ON tt.term_id = t.term_id WHERE tt.taxonomy = %s AND t.term_id = %d", $taxonomy, $term_id) );
     1616
     1617                $this->db->update( $this->db->term_taxonomy, compact( 'term_id', 'taxonomy', 'description', 'parent' ), array( 'term_taxonomy_id' => $tt_id ) );
     1618
     1619                do_action("edit_term", $term_id, $tt_id);
     1620                do_action("edit_$taxonomy", $term_id, $tt_id);
     1621
     1622                $term_id = apply_filters('term_id_filter', $term_id, $tt_id);
     1623
     1624                $this->clean_term_cache($term_id, $taxonomy);
     1625
     1626                do_action("edited_term", $term_id, $tt_id);
     1627                do_action("edited_$taxonomy", $term_id, $tt_id);
     1628
     1629                return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
     1630        }
     1631
     1632        /**
     1633         * Enable or disable term counting.
     1634         *
     1635         * @since 2.5.0
     1636         *
     1637         * @param bool $defer Optional. Enable if true, disable if false.
     1638         * @return bool Whether term counting is enabled or disabled.
     1639         */
     1640        function defer_term_counting($defer=NULL) {
     1641                static $_defer = false;
     1642
     1643                if ( is_bool($defer) ) {
     1644                        $_defer = $defer;
     1645                        // flush any deferred counts
     1646                        if ( !$defer )
     1647                                $this->update_term_count( NULL, NULL, true );
     1648                }
     1649
     1650                return $_defer;
     1651        }
     1652
     1653        /**
     1654         * Updates the amount of terms in taxonomy.
     1655         *
     1656         * If there is a taxonomy callback applied, then it will be called for updating
     1657         * the count.
     1658         *
     1659         * The default action is to count what the amount of terms have the relationship
     1660         * of term ID. Once that is done, then update the database.
     1661         *
     1662         * @package WordPress
     1663         * @subpackage Taxonomy
     1664         * @since 2.3.0
     1665         * @uses $this->db
     1666         *
     1667         * @param int|array $terms The ID of the terms
     1668         * @param string $taxonomy The context of the term.
     1669         * @return bool If no terms will return false, and if successful will return true.
     1670         */
     1671        function update_term_count( $terms, $taxonomy, $do_deferred=false ) {
     1672                static $_deferred = array();
     1673
     1674                if ( $do_deferred ) {
     1675                        foreach ( (array) array_keys($_deferred) as $tax ) {
     1676                                $this->update_term_count_now( $_deferred[$tax], $tax );
     1677                                unset( $_deferred[$tax] );
     1678                        }
     1679                }
     1680
     1681                if ( empty($terms) )
     1682                        return false;
     1683
     1684                if ( !is_array($terms) )
     1685                        $terms = array($terms);
     1686
     1687                if ( $this->defer_term_counting() ) {
     1688                        if ( !isset($_deferred[$taxonomy]) )
     1689                                $_deferred[$taxonomy] = array();
     1690                        $_deferred[$taxonomy] = array_unique( array_merge($_deferred[$taxonomy], $terms) );
     1691                        return true;
     1692                }
     1693
     1694                return $this->update_term_count_now( $terms, $taxonomy );
     1695        }
     1696
     1697        /**
     1698         * Perform term count update immediately.
     1699         *
     1700         * @since 2.5.0
     1701         *
     1702         * @param array $terms The term_taxonomy_id of terms to update.
     1703         * @param string $taxonomy The context of the term.
     1704         * @return bool Always true when complete.
     1705         */
     1706        function update_term_count_now( $terms, $taxonomy ) {
     1707                $terms = array_map('intval', $terms);
     1708
     1709                $taxonomy = $this->get_taxonomy($taxonomy);
     1710                if ( !empty($taxonomy->update_count_callback) ) {
     1711                        call_user_func($taxonomy->update_count_callback, $terms);
     1712                } else {
     1713                        // Default count updater
     1714                        foreach ( (array) $terms as $term ) {
     1715                                $count = $this->db->get_var( $this->db->prepare( "SELECT COUNT(*) FROM {$this->db->term_relationships} WHERE term_taxonomy_id = %d", $term) );
     1716                                $this->db->update( $this->db->term_taxonomy, compact( 'count' ), array( 'term_taxonomy_id' => $term ) );
     1717                        }
     1718
     1719                }
     1720
     1721                $this->clean_term_cache($terms);
     1722
     1723                return true;
     1724        }
     1725
     1726        //
     1727        // Cache
     1728        //
     1729
     1730        /**
     1731         * Removes the taxonomy relationship to terms from the cache.
     1732         *
     1733         * Will remove the entire taxonomy relationship containing term $object_id. The
     1734         * term IDs have to exist within the taxonomy $object_type for the deletion to
     1735         * take place.
     1736         *
     1737         * @package WordPress
     1738         * @subpackage Taxonomy
     1739         * @since 2.3
     1740         *
     1741         * @see $this->get_object_taxonomies() for more on $object_type
     1742         * @uses do_action() Will call action hook named, 'clean_object_term_cache' after completion.
     1743         *      Passes, function params in same order.
     1744         *
     1745         * @param int|array $object_ids Single or list of term object ID(s)
     1746         * @param string $object_type The taxonomy object type
     1747         */
     1748        function clean_object_term_cache($object_ids, $object_type) {
     1749                if ( !is_array($object_ids) )
     1750                        $object_ids = array($object_ids);
     1751
     1752                foreach ( $object_ids as $id )
     1753                        foreach ( $this->get_object_taxonomies($object_type) as $taxonomy )
     1754                                wp_cache_delete($id, "{$taxonomy}_relationships");
     1755
     1756                do_action('clean_object_term_cache', $object_ids, $object_type);
     1757        }
     1758
     1759        /**
     1760         * Will remove all of the term ids from the cache.
     1761         *
     1762         * @package WordPress
     1763         * @subpackage Taxonomy
     1764         * @since 2.3.0
     1765         *
     1766         * @param int|array $ids Single or list of Term IDs
     1767         * @param string $taxonomy Can be empty and will assume tt_ids, else will use for context.
     1768         */
     1769        function clean_term_cache($ids, $taxonomy = '') {
     1770                static $cleaned = array();
     1771
     1772                if ( !is_array($ids) )
     1773                        $ids = array($ids);
     1774
     1775                $taxonomies = array();
     1776                // If no taxonomy, assume tt_ids.
     1777                if ( empty($taxonomy) ) {
     1778                        $tt_ids = implode( ',', array_map( 'intval', $ids ) );
     1779                        $terms = $this->db->get_results("SELECT term_id, term_taxonomy_id, taxonomy FROM {$this->db->term_taxonomy} WHERE term_taxonomy_id IN ($tt_ids)");
     1780                        foreach ( (array) $terms as $term ) {
     1781                                $taxonomies[] = $term->taxonomy;
     1782                                wp_cache_delete($term->term_id, $term->taxonomy);
     1783                                wp_cache_delete($term->term_taxonomy_id, "{$term->taxonomy}:tt_id");
     1784                        }
     1785                        $taxonomies = array_unique($taxonomies);
     1786                } else {
     1787                        $tt_ids = implode( ',', array_map( 'intval', $ids ) );
     1788                        $terms = $this->db->get_results("SELECT term_id, term_taxonomy_id FROM {$this->db->term_taxonomy} WHERE term_id IN ($tt_ids)");
     1789                        foreach ( (array) $terms as $term ) {
     1790                                wp_cache_delete($term->term_id, $taxonomy);
     1791                                wp_cache_delete($term->term_taxonomy_id, "$taxonomy:tt_id");
     1792                        }
     1793                        $taxonomies = array($taxonomy);
     1794                }
     1795
     1796                foreach ( $taxonomies as $taxonomy ) {
     1797                        if ( isset($cleaned[$taxonomy]) )
     1798                                continue;
     1799                        $cleaned[$taxonomy] = true;
     1800                        wp_cache_delete('all_ids', $taxonomy);
     1801                        wp_cache_delete('get', $taxonomy);
     1802                        $this->delete_children_cache($taxonomy);
     1803                }
     1804
     1805                wp_cache_delete('get_terms', 'terms');
     1806
     1807                do_action('clean_term_cache', $ids, $taxonomy);
     1808        }
     1809
     1810        /**
     1811         * Retrieves the taxonomy relationship to the term object id.
     1812         *
     1813         * @package WordPress
     1814         * @subpackage Taxonomy
     1815         * @since 2.3.0
     1816         *
     1817         * @uses wp_cache_get() Retrieves taxonomy relationship from cache
     1818         *
     1819         * @param int|array $id Term object ID
     1820         * @param string $taxonomy Taxonomy Name
     1821         * @return bool|array Empty array if $terms found, but not $taxonomy. False if nothing is in cache for $taxonomy and $id.
     1822         */
     1823        function &get_object_term_cache($id, $taxonomy) {
     1824                $cache = wp_cache_get($id, "{$taxonomy}_relationships");
     1825                return $cache;
     1826        }
     1827
     1828        /**
     1829         * Updates the cache for Term ID(s).
     1830         *
     1831         * Will only update the cache for terms not already cached.
     1832         *
     1833         * The $object_ids expects that the ids be separated by commas, if it is a
     1834         * string.
     1835         *
     1836         * It should be noted that update_object_term_cache() is very time extensive. It
     1837         * is advised that the function is not called very often or at least not for a
     1838         * lot of terms that exist in a lot of taxonomies. The amount of time increases
     1839         * for each term and it also increases for each taxonomy the term belongs to.
     1840         *
     1841         * @package WordPress
     1842         * @subpackage Taxonomy
     1843         * @since 2.3.0
     1844         * @uses $this->get_object_terms() Used to get terms from the database to update
     1845         *
     1846         * @param string|array $object_ids Single or list of term object ID(s)
     1847         * @param string $object_type The taxonomy object type
     1848         * @return null|bool Null value is given with empty $object_ids. False if
     1849         */
     1850        function update_object_term_cache($object_ids, $object_type) {
     1851                if ( empty($object_ids) )
     1852                        return;
     1853
     1854                if ( !is_array($object_ids) )
     1855                        $object_ids = explode(',', $object_ids);
     1856
     1857                $object_ids = array_map('intval', $object_ids);
     1858
     1859                $taxonomies = $this->get_object_taxonomies($object_type);
     1860
     1861                $ids = array();
     1862                foreach ( (array) $object_ids as $id ) {
     1863                        foreach ( $taxonomies as $taxonomy ) {
     1864                                if ( false === wp_cache_get($id, "{$taxonomy}_relationships") ) {
     1865                                        $ids[] = $id;
     1866                                        break;
     1867                                }
     1868                        }
     1869                }
     1870
     1871                if ( empty( $ids ) )
     1872                        return false;
     1873
     1874                $terms = $this->get_object_terms($ids, $taxonomies, 'fields=all_with_object_id');
     1875
     1876                $object_terms = array();
     1877                foreach ( (array) $terms as $term )
     1878                        $object_terms[$term->object_id][$term->taxonomy][$term->term_id] = $term;
     1879
     1880                foreach ( $ids as $id ) {
     1881                        foreach ( $taxonomies  as $taxonomy ) {
     1882                                if ( ! isset($object_terms[$id][$taxonomy]) ) {
     1883                                        if ( !isset($object_terms[$id]) )
     1884                                                $object_terms[$id] = array();
     1885                                        $object_terms[$id][$taxonomy] = array();
     1886                                }
     1887                        }
     1888                }
     1889
     1890                foreach ( $object_terms as $id => $value ) {
     1891                        foreach ( $value as $taxonomy => $terms ) {
     1892                                wp_cache_set($id, $terms, "{$taxonomy}_relationships");
     1893                        }
     1894                }
     1895        }
     1896
     1897        /**
     1898         * Updates Terms to Taxonomy in cache.
     1899         *
     1900         * @package WordPress
     1901         * @subpackage Taxonomy
     1902         * @since 2.3.0
     1903         *
     1904         * @param array $terms List of Term objects to change
     1905         * @param string $taxonomy Optional. Update Term to this taxonomy in cache
     1906         */
     1907        function update_term_cache($terms, $taxonomy = '') {
     1908                foreach ( (array) $terms as $term ) {
     1909                        $term_taxonomy = $taxonomy;
     1910                        if ( empty($term_taxonomy) )
     1911                                $term_taxonomy = $term->taxonomy;
     1912
     1913                        wp_cache_add($term->term_id, $term, $term_taxonomy);
     1914                        wp_cache_add($term->term_taxonomy_id, $term->term_id, "$term_taxonomy:tt_id");
     1915                }
     1916        }
     1917
     1918        //
     1919        // Private
     1920        //
     1921
     1922        /**
     1923         * Retrieves children of taxonomy as Term IDs.
     1924         *
     1925         * @package WordPress
     1926         * @subpackage Taxonomy
     1927         * @access private
     1928         * @since 2.3.0
     1929         *
     1930         * @uses backpress_update_option() Stores all of the children in "$taxonomy_children"
     1931         *  option. That is the name of the taxonomy, immediately followed by '_children'.
     1932         *
     1933         * @param string $taxonomy Taxonomy Name
     1934         * @return array Empty if $taxonomy isn't hierarachical or returns children as Term IDs.
     1935         */
     1936        function _get_term_hierarchy($taxonomy) {
     1937                if ( !$this->is_taxonomy_hierarchical($taxonomy) )
     1938                        return array();
     1939                $children = $this->get_children_cache($taxonomy);
     1940                if ( is_array($children) )
     1941                        return $children;
     1942
     1943                $children = array();
     1944                $terms = $this->get_terms($taxonomy, 'get=all');
     1945                foreach ( $terms as $term ) {
     1946                        if ( $term->parent > 0 )
     1947                                $children[$term->parent][] = $term->term_id;
     1948                }
     1949                $this->set_children_cache($taxonomy, $children);
     1950
     1951                return $children;
     1952        }
     1953
     1954        /**
     1955         * Get the subset of $terms that are descendants of $term_id.
     1956         *
     1957         * If $terms is an array of objects, then _get_term_children returns an array of objects.
     1958         * If $terms is an array of IDs, then _get_term_children returns an array of IDs.
     1959         *
     1960         * @package WordPress
     1961         * @subpackage Taxonomy
     1962         * @access private
     1963         * @since 2.3.0
     1964         *
     1965         * @param int $term_id The ancestor term: all returned terms should be descendants of $term_id.
     1966         * @param array $terms The set of terms---either an array of term objects or term IDs---from which those that are descendants of $term_id will be chosen.
     1967         * @param string $taxonomy The taxonomy which determines the hierarchy of the terms.
     1968         * @return array The subset of $terms that are descendants of $term_id.
     1969         */
     1970        function &_get_term_children($term_id, $terms, $taxonomy) {
     1971                $empty_array = array();
     1972                if ( empty($terms) )
     1973                        return $empty_array;
     1974
     1975                $term_list = array();
     1976                $has_children = $this->_get_term_hierarchy($taxonomy);
     1977
     1978                if  ( ( 0 != $term_id ) && ! isset($has_children[$term_id]) )
     1979                        return $empty_array;
     1980
     1981                foreach ( (array) $terms as $term ) {
     1982                        $use_id = false;
     1983                        if ( !is_object($term) ) {
     1984                                $term = $this->get_term($term, $taxonomy);
     1985                                if ( is_wp_error( $term ) )
     1986                                        return $term;
     1987                                $use_id = true;
     1988                        }
     1989
     1990                        if ( $term->term_id == $term_id )
     1991                                continue;
     1992
     1993                        if ( $term->parent == $term_id ) {
     1994                                if ( $use_id )
     1995                                        $term_list[] = $term->term_id;
     1996                                else
     1997                                        $term_list[] = $term;
     1998
     1999                                if ( !isset($has_children[$term->term_id]) )
     2000                                        continue;
     2001
     2002                                if ( $children = $this->_get_term_children($term->term_id, $terms, $taxonomy) )
     2003                                        $term_list = array_merge($term_list, $children);
     2004                        }
     2005                }
     2006
     2007                return $term_list;
     2008        }
     2009
     2010        /**
     2011         * Add count of children to parent count.
     2012         *
     2013         * Recalculates term counts by including items from child terms. Assumes all
     2014         * relevant children are already in the $terms argument.
     2015         *
     2016         * @package WordPress
     2017         * @subpackage Taxonomy
     2018         * @access private
     2019         * @since 2.3
     2020         *
     2021         * @param array $terms List of Term IDs
     2022         * @param string $taxonomy Term Context
     2023         * @return null Will break from function if conditions are not met.
     2024         */
     2025        function _pad_term_counts(&$terms, $taxonomy) {
     2026                return;
     2027        }
     2028
     2029        /**
     2030         * Determine if the given object is associated with any of the given terms.
     2031         *
     2032         * The given terms are checked against the object's terms' term_ids, names and slugs.
     2033         * Terms given as integers will only be checked against the object's terms' term_ids.
     2034         * If no terms are given, determines if object is associated with any terms in the given taxonomy.
     2035         *
     2036         * @since 2.7.0
     2037         * @uses WP_Taxonomy::get_object_term_cache()
     2038         * @uses WP_Taxonomy::get_object_terms()
     2039         *
     2040         * @param int $object_id.  ID of the object (post ID, link ID, ...)
     2041         * @param string $taxonomy.  Single taxonomy name
     2042         * @param int|string|array $terms Optional.  Term term_id, name, slug or array of said
     2043         * @return bool|WP_Error. WP_Error on input error.
     2044         */
     2045        function is_object_in_term( $object_id, $taxonomy, $terms = null ) {
     2046                if ( !$object_id = (int) $object_id )
     2047                        return new WP_Error( 'invalid_object', __( 'Invalid object ID' ) );
     2048
     2049                $object_terms = $this->get_object_term_cache( $object_id, $taxonomy );
     2050                if ( empty( $object_terms ) )
     2051                         $object_terms = $this->get_object_terms( $object_id, $taxonomy );
     2052
     2053                if ( is_wp_error( $object_terms ) )
     2054                        return $object_terms;
     2055                if ( empty( $object_terms ) )
     2056                        return false;
     2057                if ( empty( $terms ) )
     2058                        return ( !empty( $object_terms ) );
     2059
     2060                $terms = (array) $terms;
     2061
     2062                if ( $ints = array_filter( $terms, 'is_int' ) )
     2063                        $strs = array_diff( $terms, $ints );
     2064                else
     2065                        $strs =& $terms;
     2066
     2067                foreach ( $object_terms as $object_term ) {
     2068                        if ( $ints && in_array( $object_term->term_id, $ints ) ) return true; // If int, check against term_id
     2069                        if ( $strs ) {
     2070                                if ( in_array( $object_term->term_id, $strs ) ) return true;
     2071                                if ( in_array( $object_term->name, $strs ) )    return true;
     2072                                if ( in_array( $object_term->slug, $strs ) )    return true;
     2073                        }
     2074                }
     2075
     2076                return false;
     2077        }
     2078
     2079        function get_children_cache( $taxonomy ) { return false; }
     2080        function set_children_cache( $taxonomy, $children ) {}
     2081        function delete_children_cache( $taxonomy ) {}
     2082}
  • new file src/bp-forums/class.bb-taxonomy.php

    diff --git src/bp-forums/class.bb-taxonomy.php src/bp-forums/class.bb-taxonomy.php
    new file mode 100644
    index 00000000..ead4e34a
    - +  
     1<?php
     2/**
     3 * Taxonomy API
     4 *
     5 * @package bbPress
     6 * @subpackage Taxonomy
     7 * @since 1.0
     8 * @todo cache
     9 */
     10class BB_Taxonomy extends BackPress_Taxonomy
     11{
     12        /**
     13         * Retrieve object_ids of valid taxonomy and term.
     14         *
     15         * The strings of $taxonomies must exist before this function will continue. On
     16         * failure of finding a valid taxonomy, it will return an WP_Error class, kind
     17         * of like Exceptions in PHP 5, except you can't catch them. Even so, you can
     18         * still test for the WP_Error class and get the error message.
     19         *
     20         * The $terms aren't checked the same as $taxonomies, but still need to exist
     21         * for $object_ids to be returned.
     22         *
     23         * It is possible to change the order that object_ids is returned by either
     24         * using PHP sort family functions or using the database by using $args with
     25         * either ASC or DESC array. The value should be in the key named 'order'.
     26         *
     27         * @package bbPress
     28         * @subpackage Taxonomy
     29         * @since 1.0
     30         *
     31         * @uses wp_parse_args() Creates an array from string $args.
     32         *
     33         * @param string|array $terms String of term or array of string values of terms that will be used
     34         * @param string|array $taxonomies String of taxonomy name or Array of string values of taxonomy names
     35         * @param array|string $args Change the order of the object_ids, either ASC or DESC
     36         * @return WP_Error|array If the taxonomy does not exist, then WP_Error will be returned. On success
     37         *      the array can be empty meaning that there are no $object_ids found or it will return the $object_ids found.
     38         */
     39        function get_objects_in_term( $terms, $taxonomies, $args = null ) {
     40                if ( !is_array($terms) )
     41                        $terms = array($terms);
     42
     43                if ( !is_array($taxonomies) )
     44                        $taxonomies = array($taxonomies);
     45
     46                foreach ( (array) $taxonomies as $taxonomy ) {
     47                        if ( !$this->is_taxonomy($taxonomy) )
     48                                return new WP_Error('invalid_taxonomy', __('Invalid Taxonomy'));
     49                }
     50
     51                $defaults = array('order' => 'ASC', 'field' => 'term_id', 'user_id' => 0);
     52                $args = wp_parse_args( $args, $defaults );
     53                extract($args, EXTR_SKIP);
     54
     55                if ( 'tt_id' == $field )
     56                        $field = 'tt.term_taxonomy_id';
     57                else
     58                        $field = 'tt.term_id';
     59
     60                $order = ( 'desc' == strtolower($order) ) ? 'DESC' : 'ASC';
     61                $user_id = (int) $user_id;
     62
     63                $terms = array_map('intval', $terms);
     64
     65                $taxonomies = "'" . implode("', '", $taxonomies) . "'";
     66                $terms = "'" . implode("', '", $terms) . "'";
     67
     68                $sql = "SELECT tr.object_id FROM {$this->db->term_relationships} AS tr INNER JOIN {$this->db->term_taxonomy} AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id WHERE tt.taxonomy IN ($taxonomies) AND $field IN ($terms)";
     69                if ( $user_id )
     70                        $sql .= " AND tr.user_id = '$user_id'";
     71                $sql .= " ORDER BY tr.object_id $order";
     72
     73                $object_ids = $this->db->get_col( $sql );
     74
     75                if ( ! $object_ids )
     76                        return array();
     77
     78                return $object_ids;
     79        }
     80
     81        /**
     82         * Will unlink the term from the taxonomy.
     83         *
     84         * Will remove the term's relationship to the taxonomy, not the term or taxonomy
     85         * itself. The term and taxonomy will still exist. Will require the term's
     86         * object ID to perform the operation.
     87         *
     88         * @package bbPress
     89         * @subpackage Taxonomy
     90         * @since 1.0
     91         *
     92         * @param int $object_id The term Object Id that refers to the term
     93         * @param string|array $taxonomy List of Taxonomy Names or single Taxonomy name.
     94         * @param int $user_id The ID of the user who created the relationship.
     95         */
     96        function delete_object_term_relationships( $object_id, $taxonomies, $user_id = 0 ) {
     97                $object_id = (int) $object_id;
     98                $user_id = (int) $user_id;
     99
     100                if ( !is_array($taxonomies) )
     101                        $taxonomies = array($taxonomies);
     102
     103                foreach ( (array) $taxonomies as $taxonomy ) {
     104                        $terms = $this->get_object_terms($object_id, $taxonomy, array('fields' => 'tt_ids', 'user_id' => $user_id));
     105                        $in_terms = "'" . implode("', '", $terms) . "'";
     106                        $sql = "DELETE FROM {$this->db->term_relationships} WHERE object_id = %d AND term_taxonomy_id IN ($in_terms)";
     107                        if ( $user_id )
     108                                $sql .= " AND user_id = %d";
     109                        $this->db->query( $this->db->prepare( $sql, $object_id, $user_id ) );
     110                        $this->update_term_count($terms, $taxonomy);
     111                }
     112        }
     113
     114        /**
     115         * Retrieves the terms associated with the given object(s), in the supplied taxonomies.
     116         *
     117         * The following information has to do the $args parameter and for what can be
     118         * contained in the string or array of that parameter, if it exists.
     119         *
     120         * The first argument is called, 'orderby' and has the default value of 'name'.
     121         * The other value that is supported is 'count'.
     122         *
     123         * The second argument is called, 'order' and has the default value of 'ASC'.
     124         * The only other value that will be acceptable is 'DESC'.
     125         *
     126         * The final argument supported is called, 'fields' and has the default value of
     127         * 'all'. There are multiple other options that can be used instead. Supported
     128         * values are as follows: 'all', 'ids', 'names', and finally
     129         * 'all_with_object_id'.
     130         *
     131         * The fields argument also decides what will be returned. If 'all' or
     132         * 'all_with_object_id' is choosen or the default kept intact, then all matching
     133         * terms objects will be returned. If either 'ids' or 'names' is used, then an
     134         * array of all matching term ids or term names will be returned respectively.
     135         *
     136         * @package bbPress
     137         * @subpackage Taxonomy
     138         * @since 1.0
     139         *
     140         * @param int|array $object_id The id of the object(s) to retrieve.
     141         * @param string|array $taxonomies The taxonomies to retrieve terms from.
     142         * @param array|string $args Change what is returned
     143         * @return array|WP_Error The requested term data or empty array if no terms found. WP_Error if $taxonomy does not exist.
     144         */
     145        function get_object_terms($object_ids, $taxonomies, $args = array()) {
     146                if ( !is_array($taxonomies) )
     147                        $taxonomies = array($taxonomies);
     148
     149                foreach ( (array) $taxonomies as $taxonomy ) {
     150                        if ( !$this->is_taxonomy($taxonomy) )
     151                                return new WP_Error('invalid_taxonomy', __('Invalid Taxonomy'));
     152                }
     153
     154                if ( !is_array($object_ids) )
     155                        $object_ids = array($object_ids);
     156                $object_ids = array_map('intval', $object_ids);
     157
     158                $defaults = array('orderby' => 'name', 'order' => 'ASC', 'fields' => 'all', 'user_id' => 0);
     159                $args = wp_parse_args( $args, $defaults );
     160                $args['user_id'] = (int) $args['user_id'];
     161
     162                $terms = array();
     163                if ( count($taxonomies) > 1 ) {
     164                        foreach ( $taxonomies as $index => $taxonomy ) {
     165                                $t = $this->get_taxonomy($taxonomy);
     166                                if ( isset($t->args) && is_array($t->args) && $args != array_merge($args, $t->args) ) {
     167                                        unset($taxonomies[$index]);
     168                                        $terms = array_merge($terms, $this->get_object_terms($object_ids, $taxonomy, array_merge($args, $t->args)));
     169                                }
     170                        }
     171                } else {
     172                        $t = $this->get_taxonomy($taxonomies[0]);
     173                        if ( isset($t->args) && is_array($t->args) )
     174                                $args = array_merge($args, $t->args);
     175                }
     176
     177                extract($args, EXTR_SKIP);
     178                $user_id = (int) $user_id;
     179
     180                if ( 'count' == $orderby )
     181                        $orderby = 'tt.count';
     182                else if ( 'name' == $orderby )
     183                        $orderby = 't.name';
     184                else if ( 'slug' == $orderby )
     185                        $orderby = 't.slug';
     186                else if ( 'term_group' == $orderby )
     187                        $orderby = 't.term_group';
     188                else if ( 'term_order' == $orderby )
     189                        $orderby = 'tr.term_order';
     190                else if ( 'none' == $orderby ) {
     191                        $orderby = '';
     192                        $order = '';
     193                } else {
     194                        $orderby = 't.term_id';
     195                }
     196
     197                // tt_ids queries can only be none or tr.term_taxonomy_id
     198                if ( ('tt_ids' == $fields) && !empty($orderby) )
     199                        $orderby = 'tr.term_taxonomy_id';
     200
     201                if ( !empty($orderby) )
     202                        $orderby = "ORDER BY $orderby";
     203
     204                $taxonomies = "'" . implode("', '", $taxonomies) . "'";
     205                $object_ids = implode(', ', $object_ids);
     206
     207                $select_this = '';
     208                if ( 'all' == $fields )
     209                        $select_this = 't.*, tt.*, tr.user_id';
     210                else if ( 'ids' == $fields )
     211                        $select_this = 't.term_id';
     212                else if ( 'names' == $fields )
     213                        $select_this = 't.name';
     214                else if ( 'all_with_object_id' == $fields )
     215                        $select_this = 't.*, tt.*, tr.user_id, tr.object_id';
     216
     217                $query = "SELECT $select_this FROM {$this->db->terms} AS t INNER JOIN {$this->db->term_taxonomy} AS tt ON tt.term_id = t.term_id INNER JOIN {$this->db->term_relationships} AS tr ON tr.term_taxonomy_id = tt.term_taxonomy_id WHERE tt.taxonomy IN ($taxonomies) AND tr.object_id IN ($object_ids)";
     218                if ( $user_id )
     219                        $query .= " AND user_id = '$user_id'";
     220                $query .= " $orderby $order";
     221
     222                if ( 'all' == $fields || 'all_with_object_id' == $fields ) {
     223                        $terms = array_merge($terms, $this->db->get_results($query));
     224                        $this->update_term_cache($terms);
     225                } else if ( 'ids' == $fields || 'names' == $fields ) {
     226                        $terms = array_merge($terms, $this->db->get_col($query));
     227                } else if ( 'tt_ids' == $fields ) {
     228                        $query = "SELECT tr.term_taxonomy_id FROM {$this->db->term_relationships} AS tr INNER JOIN {$this->db->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)";
     229                        if ( $user_id )
     230                                $query .= " AND tr.user_id = '$user_id'";
     231                        $query .= " $orderby $order";
     232                        $terms = $this->db->get_col( $query );
     233                }
     234
     235                if ( ! $terms )
     236                        $terms = array();
     237
     238                return apply_filters('wp_get_object_terms', $terms, $object_ids, $taxonomies, $args);
     239        }
     240
     241        /**
     242         * Create Term and Taxonomy Relationships.
     243         *
     244         * Relates an object (post, link etc) to a term and taxonomy type. Creates the
     245         * term and taxonomy relationship if it doesn't already exist. Creates a term if
     246         * it doesn't exist (using the slug).
     247         *
     248         * A relationship means that the term is grouped in or belongs to the taxonomy.
     249         * A term has no meaning until it is given context by defining which taxonomy it
     250         * exists under.
     251         *
     252         * @package bbPress
     253         * @subpackage Taxonomy
     254         * @since 1.0
     255         *
     256         * @param int $object_id The object to relate to.
     257         * @param array|int|string $term The slug or id of the term, will replace all existing
     258         * related terms in this taxonomy.
     259         * @param array|string $taxonomy The context in which to relate the term to the object.
     260         * @param bool $append If false will delete difference of terms.
     261         * @return array|WP_Error Affected Term IDs
     262         */
     263        function set_object_terms($object_id, $terms, $taxonomy, $args = null) {
     264                $object_id = (int) $object_id;
     265
     266                $defaults = array( 'append' => false, 'user_id' => 0 );
     267                if ( is_scalar( $args ) )
     268                        $args = array( 'append' => (bool) $args );
     269                $args = wp_parse_args( $args, $defaults );
     270                extract( $args, EXTR_SKIP );
     271                if ( !$user_id = (int) $user_id )
     272                        return new WP_Error('invalid_user_id', __('Invalid User ID'));
     273
     274                if ( !$this->is_taxonomy($taxonomy) )
     275                        return new WP_Error('invalid_taxonomy', __('Invalid Taxonomy'));
     276
     277                if ( !is_array($terms) )
     278                        $terms = array($terms);
     279
     280                if ( ! $append )
     281                        $old_tt_ids = $this->get_object_terms($object_id, $taxonomy, array('user_id' => $user_id, 'fields' => 'tt_ids', 'orderby' => 'none'));
     282
     283                $tt_ids = array();
     284                $term_ids = array();
     285
     286                foreach ( (array) $terms as $term ) {
     287                        if ( !strlen(trim($term)) )
     288                                continue;
     289
     290                        if ( !$id = $this->is_term($term, $taxonomy) )
     291                                $id = $this->insert_term($term, $taxonomy);
     292                        if ( is_wp_error($id) )
     293                                return $id;
     294                        $term_ids[] = $id['term_id'];
     295                        $id = $id['term_taxonomy_id'];
     296                        $tt_ids[] = $id;
     297
     298                        if ( $this->db->get_var( $this->db->prepare( "SELECT term_taxonomy_id FROM {$this->db->term_relationships} WHERE object_id = %d AND term_taxonomy_id = %d AND user_id = %d", $object_id, $id, $user_id ) ) )
     299                                continue;
     300                        $this->db->insert( $this->db->term_relationships, array( 'object_id' => $object_id, 'term_taxonomy_id' => $id, 'user_id' => $user_id ) );
     301                }
     302
     303                $this->update_term_count($tt_ids, $taxonomy);
     304
     305                if ( ! $append ) {
     306                        $delete_terms = array_diff($old_tt_ids, $tt_ids);
     307                        if ( $delete_terms ) {
     308                                $in_delete_terms = "'" . implode("', '", $delete_terms) . "'";
     309                                $this->db->query( $this->db->prepare("DELETE FROM {$this->db->term_relationships} WHERE object_id = %d AND user_id = %d AND term_taxonomy_id IN ($in_delete_terms)", $object_id, $user_id) );
     310                                $this->update_term_count($delete_terms, $taxonomy);
     311                        }
     312                }
     313
     314                $t = $this->get_taxonomy($taxonomy);
     315                if ( ! $append && isset($t->sort) && $t->sort ) {
     316                        $values = array();
     317                        $term_order = 0;
     318                        $final_tt_ids = $this->get_object_terms($object_id, $taxonomy, array( 'user_id' => $user_id, 'fields' => 'tt_ids' ));
     319                        foreach ( $tt_ids as $tt_id )
     320                                if ( in_array($tt_id, $final_tt_ids) )
     321                                        $values[] = $this->db->prepare( "(%d, %d, %d, %d)", $object_id, $tt_id, $user_id, ++$term_order);
     322                        if ( $values )
     323                                $this->db->query("INSERT INTO {$this->db->term_relationships} (object_id, term_taxonomy_id, user_id, term_order) VALUES " . join(',', $values) . " ON DUPLICATE KEY UPDATE term_order = VALUES(term_order)");
     324                }
     325
     326                do_action('set_object_terms', $object_id, $terms, $tt_ids, $taxonomy, $append);
     327                return $tt_ids;
     328        }
     329} // END class BB_Taxonomy extends WP_Taxonomy