Wordpress 2.3.2-scripts
[autoinstalls/wordpress.git] / wp-includes / taxonomy.php
1 <?php
2
3 //
4 // Taxonomy Registration
5 //
6
7 /**
8  * @global array $wp_taxonomies Fill me out please
9  */
10 $wp_taxonomies = array();
11 $wp_taxonomies['category'] = (object) array('name' => 'category', 'object_type' => 'post', 'hierarchical' => true, 'update_count_callback' => '_update_post_term_count');
12 $wp_taxonomies['post_tag'] = (object) array('name' => 'post_tag', 'object_type' => 'post', 'hierarchical' => false, 'update_count_callback' => '_update_post_term_count');
13 $wp_taxonomies['link_category'] = (object) array('name' => 'link_category', 'object_type' => 'link', 'hierarchical' => false);
14
15 /**
16  * get_object_taxonomies() - Return all of the taxonomy names that are of $object_type
17  *
18  * It appears that this function can be used to find all of the names inside of
19  * $wp_taxonomies global variable.
20  *
21  * @example
22  *      <?php $taxonomies = get_object_taxonomies('post'); ?>
23  *      Should result in <pre>Array(
24  *      'category',
25  *      'post_tag'
26  *      )</pre>
27  *
28  * @package Taxonomy
29  * @global array $wp_taxonomies
30  * @param string $object_type Name of the type of taxonomy object
31  * @return array The names of all within the object_type.
32  *
33  * @internal
34  *      This is all conjecture and might be partially or completely inaccurate.
35  */
36 function get_object_taxonomies($object_type) {
37         global $wp_taxonomies;
38
39         $taxonomies = array();
40         foreach ( $wp_taxonomies as $taxonomy ) {
41                 if ( $object_type == $taxonomy->object_type )
42                         $taxonomies[] = $taxonomy->name;
43         }
44
45         return $taxonomies;
46 }
47
48 /**
49  * get_taxonomy() - Returns the "taxonomy" object of $taxonomy.
50  *
51  * The get_taxonomy function will first check that the parameter string given
52  * is a taxonomy object and if it is, it will return it.
53  *
54  * @package Taxonomy
55  * @global array $wp_taxonomies
56  * @param string $taxonomy Name of taxonomy object to return
57  * @return object|bool The Taxonomy Object or false if taxonomy doesn't exist
58  *
59  * @internal
60  *      This is all conjecture and might be partially or completely inaccurate.
61  */
62 function get_taxonomy( $taxonomy ) {
63         global $wp_taxonomies;
64
65         if ( ! is_taxonomy($taxonomy) )
66                 return false;
67
68         return $wp_taxonomies[$taxonomy];
69 }
70
71 /**
72  * is_taxonomy() - Checks that the taxonomy name exists
73  *
74  * @package Taxonomy
75  * @global array $wp_taxonomies
76  * @param string $taxonomy Name of taxonomy object
77  * @return bool Whether the taxonomy exists or not.
78  *
79  * @internal
80  *      This is all conjecture and might be partially or completely inaccurate.
81  */
82 function is_taxonomy( $taxonomy ) {
83         global $wp_taxonomies;
84
85         return isset($wp_taxonomies[$taxonomy]);
86 }
87
88 /**
89  * is_taxonomy_hierarchical() - Whether the taxonomy object is hierarchical
90  *
91  * Checks to make sure that the taxonomy is an object first. Then Gets the object, and finally
92  * returns the hierarchical value in the object.
93  *
94  * A false return value, might also mean that the taxonomy does not exist.
95  *
96  * @package Taxonomy
97  * @global array $wp_taxonomies
98  * @param string $taxonomy Name of taxonomy object
99  * @return bool Whether the taxonomy is hierarchical
100  *
101  * @internal
102  *      This is all conjecture and might be partially or completely inaccurate.
103  */
104 function is_taxonomy_hierarchical($taxonomy) {
105         if ( ! is_taxonomy($taxonomy) )
106                 return false;
107
108         $taxonomy = get_taxonomy($taxonomy);
109         return $taxonomy->hierarchical;
110 }
111
112 /**
113  * register_taxonomy() - Create or modify a taxonomy object.
114  *
115  * A simple function for creating or modifying a taxonomy object based on the parameters given.
116  * The function will accept an array (third optional parameter), along with strings for the
117  * taxonomy name and another string for the object type.
118  *
119  * The function keeps a default set, allowing for the $args to be optional but allow the other
120  * functions to still work. It is possible to overwrite the default set, which contains two
121  * keys: hierarchical and update_count_callback.
122  *
123  * hierarachical has some defined purpose at other parts of the API and is a boolean value.
124  *
125  * update_count_callback works much like a hook, in that it will be called (or something from
126  *      somewhere).
127  *
128  * @package Taxonomy
129  * @global array $wp_taxonomies
130  * @param string $taxonomy Name of taxonomy object
131  * @param string $object_type Name of the object type for the taxonomy object.
132  * @param array|string $args See above description for the two keys values.
133  * @return null Nothing is returned, so expect error maybe or use is_taxonomy() to check.
134  *
135  * @internal
136  *      This is all conjecture and might be partially or completely inaccurate.
137  */
138 function register_taxonomy( $taxonomy, $object_type, $args = array() ) {
139         global $wp_taxonomies;
140
141         $defaults = array('hierarchical' => false, 'update_count_callback' => '');
142         $args = wp_parse_args($args, $defaults);
143
144         $args['name'] = $taxonomy;
145         $args['object_type'] = $object_type;
146         $wp_taxonomies[$taxonomy] = (object) $args;
147 }
148
149 //
150 // Term API
151 //
152
153 /**
154  * get_objects_in_term() - Return object_ids of valid taxonomy and term
155  *
156  * The strings of $taxonomies must exist before this function will continue. On failure of finding
157  * a valid taxonomy, it will return an WP_Error class, kind of like Exceptions in PHP 5, except you
158  * can't catch them. Even so, you can still test for the WP_Error class and get the error message.
159  *
160  * The $terms aren't checked the same as $taxonomies, but still need to exist for $object_ids to
161  * be returned.
162  *
163  * It is possible to change the order that object_ids is returned by either using PHP sort family
164  * functions or using the database by using $args with either ASC or DESC array. The value should
165  * be in the key named 'order'.
166  *
167  * @package Taxonomy
168  * @subpackage Term
169  * @global object $wpdb Database Query
170  * @param string|array $terms String of term or array of string values of terms that will be used
171  * @param string|array $taxonomies String of taxonomy name or Array of string values of taxonomy names
172  * @param array|string $args Change the order of the object_ids, either ASC or DESC
173  * @return object WP_Error - A PHP 4 compatible Exception class prototype
174  * @return array Empty array if there are no $object_ids
175  * @return array Array of $object_ids
176  *
177  * @internal
178  *      This is all conjecture and might be partially or completely inaccurate.
179  */
180 function get_objects_in_term( $terms, $taxonomies, $args = array() ) {
181         global $wpdb;
182
183         if ( !is_array( $terms) )
184                 $terms = array($terms);
185
186         if ( !is_array($taxonomies) )
187                 $taxonomies = array($taxonomies);
188
189         foreach ( $taxonomies as $taxonomy ) {
190                 if ( ! is_taxonomy($taxonomy) )
191                         return new WP_Error('invalid_taxonomy', __('Invalid Taxonomy'));
192         }
193
194         $defaults = array('order' => 'ASC');
195         $args = wp_parse_args( $args, $defaults );
196         extract($args, EXTR_SKIP);
197
198         $terms = array_map('intval', $terms);
199
200         $taxonomies = "'" . implode("', '", $taxonomies) . "'";
201         $terms = "'" . implode("', '", $terms) . "'";
202
203         $object_ids = $wpdb->get_col("SELECT tr.object_id FROM $wpdb->term_relationships AS tr INNER JOIN $wpdb->term_taxonomy AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id WHERE tt.taxonomy IN ($taxonomies) AND tt.term_id IN ($terms) ORDER BY tr.object_id $order");
204
205         if ( ! $object_ids )
206                 return array();
207
208         return $object_ids;
209 }
210
211 /**
212  * get_term() -
213  *
214  *
215  *
216  * @package Taxonomy
217  * @subpackage Term
218  * @global object $wpdb Database Query
219  * @param int|object $term
220  * @param string $taxonomy
221  * @param string $output Either OBJECT, ARRAY_A, or ARRAY_N
222  * @return mixed Term Row from database
223  *
224  * @internal
225  *      This won't appear but just a note to say that this is all conjecture and parts or whole
226  *      might be inaccurate or wrong.
227  */
228 function &get_term($term, $taxonomy, $output = OBJECT, $filter = 'raw') {
229         global $wpdb;
230
231         if ( empty($term) )
232                 return null;
233
234         if ( ! is_taxonomy($taxonomy) )
235                 return new WP_Error('invalid_taxonomy', __('Invalid Taxonomy'));
236
237         if ( is_object($term) ) {
238                 wp_cache_add($term->term_id, $term, $taxonomy);
239                 $_term = $term;
240         } else {
241                 $term = (int) $term;
242                 if ( ! $_term = wp_cache_get($term, $taxonomy) ) {
243                         $_term = $wpdb->get_row("SELECT t.*, tt.* FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id WHERE tt.taxonomy = '$taxonomy' AND t.term_id = '$term' LIMIT 1");
244                         wp_cache_add($term, $_term, $taxonomy);
245                 }
246         }
247         
248         /**
249          * @internal
250          * Filter tag is basically: filter 'type' 'hook_name' 'description'
251          *
252          * Takes two parameters the term Object and the taxonomy name. Must return term object.
253          * @filter object get_term Used in @see get_term() as a catch-all filter for every $term
254          */
255         $_term = apply_filters('get_term', $_term, $taxonomy);
256         /**
257          * @internal
258          * Filter tag is basically: filter 'type' 'hook_name' 'description'
259          *
260          * Takes two parameters the term Object and the taxonomy name. Must return term object.
261          * $taxonomy will be the taxonomy name, so for example, if 'category', it would be 'get_category'
262          * as the filter name.
263          * Useful for custom taxonomies or plugging into default taxonomies.
264          * @filter object get_$taxonomy Used in @see get_term() as specific filter for each $taxonomy.
265          */
266         $_term = apply_filters("get_$taxonomy", $_term, $taxonomy);
267         $_term = sanitize_term($_term, $taxonomy, $filter);
268
269         if ( $output == OBJECT ) {
270                 return $_term;
271         } elseif ( $output == ARRAY_A ) {
272                 return get_object_vars($_term);
273         } elseif ( $output == ARRAY_N ) {
274                 return array_values(get_object_vars($_term));
275         } else {
276                 return $_term;
277         }
278 }
279
280 /**
281  * get_term_by() -
282  *
283  *
284  *
285  * @package Taxonomy
286  * @subpackage Term
287  * @global object $wpdb Database Query
288  * @param string $field
289  * @param string $value
290  * @param string $taxonomy
291  * @param string $output Either OBJECT, ARRAY_A, or ARRAY_N
292  * @return mixed Term Row from database
293  *
294  * @internal
295  *      This won't appear but just a note to say that this is all conjecture and parts or whole
296  *      might be inaccurate or wrong.
297  */
298 function get_term_by($field, $value, $taxonomy, $output = OBJECT, $filter = 'raw') {
299         global $wpdb;
300
301         if ( ! is_taxonomy($taxonomy) )
302                 return false;
303
304         if ( 'slug' == $field ) {
305                 $field = 't.slug';
306                 $value = sanitize_title($value);
307                 if ( empty($value) )
308                         return false;
309         } else if ( 'name' == $field ) {
310                 // Assume already escaped
311                 $field = 't.name';
312         } else {
313                 $field = 't.term_id';
314                 $value = (int) $value;
315         }
316
317         $term = $wpdb->get_row("SELECT t.*, tt.* FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id WHERE tt.taxonomy = '$taxonomy' AND $field = '$value' LIMIT 1");
318         if ( !$term )
319                 return false;
320
321         wp_cache_add($term->term_id, $term, $taxonomy);
322
323         $term = sanitize_term($term, $taxonomy, $filter);
324
325         if ( $output == OBJECT ) {
326                 return $term;
327         } elseif ( $output == ARRAY_A ) {
328                 return get_object_vars($term);
329         } elseif ( $output == ARRAY_N ) {
330                 return array_values(get_object_vars($term));
331         } else {
332                 return $term;
333         }
334 }
335
336 /**
337  * get_term_children() - Merge all term children into a single array.
338  *
339  * This recursive function will merge all of the children of $term into
340  * the same array.
341  *
342  * Only useful for taxonomies which are hierarchical.
343  * 
344  * @package Taxonomy
345  * @subpackage Term
346  * @global object $wpdb Database Query
347  * @param string $term Name of Term to get children
348  * @param string $taxonomy Taxonomy Name
349  * @return array List of Term Objects
350  *
351  * @internal
352  *      This is all conjecture and might be partially or completely inaccurate.
353  */
354 function get_term_children( $term, $taxonomy ) {
355         if ( ! is_taxonomy($taxonomy) )
356                 return new WP_Error('invalid_taxonomy', __('Invalid Taxonomy'));
357
358         $terms = _get_term_hierarchy($taxonomy);
359
360         if ( ! isset($terms[$term]) )
361                 return array();
362
363         $children = $terms[$term];
364
365         foreach ( $terms[$term] as $child ) {
366                 if ( isset($terms[$child]) )
367                         $children = array_merge($children, get_term_children($child, $taxonomy));
368         }
369
370         return $children;
371 }
372
373 /**
374  * get_term_field() - Get sanitized Term field
375  * 
376  * Does checks for $term, based on the $taxonomy. The function is for
377  * contextual reasons and for simplicity of usage. @see sanitize_term_field() for
378  * more information.
379  *
380  * @package Taxonomy
381  * @subpackage Term
382  * @param string $field Term field to fetch
383  * @param int $term Term ID
384  * @param string $taxonomy Taxonomy Name
385  * @param string $context ??
386  * @return mixed @see sanitize_term_field()
387  *
388  * @internal
389  *      This is all conjecture and might be partially or completely inaccurate.
390  */
391 function get_term_field( $field, $term, $taxonomy, $context = 'display' ) {
392         $term = (int) $term;
393         $term = get_term( $term, $taxonomy );
394         if ( is_wp_error($term) )
395                 return $term;
396
397         if ( !is_object($term) )
398                 return '';
399
400         if ( !isset($term->$field) )
401                 return '';
402
403         return sanitize_term_field($field, $term->$field, $term->term_id, $taxonomy, $context);
404 }
405
406 /**
407  * get_term_to_edit() - Sanitizes Term for editing
408  *
409  * Return value is @see sanitize_term() and usage is for sanitizing the term
410  * for editing. Function is for contextual and simplicity.
411  * 
412  * @package Taxonomy
413  * @subpackage Term
414  * @param int|object $id Term ID or Object
415  * @param string $taxonomy Taxonomy Name
416  * @return mixed @see sanitize_term()
417  *
418  * @internal
419  *      This is all conjecture and might be partially or completely inaccurate.
420  */
421 function get_term_to_edit( $id, $taxonomy ) {
422         $term = get_term( $id, $taxonomy );
423
424         if ( is_wp_error($term) )
425                 return $term;
426
427         if ( !is_object($term) )
428                 return '';
429
430         return sanitize_term($term, $taxonomy, 'edit');
431 }
432
433 /**
434  * get_terms() - 
435  *
436  * 
437  * 
438  * @package Taxonomy
439  * @subpackage Term
440  * @param string|array Taxonomy name or list of Taxonomy names
441  * @param string|array $args ??
442  * @return array List of Term Objects and their children.
443  *
444  * @internal
445  *      This is all conjecture and might be partially or completely inaccurate.
446  */
447 function &get_terms($taxonomies, $args = '') {
448         global $wpdb;
449
450         $single_taxonomy = false;
451         if ( !is_array($taxonomies) ) {
452                 $single_taxonomy = true;
453                 $taxonomies = array($taxonomies);
454         }
455
456         foreach ( $taxonomies as $taxonomy ) {
457                 if ( ! is_taxonomy($taxonomy) )
458                         return new WP_Error('invalid_taxonomy', __('Invalid Taxonomy'));
459         }
460
461         $in_taxonomies = "'" . implode("', '", $taxonomies) . "'";
462
463         $defaults = array('orderby' => 'name', 'order' => 'ASC',
464                 'hide_empty' => true, 'exclude' => '', 'include' => '',
465                 'number' => '', 'fields' => 'all', 'slug' => '', 'parent' => '',
466                 'hierarchical' => true, 'child_of' => 0, 'get' => '', 'name__like' => '',
467                 'pad_counts' => false);
468         $args = wp_parse_args( $args, $defaults );
469         $args['number'] = (int) $args['number'];
470         if ( !$single_taxonomy || !is_taxonomy_hierarchical($taxonomies[0]) ||
471                 '' != $args['parent'] ) {
472                 $args['child_of'] = 0;
473                 $args['hierarchical'] = false;
474                 $args['pad_counts'] = false;
475         }
476
477         if ( 'all' == $args['get'] ) {
478                 $args['child_of'] = 0;
479                 $args['hide_empty'] = 0;
480                 $args['hierarchical'] = false;
481                 $args['pad_counts'] = false;
482         }
483         extract($args, EXTR_SKIP);
484
485         if ( $child_of ) {
486                 $hierarchy = _get_term_hierarchy($taxonomies[0]);
487                 if ( !isset($hierarchy[$child_of]) )
488                         return array();
489         }
490
491         if ( $parent ) {
492                 $hierarchy = _get_term_hierarchy($taxonomies[0]);
493                 if ( !isset($hierarchy[$parent]) )
494                         return array();
495         }
496
497         $key = md5( serialize( $args ) . serialize( $taxonomies ) );
498         if ( $cache = wp_cache_get( 'get_terms', 'terms' ) ) {
499                 if ( isset( $cache[ $key ] ) )
500                         return apply_filters('get_terms', $cache[$key], $taxonomies, $args);
501         }
502
503         if ( 'count' == $orderby )
504                 $orderby = 'tt.count';
505         else if ( 'name' == $orderby )
506                 $orderby = 't.name';
507         else
508                 $orderby = 't.term_id';
509
510         $where = '';
511         $inclusions = '';
512         if ( !empty($include) ) {
513                 $exclude = '';
514                 $interms = preg_split('/[\s,]+/',$include);
515                 if ( count($interms) ) {
516                         foreach ( $interms as $interm ) {
517                                 if (empty($inclusions))
518                                         $inclusions = ' AND ( t.term_id = ' . intval($interm) . ' ';
519                                 else
520                                         $inclusions .= ' OR t.term_id = ' . intval($interm) . ' ';
521                         }
522                 }
523         }
524
525         if ( !empty($inclusions) )
526                 $inclusions .= ')';
527         $where .= $inclusions;
528
529         $exclusions = '';
530         if ( !empty($exclude) ) {
531                 $exterms = preg_split('/[\s,]+/',$exclude);
532                 if ( count($exterms) ) {
533                         foreach ( $exterms as $exterm ) {
534                                 if (empty($exclusions))
535                                         $exclusions = ' AND ( t.term_id <> ' . intval($exterm) . ' ';
536                                 else
537                                         $exclusions .= ' AND t.term_id <> ' . intval($exterm) . ' ';
538                         }
539                 }
540         }
541
542         if ( !empty($exclusions) )
543                 $exclusions .= ')';
544         $exclusions = apply_filters('list_terms_exclusions', $exclusions, $args );
545         $where .= $exclusions;
546
547         if ( !empty($slug) ) {
548                 $slug = sanitize_title($slug);
549                 $where .= " AND t.slug = '$slug'";
550         }
551
552         if ( !empty($name__like) )
553                 $where .= " AND t.name LIKE '{$name__like}%'";
554
555         if ( '' != $parent ) {
556                 $parent = (int) $parent;
557                 $where .= " AND tt.parent = '$parent'";
558         }
559
560         if ( $hide_empty && !$hierarchical )
561                 $where .= ' AND tt.count > 0';
562
563         if ( !empty($number) )
564                 $number = 'LIMIT ' . $number;
565         else
566                 $number = '';
567
568         if ( 'all' == $fields )
569                 $select_this = 't.*, tt.*';
570         else if ( 'ids' == $fields )
571                 $select_this = 't.term_id';
572         else if ( 'names' == $fields )
573                 $select_this == 't.name';
574
575         $query = "SELECT $select_this FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id WHERE tt.taxonomy IN ($in_taxonomies) $where ORDER BY $orderby $order $number";
576
577         if ( 'all' == $fields ) {
578                 $terms = $wpdb->get_results($query);
579                 update_term_cache($terms);
580         } else if ( 'ids' == $fields ) {
581                 $terms = $wpdb->get_col($query);
582         }
583
584         if ( empty($terms) )
585                 return array();
586
587         if ( $child_of || $hierarchical ) {
588                 $children = _get_term_hierarchy($taxonomies[0]);
589                 if ( ! empty($children) )
590                         $terms = & _get_term_children($child_of, $terms, $taxonomies[0]);
591         }
592
593         // Update term counts to include children.
594         if ( $pad_counts )
595                 _pad_term_counts($terms, $taxonomies[0]);
596
597         // Make sure we show empty categories that have children.
598         if ( $hierarchical && $hide_empty ) {
599                 foreach ( $terms as $k => $term ) {
600                         if ( ! $term->count ) {
601                                 $children = _get_term_children($term->term_id, $terms, $taxonomies[0]);
602                                 foreach ( $children as $child )
603                                         if ( $child->count )
604                                                 continue 2;
605
606                                 // It really is empty
607                                 unset($terms[$k]);
608                         }
609                 }
610         }
611         reset ( $terms );
612
613         $cache[ $key ] = $terms;
614         wp_cache_set( 'get_terms', $cache, 'terms' );
615
616         $terms = apply_filters('get_terms', $terms, $taxonomies, $args);
617         return $terms;
618 }
619
620 /**
621  * is_term() - Check if Term exists
622  *
623  * Returns the index of a defined term, or 0 (false) if the term doesn't exist.
624  *
625  * @global $wpdb Database Object
626  * @param int|string $term The term to check
627  * @param string $taxonomy The taxonomy name to use
628  * @return mixed Get the term id or Term Object, if exists.
629  */
630 function is_term($term, $taxonomy = '') {
631         global $wpdb;
632
633         if ( is_int($term) ) {
634                 if ( 0 == $term )
635                         return 0;
636                 $where = "t.term_id = '$term'";
637         } else {
638                 if ( ! $term = sanitize_title($term) )
639                         return 0;
640                 $where = "t.slug = '$term'";
641         }
642
643         $term_id = $wpdb->get_var("SELECT term_id FROM $wpdb->terms as t WHERE $where");
644
645         if ( empty($taxonomy) || empty($term_id) )
646                 return $term_id;
647
648         return $wpdb->get_row("SELECT tt.term_id, tt.term_taxonomy_id FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy as tt ON tt.term_id = t.term_id WHERE $where AND tt.taxonomy = '$taxonomy'", ARRAY_A);
649 }
650
651 /**
652  * sanitize_term() - Sanitize Term all fields
653  *
654  * Relys on @see sanitize_term_field() to sanitize the term. The difference
655  * is that this function will sanitize <strong>all</strong> fields. The context
656  * is based on @see sanitize_term_field().
657  *
658  * The $term is expected to be either an array or an object.
659  *
660  * @param array|object $term The term to check
661  * @param string $taxonomy The taxonomy name to use
662  * @param string $context Default is display
663  * @return array|object Term with all fields sanitized
664  */
665 function sanitize_term($term, $taxonomy, $context = 'display') {
666
667         if ( 'raw' == $context )
668                 return $term;
669
670         $fields = array('term_id', 'name', 'description', 'slug', 'count', 'parent', 'term_group');
671
672         $do_object = false;
673         if ( is_object($term) )
674                 $do_object = true;
675
676         foreach ( $fields as $field ) {
677                 if ( $do_object )
678                         $term->$field = sanitize_term_field($field, $term->$field, $term->term_id, $taxonomy, $context);
679                 else
680                         $term[$field] = sanitize_term_field($field, $term[$field], $term['term_id'], $taxonomy, $context);
681         }
682
683         return $term;
684 }
685
686 /**
687  * sanitize_term_field() - 
688  *
689  *
690  *
691  * @global object $wpdb Database Object
692  * @param string $field Term field to sanitize
693  * @param string $value Search for this term value
694  * @param int $term_id Term ID
695  * @param string $taxonomy Taxonomy Name
696  * @param string $context Either edit, db, display, attribute, or js.
697  * @return mixed sanitized field
698  */
699 function sanitize_term_field($field, $value, $term_id, $taxonomy, $context) {
700         if ( 'parent' == $field  || 'term_id' == $field || 'count' == $field
701                 || 'term_group' == $field )
702                 $value = (int) $value;
703
704         if ( 'edit' == $context ) {
705                 $value = apply_filters("edit_term_$field", $value, $term_id, $taxonomy);
706                 $value = apply_filters("edit_${taxonomy}_$field", $value, $term_id);
707                 if ( 'description' == $field )
708                         $value = format_to_edit($value);
709                 else
710                         $value = attribute_escape($value);
711         } else if ( 'db' == $context ) {
712                 $value = apply_filters("pre_term_$field", $value, $taxonomy);
713                 $value = apply_filters("pre_${taxonomy}_$field", $value);
714                 // Back compat filters
715                 if ( 'slug' == $field )
716                         $value = apply_filters('pre_category_nicename', $value);
717                         
718         } else if ( 'rss' == $context ) {
719                 $value = apply_filters("term_${field}_rss", $value, $taxonomy);
720                 $value = apply_filters("${taxonomy}_$field_rss", $value);
721         } else {
722                 // Use display filters by default.
723                 $value = apply_filters("term_$field", $value, $term_id, $taxonomy, $context);
724                 $value = apply_filters("${taxonomy}_$field", $value, $term_id, $context);
725         }
726
727         if ( 'attribute' == $context )
728                 $value = attribute_escape($value);
729         else if ( 'js' == $context )
730                 $value = js_escape($value);
731
732         return $value;
733 }
734
735 /**
736  * wp_count_terms() - Count how many terms are in Taxonomy
737  *
738  * Default $args is 'ignore_empty' which can be @example 'ignore_empty=true' or
739  * @example array('ignore_empty' => true); See @see wp_parse_args() for more
740  * information on parsing $args.
741  *
742  * @global object $wpdb Database Object
743  * @param string $taxonomy Taxonomy name
744  * @param array|string $args Overwrite defaults
745  * @return int How many terms are in $taxonomy
746  */
747 function wp_count_terms( $taxonomy, $args = array() ) {
748         global $wpdb;
749
750         $defaults = array('ignore_empty' => false);
751         $args = wp_parse_args($args, $defaults);
752         extract($args, EXTR_SKIP);
753
754         $where = '';
755         if ( $ignore_empty )
756                 $where = 'AND count > 0';
757
758         return $wpdb->get_var("SELECT COUNT(*) FROM $wpdb->term_taxonomy WHERE taxonomy = '$taxonomy' $where");
759 }
760
761 /**
762  * wp_delete_object_term_relationships() - 
763  *
764  *
765  *
766  * @global object $wpdb Database Object
767  * @param int $object_id ??
768  * @param string|array $taxonomy List of Taxonomy Names or single Taxonomy name.
769  */
770 function wp_delete_object_term_relationships( $object_id, $taxonomies ) {
771         global $wpdb;
772
773         $object_id = (int) $object_id;
774
775         if ( !is_array($taxonomies) )
776                 $taxonomies = array($taxonomies);
777
778         foreach ( $taxonomies as $taxonomy ) {
779                 $terms = wp_get_object_terms($object_id, $taxonomy, 'fields=tt_ids');
780                 $in_terms = "'" . implode("', '", $terms) . "'";
781                 $wpdb->query("DELETE FROM $wpdb->term_relationships WHERE object_id = '$object_id' AND term_taxonomy_id IN ($in_terms)");
782                 wp_update_term_count($terms, $taxonomy);
783         }
784 }
785
786 /**
787  * Removes a term from the database.
788  */
789 function wp_delete_term( $term, $taxonomy, $args = array() ) {
790         global $wpdb;
791
792         $term = (int) $term;
793
794         if ( ! $ids = is_term($term, $taxonomy) )
795                 return false;
796         $tt_id = $ids['term_taxonomy_id'];
797
798         $defaults = array();
799         $args = wp_parse_args($args, $defaults);
800         extract($args, EXTR_SKIP);
801
802         if ( isset($default) ) {
803                 $default = (int) $default;
804                 if ( ! is_term($default, $taxonomy) )
805                         unset($default);
806         }
807
808         // Update children to point to new parent
809         if ( is_taxonomy_hierarchical($taxonomy) ) {
810                 $term_obj = get_term($term, $taxonomy);
811                 if ( is_wp_error( $term_obj ) )
812                         return $term_obj;
813                 $parent = $term_obj->parent;
814
815                 $wpdb->query("UPDATE $wpdb->term_taxonomy SET parent = '$parent' WHERE parent = '$term_obj->term_id' AND taxonomy = '$taxonomy'");
816         }
817
818         $objects = $wpdb->get_col("SELECT object_id FROM $wpdb->term_relationships WHERE term_taxonomy_id = '$tt_id'");
819
820         foreach ( (array) $objects as $object ) {
821                 $terms = wp_get_object_terms($object, $taxonomy, 'fields=ids');
822                 if ( 1 == count($terms) && isset($default) )
823                         $terms = array($default);
824                 else
825                         $terms = array_diff($terms, array($term));
826                 $terms = array_map('intval', $terms);
827                 wp_set_object_terms($object, $terms, $taxonomy);
828         }
829
830         $wpdb->query("DELETE FROM $wpdb->term_taxonomy WHERE term_taxonomy_id = '$tt_id'");
831
832         // Delete the term if no taxonomies use it.
833         if ( !$wpdb->get_var("SELECT COUNT(*) FROM $wpdb->term_taxonomy WHERE term_id = '$term'") )
834                 $wpdb->query("DELETE FROM $wpdb->terms WHERE term_id = '$term'");
835
836         clean_term_cache($term, $taxonomy);
837
838         do_action("delete_$taxonomy", $term, $tt_id);
839
840         return true;
841 }
842
843 /**
844  * Returns the terms associated with the given object(s), in the supplied taxonomies.
845  * @param int|array $object_id The id of the object(s)) to retrieve for.
846  * @param string|array $taxonomies The taxonomies to retrieve terms from.
847  * @return array The requested term data.
848  */
849 function wp_get_object_terms($object_ids, $taxonomies, $args = array()) {
850         global $wpdb;
851
852         if ( !is_array($taxonomies) )
853                 $taxonomies = array($taxonomies);
854
855         foreach ( $taxonomies as $taxonomy ) {
856                 if ( ! is_taxonomy($taxonomy) )
857                         return new WP_Error('invalid_taxonomy', __('Invalid Taxonomy'));
858         }
859
860         if ( !is_array($object_ids) )
861                 $object_ids = array($object_ids);
862         $object_ids = array_map('intval', $object_ids);
863
864         $defaults = array('orderby' => 'name', 'order' => 'ASC', 'fields' => 'all');
865         $args = wp_parse_args( $args, $defaults );
866         extract($args, EXTR_SKIP);
867
868         if ( 'count' == $orderby )
869                 $orderby = 'tt.count';
870         else if ( 'name' == $orderby )
871                 $orderby = 't.name';
872
873         $taxonomies = "'" . implode("', '", $taxonomies) . "'";
874         $object_ids = implode(', ', $object_ids);
875
876         if ( 'all' == $fields )
877                 $select_this = 't.*, tt.*';
878         else if ( 'ids' == $fields )
879                 $select_this = 't.term_id';
880         else if ( 'names' == $fields ) 
881                 $select_this = 't.name';
882         else if ( 'all_with_object_id' == $fields )
883                 $select_this = 't.*, tt.*, tr.object_id';
884
885         $query = "SELECT $select_this FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS tt ON tt.term_id = t.term_id INNER JOIN $wpdb->term_relationships AS tr ON tr.term_taxonomy_id = tt.term_taxonomy_id WHERE tt.taxonomy IN ($taxonomies) AND tr.object_id IN ($object_ids) ORDER BY $orderby $order";
886
887         if ( 'all' == $fields || 'all_with_object_id' == $fields ) {
888                 $terms = $wpdb->get_results($query);
889                 update_term_cache($terms);
890         } else if ( 'ids' == $fields || 'names' == $fields ) {
891                 $terms = $wpdb->get_col($query);
892         } else if ( 'tt_ids' == $fields ) {
893                 $terms = $wpdb->get_col("SELECT tr.term_taxonomy_id FROM $wpdb->term_relationships AS tr INNER JOIN $wpdb->term_taxonomy AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id WHERE tr.object_id IN ($object_ids) AND tt.taxonomy IN ($taxonomies) ORDER BY tr.term_taxonomy_id $order");
894         }
895
896         if ( ! $terms )
897                 return array();
898
899         return $terms;
900 }
901
902 /**
903  * wp_insert_term() - Adds a new term to the database. Optionally marks it as an alias of an existing term.
904  *
905  * 
906  *
907  * @global $wpdb Database Object
908  * @param int|string $term The term to add or update.
909  * @param string $taxonomy The taxonomy to which to add the term
910  * @param array|string $args Change the values of the inserted term
911  * @return array The Term ID and Term Taxonomy ID
912  */
913 function wp_insert_term( $term, $taxonomy, $args = array() ) {
914         global $wpdb;
915
916         if ( ! is_taxonomy($taxonomy) )
917                 return new WP_Error('invalid_taxonomy', __('Invalid taxonomy'));
918
919         if ( is_int($term) && 0 == $term )
920                 return new WP_Error('invalid_term_id', __('Invalid term ID'));
921
922         $defaults = array( 'alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => '');
923         $args = wp_parse_args($args, $defaults);
924         $args['name'] = $term;
925         $args['taxonomy'] = $taxonomy;
926         $args = sanitize_term($args, $taxonomy, 'db');
927         extract($args, EXTR_SKIP);
928
929         if ( empty($slug) )
930                 $slug = sanitize_title($name);
931
932         $term_group = 0;
933         if ( $alias_of ) {
934                 $alias = $wpdb->fetch_row("SELECT term_id, term_group FROM $wpdb->terms WHERE slug = '$alias_of'");
935                 if ( $alias->term_group ) {
936                         // The alias we want is already in a group, so let's use that one.
937                         $term_group = $alias->term_group;
938                 } else {
939                         // The alias isn't in a group, so let's create a new one and firstly add the alias term to it.
940                         $term_group = $wpdb->get_var("SELECT MAX(term_group) FROM $wpdb->terms GROUP BY term_group") + 1;
941                         $wpdb->query("UPDATE $wpdb->terms SET term_group = $term_group WHERE term_id = $alias->term_id");
942                 }
943         }
944
945         if ( ! $term_id = is_term($slug) ) {
946                 $wpdb->query("INSERT INTO $wpdb->terms (name, slug, term_group) VALUES ('$name', '$slug', '$term_group')");
947                 $term_id = (int) $wpdb->insert_id;
948         } else if ( is_taxonomy_hierarchical($taxonomy) && !empty($parent) ) {
949                 // If the taxonomy supports hierarchy and the term has a parent, make the slug unique
950                 // by incorporating parent slugs.
951                 $slug = wp_unique_term_slug($slug, (object) $args);
952                 $wpdb->query("INSERT INTO $wpdb->terms (name, slug, term_group) VALUES ('$name', '$slug', '$term_group')");
953                 $term_id = (int) $wpdb->insert_id;
954         }
955
956         if ( empty($slug) ) {
957                 $slug = sanitize_title($slug, $term_id);
958                 $wpdb->query("UPDATE $wpdb->terms SET slug = '$slug' WHERE term_id = '$term_id'");
959         }
960
961         $tt_id = $wpdb->get_var("SELECT tt.term_taxonomy_id FROM $wpdb->term_taxonomy AS tt INNER JOIN $wpdb->terms AS t ON tt.term_id = t.term_id WHERE tt.taxonomy = '$taxonomy' AND t.term_id = $term_id");
962
963         if ( !empty($tt_id) )
964                 return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
965
966         $wpdb->query("INSERT INTO $wpdb->term_taxonomy (term_id, taxonomy, description, parent, count) VALUES ('$term_id', '$taxonomy', '$description', '$parent', '0')");
967         $tt_id = (int) $wpdb->insert_id;
968
969         do_action("create_term", $term_id, $tt_id);
970         do_action("create_$taxonomy", $term_id, $tt_id);
971
972         $term_id = apply_filters('term_id_filter', $term_id, $tt_id);
973
974         clean_term_cache($term_id, $taxonomy);
975
976         do_action("created_term", $term_id, $tt_id);
977         do_action("created_$taxonomy", $term_id, $tt_id);
978
979         return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
980 }
981
982 /**
983  * wp_set_object_terms() - 
984  * 
985  * Relates an object (post, link etc) to a term and taxonomy type.  Creates the term and taxonomy
986  * relationship if it doesn't already exist.  Creates a term if it doesn't exist (using the slug).
987  *
988  * @global $wpdb Database Object
989  * @param int $object_id The object to relate to.
990  * @param array|int|string $term The slug or id of the term.
991  * @param array|string $taxonomy The context in which to relate the term to the object.
992  * @param bool $append If false will delete difference of terms.
993  */
994 function wp_set_object_terms($object_id, $terms, $taxonomy, $append = false) {
995         global $wpdb;
996
997         $object_id = (int) $object_id;
998
999         if ( ! is_taxonomy($taxonomy) )
1000                 return new WP_Error('invalid_taxonomy', __('Invalid Taxonomy'));
1001
1002         if ( !is_array($terms) )
1003                 $terms = array($terms);
1004
1005         if ( ! $append )
1006                 $old_terms =  wp_get_object_terms($object_id, $taxonomy, 'fields=tt_ids');
1007
1008         $tt_ids = array();
1009         $term_ids = array();
1010
1011         foreach ($terms as $term) {
1012                 if ( !$id = is_term($term, $taxonomy) )
1013                         $id = wp_insert_term($term, $taxonomy);
1014                 $term_ids[] = $id['term_id'];
1015                 $id = $id['term_taxonomy_id'];
1016                 $tt_ids[] = $id;
1017
1018                 if ( $wpdb->get_var("SELECT term_taxonomy_id FROM $wpdb->term_relationships WHERE object_id = '$object_id' AND term_taxonomy_id = '$id'") )
1019                         continue;
1020                 $wpdb->query("INSERT INTO $wpdb->term_relationships (object_id, term_taxonomy_id) VALUES ('$object_id', '$id')");
1021         }
1022
1023         wp_update_term_count($tt_ids, $taxonomy);
1024
1025         if ( ! $append ) {
1026                 $delete_terms = array_diff($old_terms, $tt_ids);
1027                 if ( $delete_terms ) {
1028                         $in_delete_terms = "'" . implode("', '", $delete_terms) . "'";
1029                         $wpdb->query("DELETE FROM $wpdb->term_relationships WHERE object_id = '$object_id' AND term_taxonomy_id IN ($in_delete_terms)");
1030                         wp_update_term_count($delete_terms, $taxonomy);
1031                 }
1032         }
1033
1034         return $tt_ids;
1035 }
1036
1037 function wp_unique_term_slug($slug, $term) {
1038         global $wpdb;
1039
1040         // If the taxonomy supports hierarchy and the term has a parent, make the slug unique
1041         // by incorporating parent slugs.
1042         if ( is_taxonomy_hierarchical($term->taxonomy) && !empty($term->parent) ) {
1043                 $the_parent = $term->parent;
1044                 while ( ! empty($the_parent) ) {
1045                         $parent_term = get_term($the_parent, $term->taxonomy);
1046                         if ( is_wp_error($parent_term) || empty($parent_term) )
1047                                 break;
1048                                 $slug .= '-' . $parent_term->slug;
1049                         if ( empty($parent_term->parent) )
1050                                 break;
1051                         $the_parent = $parent_term->parent;
1052                 }
1053         }
1054
1055         // If we didn't get a unique slug, try appending a number to make it unique.
1056         if ( $wpdb->get_var("SELECT slug FROM $wpdb->terms WHERE slug = '$slug'") ) {
1057                 $num = 2;
1058                 do {
1059                         $alt_slug = $slug . "-$num";
1060                         $num++;
1061                         $slug_check = $wpdb->get_var("SELECT slug FROM $wpdb->terms WHERE slug = '$alt_slug'");
1062                 } while ( $slug_check );
1063                 $slug = $alt_slug;
1064         }
1065
1066         return $slug;
1067 }
1068
1069 function wp_update_term( $term, $taxonomy, $args = array() ) {
1070         global $wpdb;
1071
1072         if ( ! is_taxonomy($taxonomy) )
1073                 return new WP_Error('invalid_taxonomy', __('Invalid taxonomy'));
1074
1075         $term_id = (int) $term;
1076
1077         // First, get all of the original args
1078         $term = get_term ($term_id, $taxonomy, ARRAY_A);
1079
1080         // Escape data pulled from DB.
1081         $term = add_magic_quotes($term);
1082
1083         // Merge old and new args with new args overwriting old ones.
1084         $args = array_merge($term, $args);
1085
1086         $defaults = array( 'alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => '');
1087         $args = wp_parse_args($args, $defaults);
1088         $args = sanitize_term($args, $taxonomy, 'db');
1089         extract($args, EXTR_SKIP);
1090
1091         $empty_slug = false;
1092         if ( empty($slug) ) {
1093                 $empty_slug = true;
1094                 $slug = sanitize_title($name);
1095         }
1096
1097         if ( $alias_of ) {
1098                 $alias = $wpdb->fetch_row("SELECT term_id, term_group FROM $wpdb->terms WHERE slug = '$alias_of'");
1099                 if ( $alias->term_group ) {
1100                         // The alias we want is already in a group, so let's use that one.
1101                         $term_group = $alias->term_group;
1102                 } else {
1103                         // The alias isn't in a group, so let's create a new one and firstly add the alias term to it.
1104                         $term_group = $wpdb->get_var("SELECT MAX(term_group) FROM $wpdb->terms GROUP BY term_group") + 1;
1105                         $wpdb->query("UPDATE $wpdb->terms SET term_group = $term_group WHERE term_id = $alias->term_id");
1106                 }
1107         }
1108
1109         // Check for duplicate slug
1110         $id = $wpdb->get_var("SELECT term_id FROM $wpdb->terms WHERE slug = '$slug'");
1111         if ( $id && ($id != $term_id) ) {
1112                 // If an empty slug was passed, reset the slug to something unique.
1113                 // Otherwise, bail.
1114                 if ( $empty_slug )
1115                         $slug = wp_unique_term_slug($slug, (object) $args);
1116                 else
1117                         return new WP_Error('duplicate_term_slug', sprintf(__('The slug "%s" is already in use by another term'), $slug));
1118         }
1119
1120         $wpdb->query("UPDATE $wpdb->terms SET name = '$name', slug = '$slug', term_group = '$term_group' WHERE term_id = '$term_id'");
1121
1122         if ( empty($slug) ) {
1123                 $slug = sanitize_title($name, $term_id);
1124                 $wpdb->query("UPDATE $wpdb->terms SET slug = '$slug' WHERE term_id = '$term_id'");
1125         }
1126
1127         $tt_id = $wpdb->get_var("SELECT tt.term_taxonomy_id FROM $wpdb->term_taxonomy AS tt INNER JOIN $wpdb->terms AS t ON tt.term_id = t.term_id WHERE tt.taxonomy = '$taxonomy' AND t.term_id = $term_id");
1128
1129         $wpdb->query("UPDATE $wpdb->term_taxonomy SET term_id = '$term_id', taxonomy = '$taxonomy', description = '$description', parent = '$parent' WHERE term_taxonomy_id = '$tt_id'");
1130
1131         do_action("edit_term", $term_id, $tt_id);
1132         do_action("edit_$taxonomy", $term_id, $tt_id);
1133
1134         $term_id = apply_filters('term_id_filter', $term_id, $tt_id);
1135
1136         clean_term_cache($term_id, $taxonomy);
1137
1138         do_action("edited_term", $term_id, $tt_id);
1139         do_action("edited_$taxonomy", $term_id, $tt_id);
1140
1141         return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
1142 }
1143
1144 function wp_update_term_count( $terms, $taxonomy ) {
1145         global $wpdb;
1146
1147         if ( empty($terms) )
1148                 return false;
1149
1150         if ( !is_array($terms) )
1151                 $terms = array($terms);
1152
1153         $terms = array_map('intval', $terms);
1154
1155         $taxonomy = get_taxonomy($taxonomy);
1156         if ( !empty($taxonomy->update_count_callback) ) {
1157                 call_user_func($taxonomy->update_count_callback, $terms);
1158         } else {
1159                 // Default count updater
1160                 foreach ($terms as $term) {
1161                         $count = $wpdb->get_var("SELECT COUNT(*) FROM $wpdb->term_relationships WHERE term_taxonomy_id = '$term'");
1162                         $wpdb->query("UPDATE $wpdb->term_taxonomy SET count = '$count' WHERE term_taxonomy_id = '$term'");
1163                 }
1164
1165         }
1166
1167         clean_term_cache($terms);
1168
1169         return true;
1170 }
1171
1172 //
1173 // Cache
1174 //
1175
1176 function clean_object_term_cache($object_ids, $object_type) {
1177         global $object_term_cache, $blog_id;
1178
1179         if ( !is_array($object_ids) )
1180                 $object_ids = array($object_ids);
1181
1182         $taxonomies = get_object_taxonomies($object_type);
1183
1184         foreach ( $object_ids as $id ) {
1185                 foreach ( $taxonomies as $taxonomy ) {
1186                         if ( isset($object_term_cache[$blog_id][$id][$taxonomy]) )
1187                                 unset($object_term_cache[$blog_id][$id][$taxonomy]);
1188                 }
1189         }
1190 }
1191
1192 function clean_term_cache($ids, $taxonomy = '') {
1193         global $wpdb;
1194
1195         if ( !is_array($ids) )
1196                 $ids = array($ids);
1197
1198         $taxonomies = array();
1199         // If no taxonomy, assume tt_ids.
1200         if ( empty($taxonomy) ) {
1201                 $tt_ids = implode(', ', $ids);
1202                 $terms = $wpdb->get_results("SELECT term_id, taxonomy FROM $wpdb->term_taxonomy WHERE term_taxonomy_id IN ($tt_ids)");
1203                 foreach ( (array) $terms as $term ) {
1204                         $taxonomies[] = $term->taxonomy;
1205                         wp_cache_delete($term->term_id, $term->taxonomy);
1206                 }
1207                 $taxonomies = array_unique($taxonomies);
1208         } else {
1209                 foreach ( $ids as $id ) {
1210                         wp_cache_delete($id, $taxonomy);
1211                 }
1212                 $taxonomies = array($taxonomy);
1213         }
1214
1215         foreach ( $taxonomies as $taxonomy ) {
1216                 wp_cache_delete('all_ids', $taxonomy);
1217                 wp_cache_delete('get', $taxonomy);
1218                 delete_option("{$taxonomy}_children");
1219         }
1220
1221         wp_cache_delete('get_terms', 'terms');
1222 }
1223
1224 function &get_object_term_cache($id, $taxonomy) {
1225         global $object_term_cache, $blog_id;
1226
1227         if ( isset($object_term_cache[$blog_id][$id][$taxonomy]) )
1228                 return $object_term_cache[$blog_id][$id][$taxonomy];
1229
1230         if ( isset($object_term_cache[$blog_id][$id]) )
1231                 return array();
1232
1233         return false;
1234 }
1235
1236 function update_object_term_cache($object_ids, $object_type) {
1237         global $wpdb, $object_term_cache, $blog_id;
1238
1239         if ( empty($object_ids) )
1240                 return;
1241
1242         if ( !is_array($object_ids) )
1243                 $object_ids = explode(',', $object_ids);
1244
1245         $count = count( $object_ids);
1246         for ( $i = 0; $i < $count; $i++ ) {
1247                 $object_id = (int) $object_ids[ $i ];
1248                 if ( isset( $object_term_cache[$blog_id][$object_id] ) ) {
1249                         unset( $object_ids[ $i ] );
1250                         continue;
1251                 }
1252         }
1253
1254         if ( count( $object_ids ) == 0 )
1255                 return;
1256
1257         $terms = wp_get_object_terms($object_ids, get_object_taxonomies($object_type), 'fields=all_with_object_id');
1258
1259         if ( empty($terms) )
1260                 return;
1261
1262         foreach ( $terms as $term )
1263                 $object_term_cache[$blog_id][$term->object_id][$term->taxonomy][$term->term_id] = $term;
1264
1265         foreach ( $object_ids as $id ) {
1266                 if ( ! isset($object_term_cache[$blog_id][$id]) )
1267                                 $object_term_cache[$blog_id][$id] = array();
1268         }
1269 }
1270
1271 function update_term_cache($terms, $taxonomy = '') {
1272         foreach ( $terms as $term ) {
1273                 $term_taxonomy = $taxonomy;
1274                 if ( empty($term_taxonomy) )
1275                         $term_taxonomy = $term->taxonomy;
1276
1277                 wp_cache_add($term->term_id, $term, $term_taxonomy);
1278         }
1279 }
1280
1281 //
1282 // Private
1283 //
1284
1285 function _get_term_hierarchy($taxonomy) {
1286         if ( !is_taxonomy_hierarchical($taxonomy) )
1287                 return array();
1288         $children = get_option("{$taxonomy}_children");
1289         if ( is_array($children) )
1290                 return $children;
1291
1292         $children = array();
1293         $terms = get_terms($taxonomy, 'get=all');
1294         foreach ( $terms as $term ) {
1295                 if ( $term->parent > 0 )
1296                         $children[$term->parent][] = $term->term_id;
1297         }
1298         update_option("{$taxonomy}_children", $children);
1299
1300         return $children;
1301 }
1302
1303 function &_get_term_children($term_id, $terms, $taxonomy) {
1304         if ( empty($terms) )
1305                 return array();
1306
1307         $term_list = array();
1308         $has_children = _get_term_hierarchy($taxonomy);
1309
1310         if  ( ( 0 != $term_id ) && ! isset($has_children[$term_id]) )
1311                 return array();
1312
1313         foreach ( $terms as $term ) {
1314                 $use_id = false;
1315                 if ( !is_object($term) ) {
1316                         $term = get_term($term, $taxonomy);
1317                         if ( is_wp_error( $term ) )
1318                                 return $term;
1319                         $use_id = true;
1320                 }
1321
1322                 if ( $term->term_id == $term_id )
1323                         continue;
1324
1325                 if ( $term->parent == $term_id ) {
1326                         if ( $use_id )
1327                                 $term_list[] = $term->term_id;
1328                         else
1329                                 $term_list[] = $term;
1330
1331                         if ( !isset($has_children[$term->term_id]) )
1332                                 continue;
1333
1334                         if ( $children = _get_term_children($term->term_id, $terms, $taxonomy) )
1335                                 $term_list = array_merge($term_list, $children);
1336                 }
1337         }
1338
1339         return $term_list;
1340 }
1341
1342 // Recalculates term counts by including items from child terms
1343 // Assumes all relevant children are already in the $terms argument
1344 function _pad_term_counts(&$terms, $taxonomy) {
1345         global $wpdb;
1346
1347         // This function only works for post categories.
1348         if ( 'category' != $taxonomy )
1349                 return;
1350
1351         $term_hier = _get_term_hierarchy($taxonomy);
1352
1353         if ( empty($term_hier) )
1354                 return;
1355
1356         $term_items = array();
1357
1358         foreach ( $terms as $key => $term ) {
1359                 $terms_by_id[$term->term_id] = & $terms[$key];
1360                 $term_ids[$term->term_taxonomy_id] = $term->term_id;
1361         }
1362
1363         // Get the object and term ids and stick them in a lookup table
1364         $results = $wpdb->get_results("SELECT object_id, term_taxonomy_id FROM $wpdb->term_relationships INNER JOIN $wpdb->posts ON object_id = ID WHERE term_taxonomy_id IN (".join(',', array_keys($term_ids)).") AND post_type = 'post' AND post_status = 'publish'");
1365         foreach ( $results as $row ) {
1366                 $id = $term_ids[$row->term_taxonomy_id];
1367                 ++$term_items[$id][$row->object_id];
1368         }
1369
1370         // Touch every ancestor's lookup row for each post in each term
1371         foreach ( $term_ids as $term_id ) {
1372                 $child = $term_id;
1373                 while ( $parent = $terms_by_id[$child]->parent ) {
1374                         if ( !empty($term_items[$term_id]) )
1375                                 foreach ( $term_items[$term_id] as $item_id => $touches )
1376                                         ++$term_items[$parent][$item_id];
1377                         $child = $parent;
1378                 }
1379         }
1380
1381         // Transfer the touched cells 
1382         foreach ( (array) $term_items as $id => $items )
1383                 if ( isset($terms_by_id[$id]) )
1384                         $terms_by_id[$id]->count = count($items);
1385 }
1386
1387 //
1388 // Default callbacks
1389 //
1390
1391 function _update_post_term_count( $terms ) {
1392         global $wpdb;
1393
1394         foreach ( $terms as $term ) {
1395                 $count = $wpdb->get_var("SELECT COUNT(*) FROM $wpdb->term_relationships, $wpdb->posts WHERE $wpdb->posts.ID = $wpdb->term_relationships.object_id AND post_status = 'publish' AND post_type = 'post' AND term_taxonomy_id = '$term'");
1396                 $wpdb->query("UPDATE $wpdb->term_taxonomy SET count = '$count' WHERE term_taxonomy_id = '$term'");
1397         }
1398 }
1399
1400 ?>