<?php

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

class FrmProEntryMetaHelper {

	/**
	 * @param array    $entries
	 * @param stdClass $field
	 * @param array    $atts
	 *
	 * @return array
	 */
	public static function get_sub_meta_values( $entries, $field, $atts = array() ) {
		$values = array();

		foreach ( $entries as $entry ) {
			$sub_val       = self::get_post_or_meta_value( $entry, $field, $atts );
			$include_blank = ! empty( $atts['include_blank'] );

			if ( $sub_val != '' || $include_blank ) {
				$values[ $entry->id ] = $sub_val;
			}
		}

		return $values;
	}

	/**
	 * @param int|stdClass $entry
	 * @param stdClass    $field
	 * @param array       $atts
	 *
	 * @return mixed
	 */
	public static function get_post_or_meta_value( $entry, $field, $atts = array() ) {
		$defaults = array(
			'links'    => true,
			'show'     => '',
			'truncate' => true,
			'sep'      => ', ',
		);
		$atts     = wp_parse_args( (array) $atts, $defaults );

		FrmEntry::maybe_get_entry( $entry );

		if ( ! $entry || ! $field ) {
			return '';
		}

		if ( ! $entry->post_id ) {
			$value = FrmEntryMeta::get_meta_value( $entry, $field->id );
			self::convert_non_post_taxonomy_ids_to_names( $field, $atts, $value );
			return $value;
		}

		if ( ! isset( $field->field_options['custom_field'] ) ) {
			$field->field_options['custom_field'] = '';
		}

		if ( ! isset( $field->field_options['post_field'] ) ) {
			$field->field_options['post_field'] = '';
		}

		if ( $field->type !== 'tag' && ! $field->field_options['post_field'] ) {
			return FrmEntryMeta::get_meta_value( $entry, $field->id );
		}

		$links = $atts['links'];

		$post_args = array(
			'type'        => $field->type,
			'form_id'     => $field->form_id,
			'field'       => $field,
			'links'       => $links,
			'exclude_cat' => $field->field_options['exclude_cat'] ?? array(),
		);

		foreach ( array( 'show', 'truncate', 'sep' ) as $p ) {
			$post_args[ $p ] = $atts[ $p ];
			unset( $p );
		}

		return self::get_post_value(
			$entry->post_id,
			$field->field_options['post_field'],
			$field->field_options['custom_field'],
			$post_args
		);
	}

	/**
	 * Convert taxonomy IDs to taxonomy names if field is a category field and no post is connected to entry
	 *
	 * @since 2.02.05
	 *
	 * @param object $field
	 * @param array $atts
	 * @param array|string $value
	 */
	private static function convert_non_post_taxonomy_ids_to_names( $field, $atts, &$value ) {
		if ( ! isset( $field->field_options['post_field'] ) || $field->field_options['post_field'] !== 'post_category' || ! $value || ! $atts['truncate'] ) {
			return;
		}

		FrmAppHelper::unserialize_or_decode( $value );

		$new_value = array();

		foreach ( (array) $value as $tax_id ) {
			if ( is_numeric( $tax_id ) ) {
				$new_value[] = FrmProPost::get_taxonomy_term_name_from_id( $tax_id, $field->field_options['taxonomy'] );
			} else {
				$new_value[] = $tax_id;
			}
		}

		$value = $new_value;
	}

	/**
	 * @param int    $post_id
	 * @param string $post_field
	 * @param string $custom_field
	 * @param array  $atts
	 *
	 * @return string
	 */
	public static function get_post_value( $post_id, $post_field, $custom_field, $atts ) {
		if ( ! $post_id ) {
			return '';
		}

		$post = get_post( $post_id );

		if ( ! $post ) {
			return '';
		}

		$defaults = array(
			'sep'      => ', ',
			'truncate' => true,
			'form_id'  => false,
			'field'    => array(),
			'links'    => false,
			'show'     => '',
		);

		$atts  = wp_parse_args( $atts, $defaults );
		$value = '';

		if ( $atts['type'] === 'tag' ) {
			if ( isset( $atts['field']->field_options ) ) {
				$field_options = $atts['field']->field_options;
				FrmAppHelper::unserialize_or_decode( $field_options );
				$tax  = $field_options['taxonomy'] ?? 'frm_tag';
				$tags = get_the_terms( $post_id, $tax );

				if ( $tags ) {
					$names = array();

					foreach ( $tags as $tag ) {
						self::get_term_with_link( $tag, $tax, $names, $atts );
					}

					$value = implode( $atts['sep'], $names );
				}
			}
		} elseif ( $post_field === 'post_custom' ) {
			// Get custom post field value
				$value = self::get_post_meta_value( $post_id, $custom_field );
			} elseif ( $post_field === 'post_category' ) {
			if ( $atts['form_id'] ) {
				$post_type = FrmProFormsHelper::post_type( $atts['form_id'] );
				$taxonomy  = FrmProAppHelper::get_custom_taxonomy( $post_type, $atts['field'] );
			} else {
				$taxonomy = 'category';
			}

			$categories = get_the_terms( $post_id, $taxonomy );
			$names      = array();
			$cat_ids    = array();

			if ( $categories ) {
				foreach ( $categories as $cat ) {
					if ( isset( $atts['exclude_cat'] ) && in_array( $cat->term_id, (array) $atts['exclude_cat'] ) ) {
						continue;
						}

					self::get_term_with_link( $cat, $taxonomy, $names, $atts );

					$cat_ids[] = $cat->term_id;
					}
				}

			if ( $atts['show'] === 'id' ) {
				$value = implode( $atts['sep'], $cat_ids );
			} elseif ( $atts['truncate'] ) {
				$value = implode( $atts['sep'], $names );
			} else {
				$value = $cat_ids;
			}
		} else {
			$post  = (array) $post;
			$value = $post[ $post_field ];
		}

		return $value;
	}

	private static function get_post_meta_value( $post_id, $key ) {
		if ( FrmProPost::is_acf_field( $post_id, $key ) ) {
			return get_field( substr( $key, 1 ), $post_id );
		}
		return get_post_meta( $post_id, $key, true );
	}

	/**
	 * @param WP_Term $tag
	 * @param string  $tax
	 * @param array   $names
	 * @param array   $atts
	 *
	 * @return void
	 */
	private static function get_term_with_link( $tag, $tax, &$names, $atts ) {
		$tag_name = $tag->name;

		if ( $atts['links'] ) {
			$tag_name = '<a href="' . esc_url( get_term_link( $tag, $tax ) ) . '" title="' . esc_attr( sprintf( __( 'View all posts filed under %s', 'formidable-pro' ), $tag_name ) ) . '">' . $tag_name . '</a>';
		}

		$names[] = $tag_name;
	}

	/**
	 * @param array $errors
	 */
	public static function set_post_fields( $field, $value, $errors ) {
		// Save file ids for later use
		if ( 'file' === $field->type ) {
			global $frm_vars;

			if ( ! isset( $frm_vars['media_id'] ) ) {
				$frm_vars['media_id'] = array();
			}

			$frm_vars['media_id'][ $field->id ] = $value;
		}

		if ( ! $value || ! FrmField::is_option_true( $field, 'unique' ) ) {
			return $errors;
		}

		$post_form_action = FrmFormAction::get_action_for_form( $field->form_id, 'wppost', 1 );

		if ( ! $post_form_action ) {
			return $errors;
		}

		// Check if this is a regular post field
		$post_field   = array_search( $field->id, $post_form_action->post_content );
		$custom_field = '';

		if ( ! $post_field ) {
			// Check if this is a custom field
			foreach ( $post_form_action->post_content['post_custom_fields'] as $custom_field ) {
				if ( ! empty( $custom_field['field_id'] ) && ! empty( $custom_field['meta_name'] ) && $field->id == $custom_field['field_id'] ) {
					$post_field   = 'post_custom';
					$custom_field = $custom_field['meta_name'];
				}
			}

			if ( ! $post_field ) {
				return $errors;
			}
		}

		// Check for unique values in post fields
		$entry_id = FrmAppHelper::get_post_param( 'id', false, 'absint' );
		$post_id  = false;

		if ( $entry_id ) {
			$post_id = FrmDb::get_var( 'frm_items', array( 'id' => $entry_id ), 'post_id' );
		}

		if ( self::post_value_exists( $post_field, $value, $post_id, $custom_field ) ) {
			$errors[ 'field' . $field->id ] = FrmFieldsHelper::get_error_msg( $field, 'unique_msg' );
		}

		return $errors;
	}

	/**
	 * @param int|string    $hide_field
	 * @param stdClass|null $selected_field
	 * @param array|string  $observed_field_val
	 * @param stdClass|null $this_field
	 * @param array         $metas
	 *
	 * @return void
	 */
	public static function meta_through_join( $hide_field, $selected_field, $observed_field_val, $this_field, &$metas ) {
		if ( is_array( $observed_field_val ) ) {
			$observed_field_val = array_filter( $observed_field_val );
		}

		if ( ! $observed_field_val || ( ! is_numeric( $observed_field_val ) && ! is_array( $observed_field_val ) ) ) {
			return;
		}

		$observed_info = FrmField::getOne( $hide_field );

		if ( ! $selected_field || ! $observed_info ) {
			return;
		}

		$form_id     = FrmProFieldsHelper::get_parent_form_id( $selected_field );
		$join_fields = FrmField::get_all_types_in_form( $form_id, 'data' );

		if ( ! $join_fields ) {
			return;
		}

		foreach ( $join_fields as $jf ) {
			if ( isset( $jf->field_options['form_select'] ) && isset( $observed_info->field_options['form_select'] ) && $jf->field_options['form_select'] == $observed_info->field_options['form_select'] ) {
				$join_field = $jf->id;
			}
		}

		if ( ! isset( $join_field ) ) {
			return;
		}

		$observed_field_val = array_filter( (array) $observed_field_val );
		$query              = array( 'field_id' => (int) $join_field );
		$sub_query          = array( 'it.meta_value' => $observed_field_val );

		foreach ( $observed_field_val as $obs_val ) {
			$sub_query['or']                 = 1;
			$sub_query['it.meta_value LIKE'] = ':"' . $obs_val . '"';
		}

		$query[] = $sub_query;

		if ( $this_field && ! empty( $this_field->field_options['restrict'] ) ) {
			$query['e.user_id'] = self::get_entry_id_for_dynamic_opts( array( 'field' => $this_field ) );
		}

		// The ids of all the entries that have been selected in the linked form
		$entry_ids = FrmEntryMeta::getEntryIds( $query );

		if ( ! $entry_ids ) {
			return;
		}

		if ( $form_id != $selected_field->form_id ) {
			// This is a child field so we need to get the child entries
			$entry_ids = FrmDb::get_col( 'frm_items', array( 'parent_item_id' => $entry_ids ) );
		}

		if ( $entry_ids ) {
			$metas = FrmEntryMeta::getAll(
				array(
					'item_id'  => $entry_ids,
					'field_id' => $selected_field->id,
				),
				' ORDER BY meta_value'
			);
		}
	}

	/**
	 * @param array $atts
	 */
	private static function get_entry_id_for_dynamic_opts( $atts ) {
		$user_id  = get_current_user_id();
		$entry_id = 0;

		if ( FrmAppHelper::is_admin() ) {
			$entry_id = FrmAppHelper::get_param( 'id', 0, 'get', 'absint' );
		} elseif ( FrmAppHelper::doing_ajax() ) {
			$entry_id = FrmAppHelper::get_param( 'editing_entry', 0, 'get', 'absint' );
		}

		$atts['entry_id'] = $entry_id;
		return self::user_for_dynamic_opts( $user_id, $atts );
	}

	/**
	 * @param int   $user_id
	 * @param array $atts
	 */
	public static function user_for_dynamic_opts( $user_id, $atts ) {
		$entry_user = (array) $user_id;

		if ( $atts['entry_id'] ) {
			$entry_owner = FrmDb::get_var( 'frm_items', array( 'id' => $atts['entry_id'] ), 'user_id' );

			if ( $entry_owner ) {
				$entry_user[] = $entry_owner;
			}
		}

		/**
		 * Set the user id(s) for the limited dynamic field options
		 *
		 * @since 2.2.8
		 *
		 * @return array|int
		 */
		return apply_filters( 'frm_dynamic_field_user', $entry_user, $atts );
	}

	/**
	 * Check in the database if a value exists for a target field.
	 * This is used for unique field validation.
	 *
	 * @param int|object|string $field_id The target field ID being checked for uniqueness.
	 * @param array|string      $value    The value that is being checked for uniqueness.
	 * @param false|int|string  $entry_id Pass this to avoid conflicts with the data for the entry being validated.
	 *
	 * @return array|object|string|null
	 */
	public static function &value_exists( $field_id, $value, $entry_id = false ) {
		global $wpdb;

		if ( is_object( $field_id ) ) {
			$field_id = $field_id->id;
		}

		// Makes sure this works when $value is an array.
		$value = maybe_serialize( $value );

		$query = array(
			'm.meta_value' => $value,
			'm.field_id'   => $field_id,
			'i.is_draft'   => '0',
		);

		if ( $entry_id ) {
			$query['m.item_id !'] = $entry_id;
		}

		$value = FrmDb::get_var(
			$wpdb->prefix . 'frm_item_metas m JOIN ' . $wpdb->prefix . 'frm_items i ON m.item_id = i.id',
			$query,
			'm.id'
		);

		return $value;
	}

	public static function post_value_exists( $post_field, $value, $post_id, $custom_field = '' ) {
		global $wpdb;
		$query = array( 'post_status' => array( 'publish', 'draft', 'pending', 'future' ) );

		if ( $post_field === 'post_custom' ) {
			$table               = $wpdb->postmeta . ' pm LEFT JOIN ' . $wpdb->posts . ' p ON (p.ID=pm.post_id)';
			$db_field            = 'post_id';
			$query['meta_value'] = $value;
			$query['meta_key']   = $custom_field;

			if ( $post_id && is_numeric( $post_id ) ) {
				$query['post_id !'] = $post_id;
			}
		} else {
			$table                = $wpdb->posts;
			$db_field             = 'ID';
			$query[ $post_field ] = $value;

			if ( $post_id && is_numeric( $post_id ) ) {
				$query['ID !'] = $post_id;
			}
		}

		return FrmDb::get_var( $table, $query, $db_field );
	}

	public static function &get_max( $field ) {
		if ( ! is_object( $field ) ) {
			$field = FrmField::getOne( $field );
		}

		if ( ! $field ) {
			$max = '';
			return $max;
		}

		// The most recent entry ID is not necessarily the highest because of draft statuses.
		// So try to get the highest from the most recent few meta values.
		global $wpdb;
		$max_values = FrmDb::get_results(
			$wpdb->prefix . 'frm_item_metas m JOIN ' . $wpdb->prefix . 'frm_items i ON i.id = m.item_id',
			array(
				'm.field_id' => $field->id,
				'i.is_draft' => 0,
			),
			'i.user_id, m.meta_value',
			array(
				'order_by' => 'item_id DESC',
			)
		);

		if ( $max_values ) {
			$max    = self::get_increment_from_value( $max_values[0]->meta_value, $field, $max_values[0]->user_id );
			$length = count( $max_values );

			for ( $i = 1; $i < $length; ++$i ) {
				$max_value = self::get_increment_from_value( $max_values[ $i ]->meta_value, $field, $max_values[ $i ]->user_id );

				if ( $max_value > $max ) {
					$max = $max_value;
				}
			}
		} else {
			$max = '0';
		}

		if ( ! self::field_supports_post_autoid( $field ) ) {
			return $max;
		}

		$post_max = self::get_post_max_value( $field );

		if ( $post_max ) {
			$post_max = self::get_increment_from_value( $post_max, $field );

			if ( (float) $post_max > (float) $max ) {
				$max = $post_max;
			}
		}

		return $max;
	}

	/**
	 * Fields with associated Post actions will have a post_field field option set specifying how the field is mapped to the post data.
	 * [autoid] should work for several keys, as well as for custom post meta.
	 *
	 * @param object $field
	 *
	 * @return bool
	 */
	private static function field_supports_post_autoid( $field ) {
		return isset( $field->field_options['post_field'] ) && in_array( $field->field_options['post_field'], array( 'post_custom', 'post_title', 'post_content', 'post_name', 'post_excerpt' ), true );
	}

	/**
	 * @param object $field
	 *
	 * @return string the most recently submitted meta value to increment from.
	 */
	private static function get_post_max_value( $field ) {
		global $wpdb;
		$post_field = $field->field_options['post_field'];

		if ( 'post_custom' === $post_field ) {
			return FrmDb::get_var( $wpdb->postmeta, array( 'meta_key' => $field->field_options['custom_field'] ), 'meta_value', array( 'order_by' => 'post_ID DESC' ) );
		}

		return FrmDb::get_var(
			$wpdb->posts . ' AS t INNER JOIN ' . $wpdb->prefix . 'frm_items AS i ON i.post_id = t.ID',
			array( 'i.form_id' => $field->form_id ),
			't.' . $post_field,
			array( 'order_by' => 't.ID DESC' )
		);
	}

	/**
	 * If an auto_id field includes a prefix or suffix, strip them from the last value
	 *
	 * @param string|null $max
	 * @param stdClass    $field
	 * @param int         $user_id
	 *
	 * @return string
	 */
	private static function get_increment_from_value( $max, $field, $user_id = 0 ) {
		if ( is_null( $max ) ) {
			return '0';
		}

		$default_value = $field->default_value;

		if ( str_contains( $default_value, '[auto_id' ) ) {
			list( $prefix, $shortcode ) = explode( '[auto_id', $default_value );
			list( $shortcode, $suffix ) = explode( ']', $shortcode, 2 );

			if ( $prefix !== '' ) {
				list ( $max, $prefix ) = self::maybe_remove_date_or_time_from_from_autoid( $max, $prefix, true );
				$prefix                = self::maybe_replace_user_id_shortcode_in_auto_id( $prefix, $user_id );
				FrmProFieldsHelper::replace_non_standard_formidable_shortcodes( array(), $prefix );
			}

			if ( $suffix !== '' ) {
				list ( $max, $suffix ) = self::maybe_remove_date_or_time_from_from_autoid( $max, $suffix, false );
				$suffix                = self::maybe_replace_user_id_shortcode_in_auto_id( $suffix, $user_id );
				FrmProFieldsHelper::replace_non_standard_formidable_shortcodes( array(), $suffix );
			}

			$max = str_replace( $prefix, '', $max );
			$max = str_replace( $suffix, '', $max );
		}

		return filter_var( $max, FILTER_SANITIZE_NUMBER_INT );
	}

	private static function maybe_replace_user_id_shortcode_in_auto_id( $pattern, $user_id ) {
		if ( ! $user_id || ! str_contains( $pattern, '[user_id]' ) ) {
			return $pattern;
		}

		return str_replace( '[user_id]', $user_id, $pattern );
	}

	/**
	 * @since 5.0.06
	 *
	 * @param string $max
	 * @param string $pattern either a prefix or a suffix.
	 * @param bool   $is_prefix
	 *
	 * @return array
	 */
	private static function maybe_remove_date_or_time_from_from_autoid( $max, $pattern, $is_prefix ) {
		list( $max, $pattern ) = self::replace_datetime_from_autoid( 'date', $max, $pattern, $is_prefix );
		list( $max, $pattern ) = self::replace_datetime_from_autoid( 'time', $max, $pattern, $is_prefix );
		return array( $max, $pattern );
	}

	/**
	 * @since 5.0.06
	 *
	 * @param string $shortcode either 'date' or 'time'.
	 * @param string $max
	 * @param string $pattern either a prefix or a suffix.
	 * @param bool   $is_prefix
	 *
	 * @return array
	 */
	private static function replace_datetime_from_autoid( $shortcode, $max, $pattern, $is_prefix ) {
		$check = '[' . $shortcode;
		$start = strpos( $pattern, $check );

		if ( false !== $start ) {
			$start = strpos( $pattern, 'format=', $start );
		}

		if ( false !== $start ) {
			$start        += strlen( 'format=' );
			$end           = strpos( $pattern, ']', $start );
			$format        = substr( $pattern, $start, $end - $start );
			$format        = trim( $format, '"\'' );
			$reverse_regex = ! $is_prefix;
			$regex         = self::build_regex_from_datetime_format( $format, $reverse_regex );

			$max = $reverse_regex ? strrev( preg_replace( $regex, '', strrev( $max ), 1 ) ) : preg_replace( $regex, '', $max, 1 );

			$replace_regex = '/\[' . $shortcode . '\s+format=("|\'){0,1}' . $format . '("|\'){0,1}\]/';
			$pattern       = preg_replace( $replace_regex, '', $pattern, 1 );
		}

		return array( $max, $pattern );
	}

	/**
	 * @since 5.0.06
	 *
	 * @param string $format
	 * @param bool   $reverse
	 *
	 * @return string
	 */
	private static function build_regex_from_datetime_format( $format, $reverse = false ) {
		$regex      = array();
		$characters = str_split( $format );

		foreach ( $characters as $character ) {
			switch ( $character ) {
				case 'Y':
					$regex[] = '\d{4}';
					break;

				case 'm':
				case 'd':
				case 'H':
				case 'h':
				case 'i':
				case 'y':
				case 's':
					$regex[] = '\d{2}';
					break;

				case 'G':
					$regex[] = '\d{1,2}';
					break;

				case 'w':
					$regex[] = '\d{1}';
					break;

				case '_':
				case '-':
				case ':':
					$regex[] = $character;
					break;
			}
		}

		if ( $reverse ) {
			$regex = array_reverse( $regex );
		}

		return '/' . implode( '', $regex ) . '/';
	}
}
