PluginProbe
Content Control – The Ultimate Content Restriction Plugin! Restrict Content, Create Conditional Blocks & More / trunk
Content Control – The Ultimate Content Restriction Plugin! Restrict Content, Create Conditional Blocks & More vtrunk
trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.1.10 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.1.8 2.0.0 2.0.1 2.0.10 2.0.11 2.0.12 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 All 47 releases
content-control / inc / functions / query.php

query.php in Content Control – The Ultimate Content Restriction Plugin! Restrict Content, Create Conditional Blocks & More trunk, at inc/functions/query.php

592 lines 14.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Query functions.
4 *
5 * @package ContentControl
6 */
7
8 namespace ContentControl;
9
10 defined( 'ABSPATH' ) || exit;
11
12 /**
13 * Get the main query.
14 *
15 * @return \WP_Query|null
16 */
17 function get_main_wp_query() {
18 global $wp_the_query;
19
20 if ( ! is_null( $wp_the_query ) ) {
21 /**
22 * WP Query object.
23 *
24 * @var \WP_Query $wp_the_query
25 */
26 return $wp_the_query;
27 }
28
29 return null;
30 }
31
32 /**
33 * Get the current wp query.
34 *
35 * Helper that returns the current query object, reguardless of if
36 * it's the main query or not.
37 *
38 * @return \WP_Query|null
39 */
40 function get_current_wp_query() {
41 global $wp_query;
42
43 if ( ! is_null( $wp_query ) ) {
44 /**
45 * WP Query object.
46 *
47 * @var \WP_Query $wp_query
48 */
49 return $wp_query;
50 }
51
52 return get_main_wp_query();
53 }
54
55 /**
56 * Get the current query.
57 *
58 * @param \WP_Query|\WP_Term_Query|null $query Query object.
59 *
60 * @return \WP_Query|\WP_Term_Query|null
61 */
62 function get_query( $query = null ) {
63 if ( is_null( $query ) ) {
64 if ( ! global_is_empty( 'current_query' ) ) {
65 /**
66 * WP Query object.
67 *
68 * @var \WP_Query|\WP_Term_Query $query
69 */
70 $query = get_global( 'current_query' );
71 } else {
72 $query = get_current_wp_query();
73 }
74 }
75
76 return $query;
77 }
78
79 /**
80 * Set the current query context.
81 *
82 * @param string $context 'main', 'main/posts', 'posts', 'main/blocks', 'blocks`.
83 *
84 * @return void
85 */
86 function override_query_context( $context ) {
87 set_global( 'current_query_context', $context );
88 }
89
90 /**
91 * Reset the current query context.
92 *
93 * @return void
94 */
95 function reset_query_context() {
96 reset_global( 'current_query_context' );
97 }
98
99 /**
100 * Get or set the current rule context (globaly accessible).
101 *
102 * 'main', 'main/posts', 'posts', 'main/blocks', 'blocks`
103 *
104 * Rules can work differently depending on the context they are being checked in.
105 * This context allows us to handle the main query differently to other queries,
106 * and blocks. It further allows us to handle blocks in several unique ways per
107 * rule.
108 *
109 * 1. Main query is checked in the template_redirect action.
110 * 2. Main query posts are checked in the the_posts filter & $wp_query->is_main_query().
111 * 3. Alternate query posts are checked in the_posts or pre_get_posts & ! $wp_query->is_main_query().
112 * 4. Blocks are checked in the content_control/should_hide_block filter.
113 *
114 * switch ( current_query_context() ) {
115 * // Catch all known contexts.
116 * case 'main':
117 * case 'main/blocks':
118 * case 'main/posts':
119 * case 'posts':
120 * case 'blocks':
121 * case 'restapi/posts':
122 * case 'restapi':
123 * case 'restapi/terms':
124 * case 'terms':
125 * case 'unknown':
126 * return false;
127 * }
128 *
129 * @param \WP_Query|null $query Query object.
130 *
131 * @return 'main'|'main/posts'|'posts'|'main/blocks'|'blocks'|'restapi'|'restapi/posts'|'restapi/terms'|'terms'|'unknown'
132 */
133 function current_query_context( $query = null ) {
134 if ( ! global_is_empty( 'current_query_context' ) ) {
135 return get_global( 'current_query_context' );
136 }
137
138 $query = get_query( $query );
139 $is_main = is_a( $query, '\WP_Query' ) && $query->is_main_query();
140
141 // Blocks in the main page or other locations.
142 if ( doing_filter( 'content_control/should_hide_block' ) ) {
143 return $is_main ? 'main/blocks' : 'blocks';
144 }
145
146 // Main query (page/psst/home/search/archive etc) (template_redirect).
147 if ( $is_main && doing_action( 'template_redirect' ) ) {
148 return 'main';
149 }
150
151 // Before we process plain queries, we need to check if we're in a REST API request.
152 if ( is_rest() ) {
153 if ( doing_filter( 'get_terms' ) ) {
154 return 'restapi/terms';
155 }
156
157 if ( doing_filter( 'pre_get_posts' ) || doing_filter( 'the_posts' ) ) {
158 return 'restapi/posts';
159 }
160
161 return 'restapi';
162 }
163
164 // Process plain queries.
165 if ( doing_filter( 'get_terms' ) ) {
166 return 'terms';
167 }
168
169 if ( doing_filter( 'pre_get_posts' ) || doing_filter( 'the_posts' ) ) {
170 return $is_main ? 'main/posts' : 'posts';
171 }
172
173 // Default to posts.
174 return 'posts';
175 }
176
177 /**
178 * Set the current rule (globaly accessible).
179 *
180 * Because we check posts in `the_posts`, we can't trust the global $wp_query
181 * has been set yet, so we need to manage global state ourselves.
182 *
183 * @param \WP_Query|\WP_Term_Query|null $query WP_Query object.
184 *
185 * @return void
186 */
187 function set_rules_query( $query ) {
188 set_global( 'current_query', $query );
189 }
190
191 /**
192 * Setup the current content.
193 *
194 * @param int|\WP_Post|\WP_Term|null $content_id Content ID.
195 *
196 * @return bool
197 *
198 * @since 2.4.0 - Added support for `terms` context.
199 */
200 function setup_content_globals( $content_id ) {
201 // Return early if we don't have a post ID.
202 if ( is_null( $content_id ) ) {
203 return false;
204 }
205
206 switch ( current_query_context() ) {
207 case 'terms':
208 case 'restapi/terms':
209 return setup_term_globals( $content_id );
210 default:
211 return setup_post_globals( $content_id );
212 }
213 }
214
215 /**
216 * Check and overload global post if needed.
217 *
218 * This has no effect when checking global queries ($post_id = null).
219 *
220 * @param int|\WP_Post|null $post_id Post ID.
221 *
222 * @return bool
223 *
224 * @since 2.4.0 - Added support for `terms` context.
225 */
226 function setup_post_globals( $post_id = null ) {
227 global $post;
228
229 // Return early if we don't have a post ID.
230 if ( is_null( $post_id ) ) {
231 return false;
232 }
233
234 // Set current post id based on global $post.
235 $current_post_id = $post->ID ?? null;
236
237 // Check if we should overload the post. This means its not the current post.
238 $overload_post =
239 // If we have a post ID, check if it's different from the current post ID.
240 ( is_object( $post_id ) && $post_id->ID !== $current_post_id ) ||
241 // If we have an int, check if it's different from the current post ID.
242 ( is_int( $post_id ) && $post_id !== $current_post_id );
243
244 if ( $overload_post ) {
245 // Push the current $post to the stack so we can restore it later.
246 push_to_global( 'overloaded_posts', $current_post_id );
247
248 // Overload the globals so conditionals work properly.
249 // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
250 $post = get_post( $post_id );
251 setup_postdata( $post );
252 }
253
254 return $overload_post;
255 }
256
257 /**
258 * Check and overload global term if needed.
259 *
260 * This has no effect when checking global queries ($term_id = null).
261 *
262 * @param int|\WP_Term|null $term_id Term ID.
263 *
264 * @return bool
265 *
266 * @since 2.4.0 - Added support for `terms` context.
267 */
268 function setup_term_globals( $term_id = null ) {
269 /**
270 * Legacy term context global retained for backward compatibility.
271 *
272 * `$cc_term` predates the managed term-context service. It remains
273 * synchronized so existing integrations do not break, but Content Control
274 * itself reads the managed `term` value and new integrations should do the
275 * same.
276 *
277 * @deprecated 2.7.1 Use get_global( 'term' ) instead.
278 * @var \WP_Term|\WP_Error|false|null $cc_term
279 */
280 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- Deprecated compatibility global.
281 global $cc_term;
282
283 $current_term = get_global( 'term' ); // Used instead of global $cc_term.
284
285 // Return early if we don't have a term ID.
286 if ( is_null( $term_id ) ) {
287 return false;
288 }
289
290 // Set current term id based on global $current_term.
291 $current_term_id = $current_term->term_id ?? null;
292
293 // Check if we should overload the term.
294 $overload_term =
295 // If we have a term ID, check if it's different from the current term ID.
296 ( is_object( $term_id ) && $term_id->term_id !== $current_term_id ) ||
297 // If we have an int, check if it's different from the current term ID.
298 ( is_int( $term_id ) && $term_id !== $current_term_id );
299
300 if ( $overload_term ) {
301 // Store only the ID of the current term for later restoration.
302 push_to_global( 'overloaded_terms', $current_term_id );
303
304 // Overload the globals so conditionals work properly.
305 $cc_term = get_term( $term_id );
306 // Set the global term object (forward compatibility).
307 set_global( 'term', $cc_term );
308 }
309
310 return $overload_term;
311 }
312
313 /**
314 * Setup the current content.
315 *
316 * @return void
317 *
318 * @since 2.4.0 - Added support for `terms` context.
319 */
320 function reset_content_globals() {
321 switch ( current_query_context() ) {
322 case 'terms':
323 case 'restapi/terms':
324 reset_term_globals();
325 break;
326 default:
327 reset_post_globals();
328 break;
329 }
330 }
331
332 /**
333 * Check and clear global post if needed.
334 *
335 * @global \WP_Post $post
336 *
337 * @return void
338 *
339 * @since 2.4.0 - Added support for `terms` context.
340 */
341 function reset_post_globals() {
342 if ( global_is_empty( 'overloaded_posts' ) ) {
343 return;
344 }
345
346 global $post;
347
348 $stored_post_id = pop_from_global( 'overloaded_posts' );
349 // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
350 $post = get_post( $stored_post_id );
351 // Reset global post object.
352 setup_postdata( $post );
353 }
354
355 /**
356 * Check and clear global term if needed.
357 *
358 * @return void
359 *
360 * @since 2.4.0 - Added support for `terms` context.
361 */
362 function reset_term_globals() {
363 if ( global_is_empty( 'overloaded_terms' ) ) {
364 // Reset global term object since it never really existed.
365 reset_global( 'term' );
366 return;
367 }
368
369 /**
370 * Legacy term context global retained for backward compatibility.
371 *
372 * `$cc_term` predates the managed term-context service. It remains
373 * synchronized so existing integrations do not break, but Content Control
374 * itself reads the managed `term` value and new integrations should do the
375 * same.
376 *
377 * @deprecated 2.7.1 Use get_global( 'term' ) instead.
378 * @var \WP_Term|\WP_Error|false|null $cc_term
379 */
380 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- Deprecated compatibility global.
381 global $cc_term;
382
383 $stored_term_id = pop_from_global( 'overloaded_terms' );
384 // Reset global post object.
385 $cc_term = get_term( $stored_term_id );
386 set_global( 'term', $cc_term );
387 }
388
389 /**
390 * Get the content ID for the current query item (post, term, etc).
391 *
392 * @return int|null
393 */
394 function get_the_content_id() {
395 $context = current_query_context();
396
397 switch ( $context ) {
398 case 'terms':
399 case 'restapi/terms':
400 $term = get_global( 'term' ); // Used instead of global $cc_term.
401 return $term->term_id ?? null;
402
403 default:
404 return get_the_ID();
405 }
406 }
407
408 /**
409 * Set up the post globals.
410 *
411 * @param int|\WP_Post|null $post_id Post ID.
412 *
413 * @return boolean
414 *
415 * @deprecated 2.4.0 - Use `setup_content_globals() or `setup_post_globals()` instead.
416 */
417 function setup_post( $post_id = null ) {
418 return setup_content_globals( $post_id );
419 }
420
421 /**
422 * Set up the term globals.
423 *
424 * @param int|\WP_Term|null $term_id Term ID.
425 *
426 * @return boolean
427 *
428 * @deprecated 2.4.0 - Use `setup_term_globals()` instead.
429 */
430 function setup_term_object( $term_id = null ) {
431 return setup_term_globals( $term_id );
432 }
433
434 /**
435 * Check and clear global post if needed.
436 *
437 * @global \WP_Post $post
438 *
439 * @return void
440 *
441 * @deprecated 2.4.0 - Use `reset_post_globals()` instead.
442 */
443 function reset_post() {
444 reset_post_globals();
445 }
446
447 /**
448 * Check and clear global term if needed.
449 *
450 * @return void
451 *
452 * @deprecated 2.4.0 - Use `reset_term_globals()` instead.
453 */
454 function reset_term_object() {
455 reset_term_globals();
456 }
457
458 /**
459 * Get the endpoints for a registered post types.
460 *
461 * @since 2.2.0
462 * @since 2.5.0 Use `rest_get_route_for_post_type_items()` instead of `get_post_type_object()->rest_base`.
463 *
464 * @return array<string,string>
465 */
466 function get_post_type_endpoints() {
467 $endpoints = [];
468 $post_types = get_post_types();
469
470 foreach ( $post_types as $post_type ) {
471 $endpoint = rest_get_route_for_post_type_items( $post_type );
472
473 // Possible if show_in_rest is false or if the post type is not registered.
474 if ( '' === $endpoint ) {
475 $object = get_post_type_object( $post_type );
476
477 if ( ! $object ) {
478 continue;
479 }
480
481 $endpoint = "/wp/v2/{$object->rest_base}";
482 }
483
484 $endpoints[ $endpoint ] = $post_type;
485 }
486
487 return $endpoints;
488 }
489
490 /**
491 * Get the endpoints for a registered taxonomies.
492 *
493 * @since 2.2.0
494 * @since 2.5.0 Use `rest_get_route_for_taxonomy_items()` instead of `get_taxonomy_object()->rest_base`.
495 *
496 * @return array<string,string>
497 */
498 function get_taxonomy_endpoints() {
499 $endpoints = [];
500 $taxonomies = get_taxonomies();
501
502 foreach ( $taxonomies as $taxonomy ) {
503 $endpoint = rest_get_route_for_taxonomy_items( $taxonomy );
504
505 // Possible if show_in_rest is false or if the taxonomy is not registered.
506 if ( '' === $endpoint ) {
507 $object = get_taxonomy( $taxonomy );
508
509 if ( ! $object ) {
510 continue;
511 }
512
513 $endpoint = "/wp/v2/{$object->rest_base}";
514 }
515
516 $endpoints[ $endpoint ] = $taxonomy;
517 }
518
519 return $endpoints;
520 }
521
522 /**
523 * Get the intent of the current REST API request.
524 *
525 * @since 2.2.0
526 * @since 2.3.0 - Added filter to allow overriding the intent.
527 * @since 2.4.0 - Added second paramter to the`content_control/get_rest_api_intent` filter pass the `$rest_route`.
528 *
529 * @return array{type:'post_type'|'taxonomy'|'unknown',name:string,id:int,index:bool,search:string|false}
530 */
531 function get_rest_api_intent() {
532 global $wp;
533
534 $intent = get_global( 'rest_intent' );
535
536 $rest_route = null;
537
538 if ( is_null( $intent ) ) {
539 $intent = [
540 'type' => 'unknown',
541 'name' => '',
542 'id' => 0,
543 'index' => false,
544 'search' => false,
545 ];
546
547 // Handle built-in REST API endpoints.
548 if ( ! empty( $wp->query_vars['rest_route'] ) ) {
549 $rest_route = $wp->query_vars['rest_route'];
550
551 if ( strpos( $rest_route, '/wp/v2/' ) === 0 ) {
552 $post_type_endpoints = get_post_type_endpoints();
553 $taxonomy_endpoints = get_taxonomy_endpoints();
554
555 $endpoint_parts = explode( '/', str_replace( '/wp/v2/', '', $rest_route ) );
556
557 // If we have a post type or taxonomy, the name is the first part (posts, categories).
558 $intent['name'] = sanitize_key( $endpoint_parts[0] );
559 // Check if this is a search request.
560 $intent['search'] = isset( $wp->query_vars['search'] ) ? sanitize_title( $wp->query_vars['search'] ) : false;
561
562 if ( count( $endpoint_parts ) > 1 ) {
563 // If we have an ID, then the second part is the ID.
564 $intent['id'] = absint( $endpoint_parts[1] );
565 } else {
566 // If we have no ID, then we are either searching or indexing.
567 $intent['index'] = true;
568 }
569
570 // Build a matching route.
571 $endpoint_route = "/wp/v2/{$intent['name']}";
572
573 if ( isset( $post_type_endpoints[ $endpoint_route ] ) ) {
574 $intent['type'] = 'post_type';
575 } elseif ( isset( $taxonomy_endpoints[ $endpoint_route ] ) ) {
576 $intent['type'] = 'taxonomy';
577 } elseif ( 'search' === $intent['name'] ) {
578 $intent['type'] = 'search';
579 }
580 } else {
581 // We currently have no way of really dealing with non WP REST requests.
582 // This filter allows us or others to correctly handle these requests in the future.
583 apply_filters( 'content_control/determine_uknonwn_rest_api_intent', $intent, $rest_route );
584 }
585 }
586
587 set_global( 'rest_intent', $intent );
588 }
589
590 return apply_filters( 'content_control/get_rest_api_intent', $intent, $rest_route );
591 }
592