PluginProbe
Filter Everything — WordPress & WooCommerce Filters / trunk
Filter Everything — WordPress & WooCommerce Filters vtrunk
1.9.6 1.9.5 1.9.4 1.9.3 1.9.2.2 1.9.2.1 trunk 1.2.1 1.2.3 1.2.4 1.2.5 1.3.0 1.3.1 1.3.2 1.4.1 1.4.4 1.4.5 1.4.8 1.4.9 1.5.0 1.5.1 1.6.0 1.6.1 1.6.2 1.6.3 All 51 releases
filter-everything / src / wpc-helpers.php

wpc-helpers.php in Filter Everything — WordPress & WooCommerce Filters trunk, at src/wpc-helpers.php

2,709 lines 85.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('ABSPATH')) {
4 exit;
5 }
6
7 use \FilterEverything\Filter\Container;
8 use \FilterEverything\Filter\FilterSet;
9 use \FilterEverything\Filter\FilterFields;
10 use \FilterEverything\Filter\PostMetaNumEntity;
11 use \FilterEverything\Filter\PostDateEntity;
12 use \FilterEverything\Filter\PostMetaDateEntity;
13 /**
14 * Returns user's caps level that allows to use the plugin.
15 * Developers can modify this level via hook 'wpc_plugin_user_caps' ot their own risk.
16 * @return string
17 */
18 function flrt_plugin_user_caps()
19 {
20 return apply_filters('wpc_plugin_user_caps', 'manage_options');
21 }
22
23 function flrt_the_set($set_id = 0)
24 {
25 global $flrt_sets;
26
27 if (function_exists('brizy_load')) {
28 if (!did_action('wp_print_scripts')) {
29 return false;
30 }
31 }
32
33 if ($set_id) {
34 foreach ($flrt_sets as $k => $set) {
35 if ($set['ID'] === $set_id) {
36 unset($flrt_sets[$k]);
37 return $set;
38 }
39 }
40 }
41
42 return array_shift($flrt_sets);
43 }
44
45 function flrt_print_filters_for($hook = '')
46 {
47 global $wp_filter;
48 if (empty($hook) || !isset($wp_filter[$hook]))
49 return;
50 return $wp_filter[$hook];
51 }
52
53 function flrt_is_filter_request()
54 {
55 $wpManager = Container::instance()->getWpManager();
56 return $wpManager->getQueryVar('wpc_is_filter_request');
57 }
58
59 function flrt_include($filename)
60 {
61 $path = flrt_get_path($filename);
62
63 if (file_exists($path)) {
64 include_once($path);
65 }
66 }
67
68 function flrt_get_path($path = '')
69 {
70 return FLRT_PLUGIN_DIR_PATH . ltrim($path, '/');
71 }
72
73 function flrt_ucfirst($text)
74 {
75 if (!is_string($text)) {
76 return $text;
77 }
78 return mb_strtoupper(mb_substr($text, 0, 1)) . mb_substr($text, 1);
79 }
80
81 function flrt_lcfirst( $text )
82 {
83 if( ! is_string( $text ) ){
84 return $text;
85 }
86 return mb_strtolower( mb_substr( $text, 0, 1 ) ) . mb_substr( $text, 1 );
87 }
88
89 function flrt_sanitize_tooltip($var )
90 {
91 return htmlspecialchars(
92 wp_kses(
93 html_entity_decode($var),
94 array(
95 'br' => array(),
96 'em' => array(),
97 'strong' => array(),
98 'small' => array(),
99 'span' => array(),
100 'ul' => array(),
101 'li' => array(),
102 'ol' => array(),
103 'p' => array(),
104 'a' => array('href' => true)
105 )
106 )
107 );
108 }
109
110 function flrt_help_tip($tip, $allow_html = false)
111 {
112 if ($allow_html) {
113 $tip = flrt_sanitize_tooltip($tip);
114 } else {
115 $tip = esc_attr($tip);
116 }
117
118 return '<span class="wpc-help-tip" data-tip="' . $tip . '"></span>';
119 }
120
121 function flrt_tooltip($attr)
122 {
123 if (!isset($attr['tooltip']) || !$attr['tooltip']) {
124 return false;
125 }
126
127 return flrt_help_tip($attr['tooltip'], true);
128 }
129
130 function flrt_field_instructions($attr)
131 {
132 if (!isset($attr['instructions']) || !$attr['instructions']) {
133 return false;
134 }
135 $instructions = wp_kses(
136 $attr['instructions'],
137 array(
138 'br' => array(),
139 'span' => array('class' => true),
140 'strong' => array(),
141 'a' => array('href' => true, 'title' => true)
142 )
143 );
144 return '<p class="wpc-field-description">' . $instructions . '</p>';
145 }
146
147 function flrt_add_query_arg(...$args)
148 {
149 if (is_array($args[0])) {
150 if (count($args) < 2 || false === $args[1]) {
151 $uri = $_SERVER['REQUEST_URI'];
152 } else {
153 $uri = $args[1];
154 }
155 } else {
156 if (count($args) < 3 || false === $args[2]) {
157 $uri = $_SERVER['REQUEST_URI'];
158 } else {
159 $uri = $args[2];
160 }
161 }
162
163 $frag = strstr($uri, '#');
164 if ($frag) {
165 $uri = substr($uri, 0, -strlen($frag));
166 } else {
167 $frag = '';
168 }
169
170 if (0 === stripos($uri, 'http://')) {
171 $protocol = 'http://';
172 $uri = substr($uri, 7);
173 } elseif (0 === stripos($uri, 'https://')) {
174 $protocol = 'https://';
175 $uri = substr($uri, 8);
176 } else {
177 $protocol = '';
178 }
179
180 if (strpos($uri, '?') !== false) {
181 list($base, $query) = explode('?', $uri, 2);
182 $base .= '?';
183 } elseif ($protocol || strpos($uri, '=') === false) {
184 $base = $uri . '?';
185 $query = '';
186 } else {
187 $base = '';
188 $query = $uri;
189 }
190
191 wp_parse_str($query, $qs);
192
193 if (is_array($args[0])) {
194 foreach ($args[0] as $k => $v) {
195 $qs[$k] = $v;
196 }
197 } else {
198 $qs[$args[0]] = $args[1];
199 }
200
201 foreach ($qs as $k => $v) {
202 if (false === $v) {
203 unset($qs[$k]);
204 }
205 }
206
207 $ret = build_query($qs);
208 $ret = trim($ret, '?');
209 $ret = preg_replace('#=(&|$)#', '$1', $ret);
210 $ret = $protocol . $base . $ret . $frag;
211 $ret = rtrim($ret, '?');
212 return $ret;
213 }
214
215 /**
216 * @param $terms array
217 * @param $keys array
218 *
219 * @return array Array of objects with required keys
220 */
221 function flrt_extract_objects_vars($terms, $keys = [])
222 {
223 $required = [];
224
225 foreach ($terms as $i => $term) {
226 $new_object = new \stdClass();
227
228 foreach ($keys as $key) {
229 if (isset($term->$key)) {
230 $new_object->$key = $term->$key;
231 $required[$term->term_id] = $new_object;
232 }
233 }
234 }
235
236 return $required;
237 }
238
239 add_filter('wpc_check_broken_query_vars', function ($query_vars, $query) {
240 if (is_admin()) {
241 return $query_vars;
242 }
243
244 $detector_class = 'FilterEverything\\Filter\\WP_Query_Source_Detector';
245 $is_allowed_method = 'is_allowed';
246 $source_var = 'flrt_detected_source';
247
248 if (defined('FLRT_FILTERS_PRO')) {
249 if (defined('FLRT_PRO_BUILDER_KEY')) {
250 $builder_key = apply_filters('wpc_builder_key_pro', $query->get($source_var), constant('FLRT_PRO_BUILDER_KEY'));
251
252 if ($detector_class::$is_allowed_method($builder_key)) {
253 return $query_vars;
254 }
255 }
256 }
257
258 if (!defined('FLRT_FILTERS_PRO')) {
259 $builder_key = apply_filters('wpc_builder_key', $query->get($source_var), $detector_class::$builder_key);
260
261 if ($detector_class::$is_allowed_method($builder_key)) {
262 return $query_vars;
263 }
264 }
265
266 return [];
267 }, 10, 2);
268
269 add_filter('wpc_builder_key', function ($source, $builder_id) {
270 return (int) sprintf("%u", crc32($source . $builder_id));
271 }, 10, 2);
272
273 /**
274 * Compatibility with membership / access-control plugins.
275 *
276 * Such plugins (e.g. Ultimate Membership Pro — indeed-membership-pro) inject
277 * post__in / post__not_in into front-end queries to hide protected content from
278 * non-authorised or logged-out visitors. That flips is_post__in / is_post__not_in
279 * in the query-identity hash FE uses to match a Filter Set to a page query — so a
280 * page that filters correctly for the (unrestricted) admin who saved the set stops
281 * matching for restricted visitors, and filtering silently does nothing for them.
282 *
283 * We normalise those two flags back to their save-time value (false), but ONLY when
284 * such a plugin is actually active. This keeps every saved query hash untouched on
285 * sites that don't have this conflict. The per-page order counter still distinguishes
286 * genuinely different same-shape queries, so filtering stays correct.
287 */
288 add_filter('wpc_check_broken_query_vars', function ($query_vars, $query) {
289 // Add markers of other post-hiding plugins here if needed; overridable via the filter.
290 $context_hides_posts = defined('IHC_PATH') // Ultimate Membership Pro
291 || defined('WCB2B_PLUGIN_FILE'); // WooCommerce B2B — hides "unallowed" products from guests/other groups via post__not_in
292
293 if ( ! apply_filters('wpc_context_hides_posts', $context_hides_posts, $query) ) {
294 return $query_vars;
295 }
296
297 // Only touch queries FE still considers filterable (its own callback empties the
298 // rest at priority 10); never re-populate an emptied set.
299 if ( is_array($query_vars) && array_key_exists('is_post__not_in', $query_vars) ) {
300 $query_vars['is_post__in'] = false;
301 $query_vars['is_post__not_in'] = false;
302 }
303
304 return $query_vars;
305 }, 20, 2);
306
307 function flrt_remove_level_array($array)
308 {
309 /**
310 * @feature maybe rewrite this full of shame code
311 */
312 if (!is_array($array)) {
313 return [];
314 }
315
316 $flatten = [];
317
318 array_map(function ($a) use (&$flatten) {
319 if (is_array($a)) {
320 $flatten = array_merge($flatten, $a);
321 }
322 },
323 $array);
324
325 return $flatten;
326 }
327
328 add_filter('wpc_check_errors_ids', function ($error_ids, $query) {
329 $source_key = 'flrt_detected_source';
330 if (empty($query->query_vars[$source_key])) {
331 return $error_ids;
332 }
333
334 $source = $query->query_vars[$source_key];
335 $detector_class = 'FilterEverything\\Filter\\WP_Query_Source_Detector';
336 $is_allowed_method = 'is_allowed';
337
338 if (defined('FLRT_FILTERS_PRO')) {
339 if (defined('FLRT_PRO_BUILDER_KEY')) {
340 $builder_key = apply_filters('wpc_builder_key_pro', $source, constant('FLRT_PRO_BUILDER_KEY'));
341 if ($detector_class::$is_allowed_method($builder_key)) {
342 return $error_ids;
343 }
344 }
345 }
346
347 if (!defined('FLRT_FILTERS_PRO')) {
348 $builder_key = apply_filters('wpc_builder_key', $source, $detector_class::$builder_key);
349
350 if ($detector_class::$is_allowed_method($builder_key)) {
351 return $error_ids;
352 }
353 }
354
355 return [];
356 }, 10, 2);
357
358 function flrt_get_forbidden_prefixes()
359 {
360 //@todo it seems all existing tax prefixes should be there
361 // All them actual only when permalinks off
362 $forbidden_prefixes = ['srch'];
363 $permalinksEnabled = defined('FLRT_PERMALINKS_ENABLED') ? FLRT_PERMALINKS_ENABLED : false;
364 if (!$permalinksEnabled) {
365 $forbidden_prefixes = array_merge($forbidden_prefixes, array('cat', 'tag', 'page', 'author'));
366 }
367
368 if (flrt_wpml_active()) {
369 $wpml_url_format = apply_filters('wpml_setting', 0, 'language_negotiation_type');
370 if ($wpml_url_format === '3') {
371 $forbidden_prefixes[] = 'lang';
372 }
373 }
374
375 return apply_filters('wpc_forbidden_prefixes', $forbidden_prefixes);
376 }
377
378 function flrt_get_forbidden_meta_keys()
379 {
380 $forbidden_meta_keys = array('wpc_filter_set_post_type', 'wpc_seo_rule_post_type');
381 return apply_filters('wpc_forbidden_meta_keys', $forbidden_meta_keys);
382 }
383
384 function flrt_array_contains_duplicate($array)
385 {
386 return count($array) != count(array_unique($array));
387 }
388
389 function flrt_maybe_hide_row($atts)
390 {
391 if ($atts['type'] === 'Hidden') {
392 echo ' style="display:none;"';
393 }
394 }
395
396 function flrt_filter_row_class($field_atts)
397 {
398 $classes = ['wpc-filter-tr'];
399
400 if (isset($field_atts['class'])) {
401 $classes[] = $field_atts['class'] . '-tr';
402 }
403
404 if (isset($field_atts['additional_class'])) {
405 $classes[] = $field_atts['additional_class'];
406 }
407
408 return implode(" ", $classes);
409 }
410
411
412 function flrt_include_admin_view($path, $args = [])
413 {
414 $templateManager = Container::instance()->getTemplateManager();
415 $templateManager->includeAdminView($path, $args);
416 }
417
418 function flrt_include_front_view($path, $args = [])
419 {
420 $templateManager = Container::instance()->getTemplateManager();
421 $templateManager->includeFrontView($path, $args);
422 }
423
424 function flrt_create_filters_nonce()
425 {
426 return FilterSet::createNonce();
427 }
428
429 function flrt_get_filter_fields_mapping()
430 {
431 return Container::instance()->getFilterFieldsService()->getFieldsMapping();
432 }
433
434 function flrt_get_configured_filters($post_id)
435 {
436 $filterFields = Container::instance()->getFilterFieldsService();
437 return $filterFields->getFiltersInputs($post_id);
438 }
439
440 function flrt_get_filter_view_name($view_key)
441 {
442 $view_options = FilterFields::getViewOptions();
443 if (isset($view_options[$view_key])) {
444 return esc_html($view_options[$view_key]);
445 }
446
447 return esc_html($view_key);
448 }
449
450 function flrt_get_filter_entity_name($entity_key)
451 {
452 $em = Container::instance()->getEntityManager();
453 $entities = $em->getPossibleEntities();
454
455 foreach ($entities as $key => $entity_array) {
456 if (isset($entity_array['entities'][$entity_key])) {
457 return esc_html($entity_array['entities'][$entity_key]);
458 }
459 }
460
461 if ($entity_key === 'post_meta_exists' && !defined('FLRT_FILTERS_PRO')) {
462 return esc_html__('Available in PRO', 'filter-everything');
463 }
464
465 return esc_html($entity_key);
466 }
467
468 function flrt_get_set_settings_fields($post_id)
469 {
470 $filterSet = Container::instance()->getFilterSetService();
471 return $filterSet->getSettingsTypeFields($post_id);
472 }
473
474 function flrt_get_set_settings_location_fields($post_id)
475 {
476 $filterSet = Container::instance()->getFilterSetService();
477 return $filterSet->getSettingsLocationTypeFields($post_id);
478 }
479
480 function flrt_render_input($atts)
481 {
482 $className = isset($atts['type']) ? '\FilterEverything\Filter\\' . $atts['type'] : '\FilterEverything\Filter\Text';
483
484 if (class_exists($className)) {
485 $input = new $className($atts);
486 return $input->render();
487 }
488
489 return false;
490 }
491
492 function flrt_extract_vars(&$array, $keys)
493 {
494 $r = [];
495 foreach ($keys as $key) {
496 $var = flrt_extract_var($array, $key);
497 if ($var) {
498 $r[$key] = $var;
499 }
500 }
501 return $r;
502 }
503
504 function flrt_extract_var(&$array, $key, $default = null)
505 {
506 // check if exists
507 // - uses array_key_exists to extract NULL values (isset will fail)
508 if (is_array($array) && array_key_exists($key, $array)) {
509 $v = $array[$key];
510 unset($array[$key]);
511 return $v;
512 }
513 return $default;
514 }
515
516 function flrt_get_empty_filter($set_id)
517 {
518 $filterFields = Container::instance()->getFilterFieldsService();
519 return $filterFields->getEmptyFilterObject($set_id);
520 }
521
522 function flrt_excluded_taxonomies()
523 {
524 $excluded_taxonomies = array(
525 'nav_menu',
526 'link_category',
527 'post_format',
528 'template_category',
529 'element_category',
530 'fusion_tb_category',
531 'slide-page',
532 'elementor_font_type',
533 'post_translations',
534 'term_language',
535 'term_translations',
536 'wp_theme',
537 'wp_template_part_area',
538 'wp_pattern_category',
539 'elementor_library_type',
540 'elementor_library_category',
541 );
542
543 return apply_filters('wpc_excluded_taxonomies', $excluded_taxonomies);
544 }
545
546 function flrt_force_non_unique_slug($notNull, $originalSlug)
547 {
548 return $originalSlug;
549 }
550
551 function flrt_redirect_to_error($post_id, $errors)
552 {
553 $redirect = get_edit_post_link($post_id, 'url');
554 $error_code = 20; // Default error code
555
556 if (!empty($errors) && is_array($errors)) {
557 $error_code = reset($errors);
558 }
559
560 $redirect = add_query_arg('message', $error_code, $redirect);
561 wp_redirect($redirect);
562 exit;
563 }
564
565 function flrt_sanitize_int($var)
566 {
567 return preg_replace('/[^\d]+/', '', $var);
568 }
569
570 function flrt_range_input_name($slug, $edge = 'min', $type = 'num')
571 {
572 if ($type === 'date') {
573 return PostDateEntity::inputName($slug, $edge);
574 }
575
576 return PostMetaNumEntity::inputName($slug, $edge);
577 }
578
579 function flrt_query_string_form_fields($values = null, $exclude = [], $current_key = '', $return = false)
580 {
581
582 $filter_everything_exclude = array_keys(apply_filters('wpc_unnecessary_get_parameters', []));
583 $exclude = array_merge($exclude, $filter_everything_exclude);
584
585 if (is_null($values)) {
586 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
587 $values = Container::instance()->getTheGet();
588 // For compatibility with some Nginx configurations
589 unset($values['q']);
590 } elseif (is_string($values)) {
591 $url_parts = wp_parse_url($values);
592 $values = [];
593
594 if (!empty($url_parts['query'])) {
595 // This is to preserve full-stops, pluses and spaces in the query string when ran through parse_str.
596 $replace_chars = array(
597 '.' => '{dot}',
598 '+' => '{plus}',
599 );
600
601 $query_string = str_replace(array_keys($replace_chars), array_values($replace_chars), $url_parts['query']);
602
603 // Parse the string.
604 parse_str($query_string, $parsed_query_string);
605
606 // Convert the full-stops, pluses and spaces back and add to values array.
607 foreach ($parsed_query_string as $key => $value) {
608 $new_key = str_replace(array_values($replace_chars), array_keys($replace_chars), $key);
609 $new_value = str_replace(array_values($replace_chars), array_keys($replace_chars), $value);
610 $values[$new_key] = $new_value;
611 }
612 }
613 }
614 $html = '';
615
616 foreach ($values as $key => $value) {
617 if (in_array($key, $exclude, true)) {
618 continue;
619 }
620 if ($current_key) {
621 $key = $current_key . '[' . $key . ']';
622 }
623 if (is_array($value)) {
624 $html .= flrt_query_string_form_fields($value, $exclude, $key, true);
625 } else {
626 $html .= '<input type="hidden" name="' . esc_attr($key) . '" value="' . esc_attr(wp_unslash($value)) . '" />';
627 }
628 }
629
630 if ($return) {
631 return $html;
632 }
633
634 echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
635 }
636
637 function flrt_get_query_string_parameters()
638 {
639 $container = Container::instance();
640 $get = $container->getTheGet();
641 $post = $container->getThePost();
642
643 // For compatibility with some Nginx configurations
644 unset($get['q']);
645
646 if (isset($post['flrt_ajax_link'])) {
647 $parts = parse_url($post['flrt_ajax_link']);
648 if (isset($parts['query'])) {
649 parse_str($parts['query'], $output);
650 return $output;
651 }
652 }
653
654 return $get;
655 }
656
657
658 function flrt_count($term, $show = 'yes')
659 {
660 _deprecated_function('flrt_count', '1.7.6', 'flrt_filter_count()');
661 flrt_filter_count($term, $show);
662 }
663
664 if (!function_exists('flrt_filter_count')) {
665 function flrt_filter_count($term, $show = 'yes')
666 {
667 if ($show === 'yes') :
668 echo flrt_filter_get_count($term);
669 endif;
670 }
671 }
672
673 /**
674 * @param $term
675 * @return string
676 * @since 1.0.5
677 */
678 function flrt_filter_get_count($term)
679 {
680 return '<span class="wpc-term-count"><span class="wpc-term-count-brackets-open">(</span><span class="wpc-term-count-value">' . esc_html($term->cross_count) . '</span><span class="wpc-term-count-brackets-close">)</span></span>&nbsp;';
681 }
682
683 if (!function_exists('flrt_spinner_html')) {
684 function flrt_spinner_html()
685 {
686 return '<div class="wpc-spinner"></div>';
687 }
688 }
689
690 function flrt_filters_widget_content_class($setId)
691 {
692 if (isset($_COOKIE[FLRT_OPEN_CLOSE_BUTTON_COOKIE_NAME])) {
693
694 if ($_COOKIE[FLRT_OPEN_CLOSE_BUTTON_COOKIE_NAME] === $setId) {
695 return ' wpc-opened';
696 } else {
697 return ' wpc-closed';
698 }
699 }
700 }
701
702 function flrt_filters_button($setId = 0, $class = '', $wrap_class = '')
703 {
704 /**
705 * @feature add nice wrapper to this functions to allow users put it into themes.
706 */
707 $classes = [];
708 $sets = [];
709 $wpManager = \FilterEverything\Filter\Container::instance()->getWpManager();
710 $templateManager = \FilterEverything\Filter\Container::instance()->getTemplateManager();
711
712 $draft_sets = $wpManager->getQueryVar('wpc_page_related_set_ids');
713
714 if ( ! is_array( $draft_sets ) ) {
715 $draft_sets = [];
716 }
717
718 foreach ($draft_sets as $set) {
719 if (isset($set['show_on_the_page']) && $set['show_on_the_page']) {
720 $sets[] = $set;
721 }
722 }
723
724 if (!$setId && isset($sets[0]['ID'])) {
725 $setId = $sets[0]['ID'];
726 }
727
728 foreach ($sets as $set) {
729 if ($set['ID'] === $setId) {
730 $theSet = $set;
731 break;
732 }
733 }
734
735 if (flrt_get_option('mobile_filter_settings') === 'show_bottom_widget') {
736 $classes[] = 'wpc-filters-open-widget';
737 } else {
738 $classes[] = 'wpc-open-close-filters-button';
739 }
740
741 if ($class) {
742 $classes[] = trim($class);
743 }
744
745 $attrClass = implode(" ", $classes);
746 $setId = preg_replace('/[^\d]+/', '', $setId);
747
748 $wpc_found_posts = NULL;
749 $srch = isset($_GET['srch']) ? filter_input(INPUT_GET, 'srch', FILTER_SANITIZE_SPECIAL_CHARS) : '';
750 $all = false;
751 if ($srch) {
752 $all = true;
753 }
754
755 if ($wpManager->getQueryVar('wpc_is_filter_request') || $srch) {
756 $wpc_found_posts = flrt_posts_found_quantity($setId, $all);
757 }
758
759 $button_error = [];
760
761 if (current_user_can(flrt_plugin_user_caps())) {
762 $button_error = [
763 'filter_set_error' => esc_html__('There are no filter sets on this page.', 'filter-everything'),
764 'filter_widget_error' => esc_html__('There is no filter widget on this page.', 'filter-everything'),
765 ];
766
767 if (!empty($theSet)) {
768 unset($button_error['filter_set_error']);
769 }
770 }
771
772 $templateManager->includeFrontView('filters-button', array('wpc_found_posts' => $wpc_found_posts, 'class' => $attrClass, 'set_id' => $setId, 'wrap_class' => $wrap_class, 'button_error' => $button_error));
773 }
774
775 function flrt_posts_found($setid = 0, $all = false, $hidden = false )
776 {
777 $templateManager = \FilterEverything\Filter\Container::instance()->getTemplateManager();
778 $fss = \FilterEverything\Filter\Container::instance()->getFilterSetService();
779
780 if (isset($_GET['srch']) && $_GET['srch']) {
781 $all = true;
782 }
783 $count = flrt_posts_found_quantity($setid, $all);
784
785 $theSet = $fss->getSet($setid);
786 $postType = isset($theSet['post_type']['value']) ? $theSet['post_type']['value'] : '';
787
788 $obj = get_post_type_object($postType);
789 $pluralLabel = isset($obj->label) ? apply_filters('wpc_label_singular_posts_found_msg', $obj->label) : esc_html__('items', 'filter-everything');
790 $singularLabel = isset($obj->labels->singular_name) ? apply_filters('wpc_label_plural_posts_found_msg', $obj->labels->singular_name) : esc_html__('item', 'filter-everything');
791
792 $templateManager->includeFrontView('posts-found', array('posts_found_count' => $count, 'singular_label' => $singularLabel, 'plural_label' => $pluralLabel, 'hidden' => $hidden));
793 }
794
795 function flrt_get_option($key, $default = false)
796 {
797 $settings = get_option('wpc_filter_settings');
798
799 if (isset($settings[$key])) {
800 return apply_filters('wpc_get_option', $settings[$key], $key);
801 }
802
803 if ($default) {
804 return $default;
805 }
806
807 return false;
808
809 }
810
811 function flrt_remove_option($key)
812 {
813 $settings = get_option('wpc_filter_settings');
814
815 if (isset($settings[$key]) && $settings[$key]) {
816 unset($settings[$key]);
817 return update_option('wpc_filter_settings', $settings);
818 }
819
820 return false;
821 }
822
823 function flrt_change_option($option_key, $key, $value, $autoload = false)
824 {
825 $settings = get_option($option_key);
826
827 if ($settings[$key] === $value) {
828 return true;
829 }
830
831 $settings = is_array($settings) ? $settings : [];
832 $settings[$key] = $value;
833
834 return update_option($option_key, $settings, $autoload);
835 }
836
837 function flrt_get_experimental_option($key, $default = false)
838 {
839 /**
840 * @todo This should be rewritten
841 */
842 $settings = get_option('wpc_filter_experimental');
843
844 if (isset($settings[$key])) {
845 return apply_filters('wpc_get_option', $settings[$key], $key);
846 }
847
848 if ($default !== false) {
849 return apply_filters('wpc_get_option', $default, $key);
850 }
851
852 return apply_filters('wpc_get_option', false, $key);
853
854 }
855
856 function flrt_get_status_css_class($id, $cookieName, $classes = ['opened' => 'wpc-opened', 'closed' => 'wpc-closed'])
857 {
858
859 if (isset($_COOKIE[$cookieName])) {
860 $openediDs = explode(",", $_COOKIE[$cookieName]);
861
862 if (in_array($id, $openediDs)) {
863 return $classes['opened'];
864 } elseif (in_array(-$id, $openediDs)) {
865 return $classes['closed'];
866 } else {
867 return '';
868 }
869 }
870
871 return '';
872 }
873
874 if (!function_exists('flrt_filter_header')) {
875 function flrt_filter_header($filter, $terms)
876 {
877 $openButton = ($filter['collapse'] === 'yes') ? '<button><span class="wpc-wrap-icons">' : '';
878 $closeButton = ($filter['collapse'] === 'yes') ? '</span><span class="wpc-open-icon"></span></button>' : '';
879 $tooltip = '';
880
881 if ($filter['collapse'] === 'yes' && !empty($filter['values']) && !empty($terms)) {
882 $selected = [];
883 $list = '<span class="wpc-filter-selected-values">&mdash; ';
884 // Does not work for numeric filters
885 // @todo
886 foreach ($terms as $id => $term_object) {
887
888 if (in_array($term_object->slug, $filter['values'])) {
889 $selected[] = $term_object->name;
890 }
891 }
892
893 $list .= implode(", ", $selected) . '</span>';
894
895 $closeButton = $list . $closeButton;
896 }
897
898 if (isset($filter['tooltip']) && $filter['tooltip']) {
899 $tooltip = flrt_help_tip($filter['tooltip'], true);
900 }
901
902 $filter_label = apply_filters('wpc_filter_title', $filter['label'], $filter);
903
904 ?>
905 <div class="wpc-filter-header">
906 <div class="widget-title wpc-filter-title"><?php
907 echo $openButton . esc_html($filter_label) . $tooltip . $closeButton;
908 ?></div></div><?php
909 }
910 }
911
912 function flrt_filter_class($filter, $default_classes = [], $terms = [], $args = [])
913 {
914 $digits = [];
915 $length = 1;
916 $isHideEmpty = (!empty($args['hide_empty']) && $args['hide_empty'] === 'yes');
917 $isParentFilter = flrtParentFilter($filter);
918 $count_terms_greater_than_zero = 0;
919 $total_terms_count = 0;
920
921 if ( ! empty( $terms ) ) {
922 foreach ( $terms as $term ) {
923 $digits[] = $term->cross_count;
924
925 if ( $term->cross_count > 0 ) {
926 $total_terms_count += $term->cross_count;
927 }
928
929 $is_visible = ! $isParentFilter || ( isset( $term->show_with_parent ) && $term->show_with_parent === true ) || !isset($term->show_with_parent);
930 $has_results = ! $isHideEmpty || $term->cross_count > 0;
931
932 if ( $is_visible && $has_results ) {
933 $count_terms_greater_than_zero++;
934 }
935 }
936 }
937
938 if (!empty($digits)) {
939 $length = strlen((string)max($digits));
940 }
941
942 $classes = array(
943 'wpc-filters-section',
944 'wpc-filters-section-' . esc_attr($filter['ID']),
945 'wpc-filter-' . esc_attr($filter['e_name']),
946 'wpc-filter-' . esc_attr($filter['entity']),
947 'wpc-filter-layout-' . esc_attr($filter['view']),
948 'wpc-counter-length-' . esc_attr($length)
949 );
950
951 if ((isset($args['hide_empty_filter']) && $args['hide_empty_filter'] === 'yes')) {
952 if ($total_terms_count <= 0) {
953
954 $classes[] = 'wpc-filters-section-0';
955 }
956 }
957
958 if (
959 $isParentFilter
960 && (!empty($filter['hide_until_parent']) && $filter['hide_until_parent'] === 'yes')
961 && (isset($args['hide_until_parent_class']) && $filter['hide_until_parent_class'] !== true)
962 ) {
963 $classes[] = 'wpc-filters-hide-until-parent-section';
964 }
965
966 if (isset($filter['values']) && !empty($filter['values'])) {
967 $classes[] = 'wpc-filter-has-selected';
968 }
969
970 // Set correct more/less class for specific views
971 if (in_array($filter['view'], ['checkboxes', 'radio', 'labels'])) {
972 if (isset($filter['more_less']) && $filter['more_less'] === 'yes') {
973
974 $classes[] = 'wpc-filter-more-less';
975
976 if (in_array($filter['ID'], flrt_more_less_opened())) {
977 $classes[] = 'wpc-show-more-reverse';
978 }
979
980 $classes[] = flrt_get_status_css_class($filter['ID'], FLRT_MORELESS_COOKIE_NAME, ['opened' => 'wpc-show-more', 'closed' => 'wpc-show-less']);
981
982 // We have to count only first-level terms if hierarchy is enabled
983 if (isset($filter['hierarchy']) && $filter['hierarchy'] === 'yes') {
984 if (!empty($terms)) {
985 $only_parents = [];
986 foreach ($terms as $term_id => $term) {
987 if ($term->parent == 0) {
988 $only_parents[$term_id] = $term;
989 }
990 }
991
992 $terms = $only_parents;
993 unset($only_parents);
994 }
995 }
996 if (count($terms) <= flrt_more_less_count() || ($args['hide'] && !$args['use_apply_button'])) {
997 $classes[] = 'wpc-filter-few-terms';
998 }
999
1000 if ($count_terms_greater_than_zero <= flrt_more_less_count()) {
1001 $classes[] = 'wpc-filter-few-terms';
1002 }
1003
1004
1005
1006 } else {
1007 $classes[] = 'wpc-filter-full-height';
1008 }
1009 }
1010
1011 if (isset($filter['collapse']) && $filter['collapse'] === 'yes') {
1012 if (in_array($filter['ID'], flrt_folding_opened())) {
1013 $classes[] = 'wpc-filter-collapsible-reverse';
1014 }
1015
1016 $classes[] = 'wpc-filter-collapsible';
1017
1018 $classes[] = flrt_get_status_css_class($filter['ID'], FLRT_FOLDING_COOKIE_NAME);
1019 }
1020
1021 if (in_array($filter['ID'], flrt_hierarchy_opened())) {
1022 if (isset($filter['hierarchy']) && $filter['hierarchy'] === 'yes') {
1023 $classes[] = 'wpc-filter-hierarchy-reverse';
1024 }
1025 }
1026
1027 if (in_array($filter['entity'], ['post_date', 'post_meta_date'])) {
1028 $classes[] = 'wpc-datetype-' . $filter['date_type'];
1029 }
1030
1031 if (!empty($default_classes)) {
1032 $classes = array_merge($classes, $default_classes);
1033 }
1034
1035 $classes[] = 'wpc-filter-terms-count-' . count($terms);
1036
1037 $classes = apply_filters('wpc_filter_classes', $classes, $filter, $default_classes, $terms, $args);
1038
1039 return implode(" ", $classes);
1040 }
1041
1042 function flrt_filter_content_class($filter, $default_classes = [])
1043 {
1044 $classes = array(
1045 'wpc-filter-content'
1046 );
1047
1048 if (isset($filter['e_name'])) {
1049 $classes[] = 'wpc-filter-' . $filter['e_name'];
1050 }
1051
1052 if (isset($filter['hierarchy']) && $filter['hierarchy'] === 'yes') {
1053 $classes[] = 'wpc-filter-has-hierarchy';
1054 }
1055
1056 if (!empty($default_classes)) {
1057 $classes = array_merge($classes, $default_classes);
1058 }
1059
1060 $classes = apply_filters('wpc_filter_content_classes', $classes, $default_classes);
1061
1062 return implode(" ", $classes);
1063
1064 }
1065
1066 if (!function_exists('flrt_filter_no_terms_message')) {
1067 /**
1068 * Outputs "No terms" message
1069 * @param string $tag HTML tag name for the message wrapper
1070 * @since 1.7.6
1071 */
1072 function flrt_filter_no_terms_message($tag = 'li')
1073 {
1074 if (!$tag || $tag === '') {
1075 $tag = 'li';
1076 }
1077
1078 $srch = isset($_GET['srch']) ? filter_input(INPUT_GET, 'srch', FILTER_SANITIZE_SPECIAL_CHARS) : '';
1079
1080 echo '<' . $tag . ' class="wpc-no-filter-terms">';
1081 if (!flrt_is_filter_request() && !$srch) {
1082 esc_html_e('There are no filter terms yet', 'filter-everything');
1083 if (flrt_is_debug_mode()) {
1084 echo '&nbsp;' . flrt_help_tip(
1085 esc_html__('Possible reasons: 1) Filter\'s criterion doesn\'t contain any terms yet, and you have to add them 2) Terms may be created, but no one post that should be filtered attached to these terms 3) You excluded all possible terms in Filter\'s options.', 'filter-everything')
1086 );
1087 }
1088 } else {
1089 esc_html_e('N/A', 'filter-everything');
1090 }
1091 echo '</' . $tag . '>';
1092 }
1093 }
1094
1095 if (!function_exists('flrt_filter_more_less')) {
1096 /**
1097 * Outputs More/Less toggle link
1098 * @param array $filter Filter array
1099 * @since 1.7.6
1100 */
1101 function flrt_filter_more_less($filter)
1102 {
1103 if (isset($filter['more_less']) && $filter['more_less'] === 'yes'): ?>
1104 <a class="wpc-see-more-control wpc-toggle-a" href="javascript:void(0);"
1105 data-fid="<?php echo esc_attr($filter['ID']); ?>"><?php esc_html_e('See more', 'filter-everything'); ?></a>
1106 <a class="wpc-see-less-control wpc-toggle-a" href="javascript:void(0);"
1107 data-fid="<?php echo esc_attr($filter['ID']); ?>"><?php esc_html_e('See less', 'filter-everything'); ?></a>
1108 <?php endif;
1109 }
1110 }
1111
1112 if (!function_exists('flrt_filter_search_field')) {
1113 /**
1114 * Outputs filter search field
1115 * @since 1.7.6
1116 */
1117 function flrt_filter_search_field($filter, $view_args, $terms)
1118 {
1119 if (empty($terms)) {
1120 return false;
1121 }
1122
1123 if ($filter['search'] === 'yes' && $view_args['ask_to_select_parent'] === false): ?>
1124 <div class="wpc-filter-search-wrapper wpc-filter-search-wrapper-<?php echo esc_attr($filter['ID']); ?>">
1125 <span class="wpc-search-icon"></span>
1126 <input class="wpc-filter-search-field" type="text" value=""
1127 placeholder="<?php esc_html_e('Search', 'filter-everything') ?>"/>
1128 <button class="wpc-search-clear" type="button"
1129 title="<?php esc_html_e('Clear search', 'filter-everything') ?>"><span
1130 class="wpc-search-clear-icon">&#215;</span></button>
1131 </div>
1132 <?php endif;
1133 }
1134 }
1135
1136 function flrt_get_contrast_ratio($hexColor)
1137 {
1138 // hexColor RGB
1139 $R1 = hexdec(substr($hexColor, 1, 2));
1140 $G1 = hexdec(substr($hexColor, 3, 2));
1141 $B1 = hexdec(substr($hexColor, 5, 2));
1142
1143 // Black RGB
1144 $blackColor = "#000000";
1145 $R2BlackColor = hexdec(substr($blackColor, 1, 2));
1146 $G2BlackColor = hexdec(substr($blackColor, 3, 2));
1147 $B2BlackColor = hexdec(substr($blackColor, 5, 2));
1148
1149 // Calc contrast ratio
1150 $L1 = 0.2126 * pow($R1 / 255, 2.2) +
1151 0.7152 * pow($G1 / 255, 2.2) +
1152 0.0722 * pow($B1 / 255, 2.2);
1153
1154 $L2 = 0.2126 * pow($R2BlackColor / 255, 2.2) +
1155 0.7152 * pow($G2BlackColor / 255, 2.2) +
1156 0.0722 * pow($B2BlackColor / 255, 2.2);
1157
1158 $contrastRatio = 0;
1159 if ($L1 > $L2) {
1160 $contrastRatio = (int)(($L1 + 0.05) / ($L2 + 0.05));
1161 } else {
1162 $contrastRatio = (int)(($L2 + 0.05) / ($L1 + 0.05));
1163 }
1164 return round($contrastRatio);
1165 }
1166
1167 function flrt_get_contrast_color($hexColor)
1168 {
1169
1170 $contrastRatio = flrt_get_contrast_ratio($hexColor);
1171 // If contrast is more than 5, return black color
1172 if ($contrastRatio > 10) {
1173 return '#333333';
1174 } else {
1175 // if not, return white color.
1176 return '#f5f5f5';
1177 }
1178 }
1179
1180 function flrt_hex_to_rgb($hexColor, $opacity = 100)
1181 {
1182 $hexColor = ltrim($hexColor, '#');
1183
1184 $r = hexdec(substr($hexColor, 0, 2));
1185 $g = hexdec(substr($hexColor, 2, 2));
1186 $b = hexdec(substr($hexColor, 4, 2));
1187 $opacity = $opacity / 100;
1188 return "rgb($r $g $b / $opacity)";
1189 }
1190
1191 function flrt_add_color_opacity($hexColor, $opacity = 50)
1192 {
1193 $contrastRatio = flrt_get_contrast_ratio($hexColor);
1194
1195 if ($contrastRatio <= 15) {
1196 return flrt_hex_to_rgb($hexColor, $opacity);
1197 } else {
1198 return $hexColor;
1199 }
1200 }
1201
1202
1203 function flrt_default_posts_container()
1204 {
1205 return apply_filters('wpc_theme_posts_container', '#primary');
1206 }
1207
1208 function flrt_default_theme_color()
1209 {
1210 return apply_filters('wpc_theme_color', '#0570e2');
1211 }
1212
1213 function flrt_term_id($name, $filter, $id, $echo = true)
1214 {
1215 $attr = esc_attr("wpc-" . $name . "-" . $filter['entity'] . "-" . esc_attr($filter['e_name']) . "-" . $id);
1216 if ($echo) {
1217 echo $attr;
1218 } else {
1219 return $attr;
1220 }
1221 }
1222
1223 function flrt_get_icon_logo_svg($width = 24, $height = 24, $color = 'currentColor')
1224 {
1225 return '<svg viewBox="0 0 53 53" fill="' . $color . '" width="' . $width . '" height="' . $height . '" xmlns="http://www.w3.org/2000/svg">
1226 <g id="Group 1">
1227 <g id="Layer_1_00000162333103265806981530000017146624247591674556_">
1228 <g id="Group">
1229 <g id="Clip path group">
1230 <mask id="mask0_5_4" maskUnits="userSpaceOnUse" x="0" y="0" width="53" height="53">
1231 <g id="SVGID_00000112608340804245442460000013986219178199086244_">
1232 <path id="Vector" fill="white" d="M0 0H53V53H0V0ZM23.3 37.2C24.7 37.2 25.8 36.1 25.8 34.7C25.8 33.3 24.7 32.2 23.3 32.2C21.9 32.2 20.8 33.3 20.8 34.7C20.8 36.1 21.9 37.2 23.3 37.2ZM33.4 27.2C34.8 27.2 35.9 26.1 35.9 24.7C35.9 23.3 34.8 22.2 33.4 22.2C32 22.2 30.9 23.3 30.9 24.7C30.9 26.1 32 27.2 33.4 27.2ZM23 22.8C24.6 22.8 25.9 21.5 25.9 19.9C25.9 18.3 24.6 17 23 17C21.4 17 20.1 18.3 20.1 19.9C20.2 21.5 21.5 22.8 23 22.8Z" />
1233 </g>
1234 </mask>
1235 <g mask="url(#mask0_5_4)">
1236 <g id="BarsClipped">
1237 <path id="Vector_2" d="M39.9 31.5L18 37.3C17.4 37.5 16.8 37.1 16.6 36.5C16.4 35.9 16.8 35.3 17.4 35.1L39.3 29.2C39.9 29 40.5 29.4 40.7 30C40.8 30.7 40.5 31.3 39.9 31.5Z" fill="currentColor"/>
1238 <path id="Vector_3" d="M38.1 24.6L16.2 30.5C15.6 30.7 15 30.3 14.8 29.7C14.6 29.1 15 28.5 15.6 28.3L37.5 22.4C38.1 22.2 38.7 22.6 38.9 23.2C39 23.8 38.7 24.5 38.1 24.6Z" fill="currentColor"/>
1239 <path id="Vector_4" d="M36.2 17.9L14.3 23.8C13.7 24 13.1 23.6 12.9 23C12.7 22.4 13.1 21.8 13.7 21.6L35.6 15.7C36.2 15.5 36.8 15.9 37 16.5C37.2 17.1 36.8 17.7 36.2 17.9Z" fill="currentColor"/>
1240 </g>
1241 </g>
1242 </g>
1243 </g>
1244 <path id="Vector (Stroke)" d="M50 26.5C50 13.5284 39.4716 3 26.5 3C13.5284 3 3 13.5284 3 26.5C3 39.4716 13.5284 50 26.5 50C39.4716 50 50 39.4716 50 26.5ZM53 26.5C53 41.1284 41.1284 53 26.5 53C11.8716 53 0 41.1284 0 26.5C0 11.8716 11.8716 0 26.5 0C41.1284 0 53 11.8716 53 26.5Z" fill="currentColor"/>
1245 </g>
1246 <path id="Vector (Stroke)_2" d="M24.8 20.1C24.8 19.2716 24.1284 18.6 23.3 18.6C22.4716 18.6 21.8 19.2716 21.8 20.1C21.8 20.9284 22.4716 21.6 23.3 21.6C24.1284 21.6 24.8 20.9284 24.8 20.1ZM26.8 20.1C26.8 22.033 25.233 23.6 23.3 23.6C21.367 23.6 19.8 22.033 19.8 20.1C19.8 18.167 21.367 16.6 23.3 16.6C25.233 16.6 26.8 18.167 26.8 20.1Z" fill="currentColor"/>
1247 <path id="Vector (Stroke)_3" d="M34.7 24.8C34.7 23.9716 34.0284 23.3 33.2 23.3C32.3716 23.3 31.7 23.9716 31.7 24.8C31.7 25.6284 32.3716 26.3 33.2 26.3C34.0284 26.3 34.7 25.6284 34.7 24.8ZM36.7 24.8C36.7 26.733 35.133 28.3 33.2 28.3C31.267 28.3 29.7 26.733 29.7 24.8C29.7 22.867 31.267 21.3 33.2 21.3C35.133 21.3 36.7 22.867 36.7 24.8Z" fill="currentColor"/>
1248 <path id="Vector (Stroke)_4" d="M24.5 34.8C24.5 33.9716 23.8284 33.3 23 33.3C22.1716 33.3 21.5 33.9716 21.5 34.8C21.5 35.6284 22.1716 36.3 23 36.3C23.8284 36.3 24.5 35.6284 24.5 34.8ZM26.5 34.8C26.5 36.733 24.933 38.3 23 38.3C21.067 38.3 19.5 36.733 19.5 34.8C19.5 32.867 21.067 31.3 23 31.3C24.933 31.3 26.5 32.867 26.5 34.8Z" fill="currentColor"/>
1249 </g>
1250 </svg>';
1251 }
1252 function flrt_get_icon_logo_svg_css($width = 24, $height = 24)
1253 {
1254 return 'data:image/svg+xml;base64,' . base64_encode(flrt_get_icon_logo_svg($width, $height));
1255 }
1256 function flrt_get_icon_svg($color = '#ffffff')
1257 {
1258 $svg = flrt_get_icon_logo_svg(null, null, $color);
1259 return 'data:image/svg+xml;base64,' . base64_encode($svg);
1260 }
1261
1262 function flrt_get_icon_html()
1263 {
1264 ?>
1265 <span class="wpc-icon-html-wrapper">
1266 <span class="wpc-icon-line-1"></span>
1267 <span class="wpc-icon-line-2"></span>
1268 <span class="wpc-icon-line-3"></span>
1269 </span>
1270 <?php
1271 }
1272
1273 function flrt_get_plugin_name()
1274 {
1275 if (defined('FLRT_FILTERS_PRO')) {
1276 return esc_html__('Filter Everything Pro', 'filter-everything');
1277 } else {
1278 return esc_html__('Filter Everything', 'filter-everything');
1279 }
1280 }
1281
1282 function flrt_get_plugin_url($type = 'about', $full = false)
1283 {
1284 if ($full) {
1285 return esc_url($full);
1286 }
1287
1288 return esc_url(FLRT_PLUGIN_URL . '/' . $type);
1289 }
1290
1291 function flrt_get_term_by_slug($prefix)
1292 {
1293 global $wpdb;
1294
1295 $sql = "SELECT {$wpdb->terms}.slug FROM {$wpdb->terms} WHERE {$wpdb->terms}.slug = '%s'";
1296 $sql = $wpdb->prepare($sql, $prefix);
1297 $result = $wpdb->get_row($sql);
1298
1299 if (isset($result->slug) && $result->slug) {
1300 return $result->slug;
1301 }
1302
1303 return false;
1304 }
1305
1306 function flrt_walk_terms_tree($terms, $args)
1307 {
1308 _deprecated_function('flrt_walk_terms_tree', '1.7.6', 'flrt_filter_walk_terms_tree()');
1309 flrt_filter_walk_terms_tree($terms, $args);
1310 }
1311
1312 function flrt_filter_walk_terms_tree($terms, $args)
1313 {
1314 $walker = new \FilterEverything\Filter\WalkerCheckbox();
1315
1316 $depth = -1;
1317 if (isset($args['filter']['hierarchy']) && $args['filter']['hierarchy'] === 'yes') {
1318 $depth = 10;
1319 }
1320
1321 return $walker->walk($terms, $depth, $args);
1322 }
1323
1324 function flrt_get_all_parents($elements, $parent_id, &$ids)
1325 {
1326 if (isset($elements[$parent_id]->parent) && $elements[$parent_id]->parent > 0) {
1327 $id = $elements[$parent_id]->parent;
1328 $ids_flipped = array_flip($ids);
1329
1330 if (!isset($ids_flipped[$id])) {
1331 $ids[] = $id;
1332 }
1333
1334 flrt_get_all_parents($elements, $id, $ids);
1335 } else {
1336 return $ids;
1337 }
1338 }
1339
1340 function flrt_get_parents_with_not_empty_children($elements, $key = 'cross_count')
1341 {
1342 $has_posts_in_children = [];
1343
1344 if (empty($elements) || !is_array($elements)) {
1345 return $has_posts_in_children;
1346 }
1347
1348 $new_elements = [];
1349
1350 foreach ($elements as $k => $e) {
1351 $new_elements[$e->term_id] = $e;
1352 }
1353
1354 $has_posts_in_children_flipped = array_flip($has_posts_in_children);
1355
1356 foreach ($new_elements as $e) {
1357 if (isset($e->parent) && !empty($e->parent) && $e->$key > 0) {
1358 // Find all parents for term that contains posts
1359 if (!isset($has_posts_in_children_flipped[$e->parent])) {
1360 $has_posts_in_children[] = $e->parent;
1361 }
1362
1363 flrt_get_all_parents($new_elements, $e->parent, $has_posts_in_children);
1364 }
1365 }
1366
1367 return $has_posts_in_children;
1368 }
1369
1370 /**
1371 * Combines all filter sets for the same WP_Query
1372 *
1373 * @param array $all_sets - list of all page related sets
1374 * @param $current_set
1375 * @return array $queryRelatedSets IDs of all query related sets
1376 */
1377 function flrt_get_sets_with_the_same_query($all_sets, $current_set)
1378 {
1379 $queryRelatedSets = [];
1380 // First detect desired query index;
1381 $query = '';
1382 $post_type = '';
1383 $location = '';
1384 $set_id = $current_set['ID'];
1385
1386 foreach ($all_sets as $set) {
1387 if ($set['ID'] === $set_id) {
1388 // Current Set values
1389 $query = $set['query'];
1390 $post_type = $set['filtered_post_type'];
1391 $location = $set['query_location'];
1392 break;
1393 }
1394 }
1395
1396 // Then find all sets with such query
1397 foreach ($all_sets as $set) {
1398 if ($set['query'] === $query && $post_type === $set['filtered_post_type'] && $location === $set['query_location']) {
1399 $queryRelatedSets[] = $set['ID'];
1400 }
1401 }
1402
1403 if (empty($queryRelatedSets)) {
1404 $queryRelatedSets[] = $set_id;
1405 }
1406
1407 return $queryRelatedSets;
1408 }
1409
1410 function flrt_find_all_descendants($arr)
1411 {
1412 $all_results = [];
1413
1414 if (empty($arr) || !is_array($arr)) {
1415 return $all_results;
1416 }
1417
1418 foreach ($arr as $k => $v) {
1419 $curr_result = [];
1420
1421 for ($stack = [$k]; count($stack);) {
1422 $el = array_pop($stack);
1423
1424 if (array_key_exists($el, $arr) && is_array($arr[$el])) {
1425 foreach ($arr[$el] as $child) {
1426 $curr_result [] = $child;
1427 $stack [] = $child;
1428 }
1429 }
1430 }
1431
1432 if (count($curr_result)) {
1433 $all_results[$k] = $curr_result;
1434 }
1435 }
1436
1437 return $all_results;
1438 }
1439
1440 function flrt_debug_title()
1441 {
1442
1443 echo '<div class="wpc-debug-title">' . esc_html__('Filter Everything debug', 'filter-everything');
1444 echo '&nbsp;' . flrt_help_tip(
1445 sprintf(
1446 __('Debug messages are visible for logged in administrators only. You can disable them in Filters -> <a href="%s">Settings</a> -> Debug mode.', 'filter-everything'),
1447 admin_url('edit.php?post_type=filter-set&page=filters-settings')
1448 ), true) . '</div>';
1449 }
1450
1451 function flrt_is_debug_mode()
1452 {
1453 $debug_mode = false;
1454 if (flrt_get_option('widget_debug_messages') === 'on') {
1455 if (current_user_can(flrt_plugin_user_caps())) {
1456 $debug_mode = true;
1457 }
1458 }
1459
1460 return $debug_mode;
1461 }
1462
1463 function flrt_clean($var)
1464 {
1465 if (is_array($var)) {
1466 return array_map('flrt_clean', $var);
1467 } else {
1468 return is_scalar($var) ? sanitize_text_field($var) : $var;
1469 }
1470 }
1471
1472 function flrt_sorting_option_value($order_by_value, $meta_keys, $orders, $i)
1473 {
1474 $meta_key = isset($meta_keys[$i]) ? $meta_keys[$i] : '';
1475 $order = isset($orders[$i]) ? $orders[$i] : '';
1476
1477 $option_value = $order_by_value;
1478
1479 if (in_array($order_by_value, ['m', 'n'], true)) {
1480 $option_value .= $meta_key;
1481 }
1482
1483 $option_value .= ($order === 'desc') ? '-' . $order : '';
1484
1485 return $option_value;
1486 }
1487
1488 function flrt_get_active_plugins()
1489 {
1490
1491 if (is_multisite()) {
1492 $active_plugins = get_site_option('active_sitewide_plugins');
1493 if (is_array($active_plugins)) {
1494 $active_plugins = array_keys($active_plugins);
1495 }
1496
1497 $site_active_plugins = apply_filters('active_plugins', get_option('active_plugins'));
1498 $active_plugins = array_merge($active_plugins, $site_active_plugins);
1499 } else {
1500 $active_plugins = apply_filters('active_plugins', get_option('active_plugins'));
1501 }
1502
1503 return $active_plugins;
1504 }
1505
1506 /**
1507 * Version tag for FE transient cache NAMES. Bump it whenever the cached data
1508 * SHAPE changes (v2 = compact aggregated maps instead of raw SQL rows).
1509 * Versioned key names keep branches/versions with different cache formats
1510 * from ever reading each other's transients (e.g. when switching git branches
1511 * on a dev site) - each format lives under its own keys and stale ones simply
1512 * expire.
1513 */
1514 if ( ! defined( 'FLRT_CACHE_FORMAT_SUFFIX' ) ) {
1515 define( 'FLRT_CACHE_FORMAT_SUFFIX', '_v2' );
1516 }
1517
1518 function flrt_get_terms_transient_key( $salt, $include_lang = true ){
1519 $key = 'wpc_terms_' . $salt . FLRT_CACHE_FORMAT_SUFFIX;
1520 if ( flrt_wpml_active() && defined( 'ICL_LANGUAGE_CODE' ) && $include_lang ) {
1521 $key .= '_'.ICL_LANGUAGE_CODE;
1522 }
1523
1524 if (function_exists('pll_current_language') && $include_lang) {
1525 $pll_lang = pll_current_language();
1526 if ($pll_lang) {
1527 $key .= '_' . $pll_lang;
1528 }
1529 }
1530
1531 return $key;
1532 }
1533
1534 function flrt_get_post_ids_transient_key( $salt ){
1535 $key = 'wpc_posts_' . $salt . FLRT_CACHE_FORMAT_SUFFIX;
1536 if (flrt_wpml_active() && defined('ICL_LANGUAGE_CODE')) {
1537 $key .= '_' . ICL_LANGUAGE_CODE;
1538 }
1539
1540 if (function_exists('pll_current_language')) {
1541 $pll_lang = pll_current_language();
1542 if ($pll_lang) {
1543 $key .= '_' . $pll_lang;
1544 }
1545 }
1546
1547 return $key;
1548 }
1549
1550 function flrt_get_variations_transient_key( $salt ){
1551 $key = 'wpc_variations_' . $salt . FLRT_CACHE_FORMAT_SUFFIX;
1552 if (flrt_wpml_active() && defined('ICL_LANGUAGE_CODE')) {
1553 $key .= '_' . ICL_LANGUAGE_CODE;
1554 }
1555
1556 if (function_exists('pll_current_language')) {
1557 $pll_lang = pll_current_language();
1558 if ($pll_lang) {
1559 $key .= '_' . $pll_lang;
1560 }
1561 }
1562
1563 return $key;
1564 }
1565
1566 function flrt_is_query_on_page($setPosts, $searchKey)
1567 {
1568 $filterSet = Container::instance()->getFilterSetService();
1569 $sets = [];
1570 if (!is_array($setPosts)) {
1571 return $sets;
1572 }
1573
1574 foreach ($setPosts as $set) {
1575
1576 $parameters = maybe_unserialize($set->post_content);
1577 $query = isset($parameters['wp_filter_query']) ? $parameters['wp_filter_query'] : '-1';
1578 if ($filterSet->under_limit_filter_set($set->ID)) {
1579 continue;
1580 }
1581
1582 if (isset($parameters['use_apply_button']) && $parameters['use_apply_button'] === 'yes') {
1583
1584 $query_on_the_page = false;
1585 $show_on_the_page = false;
1586
1587 if (defined('FLRT_FILTERS_PRO') && FLRT_FILTERS_PRO) {
1588 if (isset($parameters['apply_button_post_name'])) {
1589
1590 if ($parameters['apply_button_post_name'] === $set->post_name ||
1591 $parameters['apply_button_post_name'] === 'no_page___no_page') {
1592 $query_on_the_page = true;
1593 }
1594
1595 if (in_array($parameters['apply_button_post_name'], $searchKey) || ($parameters['apply_button_post_name'] === 'no_page___no_page')) {
1596 $show_on_the_page = true;
1597 }
1598 }
1599
1600 } else {
1601 $query_on_the_page = true;
1602 $show_on_the_page = true;
1603 }
1604
1605 $sets[] = array(
1606 'ID' => (string)$set->ID,
1607 'filtered_post_type' => $set->post_excerpt,
1608 'query' => $query, // query hash
1609 'query_location' => $set->post_name,
1610 'query_on_the_page' => $query_on_the_page,
1611 'page_search_keys' => $searchKey,
1612 'show_on_the_page' => $show_on_the_page
1613 );
1614
1615 } else {
1616 if (in_array($set->post_name, $searchKey)) {
1617 $sets[] = array(
1618 'ID' => (string)$set->ID,
1619 'filtered_post_type' => $set->post_excerpt,
1620 'query' => $query, // query hash
1621 'query_location' => $set->post_name,
1622 'query_on_the_page' => true,
1623 'page_search_keys' => $searchKey,
1624 'show_on_the_page' => true
1625 );
1626 } else {
1627 // This set is for another page and was selected by Apply button location but the button disabled
1628 continue;
1629 }
1630 }
1631
1632 }
1633
1634 return $sets;
1635 }
1636
1637 function flrt_remove_empty_terms($checkTerms, $filter, $has_not_empty_children_flipped = [], $use_apply_button = false)
1638 {
1639
1640 if (!$use_apply_button) {
1641 foreach ($checkTerms as $index => $term) {
1642 if ($filter['hierarchy'] === 'yes') {
1643
1644 if ($term->cross_count === 0
1645 && !isset($has_not_empty_children_flipped[$term->term_id])) {
1646 unset($checkTerms[$index]);
1647 }
1648
1649 } else {
1650 if ($term->cross_count === 0) {
1651 unset($checkTerms[$index]);
1652 }
1653 }
1654 }
1655 }
1656
1657 return $checkTerms;
1658 }
1659
1660 function flrt_get_wp_queried_term($terms)
1661 {
1662 $wp_queried_terms = false;
1663
1664 foreach ($terms as $term) {
1665 if ($term->wp_queried === true) {
1666 $wp_queried_terms = $term;
1667 break;
1668 }
1669 }
1670
1671 return $wp_queried_terms;
1672 }
1673
1674 function flrt_get_filter_terms($filter, $posType, $em = false)
1675 {
1676 if (!$em) {
1677 $em = Container::instance()->getEntityManager();
1678 }
1679
1680 $entityObj = $em->getEntityByFilter($filter, $posType);
1681
1682 // The filter may lack entity/e_name (e.g. a parent_filter reference
1683 // to a filter that is absent from the rendered set)
1684 if (!$entityObj) {
1685 return [];
1686 }
1687
1688 // Exclude or include terms
1689 $isInclude = (isset($filter['include']) && $filter['include'] === 'yes');
1690 $entityObj->setExcludedTerms($filter['exclude'], $isInclude);
1691
1692 $terms = $entityObj->getTerms();
1693
1694 return apply_filters('wpc_items_after_calc_term_count', $terms);
1695 }
1696
1697 function flrt_get_term_brand_image($term_id, $filter)
1698 {
1699 $src = false;
1700
1701 if ($filter['e_name'] === 'pwb-brand') {
1702 $attachment_id = get_term_meta($term_id, 'pwb_brand_image', true);
1703 $attachment_props = wp_get_attachment_image_src($attachment_id, 'small');
1704 $src = isset($attachment_props[0]) ? $attachment_props[0] : false;
1705 } elseif (in_array($filter['e_name'], ['yith_product_brand', 'product_brand'])) {
1706 $attachment_id = get_term_meta($term_id, 'thumbnail_id', true);
1707 $attachment_props = wp_get_attachment_image_src($attachment_id, 'small');
1708 $src = isset($attachment_props[0]) ? $attachment_props[0] : false;
1709 } else {
1710 // pa_brand and other custom brand taxonomies. The plugin's own term
1711 // Image field saves 'product_attribute_image' for pa_* taxonomies
1712 // (see Swatches::save_image_field) — read the same key it writes.
1713 $image_meta_key = ( strpos( $filter['e_name'], 'pa_' ) === 0 ) ? 'product_attribute_image' : 'image';
1714 $src = get_term_meta($term_id, $image_meta_key, true);
1715
1716 if ( ! $src && $image_meta_key !== 'image' ) {
1717 $src = get_term_meta($term_id, 'image', true);
1718 }
1719
1720 if (intval($src) > 0) {
1721 $src = wp_get_attachment_image_url($src, 'full');
1722 }
1723
1724 if (isset($src['id']) && $src['id']) {
1725 $src = wp_get_attachment_image_url($src['id'], 'full');
1726 }
1727 }
1728
1729 return $src;
1730 }
1731
1732 function flrt_get_term_swatch_image($term_id, $filter)
1733 {
1734 $src = false;
1735 $image_key = 'image';
1736
1737 if (strpos($filter['e_name'], 'pa_') === 0) {
1738 $image_key = 'product_attribute_' . $image_key;
1739 }
1740
1741 if ($filter['e_name'] === 'product_cat') {
1742 $image_key = 'thumbnail_id';
1743 }
1744
1745 $image_key = apply_filters('wpc_image_term_meta_key', $image_key, $filter);
1746
1747 $image_id = get_term_meta($term_id, $image_key, true);
1748 $swatch_image_size = apply_filters('wpc_swatch_image_size', 'thumbnail');
1749
1750 if ($image_id) {
1751 $src = wp_get_attachment_image_url($image_id, $swatch_image_size);
1752 }
1753
1754 return $src;
1755 }
1756
1757 function flrt_get_term_swatch_color($term_id, $filter)
1758 {
1759 $color = false;
1760 $color_key = 'color';
1761
1762 if (strpos($filter['e_name'], 'pa_') === 0) {
1763 $color_key = 'product_attribute_' . $color_key;
1764 }
1765
1766 $color_key = apply_filters('wpc_color_term_meta_key', $color_key, $filter);
1767 $color = get_term_meta($term_id, $color_key, true);
1768
1769 return $color;
1770 }
1771
1772 /**
1773 * Checks and returns date format.
1774 * Does not check if date is valid
1775 * @param $date
1776 * @return string|false date, time format or false
1777 */
1778 function flrt_detect_date_type($date_or_time)
1779 {
1780 if (!$date_or_time) {
1781 return false;
1782 }
1783 $format = false;
1784 $date = false;
1785 $time = false;
1786
1787 $date_or_time = str_replace(FLRT_DATE_TIME_SEPARATOR, ' ', $date_or_time);
1788
1789 $pcs = date_parse($date_or_time);
1790 if ($pcs['year'] !== false && $pcs['month'] !== false && $pcs['day'] !== false) {
1791 $date = true;
1792 }
1793
1794 if ($pcs['hour'] !== false && $pcs['minute'] !== false && $pcs['second'] !== false) {
1795 $time = true;
1796 }
1797
1798 if ($date && $time) {
1799 $format = 'datetime';
1800 } else {
1801 if ($date) {
1802 $format = 'date';
1803 }
1804 if ($time) {
1805 $format = 'time';
1806 }
1807 }
1808
1809 return $format;
1810 }
1811
1812 /**
1813 * Modifies datetime to the human format
1814 * @param $datetime
1815 * @param $date_type
1816 * @param string $sep
1817 * @return mixed|string
1818 */
1819 function flrt_clean_date_time($datetime, $date_type, $sep = " ")
1820 {
1821 if ($date_type === 'date' || $date_type === 'DATE') {
1822 $pieces = explode($sep, $datetime);
1823 return $pieces[0]; //date e.g. 2021-05-14
1824 } else if ($date_type === 'time' || $date_type === 'TIME') {
1825 $pieces = explode($sep, $datetime);
1826 if (isset($pieces[1])) {
1827 return $pieces[1]; //time e.g. 14:15:47
1828 }
1829 } else {
1830 return $datetime; // str_replace( $sep, ' ', $datetime ); //datetime e.g. 2021-05-14 14:15:47
1831 }
1832 }
1833
1834 function flrt_apply_date_format($income_date, $format = "Y-m-d H:i:s")
1835 {
1836 $timestamp = strtotime($income_date);
1837 return flrt_date($format, $timestamp);
1838 }
1839
1840 function flrt_date($format, $timestamp = null)
1841 {
1842 global $wp_locale;
1843
1844 if (null === $timestamp) {
1845 $timestamp = time();
1846 } elseif (!is_numeric($timestamp)) {
1847 return false;
1848 }
1849
1850 $datetime = date_create('@' . $timestamp);
1851
1852 if (empty($wp_locale->month) || empty($wp_locale->weekday)) {
1853 $date = $datetime->format($format);
1854 } else {
1855 // We need to unpack shorthand `r` format because it has parts that might be localized.
1856 $format = preg_replace('/(?<!\\\\)r/', DATE_RFC2822, $format);
1857
1858 $new_format = '';
1859 $format_length = strlen($format);
1860 $month = $wp_locale->get_month($datetime->format('m'));
1861 $weekday = $wp_locale->get_weekday($datetime->format('w'));
1862
1863 for ($i = 0; $i < $format_length; $i++) {
1864 switch ($format[$i]) {
1865 case 'D':
1866 $new_format .= addcslashes($wp_locale->get_weekday_abbrev($weekday), '\\A..Za..z');
1867 break;
1868 case 'F':
1869 $new_format .= addcslashes($month, '\\A..Za..z');
1870 break;
1871 case 'l':
1872 $new_format .= addcslashes($weekday, '\\A..Za..z');
1873 break;
1874 case 'M':
1875 $new_format .= addcslashes($wp_locale->get_month_abbrev($month), '\\A..Za..z');
1876 break;
1877 case 'a':
1878 $new_format .= addcslashes($wp_locale->get_meridiem($datetime->format('a')), '\\A..Za..z');
1879 break;
1880 case 'A':
1881 $new_format .= addcslashes($wp_locale->get_meridiem($datetime->format('A')), '\\A..Za..z');
1882 break;
1883 case '\\':
1884 $new_format .= $format[$i];
1885
1886 // If character follows a slash, we add it without translating.
1887 if ($i < $format_length) {
1888 $new_format .= $format[++$i];
1889 }
1890 break;
1891 default:
1892 $new_format .= $format[$i];
1893 break;
1894 }
1895 }
1896
1897 $date = date_format($datetime, $new_format);
1898 }
1899
1900 return $date;
1901 }
1902
1903 function flrt_default_date_format($date_type = 'date')
1904 {
1905 /**
1906 * @todo date format depend from localization and geo settings
1907 * we have to relate them here
1908 */
1909 $date_format = __('F j, Y');
1910
1911 switch ($date_type) {
1912 case 'date':
1913 $date_format = __('F j, Y'); //'d-m-Y';
1914 break;
1915 case 'datetime':
1916 $date_format = __('F j, Y g:i a'); //'d-m-Y H:i:s';
1917 break;
1918 case 'time':
1919 $date_format = __('g:i a'); // 'H:i:s';
1920 break;
1921 }
1922
1923 return $date_format;
1924 }
1925
1926 function flrt_convert_date_to_js($date_or_time)
1927 {
1928 $date_php_to_js = Container::instance()->getParam('php_to_js_date_formats');
1929 return flrt_str_replace($date_or_time, $date_php_to_js);
1930 }
1931
1932 function flrt_convert_time_to_js($date_or_time)
1933 {
1934 $time_php_to_js = Container::instance()->getParam('php_to_js_time_formats');
1935 return flrt_str_replace($date_or_time, $time_php_to_js);
1936 }
1937
1938 function flrt_str_replace($string = '', $search_replace = array())
1939 {
1940 $ignore = array();
1941 unset($search_replace['']);
1942
1943 foreach ($search_replace as $search => $replace) {
1944 if (in_array($search, $ignore)) {
1945 continue;
1946 }
1947 if (strpos($string, $search) === false) {
1948 continue;
1949 }
1950 $string = str_replace($search, $replace, $string);
1951 $ignore[] = $replace;
1952 }
1953
1954 return $string;
1955 }
1956
1957 function flrt_split_date_time($date_time = '')
1958 {
1959 $php_date = Container::instance()->getParam('php_to_js_date_formats');
1960 $php_time = Container::instance()->getParam('php_to_js_time_formats');
1961 $chars = str_split($date_time);
1962 $type = 'date';
1963
1964 $data = array(
1965 'date' => '',
1966 'time' => '',
1967 );
1968
1969 foreach ($chars as $i => $c) {
1970 if (isset($php_date[$c])) {
1971 $type = 'date';
1972 } elseif (isset($php_time[$c])) {
1973 $type = 'time';
1974 }
1975 $data[$type] .= $c;
1976 }
1977
1978 $data['date'] = trim($data['date']);
1979 $data['time'] = trim($data['time']);
1980
1981 return $data;
1982 }
1983
1984 function flrt_string_polyfill($data) {
1985 return map_deep( $data,'flrt_string_polyfill_body');
1986 }
1987
1988 function flrt_string_polyfill_body( $string ){
1989
1990 if ( ! is_string( $string ) ) {
1991 return $string;
1992 }
1993
1994 $str = preg_replace('/\x00|<[^>]*>?/', '', $string );
1995 return str_replace( ["'", '"'], ['&#39;', '&#34;'], $str );
1996 }
1997
1998 function flrt_rating_star()
1999 {
2000 return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 25">
2001 <polygon class="cls-1" points="19.89 24.5 12.48 19.8 5.06 24.48 7.03 15.62 0.5 9.64 9.12 8.87 12.51 0.5 15.88 8.88 24.5 9.68 17.96 15.63 19.89 24.5"/>
2002 </svg>';
2003 }
2004
2005 function flrt_check_update_mobile_settings()
2006 {
2007
2008 $settings = get_option('wpc_filter_settings', false);
2009 if ($settings !== false) {
2010 if (!flrt_get_option('mobile_filter_settings')) {
2011
2012 $mobile_filter_settings = 'nothing';
2013
2014 if ((flrt_get_option('show_bottom_widget') === 'on' && flrt_get_option('show_open_close_button') === 'on')
2015 || (flrt_get_option('show_bottom_widget') === 'on' && !flrt_get_option('show_open_close_button'))) {
2016 $mobile_filter_settings = 'show_bottom_widget';
2017 } elseif (flrt_get_option('show_open_close_button') == 'on' && !flrt_get_option('show_bottom_widget')) {
2018 $mobile_filter_settings = 'show_open_close_button';
2019 }
2020
2021 $settings = get_option('wpc_filter_settings');
2022
2023 if (isset($settings['show_bottom_widget']) && $settings['show_bottom_widget']) {
2024 unset($settings['show_bottom_widget']);
2025 }
2026
2027 if (isset($settings['show_open_close_button']) && $settings['show_open_close_button']) {
2028 unset($settings['show_open_close_button']);
2029 }
2030
2031 $settings['mobile_filter_settings'] = $mobile_filter_settings;
2032 update_option('wpc_filter_settings', $settings);
2033 }
2034 }
2035 }
2036
2037 if (!function_exists('flrt_set_transient')) {
2038 function flrt_set_transient($transient, $value, $expiration = 0)
2039 {
2040 if (defined('FLRT_SET_TRANSIENT_ENABLED') && FLRT_SET_TRANSIENT_ENABLED) {
2041 set_transient($transient, $value, $expiration);
2042 }
2043 }
2044 }
2045
2046 if (!function_exists('flrt_get_transient')) {
2047 function flrt_get_transient($transient)
2048 {
2049 if (defined('FLRT_SET_TRANSIENT_ENABLED') && FLRT_SET_TRANSIENT_ENABLED) {
2050 return get_transient($transient);
2051 }
2052 return false;
2053 }
2054 }
2055
2056 if (!class_exists('FlrtWooDiscountRules')) {
2057 class FlrtWooDiscountRules
2058 {
2059
2060 protected $rules;
2061
2062 protected $base;
2063 protected $rule_helper;
2064 protected $discount_calculator;
2065 protected $manage_discount;
2066
2067 //public $filter;
2068 public function __construct()
2069 {
2070 $this->base = new Wdr\App\Controllers\Base();
2071 $this->rule_helper = new Wdr\App\Helpers\Rule();
2072 $this->manage_discount = new Wdr\App\Controllers\ManageDiscount();
2073 $this->rules = $this->manage_discount->getDiscountRules();
2074 $this->discount_calculator = new Wdr\App\Controllers\DiscountCalculator($this->rule_helper->getAvailableRules($this->base->getAvailableConditions()));
2075 }
2076
2077 public function getProductPriceToDisplay($product)
2078 {
2079 return $this->discount_calculator->getProductPriceToDisplay($product, 1);
2080 }
2081 }
2082
2083 function flrt_woo_discount_rules_class()
2084 {
2085 return new FlrtWooDiscountRules();
2086 }
2087 }
2088
2089
2090 if (!function_exists('flrt_is_sitemap_exists')) {
2091 function flrt_is_sitemap_exists()
2092 {
2093 $filepath = rtrim(FLRT_XML_PATH, '/\\') . '/filter-sitemap-index.xml';
2094 return file_exists($filepath);
2095 }
2096 }
2097 if (!function_exists('flrt_get_index_sitemap')) {
2098 function flrt_get_index_sitemap()
2099 {
2100 return fltr_get_url_from_absolute_path(FLRT_XML_PATH . '/filter-sitemap-index.xml');
2101 }
2102 }
2103
2104 if (!function_exists('fltr_get_url_from_absolute_path')) {
2105 function fltr_get_url_from_absolute_path($absolute_path)
2106 {
2107 $wp_root_path = realpath(ABSPATH);
2108 $wp_url = site_url();
2109
2110 $file_path = realpath($absolute_path);
2111
2112 if (!$file_path || strpos($file_path, $wp_root_path) !== 0) {
2113 return false;
2114 }
2115
2116
2117 $relative_path = str_replace($wp_root_path, '', $file_path);
2118 $relative_path = str_replace('\\', '/', $relative_path);
2119
2120 return rtrim($wp_url, '/') . $relative_path;
2121 }
2122 }
2123
2124 if (!function_exists('flrt_has_filter_seo_rules')) {
2125 function flrt_has_filter_seo_rules()
2126 {
2127 $args = [
2128 'post_type' => 'filter-seo-rule',
2129 'post_status' => 'publish',
2130 'posts_per_page' => 1,
2131 'fields' => 'ids',
2132 'no_found_rows' => true,
2133 ];
2134
2135 $query = new WP_Query($args);
2136 return $query->have_posts();
2137 }
2138 }
2139
2140 if (!function_exists('flrt_get_last_modified_filter_seo_rule')) {
2141 function flrt_get_last_modified_filter_post_type($post_type)
2142 {
2143 global $wpdb;
2144 $sql = "
2145 SELECT post_modified
2146 FROM {$wpdb->posts}
2147 WHERE post_type = '%s'
2148 AND post_status IN ('publish', 'trash')
2149 ORDER BY post_modified DESC
2150 LIMIT 1";
2151 $query = $wpdb->prepare($sql, $post_type);
2152 $last_modified = $wpdb->get_var($query);
2153
2154 if (!$last_modified) {
2155 return false;
2156 }
2157 return $last_modified;
2158 }
2159 }
2160
2161 if (!function_exists('flrt_check_to_update_xml')) {
2162 function flrt_check_to_update_xml()
2163 {
2164 $wpc_xml_write_date = get_option('wpc_xml_write_date');
2165 if (!$wpc_xml_write_date) return false;
2166
2167 $last_modified_filter_seo_rule = flrt_get_last_modified_filter_post_type(FLRT_SEO_RULES_POST_TYPE);
2168 $last_modified_filter_set = flrt_get_last_modified_filter_post_type(FLRT_FILTERS_SET_POST_TYPE);
2169
2170 if (!$last_modified_filter_seo_rule && !$last_modified_filter_set) return false;
2171
2172 $wpc_xml_write_date = strtotime($wpc_xml_write_date);
2173 $last_modified_filter_seo_rule = strtotime($last_modified_filter_seo_rule);
2174 $last_modified_filter_set = strtotime($last_modified_filter_set);
2175
2176 if ($last_modified_filter_seo_rule > $wpc_xml_write_date || $last_modified_filter_set > $wpc_xml_write_date) {
2177 return true;
2178 }
2179 if ($wpc_xml_write_date !== false && !flrt_has_filter_seo_rules()) {
2180 return true;
2181 }
2182 return false;
2183 }
2184 }
2185
2186
2187 if (!function_exists('flrt_post_type_underline_transform')) {
2188 function flrt_post_type_underline_transform($post_type)
2189 {
2190 if (mb_strpos($post_type, '-') !== false) {
2191 return str_replace('-', '_', $post_type);
2192 }
2193 return $post_type;
2194 }
2195 }
2196
2197
2198 if (!function_exists('flrt_generate_unique_copy_title')) {
2199
2200 /**
2201 * Generates a unique copy title in the format:
2202 * "Base Title – {copy_text} {N}".
2203 *
2204 * @param string $original_title The original post title to derive the base title from.
2205 * If it already ends with "– {copy_text} N", that suffix is stripped.
2206 * @param string $post_type The WordPress post type within which to check for duplicate titles.
2207 * By default expects the FLRT_FILTERS_SET_POST_TYPE constant.
2208 * @param string $copy_text The suffix text for copies (e.g., 'copy', 'duplicate').
2209 * @param int $start_number Numbering threshold:
2210 * - if 0 (default), the first copy gets number "1";
2211 * - if 1, the first copy has no number; numbering appears only when
2212 * a "… – {copy_text} 1" already exists.
2213 *
2214 * @return string The generated unique copy title.
2215 */
2216
2217 function flrt_generate_unique_copy_title($original_title, $post_type = FLRT_FILTERS_SET_POST_TYPE, $copy_text = 'copy', $number_position = true)
2218 {
2219 global $wpdb;
2220 if ($number_position) {
2221 $preg_match_pattern = '/^(.*) – ' . preg_quote($copy_text, '/') . ' \d+$/';
2222 }
2223
2224 if (!$number_position) {
2225 $preg_match_pattern = '/^(.*) \d+ – ' . preg_quote($copy_text, '/') . '$/';
2226 }
2227
2228 if (preg_match($preg_match_pattern, $original_title, $matches)) {
2229 $base_title = $matches[1];
2230 } else {
2231 $base_title = $original_title;
2232 }
2233
2234 if ($number_position) {
2235 $copy_pattern = $wpdb->esc_like($base_title) . '' . $wpdb->esc_like($copy_text) . '%';
2236 $titles = $wpdb->get_col(
2237 $wpdb->prepare(
2238 "SELECT post_title FROM $wpdb->posts
2239 WHERE (post_title = %s OR post_title LIKE %s)
2240 AND post_type = %s",
2241 $base_title,
2242 $copy_pattern,
2243 $post_type
2244 )
2245 );
2246 }
2247 if (!$number_position) {
2248 $copy_pattern = $wpdb->esc_like($base_title) . ' % – ' . $wpdb->esc_like($copy_text);
2249 $titles = $wpdb->get_col(
2250 $wpdb->prepare(
2251 "SELECT post_title FROM $wpdb->posts
2252 WHERE post_title LIKE %s
2253 AND post_type = %s",
2254 $copy_pattern,
2255 $post_type
2256 )
2257 );
2258 }
2259
2260 $max_copy_number = 0;
2261
2262 if ($number_position) {
2263 $preg_match_pattern_title = '/^' . preg_quote($base_title, '/') . '' . preg_quote($copy_text, '/') . ' (\d+)$/';
2264
2265 }
2266 if (!$number_position) {
2267 $preg_match_pattern_title = '/^' . preg_quote($base_title, '/') . ' (\d+) – ' . preg_quote($copy_text, '/') . '$/';
2268 }
2269
2270 foreach ($titles as $title) {
2271 if (preg_match($preg_match_pattern_title, $title, $m)) {
2272 if (isset($m[1]) && is_numeric($m[1])) {
2273 $num = intval($m[1]);
2274 if ($num > $max_copy_number) {
2275 $max_copy_number = $num;
2276 }
2277 }
2278 }
2279 }
2280
2281 $new_number = $max_copy_number + 1;
2282
2283 if ($number_position) {
2284 $text = $base_title . '' . $copy_text . ' ' . $new_number;
2285 }
2286 if (!$number_position) {
2287 $text = $base_title . ' ' . $new_number . '' . $copy_text;
2288 }
2289 return $text;
2290 }
2291 }
2292
2293 if (!function_exists('flrt_get_delete_set_transient')) {
2294 function flrt_refresh_temp_transient($set_name, $data)
2295 {
2296 if (get_transient($set_name) !== false) {
2297 delete_transient($set_name);
2298 }
2299 set_transient($set_name, $data, 300);
2300 }
2301 }
2302
2303 if (!function_exists('flrt_view_admin_error')) {
2304 function flrt_view_admin_error($text)
2305 {
2306 $error_str = '<div class="notice notice-error is-dismissible"><p>%s</p></div>';
2307 printf(
2308 $error_str,
2309 $text
2310 );
2311 }
2312 }
2313 if (!function_exists('flrt_export_setting_icon')) {
2314 function flrt_export_setting_icon()
2315 {
2316 return '<svg xmlns="http://www.w3.org/2000/svg" fill="#99a2b2" width="19px" height="19px" viewBox="0 0 24 20"><polyline id="primary" points="15 3 21 3 21 9" style="fill: none; stroke: rgb(153,162,178); stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"/><line id="primary-2" data-name="primary" x1="11" y1="13" x2="21" y2="3" style="fill: none; stroke: rgb(153,162,178); stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"/><path id="primary-3" data-name="primary" d="M21,13v7a1,1,0,0,1-1,1H4a1,1,0,0,1-1-1V4A1,1,0,0,1,4,3h7" style="fill: none; stroke: rgb(153,162,178); stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"/></svg>';
2317 }
2318 }
2319
2320 if (!function_exists('flrt_import_setting_icon')) {
2321 function flrt_import_setting_icon()
2322 {
2323 return '<svg xmlns="http://www.w3.org/2000/svg" fill="#000000" width="19px" height="19px" viewBox="0 0 24 23" id="wpc-import-icon"><polyline id="primary" points="17 13 11 13 11 7" style="fill: none; stroke: rgb(153,162,178); stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"/><line id="primary-2" data-name="primary" x1="21" y1="3" x2="11" y2="13" style="fill: none; stroke: rgb(153,162,178); stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"/><path id="primary-3" data-name="primary" d="M21,13v7a1,1,0,0,1-1,1H4a1,1,0,0,1-1-1V4A1,1,0,0,1,4,3h7" style="fill: none; stroke: rgb(153,162,178); stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"/></svg>';
2324 }
2325 }
2326
2327 if (!function_exists('flrt_open_in_new_tab_icon')) {
2328 function flrt_open_in_new_tab_icon()
2329 {
2330 $icon = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 21" width="18" height="18" aria-hidden="true" focusable="false">
2331 <path d="M19.5 4.5h-7V6h4.44l-5.97 5.97 1.06 1.06L18 7.06v4.44h1.5v-7Zm-13 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-3H17v3a.5.5 0 0 1-.5.5h-10a.5.5 0 0 1-.5-.5v-10a.5.5 0 0 1 .5-.5h3V5.5h-3Z"></path></svg>';
2332 return wp_kses(
2333 $icon,
2334 array(
2335 'svg' => array(
2336 'xmlns' => true,
2337 'viewbox' => true,
2338 'width' => true,
2339 'height' => true,
2340 'aria-hidden' => true,
2341 'focusable' => true,
2342 ),
2343 'path' => array(
2344 'd' => true
2345 ),
2346 )
2347 );
2348 }
2349 }
2350
2351 if (!function_exists('flrt_vailable_in_pro_attr_link')) {
2352 function flrt_vailable_in_pro_attr_link($target_blank = false): string
2353 {
2354 $link = 'edit.php?post_type=' . FLRT_FILTERS_SET_POST_TYPE . '&page=flrt-pro';
2355 if ($target_blank) {
2356 $link .= '_target=blank';
2357 }
2358 return $link;
2359 }
2360 }
2361 if (!function_exists('flrt_pro_promo_label')) {
2362 function flrt_pro_promo_label($replace_class = false): string
2363 {
2364 $class = $replace_class ? 'wpc-pro-badge' : 'wpc-pro-badge-transparent';
2365 $label = ' <span class="' . $class . '">' . esc_html__('PRO', 'filter-everything') . '</span>';
2366
2367 return wp_kses($label, [
2368 'span' => [
2369 'class' => []
2370 ]
2371 ]);
2372 }
2373 }
2374
2375 if(!function_exists( 'flrt_unlock_icon')){
2376 function flrt_unlock_icon($width = '20px', $height = '20px', $color = '#3858E9')
2377 {
2378 return '<svg xmlns="http://www.w3.org/2000/svg" height="' . $height . '" viewBox="0 -960 960 960" width="' . $width . '" fill="' . $color . '"><path d="M264-624h336v-96q0-50-35-85t-85-35q-50 0-85 35t-35 85h-72q0-80 56.23-136 56.22-56 136-56Q560-912 616-855.84q56 56.16 56 135.84v96h24q29.7 0 50.85 21.15Q768-581.7 768-552v384q0 29.7-21.16 50.85Q725.68-96 695.96-96H263.72Q234-96 213-117.15T192-168v-384q0-29.7 21.15-50.85Q234.3-624 264-624Zm0 456h432v-384H264v384Zm216.21-120Q510-288 531-309.21t21-51Q552-390 530.79-411t-51-21Q450-432 429-410.79t-21 51Q408-330 429.21-309t51 21ZM264-168v-384 384Z"/></svg>';
2379 }
2380 }
2381
2382 if(!function_exists( 'flrt_crown_icon')){
2383 function flrt_crown_icon($width = '16px', $height = '16px', $color = '#FFFFFF')
2384 {
2385 return '<svg xmlns="http://www.w3.org/2000/svg" fill="' . $color . '" viewBox="0 0 16 16" width="' . $width . '" height="' . $height . '" class="wpc-crown-icon"><path fill="' . $color . '" d="M8.687 1.932c-.238-.634-1.136-.634-1.374 0l-1.642 4.38-3.167-2.11a.733.733 0 0 0-1.126.753L3.333 12h9.334l1.955-7.045a.733.733 0 0 0-1.126-.754L10.33 6.313zM6.654 7.49 8 3.899l1.346 3.59c.166.442.7.614 1.094.352l2.59-1.727-1.363 4.553H4.333L2.97 6.114 5.56 7.841a.733.733 0 0 0 1.094-.352m6.013 5.844H3.333v1.334h9.334z"></path></svg>';
2386 }
2387 }
2388
2389 if(!function_exists( 'flrt_unlock_in_pro')){
2390 function flrt_unlock_in_pro($button_text = '')
2391 {
2392 $string = '<a class="wpc-available-in-pro-button" href="' . admin_url(flrt_vailable_in_pro_attr_link()) . '">';
2393 $string .= !empty($button_text) ? $button_text . ' - ': '';
2394 $string .= flrt_unlock_icon();
2395 $string .= '<span>' . esc_html__('Unlock with PRO', 'filter-everything') . '</span></a>';
2396 return $string;
2397 }
2398 }
2399
2400
2401 add_filter('wpc_filter_default_fields', 'flrt_add_pro_promo_fields', 10, 2);
2402 function flrt_add_pro_promo_fields($defaultFields, $filterFields)
2403 {
2404
2405 if (!defined('FLRT_FILTERS_PRO')) {
2406 if (flrt_is_woocommerce()) {
2407 $updatedFields = [];
2408 foreach ($defaultFields as $key => $field) {
2409 $updatedFields[$key] = $field;
2410
2411 if ($key === 'hierarchy') {
2412 $updatedFields['used_for_variations'] = array(
2413 'type' => 'inProButton',
2414 'pro_label' => flrt_pro_promo_label(),
2415 'label' => esc_html__('Use for Variations', 'filter-everything'),
2416 'class' => 'wpc-field-for-variations',
2417 'default' => 'no',
2418 'instructions' => esc_html__('If checked, filtering will take into account variations with this attribute or meta key', 'filter-everything'),
2419 );
2420 }
2421 }
2422 return $updatedFields;
2423 }
2424 }
2425
2426 return $defaultFields;
2427
2428 }
2429
2430 function flrt_pro_features_link()
2431 {
2432 return 'https://filtereverything.pro/?utm_source=free_plugin&utm_medium=internal&utm_campaign=free_plugin_upgrade&utm_content=popup_features_lnk#why-choose-pro';
2433 }
2434
2435 function flrt_unlock_pro_link( $utm_content = '' )
2436 {
2437 return 'https://filtereverything.pro/pricing/?utm_source=free_plugin&utm_medium=internal&utm_campaign=free_plugin_upgrade&utm_content=' . $utm_content;
2438 }
2439
2440 /**
2441 * Link to the refund policy. Currently a FAQ anchor on the main landing page;
2442 * will point to a dedicated refund-policy page once that exists.
2443 */
2444 function flrt_refund_policy_link()
2445 {
2446 return 'https://filtereverything.pro/#refund-policy';
2447 }
2448
2449
2450 function wpc_clear_folder($directory)
2451 {
2452
2453 if (!is_dir($directory)) {
2454 return false;
2455 }
2456
2457 $files = glob(rtrim($directory, '/\\') . DIRECTORY_SEPARATOR . '*');
2458
2459 if(!empty($files)){
2460 foreach ($files as $file) {
2461 if (is_file($file)) {
2462 unlink($file);
2463 }
2464 }
2465 }
2466 }
2467
2468 function flrt_diamond_icon($svg_fill = "var(--wpc-pro-color, #3858E9)")
2469 {
2470 return '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="' . $svg_fill. '" version="1.1" id="Layer_1" width="16px" height="16px" viewBox="0 0 70 70" enable-background="new 0 0 70 70" xml:space="preserve">
2471 <g>
2472 <path d="M67.142,23.641L55.405,10.456c-0.379-0.423-0.92-0.873-1.488-0.873h-37.98c-0.568,0-1.109,0.45-1.489,0.874L2.711,23.752 c-0.691,0.771-0.68,1.94,0.025,2.697L33.462,59.46c0.378,0.407,0.909,0.638,1.464,0.638s1.086-0.257,1.464-0.664l30.728-33.042 C67.822,25.634,67.833,24.411,67.142,23.641z M46.555,25.583L34.902,53.414L22.608,25.583H46.555z M21.725,23.583l-4.417-10h34.272 l-4.188,10H21.725z M32.231,52.152L7.586,25.583h12.879L32.231,52.152z M48.702,25.583H62c0.094,0,0.179-0.029,0.265-0.054 L37.462,52.318L48.702,25.583z M61.871,23.583H49.543l3.971-9.447L61.871,23.583z M15.714,14.851l3.867,8.732H8.027L15.714,14.851z "/>
2473 <path d="M35,14.583H23c-0.552,0-1,0.447-1,1s0.448,1,1,1h12c0.552,0,1-0.447,1-1S35.552,14.583,35,14.583z"/>
2474 <path d="M45,14.583h-5c-0.552,0-1,0.447-1,1s0.448,1,1,1h5c0.552,0,1-0.447,1-1S45.552,14.583,45,14.583z"/>
2475 </g>
2476 </svg>';
2477 }
2478
2479
2480 add_action('wp_footer', function () {
2481 if (current_user_can(flrt_plugin_user_caps()))
2482 if (!wp_script_is('wpc-filter-everything')) {
2483 {
2484 ?>
2485 <script>
2486 jQuery(function ($) {
2487 $(document).on('click', '.wpc-open-close-filters-button', function (e) {
2488 e.preventDefault();
2489 let openCloseButton = $(this);
2490 let wpcButtonFilterSetError = openCloseButton.data('wpcButtonFilterSetError');
2491 let wpcButtonWidgetError = openCloseButton.data('wpcButtonWidgetError');
2492
2493 if (typeof wpcButtonFilterSetError !== 'undefined') {
2494 alert(wpcButtonFilterSetError);
2495 }
2496 if (typeof wpcButtonWidgetError !== 'undefined') {
2497 if (typeof window.wpcFilterWidgetActive === 'undefined') {
2498 alert(wpcButtonWidgetError);
2499 }
2500 }
2501 });
2502 });
2503 </script>
2504 <?php
2505 }
2506 }
2507 });
2508
2509 function flrt_log( $message, $data = null ) {
2510 if ( ! defined('WP_DEBUG_LOG') || ! WP_DEBUG_LOG ) {
2511 return;
2512 }
2513
2514 $log = '[Filter Everything] ' . $message;
2515
2516 if ( $data !== null ) {
2517 $log .= ' | ' . wp_json_encode( $data );
2518 }
2519
2520 error_log( $log );
2521 }
2522
2523 function prepare_wpc_filter_set_json_data($postData)
2524 {
2525 if(!empty($postData['wpc_filter_set_json_data'])){
2526 $postData = json_decode(stripslashes($postData['wpc_filter_set_json_data']), true);
2527 $postData['wpc_set_fields']['wp_filter_query_vars'] = wp_slash($postData['wpc_set_fields']['wp_filter_query_vars']);
2528 }
2529 return $postData;
2530 }
2531
2532 function flrt_rating_slugs()
2533 {
2534 return array(
2535 'rated-1' => 1,
2536 'rated-2' => 2,
2537 'rated-3' => 3,
2538 'rated-4' => 4,
2539 'rated-5' => 5
2540 );
2541 }
2542
2543 /**
2544 * Whether Apply button Sets recalculate counters instantly in the browser
2545 * (the 1.9.3 client-side recount) instead of the legacy per-click AJAX
2546 * request. Disabled by default, so existing Apply button users keep the
2547 * pre-1.9.3 behaviour after the update until they opt in.
2548 *
2549 * The option is PRO-only: in the free version the preserved DB value stays
2550 * inert and Apply button Sets always use the legacy AJAX recount. The whole
2551 * client-side engine works in free too — this gate is the only thing to
2552 * relax if instant recount ever ships in the free version.
2553 */
2554 function flrt_instant_recount()
2555 {
2556 return defined('FLRT_FILTERS_PRO') && flrt_get_option('apply_button_instant_recount') === 'on';
2557 }
2558
2559 function flrt_check_apply_buttom_mode($set)
2560 {
2561 return ((isset($set['use_apply_button']['value']) && $set['use_apply_button']['value'] === 'yes') && flrt_instant_recount());
2562 }
2563
2564
2565 function flrt_parent_filter_apply_button_data($filter, $args = [])
2566 {
2567 $data = '';
2568 if($args['use_apply_button'] && (int) $filter['parent_filter'] > 0){
2569 $hide_until_parent = 0;
2570 if($filter['hide_until_parent'] === 'yes'){
2571 $hide_until_parent = 1;
2572 }
2573 $data .= ' data-parent-filter-id="' . esc_attr($filter['parent_filter']) . '" data-hide-until-parent="' . esc_attr($hide_until_parent) .'"';
2574 }
2575 return $data;
2576 }
2577
2578 function flrt_parent_filter_apply_class($filter, $args = [], $terms = [])
2579 {
2580 $css_class = '';
2581 // '-1', the legacy 'no' placeholder and anything non-numeric all mean "no parent"
2582 if( (int) $filter['parent_filter'] <= 0 ){
2583 return $css_class;
2584 }
2585
2586 $checked = false;
2587
2588 $css_class = ' ' . esc_attr('wpc-has-parent-filter');
2589
2590 $is_parent_has_terms = false;
2591 if(!empty($terms)){
2592 foreach ($terms as $term){
2593 if(isset($term->show_with_parent)){
2594 $is_parent_has_terms = true;
2595 }
2596 if( in_array( $term->slug, $filter['values'] ) ){
2597 $checked = true;
2598 }
2599 if($is_parent_has_terms && $checked){
2600 break;
2601 }
2602 }
2603 }
2604
2605
2606 if($args['use_apply_button'] && $is_parent_has_terms === false){
2607 $css_class .= ' ' . esc_attr('wpc-parent-filter-terms-unselected');
2608 }
2609
2610 if($args['use_apply_button'] && $is_parent_has_terms === false && $checked){
2611 $css_class .= ' ' . esc_attr('wpc-child-selected-no-parent');
2612 }
2613
2614 if($args['use_apply_button'] && $is_parent_has_terms === true){
2615 $css_class .= ' ' . esc_attr('wpc-parent-filter-terms-selected');
2616 }
2617
2618 if(empty($filter['hide_until_parent']) || $filter['hide_until_parent'] !== 'yes'){
2619 return $css_class;
2620 }
2621
2622 if($args['use_apply_button'] && !empty($filter['hide_until_parent']) && $filter['hide_until_parent'] === 'yes' && $is_parent_has_terms === false){
2623 $css_class .= ' ' . esc_attr('wpc-hide-terms-until-parent-unselected');
2624 }
2625
2626 return $css_class;
2627 }
2628
2629 function flrtIsMoreLess($filter)
2630 {
2631 return (!empty($filter['more_less']) && $filter['more_less'] === 'yes') ? true : false;
2632 }
2633
2634 function flrtParentFilter($filter)
2635 {
2636 // Positive filter ID only. '-1' means no parent, and single-filter sets used
2637 // to save the 'no' select placeholder — on PHP 8 the string comparison
2638 // 'no' > 0 is true, so a plain numeric check would treat it as a parent
2639 return !empty($filter['parent_filter']) && (int) $filter['parent_filter'] > 0;
2640 }
2641
2642 function flrt_get_filtered_term_url( $term, $filter, $url_manager ) {
2643 $exclude_from_url = [];
2644
2645 if ( ! empty( $filter['has_child_filter'] ) ) {
2646 if ( $filter['has_child_filter'] === true && count( $filter['values'] ) === 1 ) {
2647 if ( $term->slug === $filter['values'][0] ) {
2648 foreach ( $filter['child_values'] as $child_value ) {
2649 $exclude_from_url[ $child_value ] = true;
2650 }
2651 }
2652 }
2653 }
2654
2655 return $url_manager->getTermUrl(
2656 $term->slug,
2657 $filter['e_name'],
2658 $filter['entity'],
2659 '',
2660 $exclude_from_url
2661 );
2662 }
2663 function flrtDetectTimeComponents(string $format): array
2664 {
2665 $hasHours = false;
2666 $hasMinutes = false;
2667 $hasSeconds = false;
2668
2669 $len = strlen($format);
2670 for ($i = 0; $i < $len; $i++) {
2671 $char = $format[$i];
2672
2673 if ($char === '\\') {
2674 $i++;
2675 continue;
2676 }
2677
2678 if (in_array($char, ['g', 'G', 'h', 'H'], true)) {
2679 $hasHours = true;
2680 } elseif ($char === 'i') {
2681 $hasMinutes = true;
2682 } elseif ($char === 's') {
2683 $hasSeconds = true;
2684 }
2685 }
2686
2687 return [
2688 'hours' => $hasHours,
2689 'minutes' => $hasMinutes,
2690 'seconds' => $hasSeconds,
2691 ];
2692 }
2693
2694 function flrtBuildDateTimeString(string $value, string $format): ?string
2695 {
2696 $date = DateTime::createFromFormat($format, $value);
2697 if ($date === false) {
2698 return null;
2699 }
2700
2701 $components = flrtDetectTimeComponents($format);
2702
2703 $hours = $components['hours'] ? $date->format('H') : '00';
2704 $minutes = $components['minutes'] ? $date->format('i') : '00';
2705 $seconds = $components['seconds'] ? $date->format('s') : '00';
2706
2707 return $date->format('Y-m-d') . " {$hours}:{$minutes}:{$seconds}";
2708 }
2709