<?php

if ( ! class_exists( 'GFForms' ) ) {
	die();
}

use Gravity_Forms\Gravity_Forms\License;
use Gravity_Forms\Gravity_Forms\Query\Batch_Processing\GF_Entry_Meta_Batch_Processor;
use Gravity_Forms\Gravity_Forms\Query\Batch_Processing\GF_Batch_Operations_Service_Provider;

require_once GF_PLUGIN_DIR_PATH . 'includes/legacy/forms_model_legacy.php';

/**
 * Class GFFormsModel
 *
 * Handles database calls and formatting of stored data regarding forms
 */
class GFFormsModel {

	/**
	 * Stores the values containing and uploaded files for later access
	 *
	 * @since  Unknwon
	 * @access public
	 *
	 * @var array Defaults to an empty array.
	 */
	public static $uploaded_files = array();
	/**
	 * Stores unique form IDs found.
	 *
	 * @since  Unknown
	 * @access public
	 *
	 * @var array Defaults to an empty array.
	 */
	public static $unique_ids = array();

	/**
	 * Stores confirmations found.
	 *
	 * @since  Unknown
	 * @access private
	 *
	 * @var array Defaults to an empty array.
	 */
	private static $_confirmations = array();

	/**
	 * An in-memory cache for the form meta for the current blog.
	 *
	 *  Use "{Blog ID}_{Form ID}" as the key.
	 *
	 * @since   Unknown
	 * @access  private
	 * @example $_current_forms['1_2']
	 *
	 * @var array $_current_forms
	 */
	private static $_current_forms = array();

	/**
	 * An in-memory cache of form properties using "{Blog ID}_{Form ID}" as the key.
	 *
	 * @since 2.7
	 *
	 * @var array $_current_forms_props
	 */
	private static $_current_forms_props = array();

	/**
	 * Handles batch operations for entry meta updates.
	 *
	 * @since 2.5.16
	 *
	 * @var GF_Entry_Meta_Batch_Processor
	 */
	private static $entry_meta_batch_processor;

	/**
	 * The entry data for the current site.
	 *.
	 * @access private
	 *
	 * @var null Defaults to null.
	 */
	private static $_current_lead = null;
	private static $_batch_field_updates = array();
	private static $_batch_field_inserts = array();
	private static $_batch_field_deletes = array();

	/**
	 * Returns the current database version.
	 *
	 * @since 2.2
	 *
	 * @return string
	 */
	public static function get_database_version() {
		static $db_version = array();
		$blog_id = get_current_blog_id();
		if ( empty( $db_version[ $blog_id ] ) ) {
			$db_version[ $blog_id ] = get_option( 'gf_db_version' );
		}

		return $db_version[ $blog_id ];
	}

	/**
	 * Flushes the data stored within GFFormsModel::$_current_forms.
	 *
	 * @since  Unknown
	 * @access public
	 *
	 * @uses GFFormsModel::$_current_forms
	 *
	 * @return void
	 */
	public static function flush_current_forms() {
		self::$_current_forms       = array();
		self::$_current_forms_props = array();
		self::flush_confirmations();
	}

	/**
	 * Flushes the data stored within GFFormsModel::$_current_lead.
	 *
	 * @since  Unknown
	 * @access public
	 *
	 * @uses GFFormsModel::$_current_lead
	 *
	 * @return void
	 */
	public static function flush_current_lead() {
		self::$_current_lead = null;
	}

	/**
	 * Removes the cached properties, meta, and confirmations for a specific form.
	 *
	 * @since 2.6
	 *
	 * @var string $key The cache key.
	 *
	 * @return void
	 */
	public static function flush_current_form( $key ) {
		unset( self::$_current_forms[ $key ], self::$_current_forms_props[ $key ], self::$_confirmations[ $key ] );
	}

	/**
	 * Flushes the data stored within GFFormsModel::$_confirmations
	 *
	 * @since  Unknown
	 * @access public
	 *
	 * @uses GFFormsModel::$_confirmations
	 *
	 * @return void
	 */
	public static function flush_confirmations() {
		self::$_confirmations = array();
	}

	/**
	 * Gets the form table name, including the site's database prefix.
	 *
	 * @since  Unknown
	 * @access public
	 * @global $wpdb
	 *
	 * @return string The form table name.
	 */
	public static function get_form_table_name() {
		global $wpdb;

		if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
			return $wpdb->prefix . 'rg_form';
		}

		return $wpdb->prefix . 'gf_form';
	}

	/**
	 * Gets the form meta table, including the site's database prefix.
	 *
	 * @since  Unknown
	 * @access public
	 * @global $wpdb
	 *
	 * @return string The form meta table.
	 */
	public static function get_meta_table_name() {
		global $wpdb;

		if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
			return $wpdb->prefix . 'rg_form_meta';
		}

		return $wpdb->prefix . 'gf_form_meta';
	}

	/**
	 * Gets the form view table name, including the site's database prefix.
	 *
	 * @since  Unknown
	 * @access public
	 * @global $wpdb
	 *
	 * @return string The form view table name.
	 */
	public static function get_form_view_table_name() {
		global $wpdb;

		if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
			return $wpdb->prefix . 'rg_form_view';
		}

		return $wpdb->prefix . 'gf_form_view';
	}

	/**
	 * Gets the form revisions table name, including the site's database prefix.
	 *
	 * @since  2.4-dev
	 *
	 * @return string The form revisions table name.
	 */
	public static function get_form_revisions_table_name() {
		global $wpdb;

		return $wpdb->prefix . 'gf_form_revisions';
	}

	/**
	 * Gets the lead (entries) table name, including the site's database prefix.
	 *
	 * @since  Unknown
	 * @access public
	 * @global $wpdb
	 *
	 * @return string The lead (entry) table name.
	 */
	public static function get_lead_table_name() {
		return GF_Forms_Model_Legacy::get_lead_table_name();
	}

	/**
	 * Gets the lead (entry) meta table name, including the site's database prefix.
	 *
	 * @since  Unknown
	 * @access public
	 * @global $wpdb
	 *
	 * @return string The lead (entry) meta table name.
	 */
	public static function get_lead_meta_table_name() {
		return GF_Forms_Model_Legacy::get_lead_meta_table_name();
	}

	/**
	 * Gets the lead (entry) notes table name, including the site's database prefix.
	 *
	 * @since  Unknown
	 * @access public
	 * @global $wpdb
	 *
	 * @return string The lead (entry) notes table name.
	 */
	public static function get_lead_notes_table_name() {
		return GF_Forms_Model_Legacy::get_lead_notes_table_name();
	}

	/**
	 * Gets the lead (entry) details table name, including the site's database prefix.
	 *
	 * @since  Unknown
	 * @access public
	 * @global $wpdb
	 *
	 * @return string The lead (entry) details table name.
	 */
	public static function get_lead_details_table_name() {
		return GF_Forms_Model_Legacy::get_lead_details_table_name();
	}

	/**
	 * Gets the lead (entry) details long table name, including the site's database prefix.
	 *
	 * @since  Unknown
	 * @access public
	 * @global $wpdb
	 *
	 * @return string The lead (entry) details long table name.
	 */
	public static function get_lead_details_long_table_name() {
		return GF_Forms_Model_Legacy::get_lead_details_long_table_name();
	}

	/**
	 * Gets the lead (entry) view table name, including the site's database prefix.
	 *
	 * @since  Unknown
	 * @access public
	 * @global $wpdb
	 *
	 * @return string The lead (entry) view table name.
	 */
	public static function get_lead_view_name() {
		return GF_Forms_Model_Legacy::get_lead_view_name();
	}

	/**
	 * Gets the incomplete submissions table name, including the site's database prefix.
	 *
	 * @since  Unknown
	 * @access public
	 * @global $wpdb
	 *
	 * @return string he incomplete submissions table name.
	 */
	public static function get_incomplete_submissions_table_name() {
		return GF_Forms_Model_Legacy::get_incomplete_submissions_table_name();
	}

	/**
	 * Gets the entry table name, including the site's database prefix
	 *
	 * @access public
	 * @static
	 * @global $wpdb
	 *
	 * @return string The entry table name
	 */
	public static function get_entry_table_name() {
		global $wpdb;

		return $wpdb->prefix . 'gf_entry';
	}

	/**
	 * Gets the entry meta table name, including the site's database prefix
	 *
	 * @access public
	 * @static
	 * @global $wpdb
	 *
	 * @return string The entry meta table name
	 */
	public static function get_entry_meta_table_name() {
		global $wpdb;

		return $wpdb->prefix . 'gf_entry_meta';
	}

	/**
	 * Gets the lead (entry) notes table name, including the site's database prefix
	 *
	 * @access public
	 * @static
	 * @global $wpdb
	 *
	 * @return string The lead (entry) notes table name
	 */
	public static function get_entry_notes_table_name() {
		global $wpdb;

		return $wpdb->prefix . 'gf_entry_notes';
	}


	/**
	 * Gets the draft submissions table name, including the site's database prefix
	 *
	 * @access public
	 * @static
	 * @global $wpdb
	 *
	 * @return string The draft submissions table name
	 */
	public static function get_draft_submissions_table_name() {
		global $wpdb;

		return $wpdb->prefix . 'gf_draft_submissions';
	}

	/**
	 * Gets the REST API Key table name, including the site's database prefix
	 *
	 * @access public
	 * @static
	 * @global $wpdb
	 *
	 * @return string The REST API Keys submissions table name
	 */
	public static function get_rest_api_keys_table_name() {
		global $wpdb;

		return $wpdb->prefix . 'gf_rest_api_keys';
	}

	/**
	 * Returns the name of the table where add-on feeds are stored, including the site's database prefix.
	 *
	 * @since 2.4.24
	 *
	 * @return string
	 */
	public static function get_addon_feed_table_name() {
		global $wpdb;

		return $wpdb->prefix . 'gf_addon_feed';
	}


	/**
	 * Gets all forms.
	 *
	 * @since  Unknown
	 * @access public
	 * @global $wpdb
	 *
	 * @uses GFFormsModel::get_form_table_name()
	 * @uses GFFormsModel::get_form_db_columns()
	 * @uses GFFormsModel::get_entry_count_per_form()
	 * @uses GFFormsModel::get_view_count_per_form()
	 *
	 * @param bool   $is_active   Optional. Defines if inactive forms should be displayed. Defaults to null.
	 * @param string $sort_column Optional. The column to be used for sorting the forms. Defaults to 'title'.
	 * @param string $sort_dir    Optional. Defines the direction that sorting should occur. Defaults to 'ASC' (ascending). Use 'DESC' for descending.
	 * @param bool   $is_trash    Optional. Defines if forms within the trash should be displayed. Defaults to false.
	 *
	 * @return array $forms All forms found.
	 */
	public static function get_forms( $is_active = null, $sort_column = 'title', $sort_dir = 'ASC', $is_trash = false ) {
		global $wpdb;
		$form_table_name = esc_sql( self::get_form_table_name() );

		$where_arr   = array();
		$where_arr[] = $wpdb->prepare( 'is_trash=%d', $is_trash );
		if ( $is_active !== null ) {
			$where_arr[] = $wpdb->prepare( 'is_active=%d', $is_active );
		}

		$where_clause = 'WHERE ' . join( ' AND ', $where_arr );
		$sort_keyword = $sort_dir == 'ASC' ? 'ASC' : 'DESC';

		$db_columns = self::get_form_db_columns();

		if ( ! in_array( strtolower( $sort_column ), $db_columns ) ) {
			$sort_column = 'title';
		}

		$sort_column = sanitize_sql_orderby( $sort_column );
		$order_by    = ! empty( $sort_column ) ? "ORDER BY $sort_column $sort_keyword" : '';

		$sql = "SELECT f.id, f.title, f.date_created, f.is_active, 0 as entry_count, 0 view_count
                FROM $form_table_name f
                $where_clause
                $order_by";

		//Getting all forms
		$forms = $wpdb->get_results( $sql );

		//Getting entry count per form
		$entry_count = self::get_entry_count_per_form();

		//Getting view count per form
		$view_count = self::get_view_count_per_form();

		//Adding entry counts and to form array
		foreach ( $forms as &$form ) {
			foreach ( $view_count as $count ) {
				if ( $count->form_id == $form->id ) {
					$form->view_count = $count->view_count;
					break;
				}
			}

			foreach ( $entry_count as $count ) {
				if ( $count->form_id == $form->id ) {
					$form->entry_count = $count->entry_count;
					break;
				}
			}
		}

		return $forms;
	}

	/**
	 * Searches form titles based on query.
	 *
	 * @access public
	 * @static
	 * @global $wpdb
	 * @see GFFormsModel::get_form_table_name
	 * @see GFFormsModel::get_form_db_columns
	 * @see GFFormsModel::get_entry_count_per_form
	 * @see GFFormsModel::get_view_count_per_form
	 *
	 * @param string $query       Optional. The query to search.
	 * @param bool   $is_active   Optional. Defines if inactive forms should be displayed. Defaults to null.
	 * @param string $sort_column Optional. The column to be used for sorting the forms. Defaults to 'title'.
	 * @param string $sort_dir    Optional. Defines the direction that sorting should occur. Defaults to 'ASC' (ascending). Use 'DESC' for descending.
	 * @param bool   $is_trash    Optional. Defines if forms within the trash should be displayed. Defaults to false.
	 *
	 * @return array $forms All forms found.
	 */
	public static function search_forms( $query = '', $is_active = null, $sort_column = 'title', $sort_dir = 'ASC', $is_trash = false ) {
		global $wpdb;
		$form_table_name = esc_sql( self::get_form_table_name() );

		$where_arr   = array();
		$where_arr[] = $wpdb->prepare( 'is_trash=%d', $is_trash );
		if ( $is_active !== null ) {
			$where_arr[] = $wpdb->prepare( 'is_active=%d', $is_active );
		}

		if ( ! rgblank( $query ) ) {
			$where_arr[] = $wpdb->prepare( 'title LIKE %s', '%' . $query . '%' );
		}

		$sort_keyword = $sort_dir == 'ASC' ? 'ASC' : 'DESC';

		$db_columns = self::get_form_db_columns();

		if ( ! in_array( strtolower( $sort_column ), $db_columns ) ) {
			$sort_column = 'title';
		}
		$sort_column = sanitize_sql_orderby( $sort_column );

		$where_clause = 'WHERE ' . join( ' AND ', $where_arr );
		$order_by     = ! empty( $sort_column ) ? "ORDER BY $sort_column $sort_keyword" : '';

		// All of the pieces of this SQL statement have already gone through $wpdb->prepare, so we don't prepare it again here.
		$sql = "SELECT f.id, f.title, f.date_created, f.is_active, 0 as entry_count, 0 view_count
                FROM $form_table_name f
                $where_clause
                $order_by";

		//Getting all forms
		$forms = $wpdb->get_results( $sql );

		//Getting entry count per form
		$entry_count = self::get_entry_count_per_form();

		//Getting view count per form
		$view_count = self::get_view_count_per_form();

		//Adding entry counts and to form array
		foreach ( $forms as &$form ) {
			foreach ( $view_count as $count ) {
				if ( $count->form_id == $form->id ) {
					$form->view_count = $count->view_count;
					break;
				}
			}

			foreach ( $entry_count as $count ) {
				if ( $count->form_id == $form->id ) {
					$form->entry_count = $count->entry_count;
					break;
				}
			}
		}

		return $forms;
	}

	/**
	 * Gets the number of entries per form.
	 *
	 * First attempts to read from cache. If unavailable, gets the entry count, caches it, and returns it.
	 *
	 * @since  Unknown
	 * @access public
	 * @global $wpdb
	 *
	 * @uses GFFormsModel::get_lead_table_name()
	 * @uses GFCache::get()
	 * @uses GFCache::set()
	 *
	 * @return array $entry_count Array of forms, containing the form ID and the entry count
	 */
	public static function get_entry_count_per_form() {

		if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
			return GF_Forms_Model_Legacy::get_entry_count_per_form();
		}

		global $wpdb;
		$entry_table_name = esc_sql( self::get_entry_table_name() );

		$entry_count = GFCache::get( 'get_entry_count_per_form' );
		if ( empty( $entry_count ) ) {
			//Getting entry count per form
			$sql         = $wpdb->prepare( "SELECT form_id, count(id) as entry_count FROM $entry_table_name l WHERE status=%s GROUP BY form_id", 'active' );
			$entry_count = $wpdb->get_results( $sql );

			GFCache::set( 'get_entry_count_per_form', $entry_count, true, 30 );
		}

		return $entry_count;
	}

	/**
	 * Gets the number of views per form
	 *
	 * Checks the cache first.  If not there, gets the count from the database, stores it in the cache, and returns it.
	 *
	 * @since  Unknown
	 * @access public
	 * @global $wpdb
	 *
	 * @uses GFFormsModel::get_form_view_table_name()
	 * @uses GFCache::get()
	 * @uses GFCache::set()
	 *
	 * @return array $view_count Array of forms, containing the form ID and the view count
	 */
	public static function get_view_count_per_form() {
		global $wpdb;
		$view_table_name = esc_sql( self::get_form_view_table_name() );

		$view_count = GFCache::get( 'get_view_count_per_form' );
		if ( empty( $view_count ) ){
			$sql        = "SELECT form_id, sum(count) as view_count FROM $view_table_name GROUP BY form_id";
			$view_count = $wpdb->get_results( $sql );

			GFCache::set( 'get_view_count_per_form', $view_count, true, 30 );
		}

		return $view_count;
	}

	/**
	 * Returns the form database columns.
	 *
	 * @since  Unknown
	 * @access public
	 *
	 * @return array The column IDs
	 */
	public static function get_form_db_columns() {
		return array( 'id', 'title', 'date_created', 'date_updated', 'is_active', 'is_trash' );
	}

	/**
	 * Gets the payment totals for a particular form ID.
	 *
	 * @since  Unknown
	 * @access public
	 * @global $wpdb
	 *
	 * @uses GFFormsModel::get_lead_table_name()
	 *
	 * @param int $form_id The form ID to get payment totals for.
	 *
	 * @return array $totals The payment totals found.
	 */
	public static function get_form_payment_totals( $form_id ) {
		global $wpdb;
		$entry_table_name = version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ? self::get_lead_table_name() : self::get_entry_table_name();

		$sql = $wpdb->prepare(
			" SELECT sum(payment_amount) revenue, count(l.id) orders
             FROM $entry_table_name l
             WHERE form_id=%d AND payment_amount IS NOT null", $form_id
		);

		$totals = $wpdb->get_row( $sql, ARRAY_A );

		$active = $wpdb->get_var(
			$wpdb->prepare(
				" SELECT count(id) as active
                 FROM $entry_table_name
                 WHERE form_id=%d AND payment_status='Active'", $form_id
			)
		);

		if ( empty( $active ) ) {
			$active = 0;
		}

		$totals['active'] = $active;

		return $totals;
	}

	/**
	 * Gets the total, unread, starred, spam, and trashed entry counts.
	 *
	 * @since  Unknown
	 * @access public
	 * @global $wpdb
	 *
	 * @uses GFFormsModel::get_lead_table_name()
	 * @uses GFFormsModel::get_lead_details_table_name()
	 *
	 * @param int $form_id The ID of the form to check.
	 *
	 * @return array $results[0] The form counts.
	 */
	public static function get_form_counts( $form_id ) {

		if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
			return GF_Forms_Model_Legacy::get_form_counts( $form_id );
		}

		global $wpdb;

		$cache_key = 'form_counts_' . $form_id;

		$results = GFCache::get( $cache_key );

		if ( ! empty( $results ) ) {
			return $results;
		}

		$entry_table_name = self::get_entry_table_name();
		$entry_detail_table_name = self::get_entry_meta_table_name();

		$sql             = $wpdb->prepare(
			"SELECT
                    (SELECT count(DISTINCT(l.id)) FROM $entry_table_name l WHERE l.form_id=%d AND l.status='active') as total,
                    (SELECT count(DISTINCT(l.id)) FROM $entry_table_name l WHERE l.is_read=0 AND l.status='active' AND l.form_id=%d) as unread,
                    (SELECT count(DISTINCT(l.id)) FROM $entry_table_name l WHERE l.is_starred=1 AND l.status='active' AND l.form_id=%d) as starred,
                    (SELECT count(DISTINCT(l.id)) FROM $entry_table_name l WHERE l.status='spam' AND l.form_id=%d) as spam,
                    (SELECT count(DISTINCT(l.id)) FROM $entry_table_name l WHERE l.status='trash' AND l.form_id=%d) as trash",
			$form_id, $form_id, $form_id, $form_id, $form_id
		);

		$wpdb->timer_start();
		$results = $wpdb->get_results( $sql, ARRAY_A );
		$time_total = $wpdb->timer_stop();
		if ( $time_total > 1 ) {
			GFCache::set( $cache_key, $results[0], true, 10 * MINUTE_IN_SECONDS );
		}

		return $results[0];

	}

	/**
	 * Gets the form summary for all forms.
	 *
	 * @since  Unknown
	 * @access public
	 * @global $wpdb
	 *
	 * @uses GFFormsModel::get_form_table_name()
	 * @uses GFFormsModel::get_lead_table_name()
	 *
	 * @return array $forms Contains the form summary for all forms.
	 */
	public static function get_form_summary() {
		global $wpdb;
		$form_table_name = esc_sql( self::get_form_table_name() );
		$entry_table_name = version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ? esc_sql( self::get_lead_table_name() ) : esc_sql( self::get_entry_table_name() );

		$sql = $wpdb->prepare( "SELECT l.form_id, count(l.id) as unread_count
            FROM $entry_table_name l
            WHERE is_read=%d AND status=%s
            GROUP BY form_id",
			0,
			'active'
		);

		// Getting number of unread and total leads for all forms
		$unread_results = $wpdb->get_results( $sql, ARRAY_A );

		$sql = $wpdb->prepare( "SELECT l.form_id, max(l.date_created) as last_entry_date, count(l.id) as total_entries
            FROM $entry_table_name l
            WHERE status=%s
            GROUP BY form_id",
			'active'
		);

		$lead_date_results = $wpdb->get_results( $sql, ARRAY_A );

		$sql = $wpdb->prepare( "SELECT id, title, is_trash, '' as last_entry_date, 0 as unread_count
            FROM $form_table_name
            WHERE is_active=%d
            ORDER BY title",
			1
		);

		$forms = $wpdb->get_results( $sql, ARRAY_A );

		for ( $i = 0; $count = sizeof( $forms ), $i < $count; $i ++ ) {
			if ( is_array( $unread_results ) ) {
				foreach ( $unread_results as $unread_result ) {
					if ( $unread_result['form_id'] == $forms[ $i ]['id'] ) {
						$forms[ $i ]['unread_count'] = $unread_result['unread_count'];
						break;
					}
				}
			}

			if ( is_array( $lead_date_results ) ) {
				foreach ( $lead_date_results as $entry_date_result ) {
					if ( $entry_date_result['form_id'] == $forms[ $i ]['id'] ) {
						$forms[ $i ]['last_entry_date'] = $entry_date_result['last_entry_date'];
						$forms[ $i ]['total_entries']    = $entry_date_result['total_entries'];
						break;
					}
				}
			}
		}

		/**
		 * Modifies the summary of all forms, includes unread and total entry counts.
		 *
		 * @since 2.4.16
		 *
		 * @param array $forms Form summary.
		 */
		$forms = apply_filters( 'gform_form_summary', $forms );

		return $forms;
	}

	/**
	 * Gets the total, active, inactive, and trashed form counts.
	 *
	 * @since  Unknown
	 * @access public
	 * @global $wpdb
	 *
	 * @uses GFFormsModel::get_form_table_name()
	 *
	 * @return array The form counts.
	 */
	public static function get_form_count() {
		global $wpdb;
		$form_table_name = self::get_form_table_name();

		if ( ! GFCommon::table_exists( $form_table_name ) ) {
			return array(
				'total'    => 0,
				'active'   => 0,
				'inactive' => 0,
				'trash'    => 0,
			);
		}

		$results = $wpdb->get_results(
			"
            SELECT
            (SELECT count(0) FROM $form_table_name WHERE is_trash = 0) as total,
            (SELECT count(0) FROM $form_table_name WHERE is_active=1 AND is_trash = 0 ) as active,
            (SELECT count(0) FROM $form_table_name WHERE is_active=0 AND is_trash = 0 ) as inactive,
            (SELECT count(0) FROM $form_table_name WHERE is_trash=1) as trash
            "
		);

		return array(
			'total'    => intval( $results[0]->total ),
			'active'   => intval( $results[0]->active ),
			'inactive' => intval( $results[0]->inactive ),
			'trash'    => intval( $results[0]->trash ),
		);
	}

	/**
	 * Gets the form ID based on the form title.
	 *
	 * @since  Unknown
	 * @access public
	 *
	 * @uses GFFormsModel::get_forms()
	 *
	 * @param string $form_title The form title to search for.
	 *
	 * @return int The form ID. Returns 0 if not found.
	 */
	public static function get_form_id( $form_title ) {
		$forms = self::get_forms();
		foreach ( $forms as $form ) {
			$sanitized_name = str_replace( '[', '', str_replace( ']', '', $form->title ) );
			if ( $form->title == $form_title || $sanitized_name == $form_title ) {
				return absint( $form->id );
			}
		}

		return 0;
	}

	/**
	 * Returns the cache key for the specified form.
	 *
	 * @since 2.7
	 *
	 * @param int $form_id The form ID.
	 *
	 * @return string
	 */
	public static function get_form_cache_key( $form_id ) {
		return get_current_blog_id() . '_' . $form_id;
	}

	/**
	 * Returns an object containing the properties of the specified form or false if the form doesn't exist.
	 *
	 * @since Unknown
	 * @since 2.7 Updated to cache the result.
	 *
	 * @param int  $form_id     The ID of the form to get.
	 * @param bool $allow_trash Optional. Set to true to allow trashed results. Defaults to false.
	 *
	 * @return bool|object
	 */
	public static function get_form( $form_id, $allow_trash = false ) {
		$form_id = absint( $form_id );
		if ( empty( $form_id ) ) {
			return false;
		}

		$key        = self::get_form_cache_key( $form_id );
		$form_props = rgar( self::$_current_forms_props, $key );

		if ( is_object( $form_props ) ) {
			if ( ! $allow_trash && $form_props->is_trash ) {
				return false;
			}

			return $form_props;
		}

		global $wpdb;
		$table_name   = self::get_form_table_name();
		$trash_clause = $allow_trash ? '' : 'AND is_trash = 0';
		$result       = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_name WHERE id=%d {$trash_clause}", $form_id ) );

		if ( empty( $result ) ) {
			return false;
		}

		self::$_current_forms_props[ $key ] = $result;

		return $result;
	}

	/**
	 * Converts a serialized string or JSON for access in PHP.
	 *
	 * @since  Unknown
	 * @access public
	 *
	 * @param string $string The string to convert.
	 *
	 * @return mixed
	 */
	public static function unserialize( $string ) {
		if ( empty( $string ) ) {
			return null;
		} elseif ( is_serialized( $string ) ) {
			$obj = @unserialize( $string );
		} else {
			$obj = json_decode( $string, true );
		}

		return $obj;
	}

	/**
	 * Gets the form meta based on the form ID.
	 *
	 * @since  Unknown
	 * @access public
	 * @global $wpdb
	 *
	 * @uses GFFormsModel::get_meta_table_name()
	 * @uses GFFormsModel::unserialize()
	 * @uses GFFormsModel::convert_field_objects()
	 * @uses GFFormsModel::load_notifications_from_legacy()
	 * @uses GFFormsModel::$_current_forms
	 *
	 * @param int $form_id The form ID.
	 *
	 * @return array|null $form Form object if found. Null if not found.
	 */
	public static function get_form_meta( $form_id ) {
		global $wpdb;

		$form_id = absint( $form_id );
		if ( empty( $form_id ) ) {
			return null;
		}

		$key = self::get_form_cache_key( $form_id );
		// Return cached version if form meta has been previously retrieved for this form
		if ( isset( self::$_current_forms[ $key ] ) ) {
			return self::$_current_forms[ $key ];
		}

		$table_name = self::get_meta_table_name();
		$form_row   = $wpdb->get_row( $wpdb->prepare( "SELECT display_meta, notifications FROM {$table_name} WHERE form_id=%d", $form_id ), ARRAY_A );


		// Loading main form object (supports serialized strings as well as JSON strings)
		$form = self::unserialize( rgar( $form_row, 'display_meta' ) );

		if ( ! $form ) {
			return null;
		}

		// Ensure the fields property is in the correct format, an associative array will cause warnings and js errors in the form editor.
		$form['fields'] = is_array( rgar( $form, 'fields' ) ) ? array_values( $form['fields'] ) : array();

		// Loading notifications
		$form['notifications'] = self::unserialize( $form_row['notifications'] );

		// Creating field objects and copying some form variables down to fields for easier access
		$form = self::convert_field_objects( $form );

		// Loading confirmations from legacy structure into new structure
		$form = self::load_confirmations( $form );

		//only migrate legacy notification if there isn't any notification configured in new structure
		if ( ! isset( $form['notifications'] ) ) {
			$form = self::load_notifications_from_legacy( $form ); // Moving notification data from legacy structure into new 'notifications' array
		}

		// Load notifications to legacy structure to maintain backward compatibility with legacy hooks and functions
		$form = self::load_notifications_to_legacy( $form );

		// Ensure the next field ID is set correctly.
		$form['nextFieldId'] = self::get_next_field_id( $form['fields'] );

		/**
		 * Filters the Form object after the form meta is obtained
		 *
		 * @param array $form The Form object
		 */
		$form = gf_apply_filters( array( 'gform_form_post_get_meta', $form_id ), $form );

		// Cached form meta for cheaper retrieval on subsequent requests
		self::$_current_forms[ $key ] = $form;

		return $form;
	}

	/**
	 * Recursively checks the highest ID for all the fields in the form and then returns the highest ID + 1.
	 *
	 * @since 2.4.6.12
	 *
	 * @param GF_Field[] $fields
	 * @param int        $next_field_id
	 *
	 * @return int
	 */
	public static function get_next_field_id( $fields, $next_field_id = 1 ) {

		foreach ( $fields as $field ) {

			if ( is_array( $field->fields ) ) {
				$next_field_id = self::get_next_field_id( $field->fields, $next_field_id );
			}

			if ( $field->id >= $next_field_id ) {
				$next_field_id = $field->id + 1;
			}

		}

		return (int) $next_field_id;
	}

	/**
	 * Converts all field objects in a form, based on field type.
	 *
	 * @since  Unknown
	 * @access public
	 *
	 * @uses GF_Field_CreditCard::maybe_upgrade_inputs()
	 *
	 * @param array $form The Form object.
	 *
	 * @return array $form The Form object after the field objects are converted.
	 */
	public static function convert_field_objects( $form ) {
		$page_number    = 1;
		$form['fields'] = is_array( rgar( $form, 'fields' ) ) ? array_values( $form['fields'] ) : array();

		foreach ( $form['fields'] as &$field ) {
			// convert adminOnly property to visibility
			if ( ! isset( $field['visibility'] ) ) {
				$field['visibility'] = isset( $field['adminOnly'] ) && $field['adminOnly'] ? 'administrative' : 'visible';
				unset( $field['adminOnly'] );
			}

			$field = GF_Fields::create( $field );
			if ( isset( $form['id'] ) ) {
				$field->formId = $form['id'];
			}

			$field->pageNumber = $page_number;

			if ( is_array( $field->fields ) ) {
				self::convert_sub_field_objects( $field, $form['id'], $page_number );
			}

			if ( $field->type == 'page' ) {
				$page_number ++;
				$field->pageNumber = $page_number;
			}

			// Populate required cssClass property with empty string if not set to avoid JS errors when rendering.
			if ( ! isset( $field->cssClass ) ) {
				$field->cssClass = '';
			}

			$field->post_convert_field();
		}

		return $form;
	}

	/**
	 * Converts the sub fields to field objects.
	 *
	 * @since 2.4
	 *
	 * @param GF_Field $field         The repeater field to be converted to objects.
	 * @param int      $form_id       The current form ID.
	 * @param int      $page_number   The page number the parent field is located on.
	 * @param int      $nesting_level The level at which a repeater field is nested.
	 */
	private static function convert_sub_field_objects( &$field, $form_id, $page_number, $nesting_level = 0 ) {
		$field->nestingLevel = $nesting_level;
		foreach ( $field->fields as &$field ) {
			$field             = GF_Fields::create( $field );
			$field->formId     = $form_id;
			$field->pageNumber = $page_number;
			$field->post_convert_field();
			if ( is_array( $field['fields'] ) ) {
				$new_nesting_level = $nesting_level + 1;
				self::convert_sub_field_objects( $field, $form_id, $page_number, $new_nesting_level );
			}
		}
	}

	/**
	 * Gets the form meta for multiple forms based on an array for form IDs.
	 *
	 * @since  Unknown
	 * @access public
	 * @global $wpdb
	 *
	 * @uses GFFormsModel::get_form_table_name()
	 * @uses GFFormsModel::get_meta_table_name()
	 * @uses GFFormsModel::unserialize()
	 * @uses GFFormsModel::convert_field_objects()
	 *
	 * @param array $ids Array of form IDs.
	 *
	 * @return array $results
	 */
	public static function get_form_meta_by_id( $ids ) {
		global $wpdb;
		$form_table_name = self::get_form_table_name();
		$meta_table_name = self::get_meta_table_name();

		if ( is_array( $ids ) ) {
			$ids = implode( ',', array_map( 'intval', $ids ) );
		} else {
			$ids = intval( $ids );
		}

		$results = $wpdb->get_results(
			" SELECT display_meta, confirmations, notifications FROM {$form_table_name} f
                                        INNER JOIN {$meta_table_name} m ON f.id = m.form_id
                                        WHERE id in({$ids})", ARRAY_A
		);

		foreach ( $results as &$result ) {
			$form                  = self::unserialize( $result['display_meta'] );
			$form['confirmations'] = self::unserialize( $result['confirmations'] );
			$form['notifications'] = self::unserialize( $result['notifications'] );
			// Creating field objects and copying some form variables down to fields for easier access
			$form   = self::convert_field_objects( $form );
			$result = $form;
		}

		return $results;

	}

	/**
	 * Converts current notification structure to legacy.
	 *
	 * @since  Unknown
	 * @access private
	 *
	 * @param array $form The Form object.
	 *
	 * @return array $form The Form object.
	 */
	private static function load_notifications_to_legacy( $form ) {
		if ( ! is_array( rgar( $form, 'notifications' ) ) ) {
			return $form;
		}

		foreach ( $form['notifications'] as $notification ) {
			if ( ! in_array( rgar( $notification, 'type' ), array( 'user', 'admin' ) ) ) {
				continue;
			}

			$legacy_notification = $notification;

			if ( $notification['toType'] == 'field' ) {
				$legacy_notification['toField'] = $notification['to'];
				unset( $legacy_notification['to'] );
			}

			// Unsetting new properties
			unset( $legacy_notification['toType'] );
			unset( $legacy_notification['id'] );
			unset( $legacy_notification['event'] );
			unset( $legacy_notification['name'] );
			if ( isset( $legacy_notification['type'] ) ) {
				unset( $legacy_notification['type'] );
			}

			//saving into form object
			$property        = $notification['type'] == 'user' ? 'autoResponder' : 'notification';
			$form[ $property ] = $legacy_notification;
		}

		return $form;
	}

	/**
	 * Loads notifications using from the legacy format.
	 *
	 * @since  Unknown
	 * @access private
	 *
	 * @uses GFCommon::has_admin_notification()
	 * @uses GFFormsModel::convert_property_to_merge_tag()
	 * @uses GFCommon::has_user_notification()
	 * @uses GFFormsModel::save_form_notifications()
	 *
	 * @param array $form The Form object.
	 *
	 * @return array $form The Form object.
	 */
	private static function load_notifications_from_legacy( $form ) {

		$form['notifications'] = array();
		if ( GFCommon::has_admin_notification( $form ) ) {
			$admin_notification = $form['notification'];

			//if there is a fromField configured, move it to 'from' as a merge tag
			$admin_notification = self::convert_property_to_merge_tag( $form, $admin_notification, 'from', 'fromField' );

			//if there is a fromNameField configured, move it to 'fromName' as a merge tag
			$admin_notification = self::convert_property_to_merge_tag( $form, $admin_notification, 'fromName', 'fromNameField' );

			//if there is a replyToField configured, move it to 'replyTo' as a merge tag
			$admin_notification = self::convert_property_to_merge_tag( $form, $admin_notification, 'replyTo', 'replyToField' );

			//if routing is configured, set toType to routing, otherwise, set it to email
			$admin_notification['toType'] = ! rgempty( 'routing', $admin_notification ) ? 'routing' : 'email';

			$notification_id = uniqid();

			//assigning this notification to the form_submission action
			$admin_notification['event'] = 'form_submission';
			$admin_notification['name']  = esc_html__( 'Admin Notification', 'gravityforms' );
			$admin_notification['type']  = 'admin';
			$admin_notification['id']    = $notification_id;

			//copying admin notification as an item in the new notifications array
			$form['notifications'][ $notification_id ] = $admin_notification;
		}

		if ( GFCommon::has_user_notification( $form ) ) {

			$user_notification = $form['autoResponder'];

			//if there is a toField configured, set toType to field, if not, set it toemail
			$to_field = rgar( $user_notification, 'toField' );
			if ( ! empty( $to_field ) ) {
				$user_notification['toType'] = 'field';
				$user_notification['to']     = $to_field;
			} else {
				$user_notification['toType'] = 'email';
			}

			$notification_id = uniqid();
			//assigning this notification to the form_submission action
			$user_notification['event'] = 'form_submission';
			$user_notification['name']  = esc_html__( 'User Notification', 'gravityforms' );
			$user_notification['type']  = 'user';
			$user_notification['id']    = $notification_id;

			//copying user notification as an item in the new notifications array
			$form['notifications'][ $notification_id ] = $user_notification;
		}

		self::save_form_notifications( $form['id'], $form['notifications'] );

		return $form;
	}

	/**
	 * Converts a form property to the merge tag format.
	 *
	 * @since  Unknown
	 * @access private
	 *
	 * @uses GFFormsModel::get_field_merge_tag()
	 *
	 * @param array  $form            The Form object.
	 * @param array  $array           Array of properties to search through.
	 * @param string $target_property The property to move the value to.
	 * @param string $source_property The property to search for.
	 *
	 * @return array $array The array that was searched through.
	 */
	private static function convert_property_to_merge_tag( $form, $array, $target_property, $source_property ) {
		$merge_tag = self::get_field_merge_tag( $form, rgar( $array, $source_property ) );
		if ( $merge_tag ) {
			$array[ $target_property ] = $merge_tag;
			unset( $array[ $source_property ] );
		}

		return $array;
	}

	/**
	 * Gets a formatted merge tag for a field.
	 *
	 * @since  Unknown
	 * @access private
	 *
	 * @uses GFFormsModel::get_field()
	 * @uses GFCommon::get_label()
	 *
	 * @param array $form     The Form object.
	 * @param int   $field_id The field ID.
	 *
	 * @return string|false The merge tag if found. False if not found.
	 */
	private static function get_field_merge_tag( $form, $field_id ) {
		$field = self::get_field( $form, $field_id );
		if ( ! $field ) {
			return false;
		}

		return '{' . GFCommon::get_label( $field, $field_id ) . ':' . $field_id . '}';
	}

	/**
	 * Adds default form properties
	 *
	 * @deprecated 1.9
	 */
	public static function add_default_properties( $form ) {
		_deprecated_function( 'GFFormsModel::add_default_properties', '1.9' );

		if ( is_array( rgar( $form, 'fields' ) ) ) {
			$all_fields = array(
				'adminLabel'        => '', 'allowsPrepopulate' => '', 'defaultValue' => '', 'description' => '', 'content' => '', 'cssClass' => '',
				'errorMessage'      => '', 'id' => '', 'inputName' => '', 'isRequired' => '', 'label' => '', 'noDuplicates' => '',
				'size'              => '', 'type' => '', 'postCustomFieldName' => '', 'displayAllCategories' => '', 'displayCaption' => '', 'displayDescription' => '',
				'displayTitle'      => '', 'displayAlt' => '', 'inputType' => '', 'rangeMin' => '', 'rangeMax' => '', 'calendarIconType' => '',
				'calendarIconUrl'   => '', 'dateType' => '', 'dateFormat' => '', 'phoneFormat' => '', 'addressType' => '', 'defaultCountry' => '', 'defaultProvince' => '',
				'defaultState'      => '', 'hideAddress2' => '', 'hideCountry' => '', 'hideState' => '', 'inputs' => '', 'nameFormat' => '', 'allowedExtensions' => '',
				'captchaType'       => '', 'pageNumber' => '', 'captchaTheme' => '', 'simpleCaptchaSize' => '', 'simpleCaptchaFontColor' => '', 'simpleCaptchaBackgroundColor' => '',
				'failed_validation' => '', 'productField' => '', 'enablePasswordInput' => '', 'maxLength' => '', 'enablePrice' => '', 'basePrice' => '',
				'visibility'        => 'visible',
			);

			foreach ( $form['fields'] as &$field ) {
				if ( is_array( $field ) ) {
					$field = wp_parse_args( $field, $all_fields );
				}
			}
		}

		return $form;
	}

	/**
	 * Gets the column info for the entry listing page.
	 *
	 * @since  Unknown
	 * @access public
	 * @global $wpdb
	 *
	 * @uses GFFormsModel::get_meta_table_name()
	 *
	 * @param int $form_id The ID of the form that entries are coming from.
	 *
	 * @return mixed
	 */
	public static function get_grid_column_meta( $form_id ) {
		global $wpdb;

		$table_name = self::get_meta_table_name();

		return maybe_unserialize( $wpdb->get_var( $wpdb->prepare( "SELECT entries_grid_meta FROM $table_name WHERE form_id=%d", $form_id ) ) );
	}

	public static function update_grid_column_meta( $form_id, $columns ) {
		global $wpdb;

		$table_name = self::get_meta_table_name();
		$meta       = maybe_serialize( stripslashes_deep( $columns ) );
		$wpdb->query( $wpdb->prepare( "UPDATE $table_name SET entries_grid_meta=%s WHERE form_id=%d", $meta, $form_id ) );
	}

	public static function get_lead_detail_id( $current_fields, $field_number, $item_index = '' ) {
		if ( version_compare( GFFormsModel::get_database_version(), '2.3-dev-1', '<' ) ) {
			return GF_Forms_Model_Legacy::get_lead_detail_id( $current_fields, $field_number );
		}

		foreach ( $current_fields as $field ) {
			if ( (string) $field->meta_key === (string) $field_number && $field->item_index == $item_index) {
				return $field->id;
			}
		}

		return 0;
	}

	/**
	 * Updates the form is_active property in the database.
	 *
	 * @since unknown
	 * @since 2.7 Updated to clear the form from the cache.
	 *
	 * @param int      $form_id   The ID of the form to be updated.
	 * @param int|bool $is_active Indicates if the form is being set as active.
	 *
	 * @return void
	 */
	public static function update_form_active( $form_id, $is_active ) {
		global $wpdb;
		$form_table = self::get_form_table_name();
		$sql        = $wpdb->prepare( "UPDATE $form_table SET is_active=%d WHERE id=%d", $is_active, $form_id );
		$wpdb->query( $sql );

		if ( $is_active ) {

			/**
			 * Fires after an inactive form gets marked as active
			 *
			 * @since 1.9
			 *
			 * @param int $form_id The Form ID used to specify which form to activate
			 */
			do_action( 'gform_post_form_activated', $form_id );
		} else {

			/**
			 * Fires after an active form gets marked as inactive
			 *
			 * @since 1.9
			 *
			 * @param int $form_id The Form ID used to specify which form to activate
			 */
			do_action( 'gform_post_form_deactivated', $form_id );
		}

		self::flush_current_form( self::get_form_cache_key( $form_id ) );
	}

	public static function update_notification_active( $form_id, $notification_id, $is_active ) {
		$form = GFFormsModel::get_form_meta( $form_id );

		if ( ! isset( $form['notifications'][ $notification_id ] ) ) {
			return new WP_Error( 'not_found', __( 'Notification not found', 'gravityforms' ) );
		}

		$form['notifications'][ $notification_id ]['isActive'] = (bool) $is_active;

		if ( (bool) $is_active ) {
            /**
             * Fires before a notification is activated
             *
             * @param int   $form['notifications'][ $notification_id ] The ID of the notification that was activated
             * @param array $form                                      The Form object
             */
			do_action( 'gform_pre_notification_activated', $form['notifications'][ $notification_id ], $form );
		} else {
            /**
             * Fires before a notification is deactivated
             *
             * @param int   $form['notifications'][ $notification_id ] The ID of the notification that was deactivated
             * @param array $form                                      The Form object
             */
			do_action( 'gform_pre_notification_deactivated', $form['notifications'][ $notification_id ], $form );
		}

		$result = GFFormsModel::update_form_meta( $form_id, $form['notifications'], 'notifications' );

		return $result;
	}

	public static function update_confirmation_active( $form_id, $confirmation_id, $is_active ) {
		$form = GFFormsModel::get_form_meta( $form_id );

		if ( ! isset( $form['confirmations'][ $confirmation_id ] ) ) {
			return new WP_Error( 'not_found', __( 'Confirmation not found', 'gravityforms' ) );
		}

		$form['confirmations'][ $confirmation_id ]['isActive'] = (bool) $is_active;



		$result = GFFormsModel::update_form_meta( $form_id, $form['confirmations'], 'confirmations' );

		return $result;
	}

	public static function update_forms_active( $forms, $is_active ) {
		foreach ( $forms as $form_id ) {
			self::update_form_active( $form_id, $is_active );
		}
	}

	public static function update_leads_property( $leads, $property_name, $property_value ) {
		self::update_entries_property( $leads, $property_name, $property_value );
	}

	public static function update_lead_property( $lead_id, $property_name, $property_value, $update_akismet = true, $disable_hook = false ) {
		return self::update_entry_property( $lead_id, $property_name, $property_value, $update_akismet, $disable_hook );
	}

	public static function update_entries_property( $leads, $property_name, $property_value ) {
		foreach ( $leads as $lead ) {
			self::update_entry_property( $lead, $property_name, $property_value );
		}
	}

	public static function update_entry_property( $lead_id, $property_name, $property_value, $update_akismet = true, $disable_hook = false ) {
		global $wpdb, $current_user;

		if ( version_compare( GFFormsModel::get_database_version(), '2.3-dev-1', '<' ) ) {
			return GF_Forms_Model_Legacy::update_lead_property( $lead_id, $property_name, $property_value, $update_akismet, $disable_hook );
		}

		$entry_table = self::get_entry_table_name();

		$lead = self::get_entry( $lead_id );

		//marking entry as 'spam' or 'not spam' with Akismet if the plugin is installed
		if ( $update_akismet && GFCommon::akismet_enabled( $lead['form_id'] ) && $property_name == 'status' && in_array( $property_value, array( 'active', 'spam' ) ) ) {

			$current_status = $lead['status'];
			if ( $current_status == 'spam' && $property_value == 'active' ) {
				$form = self::get_form_meta( $lead['form_id'] );
				GFCommon::mark_akismet_spam( $form, $lead, false );
			} else if ( $current_status == 'active' && $property_value == 'spam' ) {
				$form = self::get_form_meta( $lead['form_id'] );
				GFCommon::mark_akismet_spam( $form, $lead, true );
			}
		}

		// If property is trash, log user login
		if ( $property_name == 'status' && $property_value == 'trash' && ! empty( $current_user->user_login ) ) {
			GFCommon::log_debug( __METHOD__ . "(): User ID {$current_user->ID} requested moving of entry #{$lead_id} to trash." );
		}

		//updating lead
		$result = $wpdb->update( $entry_table, array( $property_name => $property_value ), array( 'id' => $lead_id ) );

		if ( ! $disable_hook ) {

			$previous_value = rgar( $lead, $property_name );

			if ( $previous_value != $property_value ) {

				// if property is status, prev value is spam and new value is active
				if ( $property_name == 'status' && $previous_value == 'spam' && $property_value == 'active' && ! rgar( $lead, 'post_id' ) ) {
					$lead[ $property_name ] = $property_value;
					$lead['post_id']      = GFCommon::create_post( isset( $form ) ? $form : GFAPI::get_form( $lead['form_id'] ), $lead );
				}

				/**
				 * Fired after an entry property is updated
				 *
				 * @param string $property_name Used within the action string.  Defines the property that fires the action.
				 *
				 * @param int    $lead_id        The Entry ID
				 * @param string $property_value The new value of the property that was updated
				 * @param string $previous_value The previous property value before the update
				 */
				do_action( "gform_update_{$property_name}", $lead_id, $property_value, $previous_value );

				/**
				 * Fired after an entry property is updated.
				 *
				 * @param int    $lead_id        The Entry ID.
				 * @param string $property_name  The property that was updated.
				 * @param string $property_value The new value of the property that was updated.
				 * @param string $previous_value The previous property value before the update.
				 *
				 * @since 2.3.3.9
				 */
				do_action( "gform_post_update_entry_property", $lead_id, $property_name, $property_value, $previous_value );
			}
		}

		return $result;
	}

	private static function truncate( $str, $length ) {
		if ( strlen( $str ) > $length ) {
			$str = substr( $str, 0, $length );
		}

		return $str;
	}

	/**
	 *
	 * @param $leads
	 */
	public static function delete_leads( $leads ) {
		self::delete_entries( $leads );
	}

	public static function delete_entries( $entries ) {
		foreach ( $entries as $entry_id ) {
			self::delete_entry( $entry_id );
		}
	}

	public static function delete_forms( $forms ) {
		foreach ( $forms as $form_id ) {
			self::delete_form( $form_id );
		}
	}

	public static function trash_forms( $form_ids ) {
		foreach ( $form_ids as $form_id ) {
			self::trash_form( $form_id );
		}
	}

	public static function restore_forms( $form_ids ) {
		foreach ( $form_ids as $form_id ) {
			self::restore_form( $form_id );
		}
	}

	public static function delete_leads_by_form( $form_id, $status = '' ) {
		self::delete_entries_by_form( $form_id, $status );
	}

	public static function delete_entries_by_form( $form_id, $status = '' ) {
		global $wpdb, $current_user;

		if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
			GF_Forms_Model_Legacy::delete_leads_by_form( $form_id, $status );
			return;
		}

		$entry_table       = self::get_entry_table_name();
		$entry_notes_table = self::get_entry_notes_table_name();
		$entry_meta_table  = self::get_entry_meta_table_name();

		GFCommon::log_debug( __METHOD__ . "(): Deleting entries for form #{$form_id}." );

		/**
		 * Fires when you delete entries for a specific form
		 *
		 * @param int    $form_id The form ID to specify from which form to delete entries
		 * @param string $status  Allows you to set the form entries to a deleted status
		 */
		do_action( 'gform_delete_entries', $form_id, $status );

		// Get status filter.
		$status_filter = empty( $status ) ? '' : $wpdb->prepare( 'AND status=%s', $status );

		// Get entry IDs.
		$entry_ids = $wpdb->get_col( $wpdb->prepare( "SELECT id FROM $entry_table WHERE form_id=%d {$status_filter}", $form_id ) );

		// If entries were found, loop through them and run action.
		if ( ! empty( $entry_ids ) ) {

		// Log user login for user requesting the deletion of entries
		if ( ! empty( $current_user->user_login ) ) {
			GFCommon::log_debug( __METHOD__ . "(): User ID {$current_user->ID} requested deletion of entries: " . json_encode( $entry_ids ) );
		}

			foreach ( $entry_ids as $entry_id ) {

				/**
				 * Fires before an entry is deleted.
				 *
				 * @param int $entry_id Entry ID to be deleted.
				 */
				do_action( 'gform_delete_entry', $entry_id );


				/**
				 * Fires before a lead is deleted
				 * @param $lead_id
				 * @deprecated Use gform_delete_entry instead
				 * @see gform_delete_entry
				 */
				do_action( 'gform_delete_lead', $entry_id );

			}

		}

		// Deleting uploaded files
		self::delete_files_by_form( $form_id, $status );
		// Delete from entry notes
		$sql = $wpdb->prepare(
			" DELETE FROM $entry_notes_table
                                WHERE entry_id IN (
                                    SELECT id FROM $entry_table WHERE form_id=%d {$status_filter}
                                )", $form_id
		);
		$wpdb->query( $sql );

		// Delete from entry meta
		$sql = $wpdb->prepare(
			" DELETE FROM $entry_meta_table
        						WHERE entry_id IN (
        							SELECT id FROM $entry_table WHERE form_id=%d {$status_filter}
                                )", $form_id
		);
		$wpdb->query( $sql );

		// Delete from entry
		$sql = $wpdb->prepare( "DELETE FROM $entry_table WHERE form_id=%d {$status_filter}", $form_id );
		$wpdb->query( $sql );
	}

	/**
	 * Delete the views for the specified form.
	 *
	 * @param int $form_id The form ID.
	 */
	public static function delete_views( $form_id ) {

		if ( gf_upgrade()->get_submissions_block() ) {
			return new WP_Error( 'submissions_blocked', __( 'Submissions are currently blocked due to an upgrade in progress', 'gravityforms' ) );
		}

		global $wpdb, $current_user;

		// Log user login for user requesting deletion of views
		if ( ! empty( $current_user->user_login ) ) {
			GFCommon::log_debug( __METHOD__ . "(): User ID {$current_user->ID} requested deletion of views for form #{$form_id}." );
		}

		$form_view_table = self::get_form_view_table_name();

		//Delete form view
		$sql = $wpdb->prepare( "DELETE FROM $form_view_table WHERE form_id=%d", $form_id );
		$wpdb->query( $sql );

		/**
         * Fires after form views are deleted
         *
         * @param int $form_id The ID of the form that views were deleted from
         */
		do_action( 'gform_post_form_views_deleted', $form_id );
	}

	public static function delete_form( $form_id ) {

		if ( gf_upgrade()->get_submissions_block() ) {
			return new WP_Error( 'submissions_blocked', __( 'Submissions are currently blocked due to an upgrade in progress', 'gravityforms' ) );
		}

		global $wpdb, $current_user;

		// Log user login for user requesting deletion of form
		if ( ! empty( $current_user->user_login ) ) {
			GFCommon::log_debug( __METHOD__ . "(): User ID {$current_user->ID} requested deletion of form #{$form_id}." );
		}

        /**
         * Fires before a form is deleted
         *
         * @param int $form_id The ID of the form being deleted
         */
		do_action( 'gform_before_delete_form', $form_id );

		$form_meta_table      = self::get_meta_table_name();
		$form_table           = self::get_form_table_name();
		$form_revisions_table = self::get_form_revisions_table_name();

		//Deleting form Entries
		self::delete_leads_by_form( $form_id );

		//Delete form meta
		$sql = $wpdb->prepare( "DELETE FROM $form_meta_table WHERE form_id=%d", $form_id );
		$wpdb->query( $sql );

		//Delete form revisions
		$sql = $wpdb->prepare( "DELETE FROM $form_revisions_table WHERE form_id=%d", $form_id );
		$wpdb->query( $sql );

		//Deleting form Views
		self::delete_views( $form_id );

		//Delete form
		$sql = $wpdb->prepare( "DELETE FROM $form_table WHERE id=%d", $form_id );
		$wpdb->query( $sql );

		self::flush_current_form( self::get_form_cache_key( $form_id ) );

        /**
         * Fires after a form is deleted
         *
         * @param int $form_id The ID of the form that was deleted
         */
		do_action( 'gform_after_delete_form', $form_id );
	}

	public static function trash_form( $form_id ) {

		if ( gf_upgrade()->get_submissions_block() ) {
			return new WP_Error( 'submissions_blocked', __( 'Submissions are currently blocked due to an upgrade in progress', 'gravityforms' ) );
		}

		global $wpdb, $current_user;
		// Log user login for user moving the form to trash
		if ( ! empty( $current_user->user_login ) ) {
			GFCommon::log_debug( __METHOD__ . "(): User ID {$current_user->ID} requested moving of form #{$form_id} to trash." );
		}
		$form_table_name = self::get_form_table_name();
		$sql             = $wpdb->prepare( "UPDATE $form_table_name SET is_trash=1 WHERE id=%d", $form_id );
		$result          = $wpdb->query( $sql );

		self::flush_current_form( self::get_form_cache_key( $form_id ) );

		$success = $result == false;

		/**
		 * Fires after a form is trashed
		 *
		 * @since 1.9
		 *
		 * @param int $form_id The ID of the form that was trashed
		 */
		do_action( 'gform_post_form_trashed', $form_id );

		self::update_recent_forms( $form_id, true );

		return $success;
	}

	public static function restore_form( $form_id ) {

		if ( gf_upgrade()->get_submissions_block() ) {
			return new WP_Error( 'submissions_blocked', __( 'Submissions are currently blocked due to an upgrade in progress', 'gravityforms' ) );
		}

		global $wpdb;
		$form_table_name = self::get_form_table_name();
		$sql             = $wpdb->prepare( "UPDATE $form_table_name SET is_trash=0 WHERE id=%d", $form_id );
		$result          = $wpdb->query( $sql );

		self::flush_current_form( self::get_form_cache_key( $form_id ) );

		$success = $result == false;

        /**
         * Fires after a form is restored from trash
         *
         * @since 1.9
         *
         * @param int $form_id The ID of the form that was restored
         */
		do_action( 'gform_post_form_restored', $form_id );

		return $success;
	}

	/**
	 * Duplicate form.
	 *
	 * @access public
	 * @static
	 * @param  int $form_id Form ID to duplicate.
	 *
	 * @return int|WP_Error
	 */
	public static function duplicate_form( $form_id ) {

		if ( gf_upgrade()->get_submissions_block() ) {
			return new WP_Error( 'submissions_blocked', __( 'Submissions are currently blocked due to an upgrade in progress', 'gravityforms' ) );
		}

		// Get form to be duplicated.
		$form = self::get_form( $form_id );

		// Set initial form title.
		$new_title = self::maybe_increment_title( $form->title );

		// Create new form.
		$new_id = self::insert_form( $new_title );

		// Copying form meta to new form.
		$meta          = self::get_form_meta( $form_id );
		$meta['title'] = $new_title;
		$meta['id']    = $new_id;

		// Add notifications to new form.
		self::update_form_meta( $new_id, $meta['notifications'], 'notifications' );
		unset( $meta['notifications'] );

		// Add confirmations to new form.
		self::update_form_meta( $new_id, $meta['confirmations'], 'confirmations' );
		unset( $meta['confirmations'] );

		// Save form meta.
		self::update_form_meta( $new_id, $meta );

		// Set active state.
		self::update_form_active( $new_id, $form->is_active );

		// The gform_after_duplicate_form action is deprecated since version 1.9. Please use gform_post_form_duplicated instead

        /**
         * @deprecated
         * @see gform_post_form_duplicated
         */
        do_action( 'gform_after_duplicate_form', $form_id, $new_id );

		/**
		 * Fires after a form is duplicated
		 *
		 * @param int $form_id The original form's ID
		 * @param int $new_id  The ID of the new, duplicated form
		 */
		do_action( 'gform_post_form_duplicated', $form_id, $new_id );

		return $new_id;

	}


	public static function is_unique_title( $title, $form_id=0 ) {
		$forms = self::get_forms();
		foreach ( $forms as $form ) {
			if ( strtolower( $form->title ) === strtolower( $title ) && (int) $form->id !== (int) $form_id ) {
				return false;
			}
		}

		return true;
	}

	/**
	 * If a form title isn't unique, add a number to it to make it unique.
	 *
	 * @since 2.5
	 *
	 * @param string     $title
	 * @param int|string $form_id
	 *
	 * @return string
	 */
	public static function maybe_increment_title( $title, $form_id = '' ) {
		// Title is unique, so we don't need to do anything to it.
		if ( self::is_unique_title( $title, $form_id ) ) {
			return $title;
		}

		// Check for form count in form title.
		preg_match_all( '/(\\(([0-9])*\\))$/mi', $title, $count_exists_in_title );

		// If count does not exist, set count to 1.
		if ( empty( $count_exists_in_title[0] ) ) {

			// Set initial count.
			$count = 1;

		} else {

			// Set existing count to current count plus one.
			$count = (int) $count_exists_in_title[2][0] + 1;

			// Remove existing count from title.
			$title = preg_replace( '/(\\(([0-9])*\\))$/mi', null, $title );

		}

		// Trim title.
		$title = trim( $title );

		// Add copy count to form title.
		$new_title = $title . " ($count)";

		// If new form title is not unique, increment the count until a unique form title is created.
		while ( ! self::is_unique_title( $new_title, $form_id ) ) {
			$count++;
			$new_title = $title . " ($count)";
		}

		return $new_title;
	}

	public static function ensure_tables_exist() {
		global $wpdb;
		$form_table_name = self::get_form_table_name();
		$form_count      = $wpdb->get_var( "SELECT count(0) FROM {$form_table_name}" );
		if ( $wpdb->last_error ) {
			GFCommon::log_debug( 'GFFormsModel::ensure_tables_exist(): Blog ' . get_current_blog_id() . ' - Form database table does not exist. Forcing database setup.' );
			gf_upgrade()->upgrade_schema();
		}
	}

	public static function insert_form( $form_title ) {
		global $wpdb;
		$form_table_name = self::get_form_table_name();

		//creating new form
		$wpdb->query( $wpdb->prepare( "INSERT INTO $form_table_name(title, date_created) VALUES(%s, utc_timestamp())", $form_title ) );

		//returning newly created form id
		return $wpdb->insert_id;

	}

	/**
	 * Update form meta.
	 *
	 * @since 2.4 Added the form revision creation functionality.
	 *
	 * @param int    $form_id Form id.
	 * @param array  $form_meta Form meta.
	 * @param string $meta_name Meta name.
	 *
	 * @return false|int Number of rows affected/selected or false on error.
	 */
	public static function update_form_meta( $form_id, $form_meta, $meta_name = 'display_meta' ) {
		global $wpdb;

		$form_meta = gf_apply_filters( array( 'gform_form_update_meta', $form_id ), $form_meta, $form_id, $meta_name );

		$meta_table_name = self::get_meta_table_name();
		$new_display_meta = $form_meta;
		$form_meta       = json_encode( $form_meta );

		if ( $meta_name === 'display_meta' ) {
			self::maybe_create_form_revision( $new_display_meta, $form_id );
		}

		if ( intval( $wpdb->get_var( $wpdb->prepare( "SELECT count(0) FROM $meta_table_name WHERE form_id=%d", $form_id ) ) ) > 0 ) {
			$result = $wpdb->query( $wpdb->prepare( "UPDATE $meta_table_name SET $meta_name=%s WHERE form_id=%d", $form_meta, $form_id ) );
		} else {
			$result = $wpdb->query( $wpdb->prepare( "INSERT INTO $meta_table_name(form_id, $meta_name) VALUES(%d, %s)", $form_id, $form_meta ) );
		}

		self::flush_current_form( self::get_form_cache_key( $form_id ) );

		/**
		 * Fires after form meta has been updated for any form
		 *
		 * @param mixed  $form_meta The Form Meta object from the database
		 * @param int    $form_id   The ID of the form data was updated
		 * @param string $meta_name The name of the meta updated
		 */
		gf_do_action( array( 'gform_post_update_form_meta', $form_id ), $form_meta, $form_id, $meta_name );

		return $result;
	}

	/**
	 * Create form revision if conditions met.
	 *
	 * @since 2.4
	 *
	 * @param array $new_display_meta Form meta.
	 * @param int    $form_id Form ID.
	 */
	public static function maybe_create_form_revision( $new_display_meta, $form_id ) {
		global $wpdb;

		// Make sure the form isn't in the cache before calling get_form_meta().
		self::flush_current_form( self::get_form_cache_key( $form_id ) );
		$form = self::get_form_meta( $form_id );
		// check if form has consent field.
		if ( GFCommon::has_consent_field( $new_display_meta ) ) {
			$revisions_table_name = self::get_form_revisions_table_name();

			// create the first revision.
			if ( intval( $wpdb->get_var( $wpdb->prepare( "SELECT count(0) FROM $revisions_table_name WHERE form_id=%d", $form_id ) ) ) === 0 ) {
				$wpdb->query( $wpdb->prepare( "INSERT INTO $revisions_table_name(form_id, display_meta, date_created) VALUES(%d, %s, utc_timestamp())", $form_id, json_encode( $new_display_meta ) ) );

				return;
			}

			$old_consent_fields = array();
			foreach ( $form['fields'] as $field ) {
				if ( $field->get_input_type() === 'consent' ) {
					$old_consent_fields[ $field->id ] = $field->description;
				}
			}

			// check if consent field description changed.
			$create_revision = false;
			foreach ( $new_display_meta['fields'] as $field ) {
				if ( self::get_input_type( $field ) === 'consent' ) {
					if ( $field['description'] !== rgar( $old_consent_fields, $field['id'] ) ) {
						$create_revision = true;

						break;
					}
				}
			}

			if ( $create_revision ) {
				$wpdb->query( $wpdb->prepare( "INSERT INTO $revisions_table_name(form_id, display_meta, date_created) VALUES(%d, %s, utc_timestamp())", $form_id, json_encode( $new_display_meta ) ) );
			}
		}
	}

	/**
	 * Get the latest revision ID from form revisions.
	 *
	 * @since 2.4
	 *
	 * @param int $form_id Form ID.
	 *
	 * @return int Revision ID.
	 */
	public static function get_latest_form_revisions_id( $form_id ) {
		global $wpdb;
		$revisions_table_name = GFFormsModel::get_form_revisions_table_name();
		$value                = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM $revisions_table_name WHERE form_id=%d ORDER BY date_created DESC, id DESC LIMIT 1", $form_id ) );

		return $value;
	}

	public static function delete_files( $lead_id, $form = null ) {
		$lead = self::get_lead( $lead_id );

		if ( $form == null ) {
			$form = self::get_form_meta( $lead['form_id'] );
		}

		$field_types = self::get_delete_file_field_types( $form );
		$fields      = self::get_fields_by_type( $form, $field_types );

		if ( is_array( $fields ) ) {
			foreach ( $fields as $field ) {

				if ( $field->multipleFiles ) {
					$value_json = self::get_lead_field_value( $lead, $field );
					if ( ! empty( $value_json ) ) {
						$files = json_decode( $value_json, true );
						if ( false === empty( $files ) && is_array( $files ) ) {
							foreach ( $files as $file ) {
								self::delete_physical_file( $file, $lead_id );
							}
						}
					}
				} else {
					$value = self::get_lead_field_value( $lead, $field );
					self::delete_physical_file( $value, $lead_id );
				}
			}
		}
	}

	public static function delete_files_by_form( $form_id, $status = '' ) {
		global $wpdb;

		$entry_table_name = version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ? self::get_lead_table_name() : self::get_entry_table_name();

		$form        = self::get_form_meta( $form_id );
		$field_types = self::get_delete_file_field_types( $form );
		$fields      = self::get_fields_by_type( $form, $field_types );

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

		$status_filter = empty( $status ) ? '' : $wpdb->prepare( 'AND status=%s', $status );
		$results       = $wpdb->get_results( $wpdb->prepare( "SELECT id FROM {$entry_table_name} WHERE form_id=%d {$status_filter}", $form_id ), ARRAY_A );

		foreach ( $results as $result ) {
			self::delete_files( $result['id'], $form );
		}
	}

	/**
	 * Returns an array of field types for which can uploaded files can be deleted.
	 *
	 * @since 2.4.6.1
	 *
	 * @param array $form The current form.
	 *
	 * @return array
	 */
	public static function get_delete_file_field_types( $form ) {
		$field_types = array( 'fileupload', 'post_image' );

		/**
		 * Allows more files to be deleted
		 *
		 * @since 1.9.10
		 *
		 * @param array $field_types Field types which contain file uploads
		 * @param array $form The Form Object
		 */
		return gf_apply_filters( array( 'gform_field_types_delete_files', $form['id'] ), $field_types, $form );
	}

	/**
	 * Deletes the uploaded files for the specified form and field.
	 *
	 * Note: Does not delete the file URLs from the entries, that is done by GFFormsModel::delete_field_values().
	 *
	 * @since 2.4.6.1
	 *
	 * @since 2.5.16 changed the query to return entry ID as well.
	 *
	 * @param int $form_id The current form ID.
	 * @param int $field_id The ID of field being deleted.
	 */
	public static function delete_field_files( $form_id, $field_id ) {
		if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
			return;
		}

		global $wpdb;

		$entry_meta_table_name = self::get_entry_meta_table_name();

		$results = $wpdb->get_results( $wpdb->prepare( "SELECT entry_id, meta_value FROM {$entry_meta_table_name} WHERE form_id=%d AND meta_key=%s", $form_id, $field_id ), ARRAY_A );
		if ( is_array( $results ) ) {
			foreach ( $results as $result ) {
				if ( ! is_array( $result ) || empty( rgar( $result, 'meta_value' ) ) ) {
					continue;
				}
				if ( strpos( $result['meta_value'], '[' ) === 0 ) {
					// Value from a multi-file enabled field.
					$files = json_decode( $result['meta_value'], true );

					if ( is_array( $files ) ) {
						foreach ( $files as $file ) {
							self::delete_physical_file( $file, $result['entry_id'] );
						}
					}
				} else {
					// Value from a single file or post image field.
					self::delete_physical_file( $result['meta_value'], $result['entry_id'] );
				}
			}
		}
	}

	public static function delete_file( $entry_id, $field_id, $file_index = 0 ) {
		global $wpdb;

		if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
			GF_Forms_Model_Legacy::delete_file( $entry_id, $field_id, $file_index );
			return;
		}


		if ( $entry_id == 0 || $field_id == 0 ) {
			return;
		}

		$entry          = self::get_lead( $entry_id );
		$form_id        = $entry['form_id'];
		$form           = self::get_form_meta( $form_id );
		$field          = self::get_field( $form, $field_id );
		$multiple_files = $field->multipleFiles;
		if ( $multiple_files ) {
			$file_urls = json_decode( $entry[ $field_id ], true );
			$file_url  = $file_urls[ $file_index ];
			unset( $file_urls[ $file_index ] );
			$file_urls   = array_values( $file_urls );
			$field_value = empty( $file_urls ) ? '' : json_encode( $file_urls );
		} else {
			$file_url    = $entry[ $field_id ];
			$field_value = '';
		}

		self::delete_physical_file( $file_url, $entry_id );

		// Update entry field value - simulate form submission.
		$entry_meta_table_name = self::get_entry_meta_table_name();
		$sql                   = $wpdb->prepare( "SELECT id FROM {$entry_meta_table_name} WHERE entry_id=%d AND meta_key = %s", $entry_id, $field_id );
		$entry_meta_id         = $wpdb->get_var( $sql );

		self::update_entry_field_value( $form, $entry, $field, $entry_meta_id, $field_id, $field_value );

	}

	private static function delete_physical_file( $file_url, $entry_id ) {

		$ary = explode( '|:|', $file_url );
		$url = rgar( $ary, 0 );
		if ( empty( $url ) ) {
			return;
		}

		$file_path = self::get_physical_file_path( $url, $entry_id );

		/**
		 * Allow the file path to be overridden so files stored outside the /wp-content/uploads/gravity_forms/ directory can be deleted.
		 *
		 * @since 2.2.3.1
		 *
		 * @param string $file_path The path of the file to be deleted.
		 * @param string $url       The URL of the file to be deleted.
		 */
		$file_path = apply_filters( 'gform_file_path_pre_delete_file', $file_path, $url );

		if ( file_exists( $file_path ) ) {
			unlink( $file_path );
			gform_delete_meta( $entry_id, GF_Field_FileUpload::get_file_upload_path_meta_key_hash( $url ) );
		}


	}

	/**
	 * Gets the physical path of a file.
	 *
	 * @since unknown
	 *
	 * @since 2.5.16  Added optional $entry_id parameter.
	 *
	 * @param string       $url
	 * @param integer|null $entry_id
	 *
	 * @return string
	 */
	public static function get_physical_file_path( $url, $entry_id = null ) {

		$path_info = GF_Field_FileUpload::get_file_upload_path_info( $url, $entry_id );

		// convert from url to physical path
		if ( is_multisite() && get_site_option( 'ms_files_rewriting' ) ) {
			$file_path = preg_replace( "|^(.*?)/files/gravity_forms/|", BLOGUPLOADDIR . 'gravity_forms/', $url );
		} else {
			$file_path = str_replace( trailingslashit( $path_info['url'] ), trailingslashit( $path_info['path'] ), $url );
		}

		return $file_path;
	}

	public static function delete_field( $form_or_id, $field_id, $save_form = true ) {
		$form = is_numeric( $form_or_id ) ? self::get_form_meta( $form_or_id ) : $form_or_id;

		if ( empty( $form['id'] ) || ! isset( $form['fields'] ) || ! is_array( $form['fields'] ) ) {
			return null;
		}

		$form_id = $form['id'];

        /**
         * Fires before a field is deleted
         *
         * @param int $form_id  The ID of the form that the field is being deleted from
         * @param int $field_id The ID of the field being deleted
         */
		do_action( 'gform_before_delete_field', $form_id, $field_id );

		$field_type = '';

		$count = sizeof( $form['fields'] );
		for ( $i = $count - 1; $i >= 0; $i -- ) {
			/** @var GF_Field $field */
			$field = $form['fields'][ $i ];

			// Deleting associated conditional logic rules.
			if ( ! empty( $field->conditionalLogic ) ) {
				$field->conditionalLogic = self::delete_field_from_conditional_logic( $field->conditionalLogic, $field_id );
			}

			if ( $field->type === 'page' && ! empty( $field->nextButton['conditionalLogic'] ) ) {
				$field->nextButton['conditionalLogic'] = self::delete_field_from_conditional_logic( $field->nextButton['conditionalLogic'], $field_id );
			}

			// Deleting field from form meta.
			if ( $field->id == $field_id ) {
				$field_type = $field->type;
				unset( $form['fields'][ $i ] );
			}
		}

		// The field has already been removed from the form passed by GFFormDetail::save_form_info(), get the field from the db.
		if ( empty( $field_type ) && $deleted_field = self::get_field( $form_id, $field_id ) ) {
			$field_type = $deleted_field->type;
		}

		// Removing post content and title template if the field being deleted is a post content field or post title field.
		if ( $field_type == 'post_content' ) {
			$form['postContentTemplateEnabled'] = false;
			$form['postContentTemplate']        = '';
		} else if ( $field_type == 'post_title' ) {
			$form['postTitleTemplateEnabled'] = false;
			$form['postTitleTemplate']        = '';
		}

		if ( ! empty( $form['button']['conditionalLogic'] ) ) {
			$form['button']['conditionalLogic'] = self::delete_field_from_conditional_logic( $form['button']['conditionalLogic'], $field_id );
		}

		// Notifications/confirmations are not present in the form passed by GFFormDetail::save_form_info() but they could be present in other scenarios.
		$form = GFFormsModel::delete_field_from_confirmations( $form, $field_id );
		$form = GFFormsModel::delete_field_from_notifications( $form, $field_id );

		if ( in_array( $field_type, self::get_delete_file_field_types( $form ) ) ) {
			self::delete_field_files( $form_id, $field_id );
		}

		$form['fields'] = array_values( $form['fields'] );

		if ( $save_form ) {
			self::update_form_meta( $form_id, $form );
		}

		//Delete from grid column meta
		$columns = self::get_grid_column_meta( $form_id );
		if ( is_array( $columns ) ) {
			$count = sizeof( $columns );
			for ( $i = $count - 1; $i >= 0; $i -- ) {
				if ( intval( rgar( $columns, $i ) ) == intval( $field_id ) ) {
					unset( $columns[ $i ] );
				}
			}
			self::update_grid_column_meta( $form_id, $columns );
		}

		self::delete_field_values( $form_id, $field_id );

		/**
		 * Fires after a field is deleted
		 *
		 * @param int $form_id  The form ID where the form was deleted
		 * @param int $field_id The ID of the field that was deleted
         *
		 */
		do_action( 'gform_after_delete_field', $form_id, $field_id );

		return $form;
	}

	/**
	 * Deletes confirmation conditional logic rules based on the deleted field.
	 *
	 * @since 2.4.6.1
	 *
	 * @param array $form     The form containing the confirmations to be processed.
	 * @param int   $field_id The ID of the field being deleted.
	 *
	 * @return array
	 */
	public static function delete_field_from_confirmations( $form, $field_id ) {
		if ( empty( $form['confirmations'] ) ) {
			return $form;
		}

		$save = false;

		foreach ( $form['confirmations'] as &$confirmation ) {
			if ( ! empty( $confirmation['conditionalLogic'] ) ) {
				$processed = self::delete_field_from_conditional_logic( $confirmation['conditionalLogic'], $field_id );
				if ( $confirmation['conditionalLogic'] != $processed ) {
					$confirmation['conditionalLogic'] = $processed;
					$save                             = true;
				}
			}
		}

		if ( $save ) {
			GFFormsModel::update_form_meta( $form['id'], $form['confirmations'], 'confirmations' );
		}

		return $form;
	}

	/**
	 * Deletes notification routing and conditional logic rules based on the deleted field.
	 *
	 * @since 2.4.6.1
	 *
	 * @param array $form     The form containing the notifications to be processed.
	 * @param int   $field_id The ID of the field being deleted.
	 *
	 * @return array
	 */
	public static function delete_field_from_notifications( $form, $field_id ) {
		if ( empty( $form['notifications'] ) ) {
			return $form;
		}

		$save = false;

		foreach ( $form['notifications'] as &$notification ) {
			if ( ! empty( $notification['routing'] ) ) {
				$dirty = false;

				foreach ( $notification['routing'] as $key => $rule ) {
					if ( intval( rgar( $rule, 'fieldId' ) ) == $field_id ) {
						unset( $notification['routing'][ $key ] );
						$dirty = true;
					}
				}

				if ( $dirty ) {
					$notification['routing'] = empty( $notification['routing'] ) ? null : array_values( $notification['routing'] );
					$save                    = true;
				}
			}

			if ( ! empty( $notification['conditionalLogic'] ) ) {
				$processed = self::delete_field_from_conditional_logic( $notification['conditionalLogic'], $field_id );
				if ( $notification['conditionalLogic'] != $processed ) {
					$notification['conditionalLogic'] = $processed;
					$save                             = true;
				}
			}
		}

		if ( $save ) {
			GFFormsModel::update_form_meta( $form['id'], $form['notifications'], 'notifications' );
		}

		return $form;
	}

	/**
	 * Deletes conditional logic rules based on the deleted field.
	 *
	 * If no rules remain following the deletion conditional logic is disabled.
	 *
	 * @since 2.4.6.1
	 *
	 * @param array $logic    The conditional logic object to be processed.
	 * @param int   $field_id The ID of the field being deleted.
	 *
	 * @return null|array
	 */
	public static function delete_field_from_conditional_logic( $logic, $field_id ) {
		if ( empty( $logic['rules'] ) ) {
			return null;
		}

		$dirty = false;

		foreach ( $logic['rules'] as $key => $rule ) {
			if ( intval( rgar( $rule, 'fieldId' ) ) == $field_id ) {
				unset( $logic['rules'][ $key ] );
				$dirty = true;
			}
		}

		if ( $dirty ) {
			if ( empty( $logic['rules'] ) ) {
				$logic = null;
			} else {
				$logic['rules'] = array_values( $logic['rules'] );
			}
		}

		return $logic;
	}

	public static function delete_field_values( $form_id, $field_id ) {
		global $wpdb;

		if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
			GF_Forms_Model_Legacy::delete_field_values( $form_id, $field_id );
			return;
		}

		$entry_table      = self::get_entry_table_name();
		$entry_meta_table = self::get_entry_meta_table_name();

		// Delete from entry meta
		$sql = $wpdb->prepare( "DELETE FROM $entry_meta_table WHERE form_id=%d AND meta_key = %s", $form_id, $field_id );
		if ( is_numeric( $field_id ) ) {
			$sql .= $wpdb->prepare( " OR form_id=%d AND meta_key LIKE %s", $form_id, sprintf( '%d.%%', $field_id ) );
		}
		$wpdb->query( $sql );

		// Delete leads with no details
		$sql = $wpdb->prepare(
			" DELETE FROM $entry_table
	            WHERE form_id=%d
	            AND id NOT IN(
	                SELECT DISTINCT(entry_id) FROM $entry_meta_table WHERE form_id=%d
	            )", $form_id, $form_id
		);
		$wpdb->query( $sql );
	}

	/**
	 * Deletes a lead.
	 *
	 * @param $lead_id
	 */
	public static function delete_lead( $lead_id ) {
		self::delete_entry( $lead_id );
	}

	public static function delete_entry( $entry_id ) {
		global $wpdb, $current_user;

		// Log if user requested deletion of entries
		if ( ! empty( $current_user->user_login ) ) {
			GFCommon::log_debug( __METHOD__ . "(): User ID {$current_user->ID} requested deletion of entry #{$entry_id}" );
		}

		if ( version_compare( GFFormsModel::get_database_version(), '2.3-dev-1', '<' ) ) {
			GF_Forms_Model_Legacy::delete_lead( $entry_id );
			return;
		}

		GFCommon::log_debug( __METHOD__ . "(): Deleting entry #{$entry_id}." );

		/**
		 * Fires before an entry is deleted.
		 *
		 * @param $entry_id
		 */
		do_action( 'gform_delete_entry', $entry_id );

		/**
		 * Fires before a lead is deleted
		 * @param $lead_id
		 * @deprecated Use gform_delete_entry instead
		 * @see gform_delete_entry
		 */
		do_action( 'gform_delete_lead', $entry_id );


		$entry_table             = self::get_entry_table_name();
		$entry_notes_table       = self::get_entry_notes_table_name();
		$entry_meta_table_name = self::get_entry_meta_table_name();

		// Deleting uploaded files
		self::delete_files( $entry_id );

		// Delete from entry meta
		$sql = $wpdb->prepare( "DELETE FROM $entry_meta_table_name WHERE entry_id=%d", $entry_id );
		$wpdb->query( $sql );

		// Delete from lead notes
		$sql = $wpdb->prepare( "DELETE FROM $entry_notes_table WHERE entry_id=%d", $entry_id );
		$wpdb->query( $sql );


		// Delete from entry table
		$sql = $wpdb->prepare( "DELETE FROM $entry_table WHERE id=%d", $entry_id );
		$wpdb->query( $sql );
	}

	/**
	 * Adds a note.
	 *
	 * @since 2.4.18 Return statement added.
	 * @since unknown
	 *
	 * @param int     $entry_id  ID of the entry to add the note to.
	 * @param int     $user_id   ID of the user who created the note.
	 * @param string  $user_name Name of the user who created the note.
	 * @param string  $note      Text of the note.
	 * @param string  $note_type Note type.
	 * @param null    $sub_type  Note sub-type.
	 * @return int               ID of the new note.
	 */
	public static function add_note( $entry_id, $user_id, $user_name, $note, $note_type = 'user', $sub_type = null ) {
		global $wpdb;

		if ( version_compare( GFFormsModel::get_database_version(), '2.3-dev-1', '<' ) ) {
			GF_Forms_Model_Legacy::add_note( $entry_id, $user_id, $user_name, $note, $note_type );
			return;
		}

		$table_name = self::get_entry_notes_table_name();
		$sql        = $wpdb->prepare( "INSERT INTO $table_name(entry_id, user_id, user_name, value, note_type, sub_type, date_created) values(%d, %d, %s, %s, %s, %s, utc_timestamp())", $entry_id, $user_id, $user_name, $note, $note_type, $sub_type );

		$wpdb->query( $sql );

		/**
		 * Fires after a note has been added to an entry
		 *
		 * @since 2.4.13  Added sub_type parameter.
		 * @since Unknown
		 *
		 * @param int    $wpdb->insert_id The row ID of this note in the database
		 * @param int    $entry_id        The ID of the entry that the note was added to
		 * @param int    $user_id         The ID of the current user adding the note
		 * @param string $user_name       The user name of the current user
		 * @param string $note            The content of the note being added
		 * @param string $note_type       The type of note being added.  Defaults to 'note'
		 * @param string $sub_type        The sub-type of note being added.
		 *
		 */
		do_action( 'gform_post_note_added', $wpdb->insert_id, $entry_id, $user_id, $user_name, $note, $note_type, $sub_type );

		return $wpdb->insert_id;
	}

	/**
	 * Updates a note.
	 *
	 * @since 2.4.18
	 *
	 * @param int       $note_id      ID of the note to update.
	 * @param int       $entry_id     ID of the entry to add the note to.
	 * @param int       $user_id      ID of the user who created the note.
	 * @param string    $user_name    Name of the user who created the note.
	 * @param string    $date_created Date and time the note was created in SQL datetime format.
	 * @param string    $note         Text of the note.
	 * @param string    $note_type    Note type.
	 * @param null      $sub_type     Note sub-type.
	 * @return string
	 */
	public static function update_note( $note_id, $entry_id, $user_id, $user_name, $date_created, $note, $note_type = 'user', $sub_type = null ) {
		global $wpdb;

		$table_name = self::get_entry_notes_table_name();
		$sql = $wpdb->prepare(
			"
                UPDATE $table_name
                SET
                entry_id = %d,
                user_id = %d,
                user_name = %s,
                date_created = %s,
                value = %s,
                note_type = %s,
                sub_type = %s
                WHERE
                id = %d
                ", $entry_id, $user_id, $user_name, $date_created, $note, $note_type, $sub_type, $note_id
		);

		$result = $wpdb->query( $sql );

		return $result;
	}

	/**
	 * Deletes a note.
	 *
	 * @since 2.4.18 Return statement added.
	 * @since unknown
	 *
	 * @param $note_id  int  ID of the note to delete.
	 * @return bool|int Success or failure.
	 */
	public static function delete_note( $note_id ) {
		global $wpdb;

		if ( version_compare( GFFormsModel::get_database_version(), '2.3-dev-1', '<' ) ) {
			GF_Forms_Model_Legacy::delete_note( $note_id );
			return;
		}

		$table_name = self::get_entry_notes_table_name();

		$lead_id = $wpdb->get_var( $wpdb->prepare( "SELECT entry_id FROM $table_name WHERE id = %d", $note_id ) );

		/**
		 * Fires before a note is deleted
		 *
		 * @param int $note_id The current note ID
		 * @param int $lead_id The current lead ID
		 */
		do_action( 'gform_pre_note_deleted', $note_id, $lead_id );

		$sql    = $wpdb->prepare( "DELETE FROM $table_name WHERE id=%d", $note_id );
		$result = $wpdb->query( $sql );

		return $result;
	}

	public static function delete_notes( $notes ) {
		if ( ! is_array( $notes ) ) {
			return;
		}

		foreach ( $notes as $note_id ) {
			self::delete_note( $note_id );
		}
	}

	/**
	 * Add note to entry containing the notification sending result
	 *
	 * @since  2.4.14
	 *
	 * @param integer        $entry_id       Id number for entry being processed.
	 * @param string|boolean $result         The result returned by wp_mail().
	 * @param array          $notification   The notification properties.
	 * @param string         $error_info     Additional details for notifications with error.
	 * @param array          $email          Array containing email details.
	 * @param array          $note_args      Array containing text, type and subtype for the note.
	 */
	public static function add_notification_note( $entry_id, $result, $notification, $error_info = '', $email = array(), $note_args = array() ) {

		// Skip if no entry id (e.g. Save and Continue notifications).
		if ( empty( $entry_id ) ) {
			return;
		}

		// If $note_args is empty, use default arguments for Gravity Forms core notifications.
		if ( empty( $note_args ) ) {
			if ( $result === true ) {

				$note_args['type'] = 'notification';
				$note_args['subtype'] = 'success';
				$note_args['text'] = esc_html__( 'WordPress successfully passed the notification email to the sending server.', 'gravityforms' );

			} elseif ( $result === false ) {

				$note_args['type'] = 'notification';
				$note_args['subtype'] = 'error';
				$note_args['text'] = esc_html__( 'WordPress was unable to send the notification email.', 'gravityforms' );

				// Add additional error message if any.
				if ( ! empty( $error_info ) ) {
					$note_args['text'] .=  ' ' . $error_info;
				}
			}
		}

		/**
		 * Allow customization of the Sending Result Note.
		 *
		 * @param array  $note_args        Array containing text, type and subtype for the note.
		 * @param int    $entry_id         Id number for entry being processed.
		 * @param bool   $result           The result returned by wp_mail().
		 * @param array  $notification     The notification properties.
		 * @param string $error_info       Additional details for notifications with error.
		 * @param array  $email            Array containing email details.
		 */
		$note_args = apply_filters( 'gform_notification_note', $note_args, $entry_id, $result, $notification, $error_info, $email );

		if ( ! empty( $note_args['text'] ) ) {
			// translators: Notification name followed by its ID. e.g. Admin Notification (ID: 5d4c0a2a37204).
			self::add_note( $entry_id, 0, sprintf( esc_html__( '%1$s (ID: %2$s)', 'gravityforms' ), $notification['name'], $notification['id'] ), $note_args['text'], $note_args['type'], $note_args['subtype'] );
		}

	}

	/**
	 * Gets the IP to be used within the entry.
	 *
	 * @since 2.2 Using $_SERVER['REMOTE_ADDR'].
	 *
	 * @return string The IP to be stored in the entry.
	 */
	public static function get_ip() {

		$ip = rgar( $_SERVER, 'REMOTE_ADDR' );

		/**
		 * Allows the IP address of the client to be modified.
		 *
		 * Use this filter if the server is behind a proxy.
		 *
		 * @since 2.2
		 * @example https://docs.gravityforms.com/gform_ip_address/
		 *
		 * @param string $ip The IP being used.
		 */
		$ip = apply_filters( 'gform_ip_address', $ip );

		// HTTP_X_FORWARDED_FOR can return a comma separated list of IPs; use the first one
		$ips = explode( ',', $ip );

		return $ips[0];
	}

	public static function save_lead( $form, &$entry ) {
		self::save_entry( $form, $entry );
	}

	/**
	 * Save Entry to database.
	 *
	 * @since 2.4.8.13 Updated created_by property to save as an empty value when undefined.
	 * @since Unknown
	 *
	 * @param array $form  Form object.
	 * @param array $entry Entry object.
	 */
	public static function save_entry( $form, &$entry ) {
		global $wpdb, $current_user;

		if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
			GF_Forms_Model_Legacy::save_lead( $form, $entry );
			$entry = GFAPI::get_entry( $entry['id'] );
			return;
		}

		GFCommon::timer_start( __METHOD__ );
		GFCommon::log_debug( __METHOD__ . '(): Saving entry.' );

		$is_form_editor  = GFCommon::is_form_editor();
		$is_entry_detail = GFCommon::is_entry_detail();
		$is_admin        = $is_form_editor || $is_entry_detail;

		if ( $is_admin && ! GFCommon::current_user_can_any( 'gravityforms_edit_entries' ) ) {
			wp_die( esc_html__( "You don't have adequate permission to edit entries.", 'gravityforms' ) );
		}

		$entry_meta_table = self::get_entry_meta_table_name();
		$is_new_lead      = empty( $entry );

		// Log user login for user updating the entry
		if ( ! $is_new_lead && ! empty( $entry['id'] ) && ! empty( $current_user->ID ) ) {
			GFCommon::log_debug( __METHOD__ . "(): User ID {$current_user->ID} requested update of entry #{$entry['id']}." );
		}

		if ( ! $is_new_lead && ! self::entry_exists( rgar( $entry, 'id' ) ) ) {
			// Force a new entry to be saved when an entry does not exist for the supplied id.
			$entry       = array();
			$is_new_lead = true;
			GFCommon::log_debug( __METHOD__ . '(): Existing entry not found. Saving new entry instead.' );
		}

		$die_message = esc_html__( 'An error prevented the entry for this form submission being saved. Please contact support.', 'gravityforms' );

		$entry_table = GFFormsModel::get_entry_table_name();

		$current_date = $wpdb->get_var( 'SELECT utc_timestamp()' );

		if ( $is_new_lead ) {
			// Saving the new entry.

			$user_id = $current_user && $current_user->ID ? $current_user->ID : null;

			$user_agent = self::truncate( rgar( $_SERVER, 'HTTP_USER_AGENT' ), 250 );
			$user_agent = sanitize_text_field( $user_agent );
			$source_url = self::truncate( self::get_current_page_url(), 200 );

			/**
			 * Allow the currency code to be overridden.
			 *
			 * @param string $currency The three character ISO currency code to be stored in the entry. Default is value returned by GFCommon::get_currency()
			 * @param array $form The form currently being processed.
			 *
			 */
			$currency = gf_apply_filters( array( 'gform_currency_pre_save_entry', $form['id'] ), GFCommon::get_currency(), $form );

			$ip = rgars( $form, 'personalData/preventIP' ) ? '' : self::get_ip();

			$wpdb->insert(
				$entry_table,
				array(
					'form_id'      => $form['id'],
					'ip'           => $ip,
					'source_url'   => $source_url,
					'date_created' => $current_date,
					'date_updated' => $current_date,
					'user_agent'   => $user_agent,
					'currency'     => $currency,
					'created_by'   => $user_id,
				),
				array(
					'form_id'      => '%d',
					'ip'           => '%s',
					'source_url'   => '%s',
					'date_created' => '%s',
					'date_updated' => '%s',
					'user_agent'   => '%s',
					'currency'     => '%s',
					'created_by'   => '%s',
				)
			);

			// Reading newly created lead id
			$lead_id = $wpdb->insert_id;

			if ( $lead_id == 0 ) {
				GFCommon::log_error( __METHOD__ . '(): Unable to save entry. ' . $wpdb->last_error );
				wp_die( $die_message );
			}

			$entry = array(
				'id'               => (string) $lead_id,
				'status'           => 'active',
				'form_id'          => (string) $form['id'],
				'ip'               => $ip,
				'source_url'       => $source_url,
				'currency'         => $currency,
				'post_id'          => null,
				'date_created'     => $current_date,
				'date_updated'     => $current_date,
				'is_starred'       => 0,
				'is_read'          => 0,
				'user_agent'       => $user_agent,
				'payment_status'   => null,
				'payment_date'     => null,
				'payment_amount'   => null,
				'payment_method'   => '',
				'transaction_id'   => null,
				'is_fulfilled'     => null,
				'created_by'       => (string) $user_id,
				'transaction_type' => null,
			);

			GFCommon::log_debug( __METHOD__ . "(): Entry record created in the database. ID: {$lead_id}." );
		} else {
			GFCommon::log_debug( __METHOD__ . "(): Updating existing entry. ID: {$entry['id']}." );

			// Ensures the entry being updated contains all the current properties and registered meta.
			self::add_properties_to_entry( $entry );
			self::add_meta_to_entry( $entry );

			GFAPI::update_entry_property( $entry['id'], 'date_updated', $current_date );
			$entry['date_updated'] = $current_date;
		}

		$current_fields = $wpdb->get_results( $wpdb->prepare( "SELECT id, meta_key, item_index FROM $entry_meta_table WHERE entry_id=%d", $entry['id'] ) );
		$total_fields = array();
		/* @var $calculation_fields GF_Field[] */
		$calculation_fields = array();
		$recalculate_total  = false;

		GFCommon::log_debug( __METHOD__ . '(): Saving entry fields.' );

		GFFormsModel::begin_batch_field_operations();

		foreach ( $form['fields'] as $field ) {
			/* @var $field GF_Field */

			// ignore the honeypot field
			if ( $field->type == 'honeypot' ) {
				continue;
			}

			//Ignore fields that are marked as display only
			if ( $field->displayOnly ) {
				continue;
			}

			// Ignore pricing fields in the entry detail
			if ( $is_entry_detail && GFCommon::is_pricing_field( $field->type ) ) {
				continue;
			}


			// Process total field after all fields have been saved
			if ( $field->type == 'total' ) {
				$total_fields[] = $field;
				continue;
			}

			/**
			 * Specify whether to fetch values from the $_POST when evaluating a field's conditional logic. Defaults to true
			 * for new entries and false for existing entries.
			 *
			 * @since 2.3.1.11
			 *
			 * @param bool  $read_value_from_post Should value be fetched from $_POST?
			 * @param array $form                The current form object.
			 * @param array $entry               The current entry object.
			 */
			$read_value_from_post = gf_apply_filters( array( 'gform_use_post_value_for_conditional_logic_save_entry', $form['id'] ), $is_new_lead || ! isset( $entry[ 'date_created' ] ), $form, $entry );

			// Only save fields that are not hidden (except when updating an entry)
			if ( $is_entry_detail || ! GFFormsModel::is_field_hidden( $form, $field, array(), $read_value_from_post ? null : $entry ) ) {

				// process calculation fields after all fields have been saved (moved after the is hidden check)
				if ( $field->has_calculation() ) {
					$calculation_fields[] = $field;
					continue;
				}

				if ( $field->type == 'post_category' ) {
					$field = GFCommon::add_categories_as_choices( $field, '' );
				}

				$inputs = $field->get_entry_inputs();
				if ( is_array( $inputs ) ) {
					foreach ( $inputs as $input ) {
						self::save_input( $form, $field, $entry, $current_fields, $input['id'] );
					}
				} else {
					self::save_input( $form, $field, $entry, $current_fields, $field->id );
				}
			}
		}

		$results = GFFormsModel::commit_batch_field_operations();

		if ( $is_new_lead && is_wp_error( $results['inserts'] ) ) {
			/* @var WP_Error $error */
			$error = $results['inserts'];
			GFCommon::log_error( __METHOD__ . '(): Error while saving field values for new entry. ' . $error->get_error_message() );
			self::add_note( $entry['id'], 0, 'Gravity Forms', sprintf( esc_html__( 'Error while saving field values: %s', 'gravityforms' ), $error->get_error_message() ), 'save_entry', 'error' );
			wp_die( $die_message );
		}

		if ( ! empty( $calculation_fields ) ) {
			GFFormsModel::begin_batch_field_operations();
			foreach ( $calculation_fields as $calculation_field ) {
				$inputs = $calculation_field->get_entry_inputs();
				if ( is_array( $inputs ) ) {
					foreach ( $inputs as $input ) {
						self::save_input( $form, $calculation_field, $entry, $current_fields, $input['id'] );
					}
				} else {
					self::save_input( $form, $calculation_field, $entry, $current_fields, $calculation_field->id );
				}
			}
			$results = GFFormsModel::commit_batch_field_operations();

			if ( $is_new_lead && is_wp_error( $results['inserts'] ) ) {
				/* @var WP_Error $error */
				$error = $results['inserts'];
				GFCommon::log_error( __METHOD__ . '(): Error while saving calculation field values for new entry. ' . $error->get_error_message() );
				self::add_note( $entry['id'], 0, 'Gravity Forms', sprintf( esc_html__( 'Error while saving calculation field values: %s', 'gravityforms' ), $error->get_error_message() ), 'save_entry', 'error' );
				wp_die( $die_message );
			}

			self::refresh_product_cache( $form, $entry );
		}

		//saving total field as the last field of the form.
		if ( ! empty( $total_fields ) ) {
			GFFormsModel::begin_batch_field_operations();
			foreach ( $total_fields as $total_field ) {
				self::save_input( $form, $total_field, $entry, $current_fields, $total_field->id );
			}
			$results = GFFormsModel::commit_batch_field_operations();

			if ( $is_new_lead && is_wp_error( $results['inserts'] ) ) {
				/* @var WP_Error $error */
				$error = $results['inserts'];
				GFCommon::log_error( __METHOD__ . '(): Error while saving total field values for new entry. ' . $error->get_error_message() );
				self::add_note( $entry['id'], 0, 'Gravity Forms', sprintf( esc_html__( 'Error while saving total field values: %s', 'gravityforms' ), $error->get_error_message() ), 'save_entry', 'error' );
				wp_die( $die_message );
			}
		}

		$processor = self::get_entry_meta_batch_processor();
		$processor::begin_batch_entry_meta_operations();

		foreach ( $form['fields'] as $field ) {
			/* @var GF_Field $field */

			if ( $field->displayOnly ) {
				continue;
			}

			$inputs = $field->get_entry_inputs();

			if ( is_array( $inputs ) ) {
				foreach ( $inputs as $input ) {
					$entry[ (string) $input['id'] ] = gf_apply_filters( array( 'gform_get_input_value', $form['id'], $field->id, $input['id'] ), rgar( $entry, (string) $input['id'] ), $entry, $field, $input['id'] );
				}
			} else {

				$value = rgar( $entry, (string) $field->id );

				if ( GFFormsModel::is_openssl_encrypted_field( $entry['id'], $field->id ) ) {
					$value = GFCommon::openssl_decrypt( $value );
				}

				$entry[ (string) $field->id ] = gf_apply_filters( array( 'gform_get_input_value', $form['id'], $field->id ), $value, $entry, $field, '' );

			}

			self::save_extra_field_meta( $field, $form, $entry );

		}

		$processor::commit_batch_entry_meta_operations();

		self::hydrate_repeaters( $entry, $form );

		GFCommon::log_debug( __METHOD__ . '(): Finished saving entry fields.' );
		GFCommon::log_debug( __METHOD__ . sprintf( '(): %s entry completed in %F seconds.', $is_new_lead ? 'Saving' : 'Updating', GFCommon::timer_end( __METHOD__ ) ) );
	}


	/**
	 * Gets the extra meta data a field wants to save to the entry and updates the entry meta with the retrieved data.
	 *
	 * This method depends on batch operations, so begin_batch_entry_meta_operations should be called before this method is called
	 * and commit_batch_entry_meta_operations should be called after.
	 *
	 * @since 2.5.16
	 *
	 * @param $field
	 * @param $form
	 * @param $entry
	 */
	public static function save_extra_field_meta( $field, $form, $entry ) {

		$extra_meta = $field->get_extra_entry_metadata( $form, $entry );
		foreach( $extra_meta as $key => $value ) {
			$processor = self::get_entry_meta_batch_processor();
			$processor::queue_batch_entry_meta_operation( $form, $entry, $key, $value );
		}
	}

	/**
	 * Creates a new instance of the entry meta batch operations handler if required and returns it.
	 *
	 * @since 2.5.16
	 *
	 * @return GF_Entry_Meta_Batch_Processor
	 */
	public static function get_entry_meta_batch_processor() {
		if ( is_null( self::$entry_meta_batch_processor ) ) {
			$processor = GFForms::get_service_container()->get( GF_Batch_Operations_Service_Provider::ENTRY_META_BATCH_PROCESSOR );
			self::$entry_meta_batch_processor = $processor;
		}

		return self::$entry_meta_batch_processor;
	}

	/**
	 * Populates the supplied entry with missing properties.
	 *
	 * @since 2.4.5.8
	 *
	 * @param array $entry The partial or complete entry currently being updated.
	 */
	public static function add_properties_to_entry( &$entry ) {
		if ( empty( $entry['id'] ) ) {
			return;
		}

		global $wpdb;
		$entry_table = GFFormsModel::get_entry_table_name();
		$sql         = $wpdb->prepare( "SELECT * FROM $entry_table WHERE id=%d", $entry['id'] );
		$properties  = $wpdb->get_row( $sql, ARRAY_A );

		foreach ( $properties as $key => $property ) {
			if ( ! isset( $entry[ (string) $key ] ) ) {
				// Add the missing entry property.
				$entry[ (string) $key ] = $properties[ $key ];
			}
		}
	}

	/**
	 * Populates the supplied entry with missing meta.
	 *
	 * @since 2.4.5.8
	 *
	 * @param array $entry The partial or complete entry currently being updated.
	 */
	public static function add_meta_to_entry( &$entry ) {
		if ( empty( $entry['id'] ) || empty( $entry['form_id'] ) ) {
			return;
		}

		$meta_keys = array_keys( self::get_entry_meta( $entry['form_id'] ) );

		foreach ( $meta_keys as $meta_key ) {
			if ( ! isset( $entry[ $meta_key ] ) ) {
				// Add the missing entry meta.
				$entry[ $meta_key ] = gform_get_meta( $entry['id'], $meta_key );
			}
		}
	}

	/**
	 * Fill the repeater field with the entry data.
	 *
	 * @since Unknown.
	 * @since 2.5 Added the $apply_filters parameter.
	 *
	 * @param       $entry
	 * @param       $form
	 * @param bool  $apply_filters Whether to apply the filter_input_value filter to the entry.
	 */
	public static function hydrate_repeaters( &$entry, $form, $apply_filters = false ) {
		$fields = $form['fields'];
		foreach ( $fields as $field ) {
			if ( $field instanceof GF_Field_Repeater && isset( $field->fields ) && is_array( $field->fields ) ) {
				/* @var GF_Field_Repeater $field */
				$entry = $field->hydrate( $entry, $form );
			}
		}
	}

	public static function create_lead( $form ) {
		GFCommon::timer_start( __METHOD__ );
		$form_id = absint( rgar( $form, 'id' ) );

		global $current_user;

		$total_fields       = array();
		$calculation_fields = array();

		$lead                 = array();
		$lead['id']           = null;
		$lead['post_id']      = null;
		$lead['date_created'] = null;
		$lead['date_updated'] = null;
		$lead['form_id']      = rgar( $form, 'id' );
		$lead['ip']           = rgars( $form, 'personalData/preventIP' ) ? '' : self::get_ip();
		$source_url           = self::truncate( self::get_current_page_url(), 200 );
		$lead['source_url']   = esc_url_raw( $source_url );
		$user_agent           = self::truncate( rgar( $_SERVER, 'HTTP_USER_AGENT' ), 250 );
		$lead['user_agent']   = sanitize_text_field( $user_agent );
		$lead['created_by']   = $current_user && $current_user->ID ? $current_user->ID : 'NULL';

		/**
		 * Allow the currency code to be overridden.
		 *
		 * @param string $currency The three character ISO currency code to be stored in the entry. Default is value returned by GFCommon::get_currency()
		 * @param array $form The form currently being processed.
		 *
		 */
		$lead['currency'] = gf_apply_filters( array( 'gform_currency_pre_save_entry', $form_id ), GFCommon::get_currency(), $form );

		foreach ( $form['fields'] as $field ) {
			/* @var $field GF_Field */

			// ignore fields that are marked as display only
			if ( $field->displayOnly ) {
				continue;
			}

			// process total field after all fields have been saved
			if ( $field->type == 'total' ) {
				$total_fields[] = $field;
				continue;
			}

			// process calculation fields after all fields have been saved
			if ( $field->has_calculation() ) {
				$calculation_fields[] = $field;
				continue;
			}

			// only save fields that are not hidden
			if ( ! RGFormsModel::is_field_hidden( $form, $field, array() ) ) {

				if ( $field->type == 'post_category' ) {
					$field = GFCommon::add_categories_as_choices( $field, '' );
				}

				$inputs = $field->get_entry_inputs();

				if ( is_array( $inputs ) ) {
					foreach ( $inputs as $input ) {
						$lead[ (string) $input['id'] ] = self::get_prepared_input_value( $form, $field, $lead, $input['id'] );
					}
				} else {
					$lead[ $field->id ] = self::get_prepared_input_value( $form, $field, $lead, $field->id );
				}
			}
		}

		if ( ! empty( $calculation_fields ) ) {
			foreach ( $calculation_fields as $field ) {
				/* @var $field GF_Field */

				// only save fields that are not hidden
				if ( RGFormsModel::is_field_hidden( $form, $field, array() ) ) {
					continue;
				}

				$inputs = $field->get_entry_inputs();

				if ( is_array( $inputs ) ) {
					foreach ( $inputs as $input ) {
						$lead[ (string) $input['id'] ] = self::get_prepared_input_value( $form, $field, $lead, $input['id'] );
					}
				} else {
					$lead[ $field->id ] = self::get_prepared_input_value( $form, $field, $lead, $field->id );
				}
			}
			self::refresh_product_cache( $form, $lead );
		}

		// saving total field as the last field of the form.
		if ( ! empty( $total_fields ) ) {
			foreach ( $total_fields as $total_field ) {
				$lead[ $total_field->id ] = self::get_prepared_input_value( $form, $total_field, $lead, $total_field->id );
			}
		}

		GFCommon::log_debug( __METHOD__ . sprintf( '(): Draft entry created for form (#%d) in %F seconds.', $form_id, GFCommon::timer_end( __METHOD__ ) ) );

		return $lead;
	}

	public static function get_prepared_input_value( $form, $field, $lead, $input_id ) {

		$input_name = 'input_' . str_replace( '.', '_', $input_id );
		if ( $field->enableCopyValuesOption && rgpost( 'input_' . $field->id . '_copy_values_activated' ) ) {
			$source_field_id   = $field->copyValuesOptionField;
			$source_input_name = str_replace( 'input_' . $field->id, 'input_' . $source_field_id, $input_name );
			$value             = rgpost( $source_input_name );
		} else {
			$value = rgpost( $input_name );
		}

		$value = self::maybe_trim_input( $value, $form['id'], $field );

		$is_form_editor = GFCommon::is_form_editor();
		$is_entry_detail = GFCommon::is_entry_detail();
		$is_admin = $is_form_editor || $is_entry_detail;

		if ( empty( $value ) && $field->is_administrative() && ! $is_admin ) {
			$value = self::get_default_value( $field, $input_id );
		}

		switch ( self::get_input_type( $field ) ) {

			case 'post_image':
				$file_info = self::get_temp_filename( $form['id'], $input_name );
				if ( ! empty( $file_info ) ) {
					$file_path = self::get_file_upload_path( $form['id'], $file_info['uploaded_filename'] );
					$url       = $file_path['url'];

					$image_alt         = isset( $_POST[ "{$input_name}_2" ] ) ? strip_tags( $_POST[ "{$input_name}_2" ] ) : '';
					$image_title       = isset( $_POST[ "{$input_name}_1" ] ) ? strip_tags( $_POST[ "{$input_name}_1" ] ) : '';
					$image_caption     = isset( $_POST[ "{$input_name}_4" ] ) ? strip_tags( $_POST[ "{$input_name}_4" ] ) : '';
					$image_description = isset( $_POST[ "{$input_name}_7" ] ) ? strip_tags( $_POST[ "{$input_name}_7" ] ) : '';

					$value = ! empty( $url ) ? $url . '|:|' . $image_title . '|:|' . $image_caption . '|:|' . $image_description . '|:|' . $image_alt : '';
				}
				break;

			case 'fileupload' :
				if ( $field->multipleFiles ) {
					if ( ! empty( $value ) ) {
						$value = json_encode( $value );
					}
				} else {
					$file_info = self::get_temp_filename( $form['id'], $input_name );
					if ( ! empty( $file_info ) ) {
						$file_path = self::get_file_upload_path( $form['id'], $file_info['uploaded_filename'] );
						$value     = $file_path['url'];
					}
				}

				break;

			default:

				// processing values so that they are in the correct format for each input type
				$value = self::prepare_value( $form, $field, $value, $input_name, rgar( $lead, 'id' ), $lead );

		}

		return gf_apply_filters( array( 'gform_save_field_value', $form['id'], $field->id ), $value, $lead, $field, $form, $input_id );
	}

	public static function refresh_product_cache( $form, $lead, $use_choice_text = false, $use_admin_label = false ) {

		$cache_options = array(
			array( false, false ),
			array( false, true ),
			array( true, false ),
			array( true, true ),
		);

		foreach ( $cache_options as $cache_option ) {
			list( $use_choice_text, $use_admin_label ) = $cache_option;
			if ( gform_get_meta( rgar( $lead, 'id' ), "gform_product_info_{$use_choice_text}_{$use_admin_label}" ) ) {
				gform_delete_meta( rgar( $lead, 'id' ), "gform_product_info_{$use_choice_text}_{$use_admin_label}" );
				GFCommon::get_product_fields( $form, $lead, $use_choice_text, $use_admin_label );
			}
		}

	}

	/**
	 * Check whether a field is hidden via conditional logic.
	 *
	 * @param array    $form         Form object.
	 * @param GF_Field $field        Field object.
	 * @param array    $field_values Default field values for this form. Used when form has not yet been submitted. Pass an array if no default field values are available/required.
	 * @param array    $lead         Optional, default is null. If lead object is available, pass the lead.
	 *
	 * @return mixed
	 */
	public static function is_field_hidden( $form, $field, $field_values, $lead = null ) {

		if ( empty( $field ) ) {
			return false;
		}

		$cache_key = 'GFFormsModel::is_field_hidden_' . $form['id'] . '_' . $field->id;

		if ( ! empty( $lead ) && isset( $lead['id'] ) ) {
			// Make sure that we cache field visiblity on a per-entry basis
			// https://github.com/gravityview/GravityView/issues/1307
			$cache_key = $cache_key . '_' . $lead['id'];
		}

		$display   = GFCache::get( $cache_key, $is_hit, false );
		if ( $display !== false ) {
			return $display;
		}

		$section         = self::get_section( $form, $field->id );
		$section_display = self::get_field_display( $form, $section, $field_values, $lead );

		//if section is hidden, hide field no matter what. if section is visible, see if field is supposed to be visible
		if ( $section_display == 'hide' ) {
			$display = 'hide';
		} else if ( self::is_page_hidden( $form, $field->pageNumber, $field_values, $lead ) ) {
			$display = 'hide';
		} else {
			$display = self::get_field_display( $form, $field, $field_values, $lead );

			return $display == 'hide';
		}

		GFCache::set( $cache_key, $display );

		return $display == 'hide';
	}

	/***
	 * Determines if the submit button was supposed to be hidden by conditional logic. This function helps ensure that
	 *  the form doesn't get submitted when the submit button is hidden by conditional logic.
	 *
	 * @param $form The Form object
	 *
	 * @return bool Returns true if the submit button is hidden by conditional logic, false otherwise.
	 */
	public static function is_submit_button_hidden( $form ) {

		if( ! isset( $form['button']['conditionalLogic'] ) ){
			return false;
		}

		$is_visible = self::evaluate_conditional_logic( $form, $form['button']['conditionalLogic'], array() );

		return ! $is_visible;
	}

	public static function is_page_hidden( $form, $page_number, $field_values, $lead = null ) {
		$page = self::get_page_by_number( $form, $page_number );

		if ( ! $page ) {
			return false;
		}

		$display = self::get_field_display( $form, $page, $field_values, $lead );

		return $display == 'hide';
	}

	public static function get_page_by_number( $form, $page_number ) {
		foreach ( $form['fields'] as $field ) {
			if ( $field->type == 'page' && $field->pageNumber == $page_number ) {
				return $field;
			}
		}

		return null;
	}

	//gets the section that the specified field belongs to, or null if none
	public static function get_section( $form, $field_id ) {
		$current_section = null;
		foreach ( $form['fields'] as $field ) {
			if ( $field->type == 'section' ) {
				$current_section = $field;
			}

			//stop section at a page break (sections don't go cross page)
			if ( $field->type == 'page' ) {
				$current_section = null;
			}

			if ( $field->id == $field_id ) {
				return $current_section;
			}
		}

		return null;
	}

    /**
	 * Determines if the field value matches the conditional logic rule value.
	 *
	 * @param mixed         $field_value  The field value to be checked.
	 * @param mixed         $target_value The conditional logic rule value.
	 * @param string        $operation    The conditional logic rule operator.
	 * @param null|GF_Field $source_field The field the rule is based on.
	 * @param null|array    $rule         The conditional logic rule properties.
	 * @param null|array    $form         The current form.
	 *
	 * @return bool
	 */
	public static function is_value_match( $field_value, $target_value, $operation = 'is', $source_field = null, $rule = null, $form = null ) {

		$is_match = false;

		if ( $source_field && is_subclass_of( $source_field, 'GF_Field' ) ) {
			if ( $source_field->type == 'post_category' ) {
				$field_value = GFCommon::prepare_post_category_value( $field_value, $source_field, 'conditional_logic' );
			} elseif ( $source_field instanceof GF_Field_MultiSelect && ! empty( $field_value ) && ! is_array( $field_value ) ) {
				// Convert the comma-delimited string into an array.
				$field_value = $source_field->to_array( $field_value );
			} elseif ( $source_field->get_input_type() != 'checkbox' && is_array( $field_value ) && $source_field->id != $rule['fieldId'] && is_array( $source_field->get_entry_inputs() ) ) {
				// Get the specific input value from the full field value.
				$field_value = rgar( $field_value, $rule['fieldId'] );
			}
		}

		$form_id = $source_field instanceof GF_Field ? $source_field->formId : 0;

		$target_value = GFFormsModel::maybe_trim_input( $target_value, $form_id, $source_field );


		if ( is_array( $field_value ) ) {
			$field_value = array_values( $field_value ); // Returning array values, ignoring keys if array is associative.
			$match_count = 0;
			foreach ( $field_value as $val ) {
				$val = GFFormsModel::maybe_trim_input( GFCommon::get_selection_value( $val ), $form_id, $source_field );
				if ( self::matches_operation( $val, $target_value, $operation ) ) {
					$match_count ++;
				}
			}

			// If operation is Is Not, none of the values in the array can match the target value.
			// Except when operation is Is Not Empty. In that case, one non-empty value is enough
			$must_match_all = ( $operation == 'isnot' && ! rgblank( $target_value ) ) || ( $operation == 'is' && rgblank( $target_value ) );
			$is_match = $must_match_all ? $match_count == count( $field_value ) : $match_count > 0;

		} else if ( self::matches_operation( GFFormsModel::maybe_trim_input( GFCommon::get_selection_value( $field_value ), $form_id, $source_field ), $target_value, $operation ) ) {
			$is_match = true;
		}

		return apply_filters( 'gform_is_value_match', $is_match, $field_value, $target_value, $operation, $source_field, $rule );
	}

	private static function try_convert_float( $text ) {

		/*
		global $wp_locale;
		$number_format = $wp_locale->number_format['decimal_point'] == ',' ? 'decimal_comma' : 'decimal_dot';

		if ( is_numeric( $text ) && $number_format == 'decimal_comma' ) {
			return GFCommon::format_number( $text, 'decimal_comma' );
		} else if ( GFCommon::is_numeric( $text, $number_format ) ) {
			return GFCommon::clean_number( $text, $number_format );
		}
		*/

		$number_format = 'decimal_dot';

		if ( GFCommon::is_numeric( $text, 'currency' ) ) {
			$number_format = 'currency';
		}

		if ( GFCommon::is_numeric( $text, $number_format ) ) {
			return GFCommon::clean_number( $text, $number_format );
		}

		return 0;
	}

	public static function matches_operation( $val1, $val2, $operation ) {
		$val1 = ! rgblank( $val1 ) ? strtolower( $val1 ) : '';
		$val2 = ! rgblank( $val2 ) ? strtolower( $val2 ) : '';

		switch ( $operation ) {
			case 'is' :
				return $val1 == $val2;
				break;

			case 'isnot' :
				return $val1 != $val2;
				break;

			case 'greater_than':
			case '>' :
				$val1 = self::try_convert_float( $val1 );
				$val2 = self::try_convert_float( $val2 );

				return $val1 > $val2;
				break;

			case 'less_than':
			case '<' :
				$val1 = self::try_convert_float( $val1 );
				$val2 = self::try_convert_float( $val2 );

				return $val1 < $val2;
				break;

			case 'contains' :
				return ! rgblank( $val2 ) && strpos( $val1, $val2 ) !== false;
				break;

			case 'starts_with' :
				return ! rgblank( $val2 ) && strpos( $val1, $val2 ) === 0;
				break;

			case 'ends_with' :
				// If target value is a 0 set $val2 to 0 rather than the empty string it currently is to prevent false positives.
				if ( empty( $val2 ) ) {
					$val2 = '0';
				}

				$start = strlen( $val1 ) - strlen( $val2 );

				if ( $start < 0 ) {
					return false;
				}

				$tail = substr( $val1, $start );

				return $val2 == $tail;
				break;
		}


		return false;
	}

	/**
	 * @param          $form
	 * @param GF_Field $field
	 * @param          $field_values
	 * @param null     $lead
	 *
	 * @return string
	 */
	private static function get_field_display( $form, $field, $field_values, $lead = null ) {

		if ( empty( $field ) ) {
			return 'show';
		}

		$logic = $field->conditionalLogic;

		//if this field does not have any conditional logic associated with it, it won't be hidden
		if ( empty( $logic ) ) {
			return 'show';
		}

		$is_visible = self::evaluate_conditional_logic( $form, $logic, $field_values, $lead );

		return $is_visible ? 'show' : 'hide';
	}



	public static function get_custom_choices() {
		$choices = get_option( 'gform_custom_choices' );
		if ( ! $choices ) {
			$choices = array();
		}

		return $choices;
	}

	public static function delete_custom_choice( $name ) {
		$choices = self::get_custom_choices();
		if ( array_key_exists( $name, $choices ) ) {
			unset( $choices[ $name ] );
		}

		update_option( 'gform_custom_choices', $choices );
	}

	public static function save_custom_choice( $previous_name, $new_name, $choices ) {
		$all_choices = self::get_custom_choices();

		if ( array_key_exists( $previous_name, $all_choices ) ) {
			unset( $all_choices[ $previous_name ] );
		}

		$all_choices[ $new_name ] = $choices;

		update_option( 'gform_custom_choices', $all_choices );
	}


	/**
	 * Returns the value for a field.
	 *
	 * @param GF_Field $field
	 * @param array    $field_values
	 * @param bool     $get_from_post Whether to get the value from the $_POST array as opposed to $field_values
	 *
	 * @return array|mixed|string
	 */
	public static function get_field_value( &$field, $field_values = array(), $get_from_post = true ) {

		if ( ! $field instanceof GF_Field ) {
			$field = GF_Fields::create( $field );
		}

		if ( $field->type == 'post_category' ) {
			$field = GFCommon::add_categories_as_choices( $field, '' );
		}

		$value = $field->get_value_submission( $field_values, $get_from_post );

		if ( $field->get_input_type() == 'list' && $field->enableColumns && $get_from_post && rgpost( 'is_submit_' . $field->formId ) ) {
			/** @var GF_Field_List $field */
			$value = $field->create_list_array_recursive( $value );
		}

		return $value;
	}

	/**
	 * @deprecated 2.4
	 *
	 * @param int $expiration_days
	 *
	 * @return false|int
	 */
	public static function purge_expired_incomplete_submissions( $expiration_days = 30 ) {
		_deprecated_function( 'GFFormsModel::purge_expired_incomplete_submissions', '2.4', 'GFFormsModel::purge_expired_draft_submissions' );
		return self::purge_expired_draft_submissions( $expiration_days = 30 );
	}

	/**
	 * Purges expired draft submissions.
	 *
	 * @since 2.4
	 *
	 * @param int $expiration_days
	 *
	 * @return false|int
	 */
	public static function purge_expired_draft_submissions( $expiration_days = 30 ) {
		global $wpdb;

		/**
		 * Overrides the number of days until draft submissions are purged.
		 *
		 * @since 1.9
		 *
		 * @param int $expiration_days The number of days until expiration. Defaults to 30.
		 */
		$expiration_days = apply_filters( 'gform_incomplete_submissions_expiration_days', $expiration_days );
		$expiration_date = gmdate( 'Y-m-d H:i:s', time() - ( $expiration_days * 24 * 60 * 60 ) );

		$table  = version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ? self::get_incomplete_submissions_table_name() : self::get_draft_submissions_table_name();

		$query = array(
			'delete' => 'DELETE',
			'from'   => sprintf( 'FROM %s', $table ),
			'where'  => $wpdb->prepare( 'WHERE date_created < %s', $expiration_date ),
		);

		/**
		 * Allows the query used to purge expired draft (save and continue) submissions to be overridden.
		 *
		 * @since 2.1.1.20
		 *
		 * @param array $query The delete, from, and where arguments to be used when the query is performed.
		 */
		$query = apply_filters( 'gform_purge_expired_incomplete_submissions_query', $query );

		$result = $wpdb->query( implode( "\n", $query ) );
		return $result;
	}

	/**
	 *
	 * @deprecated 2.4
	 *
	 * @param $token
	 *
	 * @return false|int
	 */
	public static function delete_incomplete_submission( $token ) {
		_deprecated_function( 'GFFormsModel::delete_incomplete_submission', '2.4', 'GFFormsModel::delete_draft_submission' );
		return self::delete_draft_submission( $token );
	}

	/**
	 * Deletes a draft submission.
	 *
	 * @since 2.4
	 *
	 * @param $token
	 *
	 * @return false|int
	 */
	public static function delete_draft_submission( $token ) {
		global $wpdb;

		$table  = version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ? self::get_incomplete_submissions_table_name() : self::get_draft_submissions_table_name();
		$result = $wpdb->query( $wpdb->prepare( "DELETE FROM $table WHERE uuid = %s", $token ) );

		return $result;
	}

	/**
	 *
	 * @deprecated 2.4
	 *
	 * @param        $form
	 * @param        $entry
	 * @param        $field_values
	 * @param        $page_number
	 * @param        $files
	 * @param        $form_unique_id
	 * @param        $ip
	 * @param        $source_url
	 * @param string $resume_token
	 *
	 * @return bool|false|int|string
	 */
	public static function save_incomplete_submission( $form, $entry, $field_values, $page_number, $files, $form_unique_id, $ip, $source_url, $resume_token = '' ) {
		_deprecated_function( 'GFFormsModel::save_incomplete_submission', '2.4', 'GFFormsModel::save_draft_submission' );
		return self::save_draft_submission( $form, $entry, $field_values, $page_number, $files, $form_unique_id, $ip, $source_url, $resume_token );
	}

	/**
	 * Saves the draft submission.
	 *
	 * @since 2.4
	 *
	 * @param        $form
	 * @param        $entry
	 * @param        $field_values
	 * @param        $page_number
	 * @param        $files
	 * @param        $form_unique_id
	 * @param        $ip
	 * @param        $source_url
	 * @param string $resume_token
	 *
	 * @return bool|false|int|string
	 */
	public static function save_draft_submission( $form, $entry, $field_values, $page_number, $files, $form_unique_id, $ip, $source_url, $resume_token = '' ) {
		if ( ! is_array( $form['fields'] ) ) {
			return;
		}
		global $wpdb;

		$table  = version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ? self::get_incomplete_submissions_table_name() : self::get_draft_submissions_table_name();

		$submitted_values = array();
		foreach ( $form['fields'] as $field ) {
			/* @var GF_Field $field */
			if ( $field->type == 'creditcard' ) {
				continue;
			}

			$submitted_values[ $field->id ] = RGFormsModel::get_field_value( $field, $field_values );
		}

		/**
		 * Allows the modification of submitted values before the draft submission is saved.
		 *
		 * @since 1.9
		 *
		 * @param array $submitted_values The submitted values
		 * @param array $form             The Form object
		 */
		$submitted_values = apply_filters( 'gform_submission_values_pre_save', $submitted_values, $form );

		$submission['submitted_values'] = $submitted_values;
		$submission['partial_entry']    = $entry;
		$submission['field_values']     = $field_values;
		$submission['page_number']      = $page_number;
		$submission['files']            = $files;
		$submission['gform_unique_id']  = $form_unique_id;

		// Issue a new token if no longer valid
		if ( ! empty( $resume_token ) ) {
			$sql = $wpdb->prepare("SELECT COUNT(*) FROM {$table} WHERE uuid = %s", $resume_token );
			$count = $wpdb->get_var( $sql );
			if ( $count != 1 ) {
				$resume_token = false;
			}
		}

		$is_new = empty( $resume_token );

		if ( $is_new ) {
			$resume_token = self::get_uuid();
		}

		$submission_json = json_encode( $submission );

		$submission_json = self::filter_draft_submission_pre_save( $submission_json, $resume_token, $form );

		if ( $is_new ) {
			$result = $wpdb->insert(
				$table,
				array(
					'uuid'         => $resume_token,
					'form_id'      => $form['id'],
					'date_created' => current_time( 'mysql', true ),
					'submission'   => $submission_json,
					'ip'           => $ip,
					'source_url'   => $source_url,
				),
				array(
					'%s',
					'%d',
					'%s',
					'%s',
					'%s',
					'%s',
				)
			);
		} else {
			$result = $wpdb->update(
				$table,
				array(
					'form_id'      => $form['id'],
					'date_created' => current_time( 'mysql', true ),
					'submission'   => $submission_json,
					'ip'           => $ip,
					'source_url'   => $source_url,
				),
				array( 'uuid' => $resume_token ),
				array(
					'%d',
					'%s',
					'%s',
					'%s',
					'%s',
				),
				array( '%s' )
			);
		}

		/**
		 * Fires after an draft submission is saved
		 *
		 * @since 1.9
		 *
		 * @param array  $submission   Contains the partially submitted entry, fields, values, and files.
		 * @param string $resume_token The unique resume token that was generated for this partial submission
		 * @param array  $form         The Form object
		 * @param array  $entry        The Entry object
		 */
		do_action( 'gform_incomplete_submission_post_save', $submission, $resume_token, $form, $entry );

		return $result ? $resume_token : $result;
	}

	/**
	 * Filters the submission json string before saving.
	 *
	 * @since 2.4
	 *
	 * @param $submission_json
	 * @param $resume_token
	 * @param $form
	 *
	 * @return string
	 */
	private static function filter_draft_submission_pre_save( $submission_json, $resume_token, $form ) {
		/**
		 * Allows the draft submission to be overridden before it is saved to the database.
		 *
		 * @since 2.3.3.1
		 *
		 * @param string $submission_json {
		 *    JSON encoded associative array containing this incomplete submission.
		 *
		 *    @type array      $submitted_values The submitted values.
		 *    @type array      $partial_entry    The draft entry created from the submitted values.
		 *    @type null|array $field_values     The dynamic population field values.
		 *    @type int        $page_number      The forms current page number.
		 *    @type array      $files            The uploaded file properties.
		 *    @type string     $gform_unique_id  The unique id for this submission.
		 * }
		 * @param string $resume_token The unique token which can be used to resume this incomplete submission at a later date/time.
		 * @param array  $form         The form which this incomplete submission was created for.
		 */
		$submission_json = apply_filters( 'gform_incomplete_submission_pre_save', $submission_json, $resume_token, $form );

		return $submission_json;
	}

	/**
	 * Updates a draft submission.
	 *
	 * @since 2.4
	 *
	 * @param string $resume_token The uuid of the draft submission to be updated.
	 * @param array  $form
	 * @param string $date_created
	 * @param string $ip
	 * @param string $source_url
	 * @param string $submission_json
	 *
	 * @return bool|false|int|string
	 */
	public static function update_draft_submission( $resume_token, $form, $date_created, $ip, $source_url, $submission_json ) {
		global $wpdb;

		$form_id = $form['id'];

		$submission_json = self::filter_draft_submission_pre_save( $submission_json, $resume_token, $form );

		$table = version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ? self::get_incomplete_submissions_table_name() : self::get_draft_submissions_table_name();

		$result = $wpdb->update(
			$table,
			array(
				'form_id'      => $form_id,
				'date_created' => $date_created,
				'submission'   => $submission_json,
				'ip'           => $ip,
				'source_url'   => $source_url,
			),
			array( 'uuid' => $resume_token ),
			array(
				'%d',
				'%s',
				'%s',
				'%s',
				'%s',
			),
			array( '%s' )
		);

		return $result ? $resume_token : $result;
	}

	/**
	 * Returns a UUID. Uses openssl_random_pseudo_bytes() if available and falls back to mt_rand().
	 *
	 * source: http://stackoverflow.com/questions/2040240/php-function-to-generate-v4-uuid
	 *
	 * @param string $s The separator e.g. '-'
	 *
	 * @return string
	 */
	public static function get_uuid( $s = '' ) {

		if ( function_exists( 'openssl_random_pseudo_bytes' ) ) { // PHP 5 >= 5.3.0
			$data = openssl_random_pseudo_bytes( 16 );

			$data[6] = chr( ord( $data[6] ) & 0x0f | 0x40 ); // set version to 0100
			$data[8] = chr( ord( $data[8] ) & 0x3f | 0x80 ); // set bits 6-7 to 10

			return vsprintf( "%s%s{$s}%s{$s}%s{$s}%s{$s}%s%s%s", str_split( bin2hex( $data ), 4 ) );
		} else {
			return sprintf(
				"%04x%04x{$s}%04x{$s}%04x{$s}%04x{$s}%04x%04x%04x",
				// 32 bits for "time_low"
				mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ),

				// 16 bits for 'time_mid'
				mt_rand( 0, 0xffff ),

				// 16 bits for 'time_hi_and_version',
				// four most significant bits holds version number 4
				mt_rand( 0, 0x0fff ) | 0x4000,

				// 16 bits, 8 bits for 'clk_seq_hi_res',
				// 8 bits for 'clk_seq_low',
				// two most significant bits holds zero and one for variant DCE1.1
				mt_rand( 0, 0x3fff ) | 0x8000,

				// 48 bits for 'node'
				mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff )
			);
		}

	}

	/**
	 * @deprecated 2.4
	 *
	 * @param $resume_token
	 *
	 * @return array|null|object
	 */
	public static function get_incomplete_submission_values( $resume_token ) {
		_deprecated_function( 'GFFormsModel::get_incomplete_submission_values', '2.4', 'GFFormsModel::get_draft_submission_values' );
		return self::get_draft_submission_values( $resume_token );
	}

	/**
	 * Returns the values for the draft submission.
	 *
	 * @since 2.4
	 *
	 * @param $resume_token
	 *
	 * @return array|null|object
	 */
	public static function get_draft_submission_values( $resume_token ) {
		global $wpdb;

		self::purge_expired_draft_submissions();

		$table = version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ? self::get_incomplete_submissions_table_name() : self::get_draft_submissions_table_name();
		$sql   = $wpdb->prepare( "SELECT date_created, form_id, submission, source_url FROM {$table} WHERE uuid = %s", $resume_token );
		$row   = $wpdb->get_row( $sql, ARRAY_A );

		if ( ! empty( $row ) ) {
			$form = self::get_form_meta( $row['form_id'] );
			$row['submission'] = self::filter_draft_submission_post_get( $row['submission'], $resume_token, $form );
		}

		return $row;
	}

	/**
	 * Filters the draft submission after reading it from the database.
	 *
	 * @since 2.4
	 *
	 * @param $submission_json
	 * @param $resume_token
	 * @param $form
	 *
	 * @return string
	 */
	private static function filter_draft_submission_post_get( $submission_json, $resume_token, $form ) {

		/**
		 * Allows the draft submission to be overridden after it is retrieved from the database but before it used to populate the form.
		 *
		 * @since 2.3.3.1
		 *
		 * @param string $submission_json {
		 *    JSON encoded associative array containing the draft submission being resumed.
		 *
		 *    @type array      $submitted_values The submitted values.
		 *    @type array      $partial_entry    The draft entry created from the submitted values.
		 *    @type null|array $field_values     The dynamic population field values.
		 *    @type int        $page_number      The forms current page number.
		 *    @type array      $files            The uploaded file properties.
		 *    @type string     $gform_unique_id  The unique id for this submission.
		 * }
		 * @param string $resume_token The unique token which was used to resume this incomplete submission.
		 * @param array  $form         The form which this incomplete submission was created for.
		 */
		$submission_json = apply_filters( 'gform_incomplete_submission_post_get', $submission_json, $resume_token, $form );
		return $submission_json;
	}

	/**
	 *
	 * @deprecated 2.4
	 *
	 * @param $token
	 * @param $email
	 *
	 * @return false|int
	 */
	public static function add_email_to_incomplete_sumbmission( $token, $email ) {
		_deprecated_function( 'GFFormsModel::add_email_to_incomplete_sumbmission', '2.4', 'GFFormsModel::add_email_to_draft_sumbmission' );
		return self::add_email_to_draft_sumbmission( $token, $email );
	}

	/**
	 * Adds the email address to the draft submission.
	 *
	 * @since 2.4
	 *
	 * @param $token
	 * @param $email
	 *
	 * @return false|int
	 */
	public static function add_email_to_draft_sumbmission( $token, $email ) {
		global $wpdb;
		self::purge_expired_draft_submissions();

		$table  = version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ? self::get_incomplete_submissions_table_name() : self::get_draft_submissions_table_name();
		$sql    = $wpdb->prepare( "UPDATE $table SET email = %s WHERE uuid = %s", $email, $token );
		$result = $wpdb->query( $sql );

		return $result;
	}

	public static function maybe_trim_input( $value, $form_id, $field ) {

		if ( is_null( $value ) ) {
			return $value;
		}

		$trim_value = apply_filters( 'gform_trim_input_value', true, $form_id, $field );

		if ( $trim_value ) {
			$value = is_array( $value ) ? GFCommon::trim_deep( $value ) : trim( $value );
		}

		return $value;
	}

	public static function get_parameter_value( $name, $field_values, $field ) {
		$value = stripslashes_deep( rgget( $name ) );
		if ( rgblank( $value ) ) {
			$value = rgget( $name, $field_values );
		}

		// Converting list format
		if ( ! empty( $value ) && RGFormsModel::get_input_type( $field ) == 'list' ) {

			// Transforms this: col1|col2,col1b|col2b into this: col1,col2,col1b,col2b
			$column_count = is_array( $field->choices ) ? count( $field->choices ) : 0;

			$rows = is_array( $value ) ? $value : explode( ',', $value );

			if ( ! empty( $rows ) ) {
				$ary_rows = array();

				foreach ( $rows as $row ) {
					/**
					 * Allow modification of the delimiter used to parse List field URL parameters.
					 *
					 * @since 2.0.0
					 *
					 * @param string $delimiter    Defaults to '|';
					 * @param array  $field        GF_Field object for the current field.
					 * @param string $name         Name of the current dynamic population parameter.
					 * @param array  $field_values Array of values provided for pre-population into the form.
					 */
					$delimiter = apply_filters( 'gform_list_field_parameter_delimiter', '|', $field, $name, $field_values );
					$ary_rows  = array_merge( $ary_rows, rgexplode( $delimiter, $row, $column_count ) );
				}

				$value = $ary_rows;
			}
		}

		return gf_apply_filters( array( 'gform_field_value', $name ), $value, $field, $name );
	}

	public static function get_default_value( $field, $input_id ) {
		if ( ! is_array( $field->choices ) ) {
			// if entry is saved in separate inputs get requsted input's default value ($input_id = 2.1)
			// some fields (like Date, Time) do not save their values in separate inputs and are correctly filtered out by this condition ($input_id = 2)
			// other fields (like Email w/ Confirm-enabled) also do not save their values in separate inputs but *should be* processed as input-specific submissions ($input_id = 2)
			if ( is_array( $field->get_entry_inputs() ) || ( $field->get_input_type() == 'email' && is_array( $field->inputs ) ) ) {
				$input = RGFormsModel::get_input( $field, $input_id );
				return rgar( $input, 'defaultValue' );
			} else {
				$value = $field->get_value_default();
				if( ! IS_ADMIN ) {
					if( is_array( $value ) ) {
						foreach( $value as &$_value ) {
							$_value = GFCommon::replace_variables_prepopulate( $_value );
						}
					} else {
						$value = GFCommon::replace_variables_prepopulate( $value );
					}
				}
				return $value;
			}
		} else if ( $field->type == 'checkbox' ) {
			for ( $i = 0, $count = sizeof( $field->inputs ); $i < $count; $i ++ ) {
				$input  = $field->inputs[ $i ];
				$choice = $field->choices[ $i ];
				if ( $input['id'] == $input_id && rgar( $choice, 'isSelected' ) ) {
					return $choice['value'];
				}
			}

			return '';
		} else {
			foreach ( $field->choices as $choice ) {
				if ( rgar( $choice, 'isSelected' ) || $field->type == 'post_category' ) {
					return $choice['value'];
				}
			}

			return '';
		}

	}

	/**
	 * @param GF_Field $field
	 *
	 * @return string
	 */
	public static function get_input_type( $field ) {
		// TODO: Deprecate
		if ( ! $field instanceof GF_Field ) {
			return empty( $field['inputType'] ) ? $field['type'] : $field['inputType'];
		}

		return $field->get_input_type();
	}

	private static function get_post_field_value( $field, $lead ) {

		if ( is_array( $field->get_entry_inputs() ) ) {
			$value = array();
			foreach ( $field->inputs as $input ) {
				$val = isset( $lead[ strval( $input['id'] ) ] ) ? $lead[ strval( $input['id'] ) ] : '';
				if ( ! empty( $val ) ) {

					// replace commas in individual values to prevent individual value from being split into multiple values (checkboxes, multiselects)
					if ( $field->get_input_type() === 'checkbox' ) {
						$val = str_replace( ',', '&#44;', $val );
					}

					$value[] = $val;
				}
			}
			$value = implode( ',', $value );
		} else {
			$value = isset( $lead[ $field->id ] ) ? $lead[ $field->id ] : '';

			if ( ! empty( $value ) && $field->get_input_type() === 'multiselect' ) {
				$items = $field->to_array( $value );

				foreach ( $items as &$item ) {
					$item = str_replace( ',', '&#44;', $item );
				}

				$value = implode( ',', $items );
			}
		}

		return $value;
	}

	private static function get_post_fields( $form, $lead ) {

		$post_data                       = array();
		$post_data['post_custom_fields'] = array();
		$post_data['tags_input']         = array();
		$categories                      = array();
		$images                          = array();

		foreach ( $form['fields'] as $field ) {
			if ( self::is_field_hidden( $form, $field, array(), $lead ) ) {
				continue;
			}

			if ( $field->type == 'post_category' ) {
				$field = GFCommon::add_categories_as_choices( $field, '' );
			}

			$value = self::get_post_field_value( $field, $lead );

			switch ( $field->type ) {
				case 'post_title' :
				case 'post_excerpt' :
				case 'post_content' :
					// Prevent shortcodes from being parsed.
					$post_data[ $field->type ] = GFCommon::encode_shortcodes( $value );
					break;

				case 'post_tags' :
					$tags = explode( ',', $value );
					if ( is_array( $tags ) && sizeof( $tags ) > 0 ) {
						$post_data['tags_input'] = array_merge( $post_data['tags_input'], $tags );
					}
					break;

				case 'post_custom_field' :

					$type = self::get_input_type( $field );
					if ( 'fileupload' === $type && $field->multipleFiles ) {
						$value = json_decode( $value, true );
					}

					$meta_name = $field->postCustomFieldName;

					if ( ! isset( $post_data['post_custom_fields'][ $meta_name ] ) ) {
						$post_data['post_custom_fields'][ $meta_name ] = $value;
					} else if ( ! is_array( $post_data['post_custom_fields'][ $meta_name ] ) ) {
						$post_data['post_custom_fields'][ $meta_name ] = array( $post_data['post_custom_fields'][ $meta_name ], $value );
					} else {
						$post_data['post_custom_fields'][ $meta_name ][] = $value;
					}

					break;

				case 'post_category' :
					foreach ( explode( ',', $value ) as $cat_string ) {
						$cat_array = explode( ':', $cat_string );
						// the category id is the last item in the array, access it using end() in case the category name includes colons.
						array_push( $categories, end( $cat_array ) );
					}
					break;

				case 'post_image' :
					$ary         = ! empty( $value ) ? explode( '|:|', $value ) : array();
					$url         = count( $ary ) > 0 ? $ary[0] : '';
					$title       = count( $ary ) > 1 ? $ary[1] : '';
					$caption     = count( $ary ) > 2 ? $ary[2] : '';
					$description = count( $ary ) > 3 ? $ary[3] : '';
					$alt         = count( $ary ) > 4 ? $ary[4] : '';

					array_push( $images, array( 'field_id' => $field->id, 'url' => $url, 'title' => $title, 'description' => $description, 'caption' => $caption, 'alt' => $alt ) );
					break;
			}
		}

		$post_data['post_status']   = rgar( $form, 'postStatus' );
		$post_data['post_category'] = ! empty( $categories ) ? $categories : array( rgar( $form, 'postCategory' ) );
		$post_data['images']        = $images;

		//setting current user as author depending on settings
		$post_data['post_author'] = $form['useCurrentUserAsAuthor'] && ! empty( $lead['created_by'] ) ? $lead['created_by'] : rgar( $form, 'postAuthor' );

		return $post_data;
	}

	/**
	 * Retrieves the custom field names (meta keys) for the custom field select in the form editor.
	 *
	 * @since unknown
	 *
	 * @return array
	 */
	public static function get_custom_field_names() {
		$form_id = absint( rgget( 'id' ) );

		/**
		 * Allow the postmeta query which retrieves the custom field names (meta keys) to be disabled.
		 *
		 * @since 2.3.4.1
		 *
		 * @param bool $disable_query Indicates if the custom field names query should be disabled. Default is false.
		 */
		$disable_query = gf_apply_filters( array( 'gform_disable_custom_field_names_query', $form_id ), false );

		if ( $disable_query ) {
			return array();
		}

		global $wpdb;
		$sql = "SELECT DISTINCT meta_key
			FROM $wpdb->postmeta
			WHERE meta_key NOT BETWEEN '_' AND '_z'
			HAVING meta_key NOT LIKE %s
			ORDER BY meta_key";
		$keys = $wpdb->get_col( $wpdb->prepare( $sql, $wpdb->esc_like( '_' ) . '%' ) );

		return $keys;
	}

	public static function get_input_masks() {

		$masks = array(
			'US Phone'       => '(999) 999-9999',
			'US Phone + Ext' => '(999) 999-9999? x99999',
			'Date'           => '99/99/9999',
			'Tax ID'         => '99-9999999',
			'SSN'            => '999-99-9999',
			'Zip Code'       => '99999',
			'Full Zip Code'  => '99999?-9999',
		);

		return apply_filters( 'gform_input_masks', $masks );
	}

	private static function get_default_post_title() {
		global $wpdb;
		$title = 'Untitled';
		$count = 1;

		$titles = $wpdb->get_col( "SELECT post_title FROM $wpdb->posts WHERE post_title like '%Untitled%'" );
		$titles = array_values( $titles );
		while ( in_array( $title, $titles ) ) {
			$title = "Untitled_$count";
			$count ++;
		}

		return $title;
	}

	public static function prepare_date( $date_format, $value ) {
		$format    = empty( $date_format ) ? 'mdy' : $date_format;
		$date_info = GFCommon::parse_date( $value, $format );
		if ( ! empty( $date_info ) && ! GFCommon::is_empty_array( $date_info ) ) {
			$value = sprintf( '%s-%02d-%02d', $date_info['year'], $date_info['month'], $date_info['day'] );
		} else {
			$value = '';
		}

		return $value;
	}

	/**
	 * Prepare the value before saving it to the lead. For multi-input fields this will be called for each input.
	 *
	 * @param mixed    $form
	 * @param GF_Field $field
	 * @param mixed    $value
	 * @param mixed    $input_name
	 * @param mixed    $lead_id the current lead ID, used for fields that are processed after other fields have been saved (ie Total, Calculations)
	 * @param mixed    $lead    passed by the RGFormsModel::create_lead() method, lead ID is not available for leads created by this function
	 *
	 * @return mixed
	 */
	public static function prepare_value( $form, $field, $value, $input_name, $lead_id, $lead = array() ) {

		$value = $field->get_value_save_entry( $value, $form, $input_name, $lead_id, $lead );


		// special format for Post Category fields
		if ( $field->type == 'post_category' ) {
			$is_multiselect = $field->inputType === 'multiselect';
			$full_values    = array();

			if ( ! is_array( $value ) ) {
				$value = $is_multiselect ? $field->to_array( $value ) : explode( ',', $value );
			}

			foreach ( $value as $cat_id ) {
				$cat           = get_term( $cat_id, 'category' );
				$full_values[] = ! is_wp_error( $cat ) && is_object( $cat ) ? $cat->name . ':' . $cat_id : '';
			}

			$value = $is_multiselect ? $field->to_string( $full_values ) : implode( ',', $full_values );
		}

		//do not save price fields with blank price
		if ( $field->enablePrice ) {
			$ary   = explode( '|', $value );
			$label = count( $ary ) > 0 ? $ary[0] : '';
			$price = count( $ary ) > 1 ? $ary[1] : '';

			$is_empty = ( strlen( trim( $price ) ) <= 0 );
			if ( $is_empty ) {
				$value = '';
			}
		}

		return $value;
	}

	public static function is_checkbox_checked( $field_id, $field_label, $lead, $form ) {

		//looping through lead detail values trying to find an item identical to the column label. Mark with a tick if found.
		$lead_field_keys = array_keys( $lead );
		foreach ( $lead_field_keys as $input_id ) {
			//mark as a tick if input label (from form meta) is equal to submitted value (from lead)
			if ( is_numeric( $input_id ) && absint( $input_id ) == absint( $field_id ) ) {
				if ( $lead[ $input_id ] == $field_label ) {
					return $lead[ $input_id ];
				} else {
					$field = RGFormsModel::get_field( $form, $field_id );
					if ( $field->enableChoiceValue || $field->enablePrice ) {
						foreach ( $field->choices as $choice ) {
							if ( $choice['value'] == $lead[ $field_id ] ) {
								return $choice['value'];
							} else if ( $field->enablePrice ) {
								$ary   = explode( '|', $lead[ $field_id ] );
								$val   = count( $ary ) > 0 ? $ary[0] : '';
								$price = count( $ary ) > 1 ? $ary[1] : '';

								if ( $val == $choice['value'] ) {
									return $choice['value'];
								}
							}
						}
					}
				}
			}
		}

		return false;
	}

	public static function get_fileupload_value( $form_id, $input_name ) {
		_deprecated_function( 'GFFormsModel::get_fileupload_value', '1.9', 'GF_Field_Fileupload::get_fileupload_value' );
		global $_gf_uploaded_files;

		GFCommon::log_debug( 'GFFormsModel::get_fileupload_value(): Starting.' );

		if ( empty( $_gf_uploaded_files ) ) {
			GFCommon::log_debug( 'GFFormsModel::get_fileupload_value(): No files uploaded. Exiting.' );
			$_gf_uploaded_files = array();
		}


		if ( ! isset( $_gf_uploaded_files[ $input_name ] ) ) {

			//check if file has already been uploaded by previous step
			$file_info     = self::get_temp_filename( $form_id, $input_name );
			$temp_filepath = self::get_upload_path( $form_id ) . '/tmp/' . $file_info['temp_filename'];
			GFCommon::log_debug( 'GFFormsModel::get_fileupload_value(): Temp file path: ' . $temp_filepath );
			if ( $file_info && file_exists( $temp_filepath ) ) {
				GFCommon::log_debug( 'GFFormsModel::get_fileupload_value(): Moving temp file: ' . $temp_filepath );
				$_gf_uploaded_files[ $input_name ] = self::move_temp_file( $form_id, $file_info );
			} else if ( ! empty( $_FILES[ $input_name ]['name'] ) ) {
				GFCommon::log_debug( 'GFFormsModel::get_fileupload_value(): Uploading file: ' . $_FILES[ $input_name ]['name'] );
				$_gf_uploaded_files[ $input_name ] = self::upload_file( $form_id, $_FILES[ $input_name ] );
			}
		}

		return rgget( $input_name, $_gf_uploaded_files );
	}

	public static function get_form_unique_id( $form_id ) {
		$unique_id = '';
		if ( rgpost( 'gform_submit' ) == $form_id ) {
			$posted_uid = rgpost( 'gform_unique_id' );
			if ( false === empty( $posted_uid ) && ctype_alnum( $posted_uid )) {
				$unique_id = $posted_uid;
				self::$unique_ids[ $form_id ] = $unique_id;
			} elseif ( isset( self::$unique_ids[ $form_id ] ) ) {
				$unique_id = self::$unique_ids[ $form_id ];
			} else {
				$unique_id = uniqid();
				self::$unique_ids[ $form_id ] = $unique_id;
			}
		}

		return $unique_id;
	}

	public static function get_temp_filename( $form_id, $input_name ) {

		$uploaded_filename = ! empty( $_FILES[ $input_name ]['name'] ) && $_FILES[ $input_name ]['error'] === 0 ? $_FILES[ $input_name ]['name'] : '';

		if ( empty( $uploaded_filename ) && isset( self::$uploaded_files[ $form_id ] ) ) {
			$uploaded_filename = rgget( $input_name, self::$uploaded_files[ $form_id ] );
		}

		if ( empty( $uploaded_filename ) ) {
			return false;
		}

		$ext_check      = is_array( $uploaded_filename ) ? $uploaded_filename[0]['uploaded_filename'] : $uploaded_filename;
		$form_unique_id = self::get_form_unique_id( $form_id );
		$extension      = pathinfo( $ext_check, PATHINFO_EXTENSION );
		$temp_filename  = "{$form_unique_id}_{$input_name}.{$extension}";

		GFCommon::log_debug( __METHOD__ . '(): Uploaded filename is ' . $ext_check . ' and temporary filename is ' . $temp_filename );
		return array( 'uploaded_filename' => $uploaded_filename, 'temp_filename' => $temp_filename );

	}

	public static function get_choice_text( $field, $value, $input_id = 0 ) {
		if ( ! is_array( $field->choices ) ) {
			return $value;
		}

		foreach ( $field->choices as $choice ) {
			if ( is_array( $value ) && self::choice_value_match( $field, $choice, $value[ $input_id ] ) ) {
				return $choice['text'];
			} else if ( ! is_array( $value ) && self::choice_value_match( $field, $choice, $value ) ) {
				return $choice['text'];
			}
		}

		return is_array( $value ) ? '' : $value;
	}


	public static function choice_value_match( $field, $choice, $value ) {
		$choice_value = GFFormsModel::maybe_trim_input( $choice['value'], $field->formId, $field );
		$value        = GFFormsModel::maybe_trim_input( $value, $field->formId, $field );

		$allowed_html    = wp_kses_allowed_html( 'post' );
		$sanitized_value = wp_kses( $value, $allowed_html );

		if ( $choice_value == $value || $choice_value == $sanitized_value ) {
			return true;
		} else if ( $field->enablePrice ) {
			$ary = explode( '|', $value );

			$val           = count( $ary ) > 0 ? $ary[0] : '';
			$sanitized_val = wp_kses( $val, $allowed_html );

			$price = count( $ary ) > 1 ? $ary[1] : '';

			if ( $choice['value'] == $val || $choice['value'] == $sanitized_val ) {
				return true;
			}
		} // add support for prepopulating multiselects @alex
		else if ( RGFormsModel::get_input_type( $field ) == 'multiselect' ) {
			$values           = $field->to_array( $value );
			$sanitized_values = $field->to_array( $sanitized_value );

			if ( in_array( $choice_value, $values ) || in_array( $choice_value, $sanitized_values ) ) {
				return true;
			}
		}

		return false;
	}

	public static function choices_value_match( $field, $choices, $value ) {
		foreach ( $choices as $choice ) {
			if ( self::choice_value_match( $field, $choice, $value ) ) {
				return true;
			}
		}

		return false;
	}

	public static function create_post( $form, &$lead ) {
		GFCommon::timer_start( __METHOD__ );
		GFCommon::log_debug( 'GFFormsModel::create_post(): Starting.' );

		$has_post_field = false;
		foreach ( $form['fields'] as $field ) {
			$is_hidden = self::is_field_hidden( $form, $field, array(), $lead );
			if ( ! $is_hidden && in_array( $field->type, array( 'post_category', 'post_title', 'post_content', 'post_excerpt', 'post_tags', 'post_custom_field', 'post_image' ) ) ) {
				$has_post_field = true;
				break;
			}
		}

		//if this form does not have any post fields, don't create a post
		if ( ! $has_post_field ) {
			GFCommon::log_debug( "GFFormsModel::create_post(): Stopping. The form doesn't have any post fields." );

			return $lead;
		}

		//processing post fields
		GFCommon::log_debug( 'GFFormsModel::create_post(): Getting post fields.' );
		$post_data = self::get_post_fields( $form, $lead );

		//allowing users to change post fields before post gets created
		$post_data = gf_apply_filters( array( 'gform_post_data', $form['id'] ), $post_data, $form, $lead );

		//adding default title if none of the required post fields are in the form (will make sure wp_insert_post() inserts the post)
		if ( empty( $post_data['post_title'] ) && empty( $post_data['post_content'] ) && empty( $post_data['post_excerpt'] ) ) {
			$post_data['post_title'] = self::get_default_post_title();
		}

		// remove original post status and save it for later
		$post_status = $post_data['post_status'];

		// replace original post status with 'draft' so other plugins know this post is not fully populated yet
		$post_data['post_status'] = 'draft';

		// inserting post
		GFCommon::log_debug( 'GFFormsModel::create_post(): Inserting post via wp_insert_post().' );
		$post_id = wp_insert_post( $post_data, true );
		GFCommon::log_debug( 'GFFormsModel::create_post(): Result from wp_insert_post(): ' . print_r( $post_id, 1 ) );

		if ( is_wp_error( $post_id ) ) {
			GFCommon::log_debug( __METHOD__ . '(): $post_data => ' . print_r( $post_data, 1 ) );

			return false;
		}

		// Add the post id to the entry so it is available during merge tag replacement.
		$lead['post_id'] = $post_id;

		//adding form id and entry id hidden custom fields
		add_post_meta( $post_id, '_gform-form-id', $form['id'] );
		add_post_meta( $post_id, '_gform-entry-id', $lead['id'] );

		$post_images = array();
		if ( ! empty( $post_data['images'] ) ) {
			// Creating post images.
			GFCommon::log_debug( 'GFFormsModel::create_post(): Processing post images.' );

			foreach ( $post_data['images'] as $image ) {
				if ( empty( $image['url'] ) ) {
					GFCommon::log_debug( __METHOD__ . '(): No image to process for field #' . $image['field_id'] );
					continue;
				}

				$image_meta = array(
					'post_excerpt' => $image['caption'],
					'post_content' => $image['description'],
				);

				// Adding title only if it is not empty. It will default to the file name if it is not in the array.
				if ( ! empty( $image['title'] ) ) {
					$image_meta['post_title'] = $image['title'];
				}

				GFCommon::log_debug( sprintf( '%s(): Field #%s. URL: %s', __METHOD__, $image['field_id'], $image['url'] ) );
				$media_id = self::media_handle_upload( $image['url'], $post_id, $image_meta );

				if ( $media_id ) {

					// Save media id for post body/title template variable replacement (below).
					$post_images[ $image['field_id'] ] = $media_id;
					$lead[ $image['field_id'] ] .= "|:|$media_id";

					// Update the alt text.
					update_post_meta( $media_id, '_wp_attachment_image_alt', $image['alt'] );

					// Setting the featured image.
					$field = RGFormsModel::get_field( $form, $image['field_id'] );
					if ( $field->postFeaturedImage ) {
						$result = set_post_thumbnail( $post_id, $media_id );
						GFCommon::log_debug( __METHOD__ . '(): Setting the featured image. Result from set_post_thumbnail(): ' . var_export( $result, 1 ) );
					}
				}
			}
		}

		//adding custom fields
		GFCommon::log_debug( 'GFFormsModel::create_post(): Adding custom fields.' );
		foreach ( $post_data['post_custom_fields'] as $meta_name => $meta_value ) {
			if ( ! is_array( $meta_value ) ) {
				$meta_value = array( $meta_value );
			}

			$meta_index = 0;
			foreach ( $meta_value as $value ) {
				GFCommon::log_debug( 'GFFormsModel::create_post(): Getting custom field: ' . $meta_name );
				$custom_field = self::get_custom_field( $form, $meta_name, $meta_index );

				//replacing template variables if template is enabled
				if ( $custom_field && $custom_field->customFieldTemplateEnabled ) {
					$value = self::process_post_template( $custom_field->customFieldTemplate, 'post_custom_field', $post_images, $post_data, $form, $lead );
				}
				switch ( RGFormsModel::get_input_type( $custom_field ) ) {
					case 'list' :
						$value = maybe_unserialize( $value );
						if ( is_array( $value ) ) {
							foreach ( $value as $item ) {
								if ( is_array( $item ) ) {
									$item = implode( '|', $item );
								}

								if ( ! rgblank( $item ) ) {
									add_post_meta( $post_id, $meta_name, $item );
								}
							}
						}
						break;

					case 'multiselect' :
					case 'checkbox' :
						$value = explode( ',', $value );
						if ( is_array( $value ) ) {
							foreach ( $value as $item ) {
								if ( ! rgblank( $item ) ) {
									// add post meta and replace HTML symbol in $item with real comma
									add_post_meta( $post_id, $meta_name, str_replace( '&#44;', ',', $item ) );
								}
							}
						}
						break;

					case 'date' :
						$value = GFCommon::date_display( $value, rgar( $custom_field, 'dateFormat' ) );
						if ( ! rgblank( $value ) ) {
							add_post_meta( $post_id, $meta_name, $value );
						}
						break;

					default :
						if ( ! rgblank( $value ) ) {
							add_post_meta( $post_id, $meta_name, $value );
						}
						break;
				}

				$meta_index ++;
			}
		}

		$has_content_field = sizeof( self::get_fields_by_type( $form, array( 'post_content' ) ) ) > 0;
		$has_title_field   = sizeof( self::get_fields_by_type( $form, array( 'post_title' ) ) ) > 0;
		$post              = false;

		//if a post field was configured with a content or title template, process template
		if ( ( rgar( $form, 'postContentTemplateEnabled' ) && $has_content_field ) || ( rgar( $form, 'postTitleTemplateEnabled' ) && $has_title_field ) ) {

			$post = get_post( $post_id );

			if ( rgar( $form, 'postContentTemplateEnabled' ) && $has_content_field ) {
				$post_content = self::process_post_template( $form['postContentTemplate'], 'post_content', $post_images, $post_data, $form, $lead );

				//updating post content
				$post->post_content = $post_content;
			}

			if ( rgar( $form, 'postTitleTemplateEnabled' ) && $has_title_field ) {
				$post_title = self::process_post_template( $form['postTitleTemplate'], 'post_title', $post_images, $post_data, $form, $lead );

				//updating post
				$post->post_title = $post_title;
				$post->post_name  = $post_title;
			}
		}

		// update post status back to original status (if not draft)
		if ( $post_status != 'draft' ) {
			$post              = is_object( $post ) ? $post : get_post( $post_id );
			$post->post_status = $post_status;
		}

		// if post has been modified since creation, save updates
		if ( is_object( $post ) ) {
			GFCommon::log_debug( 'GFFormsModel::create_post(): Updating post.' );
			wp_update_post( $post );
		}


		//adding post format
		if ( current_theme_supports( 'post-formats' ) && rgar( $form, 'postFormat' ) ) {

			$formats     = get_theme_support( 'post-formats' );
			$post_format = rgar( $form, 'postFormat' );

			if ( is_array( $formats ) ) {
				$formats = $formats[0];
				if ( in_array( $post_format, $formats ) ) {
					set_post_format( $post_id, $post_format );
				} else if ( '0' == $post_format ) {
					set_post_format( $post_id, false );
				}
			}
		}

		// Update the post_id in the database for this entry.
		GFCommon::log_debug( 'GFFormsModel::create_post(): Updating entry with post id.' );
		self::update_lead_property( $lead['id'], 'post_id', $post_id );

		$gform_after_create_post_args = array( 'gform_after_create_post', $form['id'] );
		if ( gf_has_action( $gform_after_create_post_args ) ) {
			GFCommon::log_debug( __METHOD__ . '(): Executing functions hooked to gform_after_create_post.' );
			/**
			 * Fires after a post, from a form with post fields, is created
			 *
			 * @param int   $form    ['id'] The ID of the form where the new post was created
			 * @param int   $post_id The new Post ID created after submission
			 * @param array $lead    The Lead Object
			 * @param array $form    The Form Object for the form used to create the post
			 */
			gf_do_action( $gform_after_create_post_args, $post_id, $lead, $form );
			GFCommon::log_debug( __METHOD__ . '(): Completed gform_after_create_post.' );
		}

		GFCommon::log_debug( __METHOD__ . sprintf( '(): Post creation completed in %F seconds.', GFCommon::timer_end( __METHOD__ ) ) );

		return $post_id;
	}

	/**
	 * Process any merge tags and shortcodes found in the template.
	 *
	 * @param string $template The template.
	 * @param string $field_type The field type currently being processed. Possible values: post_custom_field, post_content, or post_title.
	 * @param array $post_images The uploaded post images.
	 * @param array $post_data The post data prepared from the current entry.
	 * @param array $form The form currently being processed.
	 * @param array $entry The entry currently being processed.
	 *
	 * @return string
	 */
	public static function process_post_template( $template, $field_type, $post_images, $post_data, $form, $entry ) {
		GFCommon::log_debug( __METHOD__ . "(): Processing {$field_type} template." );

		//replacing post image variables
		$template = GFCommon::replace_variables_post_image( $template, $post_images, $entry );

		//replacing all other variables
		$template = GFCommon::replace_variables( $template, $form, $entry, false, false, false );

		if ( $field_type != 'post_content' ) {
			$process_template_shortcodes = true;

			/**
			 * Allow shortcode processing of custom field and post title templates to be disabled.
			 *
			 * @param boolean $process_template_shortcodes Should the shortcodes be processed? Default is true.
			 * @param string $field_type The field type currently being processed. Possible values: post_custom_field, post_content, or post_title.
			 * @param array $post_data The post data prepared from the current entry.
			 * @param array $form The form currently being processed.
			 * @param array $entry The entry currently being processed.
			 *
			 * @since 2.0.0.4
			 */
			$process_template_shortcodes = apply_filters( 'gform_process_template_shortcodes_pre_create_post', $process_template_shortcodes, $field_type, $post_data, $form, $entry );
			$process_template_shortcodes = apply_filters( 'gform_process_template_shortcodes_pre_create_post_' . $form['id'], $process_template_shortcodes, $field_type, $post_data, $form, $entry );


			if ( $process_template_shortcodes ) {
				$template = do_shortcode( $template );
			}
		}

		return $template;
	}

	private static function get_custom_field( $form, $meta_name, $meta_index ) {
		$custom_fields = self::get_fields_by_type( $form, array( 'post_custom_field' ) );

		$index = 0;
		foreach ( $custom_fields as $field ) {
			if ( $field->postCustomFieldName == $meta_name ) {
				if ( $meta_index == $index ) {
					return $field;
				}
				$index ++;
			}
		}

		return false;
	}

	private static function copy_post_image( $url, $post_id ) {
		$time = current_time( 'mysql' );

		if ( $post = get_post( $post_id ) ) {
			if ( substr( $post->post_date, 0, 4 ) > 0 ) {
				$time = $post->post_date;
			}
		}

		//making sure there is a valid upload folder
		if ( ! ( ( $upload_dir = wp_upload_dir( $time ) ) && false === $upload_dir['error'] ) ) {
			return false;
		}

		$form_id = get_post_meta( $post_id, '_gform-form-id', true );

		/**
		 * Filter the media upload location.
		 *
		 * @param array $upload_dir The current upload directory’s path and url.
		 * @param int $form_id The ID of the form currently being processed.
		 * @param int $post_id The ID of the post created from the entry currently being processed.
		 */
		$upload_dir = gf_apply_filters( 'gform_media_upload_path', $form_id, $upload_dir, $form_id, $post_id );

		if ( ! file_exists( $upload_dir['path'] ) ) {
			if ( ! wp_mkdir_p( $upload_dir['path'] ) ) {
				return false;
			}
		}

		$name     = wp_basename( $url );
		$filename = wp_unique_filename( $upload_dir['path'], $name );

		// the destination path
		$new_file = $upload_dir['path'] . "/$filename";

		// the source path
		$upload_root_info = GF_Field_FileUpload::get_upload_root_info( $form_id );
		$path             = str_replace( $upload_root_info['url'], $upload_root_info['path'], $url );

		// copy the file to the destination path
		if ( ! copy( $path, $new_file ) ) {
			return false;
		}

		// Set correct file permissions
		$stat  = stat( dirname( $new_file ) );
		$perms = $stat['mode'] & 0000666;
		@ chmod( $new_file, $perms );

		// Compute the URL
		$url = $upload_dir['url'] . "/$filename";

		if ( is_multisite() ) {
			delete_transient( 'dirsize_cache' );
		}

		$type = wp_check_filetype( $new_file );

		return array( 'file' => $new_file, 'url' => $url, 'type' => $type['type'] );

	}

	public static function media_handle_upload( $url, $post_id, $post_data = array() ) {

		// WordPress Administration API required for the media_handle_upload() function.
		require_once( ABSPATH . 'wp-admin/includes/image.php' );
		require_once( ABSPATH . 'wp-admin/includes/media.php' );

		$name = wp_basename( $url );

		$file = self::copy_post_image( $url, $post_id );

		if ( ! $file ) {
			GFCommon::log_debug( __METHOD__ . '(): Image could not be copied to the media directory.' );

			return false;
		}

		$name_parts = pathinfo( $name );
		$name       = trim( substr( $name, 0, - ( 1 + strlen( $name_parts['extension'] ) ) ) );

		$url     = $file['url'];
		$type    = $file['type'];
		$file    = $file['file'];
		$title   = $name;
		$content = '';

		// use image exif/iptc data for title and caption defaults if possible
		if ( $image_meta = @wp_read_image_metadata( $file ) ) {
			if ( trim( $image_meta['title'] ) && ! is_numeric( sanitize_title( $image_meta['title'] ) ) ) {
				$title = $image_meta['title'];
			}
			if ( trim( $image_meta['caption'] ) ) {
				$content = $image_meta['caption'];
			}
		}

		// Construct the attachment array
		$attachment = array_merge(
			array(
				'post_mime_type' => $type,
				'guid'           => $url,
				'post_parent'    => $post_id,
				'post_title'     => $title,
				'post_content'   => $content,
			), $post_data
		);

		// Save the data
		$id = wp_insert_attachment( $attachment, $file, $post_id );
		if ( ! is_wp_error( $id ) ) {
			wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $file ) );
		}

		GFCommon::log_debug( __METHOD__ . '(): Image copied to the media directory. Result from wp_insert_attachment(): ' . print_r( $id, 1 ) );

		return $id;
	}

	public static function save_input( $form, $field, &$lead, $current_fields, $input_id ) {

		if ( isset( $field->fields ) && is_array( $field->fields ) ) {
			foreach( $field->fields as $sub_field ) {
				$inputs = $sub_field->get_entry_inputs();
				if ( is_array( $inputs ) ) {
					foreach ( $inputs as $input ) {
						self::save_input( $form, $sub_field, $lead, $current_fields, $input['id'] );
					}
				} else {
					self::save_input( $form, $sub_field, $lead, $current_fields, $sub_field->id );
				}
				foreach ( $current_fields as $current_field ) {
					if ( intval( $current_field->meta_key ) == $sub_field->id && ! isset( $current_field->update ) ) {
						$current_field->delete = true;
						$result = self::queue_batch_field_operation( $form, $lead, $sub_field, $current_field->id, $current_field->meta_key, '', $current_field->item_index );
						GFCommon::log_debug( __METHOD__ . "(): Deleting: {$field->label}(#{$sub_field->id}{$current_field->item_index} - {$field->type}). Result: " . var_export( $result, 1 ) );
					}
				}
			}
			return;
		}

		$input_name = 'input_' . str_replace( '.', '_', $input_id );

		if ( $field->enableCopyValuesOption && rgpost( 'input_' . $field->id . '_copy_values_activated' ) ) {
			$source_field_id   = $field->copyValuesOptionField;
			$source_input_name = str_replace( 'input_' . $field->id, 'input_' . $source_field_id, $input_name );
			$value             = rgpost( $source_input_name );
		} else {
			$value = rgpost( $input_name );
		}

		$value = self::maybe_trim_input( $value, $form['id'], $field );

		//ignore file upload when nothing was sent in the admin
		//ignore post fields in the admin
		$type           = self::get_input_type( $field );
		$multiple_files = $field->multipleFiles;
		$uploaded_files = GFFormsModel::$uploaded_files;
		$form_id        = $form['id'];
		if ( rgget( 'view' ) == 'entry' && $type == 'fileupload' && ( ( ! $multiple_files && empty( $_FILES[ $input_name ]['name'] ) ) || ( $multiple_files && ! isset( $uploaded_files[ $form_id ][ $input_name ] ) ) ) ) {
			return;
		} else if ( rgget( 'view' ) == 'entry' && in_array( $field->type, array( 'post_category', 'post_title', 'post_content', 'post_excerpt', 'post_tags', 'post_custom_field', 'post_image' ) ) ) {
			return;
		}

		$is_form_editor = GFCommon::is_form_editor();
		$is_entry_detail = GFCommon::is_entry_detail();
		$is_admin = $is_form_editor || $is_entry_detail;

		if ( empty( $value ) && $field->is_administrative() && ! $is_admin ) {
			$value = self::get_default_value( $field, $input_id );
		}

		self::queue_save_input_value( $value, $form, $field, $lead, $current_fields, $input_id );
	}

	/**
	 * Queues the input value for saving.
	 *
	 * @since 2.4
	 *
	 * @param string|array $value
	 * @param array        $form
	 * @param GF_Field     $field
	 * @param array        $lead
	 * @param array        $current_fields
	 * @param string       $input_id
	 * @param string       $item_index
	 */
	public static function queue_save_input_value( $value, $form, $field, &$lead, $current_fields, $input_id, $item_index = '' ) {

		$input_name = 'input_' . str_replace( '.', '_', $input_id );
		if ( is_array( $value ) && ! ( $field->is_value_submission_array() && ! is_array( $value[0] ) ) ) {
			foreach ( $value as $i => $v ) {
				$new_item_index = $item_index . '_' . $i;
				if ( is_array( $v ) && ! ( $field->is_value_submission_array() && ! is_array( $v[0] ) ) ) {
					self::queue_save_input_value( $v, $form, $field, $lead, $current_fields, $input_id, $new_item_index );
					continue;
				}
				//processing values so that they are in the correct format for each input type
				$v = self::prepare_value( $form, $field, $v, $input_name, rgar( $lead, 'id' ) );

				$lead_detail_id               = self::get_lead_detail_id( $current_fields, $input_id, $new_item_index );
				$result                       = self::queue_batch_field_operation( $form, $lead, $field, $lead_detail_id, $input_id, $v, $new_item_index );
				GFCommon::log_debug( __METHOD__ . "(): Saving: {$field->label}(#{$input_id}{$item_index} - {$field->type}). Result: " . var_export( $result, 1 ) );
				foreach ( $current_fields as $current_field ) {
					if ( (string) $current_field->meta_key === (string) $input_id && $current_field->item_index == $new_item_index ) {
						$current_field->update = true;
					}
				}
			}

		} else {
			//processing values so that they are in the correct format for each input type
			$value = self::prepare_value( $form, $field, $value, $input_name, rgar( $lead, 'id' ), $lead );

			//ignore fields that have not changed
			if ( $lead != null && isset( $lead[ $input_id ] ) && $value === rgget( (string) $input_id, $lead ) ) {
				return;
			}

			$lead_detail_id = self::get_lead_detail_id( $current_fields, $input_id );
			$result         = self::queue_batch_field_operation( $form, $lead, $field, $lead_detail_id, $input_id, $value );
			GFCommon::log_debug( __METHOD__ . "(): Queued field operation: {$field->label}(#{$input_id} - {$field->type})." );

		}
	}

	/**
	 * Updates an existing field value in the database.
	 *
	 * @param array $form
	 * @param array $lead
	 * @param GF_Field $field
	 * @param int $lead_detail_id
	 * @param string $input_id
	 * @param string $value
	 *
	 * @return bool
	 */
	public static function update_lead_field_value( $form, $lead, $field, $lead_detail_id, $input_id, $value ) {
		return self::update_entry_field_value( $form, $lead, $field, $lead_detail_id, $input_id, $value );
	}

	/**
	 * Updates an existing field value in the database.
	 *
	 * @since 2.3
	 *
	 * @param array $form
	 * @param array $entry
	 * @param GF_Field $field
	 * @param int $entry_meta_id
	 * @param string $input_id
	 * @param string $value
	 *
	 * @return bool
	 */
	public static function update_entry_field_value( $form, $entry, $field, $entry_meta_id, $input_id, $value, $item_index = '' ) {
		global $wpdb;

		if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
			return GF_Forms_Model_Legacy::update_lead_field_value( $form, $entry, $field, $entry_meta_id, $input_id, $value );
		}

		/**
		 * Filter the value before it's saved to the database.
		 *
		 * @since 1.5.0
		 * @since 1.8.6 Added the $input_id parameter.
		 * @since 1.9.14 Added form and field specific versions.
		 *
		 * @param string|array $value The fields input value.
		 * @param array $entry The current entry object.
		 * @param GF_Field $field The current field object.
		 * @param array $form The current form object.
		 * @param string $input_id The ID of the input being saved or the field ID for single input field types.
		 */
		$value = apply_filters( 'gform_save_field_value', $value, $entry, $field, $form, $input_id );
		$value = apply_filters( "gform_save_field_value_{$form['id']}", $value, $entry, $field, $form, $input_id );

		if ( is_object( $field ) ) {
			$value = apply_filters( "gform_save_field_value_{$form['id']}_{$field->id}", $value, $entry, $field, $form, $input_id );
		}

		if ( is_array( $value ) ) {
			GFCommon::log_debug( __METHOD__ . '(): bailing. value is an array.' );
			return false;
		}

		$entry_id                = $entry['id'];
		$form_id                = $form['id'];
		$entry_meta_table_name      = self::get_entry_meta_table_name();

		// Add emoji support.
		if ( version_compare( get_bloginfo( 'version' ), '4.2', '>=' ) ) {

			// Get charset for lead detail value column .
			$charset = $wpdb->get_col_charset( $entry_meta_table_name, 'meta_value' );

			// If entry detail value column is UTF-8, encode emoji.
			if ( 'utf8' === $charset ) {
				$value = wp_encode_emoji( $value );
			}
		}

		if ( ! rgblank( $value ) ) {

			if ( $entry_meta_id > 0 ) {

				$result = $wpdb->update( $entry_meta_table_name, array( 'meta_value' => $value ), array( 'id' => $entry_meta_id ), array( '%s' ), array( '%d' ) );
				if ( false === $result ) {
					return false;
				}

			} else {
				$result = $wpdb->insert( $entry_meta_table_name, array( 'entry_id' => $entry_id, 'form_id' => $form_id, 'meta_key' => $input_id, 'meta_value' => $value, 'item_index' => $item_index ), array( '%d', '%d', '%s', '%s', '%s' ) );
				if ( false === $result ) {
					return false;
				}

			}

		} else {
			// when the value is empty and no $entry_meta_id was set, check if it's a repeater field.
			if ( empty( $entry_meta_id ) && $field instanceof GF_Field_Repeater && isset( $field->fields ) && is_array( $field->fields ) ) {
				foreach ( $field->fields as $subfield ) {
					self::update_entry_field_value( $form, $entry, $subfield, 0, $subfield->id, '' );
				}
			} else {
				// Deleting details for this field
				if ( is_array( $field->get_entry_inputs() ) ) {
					$_input_id = ( false === strpos( $input_id, '.' ) ) ? sprintf( '%d.%%', $input_id ) : $input_id;
					$sql = $wpdb->prepare( "DELETE FROM $entry_meta_table_name WHERE entry_id=%d AND meta_key LIKE %s ", $entry_id, $_input_id );
				} else {
					$sql = $wpdb->prepare( "DELETE FROM $entry_meta_table_name WHERE entry_id=%d AND meta_key = %s ", $entry_id, $input_id );
				}
				if ( $item_index ) {
					$sql .= $wpdb->prepare( ' AND item_index=%s', $item_index );
				}
				$result = $wpdb->query( $sql );
				if ( false === $result ) {
					return false;
				}
			}
		}

		return true;
	}

	/**
	 * Returns the SQL to update or insert field values..
	 *
	 * @param array    $form
	 * @param array    $entry
	 * @param GF_Field $field
	 * @param int      $entry_meta_id
	 * @param string   $input_id
	 * @param string   $value
	 * @param string   $item_index
	 *
	 * @return bool
	 */
	public static function queue_batch_field_operation( $form, &$entry, $field, $entry_meta_id, $input_id, $value, $item_index = '' ) {
		/**
		 * Filter the value before it's saved to the database.
		 *
		 * @since 1.5.0
		 * @since 1.8.6 Added the $input_id parameter.
		 * @since 1.9.14 Added form and field specific versions.
		 *
		 * @param string|array $value The fields input value.
		 * @param array $entry The current entry object.
		 * @param GF_Field $field The current field object.
		 * @param array $form The current form object.
		 * @param string $input_id The ID of the input being saved or the field ID for single input field types.
		 */
		$value = apply_filters( 'gform_save_field_value', $value, $entry, $field, $form, $input_id );
		$value = apply_filters( "gform_save_field_value_{$form['id']}", $value, $entry, $field, $form, $input_id );

		if ( is_object( $field ) ) {
			$value = apply_filters( "gform_save_field_value_{$form['id']}_{$field->id}", $value, $entry, $field, $form, $input_id );
		}

		if ( is_array( $value ) ) {
			GFCommon::log_debug( __METHOD__ . '(): bailing. value is an array.' );
			return false;
		}

		$entry[ (string) $input_id . $item_index ] = $value;

		$entry_id = $entry['id'];
		$form_id  = $form['id'];

		if ( ! rgblank( $value ) ) {
			if ( $entry_meta_id > 0 ) {
				self::$_batch_field_updates[] = array( 'meta_value' => $value, 'id' => $entry_meta_id );
			} else {
				self::$_batch_field_inserts[] = array( 'entry_id' => $entry_id, 'form_id' => $form_id, 'meta_key' => $input_id, 'meta_value' => $value, 'item_index' => $item_index );
			}
		} elseif ( $entry_meta_id > 0 && ! in_array( $input_id, GFFormsModel::get_lead_db_columns() ) ) {
			self::$_batch_field_deletes[] = $entry_meta_id;
		}

		return true;
	}

	/**
	 * Checks if any field updates, inserts, or deletions have been registered for batch processing.
	 *
	 * @since 2.4.17
	 *
	 * @return bool
	 */
	public static function has_batch_field_operations() {
		return ! empty( self::$_batch_field_updates ) || ! empty( self::$_batch_field_inserts ) || ! empty( self::$_batch_field_deletes );
	}

	public static function flush_batch_field_operations() {
		self::$_batch_field_updates = array();
		self::$_batch_field_inserts = array();
		self::$_batch_field_deletes = array();
	}

	public static function begin_batch_field_operations() {
		self::flush_batch_field_operations();
	}


	/**
	 * Performs the update, inserts and deletes registered by queue_batch_field_operation()
	 *
	 * @return array An array of results.
	 */
	public static function commit_batch_field_operations() {
		global $wpdb;

		$meta_table = self::get_entry_meta_table_name();

		$results = array(
			'updates' => null,
			'inserts' => null,
			'deletes' => null,
		);

		// Updates
		if ( ! empty( self::$_batch_field_updates ) ) {
			$values = array();
			foreach ( self::$_batch_field_updates as $update ) {
				$values[] = $wpdb->prepare( '(%s,%s)', $update['id'], $update['meta_value'] );
			}
			$values_str = join( ',', $values );
			$update_sql =  "INSERT INTO {$meta_table} (id,meta_value)
						VALUES {$values_str}
						ON DUPLICATE KEY UPDATE meta_value=VALUES(meta_value);";
			$result = $wpdb->query( $update_sql );
			if ( $result === false ) {
				$result = new WP_Error( 'update_error', $wpdb->last_error );
			}
			$results['updates'] = $result;
		}

		// Inserts
		if ( ! empty( self::$_batch_field_inserts ) ) {
			$values = array();
			foreach ( self::$_batch_field_inserts as $insert ) {
				$values[] = $wpdb->prepare( '(%d,%d,%s,%s,%s)', $insert['entry_id'], $insert['form_id'], $insert['meta_key'], $insert['meta_value'], $insert['item_index'] );
			}
			$values_str = join( ',', $values );
			$insert_sql = "INSERT INTO {$meta_table} (entry_id, form_id, meta_key, meta_value, item_index)  VALUES {$values_str};";
			$result = $wpdb->query( $insert_sql );
			if ( $result === false ) {
				$result = new WP_Error( 'insert_error', $wpdb->last_error );
			}
			$results['inserts'] = $result;
		}

		// Deletes
		if ( ! empty( self::$_batch_field_deletes ) ) {
			$in_str_arr    = array_fill( 0, count( self::$_batch_field_deletes ), '%d' );
			$in_str        = join( ',', $in_str_arr );
			$ids = array_map( 'absint', self::$_batch_field_deletes );
			$delete_sql = $wpdb->prepare( "DELETE FROM {$meta_table} WHERE id IN ( {$in_str} )", $ids);
			$result = $wpdb->query( $delete_sql );
			if ( $result === false ) {
				$result = new WP_Error( 'delete_error', $wpdb->last_error );
			}
			$results['deletes'] = $result;
		}

		self::flush_batch_field_operations();

		return $results;
	}

	private static function move_temp_file( $form_id, $tempfile_info ) {
		_deprecated_function( 'move_temp_file', '1.9', 'GF_Field_Fileupload::move_temp_file' );

		$target = self::get_file_upload_path( $form_id, $tempfile_info['uploaded_filename'] );
		$source = self::get_upload_path( $form_id ) . '/tmp/' . $tempfile_info['temp_filename'];

		if ( rename( $source, $target['path'] ) ) {
			self::set_permissions( $target['path'] );

			return $target['url'];
		} else {
			return 'FAILED (Temporary file could not be moved.)';
		}
	}

	public static function set_permissions( $path ) {
		$permission = apply_filters( 'gform_file_permission', 0644, $path );
		if ( $permission ) {
			@chmod( $path, $permission );
		}
	}

	public static function upload_file( $form_id, $file ) {
		_deprecated_function( 'upload_file', '1.9', 'GF_Field_Fileupload::upload_file' );
		$target = self::get_file_upload_path( $form_id, $file['name'] );
		if ( ! $target ) {
			GFCommon::log_debug( 'GFFormsModel::upload_file(): FAILED (Upload folder could not be created.)' );

			return 'FAILED (Upload folder could not be created.)';
		}


		if ( move_uploaded_file( $file['tmp_name'], $target['path'] ) ) {
			GFCommon::log_debug( 'GFFormsModel::upload_file(): Setting permissions on ' . $target['path'] );
			self::set_permissions( $target['path'] );

			return $target['url'];
		} else {
			GFCommon::log_debug( 'GFFormsModel::upload_file(): FAILED (Temporary file could not be copied.)' );

			return 'FAILED (Temporary file could not be copied.)';
		}
	}


	public static function get_upload_root() {
		$dir = wp_upload_dir();

		if ( $dir['error'] ) {
			return null;
		}

		return $dir['basedir'] . '/gravity_forms/';
	}

	public static function get_upload_url_root() {
		$dir = wp_upload_dir();

		if ( $dir['error'] ) {
			return null;
		}

		return $dir['baseurl'] . '/gravity_forms/';
	}

	public static function get_upload_path( $form_id ) {
		$form_id = absint( $form_id );
		return self::get_upload_root() . $form_id . '-' . wp_hash( $form_id );
	}

	public static function get_upload_url( $form_id ) {
		$form_id = absint( $form_id );
		$dir = wp_upload_dir();

		return $dir['baseurl'] . "/gravity_forms/$form_id" . '-' . wp_hash( $form_id );
	}

	public static function get_file_upload_path( $form_id, $file_name, $increment_found = true ) {

		if ( version_compare( phpversion(), '7.4', '<' ) && get_magic_quotes_gpc() ) {
			$file_name = stripslashes( $file_name );
		}
		$form_id = absint( $form_id );
		//adding filter to upload root path and url
		$upload_root_info    = GF_Field_FileUpload::get_upload_root_info( $form_id );
		$target_root         = $upload_root_info['path'];
		$target_root_url     = $upload_root_info['url'];

		$default_upload_root_info =  GF_Field_FileUpload::get_default_upload_roots( $form_id );
		$default_target_root      = rgar( $default_upload_root_info, 'path' );
		$y                        = rgar( $default_upload_root_info, 'y' );
		$m                        = rgar( $default_upload_root_info, 'm' );


		$target_root = trailingslashit( $target_root );

		if ( ! is_dir( $target_root ) ) {
			if ( ! wp_mkdir_p( $target_root ) ) {
				return false;
			}

			// Adding index.html files to all subfolders.
			if ( $default_target_root != $target_root && ! file_exists( $target_root . 'index.html' ) ) {
				GFCommon::recursive_add_index_file( $target_root );
			} elseif ( ! file_exists( self::get_upload_root() . '/index.html' ) ) {
				GFCommon::recursive_add_index_file( self::get_upload_root() );
			} elseif ( ! file_exists( self::get_upload_path( $form_id ) . '/index.html' ) ) {
				GFCommon::recursive_add_index_file( self::get_upload_path( $form_id ) );
			} elseif ( ! file_exists( self::get_upload_path( $form_id ) . "/$y/index.html" ) ) {
				GFCommon::recursive_add_index_file( self::get_upload_path( $form_id ) . "/$y" );
			} else {
				GFCommon::recursive_add_index_file( self::get_upload_path( $form_id ) . "/$y/$m" );
			}
		}

		$file_name = sanitize_file_name( $file_name );

		//Add the original filename to our target path.
		//Result is "uploads/filename.extension"
		$extension = pathinfo( $file_name, PATHINFO_EXTENSION );
		if ( ! empty( $extension ) ) {
			$extension = '.' . $extension;
		}

		$file_name = wp_basename( $file_name, $extension );

		$counter     = 1;
		$target_path = $target_root . $file_name . $extension;
		while ( $increment_found && file_exists( $target_path ) ) {
			$target_path = $target_root . $file_name . "$counter" . $extension;
			$counter ++;
		}

		//Remove '.' from the end if file does not have a file extension
		$target_path = trim( $target_path, '.' );

		//creating url
		$target_url = str_replace( $target_root, $target_root_url, $target_path );

		return array( 'path' => $target_path, 'url' => $target_url );
	}

	public static function get_tables() {
		return array(
			self::get_form_view_table_name(),
			self::get_meta_table_name(),
			self::get_form_table_name(),
			self::get_form_revisions_table_name(),
			self::get_entry_table_name(),
			self::get_entry_meta_table_name(),
			self::get_entry_notes_table_name(),
			self::get_draft_submissions_table_name(),
			self::get_rest_api_keys_table_name(),
		);
	}

	public static function drop_tables() {
		global $wpdb;
		remove_filter( 'query', array( 'GFForms', 'filter_query' ) );
		foreach ( GF_Forms_Model_Legacy::get_legacy_tables() as $table ) {
			$wpdb->query( "DROP TABLE IF EXISTS $table" );
		}
		foreach ( self::get_tables() as $table ) {
			$wpdb->query( "DROP TABLE IF EXISTS $table" );
		}
		add_filter( 'query', array( 'GFForms', 'filter_query' ) );
	}

	/**
	 * Target for the wpmu_drop_tables filter. Adds all tables for Gravity Forms and the Add-On Framework to list
	 * of tables to drop when a site is deleted.
	 *
	 * @param $drop_tables
	 *
	 * @return array
	 */
	public static function mu_drop_tables( $drop_tables ) {
		global $wpdb;

		$addon_tables = array(
			self::get_addon_feed_table_name(),
			$wpdb->prefix . 'gf_addon_payment_callback',
			$wpdb->prefix . 'gf_addon_payment_transaction',
		);

		$drop_tables = array_merge( $drop_tables, $addon_tables );

		$core_tables = self::get_tables();

		$drop_tables = array_merge( $drop_tables, $core_tables );

		$legacy_tables = GF_Forms_Model_Legacy::get_legacy_tables();

		$drop_tables = array_merge( $drop_tables, $legacy_tables );

		// Prevent the legacy table query notice when they are dropped by wp_uninitialize_site().
		remove_filter( 'query', array( 'GFForms', 'filter_query' ) );

		return $drop_tables;
	}

	public static function insert_form_view( $form_id, $deprecated = null ) {
		global $wpdb;
		$table_name = self::get_form_view_table_name();

		$sql = $wpdb->prepare(
			" SELECT id FROM $table_name
				WHERE form_id=%d
				AND date_created BETWEEN DATE_SUB(utc_timestamp(), INTERVAL 1 DAY) AND utc_timestamp()", $form_id
		);

		$id = $wpdb->get_var( $sql, 0, 0 );

		if ( empty( $id ) ) {
			$wpdb->query( $wpdb->prepare( "INSERT INTO $table_name(form_id, date_created, ip) values(%d, utc_timestamp(), %s)", $form_id, '' ) );
		} else {
			$wpdb->query( $wpdb->prepare( "UPDATE $table_name SET count = count+1 WHERE id=%d", $id ) );
		}
	}

	public static function is_duplicate( $form_id, $field, $value ) {
		global $wpdb;

		if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
			return GF_Forms_Model_Legacy::is_duplicate( $form_id, $field, $value );
		}

		$entry_meta_table_name   = self::get_entry_meta_table_name();
		$entry_table_name        = self::get_entry_table_name();
		$sql_comparison          = 'ld.meta_value = %s';

		switch ( GFFormsModel::get_input_type( $field ) ) {
			case 'time':
				$value = sprintf( "%02d:%02d %s", $value[0], $value[1], $value[2] );
				break;
			case 'date':
				$value = self::prepare_date( $field->dateFormat, $value );
				break;
			case 'number':
				$value = GFCommon::clean_number( $value, $field->numberFormat );
				break;
			case 'phone':
				$value          = str_replace( array( ')', '(', '-', ' ' ), '', $value );
				$sql_comparison = 'replace( replace( replace( replace( ld.meta_value, ")", "" ), "(", "" ), "-", "" ), " ", "" ) = %s';
				break;
			case 'email':
				$value = is_array( $value ) ? rgar( $value, 0 ) : $value;
				break;
		}

		$inner_sql_template = "SELECT %s as input, ld.entry_id
                                FROM {$entry_meta_table_name} ld
                                INNER JOIN {$entry_table_name} l ON l.id = ld.entry_id\n";


		$inner_sql_template .= "WHERE l.form_id=%d AND ld.form_id=%d
                                AND ld.meta_key = %s
                                AND status='active' AND {$sql_comparison}";

		$sql = "SELECT count(distinct input) as match_count FROM ( ";

		$input_count = 1;
		if ( is_array( $field->get_entry_inputs() ) ) {
			$input_count = sizeof( $field->inputs );
			$inner_sql = '';
			foreach ( $field->inputs as $input ) {
				$union = empty( $inner_sql ) ? '' : ' UNION ALL ';
				$inner_sql .= $union . $wpdb->prepare( $inner_sql_template, $input['id'], $form_id, $form_id, $input['id'], $value[ $input['id'] ] );
			}
		} else {
			$inner_sql = $wpdb->prepare( $inner_sql_template, $field->id, $form_id, $form_id, $field->id, $value );
		}

		$sql .= $inner_sql . "
                ) as count
                GROUP BY entry_id
                ORDER BY match_count DESC";

		$count = gf_apply_filters( array( 'gform_is_duplicate', $form_id ), $wpdb->get_var( $sql ), $form_id, $field, $value );

		return $count != null && $count >= $input_count;
	}

	public static function get_lead( $lead_id ) {
		$entry = GFAPI::get_entry( $lead_id );
		if ( is_wp_error( $entry ) ) {
			$entry = false;
		}
		return $entry;
	}

	public static function get_entry( $entry_id ) {
		return GFAPI::get_entry( $entry_id );
	}

	public static function get_lead_notes( $lead_id ) {
		global $wpdb;

		if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
			return GF_Forms_Model_Legacy::get_lead_notes( $lead_id );
		}

		$notes_table = self::get_entry_notes_table_name();

		return $wpdb->get_results(
			$wpdb->prepare(
				"  SELECT n.id, n.user_id, n.date_created, n.value, n.note_type, n.sub_type, ifnull(u.display_name,n.user_name) as user_name, u.user_email
                                                    FROM $notes_table n
                                                    LEFT OUTER JOIN $wpdb->users u ON n.user_id = u.id
                                                    WHERE entry_id=%d ORDER BY id", $lead_id
			)
		);
	}

	/**
	 * Get a collection of notes.
	 *
	 * @since 2.4.18
	 *
	 * @param array      $search_criteria {
	 * 		Array of search criteria.
	 *
	 * 		@type int    $id         Get the note with this ID.
	 * 		@type int    $entry_id   Get notes associated with this entry ID.
	 * 		@type int    $user_id    Get notes with this user ID.
	 * 		@type string $user_name  Get notes with this user name.
	 * 		@type string $note_type  Get notes with this note type.
	 * 		@type string $sub_type   Get notes with this sub type.
	 * 		@type string $start_date Get notes on or after this date.  Expects SQL datetime format.
	 * 		@type string $end_date   Get notes on or before this date.  Expects SQL datetime format.
	 * }
	 * @param null|array $sorting {
	 * 		Array of sort key and direction.
	 *
	 * 		@type string $key       Key to sort on.  Options: id, entry_id, user_id, user_name, note_type, sub_type, date.
	 * 		@type string $direction Sort direction.  Options: ASC, DESC.
	 * }
	 * @return array|null|object
	 */
	public static function get_notes( $search_criteria = array(), $sorting = null ) {
		global $wpdb;

		$where = array();

		if ( rgar( $search_criteria, 'id' ) ) {
			$where[] = $wpdb->prepare( 'n.id = %d', $search_criteria['id'] );
		}

		if ( rgar( $search_criteria, 'entry_id' ) ) {
			$where[] = $wpdb->prepare( 'entry_id = %d', $search_criteria['entry_id'] );
		}

		if ( rgars( $search_criteria, 'user_id' ) ) {
			$where[] = $wpdb->prepare( 'user_id = %d', $search_criteria['user_id'] );
		}

		if ( isset( $search_criteria['user_name'] ) ) {
			if ( '' !== $search_criteria['user_name'] ) {
				$where[] = $wpdb->prepare('user_name = %s', $search_criteria['user_name']);
			} else {
				$where[] = "( user_name = '' OR user_name IS NULL )";
			}
		}

		if ( rgar( $search_criteria, 'note_type' ) ) {
			$where[] = $wpdb->prepare( 'note_type = %s', $search_criteria['note_type'] );
		}

		if ( isset( $search_criteria['sub_type'] ) ) {
			if ( '' !== $search_criteria['sub_type'] ) {
				$where[] = $wpdb->prepare('sub_type = %s', $search_criteria['sub_type']);
			} else {
				$where[] = "( sub_type = '' OR sub_type IS NULL )";
			}
		}

		if ( rgar( $search_criteria, 'start_date' ) ) {
			if ( ! is_numeric( $search_criteria['start_date'] ) || (int) $search_criteria['start_date'] != $search_criteria['start_date'] ) {
				$search_criteria['start_date'] = strtotime( $search_criteria['start_date'] );
			}
			$valid_timestamp = gmdate( 'Y-m-d H:i:s', $search_criteria['start_date'] );
			$where[]         = $wpdb->prepare( 'timestampdiff(SECOND, %s, date_created) >= 0', $valid_timestamp );
		}

		if ( rgar( $search_criteria, 'end_date' ) ) {
			if ( ! is_numeric( $search_criteria['end_date'] ) || (int) $search_criteria['end_date'] != $search_criteria['end_date'] ) {
				$search_criteria['end_date'] = strtotime( $search_criteria['end_date'] );
			}
			$valid_timestamp = gmdate( 'Y-m-d H:i:s', $search_criteria['end_date'] );
			// The user didn't specify and end time, so search until the end of the day.
			if ( '00:00:00' == substr( $valid_timestamp, -8 ) ) {
				$valid_timestamp = gmdate( 'Y-m-d', $search_criteria['end_date'] ) . ' 23:59:59';
			}
			$where[] = $wpdb->prepare( 'timestampdiff(SECOND, %s, date_created) <= 0', $valid_timestamp );
		}

		$where = 'WHERE ' . implode( ' AND ', $where );

		if ( empty( $search_criteria ) ) {
			$where = '';
		}

		if ( is_array( $sorting ) && ! empty( $sorting ) ) {
			$sorting_options = array( 'entry_id', 'id', 'user_id', 'date_created', 'value', 'note_type', 'sub_type', 'user_name', 'user_email' );
			if ( ! isset( $sorting['key'] ) || ! in_array( $sorting['key'], $sorting_options ) ) {
				$sorting['key'] = 'n.id';
			}

			if ( 'id' == $sorting['key'] ) {
				$sorting['key'] = 'n.id';
			}
			$direction_options = array( 'ASC', 'DESC' );
			if ( ! isset( $sorting['direction'] ) || ! in_array( $sorting['direction'], $direction_options )  ) {
				$sorting['direction'] = 'ASC';
			}
			$orderby = 'ORDER BY ' . $sorting['key'] . ' ' . $sorting['direction'];
		} else {
			$orderby = 'ORDER BY n.id ASC';
		}

		$notes_table = self::get_entry_notes_table_name();

		return $wpdb->get_results(
			"  SELECT n.entry_id, n.id, n.user_id, n.date_created, n.value, n.note_type, n.sub_type, ifnull(u.display_name,n.user_name) as user_name, u.user_email
												FROM $notes_table n
												LEFT OUTER JOIN $wpdb->users u ON n.user_id = u.id
												$where 
												$orderby"
		);
	}

	public static function refresh_lead_field_value( $lead_id, $field_id ) {
		if ( version_compare( GFForms::$version, '2.3-dev', '>=' ) ) {
			_deprecated_function( 'GFFormsModel::refresh_lead_field_value', '2.3' );
		}
		$cache_key = 'GFFormsModel::get_lead_field_value_' . $lead_id . '_' . $field_id;
		GFCache::delete( $cache_key );
	}

	/**
	 * @param $lead
	 * @param $field GF_Field
	 *
	 * @return array|bool|mixed|string|null
	 */
	public static function get_lead_field_value( $lead, $field ) {

		if ( empty( $lead ) || ! is_array( $lead ) ) {
			return null;
		}

		$field_id = $field instanceof GF_Field ? $field->id : rgar( $field, 'id' );

		$value = array();

		$inputs = $field instanceof GF_Field ? $field->get_entry_inputs() : rgar( $field, 'inputs' );

		if ( is_array( $inputs ) ) {
			// making sure values submitted are sent in the value even if
			// there isn't an input associated with it
			$lead_field_keys = array_keys( $lead );
			natsort( $lead_field_keys );
			foreach ( $lead_field_keys as $input_id ) {
				if ( is_numeric( $input_id ) && absint( $input_id ) == absint( $field_id ) ) {
					$val = $lead[ $input_id ];
					$value[ $input_id ] = $val;
				}
			}
		} else {
			$value = rgget( $field_id, $lead );
		}

		// filtering lead value
		$value = apply_filters( 'gform_get_field_value', $value, $lead, $field );

		return $value;
	}

	/**
	 *
	 * @deprecated 2.0
	 * @param      $lead
	 * @param      $field_number
	 * @param      $form
	 * @param bool $apply_filter
	 *
	 * @return mixed|null|string
	 */
	public static function get_field_value_long( $lead, $field_number, $form, $apply_filter = true ) {
		_deprecated_function( 'get_field_value_long', '2.0', 'get_lead_field_value' );

		global $wpdb;
		$detail_table_name = self::get_lead_details_table_name();
		$long_table_name   = self::get_lead_details_long_table_name();

		$sql = $wpdb->prepare(
			" SELECT l.value FROM $detail_table_name d
                                INNER JOIN $long_table_name l ON l.lead_detail_id = d.id
                                WHERE lead_id=%d AND field_number BETWEEN %s AND %s", $lead['id'], doubleval( $field_number ) - 0.0001, doubleval( $field_number ) + 0.0001
		);

		$val = $wpdb->get_var( $sql );

		//running aform_get_input_value when needed
		if ( $apply_filter ) {
			$field    = RGFormsModel::get_field( $form, $field_number );
			$input_id = (string) $field_number == (string) $field->id ? '' : $field_number;
			$val      = gf_apply_filters( array( 'gform_get_input_value', $field->formId, $field->id, $input_id ), $val, $lead, $field, $input_id );
		}

		return $val;
	}

	/**
	 * @param $meta_key
	 * @param $meta_value
	 *
	 * @return array
	 */
	public static function get_leads_by_meta( $meta_key, $meta_value ) {
		return self::get_entries_by_meta( $meta_key, $meta_value );
	}

	/**
	 * Searches entries by entry meta
	 *
	 * @since 2.3
	 *
	 * @param $meta_key
	 * @param $meta_value
	 *
	 * @return array
	 */
	public static function get_entries_by_meta( $meta_key, $meta_value ) {
		if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
			return GF_Forms_Model_Legacy::get_leads_by_meta( $meta_key, $meta_value );
		}
		$args = array(
			'meta_key'     => $meta_key,
			'meta_value'   => $meta_value,
			'meta_compare' => '=',
		);
		$query = new GF_Query( $args );
		return $query->entries;
	}

	/**
	 *
	 * @deprecated 2.3
	 *
	 * @param $form_id
	 * @param int $sort_field_number
	 * @param string $sort_direction
	 * @param string $search
	 * @param int $offset
	 * @param int $page_size
	 * @param null $star
	 * @param null $read
	 * @param bool $is_numeric_sort
	 * @param null $start_date
	 * @param null $end_date
	 * @param string $status
	 * @param bool $payment_status
	 *
	 * @return mixed
	 */
	public static function get_leads( $form_id, $sort_field_number = 0, $sort_direction = 'DESC', $search = '', $offset = 0, $page_size = 30, $star = null, $read = null, $is_numeric_sort = false, $start_date = null, $end_date = null, $status = 'active', $payment_status = false ) {

		_deprecated_function( 'GFFormsModel::get_leads', '2.3', 'GFAPI::get_entries' );

		$search_criteria = array(
			'status' => $status,
		);

		if ( ! empty( $search ) ) {
			$search_criteria['field_filters'][] = array( 'value' => $search );
		}

		if ( ! is_null( $star ) ) {
			$search_criteria['field_filters'][] = array( 'is_starred' => $star );
		}

		if ( ! is_null( $read ) ) {
			$search_criteria['field_filters'][] = array( 'is_read' => $read );
		}

		if ( $payment_status ) {
			$search_criteria['field_filters'][] = array( 'payment_status' => $read );
		}

		$sorting = array(
			'key' => $sort_field_number,
			'direction' => $sort_direction
		);

		if ( $is_numeric_sort ) {
			$sorting['is_numeric'] = true;
		}

		$paging = array(
			'offset' => $offset,
			'page_size' => $page_size,
		);

		if ( ! is_null( $start_date ) ) {
			$search_criteria['start_date'] = $start_date;
		}

		if ( ! is_null( $end_date ) ) {
			$search_criteria['end_date'] = $end_date;
		}

		return GFAPI::get_entries( $form_id, $search_criteria, $sorting, $paging );
	}


	/**
	 *
	 * @deprecated 2.3
	 *
	 * @param $args
	 *
	 * @return string
	 */
	public static function get_leads_where_sql( $args ) {

		if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
			return GF_Forms_Model_Legacy::get_leads_where_sql( $args ) ;
		}

		return self::get_entries_where_sql( $args );
	}

	/**
	 * @deprecated 2.3
	 * @param $results
	 *
	 * @return array
	 */
	public static function build_lead_array( $results ) {
		return GF_Forms_Model_Legacy::build_lead_array( $results );
	}

	/***
	 * Saves the Gravity Forms license key to the database and registers the site and license key with the Gravity Forms licensing server.
	 *
	 * @since 1.0
	 *
	 * @param string $new_key Gravity Forms license key to be saved.
	 */
	public static function save_key( $new_key ) {

		$new_key      = trim( $new_key );
		$new_key_md5  = md5( $new_key );
		$previous_key = get_option( 'rg_gforms_key' );

		/**
		 * @var License\GF_License_API_Connector $license_connector
		 */
		$license_connector = GFForms::get_service_container()->get( License\GF_License_Service_Provider::LICENSE_API_CONNECTOR );
		$license_connector->clear_cache_for_key( $new_key_md5 );

		// Delete gform_version_info so GF will ping version.php to send site record update.
		delete_option( 'gform_version_info' );

		if ( empty( $new_key ) ) {

			if ( is_multisite() && is_main_site() ) {
				$sites = get_sites();
				foreach ( $sites as $site ) {
					delete_blog_option( $site->blog_id, 'rg_gforms_key' );
				}
			}

			delete_option( 'rg_gforms_key' );

			// Unlink the site with the license key on Gravity API.
			$license_connector->update_site_registration( '' );

		} elseif ( $previous_key != $new_key ) {
			self::update_license_key( $new_key_md5 );

			// Updating site registration with Gravity Server.
			$result = $license_connector->update_site_registration( $new_key_md5, true );

			// New key is invalid, revert to old key.
			if ( ! $result->can_be_used() ) {
				self::update_license_key( $previous_key );
			}
		} else {
			// Updating site registration with Gravity Server.
			$license_connector->update_site_registration( $new_key_md5, true );
		}

	}

	/**
	 * Use GFAPI::count_entries() instead.
	 *
	 * @deprecated 2.3.0.1
	 *
	 *
	 * @param $form_id
	 * @param $search
	 * @param null $star
	 * @param null $read
	 * @param null $start_date
	 * @param null $end_date
	 * @param null $status
	 * @param null $payment_status
	 *
	 * @return null|string
	 */
	public static function get_lead_count( $form_id, $search, $star = null, $read = null, $start_date = null, $end_date = null, $status = null, $payment_status = null ) {

		_deprecated_function( 'GFFormsModel::get_lead_count', '2.3.0.1', 'GFAPI::count_entries');

		global $wpdb;

		if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
			return GF_Forms_Model_Legacy::get_lead_count( $form_id, $search, $star, $read, $start_date, $end_date, $status, $payment_status ) ;
		}

		if ( ! is_numeric( $form_id ) ) {
			return '';
		}

		$entry_meta_table_name = self::get_entry_meta_table_name();
		$entry_table_name   = self::get_entry_table_name();

		$where = self::get_entries_where_sql( compact( 'form_id', 'search', 'status', 'star', 'read', 'start_date', 'end_date', 'payment_status', 'is_default' ) );

		$sql = "SELECT count(distinct l.id)
                FROM $entry_table_name l
                INNER JOIN $entry_meta_table_name ld ON l.id = ld.entry_id
                $where";

		return $wpdb->get_var( $sql );
	}

	/**
	 * Returns the WHERE clause for an entry search.
	 *
	 * This function is not used and is only included for backwards compatibility. Use GFAPI::count_entries() instead.
	 *
	 * @deprecated 2.3.0.1
	 *
	 * @since 2.3.0.1
	 *
	 * @param $args
	 *
	 * @return string
	 */
	public static function get_entries_where_sql( $args ) {

		_doing_it_wrong( 'GFFormsModel::get_entries_where_sql', 'Use GFAPI::count_entries instead', '2.3.0.1');

		global $wpdb;

		extract(
			wp_parse_args(
				$args, array(
					'form_id'        => false,
					'search'         => '',
					'status'         => 'active',
					'star'           => null,
					'read'           => null,
					'start_date'     => null,
					'end_date'       => null,
					'payment_status' => null,
					'is_default'     => true,
				)
			)
		);

		$where = array();

		if ( $is_default ) {
			$where[] = "l.form_id = $form_id";
		}

		if ( $search && $is_default ) {
			$where[] = $wpdb->prepare( 'meta_value LIKE %s', "%$search%" );
		} else if ( $search ) {
			$where[] = $wpdb->prepare( 'd.meta_value LIKE %s', "%$search%" );
		}

		if ( $star !== null && $status == 'active' ) {
			$where[] = $wpdb->prepare( "is_starred = %d AND status = 'active'", $star );
		}

		if ( $read !== null && $status == 'active' ) {
			$where[] = $wpdb->prepare( "is_read = %d AND status = 'active'", $read );
		}

		if ( $payment_status ) {
			$where[] = $wpdb->prepare( "payment_status = '%s'", $payment_status );
		}

		if ( $status !== null ) {
			$where[] = $wpdb->prepare( 'status = %s', $status );
		}

		if ( ! empty( $start_date ) ) {
			$where[] = "timestampdiff(SECOND, '$start_date', date_created) >= 0";
		}

		if ( ! empty( $end_date ) ) {
			$where[] = "timestampdiff(SECOND, '$end_date', date_created) <= 0";
		}

		return 'WHERE ' . implode( ' AND ', $where );
	}

	/**
	 *
	 *
	 * @param $form_id
	 * @param $search
	 * @param null $star
	 * @param null $read
	 * @param null $start_date
	 * @param null $end_date
	 * @param null $status
	 * @param null $payment_status
	 *
	 * @return array|string
	 */
	public static function get_lead_ids( $form_id, $search, $star = null, $read = null, $start_date = null, $end_date = null, $status = null, $payment_status = null ) {
		global $wpdb;

		if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
			return GF_Forms_Model_Legacy::get_lead_ids( $form_id, $search, $star, $read, $start_date, $end_date, $status, $payment_status ) ;
		}

		if ( ! is_numeric( $form_id ) ) {
			return '';
		}

		$entry_meta_table_name = self::get_entry_meta_table_name();
		$entry_table_name   = self::get_entry_table_name();

		$where = self::get_entries_where_sql( compact( 'form_id', 'search', 'status', 'star', 'read', 'start_date', 'end_date', 'payment_status', 'is_default' ) );

		$sql = "SELECT distinct l.id
                FROM $entry_table_name l
                INNER JOIN $entry_meta_table_name ld ON l.id = ld.entry_id
                $where";

		$rows = $wpdb->get_results( $sql );

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

		$entry_ids = array();

		foreach ( $rows as $row ) {
			$entry_ids[] = $row->id;
		}

		return $entry_ids;

	}

	public static function get_grid_columns( $form_id, $input_label_only = false ) {
		$form      = self::get_form_meta( $form_id );
		$field_ids = self::get_grid_column_meta( $form_id );

		if ( ! is_array( $field_ids ) ) {
			$field_ids = array();

			foreach ( $form['fields'] as $field ) {
				/* @var GF_Field $field */

				//loading post category fields with choices and inputs
				if ( $field->type == 'post_category' ) {
					$field = GFCommon::add_categories_as_choices( $field, '' );
				}

				if ( $field->displayOnly || $field->get_input_type() == 'list' || $field->get_input_type() == 'repeater' ) {
					continue;
				}

				$inputs = $field->get_entry_inputs();
				if ( is_array( $inputs ) ) {
					if ( $field->type == 'name' ) {
						$field_ids[] = $field->id . '.3'; //adding first name
						$field_ids[] = $field->id . '.6'; //adding last name
					} else {
						foreach ( $inputs as $input ) {
							if ( rgar( $input, 'isHidden' ) ) {
								continue;
							}

							$field_ids[] = $input['id']; //getting first input
							break;
						}
					}
				} else {
					$field_ids[] = $field->id;
				}

				if ( count( $field_ids ) >= 5 ) {
					break;
				}
			}
			//adding default entry meta columns
			$entry_metas = GFFormsModel::get_entry_meta( $form_id );
			foreach ( $entry_metas as $key => $entry_meta ) {
				if ( rgar( $entry_meta, 'is_default_column' ) ) {
					$field_ids[] = $key;
				}
			}
		}

		$columns    = array();
		$entry_meta = self::get_entry_meta( $form_id );
		foreach ( $field_ids as $field_id ) {

			switch ( $field_id ) {
				case 'id' :
					$columns[ $field_id ] = array( 'label' => esc_html__( 'Entry Id', 'gravityforms' ), 'type' => 'id' );
					break;
				case 'ip' :
					$columns[ $field_id ] = array( 'label' => esc_html__( 'User IP', 'gravityforms' ), 'type' => 'ip' );
					break;
				case 'date_created' :
					$columns[ $field_id ] = array( 'label' => esc_html__( 'Entry Date', 'gravityforms' ), 'type' => 'date_created' );
					break;
				case 'source_url' :
					$columns[ $field_id ] = array( 'label' => esc_html__( 'Source Url', 'gravityforms' ), 'type' => 'source_url' );
					break;
				case 'payment_status' :
					$columns[ $field_id ] = array( 'label' => esc_html__( 'Payment Status', 'gravityforms' ), 'type' => 'payment_status' );
					break;
				case 'transaction_id' :
					$columns[ $field_id ] = array( 'label' => esc_html__( 'Transaction Id', 'gravityforms' ), 'type' => 'transaction_id' );
					break;
				case 'payment_date' :
					$columns[ $field_id ] = array( 'label' => esc_html__( 'Payment Date', 'gravityforms' ), 'type' => 'payment_date' );
					break;
				case 'payment_amount' :
					$columns[ $field_id ] = array( 'label' => esc_html__( 'Payment Amount', 'gravityforms' ), 'type' => 'payment_amount' );
					break;
				case 'created_by' :
					$columns[ $field_id ] = array( 'label' => esc_html__( 'User', 'gravityforms' ), 'type' => 'created_by' );
					break;
				case ( ( is_string( $field_id ) || is_int( $field_id ) ) && array_key_exists( $field_id, $entry_meta ) ) :
					$columns[ $field_id ] = array( 'label' => $entry_meta[ $field_id ]['label'], 'type' => $field_id );
					break;
				default :
					$field = self::get_field( $form, $field_id );
					if ( $field ) {
						if ( $field->type === 'consent' ) {
							if ( false !== strpos( $field_id, '.1' ) ) {
								$columns[ strval( $field_id ) ] = array( 'label'     => self::get_label( $field, $field_id, false ),
								                                         'type'      => $field->type,
								                                         'inputType' => $field->inputType
								);
							}
						} else {
							$input_label_only               = apply_filters( 'gform_entry_list_column_input_label_only', $input_label_only, $form, $field );
							$columns[ strval( $field_id ) ] = array( 'label'     => self::get_label( $field, $field_id, $input_label_only ),
							                                         'type'      => $field->type,
							                                         'inputType' => $field->inputType
							);
						}
					}
			}
		}

		return $columns;
	}

	/**
	 * @param GF_Field $field
	 * @param int $input_id
	 * @param bool $input_only
	 *
	 * @return string
	 */
	public static function get_label( $field, $input_id = 0, $input_only = false, $allow_admin_label = true ) {
		if ( ! $field instanceof GF_Field ) {
			$field = GF_Fields::create( $field );
		}

		$field_label = ( GFForms::get_page() ||
		                 RG_CURRENT_PAGE == 'select_columns.php' ||
		                 RG_CURRENT_PAGE == 'print-entry.php' ||
		                 rgget( 'gf_page', $_GET ) == 'select_columns' ||
		                 rgget( 'gf_page', $_GET ) == 'print-entry' ||
		                 $field->get_context_property( 'use_admin_label' )
		               ) && ! empty( $field->adminLabel ) && $allow_admin_label ? $field->adminLabel : $field->label;

		$input = self::get_input( $field, $input_id );

		if ( self::get_input_type( $field ) == 'checkbox' && $input != null ) {
			return $input['label'];
		} else if ( $input != null ) {
			if ( self::get_input_type( $field ) === 'consent' &&
			     ( RG_CURRENT_PAGE == 'select_columns.php' ||
			       RG_CURRENT_PAGE == 'print-entry.php' ||
			       rgget( 'gf_page', $_GET ) == 'select_columns' ||
			       rgget( 'gf_page', $_GET ) == 'print-entry' ||
			       GFForms::get_page() === 'entry_list'
			     ) ) {
				return $field_label;
			}

			$input_label = rgar( $input, 'customLabel', rgar( $input, 'label' ) );

			return $input_only ? $input_label : $field_label . ' (' . $input_label . ')';
		} else {
			return $field_label;
		}
	}

	/**
	 * Get the text that tells the user that the field is required.
	 *
	 * @since 2.5
	 *
	 * @param $form_id
	 *
	 * @return string HTML required indicator.
	 */
	public static function get_required_indicator( $form_id ) {
		$meta = self::get_form_meta( $form_id );
		$required_indicator = rgar( $meta, 'requiredIndicator' );

		switch( $required_indicator ) {
			case 'text':
				$indicator       = esc_html__( '(Required)', 'gravityforms' );
				$indicator_class = 'gfield_required_text';
				break;
			case 'asterisk':
				$indicator       = '*';
				$indicator_class = 'gfield_required_asterisk';
				break;
			case 'custom':
				$indicator       = rgar( $meta, 'customRequiredIndicator' ) ? $meta['customRequiredIndicator'] : esc_html__( '(Required)', 'gravityforms' );
				$indicator_class = 'gfield_required_custom';
				break;
			default:
				$legacy_markup   = GFCommon::is_legacy_markup_enabled( $meta );
				$indicator       = $legacy_markup ? '*' : esc_html__( '(Required)', 'gravityforms' );
				$indicator_class = $legacy_markup ? 'gfield_required_asterisk' : 'gfield_required_text';
				break;
		}

		return '<span class="gfield_required ' . $indicator_class . '">' . $indicator . '</span>';
	}

	/**
	 * @param GF_Field $field
	 * @param          $id
	 *
	 * @return null
	 */
	public static function get_input( $field, $id ) {
		if ( is_array( $field->inputs ) ) {
			foreach ( $field->inputs as $input ) {
				if ( $input['id'] == $id ) {
					return $input;
				}
			}
		}

		return null;
	}

	public static function has_input( $field, $input_id ) {
		if ( ! is_array( $field->inputs ) ) {
			return false;
		} else {
			foreach ( $field->inputs as $input ) {
				if ( $input['id'] == $input_id ) {
					return true;
				}
			}

			return false;
		}
	}

	public static function get_current_page_url( $force_ssl = false ) {
		$pageURL = 'http';
		if ( RGForms::get( 'HTTPS', $_SERVER ) == 'on' || $force_ssl ) {
			$pageURL .= 's';
		}
		$pageURL .= '://';

		$pageURL .= RGForms::get( 'HTTP_HOST', $_SERVER ) . rgget( 'REQUEST_URI', $_SERVER );

		return $pageURL;
	}

	public static function get_submitted_fields( $form_id ) {
		global $wpdb;

		if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
			return GF_Forms_Model_Legacy::get_submitted_fields( $form_id );
		}

		$entry_meta_table_name = self::get_entry_meta_table_name();
		$field_list             = '';
		$fields                 = $wpdb->get_results( $wpdb->prepare( "SELECT DISTINCT meta_key FROM $entry_meta_table_name WHERE form_id=%d", $form_id ) );
		foreach ( $fields as $field ) {
			$field_list .= intval( $field->meta_key ) . ',';
		}

		if ( ! empty( $field_list ) ) {
			$field_list = substr( $field_list, 0, strlen( $field_list ) - 1 );
		}

		return $field_list;
	}

	/**
	 * Returns the field object for the requested field or input ID from the supplied or specified form.
	 *
	 * @since  2.3 Updated to support being passed the form id or form object as the first parameter.
	 * @since  unknown.
	 * @access public
	 *
	 * @param array|int  $form_or_id The Form Object or ID.
	 * @param string|int $field_id   The field or input ID.
	 *
	 * @return GF_Field|null
	 */
	public static function get_field( $form_or_id, $field_id ) {
		$form = is_numeric( $form_or_id ) ? self::get_form_meta( $form_or_id ) : $form_or_id;

		if ( ! isset( $form['fields'] ) || ! isset( $form['id'] ) || ! is_array( $form['fields'] ) ) {
			return null;
		}

		if ( is_numeric( $field_id ) ) {
			// Removing floating part of field (i.e 1.3 -> 1) to return field by input id.
			$field_id = intval( $field_id );
		}

		global $_fields;
		$key = $form['id'] . '_' . $field_id;
		$return = null;
		if (isset( $_fields[ $key ] ) ) {
			return $_fields[ $key ];
		}

		$_fields[ $key ] = null;
		foreach ( $form['fields'] as $field ) {
			if ( $field->id == $field_id ) {
				$_fields[ $key ] = $field;
				$return          = $field;
				break;
			} elseif ( is_array( $field->fields ) ) {
				$sub_field = self::get_sub_field( $field, $field_id );
				if ( $sub_field ) {
					$_fields[ $key ] = $sub_field;
					return $sub_field;
				}

			}
		}

		return $return;
	}

	/**
	 * Returns the field inside a repeater field with the specified ID.
	 *
	 * @since 2.4
	 *
	 * @param GF_Field_Repeater $repeater_field The repeater field.
	 * @param int $field_id                     The field ID.
	 *
	 * @return null|GF_Field
	 */
	public static function get_sub_field( $repeater_field, $field_id ) {
		if ( is_array( $repeater_field->fields ) ) {
			foreach ( $repeater_field->fields as $field ) {
				if ( $field->id == $field_id ) {
					return $field;
				} elseif ( is_array( $field->fields ) ) {
					$f = self::get_sub_field( $field, $field_id );
					if ( $f ) {
						return $f;
					}
				}
			}
		}
		return null;
	}

	public static function is_html5_enabled() {
		return get_option( 'rg_gforms_enable_html5', false );
	}

	/**
	 * Return the current lead being processed. Should only be called when a form has been submitted.
	 * If called before the "real" lead has been saved to the database, uses self::create_lead() to create
	 * a temporary lead to work with.
	 *
	 * @since Unknown
	 * @since 2.7.1 Added the optional $form arg.
	 *
	 * @param array $form The form being processed or an empty array to get the form based on the ID from the gform_submit input.
	 *
	 * @return false|array
	 */
	public static function get_current_lead( $form = array() ) {
		$form_id = absint( rgpost( 'gform_submit' ) );

		// If a GF submission is not in process, always return false.
		if ( empty( $form_id ) ) {
			return false;
		}

		if ( rgar( self::$_current_lead, 'form_id' ) != $form_id ) {
			if ( absint( rgar( $form, 'id' ) ) !== $form_id ) {
				$form = self::get_form_meta( $form_id );
			}
			self::$_current_lead = self::create_lead( $form );
		}

		return self::$_current_lead;
	}

	/**
	 * Set RGFormsModel::$lead for use in hooks where $lead is not explicitly passed.
	 *
	 * @param mixed $lead
	 */
	public static function set_current_lead( $lead ) {
		GFCache::flush();
		self::$_current_lead = $lead;
	}

	/**
	 * Converts the legacy confirmation from forms created prior to v1.7 to the current format or adds the default confirmation.
	 *
	 * @since 1.7
	 * @since 2.4.15 Fixed corrupt confirmation being created when form doesn't have one to convert.
	 *
	 * @param array $form The form being processed.
	 *
	 * @return array
	 */
	public static function convert_confirmation( $form ) {
		$confirmation = rgar( $form, 'confirmation' );

		if ( ! empty( $confirmation['type'] ) ) {
			// Complete converting legacy confirmation by adding missing properties.
			$confirmation['id']        = uniqid();
			$confirmation['name']      = esc_html__( 'Default Confirmation', 'gravityforms' );
			$confirmation['isDefault'] = true;
		} else {
			// Form does not have a valid legacy confirmation add the default confirmation instead.
			$confirmation = self::get_default_confirmation();
		}

		$form['confirmations'] = array( $confirmation['id'] => $confirmation );

		self::save_form_confirmations( $form['id'], $form['confirmations'] );

		return $form;
	}

	/**
	 * Returns a default confirmation.
	 *
	 * @since 2.4.15
	 *
	 * @param string $event The confirmation event. form_saved, form_save_email_sent, or an empty string for the default form submission event.
	 *
	 * @return array
	 */
	public static function get_default_confirmation( $event = '' ) {
		switch ( $event ) {
			case 'form_saved':
				return array(
					'id'          => uniqid( 'sc1' ),
					'event'       => 'form_saved',
					'name'        => __( 'Save and Continue Confirmation', 'gravityforms' ),
					'isDefault'   => true,
					'type'        => 'message',
					'message'     => sprintf(
						'<h2>%s</h2><p role="alert">%s</p><p class="resume_form_link_wrapper">{save_link}</p><p>%s<br />%s</p>{save_email_input}',
						__( 'Link to continue editing later', 'gravityforms' ),
						__( 'Please use the following link to return and complete this form from any computer.', 'gravityforms' ),
						__( 'Note: This link will expire after 30 days.', 'gravityforms' ),
						__( 'Enter your email address if you would like to receive the link via email.', 'gravityforms' )
					),
					'url'         => '',
					'pageId'      => '',
					'queryString' => '',
				);

			case 'form_save_email_sent':
				return array(
					'id'          => uniqid( 'sc2' ),
					'event'       => 'form_save_email_sent',
					'name'        => __( 'Save and Continue Email Sent Confirmation', 'gravityforms' ),
					'isDefault'   => true,
					'type'        => 'message',
					'message'     => sprintf(
						'<h2 class="saved_message_success">%s</h2><p>%s <span class="saved_message_email">{save_email}</span></p>',
						__( 'Success!', 'gravityforms' ),
						__( 'The link was sent to the following email address:', 'gravityforms' )
					),
					'url'         => '',
					'pageId'      => '',
					'queryString' => '',
				);

			default:
				return array(
					'id'          => uniqid(),
					'name'        => __( 'Default Confirmation', 'gravityforms' ),
					'isDefault'   => true,
					'type'        => 'message',
					'message'     => __( 'Thanks for contacting us! We will get in touch with you shortly.', 'gravityforms' ),
					'url'         => '',
					'pageId'      => '',
					'queryString' => '',
				);
		}
	}

	public static function load_confirmations( $form ) {

		$confirmations = self::get_form_confirmations( $form['id'] );

		// If there are no confirmations convert the legacy confirmation or add the default.
		if ( empty( $confirmations ) ) {
			$form = self::convert_confirmation( $form );
		} else {
			$form['confirmations'] = $confirmations;
		}

		return $form;
	}

	public static function get_form_confirmations( $form_id ) {
		global $wpdb;

		$key = self::get_form_cache_key( $form_id );

		if ( isset( self::$_confirmations[ $key ] ) ) {
			return self::$_confirmations[ $key ];
		}

		$tablename     = GFFormsModel::get_meta_table_name();
		$sql           = $wpdb->prepare( "SELECT confirmations FROM $tablename WHERE form_id = %d", $form_id );
		$results       = $wpdb->get_results( $sql, ARRAY_A );
		$confirmations = rgars( $results, '0/confirmations', array() );

		if ( ! empty( $confirmations ) ) {
			$confirmations = self::remove_corrupt_confirmations( self::unserialize( $confirmations ) );
		}

		self::$_confirmations[ $key ] = $confirmations;

		return self::$_confirmations[ $key ];
	}

	/**
	 * Delete a form confirmation by ID.
	 *
	 * @since  2.5
	 *
	 * @param string    $confirmation_id The confirmation to be deleted.
	 * @param int|array $form_id         The form ID or Form Object form the confirmation being deleted.
	 *
	 * @return false|int The result of the database operation.
	 */
	public static function delete_form_confirmation( $confirmation_id, $form_id ) {

		// If no Form ID or object was provided, exit.
		if ( ! $form_id ) {
			return false;
		}

		// Get Form object if only ID was provided.
		$form = ! is_array( $form_id ) ? self::get_form_meta( $form_id ) : $form_id;

		/**
		 * Fires right before a confirmation is deleted.
		 *
		 * @since 1.9
		 *
		 * @param int   $form ['confirmations'][$confirmation_id] The ID of the confirmation being deleted.
		 * @param array $form The Form object.
		 */
		do_action( 'gform_pre_confirmation_deleted', $form['confirmations'][ $confirmation_id ], $form );

		unset( $form['confirmations'][ $confirmation_id ] );

		// Clear form cache so next retrieval of form meta will reflect deleted confirmation.
		self::flush_current_form( self::get_form_cache_key( $form_id ) );

		return self::save_form_confirmations( $form['id'], $form['confirmations'] );

	}

	/**
	 * Remove corrupt confirmations created by old versions of GFFormsModel::convert_confirmation().
	 *
	 * @since 2.4.15
	 *
	 * @param mixed $confirmations The confirmations to be processed.
	 *
	 * @return array
	 */
	public static function remove_corrupt_confirmations( $confirmations ) {
		if ( ! is_array( $confirmations ) ) {
			return array();
		}

		foreach ( $confirmations as $id => $confirmation ) {
			if ( is_array( $confirmation ) && ! empty( $confirmation['type'] ) ) {
				continue;
			}

			unset( $confirmations[ $id ] );
		}

		return $confirmations;
	}

	public static function save_form_confirmations( $form_id, $confirmations ) {
		return self::update_form_meta( $form_id, $confirmations, 'confirmations' );
	}

	public static function save_form_notifications( $form_id, $notifications ) {
		return self::update_form_meta( $form_id, $notifications, 'notifications' );
	}

	/**
	 * Returns the ids of the specified forms.
	 *
	 * @since unknown
	 * @since 2.5     Added $sort_column and $sort_dir parameters.
	 * @since 2.5.8   Added support for passing null for the $active and $trash args.
	 *
	 * @param bool|null $active      True if active forms are returned. False to get inactive forms. Null to ignore the is_active property. Defaults to true.
	 * @param bool|null $trash       True if trashed forms are returned. False to exclude trash. Null to ignore the is_trash property. Defaults to false.
	 * @param string    $sort_column The column to sort the results on.
	 * @param string    $sort_dir    The sort direction, ASC or DESC.
	 *
	 * @return array of form IDs.
	 */
	public static function get_form_ids( $active = true, $trash = false, $sort_column = 'id', $sort_dir = 'ASC' ) {
		global $wpdb;

		$sql   = 'SELECT id FROM ' . self::get_form_table_name();
		$where = array();

		if ( null !== $active ) {
			$where[] = $wpdb->prepare( 'is_active=%d', $active );
		}

		if ( null !== $trash ) {
			$where[] = $wpdb->prepare( 'is_trash=%d', $trash );
		}

		if ( ! empty( $where ) ) {
			$sql .= ' WHERE ' . join( ' AND ', $where );
		}

		if ( ! in_array( strtolower( $sort_column ), GFFormsModel::get_form_db_columns() ) ) {
			$sort_column = 'id';
		}

		if ( ! empty( $sort_column ) ) {
			$sql .= " ORDER BY $sort_column " . ( $sort_dir == 'ASC' ? 'ASC' : 'DESC' );
		}

		return $wpdb->get_col( $sql );
	}

	/**
	 * Get forms and return any form column(s).
	 *
	 * @see GFFormsModel::get_form_ids()
	 * @since 2.7.5
	 *
	 * @param bool   $active      True if active forms are returned. False to get inactive forms. Defaults to true.
	 * @param bool   $trash       True if trashed forms are returned. False to exclude trash. Defaults to false.
	 * @param string $sort_column The column to sort the results on.
	 * @param string $sort_dir    The sort direction, ASC or DESC.
	 * @param array  $columns     The columns to return. Defaults to ['id']. Other options are 'title', 'date_created', 'is_active', 'is_trash'. 'date_updated' may be supported, depending on the Gravity Forms version.
	 *
	 * @return array Forms indexed from 0 by SQL result row number. Each row is an associative array (column => value).
	 */
	public static function get_forms_columns( $active = true, $trash = false, $sort_column = 'id', $sort_dir = 'ASC', $columns = array( 'id' ) ) {
		global $wpdb;

		// Only allow valid columns.
		$columns = array_intersect( $columns, GFFormsModel::get_form_db_columns() );

		$sql   = 'SELECT ' . implode( ', ', $columns ) . ' FROM ' . GFFormsModel::get_form_table_name();
		$where = array();

		if ( null !== $active ) {
			$where[] = $wpdb->prepare( 'is_active=%d', $active );
		}

		if ( null !== $trash ) {
			$where[] = $wpdb->prepare( 'is_trash=%d', $trash );
		}

		if ( ! empty( $where ) ) {
			$sql .= ' WHERE ' . join( ' AND ', $where );
		}

		if ( ! in_array( strtolower( $sort_column ), GFFormsModel::get_form_db_columns() ) ) {
			$sort_column = 'id';
		}

		if ( ! empty( $sort_column ) ) {
			$sql .= " ORDER BY $sort_column " . ( $sort_dir == 'ASC' ? 'ASC' : 'DESC' );
		}

		return $wpdb->get_results( $sql, ARRAY_A );
	}

	public static function get_entry_meta( $form_ids ) {
		global $_entry_meta;

		if ( $form_ids == 0 ) {
			$form_ids = self::get_form_ids();
		}

		if ( ! is_array( $form_ids ) ) {
			$form_ids = array( $form_ids );
		}
		$meta = array();
		foreach ( $form_ids as $form_id ) {
			if ( ! isset( $_entry_meta[ $form_id ] ) ) {
				$_entry_meta           = array();
				$_entry_meta[ $form_id ] = apply_filters( 'gform_entry_meta', array(), $form_id );
			}
			$meta = array_merge( $meta, $_entry_meta[ $form_id ] );
		}

		return $meta;
	}

	public static function set_entry_meta( $lead, $form ) {
		GFCommon::timer_start( __METHOD__ );
		$entry_id   = absint( rgar( $lead, 'id' ) );
		$entry_meta = self::get_entry_meta( $form['id'] );
		$keys       = array_keys( $entry_meta );
		foreach ( $keys as $key ) {
			if ( isset( $entry_meta[ $key ]['update_entry_meta_callback'] ) ) {
				$callback = $entry_meta[ $key ]['update_entry_meta_callback'];
				$value    = call_user_func_array( $callback, array( $key, $lead, $form ) );
				gform_update_meta( $entry_id, $key, $value );
				$lead[ $key ] = $value;
			}
		}

		GFCommon::log_debug( __METHOD__ . sprintf( '(): Saving meta for entry (#%d) completed in %F seconds.', $entry_id, GFCommon::timer_end( __METHOD__ ) ) );

		return $lead;
	}

	/**
	 *
	 * @param $form_id
	 * @param array $search_criteria
	 * @param null $sorting
	 * @param null $paging
	 *
	 * @return array
	 */
	public static function search_leads( $form_id, $search_criteria = array(), $sorting = null, $paging = null ) {
		return GFAPI::get_entries( $form_id, $search_criteria, $sorting, $paging );
	}


	public static function search_lead_ids( $form_id, $search_criteria = array() ) {
		return GFAPI::get_entry_ids( $form_id, $search_criteria );
	}

	/**
	 * Returns the gf_entry table field names.
	 *
	 * @since 2.3.2.13 Added date_updated.
	 * @since unknown
	 *
	 * @return array
	 */
	public static function get_lead_db_columns() {
		return array(
			'id',
			'form_id',
			'post_id',
			'date_created',
			'date_updated',
			'is_starred',
			'is_read',
			'ip',
			'source_url',
			'user_agent',
			'currency',
			'payment_status',
			'payment_date',
			'payment_amount',
			'payment_method',
			'transaction_id',
			'is_fulfilled',
			'created_by',
			'transaction_type',
			'status',
		);
	}

	/**
	 *
	 * @param $form_id
	 * @param array $search_criteria
	 *
	 * @return null|string
	 */
	public static function count_search_leads( $form_id, $search_criteria = array() ) {
		return GFAPI::count_entries( $form_id, $search_criteria );
	}

	/**
	 * Returns the lead (entry) count for all forms.
	 *
	 * @param string $status
	 *
	 * @return null|string
	 */
	public static function get_lead_count_all_forms( $status = 'active' ) {
		return self::get_entry_count_all_forms( $status );
	}

	/**
	 * Returns the entry count for all forms.
	 *
	 * @param string $status
	 *
	 * @return null|string
	 */
	public static function get_entry_count_all_forms( $status = 'active' ) {
		global $wpdb;

		if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
			return GF_Forms_Model_Legacy::get_lead_count_all_forms( $status );
		}

		$entry_table_name   = self::get_entry_table_name();

		if ( ! GFCommon::table_exists( $entry_table_name ) ) {
			return 0;
		}

		$sql = $wpdb->prepare( "SELECT count(id)
								FROM $entry_table_name
								WHERE status=%s", $status );

		return $wpdb->get_var( $sql );
	}

	public static function get_entry_meta_counts() {
		global $wpdb;


		if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
			return GF_Forms_Model_Legacy::get_entry_meta_counts();
		}

		$meta_table_name = self::get_entry_meta_table_name();
		$notes_table_name = self::get_entry_notes_table_name();

		if ( ! GFCommon::table_exists( $meta_table_name ) ) {
			return array(
				'details' => 0,
				'meta'    => 0,
				'notes'   => 0,
			);
		}

		$results = $wpdb->get_results(
			"
            SELECT
            (SELECT count(0) FROM $meta_table_name) as meta,
            (SELECT count(0) FROM $notes_table_name) as notes
            "
		);

		return array(
			'details' => intval( $results[0]->meta ),
			'meta'    => intval( $results[0]->meta ),
			'notes'   => intval( $results[0]->notes ),
		);

	}

	/**
	 * @deprecated 2.2 Use gf_upgrade()->dbDelta() instead
	 */
	public static function dbDelta( $sql ) {
		_deprecated_function( 'dbDelta', '2.2', 'gf_upgrade()->dbDelta()' );

		gf_upgrade()->dbDelta( $sql );

	}

	public static function get_db_charset() {
		global $wpdb;

		if ( ! empty( $wpdb->charset ) ) {
			$charset_collate = "DEFAULT CHARACTER SET $wpdb->charset";
		}

		if ( ! empty( $wpdb->collate ) ) {
			$charset_collate .= " COLLATE $wpdb->collate";
		}

		return $charset_collate;
	}

	public static function is_valid_table( $table_name ){
		global $wpdb;

		$tables = array(
			GFFormsModel::get_form_table_name(),
			GFFormsModel::get_form_view_table_name(),
			GFFormsModel::get_meta_table_name(),
			GFFormsModel::get_lead_table_name(),
			GFFormsModel::get_lead_notes_table_name(),
			GFFormsModel::get_lead_details_table_name(),
			GFFormsModel::get_lead_details_long_table_name(),
			GFFormsModel::get_lead_meta_table_name(),
			GFFormsModel::get_incomplete_submissions_table_name(),
			self::get_addon_feed_table_name(),
			"{$wpdb->prefix}gf_addon_payment_transaction",
			"{$wpdb->prefix}gf_addon_payment_callback",

			GFFormsModel::get_entry_table_name(),
			GFFormsModel::get_entry_notes_table_name(),
			GFFormsModel::get_entry_meta_table_name(),
			GFFormsModel::get_draft_submissions_table_name(),
		);

		return in_array( $table_name, $tables );
	}

	public static function is_valid_index( $index_name ){

		$indexes = array(
			'id',
			'form_id',
			'status',
			'lead_id',
			'lead_user_key',
			'lead_field_number',
			'lead_detail_id',
			'lead_detail_key',
			'meta_key',
			'form_id_meta_key',
			'uuid',
			'transaction_type',
			'type_lead',
			'slug_callback_id',
			'addon_slug_callback_id',
			'addon_form',
		);

		return in_array( $index_name, $indexes );
	}

	/**
	 * Trims values inside choice texts, choice values, input labels, field labels and field conditionalLogic
	 *
	 * @param array $form         Form object.
	 * @param bool  $form_updated Output parameter.
	 *
	 * @return array $form
	 */
	public static function trim_form_meta_values( $form, &$form_updated = false ) {
		$form_id = $form['id'];
		GFCommon::log_debug( 'GFFormsModel::trim_form_meta_values(): Starting.' );
		if ( isset( $form['fields'] ) && is_array( $form['fields'] ) ) {
			foreach ( $form['fields'] as &$field ) {
				$trim_value = apply_filters( 'gform_trim_input_value', true, $form_id, $field );
				if ( ! $trim_value ) {
					continue;
				}

				if ( isset( $field->label ) && $field->label != trim( $field->label ) ) {
					$field->label = trim( $field->label );
					$form_updated = true;
				}
				if ( is_array( $field->choices ) ) {
					foreach ( $field->choices as &$choice ) {
						if ( isset( $choice['text'] ) && $choice['text'] != trim( $choice['text'] ) ) {
							$choice['text'] = trim( $choice['text'] );
							$form_updated   = true;
						}
						if ( isset( $choice['value'] ) && $choice['value'] != trim( $choice['value'] ) ) {
							$choice['value'] = trim( $choice['value'] );
							$form_updated    = true;
						}
					}
				}
				if ( is_array( $field->inputs ) ) {
					foreach ( $field->inputs as &$input ) {
						if ( isset( $input['label'] ) && $input['label'] != trim( $input['label'] ) ) {
							$input['label'] = trim( $input['label'] );
							$form_updated   = true;
						}
					}
				}
			}
			$form['fields'] = GFFormsModel::trim_conditional_logic_values( $form['fields'], $form, $form_updated );
		}
		if ( $form_updated ) {
			GFCommon::log_debug( 'GFFormsModel::trim_form_meta_values(): Form values trimmed.' );
		}

		return $form;
	}

	/**
	 * Trims values from an array of elements e.g. notifications and confirmations
	 *
	 * @param array $meta_array Form object.
	 * @param array $form       Form object.
	 * @param bool  $updated    Output parameter.
	 *
	 * @return array $meta_array
	 */
	public static function trim_conditional_logic_values( $meta_array, $form, &$updated = false ) {
		GFCommon::log_debug( 'GFFormsModel::trim_conditional_logic_values(): Starting.' );
		if ( is_array( $meta_array ) ) {
			foreach ( $meta_array as &$meta ) {
				$meta = self::trim_conditional_logic_values_from_element( $meta, $form, $updated );
			}
		}
		if ( $updated ) {
			GFCommon::log_debug( 'GFFormsModel::trim_conditional_logic_values(): Conditional logic values trimmed.' );
		}

		return $meta_array;
	}

	/**
	 * Trims values from elements e.g. fields, notifications and confirmations
	 *
	 * @param array $element Form object.
	 * @param array $form    Form object.
	 * @param bool  $updated Output parameter.
	 *
	 * @return array $element
	 */
	public static function trim_conditional_logic_values_from_element( $element, $form = array(), &$updated = false ) {
		if ( $element instanceof GF_Field ) {

			/* @var GF_Field $element */
			if ( is_array( $element->conditionalLogic ) && isset( $element->conditionalLogic['rules'] ) && is_array( $element->conditionalLogic['rules'] ) ) {
				foreach ( $element->conditionalLogic['rules'] as &$rule ) {
					$value = (string) $rule['value'];
					if ( $value !== trim( $value ) ) {
						$field      = isset( $form['fields'] ) ? GFFormsModel::get_field( $form, $rule['fieldId'] ) : array();
						$trim_value = apply_filters( 'gform_trim_input_value', true, rgar( $form, 'id' ), $field );
						if ( $trim_value ) {
							$rule['value'] = trim( $rule['value'] );
							$updated       = true;
						}
					}
				}
			}
		} else {
			if ( isset( $element['conditionalLogic'] ) && is_array( $element['conditionalLogic'] ) && isset( $element['conditionalLogic']['rules'] ) && is_array( $element['conditionalLogic']['rules'] ) ) {
				foreach ( $element['conditionalLogic']['rules'] as &$rule ) {
					$value = (string) rgar( $rule, 'value' );
					if ( $value !== trim( $value ) ) {
						$field      = isset( $form['fields'] ) ? GFFormsModel::get_field( $form, $rule['fieldId'] ) : array();
						$trim_value = apply_filters( 'gform_trim_input_value', true, rgar( $form, 'id' ), $field );
						if ( $trim_value ) {
							$rule['value'] = trim( $rule['value'] );
							$updated       = true;
						}
					}
				}
			}
		}

		return $element;
	}

	/**
	 * Returns an array of field IDs that have been encrypted using GFCommon::encrypt()
	 *
	 * @deprecated
	 *
	 * @since unknown
	 *
	 * @param $entry_id
	 *
	 * @return array|bool|mixed
	 */
	public static function get_encrypted_fields( $entry_id ) {

		_deprecated_function( 'GFCommon:get_encrypted_fields', '2.3', 'GFCommon:get_openssl_encrypted_fields' );

		$encrypted_fields = gform_get_meta( $entry_id, '_encrypted_fields' );

		if ( empty( $encrypted_fields ) ) {
			$encrypted_fields = array();
		}

		return $encrypted_fields;
	}

	/**
	 * Stores the field IDs that have been encrypted using GFCommon::encrypt()
	 *
	 * @deprecated
	 *
	 * @since unknown
	 *
	 * @param $entry_id
	 * @param $field_ids
	 *
	 * @return bool
	 */
	public static function set_encrypted_fields( $entry_id, $field_ids ) {

		_deprecated_function( 'GFCommon:set_encrypted_fields', '2.3', 'GFCommon:set_openssl_encrypted_fields' );

		if ( ! is_array( $field_ids ) ) {
			$field_ids = array( $field_ids );
		}

		$encrypted_fields = array_merge( self::get_encrypted_fields( $entry_id ), $field_ids );

		gform_update_meta( $entry_id, '_encrypted_fields', $encrypted_fields );

		return true;
	}

	/**
	 * Checks whether the given field was encrypted using GFCommon::encrpyt() and registered using GFCommon::set_encrypted_fields()
	 *
	 * @deprecated
	 *
	 * @since unknown
	 *
	 * @param $entry_id
	 * @param $field_id
	 *
	 * @return bool|mixed|void
	 */
	public static function is_encrypted_field( $entry_id, $field_id ) {

		_deprecated_function( 'GFCommon:is_encrypted_field', '2.3', 'GFCommon:is_openssl_encrypted_field' );

		/**
		 * Determines if an entry field is stored encrypted. Use this hook to change the default behavior of decrypting fields that have been encrypted or to completely disable the
		 * process if checking for encrypted fields.
		 *
		 * @param int $entry_id The current Entry ID
		 * @param int $field_id The current Field ID.
		 */
		$is_encrypted = apply_filters('gform_is_encrypted_field', '', $entry_id, $field_id );
		if (  $is_encrypted !== '' ){
			return $is_encrypted;
		}

		$encrypted_fields = self::get_encrypted_fields( $entry_id );

		return in_array( $field_id, $encrypted_fields );
	}

	/**
	 * Returns an array of field IDs that have been encrypted using GFCommon::openssl_encrypt()
	 *
	 * @since 2.3
	 *
	 * @param $entry_id
	 *
	 * @return array|bool|mixed
	 */
	public static function get_openssl_encrypted_fields( $entry_id ) {

		$encrypted_fields = gform_get_meta( $entry_id, '_openssl_encrypted_fields' );

		if ( empty( $encrypted_fields ) ) {
			$encrypted_fields = array();
		}

		return $encrypted_fields;
	}

	/**
	 * Returns the encrypted field IDs for each entry_id specified in the $entry_ids array.
	 *
	 * @since 2.6
	 *
	 * @param array $entry_ids An array of entry IDs.
	 *
	 * @return array Returns an array with entry_id as the key and an array of field IDs (that have been encrypted using GFCommon::openssl_encrypt()) as the value.
	 */
	public static function get_openssl_encrypted_fields_by_entries( $entry_ids ) {
		global $wpdb;

		$entry_ids        = array_unique( $entry_ids );
		$placeholders     = implode( ',', array_fill( 0, count( $entry_ids ), '%d' ) );
		$entry_meta_table = self::get_entry_meta_table_name();

		$sql     = sprintf( "SELECT entry_id, meta_value from $entry_meta_table WHERE meta_key = '_openssl_encrypted_fields' AND entry_id IN(%s)", $placeholders );
		$results = $wpdb->get_results( $wpdb->prepare( $sql, $entry_ids ), ARRAY_A );

		$encrypted_fields = array();
		foreach ( $results as $row ) {
			$encrypted_fields[ $row['entry_id'] ] = maybe_unserialize( $row['meta_value'] );
		}

		return $encrypted_fields;
	}

	/**
	 * Adds the field IDs that have been encrypted using GFCommon::encrypt(). Merges the new IDs with the existing IDs.
	 *
	 * @since 2.3
	 *
	 * @param $entry_id
	 * @param $field_ids
	 *
	 * @return bool
	 */
	public static function set_openssl_encrypted_fields( $entry_id, $field_ids ) {

		if ( ! is_array( $field_ids ) ) {
			$field_ids = array( $field_ids );
		}

		$encrypted_fields = array_merge( self::get_openssl_encrypted_fields( $entry_id ), $field_ids );

		gform_update_meta( $entry_id, '_openssl_encrypted_fields', $encrypted_fields );

		return true;
	}

	/**
	 * Checks whether the given field was encrypted using GFCommon::encrpyt() and registered using GFCommon::set_encrypted_fields()
	 *
	 * @since 2.3
	 *
	 * @param $entry_id
	 * @param $field_id
	 *
	 * @return bool|mixed|void
	 */
	public static function is_openssl_encrypted_field( $entry_id, $field_id ) {

		/**
		 * Determines if an entry field is stored encrypted. Use this hook to change the default behavior of decrypting fields that have been encrypted or to completely disable the
		 * process if checking for encrypted fields.
		 *
		 * @param int $entry_id The current Entry ID
		 * @param int $field_id The current Field ID.
		 */
		$is_encrypted = apply_filters('gform_is_encrypted_field', '', $entry_id, $field_id );
		if (  $is_encrypted !== '' ){
			return $is_encrypted;
		}

		$encrypted_fields = self::get_openssl_encrypted_fields( $entry_id );

		return in_array( $field_id, $encrypted_fields );
	}


	/**
	 * @deprecated 2.4.16
	 *
	 * @param $entry
	 * @param $form
	 *
	 * @return mixed
	 */
	public static function delete_password( $entry, $form ) {
		$password_fields = self::get_fields_by_type( $form, array( 'password' ) );
		if ( is_array( $password_fields ) ) {
			foreach ( $password_fields as $password_field ) {
				$entry[ $password_field->id ] = '';
			}
		}
		GFAPI::update_entry( $entry );

		return $entry;
	}

	public static function maybe_sanitize_form_settings( $form ) {
		if (  isset( $form['version'] ) && version_compare( $form['version'], '1.9.6.10', '>=' ) ) {
			$form = self::sanitize_settings( $form );
		}
		return $form;
	}

	public static function sanitize_settings( $form ) {

		$form['version'] = GFForms::$version;

		if ( apply_filters( 'gform_disable_form_settings_sanitization', false ) ) {
			return $form;
		}

		// -- standard form settings --

		$form['title'] = sanitize_text_field( rgar( $form, 'title' ) );
		if ( isset( $form['description'] ) ) {
			$form['description'] = self::maybe_wp_kses( $form['description'] );
		}

		if ( isset( $form['labelPlacement'] ) ) {
			$form['labelPlacement'] = GFCommon::whitelist( $form['labelPlacement'], array( 'top_label', 'left_label', 'right_label' ) );
		}
		if ( isset( $form['descriptionPlacement'] ) ) {
			$form['descriptionPlacement'] = GFCommon::whitelist( $form['descriptionPlacement'], array( 'below', 'above' ) );
		}

		if ( isset( $form['subLabelPlacement'] ) ) {
			$form['subLabelPlacement']    = GFCommon::whitelist( $form['subLabelPlacement'], array( 'below', 'above' ) );
		}

		// -- advanced form settings --

		if ( isset( $form['cssClass'] ) ) {
			$form['cssClass'] = sanitize_text_field( $form['cssClass'] );
		}

		if ( isset( $form['enableHoneypot'] ) ) {
			$form['enableHoneypot'] = (bool) $form['enableHoneypot'];
		}

		if ( isset( $form['honeypotAction'] ) ) {
			$form['honeypotAction']  = GFCommon::whitelist( $form['honeypotAction'], array( 'abort', 'spam' ) );
		}

		if ( isset( $form['enableAnimation'] ) ) {
			$form['enableAnimation'] = (bool) $form['enableAnimation'];
		}

		// form button settings
		if ( isset( $form['button'] ) ) {
			$form['button']['type']     = GFCommon::whitelist( $form['button']['type'], array( 'text', 'image' ) );
			$form['button']['text']     = $form['button']['type'] == 'text' ? sanitize_text_field( $form['button']['text'] ) : '';
			$form['button']['imageUrl'] = $form['button']['type'] == 'image' ? sanitize_text_field( $form['button']['imageUrl'] ) : '';
		}
		if ( isset( $form['button']['conditionalLogic'] ) ) {
			$form['button']['conditionalLogic'] = self::sanitize_conditional_logic( $form['button']['conditionalLogic'] );
		}

		// Save and Continue settings
		if ( isset( $form['save'] ) ) {
			$form['save']['enabled']        = (bool) $form['save']['enabled'] ;
			$form['save']['button']['type'] = 'link';
			$form['save']['button']['text'] = sanitize_text_field( $form['save']['button']['text'] );
		}


		// limit entries settings
		if ( isset( $form['limitEntries'] ) ) {
			$form['limitEntries']        = (bool) $form['limitEntries'];
			$form['limitEntriesCount']   = $form['limitEntries'] ? absint( $form['limitEntriesCount'] ) : '';
			$form['limitEntriesPeriod']  = $form['limitEntries'] ? GFCommon::whitelist( $form['limitEntriesPeriod'], array( '', 'day', 'week', 'month', 'year' ) ) : '';
			$form['limitEntriesMessage'] = $form['limitEntries'] ? self::maybe_wp_kses( $form['limitEntriesMessage'] ) : '';
		}

		// form scheduling settings
		if ( isset( $form['scheduleForm'] ) ) {
			$form['scheduleForm']           = (bool) $form['scheduleForm'];
			$form['scheduleStart']          = $form['scheduleForm'] ? wp_strip_all_tags( $form['scheduleStart'] ) : '';
			$form['scheduleStartHour']      = $form['scheduleForm'] ? GFCommon::int_range( $form['scheduleStartHour'], 1, 12 ) : '';
			$form['scheduleStartMinute']    = $form['scheduleForm'] ? GFCommon::int_range( $form['scheduleStartMinute'], 1, 60 ) : '';
			$form['scheduleStartAmpm']      = $form['scheduleForm'] ? GFCommon::whitelist( $form['scheduleStartAmpm'], array( 'am', 'pm' ) ) : '';
			$form['scheduleEnd']            = $form['scheduleForm'] ? wp_strip_all_tags( $form['scheduleEnd'] ) : '';
			$form['scheduleEndHour']        = $form['scheduleForm'] ? GFCommon::int_range( $form['scheduleEndHour'], 1, 12 ) : '';
			$form['scheduleEndMinute']      = $form['scheduleForm'] ? GFCommon::int_range( $form['scheduleEndMinute'], 1, 60 ) : '';
			$form['scheduleEndAmpm']        = $form['scheduleForm'] ? GFCommon::whitelist( $form['scheduleEndAmpm'], array( 'am', 'pm' ) ) : '';
			$form['schedulePendingMessage'] = $form['scheduleForm'] ? self::maybe_wp_kses( $form['schedulePendingMessage'] ) : '';
			$form['scheduleMessage']        = $form['scheduleForm'] ? self::maybe_wp_kses( $form['scheduleMessage'] ) : '';

		}

		// require login settings
		if ( isset( $form['requireLogin'] ) ) {
			$form['requireLogin']        = (bool) $form['requireLogin'];
			$form['requireLoginMessage'] = $form['requireLogin'] ? self::maybe_wp_kses( $form['requireLoginMessage'] ) : '';
		}

		if ( isset( $form['fields'] ) ) {
			foreach ( $form['fields'] as $field ) {
				/* @var GF_Field $field */
				$field->sanitize_settings();
			}
		}

		return $form;
	}

	public static function sanitize_conditional_logic( $logic ) {
		if ( ! is_array( $logic ) ) {
			return $logic;
		}

		if ( apply_filters( 'gform_disable_form_settings_sanitization', false ) ) {
			return $logic;
		}

		if ( isset( $logic['actionType'] ) && ! in_array( $logic['actionType'], array( 'show', 'hide' ) ) ) {
			$logic['actionType'] = 'show';
		}
		if (  isset( $logic['logicType'] ) && ! in_array( $logic['logicType'], array( 'all', 'any' ) ) ) {
			$logic['logicType'] = 'all';
		}

		if ( isset( $logic['rules'] ) && is_array( $logic['rules'] ) ) {
			foreach ( $logic['rules'] as &$rule ) {
				if ( isset( $rule['fieldId'] ) ) {
					// Field ID could be meta key.
					$rule['fieldId'] = wp_strip_all_tags( $rule['fieldId'] );
				}
				if ( isset( $rule['operator'] ) ) {
					$is_valid_operator = self::is_valid_operator( $rule['operator'] );
					$rule['operator'] = $is_valid_operator ? $rule['operator'] : 'is';
				}

				if ( isset( $rule['value'] ) ) {
					// Strip scripts but don't encode.
					$allowed_protocols = wp_allowed_protocols();
					$rule['value']     = wp_kses_no_null( $rule['value'], array( 'slash_zero' => 'keep' ) );
					$rule['value']     = wp_kses_hook( $rule['value'], 'post', $allowed_protocols );
					$rule['value']     = wp_kses_split( $rule['value'], 'post', $allowed_protocols );
				}
			}
		}
		return $logic;
	}

	private static function maybe_wp_kses( $html, $allowed_html = 'post', $allowed_protocols = array() ) {
		return GFCommon::maybe_wp_kses( $html, $allowed_html, $allowed_protocols );
	}

	/**
	 * Returns an array containing the form fields of the specified type or types.
	 *
	 * @since 1.9.9.10
	 * @param array $form
	 * @param array|string $types
	 * @param bool $use_input_type
	 *
	 * @return GF_Field[]
	 */
	public static function get_fields_by_type( $form, $types, $use_input_type = false ) {
		$fields = array();
		if ( ! is_array( rgar( $form, 'fields' ) ) ) {
			return $fields;
		}

		if ( ! is_array( $types ) ) {
			$types = array( $types );
		}

		foreach ( $form['fields'] as $field ) {
			/* @var GF_Field $field */
			$type = $use_input_type ? $field->get_input_type() : $field->type;
			if ( in_array( $type, $types ) ) {
				$fields[] = $field;
			}
		}

		return $fields;
	}

	/**
	 * Checks whether the conditional logic operator passed in is valid.
	 *
	 * @since  2.0.7.20 Refactored and added filter gform_is_valid_conditional_logic_operator.
	 * @access public
	 *
	 * @param string $operator Conditional logic operator.
	 *
	 * @return bool true if a valid operator, false if not.
	 */
	public static function is_valid_operator( $operator ) {
		$operators = array( 'is', 'isnot', '<>', 'not in', 'in', '>', '<', 'contains', 'starts_with', 'ends_with', 'like', '>=', '<=' );
		$is_valid = in_array( strtolower( $operator ), $operators );
		/**
		 * Filter which checks whether the operator is valid.
		 *
		 * Allows custom operators to be validated.
		 *
		 * @since 2.0.7.20
		 *
		 * @param bool   $is_valid Whether the operator is valid or not.
		 * @param string $operator The conditional logic operator.
		 */
		return apply_filters( 'gform_is_valid_conditional_logic_operator', $is_valid, $operator );
	}

	/**
	 * Update the recent forms list for the current user when a form is edited or trashed.
	 *
	 * @since 2.0.7.14
	 *
	 * @param int $form_id The ID of the current form.
	 * @param bool $trashed Indicates if the form was trashed. Default is false, form was opened for editing.
	 */
	public static function update_recent_forms( $form_id, $trashed = false ) {
		if ( ! get_option( 'gform_enable_toolbar_menu' ) ) {
			return;
		}

		$current_user_id = get_current_user_id();
		$recent_form_ids = self::get_recent_forms( $current_user_id );

		$i = array_search( $form_id, $recent_form_ids );

		if ( $i !== false ) {
			unset( $recent_form_ids[ $i ] );
			$recent_form_ids = array_values( $recent_form_ids );
		}

		if ( ! $trashed ) {
			// Add the current form to the top of the list.
			array_unshift( $recent_form_ids, $form_id );

			$recent_form_ids = array_slice( $recent_form_ids, 0, 10 );
		}

		update_user_meta( $current_user_id, 'gform_recent_forms', $recent_form_ids );
	}

	/**
	 * Get the recent forms list for the current user.
	 *
	 * @since 2.2.1.14
	 *
	 * @param int $current_user_id The ID of the currently logged in user.
	 *
	 * @return array
	 */
	public static function get_recent_forms( $current_user_id = 0 ) {
		if ( ! $current_user_id ) {
			$current_user_id = get_current_user_id();
		}

		$recent_form_ids = get_user_meta( $current_user_id, 'gform_recent_forms', true );

		if ( empty( $recent_form_ids ) || ! is_array( $recent_form_ids ) ) {
			$all_form_ids    = self::get_form_ids();
			$all_form_ids    = array_reverse( $all_form_ids );
			$recent_form_ids = array_slice( $all_form_ids, 0, 10 );
			if ( $recent_form_ids ) {
				update_user_meta( $current_user_id, 'gform_recent_forms', $recent_form_ids );
			}
		}

		return $recent_form_ids;
	}

	/**
	 * Evaluates the conditional logic based on the specified $logic variable. This method is used when evaluating field conditional logic.
	 * NOTE: There is a future refactoring opportunity to reduce code duplication by merging this method with GFCommon::evaluate_conditional_logic(), which currently handles non-field conditional logic (i.e. notification, confirmation and feed). A possible solution is for this method to call GFCommon::evaluate_conditional_logic().
	 *
	 * @param array $form         The current Form object.
	 * @param array $logic        The conditional logic configuration array with all the specified rules.
	 * @param array $field_values Default field values for this form. Used when form has not yet been submitted. Pass an array if no default field values are available/required.
	 * @param array $entry        Optional, default is null. If entry object is available, pass the entry.
	 *
	 * @return bool         Returns true if the conditional logic passes (i.e. field/button is supposed to be displayed), false otherwise.
	 */
	private static function evaluate_conditional_logic( $form, $logic, $field_values, $entry = null ) {

		if ( empty( $logic ) ){
			return true;
		}

		$match_count = 0;
		foreach ( $logic['rules'] as $rule ) {
			try {
				/**
				 * Filter the conditional logic rule before it is evaluated.
				 *
				 * @since 2.4.22
				 *
				 * @param array $rule         The conditional logic rule about to be evaluated.
				 * @param array $form         The current form meta.
				 * @param array $logic        All details required to evaluate an objects conditional logic.
				 * @param array $field_values The default field values for this form.
				 * @param array $entry        The current entry object (if available).
				 */
				$rule = apply_filters( 'gform_rule_pre_evaluation', $rule, $form, $logic, $field_values, $entry );
			} catch ( Error $e ) {
				GFCommon::log_error( __METHOD__ . '(): Error from function hooked to gform_rule_pre_evaluation. ' . $e->getMessage() );
			}
			$source_field   = RGFormsModel::get_field( $form, $rule['fieldId'] );
			$source_value   = empty( $entry ) ? self::get_field_value( $source_field, $field_values ) : self::get_lead_field_value( $entry, $source_field );

			/**
			 * Filter the source value of a conditional logic rule before it is compared with the target value.
			 *
			 * @since 2.6.2
			 *
			 * @param int|string $source_value The value of the rule's configured field ID, entry meta, or custom property.
			 * @param array      $rule         The conditional logic rule that is being evaluated.
			 * @param array      $form         The current form meta.
			 * @param array      $logic        All details required to evaluate an objects conditional logic.
			 * @param array      $entry        The current entry object (if available).
			 */
			$source_value = apply_filters( 'gform_rule_source_value', $source_value, $rule, $form, $logic, $entry );

			$is_value_match = self::is_value_match( $source_value, $rule['value'], $rule['operator'], $source_field, $rule, $form );

			if ( $is_value_match ) {
				$match_count ++;
			}
		}

		$do_action = ( $logic['logicType'] == 'all' && $match_count == sizeof( $logic['rules'] ) ) || ( $logic['logicType'] == 'any' && $match_count > 0 );
		$is_hidden = ( $do_action && $logic['actionType'] == 'hide' ) || ( ! $do_action && $logic['actionType'] == 'show' );

		return ! $is_hidden;
	}

	/**
	 * Returns all the draft submissions.
	 *
	 * @since 2.4
	 *
	 * @return array|null|object The query result.
	 */
	public static function get_draft_submissions() {
		global $wpdb;

		self::purge_expired_draft_submissions();

		$table = version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ? self::get_incomplete_submissions_table_name() : self::get_draft_submissions_table_name();
		$sql   = "SELECT uuid, date_created, form_id, ip, submission, source_url FROM {$table}";
		$rows   = $wpdb->get_results( $sql, ARRAY_A );

		foreach ( $rows as $row ) {
			$form = self::get_form_meta( $row['form_id'] );
			$row['submission'] = self::filter_draft_submission_post_get( $row['submission'], $row['uuid'], $form );
		}

		return $rows;
	}

	/**
	 * Sanitizes the names of the files that have been uploaded to the tmp directory and sent in
	 * $_POST['gform_uploaded_files'] and caches them in GFFormsModel::$uploaded_files.
	 *
	 * @since 2.4.3.5
	 *
	 * @param $form_id
	 *
	 * @return array
	 */
	public static function set_uploaded_files( $form_id ) {
		$files = GFCommon::json_decode( stripslashes( GFForms::post( 'gform_uploaded_files' ) ) );
		if ( ! is_array( $files ) ) {
			$files = array();
		}

		foreach ( $files as &$upload_field ) {
			if ( is_array( $upload_field ) ) {
				if ( isset( $upload_field[0] ) && is_array( $upload_field[0] ) ) {
					foreach ( $upload_field as &$upload ) {
						if ( isset( $upload['temp_filename'] ) ) {
							$upload['temp_filename'] = sanitize_file_name( wp_basename( $upload['temp_filename'] ) );
						}
						if ( isset( $upload['uploaded_filename'] ) ) {
							$upload['uploaded_filename'] = sanitize_file_name( wp_basename( $upload['uploaded_filename'] ) );
						}
					}
				}
			} else {
				$upload_field = wp_basename( $upload_field );
			}
		}

		self::$uploaded_files[ $form_id ] = $files;

		return $files;
	}

	/**
	 * Checks if an entry exists for the supplied ID.
	 *
	 * @since 2.4.6
	 * @since 2.4.24 Updated to use GFFormsModel::id_exists_in_table().
	 *
	 * @param int $entry_id The ID to be checked.
	 *
	 * @return bool
	 */
	public static function entry_exists( $entry_id ) {
		return self::id_exists_in_table( $entry_id, GFFormsModel::get_entry_table_name() );
	}

	/**
	 * Checks if an id exists in the specified table.
	 *
	 * @since 2.4.24
	 *
	 * @param int    $id    The ID to be checked.
	 * @param string $table The table name, including the site's database prefix.
	 *
	 * @return bool
	 */
	public static function id_exists_in_table( $id, $table ) {
		$id = intval( $id );
		if ( $id <= 0 ) {
			return false;
		}

		global $wpdb;
		$sql    = $wpdb->prepare( "SELECT count(id) FROM {$table} WHERE id = %d", $id );
		$result = intval( $wpdb->get_var( $sql ) );

		return $result > 0;
	}

	/**
	 * Returns an array of column names used by the gf_addon_feed table.
	 *
	 * @since 2.4.24
	 *
	 * @return string[]
	 */
	public static function get_addon_feed_db_columns() {
		return array( 'id', 'form_id', 'is_active', 'feed_order', 'meta', 'addon_slug', 'event_type' );
	}

	/**
	 * Updates the specified feed with the given property value.
	 *
	 * @since 2.4.24
	 *
	 * @param int    $feed_id        The ID of the feed being updated.
	 * @param string $property_name  The name of the property (column) being updated.
	 * @param mixed  $property_value The new value of the specified property.
	 *
	 * @return bool|WP_Error
	 */
	public static function update_feed_property( $feed_id, $property_name, $property_value ) {
		if ( $property_name === 'id' ) {
			return new WP_Error( 'invalid_property', __( 'Updating the id property is not supported', 'gravityforms' ) );
		}

		if ( ! in_array( $property_name, self::get_addon_feed_db_columns() ) ) {
			return new WP_Error( 'invalid_property', sprintf( __( '%s is not a valid feed property', 'gravityforms' ), $property_name ) );
		}

		if ( gf_upgrade()->get_submissions_block() ) {
			return new WP_Error( 'submissions_blocked', __( 'Submissions are currently blocked due to an upgrade in progress', 'gravityforms' ) );
		}

		if ( $property_name === 'meta' ) {
			if ( is_array( $property_value ) ) {
				$property_value = json_encode( $property_value );
			}

			if ( empty( $property_value ) || ! is_string( $property_value ) || $property_value[0] !== '{' ) {
				return new WP_Error( 'invalid_meta', __( 'Feed meta should be an associative array or JSON', 'gravityforms' ) );
			}
		}

		global $wpdb;
		$table  = self::get_addon_feed_table_name();
		$result = $wpdb->update( $table, array( $property_name => $property_value ), array( 'id' => $feed_id ) );

		if ( $result === false ) {
			return new WP_Error( 'error_updating', sprintf( __( 'There was an error while updating feed id %s', 'gravityforms' ), $feed_id ) );
		}

		if ( $result === 0 && ! self::id_exists_in_table( $feed_id, $table ) ) {
			return new WP_Error( 'not_found', sprintf( __( 'Feed id %s not found', 'gravityforms' ), $feed_id ) );
		}

		return (bool) $result;
	}

	/**
	 * Updates the license key, If multisite, it updates the license key for all sites in the network.
	 *
	 * @since 2.7
	 *
	 * @param string $license The license key.
	 *
	 * @return void
	 */
	public static function update_license_key( $license ) {
		if ( is_multisite() && is_main_site() ) {
			$sites = get_sites();
			foreach ( $sites as $site ) {
				update_blog_option( $site->blog_id, 'rg_gforms_key', $license );
				update_blog_option( $site->blog_id, 'gform_pending_installation', false );
			}
		}

		update_option( 'rg_gforms_key', $license );
	}

}

class RGFormsModel extends GFFormsModel {
}

/**
 * In-memory cache of entry meta using "{blog_id}_{entry_id}_{meta_key}" as the key.
 *
 * @since 2.3 Prefixed cache key with the blog id.
 * @since unknown
 *
 * @global array $_gform_lead_meta
 */
global $_gform_lead_meta;
$_gform_lead_meta = array();

// Functions to handle lead meta
function gform_get_meta( $entry_id, $meta_key ) {

	if ( version_compare( GFFormsModel::get_database_version(), '2.3-dev-1', '<' ) ) {
		return GF_Forms_Model_Legacy::gform_get_meta( $entry_id, $meta_key );
	}

	global $wpdb, $_gform_lead_meta;

	//get from cache if available
	$cache_key = get_current_blog_id() . '_' . $entry_id . '_' . $meta_key;
	if ( array_key_exists( $cache_key, $_gform_lead_meta ) ) {
		return maybe_unserialize( $_gform_lead_meta[ $cache_key ] );
	}

	$table_name                   = GFFormsModel::get_entry_meta_table_name();
	$results                      = $wpdb->get_results( $wpdb->prepare( "SELECT meta_value FROM {$table_name} WHERE entry_id=%d AND meta_key=%s", $entry_id, $meta_key ) );
	$value                        = isset( $results[0] ) ? $results[0]->meta_value : null;
	$meta_value                   = $value === null ? false : maybe_unserialize( $value );
	$_gform_lead_meta[ $cache_key ] = $meta_value;

	return $meta_value;
}

function gform_get_meta_values_for_entries( $entry_ids, $meta_keys ) {
	global $wpdb;

	if ( version_compare( GFFormsModel::get_database_version(), '2.3-dev-1', '<' ) ) {
		return GF_Forms_Model_Legacy::gform_get_meta_values_for_entries( $entry_ids, $meta_keys );
	}

	if ( empty( $meta_keys ) || empty( $entry_ids ) ) {
		return array();
	}

	$table_name            = RGFormsModel::get_entry_meta_table_name();
	$meta_key_select_array = array();

	foreach ( $meta_keys as $meta_key ) {
		$meta_key_select_array[] = "max(case when meta_key = '$meta_key' then meta_value end) as `$meta_key`";
	}

	$entry_ids_str = join( ',', $entry_ids );

	$meta_key_select = join( ',', $meta_key_select_array );

	$sql_query = "  SELECT
                    entry_id, $meta_key_select
                    FROM $table_name
                    WHERE entry_id IN ($entry_ids_str)
                    GROUP BY entry_id";

	$results = $wpdb->get_results( $sql_query );

	foreach ( $results as $result ) {
		foreach ( $meta_keys as $meta_key ) {
			$result->$meta_key = $result->$meta_key === null ? false : maybe_unserialize( $result->$meta_key );
		}
	}

	$meta_value_array = $results;

	return $meta_value_array;
}

/**
 * Add or update metadata associated with an entry.
 *
 * Data will be serialized. Don't forget to sanitize user input.
 *
 * @since Unknown
 * @since 2.5 Return the result of the query.
 *
 * @param int      $entry_id   The ID of the entry to be updated.
 * @param string   $meta_key   The key for the meta data to be stored.
 * @param mixed    $meta_value The data to be stored for the entry.
 * @param int|null $form_id    The form ID of the entry (optional, saves extra query if passed when creating the metadata).
 *
 * @return int|false           Returns the number of affected rows, or false if the operation fails.
 */
function gform_update_meta( $entry_id, $meta_key, $meta_value, $form_id = null ) {
	global $wpdb, $_gform_lead_meta;

	if ( gf_upgrade()->get_submissions_block() ) {
		return;
	}

	if ( intval( $entry_id ) <= 0 ) {
		return;
	}

	if ( version_compare( GFFormsModel::get_database_version(), '2.3-dev-1', '<' ) ) {
		GF_Forms_Model_Legacy::gform_update_meta( $entry_id, $meta_key, $meta_value, $form_id );
		return;
	}
	$table_name = GFFormsModel::get_entry_meta_table_name();


	if ( false === $meta_value ) {
		$meta_value = '0';
	}

	$serialized_meta_value  = maybe_serialize( $meta_value );
	$meta_exists = gform_get_meta( $entry_id, $meta_key ) !== false;
	if ( $meta_exists ) {
		$result = $wpdb->update( $table_name, array( 'meta_value' => $serialized_meta_value ), array( 'entry_id' => $entry_id, 'meta_key' => $meta_key ), array( '%s' ), array( '%d', '%s' ) );
	} else {

		if ( empty( $form_id ) ) {
			$entry_table_name = GFFormsModel::get_entry_table_name();
			$form_id         = $wpdb->get_var( $wpdb->prepare( "SELECT form_id from $entry_table_name WHERE id=%d", $entry_id ) );
		} else {
			$form_id = intval( $form_id );
		}

		$result = $wpdb->insert( $table_name, array( 'form_id' => $form_id, 'entry_id' => $entry_id, 'meta_key' => $meta_key, 'meta_value' => $serialized_meta_value ), array( '%d', '%d', '%s', '%s' ) );
	}

	if ( $result !== false ) {
		//updates cache
		$cache_key = get_current_blog_id() . '_' . $entry_id . '_' . $meta_key;
		if ( array_key_exists( $cache_key, $_gform_lead_meta ) ) {
			$_gform_lead_meta[ $cache_key ] = $meta_value;
		}
	}

	return $result;
}

/**
 * Add metadata associated with an entry.
 *
 * Data will be serialized; Don't forget to sanitize user input.
 *
 * @since Unknown
 * @since 2.5 Return the result of the query.
 *
 * @param int      $entry_id   The ID of the entry where metadata is to be added.
 * @param string   $meta_key   The key for the meta data to be stored.
 * @param mixed    $meta_value The data to be stored for the entry.
 * @param int|null $form_id    The form ID of the entry (optional, saves extra query if passed when creating the metadata).
 *
 * @return int|false           Returns the number of affected rows, or false if the operation fails.
 */
function gform_add_meta( $entry_id, $meta_key, $meta_value, $form_id = null ) {
	global $wpdb, $_gform_lead_meta;

	if ( gf_upgrade()->get_submissions_block() ) {
		return;
	}

	if ( intval( $entry_id ) <= 0 ) {
		return;
	}

	if ( version_compare( GFFormsModel::get_database_version(), '2.3-dev-1', '<' ) ) {
		GF_Forms_Model_Legacy::gform_add_meta( $entry_id, $meta_key, $meta_value, $form_id );
		return;
	}

	$table_name = RGFormsModel::get_entry_meta_table_name();

	if ( false === $meta_value ) {
		$meta_value = '0';
	}
	$serialized_meta_value  = maybe_serialize( $meta_value );

	if ( empty( $form_id ) ) {
		$entry_table_name = GFFormsModel::get_entry_table_name();
		$form_id         = $wpdb->get_var( $wpdb->prepare( "SELECT form_id from $entry_table_name WHERE id=%d", $entry_id ) );
	} else {
		$form_id = intval( $form_id );
	}

	$result = $wpdb->insert( $table_name, array( 'form_id' => $form_id, 'entry_id' => $entry_id, 'meta_key' => $meta_key, 'meta_value' => $serialized_meta_value ), array( '%d', '%d', '%s', '%s' ) );

	if ( $result !== false ) {
		$cache_key                      = get_current_blog_id() . '_' . $entry_id . '_' . $meta_key;
		$_gform_lead_meta[ $cache_key ] = $meta_value;
	}

	return $result;
}

/**
 * Delete metadata associated with an entry.
 *
 * @since Unknown
 * @since 2.5 Return the result of the query.
 *
 * @param int    $entry_id The ID of the entry to be deleted.
 * @param string $meta_key The key for the meta data to be deleted.
 *
 * @return int|bool       Returns true or the number of rows affected, or false if the operation fails.
 */
function gform_delete_meta( $entry_id, $meta_key = '' ) {
	global $wpdb, $_gform_lead_meta;

	if ( gf_upgrade()->get_submissions_block() ) {
		return;
	}

	if ( version_compare( GFFormsModel::get_database_version(), '2.3-dev-1', '<' ) ) {
		GF_Forms_Model_Legacy::gform_delete_meta( $entry_id, $meta_key );
		return;
	}

	$table_name  = RGFormsModel::get_entry_meta_table_name();
	$meta_filter = empty( $meta_key ) ? '' : $wpdb->prepare( 'AND meta_key=%s', $meta_key );

	$result = $wpdb->query( $wpdb->prepare( "DELETE FROM {$table_name} WHERE entry_id=%d {$meta_filter}", $entry_id ) );

	if ( $result !== false ) {
		//clears cache.
		$_gform_lead_meta = array();
	}

	return $result;
}
