PluginProbe
WowOptin: Next-Gen Popup Maker – Create Stunning Popups and Optins for Lead Generation / trunk
WowOptin: Next-Gen Popup Maker – Create Stunning Popups and Optins for Lead Generation vtrunk
1.4.48 1.4.47 1.4.46 1.4.45 1.4.44 1.4.43 1.4.42 1.4.41 1.4.40 1.4.39 1.4.38 1.4.37 1.4.36 1.1.2 1.2.0 1.2.1 1.2.2 1.3.0 1.3.1 1.4.0 1.4.1 1.4.10 1.4.11 1.4.12 1.4.13 All 64 releases
optin / includes / utils / class-display-rules.php

class-display-rules.php in WowOptin: Next-Gen Popup Maker – Create Stunning Popups and Optins for Lead Generation trunk, at includes/utils/class-display-rules.php

1,109 lines 28.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php // phpcs:ignore
2
3 namespace OPTN\Includes\Utils;
4
5 use OPTN\Includes\Utils\Utils;
6
7 /**
8 * Display Rules Class
9 */
10 class DisplayRules {
11
12 /**
13 * Default post types
14 */
15 const DEFAULT_POST_TYPES = array(
16 'post',
17 'page',
18 'attachment',
19 'nav_menu_item',
20 'wp_template',
21 'wp_template_part',
22 );
23
24 /**
25 * Is valid display rule
26 *
27 * @param object $rule rule.
28 * @return boolean
29 */
30 public static function is_valid_display_rule( $rule ) {
31 $type = $rule->type;
32
33 if ( str_starts_with( $type, 'woo' ) ) {
34 return self::is_valid_woo_display_rule( $rule );
35 }
36
37 if ( str_starts_with( $type, 'edd' ) ) {
38 return self::is_valid_edd_display_rule( $rule );
39 }
40
41 return self::is_valid_general_display_rule( $rule );
42 }
43
44 /**
45 * Validate WooCommerce Display Rules.
46 *
47 * @param object $rule rule.
48 * @return boolean
49 */
50 private static function is_valid_woo_display_rule( $rule ) {
51
52 if ( ! function_exists( 'WC' ) ) {
53 return false;
54 }
55
56 $type = $rule->type;
57 $values = $rule->values;
58 $cond = $rule->cond;
59
60 // Woo Cart Page.
61 if ( 'woo_cart_nocond' === $type ) {
62 return function_exists( 'is_cart' ) && is_cart();
63 }
64
65 // Woo Thank You Page.
66 if ( 'woo_thank_nocond' === $type ) {
67 return is_order_received_page();
68 }
69
70 // Woo Product Page.
71 if ( 'woo_single_product' === $type ) {
72 if ( is_product() ) {
73 $product = wc_get_product();
74
75 if ( ! empty( $product ) ) {
76 return Utils::check_values(
77 strval( $product->get_id() ),
78 $values,
79 $cond
80 );
81 }
82 } elseif ( in_array( $cond, array( 'not_any', 'not_all' ), true ) ) {
83 return true;
84 }
85 }
86
87 // Woo Product Categories.
88 if ( 'woo_cats' === $type ) {
89 if ( ! is_product() ) {
90 return false;
91 }
92
93 $product = wc_get_product();
94
95 if ( ! empty( $product ) ) {
96 return Utils::check_values(
97 $product->get_category_ids(),
98 $values,
99 $cond
100 );
101 }
102 }
103
104 // Woo Product Tags.
105 if ( 'woo_tags' === $type ) {
106 if ( ! is_product() ) {
107 return false;
108 }
109
110 $product = wc_get_product();
111
112 if ( ! empty( $product ) ) {
113 return Utils::check_values(
114 $product->get_tag_ids(),
115 $values,
116 $cond
117 );
118 }
119 }
120
121 // Woo Products in Cart.
122 if ( 'woo_cart_products' === $type ) {
123 if ( ! empty( WC()->cart ) ) {
124 $products = array_values(
125 array_map(
126 function ( $cart_item ) {
127 return strval( $cart_item['product_id'] );
128 },
129 WC()->cart->get_cart() ?? array()
130 )
131 );
132
133 $args = array(
134 'status' => 'publish',
135 'stock_status' => 'instock',
136 'return' => 'ids',
137 'limit' => -1,
138 );
139
140 $all_count = count( wc_get_products( $args ) ); // TODO @samin: Use count posts.
141
142 return Utils::check_values(
143 $products,
144 $values,
145 $cond,
146 $all_count,
147 );
148 }
149 }
150
151 // Woo Number of products in Cart.
152 if ( 'woo_cart_products_num' === $type ) {
153 if ( ! empty( WC()->cart ) ) {
154 $num_of_products = WC()->cart->get_cart_contents_count();
155 return Utils::check_math(
156 $num_of_products,
157 $values,
158 $cond,
159 );
160 }
161 }
162
163 // Woo Cart Total Value.
164 if ( 'woo_cart_value$' === $type ) {
165 if ( ! empty( WC()->cart ) ) {
166 $total = WC()->cart->get_cart_contents_total();
167 return Utils::check_math(
168 $total,
169 $values,
170 $cond,
171 );
172 }
173 }
174
175 return false;
176 }
177
178 /**
179 * Validate EDD Display Rules.
180 *
181 * @param object $rule rule.
182 * @return boolean
183 */
184 private static function is_valid_edd_display_rule( $rule ) {
185 $type = $rule->type;
186 $values = $rule->values;
187 $cond = $rule->cond;
188
189 // EDD Cart Page.
190 if ( 'edd_cart_nocond' === $type ) {
191 $edd_options = get_option( 'edd_settings', array() );
192 return isset( $edd_options['purchase_page'] ) && get_the_ID() == $edd_options['purchase_page']; // phpcs:ignore
193 }
194
195 // EDD Thank You Page.
196 if ( 'edd_thank_nocond' === $type ) {
197 if ( function_exists( 'edd_is_success_page' ) ) {
198 return edd_is_success_page();
199 }
200 }
201
202 // EDD Product Page.
203 if ( 'edd_single_product' === $type ) {
204 if ( self::is_edd_product() ) {
205 $product_id = get_the_ID();
206 return Utils::check_values(
207 $product_id,
208 $values,
209 $cond,
210 );
211 } elseif ( in_array( $cond, array( 'not_any', 'not_all' ), true ) ) {
212 return true;
213 }
214 }
215
216 // EDD Product Categories.
217 if ( 'edd_cats' === $type ) {
218 if ( self::is_edd_product() ) {
219 $product_id = get_the_ID();
220 $download_cats = get_the_terms( $product_id, 'download_category' ); // Meow >:3.
221
222 if ( is_array( $download_cats ) ) {
223 $product_cats = array_map(
224 function ( $item ) {
225 return $item->term_id;
226 },
227 $download_cats
228 );
229
230 return Utils::check_values(
231 $product_cats,
232 $values,
233 $cond,
234 );
235 }
236 }
237 }
238
239 if ( 'edd_tags' === $type ) {
240 if ( self::is_edd_product() ) {
241 $product_id = get_the_ID();
242 $download_tags = get_the_terms( $product_id, 'download_tag' );
243
244 if ( is_array( $download_tags ) ) {
245 $product_tags = array_map(
246 function ( $item ) {
247 return $item->term_id;
248 },
249 $download_tags
250 );
251 }
252
253 return Utils::check_values(
254 $product_tags,
255 $values,
256 $cond,
257 );
258 }
259 }
260
261 // EDD Products in Cart.
262 if ( 'edd_cart_products' === $type ) {
263 if ( function_exists( 'edd_get_cart_contents' ) ) {
264 $products = array_map(
265 function ( $cart_item ) {
266 return strval( $cart_item['id'] );
267 },
268 edd_get_cart_contents()
269 );
270
271 $args = array(
272 'post_type' => 'download',
273 'post_status' => 'publish',
274 'fields' => 'ids',
275 'posts_per_page' => -1,
276 );
277
278 $query = new \WP_Query( $args );
279
280 $all_count = $query->found_posts; // TODO @samin: Use count posts.
281
282 return Utils::check_values(
283 $products,
284 $values,
285 $cond,
286 $all_count,
287 );
288 }
289 }
290
291 // EDD Number of products in Cart.
292 if ( 'edd_cart_products_num' === $type ) {
293 if ( function_exists( 'edd_get_cart_quantity' ) ) {
294 return Utils::check_math( edd_get_cart_quantity(), $values, $cond );
295 }
296 }
297
298 // EDD Total Cart Value.
299 if ( 'edd_cart_value$' === $type ) {
300 if ( function_exists( 'edd_get_cart_total' ) ) {
301 $total = edd_get_cart_total();
302 return Utils::check_math( $total, $values, $cond );
303 }
304 }
305
306 return false;
307 }
308
309 /**
310 * Check General Display Rules.
311 *
312 * @param object $rule rule.
313 * @return boolean
314 */
315 private static function is_valid_general_display_rule( $rule ) {
316 $type = $rule->type;
317 $values = $rule->values;
318 $cond = $rule->cond;
319
320 // Entire Page.
321 if ( str_starts_with( $type, 'entire' ) ) {
322 return true;
323 }
324
325 // Front page.
326 if ( str_starts_with( $type, 'front' ) ) {
327 return is_front_page();
328 }
329
330 // URL Path.
331 if ( 'url_path' === $type ) {
332 return self::match_url_path( $values, $cond );
333 }
334
335 // Query params.
336 if ( 'custom_param_url' === $type ) {
337 $params = Utils::get_sanitized_query_parameters();
338 return Utils::validate_query_params( $values, $params );
339 }
340
341 // Cookie.
342 if ( 'cookie' === $type ) {
343 return self::validate_cookie( $values );
344 }
345
346 // Post.
347 if ( 'post_posts' === $type ||
348 'page' === $type ||
349 str_starts_with( $type, 'cpt_post:' )
350 ) {
351
352 $post_id = strval( get_the_ID() );
353 return Utils::check_values( $post_id, $values, $cond );
354 }
355
356 // Parent post.
357 if ( 'parent_page' === $type ) {
358 $parent_post_id = wp_get_post_parent_id( get_the_ID() );
359 return Utils::check_values( $parent_post_id, $values, $cond );
360 }
361
362 // Taxonomy (with custom taxonomy).
363 if (
364 'post_cat' === $type ||
365 'post_tags' === $type ||
366 str_starts_with( $type, 'cpt_tax:' )
367 ) {
368 $term_ids = Utils::get_terms_by_id( get_the_ID() );
369 return Utils::check_values( $term_ids, $values, $cond );
370 }
371
372 // Post Types.
373 if ( 'post_type' === $type ) {
374 $post_type = get_post_type( get_the_ID() );
375 return Utils::check_values( $post_type, $values, $cond );
376 }
377
378 // Author.
379 if ( 'author' === $type ) {
380 $author_id = strval( get_post_field( 'post_author', get_the_ID() ) );
381 return Utils::check_values( $author_id, $values, $cond );
382 }
383
384 return false;
385 }
386
387 /**
388 * Validate cookie display rule.
389 *
390 * @param object $values cookie settings values.
391 * @return bool
392 */
393 private static function validate_cookie( $values ) {
394 $key_type = $values->keyType; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
395 $key = sanitize_text_field( $values->keyValue ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
396 $value_type = $values->valueType; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
397 $value = sanitize_text_field( $values->valueValue ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
398 $cookie_value = sanitize_text_field( wp_unslash( $_COOKIE[ $key ] ?? '' ) );
399
400 switch ( $key_type ) {
401 case 'exists':
402 return isset( $_COOKIE[ $key ] );
403 case 'not_exists':
404 return ! isset( $_COOKIE[ $key ] );
405 }
406
407 switch ( $value_type ) {
408 case 'empty':
409 return empty( $cookie_value );
410 case 'not_empty':
411 return ! empty( $cookie_value );
412 case 'equals':
413 return $cookie_value == $value; // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
414 case 'not_equals':
415 return $cookie_value != $value; // phpcs:ignore Universal.Operators.StrictComparisons.LooseNotEqual
416 case 'contains':
417 return strpos( $cookie_value, $value ) !== false;
418 case 'not_contains':
419 return strpos( $cookie_value, $value ) === false;
420 }
421
422 return false;
423 }
424
425 /**
426 * Get display rules options for dropdown
427 *
428 * @return array
429 */
430 public static function get_options() {
431 $res = array(
432 array(
433 'isHeader' => true,
434 'label' => __( 'General', 'optin' ),
435 'value' => 'general_header',
436 ),
437 array(
438 'isChild' => true,
439 'value' => 'entire_nocond',
440 'label' => __( 'Entire Site', 'optin' ),
441 'condition' => 'none',
442 'valueField' => 'none',
443 ),
444 array(
445 'isChild' => true,
446 'value' => 'front_nocond',
447 'label' => __( 'Home Page', 'optin' ),
448 'condition' => 'none',
449 'valueField' => 'none',
450 ),
451 array(
452 'isChild' => true,
453 'value' => 'page',
454 'label' => __( 'Page', 'optin' ),
455 'condition' => 'single',
456 ),
457 array(
458 'isChild' => true,
459 'value' => 'parent_page',
460 'label' => __( 'Parent Page', 'optin' ),
461 'condition' => 'single',
462 ),
463 array(
464 'isChild' => true,
465 'value' => 'author',
466 'label' => __( 'Author', 'optin' ),
467 'condition' => 'single',
468 ),
469 array(
470 'isChild' => true,
471 'value' => 'post_type',
472 'label' => __( 'Post Type', 'optin' ),
473 'condition' => 'single',
474 ),
475 array(
476 'isChild' => true,
477 'value' => 'cookie',
478 'label' => __( 'Cookie', 'optin' ),
479 'pro' => true,
480 'Component' => 'cookie',
481 'defaultValue' => array(
482 'keyType' => 'equals',
483 'keyValue' => '',
484 'valueType' => 'equals',
485 'valueValue' => '',
486 ),
487 ),
488 array(
489 'isChild' => true,
490 'value' => 'url_path',
491 'label' => __( 'URL Path', 'optin' ),
492 'pro' => true,
493 'condition' => 'string',
494 'valueField' => 'text',
495 'props' => array(
496 'placeholder' => __( 'Ex', 'optin' ) . ': /products/electronics/televisions',
497 ),
498 ),
499 array(
500 'isChild' => true,
501 'value' => 'custom_param_url',
502 'label' => __( 'URL Parameter', 'optin' ),
503 'pro' => true,
504 'condition' => 'contains',
505 'valueField' => 'text',
506 'props' => array(
507 'placeholder' => __( 'Ex', 'optin' ) . ': param1=value1&param2=value2',
508 ),
509 ),
510
511 array(
512 'isHeader' => true,
513 'label' => __( 'Post', 'optin' ),
514 'value' => 'post_header',
515 ),
516 array(
517 'isChild' => true,
518 'value' => 'post_posts',
519 'label' => __( 'Posts', 'optin' ),
520 'condition' => 'single',
521 ),
522 array(
523 'isChild' => true,
524 'value' => 'post_cat',
525 'label' => __( 'Post Category', 'optin' ),
526 ),
527 array(
528 'isChild' => true,
529 'value' => 'post_tags',
530 'label' => __( 'Post Tag', 'optin' ),
531 ),
532 );
533
534 $post_types = get_post_types(
535 array(
536 'public' => true,
537 '_builtin' => false,
538 ),
539 'objects'
540 );
541
542 $exclude = array( 'product', 'download', 'attachment' );
543
544 if ( ! empty( $post_types ) ) {
545 foreach ( $post_types as $post_type ) {
546
547 if ( in_array( $post_type->name, $exclude ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
548 continue;
549 }
550
551 $res[] = array(
552 'isHeader' => true,
553 'label' => ucwords( $post_type->labels->singular_name ),
554 'value' => $post_type->label,
555 'pro' => true,
556 );
557
558 $res[] = array(
559 'value' => 'cpt_post:' . $post_type->name,
560 'isChild' => true,
561 'label' => $post_type->label,
562 'pro' => true,
563 'condition' => 'single',
564 );
565
566 $taxes = get_object_taxonomies( $post_type->name, 'objects' );
567
568 if ( ! empty( $taxes ) ) {
569 foreach ( $taxes as $tax ) {
570 $res[] = array(
571 'value' => 'cpt_tax:' . $post_type->name . '###' . $tax->name,
572 'isChild' => true,
573 'label' => $tax->label,
574 'pro' => true,
575 );
576 }
577 }
578 }
579 }
580
581 array_push(
582 $res,
583 array(
584 'isHeader' => true,
585 'label' => __( 'WooCommerce', 'optin' ),
586 'value' => 'woocom',
587 'pro' => true,
588 ),
589 array(
590 'value' => 'woo_single_product',
591 'label' => __( 'Product Single Page', 'optin' ),
592 'isChild' => true,
593 'pro' => true,
594 'condition' => 'single',
595 ),
596 array(
597 'value' => 'woo_cats',
598 'label' => __( 'Product Single Page with Category', 'optin' ),
599 'isChild' => true,
600 'pro' => true,
601 ),
602 array(
603 'value' => 'woo_tags',
604 'label' => __( 'Product Single Page with Tag', 'optin' ),
605 'isChild' => true,
606 'pro' => true,
607 ),
608 array(
609 'value' => 'woo_cart_products',
610 'label' => __( 'Products in Cart', 'optin' ),
611 'isChild' => true,
612 'pro' => true,
613 ),
614 array(
615 'value' => 'woo_cart_products_num',
616 'label' => __( 'Number of Products in Cart', 'optin' ),
617 'isChild' => true,
618 'pro' => true,
619 'condition' => 'math',
620 'valueField' => 'math',
621 ),
622 array(
623 'value' => 'woo_cart_value$',
624 'label' => __( 'Total Cart Amount', 'optin' ),
625 'isChild' => true,
626 'pro' => true,
627 'isCurrency' => true,
628 'valueField' => 'math',
629 ),
630 array(
631 'value' => 'woo_cart_nocond',
632 'label' => __( 'Cart Page', 'optin' ),
633 'isChild' => true,
634 'pro' => true,
635 'condition' => 'none',
636 ),
637 array(
638 'value' => 'woo_thank_nocond',
639 'label' => __( 'Thank You Page', 'optin' ),
640 'isChild' => true,
641 'pro' => true,
642 'condition' => 'none',
643 ),
644 array(
645 'isHeader' => true,
646 'label' => __( 'EDD', 'optin' ),
647 'value' => 'edd',
648 'pro' => true,
649 ),
650 array(
651 'value' => 'edd_single_product',
652 'label' => __( 'Product Single Page', 'optin' ),
653 'isChild' => true,
654 'pro' => true,
655 'condition' => 'single',
656 ),
657 array(
658 'value' => 'edd_cats',
659 'label' => __( 'Product Single Page with Category', 'optin' ),
660 'isChild' => true,
661 'pro' => true,
662 ),
663 array(
664 'value' => 'edd_tags',
665 'label' => __( 'Product Single Page with Tag', 'optin' ),
666 'isChild' => true,
667 'pro' => true,
668 ),
669 array(
670 'isChild' => true,
671 'value' => 'edd_cart_products',
672 'label' => __( 'Products in Cart', 'optin' ),
673 'pro' => true,
674 ),
675 array(
676 'isChild' => true,
677 'value' => 'edd_cart_products_num',
678 'label' => __( 'Number of Products in Cart', 'optin' ),
679 'pro' => true,
680 'condition' => 'math',
681 'valueField' => 'math',
682 ),
683 array(
684 'isChild' => true,
685 'value' => 'edd_cart_value$',
686 'label' => __( 'Total Cart Amount', 'optin' ),
687 'pro' => true,
688 'isCurrency' => true,
689 'valueField' => 'math',
690 ),
691 array(
692 'isChild' => true,
693 'value' => 'edd_cart_nocond',
694 'label' => __( 'Cart Page', 'optin' ),
695 'pro' => true,
696 'condition' => 'none',
697 ),
698 array(
699 'isChild' => true,
700 'value' => 'edd_thank_nocond',
701 'label' => __( 'Thank You Page', 'optin' ),
702 'pro' => true,
703 'condition' => 'none',
704 ),
705 );
706
707 return $res;
708 }
709
710 /**
711 * Get values for a rule.
712 *
713 * @param string $type rule type.
714 * @param string $search search query.
715 * @param mixed $exclude exclude terms.
716 * @return array
717 */
718 public static function get_values( $type, $search, $exclude ) {
719
720 if ( Utils::check_prefix( $type, 'cpt_post:', true ) ) {
721 return self::get_posts( $type, $search, $exclude );
722 }
723
724 if ( Utils::check_prefix( $type, 'cpt_tax:', true ) ) {
725 $v = explode( '###', $type );
726 return self::get_terms( $v[1], $search, $exclude );
727 }
728
729 switch ( $type ) {
730
731 case 'post_posts':
732 return self::get_posts( 'post', $search, $exclude );
733
734 case 'post_cat':
735 return self::get_terms( 'category', $search, $exclude );
736
737 case 'post_tag':
738 return self::get_terms( 'post_tag', $search, $exclude );
739
740 case 'page':
741 case 'parent_page':
742 return self::get_posts( 'page', $search, $exclude );
743
744 case 'post_type':
745 return self::get_post_types( $search, $exclude );
746
747 case 'author':
748 return self::get_users( $search, $exclude );
749
750 case 'woo_cart_products':
751 case 'woo_single_product':
752 return self::get_woo_products( $search, $exclude );
753
754 case 'woo_cats':
755 return self::get_terms( 'product_cat', $search, $exclude );
756
757 case 'woo_tags':
758 return self::get_terms( 'product_tag', $search, $exclude );
759
760 case 'edd_single_product':
761 case 'edd_cart_products':
762 return self::get_edd_products( $search, $exclude );
763
764 case 'edd_cats':
765 return self::get_terms( 'download_category', $search, $exclude );
766
767 case 'edd_tags':
768 return self::get_terms( 'download_tag', $search, $exclude );
769
770 default:
771 return array();
772 }
773 }
774
775 /**
776 * Get WooCommerce products
777 *
778 * @param string $search search query.
779 * @param array $exclude exclude terms.
780 * @return array
781 */
782 public static function get_woo_products( $search, $exclude ) {
783 $res = array();
784
785 if ( function_exists( 'wc_get_products' ) ) {
786 $products = wc_get_products(
787 array(
788 'exclude' => $exclude, //phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude
789 's' => $search,
790 'limit' => ! empty( $search ) ? -1 : 10,
791 'status' => 'publish',
792 )
793 );
794
795 if ( ! empty( $products ) ) {
796 foreach ( $products as $product ) {
797 $res[] = array(
798 'value' => $product->get_id(),
799 'label' => $product->get_name(),
800 );
801 }
802 }
803 }
804
805 return $res;
806 }
807
808 /**
809 * Get EDD products.
810 *
811 * @param string $search search query.
812 * @param mixed $exclude exclude terms.
813 * @return array
814 */
815 public static function get_edd_products( $search, $exclude ) {
816 $res = array();
817
818 $products = get_posts(
819 array(
820 'post_type' => 'download',
821 'posts_per_page' => ! empty( $search ) ? -1 : 10,
822 'post_status' => 'publish',
823 's' => $search,
824 'exclude' => $exclude, //phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude
825 )
826 );
827
828 if ( ! empty( $products ) ) {
829 foreach ( $products as $product ) {
830 $res[] = array(
831 'value' => $product->ID,
832 'label' => $product->post_title,
833 );
834 }
835 }
836
837 return $res;
838 }
839
840 /**
841 * Get terms
842 *
843 * @param string $tax taxonomy type.
844 * @param string $search search query.
845 * @param mixed $exclude exclude terms.
846 * @return array
847 */
848 public static function get_terms( $tax, $search, $exclude ) {
849 $res = array();
850
851 $terms = get_terms(
852 array(
853 'taxonomy' => $tax,
854 'hide_empty' => false,
855 'exclude' => $exclude, //phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude
856 'search' => $search,
857 'number' => ! empty( $search ) ? 0 : 10,
858 'orderby' => 'name',
859 'order' => 'ASC',
860 )
861 );
862
863 if ( ! empty( $terms ) ) {
864 foreach ( $terms as $term ) {
865 $res[] = array(
866 'label' => $term->name,
867 'value' => $term->term_id,
868 );
869 }
870 }
871
872 return $res;
873 }
874
875 /**
876 * Get posts by post type.
877 *
878 * @param string $post_type post type.
879 * @param string $search search query.
880 * @param mixed $exclude exclude terms.
881 * @return array
882 */
883 public static function get_posts( $post_type, $search, $exclude ) {
884 $res = array();
885
886 $posts = get_posts(
887 array(
888 'post_type' => $post_type,
889 's' => $search,
890 'post__not_in' => $exclude, //phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in
891 'posts_per_page' => ! empty( $search ) ? -1 : 10,
892 'status' => 'publish',
893 )
894 );
895
896 if ( ! empty( $posts ) ) {
897 foreach ( $posts as $post ) {
898 $res[] = array(
899 'label' => $post->post_title,
900 'value' => $post->ID,
901 );
902 }
903 }
904
905 return $res;
906 }
907
908 /**
909 * Get users.
910 *
911 * @param string $search search query.
912 * @param mixed $exclude exclude terms.
913 * @return array
914 */
915 public static function get_users( $search, $exclude ) {
916 $res = array();
917
918 $users = get_users(
919 array(
920 'search' => '*' . $search . '*',
921 'exclude' => $exclude,
922 )
923 );
924
925 if ( ! empty( $users ) ) {
926 foreach ( $users as $user ) {
927 $res[] = array(
928 'value' => $user->ID,
929 'label' => $user->data->display_name,
930 );
931 }
932 }
933
934 return $res;
935 }
936
937 /**
938 * Get Post types
939 *
940 * @param string $search search query.
941 * @param mixed $exclude exclude terms.
942 * @return array
943 */
944 public static function get_post_types( $search, $exclude ) {
945 $res = array();
946 $exclude = array_merge(
947 $exclude,
948 array(
949 'product',
950 'download',
951 'attachment',
952 'nav_menu_item',
953 'wp_template',
954 'wp_template_part',
955 )
956 );
957
958 $post_types = get_post_types(
959 array(
960 'public' => true,
961 ),
962 'objects'
963 );
964
965 if ( ! empty( $post_types ) ) {
966 foreach ( $post_types as $post_type ) {
967 if ( ! empty( $search ) ) {
968 if ( mb_stripos( $post_type->label, $search ) === false ) {
969 continue;
970 }
971 }
972
973 if ( ! empty( $exclude ) ) {
974 if ( in_array( $post_type->name, $exclude ) ) {
975 continue;
976 }
977 }
978
979 $res[] = array(
980 'value' => $post_type->name,
981 'label' => $post_type->label,
982 'pro' => ! in_array( $post_type->name, self::DEFAULT_POST_TYPES ),
983 );
984 }
985 }
986
987 return $res;
988 }
989
990 /**
991 * Check if EDD product single page
992 *
993 * @return boolean
994 */
995 private static function is_edd_product() {
996 return is_single() && 'download' === get_post_type();
997 }
998
999 /**
1000 * Alternative version using WordPress functions for better integration
1001 *
1002 * @param string $target_path The path to match against.
1003 * @param string $condition The matching condition.
1004 * @param bool $case_sensitive Whether the comparison should be case sensitive.
1005 * @return bool
1006 */
1007 private static function match_url_path( $target_path, $condition = 'equals', $case_sensitive = false ) {
1008 // Derive the current request path from the server environment to avoid
1009 // losing language prefixes (e.g., /en) or sub-paths added by plugins.
1010 // Using home_url() here can strip locale directories when filters are applied.
1011 $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '/';
1012
1013 $current_path = wp_parse_url( $request_uri, PHP_URL_PATH );
1014 $current_path = $current_path ? $current_path : '/';
1015
1016 // If the server/environment collapses language-prefixed home to '/',
1017 // fall back to the site's base path (e.g., '/en') for accurate matching.
1018 $site_base = wp_parse_url( home_url( '/' ), PHP_URL_PATH );
1019 if ( '/' === $current_path && ! empty( $site_base ) && '/' !== $site_base ) {
1020 $current_path = $site_base;
1021 }
1022
1023 // Build a list of path variants to match against:
1024 // 1) The raw request path (preserves language prefixes).
1025 // 2) Optionally, the path with the site's base stripped (for subdir installs).
1026 $paths_to_check = array( $current_path );
1027 if ( ! empty( $site_base ) && '/' !== $site_base && strpos( $current_path, $site_base ) === 0 ) {
1028 $without_base = substr( $current_path, strlen( $site_base ) );
1029 $without_base = '/' . ltrim( $without_base, '/' );
1030 $paths_to_check[] = $without_base;
1031 }
1032
1033 // Note: Do not strip the site's base path using home_url() because multilingual
1034 // plugins often filter it to include language prefixes (e.g., /en). We want to
1035 // preserve the visible URL path exactly as requested.
1036
1037 // Normalize paths: ensure leading slash and decode percent-encoding.
1038 $current_path = rawurldecode( '/' . ltrim( $current_path, '/' ) );
1039 $target_path = rawurldecode( '/' . ltrim( (string) $target_path, '/' ) );
1040
1041 // Remove trailing slash for consistency (except for root).
1042 if ( '/' !== $current_path ) {
1043 $current_path = rtrim( $current_path, '/' );
1044 }
1045 if ( '/' !== $target_path ) {
1046 $target_path = rtrim( $target_path, '/' );
1047 }
1048
1049 if ( class_exists( '\\QM' ) ) {
1050 \QM::debug( 'REQUEST_URI: ' . ( isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '' ) );
1051 \QM::debug( 'Site Base: ' . (string) $site_base );
1052 \QM::debug( 'Current Path Variants: ' . implode( ', ', $paths_to_check ) );
1053 \QM::debug( 'Target Path: ' . $target_path );
1054 }
1055
1056 if ( ! $case_sensitive ) {
1057 $paths_to_check = array_map( 'strtolower', $paths_to_check );
1058 $target_path = strtolower( $target_path );
1059 }
1060
1061 // Split the condition into its positive form plus a negation flag. The
1062 // negation must be applied to the aggregated result of all path variants,
1063 // never per variant -- see the loop below.
1064 $is_negated = str_starts_with( $condition, 'not_' );
1065 $base_condition = $is_negated ? substr( $condition, 4 ) : $condition;
1066
1067 // Helper comparator for a single path, positive conditions only.
1068 $matches = function ( $path ) use ( $base_condition, $target_path ) {
1069 switch ( $base_condition ) {
1070 case 'equals':
1071 return $path === $target_path;
1072 case 'starts_with':
1073 return strpos( $path, $target_path ) === 0;
1074 case 'ends_with':
1075 $target_len = strlen( $target_path );
1076 return substr( $path, -$target_len ) === $target_path;
1077 case 'contains':
1078 return strpos( $path, $target_path ) !== false;
1079 default:
1080 return false;
1081 }
1082 };
1083
1084 // Unknown condition: fail closed for both polarities.
1085 if ( ! in_array( $base_condition, array( 'equals', 'starts_with', 'ends_with', 'contains' ), true ) ) {
1086 return false;
1087 }
1088
1089 // A variant matching is enough for the positive form. For the negated form
1090 // this becomes "no variant matches" (De Morgan), so a stripped variant such
1091 // as '/shop' can no longer satisfy "does not contain /en" while the visible
1092 // URL is '/en/shop'.
1093 $positive_match = false;
1094 foreach ( $paths_to_check as $path_variant ) {
1095 // Normalize variant: ensure leading slash, remove trailing slash (except root).
1096 $path_variant = '/' . ltrim( $path_variant, '/' );
1097 if ( '/' !== $path_variant ) {
1098 $path_variant = rtrim( $path_variant, '/' );
1099 }
1100 if ( $matches( $path_variant ) ) {
1101 $positive_match = true;
1102 break;
1103 }
1104 }
1105
1106 return $is_negated ? ! $positive_match : $positive_match;
1107 }
1108 }
1109