<?php

if ( ! defined( 'ABSPATH' ) ) {
	die( 'You are not allowed to call this page directly.' );
}

class FrmProAppHelper {

	/**
	 * @var bool
	 */
	private static $included_svg = false;

	/**
	 * @since 6.22.1
	 *
	 * @var string|null
	 */
	private static $current_user_email;

	public static function plugin_folder() {
		return basename( self::plugin_path() );
	}

	public static function plugin_path() {
		return dirname( __DIR__, 2 );
	}

	/**
	 * @return string
	 */
	public static function plugin_url() {
		return plugins_url( '', self::plugin_path() . '/formidable-pro.php' );
	}

	public static function relative_plugin_url() {
		return str_replace( array( 'https:', 'http:' ), '', self::plugin_url() );
	}

	/**
	 * @since 6.4.2
	 *
	 * @return string
	 */
	public static function get_svg_folder_path() {
		return self::plugin_path() . '/images/svg/';
	}

	/**
	 * Get the Pro settings
	 *
	 * @since 2.0
	 *
	 * @return FrmProSettings
	 */
	public static function get_settings() {
		global $frmpro_settings;

		if ( ! $frmpro_settings ) {
			$frmpro_settings = new FrmProSettings();
		}

		return $frmpro_settings;
	}

	/**
	 * Only load the Pro updater once on a page
	 *
	 * @since 3.04.03
	 *
	 * @return FrmProEddController
	 */
	public static function get_updater() {
		global $frmpro_updater;

		if ( ! $frmpro_updater ) {
			$frmpro_updater = new FrmProEddController();
		}

		return $frmpro_updater;
	}

	/**
	 * @since 4.09
	 *
	 * @return bool
	 */
	public static function views_is_installed() {
		return class_exists( 'FrmViewsAppHelper' );
	}

	/**
	 * Try to show the SVG if possible. Otherwise, use the font icon.
	 *
	 * @since 4.0.02
	 *
	 * @param string $class
	 * @param array  $atts
	 */
	public static function icon_by_class( $class, $atts = array() ) {
		if ( is_callable( 'FrmAppHelper::icon_by_class' ) ) {
			return FrmAppHelper::icon_by_class( $class, $atts );
		}
	}

	/**
	 * Load an independent svg file from the external folder. Returns the svg html or the inner HTML tags of the svg.
	 *
	 * @since 6.4.2
	 *
	 * @param string $slug
	 * @param bool $inner_html_only
	 * @param array $atts
	 *
	 * @return array|false|string
	 */
	private static function init_svg_by_slug( $slug, $inner_html_only = false, $atts = array() ) {
		$svg_file_path = self::get_svg_folder_path() . $slug . '.svg';

		if ( false === file_exists( $svg_file_path ) ) {
			return false;
		}

		$svg = file_get_contents( $svg_file_path );

		if ( false === $inner_html_only ) {
			return $svg;
		}

		$svg_atts = array_merge(
			array(
				'viewBox' => '',
			),
			$atts
		);

		$svg_element = array(
			'inner_html'  => '',
			'atts'        => $svg_atts,
			'atts_string' => '',
		);

		preg_match( '/viewBox\s*=\s*["\'](.*?)["\']/i', $svg, $matches );

		if ( ! empty( $matches[1] ) ) {
			$svg_element['atts']['viewBox'] = $matches[1];
		}

		$html_params = $svg_element['atts'];
		unset( $html_params['echo'] );
		$svg_element['atts_string'] = FrmAppHelper::array_to_html_params( $html_params );

		$svg_element['inner_html'] = preg_replace( '/^<svg[^>]*>|<\/svg>$/i', '', $svg );

		return $svg_element;
	}

	/**
	 * Get svg icon by slug.
	 *
	 * @since 6.4.2
	 *
	 * @param string $slug
	 * @param string $classnames
	 * @param array  $atts
	 *
	 * @return string
	 */
	public static function get_svg_icon( $slug, $classnames, $atts = array() ) {
		$echo        = $atts['echo'] ?? false;
		$inner_html  = ( isset( $classnames ) && '' !== $classnames ) || $atts;
		$svg_context = self::init_svg_by_slug( $slug, $inner_html, $atts );

		if ( false === $svg_context ) {
			return self::echo_or_return( '', $echo );
		}

		if ( ! is_array( $svg_context ) && false === $inner_html ) {
			return self::echo_or_return( $svg_context, $echo );
		}

		return self::echo_or_return(
			'<svg ' . $svg_context['atts_string'] . ' class="' . esc_attr( $classnames ) . '">' . $svg_context['inner_html'] . '</svg>',
			$echo
		);
	}

	/**
	 * Echo the string or return it.
	 *
	 * @since 6.4.2
	 *
	 * @param string $string
	 * @param bool $echo
	 */
	private static function echo_or_return( $string, $echo = true ) {
		if ( true === $echo ) {
			echo $string; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
			return;
		}

		return $string;
	}

	/**
	 * Get the current date in the display format
	 * Used by [date] shortcode
	 *
	 * @since 2.0
	 *
	 * @param string $format
	 *
	 * @return string
	 */
	public static function get_date( $format = '' ) {
		if ( ! $format ) {
			$frmpro_settings = self::get_settings();
			$format          = $frmpro_settings->date_format;
		}

		return date_i18n( $format, strtotime( current_time( 'mysql' ) ) );
	}

	/**
	 * Get the current time
	 * Used by [time] shortcode
	 *
	 * @since 2.0
	 *
	 * @param array $atts
	 *
	 * @return string
	 */
	public static function get_time( $atts = array() ) {
		$defaults     = array(
			'format' => 'H:i:s',
			'round'  => 0,
		);
		$atts         = array_merge( $defaults, (array) $atts );
		$current_time = strtotime( current_time( 'mysql' ) );

		if ( ! empty( $atts['round'] ) ) {
			$round_numerator = 60 * (float) $atts['round'];
			$current_time    = round( $current_time / $round_numerator ) * $round_numerator;
		}

		return date_i18n( $atts['format'], $current_time );
	}

	/**
	 * Format the time field values
	 *
	 * @since 2.0.14
	 *
	 * @param array|string $time
	 * @param string       $format
	 *
	 * @return string
	 */
	public static function format_time( $time, $format = 'H:i' ) {
		if ( is_array( $time ) ) {
			$time = '';
		}

		if ( $time !== '' ) {
			if ( $format === 'h:i A' ) {
				// For reverse compatibility
				$format = 'g:i A';
			}

			$time = gmdate( $format, strtotime( $time ) );
		}

		return $time;
	}

	public static function format_time_by_reference( &$time ) {
		$time = self::format_time( $time, 'H:i' );
	}

	/**
	 * Update the current user email.
	 * This fixes the issue where the global $current_user's email is not updated when wp_update_user() is called.
	 * See Registration issue #366
	 *
	 * @since 6.22.1
	 *
	 * @param int   $user_id
	 * @param array $userdata
	 *
	 * @return void
	 */
	public static function update_current_user_email( $user_id, $userdata ) {
		if ( get_current_user_id() === $user_id ) {
			self::$current_user_email = $userdata['user_email'];
		}
	}

	/**
	 * Get a value from the current user profile
	 *
	 * @since 2.0
	 *
	 * @param string $value
	 * @param bool   $return_array
	 *
	 * @return array|string
	 */
	public static function get_current_user_value( $value, $return_array = false ) {
		if ( 'user_email' === $value && self::$current_user_email ) {
			return self::$current_user_email;
		}
		global $current_user;
		$new_value = $current_user->{$value} ?? '';

		if ( is_array( $new_value ) && ! $return_array ) {
			return implode( ', ', $new_value );
		}

		return $new_value;
	}

	/**
	 * Get the id of the current user
	 * Used by [user_id] shortcode
	 *
	 * @since 2.0
	 *
	 * @return string
	 */
	public static function get_user_id() {
		$user_ID = get_current_user_id();
		return $user_ID ? $user_ID : '';
	}

	/**
	 * @since 6.14
	 *
	 * @return WP_Post|null
	 */
	private static function get_current_post_object() {
		global $post;

		if ( $post ) {
			return $post;
		}

		$post_id = FrmProFormState::get_from_request( 'global_post', '' );
		return $post_id ? get_post( $post_id ) : null;
	}

	/**
	 * Get a value from the currently viewed post
	 *
	 * @since 2.0
	 *
	 * @param string $value
	 *
	 * @return string
	 */
	public static function get_current_post_value( $value ) {
		$post = self::get_current_post_object();

		if ( ! $post ) {
			return;
		}

		return $post->{$value} ?? get_post_meta( $post->ID, $value, true );
	}

	/**
	 * Get the email of the author of current post
	 * Used by [post_author_email] shortcode
	 *
	 * @since 2.0
	 *
	 * @return string
	 */
	public static function get_post_author_email() {
		return get_the_author_meta( 'user_email' );
	}

	/**
	 * Check for either json or serialized data. This is temporary while transitioning
	 * all data to json.
	 *
	 * @since 4.02.03
	 *
	 * @param array|string $value
	 *
	 * @return void
	 */
	public static function unserialize_or_decode( &$value ) {
		FrmAppHelper::unserialize_or_decode( $value );
	}

	/**
	 * @since 2.0.2
	 */
	public static function display_to_datepicker_format() {
		$formats = array(
			'm/d/Y' => 'mm/dd/yy',
			'n/j/Y' => 'm/d/yy',
			'Y/m/d' => 'yy/mm/dd',
			'd/m/Y' => 'dd/mm/yy',
			'd.m.Y' => 'dd.mm.yy',
			'j/m/y' => 'd/mm/y',
			'j/n/y' => 'd/m/y',
			'Y-m-d' => 'yy-mm-dd',
			'j-m-Y' => 'd-mm-yy',
		);
		return apply_filters( 'frm_datepicker_formats', $formats );
	}

	/**
	 * @param string $date_str
	 * @param string $to_format
	 *
	 * @return false|string
	 */
	public static function maybe_convert_to_db_date( $date_str, $to_format = 'Y-m-d' ) {
		$date_str     = trim( $date_str );
		$in_db_format = preg_match( '/^\d{4}-\d{2}-\d{2}/', $date_str );

		if ( ! $in_db_format ) {
			return self::convert_date( $date_str, 'db', $to_format );
		}

		return $date_str;
	}

	public static function maybe_convert_from_db_date( $date_str, $from_format = 'Y-m-d' ) {
		$date_str     = trim( $date_str );
		$in_db_format = preg_match( '/^\d{4}-\d{2}-\d{2}/', $date_str );

		if ( $in_db_format ) {
			return self::convert_date( $date_str, $from_format, 'db' );
		}

		return $date_str;
	}

	/**
	 * @param string $date_str
	 * @param string $from_format
	 * @param string $to_format
	 *
	 * @return false|string
	 */
	public static function convert_date( $date_str, $from_format, $to_format ) {
		if ( 'db' === $to_format ) {
			$frmpro_settings = self::get_settings();
			$to_format       = $frmpro_settings->date_format;
		} elseif ( 'db' === $from_format ) {
			$frmpro_settings = self::get_settings();
			$from_format     = $frmpro_settings->date_format;
		}

		if ( $from_format === 'Y-m-d' && strpos( $date_str, '00:00:00' ) ) {
			$date_str = str_replace( ' 00:00:00', '', $date_str );
		}

		try {
			// Try/catch because a ValueError will be thrown if this string contains null bytes.
			$date = date_create_from_format( $from_format, $date_str );
		} catch ( Error $e ) {
			return false;
		}

		return $date ? $date->format( $to_format ) : self::convert_date_fallback( $date_str, $from_format, $to_format );
	}

	/**
	 * @param string $date_str
	 * @param string $from_format
	 * @param string $to_format
	 *
	 * @return false|string
	 */
	private static function convert_date_fallback( $date_str, $from_format, $to_format ) {
		$base_struc     = preg_split( '/[\/|.| |-]/', $from_format );
		$date_str_parts = preg_split( '/[\/|.| |-]/', $date_str );
		$date_elements  = array();
		$p_keys         = array_keys( $base_struc );

		foreach ( $p_keys as $p_key ) {
			if ( empty( $date_str_parts[ $p_key ] ) ) {
				return false;
			}

			$date_elements[ $base_struc[ $p_key ] ] = $date_str_parts[ $p_key ];
		}

		if ( ! is_numeric( $date_elements['m'] ) ) {
			$dummy_ts = strtotime( $date_str );
			return gmdate( $to_format, $dummy_ts );
		}

		$day  = $date_elements['j'] ?? $date_elements['d'];
		$year = $date_elements['Y'] ?? $date_elements['y'];

		if ( is_numeric( $day ) && is_numeric( $year ) ) {
			$dummy_ts = mktime( 0, 0, 0, $date_elements['m'], $day, $year );
		} else {
			$dummy_ts = strtotime( $date_str );
		}

		return gmdate( $to_format, $dummy_ts );
	}

	/**
	 * @return string
	 */
	public static function get_edit_link( $id ) {
		if ( current_user_can( 'administrator' ) ) {
			return '<a href="' . esc_url( FrmProEntry::admin_edit_link( $id ) ) . '">' . esc_html__( 'Edit', 'formidable' ) . '</a>';
		}

		return '';
	}

	public static function get_custom_post_types() {
		$custom_posts = get_post_types( array(), 'object' );

		foreach ( array( 'revision', 'attachment', 'nav_menu_item' ) as $unset ) {
			unset( $custom_posts[ $unset ] );
		}

		// alphebetize
		ksort( $custom_posts );

		// Keep post and page first
		$first_types = array(
			'post' => $custom_posts['post'],
			'page' => $custom_posts['page'],
		);

		return $first_types + $custom_posts;
	}

	/**
	 * @param array $field
	 *
	 * @return bool
	 */
	public static function get_custom_taxonomy( $post_type, $field ) {
		$taxonomies = get_object_taxonomies( $post_type );

		if ( ! $taxonomies ) {
			return false;
		}

		$field = (array) $field;

		if ( ! isset( $field['taxonomy'] ) ) {
			self::unserialize_or_decode( $field['field_options'] );
			$field['taxonomy'] = FrmField::get_option( $field, 'taxonomy' );
		}

		if ( isset( $field['taxonomy'] ) && in_array( $field['taxonomy'], $taxonomies ) ) {
			return $field['taxonomy'];
		}

		return $post_type === 'post' ? 'category' : reset( $taxonomies );
	}

	/**
	 * @param array $array
	 */
	public static function sort_by_array( $array, $order_array ) {
		$array       = (array) $array;
		$order_array = (array) $order_array;
		$ordered     = array();

		foreach ( $order_array as $key ) {
			if ( array_key_exists( $key, $array ) ) {
				$ordered[ $key ] = $array[ $key ];
				unset( $array[ $key ] );
			}
		}

		return $ordered + $array;
	}

	/**
	 * @param array $entry_ids
	 * @param array $args
	 *
	 * @return array
	 */
	public static function filter_where( $entry_ids, $args ) {
		$defaults = array(
			'where_opt'   => false,
			'where_is'    => '=',
			'where_val'   => '',
			'form_id'     => false,
			'form_posts'  => array(),
			'after_where' => false,
			'display'     => false,
			'drafts'      => 0,
			'use_ids'     => false,
		);

		$args = wp_parse_args( $args, $defaults );

		if ( ! (int) $args['form_id'] ) {
			return $entry_ids;
		}

		if ( self::should_get_field_id_from_where_opt_split_val( $args['where_opt'] ) ) {
			list( $field_id ) = explode( '_', $args['where_opt'] );
		}

		if ( ! isset( $field_id ) ) {
			if ( ! $args['where_opt'] || ! is_numeric( $args['where_opt'] ) ) {
				return $entry_ids;
			}

			$field_id = $args['where_opt'];
		}

		$where_field = FrmField::getOne( $field_id );

		if ( ! $where_field ) {
			return $entry_ids;
		}

		if ( false === $args['display'] ) {
			$args['display'] = self::get_blank_display_object();
		}

		self::prepare_where_args( $args, $where_field, $entry_ids );

		$new_ids = array();
		self::filter_entry_ids( $args, $where_field, $entry_ids, $new_ids );

		unset( $args['temp_where_is'] );

		self::prepare_post_filter( $args, $where_field, $new_ids );

		// Only use entries that are found with all wheres.
		return $args['after_where'] ? array_intersect( $new_ids, $entry_ids ) : $new_ids;
	}

	/**
	 * @param string $where_opt
	 *
	 * @return bool
	 */
	private static function should_get_field_id_from_where_opt_split_val( $where_opt ) {
		if ( self::is_name_subfield_option( $where_opt ) ) {
			return true;
		}

		/**
		 * @since 6.26
		 *
		 * @param bool   $use_where_opt_split_val
		 * @param string $where_opt
		 */
		return apply_filters( 'frm_should_get_field_id_from_where_opt_split_val', false, $where_opt );
	}

	/**
	 * @since 6.8
	 *
	 * @param string $option
	 *
	 * @return bool
	 */
	private static function is_name_subfield_option( $option ) {
		if ( ! $option || is_numeric( $option ) ) {
			return false;
		}

		$split = explode( '_', $option );
		return 2 === count( $split ) && is_numeric( $split[0] ) && in_array( $split[1], array( 'first', 'last' ), true );
	}

	/**
	 * Some code examples in the documentation include examples that access $args['display']->ID without checking if it is an object first.
	 * As there might not always be a display set, the false default value is not safe enough as an attempt to read property "ID" on bool flags a warning.
	 *
	 * @since 5.0.14
	 *
	 * @return stdClass
	 */
	private static function get_blank_display_object() {
		$display     = new stdClass();
		$display->ID = 0;
		return $display;
	}

	/**
	 * Called by the filter_where function
	 *
	 * @param array    $args
	 * @param stdClass $where_field
	 * @param array    $entry_ids
	 *
	 * @return void
	 */
	private static function prepare_where_args( &$args, $where_field, $entry_ids ) {
		self::prepare_where_datetime( $args, $where_field );

		if ( $args['where_is'] === '=' && $args['where_val'] != '' && FrmField::is_field_with_multiple_values( $where_field ) && ( $where_field->type !== 'data' || $where_field->field_options['data_type'] !== 'checkbox' || is_numeric( $args['where_val'] ) ) ) {
			// leave $args['where_is'] the same if this is a data from entries checkbox with a numeric value
			$args['where_is'] = 'LIKE';

			// Set this to flag that the query was changed.
			// This is checked in double_check_entry_id_matches.
			$args['was_where_is_equals'] = true;
		}

		$args['temp_where_is'] = str_replace( array( '!', 'not ' ), '', $args['where_is'] );

		// Get values that aren't blank and then remove them from entry list
		if ( $args['where_val'] == '' && $args['temp_where_is'] === '=' ) {
			$args['temp_where_is'] = '!=';
		}

		if ( self::option_is_like( $args['where_is'] ) ) {
			// Add extra slashes to match values that are escaped in the database
			$args['where_val_esc'] = addslashes( $args['where_val'] );
		} elseif ( ! strpos( $args['where_is'], 'in' ) && ! is_numeric( $args['where_val'] ) ) {
			$args['where_val_esc'] = $args['where_val'];
		}

		$filter_args              = $args;
		$filter_args['entry_ids'] = $entry_ids;
		$args['where_val']        = apply_filters( 'frm_filter_where_val', $args['where_val'], $filter_args );

		self::prepare_dfe_text( $args, $where_field );
	}

	/**
	 * @since 2.3
	 *
	 * @param array  $args
	 * @param object $where_field
	 *
	 * @return void
	 */
	private static function prepare_where_datetime( &$args, $where_field ) {
		$is_datetime = $args['where_val'] === 'NOW' || $where_field->type === 'date' || $where_field->type === 'time';

		if ( ! $is_datetime || empty( $args['where_val'] ) ) {
			return;
		}

		$date_format = $where_field->type === 'time' ? 'H:i' : 'Y-m-d';

		if ( $args['where_val'] === 'NOW' ) {
			$args['where_val'] = self::get_date( $date_format );
		} elseif ( ! self::option_is_like( $args['where_is'] ) ) {
			$date_timestamp = strtotime( $args['where_val'] );

			if ( $date_timestamp ) {
				$args['where_val'] = gmdate( $date_format, strtotime( $args['where_val'] ) );
			}
		}
	}

	/**
	 * @since 2.3
	 *
	 * @param string $where_is
	 *
	 * @return bool
	 */
	private static function option_is_like( $where_is ) {
		return in_array( $where_is, array( 'LIKE', 'not LIKE' ), true );
	}

	/**
	 * Replace a text value where_val with the matching entry IDs for Dynamic Field filters
	 *
	 * @param array  $args
	 * @param object $where_field
	 *
	 * @return void
	 */
	private static function prepare_dfe_text( &$args, $where_field ) {
		if ( $where_field->type !== 'data' ) {
			return;
		}

		// Only proceed if we have a non-category dynamic field with a non-numeric/non-array where_val
		$is_a_string_value = $args['where_val'] && ! is_numeric( $args['where_val'] ) && ! is_array( $args['where_val'] );
		$is_a_post_field   = isset( $where_field->field_options['post_field'] ) && $where_field->field_options['post_field'] === 'post_category';
		$continue          = $is_a_string_value && ! $is_a_post_field;
		$continue          = apply_filters( 'frm_search_for_dynamic_text', $continue, $where_field, $args );

		if ( ! $continue ) {
			return;
		}

		$where_is  = ! empty( $args['was_where_is_equals'] ) ? '' : $args['temp_where_is'];
		$linked_id = FrmProField::get_dynamic_field_entry_id( $where_field->field_options['form_select'], $args['where_val'], $where_is );

		// If text doesn't return any entry IDs, get entry IDs from entry key
		// Note: Keep for reverse compatibility
		if ( ! $linked_id ) {
			$linked_field = FrmField::getOne( $where_field->field_options['form_select'] );

			if ( ! $linked_field ) {
				return;
			}

			$linked_id = FrmDb::get_col(
				'frm_items',
				array(
					'form_id' => $linked_field->form_id,
					'item_key ' . FrmDb::append_where_is( $args['temp_where_is'] ) => $args['where_val'],
				)
			);
		}

		if ( ! $linked_id ) {
			return;
		}

		// Change $args['where_val'] to linked entry IDs
		$args['where_val'] = (array) $linked_id;

		// Don't use old where_val_esc value for filtering
		unset( $args['where_val_esc'] );

		$args['where_val'] = apply_filters( 'frm_filter_dfe_where_val', $args['where_val'], $args );
	}

	/**
	 * @param array  $args
	 * @param object $where_field
	 * @param array  $entry_ids
	 * @param array  $new_ids
	 *
	 * @return void
	 */
	private static function filter_entry_ids( $args, $where_field, $entry_ids, &$new_ids ) {
		$where_statement = array( 'fi.id' => (int) $args['where_opt'] );
		$num_query       = self::maybe_query_as_number( $where_field->type );

		if ( 'name' === $where_field->type ) {
			$field_key = self::get_field_key_for_name_field_query( $num_query, $args );
		} else {
			$field_key = 'meta_value ' . $num_query . FrmDb::append_where_is( $args['temp_where_is'] );

			/**
			 * @since 6.26
			 *
			 * @param string $field_key
			 * @param object $where_field
			 * @param array  $args
			 */
			$field_key = apply_filters( 'frm_field_key_for_field_query', $field_key, $where_field, $args );
		}

		$nested_where = array( $field_key => $args['where_val'] );

		if ( isset( $args['where_val_esc'] ) && $args['where_val_esc'] != $args['where_val'] ) {
			$nested_where['or']               = 1;
			$nested_where[ ' ' . $field_key ] = $args['where_val_esc'];
		}

		$where_statement[] = $nested_where;

		$args['entry_ids'] = $entry_ids;
		$where_statement   = apply_filters( 'frm_where_filter', $where_statement, $args );

		$filter_args = array( 'is_draft' => $args['drafts'] );

		// If the field is from a repeating section (or embedded form?) get the parent ID.
		$filter_args['return_parent_id'] = is_array( $args['form_id'] ) ? ! in_array( $where_field->form_id, $args['form_id'], true ) : $where_field->form_id != $args['form_id'];

		// Add entry IDs to $where_statement.
		if ( $args['use_ids'] ) {
			if ( is_array( $where_statement ) ) {
				if ( $filter_args['return_parent_id'] ) {
					$where_statement['parent_item_id'] = $entry_ids;
				} else {
					$where_statement['item_id'] = $entry_ids;
				}
			} else {
				// If the filter changed the query to a string, allow it.
				$where_statement .= FrmDb::prepend_and_or_where( ' AND ', array( 'item_id' => $entry_ids ) );
			}
		}

		$new_ids = FrmEntryMeta::getEntryIds( $where_statement, '', '', true, $filter_args );

		if ( $args['where_is'] != $args['temp_where_is'] ) {
			$new_ids = array_diff( (array) $entry_ids, $new_ids );
		}

		if ( $new_ids && ! empty( $args['was_where_is_equals'] ) && FrmField::is_field_with_multiple_values( $where_field ) && 'address' !== $where_field->type ) {
			$new_ids = self::double_check_entry_id_matches( $new_ids, $where_statement );
		}
	}

	/**
	 * Do a second pass with the matches to make sure they are actually a match.
	 * This is because the query is modified to use LIKE instead of =.
	 * So some of the results might not really be a match.
	 *
	 * @since 6.20
	 *
	 * @param array<string> $new_ids
	 * @param array         $where_statement
	 *
	 * @return array
	 */
	private static function double_check_entry_id_matches( $new_ids, $where_statement ) {
		if ( empty( $where_statement['fi.id'] ) || empty( $where_statement[0] ) ) {
			return $new_ids;
		}

		$field_id = $where_statement['fi.id'];
		$value    = reset( $where_statement[0] );
		$matches  = array();
		$metas    = FrmDb::get_results(
			'frm_item_metas',
			array(
				'item_id'  => $new_ids,
				'field_id' => $field_id,
			),
			'item_id, meta_value'
		);

		foreach ( $metas as $row ) {
			$entry_id   = $row->item_id;
			$meta_value = $row->meta_value;

			FrmAppHelper::unserialize_or_decode( $meta_value );

			if ( ! is_array( $meta_value ) ) {
				if ( $meta_value === $value ) {
					$matches[] = $entry_id;
				}
				continue;
			}

			if ( is_array( $value ) ) {
				// Handle when value is exploded using frm_filter_where_val.
				// Only return match if all values match the meta values.
				$match = true;

				foreach ( $value as $v ) {
					if ( ! in_array( $v, $meta_value, true ) ) {
						$match = false;
						break;
					}
				}

				if ( $match ) {
					$matches[] = $entry_id;
				}

				continue;
			}

			// For better backward compatibility, match anything with a single match.
			// Even if there are multiple values.
			if ( in_array( $value, $meta_value, true ) ) {
				$matches[] = $entry_id;
			}
		}

		return $matches;
	}

	/**
	 * @since 6.8
	 *
	 * @param string $num_query
	 * @param array  $args
	 *
	 * @return string
	 */
	private static function get_field_key_for_name_field_query( $num_query, $args ) {
		if ( ! self::is_name_subfield_option( $args['where_opt'] ) ) {
			// Filter for the whole name value.
			// This query parses an "unserialized" full name string in-place with SQL.
			return 'TRIM(
				CONCAT(
					SUBSTR(
						REPLACE(
							SUBSTRING_INDEX(
								REPLACE(
									SUBSTRING_INDEX(meta_value' . $num_query . ', \':\', 7 ),
									SUBSTRING_INDEX(meta_value' . $num_query . ', \':\', 5 ),
									""
								),
								":",
								-1
							),
							\'";s\',
							""
						),
						2
					),
					" ",
					SUBSTR(
					REPLACE(
						REPLACE(
							SUBSTRING_INDEX(
								REPLACE(
									SUBSTRING_INDEX(meta_value' . $num_query . ', \':\', 11 ),
									SUBSTRING_INDEX(meta_value' . $num_query . ', \':\', 9 ),
									""
								),
								":",
								-1
							),
							\'";s\',
							""
						),
						\'";}\',
						""
					),
					2
					),
					" ",
					SUBSTR(
						REPLACE(
							REPLACE(
								SUBSTRING_INDEX(
									REPLACE(
										SUBSTRING_INDEX(meta_value' . $num_query . ', \':\', 15 ),
										SUBSTRING_INDEX(meta_value' . $num_query . ', \':\', 13 ),
										""
									),
									":",
									-1
								),
								\'";s\',
								""
							),
							\'";}\',
							""
						),
						2
					)
				)
			) ' . FrmDb::append_where_is( $args['temp_where_is'] );
		}

		$name_subfield = explode( '_', $args['where_opt'] )[1];

		// Filter for a subfield (first or last name).
		return 'TRIM( \'""\' FROM
					CONCAT(
						\'"\',
						SUBSTRING_INDEX(
							SUBSTRING_INDEX(
								REPLACE(
									meta_value' . $num_query . ',
									SUBSTRING_INDEX( meta_value' . $num_query . ', \'"' . $name_subfield . '";s\', 1 ),
									""
								),
								";",
								2
							),
							":",
							-1
						),
						\'"\'
					)
				) ' . FrmDb::append_where_is( $args['temp_where_is'] );
	}

	/**
	 * @since 3.0
	 *
	 * @param string $type
	 *
	 * @return string
	 */
	public static function maybe_query_as_number( $type ) {
		/**
		 * @param string[] $number_fields
		 */
		$number_fields = apply_filters( 'frm_number_fields', array( 'number', 'scale', 'star', 'quiz_score' ) );
		return in_array( $type, $number_fields, true ) ? ' +0 ' : '';
	}

	/**
	 * If there are posts linked to entries for this form.
	 *
	 * @param array  $args
	 * @param object $where_field
	 * @param array  $new_ids
	 *
	 * @return void
	 */
	private static function prepare_post_filter( $args, $where_field, &$new_ids ) {
		if ( empty( $args['form_posts'] ) ) {
			// There are not posts related to this view.
			return;
		}

		if ( ! isset( $where_field->field_options['post_field'] ) || ! in_array( $where_field->field_options['post_field'], array( 'post_category', 'post_custom', 'post_status', 'post_content', 'post_excerpt', 'post_title', 'post_name', 'post_date' ), true ) ) {
			// This is not a post field.
			return;
		}

		$post_ids = array();

		foreach ( $args['form_posts'] as $form_post ) {
			$post_ids[ $form_post->post_id ] = $form_post->id;

			if ( ! in_array( $form_post->id, $new_ids ) ) {
				$new_ids[] = $form_post->id;
			}
		}

		if ( ! $post_ids ) {
			return;
		}

		global $wpdb;
		$filter_args = array();

		if ( $where_field->field_options['post_field'] === 'post_category' ) {
			// Check categories

			$args['temp_where_is'] = FrmDb::append_where_is( str_replace( array( '!', 'not ' ), '', $args['where_is'] ) );

			$t_where = array(
				'or'                                  => 1,
				't.term_id ' . $args['temp_where_is'] => $args['where_val'],
				't.slug ' . $args['temp_where_is']    => $args['where_val'],
				't.name ' . $args['temp_where_is']    => $args['where_val'],
			);
			unset( $args['temp_where_is'] );

			$query   = array( 'tt.taxonomy' => $where_field->field_options['taxonomy'] );
			$query[] = $t_where;

			$add_posts = FrmDb::get_col(
				$wpdb->terms . ' AS t INNER JOIN ' . $wpdb->term_taxonomy . ' AS tt ON tt.term_id = t.term_id INNER JOIN ' . $wpdb->term_relationships . ' AS tr ON tr.term_taxonomy_id = tt.term_taxonomy_id',
				$query,
				'tr.object_id',
				$filter_args
			);
			$add_posts = array_intersect( $add_posts, array_keys( $post_ids ) );

			if ( in_array( $args['where_is'], array( '!=', 'not LIKE' ), true ) ) {
				$remove_posts = $add_posts;
				$add_posts    = false;
			} elseif ( ! $add_posts ) {
				$new_ids = array();
				return;
			}
		} else {
			$query = array();

			if ( $where_field->field_options['post_field'] === 'post_custom' && $where_field->field_options['custom_field'] != '' ) {
				// Check custom fields
				$get_field         = 'post_id';
				$get_table         = $wpdb->postmeta;
				$query['meta_key'] = $where_field->field_options['custom_field'];
				$query_key         = 'meta_value';
			} else {
				// If field is post field
				$get_field = 'ID';
				$get_table = $wpdb->posts;
				$query_key = sanitize_title( $where_field->field_options['post_field'] );
			}

			$query_key          .= self::maybe_query_as_number( $where_field->type );
			$query_key          .= FrmDb::append_where_is( $args['where_is'] );
			$query[ $query_key ] = $args['where_val'];

			$add_posts = FrmDb::get_col( $get_table, $query, $get_field, $filter_args );
			$add_posts = array_intersect( $add_posts, array_keys( $post_ids ) );
		}

		if ( $add_posts ) {
			$new_ids = array();

			foreach ( $add_posts as $add_post ) {
				if ( ! in_array( $post_ids[ $add_post ], $new_ids ) ) {
					$new_ids[] = $post_ids[ $add_post ];
				}
			}
		}

		if ( isset( $remove_posts ) ) {
			if ( ! empty( $remove_posts ) ) {
				foreach ( $remove_posts as $remove_post ) {
					$key = array_search( $post_ids[ $remove_post ], $new_ids );

					if ( $key && $new_ids[ $key ] == $post_ids[ $remove_post ] ) {
						unset( $new_ids[ $key ] );
					}

					unset( $key );
				}
			}
		} elseif ( ! $add_posts ) {
			$new_ids = array();
		}
	}

	/**
	 * Outputs the HTML selected attribute.
	 *
	 * @since 4.07
	 *
	 * @param array|string $selected
	 * @param string $current
	 * @param bool $echo
	 *
	 * @return string
	 */
	private static function strict_selected( $selected, $current, $echo = true ) {
		return selected( in_array( $current, (array) $selected, true ), true, $echo );
	}

	/**
	 * Outputs the HTML selected attribute. For string $selected values, it will select all roles that rank higher as well.
	 *
	 * @since 4.07
	 *
	 * @param array|string $selected
	 * @param string $current
	 * @param bool $echo
	 *
	 * @return string
	 */
	public static function selected( $selected, $current, $echo = true ) {
		if ( is_array( $selected ) ) {
			return self::strict_selected( $selected, $current, $echo );
		}

		$roles        = array( 'administrator', 'editor', 'author', 'contributor', 'subscriber' );
		$required_key = array_search( $selected, $roles, true );
		$key          = array_search( $current, $roles, true );

		if ( $required_key === false || $key === false ) {
			return self::strict_selected( $selected, $current, $echo );
		}

		return selected( $key <= $required_key, true, $echo );
	}

	/**
	 * @param string $path
	 *
	 * @return string mime type
	 */
	public static function get_mime_type( $path ) {
		$filetype = wp_check_filetype( $path );
		return $filetype['type'] ? $filetype['type'] : 'application/octet-stream';
	}

	/**
	 * @since 5.0.13
	 *
	 * @return string The base Google APIS url for the current version of jQuery UI.
	 */
	public static function jquery_ui_base_url() {
		$url = 'http' . ( is_ssl() ? 's' : '' ) . '://ajax.googleapis.com/ajax/libs/jqueryui/' . FrmAppHelper::script_version( 'jquery-ui-core', '1.13.2' );
		return apply_filters( 'frm_jquery_ui_base_url', $url );
	}

	/**
	 * @since 5.0.15
	 *
	 * @param array $values
	 *
	 * @return array<array>
	 */
	public static function pull_ids_and_keys( $values ) {
		$ids  = array();
		$keys = array();

		foreach ( $values as $field_id_or_key ) {
			if ( is_numeric( $field_id_or_key ) ) {
				$ids[] = (int) $field_id_or_key;
			} else {
				$keys[] = $field_id_or_key;
			}
		}

		return array( $ids, $keys );
	}

	/**
	 * Include svg images for Pro.
	 *
	 * @since 5.3
	 */
	public static function include_svg() {
		if ( self::$included_svg ) {
			return;
		}

		readfile( self::plugin_path() . '/images/icons.svg' );
		self::$included_svg = true;
	}

	/**
	 * Get the list of capabilities for pro.
	 *
	 * @since 5.3.1
	 *
	 * @param array<string,string> $permissions
	 *
	 * @return array<string,string>
	 */
	public static function add_pro_capabilities( $permissions ) {
		$permissions['frm_edit_applications']     = __( 'Add/Edit Applications', 'formidable-pro' );
		$permissions['frm_application_dashboard'] = __( 'Access Application Dashboard', 'formidable-pro' );

		return self::set_view_permissions( $permissions );
	}

	/**
	 * Sets Views permissions
	 *
	 * @since 6.5.4
	 *
	 * @param array<string,string> $permissions
	 *
	 * @return array<string,string>
	 */
	private static function set_view_permissions( $permissions ) {
		$views_is_installed = self::views_is_installed();
		$permission_is_set  = isset( $permissions['frm_edit_displays'] );

		if ( $views_is_installed && ! $permission_is_set ) {
			$permissions['frm_edit_displays'] = __( 'Add/Edit Views', 'formidable-pro' );
		} elseif ( ! $views_is_installed && $permission_is_set ) {
			unset( $permissions['frm_edit_displays'] );
		}

		return $permissions;
	}

	/**
	 * Checks if WP Cron is disabled.
	 *
	 * @since 5.4.1
	 *
	 * @return bool
	 */
	public static function is_cron_disabled() {
		return defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON;
	}

	/**
	 * Handles missing mime_content_type function.
	 *
	 * @since 6.4.3
	 *
	 * @param string $file
	 *
	 * @return false|string
	 */
	public static function get_mime_content_type( $file ) {
		if ( function_exists( 'mime_content_type' ) ) {
			return mime_content_type( $file );
		}

		$filetype = wp_check_filetype( $file );
		return $filetype['type'];
	}

	/**
	 * Let WordPress process the uploads
	 *
	 * @codeCoverageIgnore
	 *
	 * @param int $field_id
	 */
	public static function upload_file( $field_id ) {
		_deprecated_function( __FUNCTION__, '2.02', 'FrmProFileField::upload_file' );
		return FrmProFileField::upload_file( $field_id );
	}

	/**
	 * @codeCoverageIgnore
	 *
	 * @param array $uploads
	 */
	public static function upload_dir( $uploads ) {
		_deprecated_function( __FUNCTION__, '2.02', 'FrmProFileField::upload_dir' );
		return FrmProFileField::upload_dir( $uploads );
	}

	public static function get_rand( $length ) {
		$all_g = 'ABCDEFGHIJKLMNOPQRSTWXZ';
		$pass  = '';

		for ( $i = 0; $i < $length; $i++ ) {
			$pass .= $all_g[ random_int( 0, strlen( $all_g ) - 1 ) ];
		}

		return $pass;
	}

	/**
	 * Allow falling back to the legacy chosen.js for autocomplete dropdowns.
	 *
	 * @since 6.8
	 *
	 * @return bool
	 */
	public static function use_chosen_js() {
		/**
		 * To use the legacy chosen.js for autocomplete dropdowns,
		 * use add_filter( 'frm_use_chosen_js', '__return_true' );
		 *
		 * @since 6.8
		 *
		 * @param bool $use_chosen_js False by default.
		 */
		return (bool) apply_filters( 'frm_use_chosen_js', false );
	}

	/**
	 * Check if the jQuery or Flatpickr datepicker is used.
	 *
	 * @since 6.19
	 *
	 * @return bool
	 */
	public static function use_jquery_datepicker() {
		$frmpro_settings = self::get_settings();
		return 'flatpickr' !== $frmpro_settings->datepicker_library;
	}

	/**
	 * Get the server OS
	 *
	 * @since 6.4.2
	 *
	 * @return string
	 */
	public static function get_server_os() {
		if ( is_callable( 'FrmAppHelper::get_server_os' ) ) {
			return FrmAppHelper::get_server_os();
		}
		return function_exists( 'php_uname' ) ? php_uname( 's' ) : '';
	}

	/**
	 * An alternative for basename() function which doesn't work well for non-unicode characters.
	 *
	 * @since 6.10
	 *
	 * @param string $path File path.
	 *
	 * @return string
	 */
	public static function base_name( $path ) {
		$parts = preg_split( '([\\\/])', rtrim( $path, '\\/' ) );
		return end( $parts );
	}

	/**
	 * Shows tooltip icon.
	 *
	 * @since 6.12
	 *
	 * @param string $tooltip_text Tooltip text.
	 * @param array  $atts         Tooltip wrapper HTML attributes.
	 *
	 * @return void
	 */
	public static function tooltip_icon( $tooltip_text, $atts = array() ) {
		FrmAppHelper::tooltip_icon( $tooltip_text, $atts );
	}

	/**
	 * Check if GDPR cookies are disabled.
	 *
	 * @since 6.19
	 *
	 * @return bool
	 */
	public static function no_gdpr_cookies() {
		return is_callable( 'FrmAppHelper::no_gdpr_cookies' ) ? FrmAppHelper::no_gdpr_cookies() : false;
	}
}
