-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathterm-data-store.php
362 lines (318 loc) · 11.6 KB
/
term-data-store.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
<?php
/**
* Establish a relationship between a taxonomy and a custom post type such that
* there is a one to one relation between each taxonomy term and cpt post.
*
* add_relationship() sets up the relationships
* get_related_post() returns term's post for storing and returning related meta
* get_related_term() returns related term (for ex: to base a related posts query on) if we are displaying that post
*
*/
namespace TDS;
class Invalid_Input_Exception extends \Exception {
}
/**
* Sets up a term data storage relationship between a post type and a taxonomy
*
* This relationship will keep a post type and a term in a 1:1 synced relation,
* ensuring that no terms or posts ever exist without a matching entity of the
* other type.
*
* First the function makes sure get_post_type_object() and get_taxonomy()
* return something and then checks get_relationship() for the post type and for
* the taxonomy to make sure that neither is already in a relationship.
*
* The function adds a hook for save_post. The callback for the hook is the
* return value from get_save_post_hook().
*
* Then it adds a hook for create_$taxonomy. The callback for the hook is the
* return value from get_create_term_hook().
*
* Finally, this function adds the relationship to the get_relationship static
* variable by providing the post_type as the first argument and the taxonomy as
* the second.
*
* @uses get_post_type_object()
* @uses get_taxonomy()
* @uses get_relationship()
* @uses add_action() Adds the return of get_save_post_hook() to save_post (2 arguments)
* @uses add_action() Adds the return of get_create_term_hook() to "create_$taxonomy"
*
* @throws Invalid_Input_Exception If either post_type or taxonomy is invalid
*
* @param string $post_type The post type slug
* @param string $taxonomy The taxonomy slug
*/
function add_relationship( $post_type, $taxonomy ) {
if ( ! get_post_type_object( $post_type ) ) {
throw new Invalid_Input_Exception( __FUNCTION__ . '() invalid post_type input.' );
}
if ( ! get_taxonomy( $taxonomy ) ) {
throw new Invalid_Input_Exception( __FUNCTION__ . '() invalid taxonomy input.' );
}
$post_type_relationships = get_relationship( $post_type );
$taxonomy_relationships = get_relationship( $taxonomy );
if ( ! empty( $post_type_relationships ) && ! empty( $taxonomy_relationships ) ) {
throw new Invalid_Input_Exception( __FUNCTION__ . '() post_type and taxonomy already have relationships.' );
} elseif ( ! empty( $post_type_relationships ) && empty( $taxonomy_relationships ) ) {
throw new Invalid_Input_Exception( __FUNCTION__ . '() post_type already has a relationship.' );
} elseif ( empty( $post_type_relationships ) && ! empty( $taxonomy_relationships ) ) {
throw new Invalid_Input_Exception( __FUNCTION__ . '() taxonomy already has a relationship.' );
}
unset( $post_type_relationships, $taxonomy_relationships );
add_action( 'save_post', get_save_post_hook( $post_type, $taxonomy ), 10, 2 );
add_action( 'create_' . $taxonomy, get_create_term_hook( $post_type, $taxonomy ) );
get_relationship( $post_type, $taxonomy );
}
/**
* Get the name of an object's corresponding type
*
* This function stores a static variable containing an associative array with
* post types as keys and taxonomies as values. This function will return the
* taxonomy name if $for exists as a post type (i.e. a key in the array) or the
* post type name if $for exists as a taxonomy (i.e. a value found using
* array_search()). Otherwise it returns null
*
* @internal If the function is called with a non-null value for $set, add the value to the array using $for as the key and $set as the value.
*
* @param string $for The name of the object type (post type or taxonomy) to get
* @param string|null $set Used internally. Ignore
*
* @return string|null The corresponding value
*/
function get_relationship( $for, $set = null ) {
static $post_type_relationships = array();
if ( isset( $set ) ) {
return $post_type_relationships[$for] = $set;
}
if ( ! empty( $post_type_relationships[$for] ) ) {
return $post_type_relationships[$for];
}
$search = array_search( $for, $post_type_relationships );
if ( ! empty( $search ) ) {
return $search;
}
return null;
}
/**
* Returns a boolean to indicate whether a relationship is currently being balanced
*
* This allows the plugin to create posts and terms without triggering an
* infinite loop of creating posts and terms from each other. The function uses
* a static variable to store the current balancing status. To set it, pass a
* variable as an argument. The function will check for any arguments and use
* the first one cast as a boolean as the static value.
*
* @return bool Whether a relationship is currently being balanced
*/
function balancing_relationship() {
static $balancing_status = false;
if ( func_num_args() ) {
$balancing_status = (boolean) func_get_arg( 0 );
}
return $balancing_status;
}
/**
* Returns a closure to be used as the callback hooked to save_post
*
* If balancing_relationship() returns true, this function does nothing.
* Otherwise it will set balancing_relationship to true before starting and back
* to false at the end of the function.
*
* The closure will receive the post_type and taxonomy values through its use
* statement so that it will have the necessary data to filter out posts created
* for other post types and will know which taxonomy to check and create terms
* for.
*
* The function stores references to the closures in a static variable using the
* md5 hash of "$post_type|$taxonomy" to generate the key. If that value exists,
* return it instead of creating a new copy.
*
* The closure that this function generates receives two arguments ($post_id and
* $post) and does the following:
* If $post->post_type is $post_type and $post->post_status is 'publish' and
* wp_get_object_terms() is empty:
* Create a new term in $taxonomy with the same name and slug as the post
* and 'tag' the post with that term using wp_set_object_terms().
*
* @uses balancing_relationship()
* @uses wp_get_object_terms()
* @uses wp_insert_term()
* @uses wp_set_object_terms()
*
* @param string $post_type
* @param string $taxonomy
*
* @return \Closure The callback
*/
function get_save_post_hook( $post_type, $taxonomy ) {
static $existing_closures;
if ( ! isset( $existing_closures ) ) {
$existing_closures = array();
}
$md5 = md5( $post_type . '|' . $taxonomy );
if ( isset( $existing_closures[$md5] ) ) {
return $existing_closures[$md5];
}
$closure = function ( $post_id, $post ) use ( $post_type, $taxonomy ) {
if ( apply_filters( 'tds_balancing_from_post', balancing_relationship(), $post_type, $taxonomy, $post ) ) {
return;
}
if ( empty( $post ) || $post_type !== $post->post_type || ( 'publish' !== $post->post_status ) || wp_get_object_terms( $post_id, $taxonomy ) ) {
return;
}
balancing_relationship( true );
$term = get_term_by( 'slug', $post->post_name, $taxonomy, ARRAY_A );
if( !$term )
{
$term = wp_insert_term( $post->post_title, $taxonomy, array( 'slug' => $post->post_name ) );
}
wp_set_object_terms( $post->ID, (int) $term['term_id'], $taxonomy );
balancing_relationship( false );
};
$existing_closures[$md5] = $closure;
return $closure;
}
/**
* Returns a closure to be used as the callback hooked to create_{$taxonomy}
*
* If balancing_relationship() returns true, this function does nothing.
* Otherwise it will set balancing_relationship to true before starting and back
* to false at the end of the function.
*
* The closure will receive the post_type and taxonomy values through its use
* statement so that it will be aware of which taxonomy the term was created in
* and which post type to create a post for.
*
* The function stores references to the closures in a static variable using the
* md5 hash of "$post_type|$taxonomy" to generate the key. If that value exists,
* return it instead of creating a new copy.
*
* The closure that this function generates receives one argument ($term_id) and
* does the following:
* If get_objects_in_term() for the term id and the taxonomy is not a non-
* empty array:
* Get the term using get_term(). Create a new post of post type $post_type
* with the same title and slug as the term and 'tag' the post with the term
* using wp_set_object_terms().
*
* @uses balancing_relationship()
* @uses get_objects_in_term()
* @uses get_term()
* @uses wp_insert_post()
* @uses wp_set_object_terms()
*
* @param string $post_type
* @param string $taxonomy
*
* @return \Closure The callback
*/
function get_create_term_hook( $post_type, $taxonomy ) {
static $existing_closures;
if ( ! isset( $existing_closures ) ) {
$existing_closures = array();
}
$md5 = md5( $post_type . '|' . $taxonomy );
if ( isset( $existing_closures[$md5] ) ) {
return $existing_closures[$md5];
}
$closure = function ( $term_id ) use ( $post_type, $taxonomy ) {
if ( apply_filters( 'tds_balancing_from_term', balancing_relationship(), $taxonomy, $post_type, $term_id ) ) {
return;
}
if ( empty( $term_id ) ) {
return;
}
balancing_relationship( true );
$term_objects = get_objects_in_term( $term_id, $taxonomy );
if ( empty( $term_objects ) ) {
$term = get_term( $term_id, $taxonomy );
$post_id = wp_insert_post( array(
'post_type' => $post_type
, 'post_title' => $term->name
, 'post_name' => $term->slug
, 'post_status' => 'publish'
) );
wp_set_object_terms( $post_id, $term_id, $taxonomy );
}
balancing_relationship( false );
};
$existing_closures[$md5] = $closure;
return $closure;
}
/**
* Takes a term object and returns a post object related to it
*
* If $term is an ID the term is fetched using get_term(). If that function does
* not return a valid term object, the function returns null. Otherwise, get the
* related post type using get_relationship() and grab the most recent published
* post of that post type and return it.
*
* @uses get_term()
* @uses is_wp_error()
* @uses get_relationship()
* @uses get_posts()
*
* @param object|int $term The term object or a term id WITH a taxonomy
* @param string|null $taxonomy The taxonomy (required if $term is an ID)
*
* @return \WP_Post|null
*/
function get_related_post( $term, $taxonomy = null ) {
if ( is_int( $term ) ) {
if ( ! empty( $taxonomy ) ) {
$term = get_term( $term, $taxonomy );
} else {
return null;
}
}
if ( is_wp_error( $term ) || ! is_object( $term ) ) {
return null;
}
$post_type = get_relationship( $term->taxonomy );
if ( ! empty( $post_type ) ) {
$posts = get_posts( array(
'post_type' => $post_type
, 'posts_per_page' => 1
, 'tax_query' => array( array(
'taxonomy' => $term->taxonomy
, 'field' => 'id'
, 'terms' => $term->term_id
) )
) );
if ( isset( $posts[0] ) ) {
return $posts[0];
}
}
return null;
}
/**
* Takes a post object (or ID) and returns a term object related to it
*
* First $post is run through get_post() to ensure it's a valid post object. If
* it's not, null is returned. Then the terms are fetched using wp_get_object_terms().
* If a non-empty array is not returned, null is returned. Otherwise, the first
* element of the array is returned.
*
* @uses get_post()
* @uses wp_get_object_terms()
* @uses is_wp_error()
*
* @param \WP_Post|int $post The post or post id
*
* @return object|null
*/
function get_related_term( $post ) {
$post = get_post( $post );
if ( empty( $post ) ) {
return;
}
$terms = wp_get_object_terms( $post->ID, get_relationship( $post->post_type ) );
if ( is_wp_error( $terms ) || ! $terms ) {
return null;
}
if ( is_array( $terms ) && count( $terms ) ) {
return $terms[0];
}
return null;
}