Issues (4967)

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.

src/wp-includes/taxonomy.php (51 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

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
		'capabilities' => array(
65
			'manage_terms' => 'manage_categories',
66
			'edit_terms'   => 'edit_categories',
67
			'delete_terms' => 'delete_categories',
68
			'assign_terms' => 'assign_categories',
69
		),
70
		'show_in_rest' => true,
71
		'rest_base' => 'categories',
72
		'rest_controller_class' => 'WP_REST_Terms_Controller',
73
	) );
74
75
	register_taxonomy( 'post_tag', 'post', array(
76
	 	'hierarchical' => false,
77
		'query_var' => 'tag',
78
		'rewrite' => $rewrite['post_tag'],
79
		'public' => true,
80
		'show_ui' => true,
81
		'show_admin_column' => true,
82
		'_builtin' => true,
83
		'capabilities' => array(
84
			'manage_terms' => 'manage_post_tags',
85
			'edit_terms'   => 'edit_post_tags',
86
			'delete_terms' => 'delete_post_tags',
87
			'assign_terms' => 'assign_post_tags',
88
		),
89
		'show_in_rest' => true,
90
		'rest_base' => 'tags',
91
		'rest_controller_class' => 'WP_REST_Terms_Controller',
92
	) );
93
94
	register_taxonomy( 'nav_menu', 'nav_menu_item', array(
95
		'public' => false,
96
		'hierarchical' => false,
97
		'labels' => array(
98
			'name' => __( 'Navigation Menus' ),
99
			'singular_name' => __( 'Navigation Menu' ),
100
		),
101
		'query_var' => false,
102
		'rewrite' => false,
103
		'show_ui' => false,
104
		'_builtin' => true,
105
		'show_in_nav_menus' => false,
106
	) );
107
108
	register_taxonomy( 'link_category', 'link', array(
109
		'hierarchical' => false,
110
		'labels' => array(
111
			'name' => __( 'Link Categories' ),
112
			'singular_name' => __( 'Link Category' ),
113
			'search_items' => __( 'Search Link Categories' ),
114
			'popular_items' => null,
115
			'all_items' => __( 'All Link Categories' ),
116
			'edit_item' => __( 'Edit Link Category' ),
117
			'update_item' => __( 'Update Link Category' ),
118
			'add_new_item' => __( 'Add New Link Category' ),
119
			'new_item_name' => __( 'New Link Category Name' ),
120
			'separate_items_with_commas' => null,
121
			'add_or_remove_items' => null,
122
			'choose_from_most_used' => null,
123
		),
124
		'capabilities' => array(
125
			'manage_terms' => 'manage_links',
126
			'edit_terms'   => 'manage_links',
127
			'delete_terms' => 'manage_links',
128
			'assign_terms' => 'manage_links',
129
		),
130
		'query_var' => false,
131
		'rewrite' => false,
132
		'public' => false,
133
		'show_ui' => true,
134
		'_builtin' => true,
135
	) );
136
137
	register_taxonomy( 'post_format', 'post', array(
138
		'public' => true,
139
		'hierarchical' => false,
140
		'labels' => array(
141
			'name' => _x( 'Format', 'post format' ),
142
			'singular_name' => _x( 'Format', 'post format' ),
143
		),
144
		'query_var' => true,
145
		'rewrite' => $rewrite['post_format'],
146
		'show_ui' => false,
147
		'_builtin' => true,
148
		'show_in_nav_menus' => current_theme_supports( 'post-formats' ),
149
	) );
150
}
151
152
/**
153
 * Retrieves a list of registered taxonomy names or objects.
154
 *
155
 * @since 3.0.0
156
 *
157
 * @global array $wp_taxonomies The registered taxonomies.
158
 *
159
 * @param array  $args     Optional. An array of `key => value` arguments to match against the taxonomy objects.
160
 *                         Default empty array.
161
 * @param string $output   Optional. The type of output to return in the array. Accepts either taxonomy 'names'
162
 *                         or 'objects'. Default 'names'.
163
 * @param string $operator Optional. The logical operation to perform. Accepts 'and' or 'or'. 'or' means only
164
 *                         one element from the array needs to match; 'and' means all elements must match.
165
 *                         Default 'and'.
166
 * @return array A list of taxonomy names or objects.
167
 */
168 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...
169
	global $wp_taxonomies;
170
171
	$field = ('names' == $output) ? 'name' : false;
172
173
	return wp_filter_object_list($wp_taxonomies, $args, $operator, $field);
174
}
175
176
/**
177
 * Return the names or objects of the taxonomies which are registered for the requested object or object type, such as
178
 * a post object or post type name.
179
 *
180
 * Example:
181
 *
182
 *     $taxonomies = get_object_taxonomies( 'post' );
183
 *
184
 * This results in:
185
 *
186
 *     Array( 'category', 'post_tag' )
187
 *
188
 * @since 2.3.0
189
 *
190
 * @global array $wp_taxonomies The registered taxonomies.
191
 *
192
 * @param array|string|WP_Post $object Name of the type of taxonomy object, or an object (row from posts)
193
 * @param string               $output Optional. The type of output to return in the array. Accepts either
194
 *                                     taxonomy 'names' or 'objects'. Default 'names'.
195
 * @return array The names of all taxonomy of $object_type.
196
 */
197
function get_object_taxonomies( $object, $output = 'names' ) {
198
	global $wp_taxonomies;
199
200
	if ( is_object($object) ) {
201
		if ( $object->post_type == 'attachment' )
202
			return get_attachment_taxonomies( $object, $output );
203
		$object = $object->post_type;
204
	}
205
206
	$object = (array) $object;
207
208
	$taxonomies = array();
209
	foreach ( (array) $wp_taxonomies as $tax_name => $tax_obj ) {
210
		if ( array_intersect($object, (array) $tax_obj->object_type) ) {
211
			if ( 'names' == $output )
212
				$taxonomies[] = $tax_name;
213
			else
214
				$taxonomies[ $tax_name ] = $tax_obj;
215
		}
216
	}
217
218
	return $taxonomies;
219
}
220
221
/**
222
 * Retrieves the taxonomy object of $taxonomy.
223
 *
224
 * The get_taxonomy function will first check that the parameter string given
225
 * is a taxonomy object and if it is, it will return it.
226
 *
227
 * @since 2.3.0
228
 *
229
 * @global array $wp_taxonomies The registered taxonomies.
230
 *
231
 * @param string $taxonomy Name of taxonomy object to return.
232
 * @return WP_Taxonomy|false The Taxonomy Object or false if $taxonomy doesn't exist.
233
 */
234
function get_taxonomy( $taxonomy ) {
235
	global $wp_taxonomies;
236
237
	if ( ! taxonomy_exists( $taxonomy ) )
238
		return false;
239
240
	return $wp_taxonomies[$taxonomy];
241
}
242
243
/**
244
 * Checks that the taxonomy name exists.
245
 *
246
 * Formerly is_taxonomy(), introduced in 2.3.0.
247
 *
248
 * @since 3.0.0
249
 *
250
 * @global array $wp_taxonomies The registered taxonomies.
251
 *
252
 * @param string $taxonomy Name of taxonomy object.
253
 * @return bool Whether the taxonomy exists.
254
 */
255
function taxonomy_exists( $taxonomy ) {
256
	global $wp_taxonomies;
257
258
	return isset( $wp_taxonomies[$taxonomy] );
259
}
260
261
/**
262
 * Whether the taxonomy object is hierarchical.
263
 *
264
 * Checks to make sure that the taxonomy is an object first. Then Gets the
265
 * object, and finally returns the hierarchical value in the object.
266
 *
267
 * A false return value might also mean that the taxonomy does not exist.
268
 *
269
 * @since 2.3.0
270
 *
271
 * @param string $taxonomy Name of taxonomy object.
272
 * @return bool Whether the taxonomy is hierarchical.
273
 */
274
function is_taxonomy_hierarchical($taxonomy) {
275
	if ( ! taxonomy_exists($taxonomy) )
276
		return false;
277
278
	$taxonomy = get_taxonomy($taxonomy);
279
	return $taxonomy->hierarchical;
280
}
281
282
/**
283
 * Creates or modifies a taxonomy object.
284
 *
285
 * Note: Do not use before the {@see 'init'} hook.
286
 *
287
 * A simple function for creating or modifying a taxonomy object based on the
288
 * parameters given. The function will accept an array (third optional
289
 * parameter), along with strings for the taxonomy name and another string for
290
 * the object type.
291
 *
292
 * @since 2.3.0
293
 * @since 4.2.0 Introduced `show_in_quick_edit` argument.
294
 * @since 4.4.0 The `show_ui` argument is now enforced on the term editing screen.
295
 * @since 4.4.0 The `public` argument now controls whether the taxonomy can be queried on the front end.
296
 * @since 4.5.0 Introduced `publicly_queryable` argument.
297
 * @since 4.7.0 Introduced `show_in_rest`, 'rest_base' and 'rest_controller_class'
298
 *              arguments to register the Taxonomy in REST API.
299
 *
300
 * @global array $wp_taxonomies Registered taxonomies.
301
 *
302
 * @param string       $taxonomy    Taxonomy key, must not exceed 32 characters.
303
 * @param array|string $object_type Object type or array of object types with which the taxonomy should be associated.
304
 * @param array|string $args        {
305
 *     Optional. Array or query string of arguments for registering a taxonomy.
306
 *
307
 *     @type array         $labels                An array of labels for this taxonomy. By default, Tag labels are
308
 *                                                used for non-hierarchical taxonomies, and Category labels are used
309
 *                                                for hierarchical taxonomies. See accepted values in
310
 *                                                get_taxonomy_labels(). Default empty array.
311
 *     @type string        $description           A short descriptive summary of what the taxonomy is for. Default empty.
312
 *     @type bool          $public                Whether a taxonomy is intended for use publicly either via
313
 *                                                the admin interface or by front-end users. The default settings
314
 *                                                of `$publicly_queryable`, `$show_ui`, and `$show_in_nav_menus`
315
 *                                                are inherited from `$public`.
316
 *     @type bool          $publicly_queryable    Whether the taxonomy is publicly queryable.
317
 *                                                If not set, the default is inherited from `$public`
318
 *     @type bool          $hierarchical          Whether the taxonomy is hierarchical. Default false.
319
 *     @type bool          $show_ui               Whether to generate and allow a UI for managing terms in this taxonomy in
320
 *                                                the admin. If not set, the default is inherited from `$public`
321
 *                                                (default true).
322
 *     @type bool          $show_in_menu          Whether to show the taxonomy in the admin menu. If true, the taxonomy is
323
 *                                                shown as a submenu of the object type menu. If false, no menu is shown.
324
 *                                                `$show_ui` must be true. If not set, default is inherited from `$show_ui`
325
 *                                                (default true).
326
 *     @type bool          $show_in_nav_menus     Makes this taxonomy available for selection in navigation menus. If not
327
 *                                                set, the default is inherited from `$public` (default true).
328
 *     @type bool          $show_in_rest          Whether to include the taxonomy in the REST API.
329
 *     @type string        $rest_base             To change the base url of REST API route. Default is $taxonomy.
330
 *     @type string        $rest_controller_class REST API Controller class name. Default is 'WP_REST_Terms_Controller'.
331
 *     @type bool          $show_tagcloud         Whether to list the taxonomy in the Tag Cloud Widget controls. If not set,
332
 *                                                the default is inherited from `$show_ui` (default true).
333
 *     @type bool          $show_in_quick_edit    Whether to show the taxonomy in the quick/bulk edit panel. It not set,
334
 *                                                the default is inherited from `$show_ui` (default true).
335
 *     @type bool          $show_admin_column     Whether to display a column for the taxonomy on its post type listing
336
 *                                                screens. Default false.
337
 *     @type bool|callable $meta_box_cb           Provide a callback function for the meta box display. If not set,
338
 *                                                post_categories_meta_box() is used for hierarchical taxonomies, and
339
 *                                                post_tags_meta_box() is used for non-hierarchical. If false, no meta
340
 *                                                box is shown.
341
 *     @type array         $capabilities {
342
 *         Array of capabilities for this taxonomy.
343
 *
344
 *         @type string $manage_terms Default 'manage_categories'.
345
 *         @type string $edit_terms   Default 'manage_categories'.
346
 *         @type string $delete_terms Default 'manage_categories'.
347
 *         @type string $assign_terms Default 'edit_posts'.
348
 *     }
349
 *     @type bool|array    $rewrite {
350
 *         Triggers the handling of rewrites for this taxonomy. Default true, using $taxonomy as slug. To prevent
351
 *         rewrite, set to false. To specify rewrite rules, an array can be passed with any of these keys:
352
 *
353
 *         @type string $slug         Customize the permastruct slug. Default `$taxonomy` key.
354
 *         @type bool   $with_front   Should the permastruct be prepended with WP_Rewrite::$front. Default true.
355
 *         @type bool   $hierarchical Either hierarchical rewrite tag or not. Default false.
356
 *         @type int    $ep_mask      Assign an endpoint mask. Default `EP_NONE`.
357
 *     }
358
 *     @type string        $query_var             Sets the query var key for this taxonomy. Default `$taxonomy` key. If
359
 *                                                false, a taxonomy cannot be loaded at `?{query_var}={term_slug}`. If a
360
 *                                                string, the query `?{query_var}={term_slug}` will be valid.
361
 *     @type callable      $update_count_callback Works much like a hook, in that it will be called when the count is
362
 *                                                updated. Default _update_post_term_count() for taxonomies attached
363
 *                                                to post types, which confirms that the objects are published before
364
 *                                                counting them. Default _update_generic_term_count() for taxonomies
365
 *                                                attached to other object types, such as users.
366
 *     @type bool          $_builtin              This taxonomy is a "built-in" taxonomy. INTERNAL USE ONLY!
367
 *                                                Default false.
368
 * }
369
 * @return WP_Error|void WP_Error, if errors.
370
 */
371
function register_taxonomy( $taxonomy, $object_type, $args = array() ) {
372
	global $wp_taxonomies;
373
374
	if ( ! is_array( $wp_taxonomies ) )
375
		$wp_taxonomies = array();
376
377
	$args = wp_parse_args( $args );
378
379
	if ( empty( $taxonomy ) || strlen( $taxonomy ) > 32 ) {
380
		_doing_it_wrong( __FUNCTION__, __( 'Taxonomy names must be between 1 and 32 characters in length.' ), '4.2.0' );
381
		return new WP_Error( 'taxonomy_length_invalid', __( 'Taxonomy names must be between 1 and 32 characters in length.' ) );
382
	}
383
384
	$taxonomy_object = new WP_Taxonomy( $taxonomy, $object_type, $args );
385
	$taxonomy_object->add_rewrite_rules();
386
387
	$wp_taxonomies[ $taxonomy ] = $taxonomy_object;
388
389
	$taxonomy_object->add_hooks();
390
391
392
	/**
393
	 * Fires after a taxonomy is registered.
394
	 *
395
	 * @since 3.3.0
396
	 *
397
	 * @param string       $taxonomy    Taxonomy slug.
398
	 * @param array|string $object_type Object type or array of object types.
399
	 * @param array        $args        Array of taxonomy registration arguments.
400
	 */
401
	do_action( 'registered_taxonomy', $taxonomy, $object_type, (array) $taxonomy_object );
402
}
403
404
/**
405
 * Unregisters a taxonomy.
406
 *
407
 * Can not be used to unregister built-in taxonomies.
408
 *
409
 * @since 4.5.0
410
 *
411
 * @global WP    $wp            Current WordPress environment instance.
412
 * @global array $wp_taxonomies List of taxonomies.
413
 *
414
 * @param string $taxonomy Taxonomy name.
415
 * @return bool|WP_Error True on success, WP_Error on failure or if the taxonomy doesn't exist.
416
 */
417
function unregister_taxonomy( $taxonomy ) {
418
	if ( ! taxonomy_exists( $taxonomy ) ) {
419
		return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
420
	}
421
422
	$taxonomy_object = get_taxonomy( $taxonomy );
423
424
	// Do not allow unregistering internal taxonomies.
425
	if ( $taxonomy_object->_builtin ) {
426
		return new WP_Error( 'invalid_taxonomy', __( 'Unregistering a built-in taxonomy is not allowed' ) );
427
	}
428
429
	global $wp_taxonomies;
430
431
	$taxonomy_object->remove_rewrite_rules();
432
	$taxonomy_object->remove_hooks();
433
434
	// Remove the taxonomy.
435
	unset( $wp_taxonomies[ $taxonomy ] );
436
437
	/**
438
	 * Fires after a taxonomy is unregistered.
439
	 *
440
	 * @since 4.5.0
441
	 *
442
	 * @param string $taxonomy Taxonomy name.
443
	 */
444
	do_action( 'unregistered_taxonomy', $taxonomy );
445
446
	return true;
447
}
448
449
/**
450
 * Builds an object with all taxonomy labels out of a taxonomy object
451
 *
452
 * Accepted keys of the label array in the taxonomy object:
453
 *
454
 * - name - general name for the taxonomy, usually plural. The same as and overridden by $tax->label. Default is Tags/Categories
455
 * - singular_name - name for one object of this taxonomy. Default is Tag/Category
456
 * - search_items - Default is Search Tags/Search Categories
457
 * - popular_items - This string isn't used on hierarchical taxonomies. Default is Popular Tags
458
 * - all_items - Default is All Tags/All Categories
459
 * - parent_item - This string isn't used on non-hierarchical taxonomies. In hierarchical ones the default is Parent Category
460
 * - parent_item_colon - The same as `parent_item`, but with colon `:` in the end
461
 * - edit_item - Default is Edit Tag/Edit Category
462
 * - view_item - Default is View Tag/View Category
463
 * - update_item - Default is Update Tag/Update Category
464
 * - add_new_item - Default is Add New Tag/Add New Category
465
 * - new_item_name - Default is New Tag Name/New Category Name
466
 * - separate_items_with_commas - This string isn't used on hierarchical taxonomies. Default is "Separate tags with commas", used in the meta box.
467
 * - 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.
468
 * - 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.
469
 * - not_found - Default is "No tags found"/"No categories found", used in the meta box and taxonomy list table.
470
 * - no_terms - Default is "No tags"/"No categories", used in the posts and media list tables.
471
 * - items_list_navigation - String for the table pagination hidden heading.
472
 * - items_list - String for the table hidden heading.
473
 *
474
 * Above, the first default value is for non-hierarchical taxonomies (like tags) and the second one is for hierarchical taxonomies (like categories).
475
 *
476
 * @todo Better documentation for the labels array.
477
 *
478
 * @since 3.0.0
479
 * @since 4.3.0 Added the `no_terms` label.
480
 * @since 4.4.0 Added the `items_list_navigation` and `items_list` labels.
481
 *
482
 * @param WP_Taxonomy $tax Taxonomy object.
483
 * @return object object with all the labels as member variables.
484
 */
485
function get_taxonomy_labels( $tax ) {
486
	$tax->labels = (array) $tax->labels;
0 ignored issues
show
Documentation Bug introduced by
It seems like (array) $tax->labels of type array is incompatible with the declared type object of property $labels.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
487
488
	if ( isset( $tax->helps ) && empty( $tax->labels['separate_items_with_commas'] ) )
489
		$tax->labels['separate_items_with_commas'] = $tax->helps;
0 ignored issues
show
The property helps does not seem to exist in WP_Taxonomy.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
490
491
	if ( isset( $tax->no_tagcloud ) && empty( $tax->labels['not_found'] ) )
492
		$tax->labels['not_found'] = $tax->no_tagcloud;
0 ignored issues
show
The property no_tagcloud does not seem to exist in WP_Taxonomy.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
493
494
	$nohier_vs_hier_defaults = array(
495
		'name' => array( _x( 'Tags', 'taxonomy general name' ), _x( 'Categories', 'taxonomy general name' ) ),
496
		'singular_name' => array( _x( 'Tag', 'taxonomy singular name' ), _x( 'Category', 'taxonomy singular name' ) ),
497
		'search_items' => array( __( 'Search Tags' ), __( 'Search Categories' ) ),
498
		'popular_items' => array( __( 'Popular Tags' ), null ),
499
		'all_items' => array( __( 'All Tags' ), __( 'All Categories' ) ),
500
		'parent_item' => array( null, __( 'Parent Category' ) ),
501
		'parent_item_colon' => array( null, __( 'Parent Category:' ) ),
502
		'edit_item' => array( __( 'Edit Tag' ), __( 'Edit Category' ) ),
503
		'view_item' => array( __( 'View Tag' ), __( 'View Category' ) ),
504
		'update_item' => array( __( 'Update Tag' ), __( 'Update Category' ) ),
505
		'add_new_item' => array( __( 'Add New Tag' ), __( 'Add New Category' ) ),
506
		'new_item_name' => array( __( 'New Tag Name' ), __( 'New Category Name' ) ),
507
		'separate_items_with_commas' => array( __( 'Separate tags with commas' ), null ),
508
		'add_or_remove_items' => array( __( 'Add or remove tags' ), null ),
509
		'choose_from_most_used' => array( __( 'Choose from the most used tags' ), null ),
510
		'not_found' => array( __( 'No tags found.' ), __( 'No categories found.' ) ),
511
		'no_terms' => array( __( 'No tags' ), __( 'No categories' ) ),
512
		'items_list_navigation' => array( __( 'Tags list navigation' ), __( 'Categories list navigation' ) ),
513
		'items_list' => array( __( 'Tags list' ), __( 'Categories list' ) ),
514
	);
515
	$nohier_vs_hier_defaults['menu_name'] = $nohier_vs_hier_defaults['name'];
516
517
	$labels = _get_custom_object_labels( $tax, $nohier_vs_hier_defaults );
518
519
	$taxonomy = $tax->name;
520
521
	$default_labels = clone $labels;
522
523
	/**
524
	 * Filters the labels of a specific taxonomy.
525
	 *
526
	 * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
527
	 *
528
	 * @since 4.4.0
529
	 *
530
	 * @see get_taxonomy_labels() for the full list of taxonomy labels.
531
	 *
532
	 * @param object $labels Object with labels for the taxonomy as member variables.
533
	 */
534
	$labels = apply_filters( "taxonomy_labels_{$taxonomy}", $labels );
535
536
	// Ensure that the filtered labels contain all required default values.
537
	$labels = (object) array_merge( (array) $default_labels, (array) $labels );
538
539
	return $labels;
540
}
541
542
/**
543
 * Add an already registered taxonomy to an object type.
544
 *
545
 * @since 3.0.0
546
 *
547
 * @global array $wp_taxonomies The registered taxonomies.
548
 *
549
 * @param string $taxonomy    Name of taxonomy object.
550
 * @param string $object_type Name of the object type.
551
 * @return bool True if successful, false if not.
552
 */
553
function register_taxonomy_for_object_type( $taxonomy, $object_type) {
554
	global $wp_taxonomies;
555
556
	if ( !isset($wp_taxonomies[$taxonomy]) )
557
		return false;
558
559
	if ( ! get_post_type_object($object_type) )
560
		return false;
561
562
	if ( ! in_array( $object_type, $wp_taxonomies[$taxonomy]->object_type ) )
563
		$wp_taxonomies[$taxonomy]->object_type[] = $object_type;
564
565
	// Filter out empties.
566
	$wp_taxonomies[ $taxonomy ]->object_type = array_filter( $wp_taxonomies[ $taxonomy ]->object_type );
567
568
	return true;
569
}
570
571
/**
572
 * Remove an already registered taxonomy from an object type.
573
 *
574
 * @since 3.7.0
575
 *
576
 * @global array $wp_taxonomies The registered taxonomies.
577
 *
578
 * @param string $taxonomy    Name of taxonomy object.
579
 * @param string $object_type Name of the object type.
580
 * @return bool True if successful, false if not.
581
 */
582
function unregister_taxonomy_for_object_type( $taxonomy, $object_type ) {
583
	global $wp_taxonomies;
584
585
	if ( ! isset( $wp_taxonomies[ $taxonomy ] ) )
586
		return false;
587
588
	if ( ! get_post_type_object( $object_type ) )
589
		return false;
590
591
	$key = array_search( $object_type, $wp_taxonomies[ $taxonomy ]->object_type, true );
592
	if ( false === $key )
593
		return false;
594
595
	unset( $wp_taxonomies[ $taxonomy ]->object_type[ $key ] );
596
	return true;
597
}
598
599
//
600
// Term API
601
//
602
603
/**
604
 * Retrieve object_ids of valid taxonomy and term.
605
 *
606
 * The strings of $taxonomies must exist before this function will continue. On
607
 * failure of finding a valid taxonomy, it will return an WP_Error class, kind
608
 * of like Exceptions in PHP 5, except you can't catch them. Even so, you can
609
 * still test for the WP_Error class and get the error message.
610
 *
611
 * The $terms aren't checked the same as $taxonomies, but still need to exist
612
 * for $object_ids to be returned.
613
 *
614
 * It is possible to change the order that object_ids is returned by either
615
 * using PHP sort family functions or using the database by using $args with
616
 * either ASC or DESC array. The value should be in the key named 'order'.
617
 *
618
 * @since 2.3.0
619
 *
620
 * @global wpdb $wpdb WordPress database abstraction object.
621
 *
622
 * @param int|array    $term_ids   Term id or array of term ids of terms that will be used.
623
 * @param string|array $taxonomies String of taxonomy name or Array of string values of taxonomy names.
624
 * @param array|string $args       Change the order of the object_ids, either ASC or DESC.
625
 * @return WP_Error|array If the taxonomy does not exist, then WP_Error will be returned. On success.
626
 *	the array can be empty meaning that there are no $object_ids found or it will return the $object_ids found.
627
 */
628
function get_objects_in_term( $term_ids, $taxonomies, $args = array() ) {
629
	global $wpdb;
630
631
	if ( ! is_array( $term_ids ) ) {
632
		$term_ids = array( $term_ids );
633
	}
634
	if ( ! is_array( $taxonomies ) ) {
635
		$taxonomies = array( $taxonomies );
636
	}
637 View Code Duplication
	foreach ( (array) $taxonomies as $taxonomy ) {
638
		if ( ! taxonomy_exists( $taxonomy ) ) {
639
			return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
640
		}
641
	}
642
643
	$defaults = array( 'order' => 'ASC' );
644
	$args = wp_parse_args( $args, $defaults );
645
646
	$order = ( 'desc' == strtolower( $args['order'] ) ) ? 'DESC' : 'ASC';
647
648
	$term_ids = array_map('intval', $term_ids );
649
650
	$taxonomies = "'" . implode( "', '", array_map( 'esc_sql', $taxonomies ) ) . "'";
651
	$term_ids = "'" . implode( "', '", $term_ids ) . "'";
652
653
	$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");
654
655
	if ( ! $object_ids ){
656
		return array();
657
	}
658
	return $object_ids;
659
}
660
661
/**
662
 * Given a taxonomy query, generates SQL to be appended to a main query.
663
 *
664
 * @since 3.1.0
665
 *
666
 * @see WP_Tax_Query
667
 *
668
 * @param array  $tax_query         A compact tax query
669
 * @param string $primary_table
670
 * @param string $primary_id_column
671
 * @return array
0 ignored issues
show
Consider making the return type a bit more specific; maybe use array<string,string>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
672
 */
673
function get_tax_sql( $tax_query, $primary_table, $primary_id_column ) {
674
	$tax_query_obj = new WP_Tax_Query( $tax_query );
675
	return $tax_query_obj->get_sql( $primary_table, $primary_id_column );
676
}
677
678
/**
679
 * Get all Term data from database by Term ID.
680
 *
681
 * The usage of the get_term function is to apply filters to a term object. It
682
 * is possible to get a term object from the database before applying the
683
 * filters.
684
 *
685
 * $term ID must be part of $taxonomy, to get from the database. Failure, might
686
 * be able to be captured by the hooks. Failure would be the same value as $wpdb
687
 * returns for the get_row method.
688
 *
689
 * There are two hooks, one is specifically for each term, named 'get_term', and
690
 * the second is for the taxonomy name, 'term_$taxonomy'. Both hooks gets the
691
 * term object, and the taxonomy name as parameters. Both hooks are expected to
692
 * return a Term object.
693
 *
694
 * {@see 'get_term'} hook - Takes two parameters the term Object and the taxonomy name.
695
 * Must return term object. Used in get_term() as a catch-all filter for every
696
 * $term.
697
 *
698
 * {@see 'get_$taxonomy'} hook - Takes two parameters the term Object and the taxonomy
699
 * name. Must return term object. $taxonomy will be the taxonomy name, so for
700
 * example, if 'category', it would be 'get_category' as the filter name. Useful
701
 * for custom taxonomies or plugging into default taxonomies.
702
 *
703
 * @todo Better formatting for DocBlock
704
 *
705
 * @since 2.3.0
706
 * @since 4.4.0 Converted to return a WP_Term object if `$output` is `OBJECT`.
707
 *              The `$taxonomy` parameter was made optional.
708
 *
709
 * @global wpdb $wpdb WordPress database abstraction object.
710
 * @see sanitize_term_field() The $context param lists the available values for get_term_by() $filter param.
711
 *
712
 * @param int|WP_Term|object $term If integer, term data will be fetched from the database, or from the cache if
713
 *                                 available. If stdClass object (as in the results of a database query), will apply
714
 *                                 filters and return a `WP_Term` object corresponding to the `$term` data. If `WP_Term`,
715
 *                                 will return `$term`.
716
 * @param string     $taxonomy Optional. Taxonomy name that $term is part of.
717
 * @param string     $output   Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond to
718
 *                             a WP_Term object, an associative array, or a numeric array, respectively. Default OBJECT.
719
 * @param string     $filter   Optional, default is raw or no WordPress defined filter will applied.
720
 * @return array|WP_Term|WP_Error|null Object of the type specified by `$output` on success. When `$output` is 'OBJECT',
721
 *                                     a WP_Term instance is returned. If taxonomy does not exist, a WP_Error is
722
 *                                     returned. Returns null for miscellaneous failure.
723
 */
724
function get_term( $term, $taxonomy = '', $output = OBJECT, $filter = 'raw' ) {
725
	if ( empty( $term ) ) {
726
		return new WP_Error( 'invalid_term', __( 'Empty Term' ) );
727
	}
728
729
	if ( $taxonomy && ! taxonomy_exists( $taxonomy ) ) {
730
		return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
731
	}
732
733 View Code Duplication
	if ( $term instanceof WP_Term ) {
734
		$_term = $term;
735
	} elseif ( is_object( $term ) ) {
736
		if ( empty( $term->filter ) || 'raw' === $term->filter ) {
737
			$_term = sanitize_term( $term, $taxonomy, 'raw' );
738
			$_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...
739
		} else {
740
			$_term = WP_Term::get_instance( $term->term_id );
741
		}
742
	} else {
743
		$_term = WP_Term::get_instance( $term, $taxonomy );
744
	}
745
746
	if ( is_wp_error( $_term ) ) {
747
		return $_term;
748
	} elseif ( ! $_term ) {
749
		return null;
750
	}
751
752
	/**
753
	 * Filters a term.
754
	 *
755
	 * @since 2.3.0
756
	 * @since 4.4.0 `$_term` can now also be a WP_Term object.
757
	 *
758
	 * @param int|WP_Term $_term    Term object or ID.
759
	 * @param string      $taxonomy The taxonomy slug.
760
	 */
761
	$_term = apply_filters( 'get_term', $_term, $taxonomy );
762
763
	/**
764
	 * Filters a taxonomy.
765
	 *
766
	 * The dynamic portion of the filter name, `$taxonomy`, refers
767
	 * to the taxonomy slug.
768
	 *
769
	 * @since 2.3.0
770
	 * @since 4.4.0 `$_term` can now also be a WP_Term object.
771
	 *
772
	 * @param int|WP_Term $_term    Term object or ID.
773
	 * @param string      $taxonomy The taxonomy slug.
774
	 */
775
	$_term = apply_filters( "get_{$taxonomy}", $_term, $taxonomy );
776
777
	// Bail if a filter callback has changed the type of the `$_term` object.
778
	if ( ! ( $_term instanceof WP_Term ) ) {
779
		return $_term;
780
	}
781
782
	// Sanitize term, according to the specified filter.
783
	$_term->filter( $filter );
784
785 View Code Duplication
	if ( $output == ARRAY_A ) {
786
		return $_term->to_array();
787
	} elseif ( $output == ARRAY_N ) {
788
		return array_values( $_term->to_array() );
789
	}
790
791
	return $_term;
792
}
793
794
/**
795
 * Get all Term data from database by Term field and data.
796
 *
797
 * Warning: $value is not escaped for 'name' $field. You must do it yourself, if
798
 * required.
799
 *
800
 * The default $field is 'id', therefore it is possible to also use null for
801
 * field, but not recommended that you do so.
802
 *
803
 * If $value does not exist, the return value will be false. If $taxonomy exists
804
 * and $field and $value combinations exist, the Term will be returned.
805
 *
806
 * This function will always return the first term that matches the `$field`-
807
 * `$value`-`$taxonomy` combination specified in the parameters. If your query
808
 * is likely to match more than one term (as is likely to be the case when
809
 * `$field` is 'name', for example), consider using get_terms() instead; that
810
 * way, you will get all matching terms, and can provide your own logic for
811
 * deciding which one was intended.
812
 *
813
 * @todo Better formatting for DocBlock.
814
 *
815
 * @since 2.3.0
816
 * @since 4.4.0 `$taxonomy` is optional if `$field` is 'term_taxonomy_id'. Converted to return
817
 *              a WP_Term object if `$output` is `OBJECT`.
818
 *
819
 * @global wpdb $wpdb WordPress database abstraction object.
820
 * @see sanitize_term_field() The $context param lists the available values for get_term_by() $filter param.
821
 *
822
 * @param string     $field    Either 'slug', 'name', 'id' (term_id), or 'term_taxonomy_id'
823
 * @param string|int $value    Search for this term value
824
 * @param string     $taxonomy Taxonomy name. Optional, if `$field` is 'term_taxonomy_id'.
825
 * @param string     $output   Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond to
826
 *                             a WP_Term object, an associative array, or a numeric array, respectively. Default OBJECT.
827
 * @param string     $filter   Optional, default is raw or no WordPress defined filter will applied.
828
 * @return WP_Term|array|false WP_Term instance (or array) on success. Will return false if `$taxonomy` does not exist
0 ignored issues
show
Should the return type not be false|array|WP_Term|WP_Error|null? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
829
 *                             or `$term` was not found.
830
 */
831
function get_term_by( $field, $value, $taxonomy = '', $output = OBJECT, $filter = 'raw' ) {
832
833
	// 'term_taxonomy_id' lookups don't require taxonomy checks.
834
	if ( 'term_taxonomy_id' !== $field && ! taxonomy_exists( $taxonomy ) ) {
835
		return false;
836
	}
837
838
	// No need to perform a query for empty 'slug' or 'name'.
839
	if ( 'slug' === $field || 'name' === $field ) {
840
		$value = (string) $value;
841
842
		if ( 0 === strlen( $value ) ) {
843
			return false;
844
		}
845
	}
846
847
	if ( 'id' === $field || 'term_id' === $field ) {
848
		$term = get_term( (int) $value, $taxonomy, $output, $filter );
849
		if ( is_wp_error( $term ) || null === $term ) {
850
			$term = false;
851
		}
852
		return $term;
853
	}
854
855
	$args = array(
856
		'get'                    => 'all',
857
		'number'                 => 1,
858
		'taxonomy'               => $taxonomy,
859
		'update_term_meta_cache' => false,
860
		'orderby'                => 'none',
861
		'suppress_filter'        => true,
862
	);
863
864
	switch ( $field ) {
865
		case 'slug' :
866
			$args['slug'] = $value;
867
			break;
868
		case 'name' :
869
			$args['name'] = $value;
870
			break;
871
		case 'term_taxonomy_id' :
872
			$args['term_taxonomy_id'] = $value;
873
			unset( $args[ 'taxonomy' ] );
874
			break;
875
		default :
876
			return false;
877
	}
878
879
	$terms = get_terms( $args );
880
	if ( is_wp_error( $terms ) || empty( $terms ) ) {
881
		return false;
882
	}
883
884
	$term = array_shift( $terms );
885
886
	// In the case of 'term_taxonomy_id', override the provided `$taxonomy` with whatever we find in the db.
887
	if ( 'term_taxonomy_id' === $field ) {
888
		$taxonomy = $term->taxonomy;
889
	}
890
891
	return get_term( $term, $taxonomy, $output, $filter );
892
}
893
894
/**
895
 * Merge all term children into a single array of their IDs.
896
 *
897
 * This recursive function will merge all of the children of $term into the same
898
 * array of term IDs. Only useful for taxonomies which are hierarchical.
899
 *
900
 * Will return an empty array if $term does not exist in $taxonomy.
901
 *
902
 * @since 2.3.0
903
 *
904
 * @param string $term_id  ID of Term to get children.
905
 * @param string $taxonomy Taxonomy Name.
906
 * @return array|WP_Error List of Term IDs. WP_Error returned if `$taxonomy` does not exist.
907
 */
908
function get_term_children( $term_id, $taxonomy ) {
909
	if ( ! taxonomy_exists( $taxonomy ) ) {
910
		return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
911
	}
912
913
	$term_id = intval( $term_id );
914
915
	$terms = _get_term_hierarchy($taxonomy);
916
917
	if ( ! isset($terms[$term_id]) )
918
		return array();
919
920
	$children = $terms[$term_id];
921
922
	foreach ( (array) $terms[$term_id] as $child ) {
923
		if ( $term_id == $child ) {
924
			continue;
925
		}
926
927
		if ( isset($terms[$child]) )
928
			$children = array_merge($children, get_term_children($child, $taxonomy));
929
	}
930
931
	return $children;
932
}
933
934
/**
935
 * Get sanitized Term field.
936
 *
937
 * The function is for contextual reasons and for simplicity of usage.
938
 *
939
 * @since 2.3.0
940
 * @since 4.4.0 The `$taxonomy` parameter was made optional. `$term` can also now accept a WP_Term object.
941
 *
942
 * @see sanitize_term_field()
943
 *
944
 * @param string      $field    Term field to fetch.
945
 * @param int|WP_Term $term     Term ID or object.
946
 * @param string      $taxonomy Optional. Taxonomy Name. Default empty.
947
 * @param string      $context  Optional, default is display. Look at sanitize_term_field() for available options.
948
 * @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.
949
 */
950 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...
951
	$term = get_term( $term, $taxonomy );
952
	if ( is_wp_error($term) )
953
		return $term;
954
955
	if ( !is_object($term) )
956
		return '';
957
958
	if ( !isset($term->$field) )
959
		return '';
960
961
	return sanitize_term_field( $field, $term->$field, $term->term_id, $term->taxonomy, $context );
962
}
963
964
/**
965
 * Sanitizes Term for editing.
966
 *
967
 * Return value is sanitize_term() and usage is for sanitizing the term for
968
 * editing. Function is for contextual and simplicity.
969
 *
970
 * @since 2.3.0
971
 *
972
 * @param int|object $id       Term ID or object.
973
 * @param string     $taxonomy Taxonomy name.
974
 * @return string|int|null|WP_Error Will return empty string if $term is not an object.
0 ignored issues
show
Should the return type not be array|null|string|object? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
975
 */
976
function get_term_to_edit( $id, $taxonomy ) {
977
	$term = get_term( $id, $taxonomy );
978
979
	if ( is_wp_error($term) )
980
		return $term;
981
982
	if ( !is_object($term) )
983
		return '';
984
985
	return sanitize_term($term, $taxonomy, 'edit');
986
}
987
988
/**
989
 * Retrieve the terms in a given taxonomy or list of taxonomies.
990
 *
991
 * You can fully inject any customizations to the query before it is sent, as
992
 * well as control the output with a filter.
993
 *
994
 * The {@see 'get_terms'} filter will be called when the cache has the term and will
995
 * pass the found term along with the array of $taxonomies and array of $args.
996
 * This filter is also called before the array of terms is passed and will pass
997
 * the array of terms, along with the $taxonomies and $args.
998
 *
999
 * The {@see 'list_terms_exclusions'} filter passes the compiled exclusions along with
1000
 * the $args.
1001
 *
1002
 * The {@see 'get_terms_orderby'} filter passes the `ORDER BY` clause for the query
1003
 * along with the $args array.
1004
 *
1005
 * Prior to 4.5.0, the first parameter of `get_terms()` was a taxonomy or list of taxonomies:
1006
 *
1007
 *     $terms = get_terms( 'post_tag', array(
1008
 *         'hide_empty' => false,
1009
 *     ) );
1010
 *
1011
 * Since 4.5.0, taxonomies should be passed via the 'taxonomy' argument in the `$args` array:
1012
 *
1013
 *     $terms = get_terms( array(
1014
 *         'taxonomy' => 'post_tag',
1015
 *         'hide_empty' => false,
1016
 *     ) );
1017
 *
1018
 * @since 2.3.0
1019
 * @since 4.2.0 Introduced 'name' and 'childless' parameters.
1020
 * @since 4.4.0 Introduced the ability to pass 'term_id' as an alias of 'id' for the `orderby` parameter.
1021
 *              Introduced the 'meta_query' and 'update_term_meta_cache' parameters. Converted to return
1022
 *              a list of WP_Term objects.
1023
 * @since 4.5.0 Changed the function signature so that the `$args` array can be provided as the first parameter.
1024
 *              Introduced 'meta_key' and 'meta_value' parameters. Introduced the ability to order results by metadata.
1025
 * @since 4.8.0 Introduced 'suppress_filter' parameter.
1026
 *
1027
 * @internal The `$deprecated` parameter is parsed for backward compatibility only.
1028
 *
1029
 * @global wpdb  $wpdb WordPress database abstraction object.
1030
 * @global array $wp_filter
1031
 *
1032
 * @param string|array $args       Optional. Array or string of arguments. See WP_Term_Query::__construct()
1033
 *                                 for information on accepted arguments. Default empty.
1034
 * @param array        $deprecated Argument array, when using the legacy function parameter format. If present, this
0 ignored issues
show
Should the type for parameter $deprecated not be string|array? Also, consider making the array more specific, something like array<String>, or String[].

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
1035
 *                                 parameter will be interpreted as `$args`, and the first function parameter will
1036
 *                                 be parsed as a taxonomy or array of taxonomies.
1037
 * @return array|int|WP_Error List of WP_Term instances and their children. Will return WP_Error, if any of $taxonomies
1038
 *                            do not exist.
1039
 */
1040
function get_terms( $args = array(), $deprecated = '' ) {
1041
	global $wpdb;
1042
1043
	$term_query = new WP_Term_Query();
1044
1045
	$defaults = array(
1046
		'suppress_filter' => false,
1047
	);
1048
1049
	/*
1050
	 * Legacy argument format ($taxonomy, $args) takes precedence.
1051
	 *
1052
	 * We detect legacy argument format by checking if
1053
	 * (a) a second non-empty parameter is passed, or
1054
	 * (b) the first parameter shares no keys with the default array (ie, it's a list of taxonomies)
1055
	 */
1056
	$_args = wp_parse_args( $args );
1057
	$key_intersect  = array_intersect_key( $term_query->query_var_defaults, (array) $_args );
1058
	$do_legacy_args = $deprecated || empty( $key_intersect );
1059
1060
	if ( $do_legacy_args ) {
1061
		$taxonomies = (array) $args;
1062
		$args = wp_parse_args( $deprecated, $defaults );
1063
		$args['taxonomy'] = $taxonomies;
1064
	} else {
1065
		$args = wp_parse_args( $args, $defaults );
1066
		if ( isset( $args['taxonomy'] ) && null !== $args['taxonomy'] ) {
1067
			$args['taxonomy'] = (array) $args['taxonomy'];
1068
		}
1069
	}
1070
1071
	if ( ! empty( $args['taxonomy'] ) ) {
1072 View Code Duplication
		foreach ( $args['taxonomy'] as $taxonomy ) {
1073
			if ( ! taxonomy_exists( $taxonomy ) ) {
1074
				return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
1075
			}
1076
		}
1077
	}
1078
1079
	// Don't pass suppress_filter to WP_Term_Query.
1080
	$suppress_filter = $args['suppress_filter'];
1081
	unset( $args['suppress_filter'] );
1082
1083
	$terms = $term_query->query( $args );
1084
1085
	// Count queries are not filtered, for legacy reasons.
1086
	if ( ! is_array( $terms ) ) {
1087
		return $terms;
1088
	}
1089
1090
	if ( $suppress_filter ) {
1091
		return $terms;
1092
	}
1093
1094
	/**
1095
	 * Filters the found terms.
1096
	 *
1097
	 * @since 2.3.0
1098
	 * @since 4.6.0 Added the `$term_query` parameter.
1099
	 *
1100
	 * @param array         $terms      Array of found terms.
1101
	 * @param array         $taxonomies An array of taxonomies.
1102
	 * @param array         $args       An array of get_terms() arguments.
1103
	 * @param WP_Term_Query $term_query The WP_Term_Query object.
1104
	 */
1105
	return apply_filters( 'get_terms', $terms, $term_query->query_vars['taxonomy'], $term_query->query_vars, $term_query );
1106
}
1107
1108
/**
1109
 * Adds metadata to a term.
1110
 *
1111
 * @since 4.4.0
1112
 *
1113
 * @param int    $term_id    Term ID.
1114
 * @param string $meta_key   Metadata name.
1115
 * @param mixed  $meta_value Metadata value.
1116
 * @param bool   $unique     Optional. Whether to bail if an entry with the same key is found for the term.
1117
 *                           Default false.
1118
 * @return int|WP_Error|bool Meta ID on success. WP_Error when term_id is ambiguous between taxonomies.
0 ignored issues
show
Consider making the return type a bit more specific; maybe use false|WP_Error|integer.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
1119
 *                           False on failure.
1120
 */
1121 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...
1122
	// Bail if term meta table is not installed.
1123
	if ( get_option( 'db_version' ) < 34370 ) {
1124
		return false;
1125
	}
1126
1127
	if ( wp_term_is_shared( $term_id ) ) {
1128
		return new WP_Error( 'ambiguous_term_id', __( 'Term meta cannot be added to terms that are shared between taxonomies.'), $term_id );
1129
	}
1130
1131
	$added = add_metadata( 'term', $term_id, $meta_key, $meta_value, $unique );
1132
1133
	// Bust term query cache.
1134
	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...
1135
		wp_cache_set( 'last_changed', microtime(), 'terms' );
1136
	}
1137
1138
	return $added;
1139
}
1140
1141
/**
1142
 * Removes metadata matching criteria from a term.
1143
 *
1144
 * @since 4.4.0
1145
 *
1146
 * @param int    $term_id    Term ID.
1147
 * @param string $meta_key   Metadata name.
1148
 * @param mixed  $meta_value Optional. Metadata value. If provided, rows will only be removed that match the value.
1149
 * @return bool True on success, false on failure.
1150
 */
1151
function delete_term_meta( $term_id, $meta_key, $meta_value = '' ) {
1152
	// Bail if term meta table is not installed.
1153
	if ( get_option( 'db_version' ) < 34370 ) {
1154
		return false;
1155
	}
1156
1157
	$deleted = delete_metadata( 'term', $term_id, $meta_key, $meta_value );
1158
1159
	// Bust term query cache.
1160
	if ( $deleted ) {
1161
		wp_cache_set( 'last_changed', microtime(), 'terms' );
1162
	}
1163
1164
	return $deleted;
1165
}
1166
1167
/**
1168
 * Retrieves metadata for a term.
1169
 *
1170
 * @since 4.4.0
1171
 *
1172
 * @param int    $term_id Term ID.
1173
 * @param string $key     Optional. The meta key to retrieve. If no key is provided, fetches all metadata for the term.
1174
 * @param bool   $single  Whether to return a single value. If false, an array of all values matching the
1175
 *                        `$term_id`/`$key` pair will be returned. Default: false.
1176
 * @return mixed If `$single` is false, an array of metadata values. If `$single` is true, a single metadata value.
1177
 */
1178
function get_term_meta( $term_id, $key = '', $single = false ) {
1179
	// Bail if term meta table is not installed.
1180
	if ( get_option( 'db_version' ) < 34370 ) {
1181
		return false;
1182
	}
1183
1184
	return get_metadata( 'term', $term_id, $key, $single );
1185
}
1186
1187
/**
1188
 * Updates term metadata.
1189
 *
1190
 * Use the `$prev_value` parameter to differentiate between meta fields with the same key and term ID.
1191
 *
1192
 * If the meta field for the term does not exist, it will be added.
1193
 *
1194
 * @since 4.4.0
1195
 *
1196
 * @param int    $term_id    Term ID.
1197
 * @param string $meta_key   Metadata key.
1198
 * @param mixed  $meta_value Metadata value.
1199
 * @param mixed  $prev_value Optional. Previous value to check before removing.
1200
 * @return int|WP_Error|bool Meta ID if the key didn't previously exist. True on successful update.
1201
 *                           WP_Error when term_id is ambiguous between taxonomies. False on failure.
1202
 */
1203 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...
1204
	// Bail if term meta table is not installed.
1205
	if ( get_option( 'db_version' ) < 34370 ) {
1206
		return false;
1207
	}
1208
1209
	if ( wp_term_is_shared( $term_id ) ) {
1210
		return new WP_Error( 'ambiguous_term_id', __( 'Term meta cannot be added to terms that are shared between taxonomies.'), $term_id );
1211
	}
1212
1213
	$updated = update_metadata( 'term', $term_id, $meta_key, $meta_value, $prev_value );
1214
1215
	// Bust term query cache.
1216
	if ( $updated ) {
1217
		wp_cache_set( 'last_changed', microtime(), 'terms' );
1218
	}
1219
1220
	return $updated;
1221
}
1222
1223
/**
1224
 * Updates metadata cache for list of term IDs.
1225
 *
1226
 * Performs SQL query to retrieve all metadata for the terms matching `$term_ids` and stores them in the cache.
1227
 * Subsequent calls to `get_term_meta()` will not need to query the database.
1228
 *
1229
 * @since 4.4.0
1230
 *
1231
 * @param array $term_ids List of term IDs.
1232
 * @return array|false Returns false if there is nothing to update. Returns an array of metadata on success.
0 ignored issues
show
Should the return type not be null|false|array? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
1233
 */
1234
function update_termmeta_cache( $term_ids ) {
1235
	// Bail if term meta table is not installed.
1236
	if ( get_option( 'db_version' ) < 34370 ) {
1237
		return;
1238
	}
1239
1240
	return update_meta_cache( 'term', $term_ids );
1241
}
1242
1243
/**
1244
 * Get all meta data, including meta IDs, for the given term ID.
1245
 *
1246
 * @since 4.9.0
1247
 *
1248
 * @global wpdb $wpdb WordPress database abstraction object.
1249
 *
1250
 * @param int $term_id
1251
 * @return array|false
1252
 */
1253
function has_term_meta( $term_id ) {
1254
	// Bail if term meta table is not installed.
1255
	if ( get_option( 'db_version' ) < 34370 ) {
1256
		return false;
1257
	}
1258
1259
	global $wpdb;
1260
1261
	return $wpdb->get_results( $wpdb->prepare( "SELECT meta_key, meta_value, meta_id, term_id FROM $wpdb->termmeta WHERE term_id = %d ORDER BY meta_key,meta_id", $term_id ), ARRAY_A );
1262
}
1263
1264
/**
1265
 * Check if Term exists.
1266
 *
1267
 * Formerly is_term(), introduced in 2.3.0.
1268
 *
1269
 * @since 3.0.0
1270
 *
1271
 * @global wpdb $wpdb WordPress database abstraction object.
1272
 *
1273
 * @param int|string $term     The term to check. Accepts term ID, slug, or name.
1274
 * @param string     $taxonomy The taxonomy name to use
1275
 * @param int        $parent   Optional. ID of parent term under which to confine the exists search.
1276
 * @return mixed Returns null if the term does not exist. Returns the term ID
1277
 *               if no taxonomy is specified and the term ID exists. Returns
1278
 *               an array of the term ID and the term taxonomy ID the taxonomy
1279
 *               is specified and the pairing exists.
1280
 */
1281
function term_exists( $term, $taxonomy = '', $parent = null ) {
1282
	global $wpdb;
1283
1284
	$select = "SELECT term_id FROM $wpdb->terms as t WHERE ";
1285
	$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 ";
1286
1287
	if ( is_int($term) ) {
1288
		if ( 0 == $term )
1289
			return 0;
1290
		$where = 't.term_id = %d';
1291
		if ( !empty($taxonomy) )
1292
			return $wpdb->get_row( $wpdb->prepare( $tax_select . $where . " AND tt.taxonomy = %s", $term, $taxonomy ), ARRAY_A );
1293
		else
1294
			return $wpdb->get_var( $wpdb->prepare( $select . $where, $term ) );
1295
	}
1296
1297
	$term = trim( wp_unslash( $term ) );
1298
	$slug = sanitize_title( $term );
1299
1300
	$where = 't.slug = %s';
1301
	$else_where = 't.name = %s';
1302
	$where_fields = array($slug);
1303
	$else_where_fields = array($term);
1304
	$orderby = 'ORDER BY t.term_id ASC';
1305
	$limit = 'LIMIT 1';
1306
	if ( !empty($taxonomy) ) {
1307
		if ( is_numeric( $parent ) ) {
1308
			$parent = (int) $parent;
1309
			$where_fields[] = $parent;
1310
			$else_where_fields[] = $parent;
1311
			$where .= ' AND tt.parent = %d';
1312
			$else_where .= ' AND tt.parent = %d';
1313
		}
1314
1315
		$where_fields[] = $taxonomy;
1316
		$else_where_fields[] = $taxonomy;
1317
1318
		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) )
1319
			return $result;
1320
1321
		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);
1322
	}
1323
1324
	if ( $result = $wpdb->get_var( $wpdb->prepare("SELECT term_id FROM $wpdb->terms as t WHERE $where $orderby $limit", $where_fields) ) )
1325
		return $result;
1326
1327
	return $wpdb->get_var( $wpdb->prepare("SELECT term_id FROM $wpdb->terms as t WHERE $else_where $orderby $limit", $else_where_fields) );
1328
}
1329
1330
/**
1331
 * Check if a term is an ancestor of another term.
1332
 *
1333
 * You can use either an id or the term object for both parameters.
1334
 *
1335
 * @since 3.4.0
1336
 *
1337
 * @param int|object $term1    ID or object to check if this is the parent term.
1338
 * @param int|object $term2    The child term.
1339
 * @param string     $taxonomy Taxonomy name that $term1 and `$term2` belong to.
1340
 * @return bool Whether `$term2` is a child of `$term1`.
1341
 */
1342
function term_is_ancestor_of( $term1, $term2, $taxonomy ) {
1343
	if ( ! isset( $term1->term_id ) )
1344
		$term1 = get_term( $term1, $taxonomy );
1345
	if ( ! isset( $term2->parent ) )
1346
		$term2 = get_term( $term2, $taxonomy );
1347
1348
	if ( empty( $term1->term_id ) || empty( $term2->parent ) )
1349
		return false;
1350
	if ( $term2->parent == $term1->term_id )
1351
		return true;
1352
1353
	return term_is_ancestor_of( $term1, get_term( $term2->parent, $taxonomy ), $taxonomy );
0 ignored issues
show
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...
1354
}
1355
1356
/**
1357
 * Sanitize Term all fields.
1358
 *
1359
 * Relies on sanitize_term_field() to sanitize the term. The difference is that
1360
 * this function will sanitize <strong>all</strong> fields. The context is based
1361
 * on sanitize_term_field().
1362
 *
1363
 * The $term is expected to be either an array or an object.
1364
 *
1365
 * @since 2.3.0
1366
 *
1367
 * @param array|object $term     The term to check.
1368
 * @param string       $taxonomy The taxonomy name to use.
1369
 * @param string       $context  Optional. Context in which to sanitize the term. Accepts 'edit', 'db',
1370
 *                               'display', 'attribute', or 'js'. Default 'display'.
1371
 * @return array|object Term with all fields sanitized.
0 ignored issues
show
Consider making the return type a bit more specific; maybe use object|array.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
1372
 */
1373
function sanitize_term($term, $taxonomy, $context = 'display') {
1374
	$fields = array( 'term_id', 'name', 'description', 'slug', 'count', 'parent', 'term_group', 'term_taxonomy_id', 'object_id' );
1375
1376
	$do_object = is_object( $term );
1377
1378
	$term_id = $do_object ? $term->term_id : (isset($term['term_id']) ? $term['term_id'] : 0);
1379
1380 View Code Duplication
	foreach ( (array) $fields as $field ) {
1381
		if ( $do_object ) {
1382
			if ( isset($term->$field) )
1383
				$term->$field = sanitize_term_field($field, $term->$field, $term_id, $taxonomy, $context);
1384
		} else {
1385
			if ( isset($term[$field]) )
1386
				$term[$field] = sanitize_term_field($field, $term[$field], $term_id, $taxonomy, $context);
1387
		}
1388
	}
1389
1390
	if ( $do_object )
1391
		$term->filter = $context;
1392
	else
1393
		$term['filter'] = $context;
1394
1395
	return $term;
1396
}
1397
1398
/**
1399
 * Cleanse the field value in the term based on the context.
1400
 *
1401
 * Passing a term field value through the function should be assumed to have
1402
 * cleansed the value for whatever context the term field is going to be used.
1403
 *
1404
 * If no context or an unsupported context is given, then default filters will
1405
 * be applied.
1406
 *
1407
 * There are enough filters for each context to support a custom filtering
1408
 * without creating your own filter function. Simply create a function that
1409
 * hooks into the filter you need.
1410
 *
1411
 * @since 2.3.0
1412
 *
1413
 * @param string $field    Term field to sanitize.
1414
 * @param string $value    Search for this term value.
1415
 * @param int    $term_id  Term ID.
1416
 * @param string $taxonomy Taxonomy Name.
1417
 * @param string $context  Context in which to sanitize the term field. Accepts 'edit', 'db', 'display',
1418
 *                         'attribute', or 'js'.
1419
 * @return mixed Sanitized field.
1420
 */
1421
function sanitize_term_field($field, $value, $term_id, $taxonomy, $context) {
1422
	$int_fields = array( 'parent', 'term_id', 'count', 'term_group', 'term_taxonomy_id', 'object_id' );
1423
	if ( in_array( $field, $int_fields ) ) {
1424
		$value = (int) $value;
1425
		if ( $value < 0 )
1426
			$value = 0;
1427
	}
1428
1429
	if ( 'raw' == $context )
1430
		return $value;
1431
1432
	if ( 'edit' == $context ) {
1433
1434
		/**
1435
		 * Filters a term field to edit before it is sanitized.
1436
		 *
1437
		 * The dynamic portion of the filter name, `$field`, refers to the term field.
1438
		 *
1439
		 * @since 2.3.0
1440
		 *
1441
		 * @param mixed $value     Value of the term field.
1442
		 * @param int   $term_id   Term ID.
1443
		 * @param string $taxonomy Taxonomy slug.
1444
		 */
1445
		$value = apply_filters( "edit_term_{$field}", $value, $term_id, $taxonomy );
1446
1447
		/**
1448
		 * Filters the taxonomy field to edit before it is sanitized.
1449
		 *
1450
		 * The dynamic portions of the filter name, `$taxonomy` and `$field`, refer
1451
		 * to the taxonomy slug and taxonomy field, respectively.
1452
		 *
1453
		 * @since 2.3.0
1454
		 *
1455
		 * @param mixed $value   Value of the taxonomy field to edit.
1456
		 * @param int   $term_id Term ID.
1457
		 */
1458
		$value = apply_filters( "edit_{$taxonomy}_{$field}", $value, $term_id );
1459
1460
		if ( 'description' == $field )
1461
			$value = esc_html($value); // textarea_escaped
1462
		else
1463
			$value = esc_attr($value);
1464 View Code Duplication
	} elseif ( 'db' == $context ) {
1465
1466
		/**
1467
		 * Filters a term field value before it is sanitized.
1468
		 *
1469
		 * The dynamic portion of the filter name, `$field`, refers to the term field.
1470
		 *
1471
		 * @since 2.3.0
1472
		 *
1473
		 * @param mixed  $value    Value of the term field.
1474
		 * @param string $taxonomy Taxonomy slug.
1475
		 */
1476
		$value = apply_filters( "pre_term_{$field}", $value, $taxonomy );
1477
1478
		/**
1479
		 * Filters a taxonomy field before it is sanitized.
1480
		 *
1481
		 * The dynamic portions of the filter name, `$taxonomy` and `$field`, refer
1482
		 * to the taxonomy slug and field name, respectively.
1483
		 *
1484
		 * @since 2.3.0
1485
		 *
1486
		 * @param mixed $value Value of the taxonomy field.
1487
		 */
1488
		$value = apply_filters( "pre_{$taxonomy}_{$field}", $value );
1489
1490
		// Back compat filters
1491
		if ( 'slug' == $field ) {
1492
			/**
1493
			 * Filters the category nicename before it is sanitized.
1494
			 *
1495
			 * Use the {@see 'pre_$taxonomy_$field'} hook instead.
1496
			 *
1497
			 * @since 2.0.3
1498
			 *
1499
			 * @param string $value The category nicename.
1500
			 */
1501
			$value = apply_filters( 'pre_category_nicename', $value );
1502
		}
1503
1504
	} elseif ( 'rss' == $context ) {
1505
1506
		/**
1507
		 * Filters the term field for use in RSS.
1508
		 *
1509
		 * The dynamic portion of the filter name, `$field`, refers to the term field.
1510
		 *
1511
		 * @since 2.3.0
1512
		 *
1513
		 * @param mixed  $value    Value of the term field.
1514
		 * @param string $taxonomy Taxonomy slug.
1515
		 */
1516
		$value = apply_filters( "term_{$field}_rss", $value, $taxonomy );
1517
1518
		/**
1519
		 * Filters the taxonomy field for use in RSS.
1520
		 *
1521
		 * The dynamic portions of the hook name, `$taxonomy`, and `$field`, refer
1522
		 * to the taxonomy slug and field name, respectively.
1523
		 *
1524
		 * @since 2.3.0
1525
		 *
1526
		 * @param mixed $value Value of the taxonomy field.
1527
		 */
1528
		$value = apply_filters( "{$taxonomy}_{$field}_rss", $value );
1529
	} else {
1530
		// Use display filters by default.
1531
1532
		/**
1533
		 * Filters the term field sanitized for display.
1534
		 *
1535
		 * The dynamic portion of the filter name, `$field`, refers to the term field name.
1536
		 *
1537
		 * @since 2.3.0
1538
		 *
1539
		 * @param mixed  $value    Value of the term field.
1540
		 * @param int    $term_id  Term ID.
1541
		 * @param string $taxonomy Taxonomy slug.
1542
		 * @param string $context  Context to retrieve the term field value.
1543
		 */
1544
		$value = apply_filters( "term_{$field}", $value, $term_id, $taxonomy, $context );
1545
1546
		/**
1547
		 * Filters the taxonomy field sanitized for display.
1548
		 *
1549
		 * The dynamic portions of the filter name, `$taxonomy`, and `$field`, refer
1550
		 * to the taxonomy slug and taxonomy field, respectively.
1551
		 *
1552
		 * @since 2.3.0
1553
		 *
1554
		 * @param mixed  $value   Value of the taxonomy field.
1555
		 * @param int    $term_id Term ID.
1556
		 * @param string $context Context to retrieve the taxonomy field value.
1557
		 */
1558
		$value = apply_filters( "{$taxonomy}_{$field}", $value, $term_id, $context );
1559
	}
1560
1561 View Code Duplication
	if ( 'attribute' == $context ) {
1562
		$value = esc_attr($value);
1563
	} elseif ( 'js' == $context ) {
1564
		$value = esc_js($value);
1565
	}
1566
	return $value;
1567
}
1568
1569
/**
1570
 * Count how many terms are in Taxonomy.
1571
 *
1572
 * Default $args is 'hide_empty' which can be 'hide_empty=true' or array('hide_empty' => true).
1573
 *
1574
 * @since 2.3.0
1575
 *
1576
 * @param string       $taxonomy Taxonomy name.
1577
 * @param array|string $args     Optional. Array of arguments that get passed to get_terms().
1578
 *                               Default empty array.
1579
 * @return array|int|WP_Error Number of terms in that taxonomy or WP_Error if the taxonomy does not exist.
1580
 */
1581
function wp_count_terms( $taxonomy, $args = array() ) {
1582
	$defaults = array('hide_empty' => false);
1583
	$args = wp_parse_args($args, $defaults);
1584
1585
	// backward compatibility
1586
	if ( isset($args['ignore_empty']) ) {
1587
		$args['hide_empty'] = $args['ignore_empty'];
1588
		unset($args['ignore_empty']);
1589
	}
1590
1591
	$args['fields'] = 'count';
1592
1593
	return get_terms($taxonomy, $args);
1594
}
1595
1596
/**
1597
 * Will unlink the object from the taxonomy or taxonomies.
1598
 *
1599
 * Will remove all relationships between the object and any terms in
1600
 * a particular taxonomy or taxonomies. Does not remove the term or
1601
 * taxonomy itself.
1602
 *
1603
 * @since 2.3.0
1604
 *
1605
 * @param int          $object_id  The term Object Id that refers to the term.
1606
 * @param string|array $taxonomies List of Taxonomy Names or single Taxonomy name.
1607
 */
1608
function wp_delete_object_term_relationships( $object_id, $taxonomies ) {
1609
	$object_id = (int) $object_id;
1610
1611
	if ( !is_array($taxonomies) )
1612
		$taxonomies = array($taxonomies);
1613
1614
	foreach ( (array) $taxonomies as $taxonomy ) {
1615
		$term_ids = wp_get_object_terms( $object_id, $taxonomy, array( 'fields' => 'ids' ) );
1616
		$term_ids = array_map( 'intval', $term_ids );
1617
		wp_remove_object_terms( $object_id, $term_ids, $taxonomy );
1618
	}
1619
}
1620
1621
/**
1622
 * Removes a term from the database.
1623
 *
1624
 * If the term is a parent of other terms, then the children will be updated to
1625
 * that term's parent.
1626
 *
1627
 * Metadata associated with the term will be deleted.
1628
 *
1629
 * @since 2.3.0
1630
 *
1631
 * @global wpdb $wpdb WordPress database abstraction object.
1632
 *
1633
 * @param int          $term     Term ID.
1634
 * @param string       $taxonomy Taxonomy Name.
1635
 * @param array|string $args {
1636
 *     Optional. Array of arguments to override the default term ID. Default empty array.
1637
 *
1638
 *     @type int  $default       The term ID to make the default term. This will only override
1639
 *                               the terms found if there is only one term found. Any other and
1640
 *                               the found terms are used.
1641
 *     @type bool $force_default Optional. Whether to force the supplied term as default to be
1642
 *                               assigned even if the object was not going to be term-less.
1643
 *                               Default false.
1644
 * }
1645
 * @return bool|int|WP_Error True on success, false if term does not exist. Zero on attempted
1646
 *                           deletion of default Category. WP_Error if the taxonomy does not exist.
1647
 */
1648
function wp_delete_term( $term, $taxonomy, $args = array() ) {
1649
	global $wpdb;
1650
1651
	$term = (int) $term;
1652
1653
	if ( ! $ids = term_exists($term, $taxonomy) )
1654
		return false;
1655
	if ( is_wp_error( $ids ) )
1656
		return $ids;
1657
1658
	$tt_id = $ids['term_taxonomy_id'];
1659
1660
	$defaults = array();
1661
1662
	if ( 'category' == $taxonomy ) {
1663
		$defaults['default'] = get_option( 'default_category' );
1664
		if ( $defaults['default'] == $term )
1665
			return 0; // Don't delete the default category
1666
	}
1667
1668
	$args = wp_parse_args($args, $defaults);
1669
1670
	if ( isset( $args['default'] ) ) {
1671
		$default = (int) $args['default'];
1672
		if ( ! term_exists( $default, $taxonomy ) ) {
1673
			unset( $default );
1674
		}
1675
	}
1676
1677
	if ( isset( $args['force_default'] ) ) {
1678
		$force_default = $args['force_default'];
1679
	}
1680
1681
	/**
1682
	 * Fires when deleting a term, before any modifications are made to posts or terms.
1683
	 *
1684
	 * @since 4.1.0
1685
	 *
1686
	 * @param int    $term     Term ID.
1687
	 * @param string $taxonomy Taxonomy Name.
1688
	 */
1689
	do_action( 'pre_delete_term', $term, $taxonomy );
1690
1691
	// Update children to point to new parent
1692
	if ( is_taxonomy_hierarchical($taxonomy) ) {
1693
		$term_obj = get_term($term, $taxonomy);
1694
		if ( is_wp_error( $term_obj ) )
1695
			return $term_obj;
1696
		$parent = $term_obj->parent;
1697
1698
		$edit_ids = $wpdb->get_results( "SELECT term_id, term_taxonomy_id FROM $wpdb->term_taxonomy WHERE `parent` = " . (int)$term_obj->term_id );
1699
		$edit_tt_ids = wp_list_pluck( $edit_ids, 'term_taxonomy_id' );
1700
1701
		/**
1702
		 * Fires immediately before a term to delete's children are reassigned a parent.
1703
		 *
1704
		 * @since 2.9.0
1705
		 *
1706
		 * @param array $edit_tt_ids An array of term taxonomy IDs for the given term.
1707
		 */
1708
		do_action( 'edit_term_taxonomies', $edit_tt_ids );
1709
1710
		$wpdb->update( $wpdb->term_taxonomy, compact( 'parent' ), array( 'parent' => $term_obj->term_id) + compact( 'taxonomy' ) );
1711
1712
		// Clean the cache for all child terms.
1713
		$edit_term_ids = wp_list_pluck( $edit_ids, 'term_id' );
1714
		clean_term_cache( $edit_term_ids, $taxonomy );
1715
1716
		/**
1717
		 * Fires immediately after a term to delete's children are reassigned a parent.
1718
		 *
1719
		 * @since 2.9.0
1720
		 *
1721
		 * @param array $edit_tt_ids An array of term taxonomy IDs for the given term.
1722
		 */
1723
		do_action( 'edited_term_taxonomies', $edit_tt_ids );
1724
	}
1725
1726
	// Get the term before deleting it or its term relationships so we can pass to actions below.
1727
	$deleted_term = get_term( $term, $taxonomy );
1728
1729
	$object_ids = (array) $wpdb->get_col( $wpdb->prepare( "SELECT object_id FROM $wpdb->term_relationships WHERE term_taxonomy_id = %d", $tt_id ) );
1730
1731
	foreach ( $object_ids as $object_id ) {
1732
		$terms = wp_get_object_terms( $object_id, $taxonomy, array( 'fields' => 'ids', 'orderby' => 'none' ) );
1733
		if ( 1 == count($terms) && isset($default) ) {
1734
			$terms = array($default);
1735
		} else {
1736
			$terms = array_diff($terms, array($term));
1737
			if (isset($default) && isset($force_default) && $force_default)
1738
				$terms = array_merge($terms, array($default));
1739
		}
1740
		$terms = array_map('intval', $terms);
1741
		wp_set_object_terms( $object_id, $terms, $taxonomy );
1742
	}
1743
1744
	// Clean the relationship caches for all object types using this term.
1745
	$tax_object = get_taxonomy( $taxonomy );
1746
	foreach ( $tax_object->object_type as $object_type )
1747
		clean_object_term_cache( $object_ids, $object_type );
1748
1749
	$term_meta_ids = $wpdb->get_col( $wpdb->prepare( "SELECT meta_id FROM $wpdb->termmeta WHERE term_id = %d ", $term ) );
1750
	foreach ( $term_meta_ids as $mid ) {
1751
		delete_metadata_by_mid( 'term', $mid );
1752
	}
1753
1754
	/**
1755
	 * Fires immediately before a term taxonomy ID is deleted.
1756
	 *
1757
	 * @since 2.9.0
1758
	 *
1759
	 * @param int $tt_id Term taxonomy ID.
1760
	 */
1761
	do_action( 'delete_term_taxonomy', $tt_id );
1762
	$wpdb->delete( $wpdb->term_taxonomy, array( 'term_taxonomy_id' => $tt_id ) );
1763
1764
	/**
1765
	 * Fires immediately after a term taxonomy ID is deleted.
1766
	 *
1767
	 * @since 2.9.0
1768
	 *
1769
	 * @param int $tt_id Term taxonomy ID.
1770
	 */
1771
	do_action( 'deleted_term_taxonomy', $tt_id );
1772
1773
	// Delete the term if no taxonomies use it.
1774
	if ( !$wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_taxonomy WHERE term_id = %d", $term) ) )
1775
		$wpdb->delete( $wpdb->terms, array( 'term_id' => $term ) );
1776
1777
	clean_term_cache($term, $taxonomy);
1778
1779
	/**
1780
	 * Fires after a term is deleted from the database and the cache is cleaned.
1781
	 *
1782
	 * @since 2.5.0
1783
	 * @since 4.5.0 Introduced the `$object_ids` argument.
1784
	 *
1785
	 * @param int     $term         Term ID.
1786
	 * @param int     $tt_id        Term taxonomy ID.
1787
	 * @param string  $taxonomy     Taxonomy slug.
1788
	 * @param mixed   $deleted_term Copy of the already-deleted term, in the form specified
1789
	 *                              by the parent function. WP_Error otherwise.
1790
	 * @param array   $object_ids   List of term object IDs.
1791
	 */
1792
	do_action( 'delete_term', $term, $tt_id, $taxonomy, $deleted_term, $object_ids );
1793
1794
	/**
1795
	 * Fires after a term in a specific taxonomy is deleted.
1796
	 *
1797
	 * The dynamic portion of the hook name, `$taxonomy`, refers to the specific
1798
	 * taxonomy the term belonged to.
1799
	 *
1800
	 * @since 2.3.0
1801
	 * @since 4.5.0 Introduced the `$object_ids` argument.
1802
	 *
1803
	 * @param int     $term         Term ID.
1804
	 * @param int     $tt_id        Term taxonomy ID.
1805
	 * @param mixed   $deleted_term Copy of the already-deleted term, in the form specified
1806
	 *                              by the parent function. WP_Error otherwise.
1807
	 * @param array   $object_ids   List of term object IDs.
1808
	 */
1809
	do_action( "delete_{$taxonomy}", $term, $tt_id, $deleted_term, $object_ids );
1810
1811
	return true;
1812
}
1813
1814
/**
1815
 * Deletes one existing category.
1816
 *
1817
 * @since 2.0.0
1818
 *
1819
 * @param int $cat_ID
1820
 * @return bool|int|WP_Error Returns true if completes delete action; false if term doesn't exist;
1821
 * 	Zero on attempted deletion of default Category; WP_Error object is also a possibility.
1822
 */
1823
function wp_delete_category( $cat_ID ) {
1824
	return wp_delete_term( $cat_ID, 'category' );
1825
}
1826
1827
/**
1828
 * Retrieves the terms associated with the given object(s), in the supplied taxonomies.
1829
 *
1830
 * @since 2.3.0
1831
 * @since 4.2.0 Added support for 'taxonomy', 'parent', and 'term_taxonomy_id' values of `$orderby`.
1832
 *              Introduced `$parent` argument.
1833
 * @since 4.4.0 Introduced `$meta_query` and `$update_term_meta_cache` arguments. When `$fields` is 'all' or
1834
 *              'all_with_object_id', an array of `WP_Term` objects will be returned.
1835
 * @since 4.7.0 Refactored to use WP_Term_Query, and to support any WP_Term_Query arguments.
1836
 *
1837
 * @global wpdb $wpdb WordPress database abstraction object.
1838
 *
1839
 * @param int|array    $object_ids The ID(s) of the object(s) to retrieve.
1840
 * @param string|array $taxonomies The taxonomies to retrieve terms from.
1841
 * @param array|string $args       See WP_Term_Query::__construct() for supported arguments.
1842
 * @return array|WP_Error The requested term data or empty array if no terms found.
1843
 *                        WP_Error if any of the $taxonomies don't exist.
1844
 */
1845
function wp_get_object_terms($object_ids, $taxonomies, $args = array()) {
1846
	global $wpdb;
1847
1848
	if ( empty( $object_ids ) || empty( $taxonomies ) )
1849
		return array();
1850
1851
	if ( !is_array($taxonomies) )
1852
		$taxonomies = array($taxonomies);
1853
1854
	foreach ( $taxonomies as $taxonomy ) {
1855
		if ( ! taxonomy_exists($taxonomy) )
1856
			return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
1857
	}
1858
1859
	if ( !is_array($object_ids) )
1860
		$object_ids = array($object_ids);
1861
	$object_ids = array_map('intval', $object_ids);
1862
1863
	$args = wp_parse_args( $args );
1864
1865
	/*
1866
	 * When one or more queried taxonomies is registered with an 'args' array,
1867
	 * those params override the `$args` passed to this function.
1868
	 */
1869
	$terms = array();
1870
	if ( count( $taxonomies ) > 1 ) {
1871
		foreach ( $taxonomies as $index => $taxonomy ) {
1872
			$t = get_taxonomy( $taxonomy );
1873
			if ( isset( $t->args ) && is_array( $t->args ) && $args != array_merge( $args, $t->args ) ) {
1874
				unset( $taxonomies[ $index ] );
1875
				$terms = array_merge( $terms, wp_get_object_terms( $object_ids, $taxonomy, array_merge( $args, $t->args ) ) );
1876
			}
1877
		}
1878
	} else {
1879
		$t = get_taxonomy( $taxonomies[0] );
1880
		if ( isset( $t->args ) && is_array( $t->args ) ) {
1881
			$args = array_merge( $args, $t->args );
1882
		}
1883
	}
1884
1885
	$args['taxonomy'] = $taxonomies;
1886
	$args['object_ids'] = $object_ids;
1887
1888
	$terms = array_merge( $terms, get_terms( $args ) );
1889
1890
	/**
1891
	 * Filters the terms for a given object or objects.
1892
	 *
1893
	 * @since 4.2.0
1894
	 *
1895
	 * @param array $terms      An array of terms for the given object or objects.
1896
	 * @param array $object_ids Array of object IDs for which `$terms` were retrieved.
1897
	 * @param array $taxonomies Array of taxonomies from which `$terms` were retrieved.
1898
	 * @param array $args       An array of arguments for retrieving terms for the given
1899
	 *                          object(s). See wp_get_object_terms() for details.
1900
	 */
1901
	$terms = apply_filters( 'get_object_terms', $terms, $object_ids, $taxonomies, $args );
1902
1903
	$object_ids = implode( ',', $object_ids );
1904
	$taxonomies = "'" . implode( "', '", array_map( 'esc_sql', $taxonomies ) ) . "'";
1905
1906
	/**
1907
	 * Filters the terms for a given object or objects.
1908
	 *
1909
	 * The `$taxonomies` parameter passed to this filter is formatted as a SQL fragment. The
1910
	 * {@see 'get_object_terms'} filter is recommended as an alternative.
1911
	 *
1912
	 * @since 2.8.0
1913
	 *
1914
	 * @param array     $terms      An array of terms for the given object or objects.
1915
	 * @param int|array $object_ids Object ID or array of IDs.
1916
	 * @param string    $taxonomies SQL-formatted (comma-separated and quoted) list of taxonomy names.
1917
	 * @param array     $args       An array of arguments for retrieving terms for the given object(s).
1918
	 *                              See wp_get_object_terms() for details.
1919
	 */
1920
	return apply_filters( 'wp_get_object_terms', $terms, $object_ids, $taxonomies, $args );
1921
}
1922
1923
/**
1924
 * Add a new term to the database.
1925
 *
1926
 * A non-existent term is inserted in the following sequence:
1927
 * 1. The term is added to the term table, then related to the taxonomy.
1928
 * 2. If everything is correct, several actions are fired.
1929
 * 3. The 'term_id_filter' is evaluated.
1930
 * 4. The term cache is cleaned.
1931
 * 5. Several more actions are fired.
1932
 * 6. An array is returned containing the term_id and term_taxonomy_id.
1933
 *
1934
 * If the 'slug' argument is not empty, then it is checked to see if the term
1935
 * is invalid. If it is not a valid, existing term, it is added and the term_id
1936
 * is given.
1937
 *
1938
 * If the taxonomy is hierarchical, and the 'parent' argument is not empty,
1939
 * the term is inserted and the term_id will be given.
1940
 *
1941
 * Error handling:
1942
 * If $taxonomy does not exist or $term is empty,
1943
 * a WP_Error object will be returned.
1944
 *
1945
 * If the term already exists on the same hierarchical level,
1946
 * or the term slug and name are not unique, a WP_Error object will be returned.
1947
 *
1948
 * @global wpdb $wpdb WordPress database abstraction object.
1949
 *
1950
 * @since 2.3.0
1951
 *
1952
 * @param string       $term     The term to add or update.
1953
 * @param string       $taxonomy The taxonomy to which to add the term.
1954
 * @param array|string $args {
1955
 *     Optional. Array or string of arguments for inserting a term.
1956
 *
1957
 *     @type string $alias_of    Slug of the term to make this term an alias of.
1958
 *                               Default empty string. Accepts a term slug.
1959
 *     @type string $description The term description. Default empty string.
1960
 *     @type int    $parent      The id of the parent term. Default 0.
1961
 *     @type string $slug        The term slug to use. Default empty string.
1962
 * }
1963
 * @return array|WP_Error An array containing the `term_id` and `term_taxonomy_id`,
1964
 *                        WP_Error otherwise.
1965
 */
1966
function wp_insert_term( $term, $taxonomy, $args = array() ) {
1967
	global $wpdb;
1968
1969
	if ( ! taxonomy_exists($taxonomy) ) {
1970
		return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
1971
	}
1972
	/**
1973
	 * Filters a term before it is sanitized and inserted into the database.
1974
	 *
1975
	 * @since 3.0.0
1976
	 *
1977
	 * @param string $term     The term to add or update.
1978
	 * @param string $taxonomy Taxonomy slug.
1979
	 */
1980
	$term = apply_filters( 'pre_insert_term', $term, $taxonomy );
1981
	if ( is_wp_error( $term ) ) {
1982
		return $term;
1983
	}
1984
	if ( is_int( $term ) && 0 == $term ) {
1985
		return new WP_Error( 'invalid_term_id', __( 'Invalid term ID.' ) );
1986
	}
1987
	if ( '' == trim( $term ) ) {
1988
		return new WP_Error( 'empty_term_name', __( 'A name is required for this term.' ) );
1989
	}
1990
	$defaults = array( 'alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => '');
1991
	$args = wp_parse_args( $args, $defaults );
1992
1993 View Code Duplication
	if ( $args['parent'] > 0 && ! term_exists( (int) $args['parent'] ) ) {
1994
		return new WP_Error( 'missing_parent', __( 'Parent term does not exist.' ) );
1995
	}
1996
1997
	$args['name'] = $term;
1998
	$args['taxonomy'] = $taxonomy;
1999
2000
	// Coerce null description to strings, to avoid database errors.
2001
	$args['description'] = (string) $args['description'];
2002
2003
	$args = sanitize_term($args, $taxonomy, 'db');
2004
2005
	// expected_slashed ($name)
2006
	$name = wp_unslash( $args['name'] );
2007
	$description = wp_unslash( $args['description'] );
2008
	$parent = (int) $args['parent'];
2009
2010
	$slug_provided = ! empty( $args['slug'] );
2011
	if ( ! $slug_provided ) {
2012
		$slug = sanitize_title( $name );
2013
	} else {
2014
		$slug = $args['slug'];
2015
	}
2016
2017
	$term_group = 0;
2018 View Code Duplication
	if ( $args['alias_of'] ) {
2019
		$alias = get_term_by( 'slug', $args['alias_of'], $taxonomy );
2020
		if ( ! empty( $alias->term_group ) ) {
2021
			// The alias we want is already in a group, so let's use that one.
2022
			$term_group = $alias->term_group;
2023
		} elseif ( ! empty( $alias->term_id ) ) {
2024
			/*
2025
			 * The alias is not in a group, so we create a new one
2026
			 * and add the alias to it.
2027
			 */
2028
			$term_group = $wpdb->get_var("SELECT MAX(term_group) FROM $wpdb->terms") + 1;
2029
2030
			wp_update_term( $alias->term_id, $taxonomy, array(
2031
				'term_group' => $term_group,
2032
			) );
2033
		}
2034
	}
2035
2036
	/*
2037
	 * Prevent the creation of terms with duplicate names at the same level of a taxonomy hierarchy,
2038
	 * unless a unique slug has been explicitly provided.
2039
	 */
2040
	$name_matches = get_terms( $taxonomy, array(
2041
		'name' => $name,
2042
		'hide_empty' => false,
2043
		'parent' => $args['parent'],
2044
	) );
2045
2046
	/*
2047
	 * The `name` match in `get_terms()` doesn't differentiate accented characters,
2048
	 * so we do a stricter comparison here.
2049
	 */
2050
	$name_match = null;
2051
	if ( $name_matches ) {
2052
		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...
2053
			if ( strtolower( $name ) === strtolower( $_match->name ) ) {
2054
				$name_match = $_match;
2055
				break;
2056
			}
2057
		}
2058
	}
2059
2060
	if ( $name_match ) {
2061
		$slug_match = get_term_by( 'slug', $slug, $taxonomy );
2062
		if ( ! $slug_provided || $name_match->slug === $slug || $slug_match ) {
2063
			if ( is_taxonomy_hierarchical( $taxonomy ) ) {
2064
				$siblings = get_terms( $taxonomy, array( 'get' => 'all', 'parent' => $parent ) );
2065
2066
				$existing_term = null;
2067
				if ( ( ! $slug_provided || $name_match->slug === $slug ) && in_array( $name, wp_list_pluck( $siblings, 'name' ) ) ) {
2068
					$existing_term = $name_match;
2069
				} elseif ( $slug_match && in_array( $slug, wp_list_pluck( $siblings, 'slug' ) ) ) {
2070
					$existing_term = $slug_match;
2071
				}
2072
2073
				if ( $existing_term ) {
2074
					return new WP_Error( 'term_exists', __( 'A term with the name provided already exists with this parent.' ), $existing_term->term_id );
2075
				}
2076
			} else {
2077
				return new WP_Error( 'term_exists', __( 'A term with the name provided already exists in this taxonomy.' ), $name_match->term_id );
2078
			}
2079
		}
2080
	}
2081
2082
	$slug = wp_unique_term_slug( $slug, (object) $args );
2083
2084
	$data = compact( 'name', 'slug', 'term_group' );
2085
2086
	/**
2087
	 * Filters term data before it is inserted into the database.
2088
	 *
2089
	 * @since 4.7.0
2090
	 *
2091
	 * @param array  $data     Term data to be inserted.
2092
	 * @param string $taxonomy Taxonomy slug.
2093
	 * @param array  $args     Arguments passed to wp_insert_term().
2094
	 */
2095
	$data = apply_filters( 'wp_insert_term_data', $data, $taxonomy, $args );
2096
2097 View Code Duplication
	if ( false === $wpdb->insert( $wpdb->terms, $data ) ) {
2098
		return new WP_Error( 'db_insert_error', __( 'Could not insert term into the database' ), $wpdb->last_error );
2099
	}
2100
2101
	$term_id = (int) $wpdb->insert_id;
2102
2103
	// Seems unreachable, However, Is used in the case that a term name is provided, which sanitizes to an empty string.
2104
	if ( empty($slug) ) {
2105
		$slug = sanitize_title($slug, $term_id);
2106
2107
		/** This action is documented in wp-includes/taxonomy.php */
2108
		do_action( 'edit_terms', $term_id, $taxonomy );
2109
		$wpdb->update( $wpdb->terms, compact( 'slug' ), compact( 'term_id' ) );
2110
2111
		/** This action is documented in wp-includes/taxonomy.php */
2112
		do_action( 'edited_terms', $term_id, $taxonomy );
2113
	}
2114
2115
	$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 ) );
2116
2117
	if ( !empty($tt_id) ) {
2118
		return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
2119
	}
2120
	$wpdb->insert( $wpdb->term_taxonomy, compact( 'term_id', 'taxonomy', 'description', 'parent') + array( 'count' => 0 ) );
2121
	$tt_id = (int) $wpdb->insert_id;
2122
2123
	/*
2124
	 * Sanity check: if we just created a term with the same parent + taxonomy + slug but a higher term_id than
2125
	 * an existing term, then we have unwittingly created a duplicate term. Delete the dupe, and use the term_id
2126
	 * and term_taxonomy_id of the older term instead. Then return out of the function so that the "create" hooks
2127
	 * are not fired.
2128
	 */
2129
	$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 ) );
2130
	if ( $duplicate_term ) {
2131
		$wpdb->delete( $wpdb->terms, array( 'term_id' => $term_id ) );
2132
		$wpdb->delete( $wpdb->term_taxonomy, array( 'term_taxonomy_id' => $tt_id ) );
2133
2134
		$term_id = (int) $duplicate_term->term_id;
2135
		$tt_id   = (int) $duplicate_term->term_taxonomy_id;
2136
2137
		clean_term_cache( $term_id, $taxonomy );
2138
		return array( 'term_id' => $term_id, 'term_taxonomy_id' => $tt_id );
2139
	}
2140
2141
	/**
2142
	 * Fires immediately after a new term is created, before the term cache is cleaned.
2143
	 *
2144
	 * @since 2.3.0
2145
	 *
2146
	 * @param int    $term_id  Term ID.
2147
	 * @param int    $tt_id    Term taxonomy ID.
2148
	 * @param string $taxonomy Taxonomy slug.
2149
	 */
2150
	do_action( "create_term", $term_id, $tt_id, $taxonomy );
2151
2152
	/**
2153
	 * Fires after a new term is created for a specific taxonomy.
2154
	 *
2155
	 * The dynamic portion of the hook name, `$taxonomy`, refers
2156
	 * to the slug of the taxonomy the term was created for.
2157
	 *
2158
	 * @since 2.3.0
2159
	 *
2160
	 * @param int $term_id Term ID.
2161
	 * @param int $tt_id   Term taxonomy ID.
2162
	 */
2163
	do_action( "create_{$taxonomy}", $term_id, $tt_id );
2164
2165
	/**
2166
	 * Filters the term ID after a new term is created.
2167
	 *
2168
	 * @since 2.3.0
2169
	 *
2170
	 * @param int $term_id Term ID.
2171
	 * @param int $tt_id   Taxonomy term ID.
2172
	 */
2173
	$term_id = apply_filters( 'term_id_filter', $term_id, $tt_id );
2174
2175
	clean_term_cache($term_id, $taxonomy);
2176
2177
	/**
2178
	 * Fires after a new term is created, and after the term cache has been cleaned.
2179
	 *
2180
	 * @since 2.3.0
2181
	 *
2182
	 * @param int    $term_id  Term ID.
2183
	 * @param int    $tt_id    Term taxonomy ID.
2184
	 * @param string $taxonomy Taxonomy slug.
2185
	 */
2186
	do_action( 'created_term', $term_id, $tt_id, $taxonomy );
2187
2188
	/**
2189
	 * Fires after a new term in a specific taxonomy is created, and after the term
2190
	 * cache has been cleaned.
2191
	 *
2192
	 * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
2193
	 *
2194
	 * @since 2.3.0
2195
	 *
2196
	 * @param int $term_id Term ID.
2197
	 * @param int $tt_id   Term taxonomy ID.
2198
	 */
2199
	do_action( "created_{$taxonomy}", $term_id, $tt_id );
2200
2201
	return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
2202
}
2203
2204
/**
2205
 * Create Term and Taxonomy Relationships.
2206
 *
2207
 * Relates an object (post, link etc) to a term and taxonomy type. Creates the
2208
 * term and taxonomy relationship if it doesn't already exist. Creates a term if
2209
 * it doesn't exist (using the slug).
2210
 *
2211
 * A relationship means that the term is grouped in or belongs to the taxonomy.
2212
 * A term has no meaning until it is given context by defining which taxonomy it
2213
 * exists under.
2214
 *
2215
 * @since 2.3.0
2216
 *
2217
 * @global wpdb $wpdb The WordPress database abstraction object.
2218
 *
2219
 * @param int              $object_id The object to relate to.
2220
 * @param string|int|array $terms     A single term slug, single term id, or array of either term slugs or ids.
2221
 *                                    Will replace all existing related terms in this taxonomy. Passing an
2222
 *                                    empty value will remove all related terms.
2223
 * @param string           $taxonomy  The context in which to relate the term to the object.
2224
 * @param bool             $append    Optional. If false will delete difference of terms. Default false.
2225
 * @return array|WP_Error Term taxonomy IDs of the affected terms.
2226
 */
2227
function wp_set_object_terms( $object_id, $terms, $taxonomy, $append = false ) {
2228
	global $wpdb;
2229
2230
	$object_id = (int) $object_id;
2231
2232
	if ( ! taxonomy_exists( $taxonomy ) ) {
2233
		return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
2234
	}
2235
2236
	if ( !is_array($terms) )
2237
		$terms = array($terms);
2238
2239
	if ( ! $append )
2240
		$old_tt_ids =  wp_get_object_terms($object_id, $taxonomy, array('fields' => 'tt_ids', 'orderby' => 'none'));
2241
	else
2242
		$old_tt_ids = array();
2243
2244
	$tt_ids = array();
2245
	$term_ids = array();
2246
	$new_tt_ids = array();
2247
2248
	foreach ( (array) $terms as $term) {
2249
		if ( !strlen(trim($term)) )
2250
			continue;
2251
2252
		if ( !$term_info = term_exists($term, $taxonomy) ) {
2253
			// Skip if a non-existent term ID is passed.
2254
			if ( is_int($term) )
2255
				continue;
2256
			$term_info = wp_insert_term($term, $taxonomy);
2257
		}
2258
		if ( is_wp_error($term_info) )
2259
			return $term_info;
2260
		$term_ids[] = $term_info['term_id'];
2261
		$tt_id = $term_info['term_taxonomy_id'];
2262
		$tt_ids[] = $tt_id;
2263
2264
		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 ) ) )
2265
			continue;
2266
2267
		/**
2268
		 * Fires immediately before an object-term relationship is added.
2269
		 *
2270
		 * @since 2.9.0
2271
		 * @since 4.7.0 Added the `$taxonomy` parameter.
2272
		 *
2273
		 * @param int    $object_id Object ID.
2274
		 * @param int    $tt_id     Term taxonomy ID.
2275
		 * @param string $taxonomy  Taxonomy slug.
2276
		 */
2277
		do_action( 'add_term_relationship', $object_id, $tt_id, $taxonomy );
2278
		$wpdb->insert( $wpdb->term_relationships, array( 'object_id' => $object_id, 'term_taxonomy_id' => $tt_id ) );
2279
2280
		/**
2281
		 * Fires immediately after an object-term relationship is added.
2282
		 *
2283
		 * @since 2.9.0
2284
		 * @since 4.7.0 Added the `$taxonomy` parameter.
2285
		 *
2286
		 * @param int    $object_id Object ID.
2287
		 * @param int    $tt_id     Term taxonomy ID.
2288
		 * @param string $taxonomy  Taxonomy slug.
2289
		 */
2290
		do_action( 'added_term_relationship', $object_id, $tt_id, $taxonomy );
2291
		$new_tt_ids[] = $tt_id;
2292
	}
2293
2294
	if ( $new_tt_ids )
0 ignored issues
show
Bug Best Practice introduced by
The expression $new_tt_ids of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2295
		wp_update_term_count( $new_tt_ids, $taxonomy );
2296
2297
	if ( ! $append ) {
2298
		$delete_tt_ids = array_diff( $old_tt_ids, $tt_ids );
2299
2300
		if ( $delete_tt_ids ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $delete_tt_ids of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2301
			$in_delete_tt_ids = "'" . implode( "', '", $delete_tt_ids ) . "'";
2302
			$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 ) );
2303
			$delete_term_ids = array_map( 'intval', $delete_term_ids );
2304
2305
			$remove = wp_remove_object_terms( $object_id, $delete_term_ids, $taxonomy );
2306
			if ( is_wp_error( $remove ) ) {
2307
				return $remove;
2308
			}
2309
		}
2310
	}
2311
2312
	$t = get_taxonomy($taxonomy);
2313
	if ( ! $append && isset($t->sort) && $t->sort ) {
2314
		$values = array();
2315
		$term_order = 0;
2316
		$final_tt_ids = wp_get_object_terms($object_id, $taxonomy, array('fields' => 'tt_ids'));
2317
		foreach ( $tt_ids as $tt_id )
2318
			if ( in_array($tt_id, $final_tt_ids) )
2319
				$values[] = $wpdb->prepare( "(%d, %d, %d)", $object_id, $tt_id, ++$term_order);
2320
		if ( $values )
0 ignored issues
show
Bug Best Practice introduced by
The expression $values of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2321
			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)" ) )
2322
				return new WP_Error( 'db_insert_error', __( 'Could not insert term relationship into the database' ), $wpdb->last_error );
2323
	}
2324
2325
	wp_cache_delete( $object_id, $taxonomy . '_relationships' );
2326
	wp_cache_delete( 'last_changed', 'terms' );
2327
2328
	/**
2329
	 * Fires after an object's terms have been set.
2330
	 *
2331
	 * @since 2.8.0
2332
	 *
2333
	 * @param int    $object_id  Object ID.
2334
	 * @param array  $terms      An array of object terms.
2335
	 * @param array  $tt_ids     An array of term taxonomy IDs.
2336
	 * @param string $taxonomy   Taxonomy slug.
2337
	 * @param bool   $append     Whether to append new terms to the old terms.
2338
	 * @param array  $old_tt_ids Old array of term taxonomy IDs.
2339
	 */
2340
	do_action( 'set_object_terms', $object_id, $terms, $tt_ids, $taxonomy, $append, $old_tt_ids );
2341
	return $tt_ids;
2342
}
2343
2344
/**
2345
 * Add term(s) associated with a given object.
2346
 *
2347
 * @since 3.6.0
2348
 *
2349
 * @param int              $object_id The ID of the object to which the terms will be added.
2350
 * @param string|int|array $terms     The slug(s) or ID(s) of the term(s) to add.
2351
 * @param array|string     $taxonomy  Taxonomy name.
2352
 * @return array|WP_Error Term taxonomy IDs of the affected terms.
2353
 */
2354
function wp_add_object_terms( $object_id, $terms, $taxonomy ) {
2355
	return wp_set_object_terms( $object_id, $terms, $taxonomy, true );
0 ignored issues
show
It seems like $taxonomy defined by parameter $taxonomy on line 2354 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...
2356
}
2357
2358
/**
2359
 * Remove term(s) associated with a given object.
2360
 *
2361
 * @since 3.6.0
2362
 *
2363
 * @global wpdb $wpdb WordPress database abstraction object.
2364
 *
2365
 * @param int              $object_id The ID of the object from which the terms will be removed.
2366
 * @param string|int|array $terms     The slug(s) or ID(s) of the term(s) to remove.
2367
 * @param array|string     $taxonomy  Taxonomy name.
2368
 * @return bool|WP_Error True on success, false or WP_Error on failure.
2369
 */
2370
function wp_remove_object_terms( $object_id, $terms, $taxonomy ) {
2371
	global $wpdb;
2372
2373
	$object_id = (int) $object_id;
2374
2375
	if ( ! taxonomy_exists( $taxonomy ) ) {
0 ignored issues
show
It seems like $taxonomy defined by parameter $taxonomy on line 2370 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...
2376
		return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
2377
	}
2378
2379
	if ( ! is_array( $terms ) ) {
2380
		$terms = array( $terms );
2381
	}
2382
2383
	$tt_ids = array();
2384
2385
	foreach ( (array) $terms as $term ) {
2386
		if ( ! strlen( trim( $term ) ) ) {
2387
			continue;
2388
		}
2389
2390
		if ( ! $term_info = term_exists( $term, $taxonomy ) ) {
0 ignored issues
show
It seems like $taxonomy defined by parameter $taxonomy on line 2370 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...
2391
			// Skip if a non-existent term ID is passed.
2392
			if ( is_int( $term ) ) {
2393
				continue;
2394
			}
2395
		}
2396
2397
		if ( is_wp_error( $term_info ) ) {
2398
			return $term_info;
2399
		}
2400
2401
		$tt_ids[] = $term_info['term_taxonomy_id'];
2402
	}
2403
2404
	if ( $tt_ids ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $tt_ids of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2405
		$in_tt_ids = "'" . implode( "', '", $tt_ids ) . "'";
2406
2407
		/**
2408
		 * Fires immediately before an object-term relationship is deleted.
2409
		 *
2410
		 * @since 2.9.0
2411
		 * @since 4.7.0 Added the `$taxonomy` parameter.
2412
		 *
2413
		 * @param int   $object_id Object ID.
2414
		 * @param array $tt_ids    An array of term taxonomy IDs.
2415
		 * @param string $taxonomy  Taxonomy slug.
2416
		 */
2417
		do_action( 'delete_term_relationships', $object_id, $tt_ids, $taxonomy );
2418
		$deleted = $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->term_relationships WHERE object_id = %d AND term_taxonomy_id IN ($in_tt_ids)", $object_id ) );
2419
2420
		wp_cache_delete( $object_id, $taxonomy . '_relationships' );
2421
		wp_cache_delete( 'last_changed', 'terms' );
2422
2423
		/**
2424
		 * Fires immediately after an object-term relationship is deleted.
2425
		 *
2426
		 * @since 2.9.0
2427
		 * @since 4.7.0 Added the `$taxonomy` parameter.
2428
		 *
2429
		 * @param int    $object_id Object ID.
2430
		 * @param array  $tt_ids    An array of term taxonomy IDs.
2431
		 * @param string $taxonomy  Taxonomy slug.
2432
		 */
2433
		do_action( 'deleted_term_relationships', $object_id, $tt_ids, $taxonomy );
2434
2435
		wp_update_term_count( $tt_ids, $taxonomy );
0 ignored issues
show
It seems like $taxonomy defined by parameter $taxonomy on line 2370 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...
2436
2437
		return (bool) $deleted;
2438
	}
2439
2440
	return false;
2441
}
2442
2443
/**
2444
 * Will make slug unique, if it isn't already.
2445
 *
2446
 * The `$slug` has to be unique global to every taxonomy, meaning that one
2447
 * taxonomy term can't have a matching slug with another taxonomy term. Each
2448
 * slug has to be globally unique for every taxonomy.
2449
 *
2450
 * The way this works is that if the taxonomy that the term belongs to is
2451
 * hierarchical and has a parent, it will append that parent to the $slug.
2452
 *
2453
 * If that still doesn't return an unique slug, then it try to append a number
2454
 * until it finds a number that is truly unique.
2455
 *
2456
 * The only purpose for `$term` is for appending a parent, if one exists.
2457
 *
2458
 * @since 2.3.0
2459
 *
2460
 * @global wpdb $wpdb WordPress database abstraction object.
2461
 *
2462
 * @param string $slug The string that will be tried for a unique slug.
2463
 * @param object $term The term object that the `$slug` will belong to.
2464
 * @return string Will return a true unique slug.
2465
 */
2466
function wp_unique_term_slug( $slug, $term ) {
2467
	global $wpdb;
2468
2469
	$needs_suffix = true;
2470
	$original_slug = $slug;
2471
2472
	// As of 4.1, duplicate slugs are allowed as long as they're in different taxonomies.
2473
	if ( ! term_exists( $slug ) || get_option( 'db_version' ) >= 30133 && ! get_term_by( 'slug', $slug, $term->taxonomy ) ) {
2474
		$needs_suffix = false;
2475
	}
2476
2477
	/*
2478
	 * If the taxonomy supports hierarchy and the term has a parent, make the slug unique
2479
	 * by incorporating parent slugs.
2480
	 */
2481
	$parent_suffix = '';
2482
	if ( $needs_suffix && is_taxonomy_hierarchical( $term->taxonomy ) && ! empty( $term->parent ) ) {
2483
		$the_parent = $term->parent;
2484
		while ( ! empty($the_parent) ) {
2485
			$parent_term = get_term($the_parent, $term->taxonomy);
2486
			if ( is_wp_error($parent_term) || empty($parent_term) )
2487
				break;
2488
			$parent_suffix .= '-' . $parent_term->slug;
2489
			if ( ! term_exists( $slug . $parent_suffix ) ) {
2490
				break;
2491
			}
2492
2493
			if ( empty($parent_term->parent) )
2494
				break;
2495
			$the_parent = $parent_term->parent;
2496
		}
2497
	}
2498
2499
	// If we didn't get a unique slug, try appending a number to make it unique.
2500
2501
	/**
2502
	 * Filters whether the proposed unique term slug is bad.
2503
	 *
2504
	 * @since 4.3.0
2505
	 *
2506
	 * @param bool   $needs_suffix Whether the slug needs to be made unique with a suffix.
2507
	 * @param string $slug         The slug.
2508
	 * @param object $term         Term object.
2509
	 */
2510
	if ( apply_filters( 'wp_unique_term_slug_is_bad_slug', $needs_suffix, $slug, $term ) ) {
2511
		if ( $parent_suffix ) {
2512
			$slug .= $parent_suffix;
2513
		} else {
2514
			if ( ! empty( $term->term_id ) )
2515
				$query = $wpdb->prepare( "SELECT slug FROM $wpdb->terms WHERE slug = %s AND term_id != %d", $slug, $term->term_id );
2516
			else
2517
				$query = $wpdb->prepare( "SELECT slug FROM $wpdb->terms WHERE slug = %s", $slug );
2518
2519
			if ( $wpdb->get_var( $query ) ) {
2520
				$num = 2;
2521 View Code Duplication
				do {
2522
					$alt_slug = $slug . "-$num";
2523
					$num++;
2524
					$slug_check = $wpdb->get_var( $wpdb->prepare( "SELECT slug FROM $wpdb->terms WHERE slug = %s", $alt_slug ) );
2525
				} while ( $slug_check );
2526
				$slug = $alt_slug;
2527
			}
2528
		}
2529
	}
2530
2531
	/**
2532
	 * Filters the unique term slug.
2533
	 *
2534
	 * @since 4.3.0
2535
	 *
2536
	 * @param string $slug          Unique term slug.
2537
	 * @param object $term          Term object.
2538
	 * @param string $original_slug Slug originally passed to the function for testing.
2539
	 */
2540
	return apply_filters( 'wp_unique_term_slug', $slug, $term, $original_slug );
2541
}
2542
2543
/**
2544
 * Update term based on arguments provided.
2545
 *
2546
 * The $args will indiscriminately override all values with the same field name.
2547
 * Care must be taken to not override important information need to update or
2548
 * update will fail (or perhaps create a new term, neither would be acceptable).
2549
 *
2550
 * Defaults will set 'alias_of', 'description', 'parent', and 'slug' if not
2551
 * defined in $args already.
2552
 *
2553
 * 'alias_of' will create a term group, if it doesn't already exist, and update
2554
 * it for the $term.
2555
 *
2556
 * If the 'slug' argument in $args is missing, then the 'name' in $args will be
2557
 * used. It should also be noted that if you set 'slug' and it isn't unique then
2558
 * a WP_Error will be passed back. If you don't pass any slug, then a unique one
2559
 * will be created for you.
2560
 *
2561
 * For what can be overrode in `$args`, check the term scheme can contain and stay
2562
 * away from the term keys.
2563
 *
2564
 * @since 2.3.0
2565
 *
2566
 * @global wpdb $wpdb WordPress database abstraction object.
2567
 *
2568
 * @param int          $term_id  The ID of the term
2569
 * @param string       $taxonomy The context in which to relate the term to the object.
2570
 * @param array|string $args     Optional. Array of get_terms() arguments. Default empty array.
2571
 * @return array|WP_Error Returns Term ID and Taxonomy Term ID
0 ignored issues
show
Should the return type not be WP_Error|array|WP_Term|null? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
2572
 */
2573
function wp_update_term( $term_id, $taxonomy, $args = array() ) {
2574
	global $wpdb;
2575
2576
	if ( ! taxonomy_exists( $taxonomy ) ) {
2577
		return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
2578
	}
2579
2580
	$term_id = (int) $term_id;
2581
2582
	// First, get all of the original args
2583
	$term = get_term( $term_id, $taxonomy );
2584
2585
	if ( is_wp_error( $term ) ) {
2586
		return $term;
2587
	}
2588
2589
	if ( ! $term ) {
2590
		return new WP_Error( 'invalid_term', __( 'Empty Term' ) );
2591
	}
2592
2593
	$term = (array) $term->data;
2594
2595
	// Escape data pulled from DB.
2596
	$term = wp_slash( $term );
2597
2598
	// Merge old and new args with new args overwriting old ones.
2599
	$args = array_merge($term, $args);
2600
2601
	$defaults = array( 'alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => '');
2602
	$args = wp_parse_args($args, $defaults);
2603
	$args = sanitize_term($args, $taxonomy, 'db');
2604
	$parsed_args = $args;
2605
2606
	// expected_slashed ($name)
2607
	$name = wp_unslash( $args['name'] );
2608
	$description = wp_unslash( $args['description'] );
2609
2610
	$parsed_args['name'] = $name;
2611
	$parsed_args['description'] = $description;
2612
2613
	if ( '' == trim( $name ) ) {
2614
		return new WP_Error( 'empty_term_name', __( 'A name is required for this term.' ) );
2615
	}
2616
2617 View Code Duplication
	if ( $parsed_args['parent'] > 0 && ! term_exists( (int) $parsed_args['parent'] ) ) {
2618
		return new WP_Error( 'missing_parent', __( 'Parent term does not exist.' ) );
2619
	}
2620
2621
	$empty_slug = false;
2622
	if ( empty( $args['slug'] ) ) {
2623
		$empty_slug = true;
2624
		$slug = sanitize_title($name);
2625
	} else {
2626
		$slug = $args['slug'];
2627
	}
2628
2629
	$parsed_args['slug'] = $slug;
2630
2631
	$term_group = isset( $parsed_args['term_group'] ) ? $parsed_args['term_group'] : 0;
2632 View Code Duplication
	if ( $args['alias_of'] ) {
2633
		$alias = get_term_by( 'slug', $args['alias_of'], $taxonomy );
2634
		if ( ! empty( $alias->term_group ) ) {
2635
			// The alias we want is already in a group, so let's use that one.
2636
			$term_group = $alias->term_group;
2637
		} elseif ( ! empty( $alias->term_id ) ) {
2638
			/*
2639
			 * The alias is not in a group, so we create a new one
2640
			 * and add the alias to it.
2641
			 */
2642
			$term_group = $wpdb->get_var("SELECT MAX(term_group) FROM $wpdb->terms") + 1;
2643
2644
			wp_update_term( $alias->term_id, $taxonomy, array(
2645
				'term_group' => $term_group,
2646
			) );
2647
		}
2648
2649
		$parsed_args['term_group'] = $term_group;
2650
	}
2651
2652
	/**
2653
	 * Filters the term parent.
2654
	 *
2655
	 * Hook to this filter to see if it will cause a hierarchy loop.
2656
	 *
2657
	 * @since 3.1.0
2658
	 *
2659
	 * @param int    $parent      ID of the parent term.
2660
	 * @param int    $term_id     Term ID.
2661
	 * @param string $taxonomy    Taxonomy slug.
2662
	 * @param array  $parsed_args An array of potentially altered update arguments for the given term.
2663
	 * @param array  $args        An array of update arguments for the given term.
2664
	 */
2665
	$parent = apply_filters( 'wp_update_term_parent', $args['parent'], $term_id, $taxonomy, $parsed_args, $args );
2666
2667
	// Check for duplicate slug
2668
	$duplicate = get_term_by( 'slug', $slug, $taxonomy );
2669
	if ( $duplicate && $duplicate->term_id != $term_id ) {
2670
		// If an empty slug was passed or the parent changed, reset the slug to something unique.
2671
		// Otherwise, bail.
2672
		if ( $empty_slug || ( $parent != $term['parent']) ) {
2673
			$slug = wp_unique_term_slug($slug, (object) $args);
2674
		} else {
2675
			/* translators: 1: Taxonomy term slug */
2676
			return new WP_Error('duplicate_term_slug', sprintf(__('The slug &#8220;%s&#8221; is already in use by another term'), $slug));
2677
		}
2678
	}
2679
2680
	$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) );
2681
2682
	// Check whether this is a shared term that needs splitting.
2683
	$_term_id = _split_shared_term( $term_id, $tt_id );
2684
	if ( ! is_wp_error( $_term_id ) ) {
2685
		$term_id = $_term_id;
2686
	}
2687
2688
	/**
2689
	 * Fires immediately before the given terms are edited.
2690
	 *
2691
	 * @since 2.9.0
2692
	 *
2693
	 * @param int    $term_id  Term ID.
2694
	 * @param string $taxonomy Taxonomy slug.
2695
	 */
2696
	do_action( 'edit_terms', $term_id, $taxonomy );
2697
2698
	$data = compact( 'name', 'slug', 'term_group' );
2699
2700
	/**
2701
	 * Filters term data before it is updated in the database.
2702
	 *
2703
	 * @since 4.7.0
2704
	 *
2705
	 * @param array  $data     Term data to be updated.
2706
	 * @param int    $term_id  Term ID.
2707
	 * @param string $taxonomy Taxonomy slug.
2708
	 * @param array  $args     Arguments passed to wp_update_term().
2709
	 */
2710
	$data = apply_filters( 'wp_update_term_data', $data, $term_id, $taxonomy, $args );
2711
2712
	$wpdb->update( $wpdb->terms, $data, compact( 'term_id' ) );
2713
	if ( empty($slug) ) {
2714
		$slug = sanitize_title($name, $term_id);
2715
		$wpdb->update( $wpdb->terms, compact( 'slug' ), compact( 'term_id' ) );
2716
	}
2717
2718
	/**
2719
	 * Fires immediately after the given terms are edited.
2720
	 *
2721
	 * @since 2.9.0
2722
	 *
2723
	 * @param int    $term_id  Term ID
2724
	 * @param string $taxonomy Taxonomy slug.
2725
	 */
2726
	do_action( 'edited_terms', $term_id, $taxonomy );
2727
2728
	/**
2729
	 * Fires immediate before a term-taxonomy relationship is updated.
2730
	 *
2731
	 * @since 2.9.0
2732
	 *
2733
	 * @param int    $tt_id    Term taxonomy ID.
2734
	 * @param string $taxonomy Taxonomy slug.
2735
	 */
2736
	do_action( 'edit_term_taxonomy', $tt_id, $taxonomy );
2737
2738
	$wpdb->update( $wpdb->term_taxonomy, compact( 'term_id', 'taxonomy', 'description', 'parent' ), array( 'term_taxonomy_id' => $tt_id ) );
2739
2740
	/**
2741
	 * Fires immediately after a term-taxonomy relationship is updated.
2742
	 *
2743
	 * @since 2.9.0
2744
	 *
2745
	 * @param int    $tt_id    Term taxonomy ID.
2746
	 * @param string $taxonomy Taxonomy slug.
2747
	 */
2748
	do_action( 'edited_term_taxonomy', $tt_id, $taxonomy );
2749
2750
	/**
2751
	 * Fires after a term has been updated, but before the term cache has been cleaned.
2752
	 *
2753
	 * @since 2.3.0
2754
	 *
2755
	 * @param int    $term_id  Term ID.
2756
	 * @param int    $tt_id    Term taxonomy ID.
2757
	 * @param string $taxonomy Taxonomy slug.
2758
	 */
2759
	do_action( "edit_term", $term_id, $tt_id, $taxonomy );
2760
2761
	/**
2762
	 * Fires after a term in a specific taxonomy has been updated, but before the term
2763
	 * cache has been cleaned.
2764
	 *
2765
	 * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
2766
	 *
2767
	 * @since 2.3.0
2768
	 *
2769
	 * @param int $term_id Term ID.
2770
	 * @param int $tt_id   Term taxonomy ID.
2771
	 */
2772
	do_action( "edit_{$taxonomy}", $term_id, $tt_id );
2773
2774
	/** This filter is documented in wp-includes/taxonomy.php */
2775
	$term_id = apply_filters( 'term_id_filter', $term_id, $tt_id );
2776
2777
	clean_term_cache($term_id, $taxonomy);
2778
2779
	/**
2780
	 * Fires after a term has been updated, and the term cache has been cleaned.
2781
	 *
2782
	 * @since 2.3.0
2783
	 *
2784
	 * @param int    $term_id  Term ID.
2785
	 * @param int    $tt_id    Term taxonomy ID.
2786
	 * @param string $taxonomy Taxonomy slug.
2787
	 */
2788
	do_action( "edited_term", $term_id, $tt_id, $taxonomy );
2789
2790
	/**
2791
	 * Fires after a term for a specific taxonomy has been updated, and the term
2792
	 * cache has been cleaned.
2793
	 *
2794
	 * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
2795
	 *
2796
	 * @since 2.3.0
2797
	 *
2798
	 * @param int $term_id Term ID.
2799
	 * @param int $tt_id   Term taxonomy ID.
2800
	 */
2801
	do_action( "edited_{$taxonomy}", $term_id, $tt_id );
2802
2803
	return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
2804
}
2805
2806
/**
2807
 * Enable or disable term counting.
2808
 *
2809
 * @since 2.5.0
2810
 *
2811
 * @staticvar bool $_defer
2812
 *
2813
 * @param bool $defer Optional. Enable if true, disable if false.
2814
 * @return bool Whether term counting is enabled or disabled.
2815
 */
2816 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...
2817
	static $_defer = false;
2818
2819
	if ( is_bool($defer) ) {
2820
		$_defer = $defer;
2821
		// flush any deferred counts
2822
		if ( !$defer )
2823
			wp_update_term_count( null, null, true );
2824
	}
2825
2826
	return $_defer;
2827
}
2828
2829
/**
2830
 * Updates the amount of terms in taxonomy.
2831
 *
2832
 * If there is a taxonomy callback applied, then it will be called for updating
2833
 * the count.
2834
 *
2835
 * The default action is to count what the amount of terms have the relationship
2836
 * of term ID. Once that is done, then update the database.
2837
 *
2838
 * @since 2.3.0
2839
 *
2840
 * @staticvar array $_deferred
2841
 *
2842
 * @param int|array $terms       The term_taxonomy_id of the terms.
2843
 * @param string    $taxonomy    The context of the term.
2844
 * @param bool      $do_deferred Whether to flush the deferred term counts too. Default false.
2845
 * @return bool If no terms will return false, and if successful will return true.
2846
 */
2847
function wp_update_term_count( $terms, $taxonomy, $do_deferred = false ) {
2848
	static $_deferred = array();
2849
2850
	if ( $do_deferred ) {
2851
		foreach ( (array) array_keys($_deferred) as $tax ) {
2852
			wp_update_term_count_now( $_deferred[$tax], $tax );
2853
			unset( $_deferred[$tax] );
2854
		}
2855
	}
2856
2857
	if ( empty($terms) )
2858
		return false;
2859
2860
	if ( !is_array($terms) )
2861
		$terms = array($terms);
2862
2863
	if ( wp_defer_term_counting() ) {
2864
		if ( !isset($_deferred[$taxonomy]) )
2865
			$_deferred[$taxonomy] = array();
2866
		$_deferred[$taxonomy] = array_unique( array_merge($_deferred[$taxonomy], $terms) );
2867
		return true;
2868
	}
2869
2870
	return wp_update_term_count_now( $terms, $taxonomy );
2871
}
2872
2873
/**
2874
 * Perform term count update immediately.
2875
 *
2876
 * @since 2.5.0
2877
 *
2878
 * @param array  $terms    The term_taxonomy_id of terms to update.
2879
 * @param string $taxonomy The context of the term.
2880
 * @return true Always true when complete.
0 ignored issues
show
Should the return type not be boolean?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
2881
 */
2882
function wp_update_term_count_now( $terms, $taxonomy ) {
2883
	$terms = array_map('intval', $terms);
2884
2885
	$taxonomy = get_taxonomy($taxonomy);
2886
	if ( !empty($taxonomy->update_count_callback) ) {
2887
		call_user_func($taxonomy->update_count_callback, $terms, $taxonomy);
2888
	} else {
2889
		$object_types = (array) $taxonomy->object_type;
2890
		foreach ( $object_types as &$object_type ) {
2891
			if ( 0 === strpos( $object_type, 'attachment:' ) )
2892
				list( $object_type ) = explode( ':', $object_type );
2893
		}
2894
2895
		if ( $object_types == array_filter( $object_types, 'post_type_exists' ) ) {
2896
			// Only post types are attached to this taxonomy
2897
			_update_post_term_count( $terms, $taxonomy );
0 ignored issues
show
It seems like $taxonomy defined by get_taxonomy($taxonomy) on line 2885 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...
2898
		} else {
2899
			// Default count updater
2900
			_update_generic_term_count( $terms, $taxonomy );
0 ignored issues
show
It seems like $taxonomy defined by get_taxonomy($taxonomy) on line 2885 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...
2901
		}
2902
	}
2903
2904
	clean_term_cache($terms, '', false);
2905
2906
	return true;
2907
}
2908
2909
//
2910
// Cache
2911
//
2912
2913
/**
2914
 * Removes the taxonomy relationship to terms from the cache.
2915
 *
2916
 * Will remove the entire taxonomy relationship containing term `$object_id`. The
2917
 * term IDs have to exist within the taxonomy `$object_type` for the deletion to
2918
 * take place.
2919
 *
2920
 * @since 2.3.0
2921
 *
2922
 * @global bool $_wp_suspend_cache_invalidation
2923
 *
2924
 * @see get_object_taxonomies() for more on $object_type.
2925
 *
2926
 * @param int|array    $object_ids  Single or list of term object ID(s).
2927
 * @param array|string $object_type The taxonomy object type.
2928
 */
2929
function clean_object_term_cache($object_ids, $object_type) {
2930
	global $_wp_suspend_cache_invalidation;
2931
2932
	if ( ! empty( $_wp_suspend_cache_invalidation ) ) {
2933
		return;
2934
	}
2935
2936
	if ( !is_array($object_ids) )
2937
		$object_ids = array($object_ids);
2938
2939
	$taxonomies = get_object_taxonomies( $object_type );
2940
2941
	foreach ( $object_ids as $id ) {
2942
		foreach ( $taxonomies as $taxonomy ) {
2943
			wp_cache_delete($id, "{$taxonomy}_relationships");
2944
		}
2945
	}
2946
2947
	/**
2948
	 * Fires after the object term cache has been cleaned.
2949
	 *
2950
	 * @since 2.5.0
2951
	 *
2952
	 * @param array  $object_ids An array of object IDs.
2953
	 * @param string $object_type Object type.
2954
	 */
2955
	do_action( 'clean_object_term_cache', $object_ids, $object_type );
2956
}
2957
2958
/**
2959
 * Will remove all of the term ids from the cache.
2960
 *
2961
 * @since 2.3.0
2962
 *
2963
 * @global wpdb $wpdb WordPress database abstraction object.
2964
 * @global bool $_wp_suspend_cache_invalidation
2965
 *
2966
 * @param int|array $ids            Single or list of Term IDs.
2967
 * @param string    $taxonomy       Optional. Can be empty and will assume `tt_ids`, else will use for context.
2968
 *                                  Default empty.
2969
 * @param bool      $clean_taxonomy Optional. Whether to clean taxonomy wide caches (true), or just individual
2970
 *                                  term object caches (false). Default true.
2971
 */
2972
function clean_term_cache($ids, $taxonomy = '', $clean_taxonomy = true) {
2973
	global $wpdb, $_wp_suspend_cache_invalidation;
2974
2975
	if ( ! empty( $_wp_suspend_cache_invalidation ) ) {
2976
		return;
2977
	}
2978
2979
	if ( !is_array($ids) )
2980
		$ids = array($ids);
2981
2982
	$taxonomies = array();
2983
	// If no taxonomy, assume tt_ids.
2984
	if ( empty($taxonomy) ) {
2985
		$tt_ids = array_map('intval', $ids);
2986
		$tt_ids = implode(', ', $tt_ids);
2987
		$terms = $wpdb->get_results("SELECT term_id, taxonomy FROM $wpdb->term_taxonomy WHERE term_taxonomy_id IN ($tt_ids)");
2988
		$ids = array();
2989
		foreach ( (array) $terms as $term ) {
2990
			$taxonomies[] = $term->taxonomy;
2991
			$ids[] = $term->term_id;
2992
			wp_cache_delete( $term->term_id, 'terms' );
2993
		}
2994
		$taxonomies = array_unique($taxonomies);
2995
	} else {
2996
		$taxonomies = array($taxonomy);
2997
		foreach ( $taxonomies as $taxonomy ) {
2998
			foreach ( $ids as $id ) {
2999
				wp_cache_delete( $id, 'terms' );
3000
			}
3001
		}
3002
	}
3003
3004
	foreach ( $taxonomies as $taxonomy ) {
3005
		if ( $clean_taxonomy ) {
3006
			wp_cache_delete('all_ids', $taxonomy);
3007
			wp_cache_delete('get', $taxonomy);
3008
			delete_option("{$taxonomy}_children");
3009
			// Regenerate {$taxonomy}_children
3010
			_get_term_hierarchy($taxonomy);
3011
		}
3012
3013
		/**
3014
		 * Fires once after each taxonomy's term cache has been cleaned.
3015
		 *
3016
		 * @since 2.5.0
3017
		 * @since 4.5.0 Added the `$clean_taxonomy` parameter.
3018
		 *
3019
		 * @param array  $ids            An array of term IDs.
3020
		 * @param string $taxonomy       Taxonomy slug.
3021
		 * @param bool   $clean_taxonomy Whether or not to clean taxonomy-wide caches
3022
		 */
3023
		do_action( 'clean_term_cache', $ids, $taxonomy, $clean_taxonomy );
3024
	}
3025
3026
	wp_cache_set( 'last_changed', microtime(), 'terms' );
3027
}
3028
3029
/**
3030
 * Retrieves the taxonomy relationship to the term object id.
3031
 *
3032
 * Upstream functions (like get_the_terms() and is_object_in_term()) are
3033
 * responsible for populating the object-term relationship cache. The current
3034
 * function only fetches relationship data that is already in the cache.
3035
 *
3036
 * @since 2.3.0
3037
 * @since 4.7.0 Returns a WP_Error object if get_term() returns an error for
3038
 *              any of the matched terms.
3039
 *
3040
 * @param int    $id       Term object ID.
3041
 * @param string $taxonomy Taxonomy name.
3042
 * @return bool|array|WP_Error Array of `WP_Term` objects, if cached.
0 ignored issues
show
Should the return type not be false|array|WP_Term|WP_Error|null? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
3043
 *                             False if cache is empty for `$taxonomy` and `$id`.
3044
 *                             WP_Error if get_term() returns an error object for any term.
3045
 */
3046
function get_object_term_cache( $id, $taxonomy ) {
3047
	$_term_ids = wp_cache_get( $id, "{$taxonomy}_relationships" );
3048
3049
	// We leave the priming of relationship caches to upstream functions.
3050
	if ( false === $_term_ids ) {
3051
		return false;
3052
	}
3053
3054
	// Backward compatibility for if a plugin is putting objects into the cache, rather than IDs.
3055
	$term_ids = array();
3056
	foreach ( $_term_ids as $term_id ) {
3057
		if ( is_numeric( $term_id ) ) {
3058
			$term_ids[] = intval( $term_id );
3059
		} elseif ( isset( $term_id->term_id ) ) {
3060
			$term_ids[] = intval( $term_id->term_id );
3061
		}
3062
	}
3063
3064
	// Fill the term objects.
3065
	_prime_term_caches( $term_ids );
3066
3067
	$terms = array();
3068
	foreach ( $term_ids as $term_id ) {
3069
		$term = get_term( $term_id, $taxonomy );
3070
		if ( is_wp_error( $term ) ) {
3071
			return $term;
3072
		}
3073
3074
		$terms[] = $term;
3075
	}
3076
3077
	return $terms;
3078
}
3079
3080
/**
3081
 * Updates the cache for the given term object ID(s).
3082
 *
3083
 * Note: Due to performance concerns, great care should be taken to only update
3084
 * term caches when necessary. Processing time can increase exponentially depending
3085
 * on both the number of passed term IDs and the number of taxonomies those terms
3086
 * belong to.
3087
 *
3088
 * Caches will only be updated for terms not already cached.
3089
 *
3090
 * @since 2.3.0
3091
 *
3092
 * @param string|array $object_ids  Comma-separated list or array of term object IDs.
3093
 * @param array|string $object_type The taxonomy object type.
3094
 * @return void|false False if all of the terms in `$object_ids` are already cached.
3095
 */
3096
function update_object_term_cache($object_ids, $object_type) {
3097
	if ( empty($object_ids) )
3098
		return;
3099
3100
	if ( !is_array($object_ids) )
3101
		$object_ids = explode(',', $object_ids);
3102
3103
	$object_ids = array_map('intval', $object_ids);
3104
3105
	$taxonomies = get_object_taxonomies($object_type);
3106
3107
	$ids = array();
3108
	foreach ( (array) $object_ids as $id ) {
3109
		foreach ( $taxonomies as $taxonomy ) {
3110
			if ( false === wp_cache_get($id, "{$taxonomy}_relationships") ) {
3111
				$ids[] = $id;
3112
				break;
3113
			}
3114
		}
3115
	}
3116
3117
	if ( empty( $ids ) )
3118
		return false;
3119
3120
	$terms = wp_get_object_terms( $ids, $taxonomies, array(
3121
		'fields' => 'all_with_object_id',
3122
		'orderby' => 'name',
3123
		'update_term_meta_cache' => false,
3124
	) );
3125
3126
	$object_terms = array();
3127
	foreach ( (array) $terms as $term ) {
3128
		$object_terms[ $term->object_id ][ $term->taxonomy ][] = $term->term_id;
3129
	}
3130
3131
	foreach ( $ids as $id ) {
3132
		foreach ( $taxonomies as $taxonomy ) {
3133
			if ( ! isset($object_terms[$id][$taxonomy]) ) {
3134
				if ( !isset($object_terms[$id]) )
3135
					$object_terms[$id] = array();
3136
				$object_terms[$id][$taxonomy] = array();
3137
			}
3138
		}
3139
	}
3140
3141
	foreach ( $object_terms as $id => $value ) {
3142
		foreach ( $value as $taxonomy => $terms ) {
3143
			wp_cache_add( $id, $terms, "{$taxonomy}_relationships" );
3144
		}
3145
	}
3146
}
3147
3148
/**
3149
 * Updates Terms to Taxonomy in cache.
3150
 *
3151
 * @since 2.3.0
3152
 *
3153
 * @param array  $terms    List of term objects to change.
3154
 * @param string $taxonomy Optional. Update Term to this taxonomy in cache. Default empty.
3155
 */
3156
function update_term_cache( $terms, $taxonomy = '' ) {
0 ignored issues
show
The parameter $taxonomy is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
3157
	foreach ( (array) $terms as $term ) {
3158
		// Create a copy in case the array was passed by reference.
3159
		$_term = clone $term;
3160
3161
		// Object ID should not be cached.
3162
		unset( $_term->object_id );
3163
3164
		wp_cache_add( $term->term_id, $_term, 'terms' );
3165
	}
3166
}
3167
3168
//
3169
// Private
3170
//
3171
3172
/**
3173
 * Retrieves children of taxonomy as Term IDs.
3174
 *
3175
 * @ignore
3176
 * @since 2.3.0
3177
 *
3178
 * @param string $taxonomy Taxonomy name.
3179
 * @return array Empty if $taxonomy isn't hierarchical or returns children as Term IDs.
3180
 */
3181
function _get_term_hierarchy( $taxonomy ) {
3182
	if ( !is_taxonomy_hierarchical($taxonomy) )
3183
		return array();
3184
	$children = get_option("{$taxonomy}_children");
3185
3186
	if ( is_array($children) )
3187
		return $children;
3188
	$children = array();
3189
	$terms = get_terms($taxonomy, array('get' => 'all', 'orderby' => 'id', 'fields' => 'id=>parent'));
3190
	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...
3191
		if ( $parent > 0 )
3192
			$children[$parent][] = $term_id;
3193
	}
3194
	update_option("{$taxonomy}_children", $children);
3195
3196
	return $children;
3197
}
3198
3199
/**
3200
 * Get the subset of $terms that are descendants of $term_id.
3201
 *
3202
 * If `$terms` is an array of objects, then _get_term_children() returns an array of objects.
3203
 * If `$terms` is an array of IDs, then _get_term_children() returns an array of IDs.
3204
 *
3205
 * @access private
3206
 * @since 2.3.0
3207
 *
3208
 * @param int    $term_id   The ancestor term: all returned terms should be descendants of `$term_id`.
3209
 * @param array  $terms     The set of terms - either an array of term objects or term IDs - from which those that
3210
 *                          are descendants of $term_id will be chosen.
3211
 * @param string $taxonomy  The taxonomy which determines the hierarchy of the terms.
3212
 * @param array  $ancestors Optional. Term ancestors that have already been identified. Passed by reference, to keep
3213
 *                          track of found terms when recursing the hierarchy. The array of located ancestors is used
3214
 *                          to prevent infinite recursion loops. For performance, `term_ids` are used as array keys,
3215
 *                          with 1 as value. Default empty array.
3216
 * @return array|WP_Error The subset of $terms that are descendants of $term_id.
0 ignored issues
show
Should the return type not be array|WP_Term|WP_Error|null? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
3217
 */
3218
function _get_term_children( $term_id, $terms, $taxonomy, &$ancestors = array() ) {
3219
	$empty_array = array();
3220
	if ( empty($terms) )
3221
		return $empty_array;
3222
3223
	$term_list = array();
3224
	$has_children = _get_term_hierarchy($taxonomy);
3225
3226
	if  ( ( 0 != $term_id ) && ! isset($has_children[$term_id]) )
3227
		return $empty_array;
3228
3229
	// Include the term itself in the ancestors array, so we can properly detect when a loop has occurred.
3230
	if ( empty( $ancestors ) ) {
3231
		$ancestors[ $term_id ] = 1;
3232
	}
3233
3234
	foreach ( (array) $terms as $term ) {
3235
		$use_id = false;
3236 View Code Duplication
		if ( !is_object($term) ) {
3237
			$term = get_term($term, $taxonomy);
3238
			if ( is_wp_error( $term ) )
3239
				return $term;
3240
			$use_id = true;
3241
		}
3242
3243
		// Don't recurse if we've already identified the term as a child - this indicates a loop.
3244
		if ( isset( $ancestors[ $term->term_id ] ) ) {
3245
			continue;
3246
		}
3247
3248
		if ( $term->parent == $term_id ) {
3249
			if ( $use_id )
3250
				$term_list[] = $term->term_id;
3251
			else
3252
				$term_list[] = $term;
3253
3254
			if ( !isset($has_children[$term->term_id]) )
3255
				continue;
3256
3257
			$ancestors[ $term->term_id ] = 1;
3258
3259
			if ( $children = _get_term_children( $term->term_id, $terms, $taxonomy, $ancestors) )
3260
				$term_list = array_merge($term_list, $children);
3261
		}
3262
	}
3263
3264
	return $term_list;
3265
}
3266
3267
/**
3268
 * Add count of children to parent count.
3269
 *
3270
 * Recalculates term counts by including items from child terms. Assumes all
3271
 * relevant children are already in the $terms argument.
3272
 *
3273
 * @access private
3274
 * @since 2.3.0
3275
 *
3276
 * @global wpdb $wpdb WordPress database abstraction object.
3277
 *
3278
 * @param array  $terms    List of term objects, passed by reference.
3279
 * @param string $taxonomy Term context.
3280
 */
3281
function _pad_term_counts( &$terms, $taxonomy ) {
3282
	global $wpdb;
3283
3284
	// This function only works for hierarchical taxonomies like post categories.
3285
	if ( !is_taxonomy_hierarchical( $taxonomy ) )
3286
		return;
3287
3288
	$term_hier = _get_term_hierarchy($taxonomy);
3289
3290
	if ( empty($term_hier) )
3291
		return;
3292
3293
	$term_items = array();
3294
	$terms_by_id = array();
3295
	$term_ids = array();
3296
3297
	foreach ( (array) $terms as $key => $term ) {
3298
		$terms_by_id[$term->term_id] = & $terms[$key];
3299
		$term_ids[$term->term_taxonomy_id] = $term->term_id;
3300
	}
3301
3302
	// Get the object and term ids and stick them in a lookup table.
3303
	$tax_obj = get_taxonomy($taxonomy);
3304
	$object_types = esc_sql($tax_obj->object_type);
3305
	$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'");
3306
	foreach ( $results as $row ) {
3307
		$id = $term_ids[$row->term_taxonomy_id];
3308
		$term_items[$id][$row->object_id] = isset($term_items[$id][$row->object_id]) ? ++$term_items[$id][$row->object_id] : 1;
3309
	}
3310
3311
	// Touch every ancestor's lookup row for each post in each term.
3312
	foreach ( $term_ids as $term_id ) {
3313
		$child = $term_id;
3314
		$ancestors = array();
3315
		while ( !empty( $terms_by_id[$child] ) && $parent = $terms_by_id[$child]->parent ) {
3316
			$ancestors[] = $child;
3317
			if ( !empty( $term_items[$term_id] ) )
3318
				foreach ( $term_items[$term_id] as $item_id => $touches ) {
3319
					$term_items[$parent][$item_id] = isset($term_items[$parent][$item_id]) ? ++$term_items[$parent][$item_id]: 1;
3320
				}
3321
			$child = $parent;
3322
3323
			if ( in_array( $parent, $ancestors ) ) {
3324
				break;
3325
			}
3326
		}
3327
	}
3328
3329
	// Transfer the touched cells.
3330
	foreach ( (array) $term_items as $id => $items )
3331
		if ( isset($terms_by_id[$id]) )
3332
			$terms_by_id[$id]->count = count($items);
3333
}
3334
3335
/**
3336
 * Adds any terms from the given IDs to the cache that do not already exist in cache.
3337
 *
3338
 * @since 4.6.0
3339
 * @access private
3340
 *
3341
 * @global wpdb $wpdb WordPress database abstraction object.
3342
 *
3343
 * @param array $term_ids          Array of term IDs.
3344
 * @param bool  $update_meta_cache Optional. Whether to update the meta cache. Default true.
3345
 */
3346 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...
3347
	global $wpdb;
3348
3349
	$non_cached_ids = _get_non_cached_ids( $term_ids, 'terms' );
3350
	if ( ! empty( $non_cached_ids ) ) {
3351
		$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 ) ) ) );
3352
3353
		update_term_cache( $fresh_terms, $update_meta_cache );
3354
3355
		if ( $update_meta_cache ) {
3356
			update_termmeta_cache( $non_cached_ids );
3357
		}
3358
	}
3359
}
3360
3361
//
3362
// Default callbacks
3363
//
3364
3365
/**
3366
 * Will update term count based on object types of the current taxonomy.
3367
 *
3368
 * Private function for the default callback for post_tag and category
3369
 * taxonomies.
3370
 *
3371
 * @access private
3372
 * @since 2.3.0
3373
 *
3374
 * @global wpdb $wpdb WordPress database abstraction object.
3375
 *
3376
 * @param array  $terms    List of Term taxonomy IDs.
3377
 * @param object $taxonomy Current taxonomy object of terms.
3378
 */
3379
function _update_post_term_count( $terms, $taxonomy ) {
3380
	global $wpdb;
3381
3382
	$object_types = (array) $taxonomy->object_type;
3383
3384
	foreach ( $object_types as &$object_type )
3385
		list( $object_type ) = explode( ':', $object_type );
3386
3387
	$object_types = array_unique( $object_types );
3388
3389
	if ( false !== ( $check_attachments = array_search( 'attachment', $object_types ) ) ) {
3390
		unset( $object_types[ $check_attachments ] );
3391
		$check_attachments = true;
3392
	}
3393
3394
	if ( $object_types )
0 ignored issues
show
Bug Best Practice introduced by
The expression $object_types of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
3395
		$object_types = esc_sql( array_filter( $object_types, 'post_type_exists' ) );
3396
3397
	foreach ( (array) $terms as $term ) {
3398
		$count = 0;
3399
3400
		// Attachments can be 'inherit' status, we need to base count off the parent's status if so.
3401
		if ( $check_attachments )
3402
			$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 ) );
3403
3404
		if ( $object_types )
3405
			$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 ) );
3406
3407
		/** This action is documented in wp-includes/taxonomy.php */
3408
		do_action( 'edit_term_taxonomy', $term, $taxonomy->name );
3409
		$wpdb->update( $wpdb->term_taxonomy, compact( 'count' ), array( 'term_taxonomy_id' => $term ) );
3410
3411
		/** This action is documented in wp-includes/taxonomy.php */
3412
		do_action( 'edited_term_taxonomy', $term, $taxonomy->name );
3413
	}
3414
}
3415
3416
/**
3417
 * Will update term count based on number of objects.
3418
 *
3419
 * Default callback for the 'link_category' taxonomy.
3420
 *
3421
 * @since 3.3.0
3422
 *
3423
 * @global wpdb $wpdb WordPress database abstraction object.
3424
 *
3425
 * @param array  $terms    List of term taxonomy IDs.
3426
 * @param object $taxonomy Current taxonomy object of terms.
3427
 */
3428
function _update_generic_term_count( $terms, $taxonomy ) {
3429
	global $wpdb;
3430
3431
	foreach ( (array) $terms as $term ) {
3432
		$count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_relationships WHERE term_taxonomy_id = %d", $term ) );
3433
3434
		/** This action is documented in wp-includes/taxonomy.php */
3435
		do_action( 'edit_term_taxonomy', $term, $taxonomy->name );
3436
		$wpdb->update( $wpdb->term_taxonomy, compact( 'count' ), array( 'term_taxonomy_id' => $term ) );
3437
3438
		/** This action is documented in wp-includes/taxonomy.php */
3439
		do_action( 'edited_term_taxonomy', $term, $taxonomy->name );
3440
	}
3441
}
3442
3443
/**
3444
 * Create a new term for a term_taxonomy item that currently shares its term
3445
 * with another term_taxonomy.
3446
 *
3447
 * @ignore
3448
 * @since 4.2.0
3449
 * @since 4.3.0 Introduced `$record` parameter. Also, `$term_id` and
3450
 *              `$term_taxonomy_id` can now accept objects.
3451
 *
3452
 * @global wpdb $wpdb WordPress database abstraction object.
3453
 *
3454
 * @param int|object $term_id          ID of the shared term, or the shared term object.
3455
 * @param int|object $term_taxonomy_id ID of the term_taxonomy item to receive a new term, or the term_taxonomy object
3456
 *                                     (corresponding to a row from the term_taxonomy table).
3457
 * @param bool       $record           Whether to record data about the split term in the options table. The recording
3458
 *                                     process has the potential to be resource-intensive, so during batch operations
3459
 *                                     it can be beneficial to skip inline recording and do it just once, after the
3460
 *                                     batch is processed. Only set this to `false` if you know what you are doing.
3461
 *                                     Default: true.
3462
 * @return int|WP_Error When the current term does not need to be split (or cannot be split on the current
3463
 *                      database schema), `$term_id` is returned. When the term is successfully split, the
3464
 *                      new term_id is returned. A WP_Error is returned for miscellaneous errors.
3465
 */
3466
function _split_shared_term( $term_id, $term_taxonomy_id, $record = true ) {
3467
	global $wpdb;
3468
3469
	if ( is_object( $term_id ) ) {
3470
		$shared_term = $term_id;
3471
		$term_id = intval( $shared_term->term_id );
3472
	}
3473
3474
	if ( is_object( $term_taxonomy_id ) ) {
3475
		$term_taxonomy = $term_taxonomy_id;
3476
		$term_taxonomy_id = intval( $term_taxonomy->term_taxonomy_id );
3477
	}
3478
3479
	// If there are no shared term_taxonomy rows, there's nothing to do here.
3480
	$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 ) );
3481
3482
	if ( ! $shared_tt_count ) {
3483
		return $term_id;
3484
	}
3485
3486
	/*
3487
	 * Verify that the term_taxonomy_id passed to the function is actually associated with the term_id.
3488
	 * If there's a mismatch, it may mean that the term is already split. Return the actual term_id from the db.
3489
	 */
3490
	$check_term_id = $wpdb->get_var( $wpdb->prepare( "SELECT term_id FROM $wpdb->term_taxonomy WHERE term_taxonomy_id = %d", $term_taxonomy_id ) );
3491
	if ( $check_term_id != $term_id ) {
3492
		return $check_term_id;
3493
	}
3494
3495
	// Pull up data about the currently shared slug, which we'll use to populate the new one.
3496
	if ( empty( $shared_term ) ) {
3497
		$shared_term = $wpdb->get_row( $wpdb->prepare( "SELECT t.* FROM $wpdb->terms t WHERE t.term_id = %d", $term_id ) );
3498
	}
3499
3500
	$new_term_data = array(
3501
		'name' => $shared_term->name,
3502
		'slug' => $shared_term->slug,
3503
		'term_group' => $shared_term->term_group,
3504
	);
3505
3506 View Code Duplication
	if ( false === $wpdb->insert( $wpdb->terms, $new_term_data ) ) {
3507
		return new WP_Error( 'db_insert_error', __( 'Could not split shared term.' ), $wpdb->last_error );
3508
	}
3509
3510
	$new_term_id = (int) $wpdb->insert_id;
3511
3512
	// Update the existing term_taxonomy to point to the newly created term.
3513
	$wpdb->update( $wpdb->term_taxonomy,
3514
		array( 'term_id' => $new_term_id ),
3515
		array( 'term_taxonomy_id' => $term_taxonomy_id )
3516
	);
3517
3518
	// Reassign child terms to the new parent.
3519
	if ( empty( $term_taxonomy ) ) {
3520
		$term_taxonomy = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->term_taxonomy WHERE term_taxonomy_id = %d", $term_taxonomy_id ) );
3521
	}
3522
3523
	$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 ) );
3524
	if ( ! empty( $children_tt_ids ) ) {
3525
		foreach ( $children_tt_ids as $child_tt_id ) {
3526
			$wpdb->update( $wpdb->term_taxonomy,
3527
				array( 'parent' => $new_term_id ),
3528
				array( 'term_taxonomy_id' => $child_tt_id )
3529
			);
3530
			clean_term_cache( $term_id, $term_taxonomy->taxonomy );
3531
		}
3532
	} else {
3533
		// If the term has no children, we must force its taxonomy cache to be rebuilt separately.
3534
		clean_term_cache( $new_term_id, $term_taxonomy->taxonomy );
3535
	}
3536
3537
	// Clean the cache for term taxonomies formerly shared with the current term.
3538
	$shared_term_taxonomies = $wpdb->get_row( $wpdb->prepare( "SELECT taxonomy FROM $wpdb->term_taxonomy WHERE term_id = %d", $term_id ) );
3539
	if ( $shared_term_taxonomies ) {
3540
		foreach ( $shared_term_taxonomies as $shared_term_taxonomy ) {
3541
			clean_term_cache( $term_id, $shared_term_taxonomy );
3542
		}
3543
	}
3544
3545
	// Keep a record of term_ids that have been split, keyed by old term_id. See wp_get_split_term().
3546
	if ( $record ) {
3547
		$split_term_data = get_option( '_split_terms', array() );
3548
		if ( ! isset( $split_term_data[ $term_id ] ) ) {
3549
			$split_term_data[ $term_id ] = array();
3550
		}
3551
3552
		$split_term_data[ $term_id ][ $term_taxonomy->taxonomy ] = $new_term_id;
3553
		update_option( '_split_terms', $split_term_data );
3554
	}
3555
3556
	// If we've just split the final shared term, set the "finished" flag.
3557
	$shared_terms_exist = $wpdb->get_results(
3558
		"SELECT tt.term_id, t.*, count(*) as term_tt_count FROM {$wpdb->term_taxonomy} tt
3559
		 LEFT JOIN {$wpdb->terms} t ON t.term_id = tt.term_id
3560
		 GROUP BY t.term_id
3561
		 HAVING term_tt_count > 1
3562
		 LIMIT 1"
3563
	);
3564
	if ( ! $shared_terms_exist ) {
3565
		update_option( 'finished_splitting_shared_terms', true );
3566
	}
3567
3568
	/**
3569
	 * Fires after a previously shared taxonomy term is split into two separate terms.
3570
	 *
3571
	 * @since 4.2.0
3572
	 *
3573
	 * @param int    $term_id          ID of the formerly shared term.
3574
	 * @param int    $new_term_id      ID of the new term created for the $term_taxonomy_id.
3575
	 * @param int    $term_taxonomy_id ID for the term_taxonomy row affected by the split.
3576
	 * @param string $taxonomy         Taxonomy for the split term.
3577
	 */
3578
	do_action( 'split_shared_term', $term_id, $new_term_id, $term_taxonomy_id, $term_taxonomy->taxonomy );
3579
3580
	return $new_term_id;
3581
}
3582
3583
/**
3584
 * Splits a batch of shared taxonomy terms.
3585
 *
3586
 * @since 4.3.0
3587
 *
3588
 * @global wpdb $wpdb WordPress database abstraction object.
3589
 */
3590
function _wp_batch_split_terms() {
3591
	global $wpdb;
3592
3593
	$lock_name = 'term_split.lock';
3594
3595
	// Try to lock.
3596
	$lock_result = $wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO `$wpdb->options` ( `option_name`, `option_value`, `autoload` ) VALUES (%s, %s, 'no') /* LOCK */", $lock_name, time() ) );
3597
3598
	if ( ! $lock_result ) {
3599
		$lock_result = get_option( $lock_name );
3600
3601
		// Bail if we were unable to create a lock, or if the existing lock is still valid.
3602
		if ( ! $lock_result || ( $lock_result > ( time() - HOUR_IN_SECONDS ) ) ) {
3603
			wp_schedule_single_event( time() + ( 5 * MINUTE_IN_SECONDS ), 'wp_split_shared_term_batch' );
3604
			return;
3605
		}
3606
	}
3607
3608
	// Update the lock, as by this point we've definitely got a lock, just need to fire the actions.
3609
	update_option( $lock_name, time() );
3610
3611
	// Get a list of shared terms (those with more than one associated row in term_taxonomy).
3612
	$shared_terms = $wpdb->get_results(
3613
		"SELECT tt.term_id, t.*, count(*) as term_tt_count FROM {$wpdb->term_taxonomy} tt
3614
		 LEFT JOIN {$wpdb->terms} t ON t.term_id = tt.term_id
3615
		 GROUP BY t.term_id
3616
		 HAVING term_tt_count > 1
3617
		 LIMIT 10"
3618
	);
3619
3620
	// No more terms, we're done here.
3621
	if ( ! $shared_terms ) {
3622
		update_option( 'finished_splitting_shared_terms', true );
3623
		delete_option( $lock_name );
3624
		return;
3625
	}
3626
3627
	// Shared terms found? We'll need to run this script again.
3628
	wp_schedule_single_event( time() + ( 2 * MINUTE_IN_SECONDS ), 'wp_split_shared_term_batch' );
3629
3630
	// Rekey shared term array for faster lookups.
3631
	$_shared_terms = array();
3632
	foreach ( $shared_terms as $shared_term ) {
3633
		$term_id = intval( $shared_term->term_id );
3634
		$_shared_terms[ $term_id ] = $shared_term;
3635
	}
3636
	$shared_terms = $_shared_terms;
3637
3638
	// Get term taxonomy data for all shared terms.
3639
	$shared_term_ids = implode( ',', array_keys( $shared_terms ) );
3640
	$shared_tts = $wpdb->get_results( "SELECT * FROM {$wpdb->term_taxonomy} WHERE `term_id` IN ({$shared_term_ids})" );
3641
3642
	// Split term data recording is slow, so we do it just once, outside the loop.
3643
	$split_term_data = get_option( '_split_terms', array() );
3644
	$skipped_first_term = $taxonomies = array();
3645
	foreach ( $shared_tts as $shared_tt ) {
3646
		$term_id = intval( $shared_tt->term_id );
3647
3648
		// Don't split the first tt belonging to a given term_id.
3649
		if ( ! isset( $skipped_first_term[ $term_id ] ) ) {
3650
			$skipped_first_term[ $term_id ] = 1;
3651
			continue;
3652
		}
3653
3654
		if ( ! isset( $split_term_data[ $term_id ] ) ) {
3655
			$split_term_data[ $term_id ] = array();
3656
		}
3657
3658
		// Keep track of taxonomies whose hierarchies need flushing.
3659
		if ( ! isset( $taxonomies[ $shared_tt->taxonomy ] ) ) {
3660
			$taxonomies[ $shared_tt->taxonomy ] = 1;
3661
		}
3662
3663
		// Split the term.
3664
		$split_term_data[ $term_id ][ $shared_tt->taxonomy ] = _split_shared_term( $shared_terms[ $term_id ], $shared_tt, false );
3665
	}
3666
3667
	// Rebuild the cached hierarchy for each affected taxonomy.
3668
	foreach ( array_keys( $taxonomies ) as $tax ) {
3669
		delete_option( "{$tax}_children" );
3670
		_get_term_hierarchy( $tax );
3671
	}
3672
3673
	update_option( '_split_terms', $split_term_data );
3674
3675
	delete_option( $lock_name );
3676
}
3677
3678
/**
3679
 * In order to avoid the _wp_batch_split_terms() job being accidentally removed,
3680
 * check that it's still scheduled while we haven't finished splitting terms.
3681
 *
3682
 * @ignore
3683
 * @since 4.3.0
3684
 */
3685
function _wp_check_for_scheduled_split_terms() {
3686
	if ( ! get_option( 'finished_splitting_shared_terms' ) && ! wp_next_scheduled( 'wp_split_shared_term_batch' ) ) {
3687
		wp_schedule_single_event( time() + MINUTE_IN_SECONDS, 'wp_split_shared_term_batch' );
3688
	}
3689
}
3690
3691
/**
3692
 * Check default categories when a term gets split to see if any of them need to be updated.
3693
 *
3694
 * @ignore
3695
 * @since 4.2.0
3696
 *
3697
 * @param int    $term_id          ID of the formerly shared term.
3698
 * @param int    $new_term_id      ID of the new term created for the $term_taxonomy_id.
3699
 * @param int    $term_taxonomy_id ID for the term_taxonomy row affected by the split.
3700
 * @param string $taxonomy         Taxonomy for the split term.
3701
 */
3702
function _wp_check_split_default_terms( $term_id, $new_term_id, $term_taxonomy_id, $taxonomy ) {
0 ignored issues
show
The parameter $term_taxonomy_id is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
3703
	if ( 'category' != $taxonomy ) {
3704
		return;
3705
	}
3706
3707
	foreach ( array( 'default_category', 'default_link_category', 'default_email_category' ) as $option ) {
3708
		if ( $term_id == get_option( $option, -1 ) ) {
3709
			update_option( $option, $new_term_id );
3710
		}
3711
	}
3712
}
3713
3714
/**
3715
 * Check menu items when a term gets split to see if any of them need to be updated.
3716
 *
3717
 * @ignore
3718
 * @since 4.2.0
3719
 *
3720
 * @global wpdb $wpdb WordPress database abstraction object.
3721
 *
3722
 * @param int    $term_id          ID of the formerly shared term.
3723
 * @param int    $new_term_id      ID of the new term created for the $term_taxonomy_id.
3724
 * @param int    $term_taxonomy_id ID for the term_taxonomy row affected by the split.
3725
 * @param string $taxonomy         Taxonomy for the split term.
3726
 */
3727
function _wp_check_split_terms_in_menus( $term_id, $new_term_id, $term_taxonomy_id, $taxonomy ) {
0 ignored issues
show
The parameter $term_taxonomy_id is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
3728
	global $wpdb;
3729
	$post_ids = $wpdb->get_col( $wpdb->prepare(
3730
		"SELECT m1.post_id
3731
		FROM {$wpdb->postmeta} AS m1
3732
			INNER JOIN {$wpdb->postmeta} AS m2 ON ( m2.post_id = m1.post_id )
3733
			INNER JOIN {$wpdb->postmeta} AS m3 ON ( m3.post_id = m1.post_id )
3734
		WHERE ( m1.meta_key = '_menu_item_type' AND m1.meta_value = 'taxonomy' )
3735
			AND ( m2.meta_key = '_menu_item_object' AND m2.meta_value = '%s' )
3736
			AND ( m3.meta_key = '_menu_item_object_id' AND m3.meta_value = %d )",
3737
		$taxonomy,
3738
		$term_id
3739
	) );
3740
3741
	if ( $post_ids ) {
3742
		foreach ( $post_ids as $post_id ) {
3743
			update_post_meta( $post_id, '_menu_item_object_id', $new_term_id, $term_id );
3744
		}
3745
	}
3746
}
3747
3748
/**
3749
 * If the term being split is a nav_menu, change associations.
3750
 *
3751
 * @ignore
3752
 * @since 4.3.0
3753
 *
3754
 * @param int    $term_id          ID of the formerly shared term.
3755
 * @param int    $new_term_id      ID of the new term created for the $term_taxonomy_id.
3756
 * @param int    $term_taxonomy_id ID for the term_taxonomy row affected by the split.
3757
 * @param string $taxonomy         Taxonomy for the split term.
3758
 */
3759
function _wp_check_split_nav_menu_terms( $term_id, $new_term_id, $term_taxonomy_id, $taxonomy ) {
0 ignored issues
show
The parameter $term_taxonomy_id is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
3760
	if ( 'nav_menu' !== $taxonomy ) {
3761
		return;
3762
	}
3763
3764
	// Update menu locations.
3765
	$locations = get_nav_menu_locations();
3766
	foreach ( $locations as $location => $menu_id ) {
3767
		if ( $term_id == $menu_id ) {
3768
			$locations[ $location ] = $new_term_id;
3769
		}
3770
	}
3771
	set_theme_mod( 'nav_menu_locations', $locations );
3772
}
3773
3774
/**
3775
 * Get data about terms that previously shared a single term_id, but have since been split.
3776
 *
3777
 * @since 4.2.0
3778
 *
3779
 * @param int $old_term_id Term ID. This is the old, pre-split term ID.
3780
 * @return array Array of new term IDs, keyed by taxonomy.
3781
 */
3782
function wp_get_split_terms( $old_term_id ) {
3783
	$split_terms = get_option( '_split_terms', array() );
3784
3785
	$terms = array();
3786
	if ( isset( $split_terms[ $old_term_id ] ) ) {
3787
		$terms = $split_terms[ $old_term_id ];
3788
	}
3789
3790
	return $terms;
3791
}
3792
3793
/**
3794
 * Get the new term ID corresponding to a previously split term.
3795
 *
3796
 * @since 4.2.0
3797
 *
3798
 * @param int    $old_term_id Term ID. This is the old, pre-split term ID.
3799
 * @param string $taxonomy    Taxonomy that the term belongs to.
3800
 * @return int|false If a previously split term is found corresponding to the old term_id and taxonomy,
3801
 *                   the new term_id will be returned. If no previously split term is found matching
3802
 *                   the parameters, returns false.
3803
 */
3804
function wp_get_split_term( $old_term_id, $taxonomy ) {
3805
	$split_terms = wp_get_split_terms( $old_term_id );
3806
3807
	$term_id = false;
3808
	if ( isset( $split_terms[ $taxonomy ] ) ) {
3809
		$term_id = (int) $split_terms[ $taxonomy ];
3810
	}
3811
3812
	return $term_id;
3813
}
3814
3815
/**
3816
 * Determine whether a term is shared between multiple taxonomies.
3817
 *
3818
 * Shared taxonomy terms began to be split in 4.3, but failed cron tasks or
3819
 * other delays in upgrade routines may cause shared terms to remain.
3820
 *
3821
 * @since 4.4.0
3822
 *
3823
 * @param int $term_id
3824
 * @return bool
3825
 */
3826
function wp_term_is_shared( $term_id ) {
3827
	global $wpdb;
3828
3829
	if ( get_option( 'finished_splitting_shared_terms' ) ) {
3830
		return false;
3831
	}
3832
3833
	$tt_count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_taxonomy WHERE term_id = %d", $term_id ) );
3834
3835
	return $tt_count > 1;
3836
}
3837
3838
/**
3839
 * Generate a permalink for a taxonomy term archive.
3840
 *
3841
 * @since 2.5.0
3842
 *
3843
 * @global WP_Rewrite $wp_rewrite
3844
 *
3845
 * @param object|int|string $term     The term object, ID, or slug whose link will be retrieved.
3846
 * @param string            $taxonomy Optional. Taxonomy. Default empty.
3847
 * @return string|WP_Error HTML link to taxonomy term archive on success, WP_Error if term does not exist.
3848
 */
3849
function get_term_link( $term, $taxonomy = '' ) {
3850
	global $wp_rewrite;
3851
3852 View Code Duplication
	if ( !is_object($term) ) {
3853
		if ( is_int( $term ) ) {
3854
			$term = get_term( $term, $taxonomy );
3855
		} else {
3856
			$term = get_term_by( 'slug', $term, $taxonomy );
3857
		}
3858
	}
3859
3860
	if ( !is_object($term) )
3861
		$term = new WP_Error('invalid_term', __('Empty Term'));
3862
3863
	if ( is_wp_error( $term ) )
3864
		return $term;
3865
3866
	$taxonomy = $term->taxonomy;
3867
3868
	$termlink = $wp_rewrite->get_extra_permastruct($taxonomy);
3869
3870
	$slug = $term->slug;
3871
	$t = get_taxonomy($taxonomy);
3872
3873
	if ( empty($termlink) ) {
3874
		if ( 'category' == $taxonomy )
3875
			$termlink = '?cat=' . $term->term_id;
3876
		elseif ( $t->query_var )
3877
			$termlink = "?$t->query_var=$slug";
3878
		else
3879
			$termlink = "?taxonomy=$taxonomy&term=$slug";
3880
		$termlink = home_url($termlink);
3881
	} else {
3882
		if ( $t->rewrite['hierarchical'] ) {
3883
			$hierarchical_slugs = array();
3884
			$ancestors = get_ancestors( $term->term_id, $taxonomy, 'taxonomy' );
3885
			foreach ( (array)$ancestors as $ancestor ) {
3886
				$ancestor_term = get_term($ancestor, $taxonomy);
3887
				$hierarchical_slugs[] = $ancestor_term->slug;
3888
			}
3889
			$hierarchical_slugs = array_reverse($hierarchical_slugs);
3890
			$hierarchical_slugs[] = $slug;
3891
			$termlink = str_replace("%$taxonomy%", implode('/', $hierarchical_slugs), $termlink);
3892
		} else {
3893
			$termlink = str_replace("%$taxonomy%", $slug, $termlink);
3894
		}
3895
		$termlink = home_url( user_trailingslashit($termlink, 'category') );
3896
	}
3897
	// Back Compat filters.
3898
	if ( 'post_tag' == $taxonomy ) {
3899
3900
		/**
3901
		 * Filters the tag link.
3902
		 *
3903
		 * @since 2.3.0
3904
		 * @deprecated 2.5.0 Use 'term_link' instead.
3905
		 *
3906
		 * @param string $termlink Tag link URL.
3907
		 * @param int    $term_id  Term ID.
3908
		 */
3909
		$termlink = apply_filters( 'tag_link', $termlink, $term->term_id );
3910
	} elseif ( 'category' == $taxonomy ) {
3911
3912
		/**
3913
		 * Filters the category link.
3914
		 *
3915
		 * @since 1.5.0
3916
		 * @deprecated 2.5.0 Use 'term_link' instead.
3917
		 *
3918
		 * @param string $termlink Category link URL.
3919
		 * @param int    $term_id  Term ID.
3920
		 */
3921
		$termlink = apply_filters( 'category_link', $termlink, $term->term_id );
3922
	}
3923
3924
	/**
3925
	 * Filters the term link.
3926
	 *
3927
	 * @since 2.5.0
3928
	 *
3929
	 * @param string $termlink Term link URL.
3930
	 * @param object $term     Term object.
3931
	 * @param string $taxonomy Taxonomy slug.
3932
	 */
3933
	return apply_filters( 'term_link', $termlink, $term, $taxonomy );
3934
}
3935
3936
/**
3937
 * Display the taxonomies of a post with available options.
3938
 *
3939
 * This function can be used within the loop to display the taxonomies for a
3940
 * post without specifying the Post ID. You can also use it outside the Loop to
3941
 * display the taxonomies for a specific post.
3942
 *
3943
 * @since 2.5.0
3944
 *
3945
 * @param array $args {
3946
 *     Arguments about which post to use and how to format the output. Shares all of the arguments
3947
 *     supported by get_the_taxonomies(), in addition to the following.
3948
 *
3949
 *     @type  int|WP_Post $post   Post ID or object to get taxonomies of. Default current post.
3950
 *     @type  string      $before Displays before the taxonomies. Default empty string.
3951
 *     @type  string      $sep    Separates each taxonomy. Default is a space.
3952
 *     @type  string      $after  Displays after the taxonomies. Default empty string.
3953
 * }
3954
 */
3955
function the_taxonomies( $args = array() ) {
3956
	$defaults = array(
3957
		'post' => 0,
3958
		'before' => '',
3959
		'sep' => ' ',
3960
		'after' => '',
3961
	);
3962
3963
	$r = wp_parse_args( $args, $defaults );
3964
3965
	echo $r['before'] . join( $r['sep'], get_the_taxonomies( $r['post'], $r ) ) . $r['after'];
3966
}
3967
3968
/**
3969
 * Retrieve all taxonomies associated with a post.
3970
 *
3971
 * This function can be used within the loop. It will also return an array of
3972
 * the taxonomies with links to the taxonomy and name.
3973
 *
3974
 * @since 2.5.0
3975
 *
3976
 * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global $post.
0 ignored issues
show
Consider making the type for parameter $post a bit more specific; maybe use integer.
Loading history...
3977
 * @param array $args {
3978
 *     Optional. Arguments about how to format the list of taxonomies. Default empty array.
3979
 *
3980
 *     @type string $template      Template for displaying a taxonomy label and list of terms.
3981
 *                                 Default is "Label: Terms."
3982
 *     @type string $term_template Template for displaying a single term in the list. Default is the term name
3983
 *                                 linked to its archive.
3984
 * }
3985
 * @return array List of taxonomies.
3986
 */
3987
function get_the_taxonomies( $post = 0, $args = array() ) {
3988
	$post = get_post( $post );
3989
3990
	$args = wp_parse_args( $args, array(
3991
		/* translators: %s: taxonomy label, %l: list of terms formatted as per $term_template */
3992
		'template' => __( '%s: %l.' ),
3993
		'term_template' => '<a href="%1$s">%2$s</a>',
3994
	) );
3995
3996
	$taxonomies = array();
3997
3998
	if ( ! $post ) {
3999
		return $taxonomies;
4000
	}
4001
4002
	foreach ( get_object_taxonomies( $post ) as $taxonomy ) {
4003
		$t = (array) get_taxonomy( $taxonomy );
4004
		if ( empty( $t['label'] ) ) {
4005
			$t['label'] = $taxonomy;
4006
		}
4007
		if ( empty( $t['args'] ) ) {
4008
			$t['args'] = array();
4009
		}
4010
		if ( empty( $t['template'] ) ) {
4011
			$t['template'] = $args['template'];
4012
		}
4013
		if ( empty( $t['term_template'] ) ) {
4014
			$t['term_template'] = $args['term_template'];
4015
		}
4016
4017
		$terms = get_object_term_cache( $post->ID, $taxonomy );
4018
		if ( false === $terms ) {
4019
			$terms = wp_get_object_terms( $post->ID, $taxonomy, $t['args'] );
4020
		}
4021
		$links = array();
4022
4023
		foreach ( $terms as $term ) {
0 ignored issues
show
The expression $terms of type array|object<WP_Error>|object<WP_Term>|null 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...
4024
			$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...
4025
		}
4026
		if ( $links ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $links of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
4027
			$taxonomies[$taxonomy] = wp_sprintf( $t['template'], $t['label'], $links, $terms );
4028
		}
4029
	}
4030
	return $taxonomies;
4031
}
4032
4033
/**
4034
 * Retrieve all taxonomies of a post with just the names.
4035
 *
4036
 * @since 2.5.0
4037
 *
4038
 * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global $post.
0 ignored issues
show
Consider making the type for parameter $post a bit more specific; maybe use integer.
Loading history...
4039
 * @return array An array of all taxonomy names for the given post.
4040
 */
4041
function get_post_taxonomies( $post = 0 ) {
4042
	$post = get_post( $post );
4043
4044
	return get_object_taxonomies($post);
4045
}
4046
4047
/**
4048
 * Determine if the given object is associated with any of the given terms.
4049
 *
4050
 * The given terms are checked against the object's terms' term_ids, names and slugs.
4051
 * Terms given as integers will only be checked against the object's terms' term_ids.
4052
 * If no terms are given, determines if object is associated with any terms in the given taxonomy.
4053
 *
4054
 * @since 2.7.0
4055
 *
4056
 * @param int              $object_id ID of the object (post ID, link ID, ...).
4057
 * @param string           $taxonomy  Single taxonomy name.
4058
 * @param int|string|array $terms     Optional. Term term_id, name, slug or array of said. Default null.
0 ignored issues
show
Should the type for parameter $terms not be integer|string|array|null? Also, consider making the array more specific, something like array<String>, or String[].

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
4059
 * @return bool|WP_Error WP_Error on input error.
0 ignored issues
show
Should the return type not be WP_Error|array|WP_Term|null|boolean? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
4060
 */
4061
function is_object_in_term( $object_id, $taxonomy, $terms = null ) {
4062
	if ( !$object_id = (int) $object_id )
4063
		return new WP_Error( 'invalid_object', __( 'Invalid object ID' ) );
4064
4065
	$object_terms = get_object_term_cache( $object_id, $taxonomy );
4066
	if ( false === $object_terms ) {
4067
		$object_terms = wp_get_object_terms( $object_id, $taxonomy, array( 'update_term_meta_cache' => false ) );
4068
		if ( is_wp_error( $object_terms ) ) {
4069
			return $object_terms;
4070
		}
4071
4072
		wp_cache_set( $object_id, wp_list_pluck( $object_terms, 'term_id' ), "{$taxonomy}_relationships" );
4073
	}
4074
4075
	if ( is_wp_error( $object_terms ) )
4076
		return $object_terms;
4077
	if ( empty( $object_terms ) )
4078
		return false;
4079
	if ( empty( $terms ) )
4080
		return ( !empty( $object_terms ) );
4081
4082
	$terms = (array) $terms;
4083
4084
	if ( $ints = array_filter( $terms, 'is_int' ) )
4085
		$strs = array_diff( $terms, $ints );
4086
	else
4087
		$strs =& $terms;
4088
4089
	foreach ( $object_terms as $object_term ) {
0 ignored issues
show
The expression $object_terms of type array|object<WP_Error>|object<WP_Term> 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...
4090
		// If term is an int, check against term_ids only.
4091
		if ( $ints && in_array( $object_term->term_id, $ints ) ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $ints of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
4092
			return true;
4093
		}
4094
4095
		if ( $strs ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $strs of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
4096
			// Only check numeric strings against term_id, to avoid false matches due to type juggling.
4097
			$numeric_strs = array_map( 'intval', array_filter( $strs, 'is_numeric' ) );
4098
			if ( in_array( $object_term->term_id, $numeric_strs, true ) ) {
4099
				return true;
4100
			}
4101
4102
			if ( in_array( $object_term->name, $strs ) ) return true;
4103
			if ( in_array( $object_term->slug, $strs ) ) return true;
4104
		}
4105
	}
4106
4107
	return false;
4108
}
4109
4110
/**
4111
 * Determine if the given object type is associated with the given taxonomy.
4112
 *
4113
 * @since 3.0.0
4114
 *
4115
 * @param string $object_type Object type string.
4116
 * @param string $taxonomy    Single taxonomy name.
4117
 * @return bool True if object is associated with the taxonomy, otherwise false.
4118
 */
4119
function is_object_in_taxonomy( $object_type, $taxonomy ) {
4120
	$taxonomies = get_object_taxonomies( $object_type );
4121
	if ( empty( $taxonomies ) ) {
4122
		return false;
4123
	}
4124
	return in_array( $taxonomy, $taxonomies );
4125
}
4126
4127
/**
4128
 * Get an array of ancestor IDs for a given object.
4129
 *
4130
 * @since 3.1.0
4131
 * @since 4.1.0 Introduced the `$resource_type` argument.
4132
 *
4133
 * @param int    $object_id     Optional. The ID of the object. Default 0.
4134
 * @param string $object_type   Optional. The type of object for which we'll be retrieving
4135
 *                              ancestors. Accepts a post type or a taxonomy name. Default empty.
4136
 * @param string $resource_type Optional. Type of resource $object_type is. Accepts 'post_type'
4137
 *                              or 'taxonomy'. Default empty.
4138
 * @return array An array of ancestors from lowest to highest in the hierarchy.
4139
 */
4140
function get_ancestors( $object_id = 0, $object_type = '', $resource_type = '' ) {
4141
	$object_id = (int) $object_id;
4142
4143
	$ancestors = array();
4144
4145
	if ( empty( $object_id ) ) {
4146
4147
		/** This filter is documented in wp-includes/taxonomy.php */
4148
		return apply_filters( 'get_ancestors', $ancestors, $object_id, $object_type, $resource_type );
4149
	}
4150
4151
	if ( ! $resource_type ) {
4152
		if ( is_taxonomy_hierarchical( $object_type ) ) {
4153
			$resource_type = 'taxonomy';
4154
		} elseif ( post_type_exists( $object_type ) ) {
4155
			$resource_type = 'post_type';
4156
		}
4157
	}
4158
4159
	if ( 'taxonomy' === $resource_type ) {
4160
		$term = get_term($object_id, $object_type);
4161
		while ( ! is_wp_error($term) && ! empty( $term->parent ) && ! in_array( $term->parent, $ancestors ) ) {
4162
			$ancestors[] = (int) $term->parent;
4163
			$term = get_term($term->parent, $object_type);
4164
		}
4165
	} elseif ( 'post_type' === $resource_type ) {
4166
		$ancestors = get_post_ancestors($object_id);
4167
	}
4168
4169
	/**
4170
	 * Filters a given object's ancestors.
4171
	 *
4172
	 * @since 3.1.0
4173
	 * @since 4.1.1 Introduced the `$resource_type` parameter.
4174
	 *
4175
	 * @param array  $ancestors     An array of object ancestors.
4176
	 * @param int    $object_id     Object ID.
4177
	 * @param string $object_type   Type of object.
4178
	 * @param string $resource_type Type of resource $object_type is.
4179
	 */
4180
	return apply_filters( 'get_ancestors', $ancestors, $object_id, $object_type, $resource_type );
4181
}
4182
4183
/**
4184
 * Returns the term's parent's term_ID.
4185
 *
4186
 * @since 3.1.0
4187
 *
4188
 * @param int    $term_id  Term ID.
4189
 * @param string $taxonomy Taxonomy name.
4190
 * @return int|false False on error.
4191
 */
4192
function wp_get_term_taxonomy_parent_id( $term_id, $taxonomy ) {
4193
	$term = get_term( $term_id, $taxonomy );
4194
	if ( ! $term || is_wp_error( $term ) ) {
4195
		return false;
4196
	}
4197
	return (int) $term->parent;
4198
}
4199
4200
/**
4201
 * Checks the given subset of the term hierarchy for hierarchy loops.
4202
 * Prevents loops from forming and breaks those that it finds.
4203
 *
4204
 * Attached to the {@see 'wp_update_term_parent'} filter.
4205
 *
4206
 * @since 3.1.0
4207
 *
4208
 * @param int    $parent   `term_id` of the parent for the term we're checking.
4209
 * @param int    $term_id  The term we're checking.
4210
 * @param string $taxonomy The taxonomy of the term we're checking.
4211
 *
4212
 * @return int The new parent for the term.
4213
 */
4214 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...
4215
	// Nothing fancy here - bail
4216
	if ( !$parent )
4217
		return 0;
4218
4219
	// Can't be its own parent.
4220
	if ( $parent == $term_id )
4221
		return 0;
4222
4223
	// Now look for larger loops.
4224
	if ( !$loop = wp_find_hierarchy_loop( 'wp_get_term_taxonomy_parent_id', $term_id, $parent, array( $taxonomy ) ) )
4225
		return $parent; // No loop
4226
4227
	// Setting $parent to the given value causes a loop.
4228
	if ( isset( $loop[$term_id] ) )
4229
		return 0;
4230
4231
	// There's a loop, but it doesn't contain $term_id. Break the loop.
4232
	foreach ( array_keys( $loop ) as $loop_member )
4233
		wp_update_term( $loop_member, $taxonomy, array( 'parent' => 0 ) );
4234
4235
	return $parent;
4236
}
4237