root/trunk/wp-includes/taxonomy.php

Revision 7738, 65.7 kB (checked in by ryan, 3 weeks ago)

Only use the args defined in defaults to compute the cache key in get_terms. Props mdawaffe.

  • Property svn:eol-style set to native
Line 
1 <?php
2 /**
3  * @package WordPress
4  * @subpackage Taxonomy
5  * @since 2.3
6  */
7
8 //
9 // Taxonomy Registration
10 //
11
12 /**
13  * Default Taxonomy Objects
14  * @since 2.3
15  * @global array $wp_taxonomies
16  */
17 $wp_taxonomies = array();
18 $wp_taxonomies['category'] = (object) array('name' => 'category', 'object_type' => 'post', 'hierarchical' => true, 'update_count_callback' => '_update_post_term_count');
19 $wp_taxonomies['post_tag'] = (object) array('name' => 'post_tag', 'object_type' => 'post', 'hierarchical' => false, 'update_count_callback' => '_update_post_term_count');
20 $wp_taxonomies['link_category'] = (object) array('name' => 'link_category', 'object_type' => 'link', 'hierarchical' => false);
21
22 /**
23  * get_object_taxonomies() - Return all of the taxonomy names that are of $object_type
24  *
25  * It appears that this function can be used to find all of the names inside of
26  * $wp_taxonomies global variable.
27  *
28  * <code><?php $taxonomies = get_object_taxonomies('post'); ?></code>
29  * Should result in <code>Array('category', 'post_tag')</code>
30  *
31  * @package WordPress
32  * @subpackage Taxonomy
33  * @since 2.3
34  *
35  * @uses $wp_taxonomies
36  *
37  * @param array|string|object $object Name of the type of taxonomy object, or an object (row from posts)
38  * @return array The names of all taxonomy of $object_type.
39  */
40 function get_object_taxonomies($object) {
41     global $wp_taxonomies;
42
43     if ( is_object($object) ) {
44         if ( $object->post_type == 'attachment' )
45             return get_attachment_taxonomies($object);
46         $object = $object->post_type;
47     }
48
49     $object = (array) $object;
50
51     $taxonomies = array();
52     foreach ( $wp_taxonomies as $taxonomy ) {
53         if ( array_intersect($object, (array) $taxonomy->object_type) )
54             $taxonomies[] = $taxonomy->name;
55     }
56
57     return $taxonomies;
58 }
59
60 /**
61  * get_taxonomy() - Returns the taxonomy object of $taxonomy.
62  *
63  * The get_taxonomy function will first check that the parameter string given
64  * is a taxonomy object and if it is, it will return it.
65  *
66  * @package WordPress
67  * @subpackage Taxonomy
68  * @since 2.3
69  *
70  * @uses $wp_taxonomies
71  * @uses is_taxonomy() Checks whether taxonomy exists
72  *
73  * @param string $taxonomy Name of taxonomy object to return
74  * @return object|bool The Taxonomy Object or false if $taxonomy doesn't exist
75  */
76 function get_taxonomy( $taxonomy ) {
77     global $wp_taxonomies;
78
79     if ( ! is_taxonomy($taxonomy) )
80         return false;
81
82     return $wp_taxonomies[$taxonomy];
83 }
84
85 /**
86  * is_taxonomy() - Checks that the taxonomy name exists
87  *
88  * @package WordPress
89  * @subpackage Taxonomy
90  * @since 2.3
91  *
92  * @uses $wp_taxonomies
93  *
94  * @param string $taxonomy Name of taxonomy object
95  * @return bool Whether the taxonomy exists or not.
96  */
97 function is_taxonomy( $taxonomy ) {
98     global $wp_taxonomies;
99
100     return isset($wp_taxonomies[$taxonomy]);
101 }
102
103 /**
104  * is_taxonomy_hierarchical() - Whether the taxonomy object is hierarchical
105  *
106  * Checks to make sure that the taxonomy is an object first. Then Gets the object, and finally
107  * returns the hierarchical value in the object.
108  *
109  * A false return value might also mean that the taxonomy does not exist.
110  *
111  * @package WordPress
112  * @subpackage Taxonomy
113  * @since 2.3
114  *
115  * @uses is_taxonomy() Checks whether taxonomy exists
116  * @uses get_taxonomy() Used to get the taxonomy object
117  *
118  * @param string $taxonomy Name of taxonomy object
119  * @return bool Whether the taxonomy is hierarchical
120  */
121 function is_taxonomy_hierarchical($taxonomy) {
122     if ( ! is_taxonomy($taxonomy) )
123         return false;
124
125     $taxonomy = get_taxonomy($taxonomy);
126     return $taxonomy->hierarchical;
127 }
128
129 /**
130  * register_taxonomy() - Create or modify a taxonomy object. Do not use before init.
131  *
132  * A simple function for creating or modifying a taxonomy object based on the parameters given.
133  * The function will accept an array (third optional parameter), along with strings for the
134  * taxonomy name and another string for the object type.
135  *
136  * Nothing is returned, so expect error maybe or use is_taxonomy() to check whether taxonomy exists.
137  *
138  * Optional $args contents:
139  * hierarachical - has some defined purpose at other parts of the API and is a boolean value.
140  * update_count_callback - works much like a hook, in that it will be called when the count is updated.
141  * rewrite - false to prevent rewrite, or array('slug'=>$slug) to customize permastruct; default will use $taxonomy as slug
142  * query_var - false to prevent queries, or string to customize query var (?$query_var=$term); default will use $taxonomy as query var
143  *
144  * @package WordPress
145  * @subpackage Taxonomy
146  * @since 2.3
147  * @uses $wp_taxonomies Inserts new taxonomy object into the list
148  * @uses $wp_rewrite Adds rewrite tags and permastructs
149  * @uses $wp Adds query vars
150  *
151  * @param string $taxonomy Name of taxonomy object
152  * @param array|string $object_type Name of the object type for the taxonomy object.
153  * @param array|string $args See above description for the two keys values.
154  */
155 function register_taxonomy( $taxonomy, $object_type, $args = array() ) {
156     global $wp_taxonomies, $wp_rewrite, $wp;
157
158     $defaults = array('hierarchical' => false, 'update_count_callback' => '', 'rewrite' => true, 'query_var' => true);
159     $args = wp_parse_args($args, $defaults);
160
161     if ( false !== $args['query_var'] && !empty($wp) ) {
162         if ( empty($args['query_var']) )
163             $args['query_var'] = $taxonomy;
164         $args['query_var'] = sanitize_title_with_dashes($args['query_var']);
165         $wp->add_query_var($args['query_var']);
166     }
167
168     if ( false !== $args['rewrite'] && !empty($wp_rewrite) ) {
169         if ( !is_array($args['rewrite']) )
170             $args['rewrite'] = array();
171         if ( !isset($args['rewrite']['slug']) )
172             $args['rewrite']['slug'] = sanitize_title_with_dashes($taxonomy);
173         $wp_rewrite->add_rewrite_tag("%$taxonomy%", '([^/]+)', $args['query_var'] ? "{$args['query_var']}=" : "taxonomy=$taxonomy&term=$term");
174         $wp_rewrite->add_permastruct($taxonomy, "{$args['rewrite']['slug']}/%$taxonomy%");
175     }
176
177     $args['name'] = $taxonomy;
178     $args['object_type'] = $object_type;
179     $wp_taxonomies[$taxonomy] = (object) $args;
180 }
181
182 //
183 // Term API
184 //
185
186 /**
187  * get_objects_in_term() - Return object_ids of valid taxonomy and term
188  *
189  * The strings of $taxonomies must exist before this function will continue. On failure of finding
190  * a valid taxonomy, it will return an WP_Error class, kind of like Exceptions in PHP 5, except you
191  * can't catch them. Even so, you can still test for the WP_Error class and get the error message.
192  *
193  * The $terms aren't checked the same as $taxonomies, but still need to exist for $object_ids to
194  * be returned.
195  *
196  * It is possible to change the order that object_ids is returned by either using PHP sort family
197  * functions or using the database by using $args with either ASC or DESC array. The value should
198  * be in the key named 'order'.
199  *
200  * @package WordPress
201  * @subpackage Taxonomy
202  * @since 2.3
203  *
204  * @uses $wpdb
205  * @uses wp_parse_args() Creates an array from string $args.
206  *
207  * @param string|array $terms String of term or array of string values of terms that will be used
208  * @param string|array $taxonomies String of taxonomy name or Array of string values of taxonomy names
209  * @param array|string $args Change the order of the object_ids, either ASC or DESC
210  * @return WP_Error|array If the taxonomy does not exist, then WP_Error will be returned. On success
211  *    the array can be empty meaning that there are no $object_ids found or it will return the $object_ids found.
212  */
213 function get_objects_in_term( $terms, $taxonomies, $args = array() ) {
214     global $wpdb;
215
216     if ( !is_array( $terms) )
217         $terms = array($terms);
218
219     if ( !is_array($taxonomies) )
220         $taxonomies = array($taxonomies);
221
222     foreach ( $taxonomies as $taxonomy ) {
223         if ( ! is_taxonomy($taxonomy) )
224             return new WP_Error('invalid_taxonomy', __('Invalid Taxonomy'));
225     }
226
227     $defaults = array('order' => 'ASC');
228     $args = wp_parse_args( $args, $defaults );
229     extract($args, EXTR_SKIP);
230
231     $order = ( 'desc' == strtolower($order) ) ? 'DESC' : 'ASC';
232
233     $terms = array_map('intval', $terms);
234
235     $taxonomies = "'" . implode("', '", $taxonomies) . "'";
236     $terms = "'" . implode("', '", $terms) . "'";
237
238     $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");
239
240     if ( ! $object_ids )
241         return array();
242
243     return $object_ids;
244 }
245
246 /**
247  * get_term() - Get all Term data from database by Term ID.
248  *
249  * The usage of the get_term function is to apply filters to a term object.
250  * It is possible to get a term object from the database before applying the
251  * filters.
252  *
253  * $term ID must be part of $taxonomy, to get from the database. Failure, might be
254  * able to be captured by the hooks. Failure would be the same value as $wpdb returns for the
255  * get_row method.
256  *
257  * There are two hooks, one is specifically for each term, named 'get_term', and the second is
258  * for the taxonomy name, 'term_$taxonomy'. Both hooks gets the term object, and the taxonomy
259  * name as parameters. Both hooks are expected to return a Term object.
260  *
261  * 'get_term' hook - Takes two parameters the term Object and the taxonomy name. Must return
262  * term object. Used in get_term() as a catch-all filter for every $term.
263  *
264  * 'get_$taxonomy' hook - Takes two parameters the term Object and the taxonomy name. Must return
265  * term object. $taxonomy will be the taxonomy name, so for example, if 'category', it would be
266  * 'get_category' as the filter name. Useful for custom taxonomies or plugging into default taxonomies.
267  *
268  * @package WordPress
269  * @subpackage Taxonomy
270  * @since 2.3
271  *
272  * @uses $wpdb
273  * @uses sanitize_term() Cleanses the term based on $filter context before returning.
274  * @see sanitize_term_field() The $context param lists the available values for get_term_by() $filter param.
275  *
276  * @param int|object $term If integer, will get from database. If object will apply filters and return $term.
277  * @param string $taxonomy Taxonomy name that $term is part of.
278  * @param string $output Constant OBJECT, ARRAY_A, or ARRAY_N
279  * @param string $filter Optional, default is raw or no WordPress defined filter will applied.
280  * @return mixed|null|WP_Error Term Row from database. Will return null if $term is empty. If taxonomy does not
281  * exist then WP_Error will be returned.
282  */
283 function &get_term($term, $taxonomy, $output = OBJECT, $filter = 'raw') {
284     global $wpdb;
285
286     if ( empty($term) )
287         return null;
288
289     if ( ! is_taxonomy($taxonomy) )
290         return new WP_Error('invalid_taxonomy', __('Invalid Taxonomy'));
291
292     if ( is_object($term) ) {
293         wp_cache_add($term->term_id, $term, $taxonomy);
294         $_term = $term;
295     } else {
296         $term = (int) $term;
297         if ( ! $_term = wp_cache_get($term, $taxonomy) ) {
298             $_term = $wpdb->get_row( $wpdb->prepare( "SELECT t.*, tt.* FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id WHERE tt.taxonomy = %s AND t.term_id = %s LIMIT 1", $taxonomy, $term) );
299             wp_cache_add($term, $_term, $taxonomy);
300         }
301     }
302
303     $_term = apply_filters('get_term', $_term, $taxonomy);
304     $_term = apply_filters("get_$taxonomy", $_term, $taxonomy);
305     $_term = sanitize_term($_term, $taxonomy, $filter);
306
307     if ( $output == OBJECT ) {
308         return $_term;
309     } elseif ( $output == ARRAY_A ) {
310         return get_object_vars($_term);
311     } elseif ( $output == ARRAY_N ) {
312         return array_values(get_object_vars($_term));
313     } else {
314         return $_term;
315     }
316 }
317
318 /**
319  * get_term_by() - Get all Term data from database by Term field and data.
320  *
321  * Warning: $value is not escaped for 'name' $field. You must do it yourself, if required.
322  *
323  * The default $field is 'id', therefore it is possible to also use null for field, but not
324  * recommended that you do so.
325  *
326  * If $value does not exist, the return value will be false. If $taxonomy exists and $field
327  * and $value combinations exist, the Term will be returned.
328  *
329  * @package WordPress
330  * @subpackage Taxonomy
331  * @since 2.3
332  *
333  * @uses $wpdb
334  * @uses sanitize_term() Cleanses the term based on $filter context before returning.
335  * @see sanitize_term_field() The $context param lists the available values for get_term_by() $filter param.
336  *
337  * @param string $field Either 'slug', 'name', or 'id'
338  * @param string|int $value Search for this term value
339  * @param string $taxonomy Taxonomy Name
340  * @param string $output Constant OBJECT, ARRAY_A, or ARRAY_N
341  * @param string $filter Optional, default is raw or no WordPress defined filter will applied.
342  * @return mixed Term Row from database. Will return false if $taxonomy does not exist or $term was not found.
343  */
344 function get_term_by($field, $value, $taxonomy, $output = OBJECT, $filter = 'raw') {
345     global $wpdb;
346
347     if ( ! is_taxonomy($taxonomy) )
348         return false;
349
350     if ( 'slug' == $field ) {
351         $field = 't.slug';
352         $value = sanitize_title($value);
353         if ( empty($value) )
354             return false;
355     } else if ( 'name' == $field ) {
356         // Assume already escaped
357         $field = 't.name';
358     } else {
359         $field = 't.term_id';
360         $value = (int) $value;
361     }
362
363     $term = $wpdb->get_row( $wpdb->prepare( "SELECT t.*, tt.* FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id WHERE tt.taxonomy = %s AND $field = %s LIMIT 1", $taxonomy, $value) );
364     if ( !$term )
365         return false;
366
367     wp_cache_add($term->term_id, $term, $taxonomy);
368
369     $term = sanitize_term($term, $taxonomy, $filter);
370
371     if ( $output == OBJECT ) {
372         return $term;
373     } elseif ( $output == ARRAY_A ) {
374         return get_object_vars($term);
375     } elseif ( $output == ARRAY_N ) {
376         return array_values(get_object_vars($term));
377     } else {
378         return $term;
379     }
380 }
381
382 /**
383  * get_term_children() - Merge all term children into a single array.
384  *
385  * This recursive function will merge all of the children of $term into
386  * the same array. Only useful for taxonomies which are hierarchical.
387  *
388  * Will return an empty array if $term does not exist in $taxonomy.
389  *
390  * @package WordPress
391  * @subpackage Taxonomy
392  * @since 2.3
393  *
394  * @uses $wpdb
395  * @uses _get_term_hierarchy()
396  * @uses get_term_children() Used to get the children of both $taxonomy and the parent $term
397  *
398  * @param string $term Name of Term to get children
399  * @param string $taxonomy Taxonomy Name
400  * @return array|WP_Error List of Term Objects. WP_Error returned if $taxonomy does not exist
401  */
402 function get_term_children( $term, $taxonomy ) {
403     if ( ! is_taxonomy($taxonomy) )
404         return new WP_Error('invalid_taxonomy', __('Invalid Taxonomy'));
405
406     $terms = _get_term_hierarchy($taxonomy);
407
408     if ( ! isset($terms[$term]) )
409         return array();
410
411     $children = $terms[$term];
412
413     foreach ( $terms[$term] as $child ) {
414         if ( isset($terms[$child]) )
415             $children = array_merge($children, get_term_children($child, $taxonomy));
416     }
417
418     return $children;
419 }
420
421 /**
422  * get_term_field() - Get sanitized Term field
423  *
424  * Does checks for $term, based on the $taxonomy. The function is for
425  * contextual reasons and for simplicity of usage. See sanitize_term_field() for
426  * more information.
427  *
428  * @package WordPress
429  * @subpackage Taxonomy
430  * @since 2.3
431  *
432  * @uses sanitize_term_field() Passes the return value in sanitize_term_field on success.
433  *
434  * @param string $field Term field to fetch
435  * @param int $term Term ID
436  * @param string $taxonomy Taxonomy Name
437  * @param string $context Optional, default is display. Look at sanitize_term_field() for available options.
438  * @return mixed Will return an empty string if $term is not an object or if $field is not set in $term.
439  */
440 function get_term_field( $field, $term, $taxonomy, $context = 'display' ) {
441     $term = (int) $term;
442     $term = get_term( $term, $taxonomy );
443     if ( is_wp_error($term) )
444         return $term;
445
446     if ( !is_object($term) )
447         return '';
448
449     if ( !isset($term->$field) )
450         return '';
451
452     return sanitize_term_field($field, $term->$field, $term->term_id, $taxonomy, $context);
453 }
454
455 /**
456  * get_term_to_edit() - Sanitizes Term for editing
457  *
458  * Return value is sanitize_term() and usage is for sanitizing the term
459  * for editing. Function is for contextual and simplicity.
460  *
461  * @package WordPress
462  * @subpackage Taxonomy
463  * @since 2.3
464  *
465  * @uses sanitize_term() Passes the return value on success
466  *
467  * @param int|object $id Term ID or Object
468  * @param string $taxonomy Taxonomy Name
469  * @return mixed|null|WP_Error Will return empty string if $term is not an object.
470  */
471 function get_term_to_edit( $id, $taxonomy ) {
472     $term = get_term( $id, $taxonomy );
473
474     if ( is_wp_error($term) )
475         return $term;
476
477     if ( !is_object($term) )
478         return '';
479
480     return sanitize_term($term, $taxonomy, 'edit');
481 }
482
483 /**
484  * get_terms() - Retrieve the terms in taxonomy or list of taxonomies.
485  *
486  * You can fully inject any customizations to the query before it is sent, as well as control
487  * the output with a filter.
488  *
489  * The 'get_terms' filter will be called when the cache has the term and will pass the found
490  * term along with the array of $taxonomies and array of $args. This filter is also called
491  * before the array of terms is passed and will pass the array of terms, along with the $taxonomies
492  * and $args.
493  *
494  * The 'list_terms_exclusions' filter passes the compiled exclusions along with the $args.
495  *
496  * The list that $args can contain, which will overwrite the defaults.
497  * orderby - Default is 'name'. Can be name, count, or nothing (will use term_id).
498  * order - Default is ASC. Can use DESC.
499  * hide_empty - Default is true. Will not return empty $terms.
500  * fields - Default is all.
501  * slug - Any terms that has this value. Default is empty string.
502  * hierarchical - Whether to return hierarchical taxonomy. Default is true.
503  * name__like - Default is empty string.
504  *
505  * The argument 'pad_counts' will count all of the children along with the $terms.
506  *
507  * The 'get' argument allows for overwriting 'hide_empty' and 'child_of', which can be done by
508  * setting the value to 'all', instead of its default empty string value.
509  *
510  * The 'child_of' argument will be used if you use multiple taxonomy or the first $taxonomy
511  * isn't hierarchical or 'parent' isn't used. The default is 0, which will be translated to
512  * a false value. If 'child_of' is set, then 'child_of' value will be tested against
513  * $taxonomy to see if 'child_of' is contained within. Will return an empty array if test
514  * fails.
515  *
516  * If 'parent' is set, then it will be used to test against the first taxonomy. Much like
517  * 'child_of'. Will return an empty array if the test fails.
518  *
519  * @package WordPress
520  * @subpackage Taxonomy
521  * @since 2.3
522  *
523  * @uses $wpdb
524  * @uses wp_parse_args() Merges the defaults with those defined by $args and allows for strings.
525  *
526  *
527  * @param string|array Taxonomy name or list of Taxonomy names
528  * @param string|array $args The values of what to search for when returning terms
529  * @return array|WP_Error List of Term Objects and their children. Will return WP_Error, if any of $taxonomies do not exist.
530  */
531 function &get_terms($taxonomies, $args = '') {
532     global $wpdb;
533     $empty_array = array();
534
535     $single_taxonomy = false;
536     if ( !is_array($taxonomies) ) {
537         $single_taxonomy = true;
538         $taxonomies = array($taxonomies);
539     }
540
541     foreach ( $taxonomies as $taxonomy ) {
542         if ( ! is_taxonomy($taxonomy) )
543             return new WP_Error('invalid_taxonomy', __('Invalid Taxonomy'));
544     }
545
546     $in_taxonomies = "'" . implode("', '", $taxonomies) . "'";
547
548     $defaults = array('orderby' => 'name', 'order' => 'ASC',
549         'hide_empty' => true, 'exclude' => '', 'include' => '',
550         'number' => '', 'fields' => 'all', 'slug' => '', 'parent' => '',
551         'hierarchical' => true, 'child_of' => 0, 'get' => '', 'name__like' => '',
552         'pad_counts' => false, 'offset' => '', 'search' => '');
553     $args = wp_parse_args( $args, $defaults );
554     $args['number'] = absint( $args['number'] );
555     $args['offset'] = absint( $args['offset'] );
556     if ( !$single_taxonomy || !is_taxonomy_hierarchical($taxonomies[0]) ||
557         '' != $args['parent'] ) {
558         $args['child_of'] = 0;
559         $args['hierarchical'] = false;
560         $args['pad_counts'] = false;
561     }
562
563     if ( 'all' == $args['get'] ) {
564         $args['child_of'] = 0;
565         $args['hide_empty'] = 0;
566         $args['hierarchical'] = false;
567         $args['pad_counts'] = false;
568     }
569     extract($args, EXTR_SKIP);
570
571     if ( $child_of ) {
572         $hierarchy = _get_term_hierarchy($taxonomies[0]);
573         if ( !isset($hierarchy[$child_of]) )
574             return $empty_array;
575     }
576
577     if ( $parent ) {
578         $hierarchy = _get_term_hierarchy($taxonomies[0]);
579         if ( !isset($hierarchy[$parent]) )
580             return $empty_array;
581     }
582
583     // $args can be whatever, only use the args defined in defaults to compute the key
584     $key = md5( serialize( compact(array_keys($defaults)) ) . serialize( $taxonomies ) );
585
586     if ( $cache = wp_cache_get( 'get_terms', 'terms' ) ) {
587         if ( isset( $cache[ $key ] ) )
588             return apply_filters('get_terms', $cache[$key], $taxonomies, $args);
589     }
590
591     if ( 'count' == $orderby )
592         $orderby = 'tt.count';
593     else if ( 'name' == $orderby )
594         $orderby = 't.name';
595     else if ( 'slug' == $orderby )
596         $orderby = 't.slug';
597     else if ( 'term_group' == $orderby )
598         $orderby = 't.term_group';
599     else
600         $orderby = 't.term_id';
601
602     $where = '';
603     $inclusions = '';
604     if ( !empty($include) ) {
605         $exclude = '';
606         $interms = preg_split('/[\s,]+/',$include);
607         if ( count($interms) ) {
608             foreach ( $interms as $interm ) {
609                 if (empty($inclusions))
610                     $inclusions = ' AND ( t.term_id = ' . intval($interm) . ' ';
611                 else
612                     $inclusions .= ' OR t.term_id = ' . intval($interm) . ' ';
613             }
614         }
615     }
616
617     if ( !empty($inclusions) )
618         $inclusions .= ')';
619     $where .=