run:R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
23.8 KB
2026-03-11 16:18:51
R W Run
7.8 KB
2026-03-11 16:18:52
R W Run
36.1 KB
2026-03-11 16:18:51
R W Run
11.9 KB
2026-03-11 16:18:52
R W Run
18.94 KB
2026-03-11 16:18:52
R W Run
7.35 KB
2026-03-11 16:18:52
R W Run
28.6 KB
2026-03-11 16:18:51
R W Run
316 By
2026-03-11 16:18:51
R W Run
12.9 KB
2026-03-11 16:18:51
R W Run
61.02 KB
2026-03-11 16:18:52
R W Run
15 KB
2026-03-11 16:18:51
R W Run
112.05 KB
2026-03-11 16:18:51
R W Run
12.47 KB
2026-03-11 16:18:51
R W Run
15.07 KB
2026-03-11 16:18:52
R W Run
9.84 KB
2026-03-11 16:18:52
R W Run
13.17 KB
2026-03-11 16:18:52
R W Run
33.83 KB
2026-03-11 16:18:51
R W Run
42.63 KB
2026-03-11 16:18:51
R W Run
55.71 KB
2026-03-11 16:18:52
R W Run
12.53 KB
2026-03-11 16:18:51
R W Run
2.55 KB
2026-03-11 16:18:52
R W Run
28.92 KB
2026-03-11 16:18:52
R W Run
539 By
2026-03-11 16:18:51
R W Run
367 By
2026-03-11 16:18:52
R W Run
42.65 KB
2026-03-11 16:18:51
R W Run
401 By
2026-03-11 16:18:51
R W Run
6.61 KB
2026-03-11 16:18:51
R W Run
664 By
2026-03-11 16:18:52
R W Run
20.63 KB
2026-03-11 16:18:51
R W Run
2.18 KB
2026-03-11 16:18:52
R W Run
453 By
2026-03-11 16:18:52
R W Run
457 By
2026-03-11 16:18:51
R W Run
36.83 KB
2026-03-11 16:18:52
R W Run
2.41 KB
2026-03-11 16:18:52
R W Run
8.28 KB
2026-03-11 16:18:51
R W Run
13.89 KB
2026-03-11 16:18:51
R W Run
11.76 KB
2026-03-11 16:18:51
R W Run
2.65 KB
2026-03-11 16:18:51
R W Run
7.43 KB
2026-03-11 16:18:51
R W Run
17.46 KB
2026-03-11 16:18:51
R W Run
5.14 KB
2026-03-11 16:18:52
R W Run
16.7 KB
2026-03-11 16:18:51
R W Run
8.28 KB
2026-03-11 16:18:52
R W Run
2.92 KB
2026-03-11 16:18:52
R W Run
1.32 KB
2026-03-11 16:18:51
R W Run
4.6 KB
2026-03-11 16:18:52
R W Run
11.62 KB
2026-03-11 16:18:52
R W Run
2.5 KB
2026-03-11 16:18:51
R W Run
1.97 KB
2026-03-11 16:18:51
R W Run
11.25 KB
2026-03-11 16:18:52
R W Run
5.32 KB
2026-03-11 16:18:51
R W Run
10.99 KB
2026-03-11 16:18:52
R W Run
68.32 KB
2026-03-11 16:18:51
R W Run
6.34 KB
2026-03-11 16:18:51
R W Run
5.49 KB
2026-03-11 16:18:51
R W Run
1.99 KB
2026-03-11 16:18:52
R W Run
7.02 KB
2026-03-11 16:18:51
R W Run
4.91 KB
2026-03-11 16:18:52
R W Run
16.86 KB
2026-03-11 16:18:51
R W Run
24.23 KB
2026-03-11 16:18:51
R W Run
3.97 KB
2026-03-11 16:18:51
R W Run
47.66 KB
2026-03-11 16:18:51
R W Run
9.22 KB
2026-03-11 16:18:51
R W Run
25.51 KB
2026-03-11 16:18:51
R W Run
198.38 KB
2026-03-11 16:18:52
R W Run
56.65 KB
2026-03-11 16:18:51
R W Run
10.46 KB
2026-03-11 16:18:51
R W Run
10.95 KB
2026-03-11 16:18:52
R W Run
29.26 KB
2026-03-11 16:18:51
R W Run
70.91 KB
2026-03-11 16:18:52
R W Run
35.3 KB
2026-03-11 16:18:52
R W Run
16.61 KB
2026-03-11 16:18:52
R W Run
2.57 KB
2026-03-11 16:18:52
R W Run
39.83 KB
2026-03-11 16:18:51
R W Run
70.64 KB
2026-03-11 16:18:51
R W Run
15.56 KB
2026-03-11 16:18:52
R W Run
7.33 KB
2026-03-11 16:18:52
R W Run
253 By
2026-03-11 16:18:51
R W Run
7.96 KB
2026-03-11 16:18:52
R W Run
3.23 KB
2026-03-11 16:18:52
R W Run
969 By
2026-03-11 16:18:52
R W Run
16.28 KB
2026-03-11 16:18:51
R W Run
7.22 KB
2026-03-11 16:18:51
R W Run
12.95 KB
2026-03-11 16:18:51
R W Run
6.53 KB
2026-03-11 16:18:51
R W Run
3.42 KB
2026-03-11 16:18:52
R W Run
5.84 KB
2026-03-11 16:18:51
R W Run
1.97 KB
2026-03-11 16:18:51
R W Run
4.3 KB
2026-03-11 16:18:52
R W Run
2.91 KB
2026-03-11 16:18:51
R W Run
16.46 KB
2026-03-11 16:18:52
R W Run
40.6 KB
2026-03-11 16:18:51
R W Run
20.22 KB
2026-03-11 16:18:51
R W Run
36.11 KB
2026-03-11 16:18:52
R W Run
17.01 KB
2026-03-11 16:18:51
R W Run
7.27 KB
2026-03-11 16:18:52
R W Run
6.62 KB
2026-03-11 16:18:52
R W Run
16.49 KB
2026-03-11 16:18:52
R W Run
1.79 KB
2026-03-11 16:18:52
R W Run
29.82 KB
2026-03-11 16:18:51
R W Run
6.67 KB
2026-03-11 16:18:52
R W Run
8.98 KB
2026-03-11 16:18:52
R W Run
19.42 KB
2026-03-11 16:18:51
R W Run
12.01 KB
2026-03-11 16:18:51
R W Run
17.11 KB
2026-03-11 16:18:51
R W Run
6.74 KB
2026-03-11 16:18:52
R W Run
30.93 KB
2026-03-11 16:18:51
R W Run
4.99 KB
2026-03-11 16:18:51
R W Run
4.25 KB
2026-03-11 16:18:51
R W Run
24.72 KB
2026-03-11 16:18:51
R W Run
29.96 KB
2026-03-11 16:18:52
R W Run
6.41 KB
2026-03-11 16:18:51
R W Run
160 KB
2026-03-11 16:18:51
R W Run
6.72 KB
2026-03-11 16:18:52
R W Run
10.92 KB
2026-03-11 16:18:51
R W Run
4.77 KB
2026-03-11 16:18:51
R W Run
3.38 KB
2026-03-11 16:18:51
R W Run
11.18 KB
2026-03-11 16:18:51
R W Run
62.19 KB
2026-03-11 16:18:51
R W Run
2.46 KB
2026-03-11 16:18:51
R W Run
9.17 KB
2026-03-11 16:18:51
R W Run
32.15 KB
2026-03-11 16:18:51
R W Run
34.05 KB
2026-03-11 16:18:52
R W Run
7.15 KB
2026-03-11 16:18:51
R W Run
3.47 KB
2026-03-11 16:18:52
R W Run
1.87 KB
2026-03-11 16:18:52
R W Run
30.91 KB
2026-03-11 16:18:51
R W Run
7.29 KB
2026-03-11 16:18:52
R W Run
7.35 KB
2026-03-11 16:18:51
R W Run
12.54 KB
2026-03-11 16:18:51
R W Run
19.12 KB
2026-03-11 16:18:51
R W Run
18.12 KB
2026-03-11 16:18:52
R W Run
39.99 KB
2026-03-11 16:18:52
R W Run
5.17 KB
2026-03-11 16:18:52
R W Run
979 By
2026-03-11 16:18:51
R W Run
18.44 KB
2026-03-11 16:18:52
R W Run
10.24 KB
2026-03-11 16:18:51
R W Run
1.77 KB
2026-03-11 16:18:52
R W Run
34.9 KB
2026-03-11 16:18:51
R W Run
7.19 KB
2026-03-11 16:18:52
R W Run
160.5 KB
2026-03-11 16:18:51
R W Run
64.27 KB
2026-03-11 16:18:51
R W Run
27.95 KB
2026-03-11 16:18:51
R W Run
4.69 KB
2026-03-11 16:18:51
R W Run
2.94 KB
2026-03-11 16:18:51
R W Run
43.13 KB
2026-03-11 16:18:52
R W Run
2.25 KB
2026-03-11 16:18:52
R W Run
22.5 KB
2026-03-11 16:18:51
R W Run
13.01 KB
2026-03-11 16:18:52
R W Run
3.27 KB
2026-03-11 16:18:51
R W Run
18 KB
2026-03-11 16:18:51
R W Run
210.4 KB
2026-03-11 16:18:52
R W Run
25.86 KB
2026-03-11 16:18:52
R W Run
115.85 KB
2026-03-11 16:18:51
R W Run
373 By
2026-03-11 16:18:52
R W Run
343 By
2026-03-11 16:18:52
R W Run
338 By
2026-03-11 16:18:51
R W Run
100.73 KB
2026-03-11 16:18:52
R W Run
130.93 KB
2026-03-11 16:18:51
R W Run
19.1 KB
2026-03-11 16:18:51
R W Run
17.41 KB
2026-03-11 16:18:52
R W Run
41.98 KB
2026-03-11 16:18:52
R W Run
400 By
2026-03-11 16:18:52
R W Run
11.1 KB
2026-03-11 16:18:52
R W Run
37.02 KB
2026-03-11 16:18:51
R W Run
2.24 KB
2026-03-11 16:18:51
R W Run
188.13 KB
2026-03-11 16:18:51
R W Run
338 By
2026-03-11 16:18:51
R W Run
38 KB
2026-03-11 16:18:51
R W Run
4.02 KB
2026-03-11 16:18:52
R W Run
5.38 KB
2026-03-11 16:18:51
R W Run
3.05 KB
2026-03-11 16:18:52
R W Run
2.61 KB
2026-03-11 16:18:51
R W Run
1.16 KB
2026-03-11 16:18:52
R W Run
4.04 KB
2026-03-11 16:18:51
R W Run
3.71 KB
2026-03-11 16:18:51
R W Run
24.6 KB
2026-03-11 16:18:51
R W Run
9.56 KB
2026-03-11 16:18:51
R W Run
346.43 KB
2026-03-11 16:18:52
R W Run
281.84 KB
2026-03-11 16:18:52
R W Run
14.95 KB
2026-03-11 16:18:51
R W Run
8.44 KB
2026-03-11 16:18:52
R W Run
168.95 KB
2026-03-11 16:18:52
R W Run
20.71 KB
2026-03-11 16:18:52
R W Run
25.27 KB
2026-03-11 16:18:51
R W Run
5.72 KB
2026-03-11 16:18:51
R W Run
4.63 KB
2026-03-11 16:18:52
R W Run
81.73 KB
2026-03-11 16:18:51
R W Run
67.18 KB
2026-03-11 16:18:51
R W Run
156.36 KB
2026-03-11 16:18:52
R W Run
55.19 KB
2026-03-11 16:18:51
R W Run
162 By
2026-03-11 16:18:51
R W Run
61.72 KB
2026-03-11 16:18:51
R W Run
216.06 KB
2026-03-11 16:18:52
R W Run
65.09 KB
2026-03-11 16:18:51
R W Run
25.24 KB
2026-03-11 16:18:52
R W Run
4.81 KB
2026-03-11 16:18:51
R W Run
6.48 KB
2026-03-11 16:18:52
R W Run
21.25 KB
2026-03-11 16:18:51
R W Run
2.79 KB
2026-03-11 16:18:52
R W Run
89.69 KB
2026-03-11 16:18:52
R W Run
19.42 KB
2026-03-11 16:18:52
R W Run
3.69 KB
2026-03-11 16:18:52
R W Run
4.11 KB
2026-03-11 16:18:51
R W Run
40.74 KB
2026-03-11 16:18:51
R W Run
25.38 KB
2026-03-11 16:18:51
R W Run
43.31 KB
2026-03-11 16:18:52
R W Run
102.57 KB
2026-03-11 16:18:52
R W Run
6.18 KB
2026-03-11 16:18:51
R W Run
124.47 KB
2026-03-11 16:18:52
R W Run
35.65 KB
2026-03-11 16:18:52
R W Run
6.94 KB
2026-03-11 16:18:52
R W Run
67.04 KB
2026-03-11 16:18:52
R W Run
10.62 KB
2026-03-11 16:18:51
R W Run
289.35 KB
2026-03-11 16:18:52
R W Run
36.23 KB
2026-03-11 16:18:51
R W Run
200 By
2026-03-11 16:18:52
R W Run
200 By
2026-03-11 16:18:52
R W Run
98.29 KB
2026-03-11 16:18:52
R W Run
30.02 KB
2026-03-11 16:18:52
R W Run
19.03 KB
2026-03-11 16:18:52
R W Run
5.06 KB
2026-03-11 16:18:52
R W Run
255 By
2026-03-11 16:18:51
R W Run
22.66 KB
2026-03-11 16:18:52
R W Run
154.63 KB
2026-03-11 16:18:51
R W Run
9.68 KB
2026-03-11 16:18:51
R W Run
258 By
2026-03-11 16:18:51
R W Run
23.49 KB
2026-03-11 16:18:51
R W Run
3.16 KB
2026-03-11 16:18:51
R W Run
8.4 KB
2026-03-11 16:18:52
R W Run
441 By
2026-03-11 16:18:51
R W Run
7.39 KB
2026-03-11 16:18:51
R W Run
173 KB
2026-03-11 16:18:52
R W Run
544 By
2026-03-11 16:18:52
R W Run
4.17 KB
2026-03-11 16:18:51
R W Run
35.97 KB
2026-03-11 16:18:52
R W Run
1.69 KB
2026-03-11 16:18:51
R W Run
2.84 KB
2026-03-11 16:18:52
R W Run
6.09 KB
2026-03-11 16:18:51
R W Run
8.71 KB
2026-03-11 16:18:51
R W Run
131.84 KB
2026-03-11 16:18:51
R W Run
37.45 KB
2026-03-11 16:18:51
R W Run
173.89 KB
2026-03-11 16:18:51
R W Run
7.09 KB
2026-03-11 16:18:51
R W Run
6.41 KB
2026-03-11 16:18:51
R W Run
1.08 KB
2026-03-11 16:18:51
R W Run
69.46 KB
2026-03-11 16:18:52
R W Run
445 By
2026-03-11 16:18:51
R W Run
799 By
2026-03-11 16:18:52
R W Run
error_log
📄taxonomy.php
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 * @since 5.9.0 Added `'wp_template_part_area'` taxonomy.
22 *
23 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
24 */
25function create_initial_taxonomies() {
26 global $wp_rewrite;
27
28 WP_Taxonomy::reset_default_labels();
29
30 if ( ! did_action( 'init' ) ) {
31 $rewrite = array(
32 'category' => false,
33 'post_tag' => false,
34 'post_format' => false,
35 );
36 } else {
37
38 /**
39 * Filters the post formats rewrite base.
40 *
41 * @since 3.1.0
42 *
43 * @param string $context Context of the rewrite base. Default 'type'.
44 */
45 $post_format_base = apply_filters( 'post_format_rewrite_base', 'type' );
46 $rewrite = array(
47 'category' => array(
48 'hierarchical' => true,
49 'slug' => get_option( 'category_base' ) ? get_option( 'category_base' ) : 'category',
50 'with_front' => ! get_option( 'category_base' ) || $wp_rewrite->using_index_permalinks(),
51 'ep_mask' => EP_CATEGORIES,
52 ),
53 'post_tag' => array(
54 'hierarchical' => false,
55 'slug' => get_option( 'tag_base' ) ? get_option( 'tag_base' ) : 'tag',
56 'with_front' => ! get_option( 'tag_base' ) || $wp_rewrite->using_index_permalinks(),
57 'ep_mask' => EP_TAGS,
58 ),
59 'post_format' => $post_format_base ? array( 'slug' => $post_format_base ) : false,
60 );
61 }
62
63 register_taxonomy(
64 'category',
65 'post',
66 array(
67 'hierarchical' => true,
68 'query_var' => 'category_name',
69 'rewrite' => $rewrite['category'],
70 'public' => true,
71 'show_ui' => true,
72 'show_admin_column' => true,
73 '_builtin' => true,
74 'capabilities' => array(
75 'manage_terms' => 'manage_categories',
76 'edit_terms' => 'edit_categories',
77 'delete_terms' => 'delete_categories',
78 'assign_terms' => 'assign_categories',
79 ),
80 'show_in_rest' => true,
81 'rest_base' => 'categories',
82 'rest_controller_class' => 'WP_REST_Terms_Controller',
83 )
84 );
85
86 register_taxonomy(
87 'post_tag',
88 'post',
89 array(
90 'hierarchical' => false,
91 'query_var' => 'tag',
92 'rewrite' => $rewrite['post_tag'],
93 'public' => true,
94 'show_ui' => true,
95 'show_admin_column' => true,
96 '_builtin' => true,
97 'capabilities' => array(
98 'manage_terms' => 'manage_post_tags',
99 'edit_terms' => 'edit_post_tags',
100 'delete_terms' => 'delete_post_tags',
101 'assign_terms' => 'assign_post_tags',
102 ),
103 'show_in_rest' => true,
104 'rest_base' => 'tags',
105 'rest_controller_class' => 'WP_REST_Terms_Controller',
106 )
107 );
108
109 register_taxonomy(
110 'nav_menu',
111 'nav_menu_item',
112 array(
113 'public' => false,
114 'hierarchical' => false,
115 'labels' => array(
116 'name' => __( 'Navigation Menus' ),
117 'singular_name' => __( 'Navigation Menu' ),
118 ),
119 'query_var' => false,
120 'rewrite' => false,
121 'show_ui' => false,
122 '_builtin' => true,
123 'show_in_nav_menus' => false,
124 'capabilities' => array(
125 'manage_terms' => 'edit_theme_options',
126 'edit_terms' => 'edit_theme_options',
127 'delete_terms' => 'edit_theme_options',
128 'assign_terms' => 'edit_theme_options',
129 ),
130 'show_in_rest' => true,
131 'rest_base' => 'menus',
132 'rest_controller_class' => 'WP_REST_Menus_Controller',
133 )
134 );
135
136 register_taxonomy(
137 'link_category',
138 'link',
139 array(
140 'hierarchical' => false,
141 'labels' => array(
142 'name' => __( 'Link Categories' ),
143 'singular_name' => __( 'Link Category' ),
144 'search_items' => __( 'Search Link Categories' ),
145 'popular_items' => null,
146 'all_items' => __( 'All Link Categories' ),
147 'edit_item' => __( 'Edit Link Category' ),
148 'update_item' => __( 'Update Link Category' ),
149 'add_new_item' => __( 'Add Link Category' ),
150 'new_item_name' => __( 'New Link Category Name' ),
151 'separate_items_with_commas' => null,
152 'add_or_remove_items' => null,
153 'choose_from_most_used' => null,
154 'back_to_items' => __( '&larr; Go to Link Categories' ),
155 ),
156 'capabilities' => array(
157 'manage_terms' => 'manage_links',
158 'edit_terms' => 'manage_links',
159 'delete_terms' => 'manage_links',
160 'assign_terms' => 'manage_links',
161 ),
162 'query_var' => false,
163 'rewrite' => false,
164 'public' => false,
165 'show_ui' => true,
166 '_builtin' => true,
167 )
168 );
169
170 register_taxonomy(
171 'post_format',
172 'post',
173 array(
174 'public' => true,
175 'hierarchical' => false,
176 'labels' => array(
177 'name' => _x( 'Formats', 'post format' ),
178 'singular_name' => _x( 'Format', 'post format' ),
179 ),
180 'query_var' => true,
181 'rewrite' => $rewrite['post_format'],
182 'show_ui' => false,
183 '_builtin' => true,
184 'show_in_nav_menus' => current_theme_supports( 'post-formats' ),
185 )
186 );
187
188 register_taxonomy(
189 'wp_theme',
190 array( 'wp_template', 'wp_template_part', 'wp_global_styles' ),
191 array(
192 'public' => false,
193 'hierarchical' => false,
194 'labels' => array(
195 'name' => __( 'Themes' ),
196 'singular_name' => __( 'Theme' ),
197 ),
198 'query_var' => false,
199 'rewrite' => false,
200 'show_ui' => false,
201 '_builtin' => true,
202 'show_in_nav_menus' => false,
203 'show_in_rest' => false,
204 )
205 );
206
207 register_taxonomy(
208 'wp_template_part_area',
209 array( 'wp_template_part' ),
210 array(
211 'public' => false,
212 'hierarchical' => false,
213 'labels' => array(
214 'name' => __( 'Template Part Areas' ),
215 'singular_name' => __( 'Template Part Area' ),
216 ),
217 'query_var' => false,
218 'rewrite' => false,
219 'show_ui' => false,
220 '_builtin' => true,
221 'show_in_nav_menus' => false,
222 'show_in_rest' => false,
223 )
224 );
225
226 register_taxonomy(
227 'wp_pattern_category',
228 array( 'wp_block' ),
229 array(
230 'public' => false,
231 'publicly_queryable' => false,
232 'hierarchical' => false,
233 'labels' => array(
234 'name' => _x( 'Pattern Categories', 'taxonomy general name' ),
235 'singular_name' => _x( 'Pattern Category', 'taxonomy singular name' ),
236 'add_new_item' => __( 'Add Category' ),
237 'add_or_remove_items' => __( 'Add or remove pattern categories' ),
238 'back_to_items' => __( '&larr; Go to Pattern Categories' ),
239 'choose_from_most_used' => __( 'Choose from the most used pattern categories' ),
240 'edit_item' => __( 'Edit Pattern Category' ),
241 'item_link' => __( 'Pattern Category Link' ),
242 'item_link_description' => __( 'A link to a pattern category.' ),
243 'items_list' => __( 'Pattern Categories list' ),
244 'items_list_navigation' => __( 'Pattern Categories list navigation' ),
245 'new_item_name' => __( 'New Pattern Category Name' ),
246 'no_terms' => __( 'No pattern categories' ),
247 'not_found' => __( 'No pattern categories found.' ),
248 'popular_items' => __( 'Popular Pattern Categories' ),
249 'search_items' => __( 'Search Pattern Categories' ),
250 'separate_items_with_commas' => __( 'Separate pattern categories with commas' ),
251 'update_item' => __( 'Update Pattern Category' ),
252 'view_item' => __( 'View Pattern Category' ),
253 ),
254 'query_var' => false,
255 'rewrite' => false,
256 'show_ui' => true,
257 '_builtin' => true,
258 'show_in_nav_menus' => false,
259 'show_in_rest' => true,
260 'show_admin_column' => true,
261 'show_tagcloud' => false,
262 )
263 );
264}
265
266/**
267 * Retrieves a list of registered taxonomy names or objects.
268 *
269 * @since 3.0.0
270 *
271 * @global WP_Taxonomy[] $wp_taxonomies The registered taxonomies.
272 *
273 * @param array $args Optional. An array of `key => value` arguments to match against the taxonomy objects.
274 * Default empty array.
275 * @param string $output Optional. The type of output to return in the array. Either 'names'
276 * or 'objects'. Default 'names'.
277 * @param string $operator Optional. The logical operation to perform. Accepts 'and' or 'or'. 'or' means only
278 * one element from the array needs to match; 'and' means all elements must match.
279 * Default 'and'.
280 * @return string[]|WP_Taxonomy[] An array of taxonomy names or objects.
281 */
282function get_taxonomies( $args = array(), $output = 'names', $operator = 'and' ) {
283 global $wp_taxonomies;
284
285 $field = ( 'names' === $output ) ? 'name' : false;
286
287 return wp_filter_object_list( $wp_taxonomies, $args, $operator, $field );
288}
289
290/**
291 * Returns the names or objects of the taxonomies which are registered for the requested object or object type,
292 * such as a post object or post type name.
293 *
294 * Example:
295 *
296 * $taxonomies = get_object_taxonomies( 'post' );
297 *
298 * This results in:
299 *
300 * Array( 'category', 'post_tag' )
301 *
302 * @since 2.3.0
303 *
304 * @global WP_Taxonomy[] $wp_taxonomies The registered taxonomies.
305 *
306 * @param string|string[]|WP_Post $object_type Name of the type of taxonomy object, or an object (row from posts).
307 * @param string $output Optional. The type of output to return in the array. Accepts either
308 * 'names' or 'objects'. Default 'names'.
309 * @return string[]|WP_Taxonomy[] The names or objects of all taxonomies of `$object_type`.
310 */
311function get_object_taxonomies( $object_type, $output = 'names' ) {
312 global $wp_taxonomies;
313
314 if ( is_object( $object_type ) ) {
315 if ( 'attachment' === $object_type->post_type ) {
316 return get_attachment_taxonomies( $object_type, $output );
317 }
318 $object_type = $object_type->post_type;
319 }
320
321 $object_type = (array) $object_type;
322
323 $taxonomies = array();
324 foreach ( (array) $wp_taxonomies as $tax_name => $tax_obj ) {
325 if ( array_intersect( $object_type, (array) $tax_obj->object_type ) ) {
326 if ( 'names' === $output ) {
327 $taxonomies[] = $tax_name;
328 } else {
329 $taxonomies[ $tax_name ] = $tax_obj;
330 }
331 }
332 }
333
334 return $taxonomies;
335}
336
337/**
338 * Retrieves the taxonomy object of $taxonomy.
339 *
340 * The get_taxonomy function will first check that the parameter string given
341 * is a taxonomy object and if it is, it will return it.
342 *
343 * @since 2.3.0
344 *
345 * @global WP_Taxonomy[] $wp_taxonomies The registered taxonomies.
346 *
347 * @param string $taxonomy Name of taxonomy object to return.
348 * @return WP_Taxonomy|false The taxonomy object or false if $taxonomy doesn't exist.
349 */
350function get_taxonomy( $taxonomy ) {
351 global $wp_taxonomies;
352
353 if ( ! taxonomy_exists( $taxonomy ) ) {
354 return false;
355 }
356
357 return $wp_taxonomies[ $taxonomy ];
358}
359
360/**
361 * Determines whether the taxonomy name exists.
362 *
363 * Formerly is_taxonomy(), introduced in 2.3.0.
364 *
365 * For more information on this and similar theme functions, check out
366 * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
367 * Conditional Tags} article in the Theme Developer Handbook.
368 *
369 * @since 3.0.0
370 *
371 * @global WP_Taxonomy[] $wp_taxonomies The registered taxonomies.
372 *
373 * @param string $taxonomy Name of taxonomy object.
374 * @return bool Whether the taxonomy exists.
375 */
376function taxonomy_exists( $taxonomy ) {
377 global $wp_taxonomies;
378
379 return is_string( $taxonomy ) && isset( $wp_taxonomies[ $taxonomy ] );
380}
381
382/**
383 * Determines whether the taxonomy object is hierarchical.
384 *
385 * Checks to make sure that the taxonomy is an object first. Then Gets the
386 * object, and finally returns the hierarchical value in the object.
387 *
388 * A false return value might also mean that the taxonomy does not exist.
389 *
390 * For more information on this and similar theme functions, check out
391 * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
392 * Conditional Tags} article in the Theme Developer Handbook.
393 *
394 * @since 2.3.0
395 *
396 * @param string $taxonomy Name of taxonomy object.
397 * @return bool Whether the taxonomy is hierarchical.
398 */
399function is_taxonomy_hierarchical( $taxonomy ) {
400 if ( ! taxonomy_exists( $taxonomy ) ) {
401 return false;
402 }
403
404 $taxonomy = get_taxonomy( $taxonomy );
405 return $taxonomy->hierarchical;
406}
407
408/**
409 * Creates or modifies a taxonomy object.
410 *
411 * Note: Do not use before the {@see 'init'} hook.
412 *
413 * A simple function for creating or modifying a taxonomy object based on
414 * the parameters given. If modifying an existing taxonomy object, note
415 * that the `$object_type` value from the original registration will be
416 * overwritten.
417 *
418 * @since 2.3.0
419 * @since 4.2.0 Introduced `show_in_quick_edit` argument.
420 * @since 4.4.0 The `show_ui` argument is now enforced on the term editing screen.
421 * @since 4.4.0 The `public` argument now controls whether the taxonomy can be queried on the front end.
422 * @since 4.5.0 Introduced `publicly_queryable` argument.
423 * @since 4.7.0 Introduced `show_in_rest`, 'rest_base' and 'rest_controller_class'
424 * arguments to register the taxonomy in REST API.
425 * @since 5.1.0 Introduced `meta_box_sanitize_cb` argument.
426 * @since 5.4.0 Added the registered taxonomy object as a return value.
427 * @since 5.5.0 Introduced `default_term` argument.
428 * @since 5.9.0 Introduced `rest_namespace` argument.
429 *
430 * @global WP_Taxonomy[] $wp_taxonomies Registered taxonomies.
431 *
432 * @param string $taxonomy Taxonomy key. Must not exceed 32 characters and may only contain
433 * lowercase alphanumeric characters, dashes, and underscores. See sanitize_key().
434 * @param array|string $object_type Object type or array of object types with which the taxonomy should be associated.
435 * @param array|string $args {
436 * Optional. Array or query string of arguments for registering a taxonomy.
437 *
438 * @type string[] $labels An array of labels for this taxonomy. By default, Tag labels are
439 * used for non-hierarchical taxonomies, and Category labels are used
440 * for hierarchical taxonomies. See accepted values in
441 * get_taxonomy_labels(). Default empty array.
442 * @type string $description A short descriptive summary of what the taxonomy is for. Default empty.
443 * @type bool $public Whether a taxonomy is intended for use publicly either via
444 * the admin interface or by front-end users. The default settings
445 * of `$publicly_queryable`, `$show_ui`, and `$show_in_nav_menus`
446 * are inherited from `$public`.
447 * @type bool $publicly_queryable Whether the taxonomy is publicly queryable.
448 * If not set, the default is inherited from `$public`
449 * @type bool $hierarchical Whether the taxonomy is hierarchical. Default false.
450 * @type bool $show_ui Whether to generate and allow a UI for managing terms in this taxonomy in
451 * the admin. If not set, the default is inherited from `$public`
452 * (default true).
453 * @type bool $show_in_menu Whether to show the taxonomy in the admin menu. If true, the taxonomy is
454 * shown as a submenu of the object type menu. If false, no menu is shown.
455 * `$show_ui` must be true. If not set, default is inherited from `$show_ui`
456 * (default true).
457 * @type bool $show_in_nav_menus Makes this taxonomy available for selection in navigation menus. If not
458 * set, the default is inherited from `$public` (default true).
459 * @type bool $show_in_rest Whether to include the taxonomy in the REST API. Set this to true
460 * for the taxonomy to be available in the block editor.
461 * @type string $rest_base To change the base url of REST API route. Default is $taxonomy.
462 * @type string $rest_namespace To change the namespace URL of REST API route. Default is wp/v2.
463 * @type string $rest_controller_class REST API Controller class name. Default is 'WP_REST_Terms_Controller'.
464 * @type bool $show_tagcloud Whether to list the taxonomy in the Tag Cloud Widget controls. If not set,
465 * the default is inherited from `$show_ui` (default true).
466 * @type bool $show_in_quick_edit Whether to show the taxonomy in the quick/bulk edit panel. It not set,
467 * the default is inherited from `$show_ui` (default true).
468 * @type bool $show_admin_column Whether to display a column for the taxonomy on its post type listing
469 * screens. Default false.
470 * @type bool|callable $meta_box_cb Provide a callback function for the meta box display. If not set,
471 * post_categories_meta_box() is used for hierarchical taxonomies, and
472 * post_tags_meta_box() is used for non-hierarchical. If false, no meta
473 * box is shown.
474 * @type callable $meta_box_sanitize_cb Callback function for sanitizing taxonomy data saved from a meta
475 * box. If no callback is defined, an appropriate one is determined
476 * based on the value of `$meta_box_cb`.
477 * @type string[] $capabilities {
478 * Array of capabilities for this taxonomy.
479 *
480 * @type string $manage_terms Default 'manage_categories'.
481 * @type string $edit_terms Default 'manage_categories'.
482 * @type string $delete_terms Default 'manage_categories'.
483 * @type string $assign_terms Default 'edit_posts'.
484 * }
485 * @type bool|array $rewrite {
486 * Triggers the handling of rewrites for this taxonomy. Default true, using $taxonomy as slug. To prevent
487 * rewrite, set to false. To specify rewrite rules, an array can be passed with any of these keys:
488 *
489 * @type string $slug Customize the permastruct slug. Default `$taxonomy` key.
490 * @type bool $with_front Should the permastruct be prepended with WP_Rewrite::$front. Default true.
491 * @type bool $hierarchical Either hierarchical rewrite tag or not. Default false.
492 * @type int $ep_mask Assign an endpoint mask. Default `EP_NONE`.
493 * }
494 * @type string|bool $query_var Sets the query var key for this taxonomy. Default `$taxonomy` key. If
495 * false, a taxonomy cannot be loaded at `?{query_var}={term_slug}`. If a
496 * string, the query `?{query_var}={term_slug}` will be valid.
497 * @type callable $update_count_callback Works much like a hook, in that it will be called when the count is
498 * updated. Default _update_post_term_count() for taxonomies attached
499 * to post types, which confirms that the objects are published before
500 * counting them. Default _update_generic_term_count() for taxonomies
501 * attached to other object types, such as users.
502 * @type string|array $default_term {
503 * Default term to be used for the taxonomy.
504 *
505 * @type string $name Name of default term.
506 * @type string $slug Slug for default term. Default empty.
507 * @type string $description Description for default term. Default empty.
508 * }
509 * @type bool $sort Whether terms in this taxonomy should be sorted in the order they are
510 * provided to `wp_set_object_terms()`. Default null which equates to false.
511 * @type array $args Array of arguments to automatically use inside `wp_get_object_terms()`
512 * for this taxonomy.
513 * @type bool $_builtin This taxonomy is a "built-in" taxonomy. INTERNAL USE ONLY!
514 * Default false.
515 * }
516 * @return WP_Taxonomy|WP_Error The registered taxonomy object on success, WP_Error object on failure.
517 */
518function register_taxonomy( $taxonomy, $object_type, $args = array() ) {
519 global $wp_taxonomies;
520
521 if ( ! is_array( $wp_taxonomies ) ) {
522 $wp_taxonomies = array();
523 }
524
525 $args = wp_parse_args( $args );
526
527 if ( empty( $taxonomy ) || strlen( $taxonomy ) > 32 ) {
528 _doing_it_wrong( __FUNCTION__, __( 'Taxonomy names must be between 1 and 32 characters in length.' ), '4.2.0' );
529 return new WP_Error( 'taxonomy_length_invalid', __( 'Taxonomy names must be between 1 and 32 characters in length.' ) );
530 }
531
532 $taxonomy_object = new WP_Taxonomy( $taxonomy, $object_type, $args );
533 $taxonomy_object->add_rewrite_rules();
534
535 $wp_taxonomies[ $taxonomy ] = $taxonomy_object;
536
537 $taxonomy_object->add_hooks();
538
539 // Add default term.
540 if ( ! empty( $taxonomy_object->default_term ) ) {
541 $term = term_exists( $taxonomy_object->default_term['name'], $taxonomy );
542 if ( $term ) {
543 update_option( 'default_term_' . $taxonomy_object->name, $term['term_id'] );
544 } else {
545 $term = wp_insert_term(
546 $taxonomy_object->default_term['name'],
547 $taxonomy,
548 array(
549 'slug' => sanitize_title( $taxonomy_object->default_term['slug'] ),
550 'description' => $taxonomy_object->default_term['description'],
551 )
552 );
553
554 // Update `term_id` in options.
555 if ( ! is_wp_error( $term ) ) {
556 update_option( 'default_term_' . $taxonomy_object->name, $term['term_id'] );
557 }
558 }
559 }
560
561 /**
562 * Fires after a taxonomy is registered.
563 *
564 * @since 3.3.0
565 *
566 * @param string $taxonomy Taxonomy slug.
567 * @param array|string $object_type Object type or array of object types.
568 * @param array $args Array of taxonomy registration arguments.
569 */
570 do_action( 'registered_taxonomy', $taxonomy, $object_type, (array) $taxonomy_object );
571
572 /**
573 * Fires after a specific taxonomy is registered.
574 *
575 * The dynamic portion of the filter name, `$taxonomy`, refers to the taxonomy key.
576 *
577 * Possible hook names include:
578 *
579 * - `registered_taxonomy_category`
580 * - `registered_taxonomy_post_tag`
581 *
582 * @since 6.0.0
583 *
584 * @param string $taxonomy Taxonomy slug.
585 * @param array|string $object_type Object type or array of object types.
586 * @param array $args Array of taxonomy registration arguments.
587 */
588 do_action( "registered_taxonomy_{$taxonomy}", $taxonomy, $object_type, (array) $taxonomy_object );
589
590 return $taxonomy_object;
591}
592
593/**
594 * Unregisters a taxonomy.
595 *
596 * Can not be used to unregister built-in taxonomies.
597 *
598 * @since 4.5.0
599 *
600 * @global WP_Taxonomy[] $wp_taxonomies List of taxonomies.
601 *
602 * @param string $taxonomy Taxonomy name.
603 * @return true|WP_Error True on success, WP_Error on failure or if the taxonomy doesn't exist.
604 */
605function unregister_taxonomy( $taxonomy ) {
606 global $wp_taxonomies;
607
608 if ( ! taxonomy_exists( $taxonomy ) ) {
609 return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
610 }
611
612 $taxonomy_object = get_taxonomy( $taxonomy );
613
614 // Do not allow unregistering internal taxonomies.
615 if ( $taxonomy_object->_builtin ) {
616 return new WP_Error( 'invalid_taxonomy', __( 'Unregistering a built-in taxonomy is not allowed.' ) );
617 }
618
619 $taxonomy_object->remove_rewrite_rules();
620 $taxonomy_object->remove_hooks();
621
622 // Remove the taxonomy.
623 unset( $wp_taxonomies[ $taxonomy ] );
624
625 /**
626 * Fires after a taxonomy is unregistered.
627 *
628 * @since 4.5.0
629 *
630 * @param string $taxonomy Taxonomy name.
631 */
632 do_action( 'unregistered_taxonomy', $taxonomy );
633
634 return true;
635}
636
637/**
638 * Builds an object with all taxonomy labels out of a taxonomy object.
639 *
640 * @since 3.0.0
641 * @since 4.3.0 Added the `no_terms` label.
642 * @since 4.4.0 Added the `items_list_navigation` and `items_list` labels.
643 * @since 4.9.0 Added the `most_used` and `back_to_items` labels.
644 * @since 5.7.0 Added the `filter_by_item` label.
645 * @since 5.8.0 Added the `item_link` and `item_link_description` labels.
646 * @since 5.9.0 Added the `name_field_description`, `slug_field_description`,
647 * `parent_field_description`, and `desc_field_description` labels.
648 * @since 6.6.0 Added the `template_name` label.
649 *
650 * @param WP_Taxonomy $tax Taxonomy object.
651 * @return object {
652 * Taxonomy labels object. The first default value is for non-hierarchical taxonomies
653 * (like tags) and the second one is for hierarchical taxonomies (like categories).
654 *
655 * @type string $name General name for the taxonomy, usually plural. The same
656 * as and overridden by `$tax->label`. Default 'Tags'/'Categories'.
657 * @type string $singular_name Name for one object of this taxonomy. Default 'Tag'/'Category'.
658 * @type string $search_items Default 'Search Tags'/'Search Categories'.
659 * @type string $popular_items This label is only used for non-hierarchical taxonomies.
660 * Default 'Popular Tags'.
661 * @type string $all_items Default 'All Tags'/'All Categories'.
662 * @type string $parent_item This label is only used for hierarchical taxonomies. Default
663 * 'Parent Category'.
664 * @type string $parent_item_colon The same as `parent_item`, but with colon `:` in the end.
665 * @type string $name_field_description Description for the Name field on Edit Tags screen.
666 * Default 'The name is how it appears on your site'.
667 * @type string $slug_field_description Description for the Slug field on Edit Tags screen.
668 * Default 'The &#8220;slug&#8221; is the URL-friendly version
669 * of the name. It is usually all lowercase and contains
670 * only letters, numbers, and hyphens'.
671 * @type string $parent_field_description Description for the Parent field on Edit Tags screen.
672 * Default 'Assign a parent term to create a hierarchy.
673 * The term Jazz, for example, would be the parent
674 * of Bebop and Big Band'.
675 * @type string $desc_field_description Description for the Description field on Edit Tags screen.
676 * Default 'The description is not prominent by default;
677 * however, some themes may show it'.
678 * @type string $edit_item Default 'Edit Tag'/'Edit Category'.
679 * @type string $view_item Default 'View Tag'/'View Category'.
680 * @type string $update_item Default 'Update Tag'/'Update Category'.
681 * @type string $add_new_item Default 'Add Tag'/'Add Category'.
682 * @type string $new_item_name Default 'New Tag Name'/'New Category Name'.
683 * @type string $template_name Default 'Tag Archives'/'Category Archives'.
684 * @type string $separate_items_with_commas This label is only used for non-hierarchical taxonomies. Default
685 * 'Separate tags with commas', used in the meta box.
686 * @type string $add_or_remove_items This label is only used for non-hierarchical taxonomies. Default
687 * 'Add or remove tags', used in the meta box when JavaScript
688 * is disabled.
689 * @type string $choose_from_most_used This label is only used on non-hierarchical taxonomies. Default
690 * 'Choose from the most used tags', used in the meta box.
691 * @type string $not_found Default 'No tags found'/'No categories found', used in
692 * the meta box and taxonomy list table.
693 * @type string $no_terms Default 'No tags'/'No categories', used in the posts and media
694 * list tables.
695 * @type string $filter_by_item This label is only used for hierarchical taxonomies. Default
696 * 'Filter by category', used in the posts list table.
697 * @type string $items_list_navigation Label for the table pagination hidden heading.
698 * @type string $items_list Label for the table hidden heading.
699 * @type string $most_used Title for the Most Used tab. Default 'Most Used'.
700 * @type string $back_to_items Label displayed after a term has been updated.
701 * @type string $item_link Used in the block editor. Title for a navigation link block variation.
702 * Default 'Tag Link'/'Category Link'.
703 * @type string $item_link_description Used in the block editor. Description for a navigation link block
704 * variation. Default 'A link to a tag'/'A link to a category'.
705 * }
706 */
707function get_taxonomy_labels( $tax ) {
708 $tax->labels = (array) $tax->labels;
709
710 if ( isset( $tax->helps ) && empty( $tax->labels['separate_items_with_commas'] ) ) {
711 $tax->labels['separate_items_with_commas'] = $tax->helps;
712 }
713
714 if ( isset( $tax->no_tagcloud ) && empty( $tax->labels['not_found'] ) ) {
715 $tax->labels['not_found'] = $tax->no_tagcloud;
716 }
717
718 $nohier_vs_hier_defaults = WP_Taxonomy::get_default_labels();
719
720 $nohier_vs_hier_defaults['menu_name'] = $nohier_vs_hier_defaults['name'];
721
722 $labels = _get_custom_object_labels( $tax, $nohier_vs_hier_defaults );
723
724 if ( ! isset( $tax->labels->template_name ) && isset( $labels->singular_name ) ) {
725 /* translators: %s: Taxonomy name. */
726 $labels->template_name = sprintf( _x( '%s Archives', 'taxonomy template name' ), $labels->singular_name );
727 }
728
729 $taxonomy = $tax->name;
730
731 $default_labels = clone $labels;
732
733 /**
734 * Filters the labels of a specific taxonomy.
735 *
736 * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
737 *
738 * Possible hook names include:
739 *
740 * - `taxonomy_labels_category`
741 * - `taxonomy_labels_post_tag`
742 *
743 * @since 4.4.0
744 *
745 * @see get_taxonomy_labels() for the full list of taxonomy labels.
746 *
747 * @param object $labels Object with labels for the taxonomy as member variables.
748 */
749 $labels = apply_filters( "taxonomy_labels_{$taxonomy}", $labels );
750
751 // Ensure that the filtered labels contain all required default values.
752 $labels = (object) array_merge( (array) $default_labels, (array) $labels );
753
754 return $labels;
755}
756
757/**
758 * Adds an already registered taxonomy to an object type.
759 *
760 * @since 3.0.0
761 *
762 * @global WP_Taxonomy[] $wp_taxonomies The registered taxonomies.
763 *
764 * @param string $taxonomy Name of taxonomy object.
765 * @param string $object_type Name of the object type.
766 * @return bool True if successful, false if not.
767 */
768function register_taxonomy_for_object_type( $taxonomy, $object_type ) {
769 global $wp_taxonomies;
770
771 if ( ! isset( $wp_taxonomies[ $taxonomy ] ) ) {
772 return false;
773 }
774
775 if ( ! get_post_type_object( $object_type ) ) {
776 return false;
777 }
778
779 if ( ! in_array( $object_type, $wp_taxonomies[ $taxonomy ]->object_type, true ) ) {
780 $wp_taxonomies[ $taxonomy ]->object_type[] = $object_type;
781 }
782
783 // Filter out empties.
784 $wp_taxonomies[ $taxonomy ]->object_type = array_filter( $wp_taxonomies[ $taxonomy ]->object_type );
785
786 /**
787 * Fires after a taxonomy is registered for an object type.
788 *
789 * @since 5.1.0
790 *
791 * @param string $taxonomy Taxonomy name.
792 * @param string $object_type Name of the object type.
793 */
794 do_action( 'registered_taxonomy_for_object_type', $taxonomy, $object_type );
795
796 return true;
797}
798
799/**
800 * Removes an already registered taxonomy from an object type.
801 *
802 * @since 3.7.0
803 *
804 * @global WP_Taxonomy[] $wp_taxonomies The registered taxonomies.
805 *
806 * @param string $taxonomy Name of taxonomy object.
807 * @param string $object_type Name of the object type.
808 * @return bool True if successful, false if not.
809 */
810function unregister_taxonomy_for_object_type( $taxonomy, $object_type ) {
811 global $wp_taxonomies;
812
813 if ( ! isset( $wp_taxonomies[ $taxonomy ] ) ) {
814 return false;
815 }
816
817 if ( ! get_post_type_object( $object_type ) ) {
818 return false;
819 }
820
821 $key = array_search( $object_type, $wp_taxonomies[ $taxonomy ]->object_type, true );
822 if ( false === $key ) {
823 return false;
824 }
825
826 unset( $wp_taxonomies[ $taxonomy ]->object_type[ $key ] );
827
828 /**
829 * Fires after a taxonomy is unregistered for an object type.
830 *
831 * @since 5.1.0
832 *
833 * @param string $taxonomy Taxonomy name.
834 * @param string $object_type Name of the object type.
835 */
836 do_action( 'unregistered_taxonomy_for_object_type', $taxonomy, $object_type );
837
838 return true;
839}
840
841//
842// Term API.
843//
844
845/**
846 * Retrieves object IDs of valid taxonomy and term.
847 *
848 * The strings of `$taxonomies` must exist before this function will continue.
849 * On failure of finding a valid taxonomy, it will return a WP_Error.
850 *
851 * The `$terms` aren't checked the same as `$taxonomies`, but still need to exist
852 * for object IDs to be returned.
853 *
854 * It is possible to change the order that object IDs are returned by using `$args`
855 * with either ASC or DESC array. The value should be in the key named 'order'.
856 *
857 * @since 2.3.0
858 *
859 * @global wpdb $wpdb WordPress database abstraction object.
860 *
861 * @param int|int[] $term_ids Term ID or array of term IDs of terms that will be used.
862 * @param string|string[] $taxonomies String of taxonomy name or Array of string values of taxonomy names.
863 * @param array|string $args {
864 * Change the order of the object IDs.
865 *
866 * @type string $order Order to retrieve terms. Accepts 'ASC' or 'DESC'. Default 'ASC'.
867 * }
868 * @return string[]|WP_Error An array of object IDs as numeric strings on success,
869 * WP_Error if the taxonomy does not exist.
870 */
871function get_objects_in_term( $term_ids, $taxonomies, $args = array() ) {
872 global $wpdb;
873
874 if ( ! is_array( $term_ids ) ) {
875 $term_ids = array( $term_ids );
876 }
877 if ( ! is_array( $taxonomies ) ) {
878 $taxonomies = array( $taxonomies );
879 }
880 foreach ( (array) $taxonomies as $taxonomy ) {
881 if ( ! taxonomy_exists( $taxonomy ) ) {
882 return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
883 }
884 }
885
886 $defaults = array( 'order' => 'ASC' );
887 $args = wp_parse_args( $args, $defaults );
888
889 $order = ( 'desc' === strtolower( $args['order'] ) ) ? 'DESC' : 'ASC';
890
891 $term_ids = array_map( 'intval', $term_ids );
892
893 $taxonomies = "'" . implode( "', '", array_map( 'esc_sql', $taxonomies ) ) . "'";
894 $term_ids = "'" . implode( "', '", $term_ids ) . "'";
895
896 $sql = "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";
897
898 $last_changed = wp_cache_get_last_changed( 'terms' );
899 $cache_key = 'get_objects_in_term:' . md5( $sql );
900 $cache = wp_cache_get_salted( $cache_key, 'term-queries', $last_changed );
901 if ( false === $cache ) {
902 $object_ids = $wpdb->get_col( $sql );
903 wp_cache_set_salted( $cache_key, $object_ids, 'term-queries', $last_changed );
904 } else {
905 $object_ids = (array) $cache;
906 }
907
908 if ( ! $object_ids ) {
909 return array();
910 }
911 return $object_ids;
912}
913
914/**
915 * Given a taxonomy query, generates SQL to be appended to a main query.
916 *
917 * @since 3.1.0
918 *
919 * @see WP_Tax_Query
920 *
921 * @param array $tax_query A compact tax query
922 * @param string $primary_table
923 * @param string $primary_id_column
924 * @return string[]
925 */
926function get_tax_sql( $tax_query, $primary_table, $primary_id_column ) {
927 $tax_query_obj = new WP_Tax_Query( $tax_query );
928 return $tax_query_obj->get_sql( $primary_table, $primary_id_column );
929}
930
931/**
932 * Gets all term data from database by term ID.
933 *
934 * The usage of the get_term function is to apply filters to a term object. It
935 * is possible to get a term object from the database before applying the
936 * filters.
937 *
938 * $term ID must be part of $taxonomy, to get from the database. Failure, might
939 * be able to be captured by the hooks. Failure would be the same value as $wpdb
940 * returns for the get_row method.
941 *
942 * There are two hooks, one is specifically for each term, named 'get_term', and
943 * the second is for the taxonomy name, 'term_$taxonomy'. Both hooks gets the
944 * term object, and the taxonomy name as parameters. Both hooks are expected to
945 * return a term object.
946 *
947 * {@see 'get_term'} hook - Takes two parameters the term Object and the taxonomy name.
948 * Must return term object. Used in get_term() as a catch-all filter for every
949 * $term.
950 *
951 * {@see 'get_$taxonomy'} hook - Takes two parameters the term Object and the taxonomy
952 * name. Must return term object. $taxonomy will be the taxonomy name, so for
953 * example, if 'category', it would be 'get_category' as the filter name. Useful
954 * for custom taxonomies or plugging into default taxonomies.
955 *
956 * @todo Better formatting for DocBlock
957 *
958 * @since 2.3.0
959 * @since 4.4.0 Converted to return a WP_Term object if `$output` is `OBJECT`.
960 * The `$taxonomy` parameter was made optional.
961 *
962 * @see sanitize_term_field() The $context param lists the available values for get_term_by() $filter param.
963 *
964 * @param int|WP_Term|object $term If integer, term data will be fetched from the database,
965 * or from the cache if available.
966 * If stdClass object (as in the results of a database query),
967 * will apply filters and return a `WP_Term` object with the `$term` data.
968 * If `WP_Term`, will return `$term`.
969 * @param string $taxonomy Optional. Taxonomy name that `$term` is part of.
970 * @param string $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which
971 * correspond to a WP_Term object, an associative array, or a numeric array,
972 * respectively. Default OBJECT.
973 * @param string $filter Optional. How to sanitize term fields. Default 'raw'.
974 * @return WP_Term|array|WP_Error|null WP_Term instance (or array) on success, depending on the `$output` value.
975 * WP_Error if `$taxonomy` does not exist. Null for miscellaneous failure.
976 */
977function get_term( $term, $taxonomy = '', $output = OBJECT, $filter = 'raw' ) {
978 if ( empty( $term ) ) {
979 return new WP_Error( 'invalid_term', __( 'Empty Term.' ) );
980 }
981
982 if ( $taxonomy && ! taxonomy_exists( $taxonomy ) ) {
983 return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
984 }
985
986 if ( $term instanceof WP_Term ) {
987 $_term = $term;
988 } elseif ( is_object( $term ) ) {
989 if ( empty( $term->filter ) || 'raw' === $term->filter ) {
990 $_term = sanitize_term( $term, $taxonomy, 'raw' );
991 $_term = new WP_Term( $_term );
992 } else {
993 $_term = WP_Term::get_instance( $term->term_id );
994 }
995 } else {
996 $_term = WP_Term::get_instance( $term, $taxonomy );
997 }
998
999 if ( is_wp_error( $_term ) ) {
1000 return $_term;
1001 } elseif ( ! $_term ) {
1002 return null;
1003 }
1004
1005 // Ensure for filters that this is not empty.
1006 $taxonomy = $_term->taxonomy;
1007
1008 $old_term = $_term;
1009 /**
1010 * Filters a taxonomy term object.
1011 *
1012 * The {@see 'get_$taxonomy'} hook is also available for targeting a specific
1013 * taxonomy.
1014 *
1015 * @since 2.3.0
1016 * @since 4.4.0 `$_term` is now a `WP_Term` object.
1017 *
1018 * @param WP_Term $_term Term object.
1019 * @param string $taxonomy The taxonomy slug.
1020 */
1021 $_term = apply_filters( 'get_term', $_term, $taxonomy );
1022
1023 /**
1024 * Filters a taxonomy term object.
1025 *
1026 * The dynamic portion of the hook name, `$taxonomy`, refers
1027 * to the slug of the term's taxonomy.
1028 *
1029 * Possible hook names include:
1030 *
1031 * - `get_category`
1032 * - `get_post_tag`
1033 *
1034 * @since 2.3.0
1035 * @since 4.4.0 `$_term` is now a `WP_Term` object.
1036 *
1037 * @param WP_Term $_term Term object.
1038 * @param string $taxonomy The taxonomy slug.
1039 */
1040 $_term = apply_filters( "get_{$taxonomy}", $_term, $taxonomy );
1041
1042 // Bail if a filter callback has changed the type of the `$_term` object.
1043 if ( ! ( $_term instanceof WP_Term ) ) {
1044 return $_term;
1045 }
1046
1047 // Sanitize term, according to the specified filter.
1048 if ( $_term !== $old_term || $_term->filter !== $filter ) {
1049 $_term->filter( $filter );
1050 }
1051
1052 if ( ARRAY_A === $output ) {
1053 return $_term->to_array();
1054 } elseif ( ARRAY_N === $output ) {
1055 return array_values( $_term->to_array() );
1056 }
1057
1058 return $_term;
1059}
1060
1061/**
1062 * Gets all term data from database by term field and data.
1063 *
1064 * Warning: $value is not escaped for 'name' $field. You must do it yourself, if
1065 * required.
1066 *
1067 * The default $field is 'id', therefore it is possible to also use null for
1068 * field, but not recommended that you do so.
1069 *
1070 * If $value does not exist, the return value will be false. If $taxonomy exists
1071 * and $field and $value combinations exist, the term will be returned.
1072 *
1073 * This function will always return the first term that matches the `$field`-
1074 * `$value`-`$taxonomy` combination specified in the parameters. If your query
1075 * is likely to match more than one term (as is likely to be the case when
1076 * `$field` is 'name', for example), consider using get_terms() instead; that
1077 * way, you will get all matching terms, and can provide your own logic for
1078 * deciding which one was intended.
1079 *
1080 * @todo Better formatting for DocBlock.
1081 *
1082 * @since 2.3.0
1083 * @since 4.4.0 `$taxonomy` is optional if `$field` is 'term_taxonomy_id'. Converted to return
1084 * a WP_Term object if `$output` is `OBJECT`.
1085 * @since 5.5.0 Added 'ID' as an alias of 'id' for the `$field` parameter.
1086 *
1087 * @see sanitize_term_field() The $context param lists the available values for get_term_by() $filter param.
1088 *
1089 * @param string $field Either 'slug', 'name', 'term_id' (or 'id', 'ID'), or 'term_taxonomy_id'.
1090 * @param string|int $value Search for this term value.
1091 * @param string $taxonomy Taxonomy name. Optional, if `$field` is 'term_taxonomy_id'.
1092 * @param string $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which
1093 * correspond to a WP_Term object, an associative array, or a numeric array,
1094 * respectively. Default OBJECT.
1095 * @param string $filter Optional. How to sanitize term fields. Default 'raw'.
1096 * @return WP_Term|array|false WP_Term instance (or array) on success, depending on the `$output` value.
1097 * False if `$taxonomy` does not exist or `$term` was not found.
1098 */
1099function get_term_by( $field, $value, $taxonomy = '', $output = OBJECT, $filter = 'raw' ) {
1100
1101 // 'term_taxonomy_id' lookups don't require taxonomy checks.
1102 if ( 'term_taxonomy_id' !== $field && ! taxonomy_exists( $taxonomy ) ) {
1103 return false;
1104 }
1105
1106 // No need to perform a query for empty 'slug' or 'name'.
1107 if ( 'slug' === $field || 'name' === $field ) {
1108 $value = (string) $value;
1109
1110 if ( 0 === strlen( $value ) ) {
1111 return false;
1112 }
1113 }
1114
1115 if ( 'id' === $field || 'ID' === $field || 'term_id' === $field ) {
1116 $term = get_term( (int) $value, $taxonomy, $output, $filter );
1117 if ( is_wp_error( $term ) || null === $term ) {
1118 $term = false;
1119 }
1120 return $term;
1121 }
1122
1123 $args = array(
1124 'get' => 'all',
1125 'number' => 1,
1126 'taxonomy' => $taxonomy,
1127 'update_term_meta_cache' => false,
1128 'orderby' => 'none',
1129 'suppress_filter' => true,
1130 );
1131
1132 switch ( $field ) {
1133 case 'slug':
1134 $args['slug'] = $value;
1135 break;
1136 case 'name':
1137 $args['name'] = $value;
1138 break;
1139 case 'term_taxonomy_id':
1140 $args['term_taxonomy_id'] = $value;
1141 unset( $args['taxonomy'] );
1142 break;
1143 default:
1144 return false;
1145 }
1146
1147 $terms = get_terms( $args );
1148 if ( is_wp_error( $terms ) || empty( $terms ) ) {
1149 return false;
1150 }
1151
1152 $term = array_shift( $terms );
1153
1154 // In the case of 'term_taxonomy_id', override the provided `$taxonomy` with whatever we find in the DB.
1155 if ( 'term_taxonomy_id' === $field ) {
1156 $taxonomy = $term->taxonomy;
1157 }
1158
1159 return get_term( $term, $taxonomy, $output, $filter );
1160}
1161
1162/**
1163 * Merges all term children into a single array of their IDs.
1164 *
1165 * This recursive function will merge all of the children of $term into the same
1166 * array of term IDs. Only useful for taxonomies which are hierarchical.
1167 *
1168 * Will return an empty array if $term does not exist in $taxonomy.
1169 *
1170 * @since 2.3.0
1171 *
1172 * @param int $term_id ID of term to get children.
1173 * @param string $taxonomy Taxonomy name.
1174 * @return array|WP_Error List of term IDs. WP_Error returned if `$taxonomy` does not exist.
1175 */
1176function get_term_children( $term_id, $taxonomy ) {
1177 if ( ! taxonomy_exists( $taxonomy ) ) {
1178 return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
1179 }
1180
1181 $term_id = (int) $term_id;
1182
1183 $terms = _get_term_hierarchy( $taxonomy );
1184
1185 if ( ! isset( $terms[ $term_id ] ) ) {
1186 return array();
1187 }
1188
1189 $children = $terms[ $term_id ];
1190
1191 foreach ( (array) $terms[ $term_id ] as $child ) {
1192 if ( $term_id === $child ) {
1193 continue;
1194 }
1195
1196 if ( isset( $terms[ $child ] ) ) {
1197 $children = array_merge( $children, get_term_children( $child, $taxonomy ) );
1198 }
1199 }
1200
1201 return $children;
1202}
1203
1204/**
1205 * Gets sanitized term field.
1206 *
1207 * The function is for contextual reasons and for simplicity of usage.
1208 *
1209 * @since 2.3.0
1210 * @since 4.4.0 The `$taxonomy` parameter was made optional. `$term` can also now accept a WP_Term object.
1211 *
1212 * @see sanitize_term_field()
1213 *
1214 * @param string $field Term field to fetch.
1215 * @param int|WP_Term $term Term ID or object.
1216 * @param string $taxonomy Optional. Taxonomy name. Default empty.
1217 * @param string $context Optional. How to sanitize term fields. Look at sanitize_term_field() for available options.
1218 * Default 'display'.
1219 * @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.
1220 */
1221function get_term_field( $field, $term, $taxonomy = '', $context = 'display' ) {
1222 $term = get_term( $term, $taxonomy );
1223 if ( is_wp_error( $term ) ) {
1224 return $term;
1225 }
1226
1227 if ( ! is_object( $term ) ) {
1228 return '';
1229 }
1230
1231 if ( ! isset( $term->$field ) ) {
1232 return '';
1233 }
1234
1235 return sanitize_term_field( $field, $term->$field, $term->term_id, $term->taxonomy, $context );
1236}
1237
1238/**
1239 * Sanitizes term for editing.
1240 *
1241 * Return value is sanitize_term() and usage is for sanitizing the term for
1242 * editing. Function is for contextual and simplicity.
1243 *
1244 * @since 2.3.0
1245 *
1246 * @param int|object $id Term ID or object.
1247 * @param string $taxonomy Taxonomy name.
1248 * @return string|int|null|WP_Error Will return empty string if $term is not an object.
1249 */
1250function get_term_to_edit( $id, $taxonomy ) {
1251 $term = get_term( $id, $taxonomy );
1252
1253 if ( is_wp_error( $term ) ) {
1254 return $term;
1255 }
1256
1257 if ( ! is_object( $term ) ) {
1258 return '';
1259 }
1260
1261 return sanitize_term( $term, $taxonomy, 'edit' );
1262}
1263
1264/**
1265 * Retrieves the terms in a given taxonomy or list of taxonomies.
1266 *
1267 * You can fully inject any customizations to the query before it is sent, as
1268 * well as control the output with a filter.
1269 *
1270 * The return type varies depending on the value passed to `$args['fields']`. See
1271 * WP_Term_Query::get_terms() for details. In all cases, a `WP_Error` object will
1272 * be returned if an invalid taxonomy is requested.
1273 *
1274 * The {@see 'get_terms'} filter will be called when the cache has the term and will
1275 * pass the found term along with the array of $taxonomies and array of $args.
1276 * This filter is also called before the array of terms is passed and will pass
1277 * the array of terms, along with the $taxonomies and $args.
1278 *
1279 * The {@see 'list_terms_exclusions'} filter passes the compiled exclusions along with
1280 * the $args.
1281 *
1282 * The {@see 'get_terms_orderby'} filter passes the `ORDER BY` clause for the query
1283 * along with the $args array.
1284 *
1285 * Taxonomy or an array of taxonomies should be passed via the 'taxonomy' argument
1286 * in the `$args` array:
1287 *
1288 * $terms = get_terms( array(
1289 * 'taxonomy' => 'post_tag',
1290 * 'hide_empty' => false,
1291 * ) );
1292 *
1293 * Prior to 4.5.0, taxonomy was passed as the first parameter of `get_terms()`.
1294 *
1295 * {@internal The `$deprecated` parameter is parsed for backward compatibility only.}
1296 *
1297 * @since 2.3.0
1298 * @since 4.2.0 Introduced 'name' and 'childless' parameters.
1299 * @since 4.4.0 Introduced the ability to pass 'term_id' as an alias of 'id' for the `orderby` parameter.
1300 * Introduced the 'meta_query' and 'update_term_meta_cache' parameters. Converted to return
1301 * a list of WP_Term objects.
1302 * @since 4.5.0 Changed the function signature so that the `$args` array can be provided as the first parameter.
1303 * Introduced 'meta_key' and 'meta_value' parameters. Introduced the ability to order results by metadata.
1304 * @since 4.8.0 Introduced 'suppress_filter' parameter.
1305 *
1306 * @param array|string $args Optional. Array or string of arguments. See WP_Term_Query::__construct()
1307 * for information on accepted arguments. Default empty array.
1308 * @param array|string $deprecated Optional. Argument array, when using the legacy function parameter format.
1309 * If present, this parameter will be interpreted as `$args`, and the first
1310 * function parameter will be parsed as a taxonomy or array of taxonomies.
1311 * Default empty.
1312 * @return WP_Term[]|int[]|string[]|string|WP_Error Array of terms, a count thereof as a numeric string,
1313 * or WP_Error if any of the taxonomies do not exist.
1314 * See the function description for more information.
1315 */
1316function get_terms( $args = array(), $deprecated = '' ) {
1317 $term_query = new WP_Term_Query();
1318
1319 $defaults = array(
1320 'suppress_filter' => false,
1321 );
1322
1323 /*
1324 * Legacy argument format ($taxonomy, $args) takes precedence.
1325 *
1326 * We detect legacy argument format by checking if
1327 * (a) a second non-empty parameter is passed, or
1328 * (b) the first parameter shares no keys with the default array (ie, it's a list of taxonomies)
1329 */
1330 $_args = wp_parse_args( $args );
1331 $key_intersect = array_intersect_key( $term_query->query_var_defaults, (array) $_args );
1332 $do_legacy_args = $deprecated || empty( $key_intersect );
1333
1334 if ( $do_legacy_args ) {
1335 $taxonomies = (array) $args;
1336 $args = wp_parse_args( $deprecated, $defaults );
1337 $args['taxonomy'] = $taxonomies;
1338 } else {
1339 $args = wp_parse_args( $args, $defaults );
1340 if ( isset( $args['taxonomy'] ) ) {
1341 $args['taxonomy'] = (array) $args['taxonomy'];
1342 }
1343 }
1344
1345 if ( ! empty( $args['taxonomy'] ) ) {
1346 foreach ( $args['taxonomy'] as $taxonomy ) {
1347 if ( ! taxonomy_exists( $taxonomy ) ) {
1348 return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
1349 }
1350 }
1351 }
1352
1353 // Don't pass suppress_filter to WP_Term_Query.
1354 $suppress_filter = $args['suppress_filter'];
1355 unset( $args['suppress_filter'] );
1356
1357 $terms = $term_query->query( $args );
1358
1359 // Count queries are not filtered, for legacy reasons.
1360 if ( ! is_array( $terms ) ) {
1361 return $terms;
1362 }
1363
1364 if ( $suppress_filter ) {
1365 return $terms;
1366 }
1367
1368 /**
1369 * Filters the found terms.
1370 *
1371 * @since 2.3.0
1372 * @since 4.6.0 Added the `$term_query` parameter.
1373 *
1374 * @param array $terms Array of found terms.
1375 * @param array|null $taxonomies An array of taxonomies if known.
1376 * @param array $args An array of get_terms() arguments.
1377 * @param WP_Term_Query $term_query The WP_Term_Query object.
1378 */
1379 return apply_filters( 'get_terms', $terms, $term_query->query_vars['taxonomy'], $term_query->query_vars, $term_query );
1380}
1381
1382/**
1383 * Adds metadata to a term.
1384 *
1385 * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input.
1386 *
1387 * @since 4.4.0
1388 *
1389 * @param int $term_id Term ID.
1390 * @param string $meta_key Metadata name.
1391 * @param mixed $meta_value Metadata value. Arrays and objects are stored as serialized data and
1392 * will be returned as the same type when retrieved. Other data types will
1393 * be stored as strings in the database:
1394 * - false is stored and retrieved as an empty string ('')
1395 * - true is stored and retrieved as '1'
1396 * - numbers (both integer and float) are stored and retrieved as strings
1397 * Must be serializable if non-scalar.
1398 * @param bool $unique Optional. Whether the same key should not be added.
1399 * Default false.
1400 * @return int|false|WP_Error Meta ID on success, false on failure.
1401 * WP_Error when term_id is ambiguous between taxonomies.
1402 */
1403function add_term_meta( $term_id, $meta_key, $meta_value, $unique = false ) {
1404 if ( wp_term_is_shared( $term_id ) ) {
1405 return new WP_Error( 'ambiguous_term_id', __( 'Term meta cannot be added to terms that are shared between taxonomies.' ), $term_id );
1406 }
1407
1408 return add_metadata( 'term', $term_id, $meta_key, $meta_value, $unique );
1409}
1410
1411/**
1412 * Removes metadata matching criteria from a term.
1413 *
1414 * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input.
1415 *
1416 * @since 4.4.0
1417 *
1418 * @param int $term_id Term ID.
1419 * @param string $meta_key Metadata name.
1420 * @param mixed $meta_value Optional. Metadata value. If provided,
1421 * rows will only be removed that match the value.
1422 * Must be serializable if non-scalar. Default empty.
1423 * @return bool True on success, false on failure.
1424 */
1425function delete_term_meta( $term_id, $meta_key, $meta_value = '' ) {
1426 return delete_metadata( 'term', $term_id, $meta_key, $meta_value );
1427}
1428
1429/**
1430 * Retrieves metadata for a term.
1431 *
1432 * @since 4.4.0
1433 *
1434 * @param int $term_id Term ID.
1435 * @param string $key Optional. The meta key to retrieve. By default,
1436 * returns data for all keys. Default empty.
1437 * @param bool $single Optional. Whether to return a single value.
1438 * This parameter has no effect if `$key` is not specified.
1439 * Default false.
1440 * @return mixed An array of values if `$single` is false.
1441 * The value of the meta field if `$single` is true.
1442 * False for an invalid `$term_id` (non-numeric, zero, or negative value).
1443 * An empty array if a valid but non-existing term ID is passed and `$single` is false.
1444 * An empty string if a valid but non-existing term ID is passed and `$single` is true.
1445 * Note: Non-serialized values are returned as strings:
1446 * - false values are returned as empty strings ('')
1447 * - true values are returned as '1'
1448 * - numbers are returned as strings
1449 * Arrays and objects retain their original type.
1450 */
1451function get_term_meta( $term_id, $key = '', $single = false ) {
1452 return get_metadata( 'term', $term_id, $key, $single );
1453}
1454
1455/**
1456 * Updates term metadata.
1457 *
1458 * Use the `$prev_value` parameter to differentiate between meta fields with the same key and term ID.
1459 *
1460 * If the meta field for the term does not exist, it will be added.
1461 *
1462 * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input.
1463 *
1464 * @since 4.4.0
1465 *
1466 * @param int $term_id Term ID.
1467 * @param string $meta_key Metadata key.
1468 * @param mixed $meta_value Metadata value. Must be serializable if non-scalar.
1469 * @param mixed $prev_value Optional. Previous value to check before updating.
1470 * If specified, only update existing metadata entries with
1471 * this value. Otherwise, update all entries. Default empty.
1472 * @return int|bool|WP_Error Meta ID if the key didn't exist. true on successful update,
1473 * false on failure or if the value passed to the function
1474 * is the same as the one that is already in the database.
1475 * WP_Error when term_id is ambiguous between taxonomies.
1476 */
1477function update_term_meta( $term_id, $meta_key, $meta_value, $prev_value = '' ) {
1478 if ( wp_term_is_shared( $term_id ) ) {
1479 return new WP_Error( 'ambiguous_term_id', __( 'Term meta cannot be added to terms that are shared between taxonomies.' ), $term_id );
1480 }
1481
1482 return update_metadata( 'term', $term_id, $meta_key, $meta_value, $prev_value );
1483}
1484
1485/**
1486 * Updates metadata cache for list of term IDs.
1487 *
1488 * Performs SQL query to retrieve all metadata for the terms matching `$term_ids` and stores them in the cache.
1489 * Subsequent calls to `get_term_meta()` will not need to query the database.
1490 *
1491 * @since 4.4.0
1492 *
1493 * @param array $term_ids List of term IDs.
1494 * @return array|false An array of metadata on success, false if there is nothing to update.
1495 */
1496function update_termmeta_cache( $term_ids ) {
1497 return update_meta_cache( 'term', $term_ids );
1498}
1499
1500
1501/**
1502 * Queue term meta for lazy-loading.
1503 *
1504 * @since 6.3.0
1505 *
1506 * @param array $term_ids List of term IDs.
1507 */
1508function wp_lazyload_term_meta( array $term_ids ) {
1509 if ( empty( $term_ids ) ) {
1510 return;
1511 }
1512 $lazyloader = wp_metadata_lazyloader();
1513 $lazyloader->queue_objects( 'term', $term_ids );
1514}
1515
1516/**
1517 * Gets all meta data, including meta IDs, for the given term ID.
1518 *
1519 * @since 4.9.0
1520 *
1521 * @global wpdb $wpdb WordPress database abstraction object.
1522 *
1523 * @param int $term_id Term ID.
1524 * @return array|false Array with meta data, or false when the meta table is not installed.
1525 */
1526function has_term_meta( $term_id ) {
1527 $check = wp_check_term_meta_support_prefilter( null );
1528 if ( null !== $check ) {
1529 return $check;
1530 }
1531
1532 global $wpdb;
1533
1534 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 );
1535}
1536
1537/**
1538 * Registers a meta key for terms.
1539 *
1540 * @since 4.9.8
1541 *
1542 * @param string $taxonomy Taxonomy to register a meta key for. Pass an empty string
1543 * to register the meta key across all existing taxonomies.
1544 * @param string $meta_key The meta key to register.
1545 * @param array $args Data used to describe the meta key when registered. See
1546 * {@see register_meta()} for a list of supported arguments.
1547 * @return bool True if the meta key was successfully registered, false if not.
1548 */
1549function register_term_meta( $taxonomy, $meta_key, array $args ) {
1550 $args['object_subtype'] = $taxonomy;
1551
1552 return register_meta( 'term', $meta_key, $args );
1553}
1554
1555/**
1556 * Unregisters a meta key for terms.
1557 *
1558 * @since 4.9.8
1559 *
1560 * @param string $taxonomy Taxonomy the meta key is currently registered for. Pass
1561 * an empty string if the meta key is registered across all
1562 * existing taxonomies.
1563 * @param string $meta_key The meta key to unregister.
1564 * @return bool True on success, false if the meta key was not previously registered.
1565 */
1566function unregister_term_meta( $taxonomy, $meta_key ) {
1567 return unregister_meta_key( 'term', $meta_key, $taxonomy );
1568}
1569
1570/**
1571 * Determines whether a taxonomy term exists.
1572 *
1573 * Formerly is_term(), introduced in 2.3.0.
1574 *
1575 * For more information on this and similar theme functions, check out
1576 * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
1577 * Conditional Tags} article in the Theme Developer Handbook.
1578 *
1579 * @since 3.0.0
1580 * @since 6.0.0 Converted to use `get_terms()`.
1581 *
1582 * @global bool $_wp_suspend_cache_invalidation
1583 *
1584 * @param int|string $term The term to check. Accepts term ID, slug, or name.
1585 * @param string $taxonomy Optional. The taxonomy name to use.
1586 * @param int $parent_term Optional. ID of parent term under which to confine the exists search.
1587 * @return mixed Returns null if the term does not exist.
1588 * Returns the term ID if no taxonomy is specified and the term ID exists.
1589 * Returns an array of the term ID and the term taxonomy ID if the taxonomy is specified and the pairing exists.
1590 * Returns 0 if term ID 0 is passed to the function.
1591 */
1592function term_exists( $term, $taxonomy = '', $parent_term = null ) {
1593 global $_wp_suspend_cache_invalidation;
1594
1595 if ( null === $term ) {
1596 return null;
1597 }
1598
1599 $defaults = array(
1600 'get' => 'all',
1601 'fields' => 'ids',
1602 'number' => 1,
1603 'update_term_meta_cache' => false,
1604 'order' => 'ASC',
1605 'orderby' => 'term_id',
1606 'suppress_filter' => true,
1607 );
1608
1609 // Ensure that while importing, queries are not cached.
1610 if ( ! empty( $_wp_suspend_cache_invalidation ) ) {
1611 $defaults['cache_results'] = false;
1612 }
1613
1614 if ( ! empty( $taxonomy ) ) {
1615 $defaults['taxonomy'] = $taxonomy;
1616 $defaults['fields'] = 'all';
1617 }
1618
1619 /**
1620 * Filters default query arguments for checking if a term exists.
1621 *
1622 * @since 6.0.0
1623 *
1624 * @param array $defaults An array of arguments passed to get_terms().
1625 * @param int|string $term The term to check. Accepts term ID, slug, or name.
1626 * @param string $taxonomy The taxonomy name to use. An empty string indicates
1627 * the search is against all taxonomies.
1628 * @param int|null $parent_term ID of parent term under which to confine the exists search.
1629 * Null indicates the search is unconfined.
1630 */
1631 $defaults = apply_filters( 'term_exists_default_query_args', $defaults, $term, $taxonomy, $parent_term );
1632
1633 if ( ! empty( $taxonomy ) && is_numeric( $parent_term ) ) {
1634 $defaults['parent'] = (int) $parent_term;
1635 }
1636
1637 if ( is_int( $term ) ) {
1638 if ( 0 === $term ) {
1639 return 0;
1640 }
1641 $args = wp_parse_args( array( 'include' => array( $term ) ), $defaults );
1642 $terms = get_terms( $args );
1643 } else {
1644 $term = trim( wp_unslash( $term ) );
1645 if ( '' === $term ) {
1646 return null;
1647 }
1648
1649 $args = wp_parse_args( array( 'slug' => sanitize_title( $term ) ), $defaults );
1650 $terms = get_terms( $args );
1651 if ( empty( $terms ) || is_wp_error( $terms ) ) {
1652 $args = wp_parse_args( array( 'name' => $term ), $defaults );
1653 $terms = get_terms( $args );
1654 }
1655 }
1656
1657 if ( empty( $terms ) || is_wp_error( $terms ) ) {
1658 return null;
1659 }
1660
1661 $_term = array_shift( $terms );
1662
1663 if ( ! empty( $taxonomy ) ) {
1664 return array(
1665 'term_id' => (string) $_term->term_id,
1666 'term_taxonomy_id' => (string) $_term->term_taxonomy_id,
1667 );
1668 }
1669
1670 return (string) $_term;
1671}
1672
1673/**
1674 * Checks if a term is an ancestor of another term.
1675 *
1676 * You can use either an ID or the term object for both parameters.
1677 *
1678 * @since 3.4.0
1679 *
1680 * @param int|object $term1 ID or object to check if this is the parent term.
1681 * @param int|object $term2 The child term.
1682 * @param string $taxonomy Taxonomy name that $term1 and `$term2` belong to.
1683 * @return bool Whether `$term2` is a child of `$term1`.
1684 */
1685function term_is_ancestor_of( $term1, $term2, $taxonomy ) {
1686 if ( ! isset( $term1->term_id ) ) {
1687 $term1 = get_term( $term1, $taxonomy );
1688 }
1689 if ( ! isset( $term2->parent ) ) {
1690 $term2 = get_term( $term2, $taxonomy );
1691 }
1692
1693 if ( empty( $term1->term_id ) || empty( $term2->parent ) ) {
1694 return false;
1695 }
1696 if ( $term2->parent === $term1->term_id ) {
1697 return true;
1698 }
1699
1700 return term_is_ancestor_of( $term1, get_term( $term2->parent, $taxonomy ), $taxonomy );
1701}
1702
1703/**
1704 * Sanitizes all term fields.
1705 *
1706 * Relies on sanitize_term_field() to sanitize the term. The difference is that
1707 * this function will sanitize **all** fields. The context is based
1708 * on sanitize_term_field().
1709 *
1710 * The `$term` is expected to be either an array or an object.
1711 *
1712 * @since 2.3.0
1713 *
1714 * @param array|object $term The term to check.
1715 * @param string $taxonomy The taxonomy name to use.
1716 * @param string $context Optional. Context in which to sanitize the term.
1717 * Accepts 'raw', 'edit', 'db', 'display', 'rss',
1718 * 'attribute', or 'js'. Default 'display'.
1719 * @return array|object Term with all fields sanitized.
1720 */
1721function sanitize_term( $term, $taxonomy, $context = 'display' ) {
1722 $fields = array( 'term_id', 'name', 'description', 'slug', 'count', 'parent', 'term_group', 'term_taxonomy_id', 'object_id' );
1723
1724 $do_object = is_object( $term );
1725
1726 $term_id = $do_object ? $term->term_id : ( isset( $term['term_id'] ) ? $term['term_id'] : 0 );
1727
1728 foreach ( (array) $fields as $field ) {
1729 if ( $do_object ) {
1730 if ( isset( $term->$field ) ) {
1731 $term->$field = sanitize_term_field( $field, $term->$field, $term_id, $taxonomy, $context );
1732 }
1733 } else {
1734 if ( isset( $term[ $field ] ) ) {
1735 $term[ $field ] = sanitize_term_field( $field, $term[ $field ], $term_id, $taxonomy, $context );
1736 }
1737 }
1738 }
1739
1740 if ( $do_object ) {
1741 $term->filter = $context;
1742 } else {
1743 $term['filter'] = $context;
1744 }
1745
1746 return $term;
1747}
1748
1749/**
1750 * Sanitizes the field value in the term based on the context.
1751 *
1752 * Passing a term field value through the function should be assumed to have
1753 * cleansed the value for whatever context the term field is going to be used.
1754 *
1755 * If no context or an unsupported context is given, then default filters will
1756 * be applied.
1757 *
1758 * There are enough filters for each context to support a custom filtering
1759 * without creating your own filter function. Simply create a function that
1760 * hooks into the filter you need.
1761 *
1762 * @since 2.3.0
1763 *
1764 * @param string $field Term field to sanitize.
1765 * @param string $value Search for this term value.
1766 * @param int $term_id Term ID.
1767 * @param string $taxonomy Taxonomy name.
1768 * @param string $context Context in which to sanitize the term field.
1769 * Accepts 'raw', 'edit', 'db', 'display', 'rss',
1770 * 'attribute', or 'js'. Default 'display'.
1771 * @return mixed Sanitized field.
1772 */
1773function sanitize_term_field( $field, $value, $term_id, $taxonomy, $context ) {
1774 $int_fields = array( 'parent', 'term_id', 'count', 'term_group', 'term_taxonomy_id', 'object_id' );
1775 if ( in_array( $field, $int_fields, true ) ) {
1776 $value = (int) $value;
1777 if ( $value < 0 ) {
1778 $value = 0;
1779 }
1780 }
1781
1782 $context = strtolower( $context );
1783
1784 if ( 'raw' === $context ) {
1785 return $value;
1786 }
1787
1788 if ( 'edit' === $context ) {
1789
1790 /**
1791 * Filters a term field to edit before it is sanitized.
1792 *
1793 * The dynamic portion of the hook name, `$field`, refers to the term field.
1794 *
1795 * @since 2.3.0
1796 *
1797 * @param mixed $value Value of the term field.
1798 * @param int $term_id Term ID.
1799 * @param string $taxonomy Taxonomy slug.
1800 */
1801 $value = apply_filters( "edit_term_{$field}", $value, $term_id, $taxonomy );
1802
1803 /**
1804 * Filters the taxonomy field to edit before it is sanitized.
1805 *
1806 * The dynamic portions of the filter name, `$taxonomy` and `$field`, refer
1807 * to the taxonomy slug and taxonomy field, respectively.
1808 *
1809 * @since 2.3.0
1810 *
1811 * @param mixed $value Value of the taxonomy field to edit.
1812 * @param int $term_id Term ID.
1813 */
1814 $value = apply_filters( "edit_{$taxonomy}_{$field}", $value, $term_id );
1815
1816 if ( 'description' === $field ) {
1817 $value = esc_html( $value ); // textarea_escaped
1818 } else {
1819 $value = esc_attr( $value );
1820 }
1821 } elseif ( 'db' === $context ) {
1822
1823 /**
1824 * Filters a term field value before it is sanitized.
1825 *
1826 * The dynamic portion of the hook name, `$field`, refers to the term field.
1827 *
1828 * @since 2.3.0
1829 *
1830 * @param mixed $value Value of the term field.
1831 * @param string $taxonomy Taxonomy slug.
1832 */
1833 $value = apply_filters( "pre_term_{$field}", $value, $taxonomy );
1834
1835 /**
1836 * Filters a taxonomy field before it is sanitized.
1837 *
1838 * The dynamic portions of the filter name, `$taxonomy` and `$field`, refer
1839 * to the taxonomy slug and field name, respectively.
1840 *
1841 * @since 2.3.0
1842 *
1843 * @param mixed $value Value of the taxonomy field.
1844 */
1845 $value = apply_filters( "pre_{$taxonomy}_{$field}", $value );
1846
1847 // Back compat filters.
1848 if ( 'slug' === $field ) {
1849 /**
1850 * Filters the category nicename before it is sanitized.
1851 *
1852 * Use the {@see 'pre_$taxonomy_$field'} hook instead.
1853 *
1854 * @since 2.0.3
1855 *
1856 * @param string $value The category nicename.
1857 */
1858 $value = apply_filters( 'pre_category_nicename', $value );
1859 }
1860 } elseif ( 'rss' === $context ) {
1861
1862 /**
1863 * Filters the term field for use in RSS.
1864 *
1865 * The dynamic portion of the hook name, `$field`, refers to the term field.
1866 *
1867 * @since 2.3.0
1868 *
1869 * @param mixed $value Value of the term field.
1870 * @param string $taxonomy Taxonomy slug.
1871 */
1872 $value = apply_filters( "term_{$field}_rss", $value, $taxonomy );
1873
1874 /**
1875 * Filters the taxonomy field for use in RSS.
1876 *
1877 * The dynamic portions of the hook name, `$taxonomy`, and `$field`, refer
1878 * to the taxonomy slug and field name, respectively.
1879 *
1880 * @since 2.3.0
1881 *
1882 * @param mixed $value Value of the taxonomy field.
1883 */
1884 $value = apply_filters( "{$taxonomy}_{$field}_rss", $value );
1885 } else {
1886 // Use display filters by default.
1887
1888 /**
1889 * Filters the term field sanitized for display.
1890 *
1891 * The dynamic portion of the hook name, `$field`, refers to the term field name.
1892 *
1893 * @since 2.3.0
1894 *
1895 * @param mixed $value Value of the term field.
1896 * @param int $term_id Term ID.
1897 * @param string $taxonomy Taxonomy slug.
1898 * @param string $context Context to retrieve the term field value.
1899 */
1900 $value = apply_filters( "term_{$field}", $value, $term_id, $taxonomy, $context );
1901
1902 /**
1903 * Filters the taxonomy field sanitized for display.
1904 *
1905 * The dynamic portions of the filter name, `$taxonomy`, and `$field`, refer
1906 * to the taxonomy slug and taxonomy field, respectively.
1907 *
1908 * @since 2.3.0
1909 *
1910 * @param mixed $value Value of the taxonomy field.
1911 * @param int $term_id Term ID.
1912 * @param string $context Context to retrieve the taxonomy field value.
1913 */
1914 $value = apply_filters( "{$taxonomy}_{$field}", $value, $term_id, $context );
1915 }
1916
1917 if ( 'attribute' === $context ) {
1918 $value = esc_attr( $value );
1919 } elseif ( 'js' === $context ) {
1920 $value = esc_js( $value );
1921 }
1922
1923 // Restore the type for integer fields after esc_attr().
1924 if ( in_array( $field, $int_fields, true ) ) {
1925 $value = (int) $value;
1926 }
1927
1928 return $value;
1929}
1930
1931/**
1932 * Counts how many terms are in taxonomy.
1933 *
1934 * Default $args is 'hide_empty' which can be 'hide_empty=true' or array('hide_empty' => true).
1935 *
1936 * {@internal The `$deprecated` parameter is parsed for backward compatibility only.}
1937 *
1938 * @since 2.3.0
1939 * @since 5.6.0 Changed the function signature so that the `$args` array can be provided as the first parameter.
1940 *
1941 * @param array|string $args Optional. Array or string of arguments. See WP_Term_Query::__construct()
1942 * for information on accepted arguments. Default empty array.
1943 * @param array|string $deprecated Optional. Argument array, when using the legacy function parameter format.
1944 * If present, this parameter will be interpreted as `$args`, and the first
1945 * function parameter will be parsed as a taxonomy or array of taxonomies.
1946 * Default empty.
1947 * @return string|WP_Error Numeric string containing the number of terms in that
1948 * taxonomy or WP_Error if the taxonomy does not exist.
1949 */
1950function wp_count_terms( $args = array(), $deprecated = '' ) {
1951 $use_legacy_args = false;
1952
1953 // Check whether function is used with legacy signature: `$taxonomy` and `$args`.
1954 if ( $args
1955 && ( is_string( $args ) && taxonomy_exists( $args )
1956 || is_array( $args ) && wp_is_numeric_array( $args ) )
1957 ) {
1958 $use_legacy_args = true;
1959 }
1960
1961 $defaults = array( 'hide_empty' => false );
1962
1963 if ( $use_legacy_args ) {
1964 $defaults['taxonomy'] = $args;
1965 $args = $deprecated;
1966 }
1967
1968 $args = wp_parse_args( $args, $defaults );
1969
1970 // Backward compatibility.
1971 if ( isset( $args['ignore_empty'] ) ) {
1972 $args['hide_empty'] = $args['ignore_empty'];
1973 unset( $args['ignore_empty'] );
1974 }
1975
1976 $args['fields'] = 'count';
1977
1978 return get_terms( $args );
1979}
1980
1981/**
1982 * Unlinks the object from the taxonomy or taxonomies.
1983 *
1984 * Will remove all relationships between the object and any terms in
1985 * a particular taxonomy or taxonomies. Does not remove the term or
1986 * taxonomy itself.
1987 *
1988 * @since 2.3.0
1989 *
1990 * @param int $object_id The term object ID that refers to the term.
1991 * @param string|array $taxonomies List of taxonomy names or single taxonomy name.
1992 */
1993function wp_delete_object_term_relationships( $object_id, $taxonomies ) {
1994 $object_id = (int) $object_id;
1995
1996 if ( ! is_array( $taxonomies ) ) {
1997 $taxonomies = array( $taxonomies );
1998 }
1999
2000 foreach ( (array) $taxonomies as $taxonomy ) {
2001 $term_ids = wp_get_object_terms( $object_id, $taxonomy, array( 'fields' => 'ids' ) );
2002 $term_ids = array_map( 'intval', $term_ids );
2003 wp_remove_object_terms( $object_id, $term_ids, $taxonomy );
2004 }
2005}
2006
2007/**
2008 * Removes a term from the database.
2009 *
2010 * If the term is a parent of other terms, then the children will be updated to
2011 * that term's parent.
2012 *
2013 * Metadata associated with the term will be deleted.
2014 *
2015 * @since 2.3.0
2016 *
2017 * @global wpdb $wpdb WordPress database abstraction object.
2018 *
2019 * @param int $term Term ID.
2020 * @param string $taxonomy Taxonomy name.
2021 * @param array|string $args {
2022 * Optional. Array of arguments to override the default term ID. Default empty array.
2023 *
2024 * @type int $default The term ID to make the default term. This will only override
2025 * the terms found if there is only one term found. Any other and
2026 * the found terms are used.
2027 * @type bool $force_default Optional. Whether to force the supplied term as default to be
2028 * assigned even if the object was not going to be term-less.
2029 * Default false.
2030 * }
2031 * @return bool|int|WP_Error True on success, false if term does not exist. Zero on attempted
2032 * deletion of default Category. WP_Error if the taxonomy does not exist.
2033 */
2034function wp_delete_term( $term, $taxonomy, $args = array() ) {
2035 global $wpdb;
2036
2037 $term = (int) $term;
2038
2039 $ids = term_exists( $term, $taxonomy );
2040 if ( ! $ids ) {
2041 return false;
2042 }
2043 if ( is_wp_error( $ids ) ) {
2044 return $ids;
2045 }
2046
2047 $tt_id = $ids['term_taxonomy_id'];
2048
2049 $defaults = array();
2050
2051 if ( 'category' === $taxonomy ) {
2052 $defaults['default'] = (int) get_option( 'default_category' );
2053 if ( $defaults['default'] === $term ) {
2054 return 0; // Don't delete the default category.
2055 }
2056 }
2057
2058 // Don't delete the default custom taxonomy term.
2059 $taxonomy_object = get_taxonomy( $taxonomy );
2060 if ( ! empty( $taxonomy_object->default_term ) ) {
2061 $defaults['default'] = (int) get_option( 'default_term_' . $taxonomy );
2062 if ( $defaults['default'] === $term ) {
2063 return 0;
2064 }
2065 }
2066
2067 $args = wp_parse_args( $args, $defaults );
2068
2069 if ( isset( $args['default'] ) ) {
2070 $default = (int) $args['default'];
2071 if ( ! term_exists( $default, $taxonomy ) ) {
2072 unset( $default );
2073 }
2074 }
2075
2076 if ( isset( $args['force_default'] ) ) {
2077 $force_default = $args['force_default'];
2078 }
2079
2080 /**
2081 * Fires when deleting a term, before any modifications are made to posts or terms.
2082 *
2083 * @since 4.1.0
2084 *
2085 * @param int $term Term ID.
2086 * @param string $taxonomy Taxonomy name.
2087 */
2088 do_action( 'pre_delete_term', $term, $taxonomy );
2089
2090 // Update children to point to new parent.
2091 if ( is_taxonomy_hierarchical( $taxonomy ) ) {
2092 $term_obj = get_term( $term, $taxonomy );
2093 if ( is_wp_error( $term_obj ) ) {
2094 return $term_obj;
2095 }
2096 $parent = $term_obj->parent;
2097
2098 $edit_ids = $wpdb->get_results( "SELECT term_id, term_taxonomy_id FROM $wpdb->term_taxonomy WHERE `parent` = " . (int) $term_obj->term_id );
2099 $edit_tt_ids = wp_list_pluck( $edit_ids, 'term_taxonomy_id' );
2100
2101 /**
2102 * Fires immediately before a term to delete's children are reassigned a parent.
2103 *
2104 * @since 2.9.0
2105 *
2106 * @param array $edit_tt_ids An array of term taxonomy IDs for the given term.
2107 */
2108 do_action( 'edit_term_taxonomies', $edit_tt_ids );
2109
2110 $wpdb->update( $wpdb->term_taxonomy, compact( 'parent' ), array( 'parent' => $term_obj->term_id ) + compact( 'taxonomy' ) );
2111
2112 // Clean the cache for all child terms.
2113 $edit_term_ids = wp_list_pluck( $edit_ids, 'term_id' );
2114 clean_term_cache( $edit_term_ids, $taxonomy );
2115
2116 /**
2117 * Fires immediately after a term to delete's children are reassigned a parent.
2118 *
2119 * @since 2.9.0
2120 *
2121 * @param array $edit_tt_ids An array of term taxonomy IDs for the given term.
2122 */
2123 do_action( 'edited_term_taxonomies', $edit_tt_ids );
2124 }
2125
2126 // Get the term before deleting it or its term relationships so we can pass to actions below.
2127 $deleted_term = get_term( $term, $taxonomy );
2128
2129 $object_ids = (array) $wpdb->get_col( $wpdb->prepare( "SELECT object_id FROM $wpdb->term_relationships WHERE term_taxonomy_id = %d", $tt_id ) );
2130
2131 foreach ( $object_ids as $object_id ) {
2132 if ( ! isset( $default ) ) {
2133 wp_remove_object_terms( $object_id, $term, $taxonomy );
2134 continue;
2135 }
2136
2137 $terms = wp_get_object_terms(
2138 $object_id,
2139 $taxonomy,
2140 array(
2141 'fields' => 'ids',
2142 'orderby' => 'none',
2143 )
2144 );
2145
2146 if ( 1 === count( $terms ) ) {
2147 $terms = array( $default );
2148 } else {
2149 $terms = array_diff( $terms, array( $term ) );
2150 if ( isset( $force_default ) && $force_default ) {
2151 $terms = array_merge( $terms, array( $default ) );
2152 }
2153 }
2154
2155 $terms = array_map( 'intval', $terms );
2156 wp_set_object_terms( $object_id, $terms, $taxonomy );
2157 }
2158
2159 // Clean the relationship caches for all object types using this term.
2160 $tax_object = get_taxonomy( $taxonomy );
2161 foreach ( $tax_object->object_type as $object_type ) {
2162 clean_object_term_cache( $object_ids, $object_type );
2163 }
2164
2165 $term_meta_ids = $wpdb->get_col( $wpdb->prepare( "SELECT meta_id FROM $wpdb->termmeta WHERE term_id = %d ", $term ) );
2166 foreach ( $term_meta_ids as $mid ) {
2167 delete_metadata_by_mid( 'term', $mid );
2168 }
2169
2170 /**
2171 * Fires immediately before a term taxonomy ID is deleted.
2172 *
2173 * @since 2.9.0
2174 *
2175 * @param int $tt_id Term taxonomy ID.
2176 */
2177 do_action( 'delete_term_taxonomy', $tt_id );
2178
2179 $wpdb->delete( $wpdb->term_taxonomy, array( 'term_taxonomy_id' => $tt_id ) );
2180
2181 /**
2182 * Fires immediately after a term taxonomy ID is deleted.
2183 *
2184 * @since 2.9.0
2185 *
2186 * @param int $tt_id Term taxonomy ID.
2187 */
2188 do_action( 'deleted_term_taxonomy', $tt_id );
2189
2190 // Delete the term if no taxonomies use it.
2191 if ( ! $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_taxonomy WHERE term_id = %d", $term ) ) ) {
2192 $wpdb->delete( $wpdb->terms, array( 'term_id' => $term ) );
2193 }
2194
2195 clean_term_cache( $term, $taxonomy );
2196
2197 /**
2198 * Fires after a term is deleted from the database and the cache is cleaned.
2199 *
2200 * The {@see 'delete_$taxonomy'} hook is also available for targeting a specific
2201 * taxonomy.
2202 *
2203 * @since 2.5.0
2204 * @since 4.5.0 Introduced the `$object_ids` argument.
2205 *
2206 * @param int $term Term ID.
2207 * @param int $tt_id Term taxonomy ID.
2208 * @param string $taxonomy Taxonomy slug.
2209 * @param WP_Term $deleted_term Copy of the already-deleted term.
2210 * @param array $object_ids List of term object IDs.
2211 */
2212 do_action( 'delete_term', $term, $tt_id, $taxonomy, $deleted_term, $object_ids );
2213
2214 /**
2215 * Fires after a term in a specific taxonomy is deleted.
2216 *
2217 * The dynamic portion of the hook name, `$taxonomy`, refers to the specific
2218 * taxonomy the term belonged to.
2219 *
2220 * Possible hook names include:
2221 *
2222 * - `delete_category`
2223 * - `delete_post_tag`
2224 *
2225 * @since 2.3.0
2226 * @since 4.5.0 Introduced the `$object_ids` argument.
2227 *
2228 * @param int $term Term ID.
2229 * @param int $tt_id Term taxonomy ID.
2230 * @param WP_Term $deleted_term Copy of the already-deleted term.
2231 * @param array $object_ids List of term object IDs.
2232 */
2233 do_action( "delete_{$taxonomy}", $term, $tt_id, $deleted_term, $object_ids );
2234
2235 return true;
2236}
2237
2238/**
2239 * Deletes one existing category.
2240 *
2241 * @since 2.0.0
2242 *
2243 * @param int $cat_id Category term ID.
2244 * @return bool|int|WP_Error Returns true if completes delete action; false if term doesn't exist;
2245 * Zero on attempted deletion of default Category; WP_Error object is
2246 * also a possibility.
2247 */
2248function wp_delete_category( $cat_id ) {
2249 return wp_delete_term( $cat_id, 'category' );
2250}
2251
2252/**
2253 * Retrieves the terms associated with the given object(s), in the supplied taxonomies.
2254 *
2255 * @since 2.3.0
2256 * @since 4.2.0 Added support for 'taxonomy', 'parent', and 'term_taxonomy_id' values of `$orderby`.
2257 * Introduced `$parent` argument.
2258 * @since 4.4.0 Introduced `$meta_query` and `$update_term_meta_cache` arguments. When `$fields` is 'all' or
2259 * 'all_with_object_id', an array of `WP_Term` objects will be returned.
2260 * @since 4.7.0 Refactored to use WP_Term_Query, and to support any WP_Term_Query arguments.
2261 * @since 6.3.0 Passing `update_term_meta_cache` argument value false by default resulting in get_terms() to not
2262 * prime the term meta cache.
2263 *
2264 * @param int|int[] $object_ids The ID(s) of the object(s) to retrieve.
2265 * @param string|string[] $taxonomies The taxonomy names to retrieve terms from.
2266 * @param array|string $args See WP_Term_Query::__construct() for supported arguments.
2267 * @return WP_Term[]|int[]|string[]|string|WP_Error Array of terms, a count thereof as a numeric string,
2268 * or WP_Error if any of the taxonomies do not exist.
2269 * See WP_Term_Query::get_terms() for more information.
2270 */
2271function wp_get_object_terms( $object_ids, $taxonomies, $args = array() ) {
2272 if ( empty( $object_ids ) || empty( $taxonomies ) ) {
2273 return array();
2274 }
2275
2276 if ( ! is_array( $taxonomies ) ) {
2277 $taxonomies = array( $taxonomies );
2278 }
2279
2280 foreach ( $taxonomies as $taxonomy ) {
2281 if ( ! taxonomy_exists( $taxonomy ) ) {
2282 return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
2283 }
2284 }
2285
2286 if ( ! is_array( $object_ids ) ) {
2287 $object_ids = array( $object_ids );
2288 }
2289 $object_ids = array_map( 'intval', $object_ids );
2290
2291 $defaults = array(
2292 'update_term_meta_cache' => false,
2293 );
2294
2295 $args = wp_parse_args( $args, $defaults );
2296
2297 /**
2298 * Filters arguments for retrieving object terms.
2299 *
2300 * @since 4.9.0
2301 *
2302 * @param array $args An array of arguments for retrieving terms for the given object(s).
2303 * See {@see wp_get_object_terms()} for details.
2304 * @param int[] $object_ids Array of object IDs.
2305 * @param string[] $taxonomies Array of taxonomy names to retrieve terms from.
2306 */
2307 $args = apply_filters( 'wp_get_object_terms_args', $args, $object_ids, $taxonomies );
2308
2309 /*
2310 * When one or more queried taxonomies is registered with an 'args' array,
2311 * those params override the `$args` passed to this function.
2312 */
2313 $terms = array();
2314 if ( count( $taxonomies ) > 1 ) {
2315 foreach ( $taxonomies as $index => $taxonomy ) {
2316 $t = get_taxonomy( $taxonomy );
2317 if ( isset( $t->args ) && is_array( $t->args ) && array_merge( $args, $t->args ) != $args ) {
2318 unset( $taxonomies[ $index ] );
2319 $terms = array_merge( $terms, wp_get_object_terms( $object_ids, $taxonomy, array_merge( $args, $t->args ) ) );
2320 }
2321 }
2322 } else {
2323 $t = get_taxonomy( $taxonomies[0] );
2324 if ( isset( $t->args ) && is_array( $t->args ) ) {
2325 $args = array_merge( $args, $t->args );
2326 }
2327 }
2328
2329 $args['taxonomy'] = $taxonomies;
2330 $args['object_ids'] = $object_ids;
2331
2332 // Taxonomies registered without an 'args' param are handled here.
2333 if ( ! empty( $taxonomies ) ) {
2334 $terms_from_remaining_taxonomies = get_terms( $args );
2335
2336 // Array keys should be preserved for values of $fields that use term_id for keys.
2337 if ( ! empty( $args['fields'] ) && str_starts_with( $args['fields'], 'id=>' ) ) {
2338 $terms = $terms + $terms_from_remaining_taxonomies;
2339 } else {
2340 $terms = array_merge( $terms, $terms_from_remaining_taxonomies );
2341 }
2342 }
2343
2344 /**
2345 * Filters the terms for a given object or objects.
2346 *
2347 * @since 4.2.0
2348 *
2349 * @param WP_Term[]|int[]|string[]|string $terms Array of terms or a count thereof as a numeric string.
2350 * @param int[] $object_ids Array of object IDs for which terms were retrieved.
2351 * @param string[] $taxonomies Array of taxonomy names from which terms were retrieved.
2352 * @param array $args Array of arguments for retrieving terms for the given
2353 * object(s). See wp_get_object_terms() for details.
2354 */
2355 $terms = apply_filters( 'get_object_terms', $terms, $object_ids, $taxonomies, $args );
2356
2357 $object_ids = implode( ',', $object_ids );
2358 $taxonomies = "'" . implode( "', '", array_map( 'esc_sql', $taxonomies ) ) . "'";
2359
2360 /**
2361 * Filters the terms for a given object or objects.
2362 *
2363 * The `$taxonomies` parameter passed to this filter is formatted as a SQL fragment. The
2364 * {@see 'get_object_terms'} filter is recommended as an alternative.
2365 *
2366 * @since 2.8.0
2367 *
2368 * @param WP_Term[]|int[]|string[]|string $terms Array of terms or a count thereof as a numeric string.
2369 * @param string $object_ids Comma separated list of object IDs for which terms were retrieved.
2370 * @param string $taxonomies SQL fragment of taxonomy names from which terms were retrieved.
2371 * @param array $args Array of arguments for retrieving terms for the given
2372 * object(s). See wp_get_object_terms() for details.
2373 */
2374 return apply_filters( 'wp_get_object_terms', $terms, $object_ids, $taxonomies, $args );
2375}
2376
2377/**
2378 * Adds a new term to the database.
2379 *
2380 * A non-existent term is inserted in the following sequence:
2381 * 1. The term is added to the term table, then related to the taxonomy.
2382 * 2. If everything is correct, several actions are fired.
2383 * 3. The 'term_id_filter' is evaluated.
2384 * 4. The term cache is cleaned.
2385 * 5. Several more actions are fired.
2386 * 6. An array is returned containing the `term_id` and `term_taxonomy_id`.
2387 *
2388 * If the 'slug' argument is not empty, then it is checked to see if the term
2389 * is invalid. If it is not a valid, existing term, it is added and the term_id
2390 * is given.
2391 *
2392 * If the taxonomy is hierarchical, and the 'parent' argument is not empty,
2393 * the term is inserted and the term_id will be given.
2394 *
2395 * Error handling:
2396 * If `$taxonomy` does not exist or `$term` is empty,
2397 * a WP_Error object will be returned.
2398 *
2399 * If the term already exists on the same hierarchical level,
2400 * or the term slug and name are not unique, a WP_Error object will be returned.
2401 *
2402 * @global wpdb $wpdb WordPress database abstraction object.
2403 *
2404 * @since 2.3.0
2405 *
2406 * @param string $term The term name to add.
2407 * @param string $taxonomy The taxonomy to which to add the term.
2408 * @param array|string $args {
2409 * Optional. Array or query string of arguments for inserting a term.
2410 *
2411 * @type string $alias_of Slug of the term to make this term an alias of.
2412 * Default empty string. Accepts a term slug.
2413 * @type string $description The term description. Default empty string.
2414 * @type int $parent The id of the parent term. Default 0.
2415 * @type string $slug The term slug to use. Default empty string.
2416 * }
2417 * @return array|WP_Error {
2418 * An array of the new term data, WP_Error otherwise.
2419 *
2420 * @type int $term_id The new term ID.
2421 * @type int|string $term_taxonomy_id The new term taxonomy ID. Can be a numeric string.
2422 * }
2423 */
2424function wp_insert_term( $term, $taxonomy, $args = array() ) {
2425 global $wpdb;
2426
2427 if ( ! taxonomy_exists( $taxonomy ) ) {
2428 return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
2429 }
2430
2431 /**
2432 * Filters a term before it is sanitized and inserted into the database.
2433 *
2434 * @since 3.0.0
2435 * @since 6.1.0 The `$args` parameter was added.
2436 *
2437 * @param string|WP_Error $term The term name to add, or a WP_Error object if there's an error.
2438 * @param string $taxonomy Taxonomy slug.
2439 * @param array|string $args Array or query string of arguments passed to wp_insert_term().
2440 */
2441 $term = apply_filters( 'pre_insert_term', $term, $taxonomy, $args );
2442
2443 if ( is_wp_error( $term ) ) {
2444 return $term;
2445 }
2446
2447 if ( is_int( $term ) && 0 === $term ) {
2448 return new WP_Error( 'invalid_term_id', __( 'Invalid term ID.' ) );
2449 }
2450
2451 if ( '' === trim( $term ) ) {
2452 return new WP_Error( 'empty_term_name', __( 'A name is required for this term.' ) );
2453 }
2454
2455 $defaults = array(
2456 'alias_of' => '',
2457 'description' => '',
2458 'parent' => 0,
2459 'slug' => '',
2460 );
2461 $args = wp_parse_args( $args, $defaults );
2462
2463 if ( (int) $args['parent'] > 0 && ! term_exists( (int) $args['parent'] ) ) {
2464 return new WP_Error( 'missing_parent', __( 'Parent term does not exist.' ) );
2465 }
2466
2467 $args['name'] = $term;
2468 $args['taxonomy'] = $taxonomy;
2469
2470 // Coerce null description to strings, to avoid database errors.
2471 $args['description'] = (string) $args['description'];
2472
2473 $args = sanitize_term( $args, $taxonomy, 'db' );
2474
2475 // expected_slashed ($name)
2476 $name = wp_unslash( $args['name'] );
2477 $description = wp_unslash( $args['description'] );
2478 $parent = (int) $args['parent'];
2479
2480 // Sanitization could clean the name to an empty string that must be checked again.
2481 if ( '' === $name ) {
2482 return new WP_Error( 'invalid_term_name', __( 'Invalid term name.' ) );
2483 }
2484
2485 $slug_provided = ! empty( $args['slug'] );
2486 if ( ! $slug_provided ) {
2487 $slug = sanitize_title( $name );
2488 } else {
2489 $slug = $args['slug'];
2490 }
2491
2492 $term_group = 0;
2493 if ( $args['alias_of'] ) {
2494 $alias = get_term_by( 'slug', $args['alias_of'], $taxonomy );
2495 if ( ! empty( $alias->term_group ) ) {
2496 // The alias we want is already in a group, so let's use that one.
2497 $term_group = $alias->term_group;
2498 } elseif ( ! empty( $alias->term_id ) ) {
2499 /*
2500 * The alias is not in a group, so we create a new one
2501 * and add the alias to it.
2502 */
2503 $term_group = $wpdb->get_var( "SELECT MAX(term_group) FROM $wpdb->terms" ) + 1;
2504
2505 wp_update_term(
2506 $alias->term_id,
2507 $taxonomy,
2508 array(
2509 'term_group' => $term_group,
2510 )
2511 );
2512 }
2513 }
2514
2515 /*
2516 * Prevent the creation of terms with duplicate names at the same level of a taxonomy hierarchy,
2517 * unless a unique slug has been explicitly provided.
2518 */
2519 $name_matches = get_terms(
2520 array(
2521 'taxonomy' => $taxonomy,
2522 'name' => $name,
2523 'hide_empty' => false,
2524 'parent' => $args['parent'],
2525 'update_term_meta_cache' => false,
2526 )
2527 );
2528
2529 /*
2530 * The `name` match in `get_terms()` doesn't differentiate accented characters,
2531 * so we do a stricter comparison here.
2532 */
2533 $name_match = null;
2534 if ( $name_matches ) {
2535 foreach ( $name_matches as $_match ) {
2536 if ( strtolower( $name ) === strtolower( $_match->name ) ) {
2537 $name_match = $_match;
2538 break;
2539 }
2540 }
2541 }
2542
2543 if ( $name_match ) {
2544 $slug_match = get_term_by( 'slug', $slug, $taxonomy );
2545 if ( ! $slug_provided || $name_match->slug === $slug || $slug_match ) {
2546 if ( is_taxonomy_hierarchical( $taxonomy ) ) {
2547 $siblings = get_terms(
2548 array(
2549 'taxonomy' => $taxonomy,
2550 'get' => 'all',
2551 'parent' => $parent,
2552 'update_term_meta_cache' => false,
2553 )
2554 );
2555
2556 $existing_term = null;
2557 $sibling_names = wp_list_pluck( $siblings, 'name' );
2558 $sibling_slugs = wp_list_pluck( $siblings, 'slug' );
2559
2560 if ( ( ! $slug_provided || $name_match->slug === $slug ) && in_array( $name, $sibling_names, true ) ) {
2561 $existing_term = $name_match;
2562 } elseif ( $slug_match && in_array( $slug, $sibling_slugs, true ) ) {
2563 $existing_term = $slug_match;
2564 }
2565
2566 if ( $existing_term ) {
2567 return new WP_Error( 'term_exists', __( 'A term with the name provided already exists with this parent.' ), $existing_term->term_id );
2568 }
2569 } else {
2570 return new WP_Error( 'term_exists', __( 'A term with the name provided already exists in this taxonomy.' ), $name_match->term_id );
2571 }
2572 }
2573 }
2574
2575 $slug = wp_unique_term_slug( $slug, (object) $args );
2576
2577 $data = compact( 'name', 'slug', 'term_group' );
2578
2579 /**
2580 * Filters term data before it is inserted into the database.
2581 *
2582 * @since 4.7.0
2583 *
2584 * @param array $data Term data to be inserted.
2585 * @param string $taxonomy Taxonomy slug.
2586 * @param array $args Arguments passed to wp_insert_term().
2587 */
2588 $data = apply_filters( 'wp_insert_term_data', $data, $taxonomy, $args );
2589
2590 if ( false === $wpdb->insert( $wpdb->terms, $data ) ) {
2591 return new WP_Error( 'db_insert_error', __( 'Could not insert term into the database.' ), $wpdb->last_error );
2592 }
2593
2594 $term_id = (int) $wpdb->insert_id;
2595
2596 // Seems unreachable. However, is used in the case that a term name is provided, which sanitizes to an empty string.
2597 if ( empty( $slug ) ) {
2598 $slug = sanitize_title( $slug, $term_id );
2599
2600 /** This action is documented in wp-includes/taxonomy.php */
2601 do_action( 'edit_terms', $term_id, $taxonomy );
2602 $wpdb->update( $wpdb->terms, compact( 'slug' ), compact( 'term_id' ) );
2603
2604 /** This action is documented in wp-includes/taxonomy.php */
2605 do_action( 'edited_terms', $term_id, $taxonomy );
2606 }
2607
2608 $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 ) );
2609
2610 if ( ! empty( $tt_id ) ) {
2611 return array(
2612 'term_id' => $term_id,
2613 'term_taxonomy_id' => $tt_id,
2614 );
2615 }
2616
2617 if ( false === $wpdb->insert( $wpdb->term_taxonomy, compact( 'term_id', 'taxonomy', 'description', 'parent' ) + array( 'count' => 0 ) ) ) {
2618 return new WP_Error( 'db_insert_error', __( 'Could not insert term taxonomy into the database.' ), $wpdb->last_error );
2619 }
2620
2621 $tt_id = (int) $wpdb->insert_id;
2622
2623 /*
2624 * Confidence check: if we just created a term with the same parent + taxonomy + slug but a higher term_id than
2625 * an existing term, then we have unwittingly created a duplicate term. Delete the dupe, and use the term_id
2626 * and term_taxonomy_id of the older term instead. Then return out of the function so that the "create" hooks
2627 * are not fired.
2628 */
2629 $duplicate_term = $wpdb->get_row( $wpdb->prepare( "SELECT t.term_id, t.slug, tt.term_taxonomy_id, tt.taxonomy FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS 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 ) );
2630
2631 /**
2632 * Filters the duplicate term check that takes place during term creation.
2633 *
2634 * Term parent + taxonomy + slug combinations are meant to be unique, and wp_insert_term()
2635 * performs a last-minute confirmation of this uniqueness before allowing a new term
2636 * to be created. Plugins with different uniqueness requirements may use this filter
2637 * to bypass or modify the duplicate-term check.
2638 *
2639 * @since 5.1.0
2640 *
2641 * @param object $duplicate_term Duplicate term row from terms table, if found.
2642 * @param string $term Term being inserted.
2643 * @param string $taxonomy Taxonomy name.
2644 * @param array $args Arguments passed to wp_insert_term().
2645 * @param int $tt_id term_taxonomy_id for the newly created term.
2646 */
2647 $duplicate_term = apply_filters( 'wp_insert_term_duplicate_term_check', $duplicate_term, $term, $taxonomy, $args, $tt_id );
2648
2649 if ( $duplicate_term ) {
2650 $wpdb->delete( $wpdb->terms, array( 'term_id' => $term_id ) );
2651 $wpdb->delete( $wpdb->term_taxonomy, array( 'term_taxonomy_id' => $tt_id ) );
2652
2653 $term_id = (int) $duplicate_term->term_id;
2654 $tt_id = (int) $duplicate_term->term_taxonomy_id;
2655
2656 clean_term_cache( $term_id, $taxonomy );
2657 return array(
2658 'term_id' => $term_id,
2659 'term_taxonomy_id' => $tt_id,
2660 );
2661 }
2662
2663 /**
2664 * Fires immediately after a new term is created, before the term cache is cleaned.
2665 *
2666 * The {@see 'create_$taxonomy'} hook is also available for targeting a specific
2667 * taxonomy.
2668 *
2669 * @since 2.3.0
2670 * @since 6.1.0 The `$args` parameter was added.
2671 *
2672 * @param int $term_id Term ID.
2673 * @param int $tt_id Term taxonomy ID.
2674 * @param string $taxonomy Taxonomy slug.
2675 * @param array $args Arguments passed to wp_insert_term().
2676 */
2677 do_action( 'create_term', $term_id, $tt_id, $taxonomy, $args );
2678
2679 /**
2680 * Fires after a new term is created for a specific taxonomy.
2681 *
2682 * The dynamic portion of the hook name, `$taxonomy`, refers
2683 * to the slug of the taxonomy the term was created for.
2684 *
2685 * Possible hook names include:
2686 *
2687 * - `create_category`
2688 * - `create_post_tag`
2689 *
2690 * @since 2.3.0
2691 * @since 6.1.0 The `$args` parameter was added.
2692 *
2693 * @param int $term_id Term ID.
2694 * @param int $tt_id Term taxonomy ID.
2695 * @param array $args Arguments passed to wp_insert_term().
2696 */
2697 do_action( "create_{$taxonomy}", $term_id, $tt_id, $args );
2698
2699 /**
2700 * Filters the term ID after a new term is created.
2701 *
2702 * @since 2.3.0
2703 * @since 6.1.0 The `$args` parameter was added.
2704 *
2705 * @param int $term_id Term ID.
2706 * @param int $tt_id Term taxonomy ID.
2707 * @param array $args Arguments passed to wp_insert_term().
2708 */
2709 $term_id = apply_filters( 'term_id_filter', $term_id, $tt_id, $args );
2710
2711 clean_term_cache( $term_id, $taxonomy );
2712
2713 /**
2714 * Fires after a new term is created, and after the term cache has been cleaned.
2715 *
2716 * The {@see 'created_$taxonomy'} hook is also available for targeting a specific
2717 * taxonomy.
2718 *
2719 * @since 2.3.0
2720 * @since 6.1.0 The `$args` parameter was added.
2721 *
2722 * @param int $term_id Term ID.
2723 * @param int $tt_id Term taxonomy ID.
2724 * @param string $taxonomy Taxonomy slug.
2725 * @param array $args Arguments passed to wp_insert_term().
2726 */
2727 do_action( 'created_term', $term_id, $tt_id, $taxonomy, $args );
2728
2729 /**
2730 * Fires after a new term in a specific taxonomy is created, and after the term
2731 * cache has been cleaned.
2732 *
2733 * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
2734 *
2735 * Possible hook names include:
2736 *
2737 * - `created_category`
2738 * - `created_post_tag`
2739 *
2740 * @since 2.3.0
2741 * @since 6.1.0 The `$args` parameter was added.
2742 *
2743 * @param int $term_id Term ID.
2744 * @param int $tt_id Term taxonomy ID.
2745 * @param array $args Arguments passed to wp_insert_term().
2746 */
2747 do_action( "created_{$taxonomy}", $term_id, $tt_id, $args );
2748
2749 /**
2750 * Fires after a term has been saved, and the term cache has been cleared.
2751 *
2752 * The {@see 'saved_$taxonomy'} hook is also available for targeting a specific
2753 * taxonomy.
2754 *
2755 * @since 5.5.0
2756 * @since 6.1.0 The `$args` parameter was added.
2757 *
2758 * @param int $term_id Term ID.
2759 * @param int $tt_id Term taxonomy ID.
2760 * @param string $taxonomy Taxonomy slug.
2761 * @param bool $update Whether this is an existing term being updated.
2762 * @param array $args Arguments passed to wp_insert_term().
2763 */
2764 do_action( 'saved_term', $term_id, $tt_id, $taxonomy, false, $args );
2765
2766 /**
2767 * Fires after a term in a specific taxonomy has been saved, and the term
2768 * cache has been cleared.
2769 *
2770 * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
2771 *
2772 * Possible hook names include:
2773 *
2774 * - `saved_category`
2775 * - `saved_post_tag`
2776 *
2777 * @since 5.5.0
2778 * @since 6.1.0 The `$args` parameter was added.
2779 *
2780 * @param int $term_id Term ID.
2781 * @param int $tt_id Term taxonomy ID.
2782 * @param bool $update Whether this is an existing term being updated.
2783 * @param array $args Arguments passed to wp_insert_term().
2784 */
2785 do_action( "saved_{$taxonomy}", $term_id, $tt_id, false, $args );
2786
2787 return array(
2788 'term_id' => $term_id,
2789 'term_taxonomy_id' => $tt_id,
2790 );
2791}
2792
2793/**
2794 * Creates term and taxonomy relationships.
2795 *
2796 * Relates an object (post, link, etc.) to a term and taxonomy type. Creates the
2797 * term and taxonomy relationship if it doesn't already exist. Creates a term if
2798 * it doesn't exist (using the slug).
2799 *
2800 * A relationship means that the term is grouped in or belongs to the taxonomy.
2801 * A term has no meaning until it is given context by defining which taxonomy it
2802 * exists under.
2803 *
2804 * @since 2.3.0
2805 *
2806 * @global wpdb $wpdb WordPress database abstraction object.
2807 *
2808 * @param int $object_id The object to relate to.
2809 * @param string|int|array $terms A single term slug, single term ID, or array of either term slugs or IDs.
2810 * Will replace all existing related terms in this taxonomy. Passing an
2811 * empty array will remove all related terms.
2812 * @param string $taxonomy The context in which to relate the term to the object.
2813 * @param bool $append Optional. If false will delete difference of terms. Default false.
2814 * @return array|WP_Error Term taxonomy IDs of the affected terms or WP_Error on failure.
2815 */
2816function wp_set_object_terms( $object_id, $terms, $taxonomy, $append = false ) {
2817 global $wpdb;
2818
2819 $object_id = (int) $object_id;
2820
2821 if ( ! taxonomy_exists( $taxonomy ) ) {
2822 return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
2823 }
2824
2825 if ( empty( $terms ) ) {
2826 $terms = array();
2827 } elseif ( ! is_array( $terms ) ) {
2828 $terms = array( $terms );
2829 }
2830
2831 if ( ! $append ) {
2832 $old_tt_ids = wp_get_object_terms(
2833 $object_id,
2834 $taxonomy,
2835 array(
2836 'fields' => 'tt_ids',
2837 'orderby' => 'none',
2838 'update_term_meta_cache' => false,
2839 )
2840 );
2841 } else {
2842 $old_tt_ids = array();
2843 }
2844
2845 $tt_ids = array();
2846 $new_tt_ids = array();
2847
2848 foreach ( (array) $terms as $term ) {
2849 if ( '' === trim( $term ) ) {
2850 continue;
2851 }
2852
2853 $term_info = term_exists( $term, $taxonomy );
2854
2855 if ( ! $term_info ) {
2856 // Skip if a non-existent term ID is passed.
2857 if ( is_int( $term ) ) {
2858 continue;
2859 }
2860
2861 $term_info = wp_insert_term( $term, $taxonomy );
2862 }
2863
2864 if ( is_wp_error( $term_info ) ) {
2865 return $term_info;
2866 }
2867
2868 $tt_id = $term_info['term_taxonomy_id'];
2869 $tt_ids[] = $tt_id;
2870
2871 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 ) ) ) {
2872 continue;
2873 }
2874
2875 /**
2876 * Fires immediately before an object-term relationship is added.
2877 *
2878 * @since 2.9.0
2879 * @since 4.7.0 Added the `$taxonomy` parameter.
2880 *
2881 * @param int $object_id Object ID.
2882 * @param int $tt_id Term taxonomy ID.
2883 * @param string $taxonomy Taxonomy slug.
2884 */
2885 do_action( 'add_term_relationship', $object_id, $tt_id, $taxonomy );
2886
2887 $wpdb->insert(
2888 $wpdb->term_relationships,
2889 array(
2890 'object_id' => $object_id,
2891 'term_taxonomy_id' => $tt_id,
2892 )
2893 );
2894
2895 /**
2896 * Fires immediately after an object-term relationship is added.
2897 *
2898 * @since 2.9.0
2899 * @since 4.7.0 Added the `$taxonomy` parameter.
2900 *
2901 * @param int $object_id Object ID.
2902 * @param int $tt_id Term taxonomy ID.
2903 * @param string $taxonomy Taxonomy slug.
2904 */
2905 do_action( 'added_term_relationship', $object_id, $tt_id, $taxonomy );
2906
2907 $new_tt_ids[] = $tt_id;
2908 }
2909
2910 if ( $new_tt_ids ) {
2911 wp_update_term_count( $new_tt_ids, $taxonomy );
2912 }
2913
2914 if ( ! $append ) {
2915 $delete_tt_ids = array_diff( $old_tt_ids, $tt_ids );
2916
2917 if ( $delete_tt_ids ) {
2918 $in_delete_tt_ids = "'" . implode( "', '", $delete_tt_ids ) . "'";
2919 $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 ) );
2920 $delete_term_ids = array_map( 'intval', $delete_term_ids );
2921
2922 $remove = wp_remove_object_terms( $object_id, $delete_term_ids, $taxonomy );
2923 if ( is_wp_error( $remove ) ) {
2924 return $remove;
2925 }
2926 }
2927 }
2928
2929 $t = get_taxonomy( $taxonomy );
2930
2931 if ( ! $append && isset( $t->sort ) && $t->sort ) {
2932 $values = array();
2933 $term_order = 0;
2934
2935 $final_tt_ids = wp_get_object_terms(
2936 $object_id,
2937 $taxonomy,
2938 array(
2939 'fields' => 'tt_ids',
2940 'update_term_meta_cache' => false,
2941 )
2942 );
2943
2944 foreach ( $tt_ids as $tt_id ) {
2945 if ( in_array( (int) $tt_id, $final_tt_ids, true ) ) {
2946 $values[] = $wpdb->prepare( '(%d, %d, %d)', $object_id, $tt_id, ++$term_order );
2947 }
2948 }
2949
2950 if ( $values ) {
2951 if ( false === $wpdb->query( "INSERT INTO $wpdb->term_relationships (object_id, term_taxonomy_id, term_order) VALUES " . implode( ',', $values ) . ' ON DUPLICATE KEY UPDATE term_order = VALUES(term_order)' ) ) {
2952 return new WP_Error( 'db_insert_error', __( 'Could not insert term relationship into the database.' ), $wpdb->last_error );
2953 }
2954 }
2955 }
2956
2957 wp_cache_delete( $object_id, $taxonomy . '_relationships' );
2958 wp_cache_set_terms_last_changed();
2959
2960 /**
2961 * Fires after an object's terms have been set.
2962 *
2963 * @since 2.8.0
2964 *
2965 * @param int $object_id Object ID.
2966 * @param array $terms An array of object term IDs or slugs.
2967 * @param array $tt_ids An array of term taxonomy IDs.
2968 * @param string $taxonomy Taxonomy slug.
2969 * @param bool $append Whether to append new terms to the old terms.
2970 * @param array $old_tt_ids Old array of term taxonomy IDs.
2971 */
2972 do_action( 'set_object_terms', $object_id, $terms, $tt_ids, $taxonomy, $append, $old_tt_ids );
2973
2974 return $tt_ids;
2975}
2976
2977/**
2978 * Adds term(s) associated with a given object.
2979 *
2980 * @since 3.6.0
2981 *
2982 * @param int $object_id The ID of the object to which the terms will be added.
2983 * @param string|int|array $terms The slug(s) or ID(s) of the term(s) to add.
2984 * @param array|string $taxonomy Taxonomy name.
2985 * @return array|WP_Error Term taxonomy IDs of the affected terms.
2986 */
2987function wp_add_object_terms( $object_id, $terms, $taxonomy ) {
2988 return wp_set_object_terms( $object_id, $terms, $taxonomy, true );
2989}
2990
2991/**
2992 * Removes term(s) associated with a given object.
2993 *
2994 * @since 3.6.0
2995 *
2996 * @global wpdb $wpdb WordPress database abstraction object.
2997 *
2998 * @param int $object_id The ID of the object from which the terms will be removed.
2999 * @param string|int|array $terms The slug(s) or ID(s) of the term(s) to remove.
3000 * @param string $taxonomy Taxonomy name.
3001 * @return bool|WP_Error True on success, false or WP_Error on failure.
3002 */
3003function wp_remove_object_terms( $object_id, $terms, $taxonomy ) {
3004 global $wpdb;
3005
3006 $object_id = (int) $object_id;
3007
3008 if ( ! taxonomy_exists( $taxonomy ) ) {
3009 return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
3010 }
3011
3012 if ( ! is_array( $terms ) ) {
3013 $terms = array( $terms );
3014 }
3015
3016 $tt_ids = array();
3017
3018 foreach ( (array) $terms as $term ) {
3019 if ( '' === trim( $term ) ) {
3020 continue;
3021 }
3022
3023 $term_info = term_exists( $term, $taxonomy );
3024 if ( ! $term_info ) {
3025 // Skip if a non-existent term ID is passed.
3026 if ( is_int( $term ) ) {
3027 continue;
3028 }
3029 }
3030
3031 if ( is_wp_error( $term_info ) ) {
3032 return $term_info;
3033 }
3034
3035 $tt_ids[] = $term_info['term_taxonomy_id'];
3036 }
3037
3038 if ( $tt_ids ) {
3039 $in_tt_ids = "'" . implode( "', '", $tt_ids ) . "'";
3040
3041 /**
3042 * Fires immediately before an object-term relationship is deleted.
3043 *
3044 * @since 2.9.0
3045 * @since 4.7.0 Added the `$taxonomy` parameter.
3046 *
3047 * @param int $object_id Object ID.
3048 * @param array $tt_ids An array of term taxonomy IDs.
3049 * @param string $taxonomy Taxonomy slug.
3050 */
3051 do_action( 'delete_term_relationships', $object_id, $tt_ids, $taxonomy );
3052
3053 $deleted = $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->term_relationships WHERE object_id = %d AND term_taxonomy_id IN ($in_tt_ids)", $object_id ) );
3054
3055 wp_cache_delete( $object_id, $taxonomy . '_relationships' );
3056 wp_cache_set_terms_last_changed();
3057
3058 /**
3059 * Fires immediately after an object-term relationship is deleted.
3060 *
3061 * @since 2.9.0
3062 * @since 4.7.0 Added the `$taxonomy` parameter.
3063 *
3064 * @param int $object_id Object ID.
3065 * @param array $tt_ids An array of term taxonomy IDs.
3066 * @param string $taxonomy Taxonomy slug.
3067 */
3068 do_action( 'deleted_term_relationships', $object_id, $tt_ids, $taxonomy );
3069
3070 wp_update_term_count( $tt_ids, $taxonomy );
3071
3072 return (bool) $deleted;
3073 }
3074
3075 return false;
3076}
3077
3078/**
3079 * Makes term slug unique, if it isn't already.
3080 *
3081 * The `$slug` has to be unique global to every taxonomy, meaning that one
3082 * taxonomy term can't have a matching slug with another taxonomy term. Each
3083 * slug has to be globally unique for every taxonomy.
3084 *
3085 * The way this works is that if the taxonomy that the term belongs to is
3086 * hierarchical and has a parent, it will append that parent to the $slug.
3087 *
3088 * If that still doesn't return a unique slug, then it tries to append a number
3089 * until it finds a number that is truly unique.
3090 *
3091 * The only purpose for `$term` is for appending a parent, if one exists.
3092 *
3093 * @since 2.3.0
3094 *
3095 * @global wpdb $wpdb WordPress database abstraction object.
3096 *
3097 * @param string $slug The string that will be tried for a unique slug.
3098 * @param object $term The term object that the `$slug` will belong to.
3099 * @return string Will return a true unique slug.
3100 */
3101function wp_unique_term_slug( $slug, $term ) {
3102 global $wpdb;
3103
3104 $needs_suffix = true;
3105 $original_slug = $slug;
3106
3107 // As of 4.1, duplicate slugs are allowed as long as they're in different taxonomies.
3108 if ( ! term_exists( $slug ) || get_option( 'db_version' ) >= 30133 && ! get_term_by( 'slug', $slug, $term->taxonomy ) ) {
3109 $needs_suffix = false;
3110 }
3111
3112 /*
3113 * If the taxonomy supports hierarchy and the term has a parent, make the slug unique
3114 * by incorporating parent slugs.
3115 */
3116 $parent_suffix = '';
3117 if ( $needs_suffix && is_taxonomy_hierarchical( $term->taxonomy ) && ! empty( $term->parent ) ) {
3118 $the_parent = $term->parent;
3119 while ( ! empty( $the_parent ) ) {
3120 $parent_term = get_term( $the_parent, $term->taxonomy );
3121 if ( is_wp_error( $parent_term ) || empty( $parent_term ) ) {
3122 break;
3123 }
3124 $parent_suffix .= '-' . $parent_term->slug;
3125 if ( ! term_exists( $slug . $parent_suffix ) ) {
3126 break;
3127 }
3128
3129 if ( empty( $parent_term->parent ) ) {
3130 break;
3131 }
3132 $the_parent = $parent_term->parent;
3133 }
3134 }
3135
3136 // If we didn't get a unique slug, try appending a number to make it unique.
3137
3138 /**
3139 * Filters whether the proposed unique term slug is bad.
3140 *
3141 * @since 4.3.0
3142 *
3143 * @param bool $needs_suffix Whether the slug needs to be made unique with a suffix.
3144 * @param string $slug The slug.
3145 * @param object $term Term object.
3146 */
3147 if ( apply_filters( 'wp_unique_term_slug_is_bad_slug', $needs_suffix, $slug, $term ) ) {
3148 if ( $parent_suffix ) {
3149 $slug .= $parent_suffix;
3150 }
3151
3152 if ( ! empty( $term->term_id ) ) {
3153 $query = $wpdb->prepare( "SELECT slug FROM $wpdb->terms WHERE slug = %s AND term_id != %d", $slug, $term->term_id );
3154 } else {
3155 $query = $wpdb->prepare( "SELECT slug FROM $wpdb->terms WHERE slug = %s", $slug );
3156 }
3157
3158 if ( $wpdb->get_var( $query ) ) { // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
3159 $num = 2;
3160 do {
3161 $alt_slug = $slug . "-$num";
3162 ++$num;
3163 $slug_check = $wpdb->get_var( $wpdb->prepare( "SELECT slug FROM $wpdb->terms WHERE slug = %s", $alt_slug ) );
3164 } while ( $slug_check );
3165 $slug = $alt_slug;
3166 }
3167 }
3168
3169 /**
3170 * Filters the unique term slug.
3171 *
3172 * @since 4.3.0
3173 *
3174 * @param string $slug Unique term slug.
3175 * @param object $term Term object.
3176 * @param string $original_slug Slug originally passed to the function for testing.
3177 */
3178 return apply_filters( 'wp_unique_term_slug', $slug, $term, $original_slug );
3179}
3180
3181/**
3182 * Updates term based on arguments provided.
3183 *
3184 * The `$args` will indiscriminately override all values with the same field name.
3185 * Care must be taken to not override important information need to update or
3186 * update will fail (or perhaps create a new term, neither would be acceptable).
3187 *
3188 * Defaults will set 'alias_of', 'description', 'parent', and 'slug' if not
3189 * defined in `$args` already.
3190 *
3191 * 'alias_of' will create a term group, if it doesn't already exist, and
3192 * update it for the `$term`.
3193 *
3194 * If the 'slug' argument in `$args` is missing, then the 'name' will be used.
3195 * If you set 'slug' and it isn't unique, then a WP_Error is returned.
3196 * If you don't pass any slug, then a unique one will be created.
3197 *
3198 * @since 2.3.0
3199 *
3200 * @global wpdb $wpdb WordPress database abstraction object.
3201 *
3202 * @param int $term_id The ID of the term.
3203 * @param string $taxonomy The taxonomy of the term.
3204 * @param array $args {
3205 * Optional. Array of arguments for updating a term.
3206 *
3207 * @type string $alias_of Slug of the term to make this term an alias of.
3208 * Default empty string. Accepts a term slug.
3209 * @type string $description The term description. Default empty string.
3210 * @type int $parent The id of the parent term. Default 0.
3211 * @type string $slug The term slug to use. Default empty string.
3212 * }
3213 * @return array|WP_Error An array containing the `term_id` and `term_taxonomy_id`,
3214 * WP_Error otherwise.
3215 */
3216function wp_update_term( $term_id, $taxonomy, $args = array() ) {
3217 global $wpdb;
3218
3219 if ( ! taxonomy_exists( $taxonomy ) ) {
3220 return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
3221 }
3222
3223 $term_id = (int) $term_id;
3224
3225 // First, get all of the original args.
3226 $term = get_term( $term_id, $taxonomy );
3227
3228 if ( is_wp_error( $term ) ) {
3229 return $term;
3230 }
3231
3232 if ( ! $term ) {
3233 return new WP_Error( 'invalid_term', __( 'Empty Term.' ) );
3234 }
3235
3236 $term = (array) $term->data;
3237
3238 // Escape data pulled from DB.
3239 $term = wp_slash( $term );
3240
3241 // Merge old and new args with new args overwriting old ones.
3242 $args = array_merge( $term, $args );
3243
3244 $defaults = array(
3245 'alias_of' => '',
3246 'description' => '',
3247 'parent' => 0,
3248 'slug' => '',
3249 );
3250 $args = wp_parse_args( $args, $defaults );
3251 $args = sanitize_term( $args, $taxonomy, 'db' );
3252 $parsed_args = $args;
3253
3254 // expected_slashed ($name)
3255 $name = wp_unslash( $args['name'] );
3256 $description = wp_unslash( $args['description'] );
3257
3258 $parsed_args['name'] = $name;
3259 $parsed_args['description'] = $description;
3260
3261 if ( '' === trim( $name ) ) {
3262 return new WP_Error( 'empty_term_name', __( 'A name is required for this term.' ) );
3263 }
3264
3265 if ( (int) $parsed_args['parent'] > 0 && ! term_exists( (int) $parsed_args['parent'] ) ) {
3266 return new WP_Error( 'missing_parent', __( 'Parent term does not exist.' ) );
3267 }
3268
3269 $empty_slug = false;
3270 if ( empty( $args['slug'] ) ) {
3271 $empty_slug = true;
3272 $slug = sanitize_title( $name );
3273 } else {
3274 $slug = $args['slug'];
3275 }
3276
3277 $parsed_args['slug'] = $slug;
3278
3279 $term_group = isset( $parsed_args['term_group'] ) ? $parsed_args['term_group'] : 0;
3280 if ( $args['alias_of'] ) {
3281 $alias = get_term_by( 'slug', $args['alias_of'], $taxonomy );
3282 if ( ! empty( $alias->term_group ) ) {
3283 // The alias we want is already in a group, so let's use that one.
3284 $term_group = $alias->term_group;
3285 } elseif ( ! empty( $alias->term_id ) ) {
3286 /*
3287 * The alias is not in a group, so we create a new one
3288 * and add the alias to it.
3289 */
3290 $term_group = $wpdb->get_var( "SELECT MAX(term_group) FROM $wpdb->terms" ) + 1;
3291
3292 wp_update_term(
3293 $alias->term_id,
3294 $taxonomy,
3295 array(
3296 'term_group' => $term_group,
3297 )
3298 );
3299 }
3300
3301 $parsed_args['term_group'] = $term_group;
3302 }
3303
3304 /**
3305 * Filters the term parent.
3306 *
3307 * Hook to this filter to see if it will cause a hierarchy loop.
3308 *
3309 * @since 3.1.0
3310 *
3311 * @param int $parent_term ID of the parent term.
3312 * @param int $term_id Term ID.
3313 * @param string $taxonomy Taxonomy slug.
3314 * @param array $parsed_args An array of potentially altered update arguments for the given term.
3315 * @param array $args Arguments passed to wp_update_term().
3316 */
3317 $parent = (int) apply_filters( 'wp_update_term_parent', $args['parent'], $term_id, $taxonomy, $parsed_args, $args );
3318
3319 // Check for duplicate slug.
3320 $duplicate = get_term_by( 'slug', $slug, $taxonomy );
3321 if ( $duplicate && $duplicate->term_id !== $term_id ) {
3322 /*
3323 * If an empty slug was passed or the parent changed, reset the slug to something unique.
3324 * Otherwise, bail.
3325 */
3326 if ( $empty_slug || ( $parent !== (int) $term['parent'] ) ) {
3327 $slug = wp_unique_term_slug( $slug, (object) $args );
3328 } else {
3329 /* translators: %s: Taxonomy term slug. */
3330 return new WP_Error( 'duplicate_term_slug', sprintf( __( 'The slug &#8220;%s&#8221; is already in use by another term.' ), $slug ) );
3331 }
3332 }
3333
3334 $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 ) );
3335
3336 // Check whether this is a shared term that needs splitting.
3337 $_term_id = _split_shared_term( $term_id, $tt_id );
3338 if ( ! is_wp_error( $_term_id ) ) {
3339 $term_id = $_term_id;
3340 }
3341
3342 /**
3343 * Fires immediately before the given terms are edited.
3344 *
3345 * @since 2.9.0
3346 * @since 6.1.0 The `$args` parameter was added.
3347 *
3348 * @param int $term_id Term ID.
3349 * @param string $taxonomy Taxonomy slug.
3350 * @param array $args Arguments passed to wp_update_term().
3351 */
3352 do_action( 'edit_terms', $term_id, $taxonomy, $args );
3353
3354 $data = compact( 'name', 'slug', 'term_group' );
3355
3356 /**
3357 * Filters term data before it is updated in the database.
3358 *
3359 * @since 4.7.0
3360 *
3361 * @param array $data Term data to be updated.
3362 * @param int $term_id Term ID.
3363 * @param string $taxonomy Taxonomy slug.
3364 * @param array $args Arguments passed to wp_update_term().
3365 */
3366 $data = apply_filters( 'wp_update_term_data', $data, $term_id, $taxonomy, $args );
3367
3368 $wpdb->update( $wpdb->terms, $data, compact( 'term_id' ) );
3369
3370 if ( empty( $slug ) ) {
3371 $slug = sanitize_title( $name, $term_id );
3372 $wpdb->update( $wpdb->terms, compact( 'slug' ), compact( 'term_id' ) );
3373 }
3374
3375 /**
3376 * Fires immediately after a term is updated in the database, but before its
3377 * term-taxonomy relationship is updated.
3378 *
3379 * @since 2.9.0
3380 * @since 6.1.0 The `$args` parameter was added.
3381 *
3382 * @param int $term_id Term ID.
3383 * @param string $taxonomy Taxonomy slug.
3384 * @param array $args Arguments passed to wp_update_term().
3385 */
3386 do_action( 'edited_terms', $term_id, $taxonomy, $args );
3387
3388 /**
3389 * Fires immediate before a term-taxonomy relationship is updated.
3390 *
3391 * @since 2.9.0
3392 * @since 6.1.0 The `$args` parameter was added.
3393 *
3394 * @param int $tt_id Term taxonomy ID.
3395 * @param string $taxonomy Taxonomy slug.
3396 * @param array $args Arguments passed to wp_update_term().
3397 */
3398 do_action( 'edit_term_taxonomy', $tt_id, $taxonomy, $args );
3399
3400 $wpdb->update( $wpdb->term_taxonomy, compact( 'term_id', 'taxonomy', 'description', 'parent' ), array( 'term_taxonomy_id' => $tt_id ) );
3401
3402 /**
3403 * Fires immediately after a term-taxonomy relationship is updated.
3404 *
3405 * @since 2.9.0
3406 * @since 6.1.0 The `$args` parameter was added.
3407 *
3408 * @param int $tt_id Term taxonomy ID.
3409 * @param string $taxonomy Taxonomy slug.
3410 * @param array $args Arguments passed to wp_update_term().
3411 */
3412 do_action( 'edited_term_taxonomy', $tt_id, $taxonomy, $args );
3413
3414 /**
3415 * Fires after a term has been updated, but before the term cache has been cleaned.
3416 *
3417 * The {@see 'edit_$taxonomy'} hook is also available for targeting a specific
3418 * taxonomy.
3419 *
3420 * @since 2.3.0
3421 * @since 6.1.0 The `$args` parameter was added.
3422 *
3423 * @param int $term_id Term ID.
3424 * @param int $tt_id Term taxonomy ID.
3425 * @param string $taxonomy Taxonomy slug.
3426 * @param array $args Arguments passed to wp_update_term().
3427 */
3428 do_action( 'edit_term', $term_id, $tt_id, $taxonomy, $args );
3429
3430 /**
3431 * Fires after a term in a specific taxonomy has been updated, but before the term
3432 * cache has been cleaned.
3433 *
3434 * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
3435 *
3436 * Possible hook names include:
3437 *
3438 * - `edit_category`
3439 * - `edit_post_tag`
3440 *
3441 * @since 2.3.0
3442 * @since 6.1.0 The `$args` parameter was added.
3443 *
3444 * @param int $term_id Term ID.
3445 * @param int $tt_id Term taxonomy ID.
3446 * @param array $args Arguments passed to wp_update_term().
3447 */
3448 do_action( "edit_{$taxonomy}", $term_id, $tt_id, $args );
3449
3450 /** This filter is documented in wp-includes/taxonomy.php */
3451 $term_id = apply_filters( 'term_id_filter', $term_id, $tt_id );
3452
3453 clean_term_cache( $term_id, $taxonomy );
3454
3455 /**
3456 * Fires after a term has been updated, and the term cache has been cleaned.
3457 *
3458 * The {@see 'edited_$taxonomy'} hook is also available for targeting a specific
3459 * taxonomy.
3460 *
3461 * @since 2.3.0
3462 * @since 6.1.0 The `$args` parameter was added.
3463 *
3464 * @param int $term_id Term ID.
3465 * @param int $tt_id Term taxonomy ID.
3466 * @param string $taxonomy Taxonomy slug.
3467 * @param array $args Arguments passed to wp_update_term().
3468 */
3469 do_action( 'edited_term', $term_id, $tt_id, $taxonomy, $args );
3470
3471 /**
3472 * Fires after a term for a specific taxonomy has been updated, and the term
3473 * cache has been cleaned.
3474 *
3475 * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
3476 *
3477 * Possible hook names include:
3478 *
3479 * - `edited_category`
3480 * - `edited_post_tag`
3481 *
3482 * @since 2.3.0
3483 * @since 6.1.0 The `$args` parameter was added.
3484 *
3485 * @param int $term_id Term ID.
3486 * @param int $tt_id Term taxonomy ID.
3487 * @param array $args Arguments passed to wp_update_term().
3488 */
3489 do_action( "edited_{$taxonomy}", $term_id, $tt_id, $args );
3490
3491 /** This action is documented in wp-includes/taxonomy.php */
3492 do_action( 'saved_term', $term_id, $tt_id, $taxonomy, true, $args );
3493
3494 /** This action is documented in wp-includes/taxonomy.php */
3495 do_action( "saved_{$taxonomy}", $term_id, $tt_id, true, $args );
3496
3497 return array(
3498 'term_id' => $term_id,
3499 'term_taxonomy_id' => $tt_id,
3500 );
3501}
3502
3503/**
3504 * Enables or disables term counting.
3505 *
3506 * @since 2.5.0
3507 *
3508 * @param bool $defer Optional. Enable if true, disable if false.
3509 * @return bool Whether term counting is enabled or disabled.
3510 */
3511function wp_defer_term_counting( $defer = null ) {
3512 static $_defer = false;
3513
3514 if ( is_bool( $defer ) ) {
3515 $_defer = $defer;
3516 // Flush any deferred counts.
3517 if ( ! $defer ) {
3518 wp_update_term_count( null, null, true );
3519 }
3520 }
3521
3522 return $_defer;
3523}
3524
3525/**
3526 * Updates the amount of terms in taxonomy.
3527 *
3528 * If there is a taxonomy callback applied, then it will be called for updating
3529 * the count.
3530 *
3531 * The default action is to count what the amount of terms have the relationship
3532 * of term ID. Once that is done, then update the database.
3533 *
3534 * @since 2.3.0
3535 *
3536 * @param int|array $terms The term_taxonomy_id of the terms.
3537 * @param string $taxonomy The context of the term.
3538 * @param bool $do_deferred Whether to flush the deferred term counts too. Default false.
3539 * @return bool If no terms will return false, and if successful will return true.
3540 */
3541function wp_update_term_count( $terms, $taxonomy, $do_deferred = false ) {
3542 static $_deferred = array();
3543
3544 if ( $do_deferred ) {
3545 foreach ( (array) array_keys( $_deferred ) as $tax ) {
3546 wp_update_term_count_now( $_deferred[ $tax ], $tax );
3547 unset( $_deferred[ $tax ] );
3548 }
3549 }
3550
3551 if ( empty( $terms ) ) {
3552 return false;
3553 }
3554
3555 if ( ! is_array( $terms ) ) {
3556 $terms = array( $terms );
3557 }
3558
3559 if ( wp_defer_term_counting() ) {
3560 if ( ! isset( $_deferred[ $taxonomy ] ) ) {
3561 $_deferred[ $taxonomy ] = array();
3562 }
3563 $_deferred[ $taxonomy ] = array_unique( array_merge( $_deferred[ $taxonomy ], $terms ) );
3564 return true;
3565 }
3566
3567 return wp_update_term_count_now( $terms, $taxonomy );
3568}
3569
3570/**
3571 * Performs term count update immediately.
3572 *
3573 * @since 2.5.0
3574 *
3575 * @param array $terms The term_taxonomy_id of terms to update.
3576 * @param string $taxonomy The context of the term.
3577 * @return true Always true when complete.
3578 */
3579function wp_update_term_count_now( $terms, $taxonomy ) {
3580 $terms = array_map( 'intval', $terms );
3581
3582 $taxonomy = get_taxonomy( $taxonomy );
3583 if ( ! empty( $taxonomy->update_count_callback ) ) {
3584 call_user_func( $taxonomy->update_count_callback, $terms, $taxonomy );
3585 } else {
3586 $object_types = (array) $taxonomy->object_type;
3587 foreach ( $object_types as &$object_type ) {
3588 if ( str_starts_with( $object_type, 'attachment:' ) ) {
3589 list( $object_type ) = explode( ':', $object_type );
3590 }
3591 }
3592
3593 if ( array_filter( $object_types, 'post_type_exists' ) == $object_types ) {
3594 // Only post types are attached to this taxonomy.
3595 _update_post_term_count( $terms, $taxonomy );
3596 } else {
3597 // Default count updater.
3598 _update_generic_term_count( $terms, $taxonomy );
3599 }
3600 }
3601
3602 clean_term_cache( $terms, '', false );
3603
3604 return true;
3605}
3606
3607//
3608// Cache.
3609//
3610
3611/**
3612 * Removes the taxonomy relationship to terms from the cache.
3613 *
3614 * Will remove the entire taxonomy relationship containing term `$object_id`. The
3615 * term IDs have to exist within the taxonomy `$object_type` for the deletion to
3616 * take place.
3617 *
3618 * @since 2.3.0
3619 *
3620 * @global bool $_wp_suspend_cache_invalidation
3621 *
3622 * @see get_object_taxonomies() for more on $object_type.
3623 *
3624 * @param int|array $object_ids Single or list of term object ID(s).
3625 * @param array|string $object_type The taxonomy object type.
3626 */
3627function clean_object_term_cache( $object_ids, $object_type ) {
3628 global $_wp_suspend_cache_invalidation;
3629
3630 if ( ! empty( $_wp_suspend_cache_invalidation ) ) {
3631 return;
3632 }
3633
3634 if ( ! is_array( $object_ids ) ) {
3635 $object_ids = array( $object_ids );
3636 }
3637
3638 $taxonomies = get_object_taxonomies( $object_type );
3639
3640 foreach ( $taxonomies as $taxonomy ) {
3641 wp_cache_delete_multiple( $object_ids, "{$taxonomy}_relationships" );
3642 }
3643
3644 wp_cache_set_terms_last_changed();
3645
3646 /**
3647 * Fires after the object term cache has been cleaned.
3648 *
3649 * @since 2.5.0
3650 *
3651 * @param array $object_ids An array of object IDs.
3652 * @param string $object_type Object type.
3653 */
3654 do_action( 'clean_object_term_cache', $object_ids, $object_type );
3655}
3656
3657/**
3658 * Removes all of the term IDs from the cache.
3659 *
3660 * @since 2.3.0
3661 *
3662 * @global wpdb $wpdb WordPress database abstraction object.
3663 * @global bool $_wp_suspend_cache_invalidation
3664 *
3665 * @param int|int[] $ids Single or array of term IDs.
3666 * @param string $taxonomy Optional. Taxonomy slug. Can be empty, in which case the taxonomies of the passed
3667 * term IDs will be used. Default empty.
3668 * @param bool $clean_taxonomy Optional. Whether to clean taxonomy wide caches (true), or just individual
3669 * term object caches (false). Default true.
3670 */
3671function clean_term_cache( $ids, $taxonomy = '', $clean_taxonomy = true ) {
3672 global $wpdb, $_wp_suspend_cache_invalidation;
3673
3674 if ( ! empty( $_wp_suspend_cache_invalidation ) ) {
3675 return;
3676 }
3677
3678 if ( ! is_array( $ids ) ) {
3679 $ids = array( $ids );
3680 }
3681
3682 $taxonomies = array();
3683 // If no taxonomy, assume tt_ids.
3684 if ( empty( $taxonomy ) ) {
3685 $tt_ids = array_map( 'intval', $ids );
3686 $tt_ids = implode( ', ', $tt_ids );
3687 $terms = $wpdb->get_results( "SELECT term_id, taxonomy FROM $wpdb->term_taxonomy WHERE term_taxonomy_id IN ($tt_ids)" );
3688 $ids = array();
3689
3690 foreach ( (array) $terms as $term ) {
3691 $taxonomies[] = $term->taxonomy;
3692 $ids[] = $term->term_id;
3693 }
3694 wp_cache_delete_multiple( $ids, 'terms' );
3695 $taxonomies = array_unique( $taxonomies );
3696 } else {
3697 wp_cache_delete_multiple( $ids, 'terms' );
3698 $taxonomies = array( $taxonomy );
3699 }
3700
3701 foreach ( $taxonomies as $taxonomy ) {
3702 if ( $clean_taxonomy ) {
3703 clean_taxonomy_cache( $taxonomy );
3704 }
3705
3706 /**
3707 * Fires once after each taxonomy's term cache has been cleaned.
3708 *
3709 * @since 2.5.0
3710 * @since 4.5.0 Added the `$clean_taxonomy` parameter.
3711 *
3712 * @param array $ids An array of term IDs.
3713 * @param string $taxonomy Taxonomy slug.
3714 * @param bool $clean_taxonomy Whether or not to clean taxonomy-wide caches
3715 */
3716 do_action( 'clean_term_cache', $ids, $taxonomy, $clean_taxonomy );
3717 }
3718
3719 wp_cache_set_terms_last_changed();
3720}
3721
3722/**
3723 * Cleans the caches for a taxonomy.
3724 *
3725 * @since 4.9.0
3726 *
3727 * @param string $taxonomy Taxonomy slug.
3728 */
3729function clean_taxonomy_cache( $taxonomy ) {
3730 wp_cache_delete( 'all_ids', $taxonomy );
3731 wp_cache_delete( 'get', $taxonomy );
3732 wp_cache_set_terms_last_changed();
3733
3734 // Regenerate cached hierarchy.
3735 if ( is_taxonomy_hierarchical( $taxonomy ) ) {
3736 delete_option( "{$taxonomy}_children" );
3737 _get_term_hierarchy( $taxonomy );
3738 }
3739
3740 /**
3741 * Fires after a taxonomy's caches have been cleaned.
3742 *
3743 * @since 4.9.0
3744 *
3745 * @param string $taxonomy Taxonomy slug.
3746 */
3747 do_action( 'clean_taxonomy_cache', $taxonomy );
3748}
3749
3750/**
3751 * Retrieves the cached term objects for the given object ID.
3752 *
3753 * Upstream functions (like get_the_terms() and is_object_in_term()) are
3754 * responsible for populating the object-term relationship cache. The current
3755 * function only fetches relationship data that is already in the cache.
3756 *
3757 * @since 2.3.0
3758 * @since 4.7.0 Returns a `WP_Error` object if there's an error with
3759 * any of the matched terms.
3760 *
3761 * @param int $id Term object ID, for example a post, comment, or user ID.
3762 * @param string $taxonomy Taxonomy name.
3763 * @return bool|WP_Term[]|WP_Error Array of `WP_Term` objects, if cached.
3764 * False if cache is empty for `$taxonomy` and `$id`.
3765 * WP_Error if get_term() returns an error object for any term.
3766 */
3767function get_object_term_cache( $id, $taxonomy ) {
3768 $_term_ids = wp_cache_get( $id, "{$taxonomy}_relationships" );
3769
3770 // We leave the priming of relationship caches to upstream functions.
3771 if ( false === $_term_ids ) {
3772 return false;
3773 }
3774
3775 // Backward compatibility for if a plugin is putting objects into the cache, rather than IDs.
3776 $term_ids = array();
3777 foreach ( $_term_ids as $term_id ) {
3778 if ( is_numeric( $term_id ) ) {
3779 $term_ids[] = (int) $term_id;
3780 } elseif ( isset( $term_id->term_id ) ) {
3781 $term_ids[] = (int) $term_id->term_id;
3782 }
3783 }
3784
3785 // Fill the term objects.
3786 _prime_term_caches( $term_ids );
3787
3788 $terms = array();
3789 foreach ( $term_ids as $term_id ) {
3790 $term = get_term( $term_id, $taxonomy );
3791 if ( is_wp_error( $term ) ) {
3792 return $term;
3793 }
3794
3795 $terms[] = $term;
3796 }
3797
3798 return $terms;
3799}
3800
3801/**
3802 * Updates the cache for the given term object ID(s).
3803 *
3804 * Note: Due to performance concerns, great care should be taken to only update
3805 * term caches when necessary. Processing time can increase exponentially depending
3806 * on both the number of passed term IDs and the number of taxonomies those terms
3807 * belong to.
3808 *
3809 * Caches will only be updated for terms not already cached.
3810 *
3811 * @since 2.3.0
3812 *
3813 * @param string|int[] $object_ids Comma-separated list or array of term object IDs.
3814 * @param string|string[] $object_type The taxonomy object type or array of the same.
3815 * @return void|false Void on success or if the `$object_ids` parameter is empty,
3816 * false if all of the terms in `$object_ids` are already cached.
3817 */
3818function update_object_term_cache( $object_ids, $object_type ) {
3819 if ( empty( $object_ids ) ) {
3820 return;
3821 }
3822
3823 if ( ! is_array( $object_ids ) ) {
3824 $object_ids = explode( ',', $object_ids );
3825 }
3826
3827 $object_ids = array_map( 'intval', $object_ids );
3828 $non_cached_ids = array();
3829
3830 $taxonomies = get_object_taxonomies( $object_type );
3831
3832 foreach ( $taxonomies as $taxonomy ) {
3833 $cache_values = wp_cache_get_multiple( (array) $object_ids, "{$taxonomy}_relationships" );
3834
3835 foreach ( $cache_values as $id => $value ) {
3836 if ( false === $value ) {
3837 $non_cached_ids[] = $id;
3838 }
3839 }
3840 }
3841
3842 if ( empty( $non_cached_ids ) ) {
3843 return false;
3844 }
3845
3846 $non_cached_ids = array_unique( $non_cached_ids );
3847
3848 $terms = wp_get_object_terms(
3849 $non_cached_ids,
3850 $taxonomies,
3851 array(
3852 'fields' => 'all_with_object_id',
3853 'orderby' => 'name',
3854 'update_term_meta_cache' => false,
3855 )
3856 );
3857
3858 $object_terms = array();
3859 foreach ( (array) $terms as $term ) {
3860 $object_terms[ $term->object_id ][ $term->taxonomy ][] = $term->term_id;
3861 }
3862
3863 foreach ( $non_cached_ids as $id ) {
3864 foreach ( $taxonomies as $taxonomy ) {
3865 if ( ! isset( $object_terms[ $id ][ $taxonomy ] ) ) {
3866 if ( ! isset( $object_terms[ $id ] ) ) {
3867 $object_terms[ $id ] = array();
3868 }
3869 $object_terms[ $id ][ $taxonomy ] = array();
3870 }
3871 }
3872 }
3873
3874 $cache_values = array();
3875 foreach ( $object_terms as $id => $value ) {
3876 foreach ( $value as $taxonomy => $terms ) {
3877 $cache_values[ $taxonomy ][ $id ] = $terms;
3878 }
3879 }
3880 foreach ( $cache_values as $taxonomy => $data ) {
3881 wp_cache_add_multiple( $data, "{$taxonomy}_relationships" );
3882 }
3883}
3884
3885/**
3886 * Updates terms in cache.
3887 *
3888 * @since 2.3.0
3889 *
3890 * @param WP_Term[] $terms Array of term objects to change.
3891 * @param string $taxonomy Not used.
3892 */
3893function update_term_cache( $terms, $taxonomy = '' ) {
3894 $data = array();
3895 foreach ( (array) $terms as $term ) {
3896 // Create a copy in case the array was passed by reference.
3897 $_term = clone $term;
3898
3899 // Object ID should not be cached.
3900 unset( $_term->object_id );
3901
3902 $data[ $term->term_id ] = $_term;
3903 }
3904 wp_cache_add_multiple( $data, 'terms' );
3905}
3906
3907//
3908// Private.
3909//
3910
3911/**
3912 * Retrieves children of taxonomy as term IDs.
3913 *
3914 * @access private
3915 * @since 2.3.0
3916 *
3917 * @param string $taxonomy Taxonomy name.
3918 * @return array Empty if $taxonomy isn't hierarchical or returns children as term IDs.
3919 */
3920function _get_term_hierarchy( $taxonomy ) {
3921 if ( ! is_taxonomy_hierarchical( $taxonomy ) ) {
3922 return array();
3923 }
3924 $children = get_option( "{$taxonomy}_children" );
3925
3926 if ( is_array( $children ) ) {
3927 return $children;
3928 }
3929 $children = array();
3930 $terms = get_terms(
3931 array(
3932 'taxonomy' => $taxonomy,
3933 'get' => 'all',
3934 'orderby' => 'id',
3935 'fields' => 'id=>parent',
3936 'update_term_meta_cache' => false,
3937 )
3938 );
3939 foreach ( $terms as $term_id => $parent ) {
3940 if ( $parent > 0 ) {
3941 $children[ $parent ][] = $term_id;
3942 }
3943 }
3944 update_option( "{$taxonomy}_children", $children );
3945
3946 return $children;
3947}
3948
3949/**
3950 * Gets the subset of $terms that are descendants of $term_id.
3951 *
3952 * If `$terms` is an array of objects, then _get_term_children() returns an array of objects.
3953 * If `$terms` is an array of IDs, then _get_term_children() returns an array of IDs.
3954 *
3955 * @access private
3956 * @since 2.3.0
3957 *
3958 * @param int $term_id The ancestor term: all returned terms should be descendants of `$term_id`.
3959 * @param array $terms The set of terms - either an array of term objects or term IDs - from which those that
3960 * are descendants of $term_id will be chosen.
3961 * @param string $taxonomy The taxonomy which determines the hierarchy of the terms.
3962 * @param array $ancestors Optional. Term ancestors that have already been identified. Passed by reference, to keep
3963 * track of found terms when recursing the hierarchy. The array of located ancestors is used
3964 * to prevent infinite recursion loops. For performance, `term_ids` are used as array keys,
3965 * with 1 as value. Default empty array.
3966 * @return array|WP_Error The subset of $terms that are descendants of $term_id.
3967 */
3968function _get_term_children( $term_id, $terms, $taxonomy, &$ancestors = array() ) {
3969 $empty_array = array();
3970 if ( empty( $terms ) ) {
3971 return $empty_array;
3972 }
3973
3974 $term_id = (int) $term_id;
3975 $term_list = array();
3976 $has_children = _get_term_hierarchy( $taxonomy );
3977
3978 if ( $term_id && ! isset( $has_children[ $term_id ] ) ) {
3979 return $empty_array;
3980 }
3981
3982 // Include the term itself in the ancestors array, so we can properly detect when a loop has occurred.
3983 if ( empty( $ancestors ) ) {
3984 $ancestors[ $term_id ] = 1;
3985 }
3986
3987 foreach ( (array) $terms as $term ) {
3988 $use_id = false;
3989 if ( ! is_object( $term ) ) {
3990 $term = get_term( $term, $taxonomy );
3991 if ( is_wp_error( $term ) ) {
3992 return $term;
3993 }
3994 $use_id = true;
3995 }
3996
3997 // Don't recurse if we've already identified the term as a child - this indicates a loop.
3998 if ( isset( $ancestors[ $term->term_id ] ) ) {
3999 continue;
4000 }
4001
4002 if ( (int) $term->parent === $term_id ) {
4003 if ( $use_id ) {
4004 $term_list[] = $term->term_id;
4005 } else {
4006 $term_list[] = $term;
4007 }
4008
4009 if ( ! isset( $has_children[ $term->term_id ] ) ) {
4010 continue;
4011 }
4012
4013 $ancestors[ $term->term_id ] = 1;
4014
4015 $children = _get_term_children( $term->term_id, $terms, $taxonomy, $ancestors );
4016 if ( $children ) {
4017 $term_list = array_merge( $term_list, $children );
4018 }
4019 }
4020 }
4021
4022 return $term_list;
4023}
4024
4025/**
4026 * Adds count of children to parent count.
4027 *
4028 * Recalculates term counts by including items from child terms. Assumes all
4029 * relevant children are already in the $terms argument.
4030 *
4031 * @access private
4032 * @since 2.3.0
4033 *
4034 * @global wpdb $wpdb WordPress database abstraction object.
4035 *
4036 * @param object[]|WP_Term[] $terms List of term objects (passed by reference).
4037 * @param string $taxonomy Term context.
4038 */
4039function _pad_term_counts( &$terms, $taxonomy ) {
4040 global $wpdb;
4041
4042 // This function only works for hierarchical taxonomies like post categories.
4043 if ( ! is_taxonomy_hierarchical( $taxonomy ) ) {
4044 return;
4045 }
4046
4047 $term_hier = _get_term_hierarchy( $taxonomy );
4048
4049 if ( empty( $term_hier ) ) {
4050 return;
4051 }
4052
4053 $term_items = array();
4054 $terms_by_id = array();
4055 $term_ids = array();
4056
4057 foreach ( (array) $terms as $key => $term ) {
4058 $terms_by_id[ $term->term_id ] = & $terms[ $key ];
4059 $term_ids[ $term->term_taxonomy_id ] = $term->term_id;
4060 }
4061
4062 // Get the object and term IDs and stick them in a lookup table.
4063 $tax_obj = get_taxonomy( $taxonomy );
4064 $object_types = esc_sql( $tax_obj->object_type );
4065 $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'" );
4066
4067 foreach ( $results as $row ) {
4068 $id = $term_ids[ $row->term_taxonomy_id ];
4069
4070 $term_items[ $id ][ $row->object_id ] = isset( $term_items[ $id ][ $row->object_id ] ) ? ++$term_items[ $id ][ $row->object_id ] : 1;
4071 }
4072
4073 // Touch every ancestor's lookup row for each post in each term.
4074 foreach ( $term_ids as $term_id ) {
4075 $child = $term_id;
4076 $ancestors = array();
4077 while ( ! empty( $terms_by_id[ $child ] ) && $parent = $terms_by_id[ $child ]->parent ) {
4078 $ancestors[] = $child;
4079
4080 if ( ! empty( $term_items[ $term_id ] ) ) {
4081 foreach ( $term_items[ $term_id ] as $item_id => $touches ) {
4082 $term_items[ $parent ][ $item_id ] = isset( $term_items[ $parent ][ $item_id ] ) ? ++$term_items[ $parent ][ $item_id ] : 1;
4083 }
4084 }
4085
4086 $child = $parent;
4087
4088 if ( in_array( $parent, $ancestors, true ) ) {
4089 break;
4090 }
4091 }
4092 }
4093
4094 // Transfer the touched cells.
4095 foreach ( (array) $term_items as $id => $items ) {
4096 if ( isset( $terms_by_id[ $id ] ) ) {
4097 $terms_by_id[ $id ]->count = count( $items );
4098 }
4099 }
4100}
4101
4102/**
4103 * Adds any terms from the given IDs to the cache that do not already exist in cache.
4104 *
4105 * @since 4.6.0
4106 * @since 6.1.0 This function is no longer marked as "private".
4107 * @since 6.3.0 Use wp_lazyload_term_meta() for lazy-loading of term meta.
4108 *
4109 * @global wpdb $wpdb WordPress database abstraction object.
4110 *
4111 * @param array $term_ids Array of term IDs.
4112 * @param bool $update_meta_cache Optional. Whether to update the meta cache. Default true.
4113 */
4114function _prime_term_caches( $term_ids, $update_meta_cache = true ) {
4115 global $wpdb;
4116
4117 $non_cached_ids = _get_non_cached_ids( $term_ids, 'terms' );
4118 if ( ! empty( $non_cached_ids ) ) {
4119 $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)", implode( ',', array_map( 'intval', $non_cached_ids ) ) ) );
4120 $fresh_terms = apply_filters('godaddy/prime_term_caches/terms', $fresh_terms, $non_cached_ids);
4121
4122 update_term_cache( $fresh_terms );
4123 }
4124
4125 if ( $update_meta_cache ) {
4126 wp_lazyload_term_meta( $term_ids );
4127 }
4128}
4129
4130//
4131// Default callbacks.
4132//
4133
4134/**
4135 * Updates term count based on object types of the current taxonomy.
4136 *
4137 * Private function for the default callback for post_tag and category
4138 * taxonomies.
4139 *
4140 * @access private
4141 * @since 2.3.0
4142 *
4143 * @global wpdb $wpdb WordPress database abstraction object.
4144 *
4145 * @param int[] $terms List of term taxonomy IDs.
4146 * @param WP_Taxonomy $taxonomy Current taxonomy object of terms.
4147 */
4148function _update_post_term_count( $terms, $taxonomy ) {
4149 global $wpdb;
4150
4151 $object_types = (array) $taxonomy->object_type;
4152
4153 foreach ( $object_types as &$object_type ) {
4154 list( $object_type ) = explode( ':', $object_type );
4155 }
4156
4157 $object_types = array_unique( $object_types );
4158
4159 $check_attachments = array_search( 'attachment', $object_types, true );
4160 if ( false !== $check_attachments ) {
4161 unset( $object_types[ $check_attachments ] );
4162 $check_attachments = true;
4163 }
4164
4165 if ( $object_types ) {
4166 $object_types = esc_sql( array_filter( $object_types, 'post_type_exists' ) );
4167 }
4168
4169 $post_statuses = array( 'publish' );
4170
4171 /**
4172 * Filters the post statuses for updating the term count.
4173 *
4174 * @since 5.7.0
4175 *
4176 * @param string[] $post_statuses List of post statuses to include in the count. Default is 'publish'.
4177 * @param WP_Taxonomy $taxonomy Current taxonomy object.
4178 */
4179 $post_statuses = esc_sql( apply_filters( 'update_post_term_count_statuses', $post_statuses, $taxonomy ) );
4180
4181 foreach ( (array) $terms as $tt_id ) {
4182 $count = 0;
4183
4184 // Attachments can be 'inherit' status, we need to base count off the parent's status if so.
4185 if ( $check_attachments ) {
4186 // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.QuotedDynamicPlaceholderGeneration
4187 $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 IN ('" . implode( "', '", $post_statuses ) . "') OR ( post_status = 'inherit' AND post_parent > 0 AND ( SELECT post_status FROM $wpdb->posts WHERE ID = p1.post_parent ) IN ('" . implode( "', '", $post_statuses ) . "') ) ) AND post_type = 'attachment' AND term_taxonomy_id = %d", $tt_id ) );
4188 }
4189
4190 if ( $object_types ) {
4191 // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.QuotedDynamicPlaceholderGeneration
4192 $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 IN ('" . implode( "', '", $post_statuses ) . "') AND post_type IN ('" . implode( "', '", $object_types ) . "') AND term_taxonomy_id = %d", $tt_id ) );
4193 }
4194
4195 /**
4196 * Fires when a term count is calculated, before it is updated in the database.
4197 *
4198 * @since 6.9.0
4199 *
4200 * @param int $tt_id Term taxonomy ID.
4201 * @param string $taxonomy_name Taxonomy slug.
4202 * @param int $count Term count.
4203 */
4204 do_action( 'update_term_count', $tt_id, $taxonomy->name, $count );
4205
4206 /** This action is documented in wp-includes/taxonomy.php */
4207 do_action( 'edit_term_taxonomy', $tt_id, $taxonomy->name );
4208 $wpdb->update( $wpdb->term_taxonomy, compact( 'count' ), array( 'term_taxonomy_id' => $tt_id ) );
4209
4210 /** This action is documented in wp-includes/taxonomy.php */
4211 do_action( 'edited_term_taxonomy', $tt_id, $taxonomy->name );
4212 }
4213}
4214
4215/**
4216 * Updates term count based on number of objects.
4217 *
4218 * Default callback for the 'link_category' taxonomy.
4219 *
4220 * @since 3.3.0
4221 *
4222 * @global wpdb $wpdb WordPress database abstraction object.
4223 *
4224 * @param int[] $terms List of term taxonomy IDs.
4225 * @param WP_Taxonomy $taxonomy Current taxonomy object of terms.
4226 */
4227function _update_generic_term_count( $terms, $taxonomy ) {
4228 global $wpdb;
4229
4230 foreach ( (array) $terms as $term ) {
4231 $count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_relationships WHERE term_taxonomy_id = %d", $term ) );
4232
4233 /** This action is documented in wp-includes/taxonomy.php */
4234 do_action( 'update_term_count', $term, $taxonomy->name, $count );
4235
4236 /** This action is documented in wp-includes/taxonomy.php */
4237 do_action( 'edit_term_taxonomy', $term, $taxonomy->name );
4238 $wpdb->update( $wpdb->term_taxonomy, compact( 'count' ), array( 'term_taxonomy_id' => $term ) );
4239
4240 /** This action is documented in wp-includes/taxonomy.php */
4241 do_action( 'edited_term_taxonomy', $term, $taxonomy->name );
4242 }
4243}
4244
4245/**
4246 * Creates a new term for a term_taxonomy item that currently shares its term
4247 * with another term_taxonomy.
4248 *
4249 * @ignore
4250 * @since 4.2.0
4251 * @since 4.3.0 Introduced `$record` parameter. Also, `$term_id` and
4252 * `$term_taxonomy_id` can now accept objects.
4253 *
4254 * @global wpdb $wpdb WordPress database abstraction object.
4255 *
4256 * @param int|object $term_id ID of the shared term, or the shared term object.
4257 * @param int|object $term_taxonomy_id ID of the term_taxonomy item to receive a new term, or the term_taxonomy object
4258 * (corresponding to a row from the term_taxonomy table).
4259 * @param bool $record Whether to record data about the split term in the options table. The recording
4260 * process has the potential to be resource-intensive, so during batch operations
4261 * it can be beneficial to skip inline recording and do it just once, after the
4262 * batch is processed. Only set this to `false` if you know what you are doing.
4263 * Default: true.
4264 * @return int|WP_Error When the current term does not need to be split (or cannot be split on the current
4265 * database schema), `$term_id` is returned. When the term is successfully split, the
4266 * new term_id is returned. A WP_Error is returned for miscellaneous errors.
4267 */
4268function _split_shared_term( $term_id, $term_taxonomy_id, $record = true ) {
4269 global $wpdb;
4270
4271 if ( is_object( $term_id ) ) {
4272 $shared_term = $term_id;
4273 $term_id = (int) $shared_term->term_id;
4274 }
4275
4276 if ( is_object( $term_taxonomy_id ) ) {
4277 $term_taxonomy = $term_taxonomy_id;
4278 $term_taxonomy_id = (int) $term_taxonomy->term_taxonomy_id;
4279 }
4280
4281 // If there are no shared term_taxonomy rows, there's nothing to do here.
4282 $shared_tt_count = (int) $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 ) );
4283
4284 if ( ! $shared_tt_count ) {
4285 return $term_id;
4286 }
4287
4288 /*
4289 * Verify that the term_taxonomy_id passed to the function is actually associated with the term_id.
4290 * If there's a mismatch, it may mean that the term is already split. Return the actual term_id from the db.
4291 */
4292 $check_term_id = (int) $wpdb->get_var( $wpdb->prepare( "SELECT term_id FROM $wpdb->term_taxonomy WHERE term_taxonomy_id = %d", $term_taxonomy_id ) );
4293 if ( $check_term_id !== $term_id ) {
4294 return $check_term_id;
4295 }
4296
4297 // Pull up data about the currently shared slug, which we'll use to populate the new one.
4298 if ( empty( $shared_term ) ) {
4299 $shared_term = $wpdb->get_row( $wpdb->prepare( "SELECT t.* FROM $wpdb->terms t WHERE t.term_id = %d", $term_id ) );
4300 }
4301
4302 $new_term_data = array(
4303 'name' => $shared_term->name,
4304 'slug' => $shared_term->slug,
4305 'term_group' => $shared_term->term_group,
4306 );
4307
4308 if ( false === $wpdb->insert( $wpdb->terms, $new_term_data ) ) {
4309 return new WP_Error( 'db_insert_error', __( 'Could not split shared term.' ), $wpdb->last_error );
4310 }
4311
4312 $new_term_id = (int) $wpdb->insert_id;
4313
4314 // Update the existing term_taxonomy to point to the newly created term.
4315 $wpdb->update(
4316 $wpdb->term_taxonomy,
4317 array( 'term_id' => $new_term_id ),
4318 array( 'term_taxonomy_id' => $term_taxonomy_id )
4319 );
4320
4321 // Reassign child terms to the new parent.
4322 if ( empty( $term_taxonomy ) ) {
4323 $term_taxonomy = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->term_taxonomy WHERE term_taxonomy_id = %d", $term_taxonomy_id ) );
4324 }
4325
4326 $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 ) );
4327 if ( ! empty( $children_tt_ids ) ) {
4328 foreach ( $children_tt_ids as $child_tt_id ) {
4329 $wpdb->update(
4330 $wpdb->term_taxonomy,
4331 array( 'parent' => $new_term_id ),
4332 array( 'term_taxonomy_id' => $child_tt_id )
4333 );
4334 clean_term_cache( (int) $child_tt_id, '', false );
4335 }
4336 } else {
4337 // If the term has no children, we must force its taxonomy cache to be rebuilt separately.
4338 clean_term_cache( $new_term_id, $term_taxonomy->taxonomy, false );
4339 }
4340
4341 clean_term_cache( $term_id, $term_taxonomy->taxonomy, false );
4342
4343 /*
4344 * Taxonomy cache clearing is delayed to avoid race conditions that may occur when
4345 * regenerating the taxonomy's hierarchy tree.
4346 */
4347 $taxonomies_to_clean = array( $term_taxonomy->taxonomy );
4348
4349 // Clean the cache for term taxonomies formerly shared with the current term.
4350 $shared_term_taxonomies = $wpdb->get_col( $wpdb->prepare( "SELECT taxonomy FROM $wpdb->term_taxonomy WHERE term_id = %d", $term_id ) );
4351 $taxonomies_to_clean = array_merge( $taxonomies_to_clean, $shared_term_taxonomies );
4352
4353 foreach ( $taxonomies_to_clean as $taxonomy_to_clean ) {
4354 clean_taxonomy_cache( $taxonomy_to_clean );
4355 }
4356
4357 // Keep a record of term_ids that have been split, keyed by old term_id. See wp_get_split_term().
4358 if ( $record ) {
4359 $split_term_data = get_option( '_split_terms', array() );
4360 if ( ! isset( $split_term_data[ $term_id ] ) ) {
4361 $split_term_data[ $term_id ] = array();
4362 }
4363
4364 $split_term_data[ $term_id ][ $term_taxonomy->taxonomy ] = $new_term_id;
4365 update_option( '_split_terms', $split_term_data );
4366 }
4367
4368 // If we've just split the final shared term, set the "finished" flag.
4369 $shared_terms_exist = $wpdb->get_results(
4370 "SELECT tt.term_id, t.*, count(*) as term_tt_count FROM {$wpdb->term_taxonomy} tt
4371 LEFT JOIN {$wpdb->terms} t ON t.term_id = tt.term_id
4372 GROUP BY t.term_id
4373 HAVING term_tt_count > 1
4374 LIMIT 1"
4375 );
4376 if ( ! $shared_terms_exist ) {
4377 update_option( 'finished_splitting_shared_terms', true );
4378 }
4379
4380 /**
4381 * Fires after a previously shared taxonomy term is split into two separate terms.
4382 *
4383 * @since 4.2.0
4384 *
4385 * @param int $term_id ID of the formerly shared term.
4386 * @param int $new_term_id ID of the new term created for the $term_taxonomy_id.
4387 * @param int $term_taxonomy_id ID for the term_taxonomy row affected by the split.
4388 * @param string $taxonomy Taxonomy for the split term.
4389 */
4390 do_action( 'split_shared_term', $term_id, $new_term_id, $term_taxonomy_id, $term_taxonomy->taxonomy );
4391
4392 return $new_term_id;
4393}
4394
4395/**
4396 * Splits a batch of shared taxonomy terms.
4397 *
4398 * @since 4.3.0
4399 *
4400 * @global wpdb $wpdb WordPress database abstraction object.
4401 */
4402function _wp_batch_split_terms() {
4403 global $wpdb;
4404
4405 $lock_name = 'term_split.lock';
4406
4407 // Try to lock.
4408 $lock_result = $wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO `$wpdb->options` ( `option_name`, `option_value`, `autoload` ) VALUES (%s, %s, 'off') /* LOCK */", $lock_name, time() ) );
4409
4410 if ( ! $lock_result ) {
4411 $lock_result = get_option( $lock_name );
4412
4413 // Bail if we were unable to create a lock, or if the existing lock is still valid.
4414 if ( ! $lock_result || ( $lock_result > ( time() - HOUR_IN_SECONDS ) ) ) {
4415 wp_schedule_single_event( time() + ( 5 * MINUTE_IN_SECONDS ), 'wp_split_shared_term_batch' );
4416 return;
4417 }
4418 }
4419
4420 // Update the lock, as by this point we've definitely got a lock, just need to fire the actions.
4421 update_option( $lock_name, time() );
4422
4423 // Get a list of shared terms (those with more than one associated row in term_taxonomy).
4424 $shared_terms = $wpdb->get_results(
4425 "SELECT tt.term_id, t.*, count(*) as term_tt_count FROM {$wpdb->term_taxonomy} tt
4426 LEFT JOIN {$wpdb->terms} t ON t.term_id = tt.term_id
4427 GROUP BY t.term_id
4428 HAVING term_tt_count > 1
4429 LIMIT 10"
4430 );
4431
4432 // No more terms, we're done here.
4433 if ( ! $shared_terms ) {
4434 update_option( 'finished_splitting_shared_terms', true );
4435 delete_option( $lock_name );
4436 return;
4437 }
4438
4439 // Shared terms found? We'll need to run this script again.
4440 wp_schedule_single_event( time() + ( 2 * MINUTE_IN_SECONDS ), 'wp_split_shared_term_batch' );
4441
4442 // Rekey shared term array for faster lookups.
4443 $_shared_terms = array();
4444 foreach ( $shared_terms as $shared_term ) {
4445 $term_id = (int) $shared_term->term_id;
4446 $_shared_terms[ $term_id ] = $shared_term;
4447 }
4448 $shared_terms = $_shared_terms;
4449
4450 // Get term taxonomy data for all shared terms.
4451 $shared_term_ids = implode( ',', array_keys( $shared_terms ) );
4452 $shared_tts = $wpdb->get_results( "SELECT * FROM {$wpdb->term_taxonomy} WHERE `term_id` IN ({$shared_term_ids})" );
4453
4454 // Split term data recording is slow, so we do it just once, outside the loop.
4455 $split_term_data = get_option( '_split_terms', array() );
4456 $skipped_first_term = array();
4457 $taxonomies = array();
4458 foreach ( $shared_tts as $shared_tt ) {
4459 $term_id = (int) $shared_tt->term_id;
4460
4461 // Don't split the first tt belonging to a given term_id.
4462 if ( ! isset( $skipped_first_term[ $term_id ] ) ) {
4463 $skipped_first_term[ $term_id ] = 1;
4464 continue;
4465 }
4466
4467 if ( ! isset( $split_term_data[ $term_id ] ) ) {
4468 $split_term_data[ $term_id ] = array();
4469 }
4470
4471 // Keep track of taxonomies whose hierarchies need flushing.
4472 if ( ! isset( $taxonomies[ $shared_tt->taxonomy ] ) ) {
4473 $taxonomies[ $shared_tt->taxonomy ] = 1;
4474 }
4475
4476 // Split the term.
4477 $split_term_data[ $term_id ][ $shared_tt->taxonomy ] = _split_shared_term( $shared_terms[ $term_id ], $shared_tt, false );
4478 }
4479
4480 // Rebuild the cached hierarchy for each affected taxonomy.
4481 foreach ( array_keys( $taxonomies ) as $tax ) {
4482 delete_option( "{$tax}_children" );
4483 _get_term_hierarchy( $tax );
4484 }
4485
4486 update_option( '_split_terms', $split_term_data );
4487
4488 delete_option( $lock_name );
4489}
4490
4491/**
4492 * In order to avoid the _wp_batch_split_terms() job being accidentally removed,
4493 * checks that it's still scheduled while we haven't finished splitting terms.
4494 *
4495 * @ignore
4496 * @since 4.3.0
4497 */
4498function _wp_check_for_scheduled_split_terms() {
4499 if ( ! get_option( 'finished_splitting_shared_terms' ) && ! wp_next_scheduled( 'wp_split_shared_term_batch' ) ) {
4500 wp_schedule_single_event( time() + MINUTE_IN_SECONDS, 'wp_split_shared_term_batch' );
4501 }
4502}
4503
4504/**
4505 * Checks default categories when a term gets split to see if any of them need to be updated.
4506 *
4507 * @ignore
4508 * @since 4.2.0
4509 *
4510 * @param int $term_id ID of the formerly shared term.
4511 * @param int $new_term_id ID of the new term created for the $term_taxonomy_id.
4512 * @param int $term_taxonomy_id ID for the term_taxonomy row affected by the split.
4513 * @param string $taxonomy Taxonomy for the split term.
4514 */
4515function _wp_check_split_default_terms( $term_id, $new_term_id, $term_taxonomy_id, $taxonomy ) {
4516 if ( 'category' !== $taxonomy ) {
4517 return;
4518 }
4519
4520 foreach ( array( 'default_category', 'default_link_category', 'default_email_category' ) as $option ) {
4521 if ( (int) get_option( $option, -1 ) === $term_id ) {
4522 update_option( $option, $new_term_id );
4523 }
4524 }
4525}
4526
4527/**
4528 * Checks menu items when a term gets split to see if any of them need to be updated.
4529 *
4530 * @ignore
4531 * @since 4.2.0
4532 *
4533 * @global wpdb $wpdb WordPress database abstraction object.
4534 *
4535 * @param int $term_id ID of the formerly shared term.
4536 * @param int $new_term_id ID of the new term created for the $term_taxonomy_id.
4537 * @param int $term_taxonomy_id ID for the term_taxonomy row affected by the split.
4538 * @param string $taxonomy Taxonomy for the split term.
4539 */
4540function _wp_check_split_terms_in_menus( $term_id, $new_term_id, $term_taxonomy_id, $taxonomy ) {
4541 global $wpdb;
4542 $post_ids = $wpdb->get_col(
4543 $wpdb->prepare(
4544 "SELECT m1.post_id
4545 FROM {$wpdb->postmeta} AS m1
4546 INNER JOIN {$wpdb->postmeta} AS m2 ON ( m2.post_id = m1.post_id )
4547 INNER JOIN {$wpdb->postmeta} AS m3 ON ( m3.post_id = m1.post_id )
4548 WHERE ( m1.meta_key = '_menu_item_type' AND m1.meta_value = 'taxonomy' )
4549 AND ( m2.meta_key = '_menu_item_object' AND m2.meta_value = %s )
4550 AND ( m3.meta_key = '_menu_item_object_id' AND m3.meta_value = %d )",
4551 $taxonomy,
4552 $term_id
4553 )
4554 );
4555
4556 if ( $post_ids ) {
4557 foreach ( $post_ids as $post_id ) {
4558 update_post_meta( $post_id, '_menu_item_object_id', $new_term_id, $term_id );
4559 }
4560 }
4561}
4562
4563/**
4564 * If the term being split is a nav_menu, changes associations.
4565 *
4566 * @ignore
4567 * @since 4.3.0
4568 *
4569 * @param int $term_id ID of the formerly shared term.
4570 * @param int $new_term_id ID of the new term created for the $term_taxonomy_id.
4571 * @param int $term_taxonomy_id ID for the term_taxonomy row affected by the split.
4572 * @param string $taxonomy Taxonomy for the split term.
4573 */
4574function _wp_check_split_nav_menu_terms( $term_id, $new_term_id, $term_taxonomy_id, $taxonomy ) {
4575 if ( 'nav_menu' !== $taxonomy ) {
4576 return;
4577 }
4578
4579 // Update menu locations.
4580 $locations = get_nav_menu_locations();
4581 foreach ( $locations as $location => $menu_id ) {
4582 if ( $term_id === $menu_id ) {
4583 $locations[ $location ] = $new_term_id;
4584 }
4585 }
4586 set_theme_mod( 'nav_menu_locations', $locations );
4587}
4588
4589/**
4590 * Gets data about terms that previously shared a single term_id, but have since been split.
4591 *
4592 * @since 4.2.0
4593 *
4594 * @param int $old_term_id Term ID. This is the old, pre-split term ID.
4595 * @return array Array of new term IDs, keyed by taxonomy.
4596 */
4597function wp_get_split_terms( $old_term_id ) {
4598 $split_terms = get_option( '_split_terms', array() );
4599
4600 $terms = array();
4601 if ( isset( $split_terms[ $old_term_id ] ) ) {
4602 $terms = $split_terms[ $old_term_id ];
4603 }
4604
4605 return $terms;
4606}
4607
4608/**
4609 * Gets the new term ID corresponding to a previously split term.
4610 *
4611 * @since 4.2.0
4612 *
4613 * @param int $old_term_id Term ID. This is the old, pre-split term ID.
4614 * @param string $taxonomy Taxonomy that the term belongs to.
4615 * @return int|false If a previously split term is found corresponding to the old term_id and taxonomy,
4616 * the new term_id will be returned. If no previously split term is found matching
4617 * the parameters, returns false.
4618 */
4619function wp_get_split_term( $old_term_id, $taxonomy ) {
4620 $split_terms = wp_get_split_terms( $old_term_id );
4621
4622 $term_id = false;
4623 if ( isset( $split_terms[ $taxonomy ] ) ) {
4624 $term_id = (int) $split_terms[ $taxonomy ];
4625 }
4626
4627 return $term_id;
4628}
4629
4630/**
4631 * Determines whether a term is shared between multiple taxonomies.
4632 *
4633 * Shared taxonomy terms began to be split in 4.3, but failed cron tasks or
4634 * other delays in upgrade routines may cause shared terms to remain.
4635 *
4636 * @since 4.4.0
4637 *
4638 * @global wpdb $wpdb WordPress database abstraction object.
4639 *
4640 * @param int $term_id Term ID.
4641 * @return bool Returns false if a term is not shared between multiple taxonomies or
4642 * if splitting shared taxonomy terms is finished.
4643 */
4644function wp_term_is_shared( $term_id ) {
4645 global $wpdb;
4646
4647 if ( get_option( 'finished_splitting_shared_terms' ) ) {
4648 return false;
4649 }
4650
4651 $tt_count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_taxonomy WHERE term_id = %d", $term_id ) );
4652
4653 return $tt_count > 1;
4654}
4655
4656/**
4657 * Generates a permalink for a taxonomy term archive.
4658 *
4659 * @since 2.5.0
4660 *
4661 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
4662 *
4663 * @param WP_Term|int|string $term The term object, ID, or slug whose link will be retrieved.
4664 * @param string $taxonomy Optional. Taxonomy. Default empty.
4665 * @return string|WP_Error URL of the taxonomy term archive on success, WP_Error if term does not exist.
4666 */
4667function get_term_link( $term, $taxonomy = '' ) {
4668 global $wp_rewrite;
4669
4670 if ( ! is_object( $term ) ) {
4671 if ( is_int( $term ) ) {
4672 $term = get_term( $term, $taxonomy );
4673 } else {
4674 $term = get_term_by( 'slug', $term, $taxonomy );
4675 }
4676 }
4677
4678 if ( ! is_object( $term ) ) {
4679 $term = new WP_Error( 'invalid_term', __( 'Empty Term.' ) );
4680 }
4681
4682 if ( is_wp_error( $term ) ) {
4683 return $term;
4684 }
4685
4686 $taxonomy = $term->taxonomy;
4687
4688 $termlink = $wp_rewrite->get_extra_permastruct( $taxonomy );
4689
4690 /**
4691 * Filters the permalink structure for a term before token replacement occurs.
4692 *
4693 * @since 4.9.0
4694 *
4695 * @param string $termlink The permalink structure for the term's taxonomy.
4696 * @param WP_Term $term The term object.
4697 */
4698 $termlink = apply_filters( 'pre_term_link', $termlink, $term );
4699
4700 $slug = $term->slug;
4701 $t = get_taxonomy( $taxonomy );
4702
4703 if ( empty( $termlink ) ) {
4704 if ( 'category' === $taxonomy ) {
4705 $termlink = '?cat=' . $term->term_id;
4706 } elseif ( $t->query_var ) {
4707 $termlink = "?$t->query_var=$slug";
4708 } else {
4709 $termlink = "?taxonomy=$taxonomy&term=$slug";
4710 }
4711 $termlink = home_url( $termlink );
4712 } else {
4713 if ( ! empty( $t->rewrite['hierarchical'] ) ) {
4714 $hierarchical_slugs = array();
4715 $ancestors = get_ancestors( $term->term_id, $taxonomy, 'taxonomy' );
4716 foreach ( (array) $ancestors as $ancestor ) {
4717 $ancestor_term = get_term( $ancestor, $taxonomy );
4718 $hierarchical_slugs[] = $ancestor_term->slug;
4719 }
4720 $hierarchical_slugs = array_reverse( $hierarchical_slugs );
4721 $hierarchical_slugs[] = $slug;
4722 $termlink = str_replace( "%$taxonomy%", implode( '/', $hierarchical_slugs ), $termlink );
4723 } else {
4724 $termlink = str_replace( "%$taxonomy%", $slug, $termlink );
4725 }
4726 $termlink = home_url( user_trailingslashit( $termlink, 'category' ) );
4727 }
4728
4729 // Back compat filters.
4730 if ( 'post_tag' === $taxonomy ) {
4731
4732 /**
4733 * Filters the tag link.
4734 *
4735 * @since 2.3.0
4736 * @since 2.5.0 Deprecated in favor of {@see 'term_link'} filter.
4737 * @since 5.4.1 Restored (un-deprecated).
4738 *
4739 * @param string $termlink Tag link URL.
4740 * @param int $term_id Term ID.
4741 */
4742 $termlink = apply_filters( 'tag_link', $termlink, $term->term_id );
4743 } elseif ( 'category' === $taxonomy ) {
4744
4745 /**
4746 * Filters the category link.
4747 *
4748 * @since 1.5.0
4749 * @since 2.5.0 Deprecated in favor of {@see 'term_link'} filter.
4750 * @since 5.4.1 Restored (un-deprecated).
4751 *
4752 * @param string $termlink Category link URL.
4753 * @param int $term_id Term ID.
4754 */
4755 $termlink = apply_filters( 'category_link', $termlink, $term->term_id );
4756 }
4757
4758 /**
4759 * Filters the term link.
4760 *
4761 * @since 2.5.0
4762 *
4763 * @param string $termlink Term link URL.
4764 * @param WP_Term $term Term object.
4765 * @param string $taxonomy Taxonomy slug.
4766 */
4767 return apply_filters( 'term_link', $termlink, $term, $taxonomy );
4768}
4769
4770/**
4771 * Displays the taxonomies of a post with available options.
4772 *
4773 * This function can be used within the loop to display the taxonomies for a
4774 * post without specifying the Post ID. You can also use it outside the Loop to
4775 * display the taxonomies for a specific post.
4776 *
4777 * @since 2.5.0
4778 *
4779 * @param array $args {
4780 * Arguments about which post to use and how to format the output. Shares all of the arguments
4781 * supported by get_the_taxonomies(), in addition to the following.
4782 *
4783 * @type int|WP_Post $post Post ID or object to get taxonomies of. Default current post.
4784 * @type string $before Displays before the taxonomies. Default empty string.
4785 * @type string $sep Separates each taxonomy. Default is a space.
4786 * @type string $after Displays after the taxonomies. Default empty string.
4787 * }
4788 */
4789function the_taxonomies( $args = array() ) {
4790 $defaults = array(
4791 'post' => 0,
4792 'before' => '',
4793 'sep' => ' ',
4794 'after' => '',
4795 );
4796
4797 $parsed_args = wp_parse_args( $args, $defaults );
4798
4799 echo $parsed_args['before'] . implode( $parsed_args['sep'], get_the_taxonomies( $parsed_args['post'], $parsed_args ) ) . $parsed_args['after'];
4800}
4801
4802/**
4803 * Retrieves all taxonomies associated with a post.
4804 *
4805 * This function can be used within the loop. It will also return an array of
4806 * the taxonomies with links to the taxonomy and name.
4807 *
4808 * @since 2.5.0
4809 *
4810 * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global $post.
4811 * @param array $args {
4812 * Optional. Arguments about how to format the list of taxonomies. Default empty array.
4813 *
4814 * @type string $template Template for displaying a taxonomy label and list of terms.
4815 * Default is "Label: Terms."
4816 * @type string $term_template Template for displaying a single term in the list. Default is the term name
4817 * linked to its archive.
4818 * }
4819 * @return string[] List of taxonomies.
4820 */
4821function get_the_taxonomies( $post = 0, $args = array() ) {
4822 $post = get_post( $post );
4823
4824 $args = wp_parse_args(
4825 $args,
4826 array(
4827 /* translators: %s: Taxonomy label, %l: List of terms formatted as per $term_template. */
4828 'template' => __( '%s: %l.' ),
4829 'term_template' => '<a href="%1$s">%2$s</a>',
4830 )
4831 );
4832
4833 $taxonomies = array();
4834
4835 if ( ! $post ) {
4836 return $taxonomies;
4837 }
4838
4839 foreach ( get_object_taxonomies( $post ) as $taxonomy ) {
4840 $t = (array) get_taxonomy( $taxonomy );
4841 if ( empty( $t['label'] ) ) {
4842 $t['label'] = $taxonomy;
4843 }
4844 if ( empty( $t['args'] ) ) {
4845 $t['args'] = array();
4846 }
4847 if ( empty( $t['template'] ) ) {
4848 $t['template'] = $args['template'];
4849 }
4850 if ( empty( $t['term_template'] ) ) {
4851 $t['term_template'] = $args['term_template'];
4852 }
4853
4854 $terms = get_object_term_cache( $post->ID, $taxonomy );
4855 if ( false === $terms ) {
4856 $terms = wp_get_object_terms( $post->ID, $taxonomy, $t['args'] );
4857 }
4858 $links = array();
4859
4860 foreach ( $terms as $term ) {
4861 $links[] = wp_sprintf( $t['term_template'], esc_attr( get_term_link( $term ) ), $term->name );
4862 }
4863 if ( $links ) {
4864 $taxonomies[ $taxonomy ] = wp_sprintf( $t['template'], $t['label'], $links, $terms );
4865 }
4866 }
4867 return $taxonomies;
4868}
4869
4870/**
4871 * Retrieves all taxonomy names for the given post.
4872 *
4873 * @since 2.5.0
4874 *
4875 * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global $post.
4876 * @return string[] An array of all taxonomy names for the given post.
4877 */
4878function get_post_taxonomies( $post = 0 ) {
4879 $post = get_post( $post );
4880
4881 return get_object_taxonomies( $post );
4882}
4883
4884/**
4885 * Determines if the given object is associated with any of the given terms.
4886 *
4887 * The given terms are checked against the object's terms' term_ids, names and slugs.
4888 * Terms given as integers will only be checked against the object's terms' term_ids.
4889 * If no terms are given, determines if object is associated with any terms in the given taxonomy.
4890 *
4891 * @since 2.7.0
4892 *
4893 * @param int $object_id ID of the object (post ID, link ID, ...).
4894 * @param string $taxonomy Single taxonomy name.
4895 * @param int|string|int[]|string[] $terms Optional. Term ID, name, slug, or array of such
4896 * to check against. Default null.
4897 * @return bool|WP_Error WP_Error on input error.
4898 */
4899function is_object_in_term( $object_id, $taxonomy, $terms = null ) {
4900 $object_id = (int) $object_id;
4901 if ( ! $object_id ) {
4902 return new WP_Error( 'invalid_object', __( 'Invalid object ID.' ) );
4903 }
4904
4905 $object_terms = get_object_term_cache( $object_id, $taxonomy );
4906 if ( false === $object_terms ) {
4907 $object_terms = wp_get_object_terms( $object_id, $taxonomy, array( 'update_term_meta_cache' => false ) );
4908 if ( is_wp_error( $object_terms ) ) {
4909 return $object_terms;
4910 }
4911
4912 wp_cache_set( $object_id, wp_list_pluck( $object_terms, 'term_id' ), "{$taxonomy}_relationships" );
4913 }
4914
4915 if ( is_wp_error( $object_terms ) ) {
4916 return $object_terms;
4917 }
4918
4919 if ( empty( $object_terms ) ) {
4920 return false;
4921 }
4922
4923 if ( empty( $terms ) ) {
4924 return true;
4925 }
4926
4927 $terms = (array) $terms;
4928
4929 $ints = array_filter( $terms, 'is_int' );
4930 if ( $ints ) {
4931 $strs = array_diff( $terms, $ints );
4932 } else {
4933 $strs =& $terms;
4934 }
4935
4936 foreach ( $object_terms as $object_term ) {
4937 // If term is an int, check against term_ids only.
4938 if ( $ints && in_array( $object_term->term_id, $ints, true ) ) {
4939 return true;
4940 }
4941
4942 if ( $strs ) {
4943 // Only check numeric strings against term_id, to avoid false matches due to type juggling.
4944 $numeric_strs = array_map( 'intval', array_filter( $strs, 'is_numeric' ) );
4945 if ( in_array( $object_term->term_id, $numeric_strs, true ) ) {
4946 return true;
4947 }
4948
4949 if ( in_array( $object_term->name, $strs, true ) ) {
4950 return true;
4951 }
4952 if ( in_array( $object_term->slug, $strs, true ) ) {
4953 return true;
4954 }
4955 }
4956 }
4957
4958 return false;
4959}
4960
4961/**
4962 * Determines if the given object type is associated with the given taxonomy.
4963 *
4964 * @since 3.0.0
4965 *
4966 * @param string $object_type Object type string.
4967 * @param string $taxonomy Single taxonomy name.
4968 * @return bool True if object is associated with the taxonomy, otherwise false.
4969 */
4970function is_object_in_taxonomy( $object_type, $taxonomy ) {
4971 $taxonomies = get_object_taxonomies( $object_type );
4972 if ( empty( $taxonomies ) ) {
4973 return false;
4974 }
4975 return in_array( $taxonomy, $taxonomies, true );
4976}
4977
4978/**
4979 * Gets an array of ancestor IDs for a given object.
4980 *
4981 * @since 3.1.0
4982 * @since 4.1.0 Introduced the `$resource_type` argument.
4983 *
4984 * @param int $object_id Optional. The ID of the object. Default 0.
4985 * @param string $object_type Optional. The type of object for which we'll be retrieving
4986 * ancestors. Accepts a post type or a taxonomy name. Default empty.
4987 * @param string $resource_type Optional. Type of resource $object_type is. Accepts 'post_type'
4988 * or 'taxonomy'. Default empty.
4989 * @return int[] An array of IDs of ancestors from lowest to highest in the hierarchy.
4990 */
4991function get_ancestors( $object_id = 0, $object_type = '', $resource_type = '' ) {
4992 $object_id = (int) $object_id;
4993
4994 $ancestors = array();
4995
4996 if ( empty( $object_id ) ) {
4997
4998 /** This filter is documented in wp-includes/taxonomy.php */
4999 return apply_filters( 'get_ancestors', $ancestors, $object_id, $object_type, $resource_type );
5000 }
5001
5002 if ( ! $resource_type ) {
5003 if ( is_taxonomy_hierarchical( $object_type ) ) {
5004 $resource_type = 'taxonomy';
5005 } elseif ( post_type_exists( $object_type ) ) {
5006 $resource_type = 'post_type';
5007 }
5008 }
5009
5010 if ( 'taxonomy' === $resource_type ) {
5011 $term = get_term( $object_id, $object_type );
5012 while ( ! is_wp_error( $term ) && ! empty( $term->parent ) && ! in_array( $term->parent, $ancestors, true ) ) {
5013 $ancestors[] = (int) $term->parent;
5014 $term = get_term( $term->parent, $object_type );
5015 }
5016 } elseif ( 'post_type' === $resource_type ) {
5017 $ancestors = get_post_ancestors( $object_id );
5018 }
5019
5020 /**
5021 * Filters a given object's ancestors.
5022 *
5023 * @since 3.1.0
5024 * @since 4.1.1 Introduced the `$resource_type` parameter.
5025 *
5026 * @param int[] $ancestors An array of IDs of object ancestors.
5027 * @param int $object_id Object ID.
5028 * @param string $object_type Type of object.
5029 * @param string $resource_type Type of resource $object_type is.
5030 */
5031 return apply_filters( 'get_ancestors', $ancestors, $object_id, $object_type, $resource_type );
5032}
5033
5034/**
5035 * Returns the term's parent's term ID.
5036 *
5037 * @since 3.1.0
5038 *
5039 * @param int $term_id Term ID.
5040 * @param string $taxonomy Taxonomy name.
5041 * @return int|false Parent term ID on success, false on failure.
5042 */
5043function wp_get_term_taxonomy_parent_id( $term_id, $taxonomy ) {
5044 $term = get_term( $term_id, $taxonomy );
5045 if ( ! $term || is_wp_error( $term ) ) {
5046 return false;
5047 }
5048 return (int) $term->parent;
5049}
5050
5051/**
5052 * Checks the given subset of the term hierarchy for hierarchy loops.
5053 * Prevents loops from forming and breaks those that it finds.
5054 *
5055 * Attached to the {@see 'wp_update_term_parent'} filter.
5056 *
5057 * @since 3.1.0
5058 *
5059 * @param int $parent_term `term_id` of the parent for the term we're checking.
5060 * @param int $term_id The term we're checking.
5061 * @param string $taxonomy The taxonomy of the term we're checking.
5062 * @return int The new parent for the term.
5063 */
5064function wp_check_term_hierarchy_for_loops( $parent_term, $term_id, $taxonomy ) {
5065 // Nothing fancy here - bail.
5066 if ( ! $parent_term ) {
5067 return 0;
5068 }
5069
5070 // Can't be its own parent.
5071 if ( $parent_term === $term_id ) {
5072 return 0;
5073 }
5074
5075 // Now look for larger loops.
5076 $loop = wp_find_hierarchy_loop( 'wp_get_term_taxonomy_parent_id', $term_id, $parent_term, array( $taxonomy ) );
5077 if ( ! $loop ) {
5078 return $parent_term; // No loop.
5079 }
5080
5081 // Setting $parent_term to the given value causes a loop.
5082 if ( isset( $loop[ $term_id ] ) ) {
5083 return 0;
5084 }
5085
5086 // There's a loop, but it doesn't contain $term_id. Break the loop.
5087 foreach ( array_keys( $loop ) as $loop_member ) {
5088 wp_update_term( $loop_member, $taxonomy, array( 'parent' => 0 ) );
5089 }
5090
5091 return $parent_term;
5092}
5093
5094/**
5095 * Determines whether a taxonomy is considered "viewable".
5096 *
5097 * @since 5.1.0
5098 *
5099 * @param string|WP_Taxonomy $taxonomy Taxonomy name or object.
5100 * @return bool Whether the taxonomy should be considered viewable.
5101 */
5102function is_taxonomy_viewable( $taxonomy ) {
5103 if ( is_scalar( $taxonomy ) ) {
5104 $taxonomy = get_taxonomy( $taxonomy );
5105 if ( ! $taxonomy ) {
5106 return false;
5107 }
5108 }
5109
5110 return $taxonomy->publicly_queryable;
5111}
5112
5113/**
5114 * Determines whether a term is publicly viewable.
5115 *
5116 * A term is considered publicly viewable if its taxonomy is viewable.
5117 *
5118 * @since 6.1.0
5119 *
5120 * @param int|WP_Term $term Term ID or term object.
5121 * @return bool Whether the term is publicly viewable.
5122 */
5123function is_term_publicly_viewable( $term ) {
5124 $term = get_term( $term );
5125
5126 if ( ! $term ) {
5127 return false;
5128 }
5129
5130 return is_taxonomy_viewable( $term->taxonomy );
5131}
5132
5133/**
5134 * Sets the last changed time for the 'terms' cache group.
5135 *
5136 * @since 5.0.0
5137 */
5138function wp_cache_set_terms_last_changed() {
5139 wp_cache_set_last_changed( 'terms' );
5140}
5141
5142/**
5143 * Aborts calls to term meta if it is not supported.
5144 *
5145 * @since 5.0.0
5146 *
5147 * @param mixed $check Skip-value for whether to proceed term meta function execution.
5148 * @return mixed Original value of $check, or false if term meta is not supported.
5149 */
5150function wp_check_term_meta_support_prefilter( $check ) {
5151 if ( get_option( 'db_version' ) < 34370 ) {
5152 return false;
5153 }
5154
5155 return $check;
5156}
5157
Ui Ux Design – Teachers Night Out https://cardgames4educators.com Wed, 16 Oct 2024 22:24:18 +0000 en-US hourly 1 https://wordpress.org/?v=6.9.4 https://cardgames4educators.com/wp-content/uploads/2024/06/cropped-Card-4-Educators-logo-32x32.png Ui Ux Design – Teachers Night Out https://cardgames4educators.com 32 32 Masters In English How English Speaker https://cardgames4educators.com/masters-in-english-how-english-speaker/ https://cardgames4educators.com/masters-in-english-how-english-speaker/#comments Mon, 27 May 2024 08:54:45 +0000 https://themexriver.com/wp/kadu/?p=1

Erat himenaeos neque id sagittis massa. Hac suscipit pulvinar dignissim platea magnis eu. Don tellus a pharetra inceptos efficitur dui pulvinar. Feugiat facilisis penatibus pulvinar nunc dictumst donec odio platea habitasse. Lacus porta dolor purus elit ante bibendum tortor netus taciti nullam cubilia. Erat per suspendisse placerat morbi egestas pulvinar bibendum sollicitudin nec. Euismod cubilia eleifend velit himenaeos sodales lectus. Leo maximus cras ac porttitor aliquam torquent pulvinar odio volutpat parturient. Quisque risus finibus suspendisse mus purus magnis facilisi condimentum consectetur dui. Curae elit suspendisse cursus vehicula.

Turpis taciti class non vel pretium quis pulvinar tempor lobortis nunc. Libero phasellus parturient sapien volutpat malesuada ornare. Cubilia dignissim sollicitudin rhoncus lacinia maximus. Cras lorem fermentum bibendum pellentesque nisl etiam ligula enim cubilia. Vulputate pede sapien torquent montes tempus malesuada in mattis dis turpis vitae. Porta est tempor ex eget feugiat vulputate ipsum. Justo nec iaculis habitant diam arcu fermentum.

We offer comprehen sive emplo ment services such as assistance wit employer compliance.Our company is your strategic HR partner as instead of HR. john smithson

Cubilia dignissim sollicitudin rhoncus lacinia maximus. Cras lorem fermentum bibendum pellentesque nisl etiam ligula enim cubilia. Vulputate pede sapien torquent montes tempus malesuada in mattis dis turpis vitae.

Exploring Learning Landscapes in Academic

Feugiat facilisis penatibus pulvinar nunc dictumst donec odio platea habitasse. Lacus porta dolor purus elit ante bibendum tortor netus taciti nullam cubilia. Erat per suspendisse placerat morbi egestas pulvinar bibendum sollicitudin nec. Euismod cubilia eleifend velit himenaeos sodales lectus. Leo maximus cras ac porttitor aliquam torquent.

]]>
https://cardgames4educators.com/masters-in-english-how-english-speaker/feed/ 1