<?php
/* Prevent direct access */

use WPDRMS\ASP\Frontend\FiltersManager;
use WPDRMS\ASP\Utils\FrontendFilters;
use WPDRMS\ASP\Utils\MB;
use WPDRMS\ASP\Utils\Polylang\StringTranslations as PolylangStringTranslations;
use WPDRMS\ASP\Utils\Post;
use WPDRMS\ASP\Utils\PostMeta;
use WPDRMS\ASP\Utils\Str;

defined('ABSPATH') or die("You can't access this file directly.");

/**
 * FILE CONTENTS:
 *  1. BASIC FUNCTIONS
 *  2. FILE SYSTEM SPECIFIC WRAPPERS
 *  3. TAXONOMY AND TERM SPECIFIC
 *  4. BACK-END SPECIFIC
 *  5. NON-AJAX RESULTS
 *  6. FRONT-END
 *  7. Polyfill
*/

// ----------------------------------------------------------------------------------------------------------------------
// 1. BASIC FUNCTIONS
// ----------------------------------------------------------------------------------------------------------------------

if ( !function_exists('wpd_get_terms') ) {
	function wpd_get_terms( $args = array() ) {
		return get_terms($args);
	}
}

if ( ! function_exists( 'asp_bfi_thumb' ) ) {
	function asp_bfi_thumb( $url, $params = array(), $single = true ) {
		return call_user_func( array( '\\WPDRMS\\ASP\\Cache\\BFI_Thumb_1_3', 'thumb' ), $url, $params, $single );
	}
}


if ( !function_exists('wpd_get_languages_array') ) {
	/* Get WPML or Polylang languages list in array */
	function wpd_get_languages_array() {
		$langs = array();
		if ( function_exists('pll_languages_list') ) {
			$_langs = pll_languages_list(
				array(
					'fields' => 'slug',
				)
			);
			if ( is_array($_langs) ) {
				foreach ( $_langs as $key => $_lang ) {
					$langs[ $_lang ] = $_lang;
				}
			}
		} elseif ( function_exists('icl_get_languages') ) {
			$_langs = icl_get_languages('skip_missing=0&orderby=KEY&order=DIR&link_empty_to=str');
			if ( is_array($_langs) ) {
				foreach ( array_reverse($_langs) as $key => $_lang ) {
					$langs[ $_lang['language_code'] ] = $_lang['translated_name'];
				}
			}
		}

		return $langs;
	}
}

if ( !function_exists('asp_get_inner_substring') ) {
	/**
	 * Get the string from inbetween delimiters
	 *
	 * @param $string
	 * @param $delim
	 * @return string
	 */
	function asp_get_inner_substring( $string, $delim ) {

		$s = explode($delim, $string, 3); // also, we only need 2 items at most
		return isset($s[1]) ? $s[1] : '';
	}
}

if ( !function_exists('asp_get_outer_substring') ) {
	/**
	 * Get the string from outside the delimiters
	 *
	 * @param $string
	 * @param $delim
	 * @return string
	 */
	function asp_get_outer_substring( $string, $delim ) {
		$s = explode($delim, $string, 3); // also, we only need 2 items at most
		return isset($s[0], $s[2]) ? $s[0] . $s[2] : $string;
	}
}

if ( !function_exists('wpd_comma_separated_to_array') ) {
	/**
	 * @param $string - Input string to convert to array
	 * @param string $separator - Separator to separate by (default: ,)
	 *
	 * @return array
	 */
	function wpd_comma_separated_to_array( $string, $separator = ',' ) {
		// Explode on comma
		$vals = explode($separator, $string);

		// Trim whitespace
		foreach ( $vals as $key => $val ) {
			$vals[ $key ] = trim($val);
		}
		// Return empty array if no items found
		// http://php.net/manual/en/function.explode.php#114273
		return array_diff($vals, array( '' ));
	}
}

if ( !function_exists('wd_strip_tags_ws') ) {
	/**
	 * Strips tags, but replaces them with whitespace
	 *
	 * @param string $string
	 * @param string $allowable_tags
	 * @return string
	 * @link https://stackoverflow.com/a/38200395
	 */
	function wd_strip_tags_ws( $string, $allowable_tags = '' ) {
		$string = str_replace('<', ' <', $string);
		// Non breakable spaces to regular spaces
		$string = preg_replace('/\xc2\xa0/', ' ', $string);
		// Duplicated spaces
		$string = preg_replace('/\s+/', ' ', $string);
		$string = strip_tags($string, $allowable_tags);
		$string = trim($string);

		return $string;
	}
}

if ( !function_exists('wd_closetags') ) {
	/**
	 * Close unclosed HTML tags
	 *
	 * @param $html
	 * @return string
	 */
	function wd_closetags( $html ) {
		$html     = trim($html);
		$unpaired = array( 'hr', 'br', 'img' );

		// put all opened tags into an array
		preg_match_all( '#<([a-z]+)( .*)?(?!/)>#iU', $html, $result );
		$openedtags = $result[1];
		// remove unpaired tags
		if ( is_array($openedtags) && count($openedtags) >0 ) {
			foreach ( $openedtags as $k =>$tag ) {
				if ( in_array($tag, $unpaired) ) {
					unset($openedtags[ $k ]);
				}
			}
		} else {
			// Replace a possible un-closed tag from the end, 30 characters backwards check
			$html = preg_replace('/(.*)(\<[a-zA-Z]{0,30})$/', '$1', $html);
			return $html;
		}
		// put all closed tags into an array
		preg_match_all( '#</([a-z]+)>#iU', $html, $result );
		$closedtags = $result[1];
		$len_opened = count( $openedtags );
		// all tags are closed
		if ( count( $closedtags ) == $len_opened ) {
			// Replace a possible un-closed tag from the end, 30 characters backwards check
			$html = preg_replace('/(.*)(\<[a-zA-Z]{0,30})$/', '$1', $html);
			return $html;
		}
		$openedtags = array_reverse( $openedtags );
		// close tags
		for ( $i = 0; $i < $len_opened; $i++ ) {
			if ( !in_array( $openedtags[ $i ], $closedtags ) ) {
				$html .= '</' . $openedtags[ $i ] . '>';
			} else {
				unset( $closedtags[ array_search( $openedtags[ $i ], $closedtags) ] );
			}
		}

		// Replace a possible un-closed tag from the end, 30 characters backwards check
		$html = preg_replace('/(.*)(\<[a-zA-Z]{0,30})$/', '$1', $html);
		return $html;
	}
}

if ( !function_exists('wd_mysql_escape_mimic') ) {
	/**
	 * Mimics the old mysql_escape function
	 *
	 * @internal
	 * @param $inp
	 * @return mixed
	 */
	function wd_mysql_escape_mimic( $inp ) {
		if ( is_array($inp) ) {
			return array_map(__METHOD__, $inp);
		}

		if ( !empty($inp) && is_string($inp) ) {
			return str_replace(array( '\\', "\0", "\n", "\r", "'", '"', "\x1a" ), array( '\\\\', '\\0', '\\n', '\\r', "\\'", '\\"', '\\Z' ), $inp);
		}

		return $inp;
	}
}

if ( !function_exists('wd_substr_at_word') ) {
	/**
	 * Substring cut off at word endings
	 *
	 * @param $text
	 * @param $length
	 * @param $suffix
	 * @param $tolerance
	 * @return string
	 */
	function wd_substr_at_word( $text, $length, $suffix = ' ...', $tolerance = 8 ) {

		if ( function_exists('mb_strlen') &&
			function_exists('mb_strrpos') &&
			function_exists('mb_substr')
		) {
			$fn_strlen  = 'mb_strlen';
			$fn_strrpos = 'mb_strrpos';
			$fn_substr  = 'mb_substr';
		} else {
			$fn_strlen  = 'strlen';
			$fn_strrpos = 'strrpos';
			$fn_substr  = 'substr';
		}

		if ( $fn_strlen($text) <= $length ) {
			return $text;
		}

		$s = $fn_substr($text, 0, $length);
		$s = $fn_substr($s, 0, $fn_strrpos($s, ' '));

		// In case of a long mash-up, it will not let overflow the length
		if ( $fn_strlen($s) > ( $length + $tolerance ) ) {
			$s = $fn_substr($s, 0, ( $length + $tolerance ));
		}

		if ( $suffix != '' && $fn_strlen($s) != $fn_strlen($text) ) {
			$s .= $suffix;
		}

		return $s;
	}
}

if ( !function_exists('wd_in_array_r') ) {
	/**
	 * Recursive in_array
	 *
	 * @param $needle
	 * @param $haystack
	 * @param bool $strict
	 * @return bool
	 */
	function wd_in_array_r( $needle, $haystack, $strict = false ) {
		foreach ( $haystack as $item ) {
			if ( ( $strict ? $item === $needle : $item == $needle ) || ( is_array($item) && wd_in_array_r($needle, $item, $strict) ) ) {
				return true;
			}
		}
  
		return false;
	}
}

if ( !function_exists('wd_array_merge_recursive_distinct') ) {
	/**
	 * array_merge_recursive does indeed merge arrays, but it converts values with duplicate
	 * keys to arrays rather than overwriting the value in the first array with the duplicate
	 * value in the second array, as array_merge does. I.e., with array_merge_recursive,
	 * this happens (documented behavior):
	 *
	 * array_merge_recursive(array('key' => 'org value'), array('key' => 'new value'));
	 *     => array('key' => array('org value', 'new value'));
	 *
	 * array_merge_recursive_distinct does not change the datatypes of the values in the arrays.
	 * Matching keys' values in the second array overwrite those in the first array, as is the
	 * case with array_merge, i.e.:
	 *
	 * array_merge_recursive_distinct(array('key' => 'org value'), array('key' => 'new value'));
	 *     => array('key' => array('new value'));
	 *
	 * Parameters are passed by reference, though only for performance reasons. They're not
	 * altered by this function. -> ASP COMMENT: removed to be able to used with CONST constants
	 *
	 * @param array $array1
	 * @param array $array2
	 * @return array
	 * @author Daniel <daniel (at) danielsmedegaardbuus (dot) dk>
	 * @author Gabriel Sobrinho <gabriel (dot) sobrinho (at) gmail (dot) com>
	 */
	function wd_array_merge_recursive_distinct( array $array1, array $array2 ) {
		$merged = $array1;
		foreach ( $array2 as $key => &$value ) {
			if ( is_array($value) && isset($merged [ $key ]) && is_array($merged [ $key ]) ) {
				$merged [ $key ] = wd_array_merge_recursive_distinct($merged [ $key ], $value);
			} else {
				$merged [ $key ] = $value;
			}
		}

		return $merged;
	}
}

if ( !function_exists('wd_flatten_array') ) {
	/**
	 * Flattens array without preserving the keys
	 *
	 * @param mixed $array
	 * @return array<int, mixed>
	 */
	function wd_flatten_array( array $array ): array {
		$recursiveArrayIterator = new RecursiveArrayIterator(
			$array,
			RecursiveArrayIterator::CHILD_ARRAYS_ONLY
		);
		$iterator               = new RecursiveIteratorIterator($recursiveArrayIterator);

		return iterator_to_array($iterator, false);
	}
}

if ( !function_exists('wd_array_super_unique') ) {
	function wd_array_super_unique( $array, $key ) {
		$temp_array = array();
		foreach ( $array as &$v ) {
			if ( !isset($temp_array[ $v[ $key ] ]) ) {
				$temp_array[ $v[ $key ] ] =& $v;
			}
		}
		$array = array_values($temp_array);
		return $array;
	}
}

if ( !function_exists('wd_current_page_url') ) {
	/**
	 * Returns the current page url
	 *
	 * @return string
	 */
	function wd_current_page_url() {
		$pageURL = 'http';

		$port = !empty($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : 80;

		$server_name = !empty($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '';
		$server_name = empty($server_name) && !empty($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $server_name;

		if ( isset($_SERVER['HTTPS']) ) {
			if ( $_SERVER['HTTPS'] == 'on' ) {
				$pageURL .= 's';}
		}
		$pageURL .= '://';
		if ( $port != '80' ) {
			$pageURL .= $server_name . ':' . $port . $_SERVER['REQUEST_URI'];
		} else {
			$pageURL .= $server_name . $_SERVER['REQUEST_URI'];
		}
		return $pageURL;
	}
} 
if ( !function_exists('wpdreams_hex2rgb') ) {
	/**
	 * HEX to RGB string conversion
	 *
	 * Works both 3-6 lengths, with or without hash tags
	 *
	 * @param $color
	 * @return bool|string
	 * @uses wpdreams_rgb2hex()
	 */
	function wpdreams_hex2rgb( $color ) {
		if ( strlen($color) >7 ) {
			$color = wpdreams_rgb2hex($color);
		}
		if ( strlen($color) >7 ) {
			return $color;
		}
		if ( strlen($color) <3 ) {
			return '0, 0, 0';
		}
		if ( $color[0] == '#' ) {
			$color = substr($color, 1);
		}
		if ( strlen($color) == 6 ) {
			list($r, $g, $b) = array(
				$color[0] . $color[1],
				$color[2] . $color[3],
				$color[4] . $color[5],
			);
		} elseif ( strlen($color) == 3 ) {
			list($r, $g, $b) = array( $color[0] . $color[0], $color[1] . $color[1], $color[2] . $color[2] );
		} else {
			return false;
		}
		$r = hexdec($r);
		$g = hexdec($g);
		$b = hexdec($b); 
		return $r . ', ' . $g . ', ' . $b;
	}  
}

if ( !function_exists('wpdreams_rgb2hex') ) {
	/**
	 * RGB to HEX string converter
	 *
	 * @param $color
	 * @return string
	 */
	function wpdreams_rgb2hex( $color ) {
		if ( strlen($color) >7 ) {
			preg_match('/.*?\((\d+), (\d+), (\d+).*?/', $color, $c);
			if ( is_array($c) && count($c) >3 ) {
				$color  = '#' . sprintf('%02X', $c[1]);
				$color .= sprintf('%02X', $c[2]);
				$color .= sprintf('%02X', $c[3]);
			}
		}
		return $color;
	}
}

if ( !function_exists('asp_get_image_from_content') ) {
	/**
	 * Gets an image from the HTML content
	 *
	 * @param mixed        $content
	 * @param mixed        $number
	 * @param array|string $exclude
	 * @return bool|string
	 */
	function asp_get_image_from_content( $content, $number = 0, $exclude = array() ) {
		if ( !is_string($content) || $content === '' || !class_exists('domDocument') ) {
			return false;
		}

		if ( function_exists('mb_encode_numericentity') ) {
			$encoded_content = mb_encode_numericentity(
				htmlspecialchars_decode(
					htmlentities( $content, ENT_NOQUOTES, 'UTF-8', false ),
					ENT_NOQUOTES
				),
				array( 0x80, 0x10FFFF, 0, ~0 ),
				'UTF-8'
			);
		} else {
			$encoded_content = $content;
		}

		if ( $encoded_content === '' ) {
			return false;
		}

		// The arguments expects 1 as the first image, while it is the 0th
		$number = intval($number) - 1;
		$number = $number < 0 ? 0 : $number;

		if ( !is_array($exclude) ) {
			$exclude = strval($exclude);
			$exclude = explode(',', $exclude);
		}
		foreach ( $exclude as $k => &$v ) {
			$v = trim($v);
			if ( $v === '' ) {
				unset($exclude[ $k ]);
			}
		}

		$elements   = array( 'img', 'div' );
		$attributes = array(
			'src',
			'data-src-fg',
			'data-img',
			'data-image',
			'data-thumbnail',
			'data-thumb',
			'data-imgsrc',
		);
		$im         = '';

		foreach ( $elements as $element ) {
			$dom = new domDocument();
			/**
			 * The libxml_use_internal_errors & libxml_clear_errors solutions
			 * seem not to work on some servers, so we are stuck using @ operator instead.
			 */
			@$dom->loadHTML($encoded_content); // phpcs:ignore
			$dom->preserveWhiteSpace = false;  // phpcs:ignore
			@$images                 = $dom->getElementsByTagName($element); // phpcs:ignore
			if ( $images->length > 0 ) {
				$get = $images->length > $number ? $number : 0;
				for ( $i =$get;$i <$images->length;$i++ ) {
					foreach ( $attributes as $att ) {
						$im = $images->item($i)->getAttribute($att);
						if ( !empty($im) ) {
							break;
						}
					}
					foreach ( $exclude as $ex ) {
						if ( strpos($im, $ex) !== false ) {
							$im = '';
							continue 2;
						}
					}
					break;
				}
				if ( $im !== '' ) {
					return $im;
				}
			}
		}

		// Still no image
		return false;
	}
}

if ( !function_exists('asp_kses_content') ) {
	function asp_kses_content( string $content ): string {
		$tags          = wp_kses_allowed_html('post');
		$tags['input'] = array(
			'value'     => true,
			'min'       => true,
			'max'       => true,
			'name'      => true,
			'type'      => true,
			'class'     => true,
			'size'      => true,
			'pattern'   => true,
			'inputmode' => true,
		);
		if ( isset($tags['a']) ) {
			$tags['a']['style'] = true;
		} else {
			$tags['a'] = array(
				'class' => true,
				'style' => true,
			);
		}
		return wp_kses($content, $tags);
	}
}


if ( !function_exists('wpdreams_get_image_from_content') ) {
	/* Backwards compatibility */
	function wpdreams_get_image_from_content( $content, $number ) {
		return asp_get_image_from_content($content, $number);
	}
}

/* Extra Functions */
if ( !function_exists('wd_isEmpty') ) {
	/**
	 * @param $v
	 * @return bool
	 */
	function wd_isEmpty( $v ) {
		if ( trim($v) != '' ) {
			return false;
		} else {
			return true;
		}
	}
}

if ( !function_exists('wpdreams_on_backend_post_editor') ) {
	/**
	 * Checks if current page is the post editor
	 *
	 * @return bool
	 */
	function wpdreams_on_backend_post_editor() {
		$current_url = wd_current_page_url();
		return ( strpos($current_url, 'post-new.php') !==false ||
			strpos($current_url, 'post.php') !==false );
	}
}

if ( !function_exists('wpd_mem_convert') ) {
	/**
	 * Converts number to memory value with units
	 *
	 * @param $size
	 * @return string
	 */
	function wpd_mem_convert( $size ) {
		if ( $size <= 0 ) {
			return '0B';
		}
		$unit =array( 'B', 'KB', 'MB', 'GB', 'TB', 'PB' );
		return @round($size /pow(1024, ( $i =floor(log($size, 1024)) )), 2) . ' ' . $unit[ $i ];
	}
}

if ( !function_exists('asp_get_search_query') ) {
	function asp_get_search_query() {
		return isset($_GET['asp_ls']) ? esc_attr(stripslashes($_GET['asp_ls'])) : get_search_query( false );
	}
}


// ----------------------------------------------------------------------------------------------------------------------
// 3. TAXONOMY AND TERM SPECIFIC
// ----------------------------------------------------------------------------------------------------------------------

if ( !function_exists('wd_sort_terms_hierarchicaly') ) {
	/**
	 * Recursively sort an array of taxonomy terms hierarchically. Child categories will be
	 * placed under a 'children' member of their parent term. Handles missing parent categories as well.
	 *
	 * @param Array   $cats     taxonomy term objects to sort, use get_terms(..)
	 * @param Array   $into     result array to put them in
	 * @param integer $parentId the current parent ID to put them in
	 * @param integer $depth the current recursion depth
	 */
	function wd_sort_terms_hierarchicaly( array &$cats, array &$into, $parentId = 0, $depth = 0 ) {
		foreach ( $cats as $i => $cat ) {
			if ( $cat->parent == $parentId ) {
				$into[ $cat->term_id ] = $cat;
				unset($cats[ $i ]);
			}
		}

		foreach ( $into as $k => $topCat ) {
			$into[ $k ]->children = array();
			wd_sort_terms_hierarchicaly($cats, $into[ $k ]->children, $topCat->term_id, $depth + 1);
		}

		// Use a copy to go through, as the original is modified
		$copy_cats = $cats;

		// Try the remaining - the first parent might be excluded
		if ( is_array($copy_cats) && $depth == 0 ) {
			foreach ( $copy_cats as $k => $topCat ) {
				// This item might not exist in the original array, check it first
				if ( isset($cats[ $k ]) ) {
					$cats[ $k ]->children = array();
					wd_sort_terms_hierarchicaly($cats, $cats[ $k ]->children, $topCat->term_id, $depth + 1);
				}
			}
		}

		// Still any remaining for some satanic reason? Put the rest to the end...
		if ( is_array($cats) && $depth == 0 ) {
			foreach ( $cats as $i => $cat ) {
				$into[ $cat->term_id ] = $cat;
				unset( $cats[ $i ] );
			}
		}
	}
}


if ( !function_exists('wd_flatten_hierarchical_terms') ) {
	/**
	 * Flattens a hierarchical array of terms into a flat array, marking levels.
	 * Keeps ordering, sets a $cat->level attribute
	 *
	 * @param Array $cats     Taxonomy term objects to sort, use get_terms(..)
	 * @param Array $into     Target array
	 * @param int   $level    The current recursion depth
	 */
	function wd_flatten_hierarchical_terms( array &$cats, array &$into, $level = 0 ) {
		foreach ( $cats as $i => $cat ) {
			$cat->level = $level;
			$into[]     = $cat;
			if ( isset($cat->children) && count($cat->children) > 0 ) {
				wd_flatten_hierarchical_terms( $cat->children, $into, $level + 1 );
			}
		}

		// We don't need the children structure
		foreach ( $into as $cat ) {
			unset($cat->children);
		}
	}
}


// ----------------------------------------------------------------------------------------------------------------------
// 4. BACK-END SPECIFIC
// ----------------------------------------------------------------------------------------------------------------------

if ( !function_exists('w_isset_def') ) {
	function w_isset_def( &$v, $d ) {
		if ( isset($v) ) {
			return $v;
		}
		return $d;
	}
}

if ( !function_exists('wpdreams_parse_params') ) {
	/**
	 * This method is intended to use on params BEFORE written into the DB
	 *
	 * @param $params
	 * @return mixed
	 */
	function wpdreams_parse_params( $params ) {
		foreach ( $params as $k =>$v ) {
			$_tmp = explode('classname-', $k);
			if ( $_tmp !=null && count($_tmp) >1 ) {
				ob_start();
				$c                                = new $v('0', '0', $params[ $_tmp[1] ]);
				$out                              = ob_get_clean();
				$params[ 'selected-' . $_tmp[1] ] = $c->getSelected();
			}
			$_tmp = null;
			$_tmp = explode('wpdfont-', $k);
			if ( $_tmp !=null && count($_tmp) >1 ) {
				ob_start();
				$c                              = new $v('0', '0', $params[ $_tmp[1] ]);
				$out                            = ob_get_clean();
				$params[ 'import-' . $_tmp[1] ] = $c->getImport();

			}
		}
		return $params;
	}
}

if ( !function_exists('wpdreams_admin_hex2rgb') ) {
	function wpdreams_admin_hex2rgb( $color ) {
		if ( strlen($color) >7 ) {
			return $color;
		}
		if ( strlen($color) <3 ) {
			return 'rgba(0, 0, 0, 1)';
		}
		if ( $color[0] == '#' ) {
			$color = substr($color, 1);
		}
		if ( strlen($color) == 6 ) {
			list($r, $g, $b) = array(
				$color[0] . $color[1],
				$color[2] . $color[3],
				$color[4] . $color[5],
			);
		} elseif ( strlen($color) == 3 ) {
			list($r, $g, $b) = array( $color[0] . $color[0], $color[1] . $color[1], $color[2] . $color[2] );
		} else {
			return false;
		}
		$r = hexdec($r);
		$g = hexdec($g);
		$b = hexdec($b);
		return 'rgba(' . $r . ', ' . $g . ', ' . $b . ', 1)';
	}
}

if ( !function_exists('wpdreams_admin_rgb2hex') ) {
	function wpdreams_admin_rgb2hex( $color ) {
		if ( $color[0] == '#' ) {
			return $color;
		}
		if ( strlen($color) == 6 ) {
			return '#' . $color;
		}
		preg_match('/.*?\((.*?),[\s]*(.*?),[\s]*(.*?)[,\)]/', $color, $matches);

		return '#' . dechex($matches[1]) . dechex($matches[2]) . dechex($matches[3]);
	}
}

if ( !function_exists('wpdreams_four_to_string') ) {
	function wpdreams_four_to_string( $data ) {
		// 1.Top 2.Bottom 3.Right 4.Left
		preg_match('/\|\|(.*?)\|\|(.*?)\|\|(.*?)\|\|(.*?)\|\|/', $data, $matches);
		// 1.Top 3.Right 2.Bottom 4.Left
		return $matches[1] . ' ' . $matches[3] . ' ' . $matches[2] . ' ' . $matches[4];
	}
}

if ( !function_exists('wpdreams_four_to_array') ) {
	function wpdreams_four_to_array( $data ) {
		// 1.Top 2.Bottom 3.Right 4.Left
		preg_match('/\|\|(.*?)\|\|(.*?)\|\|(.*?)\|\|(.*?)\|\|/', $data, $matches);
		// 1.Top 3.Right 2.Bottom 4.Left
		return array(
			'top'    => $matches[1],
			'right'  => $matches[3],
			'bottom' => $matches[2],
			'left'   => $matches[4],
		);
	}
}

if ( !function_exists('wpdreams_box_shadow_css') ) {
	function wpdreams_box_shadow_css( $css ) {
		$css = str_replace("\n", '', $css);
		preg_match('/box-shadow:(.*?)px (.*?)px (.*?)px (.*?)px (.*?);/', $css, $matches);
		$ci         = $matches[5];
		$hlength    = $matches[1];
		$vlength    = $matches[2];
		$blurradius = $matches[3];
		$spread     = $matches[4];
		$moz_blur   = ( $blurradius >2 ) ?$blurradius - 2 :0;
		if ( $hlength ==0 && $vlength ==0 && $blurradius ==0 && $spread ==0 ) {
			echo 'box-shadow: none;';
		} else {
			echo 'box-shadow:' . $hlength . 'px ' . $vlength . 'px ' . $moz_blur . 'px ' . $spread . 'px ' . $ci . ';';
			echo '-webkit-box-shadow:' . $hlength . 'px ' . $vlength . 'px ' . $blurradius . 'px ' . $spread . 'px ' . $ci . ';';
			echo '-ms-box-shadow:' . $hlength . 'px ' . $vlength . 'px ' . $blurradius . 'px ' . $spread . 'px ' . $ci . ';';
		}
	}
}

if ( !function_exists('wpdreams_gradient_css') ) {
	function wpd_gradient_get_color_only( $data ) {
		if ( preg_match('/rgba\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*,\s*0\s*\)/', $data) ) {
			return 'transparent';
		}

		preg_match('/.*\-(.*?)$/', $data, $matches);
		if ( isset($matches[1]) ) {
			return wpdreams_admin_hex2rgb($matches[1]);
		} else {
			return 'transparent';
		}
	}
}

if ( !function_exists('wpdreams_gradient_css') ) {
	function wpdreams_gradient_css( $data, $print = true ) {

		$data = str_replace("\n", '', $data);
		if ( $data == '' ) {
			return '';
		}

		preg_match('/(.*?)-(.*?)-(.*?)-(.*)/', $data, $matches);

		if ( !isset($matches[1]) || !isset($matches[2]) || !isset($matches[3]) ) {
			// Probably only 1 color..
			if ( $print ) {
				echo 'background: ' . $data . ';';
			}
			return 'background: ' . $data . ';';
		}

		$type       = $matches[1];
		$deg        = $matches[2];
		$color1     = wpdreams_admin_hex2rgb($matches[3]);
		$color2     = wpdreams_admin_hex2rgb($matches[4]);
		$color1_hex = wpdreams_admin_rgb2hex($color1);
		$color2_hex = wpdreams_admin_rgb2hex($color2);

		// Check for full transparency
		preg_match('/rgba\(.*?,.*?,.*?,[\s]*(.*?)\)/', $color1, $opacity1);
		preg_match('/rgba\(.*?,.*?,.*?,[\s]*(.*?)\)/', $color2, $opacity2);
		if ( isset($opacity1[1]) && $opacity1[1] == '0' && isset($opacity2[1]) && $opacity2[1] == '0' ) {
			if ( $print ) {
				echo 'background: transparent;';
			}
			return 'background: transparent;';
		}

		ob_start();

		if ( $type !='0' || $type !=0 ) {
			?>
			background-image: -webkit-linear-gradient(<?php echo $deg; ?>deg, <?php echo $color1; ?>, <?php echo $color2; ?>);
			background-image: -moz-linear-gradient(<?php echo $deg; ?>deg, <?php echo $color1; ?>, <?php echo $color2; ?>);
			background-image: -o-linear-gradient(<?php echo $deg; ?>deg, <?php echo $color1; ?>, <?php echo $color2; ?>);
			background-image: -ms-linear-gradient(<?php echo $deg; ?>deg, <?php echo $color1; ?> 0%, <?php echo $color2; ?> 100%);
			background-image: linear-gradient(<?php echo $deg; ?>deg, <?php echo $color1; ?>, <?php echo $color2; ?>);
			<?php
		} else {
			// radial
			?>
			background-image: -moz-radial-gradient(center, ellipse cover,  <?php echo $color1; ?>, <?php echo $color2; ?>);
			background-image: -webkit-gradient(radial, center center, 0px, center center, 100%, <?php echo $color1; ?>, <?php echo $color2; ?>);
			background-image: -webkit-radial-gradient(center, ellipse cover,  <?php echo $color1; ?>, <?php echo $color2; ?>);
			background-image: -o-radial-gradient(center, ellipse cover,  <?php echo $color1; ?>, <?php echo $color2; ?>);
			background-image: -ms-radial-gradient(center, ellipse cover,  <?php echo $color1; ?>, <?php echo $color2; ?>);
			background-image: radial-gradient(ellipse at center,  <?php echo $color1; ?>, <?php echo $color2; ?>);
			<?php
		}
		$out = ob_get_clean();
		if ( $print ) {
			echo $out;
		}
		return $out;
	}
}

if ( !function_exists('wpdreams_border_width') ) {
	function wpdreams_border_width( $css ) {
		$css = str_replace("\n", '', $css);
		preg_match('/border:(.*?)px (.*?) (.*?);/', $css, $matches);

		return intval($matches[1]);
	}
}

if ( !function_exists('wpdreams_width_from_px') ) {
	function wpdreams_width_from_px( $css ) {
		$css = str_replace("\n", '', $css);
		preg_match('/(.*?)px/', $css, $matches);

		return intval($matches[1]);
	}
}

// ----------------------------------------------------------------------------------------------------------------------
// 5. NON-AJAX RESULTS
// ----------------------------------------------------------------------------------------------------------------------

if ( !class_exists('ASP_Post') ) {
	/**
	 * Class ASP_Post
	 *
	 * A default class to instantiate to generate post like results.
	 */
	class ASP_Post {

		public $ID                    = 0;                     // Don't use negative value, because WPML will break into pieces
		public $post_title            = '';
		public $post_author           = '';
		public $post_name             = '';
		public $post_type             = 'post';         // Everything unknown is going to be a post
		public $post_date             = '0000-00-00 00:00:00';             // Format: 0000-00-00 00:00:00
		public $post_date_gmt         = '0000-00-00 00:00:00';         // Format: 0000-00-00 00:00:00
		public $post_content          = '';          // The full content of the post
		public $post_content_filtered = '';
		public $post_excerpt          = '';          // User-defined post excerpt
		public $post_status           = 'publish';    // See get_post_status for values
		public $comment_status        = 'closed';  // Returns: { open, closed }
		public $ping_status           = 'closed';     // Returns: { open, closed }
		public $post_password         = '';         // Returns empty string if no password
		public $post_parent           = 0;            // Parent Post ID (default 0)
		public $post_mime_type        = '';
		public $to_ping               = '';
		public $pinged                = '';
		public $post_modified         = '';         // Format: 0000-00-00 00:00:00
		public $post_modified_gmt     = '';     // Format: 0000-00-00 00:00:00
		public $comment_count         = 0;          // Number of comments on post (numeric string)
		public $menu_order            = 0;             // Order value as set through page-attribute when enabled (numeric string. Defaults to 0)
		public $guid                  = '';
		public $asp_guid;
		public $asp_id;
		public $asp_data;                   // All the original results data
		public $blogid;
		public $filter;

		public function __construct() {}
	}
}

if ( !function_exists('asp_results_to_wp_obj') ) {
	/**
	 * Converts ajax results from Ajax Search Pro to post like objects to be displayable
	 * on the regular search results page.
	 *
	 * @param $results
	 * @param int    $from
	 * @param string $count
	 * @return array
	 */
	function asp_results_to_wp_obj( $results, $from = 0, $count = 'all' ) {
		if ( empty($results) ) {
			return array();
		}

		if ( $count == 'all' ) {
			$results_slice = array_slice($results, $from);
		} else {
			$results_slice = array_slice($results, $from, $count);
		}

		if ( empty($results_slice) ) {
			return array();
		}

		$wp_res_arr = array();

		$date_format = get_option('date_format');
		$time_format = get_option('time_format');

		$current_date = date($date_format . ' ' . $time_format, time());

		foreach ( $results_slice as $r ) {

			$switched_blog = false;
			if ( is_multisite() ) {
				if ( get_current_blog_id() != $r->blogid ) {
					switch_to_blog($r->blogid);
					$switched_blog = true;
				}
			}

			if ( !isset($r->content_type) ) {
				continue;
			}

			switch ( $r->content_type ) {
				case 'attachment':
				case 'pagepost':
					$res           = get_post($r->id);
					$res->asp_guid = get_permalink($r->id);
					$r->asp_guid   = $res->asp_guid;
					$res->asp_id   = $r->id;  // Save the ID in case needed for some reason
					$res->blogid   = $r->blogid;
					if ( isset($r->link) ) {
						$res->guid = $r->link;
					}

					/**
					 * On multisite the page and other post type links are filtered in such a way
					 * that the post type object is reset with get_post(), deleting the ->asp_guid
					 * attribute. Therefor the post type post must be enforced.
					 */
					if ( is_multisite() && $res->post_type != 'post' ) {
						// Is this a WooCommerce search?
						if (
						!(
							in_array($res->post_type, array( 'product', 'product_variation' )) &&
							isset($_GET['post_type']) &&
							$_GET['post_type'] == 'product'
						)
						) {
							$res->post_type = 'post'; // Enforce
							if ( $switched_blog ) {
								$res->ID = -10;
							}
						}
					}

					// Attachments
					if ( $r->content_type == 'attachment' ) {
						// Force post post type, so the post_type_link hook activates
						$res->post_type = 'post';
					}

					break;
				case 'blog':
					$res               = new ASP_Post();
					$res->post_title   = $r->title;
					$res->asp_guid     = $r->link;
					$res->guid         = $r->link;
					$res->post_content = $r->content;
					$res->post_excerpt = $r->content;
					$res->post_date    = $current_date;
					$res->asp_id       = $r->id;
					$res->ID           = -10;
					break;
				case 'bp_group':
					$res               = new ASP_Post();
					$res->post_title   = $r->title;
					$res->asp_guid     = $r->link;
					$res->guid         = $r->link;
					$res->post_content = $r->content;
					$res->post_excerpt = $r->content;
					$res->post_date    = $r->date;
					$res->asp_id       = $r->id;
					$res->ID           = -10;
					break;
				case 'bp_activity':
					$res               = new ASP_Post();
					$res->post_title   = $r->title;
					$res->asp_guid     = $r->link;
					$res->guid         = $r->link;
					$res->post_content = $r->content;
					$res->post_excerpt = $r->content;
					$res->post_date    = $r->date;
					$res->asp_id       = $r->id;
					$res->ID           = -10;
					break;
				case 'comment':
					$res = get_post($r->post_id);
					if ( isset($res->post_title) ) {
						$res->post_title   = $r->title;
						$res->guid         = $r->link;
						$res->asp_guid     = $r->link;
						$res->asp_id       = $r->id;
						$res->post_content = $r->content;
						$res->post_excerpt = $r->content;
					}
					break;
				case 'term':
					$res             = new ASP_Post();
					$res->post_title = $r->title;
					$res->asp_guid   = $r->link;
					$res->guid       = $r->link;
					$res->post_date  = $current_date;
					$res->asp_id     = $r->id;
					$res->ID         = -10;
					break;
				case 'user':
					$res             = new ASP_Post();
					$res->post_title = $r->title;
					$res->asp_guid   = $r->link;
					$res->guid       = $r->link;
					$res->post_date  = $current_date;
					$res->asp_id     = $r->id;
					$res->ID         = -10;
					break;
				case 'peepso_group':
					if ( class_exists('PeepSoGroup') ) {
						$pg            = new PeepSoGroup($r->id);
						$res           = get_post($r->id);
						$res->asp_guid = $pg->get_url();
						$res->asp_id   = $r->id;  // Save the ID in case needed for some reason
					}
					break;
				case 'peepso_activity':
					$res           = get_post($r->id);
					$res->asp_guid = get_permalink($r->id);
					$res->asp_id   = $r->id;  // Save the ID in case needed for some reason
					break;
			}

			if ( !empty($res) ) {
				$res->asp_data = $r;
				$res           = apply_filters('asp_regular_search_result', $res, $r);
				$wp_res_arr[]  = $res;
			}

			if ( is_multisite() ) {
				restore_current_blog();
			}
		}

		return $wp_res_arr;
	}
}

if ( !function_exists('get_asp_result_field') ) {
	function get_asp_result_field( $field = 'all', $post = false ) {
		if ( $post === false ) {
			global $post;
		}

		if ( !is_string($field) ) {
			return false;
		}

		if ( $field === 'all' ) {
			if ( isset($post, $post->asp_data) ) {
				return $post->asp_data;
			} else {
				return false;
			}
		} elseif ( isset($post, $post->asp_data) && property_exists($post->asp_data, $field) ) {
				return $post->asp_data->{$field};
		} else {
			return false;
		}
	}
}
if ( !function_exists('the_asp_result_field') ) {
	function the_asp_result_field( $field = 'title', $echo = true ) {
		if ( $echo ) {
			if ( !is_string($field) ) {
				return;
			}
			$print = $field == 'all' ? '' : get_asp_result_field($field);
			if ( $print !== false ) {
				echo $print;
			}
		} else {
			return get_asp_result_field($field);
		}
	}
}



// ----------------------------------------------------------------------------------------------------------------------
// 6. FRONT-END
// ----------------------------------------------------------------------------------------------------------------------
if ( !function_exists('asp_acf_get_post_object_field_values') ) {
	function asp_acf_get_post_object_field_values( $field_object ) {
		$return  = array();
		$_return = array();
		foreach ( $field_object['post_type'] as $post_type ) {
			$posts = get_posts(
				array(
					'post_type'        => $post_type,
					'posts_per_page'   => -1,
					'post_status'      => 'publish',
					'suppress_filters' => false,
				) 
			);
			if ( !is_wp_error($posts) && count($posts) > 0 ) {
				$_return = array_merge($_return, $posts);
			}
		}
		foreach ( $_return as $r ) {
			$return[ $r->ID ] = $r->post_title;
		}
		return $return;
	}
}

if ( !function_exists('asp_acf_get_field_choices') ) {
	function asp_acf_get_field_choices( $field_name, $multi = false ) {
		$results = array();
		if ( trim($field_name) == '' ) {
			return $results;
		}

		if ( isset($GLOBALS['acf_register_field_group']) ) {
			foreach ( $GLOBALS['acf_register_field_group'] as $acf ) {
				foreach ( $acf['fields'] as $field ) {
					if ( substr($field['key'], 0, 6) == 'field_' ) {
						if ( $field['name'] == $field_name && isset($field['choices']) ) {
							if ( !$multi ) {
								return $field['choices'];
							} else {
								$results [] = $field;
							}
						}
					}               
				}
			}
		}

		/*
		 * Method 2: This should be tried first, as Method 3 seems to miss some of the fields (reported via support)
		 * Reference: https://wp-dreams.com/forums/topic/get_value-filter-not-updating
		 */
		if ( empty($results) ) {
			$fkeys = asp_acf_get_field_keys($field_name);
			if ( !empty($fkeys) ) {
				foreach ( $fkeys as $fkey ) {
					$field = get_field_object($fkey);
					if ( is_array($field) && isset($field['type']) ) {
						if ( $field['type'] == 'post_object' ) {
							$results = asp_acf_get_post_object_field_values($field);
						} elseif ( !empty($field['choices']) ) {
								/**
								 * The + operator is needed here instead of array_merge
								 * array_merge reindexes numeric array keys, but we need to perserve the array keys
								 */
								$results = $results + $field['choices'];
						}
					}
				}
			}
		}

		// Method 3: Let us try going through the ACF registered post types
		if ( empty($results) ) {
			$acf_posts = get_posts(
				array(
					'post_type'        => 'acf',
					'posts_per_page'   => -1,
					'post_status'      => 'all',
					'suppress_filters' => false,
				) 
			);
			if ( !is_wp_error($acf_posts) ) {
				foreach ( $acf_posts as $acf ) {
					$meta = get_post_meta($acf->ID);
					foreach ( $meta as $key => $field ) {
						if ( substr($key, 0, 6) == 'field_' ) {
							$field = unserialize($field[0]);
							if ( $field['name'] == $field_name && isset($field['choices']) ) {
								if ( !$multi ) {
									return $field['choices'];
								} else {
									$results [] = $field;
								}
							}
						}
					}
				}
			}
		}

		return $results;
	}
}

if ( !function_exists('asp_acf_get_field_type') ) {
	function asp_acf_get_field_type( $field_name ) {
		$type = false;
		if ( trim($field_name) == '' ) {
			return $type;
		}

		if ( isset($GLOBALS['acf_register_field_group']) ) {
			foreach ( $GLOBALS['acf_register_field_group'] as $acf ) {
				foreach ( $acf['fields'] as $field ) {
					if ( substr($field['key'], 0, 6) == 'field_' ) {
						if ( $field['name'] == $field_name && isset($field['type']) ) {
							$type = $field['type'];
						}
					}               
				}
			}
		}

		/*
		 * Method 2: This should be tried first, as Method 3 seems to miss some of the fields (reported via support)
		 * Reference: https://wp-dreams.com/forums/topic/get_value-filter-not-updating
		 */
		if ( $type === false ) {
			$fkeys = asp_acf_get_field_keys($field_name);
			if ( !empty($fkeys) ) {
				$fkey  = end($fkeys);
				$field = get_field_object($fkey);
				if ( !empty($field['type']) ) {
					$type = $field['type'];
				}
			}
		}

		// Method 3: Let us try going through the ACF registered post types
		if ( $type === false ) {
			$acf_posts = get_posts(
				array(
					'post_type'        => 'acf',
					'posts_per_page'   => -1,
					'post_status'      => 'all',
					'suppress_filters' => false,
				) 
			);
			if ( !is_wp_error($acf_posts) ) {
				foreach ( $acf_posts as $acf ) {
					$meta = get_post_meta($acf->ID);
					foreach ( $meta as $key => $field ) {
						if ( substr($key, 0, 6) == 'field_' ) {
							$field = unserialize($field[0]);
							if ( $field['name'] == $field_name && isset($field['type']) ) {
								$type = $field['type'];
								break 2;
							}
						}
					}
				}
			}
		}
		if ( isset($field, $field['type'], $field['multiple']) && $type == 'select' && $field['multiple'] == 1 ) {
			$type = 'multiselect';
		}
		return $type;
	}
}

if ( !function_exists('asp_acf_get_field_keys') ) {
	/**
	 * @param string $field_name
	 * @return string[]|false
	 */
	function asp_acf_get_field_keys( string $field_name ) {
		global $wpdb;
		$acf_fields = wp_cache_get("asp_acf_get_field_keys_$field_name", 'asp');
		if ( $acf_fields === false ) {
			$acf_fields = $wpdb->get_results(
				$wpdb->prepare(
					"SELECT ID,post_parent,post_name FROM $wpdb->posts WHERE post_excerpt=%s AND post_type=%s",
					$field_name,
					'acf-field'
				)
			);
			wp_cache_set( "asp_acf_get_field_keys_$field_name", $acf_fields, 'asp' );
		}
		// get all fields with that name.
		switch ( count( $acf_fields ) ) {
			case 0: // no such field
				return false;
			case 1: // just one result.
				return array( $acf_fields[0]->post_name );
			default:
				return array_map(
					function ( $field_post ) {
						return $field_post->post_name;
					},
					$acf_fields
				);
		}
	}
}

if ( !function_exists('asp_parse_filters') ) {
	function asp_parse_filters( $id, $o, $clear = false, $to_display = true ) {
		if ( !isset(wd_asp()->front_filters) || is_object(wd_asp()->front_filters) ) {
			wd_asp()->front_filters = FiltersManager::getInstance();
		}
		wd_asp()->front_filters->setSearchId($id);

		$o['_id'] = $id;

		/**
		 * This is very important to cache. In some cases via search override this function gets called
		 * many times, and caused memory issues. This cache resolves that.
		 */
		static $called              = array();
		static $called_with_display = array();

		if ( $to_display ) {
			if ( $clear ) {
				wd_asp()->front_filters->clear($id);
				if ( !in_array($id, $called_with_display) ) {
					$called_with_display[] = $id;
				}
			} else {
				if ( in_array($id, $called_with_display) ) {
					return;
				}
				$called_with_display[] = $id;
			}
		} elseif ( $clear ) {
				wd_asp()->front_filters->clear($id);
			if ( !in_array($id, $called) ) {
				$called[] = $id;
			}
		} else {
			if ( in_array($id, $called) ) {
				return;
			}
			$called[] = $id;
		}

		do_action('asp_pre_parse_filters', $id, $o);

		asp_parse_tax_term_filters($o);
		asp_parse_content_type_filters($o);
		asp_parse_custom_field_filters($o, $to_display);
		asp_parse_post_type_filters($o);
		asp_parse_date_filters($o);
		asp_parse_generic_filters($o);
		asp_parse_post_tag_filters($o);
		asp_parse_button_filters($o);

		do_action('asp_post_parse_filters', $id, $o);
	}
}


if ( !function_exists('asp_parse_custom_field_filters') ) {
	function asp_parse_custom_field_filters( $o, $to_display = true ) {
		global $wpdb;

		if ( w_isset_def($o['custom_field_items'], '') == '' ) {
			return array();
		}

		// $filters = array();
		$f  = explode('|', $o['custom_field_items']);
		$id = $o['_id'];

		foreach ( $f as $k => $v ) {
			$m = null;
			// $f[$k] = json_decode(base64_decode($v));
			$bfield = json_decode(base64_decode($v));

			$user_meta  = isset($bfield->asp_f_source) && $bfield->asp_f_source == 'usermeta';
			$table_name = $user_meta ? $wpdb->usermeta : $wpdb->postmeta;
			$type       = $user_meta ? 'user' : 'post';

			$logic                            = '';
			$logic_and_separate_custom_fields = false;

			if ( !empty($bfield->asp_f_checkboxes_logic) ) {
				$logic = $bfield->asp_f_checkboxes_logic;
			}
			if ( !empty($bfield->asp_f_dropdown_logic) ) {
				$logic = $bfield->asp_f_dropdown_logic;
			}
			$date_store_format = '';
			if ( !empty($bfield->asp_f_datepicker_operator) ) {
				$date_store_format = w_isset_def($bfield->asp_f_datepicker_store_format, 'acf');
			}

			if ( $bfield->asp_f_type == 'range' || $bfield->asp_f_type == 'number_range' ) {
				$bfield->asp_f_operator = 'BETWEEN';
			}

			if ( strtolower( $logic ) == 'andse' ) {
				$logic                            = 'AND';
				$logic_and_separate_custom_fields = true;
			}

			$data = array(
				'field'                            => $bfield->asp_f_field,
				'source'                           => w_isset_def($bfield->asp_f_source, 'postmeta'),
				'required'                         => w_isset_def($bfield->asp_f_required, 'asp_unchecked') == 'asp_checked',
				'invalid_input_text'               => w_isset_def($bfield->asp_f_invalid_input_text, 'This field is required!'),
				'operator'                         => !empty($bfield->asp_f_datepicker_operator) ? $bfield->asp_f_datepicker_operator : $bfield->asp_f_operator,
				'logic'                            => $logic,
				'logic_and_separate_custom_fields' => $logic_and_separate_custom_fields,
				'date_store_format'                => $date_store_format,
				'is_api'                           => false,
			);

			if ( isset($bfield->asp_f_dropdown_search_text) ) {
				$data['placeholder'] = asp_icl_t('[CF Filter Placeholder] (' . $id . ') ' . $bfield->asp_f_dropdown_search_text, $bfield->asp_f_dropdown_search_text);
			}

			$label = w_isset_def($bfield->asp_f_show_title, 'asp_checked') == 'asp_checked' ? $bfield->asp_f_title : '';
			if ( $label != '' ) {
				$label = asp_icl_t('[CF Filter Label] (' . $id . ') ' . $label, $label);
			}

			$filter            = wd_asp()->front_filters->create(
				'custom_field',
				$label,
				$bfield->asp_f_type,
				$data
			);
			$unique_field_name = $filter->getUniqueFieldName();

			if ( !$to_display ) {
				wd_asp()->front_filters->add($filter);
				continue;
			}

			// $f[$k]->asp_f_title = asp_icl_t('[CF Filter Label] ' . $bfield->asp_f_title, $bfield->asp_f_title);

			if ( isset($bfield->asp_f_text_value) ) {
				$add     = array();
				$default = $bfield->asp_f_text_value;
				$default = asp_icl_t('[CF Filter Value] (' . $id . ') ' . $default, $default);
				$label   = str_replace('**', '', $bfield->asp_f_title);
				$label   = asp_icl_t('[CF Filter] (' . $id . ') ' . $label, $label);

				if ( isset($o['_fo'], $o['_fo']['aspf'][ $unique_field_name ]) ) {
					$add['value'] = $o['_fo']['aspf'][ $unique_field_name ];
				} else {
					$add['value'] = $default;
				}

				$add = array_merge(
					$add,
					array(
						'label'   => $label,
						'default' => $default,
					)
				);
				$filter->add($add);
			}

			if ( isset($bfield->asp_f_hidden_value) ) {
				$add     = array();
				$default = $bfield->asp_f_hidden_value;
				$default = asp_icl_t('[CF Filter Value] (' . $id . ') ' . $default, $default);

				if ( isset($o['_fo'], $o['_fo']['aspf'][ $unique_field_name ]) ) {
					$add['value'] = $o['_fo']['aspf'][ $unique_field_name ];
				} else {
					$add['value'] = $default;
				}

				$add = array_merge(
					$add,
					array(
						'label'   => '',
						'default' => $default,
					)
				);
				$filter->add($add);
			}

			if ( isset($bfield->asp_f_radio_value) ) {
				$lines = preg_split("/\\r\\n|\\r|\\n/", $bfield->asp_f_radio_value);
				// $f[$k]->asp_f_radio_value = array();
				foreach ( $lines as $kk => $val ) {
					$add_arr = array();
					if ( strpos(trim($val), '{get_values') === 0 ) {
						foreach ( FrontendFilters::getCFValues($bfield->asp_f_field, $type, $val) as $k => $item ) {
							$add_arr[] = array(
								'label' => $item['label'] . ( strpos(trim($val), 'checked') !== false && $k == 0 ? '**' : '' ),
								'value' => $item['value'],
							);
						}
					} else {
						preg_match('/^(.*?)\|\|(.*)/', $val, $m);
						if ( !isset($m[1], $m[2]) ) {
							$add_arr[] = array(
								'label' => '',
								'value' => '',
							);
						} else {
							$add_arr[] = array(
								'label' => $m[2],
								'value' => $m[1],
							);
						}
					}

					if ( count($add_arr) > 0 ) {
						foreach ( $add_arr as $add ) {
							$default = strpos($add['label'], '**') > 0;
							$label   = str_replace('**', '', $add['label']);
							$label   = asp_icl_t('[CF Filter] (' . $id . ') ' . $label, $label);

							if ( isset($o['_fo']) && isset($o['_fo']['aspf'][ $unique_field_name ]) ) {
								$checked = $o['_fo']['aspf'][ $unique_field_name ] == $add['value'];
							} else {
								$checked = $default;
							}

							$add['value'] = apply_filters('asp_cf_radio_values', $add['value'], $bfield);
							$add['value'] = asp_icl_t('[CF Filter Value] (' . $id . ') ' . $add['value'], $add['value']);
							$add          = array_merge(
								$add,
								array(
									'label'    => $label,
									'default'  => $default,
									'selected' => $checked,
								)
							);
							if ( $add['label'] != '' ) {
								$filter->add($add);
							}
						}
					}
				}
			}
			if ( isset($bfield->asp_f_dropdown_value) ) {
				if ( w_isset_def($bfield->asp_f_dropdown_search, 'asp_unchecked') == 'asp_checked' ) {
					if ( w_isset_def($bfield->asp_f_dropdown_multi, 'asp_unchecked') == 'asp_checked' ) {
						$filter->display_mode     = 'multisearch';
						$filter->data['multiple'] = true;
					} else {
						$filter->display_mode = 'dropdownsearch';
					}
				} else {
					$filter->display_mode = 'dropdown';
					if ( w_isset_def($bfield->asp_f_dropdown_multi, 'asp_unchecked') == 'asp_checked' ) {
						$filter->data['multiple'] = true;
					}
				}

				$lines = preg_split("/\\r\\n|\\r|\\n/", $bfield->asp_f_dropdown_value);
				foreach ( $lines as $kk => $val ) {
					$add_arr = array();

					if ( strpos(trim($val), '{get_values') === 0 ) {
						foreach ( FrontendFilters::getCFValues($bfield->asp_f_field, $type, $val) as $k => $item ) {
							if ( $filter->data['multiple'] ) {
								$add_arr[] = array(
									'label' => $item['label'] . ( strpos(trim($val), 'checked') !== false ? '**' : '' ),
									'value' => $item['value'],
								);
							} else {
								$add_arr[] = array(
									'label' => $item['label'] . ( strpos(trim($val), 'checked') !== false && $k == 0 ? '**' : '' ),
									'value' => $item['value'],
								);
							}
						}
					} elseif ( MB::strpos($val, '||') !== false ) {    // Individual value
						preg_match('/^(.*?)\|\|(.*)/', $val, $m);
						if ( !isset($m[1], $m[2]) ) {
							$add_arr[] = array(
								'label' => '',
								'value' => '',
							);
						} else {
							$add_arr[] = array(
								'label' => $m[2],
								'value' => $m[1],
							);
						}
					} else {    // Option group
						$add_arr[] = array(
							'label'        => $val,
							'value'        => $val,
							'option_group' => true,
						);
					}

					foreach ( $add_arr as $add ) {
						$default = strpos($add['label'], '**') > 0;
						$label   = str_replace('**', '', $add['label']);
						if ( isset($add['option_group']) ) {
							$label = asp_icl_t('[CF Filter Option Group] (' . $id . ') ' . $label, $label);
						} else {
							$label = asp_icl_t('[CF Filter] (' . $id . ') ' . $label, $label);
						}

						// Special case because of the multi-select
						if ( isset($o['_fo']) && isset($o['_fo']['aspf'][ $unique_field_name ]) ) {
							$o['_fo']['aspf'][ $unique_field_name ] = is_array($o['_fo']['aspf'][ $unique_field_name ]) ?
								$o['_fo']['aspf'][ $unique_field_name ] : array( $o['_fo']['aspf'][ $unique_field_name ] );
							$checked                                = in_array($add['value'], $o['_fo']['aspf'][ $unique_field_name ]);
						} else {
							$checked = $default;
						}

						// Backwards filter compatibility
						$vals    = apply_filters('asp_cf_dropdown_values', array( $add['value'], $label ), $bfield);
						$vals[0] = asp_icl_t('[CF Filter Value] (' . $id . ') ' . $vals[0], $vals[0]);

						$add = array_merge(
							$add,
							array(
								'label'    => $vals[1],
								'value'    => $vals[0],
								'default'  => $default,
								'selected' => $checked,
							)
						);
						if ( $add['label'] != '' ) {
							$filter->add($add);
						}
					}
				}
			}
			if ( isset($bfield->asp_f_checkboxes_value) ) {
				$lines = preg_split("/\\r\\n|\\r|\\n/", $bfield->asp_f_checkboxes_value);
				foreach ( $lines as $kk => $val ) {
					$add_arr = array();
					if ( strpos(trim($val), '{get_values') === 0 ) {
						foreach ( FrontendFilters::getCFValues($bfield->asp_f_field, $type, $val) as $k => $item ) {
							$add_arr[] = array(
								'label' => $item['label'] . ( strpos(trim($val), 'checked') !== false ? '**' : '' ),
								'value' => $item['value'],
							);
						}
					} else {
						preg_match('/^(.*?)\|\|(.*)/', $val, $m);
						// $f[$k]->asp_f_checkboxes_value[] = array($m[1], asp_icl_t('[CF Filter] ' . $m[2], $m[2]));
						if ( !isset($m[1], $m[2]) ) {
							$add_arr[] = array(
								'label' => '',
								'value' => '',
							);
						} else {
							$add_arr[] = array(
								'label' => $m[2],
								'value' => $m[1],
							);
						}
					}

					foreach ( $add_arr as $add ) {
						$default = strpos($add['label'], '**') > 0;
						$label   = str_replace('**', '', $add['label']);
						$label   = asp_icl_t('[CF Filter] (' . $id . ') ' . $label, $label);

						if ( isset($o['_fo'], $o['_fo']['aspf']) ) {
							// Select all variation, keep it on the default value, as it is not possible to check
							if ( $add['value'] == '' && $kk == 0 ) {
								$checked = $default;
							} else {
								$checked = isset($o['_fo']['aspf'][ $unique_field_name ])
									&& is_array($o['_fo']['aspf'][ $unique_field_name ]) && in_array($add['value'], $o['_fo']['aspf'][ $unique_field_name ]);
							}
						} else {
							$checked = $default;
						}

						$select_all = $kk == 0 & $add['value'] == '';

						$vals    = apply_filters('asp_cf_checkbox_values', array( $add['value'], $label ), $bfield);
						$vals[0] = asp_icl_t('[CF Filter Value] (' . $id . ') ' . $vals[0], $vals[0]);

						$add = array_merge(
							$add,
							array(
								'label'      => $vals[1],
								'value'      => $vals[0],
								'default'    => $default,
								'selected'   => $checked,
								'select_all' => $select_all,
							)
						);
						if ( $add['label'] != '' ) {
							$filter->add($add);
						}
					}
				}
			}

			if ( isset($bfield->asp_f_number_range_from, $bfield->asp_f_number_range_to) ) {
				if ( $bfield->asp_f_number_range_from === '' ) {
					$bfield->asp_f_number_range_from = PostMeta::getNumericFieldMin($bfield->asp_f_field);
				}
				if ( $bfield->asp_f_number_range_to === '' ) {
					$bfield->asp_f_number_range_to = PostMeta::getNumericFieldMax($bfield->asp_f_field);
				}

				$default = array( $bfield->asp_f_number_range_default1, $bfield->asp_f_number_range_default2 );

				if ( isset($o['_fo']) && isset($o['_fo']['aspf'][ $unique_field_name ]['lower']) ) {
					$lower = $o['_fo']['aspf'][ $unique_field_name ]['lower'];
					$upper = $o['_fo']['aspf'][ $unique_field_name ]['upper'];
					$value = array(
						$lower === '' ? $lower : Str::forceNumeric($lower),
						$upper === '' ? $upper : Str::forceNumeric($upper),
					);
				} else {
					$value = $default;
				}

				$filter->data = array_merge(
					$filter->data,
					array(
						'placeholder1'      => $bfield->asp_f_number_range_placeholder1,
						'placeholder2'      => $bfield->asp_f_number_range_placeholder2,
						'range_from'        => $bfield->asp_f_number_range_from,
						'range_to'          => $bfield->asp_f_number_range_to,
						'range_t_separator' => $bfield->asp_f_number_range_t_separator ?? ' ',
					)
				);

				$filter->add(
					array(
						'label'   => '',
						'value'   => $value,
						'default' => $default,
					)
				);
			}

			if ( isset($bfield->asp_f_slider_from, $bfield->asp_f_slider_to) ) {
				if ( $bfield->asp_f_slider_from == '' ) {
					$bfield->asp_f_slider_from = PostMeta::getNumericFieldMin($bfield->asp_f_field);
				}
				if ( $bfield->asp_f_slider_to == '' ) {
					$bfield->asp_f_slider_to = PostMeta::getNumericFieldMax($bfield->asp_f_field);
				}

				$default = $bfield->asp_f_slider_default == '' ? $bfield->asp_f_slider_from : $bfield->asp_f_slider_default;
				if ( isset($o['_fo']) && isset($o['_fo']['aspf'][ $unique_field_name ]) ) {
					$value = Str::forceNumeric($o['_fo']['aspf'][ $unique_field_name ]);
				} else {
					$value = $default;
				}
				// $bfield->asp_f_slider_default = $bfield->asp_f_slider_default == '' ? $bfield->asp_f_slider_from : $bfield->asp_f_slider_default;
				$filter->add(
					array(
						'label'   => '',
						'default' => $default,
						'value'   => $value,
					)
				);
				$filter->data = array_merge(
					$filter->data,
					array(
						'slider_prefix'      => $bfield->asp_f_slider_prefix,
						'slider_suffix'      => $bfield->asp_f_slider_suffix,
						'slider_step'        => $bfield->asp_f_slider_step,
						'slider_from'        => $bfield->asp_f_slider_from,
						'slider_to'          => $bfield->asp_f_slider_to,
						'slider_decimals'    => w_isset_def($bfield->asp_f_slider_decimals, 0),
						'slider_t_separator' => isset($bfield->asp_f_slider_t_separator) ? $bfield->asp_f_slider_t_separator : ' ',
					)
				);
			}
			if ( isset($bfield->asp_f_range_from, $bfield->asp_f_range_to) ) {
				if ( $bfield->asp_f_range_from == '' ) {
					$bfield->asp_f_range_from = PostMeta::getNumericFieldMin($bfield->asp_f_field);
				}
				if ( $bfield->asp_f_range_to == '' ) {
					$bfield->asp_f_range_to = PostMeta::getNumericFieldMax($bfield->asp_f_field);
				}

				$bfield->asp_f_range_default1 = $bfield->asp_f_range_default1 == '' ? $bfield->asp_f_range_from : $bfield->asp_f_range_default1;
				$bfield->asp_f_range_default2 = $bfield->asp_f_range_default2 == '' ? $bfield->asp_f_range_to : $bfield->asp_f_range_default2;

				$default = array( $bfield->asp_f_range_default1, $bfield->asp_f_range_default2 );

				if ( isset($o['_fo']) && isset($o['_fo']['aspf'][ $unique_field_name ]['lower']) ) {
					$value = array(
						Str::forceNumeric($o['_fo']['aspf'][ $unique_field_name ]['lower']),
						Str::forceNumeric($o['_fo']['aspf'][ $unique_field_name ]['upper']),
					);
				} else {
					$value = $default;
				}

				$filter->data = array_merge(
					$filter->data,
					array(
						'range_prefix'      => $bfield->asp_f_range_prefix,
						'range_suffix'      => $bfield->asp_f_range_suffix,
						'range_step'        => $bfield->asp_f_range_step,
						'range_from'        => $bfield->asp_f_range_from,
						'range_to'          => $bfield->asp_f_range_to,
						'range_decimals'    => w_isset_def($bfield->asp_f_range_decimals, 0),
						'range_t_separator' => isset($bfield->asp_f_range_t_separator) ? $bfield->asp_f_range_t_separator : ' ',
					)
				);

				$filter->add(
					array(
						'label'       => '',
						'slider_from' => $bfield->asp_f_range_from,
						'slider_to'   => $bfield->asp_f_range_to,
						'value'       => $value,
						'default'     => $default,
					)
				);
			}

			if ( $bfield->asp_f_type == 'datepicker' ) {
				switch ( $bfield->asp_f_datepicker_defval ) {
					case 'none':
						$default = '';
						break;
					case 'current':
						$default = '+0';
						break;
					case 'relative':
						$default = $bfield->asp_f_datepicker_from_months . 'm ' . $bfield->asp_f_datepicker_from_days . 'd';
						break;
					default:
						$default = implode('-', array_reverse(explode('/', $bfield->asp_f_datepicker_value)));
						break;
				}

				if ( isset($o['_fo']) && isset($o['_fo']['aspf'][ $unique_field_name ]) ) {
					$value = sanitize_text_field($o['_fo']['aspf'][ $unique_field_name ]);
				} else {
					$value = $default;
				}

				if ( !isset($bfield->asp_f_datepicker_placeholder) ) {
					// $bfield->asp_f_datepicker_placeholder = '';
					$filter->data['placeholder'] = '';
				} else {
					// $bfield->asp_f_datepicker_placeholder = asp_icl_t('[CF Date Filter Placeholder] ' . $bfield->asp_f_datepicker_placeholder, $bfield->asp_f_datepicker_placeholder);
					$filter->data['placeholder'] = asp_icl_t('[CF Date Filter Placeholder] (' . $id . ') ' . $bfield->asp_f_datepicker_placeholder, $bfield->asp_f_datepicker_placeholder);
				}
				$filter->data['date_format'] = $bfield->asp_f_datepicker_format;

				$filter->add(
					array(
						'label'   => '',
						'value'   => $value,
						'default' => $default,
					)
				);
			}

			// $filters[] = $filter;
			wd_asp()->front_filters->add($filter);
		}
		// return $f;
	}
}

if ( !function_exists('asp_parse_tax_term_filters') ) {
	/**
	 * Parses the taxonomy term filters from the search options
	 *
	 * @param array $o Search options
	 * @return aspTaxFilter[] Filters array
	 */
	function asp_parse_tax_term_filters( $o ) {
		/**
		 * Return value
		 * array[{
		 *      ->label
		 *      ->display_mode
		 *      ->values => array[{
		 *          ->label
		 *          ->selected
		 *          ->id
		 *          ->level
		 *          ->default
		 *          ->parent
		 *          ->taxonomy
		 *      }]
		 * }, ...]
		 */
		// $filters = array();
		$term_ordering = w_isset_def($o['selected-frontend_term_order'], array( 'name', 'ASC' ));
		$hide_empty    = w_isset_def($o['frontend_terms_hide_empty'], 0);

		// Re-group by taxonomy
		$tax_terms = array();
		foreach ( $o['show_terms']['terms'] as $t ) {
			if ( !isset($tax_terms[ $t['taxonomy'] ]) ) {
				$tax_terms[ $t['taxonomy'] ] = array();
			}
			$tax_terms[ $t['taxonomy'] ][ $t['id'] ] = $t;
		}

		if ( isset($o['_fo']) &&
			!isset($o['_fo']['termset']) ) {
			$o['_fo']['termset'] = array();
		}

		foreach ( $tax_terms as $taxonomy => $terms ) {

			$term_ids = array();
			foreach ( $terms as $k => $t ) {
				$term_ids[] = $t['id'];
			}

			if ( !empty($terms) ) {

				if ( isset($o['_fo']) &&
					!isset($o['_fo']['termset'][ $taxonomy ]) ) {
					$o['_fo']['termset'][ $taxonomy ] = array();
				}

				// Need all terms
				if ( count($terms) == 1 && isset($terms[-1], $terms[-1]['id']) && $terms[-1]['id'] == -1 ) {
					$_needed_all        = true;
					$_needed_terms_full = get_terms(
						$taxonomy,
						array(
							'taxonomy'   => $taxonomy,
							'orderby'    => $term_ordering[0],
							'order'      => $term_ordering[1],
							'hide_empty' => $hide_empty,
							'exclude'    => isset($terms[-1]['ex_ids']) ? $terms[-1]['ex_ids'] : array(),
						)
					);
				} else {
					$_needed_all        = false;
					$_needed_terms_full = get_terms(
						$taxonomy,
						array(
							'taxonomy'   => $taxonomy,
							'orderby'    => 'include',
							'order'      => 'ASC',
							'include'    => $term_ids,
							'hide_empty' => $hide_empty,
						)
					);
				}

				/*
				 * DO NOT CONTINUE HERE
				 *
				 * The filter should be still considered, as the user either:
				 *  - use the asp_fontend_get_taxonomy_terms hook
				 *  - the taxonomy is not yet registered and only the asp_parse_filters() function is trying to
				 *    fetch the filters for the Asset managers
				 */
				if ( is_wp_error($_needed_terms_full) ) {
					$_needed_terms_full = array();
				}

				$_needed_terms_full = apply_filters(
					'asp_fontend_get_taxonomy_terms',
					$_needed_terms_full,
					$taxonomy,
					array(
						'orderby'     => $term_ordering[0],
						'order'       => $term_ordering[1],
						'include'     => $terms,
						'include_ids' => $term_ids,
					),
					$_needed_all,
					$o,
				);

				$_needed_terms_sorted = array();
				$needed_terms_flat    = array();

				// This is passed as the $data argument
				$display_mode = array(
					'type'                 => 'checkboxes',
					'required'             => false,
					'invalid_input_text'   => 'This field is required!',
					'default'              => 'checked',
					'select_all'           => 0,
					'select_all_text'      => '',
					'box_header_text'      => '',
					'box_placeholder_text' => '',
				);

				if ( count($o['show_terms']['display_mode']) > 0 ) {
					if ( isset($o['show_terms']['display_mode'][ $taxonomy ]) ) {
						$display_mode = array_merge($display_mode, $o['show_terms']['display_mode'][ $taxonomy ]);
					}
				}
				$display_mode['taxonomy'] = $taxonomy;

				if ( w_isset_def($o['frontend_term_hierarchy'], 1) == 1 ) {
					wd_sort_terms_hierarchicaly( $_needed_terms_full, $_needed_terms_sorted );
					wd_flatten_hierarchical_terms( $_needed_terms_sorted, $needed_terms_flat );
				} else {
					$needed_terms_flat = $_needed_terms_full;
				}

				$display_mode['is_api']      = false;
				$display_mode['placeholder'] = $display_mode['box_placeholder_text'];

				$current_filter = wd_asp()->front_filters->create('taxonomy', $display_mode['box_header_text'], $display_mode['type'], $display_mode);

				if ( $display_mode['type'] == 'checkboxes' ) {

					if ( $display_mode['select_all'] == 1 && $current_filter->isEmpty() ) {
						$filter = array(
							'label'    => $display_mode['select_all_text'],
							'selected' => $display_mode['select_all'] == 1,
							'default'  => $display_mode['select_all'] == 1,
							'taxonomy' => $display_mode['select_all'] == 0 ? 'terms' : $taxonomy,
							'level'    => 0,
						);
						$current_filter->add($filter);
					}

					$chb_default_state = true;
					if ( $display_mode['default'] == 'unchecked' ) {
						$chb_default_state = false;
					}

					foreach ( $needed_terms_flat as $k => $term ) {
						$filter = array(
							'label'    => $term->name,
							'id'       => $term->term_id,
							'taxonomy' => $taxonomy,
							'level'    => $term->level,
						);

						/**
						 * Explanation: $tax_terms contains the originals sorted by taxonomy.
						 * If the current term is in the array, then it was excplicitly selected, not by "Use all from.."
						 * In this case it is only unchecked when in the un_checked array. Otherwise the default state applies.
						 */
						if ( isset($tax_terms[ $term->taxonomy ][ $term->term_id ]) ) {
							$filter['default'] = !in_array($term->term_id, $o['show_terms']['un_checked']);
						} else {
							$filter['default'] = $chb_default_state;
						}

						if ( isset($o['_fo']) ) {
							$filter['selected'] = in_array($term->term_id, $o['_fo']['termset'][ $taxonomy ]);
						} else {
							$filter['selected'] = $filter['default'];
						}

						$current_filter->add($filter);
					}               
				} elseif (
					$display_mode['type'] == 'dropdown' ||
					$display_mode['type'] == 'dropdownsearch' ||
					$display_mode['type'] == 'multisearch'
				) {
					if ( $display_mode['select_all'] == 1 && $current_filter->isEmpty() && $display_mode['type'] != 'multisearch' ) {
						$filter = array(
							'label'    => $display_mode['select_all_text'],
							'selected' => $display_mode['default'] == 'all',
							'default'  => $display_mode['default'] == 'all',
							'taxonomy' => $display_mode['select_all'] == 0 ? 'terms' : $taxonomy,
							'level'    => 0,
						);
						$current_filter->add($filter);
					}

					$len           = count($needed_terms_flat);
					$i             = 0;
					$have_selected = false;

					foreach ( $needed_terms_flat as $k => $term ) {
						$filter = array(
							'label'    => $term->name,
							'id'       => $term->term_id,
							'taxonomy' => $taxonomy,
							'level'    => $term->level,
						);

						if ( $display_mode['type'] == 'multisearch' ) {
							if ( isset($tax_terms[ $term->taxonomy ][ $term->term_id ]) ) {
								$filter['default'] = !in_array($term->term_id, $o['show_terms']['un_checked']);
							} else {
								$filter['default'] = false;
							}
						} elseif (
								!$have_selected && (
									( $i == 0 && $display_mode['default'] == 'first' ) ||
									( ( $i == $len -1 ) && $display_mode['default'] == 'last' ) ||
									$term->term_id == $display_mode['default']
								)
							) {

								$filter['default'] = true;
								$have_selected     = true;
						} else {
							$filter['default'] = false;
						}

						if ( isset($o['_fo']) ) {
							if ( !$have_selected ) {
								$filter['selected'] = in_array($term->term_id, $o['_fo']['termset'][ $taxonomy ]);
								if ( $filter['selected'] && $display_mode['type'] != 'multisearch' ) {
									$have_selected = true;
								}
							} else {
								$filter['selected'] = false;
							}
						} else {
							$filter['selected'] = $filter['default'];
						}

						$current_filter->add($filter);
						++$i;
					}               
				} elseif ( $display_mode['type'] == 'radio' ) {

					if ( $display_mode['select_all'] == 1 && $current_filter->isEmpty() ) {
						$filter = array(
							'label'    => $display_mode['select_all_text'],
							'selected' => $display_mode['default'] == 'all',
							'default'  => $display_mode['default'] == 'all',
							'taxonomy' => $display_mode['select_all'] == 0 ? 'terms' : $taxonomy,
							'level'    => 0,
						);
						$current_filter->add($filter);
					}

					$len           = count($needed_terms_flat);
					$i             = 0;
					$have_selected = false;

					foreach ( $needed_terms_flat as $k => $term ) {
						$filter = array(
							'label'    => $term->name,
							'id'       => $term->term_id,
							'taxonomy' => $taxonomy,
							'level'    => $term->level,
						);

						if (
							!$have_selected && (
								( $i == 0 && $display_mode['default'] == 'first' ) ||
								( ( $i == $len -1 ) && $display_mode['default'] == 'last' ) ||
								$term->term_id == $display_mode['default']
							)
						) {
							$filter['default'] = true;
							$have_selected     = true;
						} else {
							$filter['default'] = false;
						}

						if ( isset($o['_fo'], $o['_fo']['termset'], $o['_fo']['termset'][ $taxonomy ]) ) {
							$filter['selected'] = in_array($term->term_id, $o['_fo']['termset'][ $taxonomy ]);
							if ( $filter['selected'] ) {
								$have_selected = true;
							}
						} else {
							$filter['selected'] = $filter['default'];
						}
						$current_filter->add($filter);
						++$i;
					}
				}

				wd_asp()->front_filters->add($current_filter);
			}
			// --- TAX LOOP
		}
		// return apply_filters('asp_taxonomy_filters', $filters, $o);
	}
}

if ( !function_exists('asp_parse_post_tag_filters') ) {
	function asp_parse_post_tag_filters( $o ) {
		if (
			empty($o['selected-show_frontend_tags']) ||
			$o['selected-show_frontend_tags']['active'] == 0
		) {
			return;
		}

		/* Let us make it a bit more accessible */
		$_sfto = $o['selected-show_frontend_tags'];

		if ( $_sfto['source'] == 'all' ) {
			$_sftags = get_terms('post_tag', array( 'number' =>10000 ));
		} else {
			$_sftags = asp_get_terms_ordered_by_ids('post_tag', $_sfto['tag_ids']);
		}

		if ( empty($_sftags) ) {
			return;
		}

		if ( $_sfto['source'] == 'all' ) {
			$_sel_tags = $_sfto['default_tag'] != '' ? array( $_sfto['default_tag'] ) : array();
		} else {
			$_sel_tags = $_sfto['checked_tag_ids'];
		}

		if ( isset($o['_fo']) && !isset($o['_fo']['post_tag_set']) ) {
			$o['_fo']['post_tag_set'] = array();
		}

		$display_mode = array(
			'type'               => $_sfto['display_mode'],
			'taxonomy'           => 'post_tag',
			'custom_name'        => 'post_tag_set[]',
			'default'            => $_sfto['display_mode'] == 'checkboxes' ? ( $o['all_tags_check_opt_state'] == 'checked' ? 1 : 0 ) : 1,
			'select_all'         => $_sfto['display_mode'] == 'checkboxes' ? $o['display_all_tags_check_opt'] : $o['display_all_tags_option'],
			'select_all_text'    => $_sfto['display_mode'] == 'checkboxes' ? $o['all_tags_check_opt_text'] : $o['all_tags_opt_text'],
			'box_header_text'    => w_isset_def($o['frontend_tags_header'], 'Filter by Tags'),
			'placeholder'        => $o['frontend_tags_placeholder'],
			'required'           => $o['frontend_tags_required'] == 1,
			'invalid_input_text' => $o['frontend_tags_invalid_input_text'],
			'is_api'             => false,
		);

		$filter = wd_asp()->front_filters->create('post_tags', $display_mode['box_header_text'], $display_mode['type'], $display_mode);

		if ( $display_mode['select_all'] == 1 ) {
			$filter->add(
				array(
					'label'       => $display_mode['select_all_text'],
					'selected'    => $display_mode['default'],
					'default'     => $display_mode['default'],
					'taxonomy'    => 'post_tag',
					'custom_name' => 'post_tag_set[]',
				)
			);
		}

		foreach ( $_sftags as $_sftag ) {
			if ( $display_mode['type'] == 'checkboxes' && $_sfto['source'] == 'all' ) {
				$default = $_sfto['all_status'] == 'checked';
			} else {
				$default = in_array($_sftag->term_id, $_sel_tags);
			}
			if ( isset($o['_fo']) ) {
				$selected = in_array($_sftag->term_id, $o['_fo']['post_tag_set']);
			} else {
				$selected = $default;
			}

			$filter->add(
				array(
					'label'    => $_sftag->name,
					'id'       => $_sftag->term_id,
					'selected' => $selected,
					'default'  => $default,
					'taxonomy' => 'post_tag',
				)
			);
		}

		wd_asp()->front_filters->add($filter);
	}
}

if ( !function_exists('asp_parse_button_filters') ) {
	function asp_parse_button_filters( $o ) {
		if ( $o['fe_reset_button'] == 1 || $o['fe_search_button'] == 1 ) {
			$id             = $o['_id'];
			$filter         = wd_asp()->front_filters->create('button');
			$filter->is_api = false;
			if ( $o['fe_reset_button'] == 1 && $o['fe_rb_position'] == 'before' ) {
				$filter->add(
					array(
						'label'           => asp_icl_t('Reset button (' . $id . ') ', $o['fe_rb_text']),
						'type'            => 'reset',
						'container_class' => 'asp_r_btn_div',
						'button_class'    => 'asp_reset_btn asp_r_btn',
					)
				);
			}
			if ( $o['fe_search_button'] == 1 ) {
				$filter->add(
					array(
						'label'           => asp_icl_t('Search button (' . $id . ') ', $o['fe_sb_text']),
						'type'            => 'search',
						'container_class' => 'asp_s_btn_div',
						'button_class'    => 'asp_search_btn asp_s_btn',
					)
				);
			}
			if ( $o['fe_reset_button'] == 1 && $o['fe_rb_position'] == 'after' ) {
				$filter->add(
					array(
						'label'           => asp_icl_t('Reset button (' . $id . ') ', $o['fe_rb_text']),
						'type'            => 'reset',
						'container_class' => 'asp_r_btn_div',
						'button_class'    => 'asp_reset_btn asp_r_btn',
					)
				);
			}
			wd_asp()->front_filters->add($filter);
		}
	}
}

if ( !function_exists('asp_parse_generic_filters') ) {
	function asp_parse_generic_filters( $o ) {
		if ( count($o['frontend_fields']['selected']) > 0 ) {
			$settingsFullyHidden =
				$o['show_frontend_search_settings'] != 1 &&
				$o['frontend_search_settings_visible'] != 1 ? true : false;

			$id = $o['_id'];

			if ( $settingsFullyHidden ) {
				$_checked_def = array(
					'exact'   => $o['exactonly'] == 1,
					'title'   => $o['searchintitle'] == 1,
					'content' => $o['searchincontent'] == 1,
					'excerpt' => $o['searchinexcerpt'] == 1,
				);
			} else {
				$_checked_def = array(
					'exact'   => in_array('exact', $o['frontend_fields']['checked']),
					'title'   => in_array('title', $o['frontend_fields']['checked']),
					'content' => in_array('content', $o['frontend_fields']['checked']),
					'excerpt' => in_array('excerpt', $o['frontend_fields']['checked']),
				);
			}

			// Search redirection, memorize options
			if ( isset($o['_fo']) ) {
				$o['_fo']['asp_gen'] = isset($o['_fo']['asp_gen']) ? $o['_fo']['asp_gen'] : array();
				$o['_fo']['asp_gen'] = is_array($o['_fo']['asp_gen']) ? $o['_fo']['asp_gen'] : array( $o['_fo']['asp_gen'] );
				$_checked            = array(
					'exact'   => in_array('exact', $o['_fo']['asp_gen']),
					'title'   => in_array('title', $o['_fo']['asp_gen']),
					'content' => in_array('content', $o['_fo']['asp_gen']),
					'excerpt' => in_array('excerpt', $o['_fo']['asp_gen']),
				);
			} else {
				$_checked = $_checked_def;
			}

			$filter         = wd_asp()->front_filters->create(
				'generic',
				asp_icl_t('Generic filter label (' . $id . ')', $o['generic_filter_label']),
				$o['frontend_fields']['display_mode'],
				$o['frontend_fields']
			);
			$filter->is_api = false;

			$filter->data['visible'] = count($o['frontend_fields']['selected']) > 0;

			foreach ( $o['frontend_fields']['selected'] as $item ) {
				$default  = $_checked_def[ $item ];
				$selected = $_checked[ $item ];
				$filter->add(
					array(
						'value'    => $item,
						'default'  => $default,
						'label'    => asp_icl_t('Generic field[' . $item . '] (' . $id . ')', $o['frontend_fields']['labels'][ $item ]),
						// "label" => asp_icl_t('Generic field[' . $item . ']', $o['frontend_fields']['labels'][$item]),
						'selected' => $selected,
						'field'    => $item,
					)
				);
			}

			wd_asp()->front_filters->add($filter);
		}
	}
}

if ( !function_exists('asp_parse_content_type_filters') ) {
	function asp_parse_content_type_filters( $o ) {
		if ( count($o['content_type_filter']['selected']) > 0 ) {
			$id = $o['_id'];
			// Search redirection, memorize options
			if ( isset($o['_fo']) ) {
				$_carr     = isset( $o['_fo']['asp_ctf'] ) && is_array( $o['_fo']['asp_ctf'] ) ? $o['_fo']['asp_ctf'] : array();
				$_carr_def = $o['content_type_filter']['checked'];
			} else {
				$_carr     = $o['content_type_filter']['checked'];
				$_carr_def = $o['content_type_filter']['checked'];
			}

			$_checked     = array(
				'any'         => in_array('any', $_carr),
				'cpt'         => in_array('cpt', $_carr),
				'comments'    => in_array('comments', $_carr),
				'taxonomies'  => in_array('taxonomies', $_carr),
				'users'       => in_array('users', $_carr),
				'blogs'       => in_array('blogs', $_carr),
				'buddypress'  => in_array('buddypress', $_carr),
				'attachments' => in_array('attachments', $_carr),
			);
			$_checked_def = array(
				'any'         => in_array('any', $_carr_def),
				'cpt'         => in_array('cpt', $_carr_def),
				'comments'    => in_array('comments', $_carr_def),
				'taxonomies'  => in_array('taxonomies', $_carr_def),
				'users'       => in_array('users', $_carr_def),
				'blogs'       => in_array('blogs', $_carr_def),
				'buddypress'  => in_array('buddypress', $_carr_def),
				'attachments' => in_array('attachments', $_carr_def),
			);

			if ( ( $akey = array_search('any', $o['content_type_filter']['selected']) ) !== false ) {
				unset($o['content_type_filter']['selected'][ $akey ]);
				$o['content_type_filter']['selected'] = array_merge(array( 'any' ), $o['content_type_filter']['selected']);
			}

			$filter         = wd_asp()->front_filters->create(
				'content_type',
				asp_icl_t('Content type filter label (' . $id . ')', $o['content_type_filter_label']),
				$o['content_type_filter']['display_mode'],
				$o['content_type_filter']
			);
			$filter->is_api = false;

			foreach ( $o['content_type_filter']['selected'] as $item ) {
				$value    = $item == 'any' ? -1 : $item;
				$default  = $_checked_def[ $item ];
				$selected = $_checked[ $item ];
				$filter->add(
					array(
						'value'    => $value,
						'default'  => $default,
						'label'    => asp_icl_t('Content Type field[' . $item . '] (' . $id . ')', $o['content_type_filter']['labels'][ $item ] ),
						'selected' => $selected,
						'field'    => $item,
					)
				);
			}

			wd_asp()->front_filters->add($filter);
		}
	}
}

if ( !function_exists('asp_parse_post_type_filters') ) {
	function asp_parse_post_type_filters( $o ) {
		if ( !isset($o['customtypes']) || !is_array($o['customtypes']) ) {
			$o['customtypes'] = array();
		}
		$id                  = $o['_id'];
		$settingsFullyHidden =
			$o['show_frontend_search_settings'] != 1 &&
			$o['frontend_search_settings_visible'] != 1 ? true : false;
		// When settings are fully hidden, do not show the options
		if (
			$settingsFullyHidden ||
			!isset($o['selected-showcustomtypes']) ||
			!is_array($o['selected-showcustomtypes'])
		) {
			$o['selected-showcustomtypes'] = array();
		}

		if ( count($o['selected-showcustomtypes']) > 0 ) {
			$filter         = wd_asp()->front_filters->create(
				'post_type',
				asp_icl_t('Custom post types label (' . $id . ')', w_isset_def($o['custom_types_label'], 'Filter by Custom Post Type')),
				$o['cpt_display_mode'],
				array(
					'required'           => $o['cpt_required'] == 1,
					'invalid_input_text' =>$o['cpt_invalid_input_text'],
				)
			);
			$filter->is_api = false;

			if ( $o['cpt_cbx_show_select_all'] == 1 ) {
				$filter->add(
					array(
						'value'    => -1,
						'default'  => true,
						'label'    => asp_icl_t('Select all checkbox for post types (' . $id . ')', $o['cpt_cbx_show_select_all_text']),
						'selected' => true,
						'field'    => 'select_all',
					)
				);
			}

			foreach ( $o['selected-showcustomtypes'] as $k => $item ) {

				if ( $o['cpt_display_mode'] == 'checkboxes' ) {
					$default = in_array($item[0], $o['customtypes']);
				} else {
					$default = $item[0] == $o['cpt_filter_default'];
				}
				if ( isset($o['_fo'], $o['_fo']['customset']) ) {
					$selected = in_array($item[0], $o['_fo']['customset']);
				} else {
					$selected = $default;
				}

				$filter->add(
					array(
						'value'    => $item[0],
						'default'  => $default,
						'label'    => asp_icl_t($item[0], $item[1]),
						'selected' => $selected,
						'field'    => 'cpt',
					)
				);
			}

			wd_asp()->front_filters->add($filter);
		}
	}
}

if ( !function_exists('asp_parse_date_filters') ) {
	function asp_parse_date_filters( $o ) {
		/* Options not set or deactivated, return - if one is set, other is definitely set too */
		if ( empty($o['selected-date_filter_from']) ) {
			return;
		}

		$_dff = &$o['selected-date_filter_from'];
		$_dft = &$o['selected-date_filter_to'];

		if ( $_dff['state'] == 'disabled' && $_dft['state'] == 'disabled' ) {
			return;
		}

		$id             = $o['_id'];
		$filter         = wd_asp()->front_filters->create(
			'date',
			'Post type date filters',
			'date',
			array(
				'required'           => $o['date_filter_required'] == 1,
				'invalid_input_text' => $o['date_filter_invalid_input_text'],
			)
		);
		$filter->is_api = false;

		$post_types = $o['customtypes'];
		if ( $o['return_attachments'] ) {
			$post_types[] = 'attachment';
		}
		if ( isset($o['selected-showcustomtypes']) && count($o['selected-showcustomtypes']) > 0 ) {
			$selected_post_types = array_map(
				function ( $n ) {
					return $n[0];
				},
				$o['selected-showcustomtypes']
			);
			$post_types          = array_unique(array_merge($post_types, $selected_post_types));
		}

		if ( $_dff['state'] != 'disabled' ) {
			switch ( $_dff['state'] ) {
				case 'rel_date':
					$_def_dff_v = '-' . $_dff['rel_date'][0] . 'y -' . $_dff['rel_date'][1] . 'm -' . $_dff['rel_date'][2] . 'd';
					break;
				case 'earliest_date':
					$_def_dff_v = Post::getEarliestPostDate(
						array(
							'post_type' => $post_types,
						)
					);
					break;
				case 'latest_date':
					$_def_dff_v = Post::getLatestPostDate(
						array(
							'post_type' => $post_types,
						)
					);
					break;
				case 'date':
				default:
					$_def_dff_v = $_dff['date'];
					break;
			}

			$_dff_v = isset($o['_fo']) && isset($o['_fo']['post_date_from']) ? sanitize_text_field($o['_fo']['post_date_from']) : $_def_dff_v;

			$filter->add(
				array(
					'value'       => $_dff_v,
					'default'     => $_def_dff_v,
					'name'        => 'post_date_from',
					'label'       => asp_icl_t( 'Post date filter: Content from (' . $id . ')', $o['date_filter_from_t'] ),
					'format'      => $o['date_filter_from_format'],
					'placeholder' => asp_icl_t( 'Post date filter placeholder (from)', $o['date_filter_from_placeholder'] ),
				)
			);
		}
		if ( $_dft['state'] != 'disabled' ) {
			switch ( $_dft['state'] ) {
				case 'rel_date':
					$_def_dft_v = '-' . $_dft['rel_date'][0] . 'y -' . $_dft['rel_date'][1] . 'm -' . $_dft['rel_date'][2] . 'd';
					break;
				case 'earliest_date':
					$_def_dft_v = Post::getEarliestPostDate(
						array(
							'post_type' => $post_types,
						)
					);
					break;
				case 'latest_date':
					$_def_dft_v = Post::getLatestPostDate(
						array(
							'post_type' => $post_types,
						)
					);
					break;
				case 'date':
				default:
					$_def_dft_v = $_dft['date'];
					break;
			}

			$_dft_v = isset($o['_fo']) && isset($o['_fo']['post_date_to']) ? sanitize_text_field($o['_fo']['post_date_to']) : $_def_dft_v;

			$filter->add(
				array(
					'value'       => $_dft_v,
					'default'     => $_def_dft_v,
					'name'        => 'post_date_to',
					'label'       => asp_icl_t( 'Post date filter: Content to (' . $id . ')', $o['date_filter_to_t'] ),
					'format'      => $o['date_filter_to_format'],
					'placeholder' => asp_icl_t( 'Post date filter placeholder (to) (' . $id . ')', $o['date_filter_to_placeholder'] ),
				)
			);
		}

		wd_asp()->front_filters->add($filter);
	}
}

if ( !function_exists('asp_is_multilang') ) {
	function asp_is_multilang(): bool {
		if ( function_exists('pll_register_string') && function_exists('pll__') ) {
			return true;
		} elseif ( defined('WPML_PLUGIN_BASENAME') || defined('ICL_SITEPRESS_VERSION') ) {
			return true;
		}

		return false;
	}
}

if ( !function_exists('asp_icl_t') ) {
	/* Ajax Search pro wrapper for WPML and Polylang print */
	function asp_icl_t( $name, $value, $esc_html = false ) {
		if ( $value == '' ) {
			return $value;
		}

		$ret = apply_filters('asp_icl_t', $value, $name, $esc_html);

		if ( function_exists('pll_register_string') && function_exists('pll__') ) {
			PolylangStringTranslations::add($name, $value);
			$ret = pll__($value);
		} elseif ( defined('WPML_PLUGIN_BASENAME') || defined('ICL_SITEPRESS_VERSION') ) {
			do_action( 'wpml_register_single_string', 'ajax-search-pro', $name, $value );
			$ret = apply_filters('wpml_translate_single_string', $value, 'ajax-search-pro', $name);
		} elseif ( function_exists('qtranxf_useCurrentLanguageIfNotFoundUseDefaultLanguage') ) {
			$ret = qtranxf_useCurrentLanguageIfNotFoundUseDefaultLanguage( $value );
		}
		if ( $esc_html ) {
			return esc_html( stripslashes( $ret ) );
		} else {
			return stripslashes( $ret );
		}
	}
}


if ( !function_exists('asp_gen_rnd_str') ) {
	function asp_gen_rnd_str( $length = 6 ) {
		$characters       = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
		$charactersLength = strlen($characters);
		$randomString     = '';
		for ( $i = 0; $i < $length; $i++ ) {
			$randomString .= $characters[ rand(0, $charactersLength - 1) ];
		}
		return $randomString;
	}
}


if ( !function_exists('asp_get_terms_ordered_by_ids') ) {
	function asp_get_terms_ordered_by_ids( $taxonomy, $ids ) {
		if ( empty($ids) ) {
			return array();
		}

		$tag_keys_arr   = array();
		$final_tags_arr = array();

		foreach ( $ids as $position => $tag_id ) {
			$tag_keys_arr[ $tag_id ] = $position;
		}

		$tags = get_terms($taxonomy, array( 'include' => $ids ));

		foreach ( $tags as $tag ) {
			if ( isset($tag_keys_arr[ $tag->term_id ]) ) {
				$final_tags_arr[ $tag_keys_arr[ $tag->term_id ] ] = $tag;
			}
		}

		ksort($final_tags_arr);

		return $final_tags_arr;
	}
}

if ( !function_exists('asp_register_advanced_field_option') ) {
	/**
	 * Registers a new Advanced Title and Content field
	 *
	 * Adds a new advanced title and content field with back-end options and front-end callback handler.
	 * See: https://knowledgebase.ajaxsearchpro.com/extend/advanced-field
	 *
	 * @param array{
	 *       field_name: string,
	 *       callback: callable(null|array<string, mixed>, null|stdClass):void,
	 *       field_type?: 'post_type'|'user',
	 *       field_label?: string,
	 *       attributes?: Array<string, array{
	 *            label: string,
	 *            placeholder: string,
	 *            type: 'text'|'textarea'|'number'|'color'|'select'|'multiselect'|'checkbox',
	 *            name?: string,
	 *            required?: 0|1,
	 *            size?: 'xs' | 's' | 'm' | 'l' | 'xl' | '50' | '10',
	 *            min?: integer,
	 *            max?: integer,
	 *            options?: string[] | array{label: string, value: string}[],
	 *            defaultValue?: string|array{label: string, value: string}|array{label: string, value: string}[]
	 *       }>
	 *   } $args of arguments including the field name, label, attributes and callback.
	 * @return void
	 */
	function asp_register_advanced_field_option( array $args ) {
		if ( empty($args['field_name']) ) {
			return;
		}
		$args['field_name'] = $args['field_name'] . '_html'; // HTML support
		$args['field_type'] = $args['field_type'] ?? 'post_type';
		add_action(
			'wd_asp_loading_start',
			function () use ( $args ) {
				add_action(
					'asp/utils/advanced-field',
					function ( $manager ) use ( $args ) {
						$manager->add($args);
					}
				);
				if ( isset($args['attributes']) ) {
					add_action(
						'asp/asset/js/wdo-asp-global-backend',
						function () use ( $args ) {
							\WPDRMS\ASP\Utils\Script::objectToInlineScript(
								'wdo-asp-global-backend',
								'AjaxSearchProBackend.Options.AdvancedFieldEditor.' . esc_attr($args['field_type']) . '.' . esc_attr($args['field_name']),
								array(
									'field_name'  => $args['field_name'],
									'field_label' => $args['field_label'] ?? $args['field_name'],
									'attributes'  => $args['attributes'],
								),
								'after',
								true
							);
						}
					);
				}
			}
		);
	}
}

// ----------------------------------------------------------------------------------------------------------------------
// 7. Polyfill
// Well, not exactly classic polyfill, but with asp_ prefixes
// ----------------------------------------------------------------------------------------------------------------------
if ( !function_exists('asp_wp_get_wp_version') ) {
	/**
	 * wp_get_wp_version function only exists in wp 6.7+
	 *
	 * @return mixed
	 */
	function asp_wp_get_wp_version() {
		static $wp_version;

		if ( !isset($wp_version) ) {
			require ABSPATH . WPINC . '/version.php';
		}

		return $wp_version;
	}
}

// ----------------------------------------------------------------------------------------------------------------------
// 8. Unit and Integration testing
// ----------------------------------------------------------------------------------------------------------------------
if ( !function_exists('asp_get_global_function_mock') ) {
	function asp_get_global_function_mock( string $name ): bool {
		return (
			defined('ASP_UNIT_TEST') &&
			isset($GLOBALS['_mocks']) &&
			is_object($GLOBALS['_mocks']) &&
			method_exists( $GLOBALS['_mocks'], $name)
		);
	}
}

if ( !function_exists('asp_run_global_function_mock') ) {
	function asp_run_global_function_mock( string $name, ...$args ): void {
		if ( asp_get_global_function_mock($name) ) {
			call_user_func(array( $GLOBALS['_mocks'], $name ), ...$args);
		}
	}
}