Issues (2010)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

wp-includes/taxonomy.php (30 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

Code
1
<?php
2
/**
3
 * Core Taxonomy API
4
 *
5
 * @package WordPress
6
 * @subpackage Taxonomy
7
 */
8
9
//
10
// Taxonomy Registration
11
//
12
13
/**
14
 * Creates the initial taxonomies.
15
 *
16
 * This function fires twice: in wp-settings.php before plugins are loaded (for
17
 * backward compatibility reasons), and again on the {@see 'init'} action. We must
18
 * avoid registering rewrite rules before the {@see 'init'} action.
19
 *
20
 * @since 2.8.0
21
 *
22
 * @global WP_Rewrite $wp_rewrite The WordPress rewrite class.
23
 */
24
function create_initial_taxonomies() {
25
	global $wp_rewrite;
26
27
	if ( ! did_action( 'init' ) ) {
28
		$rewrite = array( 'category' => false, 'post_tag' => false, 'post_format' => false );
29
	} else {
30
31
		/**
32
		 * Filters the post formats rewrite base.
33
		 *
34
		 * @since 3.1.0
35
		 *
36
		 * @param string $context Context of the rewrite base. Default 'type'.
37
		 */
38
		$post_format_base = apply_filters( 'post_format_rewrite_base', 'type' );
39
		$rewrite = array(
40
			'category' => array(
41
				'hierarchical' => true,
42
				'slug' => get_option('category_base') ? get_option('category_base') : 'category',
43
				'with_front' => ! get_option('category_base') || $wp_rewrite->using_index_permalinks(),
44
				'ep_mask' => EP_CATEGORIES,
45
			),
46
			'post_tag' => array(
47
				'hierarchical' => false,
48
				'slug' => get_option('tag_base') ? get_option('tag_base') : 'tag',
49
				'with_front' => ! get_option('tag_base') || $wp_rewrite->using_index_permalinks(),
50
				'ep_mask' => EP_TAGS,
51
			),
52
			'post_format' => $post_format_base ? array( 'slug' => $post_format_base ) : false,
53
		);
54
	}
55
56
	register_taxonomy( 'category', 'post', array(
57
		'hierarchical' => true,
58
		'query_var' => 'category_name',
59
		'rewrite' => $rewrite['category'],
60
		'public' => true,
61
		'show_ui' => true,
62
		'show_admin_column' => true,
63
		'_builtin' => true,
64
	) );
65
66
	register_taxonomy( 'post_tag', 'post', array(
67
	 	'hierarchical' => false,
68
		'query_var' => 'tag',
69
		'rewrite' => $rewrite['post_tag'],
70
		'public' => true,
71
		'show_ui' => true,
72
		'show_admin_column' => true,
73
		'_builtin' => true,
74
	) );
75
76
	register_taxonomy( 'nav_menu', 'nav_menu_item', array(
77
		'public' => false,
78
		'hierarchical' => false,
79
		'labels' => array(
80
			'name' => __( 'Navigation Menus' ),
81
			'singular_name' => __( 'Navigation Menu' ),
82
		),
83
		'query_var' => false,
84
		'rewrite' => false,
85
		'show_ui' => false,
86
		'_builtin' => true,
87
		'show_in_nav_menus' => false,
88
	) );
89
90
	register_taxonomy( 'link_category', 'link', array(
91
		'hierarchical' => false,
92
		'labels' => array(
93
			'name' => __( 'Link Categories' ),
94
			'singular_name' => __( 'Link Category' ),
95
			'search_items' => __( 'Search Link Categories' ),
96
			'popular_items' => null,
97
			'all_items' => __( 'All Link Categories' ),
98
			'edit_item' => __( 'Edit Link Category' ),
99
			'update_item' => __( 'Update Link Category' ),
100
			'add_new_item' => __( 'Add New Link Category' ),
101
			'new_item_name' => __( 'New Link Category Name' ),
102
			'separate_items_with_commas' => null,
103
			'add_or_remove_items' => null,
104
			'choose_from_most_used' => null,
105
		),
106
		'capabilities' => array(
107
			'manage_terms' => 'manage_links',
108
			'edit_terms'   => 'manage_links',
109
			'delete_terms' => 'manage_links',
110
			'assign_terms' => 'manage_links',
111
		),
112
		'query_var' => false,
113
		'rewrite' => false,
114
		'public' => false,
115
		'show_ui' => true,
116
		'_builtin' => true,
117
	) );
118
119
	register_taxonomy( 'post_format', 'post', array(
120
		'public' => true,
121
		'hierarchical' => false,
122
		'labels' => array(
123
			'name' => _x( 'Format', 'post format' ),
124
			'singular_name' => _x( 'Format', 'post format' ),
125
		),
126
		'query_var' => true,
127
		'rewrite' => $rewrite['post_format'],
128
		'show_ui' => false,
129
		'_builtin' => true,
130
		'show_in_nav_menus' => current_theme_supports( 'post-formats' ),
131
	) );
132
}
133
134
/**
135
 * Retrieves a list of registered taxonomy names or objects.
136
 *
137
 * @since 3.0.0
138
 *
139
 * @global array $wp_taxonomies The registered taxonomies.
140
 *
141
 * @param array  $args     Optional. An array of `key => value` arguments to match against the taxonomy objects.
142
 *                         Default empty array.
143
 * @param string $output   Optional. The type of output to return in the array. Accepts either taxonomy 'names'
144
 *                         or 'objects'. Default 'names'.
145
 * @param string $operator Optional. The logical operation to perform. Accepts 'and' or 'or'. 'or' means only
146
 *                         one element from the array needs to match; 'and' means all elements must match.
147
 *                         Default 'and'.
148
 * @return array A list of taxonomy names or objects.
149
 */
150 View Code Duplication
function get_taxonomies( $args = array(), $output = 'names', $operator = 'and' ) {
0 ignored issues
show
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
151
	global $wp_taxonomies;
152
153
	$field = ('names' == $output) ? 'name' : false;
154
155
	return wp_filter_object_list($wp_taxonomies, $args, $operator, $field);
156
}
157
158
/**
159
 * Return the names or objects of the taxonomies which are registered for the requested object or object type, such as
160
 * a post object or post type name.
161
 *
162
 * Example:
163
 *
164
 *     $taxonomies = get_object_taxonomies( 'post' );
165
 *
166
 * This results in:
167
 *
168
 *     Array( 'category', 'post_tag' )
169
 *
170
 * @since 2.3.0
171
 *
172
 * @global array $wp_taxonomies The registered taxonomies.
173
 *
174
 * @param array|string|WP_Post $object Name of the type of taxonomy object, or an object (row from posts)
175
 * @param string               $output Optional. The type of output to return in the array. Accepts either
176
 *                                     taxonomy 'names' or 'objects'. Default 'names'.
177
 * @return array The names of all taxonomy of $object_type.
178
 */
179
function get_object_taxonomies( $object, $output = 'names' ) {
180
	global $wp_taxonomies;
181
182
	if ( is_object($object) ) {
183
		if ( $object->post_type == 'attachment' )
184
			return get_attachment_taxonomies( $object, $output );
185
		$object = $object->post_type;
186
	}
187
188
	$object = (array) $object;
189
190
	$taxonomies = array();
191
	foreach ( (array) $wp_taxonomies as $tax_name => $tax_obj ) {
192
		if ( array_intersect($object, (array) $tax_obj->object_type) ) {
193
			if ( 'names' == $output )
194
				$taxonomies[] = $tax_name;
195
			else
196
				$taxonomies[ $tax_name ] = $tax_obj;
197
		}
198
	}
199
200
	return $taxonomies;
201
}
202
203
/**
204
 * Retrieves the taxonomy object of $taxonomy.
205
 *
206
 * The get_taxonomy function will first check that the parameter string given
207
 * is a taxonomy object and if it is, it will return it.
208
 *
209
 * @since 2.3.0
210
 *
211
 * @global array $wp_taxonomies The registered taxonomies.
212
 *
213
 * @param string $taxonomy Name of taxonomy object to return.
214
 * @return object|false The Taxonomy Object or false if $taxonomy doesn't exist.
215
 */
216
function get_taxonomy( $taxonomy ) {
217
	global $wp_taxonomies;
218
219
	if ( ! taxonomy_exists( $taxonomy ) )
220
		return false;
221
222
	return $wp_taxonomies[$taxonomy];
223
}
224
225
/**
226
 * Checks that the taxonomy name exists.
227
 *
228
 * Formerly is_taxonomy(), introduced in 2.3.0.
229
 *
230
 * @since 3.0.0
231
 *
232
 * @global array $wp_taxonomies The registered taxonomies.
233
 *
234
 * @param string $taxonomy Name of taxonomy object.
235
 * @return bool Whether the taxonomy exists.
236
 */
237
function taxonomy_exists( $taxonomy ) {
238
	global $wp_taxonomies;
239
240
	return isset( $wp_taxonomies[$taxonomy] );
241
}
242
243
/**
244
 * Whether the taxonomy object is hierarchical.
245
 *
246
 * Checks to make sure that the taxonomy is an object first. Then Gets the
247
 * object, and finally returns the hierarchical value in the object.
248
 *
249
 * A false return value might also mean that the taxonomy does not exist.
250
 *
251
 * @since 2.3.0
252
 *
253
 * @param string $taxonomy Name of taxonomy object.
254
 * @return bool Whether the taxonomy is hierarchical.
255
 */
256
function is_taxonomy_hierarchical($taxonomy) {
257
	if ( ! taxonomy_exists($taxonomy) )
258
		return false;
259
260
	$taxonomy = get_taxonomy($taxonomy);
261
	return $taxonomy->hierarchical;
262
}
263
264
/**
265
 * Creates or modifies a taxonomy object.
266
 *
267
 * Note: Do not use before the {@see 'init'} hook.
268
 *
269
 * A simple function for creating or modifying a taxonomy object based on the
270
 * parameters given. The function will accept an array (third optional
271
 * parameter), along with strings for the taxonomy name and another string for
272
 * the object type.
273
 *
274
 * @since 2.3.0
275
 * @since 4.2.0 Introduced `show_in_quick_edit` argument.
276
 * @since 4.4.0 The `show_ui` argument is now enforced on the term editing screen.
277
 * @since 4.4.0 The `public` argument now controls whether the taxonomy can be queried on the front end.
278
 * @since 4.5.0 Introduced `publicly_queryable` argument.
279
 *
280
 * @global array $wp_taxonomies Registered taxonomies.
281
 * @global WP    $wp            WP instance.
282
 *
283
 * @param string       $taxonomy    Taxonomy key, must not exceed 32 characters.
284
 * @param array|string $object_type Name of the object type for the taxonomy object.
285
 * @param array|string $args        {
286
 *     Optional. Array or query string of arguments for registering a taxonomy.
287
 *
288
 *     @type string        $label                 Name of the taxonomy shown in the menu. Usually plural. If not set,
289
 *                                                `$labels['name']` will be used.
290
 *     @type array         $labels                An array of labels for this taxonomy. By default, Tag labels are used for
291
 *                                                non-hierarchical taxonmies, and Category labels are used for hierarchical
292
 *                                                taxonomies. See accepted values in get_taxonomy_labels().
293
 *                                                Default empty array.
294
 *     @type string        $description           A short descriptive summary of what the taxonomy is for. Default empty.
295
 *     @type bool          $public                Whether a taxonomy is intended for use publicly either via
296
 *                                                the admin interface or by front-end users. The default settings
297
 *                                                of `$publicly_queryable`, `$show_ui`, and `$show_in_nav_menus`
298
 *                                                are inherited from `$public`.
299
 *     @type bool          $publicly_queryable    Whether the taxonomy is publicly queryable.
300
 *                                                If not set, the default is inherited from `$public`
301
 *     @type bool          $hierarchical          Whether the taxonomy is hierarchical. Default false.
302
 *     @type bool          $show_ui               Whether to generate and allow a UI for managing terms in this taxonomy in
303
 *                                                the admin. If not set, the default is inherited from `$public`
304
 *                                                (default true).
305
 *     @type bool          $show_in_menu          Whether to show the taxonomy in the admin menu. If true, the taxonomy is
306
 *                                                shown as a submenu of the object type menu. If false, no menu is shown.
307
 *                                                `$show_ui` must be true. If not set, default is inherited from `$show_ui`
308
 *                                                (default true).
309
 *     @type bool          $show_in_nav_menus     Makes this taxonomy available for selection in navigation menus. If not
310
 *                                                set, the default is inherited from `$public` (default true).
311
 *     @type bool          $show_tagcloud         Whether to list the taxonomy in the Tag Cloud Widget controls. If not set,
312
 *                                                the default is inherited from `$show_ui` (default true).
313
 *     @type bool          $show_in_quick_edit    Whether to show the taxonomy in the quick/bulk edit panel. It not set,
314
 *                                                the default is inherited from `$show_ui` (default true).
315
 *     @type bool          $show_admin_column     Whether to display a column for the taxonomy on its post type listing
316
 *                                                screens. Default false.
317
 *     @type bool|callable $meta_box_cb           Provide a callback function for the meta box display. If not set,
318
 *                                                post_categories_meta_box() is used for hierarchical taxonomies, and
319
 *                                                post_tags_meta_box() is used for non-hierarchical. If false, no meta
320
 *                                                box is shown.
321
 *     @type array         $capabilities {
322
 *         Array of capabilities for this taxonomy.
323
 *
324
 *         @type string $manage_terms Default 'manage_categories'.
325
 *         @type string $edit_terms   Default 'manage_categories'.
326
 *         @type string $delete_terms Default 'manage_categories'.
327
 *         @type string $assign_terms Default 'edit_posts'.
328
 *     }
329
 *     @type bool|array    $rewrite {
330
 *         Triggers the handling of rewrites for this taxonomy. Default true, using $taxonomy as slug. To prevent
331
 *         rewrite, set to false. To specify rewrite rules, an array can be passed with any of these keys:
332
 *
333
 *         @type string $slug         Customize the permastruct slug. Default `$taxonomy` key.
334
 *         @type bool   $with_front   Should the permastruct be prepended with WP_Rewrite::$front. Default true.
335
 *         @type bool   $hierarchical Either hierarchical rewrite tag or not. Default false.
336
 *         @type int    $ep_mask      Assign an endpoint mask. Default `EP_NONE`.
337
 *     }
338
 *     @type string        $query_var             Sets the query var key for this taxonomy. Default `$taxonomy` key. If
339
 *                                                false, a taxonomy cannot be loaded at `?{query_var}={term_slug}`. If a
340
 *                                                string, the query `?{query_var}={term_slug}` will be valid.
341
 *     @type callable      $update_count_callback Works much like a hook, in that it will be called when the count is
342
 *                                                updated. Default _update_post_term_count() for taxonomies attached
343
 *                                                to post types, which confirms that the objects are published before
344
 *                                                counting them. Default _update_generic_term_count() for taxonomies
345
 *                                                attached to other object types, such as users.
346
 *     @type bool          $_builtin              This taxonomy is a "built-in" taxonomy. INTERNAL USE ONLY!
347
 *                                                Default false.
348
 * }
349
 * @return WP_Error|void WP_Error, if errors.
350
 */
351
function register_taxonomy( $taxonomy, $object_type, $args = array() ) {
352
	global $wp_taxonomies, $wp;
353
354
	if ( ! is_array( $wp_taxonomies ) )
355
		$wp_taxonomies = array();
356
357
	$args = wp_parse_args( $args );
358
359
	/**
360
	 * Filters the arguments for registering a taxonomy.
361
	 *
362
	 * @since 4.4.0
363
	 *
364
	 * @param array  $args        Array of arguments for registering a taxonomy.
365
	 * @param string $taxonomy    Taxonomy key.
366
	 * @param array  $object_type Array of names of object types for the taxonomy.
367
	 */
368
	$args = apply_filters( 'register_taxonomy_args', $args, $taxonomy, (array) $object_type );
369
370
	$defaults = array(
371
		'labels'                => array(),
372
		'description'           => '',
373
		'public'                => true,
374
		'publicly_queryable'    => null,
375
		'hierarchical'          => false,
376
		'show_ui'               => null,
377
		'show_in_menu'          => null,
378
		'show_in_nav_menus'     => null,
379
		'show_tagcloud'         => null,
380
		'show_in_quick_edit'	=> null,
381
		'show_admin_column'     => false,
382
		'meta_box_cb'           => null,
383
		'capabilities'          => array(),
384
		'rewrite'               => true,
385
		'query_var'             => $taxonomy,
386
		'update_count_callback' => '',
387
		'_builtin'              => false,
388
	);
389
	$args = array_merge( $defaults, $args );
390
391 View Code Duplication
	if ( empty( $taxonomy ) || strlen( $taxonomy ) > 32 ) {
392
		_doing_it_wrong( __FUNCTION__, __( 'Taxonomy names must be between 1 and 32 characters in length.' ), '4.2.0' );
393
		return new WP_Error( 'taxonomy_length_invalid', __( 'Taxonomy names must be between 1 and 32 characters in length.' ) );
394
	}
395
396
	// If not set, default to the setting for public.
397
	if ( null === $args['publicly_queryable'] ) {
398
		$args['publicly_queryable'] = $args['public'];
399
	}
400
401
	// Non-publicly queryable taxonomies should not register query vars, except in the admin.
402
	if ( false !== $args['query_var'] && ( is_admin() || false !== $args['publicly_queryable'] ) && ! empty( $wp ) ) {
403 View Code Duplication
		if ( true === $args['query_var'] )
404
			$args['query_var'] = $taxonomy;
405
		else
406
			$args['query_var'] = sanitize_title_with_dashes( $args['query_var'] );
407
		$wp->add_query_var( $args['query_var'] );
408
	} else {
409
		// Force query_var to false for non-public taxonomies.
410
		$args['query_var'] = false;
411
	}
412
413
	if ( false !== $args['rewrite'] && ( is_admin() || '' != get_option( 'permalink_structure' ) ) ) {
414
		$args['rewrite'] = wp_parse_args( $args['rewrite'], array(
415
			'with_front' => true,
416
			'hierarchical' => false,
417
			'ep_mask' => EP_NONE,
418
		) );
419
420
		if ( empty( $args['rewrite']['slug'] ) )
421
			$args['rewrite']['slug'] = sanitize_title_with_dashes( $taxonomy );
422
423
		if ( $args['hierarchical'] && $args['rewrite']['hierarchical'] )
424
			$tag = '(.+?)';
425
		else
426
			$tag = '([^/]+)';
427
428
		add_rewrite_tag( "%$taxonomy%", $tag, $args['query_var'] ? "{$args['query_var']}=" : "taxonomy=$taxonomy&term=" );
429
		add_permastruct( $taxonomy, "{$args['rewrite']['slug']}/%$taxonomy%", $args['rewrite'] );
430
	}
431
432
	// If not set, default to the setting for public.
433
	if ( null === $args['show_ui'] )
434
		$args['show_ui'] = $args['public'];
435
436
	// If not set, default to the setting for show_ui.
437 View Code Duplication
	if ( null === $args['show_in_menu' ] || ! $args['show_ui'] )
438
		$args['show_in_menu' ] = $args['show_ui'];
439
440
	// If not set, default to the setting for public.
441
	if ( null === $args['show_in_nav_menus'] )
442
		$args['show_in_nav_menus'] = $args['public'];
443
444
	// If not set, default to the setting for show_ui.
445
	if ( null === $args['show_tagcloud'] )
446
		$args['show_tagcloud'] = $args['show_ui'];
447
448
	// If not set, default to the setting for show_ui.
449
	if ( null === $args['show_in_quick_edit'] ) {
450
		$args['show_in_quick_edit'] = $args['show_ui'];
451
	}
452
453
	$default_caps = array(
454
		'manage_terms' => 'manage_categories',
455
		'edit_terms'   => 'manage_categories',
456
		'delete_terms' => 'manage_categories',
457
		'assign_terms' => 'edit_posts',
458
	);
459
	$args['cap'] = (object) array_merge( $default_caps, $args['capabilities'] );
460
	unset( $args['capabilities'] );
461
462
	$args['name'] = $taxonomy;
463
	$args['object_type'] = array_unique( (array) $object_type );
464
465
	$args['labels'] = get_taxonomy_labels( (object) $args );
466
	$args['label'] = $args['labels']->name;
467
468
	// If not set, use the default meta box
469
	if ( null === $args['meta_box_cb'] ) {
470
		if ( $args['hierarchical'] )
471
			$args['meta_box_cb'] = 'post_categories_meta_box';
472
		else
473
			$args['meta_box_cb'] = 'post_tags_meta_box';
474
	}
475
476
	$wp_taxonomies[ $taxonomy ] = (object) $args;
477
478
	// Register callback handling for meta box.
479
 	add_filter( 'wp_ajax_add-' . $taxonomy, '_wp_ajax_add_hierarchical_term' );
480
481
	/**
482
	 * Fires after a taxonomy is registered.
483
	 *
484
	 * @since 3.3.0
485
	 *
486
	 * @param string       $taxonomy    Taxonomy slug.
487
	 * @param array|string $object_type Object type or array of object types.
488
	 * @param array        $args        Array of taxonomy registration arguments.
489
	 */
490
	do_action( 'registered_taxonomy', $taxonomy, $object_type, $args );
491
}
492
493
/**
494
 * Unregisters a taxonomy.
495
 *
496
 * Can not be used to unregister built-in taxonomies.
497
 *
498
 * @since 4.5.0
499
 *
500
 * @global WP    $wp            Current WordPress environment instance.
501
 * @global array $wp_taxonomies List of taxonomies.
502
 *
503
 * @param string $taxonomy Taxonomy name.
504
 * @return bool|WP_Error True on success, WP_Error on failure or if the taxonomy doesn't exist.
505
 */
506
function unregister_taxonomy( $taxonomy ) {
507
	if ( ! taxonomy_exists( $taxonomy ) ) {
508
		return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
509
	}
510
511
	$taxonomy_args = get_taxonomy( $taxonomy );
512
513
	// Do not allow unregistering internal taxonomies.
514
	if ( $taxonomy_args->_builtin ) {
515
		return new WP_Error( 'invalid_taxonomy', __( 'Unregistering a built-in taxonomy is not allowed' ) );
516
	}
517
518
	global $wp, $wp_taxonomies;
519
520
	// Remove query var.
521
	if ( false !== $taxonomy_args->query_var ) {
522
		$wp->remove_query_var( $taxonomy_args->query_var );
523
	}
524
525
	// Remove rewrite tags and permastructs.
526
	if ( false !== $taxonomy_args->rewrite ) {
527
		remove_rewrite_tag( "%$taxonomy%" );
528
		remove_permastruct( $taxonomy );
529
	}
530
531
	// Unregister callback handling for meta box.
532
	remove_filter( 'wp_ajax_add-' . $taxonomy, '_wp_ajax_add_hierarchical_term' );
533
534
	// Remove the taxonomy.
535
	unset( $wp_taxonomies[ $taxonomy ] );
536
537
	/**
538
	 * Fires after a taxonomy is unregistered.
539
	 *
540
	 * @since 4.5.0
541
	 *
542
	 * @param string $taxonomy Taxonomy name.
543
	 */
544
	do_action( 'unregistered_taxonomy', $taxonomy );
545
546
	return true;
547
}
548
549
/**
550
 * Builds an object with all taxonomy labels out of a taxonomy object
551
 *
552
 * Accepted keys of the label array in the taxonomy object:
553
 *
554
 * - name - general name for the taxonomy, usually plural. The same as and overridden by $tax->label. Default is Tags/Categories
555
 * - singular_name - name for one object of this taxonomy. Default is Tag/Category
556
 * - search_items - Default is Search Tags/Search Categories
557
 * - popular_items - This string isn't used on hierarchical taxonomies. Default is Popular Tags
558
 * - all_items - Default is All Tags/All Categories
559
 * - parent_item - This string isn't used on non-hierarchical taxonomies. In hierarchical ones the default is Parent Category
560
 * - parent_item_colon - The same as `parent_item`, but with colon `:` in the end
561
 * - edit_item - Default is Edit Tag/Edit Category
562
 * - view_item - Default is View Tag/View Category
563
 * - update_item - Default is Update Tag/Update Category
564
 * - add_new_item - Default is Add New Tag/Add New Category
565
 * - new_item_name - Default is New Tag Name/New Category Name
566
 * - separate_items_with_commas - This string isn't used on hierarchical taxonomies. Default is "Separate tags with commas", used in the meta box.
567
 * - add_or_remove_items - This string isn't used on hierarchical taxonomies. Default is "Add or remove tags", used in the meta box when JavaScript is disabled.
568
 * - choose_from_most_used - This string isn't used on hierarchical taxonomies. Default is "Choose from the most used tags", used in the meta box.
569
 * - not_found - Default is "No tags found"/"No categories found", used in the meta box and taxonomy list table.
570
 * - no_terms - Default is "No tags"/"No categories", used in the posts and media list tables.
571
 * - items_list_navigation - String for the table pagination hidden heading.
572
 * - items_list - String for the table hidden heading.
573
 *
574
 * Above, the first default value is for non-hierarchical taxonomies (like tags) and the second one is for hierarchical taxonomies (like categories).
575
 *
576
 * @todo Better documentation for the labels array.
577
 *
578
 * @since 3.0.0
579
 * @since 4.3.0 Added the `no_terms` label.
580
 * @since 4.4.0 Added the `items_list_navigation` and `items_list` labels.
581
 *
582
 * @param object $tax Taxonomy object.
583
 * @return object object with all the labels as member variables.
584
 */
585
function get_taxonomy_labels( $tax ) {
586
	$tax->labels = (array) $tax->labels;
587
588
	if ( isset( $tax->helps ) && empty( $tax->labels['separate_items_with_commas'] ) )
589
		$tax->labels['separate_items_with_commas'] = $tax->helps;
590
591
	if ( isset( $tax->no_tagcloud ) && empty( $tax->labels['not_found'] ) )
592
		$tax->labels['not_found'] = $tax->no_tagcloud;
593
594
	$nohier_vs_hier_defaults = array(
595
		'name' => array( _x( 'Tags', 'taxonomy general name' ), _x( 'Categories', 'taxonomy general name' ) ),
596
		'singular_name' => array( _x( 'Tag', 'taxonomy singular name' ), _x( 'Category', 'taxonomy singular name' ) ),
597
		'search_items' => array( __( 'Search Tags' ), __( 'Search Categories' ) ),
598
		'popular_items' => array( __( 'Popular Tags' ), null ),
599
		'all_items' => array( __( 'All Tags' ), __( 'All Categories' ) ),
600
		'parent_item' => array( null, __( 'Parent Category' ) ),
601
		'parent_item_colon' => array( null, __( 'Parent Category:' ) ),
602
		'edit_item' => array( __( 'Edit Tag' ), __( 'Edit Category' ) ),
603
		'view_item' => array( __( 'View Tag' ), __( 'View Category' ) ),
604
		'update_item' => array( __( 'Update Tag' ), __( 'Update Category' ) ),
605
		'add_new_item' => array( __( 'Add New Tag' ), __( 'Add New Category' ) ),
606
		'new_item_name' => array( __( 'New Tag Name' ), __( 'New Category Name' ) ),
607
		'separate_items_with_commas' => array( __( 'Separate tags with commas' ), null ),
608
		'add_or_remove_items' => array( __( 'Add or remove tags' ), null ),
609
		'choose_from_most_used' => array( __( 'Choose from the most used tags' ), null ),
610
		'not_found' => array( __( 'No tags found.' ), __( 'No categories found.' ) ),
611
		'no_terms' => array( __( 'No tags' ), __( 'No categories' ) ),
612
		'items_list_navigation' => array( __( 'Tags list navigation' ), __( 'Categories list navigation' ) ),
613
		'items_list' => array( __( 'Tags list' ), __( 'Categories list' ) ),
614
	);
615
	$nohier_vs_hier_defaults['menu_name'] = $nohier_vs_hier_defaults['name'];
616
617
	$labels = _get_custom_object_labels( $tax, $nohier_vs_hier_defaults );
618
619
	$taxonomy = $tax->name;
620
621
	$default_labels = clone $labels;
622
623
	/**
624
	 * Filters the labels of a specific taxonomy.
625
	 *
626
	 * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
627
	 *
628
	 * @since 4.4.0
629
	 *
630
	 * @see get_taxonomy_labels() for the full list of taxonomy labels.
631
	 *
632
	 * @param object $labels Object with labels for the taxonomy as member variables.
633
	 */
634
	$labels = apply_filters( "taxonomy_labels_{$taxonomy}", $labels );
635
636
	// Ensure that the filtered labels contain all required default values.
637
	$labels = (object) array_merge( (array) $default_labels, (array) $labels );
638
639
	return $labels;
640
}
641
642
/**
643
 * Add an already registered taxonomy to an object type.
644
 *
645
 * @since 3.0.0
646
 *
647
 * @global array $wp_taxonomies The registered taxonomies.
648
 *
649
 * @param string $taxonomy    Name of taxonomy object.
650
 * @param string $object_type Name of the object type.
651
 * @return bool True if successful, false if not.
652
 */
653
function register_taxonomy_for_object_type( $taxonomy, $object_type) {
654
	global $wp_taxonomies;
655
656
	if ( !isset($wp_taxonomies[$taxonomy]) )
657
		return false;
658
659
	if ( ! get_post_type_object($object_type) )
660
		return false;
661
662
	if ( ! in_array( $object_type, $wp_taxonomies[$taxonomy]->object_type ) )
663
		$wp_taxonomies[$taxonomy]->object_type[] = $object_type;
664
665
	// Filter out empties.
666
	$wp_taxonomies[ $taxonomy ]->object_type = array_filter( $wp_taxonomies[ $taxonomy ]->object_type );
667
668
	return true;
669
}
670
671
/**
672
 * Remove an already registered taxonomy from an object type.
673
 *
674
 * @since 3.7.0
675
 *
676
 * @global array $wp_taxonomies The registered taxonomies.
677
 *
678
 * @param string $taxonomy    Name of taxonomy object.
679
 * @param string $object_type Name of the object type.
680
 * @return bool True if successful, false if not.
681
 */
682
function unregister_taxonomy_for_object_type( $taxonomy, $object_type ) {
683
	global $wp_taxonomies;
684
685
	if ( ! isset( $wp_taxonomies[ $taxonomy ] ) )
686
		return false;
687
688
	if ( ! get_post_type_object( $object_type ) )
689
		return false;
690
691
	$key = array_search( $object_type, $wp_taxonomies[ $taxonomy ]->object_type, true );
692
	if ( false === $key )
693
		return false;
694
695
	unset( $wp_taxonomies[ $taxonomy ]->object_type[ $key ] );
696
	return true;
697
}
698
699
//
700
// Term API
701
//
702
703
/**
704
 * Retrieve object_ids of valid taxonomy and term.
705
 *
706
 * The strings of $taxonomies must exist before this function will continue. On
707
 * failure of finding a valid taxonomy, it will return an WP_Error class, kind
708
 * of like Exceptions in PHP 5, except you can't catch them. Even so, you can
709
 * still test for the WP_Error class and get the error message.
710
 *
711
 * The $terms aren't checked the same as $taxonomies, but still need to exist
712
 * for $object_ids to be returned.
713
 *
714
 * It is possible to change the order that object_ids is returned by either
715
 * using PHP sort family functions or using the database by using $args with
716
 * either ASC or DESC array. The value should be in the key named 'order'.
717
 *
718
 * @since 2.3.0
719
 *
720
 * @global wpdb $wpdb WordPress database abstraction object.
721
 *
722
 * @param int|array    $term_ids   Term id or array of term ids of terms that will be used.
723
 * @param string|array $taxonomies String of taxonomy name or Array of string values of taxonomy names.
724
 * @param array|string $args       Change the order of the object_ids, either ASC or DESC.
725
 * @return WP_Error|array If the taxonomy does not exist, then WP_Error will be returned. On success.
726
 *	the array can be empty meaning that there are no $object_ids found or it will return the $object_ids found.
727
 */
728
function get_objects_in_term( $term_ids, $taxonomies, $args = array() ) {
729
	global $wpdb;
730
731
	if ( ! is_array( $term_ids ) ) {
732
		$term_ids = array( $term_ids );
733
	}
734
	if ( ! is_array( $taxonomies ) ) {
735
		$taxonomies = array( $taxonomies );
736
	}
737 View Code Duplication
	foreach ( (array) $taxonomies as $taxonomy ) {
738
		if ( ! taxonomy_exists( $taxonomy ) ) {
739
			return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
740
		}
741
	}
742
743
	$defaults = array( 'order' => 'ASC' );
744
	$args = wp_parse_args( $args, $defaults );
745
746
	$order = ( 'desc' == strtolower( $args['order'] ) ) ? 'DESC' : 'ASC';
747
748
	$term_ids = array_map('intval', $term_ids );
749
750
	$taxonomies = "'" . implode( "', '", array_map( 'esc_sql', $taxonomies ) ) . "'";
751
	$term_ids = "'" . implode( "', '", $term_ids ) . "'";
752
753
	$object_ids = $wpdb->get_col("SELECT tr.object_id FROM $wpdb->term_relationships AS tr INNER JOIN $wpdb->term_taxonomy AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id WHERE tt.taxonomy IN ($taxonomies) AND tt.term_id IN ($term_ids) ORDER BY tr.object_id $order");
754
755
	if ( ! $object_ids ){
756
		return array();
757
	}
758
	return $object_ids;
759
}
760
761
/**
762
 * Given a taxonomy query, generates SQL to be appended to a main query.
763
 *
764
 * @since 3.1.0
765
 *
766
 * @see WP_Tax_Query
767
 *
768
 * @param array  $tax_query         A compact tax query
769
 * @param string $primary_table
770
 * @param string $primary_id_column
771
 * @return array
772
 */
773
function get_tax_sql( $tax_query, $primary_table, $primary_id_column ) {
774
	$tax_query_obj = new WP_Tax_Query( $tax_query );
775
	return $tax_query_obj->get_sql( $primary_table, $primary_id_column );
776
}
777
778
/**
779
 * Get all Term data from database by Term ID.
780
 *
781
 * The usage of the get_term function is to apply filters to a term object. It
782
 * is possible to get a term object from the database before applying the
783
 * filters.
784
 *
785
 * $term ID must be part of $taxonomy, to get from the database. Failure, might
786
 * be able to be captured by the hooks. Failure would be the same value as $wpdb
787
 * returns for the get_row method.
788
 *
789
 * There are two hooks, one is specifically for each term, named 'get_term', and
790
 * the second is for the taxonomy name, 'term_$taxonomy'. Both hooks gets the
791
 * term object, and the taxonomy name as parameters. Both hooks are expected to
792
 * return a Term object.
793
 *
794
 * {@see 'get_term'} hook - Takes two parameters the term Object and the taxonomy name.
795
 * Must return term object. Used in get_term() as a catch-all filter for every
796
 * $term.
797
 *
798
 * {@see 'get_$taxonomy'} hook - Takes two parameters the term Object and the taxonomy
799
 * name. Must return term object. $taxonomy will be the taxonomy name, so for
800
 * example, if 'category', it would be 'get_category' as the filter name. Useful
801
 * for custom taxonomies or plugging into default taxonomies.
802
 *
803
 * @todo Better formatting for DocBlock
804
 *
805
 * @since 2.3.0
806
 * @since 4.4.0 Converted to return a WP_Term object if `$output` is `OBJECT`.
807
 *              The `$taxonomy` parameter was made optional.
808
 *
809
 * @global wpdb $wpdb WordPress database abstraction object.
810
 * @see sanitize_term_field() The $context param lists the available values for get_term_by() $filter param.
811
 *
812
 * @param int|WP_Term|object $term If integer, term data will be fetched from the database, or from the cache if
813
 *                                 available. If stdClass object (as in the results of a database query), will apply
814
 *                                 filters and return a `WP_Term` object corresponding to the `$term` data. If `WP_Term`,
815
 *                                 will return `$term`.
816
 * @param string     $taxonomy Optional. Taxonomy name that $term is part of.
817
 * @param string     $output   Constant OBJECT, ARRAY_A, or ARRAY_N
818
 * @param string     $filter   Optional, default is raw or no WordPress defined filter will applied.
819
 * @return array|WP_Term|WP_Error|null Object of the type specified by `$output` on success. When `$output` is 'OBJECT',
820
 *                                     a WP_Term instance is returned. If taxonomy does not exist, a WP_Error is
821
 *                                     returned. Returns null for miscellaneous failure.
822
 */
823
function get_term( $term, $taxonomy = '', $output = OBJECT, $filter = 'raw' ) {
824
	if ( empty( $term ) ) {
825
		return new WP_Error( 'invalid_term', __( 'Empty Term' ) );
826
	}
827
828
	if ( $taxonomy && ! taxonomy_exists( $taxonomy ) ) {
829
		return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
830
	}
831
832 View Code Duplication
	if ( $term instanceof WP_Term ) {
833
		$_term = $term;
834
	} elseif ( is_object( $term ) ) {
835
		if ( empty( $term->filter ) || 'raw' === $term->filter ) {
836
			$_term = sanitize_term( $term, $taxonomy, 'raw' );
837
			$_term = new WP_Term( $_term );
0 ignored issues
show
It seems like $_term can also be of type array<?,*,{"filter":"string"}>; however, WP_Term::__construct() does only seem to accept object, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
838
		} else {
839
			$_term = WP_Term::get_instance( $term->term_id );
840
		}
841
	} else {
842
		$_term = WP_Term::get_instance( $term, $taxonomy );
843
	}
844
845
	if ( is_wp_error( $_term ) ) {
846
		return $_term;
847
	} elseif ( ! $_term ) {
848
		return null;
849
	}
850
851
	/**
852
	 * Filters a term.
853
	 *
854
	 * @since 2.3.0
855
	 * @since 4.4.0 `$_term` can now also be a WP_Term object.
856
	 *
857
	 * @param int|WP_Term $_term    Term object or ID.
858
	 * @param string      $taxonomy The taxonomy slug.
859
	 */
860
	$_term = apply_filters( 'get_term', $_term, $taxonomy );
861
862
	/**
863
	 * Filters a taxonomy.
864
	 *
865
	 * The dynamic portion of the filter name, `$taxonomy`, refers
866
	 * to the taxonomy slug.
867
	 *
868
	 * @since 2.3.0
869
	 * @since 4.4.0 `$_term` can now also be a WP_Term object.
870
	 *
871
	 * @param int|WP_Term $_term    Term object or ID.
872
	 * @param string      $taxonomy The taxonomy slug.
873
	 */
874
	$_term = apply_filters( "get_{$taxonomy}", $_term, $taxonomy );
875
876
	// Bail if a filter callback has changed the type of the `$_term` object.
877
	if ( ! ( $_term instanceof WP_Term ) ) {
878
		return $_term;
879
	}
880
881
	// Sanitize term, according to the specified filter.
882
	$_term->filter( $filter );
883
884 View Code Duplication
	if ( $output == ARRAY_A ) {
885
		return $_term->to_array();
886
	} elseif ( $output == ARRAY_N ) {
887
		return array_values( $_term->to_array() );
888
	}
889
890
	return $_term;
891
}
892
893
/**
894
 * Get all Term data from database by Term field and data.
895
 *
896
 * Warning: $value is not escaped for 'name' $field. You must do it yourself, if
897
 * required.
898
 *
899
 * The default $field is 'id', therefore it is possible to also use null for
900
 * field, but not recommended that you do so.
901
 *
902
 * If $value does not exist, the return value will be false. If $taxonomy exists
903
 * and $field and $value combinations exist, the Term will be returned.
904
 *
905
 * This function will always return the first term that matches the `$field`-
906
 * `$value`-`$taxonomy` combination specified in the parameters. If your query
907
 * is likely to match more than one term (as is likely to be the case when
908
 * `$field` is 'name', for example), consider using get_terms() instead; that
909
 * way, you will get all matching terms, and can provide your own logic for
910
 * deciding which one was intended.
911
 *
912
 * @todo Better formatting for DocBlock.
913
 *
914
 * @since 2.3.0
915
 * @since 4.4.0 `$taxonomy` is optional if `$field` is 'term_taxonomy_id'. Converted to return
916
 *              a WP_Term object if `$output` is `OBJECT`.
917
 *
918
 * @global wpdb $wpdb WordPress database abstraction object.
919
 * @see sanitize_term_field() The $context param lists the available values for get_term_by() $filter param.
920
 *
921
 * @param string     $field    Either 'slug', 'name', 'id' (term_id), or 'term_taxonomy_id'
922
 * @param string|int $value    Search for this term value
923
 * @param string     $taxonomy Taxonomy name. Optional, if `$field` is 'term_taxonomy_id'.
924
 * @param string     $output   Constant OBJECT, ARRAY_A, or ARRAY_N
925
 * @param string     $filter   Optional, default is raw or no WordPress defined filter will applied.
926
 * @return WP_Term|bool WP_Term instance on success. Will return false if `$taxonomy` does not exist
927
 *                      or `$term` was not found.
928
 */
929
function get_term_by( $field, $value, $taxonomy = '', $output = OBJECT, $filter = 'raw' ) {
930
	global $wpdb;
931
932
	// 'term_taxonomy_id' lookups don't require taxonomy checks.
933
	if ( 'term_taxonomy_id' !== $field && ! taxonomy_exists( $taxonomy ) ) {
934
		return false;
935
	}
936
937
	$tax_clause = $wpdb->prepare( "AND tt.taxonomy = %s", $taxonomy );
938
939
	if ( 'slug' == $field ) {
940
		$_field = 't.slug';
941
		$value = sanitize_title($value);
942
		if ( empty($value) )
943
			return false;
944
	} elseif ( 'name' == $field ) {
945
		// Assume already escaped
946
		$value = wp_unslash($value);
947
		$_field = 't.name';
948
	} elseif ( 'term_taxonomy_id' == $field ) {
949
		$value = (int) $value;
950
		$_field = 'tt.term_taxonomy_id';
951
952
		// No `taxonomy` clause when searching by 'term_taxonomy_id'.
953
		$tax_clause = '';
954
	} else {
955
		$term = get_term( (int) $value, $taxonomy, $output, $filter );
956
		if ( is_wp_error( $term ) || is_null( $term ) ) {
957
			$term = false;
958
		}
959
		return $term;
960
	}
961
962
	$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 $_field = %s", $value ) . " $tax_clause LIMIT 1" );
963
	if ( ! $term )
964
		return false;
965
966
	// In the case of 'term_taxonomy_id', override the provided `$taxonomy` with whatever we find in the db.
967
	if ( 'term_taxonomy_id' === $field ) {
968
		$taxonomy = $term->taxonomy;
969
	}
970
971
	wp_cache_add( $term->term_id, $term, 'terms' );
972
973
	return get_term( $term, $taxonomy, $output, $filter );
974
}
975
976
/**
977
 * Merge all term children into a single array of their IDs.
978
 *
979
 * This recursive function will merge all of the children of $term into the same
980
 * array of term IDs. Only useful for taxonomies which are hierarchical.
981
 *
982
 * Will return an empty array if $term does not exist in $taxonomy.
983
 *
984
 * @since 2.3.0
985
 *
986
 * @param string $term_id  ID of Term to get children.
987
 * @param string $taxonomy Taxonomy Name.
988
 * @return array|WP_Error List of Term IDs. WP_Error returned if `$taxonomy` does not exist.
989
 */
990
function get_term_children( $term_id, $taxonomy ) {
991
	if ( ! taxonomy_exists( $taxonomy ) ) {
992
		return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
993
	}
994
995
	$term_id = intval( $term_id );
996
997
	$terms = _get_term_hierarchy($taxonomy);
998
999
	if ( ! isset($terms[$term_id]) )
1000
		return array();
1001
1002
	$children = $terms[$term_id];
1003
1004
	foreach ( (array) $terms[$term_id] as $child ) {
1005
		if ( $term_id == $child ) {
1006
			continue;
1007
		}
1008
1009
		if ( isset($terms[$child]) )
1010
			$children = array_merge($children, get_term_children($child, $taxonomy));
1011
	}
1012
1013
	return $children;
1014
}
1015
1016
/**
1017
 * Get sanitized Term field.
1018
 *
1019
 * The function is for contextual reasons and for simplicity of usage.
1020
 *
1021
 * @since 2.3.0
1022
 * @since 4.4.0 The `$taxonomy` parameter was made optional. `$term` can also now accept a WP_Term object.
1023
 *
1024
 * @see sanitize_term_field()
1025
 *
1026
 * @param string      $field    Term field to fetch.
1027
 * @param int|WP_Term $term     Term ID or object.
1028
 * @param string      $taxonomy Optional. Taxonomy Name. Default empty.
1029
 * @param string      $context  Optional, default is display. Look at sanitize_term_field() for available options.
1030
 * @return string|int|null|WP_Error Will return an empty string if $term is not an object or if $field is not set in $term.
1031
 */
1032 View Code Duplication
function get_term_field( $field, $term, $taxonomy = '', $context = 'display' ) {
0 ignored issues
show
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1033
	$term = get_term( $term, $taxonomy );
1034
	if ( is_wp_error($term) )
1035
		return $term;
1036
1037
	if ( !is_object($term) )
1038
		return '';
1039
1040
	if ( !isset($term->$field) )
1041
		return '';
1042
1043
	return sanitize_term_field( $field, $term->$field, $term->term_id, $term->taxonomy, $context );
1044
}
1045
1046
/**
1047
 * Sanitizes Term for editing.
1048
 *
1049
 * Return value is sanitize_term() and usage is for sanitizing the term for
1050
 * editing. Function is for contextual and simplicity.
1051
 *
1052
 * @since 2.3.0
1053
 *
1054
 * @param int|object $id       Term ID or object.
1055
 * @param string     $taxonomy Taxonomy name.
1056
 * @return string|int|null|WP_Error Will return empty string if $term is not an object.
1057
 */
1058
function get_term_to_edit( $id, $taxonomy ) {
1059
	$term = get_term( $id, $taxonomy );
1060
1061
	if ( is_wp_error($term) )
1062
		return $term;
1063
1064
	if ( !is_object($term) )
1065
		return '';
1066
1067
	return sanitize_term($term, $taxonomy, 'edit');
1068
}
1069
1070
/**
1071
 * Retrieve the terms in a given taxonomy or list of taxonomies.
1072
 *
1073
 * You can fully inject any customizations to the query before it is sent, as
1074
 * well as control the output with a filter.
1075
 *
1076
 * The {@see 'get_terms'} filter will be called when the cache has the term and will
1077
 * pass the found term along with the array of $taxonomies and array of $args.
1078
 * This filter is also called before the array of terms is passed and will pass
1079
 * the array of terms, along with the $taxonomies and $args.
1080
 *
1081
 * The {@see 'list_terms_exclusions'} filter passes the compiled exclusions along with
1082
 * the $args.
1083
 *
1084
 * The {@see 'get_terms_orderby'} filter passes the `ORDER BY` clause for the query
1085
 * along with the $args array.
1086
 *
1087
 * Prior to 4.5.0, the first parameter of `get_terms()` was a taxonomy or list of taxonomies:
1088
 *
1089
 *     $terms = get_terms( 'post_tag', array(
1090
 *         'hide_empty' => false,
1091
 *     ) );
1092
 *
1093
 * Since 4.5.0, taxonomies should be passed via the 'taxonomy' argument in the `$args` array:
1094
 *
1095
 *     $terms = get_terms( array(
1096
 *         'taxonomy' => 'post_tag',
1097
 *         'hide_empty' => false,
1098
 *     ) );
1099
 *
1100
 * @since 2.3.0
1101
 * @since 4.2.0 Introduced 'name' and 'childless' parameters.
1102
 * @since 4.4.0 Introduced the ability to pass 'term_id' as an alias of 'id' for the `orderby` parameter.
1103
 *              Introduced the 'meta_query' and 'update_term_meta_cache' parameters. Converted to return
1104
 *              a list of WP_Term objects.
1105
 * @since 4.5.0 Changed the function signature so that the `$args` array can be provided as the first parameter.
1106
 *              Introduced 'meta_key' and 'meta_value' parameters. Introduced the ability to order results by metadata.
1107
 *
1108
 * @internal The `$deprecated` parameter is parsed for backward compatibility only.
1109
 *
1110
 * @global wpdb  $wpdb WordPress database abstraction object.
1111
 * @global array $wp_filter
1112
 *
1113
 * @param array|string $args {
1114
 *     Optional. Array or string of arguments to get terms.
1115
 *
1116
 *     @type string|array $taxonomy               Taxonomy name, or array of taxonomies, to which results should
1117
 *                                                be limited.
1118
 *     @type string       $orderby                Field(s) to order terms by. Accepts term fields ('name', 'slug',
1119
 *                                                'term_group', 'term_id', 'id', 'description'), 'count' for term
1120
 *                                                taxonomy count, 'include' to match the 'order' of the $include param,
1121
 *                                                'meta_value', 'meta_value_num', the value of `$meta_key`, the array
1122
 *                                                keys of `$meta_query`, or 'none' to omit the ORDER BY clause.
1123
 *                                                Defaults to 'name'.
1124
 *     @type string       $order                  Whether to order terms in ascending or descending order.
1125
 *                                                Accepts 'ASC' (ascending) or 'DESC' (descending).
1126
 *                                                Default 'ASC'.
1127
 *     @type bool|int     $hide_empty             Whether to hide terms not assigned to any posts. Accepts
1128
 *                                                1|true or 0|false. Default 1|true.
1129
 *     @type array|string $include                Array or comma/space-separated string of term ids to include.
1130
 *                                                Default empty array.
1131
 *     @type array|string $exclude                Array or comma/space-separated string of term ids to exclude.
1132
 *                                                If $include is non-empty, $exclude is ignored.
1133
 *                                                Default empty array.
1134
 *     @type array|string $exclude_tree           Array or comma/space-separated string of term ids to exclude
1135
 *                                                along with all of their descendant terms. If $include is
1136
 *                                                non-empty, $exclude_tree is ignored. Default empty array.
1137
 *     @type int|string   $number                 Maximum number of terms to return. Accepts ''|0 (all) or any
1138
 *                                                positive number. Default ''|0 (all).
1139
 *     @type int          $offset                 The number by which to offset the terms query. Default empty.
1140
 *     @type string       $fields                 Term fields to query for. Accepts 'all' (returns an array of complete
1141
 *                                                term objects), 'ids' (returns an array of ids), 'id=>parent' (returns
1142
 *                                                an associative array with ids as keys, parent term IDs as values),
1143
 *                                                'names' (returns an array of term names), 'count' (returns the number
1144
 *                                                of matching terms), 'id=>name' (returns an associative array with ids
1145
 *                                                as keys, term names as values), or 'id=>slug' (returns an associative
1146
 *                                                array with ids as keys, term slugs as values). Default 'all'.
1147
 *     @type string|array $name                   Optional. Name or array of names to return term(s) for. Default empty.
1148
 *     @type string|array $slug                   Optional. Slug or array of slugs to return term(s) for. Default empty.
1149
 *     @type bool         $hierarchical           Whether to include terms that have non-empty descendants (even
1150
 *                                                if $hide_empty is set to true). Default true.
1151
 *     @type string       $search                 Search criteria to match terms. Will be SQL-formatted with
1152
 *                                                wildcards before and after. Default empty.
1153
 *     @type string       $name__like             Retrieve terms with criteria by which a term is LIKE $name__like.
1154
 *                                                Default empty.
1155
 *     @type string       $description__like      Retrieve terms where the description is LIKE $description__like.
1156
 *                                                Default empty.
1157
 *     @type bool         $pad_counts             Whether to pad the quantity of a term's children in the quantity
1158
 *                                                of each term's "count" object variable. Default false.
1159
 *     @type string       $get                    Whether to return terms regardless of ancestry or whether the terms
1160
 *                                                are empty. Accepts 'all' or empty (disabled). Default empty.
1161
 *     @type int          $child_of               Term ID to retrieve child terms of. If multiple taxonomies
1162
 *                                                are passed, $child_of is ignored. Default 0.
1163
 *     @type int|string   $parent                 Parent term ID to retrieve direct-child terms of. Default empty.
1164
 *     @type bool         $childless              True to limit results to terms that have no children. This parameter
1165
 *                                                has no effect on non-hierarchical taxonomies. Default false.
1166
 *     @type string       $cache_domain           Unique cache key to be produced when this query is stored in an
1167
 *                                                object cache. Default is 'core'.
1168
 *     @type bool         $update_term_meta_cache Whether to prime meta caches for matched terms. Default true.
1169
 *     @type array        $meta_query             Meta query clauses to limit retrieved terms by.
1170
 *                                                See `WP_Meta_Query`. Default empty.
1171
 *     @type string       $meta_key               Limit terms to those matching a specific metadata key. Can be used in
1172
 *                                                conjunction with `$meta_value`.
1173
 *     @type string       $meta_value             Limit terms to those matching a specific metadata value. Usually used
1174
 *                                                in conjunction with `$meta_key`.
1175
 * }
1176
 * @param array $deprecated Argument array, when using the legacy function parameter format. If present, this
1177
 *                          parameter will be interpreted as `$args`, and the first function parameter will
1178
 *                          be parsed as a taxonomy or array of taxonomies.
1179
 * @return array|int|WP_Error List of WP_Term instances and their children. Will return WP_Error, if any of $taxonomies
1180
 *                            do not exist.
1181
 */
1182
function get_terms( $args = array(), $deprecated = '' ) {
1183
	global $wpdb;
1184
1185
	$term_query = new WP_Term_Query();
1186
1187
	/*
1188
	 * Legacy argument format ($taxonomy, $args) takes precedence.
1189
	 *
1190
	 * We detect legacy argument format by checking if
1191
	 * (a) a second non-empty parameter is passed, or
1192
	 * (b) the first parameter shares no keys with the default array (ie, it's a list of taxonomies)
1193
	 */
1194
	$_args = wp_parse_args( $args );
1195
	$key_intersect  = array_intersect_key( $term_query->query_var_defaults, (array) $_args );
1196
	$do_legacy_args = $deprecated || empty( $key_intersect );
1197
1198
	if ( $do_legacy_args ) {
1199
		$taxonomies = (array) $args;
1200
		$args = wp_parse_args( $deprecated );
1201
		$args['taxonomy'] = $taxonomies;
1202
	} else {
1203
		$args = wp_parse_args( $args );
1204
		if ( isset( $args['taxonomy'] ) && null !== $args['taxonomy'] ) {
1205
			$args['taxonomy'] = (array) $args['taxonomy'];
1206
		}
1207
	}
1208
1209
	if ( ! empty( $args['taxonomy'] ) ) {
1210 View Code Duplication
		foreach ( $args['taxonomy'] as $taxonomy ) {
1211
			if ( ! taxonomy_exists( $taxonomy ) ) {
1212
				return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
1213
			}
1214
		}
1215
	}
1216
1217
	$terms = $term_query->query( $args );
1218
1219
	// Count queries are not filtered, for legacy reasons.
1220
	if ( ! is_array( $terms ) ) {
1221
		return $terms;
1222
	}
1223
1224
	/**
1225
	 * Filters the found terms.
1226
	 *
1227
	 * @since 2.3.0
1228
	 * @since 4.6.0 Added the `$term_query` parameter.
1229
	 *
1230
	 * @param array         $terms      Array of found terms.
1231
	 * @param array         $taxonomies An array of taxonomies.
1232
	 * @param array         $args       An array of get_terms() arguments.
1233
	 * @param WP_Term_Query $term_query The WP_Term_Query object.
1234
	 */
1235
	return apply_filters( 'get_terms', $terms, $term_query->query_vars['taxonomy'], $term_query->query_vars, $term_query );
1236
}
1237
1238
/**
1239
 * Adds metadata to a term.
1240
 *
1241
 * @since 4.4.0
1242
 *
1243
 * @param int    $term_id    Term ID.
1244
 * @param string $meta_key   Metadata name.
1245
 * @param mixed  $meta_value Metadata value.
1246
 * @param bool   $unique     Optional. Whether to bail if an entry with the same key is found for the term.
1247
 *                           Default false.
1248
 * @return int|WP_Error|bool Meta ID on success. WP_Error when term_id is ambiguous between taxonomies.
1249
 *                           False on failure.
1250
 */
1251 View Code Duplication
function add_term_meta( $term_id, $meta_key, $meta_value, $unique = false ) {
0 ignored issues
show
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1252
	// Bail if term meta table is not installed.
1253
	if ( get_option( 'db_version' ) < 34370 ) {
1254
		return false;
1255
	}
1256
1257
	if ( wp_term_is_shared( $term_id ) ) {
1258
		return new WP_Error( 'ambiguous_term_id', __( 'Term meta cannot be added to terms that are shared between taxonomies.'), $term_id );
1259
	}
1260
1261
	$added = add_metadata( 'term', $term_id, $meta_key, $meta_value, $unique );
1262
1263
	// Bust term query cache.
1264
	if ( $added ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $added of type integer|false is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1265
		wp_cache_set( 'last_changed', microtime(), 'terms' );
1266
	}
1267
1268
	return $added;
1269
}
1270
1271
/**
1272
 * Removes metadata matching criteria from a term.
1273
 *
1274
 * @since 4.4.0
1275
 *
1276
 * @param int    $term_id    Term ID.
1277
 * @param string $meta_key   Metadata name.
1278
 * @param mixed  $meta_value Optional. Metadata value. If provided, rows will only be removed that match the value.
1279
 * @return bool True on success, false on failure.
1280
 */
1281
function delete_term_meta( $term_id, $meta_key, $meta_value = '' ) {
1282
	// Bail if term meta table is not installed.
1283
	if ( get_option( 'db_version' ) < 34370 ) {
1284
		return false;
1285
	}
1286
1287
	$deleted = delete_metadata( 'term', $term_id, $meta_key, $meta_value );
1288
1289
	// Bust term query cache.
1290
	if ( $deleted ) {
1291
		wp_cache_set( 'last_changed', microtime(), 'terms' );
1292
	}
1293
1294
	return $deleted;
1295
}
1296
1297
/**
1298
 * Retrieves metadata for a term.
1299
 *
1300
 * @since 4.4.0
1301
 *
1302
 * @param int    $term_id Term ID.
1303
 * @param string $key     Optional. The meta key to retrieve. If no key is provided, fetches all metadata for the term.
1304
 * @param bool   $single  Whether to return a single value. If false, an array of all values matching the
1305
 *                        `$term_id`/`$key` pair will be returned. Default: false.
1306
 * @return mixed If `$single` is false, an array of metadata values. If `$single` is true, a single metadata value.
1307
 */
1308
function get_term_meta( $term_id, $key = '', $single = false ) {
1309
	// Bail if term meta table is not installed.
1310
	if ( get_option( 'db_version' ) < 34370 ) {
1311
		return false;
1312
	}
1313
1314
	return get_metadata( 'term', $term_id, $key, $single );
1315
}
1316
1317
/**
1318
 * Updates term metadata.
1319
 *
1320
 * Use the `$prev_value` parameter to differentiate between meta fields with the same key and term ID.
1321
 *
1322
 * If the meta field for the term does not exist, it will be added.
1323
 *
1324
 * @since 4.4.0
1325
 *
1326
 * @param int    $term_id    Term ID.
1327
 * @param string $meta_key   Metadata key.
1328
 * @param mixed  $meta_value Metadata value.
1329
 * @param mixed  $prev_value Optional. Previous value to check before removing.
1330
 * @return int|WP_Error|bool Meta ID if the key didn't previously exist. True on successful update.
1331
 *                           WP_Error when term_id is ambiguous between taxonomies. False on failure.
1332
 */
1333 View Code Duplication
function update_term_meta( $term_id, $meta_key, $meta_value, $prev_value = '' ) {
0 ignored issues
show
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1334
	// Bail if term meta table is not installed.
1335
	if ( get_option( 'db_version' ) < 34370 ) {
1336
		return false;
1337
	}
1338
1339
	if ( wp_term_is_shared( $term_id ) ) {
1340
		return new WP_Error( 'ambiguous_term_id', __( 'Term meta cannot be added to terms that are shared between taxonomies.'), $term_id );
1341
	}
1342
1343
	$updated = update_metadata( 'term', $term_id, $meta_key, $meta_value, $prev_value );
1344
1345
	// Bust term query cache.
1346
	if ( $updated ) {
1347
		wp_cache_set( 'last_changed', microtime(), 'terms' );
1348
	}
1349
1350
	return $updated;
1351
}
1352
1353
/**
1354
 * Updates metadata cache for list of term IDs.
1355
 *
1356
 * Performs SQL query to retrieve all metadata for the terms matching `$term_ids` and stores them in the cache.
1357
 * Subsequent calls to `get_term_meta()` will not need to query the database.
1358
 *
1359
 * @since 4.4.0
1360
 *
1361
 * @param array $term_ids List of term IDs.
1362
 * @return array|false Returns false if there is nothing to update. Returns an array of metadata on success.
1363
 */
1364
function update_termmeta_cache( $term_ids ) {
1365
	// Bail if term meta table is not installed.
1366
	if ( get_option( 'db_version' ) < 34370 ) {
1367
		return;
1368
	}
1369
1370
	return update_meta_cache( 'term', $term_ids );
1371
}
1372
1373
/**
1374
 * Check if Term exists.
1375
 *
1376
 * Formerly is_term(), introduced in 2.3.0.
1377
 *
1378
 * @since 3.0.0
1379
 *
1380
 * @global wpdb $wpdb WordPress database abstraction object.
1381
 *
1382
 * @param int|string $term     The term to check
1383
 * @param string     $taxonomy The taxonomy name to use
1384
 * @param int        $parent   Optional. ID of parent term under which to confine the exists search.
1385
 * @return mixed Returns null if the term does not exist. Returns the term ID
1386
 *               if no taxonomy is specified and the term ID exists. Returns
1387
 *               an array of the term ID and the term taxonomy ID the taxonomy
1388
 *               is specified and the pairing exists.
1389
 */
1390
function term_exists( $term, $taxonomy = '', $parent = null ) {
1391
	global $wpdb;
1392
1393
	$select = "SELECT term_id FROM $wpdb->terms as t WHERE ";
1394
	$tax_select = "SELECT tt.term_id, tt.term_taxonomy_id FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy as tt ON tt.term_id = t.term_id WHERE ";
1395
1396
	if ( is_int($term) ) {
1397
		if ( 0 == $term )
1398
			return 0;
1399
		$where = 't.term_id = %d';
1400
		if ( !empty($taxonomy) )
1401
			return $wpdb->get_row( $wpdb->prepare( $tax_select . $where . " AND tt.taxonomy = %s", $term, $taxonomy ), ARRAY_A );
1402
		else
1403
			return $wpdb->get_var( $wpdb->prepare( $select . $where, $term ) );
1404
	}
1405
1406
	$term = trim( wp_unslash( $term ) );
1407
	$slug = sanitize_title( $term );
1408
1409
	$where = 't.slug = %s';
1410
	$else_where = 't.name = %s';
1411
	$where_fields = array($slug);
1412
	$else_where_fields = array($term);
1413
	$orderby = 'ORDER BY t.term_id ASC';
1414
	$limit = 'LIMIT 1';
1415
	if ( !empty($taxonomy) ) {
1416
		if ( is_numeric( $parent ) ) {
1417
			$parent = (int) $parent;
1418
			$where_fields[] = $parent;
1419
			$else_where_fields[] = $parent;
1420
			$where .= ' AND tt.parent = %d';
1421
			$else_where .= ' AND tt.parent = %d';
1422
		}
1423
1424
		$where_fields[] = $taxonomy;
1425
		$else_where_fields[] = $taxonomy;
1426
1427
		if ( $result = $wpdb->get_row( $wpdb->prepare("SELECT tt.term_id, tt.term_taxonomy_id FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy as tt ON tt.term_id = t.term_id WHERE $where AND tt.taxonomy = %s $orderby $limit", $where_fields), ARRAY_A) )
1428
			return $result;
1429
1430
		return $wpdb->get_row( $wpdb->prepare("SELECT tt.term_id, tt.term_taxonomy_id FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy as tt ON tt.term_id = t.term_id WHERE $else_where AND tt.taxonomy = %s $orderby $limit", $else_where_fields), ARRAY_A);
1431
	}
1432
1433
	if ( $result = $wpdb->get_var( $wpdb->prepare("SELECT term_id FROM $wpdb->terms as t WHERE $where $orderby $limit", $where_fields) ) )
1434
		return $result;
1435
1436
	return $wpdb->get_var( $wpdb->prepare("SELECT term_id FROM $wpdb->terms as t WHERE $else_where $orderby $limit", $else_where_fields) );
1437
}
1438
1439
/**
1440
 * Check if a term is an ancestor of another term.
1441
 *
1442
 * You can use either an id or the term object for both parameters.
1443
 *
1444
 * @since 3.4.0
1445
 *
1446
 * @param int|object $term1    ID or object to check if this is the parent term.
1447
 * @param int|object $term2    The child term.
1448
 * @param string     $taxonomy Taxonomy name that $term1 and `$term2` belong to.
1449
 * @return bool Whether `$term2` is a child of `$term1`.
1450
 */
1451
function term_is_ancestor_of( $term1, $term2, $taxonomy ) {
1452
	if ( ! isset( $term1->term_id ) )
1453
		$term1 = get_term( $term1, $taxonomy );
1454
	if ( ! isset( $term2->parent ) )
1455
		$term2 = get_term( $term2, $taxonomy );
1456
1457
	if ( empty( $term1->term_id ) || empty( $term2->parent ) )
1458
		return false;
1459
	if ( $term2->parent == $term1->term_id )
1460
		return true;
1461
1462
	return term_is_ancestor_of( $term1, get_term( $term2->parent, $taxonomy ), $taxonomy );
0 ignored issues
show
It seems like $term1 defined by get_term($term1, $taxonomy) on line 1453 can also be of type array; however, term_is_ancestor_of() does only seem to accept integer|object, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
It seems like get_term($term2->parent, $taxonomy) targeting get_term() can also be of type array or null; however, term_is_ancestor_of() does only seem to accept integer|object, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1463
}
1464
1465
/**
1466
 * Sanitize Term all fields.
1467
 *
1468
 * Relies on sanitize_term_field() to sanitize the term. The difference is that
1469
 * this function will sanitize <strong>all</strong> fields. The context is based
1470
 * on sanitize_term_field().
1471
 *
1472
 * The $term is expected to be either an array or an object.
1473
 *
1474
 * @since 2.3.0
1475
 *
1476
 * @param array|object $term     The term to check.
1477
 * @param string       $taxonomy The taxonomy name to use.
1478
 * @param string       $context  Optional. Context in which to sanitize the term. Accepts 'edit', 'db',
1479
 *                               'display', 'attribute', or 'js'. Default 'display'.
1480
 * @return array|object Term with all fields sanitized.
1481
 */
1482
function sanitize_term($term, $taxonomy, $context = 'display') {
1483
	$fields = array( 'term_id', 'name', 'description', 'slug', 'count', 'parent', 'term_group', 'term_taxonomy_id', 'object_id' );
1484
1485
	$do_object = is_object( $term );
1486
1487
	$term_id = $do_object ? $term->term_id : (isset($term['term_id']) ? $term['term_id'] : 0);
1488
1489 View Code Duplication
	foreach ( (array) $fields as $field ) {
1490
		if ( $do_object ) {
1491
			if ( isset($term->$field) )
1492
				$term->$field = sanitize_term_field($field, $term->$field, $term_id, $taxonomy, $context);
1493
		} else {
1494
			if ( isset($term[$field]) )
1495
				$term[$field] = sanitize_term_field($field, $term[$field], $term_id, $taxonomy, $context);
1496
		}
1497
	}
1498
1499
	if ( $do_object )
1500
		$term->filter = $context;
1501
	else
1502
		$term['filter'] = $context;
1503
1504
	return $term;
1505
}
1506
1507
/**
1508
 * Cleanse the field value in the term based on the context.
1509
 *
1510
 * Passing a term field value through the function should be assumed to have
1511
 * cleansed the value for whatever context the term field is going to be used.
1512
 *
1513
 * If no context or an unsupported context is given, then default filters will
1514
 * be applied.
1515
 *
1516
 * There are enough filters for each context to support a custom filtering
1517
 * without creating your own filter function. Simply create a function that
1518
 * hooks into the filter you need.
1519
 *
1520
 * @since 2.3.0
1521
 *
1522
 * @param string $field    Term field to sanitize.
1523
 * @param string $value    Search for this term value.
1524
 * @param int    $term_id  Term ID.
1525
 * @param string $taxonomy Taxonomy Name.
1526
 * @param string $context  Context in which to sanitize the term field. Accepts 'edit', 'db', 'display',
1527
 *                         'attribute', or 'js'.
1528
 * @return mixed Sanitized field.
1529
 */
1530
function sanitize_term_field($field, $value, $term_id, $taxonomy, $context) {
1531
	$int_fields = array( 'parent', 'term_id', 'count', 'term_group', 'term_taxonomy_id', 'object_id' );
1532
	if ( in_array( $field, $int_fields ) ) {
1533
		$value = (int) $value;
1534
		if ( $value < 0 )
1535
			$value = 0;
1536
	}
1537
1538
	if ( 'raw' == $context )
1539
		return $value;
1540
1541
	if ( 'edit' == $context ) {
1542
1543
		/**
1544
		 * Filters a term field to edit before it is sanitized.
1545
		 *
1546
		 * The dynamic portion of the filter name, `$field`, refers to the term field.
1547
		 *
1548
		 * @since 2.3.0
1549
		 *
1550
		 * @param mixed $value     Value of the term field.
1551
		 * @param int   $term_id   Term ID.
1552
		 * @param string $taxonomy Taxonomy slug.
1553
		 */
1554
		$value = apply_filters( "edit_term_{$field}", $value, $term_id, $taxonomy );
1555
1556
		/**
1557
		 * Filters the taxonomy field to edit before it is sanitized.
1558
		 *
1559
		 * The dynamic portions of the filter name, `$taxonomy` and `$field`, refer
1560
		 * to the taxonomy slug and taxonomy field, respectively.
1561
		 *
1562
		 * @since 2.3.0
1563
		 *
1564
		 * @param mixed $value   Value of the taxonomy field to edit.
1565
		 * @param int   $term_id Term ID.
1566
		 */
1567
		$value = apply_filters( "edit_{$taxonomy}_{$field}", $value, $term_id );
1568
1569
		if ( 'description' == $field )
1570
			$value = esc_html($value); // textarea_escaped
1571
		else
1572
			$value = esc_attr($value);
1573 View Code Duplication
	} elseif ( 'db' == $context ) {
1574
1575
		/**
1576
		 * Filters a term field value before it is sanitized.
1577
		 *
1578
		 * The dynamic portion of the filter name, `$field`, refers to the term field.
1579
		 *
1580
		 * @since 2.3.0
1581
		 *
1582
		 * @param mixed  $value    Value of the term field.
1583
		 * @param string $taxonomy Taxonomy slug.
1584
		 */
1585
		$value = apply_filters( "pre_term_{$field}", $value, $taxonomy );
1586
1587
		/**
1588
		 * Filters a taxonomy field before it is sanitized.
1589
		 *
1590
		 * The dynamic portions of the filter name, `$taxonomy` and `$field`, refer
1591
		 * to the taxonomy slug and field name, respectively.
1592
		 *
1593
		 * @since 2.3.0
1594
		 *
1595
		 * @param mixed $value Value of the taxonomy field.
1596
		 */
1597
		$value = apply_filters( "pre_{$taxonomy}_{$field}", $value );
1598
1599
		// Back compat filters
1600
		if ( 'slug' == $field ) {
1601
			/**
1602
			 * Filters the category nicename before it is sanitized.
1603
			 *
1604
			 * Use the {@see 'pre_$taxonomy_$field'} hook instead.
1605
			 *
1606
			 * @since 2.0.3
1607
			 *
1608
			 * @param string $value The category nicename.
1609
			 */
1610
			$value = apply_filters( 'pre_category_nicename', $value );
1611
		}
1612
1613
	} elseif ( 'rss' == $context ) {
1614
1615
		/**
1616
		 * Filters the term field for use in RSS.
1617
		 *
1618
		 * The dynamic portion of the filter name, `$field`, refers to the term field.
1619
		 *
1620
		 * @since 2.3.0
1621
		 *
1622
		 * @param mixed  $value    Value of the term field.
1623
		 * @param string $taxonomy Taxonomy slug.
1624
		 */
1625
		$value = apply_filters( "term_{$field}_rss", $value, $taxonomy );
1626
1627
		/**
1628
		 * Filters the taxonomy field for use in RSS.
1629
		 *
1630
		 * The dynamic portions of the hook name, `$taxonomy`, and `$field`, refer
1631
		 * to the taxonomy slug and field name, respectively.
1632
		 *
1633
		 * @since 2.3.0
1634
		 *
1635
		 * @param mixed $value Value of the taxonomy field.
1636
		 */
1637
		$value = apply_filters( "{$taxonomy}_{$field}_rss", $value );
1638
	} else {
1639
		// Use display filters by default.
1640
1641
		/**
1642
		 * Filters the term field sanitized for display.
1643
		 *
1644
		 * The dynamic portion of the filter name, `$field`, refers to the term field name.
1645
		 *
1646
		 * @since 2.3.0
1647
		 *
1648
		 * @param mixed  $value    Value of the term field.
1649
		 * @param int    $term_id  Term ID.
1650
		 * @param string $taxonomy Taxonomy slug.
1651
		 * @param string $context  Context to retrieve the term field value.
1652
		 */
1653
		$value = apply_filters( "term_{$field}", $value, $term_id, $taxonomy, $context );
1654
1655
		/**
1656
		 * Filters the taxonomy field sanitized for display.
1657
		 *
1658
		 * The dynamic portions of the filter name, `$taxonomy`, and `$field`, refer
1659
		 * to the taxonomy slug and taxonomy field, respectively.
1660
		 *
1661
		 * @since 2.3.0
1662
		 *
1663
		 * @param mixed  $value   Value of the taxonomy field.
1664
		 * @param int    $term_id Term ID.
1665
		 * @param string $context Context to retrieve the taxonomy field value.
1666
		 */
1667
		$value = apply_filters( "{$taxonomy}_{$field}", $value, $term_id, $context );
1668
	}
1669
1670 View Code Duplication
	if ( 'attribute' == $context ) {
1671
		$value = esc_attr($value);
1672
	} elseif ( 'js' == $context ) {
1673
		$value = esc_js($value);
1674
	}
1675
	return $value;
1676
}
1677
1678
/**
1679
 * Count how many terms are in Taxonomy.
1680
 *
1681
 * Default $args is 'hide_empty' which can be 'hide_empty=true' or array('hide_empty' => true).
1682
 *
1683
 * @since 2.3.0
1684
 *
1685
 * @param string       $taxonomy Taxonomy name.
1686
 * @param array|string $args     Optional. Array of arguments that get passed to get_terms().
1687
 *                               Default empty array.
1688
 * @return array|int|WP_Error Number of terms in that taxonomy or WP_Error if the taxonomy does not exist.
1689
 */
1690
function wp_count_terms( $taxonomy, $args = array() ) {
1691
	$defaults = array('hide_empty' => false);
1692
	$args = wp_parse_args($args, $defaults);
1693
1694
	// backward compatibility
1695
	if ( isset($args['ignore_empty']) ) {
1696
		$args['hide_empty'] = $args['ignore_empty'];
1697
		unset($args['ignore_empty']);
1698
	}
1699
1700
	$args['fields'] = 'count';
1701
1702
	return get_terms($taxonomy, $args);
1703
}
1704
1705
/**
1706
 * Will unlink the object from the taxonomy or taxonomies.
1707
 *
1708
 * Will remove all relationships between the object and any terms in
1709
 * a particular taxonomy or taxonomies. Does not remove the term or
1710
 * taxonomy itself.
1711
 *
1712
 * @since 2.3.0
1713
 *
1714
 * @param int          $object_id  The term Object Id that refers to the term.
1715
 * @param string|array $taxonomies List of Taxonomy Names or single Taxonomy name.
1716
 */
1717
function wp_delete_object_term_relationships( $object_id, $taxonomies ) {
1718
	$object_id = (int) $object_id;
1719
1720
	if ( !is_array($taxonomies) )
1721
		$taxonomies = array($taxonomies);
1722
1723
	foreach ( (array) $taxonomies as $taxonomy ) {
1724
		$term_ids = wp_get_object_terms( $object_id, $taxonomy, array( 'fields' => 'ids' ) );
1725
		$term_ids = array_map( 'intval', $term_ids );
1726
		wp_remove_object_terms( $object_id, $term_ids, $taxonomy );
1727
	}
1728
}
1729
1730
/**
1731
 * Removes a term from the database.
1732
 *
1733
 * If the term is a parent of other terms, then the children will be updated to
1734
 * that term's parent.
1735
 *
1736
 * Metadata associated with the term will be deleted.
1737
 *
1738
 * @since 2.3.0
1739
 *
1740
 * @global wpdb $wpdb WordPress database abstraction object.
1741
 *
1742
 * @param int          $term     Term ID.
1743
 * @param string       $taxonomy Taxonomy Name.
1744
 * @param array|string $args {
1745
 *     Optional. Array of arguments to override the default term ID. Default empty array.
1746
 *
1747
 *     @type int  $default       The term ID to make the default term. This will only override
1748
 *                               the terms found if there is only one term found. Any other and
1749
 *                               the found terms are used.
1750
 *     @type bool $force_default Optional. Whether to force the supplied term as default to be
1751
 *                               assigned even if the object was not going to be term-less.
1752
 *                               Default false.
1753
 * }
1754
 * @return bool|int|WP_Error True on success, false if term does not exist. Zero on attempted
1755
 *                           deletion of default Category. WP_Error if the taxonomy does not exist.
1756
 */
1757
function wp_delete_term( $term, $taxonomy, $args = array() ) {
1758
	global $wpdb;
1759
1760
	$term = (int) $term;
1761
1762
	if ( ! $ids = term_exists($term, $taxonomy) )
1763
		return false;
1764
	if ( is_wp_error( $ids ) )
1765
		return $ids;
1766
1767
	$tt_id = $ids['term_taxonomy_id'];
1768
1769
	$defaults = array();
1770
1771
	if ( 'category' == $taxonomy ) {
1772
		$defaults['default'] = get_option( 'default_category' );
1773
		if ( $defaults['default'] == $term )
1774
			return 0; // Don't delete the default category
1775
	}
1776
1777
	$args = wp_parse_args($args, $defaults);
1778
1779
	if ( isset( $args['default'] ) ) {
1780
		$default = (int) $args['default'];
1781
		if ( ! term_exists( $default, $taxonomy ) ) {
1782
			unset( $default );
1783
		}
1784
	}
1785
1786
	if ( isset( $args['force_default'] ) ) {
1787
		$force_default = $args['force_default'];
1788
	}
1789
1790
	/**
1791
	 * Fires when deleting a term, before any modifications are made to posts or terms.
1792
	 *
1793
	 * @since 4.1.0
1794
	 *
1795
	 * @param int    $term     Term ID.
1796
	 * @param string $taxonomy Taxonomy Name.
1797
	 */
1798
	do_action( 'pre_delete_term', $term, $taxonomy );
1799
1800
	// Update children to point to new parent
1801
	if ( is_taxonomy_hierarchical($taxonomy) ) {
1802
		$term_obj = get_term($term, $taxonomy);
1803
		if ( is_wp_error( $term_obj ) )
1804
			return $term_obj;
1805
		$parent = $term_obj->parent;
1806
1807
		$edit_ids = $wpdb->get_results( "SELECT term_id, term_taxonomy_id FROM $wpdb->term_taxonomy WHERE `parent` = " . (int)$term_obj->term_id );
1808
		$edit_tt_ids = wp_list_pluck( $edit_ids, 'term_taxonomy_id' );
1809
1810
		/**
1811
		 * Fires immediately before a term to delete's children are reassigned a parent.
1812
		 *
1813
		 * @since 2.9.0
1814
		 *
1815
		 * @param array $edit_tt_ids An array of term taxonomy IDs for the given term.
1816
		 */
1817
		do_action( 'edit_term_taxonomies', $edit_tt_ids );
1818
1819
		$wpdb->update( $wpdb->term_taxonomy, compact( 'parent' ), array( 'parent' => $term_obj->term_id) + compact( 'taxonomy' ) );
1820
1821
		// Clean the cache for all child terms.
1822
		$edit_term_ids = wp_list_pluck( $edit_ids, 'term_id' );
1823
		clean_term_cache( $edit_term_ids, $taxonomy );
1824
1825
		/**
1826
		 * Fires immediately after a term to delete's children are reassigned a parent.
1827
		 *
1828
		 * @since 2.9.0
1829
		 *
1830
		 * @param array $edit_tt_ids An array of term taxonomy IDs for the given term.
1831
		 */
1832
		do_action( 'edited_term_taxonomies', $edit_tt_ids );
1833
	}
1834
1835
	// Get the term before deleting it or its term relationships so we can pass to actions below.
1836
	$deleted_term = get_term( $term, $taxonomy );
1837
1838
	$object_ids = (array) $wpdb->get_col( $wpdb->prepare( "SELECT object_id FROM $wpdb->term_relationships WHERE term_taxonomy_id = %d", $tt_id ) );
1839
1840
	foreach ( $object_ids as $object_id ) {
1841
		$terms = wp_get_object_terms( $object_id, $taxonomy, array( 'fields' => 'ids', 'orderby' => 'none' ) );
1842
		if ( 1 == count($terms) && isset($default) ) {
1843
			$terms = array($default);
1844
		} else {
1845
			$terms = array_diff($terms, array($term));
1846
			if (isset($default) && isset($force_default) && $force_default)
1847
				$terms = array_merge($terms, array($default));
1848
		}
1849
		$terms = array_map('intval', $terms);
1850
		wp_set_object_terms( $object_id, $terms, $taxonomy );
1851
	}
1852
1853
	// Clean the relationship caches for all object types using this term.
1854
	$tax_object = get_taxonomy( $taxonomy );
1855
	foreach ( $tax_object->object_type as $object_type )
1856
		clean_object_term_cache( $object_ids, $object_type );
1857
1858
	$term_meta_ids = $wpdb->get_col( $wpdb->prepare( "SELECT meta_id FROM $wpdb->termmeta WHERE term_id = %d ", $term ) );
1859
	foreach ( $term_meta_ids as $mid ) {
1860
		delete_metadata_by_mid( 'term', $mid );
1861
	}
1862
1863
	/**
1864
	 * Fires immediately before a term taxonomy ID is deleted.
1865
	 *
1866
	 * @since 2.9.0
1867
	 *
1868
	 * @param int $tt_id Term taxonomy ID.
1869
	 */
1870
	do_action( 'delete_term_taxonomy', $tt_id );
1871
	$wpdb->delete( $wpdb->term_taxonomy, array( 'term_taxonomy_id' => $tt_id ) );
1872
1873
	/**
1874
	 * Fires immediately after a term taxonomy ID is deleted.
1875
	 *
1876
	 * @since 2.9.0
1877
	 *
1878
	 * @param int $tt_id Term taxonomy ID.
1879
	 */
1880
	do_action( 'deleted_term_taxonomy', $tt_id );
1881
1882
	// Delete the term if no taxonomies use it.
1883
	if ( !$wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_taxonomy WHERE term_id = %d", $term) ) )
1884
		$wpdb->delete( $wpdb->terms, array( 'term_id' => $term ) );
1885
1886
	clean_term_cache($term, $taxonomy);
1887
1888
	/**
1889
	 * Fires after a term is deleted from the database and the cache is cleaned.
1890
	 *
1891
	 * @since 2.5.0
1892
	 * @since 4.5.0 Introduced the `$object_ids` argument.
1893
	 *
1894
	 * @param int     $term         Term ID.
1895
	 * @param int     $tt_id        Term taxonomy ID.
1896
	 * @param string  $taxonomy     Taxonomy slug.
1897
	 * @param mixed   $deleted_term Copy of the already-deleted term, in the form specified
1898
	 *                              by the parent function. WP_Error otherwise.
1899
	 * @param array   $object_ids   List of term object IDs.
1900
	 */
1901
	do_action( 'delete_term', $term, $tt_id, $taxonomy, $deleted_term, $object_ids );
1902
1903
	/**
1904
	 * Fires after a term in a specific taxonomy is deleted.
1905
	 *
1906
	 * The dynamic portion of the hook name, `$taxonomy`, refers to the specific
1907
	 * taxonomy the term belonged to.
1908
	 *
1909
	 * @since 2.3.0
1910
	 * @since 4.5.0 Introduced the `$object_ids` argument.
1911
	 *
1912
	 * @param int     $term         Term ID.
1913
	 * @param int     $tt_id        Term taxonomy ID.
1914
	 * @param mixed   $deleted_term Copy of the already-deleted term, in the form specified
1915
	 *                              by the parent function. WP_Error otherwise.
1916
	 * @param array   $object_ids   List of term object IDs.
1917
	 */
1918
	do_action( "delete_{$taxonomy}", $term, $tt_id, $deleted_term, $object_ids );
1919
1920
	return true;
1921
}
1922
1923
/**
1924
 * Deletes one existing category.
1925
 *
1926
 * @since 2.0.0
1927
 *
1928
 * @param int $cat_ID
1929
 * @return bool|int|WP_Error Returns true if completes delete action; false if term doesn't exist;
1930
 * 	Zero on attempted deletion of default Category; WP_Error object is also a possibility.
1931
 */
1932
function wp_delete_category( $cat_ID ) {
1933
	return wp_delete_term( $cat_ID, 'category' );
1934
}
1935
1936
/**
1937
 * Retrieves the terms associated with the given object(s), in the supplied taxonomies.
1938
 *
1939
 * @since 2.3.0
1940
 * @since 4.2.0 Added support for 'taxonomy', 'parent', and 'term_taxonomy_id' values of `$orderby`.
1941
 *              Introduced `$parent` argument.
1942
 * @since 4.4.0 Introduced `$meta_query` and `$update_term_meta_cache` arguments. When `$fields` is 'all' or
1943
 *              'all_with_object_id', an array of `WP_Term` objects will be returned.
1944
 *
1945
 * @global wpdb $wpdb WordPress database abstraction object.
1946
 *
1947
 * @param int|array    $object_ids The ID(s) of the object(s) to retrieve.
1948
 * @param string|array $taxonomies The taxonomies to retrieve terms from.
1949
 * @param array|string $args {
1950
 *     Array of arguments.
1951
 *     @type string $orderby                Field by which results should be sorted. Accepts 'name', 'count', 'slug',
1952
 *                                          'term_group', 'term_order', 'taxonomy', 'parent', or 'term_taxonomy_id'.
1953
 *                                          Default 'name'.
1954
 *     @type string $order                  Sort order. Accepts 'ASC' or 'DESC'. Default 'ASC'.
1955
 *     @type string $fields                 Fields to return for matched terms. Accepts 'all', 'ids', 'names', and
1956
 *                                          'all_with_object_id'. Note that 'all' or 'all_with_object_id' will result
1957
 *                                          in an array of term objects being returned, 'ids' will return an array of
1958
 *                                          integers, and 'names' an array of strings.
1959
 *     @type int    $parent                 Optional. Limit results to the direct children of a given term ID.
1960
 *     @type bool   $update_term_meta_cache Whether to prime termmeta cache for matched terms. Only applies when
1961
 *                                          `$fields` is 'all', 'all_with_object_id', or 'term_id'. Default true.
1962
 *     @type array  $meta_query             Meta query clauses to limit retrieved terms by. See `WP_Meta_Query`.
1963
 *                                          Default empty.
1964
 * }
1965
 * @return array|WP_Error The requested term data or empty array if no terms found.
1966
 *                        WP_Error if any of the $taxonomies don't exist.
1967
 */
1968
function wp_get_object_terms($object_ids, $taxonomies, $args = array()) {
1969
	global $wpdb;
1970
1971
	if ( empty( $object_ids ) || empty( $taxonomies ) )
1972
		return array();
1973
1974
	if ( !is_array($taxonomies) )
1975
		$taxonomies = array($taxonomies);
1976
1977
	foreach ( $taxonomies as $taxonomy ) {
1978
		if ( ! taxonomy_exists($taxonomy) )
1979
			return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
1980
	}
1981
1982
	if ( !is_array($object_ids) )
1983
		$object_ids = array($object_ids);
1984
	$object_ids = array_map('intval', $object_ids);
1985
1986
	$defaults = array(
1987
		'orderby' => 'name',
1988
		'order'   => 'ASC',
1989
		'fields'  => 'all',
1990
		'parent'  => '',
1991
		'update_term_meta_cache' => true,
1992
		'meta_query' => '',
1993
	);
1994
	$args = wp_parse_args( $args, $defaults );
1995
1996
	$terms = array();
1997
	if ( count($taxonomies) > 1 ) {
1998
		foreach ( $taxonomies as $index => $taxonomy ) {
1999
			$t = get_taxonomy($taxonomy);
2000
			if ( isset($t->args) && is_array($t->args) && $args != array_merge($args, $t->args) ) {
2001
				unset($taxonomies[$index]);
2002
				$terms = array_merge($terms, wp_get_object_terms($object_ids, $taxonomy, array_merge($args, $t->args)));
2003
			}
2004
		}
2005
	} else {
2006
		$t = get_taxonomy($taxonomies[0]);
2007
		if ( isset($t->args) && is_array($t->args) )
2008
			$args = array_merge($args, $t->args);
2009
	}
2010
2011
	$orderby = $args['orderby'];
2012
	$order = $args['order'];
2013
	$fields = $args['fields'];
2014
2015
	if ( in_array( $orderby, array( 'term_id', 'name', 'slug', 'term_group' ) ) ) {
2016
		$orderby = "t.$orderby";
2017
	} elseif ( in_array( $orderby, array( 'count', 'parent', 'taxonomy', 'term_taxonomy_id' ) ) ) {
2018
		$orderby = "tt.$orderby";
2019
	} elseif ( 'term_order' === $orderby ) {
2020
		$orderby = 'tr.term_order';
2021
	} elseif ( 'none' === $orderby ) {
2022
		$orderby = '';
2023
		$order = '';
2024
	} else {
2025
		$orderby = 't.term_id';
2026
	}
2027
2028
	// tt_ids queries can only be none or tr.term_taxonomy_id
2029
	if ( ('tt_ids' == $fields) && !empty($orderby) )
2030
		$orderby = 'tr.term_taxonomy_id';
2031
2032
	if ( !empty($orderby) )
2033
		$orderby = "ORDER BY $orderby";
2034
2035
	$order = strtoupper( $order );
2036 View Code Duplication
	if ( '' !== $order && ! in_array( $order, array( 'ASC', 'DESC' ) ) )
2037
		$order = 'ASC';
2038
2039
	$taxonomy_array = $taxonomies;
2040
	$object_id_array = $object_ids;
2041
	$taxonomies = "'" . implode("', '", array_map( 'esc_sql', $taxonomies ) ) . "'";
2042
	$object_ids = implode(', ', $object_ids);
2043
2044
	$select_this = '';
2045
	if ( 'all' == $fields ) {
2046
		$select_this = 't.*, tt.*';
2047
	} elseif ( 'ids' == $fields ) {
2048
		$select_this = 't.term_id';
2049
	} elseif ( 'names' == $fields ) {
2050
		$select_this = 't.name';
2051
	} elseif ( 'slugs' == $fields ) {
2052
		$select_this = 't.slug';
2053
	} elseif ( 'all_with_object_id' == $fields ) {
2054
		$select_this = 't.*, tt.*, tr.object_id';
2055
	}
2056
2057
	$where = array(
2058
		"tt.taxonomy IN ($taxonomies)",
2059
		"tr.object_id IN ($object_ids)",
2060
	);
2061
2062
	if ( '' !== $args['parent'] ) {
2063
		$where[] = $wpdb->prepare( 'tt.parent = %d', $args['parent'] );
2064
	}
2065
2066
	// Meta query support.
2067
	$meta_query_join = '';
2068
	if ( ! empty( $args['meta_query'] ) ) {
2069
		$mquery = new WP_Meta_Query( $args['meta_query'] );
2070
		$mq_sql = $mquery->get_sql( 'term', 't', 'term_id' );
2071
2072
		$meta_query_join .= $mq_sql['join'];
2073
2074
		// Strip leading AND.
2075
		$where[] = preg_replace( '/^\s*AND/', '', $mq_sql['where'] );
2076
	}
2077
2078
	$where = implode( ' AND ', $where );
2079
2080
	$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 $meta_query_join WHERE $where $orderby $order";
2081
2082
	$objects = false;
2083
	if ( 'all' == $fields || 'all_with_object_id' == $fields ) {
2084
		$_terms = $wpdb->get_results( $query );
2085
		$object_id_index = array();
2086
		foreach ( $_terms as $key => $term ) {
2087
			$term = sanitize_term( $term, $taxonomy, 'raw' );
0 ignored issues
show
The variable $taxonomy does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
2088
			$_terms[ $key ] = $term;
2089
2090
			if ( isset( $term->object_id ) ) {
2091
				$object_id_index[ $key ] = $term->object_id;
2092
			}
2093
		}
2094
2095
		update_term_cache( $_terms );
2096
		$_terms = array_map( 'get_term', $_terms );
2097
2098
		// Re-add the object_id data, which is lost when fetching terms from cache.
2099
		if ( 'all_with_object_id' === $fields ) {
2100
			foreach ( $_terms as $key => $_term ) {
2101
				if ( isset( $object_id_index[ $key ] ) ) {
2102
					$_term->object_id = $object_id_index[ $key ];
2103
				}
2104
			}
2105
		}
2106
2107
		$terms = array_merge( $terms, $_terms );
2108
		$objects = true;
2109
2110
	} elseif ( 'ids' == $fields || 'names' == $fields || 'slugs' == $fields ) {
2111
		$_terms = $wpdb->get_col( $query );
2112
		$_field = ( 'ids' == $fields ) ? 'term_id' : 'name';
2113
		foreach ( $_terms as $key => $term ) {
2114
			$_terms[$key] = sanitize_term_field( $_field, $term, $term, $taxonomy, 'raw' );
2115
		}
2116
		$terms = array_merge( $terms, $_terms );
2117
	} elseif ( 'tt_ids' == $fields ) {
2118
		$terms = $wpdb->get_col("SELECT tr.term_taxonomy_id FROM $wpdb->term_relationships AS tr INNER JOIN $wpdb->term_taxonomy AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id WHERE tr.object_id IN ($object_ids) AND tt.taxonomy IN ($taxonomies) $orderby $order");
2119
		foreach ( $terms as $key => $tt_id ) {
2120
			$terms[$key] = sanitize_term_field( 'term_taxonomy_id', $tt_id, 0, $taxonomy, 'raw' ); // 0 should be the term id, however is not needed when using raw context.
2121
		}
2122
	}
2123
2124
	// Update termmeta cache, if necessary.
2125
	if ( $args['update_term_meta_cache'] && ( 'all' === $fields || 'all_with_object_id' === $fields || 'ids' === $fields ) ) {
2126
		if ( 'ids' === $fields ) {
2127
			$term_ids = $terms;
2128
		} else {
2129
			$term_ids = wp_list_pluck( $terms, 'term_id' );
2130
		}
2131
2132
		update_termmeta_cache( $term_ids );
2133
	}
2134
2135
	if ( ! $terms ) {
2136
		$terms = array();
2137
	} elseif ( $objects && 'all_with_object_id' !== $fields ) {
2138
		$_tt_ids = array();
2139
		$_terms = array();
2140
		foreach ( $terms as $term ) {
2141
			if ( in_array( $term->term_taxonomy_id, $_tt_ids ) ) {
2142
				continue;
2143
			}
2144
2145
			$_tt_ids[] = $term->term_taxonomy_id;
2146
			$_terms[] = $term;
2147
		}
2148
		$terms = $_terms;
2149
	} elseif ( ! $objects ) {
2150
		$terms = array_values( array_unique( $terms ) );
2151
	}
2152
2153
	/**
2154
	 * Filters the terms for a given object or objects.
2155
	 *
2156
	 * @since 4.2.0
2157
	 *
2158
	 * @param array $terms           An array of terms for the given object or objects.
2159
	 * @param array $object_id_array Array of object IDs for which `$terms` were retrieved.
2160
	 * @param array $taxonomy_array  Array of taxonomies from which `$terms` were retrieved.
2161
	 * @param array $args            An array of arguments for retrieving terms for the given
2162
	 *                               object(s). See wp_get_object_terms() for details.
2163
	 */
2164
	$terms = apply_filters( 'get_object_terms', $terms, $object_id_array, $taxonomy_array, $args );
2165
2166
	/**
2167
	 * Filters the terms for a given object or objects.
2168
	 *
2169
	 * The `$taxonomies` parameter passed to this filter is formatted as a SQL fragment. The
2170
	 * {@see 'get_object_terms'} filter is recommended as an alternative.
2171
	 *
2172
	 * @since 2.8.0
2173
	 *
2174
	 * @param array     $terms      An array of terms for the given object or objects.
2175
	 * @param int|array $object_ids Object ID or array of IDs.
2176
	 * @param string    $taxonomies SQL-formatted (comma-separated and quoted) list of taxonomy names.
2177
	 * @param array     $args       An array of arguments for retrieving terms for the given object(s).
2178
	 *                              See wp_get_object_terms() for details.
2179
	 */
2180
	return apply_filters( 'wp_get_object_terms', $terms, $object_ids, $taxonomies, $args );
2181
}
2182
2183
/**
2184
 * Add a new term to the database.
2185
 *
2186
 * A non-existent term is inserted in the following sequence:
2187
 * 1. The term is added to the term table, then related to the taxonomy.
2188
 * 2. If everything is correct, several actions are fired.
2189
 * 3. The 'term_id_filter' is evaluated.
2190
 * 4. The term cache is cleaned.
2191
 * 5. Several more actions are fired.
2192
 * 6. An array is returned containing the term_id and term_taxonomy_id.
2193
 *
2194
 * If the 'slug' argument is not empty, then it is checked to see if the term
2195
 * is invalid. If it is not a valid, existing term, it is added and the term_id
2196
 * is given.
2197
 *
2198
 * If the taxonomy is hierarchical, and the 'parent' argument is not empty,
2199
 * the term is inserted and the term_id will be given.
2200
 *
2201
 * Error handling:
2202
 * If $taxonomy does not exist or $term is empty,
2203
 * a WP_Error object will be returned.
2204
 *
2205
 * If the term already exists on the same hierarchical level,
2206
 * or the term slug and name are not unique, a WP_Error object will be returned.
2207
 *
2208
 * @global wpdb $wpdb WordPress database abstraction object.
2209
 *
2210
 * @since 2.3.0
2211
 *
2212
 * @param string       $term     The term to add or update.
2213
 * @param string       $taxonomy The taxonomy to which to add the term.
2214
 * @param array|string $args {
2215
 *     Optional. Array or string of arguments for inserting a term.
2216
 *
2217
 *     @type string $alias_of    Slug of the term to make this term an alias of.
2218
 *                               Default empty string. Accepts a term slug.
2219
 *     @type string $description The term description. Default empty string.
2220
 *     @type int    $parent      The id of the parent term. Default 0.
2221
 *     @type string $slug        The term slug to use. Default empty string.
2222
 * }
2223
 * @return array|WP_Error An array containing the `term_id` and `term_taxonomy_id`,
2224
 *                        WP_Error otherwise.
2225
 */
2226
function wp_insert_term( $term, $taxonomy, $args = array() ) {
2227
	global $wpdb;
2228
2229
	if ( ! taxonomy_exists($taxonomy) ) {
2230
		return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
2231
	}
2232
	/**
2233
	 * Filters a term before it is sanitized and inserted into the database.
2234
	 *
2235
	 * @since 3.0.0
2236
	 *
2237
	 * @param string $term     The term to add or update.
2238
	 * @param string $taxonomy Taxonomy slug.
2239
	 */
2240
	$term = apply_filters( 'pre_insert_term', $term, $taxonomy );
2241
	if ( is_wp_error( $term ) ) {
2242
		return $term;
2243
	}
2244
	if ( is_int( $term ) && 0 == $term ) {
2245
		return new WP_Error( 'invalid_term_id', __( 'Invalid term ID.' ) );
2246
	}
2247
	if ( '' == trim( $term ) ) {
2248
		return new WP_Error( 'empty_term_name', __( 'A name is required for this term.' ) );
2249
	}
2250
	$defaults = array( 'alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => '');
2251
	$args = wp_parse_args( $args, $defaults );
2252
2253 View Code Duplication
	if ( $args['parent'] > 0 && ! term_exists( (int) $args['parent'] ) ) {
2254
		return new WP_Error( 'missing_parent', __( 'Parent term does not exist.' ) );
2255
	}
2256
2257
	$args['name'] = $term;
2258
	$args['taxonomy'] = $taxonomy;
2259
2260
	// Coerce null description to strings, to avoid database errors.
2261
	$args['description'] = (string) $args['description'];
2262
2263
	$args = sanitize_term($args, $taxonomy, 'db');
2264
2265
	// expected_slashed ($name)
2266
	$name = wp_unslash( $args['name'] );
2267
	$description = wp_unslash( $args['description'] );
2268
	$parent = (int) $args['parent'];
2269
2270
	$slug_provided = ! empty( $args['slug'] );
2271
	if ( ! $slug_provided ) {
2272
		$slug = sanitize_title( $name );
0 ignored issues
show
It seems like $name defined by wp_unslash($args['name']) on line 2266 can also be of type array; however, sanitize_title() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
2273
	} else {
2274
		$slug = $args['slug'];
2275
	}
2276
2277
	$term_group = 0;
2278 View Code Duplication
	if ( $args['alias_of'] ) {
2279
		$alias = get_term_by( 'slug', $args['alias_of'], $taxonomy );
2280
		if ( ! empty( $alias->term_group ) ) {
2281
			// The alias we want is already in a group, so let's use that one.
2282
			$term_group = $alias->term_group;
2283
		} elseif ( ! empty( $alias->term_id ) ) {
2284
			/*
2285
			 * The alias is not in a group, so we create a new one
2286
			 * and add the alias to it.
2287
			 */
2288
			$term_group = $wpdb->get_var("SELECT MAX(term_group) FROM $wpdb->terms") + 1;
2289
2290
			wp_update_term( $alias->term_id, $taxonomy, array(
2291
				'term_group' => $term_group,
2292
			) );
2293
		}
2294
	}
2295
2296
	/*
2297
	 * Prevent the creation of terms with duplicate names at the same level of a taxonomy hierarchy,
2298
	 * unless a unique slug has been explicitly provided.
2299
	 */
2300
	$name_matches = get_terms( $taxonomy, array(
2301
		'name' => $name,
2302
		'hide_empty' => false,
2303
	) );
2304
2305
	/*
2306
	 * The `name` match in `get_terms()` doesn't differentiate accented characters,
2307
	 * so we do a stricter comparison here.
2308
	 */
2309
	$name_match = null;
2310
	if ( $name_matches ) {
2311
		foreach ( $name_matches as $_match ) {
0 ignored issues
show
The expression $name_matches of type array|integer|object<WP_Error> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
2312
			if ( strtolower( $name ) === strtolower( $_match->name ) ) {
2313
				$name_match = $_match;
2314
				break;
2315
			}
2316
		}
2317
	}
2318
2319
	if ( $name_match ) {
2320
		$slug_match = get_term_by( 'slug', $slug, $taxonomy );
2321
		if ( ! $slug_provided || $name_match->slug === $slug || $slug_match ) {
2322
			if ( is_taxonomy_hierarchical( $taxonomy ) ) {
2323
				$siblings = get_terms( $taxonomy, array( 'get' => 'all', 'parent' => $parent ) );
2324
2325
				$existing_term = null;
2326
				if ( $name_match->slug === $slug && in_array( $name, wp_list_pluck( $siblings, 'name' ) ) ) {
2327
					$existing_term = $name_match;
2328
				} elseif ( $slug_match && in_array( $slug, wp_list_pluck( $siblings, 'slug' ) ) ) {
2329
					$existing_term = $slug_match;
2330
				}
2331
2332
				if ( $existing_term ) {
2333
					return new WP_Error( 'term_exists', __( 'A term with the name provided already exists with this parent.' ), $existing_term->term_id );
2334
				}
2335
			} else {
2336
				return new WP_Error( 'term_exists', __( 'A term with the name provided already exists in this taxonomy.' ), $name_match->term_id );
2337
			}
2338
		}
2339
	}
2340
2341
	$slug = wp_unique_term_slug( $slug, (object) $args );
2342
2343
	if ( false === $wpdb->insert( $wpdb->terms, compact( 'name', 'slug', 'term_group' ) ) ) {
2344
		return new WP_Error( 'db_insert_error', __( 'Could not insert term into the database' ), $wpdb->last_error );
2345
	}
2346
2347
	$term_id = (int) $wpdb->insert_id;
2348
2349
	// Seems unreachable, However, Is used in the case that a term name is provided, which sanitizes to an empty string.
2350
	if ( empty($slug) ) {
2351
		$slug = sanitize_title($slug, $term_id);
2352
2353
		/** This action is documented in wp-includes/taxonomy.php */
2354
		do_action( 'edit_terms', $term_id, $taxonomy );
2355
		$wpdb->update( $wpdb->terms, compact( 'slug' ), compact( 'term_id' ) );
2356
2357
		/** This action is documented in wp-includes/taxonomy.php */
2358
		do_action( 'edited_terms', $term_id, $taxonomy );
2359
	}
2360
2361
	$tt_id = $wpdb->get_var( $wpdb->prepare( "SELECT tt.term_taxonomy_id FROM $wpdb->term_taxonomy AS tt INNER JOIN $wpdb->terms AS t ON tt.term_id = t.term_id WHERE tt.taxonomy = %s AND t.term_id = %d", $taxonomy, $term_id ) );
2362
2363
	if ( !empty($tt_id) ) {
2364
		return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
2365
	}
2366
	$wpdb->insert( $wpdb->term_taxonomy, compact( 'term_id', 'taxonomy', 'description', 'parent') + array( 'count' => 0 ) );
2367
	$tt_id = (int) $wpdb->insert_id;
2368
2369
	/*
2370
	 * Sanity check: if we just created a term with the same parent + taxonomy + slug but a higher term_id than
2371
	 * an existing term, then we have unwittingly created a duplicate term. Delete the dupe, and use the term_id
2372
	 * and term_taxonomy_id of the older term instead. Then return out of the function so that the "create" hooks
2373
	 * are not fired.
2374
	 */
2375
	$duplicate_term = $wpdb->get_row( $wpdb->prepare( "SELECT t.term_id, tt.term_taxonomy_id FROM $wpdb->terms t INNER JOIN $wpdb->term_taxonomy tt ON ( tt.term_id = t.term_id ) WHERE t.slug = %s AND tt.parent = %d AND tt.taxonomy = %s AND t.term_id < %d AND tt.term_taxonomy_id != %d", $slug, $parent, $taxonomy, $term_id, $tt_id ) );
2376
	if ( $duplicate_term ) {
2377
		$wpdb->delete( $wpdb->terms, array( 'term_id' => $term_id ) );
2378
		$wpdb->delete( $wpdb->term_taxonomy, array( 'term_taxonomy_id' => $tt_id ) );
2379
2380
		$term_id = (int) $duplicate_term->term_id;
2381
		$tt_id   = (int) $duplicate_term->term_taxonomy_id;
2382
2383
		clean_term_cache( $term_id, $taxonomy );
2384
		return array( 'term_id' => $term_id, 'term_taxonomy_id' => $tt_id );
2385
	}
2386
2387
	/**
2388
	 * Fires immediately after a new term is created, before the term cache is cleaned.
2389
	 *
2390
	 * @since 2.3.0
2391
	 *
2392
	 * @param int    $term_id  Term ID.
2393
	 * @param int    $tt_id    Term taxonomy ID.
2394
	 * @param string $taxonomy Taxonomy slug.
2395
	 */
2396
	do_action( "create_term", $term_id, $tt_id, $taxonomy );
2397
2398
	/**
2399
	 * Fires after a new term is created for a specific taxonomy.
2400
	 *
2401
	 * The dynamic portion of the hook name, `$taxonomy`, refers
2402
	 * to the slug of the taxonomy the term was created for.
2403
	 *
2404
	 * @since 2.3.0
2405
	 *
2406
	 * @param int $term_id Term ID.
2407
	 * @param int $tt_id   Term taxonomy ID.
2408
	 */
2409
	do_action( "create_{$taxonomy}", $term_id, $tt_id );
2410
2411
	/**
2412
	 * Filters the term ID after a new term is created.
2413
	 *
2414
	 * @since 2.3.0
2415
	 *
2416
	 * @param int $term_id Term ID.
2417
	 * @param int $tt_id   Taxonomy term ID.
2418
	 */
2419
	$term_id = apply_filters( 'term_id_filter', $term_id, $tt_id );
2420
2421
	clean_term_cache($term_id, $taxonomy);
2422
2423
	/**
2424
	 * Fires after a new term is created, and after the term cache has been cleaned.
2425
	 *
2426
	 * @since 2.3.0
2427
	 *
2428
	 * @param int    $term_id  Term ID.
2429
	 * @param int    $tt_id    Term taxonomy ID.
2430
	 * @param string $taxonomy Taxonomy slug.
2431
	 */
2432
	do_action( 'created_term', $term_id, $tt_id, $taxonomy );
2433
2434
	/**
2435
	 * Fires after a new term in a specific taxonomy is created, and after the term
2436
	 * cache has been cleaned.
2437
	 *
2438
	 * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
2439
	 *
2440
	 * @since 2.3.0
2441
	 *
2442
	 * @param int $term_id Term ID.
2443
	 * @param int $tt_id   Term taxonomy ID.
2444
	 */
2445
	do_action( "created_{$taxonomy}", $term_id, $tt_id );
2446
2447
	return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
2448
}
2449
2450
/**
2451
 * Create Term and Taxonomy Relationships.
2452
 *
2453
 * Relates an object (post, link etc) to a term and taxonomy type. Creates the
2454
 * term and taxonomy relationship if it doesn't already exist. Creates a term if
2455
 * it doesn't exist (using the slug).
2456
 *
2457
 * A relationship means that the term is grouped in or belongs to the taxonomy.
2458
 * A term has no meaning until it is given context by defining which taxonomy it
2459
 * exists under.
2460
 *
2461
 * @since 2.3.0
2462
 *
2463
 * @global wpdb $wpdb The WordPress database abstraction object.
2464
 *
2465
 * @param int              $object_id The object to relate to.
2466
 * @param array|int|string $terms     A single term slug, single term id, or array of either term slugs or ids.
2467
 *                                    Will replace all existing related terms in this taxonomy.
2468
 * @param string           $taxonomy  The context in which to relate the term to the object.
2469
 * @param bool             $append    Optional. If false will delete difference of terms. Default false.
2470
 * @return array|WP_Error Term taxonomy IDs of the affected terms.
2471
 */
2472
function wp_set_object_terms( $object_id, $terms, $taxonomy, $append = false ) {
2473
	global $wpdb;
2474
2475
	$object_id = (int) $object_id;
2476
2477
	if ( ! taxonomy_exists( $taxonomy ) ) {
2478
		return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
2479
	}
2480
2481
	if ( !is_array($terms) )
2482
		$terms = array($terms);
2483
2484
	if ( ! $append )
2485
		$old_tt_ids =  wp_get_object_terms($object_id, $taxonomy, array('fields' => 'tt_ids', 'orderby' => 'none'));
2486
	else
2487
		$old_tt_ids = array();
2488
2489
	$tt_ids = array();
2490
	$term_ids = array();
2491
	$new_tt_ids = array();
2492
2493
	foreach ( (array) $terms as $term) {
2494
		if ( !strlen(trim($term)) )
2495
			continue;
2496
2497
		if ( !$term_info = term_exists($term, $taxonomy) ) {
2498
			// Skip if a non-existent term ID is passed.
2499
			if ( is_int($term) )
2500
				continue;
2501
			$term_info = wp_insert_term($term, $taxonomy);
2502
		}
2503
		if ( is_wp_error($term_info) )
2504
			return $term_info;
2505
		$term_ids[] = $term_info['term_id'];
2506
		$tt_id = $term_info['term_taxonomy_id'];
2507
		$tt_ids[] = $tt_id;
2508
2509
		if ( $wpdb->get_var( $wpdb->prepare( "SELECT term_taxonomy_id FROM $wpdb->term_relationships WHERE object_id = %d AND term_taxonomy_id = %d", $object_id, $tt_id ) ) )
2510
			continue;
2511
2512
		/**
2513
		 * Fires immediately before an object-term relationship is added.
2514
		 *
2515
		 * @since 2.9.0
2516
		 *
2517
		 * @param int $object_id Object ID.
2518
		 * @param int $tt_id     Term taxonomy ID.
2519
		 */
2520
		do_action( 'add_term_relationship', $object_id, $tt_id );
2521
		$wpdb->insert( $wpdb->term_relationships, array( 'object_id' => $object_id, 'term_taxonomy_id' => $tt_id ) );
2522
2523
		/**
2524
		 * Fires immediately after an object-term relationship is added.
2525
		 *
2526
		 * @since 2.9.0
2527
		 *
2528
		 * @param int $object_id Object ID.
2529
		 * @param int $tt_id     Term taxonomy ID.
2530
		 */
2531
		do_action( 'added_term_relationship', $object_id, $tt_id );
2532
		$new_tt_ids[] = $tt_id;
2533
	}
2534
2535
	if ( $new_tt_ids )
2536
		wp_update_term_count( $new_tt_ids, $taxonomy );
2537
2538
	if ( ! $append ) {
2539
		$delete_tt_ids = array_diff( $old_tt_ids, $tt_ids );
2540
2541
		if ( $delete_tt_ids ) {
2542
			$in_delete_tt_ids = "'" . implode( "', '", $delete_tt_ids ) . "'";
2543
			$delete_term_ids = $wpdb->get_col( $wpdb->prepare( "SELECT tt.term_id FROM $wpdb->term_taxonomy AS tt WHERE tt.taxonomy = %s AND tt.term_taxonomy_id IN ($in_delete_tt_ids)", $taxonomy ) );
2544
			$delete_term_ids = array_map( 'intval', $delete_term_ids );
2545
2546
			$remove = wp_remove_object_terms( $object_id, $delete_term_ids, $taxonomy );
2547
			if ( is_wp_error( $remove ) ) {
2548
				return $remove;
2549
			}
2550
		}
2551
	}
2552
2553
	$t = get_taxonomy($taxonomy);
2554
	if ( ! $append && isset($t->sort) && $t->sort ) {
2555
		$values = array();
2556
		$term_order = 0;
2557
		$final_tt_ids = wp_get_object_terms($object_id, $taxonomy, array('fields' => 'tt_ids'));
2558
		foreach ( $tt_ids as $tt_id )
2559
			if ( in_array($tt_id, $final_tt_ids) )
2560
				$values[] = $wpdb->prepare( "(%d, %d, %d)", $object_id, $tt_id, ++$term_order);
2561
		if ( $values )
2562
			if ( false === $wpdb->query( "INSERT INTO $wpdb->term_relationships (object_id, term_taxonomy_id, term_order) VALUES " . join( ',', $values ) . " ON DUPLICATE KEY UPDATE term_order = VALUES(term_order)" ) )
2563
				return new WP_Error( 'db_insert_error', __( 'Could not insert term relationship into the database' ), $wpdb->last_error );
2564
	}
2565
2566
	wp_cache_delete( $object_id, $taxonomy . '_relationships' );
2567
2568
	/**
2569
	 * Fires after an object's terms have been set.
2570
	 *
2571
	 * @since 2.8.0
2572
	 *
2573
	 * @param int    $object_id  Object ID.
2574
	 * @param array  $terms      An array of object terms.
2575
	 * @param array  $tt_ids     An array of term taxonomy IDs.
2576
	 * @param string $taxonomy   Taxonomy slug.
2577
	 * @param bool   $append     Whether to append new terms to the old terms.
2578
	 * @param array  $old_tt_ids Old array of term taxonomy IDs.
2579
	 */
2580
	do_action( 'set_object_terms', $object_id, $terms, $tt_ids, $taxonomy, $append, $old_tt_ids );
2581
	return $tt_ids;
2582
}
2583
2584
/**
2585
 * Add term(s) associated with a given object.
2586
 *
2587
 * @since 3.6.0
2588
 *
2589
 * @param int              $object_id The ID of the object to which the terms will be added.
2590
 * @param array|int|string $terms     The slug(s) or ID(s) of the term(s) to add.
2591
 * @param array|string     $taxonomy  Taxonomy name.
2592
 * @return array|WP_Error Term taxonomy IDs of the affected terms.
2593
 */
2594
function wp_add_object_terms( $object_id, $terms, $taxonomy ) {
2595
	return wp_set_object_terms( $object_id, $terms, $taxonomy, true );
0 ignored issues
show
It seems like $taxonomy defined by parameter $taxonomy on line 2594 can also be of type array; however, wp_set_object_terms() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2596
}
2597
2598
/**
2599
 * Remove term(s) associated with a given object.
2600
 *
2601
 * @since 3.6.0
2602
 *
2603
 * @global wpdb $wpdb WordPress database abstraction object.
2604
 *
2605
 * @param int              $object_id The ID of the object from which the terms will be removed.
2606
 * @param array|int|string $terms     The slug(s) or ID(s) of the term(s) to remove.
2607
 * @param array|string     $taxonomy  Taxonomy name.
2608
 * @return bool|WP_Error True on success, false or WP_Error on failure.
2609
 */
2610
function wp_remove_object_terms( $object_id, $terms, $taxonomy ) {
2611
	global $wpdb;
2612
2613
	$object_id = (int) $object_id;
2614
2615
	if ( ! taxonomy_exists( $taxonomy ) ) {
0 ignored issues
show
It seems like $taxonomy defined by parameter $taxonomy on line 2610 can also be of type array; however, taxonomy_exists() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2616
		return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
2617
	}
2618
2619
	if ( ! is_array( $terms ) ) {
2620
		$terms = array( $terms );
2621
	}
2622
2623
	$tt_ids = array();
2624
2625
	foreach ( (array) $terms as $term ) {
2626
		if ( ! strlen( trim( $term ) ) ) {
2627
			continue;
2628
		}
2629
2630
		if ( ! $term_info = term_exists( $term, $taxonomy ) ) {
0 ignored issues
show
It seems like $taxonomy defined by parameter $taxonomy on line 2610 can also be of type array; however, term_exists() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2631
			// Skip if a non-existent term ID is passed.
2632
			if ( is_int( $term ) ) {
2633
				continue;
2634
			}
2635
		}
2636
2637
		if ( is_wp_error( $term_info ) ) {
2638
			return $term_info;
2639
		}
2640
2641
		$tt_ids[] = $term_info['term_taxonomy_id'];
2642
	}
2643
2644
	if ( $tt_ids ) {
2645
		$in_tt_ids = "'" . implode( "', '", $tt_ids ) . "'";
2646
2647
		/**
2648
		 * Fires immediately before an object-term relationship is deleted.
2649
		 *
2650
		 * @since 2.9.0
2651
		 *
2652
		 * @param int   $object_id Object ID.
2653
		 * @param array $tt_ids    An array of term taxonomy IDs.
2654
		 */
2655
		do_action( 'delete_term_relationships', $object_id, $tt_ids );
2656
		$deleted = $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->term_relationships WHERE object_id = %d AND term_taxonomy_id IN ($in_tt_ids)", $object_id ) );
2657
2658
		wp_cache_delete( $object_id, $taxonomy . '_relationships' );
2659
2660
		/**
2661
		 * Fires immediately after an object-term relationship is deleted.
2662
		 *
2663
		 * @since 2.9.0
2664
		 *
2665
		 * @param int   $object_id Object ID.
2666
		 * @param array $tt_ids    An array of term taxonomy IDs.
2667
		 */
2668
		do_action( 'deleted_term_relationships', $object_id, $tt_ids );
2669
2670
		wp_update_term_count( $tt_ids, $taxonomy );
0 ignored issues
show
It seems like $taxonomy defined by parameter $taxonomy on line 2610 can also be of type array; however, wp_update_term_count() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2671
2672
		return (bool) $deleted;
2673
	}
2674
2675
	return false;
2676
}
2677
2678
/**
2679
 * Will make slug unique, if it isn't already.
2680
 *
2681
 * The `$slug` has to be unique global to every taxonomy, meaning that one
2682
 * taxonomy term can't have a matching slug with another taxonomy term. Each
2683
 * slug has to be globally unique for every taxonomy.
2684
 *
2685
 * The way this works is that if the taxonomy that the term belongs to is
2686
 * hierarchical and has a parent, it will append that parent to the $slug.
2687
 *
2688
 * If that still doesn't return an unique slug, then it try to append a number
2689
 * until it finds a number that is truly unique.
2690
 *
2691
 * The only purpose for `$term` is for appending a parent, if one exists.
2692
 *
2693
 * @since 2.3.0
2694
 *
2695
 * @global wpdb $wpdb WordPress database abstraction object.
2696
 *
2697
 * @param string $slug The string that will be tried for a unique slug.
2698
 * @param object $term The term object that the `$slug` will belong to.
2699
 * @return string Will return a true unique slug.
2700
 */
2701
function wp_unique_term_slug( $slug, $term ) {
2702
	global $wpdb;
2703
2704
	$needs_suffix = true;
2705
	$original_slug = $slug;
2706
2707
	// As of 4.1, duplicate slugs are allowed as long as they're in different taxonomies.
2708
	if ( ! term_exists( $slug ) || get_option( 'db_version' ) >= 30133 && ! get_term_by( 'slug', $slug, $term->taxonomy ) ) {
2709
		$needs_suffix = false;
2710
	}
2711
2712
	/*
2713
	 * If the taxonomy supports hierarchy and the term has a parent, make the slug unique
2714
	 * by incorporating parent slugs.
2715
	 */
2716
	$parent_suffix = '';
2717
	if ( $needs_suffix && is_taxonomy_hierarchical( $term->taxonomy ) && ! empty( $term->parent ) ) {
2718
		$the_parent = $term->parent;
2719
		while ( ! empty($the_parent) ) {
2720
			$parent_term = get_term($the_parent, $term->taxonomy);
2721
			if ( is_wp_error($parent_term) || empty($parent_term) )
2722
				break;
2723
			$parent_suffix .= '-' . $parent_term->slug;
2724
			if ( ! term_exists( $slug . $parent_suffix ) ) {
2725
				break;
2726
			}
2727
2728
			if ( empty($parent_term->parent) )
2729
				break;
2730
			$the_parent = $parent_term->parent;
2731
		}
2732
	}
2733
2734
	// If we didn't get a unique slug, try appending a number to make it unique.
2735
2736
	/**
2737
	 * Filters whether the proposed unique term slug is bad.
2738
	 *
2739
	 * @since 4.3.0
2740
	 *
2741
	 * @param bool   $needs_suffix Whether the slug needs to be made unique with a suffix.
2742
	 * @param string $slug         The slug.
2743
	 * @param object $term         Term object.
2744
	 */
2745
	if ( apply_filters( 'wp_unique_term_slug_is_bad_slug', $needs_suffix, $slug, $term ) ) {
2746
		if ( $parent_suffix ) {
2747
			$slug .= $parent_suffix;
2748
		} else {
2749
			if ( ! empty( $term->term_id ) )
2750
				$query = $wpdb->prepare( "SELECT slug FROM $wpdb->terms WHERE slug = %s AND term_id != %d", $slug, $term->term_id );
2751
			else
2752
				$query = $wpdb->prepare( "SELECT slug FROM $wpdb->terms WHERE slug = %s", $slug );
2753
2754
			if ( $wpdb->get_var( $query ) ) {
2755
				$num = 2;
2756 View Code Duplication
				do {
2757
					$alt_slug = $slug . "-$num";
2758
					$num++;
2759
					$slug_check = $wpdb->get_var( $wpdb->prepare( "SELECT slug FROM $wpdb->terms WHERE slug = %s", $alt_slug ) );
2760
				} while ( $slug_check );
2761
				$slug = $alt_slug;
2762
			}
2763
		}
2764
	}
2765
2766
	/**
2767
	 * Filters the unique term slug.
2768
	 *
2769
	 * @since 4.3.0
2770
	 *
2771
	 * @param string $slug          Unique term slug.
2772
	 * @param object $term          Term object.
2773
	 * @param string $original_slug Slug originally passed to the function for testing.
2774
	 */
2775
	return apply_filters( 'wp_unique_term_slug', $slug, $term, $original_slug );
2776
}
2777
2778
/**
2779
 * Update term based on arguments provided.
2780
 *
2781
 * The $args will indiscriminately override all values with the same field name.
2782
 * Care must be taken to not override important information need to update or
2783
 * update will fail (or perhaps create a new term, neither would be acceptable).
2784
 *
2785
 * Defaults will set 'alias_of', 'description', 'parent', and 'slug' if not
2786
 * defined in $args already.
2787
 *
2788
 * 'alias_of' will create a term group, if it doesn't already exist, and update
2789
 * it for the $term.
2790
 *
2791
 * If the 'slug' argument in $args is missing, then the 'name' in $args will be
2792
 * used. It should also be noted that if you set 'slug' and it isn't unique then
2793
 * a WP_Error will be passed back. If you don't pass any slug, then a unique one
2794
 * will be created for you.
2795
 *
2796
 * For what can be overrode in `$args`, check the term scheme can contain and stay
2797
 * away from the term keys.
2798
 *
2799
 * @since 2.3.0
2800
 *
2801
 * @global wpdb $wpdb WordPress database abstraction object.
2802
 *
2803
 * @param int          $term_id  The ID of the term
2804
 * @param string       $taxonomy The context in which to relate the term to the object.
2805
 * @param array|string $args     Optional. Array of get_terms() arguments. Default empty array.
2806
 * @return array|WP_Error Returns Term ID and Taxonomy Term ID
2807
 */
2808
function wp_update_term( $term_id, $taxonomy, $args = array() ) {
2809
	global $wpdb;
2810
2811
	if ( ! taxonomy_exists( $taxonomy ) ) {
2812
		return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
2813
	}
2814
2815
	$term_id = (int) $term_id;
2816
2817
	// First, get all of the original args
2818
	$term = get_term( $term_id, $taxonomy );
2819
2820
	if ( is_wp_error( $term ) ) {
2821
		return $term;
2822
	}
2823
2824
	if ( ! $term ) {
2825
		return new WP_Error( 'invalid_term', __( 'Empty Term' ) );
2826
	}
2827
2828
	$term = (array) $term->data;
2829
2830
	// Escape data pulled from DB.
2831
	$term = wp_slash( $term );
2832
2833
	// Merge old and new args with new args overwriting old ones.
2834
	$args = array_merge($term, $args);
2835
2836
	$defaults = array( 'alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => '');
2837
	$args = wp_parse_args($args, $defaults);
2838
	$args = sanitize_term($args, $taxonomy, 'db');
2839
	$parsed_args = $args;
2840
2841
	// expected_slashed ($name)
2842
	$name = wp_unslash( $args['name'] );
2843
	$description = wp_unslash( $args['description'] );
2844
2845
	$parsed_args['name'] = $name;
2846
	$parsed_args['description'] = $description;
2847
2848
	if ( '' == trim( $name ) ) {
2849
		return new WP_Error( 'empty_term_name', __( 'A name is required for this term.' ) );
2850
	}
2851
2852 View Code Duplication
	if ( $parsed_args['parent'] > 0 && ! term_exists( (int) $parsed_args['parent'] ) ) {
2853
		return new WP_Error( 'missing_parent', __( 'Parent term does not exist.' ) );
2854
	}
2855
2856
	$empty_slug = false;
2857
	if ( empty( $args['slug'] ) ) {
2858
		$empty_slug = true;
2859
		$slug = sanitize_title($name);
0 ignored issues
show
It seems like $name defined by wp_unslash($args['name']) on line 2842 can also be of type array; however, sanitize_title() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
2860
	} else {
2861
		$slug = $args['slug'];
2862
	}
2863
2864
	$parsed_args['slug'] = $slug;
2865
2866
	$term_group = isset( $parsed_args['term_group'] ) ? $parsed_args['term_group'] : 0;
2867 View Code Duplication
	if ( $args['alias_of'] ) {
2868
		$alias = get_term_by( 'slug', $args['alias_of'], $taxonomy );
2869
		if ( ! empty( $alias->term_group ) ) {
2870
			// The alias we want is already in a group, so let's use that one.
2871
			$term_group = $alias->term_group;
2872
		} elseif ( ! empty( $alias->term_id ) ) {
2873
			/*
2874
			 * The alias is not in a group, so we create a new one
2875
			 * and add the alias to it.
2876
			 */
2877
			$term_group = $wpdb->get_var("SELECT MAX(term_group) FROM $wpdb->terms") + 1;
2878
2879
			wp_update_term( $alias->term_id, $taxonomy, array(
2880
				'term_group' => $term_group,
2881
			) );
2882
		}
2883
2884
		$parsed_args['term_group'] = $term_group;
2885
	}
2886
2887
	/**
2888
	 * Filters the term parent.
2889
	 *
2890
	 * Hook to this filter to see if it will cause a hierarchy loop.
2891
	 *
2892
	 * @since 3.1.0
2893
	 *
2894
	 * @param int    $parent      ID of the parent term.
2895
	 * @param int    $term_id     Term ID.
2896
	 * @param string $taxonomy    Taxonomy slug.
2897
	 * @param array  $parsed_args An array of potentially altered update arguments for the given term.
2898
	 * @param array  $args        An array of update arguments for the given term.
2899
	 */
2900
	$parent = apply_filters( 'wp_update_term_parent', $args['parent'], $term_id, $taxonomy, $parsed_args, $args );
2901
2902
	// Check for duplicate slug
2903
	$duplicate = get_term_by( 'slug', $slug, $taxonomy );
2904
	if ( $duplicate && $duplicate->term_id != $term_id ) {
2905
		// If an empty slug was passed or the parent changed, reset the slug to something unique.
2906
		// Otherwise, bail.
2907
		if ( $empty_slug || ( $parent != $term['parent']) )
2908
			$slug = wp_unique_term_slug($slug, (object) $args);
2909
		else
2910
			return new WP_Error('duplicate_term_slug', sprintf(__('The slug &#8220;%s&#8221; is already in use by another term'), $slug));
2911
	}
2912
2913
	$tt_id = (int) $wpdb->get_var( $wpdb->prepare( "SELECT tt.term_taxonomy_id FROM $wpdb->term_taxonomy AS tt INNER JOIN $wpdb->terms AS t ON tt.term_id = t.term_id WHERE tt.taxonomy = %s AND t.term_id = %d", $taxonomy, $term_id) );
2914
2915
	// Check whether this is a shared term that needs splitting.
2916
	$_term_id = _split_shared_term( $term_id, $tt_id );
2917
	if ( ! is_wp_error( $_term_id ) ) {
2918
		$term_id = $_term_id;
2919
	}
2920
2921
	/**
2922
	 * Fires immediately before the given terms are edited.
2923
	 *
2924
	 * @since 2.9.0
2925
	 *
2926
	 * @param int    $term_id  Term ID.
2927
	 * @param string $taxonomy Taxonomy slug.
2928
	 */
2929
	do_action( 'edit_terms', $term_id, $taxonomy );
2930
	$wpdb->update($wpdb->terms, compact( 'name', 'slug', 'term_group' ), compact( 'term_id' ) );
2931
	if ( empty($slug) ) {
2932
		$slug = sanitize_title($name, $term_id);
0 ignored issues
show
It seems like $name defined by wp_unslash($args['name']) on line 2842 can also be of type array; however, sanitize_title() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
$term_id is of type integer|object<WP_Error>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2933
		$wpdb->update( $wpdb->terms, compact( 'slug' ), compact( 'term_id' ) );
2934
	}
2935
2936
	/**
2937
	 * Fires immediately after the given terms are edited.
2938
	 *
2939
	 * @since 2.9.0
2940
	 *
2941
	 * @param int    $term_id  Term ID
2942
	 * @param string $taxonomy Taxonomy slug.
2943
	 */
2944
	do_action( 'edited_terms', $term_id, $taxonomy );
2945
2946
	/**
2947
	 * Fires immediate before a term-taxonomy relationship is updated.
2948
	 *
2949
	 * @since 2.9.0
2950
	 *
2951
	 * @param int    $tt_id    Term taxonomy ID.
2952
	 * @param string $taxonomy Taxonomy slug.
2953
	 */
2954
	do_action( 'edit_term_taxonomy', $tt_id, $taxonomy );
2955
2956
	$wpdb->update( $wpdb->term_taxonomy, compact( 'term_id', 'taxonomy', 'description', 'parent' ), array( 'term_taxonomy_id' => $tt_id ) );
2957
2958
	/**
2959
	 * Fires immediately after a term-taxonomy relationship is updated.
2960
	 *
2961
	 * @since 2.9.0
2962
	 *
2963
	 * @param int    $tt_id    Term taxonomy ID.
2964
	 * @param string $taxonomy Taxonomy slug.
2965
	 */
2966
	do_action( 'edited_term_taxonomy', $tt_id, $taxonomy );
2967
2968
	/**
2969
	 * Fires after a term has been updated, but before the term cache has been cleaned.
2970
	 *
2971
	 * @since 2.3.0
2972
	 *
2973
	 * @param int    $term_id  Term ID.
2974
	 * @param int    $tt_id    Term taxonomy ID.
2975
	 * @param string $taxonomy Taxonomy slug.
2976
	 */
2977
	do_action( "edit_term", $term_id, $tt_id, $taxonomy );
2978
2979
	/**
2980
	 * Fires after a term in a specific taxonomy has been updated, but before the term
2981
	 * cache has been cleaned.
2982
	 *
2983
	 * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
2984
	 *
2985
	 * @since 2.3.0
2986
	 *
2987
	 * @param int $term_id Term ID.
2988
	 * @param int $tt_id   Term taxonomy ID.
2989
	 */
2990
	do_action( "edit_{$taxonomy}", $term_id, $tt_id );
2991
2992
	/** This filter is documented in wp-includes/taxonomy.php */
2993
	$term_id = apply_filters( 'term_id_filter', $term_id, $tt_id );
2994
2995
	clean_term_cache($term_id, $taxonomy);
2996
2997
	/**
2998
	 * Fires after a term has been updated, and the term cache has been cleaned.
2999
	 *
3000
	 * @since 2.3.0
3001
	 *
3002
	 * @param int    $term_id  Term ID.
3003
	 * @param int    $tt_id    Term taxonomy ID.
3004
	 * @param string $taxonomy Taxonomy slug.
3005
	 */
3006
	do_action( "edited_term", $term_id, $tt_id, $taxonomy );
3007
3008
	/**
3009
	 * Fires after a term for a specific taxonomy has been updated, and the term
3010
	 * cache has been cleaned.
3011
	 *
3012
	 * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
3013
	 *
3014
	 * @since 2.3.0
3015
	 *
3016
	 * @param int $term_id Term ID.
3017
	 * @param int $tt_id   Term taxonomy ID.
3018
	 */
3019
	do_action( "edited_{$taxonomy}", $term_id, $tt_id );
3020
3021
	return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
3022
}
3023
3024
/**
3025
 * Enable or disable term counting.
3026
 *
3027
 * @since 2.5.0
3028
 *
3029
 * @staticvar bool $_defer
3030
 *
3031
 * @param bool $defer Optional. Enable if true, disable if false.
3032
 * @return bool Whether term counting is enabled or disabled.
3033
 */
3034 View Code Duplication
function wp_defer_term_counting($defer=null) {
0 ignored issues
show
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3035
	static $_defer = false;
3036
3037
	if ( is_bool($defer) ) {
3038
		$_defer = $defer;
3039
		// flush any deferred counts
3040
		if ( !$defer )
3041
			wp_update_term_count( null, null, true );
3042
	}
3043
3044
	return $_defer;
3045
}
3046
3047
/**
3048
 * Updates the amount of terms in taxonomy.
3049
 *
3050
 * If there is a taxonomy callback applied, then it will be called for updating
3051
 * the count.
3052
 *
3053
 * The default action is to count what the amount of terms have the relationship
3054
 * of term ID. Once that is done, then update the database.
3055
 *
3056
 * @since 2.3.0
3057
 *
3058
 * @staticvar array $_deferred
3059
 *
3060
 * @param int|array $terms       The term_taxonomy_id of the terms.
3061
 * @param string    $taxonomy    The context of the term.
3062
 * @param bool      $do_deferred Whether to flush the deferred term counts too. Default false.
3063
 * @return bool If no terms will return false, and if successful will return true.
3064
 */
3065
function wp_update_term_count( $terms, $taxonomy, $do_deferred = false ) {
3066
	static $_deferred = array();
3067
3068
	if ( $do_deferred ) {
3069
		foreach ( (array) array_keys($_deferred) as $tax ) {
3070
			wp_update_term_count_now( $_deferred[$tax], $tax );
3071
			unset( $_deferred[$tax] );
3072
		}
3073
	}
3074
3075
	if ( empty($terms) )
3076
		return false;
3077
3078
	if ( !is_array($terms) )
3079
		$terms = array($terms);
3080
3081
	if ( wp_defer_term_counting() ) {
3082
		if ( !isset($_deferred[$taxonomy]) )
3083
			$_deferred[$taxonomy] = array();
3084
		$_deferred[$taxonomy] = array_unique( array_merge($_deferred[$taxonomy], $terms) );
3085
		return true;
3086
	}
3087
3088
	return wp_update_term_count_now( $terms, $taxonomy );
3089
}
3090
3091
/**
3092
 * Perform term count update immediately.
3093
 *
3094
 * @since 2.5.0
3095
 *
3096
 * @param array  $terms    The term_taxonomy_id of terms to update.
3097
 * @param string $taxonomy The context of the term.
3098
 * @return true Always true when complete.
3099
 */
3100
function wp_update_term_count_now( $terms, $taxonomy ) {
3101
	$terms = array_map('intval', $terms);
3102
3103
	$taxonomy = get_taxonomy($taxonomy);
3104
	if ( !empty($taxonomy->update_count_callback) ) {
3105
		call_user_func($taxonomy->update_count_callback, $terms, $taxonomy);
3106
	} else {
3107
		$object_types = (array) $taxonomy->object_type;
3108
		foreach ( $object_types as &$object_type ) {
3109
			if ( 0 === strpos( $object_type, 'attachment:' ) )
3110
				list( $object_type ) = explode( ':', $object_type );
3111
		}
3112
3113
		if ( $object_types == array_filter( $object_types, 'post_type_exists' ) ) {
3114
			// Only post types are attached to this taxonomy
3115
			_update_post_term_count( $terms, $taxonomy );
0 ignored issues
show
It seems like $taxonomy defined by get_taxonomy($taxonomy) on line 3103 can also be of type false; however, _update_post_term_count() does only seem to accept object, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
3116
		} else {
3117
			// Default count updater
3118
			_update_generic_term_count( $terms, $taxonomy );
0 ignored issues
show
It seems like $taxonomy defined by get_taxonomy($taxonomy) on line 3103 can also be of type false; however, _update_generic_term_count() does only seem to accept object, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
3119
		}
3120
	}
3121
3122
	clean_term_cache($terms, '', false);
3123
3124
	return true;
3125
}
3126
3127
//
3128
// Cache
3129
//
3130
3131
/**
3132
 * Removes the taxonomy relationship to terms from the cache.
3133
 *
3134
 * Will remove the entire taxonomy relationship containing term `$object_id`. The
3135
 * term IDs have to exist within the taxonomy `$object_type` for the deletion to
3136
 * take place.
3137
 *
3138
 * @since 2.3.0
3139
 *
3140
 * @global bool $_wp_suspend_cache_invalidation
3141
 *
3142
 * @see get_object_taxonomies() for more on $object_type.
3143
 *
3144
 * @param int|array    $object_ids  Single or list of term object ID(s).
3145
 * @param array|string $object_type The taxonomy object type.
3146
 */
3147
function clean_object_term_cache($object_ids, $object_type) {
3148
	global $_wp_suspend_cache_invalidation;
3149
3150
	if ( ! empty( $_wp_suspend_cache_invalidation ) ) {
3151
		return;
3152
	}
3153
3154
	if ( !is_array($object_ids) )
3155
		$object_ids = array($object_ids);
3156
3157
	$taxonomies = get_object_taxonomies( $object_type );
3158
3159
	foreach ( $object_ids as $id ) {
3160
		foreach ( $taxonomies as $taxonomy ) {
3161
			wp_cache_delete($id, "{$taxonomy}_relationships");
3162
		}
3163
	}
3164
3165
	/**
3166
	 * Fires after the object term cache has been cleaned.
3167
	 *
3168
	 * @since 2.5.0
3169
	 *
3170
	 * @param array  $object_ids An array of object IDs.
3171
	 * @param string $objet_type Object type.
3172
	 */
3173
	do_action( 'clean_object_term_cache', $object_ids, $object_type );
3174
}
3175
3176
/**
3177
 * Will remove all of the term ids from the cache.
3178
 *
3179
 * @since 2.3.0
3180
 *
3181
 * @global wpdb $wpdb WordPress database abstraction object.
3182
 * @global bool $_wp_suspend_cache_invalidation
3183
 *
3184
 * @param int|array $ids            Single or list of Term IDs.
3185
 * @param string    $taxonomy       Optional. Can be empty and will assume `tt_ids`, else will use for context.
3186
 *                                  Default empty.
3187
 * @param bool      $clean_taxonomy Optional. Whether to clean taxonomy wide caches (true), or just individual
3188
 *                                  term object caches (false). Default true.
3189
 */
3190
function clean_term_cache($ids, $taxonomy = '', $clean_taxonomy = true) {
3191
	global $wpdb, $_wp_suspend_cache_invalidation;
3192
3193
	if ( ! empty( $_wp_suspend_cache_invalidation ) ) {
3194
		return;
3195
	}
3196
3197
	if ( !is_array($ids) )
3198
		$ids = array($ids);
3199
3200
	$taxonomies = array();
3201
	// If no taxonomy, assume tt_ids.
3202
	if ( empty($taxonomy) ) {
3203
		$tt_ids = array_map('intval', $ids);
3204
		$tt_ids = implode(', ', $tt_ids);
3205
		$terms = $wpdb->get_results("SELECT term_id, taxonomy FROM $wpdb->term_taxonomy WHERE term_taxonomy_id IN ($tt_ids)");
3206
		$ids = array();
3207
		foreach ( (array) $terms as $term ) {
3208
			$taxonomies[] = $term->taxonomy;
3209
			$ids[] = $term->term_id;
3210
			wp_cache_delete( $term->term_id, 'terms' );
3211
		}
3212
		$taxonomies = array_unique($taxonomies);
3213
	} else {
3214
		$taxonomies = array($taxonomy);
3215
		foreach ( $taxonomies as $taxonomy ) {
3216
			foreach ( $ids as $id ) {
3217
				wp_cache_delete( $id, 'terms' );
3218
			}
3219
		}
3220
	}
3221
3222
	foreach ( $taxonomies as $taxonomy ) {
3223
		if ( $clean_taxonomy ) {
3224
			wp_cache_delete('all_ids', $taxonomy);
3225
			wp_cache_delete('get', $taxonomy);
3226
			delete_option("{$taxonomy}_children");
3227
			// Regenerate {$taxonomy}_children
3228
			_get_term_hierarchy($taxonomy);
3229
		}
3230
3231
		/**
3232
		 * Fires once after each taxonomy's term cache has been cleaned.
3233
		 *
3234
		 * @since 2.5.0
3235
		 * @since 4.5.0 Added the `$clean_taxonomy` parameter.
3236
		 *
3237
		 * @param array  $ids            An array of term IDs.
3238
		 * @param string $taxonomy       Taxonomy slug.
3239
		 * @param bool   $clean_taxonomy Whether or not to clean taxonomy-wide caches
3240
		 */
3241
		do_action( 'clean_term_cache', $ids, $taxonomy, $clean_taxonomy );
3242
	}
3243
3244
	wp_cache_set( 'last_changed', microtime(), 'terms' );
3245
}
3246
3247
/**
3248
 * Retrieves the taxonomy relationship to the term object id.
3249
 *
3250
 * Upstream functions (like get_the_terms() and is_object_in_term()) are
3251
 * responsible for populating the object-term relationship cache. The current
3252
 * function only fetches relationship data that is already in the cache.
3253
 *
3254
 * @since 2.3.0
3255
 *
3256
 * @param int    $id       Term object ID.
3257
 * @param string $taxonomy Taxonomy name.
3258
 * @return bool|array Array of `WP_Term` objects, if cached False if cache is empty for `$taxonomy` and `$id`.
3259
 */
3260
function get_object_term_cache( $id, $taxonomy ) {
3261
	$_term_ids = wp_cache_get( $id, "{$taxonomy}_relationships" );
3262
3263
	// We leave the priming of relationship caches to upstream functions.
3264
	if ( false === $_term_ids ) {
3265
		return false;
3266
	}
3267
3268
	// Backward compatibility for if a plugin is putting objects into the cache, rather than IDs.
3269
	$term_ids = array();
3270
	foreach ( $_term_ids as $term_id ) {
3271
		if ( is_numeric( $term_id ) ) {
3272
			$term_ids[] = intval( $term_id );
3273
		} elseif ( isset( $term_id->term_id ) ) {
3274
			$term_ids[] = intval( $term_id->term_id );
3275
		}
3276
	}
3277
3278
	// Fill the term objects.
3279
	_prime_term_caches( $term_ids );
3280
3281
	$terms = array();
3282
	foreach ( $term_ids as $term_id ) {
3283
		$terms[] = wp_cache_get( $term_id, 'terms' );
3284
	}
3285
3286
	return array_map( 'get_term', $terms );
3287
}
3288
3289
/**
3290
 * Updates the cache for the given term object ID(s).
3291
 *
3292
 * Note: Due to performance concerns, great care should be taken to only update
3293
 * term caches when necessary. Processing time can increase exponentially depending
3294
 * on both the number of passed term IDs and the number of taxonomies those terms
3295
 * belong to.
3296
 *
3297
 * Caches will only be updated for terms not already cached.
3298
 *
3299
 * @since 2.3.0
3300
 *
3301
 * @param string|array $object_ids  Comma-separated list or array of term object IDs.
3302
 * @param array|string $object_type The taxonomy object type.
3303
 * @return void|false False if all of the terms in `$object_ids` are already cached.
3304
 */
3305
function update_object_term_cache($object_ids, $object_type) {
3306
	if ( empty($object_ids) )
3307
		return;
3308
3309
	if ( !is_array($object_ids) )
3310
		$object_ids = explode(',', $object_ids);
3311
3312
	$object_ids = array_map('intval', $object_ids);
3313
3314
	$taxonomies = get_object_taxonomies($object_type);
3315
3316
	$ids = array();
3317
	foreach ( (array) $object_ids as $id ) {
3318
		foreach ( $taxonomies as $taxonomy ) {
3319
			if ( false === wp_cache_get($id, "{$taxonomy}_relationships") ) {
3320
				$ids[] = $id;
3321
				break;
3322
			}
3323
		}
3324
	}
3325
3326
	if ( empty( $ids ) )
3327
		return false;
3328
3329
	$terms = wp_get_object_terms( $ids, $taxonomies, array(
3330
		'fields' => 'all_with_object_id',
3331
		'orderby' => 'name',
3332
		'update_term_meta_cache' => false,
3333
	) );
3334
3335
	$object_terms = array();
3336
	foreach ( (array) $terms as $term ) {
3337
		$object_terms[ $term->object_id ][ $term->taxonomy ][] = $term->term_id;
3338
	}
3339
3340
	foreach ( $ids as $id ) {
3341
		foreach ( $taxonomies as $taxonomy ) {
3342
			if ( ! isset($object_terms[$id][$taxonomy]) ) {
3343
				if ( !isset($object_terms[$id]) )
3344
					$object_terms[$id] = array();
3345
				$object_terms[$id][$taxonomy] = array();
3346
			}
3347
		}
3348
	}
3349
3350
	foreach ( $object_terms as $id => $value ) {
3351
		foreach ( $value as $taxonomy => $terms ) {
3352
			wp_cache_add( $id, $terms, "{$taxonomy}_relationships" );
3353
		}
3354
	}
3355
}
3356
3357
/**
3358
 * Updates Terms to Taxonomy in cache.
3359
 *
3360
 * @since 2.3.0
3361
 *
3362
 * @param array  $terms    List of term objects to change.
3363
 * @param string $taxonomy Optional. Update Term to this taxonomy in cache. Default empty.
3364
 */
3365
function update_term_cache( $terms, $taxonomy = '' ) {
3366
	foreach ( (array) $terms as $term ) {
3367
		// Create a copy in case the array was passed by reference.
3368
		$_term = clone $term;
3369
3370
		// Object ID should not be cached.
3371
		unset( $_term->object_id );
3372
3373
		wp_cache_add( $term->term_id, $_term, 'terms' );
3374
	}
3375
}
3376
3377
//
3378
// Private
3379
//
3380
3381
/**
3382
 * Retrieves children of taxonomy as Term IDs.
3383
 *
3384
 * @ignore
3385
 * @since 2.3.0
3386
 *
3387
 * @param string $taxonomy Taxonomy name.
3388
 * @return array Empty if $taxonomy isn't hierarchical or returns children as Term IDs.
3389
 */
3390
function _get_term_hierarchy( $taxonomy ) {
3391
	if ( !is_taxonomy_hierarchical($taxonomy) )
3392
		return array();
3393
	$children = get_option("{$taxonomy}_children");
3394
3395
	if ( is_array($children) )
3396
		return $children;
3397
	$children = array();
3398
	$terms = get_terms($taxonomy, array('get' => 'all', 'orderby' => 'id', 'fields' => 'id=>parent'));
3399
	foreach ( $terms as $term_id => $parent ) {
0 ignored issues
show
The expression $terms of type array|integer|object<WP_Error> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
3400
		if ( $parent > 0 )
3401
			$children[$parent][] = $term_id;
3402
	}
3403
	update_option("{$taxonomy}_children", $children);
3404
3405
	return $children;
3406
}
3407
3408
/**
3409
 * Get the subset of $terms that are descendants of $term_id.
3410
 *
3411
 * If `$terms` is an array of objects, then _get_term_children() returns an array of objects.
3412
 * If `$terms` is an array of IDs, then _get_term_children() returns an array of IDs.
3413
 *
3414
 * @access private
3415
 * @since 2.3.0
3416
 *
3417
 * @param int    $term_id   The ancestor term: all returned terms should be descendants of `$term_id`.
3418
 * @param array  $terms     The set of terms - either an array of term objects or term IDs - from which those that
3419
 *                          are descendants of $term_id will be chosen.
3420
 * @param string $taxonomy  The taxonomy which determines the hierarchy of the terms.
3421
 * @param array  $ancestors Optional. Term ancestors that have already been identified. Passed by reference, to keep
3422
 *                          track of found terms when recursing the hierarchy. The array of located ancestors is used
3423
 *                          to prevent infinite recursion loops. For performance, `term_ids` are used as array keys,
3424
 *                          with 1 as value. Default empty array.
3425
 * @return array|WP_Error The subset of $terms that are descendants of $term_id.
3426
 */
3427
function _get_term_children( $term_id, $terms, $taxonomy, &$ancestors = array() ) {
3428
	$empty_array = array();
3429
	if ( empty($terms) )
3430
		return $empty_array;
3431
3432
	$term_list = array();
3433
	$has_children = _get_term_hierarchy($taxonomy);
3434
3435
	if  ( ( 0 != $term_id ) && ! isset($has_children[$term_id]) )
3436
		return $empty_array;
3437
3438
	// Include the term itself in the ancestors array, so we can properly detect when a loop has occurred.
3439
	if ( empty( $ancestors ) ) {
3440
		$ancestors[ $term_id ] = 1;
3441
	}
3442
3443
	foreach ( (array) $terms as $term ) {
3444
		$use_id = false;
3445 View Code Duplication
		if ( !is_object($term) ) {
3446
			$term = get_term($term, $taxonomy);
3447
			if ( is_wp_error( $term ) )
3448
				return $term;
3449
			$use_id = true;
3450
		}
3451
3452
		// Don't recurse if we've already identified the term as a child - this indicates a loop.
3453
		if ( isset( $ancestors[ $term->term_id ] ) ) {
3454
			continue;
3455
		}
3456
3457
		if ( $term->parent == $term_id ) {
3458
			if ( $use_id )
3459
				$term_list[] = $term->term_id;
3460
			else
3461
				$term_list[] = $term;
3462
3463
			if ( !isset($has_children[$term->term_id]) )
3464
				continue;
3465
3466
			$ancestors[ $term->term_id ] = 1;
3467
3468
			if ( $children = _get_term_children( $term->term_id, $terms, $taxonomy, $ancestors) )
3469
				$term_list = array_merge($term_list, $children);
3470
		}
3471
	}
3472
3473
	return $term_list;
3474
}
3475
3476
/**
3477
 * Add count of children to parent count.
3478
 *
3479
 * Recalculates term counts by including items from child terms. Assumes all
3480
 * relevant children are already in the $terms argument.
3481
 *
3482
 * @access private
3483
 * @since 2.3.0
3484
 *
3485
 * @global wpdb $wpdb WordPress database abstraction object.
3486
 *
3487
 * @param array  $terms    List of term objects, passed by reference.
3488
 * @param string $taxonomy Term context.
3489
 */
3490
function _pad_term_counts( &$terms, $taxonomy ) {
3491
	global $wpdb;
3492
3493
	// This function only works for hierarchical taxonomies like post categories.
3494
	if ( !is_taxonomy_hierarchical( $taxonomy ) )
3495
		return;
3496
3497
	$term_hier = _get_term_hierarchy($taxonomy);
3498
3499
	if ( empty($term_hier) )
3500
		return;
3501
3502
	$term_items = array();
3503
	$terms_by_id = array();
3504
	$term_ids = array();
3505
3506
	foreach ( (array) $terms as $key => $term ) {
3507
		$terms_by_id[$term->term_id] = & $terms[$key];
3508
		$term_ids[$term->term_taxonomy_id] = $term->term_id;
3509
	}
3510
3511
	// Get the object and term ids and stick them in a lookup table.
3512
	$tax_obj = get_taxonomy($taxonomy);
3513
	$object_types = esc_sql($tax_obj->object_type);
3514
	$results = $wpdb->get_results("SELECT object_id, term_taxonomy_id FROM $wpdb->term_relationships INNER JOIN $wpdb->posts ON object_id = ID WHERE term_taxonomy_id IN (" . implode(',', array_keys($term_ids)) . ") AND post_type IN ('" . implode("', '", $object_types) . "') AND post_status = 'publish'");
3515
	foreach ( $results as $row ) {
3516
		$id = $term_ids[$row->term_taxonomy_id];
3517
		$term_items[$id][$row->object_id] = isset($term_items[$id][$row->object_id]) ? ++$term_items[$id][$row->object_id] : 1;
3518
	}
3519
3520
	// Touch every ancestor's lookup row for each post in each term.
3521
	foreach ( $term_ids as $term_id ) {
3522
		$child = $term_id;
3523
		$ancestors = array();
3524
		while ( !empty( $terms_by_id[$child] ) && $parent = $terms_by_id[$child]->parent ) {
3525
			$ancestors[] = $child;
3526
			if ( !empty( $term_items[$term_id] ) )
3527
				foreach ( $term_items[$term_id] as $item_id => $touches ) {
3528
					$term_items[$parent][$item_id] = isset($term_items[$parent][$item_id]) ? ++$term_items[$parent][$item_id]: 1;
3529
				}
3530
			$child = $parent;
3531
3532
			if ( in_array( $parent, $ancestors ) ) {
3533
				break;
3534
			}
3535
		}
3536
	}
3537
3538
	// Transfer the touched cells.
3539
	foreach ( (array) $term_items as $id => $items )
3540
		if ( isset($terms_by_id[$id]) )
3541
			$terms_by_id[$id]->count = count($items);
3542
}
3543
3544
/**
3545
 * Adds any terms from the given IDs to the cache that do not already exist in cache.
3546
 *
3547
 * @since 4.6.0
3548
 * @access private
3549
 *
3550
 * @global wpdb $wpdb WordPress database abstraction object.
3551
 *
3552
 * @param array $term_ids          Array of term IDs.
3553
 * @param bool  $update_meta_cache Optional. Whether to update the meta cache. Default true.
3554
 */
3555 View Code Duplication
function _prime_term_caches( $term_ids, $update_meta_cache = true ) {
0 ignored issues
show
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3556
	global $wpdb;
3557
3558
	$non_cached_ids = _get_non_cached_ids( $term_ids, 'terms' );
3559
	if ( ! empty( $non_cached_ids ) ) {
3560
		$fresh_terms = $wpdb->get_results( sprintf( "SELECT t.*, tt.* FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id WHERE t.term_id IN (%s)", join( ",", array_map( 'intval', $non_cached_ids ) ) ) );
3561
3562
		update_term_cache( $fresh_terms, $update_meta_cache );
0 ignored issues
show
$update_meta_cache is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
3563
3564
		if ( $update_meta_cache ) {
3565
			update_termmeta_cache( $non_cached_ids );
3566
		}
3567
	}
3568
}
3569
3570
//
3571
// Default callbacks
3572
//
3573
3574
/**
3575
 * Will update term count based on object types of the current taxonomy.
3576
 *
3577
 * Private function for the default callback for post_tag and category
3578
 * taxonomies.
3579
 *
3580
 * @access private
3581
 * @since 2.3.0
3582
 *
3583
 * @global wpdb $wpdb WordPress database abstraction object.
3584
 *
3585
 * @param array  $terms    List of Term taxonomy IDs.
3586
 * @param object $taxonomy Current taxonomy object of terms.
3587
 */
3588
function _update_post_term_count( $terms, $taxonomy ) {
3589
	global $wpdb;
3590
3591
	$object_types = (array) $taxonomy->object_type;
3592
3593
	foreach ( $object_types as &$object_type )
3594
		list( $object_type ) = explode( ':', $object_type );
3595
3596
	$object_types = array_unique( $object_types );
3597
3598
	if ( false !== ( $check_attachments = array_search( 'attachment', $object_types ) ) ) {
3599
		unset( $object_types[ $check_attachments ] );
3600
		$check_attachments = true;
3601
	}
3602
3603
	if ( $object_types )
3604
		$object_types = esc_sql( array_filter( $object_types, 'post_type_exists' ) );
3605
3606
	foreach ( (array) $terms as $term ) {
3607
		$count = 0;
3608
3609
		// Attachments can be 'inherit' status, we need to base count off the parent's status if so.
3610
		if ( $check_attachments )
3611
			$count += (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_relationships, $wpdb->posts p1 WHERE p1.ID = $wpdb->term_relationships.object_id AND ( post_status = 'publish' OR ( post_status = 'inherit' AND post_parent > 0 AND ( SELECT post_status FROM $wpdb->posts WHERE ID = p1.post_parent ) = 'publish' ) ) AND post_type = 'attachment' AND term_taxonomy_id = %d", $term ) );
3612
3613
		if ( $object_types )
3614
			$count += (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_relationships, $wpdb->posts WHERE $wpdb->posts.ID = $wpdb->term_relationships.object_id AND post_status = 'publish' AND post_type IN ('" . implode("', '", $object_types ) . "') AND term_taxonomy_id = %d", $term ) );
3615
3616
		/** This action is documented in wp-includes/taxonomy.php */
3617
		do_action( 'edit_term_taxonomy', $term, $taxonomy->name );
3618
		$wpdb->update( $wpdb->term_taxonomy, compact( 'count' ), array( 'term_taxonomy_id' => $term ) );
3619
3620
		/** This action is documented in wp-includes/taxonomy.php */
3621
		do_action( 'edited_term_taxonomy', $term, $taxonomy->name );
3622
	}
3623
}
3624
3625
/**
3626
 * Will update term count based on number of objects.
3627
 *
3628
 * Default callback for the 'link_category' taxonomy.
3629
 *
3630
 * @since 3.3.0
3631
 *
3632
 * @global wpdb $wpdb WordPress database abstraction object.
3633
 *
3634
 * @param array  $terms    List of term taxonomy IDs.
3635
 * @param object $taxonomy Current taxonomy object of terms.
3636
 */
3637
function _update_generic_term_count( $terms, $taxonomy ) {
3638
	global $wpdb;
3639
3640
	foreach ( (array) $terms as $term ) {
3641
		$count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_relationships WHERE term_taxonomy_id = %d", $term ) );
3642
3643
		/** This action is documented in wp-includes/taxonomy.php */
3644
		do_action( 'edit_term_taxonomy', $term, $taxonomy->name );
3645
		$wpdb->update( $wpdb->term_taxonomy, compact( 'count' ), array( 'term_taxonomy_id' => $term ) );
3646
3647
		/** This action is documented in wp-includes/taxonomy.php */
3648
		do_action( 'edited_term_taxonomy', $term, $taxonomy->name );
3649
	}
3650
}
3651
3652
/**
3653
 * Create a new term for a term_taxonomy item that currently shares its term
3654
 * with another term_taxonomy.
3655
 *
3656
 * @ignore
3657
 * @since 4.2.0
3658
 * @since 4.3.0 Introduced `$record` parameter. Also, `$term_id` and
3659
 *              `$term_taxonomy_id` can now accept objects.
3660
 *
3661
 * @global wpdb $wpdb WordPress database abstraction object.
3662
 *
3663
 * @param int|object $term_id          ID of the shared term, or the shared term object.
3664
 * @param int|object $term_taxonomy_id ID of the term_taxonomy item to receive a new term, or the term_taxonomy object
3665
 *                                     (corresponding to a row from the term_taxonomy table).
3666
 * @param bool       $record           Whether to record data about the split term in the options table. The recording
3667
 *                                     process has the potential to be resource-intensive, so during batch operations
3668
 *                                     it can be beneficial to skip inline recording and do it just once, after the
3669
 *                                     batch is processed. Only set this to `false` if you know what you are doing.
3670
 *                                     Default: true.
3671
 * @return int|WP_Error When the current term does not need to be split (or cannot be split on the current
3672
 *                      database schema), `$term_id` is returned. When the term is successfully split, the
3673
 *                      new term_id is returned. A WP_Error is returned for miscellaneous errors.
3674
 */
3675
function _split_shared_term( $term_id, $term_taxonomy_id, $record = true ) {
3676
	global $wpdb;
3677
3678
	if ( is_object( $term_id ) ) {
3679
		$shared_term = $term_id;
3680
		$term_id = intval( $shared_term->term_id );
3681
	}
3682
3683
	if ( is_object( $term_taxonomy_id ) ) {
3684
		$term_taxonomy = $term_taxonomy_id;
3685
		$term_taxonomy_id = intval( $term_taxonomy->term_taxonomy_id );
3686
	}
3687
3688
	// If there are no shared term_taxonomy rows, there's nothing to do here.
3689
	$shared_tt_count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_taxonomy tt WHERE tt.term_id = %d AND tt.term_taxonomy_id != %d", $term_id, $term_taxonomy_id ) );
3690
3691
	if ( ! $shared_tt_count ) {
3692
		return $term_id;
3693
	}
3694
3695
	/*
3696
	 * Verify that the term_taxonomy_id passed to the function is actually associated with the term_id.
3697
	 * If there's a mismatch, it may mean that the term is already split. Return the actual term_id from the db.
3698
	 */
3699
	$check_term_id = $wpdb->get_var( $wpdb->prepare( "SELECT term_id FROM $wpdb->term_taxonomy WHERE term_taxonomy_id = %d", $term_taxonomy_id ) );
3700
	if ( $check_term_id != $term_id ) {
3701
		return $check_term_id;
3702
	}
3703
3704
	// Pull up data about the currently shared slug, which we'll use to populate the new one.
3705
	if ( empty( $shared_term ) ) {
3706
		$shared_term = $wpdb->get_row( $wpdb->prepare( "SELECT t.* FROM $wpdb->terms t WHERE t.term_id = %d", $term_id ) );
3707
	}
3708
3709
	$new_term_data = array(
3710
		'name' => $shared_term->name,
3711
		'slug' => $shared_term->slug,
3712
		'term_group' => $shared_term->term_group,
3713
	);
3714
3715 View Code Duplication
	if ( false === $wpdb->insert( $wpdb->terms, $new_term_data ) ) {
3716
		return new WP_Error( 'db_insert_error', __( 'Could not split shared term.' ), $wpdb->last_error );
3717
	}
3718
3719
	$new_term_id = (int) $wpdb->insert_id;
3720
3721
	// Update the existing term_taxonomy to point to the newly created term.
3722
	$wpdb->update( $wpdb->term_taxonomy,
3723
		array( 'term_id' => $new_term_id ),
3724
		array( 'term_taxonomy_id' => $term_taxonomy_id )
3725
	);
3726
3727
	// Reassign child terms to the new parent.
3728
	if ( empty( $term_taxonomy ) ) {
3729
		$term_taxonomy = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->term_taxonomy WHERE term_taxonomy_id = %d", $term_taxonomy_id ) );
3730
	}
3731
3732
	$children_tt_ids = $wpdb->get_col( $wpdb->prepare( "SELECT term_taxonomy_id FROM $wpdb->term_taxonomy WHERE parent = %d AND taxonomy = %s", $term_id, $term_taxonomy->taxonomy ) );
3733
	if ( ! empty( $children_tt_ids ) ) {
3734
		foreach ( $children_tt_ids as $child_tt_id ) {
3735
			$wpdb->update( $wpdb->term_taxonomy,
3736
				array( 'parent' => $new_term_id ),
3737
				array( 'term_taxonomy_id' => $child_tt_id )
3738
			);
3739
			clean_term_cache( $term_id, $term_taxonomy->taxonomy );
3740
		}
3741
	} else {
3742
		// If the term has no children, we must force its taxonomy cache to be rebuilt separately.
3743
		clean_term_cache( $new_term_id, $term_taxonomy->taxonomy );
3744
	}
3745
3746
	// Clean the cache for term taxonomies formerly shared with the current term.
3747
	$shared_term_taxonomies = $wpdb->get_row( $wpdb->prepare( "SELECT taxonomy FROM $wpdb->term_taxonomy WHERE term_id = %d", $term_id ) );
3748
	if ( $shared_term_taxonomies ) {
3749
		foreach ( $shared_term_taxonomies as $shared_term_taxonomy ) {
3750
			clean_term_cache( $term_id, $shared_term_taxonomy );
3751
		}
3752
	}
3753
3754
	// Keep a record of term_ids that have been split, keyed by old term_id. See wp_get_split_term().
3755
	if ( $record ) {
3756
		$split_term_data = get_option( '_split_terms', array() );
3757
		if ( ! isset( $split_term_data[ $term_id ] ) ) {
3758
			$split_term_data[ $term_id ] = array();
3759
		}
3760
3761
		$split_term_data[ $term_id ][ $term_taxonomy->taxonomy ] = $new_term_id;
3762
		update_option( '_split_terms', $split_term_data );
3763
	}
3764
3765
	// If we've just split the final shared term, set the "finished" flag.
3766
	$shared_terms_exist = $wpdb->get_results(
3767
		"SELECT tt.term_id, t.*, count(*) as term_tt_count FROM {$wpdb->term_taxonomy} tt
3768
		 LEFT JOIN {$wpdb->terms} t ON t.term_id = tt.term_id
3769
		 GROUP BY t.term_id
3770
		 HAVING term_tt_count > 1
3771
		 LIMIT 1"
3772
	);
3773
	if ( ! $shared_terms_exist ) {
3774
		update_option( 'finished_splitting_shared_terms', true );
3775
	}
3776
3777
	/**
3778
	 * Fires after a previously shared taxonomy term is split into two separate terms.
3779
	 *
3780
	 * @since 4.2.0
3781
	 *
3782
	 * @param int    $term_id          ID of the formerly shared term.
3783
	 * @param int    $new_term_id      ID of the new term created for the $term_taxonomy_id.
3784
	 * @param int    $term_taxonomy_id ID for the term_taxonomy row affected by the split.
3785
	 * @param string $taxonomy         Taxonomy for the split term.
3786
	 */
3787
	do_action( 'split_shared_term', $term_id, $new_term_id, $term_taxonomy_id, $term_taxonomy->taxonomy );
3788
3789
	return $new_term_id;
3790
}
3791
3792
/**
3793
 * Splits a batch of shared taxonomy terms.
3794
 *
3795
 * @since 4.3.0
3796
 *
3797
 * @global wpdb $wpdb WordPress database abstraction object.
3798
 */
3799
function _wp_batch_split_terms() {
3800
	global $wpdb;
3801
3802
	$lock_name = 'term_split.lock';
3803
3804
	// Try to lock.
3805
	$lock_result = $wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO `$wpdb->options` ( `option_name`, `option_value`, `autoload` ) VALUES (%s, %s, 'no') /* LOCK */", $lock_name, time() ) );
3806
3807
	if ( ! $lock_result ) {
3808
		$lock_result = get_option( $lock_name );
3809
3810
		// Bail if we were unable to create a lock, or if the existing lock is still valid.
3811
		if ( ! $lock_result || ( $lock_result > ( time() - HOUR_IN_SECONDS ) ) ) {
3812
			wp_schedule_single_event( time() + ( 5 * MINUTE_IN_SECONDS ), 'wp_split_shared_term_batch' );
3813
			return;
3814
		}
3815
	}
3816
3817
	// Update the lock, as by this point we've definitely got a lock, just need to fire the actions.
3818
	update_option( $lock_name, time() );
3819
3820
	// Get a list of shared terms (those with more than one associated row in term_taxonomy).
3821
	$shared_terms = $wpdb->get_results(
3822
		"SELECT tt.term_id, t.*, count(*) as term_tt_count FROM {$wpdb->term_taxonomy} tt
3823
		 LEFT JOIN {$wpdb->terms} t ON t.term_id = tt.term_id
3824
		 GROUP BY t.term_id
3825
		 HAVING term_tt_count > 1
3826
		 LIMIT 10"
3827
	);
3828
3829
	// No more terms, we're done here.
3830
	if ( ! $shared_terms ) {
3831
		update_option( 'finished_splitting_shared_terms', true );
3832
		delete_option( $lock_name );
3833
		return;
3834
	}
3835
3836
	// Shared terms found? We'll need to run this script again.
3837
	wp_schedule_single_event( time() + ( 2 * MINUTE_IN_SECONDS ), 'wp_split_shared_term_batch' );
3838
3839
	// Rekey shared term array for faster lookups.
3840
	$_shared_terms = array();
3841
	foreach ( $shared_terms as $shared_term ) {
3842
		$term_id = intval( $shared_term->term_id );
3843
		$_shared_terms[ $term_id ] = $shared_term;
3844
	}
3845
	$shared_terms = $_shared_terms;
3846
3847
	// Get term taxonomy data for all shared terms.
3848
	$shared_term_ids = implode( ',', array_keys( $shared_terms ) );
3849
	$shared_tts = $wpdb->get_results( "SELECT * FROM {$wpdb->term_taxonomy} WHERE `term_id` IN ({$shared_term_ids})" );
3850
3851
	// Split term data recording is slow, so we do it just once, outside the loop.
3852
	$split_term_data = get_option( '_split_terms', array() );
3853
	$skipped_first_term = $taxonomies = array();
3854
	foreach ( $shared_tts as $shared_tt ) {
3855
		$term_id = intval( $shared_tt->term_id );
3856
3857
		// Don't split the first tt belonging to a given term_id.
3858
		if ( ! isset( $skipped_first_term[ $term_id ] ) ) {
3859
			$skipped_first_term[ $term_id ] = 1;
3860
			continue;
3861
		}
3862
3863
		if ( ! isset( $split_term_data[ $term_id ] ) ) {
3864
			$split_term_data[ $term_id ] = array();
3865
		}
3866
3867
		// Keep track of taxonomies whose hierarchies need flushing.
3868
		if ( ! isset( $taxonomies[ $shared_tt->taxonomy ] ) ) {
3869
			$taxonomies[ $shared_tt->taxonomy ] = 1;
3870
		}
3871
3872
		// Split the term.
3873
		$split_term_data[ $term_id ][ $shared_tt->taxonomy ] = _split_shared_term( $shared_terms[ $term_id ], $shared_tt, false );
3874
	}
3875
3876
	// Rebuild the cached hierarchy for each affected taxonomy.
3877
	foreach ( array_keys( $taxonomies ) as $tax ) {
3878
		delete_option( "{$tax}_children" );
3879
		_get_term_hierarchy( $tax );
3880
	}
3881
3882
	update_option( '_split_terms', $split_term_data );
3883
3884
	delete_option( $lock_name );
3885
}
3886
3887
/**
3888
 * In order to avoid the _wp_batch_split_terms() job being accidentally removed,
3889
 * check that it's still scheduled while we haven't finished splitting terms.
3890
 *
3891
 * @ignore
3892
 * @since 4.3.0
3893
 */
3894
function _wp_check_for_scheduled_split_terms() {
3895
	if ( ! get_option( 'finished_splitting_shared_terms' ) && ! wp_next_scheduled( 'wp_split_shared_term_batch' ) ) {
3896
		wp_schedule_single_event( time() + MINUTE_IN_SECONDS, 'wp_split_shared_term_batch' );
3897
	}
3898
}
3899
3900
/**
3901
 * Check default categories when a term gets split to see if any of them need to be updated.
3902
 *
3903
 * @ignore
3904
 * @since 4.2.0
3905
 *
3906
 * @param int    $term_id          ID of the formerly shared term.
3907
 * @param int    $new_term_id      ID of the new term created for the $term_taxonomy_id.
3908
 * @param int    $term_taxonomy_id ID for the term_taxonomy row affected by the split.
3909
 * @param string $taxonomy         Taxonomy for the split term.
3910
 */
3911
function _wp_check_split_default_terms( $term_id, $new_term_id, $term_taxonomy_id, $taxonomy ) {
3912
	if ( 'category' != $taxonomy ) {
3913
		return;
3914
	}
3915
3916
	foreach ( array( 'default_category', 'default_link_category', 'default_email_category' ) as $option ) {
3917
		if ( $term_id == get_option( $option, -1 ) ) {
3918
			update_option( $option, $new_term_id );
3919
		}
3920
	}
3921
}
3922
3923
/**
3924
 * Check menu items when a term gets split to see if any of them need to be updated.
3925
 *
3926
 * @ignore
3927
 * @since 4.2.0
3928
 *
3929
 * @global wpdb $wpdb WordPress database abstraction object.
3930
 *
3931
 * @param int    $term_id          ID of the formerly shared term.
3932
 * @param int    $new_term_id      ID of the new term created for the $term_taxonomy_id.
3933
 * @param int    $term_taxonomy_id ID for the term_taxonomy row affected by the split.
3934
 * @param string $taxonomy         Taxonomy for the split term.
3935
 */
3936
function _wp_check_split_terms_in_menus( $term_id, $new_term_id, $term_taxonomy_id, $taxonomy ) {
3937
	global $wpdb;
3938
	$post_ids = $wpdb->get_col( $wpdb->prepare(
3939
		"SELECT m1.post_id
3940
		FROM {$wpdb->postmeta} AS m1
3941
			INNER JOIN {$wpdb->postmeta} AS m2 ON ( m2.post_id = m1.post_id )
3942
			INNER JOIN {$wpdb->postmeta} AS m3 ON ( m3.post_id = m1.post_id )
3943
		WHERE ( m1.meta_key = '_menu_item_type' AND m1.meta_value = 'taxonomy' )
3944
			AND ( m2.meta_key = '_menu_item_object' AND m2.meta_value = '%s' )
3945
			AND ( m3.meta_key = '_menu_item_object_id' AND m3.meta_value = %d )",
3946
		$taxonomy,
3947
		$term_id
3948
	) );
3949
3950
	if ( $post_ids ) {
3951
		foreach ( $post_ids as $post_id ) {
3952
			update_post_meta( $post_id, '_menu_item_object_id', $new_term_id, $term_id );
3953
		}
3954
	}
3955
}
3956
3957
/**
3958
 * If the term being split is a nav_menu, change associations.
3959
 *
3960
 * @ignore
3961
 * @since 4.3.0
3962
 *
3963
 * @param int    $term_id          ID of the formerly shared term.
3964
 * @param int    $new_term_id      ID of the new term created for the $term_taxonomy_id.
3965
 * @param int    $term_taxonomy_id ID for the term_taxonomy row affected by the split.
3966
 * @param string $taxonomy         Taxonomy for the split term.
3967
 */
3968
function _wp_check_split_nav_menu_terms( $term_id, $new_term_id, $term_taxonomy_id, $taxonomy ) {
3969
	if ( 'nav_menu' !== $taxonomy ) {
3970
		return;
3971
	}
3972
3973
	// Update menu locations.
3974
	$locations = get_nav_menu_locations();
3975
	foreach ( $locations as $location => $menu_id ) {
3976
		if ( $term_id == $menu_id ) {
3977
			$locations[ $location ] = $new_term_id;
3978
		}
3979
	}
3980
	set_theme_mod( 'nav_menu_locations', $locations );
3981
}
3982
3983
/**
3984
 * Get data about terms that previously shared a single term_id, but have since been split.
3985
 *
3986
 * @since 4.2.0
3987
 *
3988
 * @param int $old_term_id Term ID. This is the old, pre-split term ID.
3989
 * @return array Array of new term IDs, keyed by taxonomy.
3990
 */
3991
function wp_get_split_terms( $old_term_id ) {
3992
	$split_terms = get_option( '_split_terms', array() );
3993
3994
	$terms = array();
3995
	if ( isset( $split_terms[ $old_term_id ] ) ) {
3996
		$terms = $split_terms[ $old_term_id ];
3997
	}
3998
3999
	return $terms;
4000
}
4001
4002
/**
4003
 * Get the new term ID corresponding to a previously split term.
4004
 *
4005
 * @since 4.2.0
4006
 *
4007
 * @param int    $old_term_id Term ID. This is the old, pre-split term ID.
4008
 * @param string $taxonomy    Taxonomy that the term belongs to.
4009
 * @return int|false If a previously split term is found corresponding to the old term_id and taxonomy,
4010
 *                   the new term_id will be returned. If no previously split term is found matching
4011
 *                   the parameters, returns false.
4012
 */
4013
function wp_get_split_term( $old_term_id, $taxonomy ) {
4014
	$split_terms = wp_get_split_terms( $old_term_id );
4015
4016
	$term_id = false;
4017
	if ( isset( $split_terms[ $taxonomy ] ) ) {
4018
		$term_id = (int) $split_terms[ $taxonomy ];
4019
	}
4020
4021
	return $term_id;
4022
}
4023
4024
/**
4025
 * Determine whether a term is shared between multiple taxonomies.
4026
 *
4027
 * Shared taxonomy terms began to be split in 4.3, but failed cron tasks or other delays in upgrade routines may cause
4028
 * shared terms to remain.
4029
 *
4030
 * @since 4.4.0
4031
 *
4032
 * @param int $term_id
4033
 * @return bool
4034
 */
4035
function wp_term_is_shared( $term_id ) {
4036
	global $wpdb;
4037
4038
	if ( get_option( 'finished_splitting_shared_terms' ) ) {
4039
		return false;
4040
	}
4041
4042
	$tt_count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_taxonomy WHERE term_id = %d", $term_id ) );
4043
4044
	return $tt_count > 1;
4045
}
4046
4047
/**
4048
 * Generate a permalink for a taxonomy term archive.
4049
 *
4050
 * @since 2.5.0
4051
 *
4052
 * @global WP_Rewrite $wp_rewrite
4053
 *
4054
 * @param object|int|string $term     The term object, ID, or slug whose link will be retrieved.
4055
 * @param string            $taxonomy Optional. Taxonomy. Default empty.
4056
 * @return string|WP_Error HTML link to taxonomy term archive on success, WP_Error if term does not exist.
4057
 */
4058
function get_term_link( $term, $taxonomy = '' ) {
4059
	global $wp_rewrite;
4060
4061 View Code Duplication
	if ( !is_object($term) ) {
4062
		if ( is_int( $term ) ) {
4063
			$term = get_term( $term, $taxonomy );
4064
		} else {
4065
			$term = get_term_by( 'slug', $term, $taxonomy );
4066
		}
4067
	}
4068
4069
	if ( !is_object($term) )
4070
		$term = new WP_Error('invalid_term', __('Empty Term'));
4071
4072
	if ( is_wp_error( $term ) )
4073
		return $term;
4074
4075
	$taxonomy = $term->taxonomy;
4076
4077
	$termlink = $wp_rewrite->get_extra_permastruct($taxonomy);
4078
4079
	$slug = $term->slug;
4080
	$t = get_taxonomy($taxonomy);
4081
4082
	if ( empty($termlink) ) {
4083
		if ( 'category' == $taxonomy )
4084
			$termlink = '?cat=' . $term->term_id;
4085
		elseif ( $t->query_var )
4086
			$termlink = "?$t->query_var=$slug";
4087
		else
4088
			$termlink = "?taxonomy=$taxonomy&term=$slug";
4089
		$termlink = home_url($termlink);
4090
	} else {
4091
		if ( $t->rewrite['hierarchical'] ) {
4092
			$hierarchical_slugs = array();
4093
			$ancestors = get_ancestors( $term->term_id, $taxonomy, 'taxonomy' );
4094
			foreach ( (array)$ancestors as $ancestor ) {
4095
				$ancestor_term = get_term($ancestor, $taxonomy);
4096
				$hierarchical_slugs[] = $ancestor_term->slug;
4097
			}
4098
			$hierarchical_slugs = array_reverse($hierarchical_slugs);
4099
			$hierarchical_slugs[] = $slug;
4100
			$termlink = str_replace("%$taxonomy%", implode('/', $hierarchical_slugs), $termlink);
4101
		} else {
4102
			$termlink = str_replace("%$taxonomy%", $slug, $termlink);
4103
		}
4104
		$termlink = home_url( user_trailingslashit($termlink, 'category') );
4105
	}
4106
	// Back Compat filters.
4107
	if ( 'post_tag' == $taxonomy ) {
4108
4109
		/**
4110
		 * Filters the tag link.
4111
		 *
4112
		 * @since 2.3.0
4113
		 * @deprecated 2.5.0 Use 'term_link' instead.
4114
		 *
4115
		 * @param string $termlink Tag link URL.
4116
		 * @param int    $term_id  Term ID.
4117
		 */
4118
		$termlink = apply_filters( 'tag_link', $termlink, $term->term_id );
4119
	} elseif ( 'category' == $taxonomy ) {
4120
4121
		/**
4122
		 * Filters the category link.
4123
		 *
4124
		 * @since 1.5.0
4125
		 * @deprecated 2.5.0 Use 'term_link' instead.
4126
		 *
4127
		 * @param string $termlink Category link URL.
4128
		 * @param int    $term_id  Term ID.
4129
		 */
4130
		$termlink = apply_filters( 'category_link', $termlink, $term->term_id );
4131
	}
4132
4133
	/**
4134
	 * Filters the term link.
4135
	 *
4136
	 * @since 2.5.0
4137
	 *
4138
	 * @param string $termlink Term link URL.
4139
	 * @param object $term     Term object.
4140
	 * @param string $taxonomy Taxonomy slug.
4141
	 */
4142
	return apply_filters( 'term_link', $termlink, $term, $taxonomy );
4143
}
4144
4145
/**
4146
 * Display the taxonomies of a post with available options.
4147
 *
4148
 * This function can be used within the loop to display the taxonomies for a
4149
 * post without specifying the Post ID. You can also use it outside the Loop to
4150
 * display the taxonomies for a specific post.
4151
 *
4152
 * @since 2.5.0
4153
 *
4154
 * @param array $args {
4155
 *     Arguments about which post to use and how to format the output. Shares all of the arguments
4156
 *     supported by get_the_taxonomies(), in addition to the following.
4157
 *
4158
 *     @type  int|WP_Post $post   Post ID or object to get taxonomies of. Default current post.
4159
 *     @type  string      $before Displays before the taxonomies. Default empty string.
4160
 *     @type  string      $sep    Separates each taxonomy. Default is a space.
4161
 *     @type  string      $after  Displays after the taxonomies. Default empty string.
4162
 * }
4163
 */
4164
function the_taxonomies( $args = array() ) {
4165
	$defaults = array(
4166
		'post' => 0,
4167
		'before' => '',
4168
		'sep' => ' ',
4169
		'after' => '',
4170
	);
4171
4172
	$r = wp_parse_args( $args, $defaults );
4173
4174
	echo $r['before'] . join( $r['sep'], get_the_taxonomies( $r['post'], $r ) ) . $r['after'];
4175
}
4176
4177
/**
4178
 * Retrieve all taxonomies associated with a post.
4179
 *
4180
 * This function can be used within the loop. It will also return an array of
4181
 * the taxonomies with links to the taxonomy and name.
4182
 *
4183
 * @since 2.5.0
4184
 *
4185
 * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global $post.
4186
 * @param array $args {
4187
 *     Optional. Arguments about how to format the list of taxonomies. Default empty array.
4188
 *
4189
 *     @type string $template      Template for displaying a taxonomy label and list of terms.
4190
 *                                 Default is "Label: Terms."
4191
 *     @type string $term_template Template for displaying a single term in the list. Default is the term name
4192
 *                                 linked to its archive.
4193
 * }
4194
 * @return array List of taxonomies.
4195
 */
4196
function get_the_taxonomies( $post = 0, $args = array() ) {
4197
	$post = get_post( $post );
4198
4199
	$args = wp_parse_args( $args, array(
4200
		/* translators: %s: taxonomy label, %l: list of terms formatted as per $term_template */
4201
		'template' => __( '%s: %l.' ),
4202
		'term_template' => '<a href="%1$s">%2$s</a>',
4203
	) );
4204
4205
	$taxonomies = array();
4206
4207
	if ( ! $post ) {
4208
		return $taxonomies;
4209
	}
4210
4211
	foreach ( get_object_taxonomies( $post ) as $taxonomy ) {
4212
		$t = (array) get_taxonomy( $taxonomy );
4213
		if ( empty( $t['label'] ) ) {
4214
			$t['label'] = $taxonomy;
4215
		}
4216
		if ( empty( $t['args'] ) ) {
4217
			$t['args'] = array();
4218
		}
4219
		if ( empty( $t['template'] ) ) {
4220
			$t['template'] = $args['template'];
4221
		}
4222
		if ( empty( $t['term_template'] ) ) {
4223
			$t['term_template'] = $args['term_template'];
4224
		}
4225
4226
		$terms = get_object_term_cache( $post->ID, $taxonomy );
4227
		if ( false === $terms ) {
4228
			$terms = wp_get_object_terms( $post->ID, $taxonomy, $t['args'] );
4229
		}
4230
		$links = array();
4231
4232
		foreach ( $terms as $term ) {
0 ignored issues
show
The expression $terms of type array|object<WP_Error> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
4233
			$links[] = wp_sprintf( $t['term_template'], esc_attr( get_term_link( $term ) ), $term->name );
0 ignored issues
show
It seems like get_term_link($term) targeting get_term_link() can also be of type object<WP_Error>; however, esc_attr() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
4234
		}
4235
		if ( $links ) {
4236
			$taxonomies[$taxonomy] = wp_sprintf( $t['template'], $t['label'], $links, $terms );
4237
		}
4238
	}
4239
	return $taxonomies;
4240
}
4241
4242
/**
4243
 * Retrieve all taxonomies of a post with just the names.
4244
 *
4245
 * @since 2.5.0
4246
 *
4247
 * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global $post.
4248
 * @return array
4249
 */
4250
function get_post_taxonomies( $post = 0 ) {
4251
	$post = get_post( $post );
4252
4253
	return get_object_taxonomies($post);
0 ignored issues
show
It seems like $post defined by get_post($post) on line 4251 can also be of type null; however, get_object_taxonomies() does only seem to accept array|string|object<WP_Post>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
4254
}
4255
4256
/**
4257
 * Determine if the given object is associated with any of the given terms.
4258
 *
4259
 * The given terms are checked against the object's terms' term_ids, names and slugs.
4260
 * Terms given as integers will only be checked against the object's terms' term_ids.
4261
 * If no terms are given, determines if object is associated with any terms in the given taxonomy.
4262
 *
4263
 * @since 2.7.0
4264
 *
4265
 * @param int              $object_id ID of the object (post ID, link ID, ...).
4266
 * @param string           $taxonomy  Single taxonomy name.
4267
 * @param int|string|array $terms     Optional. Term term_id, name, slug or array of said. Default null.
4268
 * @return bool|WP_Error WP_Error on input error.
4269
 */
4270
function is_object_in_term( $object_id, $taxonomy, $terms = null ) {
4271
	if ( !$object_id = (int) $object_id )
4272
		return new WP_Error( 'invalid_object', __( 'Invalid object ID' ) );
4273
4274
	$object_terms = get_object_term_cache( $object_id, $taxonomy );
4275
	if ( false === $object_terms ) {
4276
		$object_terms = wp_get_object_terms( $object_id, $taxonomy, array( 'update_term_meta_cache' => false ) );
4277
		if ( is_wp_error( $object_terms ) ) {
4278
			return $object_terms;
4279
		}
4280
4281
		wp_cache_set( $object_id, wp_list_pluck( $object_terms, 'term_id' ), "{$taxonomy}_relationships" );
0 ignored issues
show
It seems like $object_terms defined by wp_get_object_terms($obj..._meta_cache' => false)) on line 4276 can also be of type object<WP_Error>; however, wp_list_pluck() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
4282
	}
4283
4284
	if ( is_wp_error( $object_terms ) )
4285
		return $object_terms;
4286
	if ( empty( $object_terms ) )
4287
		return false;
4288
	if ( empty( $terms ) )
4289
		return ( !empty( $object_terms ) );
4290
4291
	$terms = (array) $terms;
4292
4293
	if ( $ints = array_filter( $terms, 'is_int' ) )
4294
		$strs = array_diff( $terms, $ints );
4295
	else
4296
		$strs =& $terms;
4297
4298
	foreach ( $object_terms as $object_term ) {
0 ignored issues
show
The expression $object_terms of type array|object<WP_Error> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
4299
		// If term is an int, check against term_ids only.
4300
		if ( $ints && in_array( $object_term->term_id, $ints ) ) {
4301
			return true;
4302
		}
4303
4304
		if ( $strs ) {
4305
			// Only check numeric strings against term_id, to avoid false matches due to type juggling.
4306
			$numeric_strs = array_map( 'intval', array_filter( $strs, 'is_numeric' ) );
4307
			if ( in_array( $object_term->term_id, $numeric_strs, true ) ) {
4308
				return true;
4309
			}
4310
4311
			if ( in_array( $object_term->name, $strs ) ) return true;
4312
			if ( in_array( $object_term->slug, $strs ) ) return true;
4313
		}
4314
	}
4315
4316
	return false;
4317
}
4318
4319
/**
4320
 * Determine if the given object type is associated with the given taxonomy.
4321
 *
4322
 * @since 3.0.0
4323
 *
4324
 * @param string $object_type Object type string.
4325
 * @param string $taxonomy    Single taxonomy name.
4326
 * @return bool True if object is associated with the taxonomy, otherwise false.
4327
 */
4328
function is_object_in_taxonomy( $object_type, $taxonomy ) {
4329
	$taxonomies = get_object_taxonomies( $object_type );
4330
	if ( empty( $taxonomies ) ) {
4331
		return false;
4332
	}
4333
	return in_array( $taxonomy, $taxonomies );
4334
}
4335
4336
/**
4337
 * Get an array of ancestor IDs for a given object.
4338
 *
4339
 * @since 3.1.0
4340
 * @since 4.1.0 Introduced the `$resource_type` argument.
4341
 *
4342
 * @param int    $object_id     Optional. The ID of the object. Default 0.
4343
 * @param string $object_type   Optional. The type of object for which we'll be retrieving
4344
 *                              ancestors. Accepts a post type or a taxonomy name. Default empty.
4345
 * @param string $resource_type Optional. Type of resource $object_type is. Accepts 'post_type'
4346
 *                              or 'taxonomy'. Default empty.
4347
 * @return array An array of ancestors from lowest to highest in the hierarchy.
4348
 */
4349
function get_ancestors( $object_id = 0, $object_type = '', $resource_type = '' ) {
4350
	$object_id = (int) $object_id;
4351
4352
	$ancestors = array();
4353
4354
	if ( empty( $object_id ) ) {
4355
4356
		/** This filter is documented in wp-includes/taxonomy.php */
4357
		return apply_filters( 'get_ancestors', $ancestors, $object_id, $object_type, $resource_type );
4358
	}
4359
4360
	if ( ! $resource_type ) {
4361
		if ( is_taxonomy_hierarchical( $object_type ) ) {
4362
			$resource_type = 'taxonomy';
4363
		} elseif ( post_type_exists( $object_type ) ) {
4364
			$resource_type = 'post_type';
4365
		}
4366
	}
4367
4368
	if ( 'taxonomy' === $resource_type ) {
4369
		$term = get_term($object_id, $object_type);
4370
		while ( ! is_wp_error($term) && ! empty( $term->parent ) && ! in_array( $term->parent, $ancestors ) ) {
4371
			$ancestors[] = (int) $term->parent;
4372
			$term = get_term($term->parent, $object_type);
4373
		}
4374
	} elseif ( 'post_type' === $resource_type ) {
4375
		$ancestors = get_post_ancestors($object_id);
4376
	}
4377
4378
	/**
4379
	 * Filters a given object's ancestors.
4380
	 *
4381
	 * @since 3.1.0
4382
	 * @since 4.1.1 Introduced the `$resource_type` parameter.
4383
	 *
4384
	 * @param array  $ancestors     An array of object ancestors.
4385
	 * @param int    $object_id     Object ID.
4386
	 * @param string $object_type   Type of object.
4387
	 * @param string $resource_type Type of resource $object_type is.
4388
	 */
4389
	return apply_filters( 'get_ancestors', $ancestors, $object_id, $object_type, $resource_type );
4390
}
4391
4392
/**
4393
 * Returns the term's parent's term_ID.
4394
 *
4395
 * @since 3.1.0
4396
 *
4397
 * @param int    $term_id  Term ID.
4398
 * @param string $taxonomy Taxonomy name.
4399
 * @return int|false False on error.
4400
 */
4401
function wp_get_term_taxonomy_parent_id( $term_id, $taxonomy ) {
4402
	$term = get_term( $term_id, $taxonomy );
4403
	if ( ! $term || is_wp_error( $term ) ) {
4404
		return false;
4405
	}
4406
	return (int) $term->parent;
4407
}
4408
4409
/**
4410
 * Checks the given subset of the term hierarchy for hierarchy loops.
4411
 * Prevents loops from forming and breaks those that it finds.
4412
 *
4413
 * Attached to the {@see 'wp_update_term_parent'} filter.
4414
 *
4415
 * @since 3.1.0
4416
 *
4417
 * @param int    $parent   `term_id` of the parent for the term we're checking.
4418
 * @param int    $term_id  The term we're checking.
4419
 * @param string $taxonomy The taxonomy of the term we're checking.
4420
 *
4421
 * @return int The new parent for the term.
4422
 */
4423 View Code Duplication
function wp_check_term_hierarchy_for_loops( $parent, $term_id, $taxonomy ) {
0 ignored issues
show
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
4424
	// Nothing fancy here - bail
4425
	if ( !$parent )
4426
		return 0;
4427
4428
	// Can't be its own parent.
4429
	if ( $parent == $term_id )
4430
		return 0;
4431
4432
	// Now look for larger loops.
4433
	if ( !$loop = wp_find_hierarchy_loop( 'wp_get_term_taxonomy_parent_id', $term_id, $parent, array( $taxonomy ) ) )
4434
		return $parent; // No loop
4435
4436
	// Setting $parent to the given value causes a loop.
4437
	if ( isset( $loop[$term_id] ) )
4438
		return 0;
4439
4440
	// There's a loop, but it doesn't contain $term_id. Break the loop.
4441
	foreach ( array_keys( $loop ) as $loop_member )
4442
		wp_update_term( $loop_member, $taxonomy, array( 'parent' => 0 ) );
4443
4444
	return $parent;
4445
}
4446