Current File : //var/www/tusveterinarios/wp-content/plugins/gravityforms/includes/addon/class-gf-addon.php
<?php
/**
 * @package GFAddOn
 * @author  Rocketgenius
 */

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

/**
 * Class GFAddOn
 *
 * Handles all tasks mostly common to any Gravity Forms Add-On, including third party ones.
 */
abstract class GFAddOn {

	/**
	 * @var string Version number of the Add-On
	 */
	protected $_version;
	/**
	 * @var string Gravity Forms minimum version requirement
	 */
	protected $_min_gravityforms_version;
	/**
	 * @var string URL-friendly identifier used for form settings, add-on settings, text domain localization...
	 */
	protected $_slug;
	/**
	 * @var string Relative path to the plugin from the plugins folder. Example "gravityforms/gravityforms.php"
	 */
	protected $_path;
	/**
	 * @var string Full path the the plugin. Example: __FILE__
	 */
	protected $_full_path;
	/**
	 * @var string URL to the Gravity Forms website. Example: 'http://www.gravityforms.com' OR affiliate link.
	 */
	protected $_url;
	/**
	 * @var string Title of the plugin to be used on the settings page, form settings and plugins page. Example: 'Gravity Forms MailChimp Add-On'
	 */
	protected $_title;
	/**
	 * @var string Short version of the plugin title to be used on menus and other places where a less verbose string is useful. Example: 'MailChimp'
	 */
	protected $_short_title;
	/**
	 * @var array Members plugin integration. List of capabilities to add to roles.
	 */
	protected $_capabilities = array();
	/**
	 * @var string The hook suffix for the app menu
	 */
	public $app_hook_suffix;

	private $_saved_settings = array();
	private $_previous_settings = array();

	/**
	 * @var array Stores a copy of setting fields that failed validation; only populated after validate_settings() has been called.
	 */
	private $_setting_field_errors = array();

	// ------------ Permissions -----------
	/**
	 * @var string|array A string or an array of capabilities or roles that have access to the settings page
	 */
	protected $_capabilities_settings_page = array();
	/**
	 * @var string|array A string or an array of capabilities or roles that have access to the form settings
	 */
	protected $_capabilities_form_settings = array();
	/**
	 * @var string|array A string or an array of capabilities or roles that have access to the plugin page
	 */
	protected $_capabilities_plugin_page = array();
	/**
	 * @var string|array A string or an array of capabilities or roles that have access to the app menu
	 */
	protected $_capabilities_app_menu = array();
	/**
	 * @var string|array A string or an array of capabilities or roles that have access to the app settings page
	 */
	protected $_capabilities_app_settings = array();
	/**
	 * @var string|array A string or an array of capabilities or roles that can uninstall the plugin
	 */
	protected $_capabilities_uninstall = array();

	// ------------ RG Autoupgrade -----------

	/**
	 * @var bool Used by Rocketgenius plugins to activate auto-upgrade.
	 * @ignore
	 */
	protected $_enable_rg_autoupgrade = false;

	// ------------ Private -----------

	private $_no_conflict_scripts = array();
	private $_no_conflict_styles = array();
	private $_preview_styles = array();
	private $_print_styles = array();
	private static $_registered_addons = array( 'active' => array(), 'inactive' => array() );

	/**
	 * Class constructor which hooks the instance into the WordPress init action
	 */
	function __construct() {
		$this->update_path();

		add_action( 'init', array( $this, 'init' ) );

		if ( $this->_enable_rg_autoupgrade ) {
			require_once( 'class-gf-auto-upgrade.php' );
			$is_gravityforms_supported = $this->is_gravityforms_supported( $this->_min_gravityforms_version );
			new GFAutoUpgrade( $this->_slug, $this->_version, $this->_min_gravityforms_version, $this->_title, $this->_full_path, $this->get_path(), $this->_url, $is_gravityforms_supported );
		}

		$this->pre_init();
	}

	/**
	 * Registers an addon so that it gets initialized appropriately
	 *
	 * @param string $class - The class name
	 * @param string $overrides - Specify the class to replace/override
	 */
	public static function register( $class, $overrides = null ) {

		//Ignore classes that have been marked as inactive
		if ( in_array( $class, self::$_registered_addons['inactive'] ) ) {
			return;
		}

		//Mark classes as active. Override existing active classes if they are supposed to be overridden
		$index = array_search( $overrides, self::$_registered_addons['active'] );
		if ( $index !== false ) {
			self::$_registered_addons['active'][ $index ] = $class;
		} else {
			self::$_registered_addons['active'][] = $class;
		}

		//Mark overridden classes as inactive.
		if ( ! empty( $overrides ) ) {
			self::$_registered_addons['inactive'][] = $overrides;
		}

	}

	/**
	 * Gets all active, registered Add-Ons.
	 *
	 * @since  Unknown
	 * @access public
	 *
	 * @uses GFAddOn::$_registered_addons
	 *
	 * @return array Active, registered Add-Ons.
	 */
	public static function get_registered_addons() {
		return self::$_registered_addons['active'];
	}

	/**
	 * Initializes all addons.
	 */
	public static function init_addons() {

		//Removing duplicate add-ons
		$active_addons = array_unique( self::$_registered_addons['active'] );

		foreach ( $active_addons as $addon ) {

			call_user_func( array( $addon, 'get_instance' ) );

		}
	}

	/**
	 * Gets executed before all init functions. Override this function to perform initialization tasks that must be done prior to init
	 */
	public function pre_init() {

		if ( $this->is_gravityforms_supported() ) {

			//Entry meta
			if ( $this->method_is_overridden( 'get_entry_meta' ) ) {
				add_filter( 'gform_entry_meta', array( $this, 'get_entry_meta' ), 10, 2 );
			}
		}
	}

	/**
	 * Plugin starting point. Handles hooks and loading of language files.
	 */
	public function init() {

		$this->load_text_domain();

		add_filter( 'gform_logging_supported', array( $this, 'set_logging_supported' ) );

		add_action( 'gform_post_upgrade', array( $this, 'post_gravityforms_upgrade' ), 10, 3 );

		// Get minimum requirements state.
		$meets_requirements = $this->meets_minimum_requirements();

		if ( RG_CURRENT_PAGE == 'admin-ajax.php' ) {

			//If gravity forms is supported, initialize AJAX
			if ( $this->is_gravityforms_supported() && $meets_requirements['meets_requirements'] ) {
				$this->init_ajax();
			}
		} elseif ( is_admin() ) {

			$this->init_admin();

		} else {

			if ( $this->is_gravityforms_supported() && $meets_requirements['meets_requirements'] ) {
				$this->init_frontend();
			}
		}

	}

	/**
	 * Override this function to add initialization code (i.e. hooks) for the admin site (WP dashboard)
	 */
	public function init_admin() {

		// enqueues admin scripts
		add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ), 10, 0 );

		// message enforcing min version of Gravity Forms
		if ( isset( $this->_min_gravityforms_version ) && RG_CURRENT_PAGE == 'plugins.php' ) {
			add_action( 'after_plugin_row_' . $this->get_path(), array( $this, 'plugin_row' ), 10, 2 );
		}

		// STOP HERE IF GRAVITY FORMS IS NOT SUPPORTED
		if ( isset( $this->_min_gravityforms_version ) && ! $this->is_gravityforms_supported( $this->_min_gravityforms_version ) ) {
			return;
		}

		// STOP HERE IF CANNOT PASS MINIMUM REQUIREMENTS CHECK.
		$meets_requirements = $this->meets_minimum_requirements();
		if ( ! $meets_requirements['meets_requirements'] ) {
			$this->failed_requirements_init();
			return;
		}

		$this->setup();

		// Add form settings only when there are form settings fields configured or form_settings() method is implemented
		if ( self::has_form_settings_page() ) {
			$this->form_settings_init();
		}

		// Add plugin page when there is a plugin page configured or plugin_page() method is implemented
		if ( self::has_plugin_page() ) {
			$this->plugin_page_init();
		}

		// Add addon settings page only when there are addon settings fields configured or settings_page() method is implemented
		if ( self::has_plugin_settings_page() ) {
			if ( $this->current_user_can_any( $this->_capabilities_settings_page ) ) {
				$this->plugin_settings_init();
			}
		}

		// creates the top level app left menu
		if ( self::has_app_menu() ) {
			if ( $this->current_user_can_any( $this->_capabilities_app_menu ) ) {
				add_action( 'admin_menu', array( $this, 'create_app_menu' ) );
			}
		}


		// Members plugin integration.
		if ( $this->has_members_plugin( ) ) {
			add_action( 'members_register_cap_groups', array( $this, 'members_register_cap_group' ), 11 );
			add_action( 'members_register_caps', array( $this, 'members_register_caps' ), 11 );
		}

		// User Role Editor integration.
		add_filter( 'ure_capabilities_groups_tree', array( $this, 'filter_ure_capabilities_groups_tree' ), 11 );
		add_filter( 'ure_custom_capability_groups', array( $this, 'filter_ure_custom_capability_groups' ), 10, 2 );

		// Results page
		if ( $this->method_is_overridden( 'get_results_page_config' ) ) {
			$results_page_config  = $this->get_results_page_config();
			$results_capabilities = rgar( $results_page_config, 'capabilities' );
			if ( $results_page_config && $this->current_user_can_any( $results_capabilities ) ) {
				$this->results_page_init( $results_page_config );
			}
		}

		// Locking
		if ( $this->method_is_overridden( 'get_locking_config' ) ) {
			require_once( GFCommon::get_base_path() . '/includes/locking/class-gf-locking.php' );
			require_once( 'class-gf-addon-locking.php' );
			$config = $this->get_locking_config();
			new GFAddonLocking( $config, $this );
		}

		// No conflict scripts
		add_filter( 'gform_noconflict_scripts', array( $this, 'register_noconflict_scripts' ) );
		add_filter( 'gform_noconflict_styles', array( $this, 'register_noconflict_styles' ) );
		add_action( 'gform_enqueue_scripts', array( $this, 'enqueue_scripts' ), 10, 2 );

	}

	/**
	 * Override this function to add initialization code (i.e. hooks) for the public (customer facing) site
	 */
	public function init_frontend() {

		$this->setup();

		add_filter( 'gform_preview_styles', array( $this, 'enqueue_preview_styles' ), 10, 2 );
		add_filter( 'gform_print_styles', array( $this, 'enqueue_print_styles' ), 10, 2 );
		add_action( 'gform_enqueue_scripts', array( $this, 'enqueue_scripts' ), 10, 2 );

	}

	/**
	 * Override this function to add AJAX hooks or to add initialization code when an AJAX request is being performed
	 */
	public function init_ajax() {
		if ( rgpost( 'view' ) == 'gf_results_' . $this->_slug ) {
			require_once( GFCommon::get_base_path() . '/tooltips.php' );
			require_once( 'class-gf-results.php' );
			$gf_results = new GFResults( $this->_slug, $this->get_results_page_config() );
			add_action( 'wp_ajax_gresults_get_results_gf_results_' . $this->_slug, array( $gf_results, 'ajax_get_results' ) );
			add_action( 'wp_ajax_gresults_get_more_results_gf_results_' . $this->_slug, array( $gf_results, 'ajax_get_more_results' ) );
		} elseif ( $this->method_is_overridden( 'get_locking_config' ) ) {
			require_once( GFCommon::get_base_path() . '/includes/locking/class-gf-locking.php' );
			require_once( 'class-gf-addon-locking.php' );
			$config = $this->get_locking_config();
			new GFAddonLocking( $config, $this );
		}

		if ( $this->has_plugin_settings_page() && $this->current_user_can_any( $this->_capabilities_settings_page ) ) {
			add_filter( 'plugin_action_links', array( $this, 'plugin_settings_link' ), 10, 2 );
		}
	}


	//--------------  Minimum Requirements Check  ---------------

	/**
	 * Override this function to provide a list of requirements needed to use Add-On.
	 *
	 * Custom requirements can be defined by adding a callback to the minimum requirements array.
	 * A custom requirement receives and should return an array with two parameters:
	 *   bool  $meets_requirements If the custom requirements check passed.
	 *   array $errors             An array of error messages to present to the user.
	 *
	 * Following is an example of the array that is expected to be returned by this function:
	 * @example https://gist.github.com/JeffMatson/a8d23e16e333e5116060906c6f091aa7
	 *
	 * @since  2.2
	 * @access public
	 *
	 * @return array
	 */
	public function minimum_requirements() {

		return array();

	}

	/**
	 * Performs a check to see if WordPress environment meets minimum requirements need to use Add-On.
	 *
	 * @since  2.2
	 * @access public
	 *
	 * @uses GFAddOn::minimum_requirements()
	 * @uses GFAddOn::get_slug()
	 *
	 * @return bool|array
	 */
	public function meets_minimum_requirements() {

		// Get minimum requirements.
		$requirements = $this->minimum_requirements();

		// Prepare response.
		$meets_requirements = array( 'meets_requirements' => true, 'errors' => array() );

		// If no minimum requirements are defined, return.
		if ( empty( $requirements ) ) {
			return $meets_requirements;
		}

		// Loop through requirements.
		foreach ( $requirements as $requirement_type => $requirement ) {

			// If requirement is a callback, run it.
			if ( is_callable( $requirement ) ) {
				$meets_requirements = call_user_func( $requirement, $meets_requirements );
				continue;
			}

			// Set requirement type to lowercase.
			$requirement_type = strtolower( $requirement_type );

			// Run base requirement checks.
			switch ( $requirement_type ) {

				case 'add-ons':

					// Initialize active Add-Ons array.
					$active_addons = array();

					// Loop through active Add-Ons.
					foreach ( self::$_registered_addons['active'] as $active_addon ) {

						// Get Add-On instance.
						$active_addon = call_user_func( array( $active_addon, 'get_instance' ) );

						// Add to active Add-Ons array.
						$active_addons[ $active_addon->get_slug() ] = array(
							'slug'    => $active_addon->get_slug(),
							'title'   => $active_addon->_title,
							'version' => $active_addon->_version,
						);

					}

					// Loop through Add-Ons.
					foreach ( $requirement as $addon_slug => $addon_requirements ) {

						// If Add-On requirements is not an array, set Add-On slug to requirements value.
						if ( ! is_array( $addon_requirements ) ) {
							$addon_slug = $addon_requirements;
						}

						// If Add-On is not active, set error.
						if ( ! isset( $active_addons[ $addon_slug ] ) ) {

							// Get Add-On name.
							$addon_name = rgar( $addon_requirements, 'name' ) ? $addon_requirements['name'] : $addon_slug;

							$meets_requirements['meets_requirements'] = false;
							$meets_requirements['errors'][]           = sprintf( esc_html__( 'Required Gravity Forms Add-On is missing: %s.', 'gravityforms' ), $addon_name );
							continue;

						}

						// If Add-On does not meet minimum version, set error.
						if ( rgar( $addon_requirements, 'version' ) && ! version_compare( $active_addons[ $addon_slug ]['version'], $addon_requirements['version'], '>=' ) ) {
							$meets_requirements['meets_requirements'] = false;
							$meets_requirements['errors'][]           = sprintf( esc_html__( 'Required Gravity Forms Add-On "%s" does not meet minimum version requirement: %s.', 'gravityforms' ), $active_addons[ $addon_slug ]['title'], $addon_requirements['version'] );
							continue;
						}
					}

					break;

				case 'plugins':

					// Loop through plugins.
					foreach ( $requirement as $plugin_path => $plugin_name ) {

						// If plugin name is not defined, set plugin path to name.
						if ( is_int( $plugin_path ) ) {
							$plugin_path = $plugin_name;
						}

						// If plugin is not active, set error.
						if ( ! is_plugin_active( $plugin_path ) ) {
							$meets_requirements['meets_requirements'] = false;
							$meets_requirements['errors'][]           = sprintf( esc_html__( 'Required WordPress plugin is missing: %s.', 'gravityforms' ), $plugin_name );
							continue;
						}
					}

				case 'php':

					// Check version.
					if ( rgar( $requirement, 'version' ) && ! version_compare( PHP_VERSION, $requirement['version'], '>=' ) ) {
						$meets_requirements['meets_requirements'] = false;
						$meets_requirements['errors'][]           = sprintf( esc_html__( 'Current PHP version (%s) does not meet minimum PHP version requirement (%s).', 'gravityforms' ), PHP_VERSION, $requirement['version'] );
					}

					// Check extensions.
					if ( rgar( $requirement, 'extensions' ) ) {

						// Loop through extensions.
						foreach ( $requirement['extensions'] as $extension => $extension_requirements ) {

							// If extension requirements is not an array, set extension name to requirements value.
							if ( ! is_array( $extension_requirements ) ) {
								$extension = $extension_requirements;
							}

							// If PHP extension is not loaded, set error.
							if ( ! extension_loaded( $extension ) ) {
								$meets_requirements['meets_requirements'] = false;
								$meets_requirements['errors'][]           = sprintf( esc_html__( 'Required PHP extension missing: %s', 'gravityforms' ), $extension );
								continue;
							}

							// If PHP extension does not meet minimum version, set error.
							if ( rgar( $extension_requirements, 'version' ) && ! version_compare( phpversion( $extension ), $extension_requirements['version'], '>=' ) ) {
								$meets_requirements['meets_requirements'] = false;
								$meets_requirements['errors'][]           = sprintf( esc_html__( 'Required PHP extension "%s" does not meet minimum version requirement: %s.', 'gravityforms' ), $extension, $extension_requirements['version'] );
								continue;
							}

						}

					}

					// Check functions.
					if ( rgar( $requirement, 'functions' ) ) {

						// Loop through functions.
						foreach ( $requirement['functions'] as $function ) {
							if ( ! function_exists( $function ) ) {
								$meets_requirements['meets_requirements'] = false;
								$meets_requirements['errors'][]           = sprintf( esc_html__( 'Required PHP function missing: %s', 'gravityforms' ), $function );
							}
						}

					}

					break;

				case 'wordpress':

					// Check version.
					if ( rgar( $requirement, 'version' ) && ! version_compare( get_bloginfo( 'version' ), $requirement['version'], '>=' ) ) {
						$meets_requirements['meets_requirements'] = false;
						$meets_requirements['errors'][]           = sprintf( esc_html__( 'Current WordPress version (%s) does not meet minimum WordPress version requirement (%s).', 'gravityforms' ), get_bloginfo( 'version' ), $requirement['version'] );
					}

					break;

			}

		}

		return $meets_requirements;

	}

	/**
	 * Register failed requirements page under Gravity Forms settings.
	 *
	 * @since  2.2
	 * @access public
	 *
	 * @uses GFAddOn::current_user_can_any()
	 * @uses GFAddOn::get_short_title()
	 * @uses GFAddOn::plugin_settings_title()
	 * @uses GFCommon::get_base_path()
	 * @uses RGForms::add_settings_page()
	 */
	public function failed_requirements_init() {

		// Get failed requirements.
		$failed_requirements = $this->meets_minimum_requirements();

		// Prepare errors list.
		$errors = '';
		foreach ( $failed_requirements['errors'] as $error ) {
			$errors .= sprintf( '<li>%s</li>', esc_html( $error ) );
		}

		// Prepare error message.
		$error_message = sprintf(
			'%s<br />%s<ol>%s</ol>',
			sprintf( esc_html__( '%s is not able to run because your WordPress environment has not met the minimum requirements.', 'gravityforms' ), $this->_title ),
			sprintf( esc_html__( 'Please resolve the following issues to use %s:', 'gravityforms' ), $this->get_short_title() ),
			$errors
		);

		// Add error message.
		if ( $this->is_form_list() || $this->is_entry_list() || $this->is_form_settings() || $this->is_plugin_settings() || GFForms::get_page() === 'system_status' ) {
			GFCommon::add_error_message( $error_message );
		}

	}

	//--------------  Setup  ---------------

	/**
	 * Performs upgrade tasks when the version of the Add-On changes. To add additional upgrade tasks, override the upgrade() function, which will only get executed when the plugin version has changed.
	 */
	public function setup() {

		//Upgrading add-on
		$installed_version = get_option( 'gravityformsaddon_' . $this->_slug . '_version' );

		//Making sure version has really changed. Gets around aggressive caching issue on some sites that cause setup to run multiple times.
		if ( $installed_version != $this->_version ) {
			$installed_version = GFForms::get_wp_option( 'gravityformsaddon_' . $this->_slug . '_version' );
		}

		//Upgrade if version has changed
		if ( $installed_version != $this->_version ) {

			$this->upgrade( $installed_version );
			update_option( 'gravityformsaddon_' . $this->_slug . '_version', $this->_version );
		}
	}

	/**
	 * Override this function to add to add database update scripts or any other code to be executed when the Add-On version changes
	 */
	public function upgrade( $previous_version ) {
		return;
	}


	/**
	 * Gets called when Gravity Forms upgrade process is completed. This function is intended to be used internally, override the upgrade() function to execute database update scripts.
	 * @param $db_version - Current Gravity Forms database version
	 * @param $previous_db_version - Previous Gravity Forms database version
	 * @param $force_upgrade - True if this is a request to force an upgrade. False if this is a standard upgrade (due to version change)
	 */
	public function post_gravityforms_upgrade( $db_version, $previous_db_version, $force_upgrade ){

		// Forcing Upgrade
		if( $force_upgrade ){

			$installed_version = get_option( 'gravityformsaddon_' . $this->_slug . '_version' );

			$this->upgrade( $installed_version );
			update_option( 'gravityformsaddon_' . $this->_slug . '_version', $this->_version );

		}

	}

	//--------------  Script enqueuing  ---------------

	/**
	 * Override this function to provide a list of styles to be enqueued.
	 * When overriding this function, be sure to call parent::styles() to ensure the base class scripts are enqueued.
	 * See scripts() for an example of the format expected to be returned.
	 */
	public function styles() {
		$min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG || isset( $_GET['gform_debug'] ) ? '' : '.min';
		return array(
			array(
				'handle'  => 'gaddon_form_settings_css',
				'src'     => GFAddOn::get_gfaddon_base_url() . "/css/gaddon_settings{$min}.css",
				'version' => GFCommon::$version,
				'enqueue' => array(
					array( 'admin_page' => array( 'form_settings', 'plugin_settings', 'plugin_page', 'app_settings' ) ),
				)
			),
			array(
				'handle'  => 'gaddon_results_css',
				'src'     => GFAddOn::get_gfaddon_base_url() . "/css/gaddon_results{$min}.css",
				'version' => GFCommon::$version,
				'enqueue' => array(
					array( 'admin_page' => array( 'results' ) ),
				)
			),
		);
	}


	/**
	 * Override this function to provide a list of scripts to be enqueued.
	 * When overriding this function, be sure to call parent::scripts() to ensure the base class scripts are enqueued.
	 * Following is an example of the array that is expected to be returned by this function:
	 * <pre>
	 * <code>
	 *
	 *    array(
	 *        array(
	 *            'handle'    => 'maskedinput',
	 *            'src'       => GFCommon::get_base_url() . '/js/jquery.maskedinput-1.3.min.js',
	 *            'version'   => GFCommon::$version,
	 *            'deps'      => array( 'jquery' ),
	 *            'in_footer' => false,
	 *
	 *            // Determines where the script will be enqueued. The script will be enqueued if any of the conditions match.
	 *            'enqueue'   => array(
	 *                // admin_page - Specified one or more pages (known pages) where the script is supposed to be enqueued.
	 *                // To enqueue scripts in the front end (public website), simply don't define this setting.
	 *                array( 'admin_page' => array( 'form_settings', 'plugin_settings' ) ),
	 *
	 *                // tab - Specifies a form settings or plugin settings tab in which the script is supposed to be enqueued.
	 *                // If none are specified, the script will be enqueued in any of the form settings or plugin_settings page
	 *                array( 'tab' => 'signature'),
	 *
	 *                // query - Specifies a set of query string ($_GET) values.
	 *                // If all specified query string values match the current requested page, the script will be enqueued
	 *                array( 'query' => 'page=gf_edit_forms&view=settings&id=_notempty_' )
	 *
	 *                // post - Specifies a set of post ($_POST) values.
	 *                // If all specified posted values match the current request, the script will be enqueued
	 *                array( 'post' => 'posted_field=val' )
	 *            )
	 *        ),
	 *        array(
	 *            'handle'   => 'super_signature_script',
	 *            'src'      => $this->get_base_url() . '/super_signature/ss.js',
	 *            'version'  => $this->_version,
	 *            'deps'     => array( 'jquery'),
	 *            'callback' => array( $this, 'localize_scripts' ),
	 *            'strings'  => array(
	 *                // Accessible in JavaScript using the global variable "[script handle]_strings"
	 *                'stringKey1' => __( 'The string', 'gravityforms' ),
	 *                'stringKey2' => __( 'Another string.', 'gravityforms' )
	 *            )
	 *            "enqueue"  => array(
	 *                // field_types - Specifies one or more field types that requires this script.
	 *                // The script will only be enqueued if the current form has a field of any of the specified field types.
	 *                // Only applies when a current form is available.
	 *                array( 'field_types' => array( 'signature' ) )
	 *            )
	 *        )
	 *    );
	 *
	 * </code>
	 * </pre>
	 */
	public function scripts() {
		$min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG || isset( $_GET['gform_debug'] ) ? '' : '.min';
		return array(
			array(
				'handle'  => 'gform_form_admin',
				'enqueue' => array( array( 'admin_page' => array( 'form_settings' ) ) )
			),
			array(
				'handle'  => 'gform_gravityforms',
				'enqueue' => array( array( 'admin_page' => array( 'form_settings' ) ) )
			),
			array(
				'handle'  => 'google_charts',
				'src'     => 'https://www.google.com/jsapi',
				'version' => GFCommon::$version,
				'enqueue' => array(
					array( 'admin_page' => array( 'results' ) ),
				)
			),
			array(
				'handle'   => 'gaddon_results_js',
				'src'      => GFAddOn::get_gfaddon_base_url() . "/js/gaddon_results{$min}.js",
				'version'  => GFCommon::$version,
				'deps'     => array( 'jquery', 'sack', 'jquery-ui-resizable', 'gform_datepicker_init', 'google_charts', 'gform_field_filter' ),
				'callback' => array( 'GFResults', 'localize_results_scripts' ),
				'enqueue'  => array(
					array( 'admin_page' => array( 'results' ) ),
				)
			),
			array(
				'handle'  => 'gaddon_repeater',
				'src'     => GFAddOn::get_gfaddon_base_url() . "/js/repeater{$min}.js",
				'version' => GFCommon::$version,
				'deps'    => array( 'jquery' ),
				'enqueue' => array(
					array(
						'admin_page' => array( 'form_settings' ),
					),
				),
			),
			array(
				'handle'   => 'gaddon_fieldmap_js',
				'src'      => GFAddOn::get_gfaddon_base_url() . "/js/gaddon_fieldmap{$min}.js",
				'version'  => GFCommon::$version,
				'deps'     => array( 'jquery', 'gaddon_repeater' ),
				'enqueue'  => array(
					array( 'admin_page' => array( 'form_settings' ) ),
				)
			),
			array(
				'handle'   => 'gaddon_genericmap_js',
				'src'      => GFAddOn::get_gfaddon_base_url() . "/js/gaddon_genericmap{$min}.js",
				'version'  => GFCommon::$version,
				'deps'     => array( 'jquery', 'gaddon_repeater' ),
				'enqueue'  => array(
					array( 'admin_page' => array( 'form_settings' ) ),
				)
			),
			array(
				'handle'   => 'gaddon_selectcustom_js',
				'src'      => GFAddOn::get_gfaddon_base_url() . "/js/gaddon_selectcustom{$min}.js",
				'version'  => GFCommon::$version,
				'deps'     => array( 'jquery' ),
				'enqueue'  => array(
					array( 'admin_page' => array( 'form_settings', 'plugin_settings' ) ),
				)
			),
		);
	}


	/**
	 * Target of admin_enqueue_scripts and gform_enqueue_scripts hooks.
	 * Not intended to be overridden by child classes.
	 * In order to enqueue scripts and styles, override the scripts() and styles() functions
	 *
	 * @ignore
	 */
	public function enqueue_scripts( $form = '', $is_ajax = false ) {

		if ( empty( $form ) ) {
			$form = $this->get_current_form();
		}

		//Enqueueing scripts
		$scripts = $this->scripts();
		foreach ( $scripts as $script ) {
			$src       = isset( $script['src'] ) ? $script['src'] : false;
			$deps      = isset( $script['deps'] ) ? $script['deps'] : array();
			$version   = array_key_exists( 'version', $script ) ? $script['version'] : false;
			$in_footer = isset( $script['in_footer'] ) ? $script['in_footer'] : false;
			wp_register_script( $script['handle'], $src, $deps, $version, $in_footer );
			if ( isset( $script['enqueue'] ) && $this->_can_enqueue_script( $script['enqueue'], $form, $is_ajax ) ) {
				$this->add_no_conflict_scripts( array( $script['handle'] ) );
				wp_enqueue_script( $script['handle'] );
				if ( isset( $script['strings'] ) ) {
					wp_localize_script( $script['handle'], $script['handle'] . '_strings', $script['strings'] );
				}
				if ( isset( $script['callback'] ) && is_callable( $script['callback'] ) ) {
					$args = compact( 'form', 'is_ajax' );
					call_user_func_array( $script['callback'], $args );
				}
			}
		}

		//Enqueueing styles
		$styles = $this->styles();
		foreach ( $styles as $style ) {
			$src     = isset( $style['src'] ) ? $style['src'] : false;
			$deps    = isset( $style['deps'] ) ? $style['deps'] : array();
			$version = array_key_exists( 'version', $style ) ? $style['version'] : false;
			$media   = isset( $style['media'] ) ? $style['media'] : 'all';
			wp_register_style( $style['handle'], $src, $deps, $version, $media );
			if ( $this->_can_enqueue_script( $style['enqueue'], $form, $is_ajax ) ) {
				$this->add_no_conflict_styles( array( $style['handle'] ) );
				if ( $this->is_preview() ) {
					$this->_preview_styles[] = $style['handle'];
				} elseif ( $this->is_print() ) {
					$this->_print_styles[] = $style['handle'];
				} else {
					wp_enqueue_style( $style['handle'] );
				}
			}
		}
	}

	/**
	 * Target of gform_preview_styles. Enqueue styles to the preview page.
	 * Not intended to be overridden by child classes
	 *
	 * @ignore
	 */
	public function enqueue_preview_styles( $preview_styles, $form ) {
		return array_merge( $preview_styles, $this->_preview_styles );
	}


	/**
	 * Target of gform_print_styles. Enqueue styles to the print entry page.
	 * Not intended to be overridden by child classes
	 *
	 * @ignore
	 */
	public function enqueue_print_styles( $print_styles, $form ) {
		if ( false === $print_styles ) {
			$print_styles = array();
		}

		$styles = $this->styles();
		foreach ( $styles as $style ) {
			if ( $this->_can_enqueue_script( $style['enqueue'], $form, false ) ) {
				$this->add_no_conflict_styles( array( $style['handle'] ) );
				$src     = isset( $style['src'] ) ? $style['src'] : false;
				$deps    = isset( $style['deps'] ) ? $style['deps'] : array();
				$version = isset( $style['version'] ) ? $style['version'] : false;
				$media   = isset( $style['media'] ) ? $style['media'] : 'all';
				wp_register_style( $style['handle'], $src, $deps, $version, $media );
				$print_styles[] = $style['handle'];
			}
		}

		return array_merge( $print_styles, $this->_print_styles );
	}


	/**
	 * Adds scripts to the list of white-listed no conflict scripts.
	 *
	 * @param $scripts
	 */
	private function add_no_conflict_scripts( $scripts ) {
		$this->_no_conflict_scripts = array_merge( $scripts, $this->_no_conflict_scripts );

	}

	/**
	 * Adds styles to the list of white-listed no conflict styles.
	 *
	 * @param $styles
	 */
	private function add_no_conflict_styles( $styles ) {
		$this->_no_conflict_styles = array_merge( $styles, $this->_no_conflict_styles );
	}

	private function _can_enqueue_script( $enqueue_conditions, $form = array(), $is_ajax = false ) {
		if ( empty( $enqueue_conditions ) ) {
			return false;
		}

		foreach ( $enqueue_conditions as $condition ) {
			if ( is_callable( $condition ) ) {
				$callback_matches = call_user_func( $condition, $form, $is_ajax );
				if ( $callback_matches ) {
					return true;
				}
			} else {
				$query_matches      = isset( $condition['query'] ) ? $this->_request_condition_matches( $_GET, $condition['query'] ) : true;
				$post_matches       = isset( $condition['post'] ) ? $this->_request_condition_matches( $_POST, $condition['post'] ) : true;
				$admin_page_matches = isset( $condition['admin_page'] ) ? $this->_page_condition_matches( $condition['admin_page'], rgar( $condition, 'tab' ) ) : true;
				$field_type_matches = isset( $condition['field_types'] ) ? $this->_field_condition_matches( $condition['field_types'], $form ) : true;

				if ( $query_matches && $post_matches && $admin_page_matches && $field_type_matches ) {
					return true;
				}
			}
		}

		return false;
	}

	private function _request_condition_matches( $request, $query ) {
		parse_str( $query, $query_array );
		foreach ( $query_array as $key => $value ) {

			switch ( $value ) {
				case '_notempty_' :
					if ( rgempty( $key, $request ) ) {
						return false;
					}
					break;
				case '_empty_' :
					if ( ! rgempty( $key, $request ) ) {
						return false;
					}
					break;
				default :
					if ( rgar( $request, $key ) != $value ) {
						return false;
					}
					break;
			}
		}

		return true;
	}

	private function _page_condition_matches( $pages, $tab ) {
		if ( ! is_array( $pages ) ) {
			$pages = array( $pages );
		}

		foreach ( $pages as $page ) {
			switch ( $page ) {
				case 'form_editor' :
					if ( $this->is_form_editor() ) {
						return true;
					}

					break;

				case 'form_list' :
					if ( $this->is_form_list() ) {
						return true;
					}

					break;

				case 'form_settings' :
					if ( $this->is_form_settings( $tab ) ) {
						return true;
					}

					break;

				case 'plugin_settings' :
					if ( $this->is_plugin_settings( $tab ) ) {
						return true;
					}

					break;

				case 'app_settings' :
					if ( $this->is_app_settings( $tab ) ) {
						return true;
					}

					break;

				case 'plugin_page' :
					if ( $this->is_plugin_page() ) {
						return true;
					}

					break;

				case 'entry_list' :
					if ( $this->is_entry_list() ) {
						return true;
					}

					break;

				case 'entry_view' :
					if ( $this->is_entry_view() ) {
						return true;
					}

					break;

				case 'entry_edit' :
					if ( $this->is_entry_edit() ) {
						return true;
					}

					break;

				case 'results' :
					if ( $this->is_results() ) {
						return true;
					}

					break;

				case 'customizer' :
					if ( is_customize_preview() ) {
						return true;
					}

					break;

			}
		}

		return false;

	}

	private function _field_condition_matches( $field_types, $form ) {
		if ( ! is_array( $field_types ) ) {
			$field_types = array( $field_types );
		}

		/* @var GF_Field[] $fields */
		$fields = GFAPI::get_fields_by_type( $form, $field_types );
		if ( count( $fields ) > 0 ) {
			foreach ( $fields as $field ) {
				if ( $field->is_administrative() && ! $field->allowsPrepopulate && ! GFForms::get_page() ) {
					continue;
				}

				return true;
			}
		}

		return false;
	}

	/**
	 * Target for the gform_noconflict_scripts filter. Adds scripts to the list of white-listed no conflict scripts.
	 *
	 * Not intended to be overridden or called directed by Add-Ons.
	 *
	 * @ignore
	 *
	 * @param array $scripts Array of scripts to be white-listed
	 *
	 * @return array
	 */
	public function register_noconflict_scripts( $scripts ) {
		//registering scripts with Gravity Forms so that they get enqueued when running in no-conflict mode
		return array_merge( $scripts, $this->_no_conflict_scripts );
	}

	/**
	 * Target for the gform_noconflict_styles filter. Adds styles to the list of white-listed no conflict scripts.
	 *
	 * Not intended to be overridden or called directed by Add-Ons.
	 *
	 * @ignore
	 *
	 * @param array $styles Array of styles to be white-listed
	 *
	 * @return array
	 */
	public function register_noconflict_styles( $styles ) {
		//registering styles with Gravity Forms so that they get enqueued when running in no-conflict mode
		return array_merge( $styles, $this->_no_conflict_styles );
	}



	//--------------  Entry meta  --------------------------------------

	/**
	 * Override this method to activate and configure entry meta.
	 *
	 *
	 * @param array $entry_meta An array of entry meta already registered with the gform_entry_meta filter.
	 * @param int   $form_id    The form id
	 *
	 * @return array The filtered entry meta array.
	 */
	public function get_entry_meta( $entry_meta, $form_id ) {
		return $entry_meta;
	}


	//--------------  Results page  --------------------------------------
	/**
	 * Returns the configuration for the results page. By default this is not activated.
	 * To activate the results page override this function and return an array with the configuration data.
	 *
	 * Example:
	 * public function get_results_page_config() {
	 *      return array(
	 *       "title" => 'Quiz Results',
	 *       "capabilities" => array("gravityforms_quiz_results"),
	 *       "callbacks" => array(
	 *          "fields" => array($this, 'results_fields'),
	 *          "calculation" => array($this, 'results_calculation'),
	 *          "markup" => array($this, 'results_markup'),
	 *              )
	 *       );
	 * }
	 *
     * @return array|bool
	 */
	public function get_results_page_config() {
		return false;
	}

	/**
	 * Initializes the result page functionality. To activate result page functionality, override the get_results_page_config() function.
	 *
	 * @param $results_page_config - configuration returned by get_results_page_config()
	 */
	public function results_page_init( $results_page_config ) {
		require_once( 'class-gf-results.php' );

		if ( isset( $results_page_config['callbacks']['filters'] ) ) {
			add_filter( 'gform_filters_pre_results', $results_page_config['callbacks']['filters'], 10, 2 );
		}

		if ( isset( $results_page_config['callbacks']['filter_ui'] ) ) {
			add_filter( 'gform_filter_ui', $results_page_config['callbacks']['filter_ui'], 10, 5 );
		}

		$gf_results = new GFResults( $this->_slug, $results_page_config );
		$gf_results->init();
	}

	//--------------  Logging integration  --------------------------------------

	public function set_logging_supported( $plugins ) {
		$plugins[ $this->_slug ] = $this->_title;

		return $plugins;
	}





	// # PERMISSIONS ---------------------------------------------------------------------------------------------------

	/**
	 * Checks whether the Members plugin is installed and activated.
	 *
	 * Not intended to be overridden or called directly by Add-Ons.
	 *
	 * @ignore
	 *
	 * @return bool
	 */
	public function has_members_plugin() {
		return GFForms::has_members_plugin();
	}

	/**
	 * Register the Gravity Forms Add-Ons capabilities group with the Members plugin.
	 *
	 * @since  2.4
	 * @access public
	 */
	public function members_register_cap_group() {

		members_register_cap_group(
			'gravityforms_addons',
			array(
				'label' => esc_html__( 'GF Add-Ons', 'gravityforms' ),
				'icon'  => 'dashicons-gravityforms',
				'caps'  => array(),
			)
		);

	}

	/**
	 * Register the Add-On capabilities and their human readable labels with the Members plugin.
	 *
	 * @since  2.4
	 * @access public
	 *
	 * @uses   GFAddOn::get_short_title()
	 */
	public function members_register_caps() {

        // Get capabilities.
        $caps = $this->get_members_caps();

		// If no capabilities were found, exit.
		if ( empty( $caps ) ) {
			return;
		}

		// Register capabilities.
		foreach ( $caps as $cap => $label ) {
			members_register_cap(
				$cap,
				array(
					'label' => sprintf( '%s: %s', $this->get_short_title(), $label ),
					'group' => 'gravityforms_addons',
				)
			);
		}

	}

	/**
	 * Get Add-On capabilities and their human readable labels.
	 *
	 * @since  2.4
	 * @access public
	 *
	 * @return array
	 */
	public function get_members_caps() {

		// Initialize capabilities array.
		$caps = array();

		// Add capabilities.
		if ( ! empty( $this->_capabilities_form_settings ) && is_string( $this->_capabilities_form_settings ) ) {
			$caps[ $this->_capabilities_form_settings ] = esc_html__( 'Form Settings', 'gravityforms' );
		}
		if ( ! empty( $this->_capabilities_uninstall ) && is_string( $this->_capabilities_uninstall ) ) {
			$caps[ $this->_capabilities_uninstall ] = esc_html__( 'Uninstall', 'gravityforms' );
		}
		if ( ! empty( $this->_capabilities_plugin_page ) && is_string( $this->_capabilities_plugin_page ) ) {
			$caps[ $this->_capabilities_plugin_page ] = esc_html__( 'Add-On Page', 'gravityforms' );
		}
		if ( ! empty( $this->_capabilities_settings_page ) && is_string( $this->_capabilities_settings_page ) ) {
			$caps[ $this->_capabilities_settings_page ] = esc_html__( 'Add-On Settings', 'gravityforms' );
		}

		$results_cap = rgars( $this->get_results_page_config(), 'capabilities/0' );
		if ( ! empty( $results_cap ) && $results_cap !== 'gravityforms_view_entries' && ! isset( $caps[ $results_cap ] ) ) {
			$caps[ $results_cap ] = esc_html__( 'Results Page', 'gravityforms' );
		}

		return $caps;

	}

	/**
	 * Register Gravity Forms Add-Ons capabilities group with User Role Editor plugin.
	 *
	 * @since  2.4
	 *
	 * @param array $groups Existing capabilities groups.
	 *
	 * @return array
	 */
	public static function filter_ure_capabilities_groups_tree( $groups = array() ) {

		$groups['gravityforms_addons'] = array(
			'caption' => esc_html__( 'Gravity Forms Add-Ons', 'gravityforms' ),
			'parent'  => 'gravityforms',
			'level'   => 3,
		);

		return $groups;

	}

	/**
	 * Register Gravity Forms capabilities with Gravity Forms group in User Role Editor plugin.
	 *
	 * @since  2.4
	 *
	 * @param array  $groups Current capability groups.
	 * @param string $cap_id Capability identifier.
	 *
	 * @return array
	 */
	public function filter_ure_custom_capability_groups( $groups = array(), $cap_id = '' ) {

		// Get Add-On capabilities.
		$caps = $this->_capabilities;

		// If capability belongs to Add-On, register it to group.
		if ( in_array( $cap_id, $caps, true ) ) {
			$groups[] = 'gravityforms_addons';
		}

		return $groups;

	}

	/**
	 * Checks whether the current user is assigned to a capability or role.
	 *
	 * @since  Unknown
	 * @access public
	 *
	 * @param string|array $caps An string or array of capabilities to check
	 *
	 * @return bool Returns true if the current user is assigned to any of the capabilities.
	 */
	public function current_user_can_any( $caps ) {
		return GFCommon::current_user_can_any( $caps );
	}


	//------- Settings Helper Methods (Common to all settings pages) -------------------

	/***
	 * Renders the UI of all settings page based on the specified configuration array $sections
	 *
	 * @param array $sections - Configuration array containing all fields to be rendered grouped into sections
	 */
	public function render_settings( $sections ) {

		if ( ! $this->has_setting_field_type( 'save', $sections ) ) {
			$sections = $this->add_default_save_button( $sections );
		}

		?>

		<form id="gform-settings" action="" method="post">
			<?php wp_nonce_field( $this->_slug . '_save_settings', '_' . $this->_slug . '_save_settings_nonce' ) ?>
			<?php $this->settings( $sections ); ?>

		</form>

	<?php
	}

	/***
	 * Renders settings fields based on the specified configuration array $sections
	 *
	 * @param array $sections - Configuration array containing all fields to be rendered grouped into sections
	 */
	public function settings( $sections ) {
		$is_first = true;
		foreach ( $sections as $section ) {
			if ( $this->setting_dependency_met( rgar( $section, 'dependency' ) ) ) {
				$this->single_section( $section, $is_first );
			}

			$is_first = false;
		}
	}

	/***
	 * Displays the UI for a field section
	 *
	 * @param array $section  - The section to be displayed
	 * @param bool  $is_first - true for the first section in the list, false for all others
	 */
	public function single_section( $section, $is_first = false ) {

		extract(
			wp_parse_args(
				$section, array(
					'title'       => false,
					'description' => false,
					'id'          => '',
					'class'       => false,
					'style'       => '',
					'tooltip'     => false,
					'tooltip_class' => ''
				)
			)
		);

		$classes = array( 'gaddon-section' );

		if ( $is_first ) {
			$classes[] = 'gaddon-first-section';
		}

		if ( $class )
			$classes[] = $class;

		?>

		<div
			id="<?php echo $id; ?>"
			class="<?php echo implode( ' ', $classes ); ?>"
			style="<?php echo $style; ?>"
			>

			<?php if ( $title ): ?>
				<h4 class="gaddon-section-title gf_settings_subgroup_title">
					<?php echo $title; ?>
					<?php if( $tooltip ): ?>
						<?php gform_tooltip( $tooltip, $tooltip_class ); ?>
					<?php endif; ?>
				</h4>
			<?php endif; ?>

			<?php if ( $description ): ?>
				<div class="gaddon-section-description"><?php echo $description; ?></div>
			<?php endif; ?>

			<table class="form-table gforms_form_settings">

				<?php
				foreach ( $section['fields'] as $field ) {

					if ( ! $this->setting_dependency_met( rgar( $field, 'dependency' ) ) )
						continue;

					if ( is_callable( array( $this, "single_setting_row_{$field['type']}" ) ) ) {
						call_user_func( array( $this, "single_setting_row_{$field['type']}" ), $field );
					} else {
						$this->single_setting_row( $field );
					}
				}
				?>

			</table>

		</div>

	<?php
	}

	/***
	 * Displays the UI for the field container row
	 *
	 * @param array $field - The field to be displayed
	 */
	public function single_setting_row( $field ) {

		$display = rgar( $field, 'hidden' ) || rgar( $field, 'type' ) == 'hidden' ? 'style="display:none;"' : '';

		// Prepare setting description.
		$description = rgar( $field, 'description' ) ? '<span class="gf_settings_description">' . $field['description'] . '</span>' : null;

		?>

		<tr id="gaddon-setting-row-<?php echo $field['name'] ?>" <?php echo $display; ?>>
			<th>
				<?php $this->single_setting_label( $field ); ?>
			</th>
			<td>
				<?php
					$this->single_setting( $field );
					echo $description;
				?>
			</td>
		</tr>

	<?php
	}

	/**
	 * Displays the label for a field, including the tooltip and requirement indicator.
	 */
	public function single_setting_label( $field ) {

		echo rgar( $field, 'label' );

		if ( isset( $field['tooltip'] ) ) {
			echo $this->maybe_get_tooltip( $field );
		}

		if ( rgar( $field, 'required' ) ) {
			echo ' ' . $this->get_required_indicator( $field );
		}

	}

	public function single_setting_row_save( $field ) {
		?>

		<tr>
			<td colspan="2">
				<?php $this->single_setting( $field ); ?>
			</td>
		</tr>

	<?php
	}

	/***
	 * Calls the appropriate field function to handle rendering of each specific field type
	 *
	 * @param array $field - The field to be rendered
	 */
	public function single_setting( $field ) {
		if ( is_callable( rgar( $field, 'callback' ) ) ) {
			call_user_func( $field['callback'], $field );
		} elseif ( is_callable( array( $this, "settings_{$field['type']}" ) ) ) {
			call_user_func( array( $this, "settings_{$field['type']}" ), $field );
		} else {
			printf( esc_html__( "Field type '%s' has not been implemented", 'gravityforms' ), esc_html( $field['type'] ) );
		}
	}

	/***
	 * Sets the current saved settings to a class variable so that it can be accessed by lower level functions in order to initialize inputs with the appropriate values
	 *
	 * @param array $settings : Settings to be saved
	 */
	public function set_settings( $settings ) {
		$this->_saved_settings = $settings;
	}

	/***
	 * Sets the previous settings to a class variable so that it can be accessed by lower level functions providing support for
	 * verifying whether a value was changed before executing an action
	 *
	 * @param array $settings : Settings to be stored
	 */
	public function set_previous_settings( $settings ) {
		$this->_previous_settings = $settings;
	}

	public function get_previous_settings() {
		return $this->_previous_settings;
	}


	/***
	 * Gets settings from $_POST variable, returning a name/value collection of setting name and setting value
	 */
	public function get_posted_settings() {
		global $_gaddon_posted_settings;

		if ( isset( $_gaddon_posted_settings ) ) {
			return $_gaddon_posted_settings;
		}

		$_gaddon_posted_settings = array();
		if ( count( $_POST ) > 0 ) {
			foreach ( $_POST as $key => $value ) {
				if ( preg_match( '|_gaddon_setting_(.*)|', $key, $matches ) ) {
					$_gaddon_posted_settings[ $matches[1] ] = self::maybe_decode_json( stripslashes_deep( $value ) );
				}
			}
		}

		return $_gaddon_posted_settings;
	}

	public static function maybe_decode_json( $value ) {
		if ( self::is_json( $value ) ) {
			return json_decode( $value, ARRAY_A );
		}

		return $value;
	}

	public static function is_json( $value ) {
		if ( is_string( $value ) && in_array( substr( $value, 0, 1 ), array( '{', '[' ) ) && is_array( json_decode( $value, ARRAY_A ) ) ) {
			return true;
		}

		return false;
	}

	/***
	 * Gets the "current" settings, which are settings from $_POST variables if this is a postback request, or the current saved settings for a get request.
	 */
	public function get_current_settings() {
		//try getting settings from post
		$settings = $this->get_posted_settings();

		//if nothing has been posted, get current saved settings
		if ( empty( $settings ) ) {
			$settings = $this->_saved_settings;
		}

		return $settings;
	}

	/***
	 * Retrieves the setting for a specific field/input
	 *
	 * @param string $setting_name  The field or input name
	 * @param string $default_value Optional. The default value
	 * @param bool|array $settings Optional. THe settings array
	 *
	 * @return string|array
	 */
	public function get_setting( $setting_name, $default_value = '', $settings = false ) {

		if ( ! $settings ) {
			$settings = $this->get_current_settings();
		}

		if ( false === $settings ) {
			return $default_value;
		}

		if ( strpos( $setting_name, '[' ) !== false ) {
			$path_parts = explode( '[', $setting_name );
			foreach ( $path_parts as $part ) {
				$part = trim( $part, ']' );
				if ( $part != '0'){
					if ( empty( $part ) ) {
						return $settings;
					}
				}
				if ( false === isset( $settings[ $part ] ) ) {
					return $default_value;
				}

				$settings = rgar( $settings, $part );
			}
			$setting = $settings;
		} else {
			if ( false === isset( $settings[ $setting_name ] ) ) {
				return $default_value;
			}
			$setting = $settings[ $setting_name ];
		}

		return $setting;
	}

	/***
	 * Determines if a dependent field has been populated.
	 *
	 * @param string $dependency - Field or input name of the "parent" field.
	 *
	 * @return bool - true if the "parent" field has been filled out and false if it has not.
	 *
	 */
	public function setting_dependency_met( $dependency ) {

		// if no dependency, always return true
		if ( ! $dependency ) {
			return true;
		}

		//use a callback if one is specified in the configuration
		if ( is_callable( $dependency ) ) {
			return call_user_func( $dependency );
		}

		if ( is_array( $dependency ) ) {
			//supports: 'dependency' => array("field" => 'myfield', 'values' => array("val1", 'val2'))
			$dependency_field = $dependency['field'];
			$dependency_value = $dependency['values'];
		} else {
			//supports: 'dependency' => 'myfield'
			$dependency_field = $dependency;
			$dependency_value = '_notempty_';
		}

		if ( ! is_array( $dependency_value ) ) {
			$dependency_value = array( $dependency_value );
		}

		$current_value = $this->get_setting( $dependency_field );

		foreach ( $dependency_value as $val ) {
			if ( $current_value == $val ) {
				return true;
			}

			if ( $val == '_notempty_' && ! rgblank( $current_value ) ) {
				return true;
			}
		}

		return false;
	}

	public function has_setting_field_type( $type, $fields ) {
        if ( ! empty( $fields ) ) {
			foreach ( $fields as &$section ) {
				foreach ( $section['fields'] as $field ) {
					if ( rgar( $field, 'type' ) == $type ) {
						return true;
					}
				}
			}
		}
		return false;
	}

	public function add_default_save_button( $sections ) {
		$sections[ count( $sections ) - 1 ]['fields'][] = array( 'type' => 'save' );

		return $sections;
	}

	public function get_save_success_message( $sections ) {
		$save_button = $this->get_save_button( $sections );

		return isset( $save_button['messages']['success'] ) ? $save_button['messages']['success'] : sprintf( esc_html__( '%s settings updated.', 'gravityforms' ), $this->get_short_title() );
	}

	public function get_save_error_message( $sections ) {
		$save_button = $this->get_save_button( $sections );

		return isset( $save_button['messages']['error'] ) ? $save_button['messages']['error'] : esc_html__( 'There was an error while saving your settings.', 'gravityforms' );
	}

	public function get_save_button( $sections ) {
		$sections = array_values( $sections );
		$fields   = $sections[ count( $sections ) - 1 ]['fields'];

		foreach ( $fields as $field ) {
			if ( $field['type'] == 'save' )
				return $field;
		}

		return false;
	}



	//------------- Field Types ------------------------------------------------------

	/***
	 * Renders and initializes a text field based on the $field array
	 *
	 * @param array $field - Field array containing the configuration options of this field
	 * @param bool  $echo  = true - true to echo the output to the screen, false to simply return the contents as a string
	 *
	 * @return string The HTML for the field
	 */
	public function settings_text( $field, $echo = true ) {

		$field['type']       = 'text'; //making sure type is set to text
		$field['input_type'] = rgar( $field, 'input_type' ) ? rgar( $field, 'input_type' ) : 'text';
		$attributes          = $this->get_field_attributes( $field );
		$default_value       = rgar( $field, 'value' ) ? rgar( $field, 'value' ) : rgar( $field, 'default_value' );
		$value               = $this->get_setting( $field['name'], $default_value );

		// Add autocomplete attribute for password inputs.
		if ( 'password' === $field['input_type'] ) {
			$attributes['autocomplete'] = 'autocomplete="off"';
		}

		$html    = '';

		$html .= '<input
                    type="' . esc_attr( $field['input_type'] ) . '"
                    name="_gaddon_setting_' . esc_attr( $field['name'] ) . '"
                    value="' . esc_attr( htmlspecialchars( $value, ENT_QUOTES ) ) . '" ' .
		         implode( ' ', $attributes ) .
		         ' />';

		$html .= rgar( $field, 'after_input' );

		$feedback_callback = rgar( $field, 'feedback_callback' );
		if ( is_callable( $feedback_callback ) ) {
			$is_valid = call_user_func_array( $feedback_callback, array( $value, $field ) );
			$icon     = '';
			if ( $is_valid === true ) {
				$icon = 'icon-check fa-check gf_valid'; // check icon
			} elseif ( $is_valid === false ) {
				$icon = 'icon-remove fa-times gf_invalid'; // x icon
			}

			if ( ! empty( $icon ) ) {
				$html .= "&nbsp;&nbsp;<i class=\"fa {$icon}\"></i>";
			}
		}

		if ( $this->field_failed_validation( $field ) ) {
			$html .= $this->get_error_icon( $field );
		}

		if ( $echo ) {
			echo $html;
		}

		return $html;
	}

	/***
	 * Renders and initializes a textarea field based on the $field array
	 *
	 * @param array $field - Field array containing the configuration options of this field
	 * @param bool  $echo  = true - true to echo the output to the screen, false to simply return the contents as a string
	 *
	 * @return string The HTML for the field
	 */
	public function settings_textarea( $field, $echo = true ) {
		$field['type'] = 'textarea'; //making sure type is set to textarea
		$attributes    = $this->get_field_attributes( $field );
		$default_value = rgar( $field, 'value' ) ? rgar( $field, 'value' ) : rgar( $field, 'default_value' );
		$value         = $this->get_setting( $field['name'], $default_value );

		$name    = '' . esc_attr( $field['name'] );
		$html    = '';

		if ( rgar( $field, 'use_editor' ) ) {

			$html .= '<span class="mt-gaddon-editor mt-_gaddon_setting_'. $field['name'] .'"></span>';

			ob_start();

			wp_editor( $value, '_gaddon_setting_'. $field['name'], array( 'autop' => false, 'editor_class' => 'merge-tag-support mt-wp_editor mt-manual_position mt-position-right' ) );

			$html .= ob_get_contents();
			ob_end_clean();

		} else {

			$html .= '<textarea
                    name="_gaddon_setting_' . $name . '" ' .
		         implode( ' ', $attributes ) .
		         '>' .
			         esc_textarea( $value ) .
		         '</textarea>';

		}

		if ( $this->field_failed_validation( $field ) ) {
			$html .= $this->get_error_icon( $field );
		}

		if ( $echo ) {
			echo $html;
		}

		return $html;
	}


	/***
	 * Renders and initializes a hidden field based on the $field array
	 *
	 * @param array $field - Field array containing the configuration options of this field
	 * @param bool  $echo  = true - true to echo the output to the screen, false to simply return the contents as a string
	 *
	 * @return string The HTML for the field
	 */
	public function settings_hidden( $field, $echo = true ) {
		$field['type'] = 'hidden'; //making sure type is set to hidden
		$attributes    = $this->get_field_attributes( $field );

		$default_value = rgar( $field, 'value' ) ? rgar( $field, 'value' ) : rgar( $field, 'default_value' );
		$value         = $this->get_setting( $field['name'], $default_value );

		if ( is_array( $value ) ) {
			$value = json_encode( $value );
		}

		$html = '<input
                    type="hidden"
                    name="_gaddon_setting_' . esc_attr( $field['name'] ) . '"
                    value=\'' . esc_attr( $value ) . '\' ' .
		        implode( ' ', $attributes ) .
		        ' />';

		if ( $echo ) {
			echo $html;
		}

		return $html;
	}

	/***
	 * Renders and initializes a checkbox field or a collection of checkbox fields based on the $field array
	 *
	 * @param array $field - Field array containing the configuration options of this field
	 * @param bool  $echo  = true - true to echo the output to the screen, false to simply return the contents as a string
	 *
	 * @return string The HTML for the field
	 */
	public function settings_checkbox( $field, $echo = true ) {

		$field['type'] = 'checkbox'; //making sure type is set to checkbox

		$field_attributes   = $this->get_field_attributes( $field, array() );
		$have_icon          = $this->choices_have_icon( rgar( $field, 'choices' ) );
		$horizontal         = rgar( $field, 'horizontal' ) || $have_icon ? ' gaddon-setting-inline' : '';



		$html = '';
		$default_choice_attributes = array( 'onclick' => 'jQuery(this).siblings("input[type=hidden]").val(jQuery(this).prop("checked") ? 1 : 0);', 'onkeypress' => 'jQuery(this).siblings("input[type=hidden]").val(jQuery(this).prop("checked") ? 1 : 0);' );
		$is_first_choice = true;
		if ( is_array( $field['choices'] ) ) {
			foreach ( $field['choices'] as $choice ) {
				$choice['id']      = sanitize_title( $choice['name'] );
				$choice_attributes = $this->get_choice_attributes( $choice, $field_attributes, $default_choice_attributes );
				$value             = $this->get_setting( $choice['name'], rgar( $choice, 'default_value' ) );
				$tooltip           = $this->maybe_get_tooltip( $choice );

				//displaying error message after first checkbox item
				$error_icon = '';
				if ( $is_first_choice ){
					$error_icon = $this->field_failed_validation( $field ) ? $this->get_error_icon( $field ) : '';
				}

				// Add icon to choice if choices have icon
				if ( $have_icon ) {
					$choice['icon'] = rgar( $choice, 'icon' ) ? $choice['icon'] : 'fa-cog';
				}

				$html .= $this->checkbox_item( $choice, $horizontal, $choice_attributes, $value, $tooltip, $error_icon );

				$is_first_choice = false;
			}
		}

		if ( $echo ) {
			echo $html;
		}

		return $html;
	}


	/**
	 * Returns the markup for an individual checkbox item give the parameters
	 *
	 * @param $choice           - Choice array with all configured properties
	 * @param $horizontal_class - CSS class to style checkbox items horizontally
	 * @param $attributes       - String containing all the attributes for the input tag.
	 * @param $value            - Currently selection (1 if field has been checked. 0 or null otherwise)
	 * @param $tooltip          - String containing a tooltip for this checkbox item.
	 *
	 * @return string - The markup of an individual checkbox item
	 */
	public function checkbox_item( $choice, $horizontal_class, $attributes, $value, $tooltip, $error_icon = '' ) {

		$hidden_field_value = $value == '1' ? '1' : '0';
		$icon_class         = rgar( $choice, 'icon' ) ? ' gaddon-setting-choice-visual' : '';

		$checkbox_item  = '<div id="gaddon-setting-checkbox-choice-' . $choice['id'] . '" class="gaddon-setting-checkbox' . $horizontal_class . $icon_class . '">';
		$checkbox_item .= '<input type=hidden name="_gaddon_setting_' . esc_attr( $choice['name'] ) . '" value="' . $hidden_field_value . '" />';

		if ( is_callable( array( $this, "checkbox_input_{$choice['name']}" ) ) ) {
			$markup = call_user_func( array( $this, "checkbox_input_{$choice['name']}" ), $choice, $attributes, $value, $tooltip );
		} else {
			$markup = $this->checkbox_input( $choice, $attributes, $value, $tooltip );
		}

		$checkbox_item .= $markup . $error_icon . '</div>';

		return $checkbox_item;
	}

	/**
	 * Returns the markup for an individual checkbox input and its associated label
	 *
	 * @param $choice     - Choice array with all configured properties
	 * @param $attributes - String containing all the attributes for the input tag.
	 * @param $value      - Currently selection (1 if field has been checked. 0 or null otherwise)
	 * @param $tooltip    - String containing a tooltip for this checkbox item.
	 *
	 * @return string - The markup of an individual checkbox input and its associated label
	 */
	public function checkbox_input( $choice, $attributes, $value, $tooltip ) {

		$icon_tag = '';

		if ( rgar( $choice, 'icon' ) ) {

			/* Get the defined icon. */
			$icon = rgar( $choice, 'icon' ) ? $choice['icon'] : 'fa-cog';

			/* Set icon tag based on icon type. */
			if ( filter_var( $icon, FILTER_VALIDATE_URL ) ) {
				$icon_tag = '<img src="' . esc_attr( $icon ) .'" />';
			} else {
				$icon_tag = '<i class="fa ' . esc_attr( $icon ) .'"></i>';
			}

			$icon_tag .= '<br />';

		}

		$markup  = '<input type = "checkbox" ' . implode( ' ', $attributes ) . ' ' . checked( $value, '1', false ) . ' />';
		$markup .= '<label for="' . esc_attr( $choice['id'] ) . '"><span>' . $icon_tag . esc_html( $choice['label'] ) . $tooltip . '</span></label>';


		return $markup;
	}


	/***
	 * Renders and initializes a radio field or a collection of radio fields based on the $field array
	 *
	 * @param array $field - Field array containing the configuration options of this field
	 * @param bool  $echo  = true - true to echo the output to the screen, false to simply return the contents as a string
	 *
	 * @return string Returns the markup for the radio buttons
	 *
	 */
	public function settings_radio( $field, $echo = true ) {

		$field['type'] = 'radio'; //making sure type is set to radio

		$selected_value   = $this->get_setting( $field['name'], rgar( $field, 'default_value' ) );
		$field_attributes = $this->get_field_attributes( $field );
		$have_icon        = $this->choices_have_icon( rgar( $field, 'choices' ) );
		$horizontal       = rgar( $field, 'horizontal' ) || $have_icon ? ' gaddon-setting-inline' : '';
		$html             = '';

		if ( is_array( $field['choices'] ) ) {


			foreach ( $field['choices'] as $i => $choice ) {

				if ( rgempty( 'id', $choice ) ) {
					$choice['id'] = $field['name'] . $i;
				}

				$choice_attributes = $this->get_choice_attributes( $choice, $field_attributes );
				$tooltip           = $this->maybe_get_tooltip( $choice );
				$radio_value       = isset( $choice['value'] ) ? $choice['value'] : $choice['label'];
				$checked           = checked( $selected_value, $radio_value, false );

				if ( $have_icon ) {

					/* Get the defined icon. */
					$icon = rgar( $choice, 'icon' ) ? $choice['icon'] : 'fa-cog';

					/* Set icon tag based on icon type. */
					if ( filter_var( $icon, FILTER_VALIDATE_URL ) ) {
						$icon_tag = '<img src="' . esc_attr( $icon ) .'" />';
					} else {
						$icon_tag = '<i class="fa ' . esc_attr( $icon ) .'"></i>';
					}

					$html .= '<div id="gaddon-setting-radio-choice-' . $choice['id'] . '" class="gaddon-setting-radio gaddon-setting-choice-visual' . $horizontal . '">';
	                $html .= '<input type="radio" name="_gaddon_setting_' . esc_attr( $field['name'] ) . '" ' .
					         'value="' . $radio_value . '" ' . implode( ' ', $choice_attributes ) . ' ' . $checked . ' />';
					$html .= '<label for="' . esc_attr( $choice['id'] ) . '">';
					$html .= '<span>' . $icon_tag . '<br />' . esc_html( $choice['label'] ) . $tooltip . '</span>';
	                $html .= '</label>';
	                $html .= '</div>';

				} else {

					$html .= '<div id="gaddon-setting-radio-choice-' . $choice['id'] . '" class="gaddon-setting-radio' . $horizontal . '">';
					$html .= '<label for="' . esc_attr( $choice['id'] ) . '">';
	                $html .= '<input type="radio" name="_gaddon_setting_' . esc_attr( $field['name'] ) . '" ' .
					         'value="' . $radio_value . '" ' . implode( ' ', $choice_attributes ) . ' ' . $checked . ' />';
					$html .= '<span>' . esc_html( $choice['label'] ) . $tooltip . '</span>';
	                $html .= '</label>';
	                $html .= '</div>';

				}

			}

		}

		if ( $this->field_failed_validation( $field ) ) {
			$html .= $this->get_error_icon( $field );
		}

		if ( $echo ) {
			echo $html;
		}

		return $html;
	}

	/**
	 * Determines if any of the available settings choices have an icon.
	 *
	 * @access public
	 * @param array $choices (default: array())
	 * @return bool
	 */
	public function choices_have_icon( $choices = array() ) {

		$have_icon = false;

		foreach ( $choices as $choice ) {
			if ( rgar( $choice, 'icon' ) ) {
				$have_icon = true;
			}
		}

		return $have_icon;

	}

	/***
	 * Renders and initializes a drop down field based on the $field array
	 *
	 * @param array $field - Field array containing the configuration options of this field
	 * @param bool  $echo  = true - true to echo the output to the screen, false to simply return the contents as a string
	 *
	 * @return string The HTML for the field
	 */
	public function settings_select( $field, $echo = true ) {

		$field['type'] = 'select'; // making sure type is set to select
		$attributes    = $this->get_field_attributes( $field );
		$value         = $this->get_setting( $field['name'], rgar( $field, 'default_value' ) );
		$name          = '' . esc_attr( $field['name'] );

		// If no choices were provided and there is a no choices message, display it.
		if ( ( empty( $field['choices'] ) || ! rgar( $field, 'choices' ) ) && rgar( $field, 'no_choices' ) ) {

			$html = $field['no_choices'];

		} else {

			$html = sprintf(
				'<select name="%1$s" %2$s>%3$s</select>',
				'_gaddon_setting_' . $name, implode( ' ', $attributes ), $this->get_select_options( $field['choices'], $value )
			);

			$html .= rgar( $field, 'after_select' );

		}

		if ( $this->field_failed_validation( $field ) ) {
			$html .= $this->get_error_icon( $field );
		}

		if ( $echo ) {
			echo $html;
		}

		return $html;
	}

	/**
	 * Renders and initializes a drop down field with a input field for custom input based on the $field array.
	 *
	 * @param array $field - Field array containing the configuration options of this field
	 * @param bool  $echo  = true - true to echo the output to the screen, false to simply return the contents as a string
	 *
	 * @return string The HTML for the field
	 */
	public function settings_select_custom( $field, $echo = true ) {

		/* Prepare select field */
		$select_field             = $field;
		$select_field_value       = $this->get_setting( $select_field['name'], rgar( $select_field, 'default_value' ) );
		$select_field['onchange'] = '';
		$select_field['class']    = ( isset( $select_field['class'] ) ) ? $select_field['class'] . 'gaddon-setting-select-custom' : 'gaddon-setting-select-custom';

		/* Prepare input field */
		$input_field          = $field;
		$input_field['name'] .= '_custom';
		$input_field['style'] = 'width:200px;max-width:90%;';
		$input_field_display  = '';

		/* Loop through select choices and make sure option for custom exists */
		$has_gf_custom = false;
		foreach ( $select_field['choices'] as $choice ) {

			if ( rgar( $choice, 'name' ) == 'gf_custom' || rgar( $choice, 'value' ) == 'gf_custom' ) {
				$has_gf_custom = true;
			}

			/* If choice has choices, check inside those choices. */
			if ( rgar( $choice, 'choices' ) ) {
				foreach ( $choice['choices'] as $subchoice ) {
					if ( rgar( $subchoice, 'name' ) == 'gf_custom' || rgar( $subchoice, 'value' ) == 'gf_custom' ) {
						$has_gf_custom = true;
					}
				}
			}

		}
		if ( ! $has_gf_custom ) {
			$select_field['choices'][] = array(
				'label' => esc_html__( 'Add Custom', 'gravityforms' ) .' ' . $select_field['label'],
				'value' => 'gf_custom'
			);
		}

		/* If select value is "gf_custom", hide the select field and display the input field. */
		if ( $select_field_value == 'gf_custom' || ( count( $select_field['choices'] ) == 1 && $select_field['choices'][0]['value'] == 'gf_custom' ) ) {
			$select_field['style'] = 'display:none;';
		} else {
			$input_field_display   = ' style="display:none;"';
		}

		/* Add select field */
		$html = $this->settings_select( $select_field, false );

		/* Add input field */
		$html .= '<div class="gaddon-setting-select-custom-container"'. $input_field_display .'>';
		$html .= count( $select_field['choices'] ) > 1 ? '<a href="#" class="select-custom-reset">Reset</a>' : '';
		$html .= $this->settings_text( $input_field, false );
		$html .= '</div>';

		if ( $echo ) {
			echo $html;
		}

		return $html;

	}

	/**
	 * Prepares an HTML string of options for a drop down field.
	 *
	 * @param array  $choices - Array containing all the options for the drop down field
	 * @param string $selected_value - The value currently selected for the field
	 *
	 * @return string The HTML for the select options
	 */
	public function get_select_options( $choices, $selected_value ) {

		$options = '';

		foreach ( $choices as $choice ) {

			if ( isset( $choice['choices'] ) ) {

				$options .= sprintf( '<optgroup label="%1$s">%2$s</optgroup>', esc_attr( $choice['label'] ), $this->get_select_options( $choice['choices'], $selected_value ) );

			} else {

				if ( ! isset( $choice['value'] ) ) {
					$choice['value'] = $choice['label'];
				}

				$options .= $this->get_select_option( $choice, $selected_value );

			}
		}

		return $options;
	}

	/**
	 * Prepares an HTML string for a single drop down field option.
	 *
	 * @access protected
	 * @param array  $choice - Array containing the settings for the drop down option
	 * @param string $selected_value - The value currently selected for the field
	 *
	 * @return string The HTML for the select choice
	 */
	public function get_select_option( $choice, $selected_value ) {
		if ( is_array( $selected_value ) ) {
			$selected = in_array( $choice['value'], $selected_value ) ? "selected='selected'" : '';
		} else {
			$selected = selected( $selected_value, $choice['value'], false );
		}

		return sprintf( '<option value="%1$s" %2$s>%3$s</option>', esc_attr( $choice['value'] ), $selected, $choice['label'] );
	}





	//------------- Field Map Field Type --------------------------

	/**
	 * Renders and initializes a generic map field based on the $field array whose choices are populated by the fields to be mapped.
	 *
	 * @since  2.2
	 * @access public
	 *
	 * @uses GFAddOn::field_failed_validation()
	 * @uses GFCommon::get_base_url()
	 * @uses GFAddOn::get_current_forn()
	 * @uses GFAddOn::get_error_icon()
	 * @uses GFAddOn::get_mapping_field()
	 * @uses GFAddOn::settings_hidden()
	 *
	 * @param array $field Field array containing the configuration options of this field.
	 * @param bool  $echo  Determines if field contents should automatically be displayed. Defaults to true.
	 *
	 * @return string The HTML for the field
	 */
	public function settings_generic_map( $field, $echo = true ) {

		// Initialize return HTML string.
		$html = '';

		// Shift field map choices to key property.
		if ( isset( $field['field_map' ] ) ) {
			$field['key_choices'] = $field['field_map'];
		}

		// Shift legacy title properties.
		foreach ( array( 'key', 'value' ) as $type ) {
			if ( isset( $field[ $type . '_field_title' ] ) ) {
				$field[ $type . '_field' ]['title'] = $field[ $type . '_field_title' ];
				unset( $field[ $type . '_field_title' ] );
			}
		}

		// Set merge tags state to false if not defined.
		if ( ! rgar( $field, 'merge_tags' ) ) {
			$field['merge_tags'] = false;
		}

		// Initialize field objects for child fields.
		$value_field = $key_field = $custom_key_field = $custom_value_field = $field;

		// Define custom placeholder.
		$custom_placeholder = 'gf_custom';

		// Define key field properties.
		$key_field['name']    .= '_key';
		$key_field['choices']  = rgar( $field, 'key_choices' ) ? $field['key_choices'] : rgars( $field, 'key_field/choices' );
		$key_field['class']    = 'key key_{i}';

		// Define custom key field properties.
		$custom_key_field['name']        .= '_custom_key_{i}';
		$custom_key_field['class']        = 'custom_key custom_key_{i}';
		$custom_key_field['value']        = '{custom_key}';
		$custom_key_field['placeholder']  = rgars( $field, 'key_field/placeholder' ) ? $field['key_field']['placeholder'] : esc_html__( 'Custom Key', 'gravityforms' );

		// Define value field properties.
		$value_field['name']   .= '_custom_value';
		$value_field['choices'] = rgar( $field, 'value_choices' ) ? $field['value_choices'] : rgars( $field, 'value_field/choices' );
		$value_field['class']   = 'value value_{i}';

		// Define custom value field properties.
		$custom_value_field['name']        .= '_custom_value_{i}';
		$custom_value_field['class']        = 'custom_value custom_value_{i}';
		$custom_value_field['value']        = '{custom_value}';
		$custom_value_field['placeholder']  = rgars( $field, 'value_field/placeholder' ) ? $field['value_field']['placeholder'] : esc_html__( 'Custom Value', 'gravityforms' );

		// Get key/field column titles.
		$key_field_title   = rgars( $field, 'key_field/title' ) ? $field['key_field']['title'] : esc_html__( 'Key', 'gravityforms' );
		$value_field_title = rgars( $field, 'value_field/title' ) ? $field['value_field']['title'] : esc_html__( 'Value', 'gravityforms' );

		// Remove unneeded field properties.
		$unneeded_props = array( 'field_map', 'key_choices', 'value_choices', 'placeholders', 'callback' );
		foreach ( $unneeded_props as $unneeded_prop ) {
			unset( $field[ $unneeded_prop ] );
			unset( $key_field[ $unneeded_prop ] );
			unset( $value_field[ $unneeded_prop ] );
			unset( $custom_key_field[ $unneeded_prop ] );
			unset( $custom_value_field[ $unneeded_prop ] );
		}

		// If field failed validation, display error icon.
		if ( $this->field_failed_validation( $field ) ) {
			$html .= $this->get_error_icon( $field );
		}

		// Display hidden field containing dynamic field map value.
		$html .= $this->settings_hidden( $field, false );

		// Display map table.
		$html .= '
            <table class="settings-field-map-table" cellspacing="0" cellpadding="0">
            	<thead>
					<tr>
						<th>' . $key_field_title . '</th>
						<th>' . $value_field_title . '</th>
					</tr>
				</thead>
                <tbody class="repeater">
	                <tr>
	                    ' . $this->get_mapping_field( 'key', $key_field, $custom_key_field ) .
							$this->get_mapping_field( 'value', $value_field, $custom_value_field ) . '
						<td>
							{buttons}
						</td>
	                </tr>
                </tbody>
            </table>';

		// Get generic map limit.
		$limit = empty( $field['limit'] ) ? 0 : $field['limit'];

		// Initialize generic map via Javascript.
		$html .= "
			<script type=\"text/javascript\">
			jQuery( document ).ready( function() {
				var genericMap". esc_attr( $field['name'] ) ." = new GFGenericMap({
					'baseURL':        '". GFCommon::get_base_url() ."',
					'fieldId':        '". esc_attr( $field['name'] ) ."',
					'fieldName':      '". $field['name'] ."',
					'keyFieldName':   '". $key_field['name'] ."',
					'valueFieldName': '". $value_field['name'] ."',
					'mergeTags':      " . var_export( $field['merge_tags'], true ) . ",
					'limit':          '". $limit . "'
				});
			});
			</script>";

		// If automatic display is enabled, echo field HTML.
		if ( $echo ) {
			echo $html;
		}

		return $html;

	}

	/**
	 * Renders and initializes a field map field based on the $field array whose choices are populated by the fields to be mapped.
	 *
	 * @since  unknown
	 * @access public
	 *
	 * @uses GFAddOn::field_map_table_header()
	 * @uses GFAddOn::get_mapped_field_name()
	 * @uses GFAddOn::get_required_indicator()
	 * @uses GFAddOn::maybe_get_tooltip()
	 * @uses GFAddOn::setting_dependency_met()
	 * @uses GFAddOn::settings_field_map_select()
	 *
	 * @param array $field Field array containing the configuration options of this field.
	 * @param bool  $echo  Determines if field contents should automatically be displayed. Defaults to true.
	 *
	 * @return string The HTML for the field
	 */
	public function settings_field_map( $field, $echo = true ) {

		// Initialize return HTML string.
		$html = '';

		// Get field map choices.
		$field_map = rgar( $field, 'field_map' );

		// If no field map choices exist, return HTML.
		if ( empty( $field_map ) ) {
			return $html;
		}

		// Get current form ID.
		$form_id = rgget( 'id' );

		// Display field map table header.
		$html .= '<table class="settings-field-map-table" cellspacing="0" cellpadding="0">' .
						$this->field_map_table_header() .
                	'<tbody>';

		// Loop through field map choices.
		foreach ( $field['field_map'] as $child_field ) {

			// If field map choice does not meet the dependencies required to be displayed, skip it.
			if ( ! $this->setting_dependency_met( rgar( $child_field, 'dependency' ) ) ) {
				continue;
			}

			// Get field map choice name, tooltip and required indicator.
			$child_field['name'] = $this->get_mapped_field_name( $field, $child_field['name'] );
			$tooltip             = $this->maybe_get_tooltip( $child_field );
			$required            = rgar( $child_field, 'required' ) ? ' ' . $this->get_required_indicator( $child_field ) : '';

			// Display field map choice row.
			$html .= '
                <tr>
                    <td>
                        <label for="' . $child_field['name'] . '">' . $child_field['label'] . $tooltip . $required . '</label>
                    </td>
                    <td>' .
			         $this->settings_field_map_select( $child_field, $form_id ) .
			         '</td>
            </tr>';

		}

		// Close field map table.
		$html .= '
                </tbody>
            </table>';

		// If automatic display is enabled, echo field HTML.
		if ( $echo ) {
			echo $html;
		}

		return $html;

	}

	/**
	 * Renders and initializes a dynamic field map field based on the $field array whose choices are populated by the fields to be mapped.
	 *
	 * @since  1.9.5.13
	 * @access public
	 *
	 * @uses GFAddOn::field_failed_validation()
	 * @uses GFAddOn::get_current_form()
	 * @uses GFAddOn::get_error_icon()
	 * @uses GFAddOn::get_mapping_field()
	 * @uses GFAddOn::settings_field_map_select()
	 * @uses GFAddOn::settings_hidden()
	 * @uses GFAddOn::settings_select()
	 * @uses GFAddOn::settings_text()
	 * @uses GFCommon::get_base_url()
	 *
	 * @param array $field Field array containing the configuration options of this field.
	 * @param bool  $echo  Determines if field contents should automatically be displayed. Defaults to true.
	 *
	 * @return string The HTML for the field
	 */
	public function settings_dynamic_field_map( $field, $echo = true ) {

		// Initialize return HTML string.
		$html = '';

		// Initialize field objects for child fields.
		$value_field = $key_field = $custom_key_field = $field;

		// Get current form object.
		$form = $this->get_current_form();

		// Change disable custom property to enable custom key property.
		if ( isset( $field['disabled_custom'] ) ) {
			$field['enable_custom_key'] = ! rgar( $field, 'disabled_custom' );
			unset( $field['disabled_custom'] );
		}

		// Define key field properties.
		$key_field['name']    .= '_key';
		$key_field['choices']  = isset( $field['field_map'] ) ? $field['field_map'] : null;
		$key_field['class']    = 'key key_{i}';
		$key_field['title']    = rgar( $field, 'key_field_title' );

		// Define custom key field properties.
		$custom_key_field['name']  .= '_custom_key_{i}';
		$custom_key_field['class']  = 'custom_key custom_key_{i}';
		$custom_key_field['value']  = '{custom_key}';

		// Define custom key value.
		$custom_key = 'gf_custom';

		// Define value field properties.
		$value_field['name']  .= '_custom_value';
		$value_field['class']  = 'value value_{i}';
		$value_field['title']  = rgar( $field, 'value_field_title' );

		// Remove unneeded field properties.
		unset( $field['field_map'], $value_field['field_map'], $key_field['field_map'], $custom_key_field['field_map'] );

		// If field failed validation, display error icon.
		if ( $this->field_failed_validation( $field ) ) {
			$html .= $this->get_error_icon( $field );
		}

		$header = '';
		if ( ! empty( $key_field['title'] ) || ! empty ( $value_field['title'] ) ) {
			$header = '<th>' . $key_field['title'] . '</th>' .'
					   <th>' . $value_field['title'] . '</th>';

		}

		// Display dynamic field map table.
		$html .= '
            <table class="settings-field-map-table" cellspacing="0" cellpadding="0">
            	' . $header . '
                <tbody class="repeater">
	                <tr>
	                    '. $this->get_mapping_field( 'key', $key_field, $custom_key_field ) .'
	                    <td>' .
			                $this->settings_field_map_select( $value_field, $form['id'] ) . '
						</td>
						<td>
							{buttons}
						</td>
	                </tr>
                </tbody>
            </table>';

		// Display hidden field containing dynamic field map value.
		$html .= $this->settings_hidden( $field, false );

		// Get dynamic field map limit.
		$limit = empty( $field['limit'] ) ? 0 : $field['limit'];

		// Initialize dynamic field map via Javascript.
		$html .= "
			<script type=\"text/javascript\">
				var dynamicFieldMap". esc_attr( $field['name'] ) ." = new gfieldmap({
					'baseURL':      '". GFCommon::get_base_url() ."',
					'fieldId':      '". esc_attr( $field['name'] ) ."',
					'fieldName':    '". $field['name'] ."',
					'keyFieldName': '". $key_field['name'] ."',
					'limit':        '". $limit . "'
				});
			</script>";

		// If automatic display is enabled, echo field HTML.
		if ( $echo ) {
			echo $html;
		}

		return $html;

	}

	/**
	 * Renders a field select field for field maps.
	 *
	 * @since  unknown
	 * @access public
	 *
	 * @uses GFAddOn::get_field_map_choices()
	 * @uses GF_Field::get_form_editor_field_title()
	 *
	 * @param array $field    Field array containing the configuration options of this field.
	 * @param int   $form_id  Form ID to retrieve fields from.
	 *
	 * @return string The HTML for the field
	 */
	public function settings_field_map_select( $field, $form_id ) {

		// Get field types to only display.
		$field_type = rgempty( 'field_type', $field ) ? null : $field['field_type'];

		// Get field types to exclude.
		$exclude_field_types = rgempty( 'exclude_field_types', $field ) ? null : $field['exclude_field_types'];

		// Get form field choices based on field type inclusions/exclusions.
		$field['choices'] = $this->get_field_map_choices( $form_id, $field_type, $exclude_field_types );

		// If no choices were found, return error.
		if ( empty( $field['choices'] ) || ( count( $field['choices'] ) == 1 && rgblank( $field['choices'][0]['value'] ) ) ) {

			if ( ( ! is_array( $field_type ) && ! rgblank( $field_type ) ) || ( is_array( $field_type ) && count( $field_type ) == 1 ) ) {

				$type = is_array( $field_type ) ? $field_type[0] : $field_type;
				$type = ucfirst( GF_Fields::get( $type )->get_form_editor_field_title() );

				return sprintf( __( 'Please add a %s field to your form.', 'gravityforms' ), $type );

			}

		}

		// Set default value.
		$field['default_value'] = $this->get_default_field_select_field( $field );

		return $this->settings_select( $field, false );

	}

	/**
	 * Prepares the markup for mapping field key and value fields.
	 *
	 * @since  2.2
	 * @access public
	 *
	 * @uses GFAddOn::get_current_form()
	 * @uses GFAddOn::get_field_map_choices()
	 *
	 * @param string $type The field type being prepared; key or value.
	 * @param array  $select_field The drop down field properties.
	 * @param array  $text_field   The text field properties.
	 *
	 * @return string
	 */
	public function get_mapping_field( $type, $select_field, $text_field ) {

		// If use form fields as choices flag is set, add as choices.
		if ( isset( $select_field['choices'] ) && ! is_array( $select_field['choices'] ) && 'form_fields' === strtolower( $select_field['choices'] ) ) {

			// Set choices to form fields.
			$select_field['choices'] = $this->get_field_map_choices( rgget( 'id' ) );

		}

		// If field has no choices, display custom field only.
		if ( empty( $select_field['choices'] ) ) {

			// Set field value to custom key.
			$select_field['value'] = 'gf_custom';

			// Display field row.
			return sprintf(
				'<td>%s<div class="custom-%s-container">%s</div></td>',
				$this->settings_hidden( $select_field, false ),
				$type,
				$this->settings_text( $text_field, false )
			);

		} else {

			// Set initial additional classes.
			$additional_classes = array();

			// Set has custom key flag.
			$has_gf_custom = false;

			// Loop through key field choices.
			foreach ( $select_field['choices'] as $choice ) {

				// If choice name or value is the custom key, set custom key flag to true and exit loop.
				if ( rgar( $choice, 'name' ) == 'gf_custom' || rgar( $choice, 'value' ) == 'gf_custom' ) {
					$has_gf_custom = true;
					break;
				}

				// If choice has sub-choices, check for custom key option.
				if ( rgar( $choice, 'choices' ) ) {

					// Loop through sub-choices.
					foreach ( $choice['choices'] as $subchoice ) {

						// If sub-choice name or value is the custom key, set custom key flag to true and exit loop.
						if ( rgar( $subchoice, 'name' ) == 'gf_custom' || rgar( $subchoice, 'value' ) == 'gf_custom' ) {
							$has_gf_custom = true;
							break;
						}
					}

				}

			}

			// If custom key option is not found and we're allowed to add it, add it.
			if ( ! $has_gf_custom ) {

				if ( $type == 'key' ) {

					$enable_custom = rgars( $select_field, 'key_field/custom_value' ) ? (bool) $select_field['key_field']['custom_value'] : ! (bool) rgar( $select_field, 'disable_custom' );
					$enable_custom = isset( $select_field['enable_custom_key'] ) ? (bool) $select_field['enable_custom_key'] : $enable_custom;
					$label         = esc_html__( 'Add Custom Key', 'gravityforms' );

				} else {

					// Add merge tag class.
					if ( rgars( $select_field, 'value_field/merge_tags' ) ) {
						$additional_classes[] = 'supports-merge-tags';
					}

					$enable_custom = rgars( $select_field, 'value_field/custom_value' ) ? (bool) $select_field['value_field']['custom_value'] : (bool) rgars( $select_field, 'enable_custom_value' );
					$label         = esc_html__( 'Add Custom Value', 'gravityforms' );

				}

				if ( $enable_custom ) {
					$select_field['choices'][] = array(
						'label' => $label,
						'value' => 'gf_custom'
					);
				}

			}

			// Display field row.
			return sprintf(
				'<th>%s<div class="custom-%s-container %s">%s<a href="#" class="custom-%s-reset">%s</a></div></th>',
				$this->settings_select( $select_field, false ),
				$type,
				implode( ' ', $additional_classes ),
				$this->settings_text( $text_field, false ),
				$type,
				esc_html__( 'Reset', 'gravityforms' )
			);

		}

	}

	/**
	 * Heading row for field map table.
	 *
	 * @since  2.2
	 * @access public
	 *
	 * @uses GFAddOn::field_map_title()
	 *
	 * @return string
	 */
	public function field_map_table_header() {

		return '<thead>
					<tr>
						<th>' . $this->field_map_title() . '</th>
						<th>' . esc_html__( 'Form Field', 'gravityforms' ) . '</th>
					</tr>
				</thead>';

	}

	/**
	 * Heading for field map field column.
	 *
	 * @since  2.2
	 * @access public
	 *
	 * @used-by GFAddOn::field_map_table_header()
	 *
	 * @return string
	 */
	public function field_map_title() {

		return esc_html__( 'Field', 'gravityforms' );

	}

	/**
	 * Get field map choices for specific form.
	 *
	 * @since  unknown
	 * @access public
	 *
	 * @uses GFCommon::get_label()
	 * @uses GFFormsModel::get_entry_meta()
	 * @uses GFFormsModel::get_form_meta()
	 * @uses GF_Field::get_entry_inputs()
	 * @uses GF_Field::get_form_editor_field_title()
	 * @uses GF_Field::get_input_type()
	 *
	 * @param int          $form_id             Form ID to display fields for.
	 * @param array|string $field_type          Field types to only include as choices. Defaults to null.
	 * @param array|string $exclude_field_types Field types to exclude from choices. Defaults to null.
	 *
	 * @return array
	 */
	public static function get_field_map_choices( $form_id, $field_type = null, $exclude_field_types = null ) {

		$form = GFFormsModel::get_form_meta( $form_id );

		$fields = array();

		// Setup first choice
		if ( rgblank( $field_type ) || ( is_array( $field_type ) && count( $field_type ) > 1 ) ) {

			$first_choice_label = __( 'Select a Field', 'gravityforms' );

		} else {

			$type = is_array( $field_type ) ? $field_type[0] : $field_type;
			$type = ucfirst( GF_Fields::get( $type )->get_form_editor_field_title() );

			$first_choice_label = sprintf( __( 'Select a %s Field', 'gravityforms' ), $type );

		}

		$fields[] = array( 'value' => '', 'label' => $first_choice_label );

		// if field types not restricted add the default fields and entry meta
		if ( is_null( $field_type ) ) {
			$fields[] = array( 'value' => 'id', 'label' => esc_html__( 'Entry ID', 'gravityforms' ) );
			$fields[] = array( 'value' => 'date_created', 'label' => esc_html__( 'Entry Date', 'gravityforms' ) );
			$fields[] = array( 'value' => 'ip', 'label' => esc_html__( 'User IP', 'gravityforms' ) );
			$fields[] = array( 'value' => 'source_url', 'label' => esc_html__( 'Source Url', 'gravityforms' ) );
			$fields[] = array( 'value' => 'form_title', 'label' => esc_html__( 'Form Title', 'gravityforms' ) );

			$entry_meta = GFFormsModel::get_entry_meta( $form['id'] );
			foreach ( $entry_meta as $meta_key => $meta ) {
				$fields[] = array( 'value' => $meta_key, 'label' => rgars( $entry_meta, "{$meta_key}/label" ) );
			}
		}

		// Populate form fields
		if ( is_array( $form['fields'] ) ) {
			foreach ( $form['fields'] as $field ) {
				$input_type = $field->get_input_type();
				$inputs     = $field->get_entry_inputs();
				$field_is_valid_type = ( empty( $field_type ) || ( is_array( $field_type ) && in_array( $input_type, $field_type ) ) || ( ! empty( $field_type ) && $input_type == $field_type ) );

				if ( is_null( $exclude_field_types ) ) {
					$exclude_field = false;
				} elseif ( is_array( $exclude_field_types ) ) {
					if ( in_array( $input_type, $exclude_field_types ) ) {
						$exclude_field = true;
					} else {
						$exclude_field = false;
					}
				} else {
					//not array, so should be single string
					if ( $input_type == $exclude_field_types ) {
						$exclude_field = true;
					} else {
						$exclude_field = false;
					}
				}

				if ( is_array( $inputs ) && $field_is_valid_type && ! $exclude_field ) {
					//If this is an address field, add full name to the list
					if ( $input_type == 'address' ) {
						$fields[] = array(
							'value' => $field->id,
							'label' => strip_tags( GFCommon::get_label( $field ) . ' (' . esc_html__( 'Full', 'gravityforms' ) . ')' )
						);
					}
					//If this is a name field, add full name to the list
					if ( $input_type == 'name' ) {
						$fields[] = array(
							'value' => $field->id,
							'label' => strip_tags( GFCommon::get_label( $field ) . ' (' . esc_html__( 'Full', 'gravityforms' ) . ')' )
						);
					}
					//If this is a checkbox field, add to the list
					if ( $input_type == 'checkbox' ) {
						$fields[] = array(
							'value' => $field->id,
							'label' => strip_tags( GFCommon::get_label( $field ) . ' (' . esc_html__( 'Selected', 'gravityforms' ) . ')' )
						);
					}

					foreach ( $inputs as $input ) {
						$fields[] = array(
							'value' => $input['id'],
							'label' => strip_tags( GFCommon::get_label( $field, $input['id'] ) )
						);
					}
				} elseif ( $input_type == 'list' && $field->enableColumns && $field_is_valid_type && ! $exclude_field ) {
					$fields[] = array(
						'value' => $field->id,
						'label' => strip_tags( GFCommon::get_label( $field ) . ' (' . esc_html__( 'Full', 'gravityforms' ) . ')' )
					);
					$col_index = 0;
					foreach ( $field->choices as $column ) {
						$fields[] = array(
							'value' => $field->id . '.' . $col_index,
							'label' => strip_tags( GFCommon::get_label( $field ) . ' (' . esc_html( rgar( $column, 'text' ) ) . ')' ),
						);
						$col_index ++;
					}
				} elseif ( ! $field->displayOnly && $field_is_valid_type && ! $exclude_field ) {
					$fields[] = array( 'value' => $field->id, 'label' => strip_tags( GFCommon::get_label( $field ) ) );
				}
			}
		}

		/**
		 * Filter the choices available in the field map drop down.
		 *
		 * @since 2.0.7.11
		 *
		 * @param array             $fields              The value and label properties for each choice.
		 * @param int               $form_id             The ID of the form currently being configured.
		 * @param null|array        $field_type          Null or the field types to be included in the drop down.
		 * @param null|array|string $exclude_field_types Null or the field type(s) to be excluded from the drop down.
		 */
		$fields = apply_filters( 'gform_addon_field_map_choices', $fields, $form_id, $field_type, $exclude_field_types );

		if ( function_exists( 'get_called_class' ) ) {
			$callable = array( get_called_class(), 'get_instance' );
			if ( is_callable( $callable ) ) {
				$add_on = call_user_func( $callable );
				$slug   = $add_on->get_slug();

				$fields = apply_filters( "gform_{$slug}_field_map_choices", $fields, $form_id, $field_type, $exclude_field_types );
			}
		}

 		return $fields;
	}

	/**
	 * Get input name for field map field.
	 *
	 * @since  unknown
	 * @access public
	 *
	 * @used-by GFAddOn::settings_field_map()
	 * @used-by GFAddOn::validate_field_map_settings()
	 *
	 * @param array  $parent_field Field map field.
	 * @param string $field_name   Child field.
	 *
	 * @return string
	 */
	public function get_mapped_field_name( $parent_field, $field_name ) {

		return "{$parent_field['name']}_{$field_name}";

	}

	/**
	 * Get mapped key/value pairs for standard field map.
	 *
	 * @since  unknown
	 * @access public
	 *
	 * @param array  $feed       Feed object.
	 * @param string $field_name Field map field name.
	 *
	 * @return array
	 */
	public static function get_field_map_fields( $feed, $field_name ) {

		// Initialize return fields array.
		$fields = array();

		// Get prefix for mapped field map keys.
		$prefix = "{$field_name}_";

		// Loop through feed meta.
		foreach ( $feed['meta'] as $name => $value ) {

			// If field name matches prefix, add value to return array.
			if ( strpos( $name, $prefix ) === 0 ) {
				$name            = str_replace( $prefix, '', $name );
				$fields[ $name ] = $value;
			}

		}

		return $fields;

	}

	/**
	 * Get mapped key/value pairs for dynamic field map.
	 *
	 * @since  1.9.9.9
	 * @access public
	 *
	 * @param array  $feed       Feed object.
	 * @param string $field_name Dynamic field map field name.
	 *
	 * @return array
	 */
	public static function get_dynamic_field_map_fields( $feed, $field_name ) {

		// Initialize return fields array.
		$fields = array();

		// Get dynamic field map field.
		$dynamic_fields = rgars( $feed, 'meta/' . $field_name );

		// If dynamic field map field is found, loop through mapped fields and add to array.
		if ( ! empty( $dynamic_fields ) ) {

			// Loop through mapped fields.
			foreach ( $dynamic_fields as $dynamic_field ) {

				// Get mapped key or replace with custom value.
				$field_key = 'gf_custom' === $dynamic_field['key'] ? $dynamic_field['custom_key'] : $dynamic_field['key'];

				// Add mapped field to return array.
				$fields[ $field_key ] = $dynamic_field['value'];

			}

		}

		return $fields;

	}

	/**
	 * Get mapped key/value pairs for generic map.
	 *
	 * @since  2.2
	 * @access public
	 *
	 * @param array  $feed       Feed object or settings array.
	 * @param string $field_name Generic map field name.
	 * @param array  $form       Form object. Defaults to empty array.
	 * @param array  $entry      Entry object. Defaults to empty array.
	 *
	 * @uses GFCommon::replace_variables()
	 *
	 * @return array
	 */
	public function get_generic_map_fields( $feed, $field_name, $form = array(), $entry = array() ) {

		// Initialize return fields array.
		$fields = array();

		// Get generic map field.
		$generic_fields = rgar( $feed, 'meta' ) ? rgars( $feed, 'meta/' . $field_name ) : rgar( $feed, $field_name );

		// If generic map field is found, loop through mapped fields and add to array.
		if ( ! empty( $generic_fields ) ) {

			// Loop through mapped fields.
			foreach ( $generic_fields as $generic_field ) {

				// Get mapped key or replace with custom value.
				$field_key = 'gf_custom' === $generic_field['key'] ? $generic_field['custom_key'] : $generic_field['key'];

				// Get mapped field choice or replace with custom value.
				if ( 'gf_custom' === $generic_field['value'] ) {

					// If form isn't set, use custom value. Otherwise, replace merge tags.
					$field_value = empty( $form ) ? $generic_field['custom_value'] : GFCommon::replace_variables( $generic_field['custom_value'], $form, $entry, false, false, false, 'text' );

				} else {

					// If form isn't set, use value. Otherwise, get field value.
					$field_value = empty( $form ) ? $generic_field['value'] : $this->get_field_value( $form, $entry, $generic_field['value'] );

				}

				// Add mapped field to return array.
				$fields[ $field_key ] = $field_value;

			}

		}

		return $fields;

	}





	//------------ Field Select Field Type ------------------------

	/**
	 * Renders and initializes a drop down field based on the $field array whose choices are populated by the form's fields.
	 *
	 * @param array $field - Field array containing the configuration options of this field
	 * @param bool  $echo  = true - true to echo the output to the screen, false to simply return the contents as a string
	 *
	 * @return string The HTML for the field
	 */
	public function settings_field_select( $field, $echo = true ) {

		$field = $this->prepare_field_select_field( $field );

		$html = $this->settings_select( $field, false );

		if ( $echo ) {
			echo $html;
		}

		return $html;
	}

	public function prepare_field_select_field( $field ) {
		$args = is_array( rgar( $field, 'args' ) ) ? rgar( $field, 'args' ) : array( rgar( $field, 'args' ) );

		$args = wp_parse_args(
			$args, array(
				'append_choices'       => array(),
				'disable_first_choice' => false,
			)
		);

		$field['choices'] = array();

		if ( ! $args['disable_first_choice'] ) {

			// Setup first choice
			if ( empty( $args['input_types'] ) || ( is_array( $args['input_types'] ) && count( $args['input_types'] ) > 1 ) ) {

				$first_choice_label = __( 'Select a Field', 'gravityforms' );

			} else {

				$type = is_array( $args['input_types'] ) ? $args['input_types'][0] : $args['input_types'];
				$type = ucfirst( GF_Fields::get( $type )->get_form_editor_field_title() );

				$first_choice_label = sprintf( __( 'Select a %s Field', 'gravityforms' ), $type );

			}

			$field['choices'][] = array( 'value' => '', 'label' => $first_choice_label );

		}

		$field['choices'] = array_merge( $field['choices'], $this->get_form_fields_as_choices( $this->get_current_form(), $args ) );

		if ( ! empty( $args['append_choices'] ) ) {
			$field['choices'] = array_merge( $field['choices'], $args['append_choices'] );
		}

		// Set default value.
		$field['default_value'] = $this->get_default_field_select_field( $field );

		return $field;

	}

	/**
	 * Returns the field to be selected by default for field select fields based on matching labels.
	 *
	 * @access public
	 * @param  array $field - Field array containing the configuration options of this field
	 *
	 * @return string|null
	 */
	public function get_default_field_select_field( $field ) {

		// Prepare field name.
		$field_name = str_replace( '.', '_', $field['name'] );

		// If field's value is already set, return it.
		if ( $this->get_setting( $field_name ) ) {
			return $this->get_setting( $field_name );
		}

		// If field's default value is not an array and not empty, return it.
		if ( ! rgempty( 'default_value', $field ) && ! is_array( $field['default_value'] ) ) {
			return $field['default_value'];
		}

		// Set default value if auto populate is not disabled.
		if ( rgar( $field, 'auto_mapping' ) !== false ) {

			$field_label = rgar( $field, 'label' );

			// Initialize array to store auto-population choices.
			$default_value_choices = array( $field_label );

			// Define global aliases to help with the common case mappings.
			$global_aliases = array(
				__('First Name', 'gravityforms') => array( __( 'Name (First)', 'gravityforms' ) ),
				__('Last Name', 'gravityforms') => array( __( 'Name (Last)', 'gravityforms' ) ),
				__('Address', 'gravityforms') => array( __( 'Address (Street Address)', 'gravityforms' ) ),
				__('Address 2', 'gravityforms') => array( __( 'Address (Address Line 2)', 'gravityforms' ) ),
				__('City', 'gravityforms') => array( __( 'Address (City)', 'gravityforms' ) ),
				__('State', 'gravityforms') => array( __( 'Address (State / Province)', 'gravityforms' ) ),
				__('Zip', 'gravityforms') => array( __( 'Address (Zip / Postal Code)', 'gravityforms' ) ),
				__('Country', 'gravityforms') => array( __( 'Address (Country)', 'gravityforms' ) ),
			);

			// If one or more global aliases are defined for this particular field label, merge them into auto-population choices.
			if ( isset( $global_aliases[ $field_label ] ) ){
				$default_value_choices = array_merge( $default_value_choices, $global_aliases[ $field_label ] );
			}

			// If field aliases are defined, merge them into auto-population choices.
			if ( rgars( $field, 'default_value/aliases' ) ) {
				$default_value_choices = array_merge( $default_value_choices, $field['default_value']['aliases'] );
			}

			// Convert all auto-population choices to lowercase.
			$default_value_choices = array_map( 'strtolower', $default_value_choices );

			// Loop through fields.
			foreach ( $field['choices'] as $choice ) {

				// If choice value is empty, skip it.
				if ( rgblank( $choice['value'] ) ) {
					continue;
				}

				// If lowercase field label matches a default value choice, set it to the default value.
				if ( in_array( strtolower( $choice['label'] ), $default_value_choices ) ) {
					return $choice['value'];
				}

			}

		}

		return null;

	}

	/**
	 * Retrieve an array of form fields formatted for select, radio and checkbox settings fields.
	 *
	 * @access public
	 * @param array $form - The form object
	 * @param array $args - Additional settings to check for (field and input types to include, callback for applicable input type)
	 *
	 * @return array The array of formatted form fields
	 */
	public function get_form_fields_as_choices( $form, $args = array() ) {

		$fields = array();

		if ( ! is_array( $form['fields'] ) ) {
			return $fields;
		}

		$args = wp_parse_args(
			$args, array(
				'field_types'    => array(),
				'input_types'    => array(),
			    'callback'       => false
			)
		);

		foreach ( $form['fields'] as $field ) {

			if ( ! empty( $args['field_types'] ) && ! in_array( $field->type, $args['field_types'] ) ) {

				continue;

			}

			$input_type               = GFFormsModel::get_input_type( $field );
			$is_applicable_input_type = empty( $args['input_types'] ) || in_array( $input_type, $args['input_types'] );

			if ( is_callable( $args['callback'] ) ) {
				$is_applicable_input_type = call_user_func( $args['callback'], $is_applicable_input_type, $field, $form );
			}

			if ( ! $is_applicable_input_type ) {
				continue;
			}

			if ( ! empty( $args['property'] ) && ( ! isset( $field->{$args['property']} ) || $field->{$args['property']} != $args['property_value'] ) ) {
				continue;
			}

			$inputs = $field->get_entry_inputs();
			if ( is_array( $inputs ) ) {
				// if this is an address field, add full name to the list
				if ( $input_type == 'address' ) {
					$fields[] = array(
						'value' => $field->id,
						'label' => GFCommon::get_label( $field ) . ' (' . esc_html__( 'Full', 'gravityforms' ) . ')'
					);
				}
				// if this is a name field, add full name to the list
				if ( $input_type == 'name' ) {
					$fields[] = array(
						'value' => $field->id,
						'label' => GFCommon::get_label( $field ) . ' (' . esc_html__( 'Full', 'gravityforms' ) . ')'
					);
				}
				// if this is a checkbox field, add to the list
				if ( $input_type == 'checkbox' ) {
					$fields[] = array(
						'value' => $field->id,
						'label' => GFCommon::get_label( $field ) . ' (' . esc_html__( 'Selected', 'gravityforms' ) . ')'
					);
				}

				foreach ( $inputs as $input ) {
					$fields[] = array(
						'value' => $input['id'],
						'label' => GFCommon::get_label( $field, $input['id'] )
					);
				}
			} elseif ( $input_type == 'list' && $field->enableColumns ) {
				$fields[] = array(
					'value' => $field->id,
					'label' => GFCommon::get_label( $field ) . ' (' . esc_html__( 'Full', 'gravityforms' ) . ')'
				);
				$col_index = 0;
				foreach ( $field->choices as $column ) {
					$fields[] = array(
						'value' => $field->id . '.' . $col_index,
						'label' => GFCommon::get_label( $field ) . ' (' . rgar( $column, 'text' ) . ')',
					);
					$col_index ++;
				}
			} elseif ( ! $field->displayOnly ) {
				$fields[] = array( 'value' => $field->id, 'label' => GFCommon::get_label( $field ) );
			} else {
				$fields[] = array(
					'value' => $field->id,
					'label' => GFCommon::get_label( $field )
				);
			}
		}

		return $fields;
	}

	/**
	 * Renders and initializes a checkbox field that displays a select field when checked based on the $field array.
	 *
	 * @access public
	 * @param array $field - Field array containing the configuration options of this field
	 * @param bool  $echo  = true - true to echo the output to the screen, false to simply return the contents as a string
	 *
	 * @return string The HTML for the field
	 */
	public function settings_checkbox_and_select( $field, $echo = true ) {

		$field = $this->prepare_settings_checkbox_and_select( $field );

		$checkbox_field = $field['checkbox'];
		$select_field = $field['select'];

		$is_enabled = $this->get_setting( $checkbox_field['name'] );

		// get markup

		$html = sprintf(
			'%s <span id="%s" class="%s">%s %s</span>',
			$this->settings_checkbox( $checkbox_field, false ),
			$select_field['name'] . 'Span',
			$is_enabled ? '' : 'gf_invisible',
			$this->settings_select( $select_field, false ),
			$this->maybe_get_tooltip( $select_field )
		);

		if ( $echo ) {
			echo $html;
		}

		return $html;
	}

	public function prepare_settings_checkbox_and_select( $field ) {

		// prepare checkbox

		$checkbox_input = rgars( $field, 'checkbox' );

		$checkbox_field = array(
			'type'       => 'checkbox',
			'name'       => $field['name'] . 'Enable',
			'label'      => esc_html__( 'Enable', 'gravityforms' ),
			'horizontal' => true,
			'value'      => '1',
			'choices'    => false,
			'tooltip'    => false
		);

		$checkbox_field = wp_parse_args( $checkbox_input, $checkbox_field );

		// prepare select

		$select_input = rgars( $field, 'select' );

		$select_field = array(
			'name'    => $field['name'] . 'Value',
			'type'    => 'select',
			'class'   => '',
			'tooltip' => false
		);

		$select_field['class'] .= ' ' . $select_field['name'];

		$select_field = wp_parse_args( $select_input, $select_field );

		// a little more with the checkbox
		if( empty( $checkbox_field['choices'] ) ) {
			$checkbox_field['choices'] = array(
				array(
					'name'          => $checkbox_field['name'],
					'label'         => $checkbox_field['label'],
					'onchange'      => sprintf( "( function( $, elem ) {
						$( elem ).parents( 'td' ).css( 'position', 'relative' );
						if( $( elem ).prop( 'checked' ) ) {
							$( '%1\$s' ).css( 'visibility', 'visible' );
							$( '%1\$s' ).fadeTo( 400, 1 );
						} else {
							$( '%1\$s' ).fadeTo( 400, 0, function(){
								$( '%1\$s' ).css( 'visibility', 'hidden' );   
							} );
						}
					} )( jQuery, this );",
						"#{$select_field['name']}Span" )
				)
			);
		}

		$field['select'] = $select_field;
		$field['checkbox'] = $checkbox_field;

		return $field;
	}

	/***
	 * Renders the save button for settings pages
	 *
	 * @param array $field - Field array containing the configuration options of this field
	 * @param bool  $echo  = true - true to echo the output to the screen, false to simply return the contents as a string
	 *
	 * @return string The HTML
	 */
	public function settings_save( $field, $echo = true ) {

		$field['type']  = 'submit';
		$field['name']  = 'gform-settings-save';
		$field['class'] = 'button-primary gfbutton';

		if ( ! rgar( $field, 'value' ) ) {
			$field['value'] = esc_html__( 'Update Settings', 'gravityforms' );
		}

		$attributes = $this->get_field_attributes( $field );

		$html = '<input
					type="' . esc_attr( $field['type'] ) . '"
					name="' . esc_attr( $field['name'] ) . '"
					value="' . esc_attr( $field['value'] ) . '" ' . implode( ' ', $attributes ) . ' />';

		if ( $echo ) {
			echo $html;
		}

		return $html;
	}

	/**
	 * Parses the properties of the $field meta array and returns a set of HTML attributes to be added to the HTML element.
	 *
	 * @param array $field   - current field meta to be parsed.
	 * @param array $default - default set of properties. Will be appended to the properties specified in the $field array
	 *
	 * @return array - resulting HTML attributes ready to be included in the HTML element.
	 */
	public function get_field_attributes( $field, $default = array() ) {

		/**
		 * Each nonstandard property will be extracted from the $props array so it is not auto-output in the field HTML
		 *
		 * @param array $field The current field meta to be parsed
		 */
		$no_output_props = apply_filters(
			'gaddon_no_output_field_properties',
			array(
				'default_value', 'label', 'choices', 'feedback_callback', 'checked', 'checkbox_label', 'value', 'type',
				'validation_callback', 'required', 'hidden', 'tooltip', 'dependency', 'messages', 'name', 'args',
				'exclude_field_types', 'field_type', 'after_input', 'input_type', 'icon', 'save_callback',
				'enable_custom_value', 'enable_custom_key', 'merge_tags', 'key_field', 'value_field', 'callback',
			), $field
		);

		$default_props = array(
			'class'         => '', // will default to gaddon-setting
			'default_value' => '', // default value that should be selected or entered for the field
		);

		// Property switch case
		switch ( $field['type'] ) {
			case 'select':
				$default_props['choices'] = array();
				break;
			case 'checkbox':
				$default_props['checked']        = false;
				$default_props['checkbox_label'] = '';
				$default_props['choices']        = array();
				break;
			case 'text':
			default:
				break;
		}

		$props          = wp_parse_args( $field, $default_props );
		$props['id']    = rgempty( 'id', $props ) ? rgar( $props, 'name' ) : rgar( $props, 'id' );
		$props['class'] = trim( "{$props['class']} gaddon-setting gaddon-{$props['type']}" );

		// extract no-output properties from $props array so they are not auto-output in the field HTML
		foreach ( $no_output_props as $prop ) {
			if ( isset( $props[ $prop ] ) ) {
				${$prop} = $props[ $prop ];
				unset( $props[ $prop ] );
			}
		}

		//adding default attributes
		foreach ( $default as $key => $value ) {
			if ( isset( $props[ $key ] ) ) {
				$props[ $key ] = $value . $props[ $key ];
			} else {
				$props[ $key ] = $value;
			}
		}

		// get an array of property strings, example: name='myFieldName'
		$prop_strings = array();
		foreach ( $props as $prop => $value ) {
			$prop_strings[ $prop ] = "{$prop}='" . esc_attr( $value ) . "'";
		}

		return $prop_strings;
	}

	/**
	 * Parses the properties of the $choice meta array and returns a set of HTML attributes to be added to the HTML element.
	 *
	 * @param array $choice           - current choice meta to be parsed.
	 * @param array $field_attributes - current field's attributes.
	 *
	 * @return array - resulting HTML attributes ready to be included in the HTML element.
	 */
	public function get_choice_attributes( $choice, $field_attributes, $default_choice_attributes = array() ) {
		$choice_attributes = $field_attributes;
		foreach ( $choice as $prop => $val ) {
			$no_output_choice_attributes = array(
				'default_value', 'label', 'checked', 'value', 'type',
				'validation_callback', 'required', 'tooltip',
			);
			if ( in_array( $prop, $no_output_choice_attributes ) || is_array( $val ) ) {
				unset( $choice_attributes[ $prop ] );
			} else {
				$choice_attributes[ $prop ] = "{$prop}='" . esc_attr( $val ) . "'";
			}
		}

		//Adding default attributes. Either creating a new attribute or pre-pending to an existing one.
		foreach ( $default_choice_attributes as $default_attr_name => $default_attr_value ) {

			if ( isset( $choice_attributes[ $default_attr_name ] ) ) {
				$choice_attributes[ $default_attr_name ] = $this->prepend_attribute( $default_attr_name, $default_attr_value, $choice_attributes[ $default_attr_name ] );
			}
			else {
				$choice_attributes[ $default_attr_name ] = "{$default_attr_name}='" . esc_attr( $default_attr_value ) . "'";
			}
		}

		return $choice_attributes;
	}

	/***
	 * @param $name - The name of the attribute to be added
	 * @param $attribute - The attribute value to be added
	 * @param $current_attribute - The full string containing the current attribute value
	 * @return mixed - The new attribute string with the new value added to the beginning of the list
	 */
	public function prepend_attribute( $name, $attribute, $current_attribute ) {
		return str_replace( "{$name}='", "{$name}='{$attribute}", $current_attribute );
	}

	/**
	 * Validates settings fields.
	 * Validates that all fields are valid. Fields can be invalid when they are blank and marked as required or if it fails a custom validation check.
	 * To specify a custom validation, use the 'validation_callback' field meta property and implement the validation function with the custom logic.
	 *
	 * @param $fields   - A list of all fields from the field meta configuration
	 * @param $settings - A list of submitted settings values
	 *
	 * @return bool - Returns true if all fields have passed validation, and false otherwise.
	 */
	public function validate_settings( $fields, $settings ) {

		foreach ( $fields as $section ) {

			if ( ! $this->setting_dependency_met( rgar( $section, 'dependency' ) ) ) {
				continue;
			}

			foreach ( $section['fields'] as $field ) {

				if ( ! $this->setting_dependency_met( rgar( $field, 'dependency' ) ) ) {
					continue;
				}

				$field_setting = rgar( $settings, rgar( $field, 'name' ) );

				if ( is_callable( rgar( $field, 'validation_callback' ) ) ) {
					call_user_func( rgar( $field, 'validation_callback' ), $field, $field_setting );
					continue;
				}

				if ( is_callable( array( $this, 'validate_' . $field['type'] . '_settings' ) ) ) {
					call_user_func( array( $this, 'validate_' . $field['type'] . '_settings' ), $field, $settings );
					continue;
				}

				if ( rgar( $field, 'required' ) && rgblank( $field_setting ) ) {
					$this->set_field_error( $field, rgar( $field, 'error_message' ) );
				}
			}
		}

		$field_errors = $this->get_field_errors();
		$is_valid     = empty( $field_errors );

		return $is_valid;
	}

	public function validate_text_settings( $field, $settings ) {
		$field_setting = rgar( $settings, rgar( $field, 'name' ) );

		if ( rgar( $field, 'required' ) && rgblank( $field_setting ) ) {
			$this->set_field_error( $field, rgar( $field, 'error_message' ) );
		}

		$field_setting_safe = sanitize_text_field( $field_setting );

		if ( $field_setting !== $field_setting_safe ) {
			$message = esc_html__( 'The text you have entered is not valid. For security reasons, some characters are not allowed. ', 'gravityforms' );
			$script = sprintf( 'jQuery("input[name=\"_gaddon_setting_%s\"]").val(jQuery(this).data("safe"));', $field['name'] );
			$double_encoded_safe_value = htmlspecialchars( htmlspecialchars( $field_setting_safe, ENT_QUOTES ), ENT_QUOTES );
			$message .= sprintf( " <a href='javascript:void(0);' onclick='%s' data-safe='%s'>%s</a>", htmlspecialchars( $script, ENT_QUOTES ), $double_encoded_safe_value, esc_html__('Fix it', 'gravityforms' ) );
			$this->set_field_error( $field, $message );
		}

	}

	public function validate_textarea_settings( $field, $settings ) {
		$field_setting = rgar( $settings, rgar( $field, 'name' ) );

		if ( rgar( $field, 'required' ) && rgblank( $field_setting ) ) {
			$this->set_field_error( $field, rgar( $field, 'error_message' ) );
		}

		$field_setting_safe = $this->maybe_wp_kses( $field_setting );

		if ( $field_setting !== $field_setting_safe ) {
			$message = esc_html__( 'The text you have entered is not valid. For security reasons, some characters are not allowed. ', 'gravityforms' );
			$script = sprintf( 'jQuery("textarea[name=\"_gaddon_setting_%s\"]").val(jQuery(this).data("safe"));', $field['name'] );
			$double_encoded_safe_value = htmlspecialchars( htmlspecialchars( $field_setting_safe, ENT_QUOTES ), ENT_QUOTES );
			$message .= sprintf( " <a href='javascript:void(0);' onclick='%s' data-safe='%s'>%s</a>", htmlspecialchars( $script, ENT_QUOTES ), $double_encoded_safe_value, esc_html__('Fix it', 'gravityforms' ) );
			$this->set_field_error( $field, $message );
		}
	}

	public function validate_radio_settings( $field, $settings ) {
		$field_setting = rgar( $settings, rgar( $field, 'name' ) );

		if ( rgar( $field, 'required' ) && rgblank( $field_setting ) ) {
			$this->set_field_error( $field, rgar( $field, 'error_message' ) );
			return;
		}

		if ( rgblank( $field_setting ) ){
			return; //Nothing is selected. Let it pass validation
		}

		foreach( $field['choices'] as $choice ) {
			if ( $this->is_choice_valid( $choice, $field_setting ) ) {
				return; // Choice is valid
			}
		}
		$this->set_field_error( $field, esc_html__( 'Invalid value', 'gravityforms' ) );
	}

	public function validate_select_settings( $field, $settings ) {
		$field_name = str_replace( '[]', '', $field['name'] );
		$field_setting = rgar( $settings, $field_name );

		$multiple = rgar( $field, 'multiple' ) == 'multiple';
		$required =  rgar( $field, 'required' );

		if ( ! $multiple && $required && rgblank( $field_setting ) ) {
			$this->set_field_error( $field, rgar( $field, 'error_message' ) );
			return;
		}

		if ( rgblank( $field_setting ) ) {
			return;
		}

		if ( $multiple ) {
			$selected = 0;
			foreach( $field['choices'] as $choice ) {
				if ( isset( $choice['choices'] ) ) {
					foreach( $choice['choices'] as $optgroup_choice ) {
						if ( $this->is_choice_valid( $optgroup_choice, $field_setting ) ) {
							$selected++;
						}
					}
				} else {
					if ( $this->is_choice_valid( $choice, $field_setting ) ) {
						$selected++;
					}
				}
			}

			if ( $required && $selected == 0 ) {
				$this->set_field_error( $field, rgar( $field, 'error_message' ) );
				return;
			}

			if ( ! $required && $selected !== count( $field_setting ) ) {
				$this->set_field_error( $field, esc_html__( 'Invalid value', 'gravityforms' ) );
			}
		} else {
			foreach( $field['choices'] as $choice ) {
				if ( isset( $choice['choices'] ) ) {
					foreach( $choice['choices'] as $optgroup_choice ) {
						if ( $this->is_choice_valid( $optgroup_choice, $field_setting ) ) {
							return;
						}
					}
				} else {
					if ( $this->is_choice_valid( $choice, $field_setting ) ) {
						return; // Choice is valid
					}
				}
			}
			$this->set_field_error( $field, esc_html__( 'Invalid value', 'gravityforms' ) );
		}

	}

	public function validate_checkbox_settings( $field, $settings ) {

		if ( ! is_array( rgar( $field, 'choices' ) ) ) {
			return;
		}

		$selected = 0;

		foreach ( $field['choices'] as $choice ) {
			$value = $this->get_setting( $choice['name'], '', $settings );
			if ( ! in_array( $value, array( '1', '0' ) ) ) {
				$this->set_field_error( $field, esc_html__( 'Invalid value', 'gravityforms' ) );
				return;
			}

			if ( $value === '1' ) {
				$selected++;
			}
		}


		if ( rgar( $field, 'required' ) && $selected < 1 ) {
			$this->set_field_error( $field, rgar( $field, 'error_message' ) );
		}
	}

	public function validate_select_custom_settings( $field, $settings ) {

		if ( ! is_array( rgar( $field, 'choices' ) ) ) {
			return;
		}

		$select_value = rgar( $settings, $field['name'] );
		$custom_value = rgar( $settings, $field['name'] . '_custom' );

		if ( rgar( $field, 'required' ) && rgblank( $select_value ) ) {
			$this->set_field_error( $field );
			return;
		}

		if ( rgar( $field, 'required' ) && $select_value == 'gf_custom' && rgblank( $custom_value ) ) {
			$custom_field          = $field;
			$custom_field['name'] .= '_custom';
			$this->set_field_error( $custom_field );
			return;
		}

		if ( $select_value != 'gf_custom' ) {
			foreach( $field['choices'] as $choice ) {
				if ( isset( $choice['choices'] ) ) {
					foreach ( $choice['choices'] as $optgroup_choice ) {
						if ( $this->is_choice_valid( $optgroup_choice, $select_value ) ) {
							return;
						}
					}
				} else {
					if ( $this->is_choice_valid( $choice, $select_value ) ) {
						return;
					}
				}
			}
			$this->set_field_error( $field, esc_html__( 'Invalid value', 'gravityforms' ) );
		}
	}

	public function validate_field_select_settings( $field, $settings ) {
		$field = $this->prepare_field_select_field( $field );
		$this->validate_select_settings( $field, $settings );
	}

	public function validate_field_map_settings( $field, $settings ) {

		$field_map = rgar( $field, 'field_map' );

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

		foreach ( $field_map as $child_field ) {

			if ( ! $this->setting_dependency_met( rgar( $child_field, 'dependency' ) ) ) {
				continue;
			}

			$child_field['name'] = $this->get_mapped_field_name( $field, $child_field['name'] );
			$setting_value       = rgar( $settings, $child_field['name'] );

			if ( rgar( $child_field, 'required' ) && rgblank( $setting_value ) ) {
				$this->set_field_error( $child_field );
			} elseif ( rgar( $child_field, 'validation_callback' ) && is_callable( rgar( $child_field, 'validation_callback' ) ) ) {
				call_user_func( rgar( $child_field, 'validation_callback' ), $child_field, $field );
			}
		}

	}

	public function validate_checkbox_and_select_settings( $field, $settings ) {
		$field = $this->prepare_settings_checkbox_and_select( $field );

		$checkbox_field = $field['checkbox'];
		$select_field = $field['select'];

		$this->validate_checkbox_settings( $checkbox_field, $settings );
		$this->validate_select_settings( $select_field, $settings );
	}

	/**
	 * Helper to determine if the current choice is a match for the submitted field value.
	 *
	 * @param array $choice The choice properties.
	 * @param string|array $value The submitted field value.
	 *
	 * @return bool
	 */
	public function is_choice_valid( $choice, $value ) {
		$choice_value = isset( $choice['value'] ) ? $choice['value'] : $choice['label'];

		return is_array( $value ) ? in_array( $choice_value, $value ) : $choice_value == $value;
	}

	/**
	 * Sets the validation error message
	 * Sets the error message to be displayed when a field fails validation.
	 * When implementing a custom validation callback function, use this function to specify the error message to be displayed.
	 *
	 * @param array  $field         - The current field meta
	 * @param string $error_message - The error message to be displayed
	 */
	public function set_field_error( $field, $error_message = '' ) {

		// set default error message if none passed
		if ( ! $error_message ) {
			$error_message = esc_html__( 'This field is required.', 'gravityforms' );
		}

		$this->_setting_field_errors[ $field['name'] ] = $error_message;
	}

	/**
	 * Gets the validation errors for a field.
	 * Returns validation errors associated with the specified field or a list of all validation messages (if a field isn't specified)
	 *
	 * @param array|boolean $field - Optional. The field meta. When specified, errors for this field will be returned
	 *
	 * @return mixed - If a field is specified, a string containing the error message will be returned. Otherwise, an array of all errors will be returned
	 */
	public function get_field_errors( $field = false ) {

		if ( ! $field ) {
			return $this->_setting_field_errors;
		}

		return isset( $this->_setting_field_errors[ $field['name'] ] ) ? $this->_setting_field_errors[ $field['name'] ] : array();
	}

	/**
	 * Gets the invalid field icon
	 * Returns the markup for an alert icon to indicate and highlight invalid fields.
	 *
	 * @param array $field - The field meta.
	 *
	 * @return string - The full markup for the icon
	 */
	public function get_error_icon( $field ) {

		$error = $this->get_field_errors( $field );

		return '<span
            class="gf_tooltip tooltip"
            title="<h6>' . esc_html__( 'Validation Error', 'gravityforms' ) . '</h6>' . $error . '"
            style="display:inline-block;position:relative;right:-3px;top:1px;font-size:14px;">
                <i class="fa fa-exclamation-circle icon-exclamation-sign gf_invalid"></i>
            </span>';
	}

	/**
	 * Returns the tooltip markup if a tooltip is configured for the supplied item (field/child field/choice).
	 *
	 * @param array $item The item properties.
	 *
	 * @return string
	 */
	public function maybe_get_tooltip( $item ) {
		$html = '';

		if ( isset( $item['tooltip'] ) ) {
			$html = ' ' . gform_tooltip( $item['tooltip'], rgar( $item, 'tooltip_class' ), true );
		}

		return $html;
	}

	/**
	 * Gets the required indicator
	 * Gets the markup of the required indicator symbol to highlight fields that are required
	 *
	 * @param $field - The field meta.
	 *
	 * @return string - Returns markup of the required indicator symbol
	 */
	public function get_required_indicator( $field ) {
		return '<span class="required">*</span>';
	}

	/**
	 * Checks if the specified field failed validation
	 *
	 * @param $field - The field meta to be checked
	 *
	 * @return bool|mixed - Returns a validation error string if the field has failed validation. Otherwise returns false
	 */
	public function field_failed_validation( $field ) {
		$field_error = $this->get_field_errors( $field );

		return ! empty( $field_error ) ? $field_error : false;
	}

	/**
	 * Filter settings fields.
	 * Runs through each field and applies the 'save_callback', if set, before saving the settings.
	 * To specify a custom save filter, use the 'save_callback' field meta property and implement the save filter function with the custom logic.
	 *
	 * @param $fields   - A list of all fields from the field meta configuration
	 * @param $settings - A list of submitted settings values
	 *
	 * @return $settings - The updated settings values.
	 */
	public function filter_settings( $fields, $settings ) {

		foreach ( $fields as $section ) {

			if ( ! $this->setting_dependency_met( rgar( $section, 'dependency' ) ) ) {
				continue;
			}

			foreach ( $section['fields'] as $field ) {

				if ( ! $this->setting_dependency_met( rgar( $field, 'dependency' ) ) ) {
					continue;
				}

				$field_setting = rgar( $settings, rgar( $field, 'name' ) );

				if ( is_callable( rgar( $field, 'save_callback' ) ) ) {
					$settings[ $field['name'] ] = call_user_func( rgar( $field, 'save_callback' ), $field, $field_setting );
					continue;
				}

			}
		}

		return $settings;
	}

	public function add_field_before( $name, $fields, $settings ) {
		return $this->add_field( $name, $fields, $settings, 'before' );
	}

	public function add_field_after( $name, $fields, $settings ) {
		return $this->add_field( $name, $fields, $settings, 'after' );
	}

	public function add_field( $name, $fields, $settings, $pos ) {

		if ( rgar( $fields, 'name' ) ) {
			$fields = array( $fields );
		}

		$pos_mod = $pos == 'before' ? 0 : 1;

		foreach ( $settings as &$section ) {
			for ( $i = 0; $i < count( $section['fields'] ); $i ++ ) {
				if ( $section['fields'][ $i ]['name'] == $name ) {
					array_splice( $section['fields'], $i + $pos_mod, 0, $fields );
					break 2;
				}
			}
		}

		return $settings;
	}

	public function remove_field( $name, $settings ) {

		foreach ( $settings as &$section ) {
			for ( $i = 0; $i < count( $section['fields'] ); $i ++ ) {
				if ( $section['fields'][ $i ]['name'] == $name ) {
					array_splice( $section['fields'], $i, 1 );
					break 2;
				}
			}
		}

		return $settings;
	}

	public function replace_field( $name, $fields, $settings ) {

		if ( rgar( $fields, 'name' ) ) {
			$fields = array( $fields );
		}

		foreach ( $settings as &$section ) {
			for ( $i = 0; $i < count( $section['fields'] ); $i ++ ) {
				if ( $section['fields'][ $i ]['name'] == $name ) {
					array_splice( $section['fields'], $i, 1, $fields );
					break 2;
				}
			}
		}

		return $settings;

	}

	public function get_field( $name, $settings ) {
		foreach ( $settings as $section ) {
			for ( $i = 0; $i < count( $section['fields'] ); $i ++ ) {
				if ( rgar( $section['fields'][ $i ], 'name' ) == $name ) {
					return $section['fields'][ $i ];
				}
			}
		}

		return false;
	}

	public function build_choices( $key_value_pairs ) {

		$choices = array();

		if ( ! is_array( $key_value_pairs ) ) {
			return $choices;
		}

		$first_key  = key( $key_value_pairs );
		$is_numeric = is_int( $first_key ) && $first_key === 0;

		foreach ( $key_value_pairs as $value => $label ) {
			if ( $is_numeric ) {
				$value = $label;
			}
			$choices[] = array( 'value' => $value, 'label' => $label );
		}

		return $choices;
	}

	//--------------  Simple Condition  ------------------------------------------------

	/**
	 * Helper to create a simple conditional logic set of fields. It creates one row of conditional logic with Field/Operator/Value inputs.
	 *
	 * @param mixed $setting_name_root - The root name to be used for inputs. It will be used as a prefix to the inputs that make up the conditional logic fields.
	 *
	 * @return string The HTML
	 */
	public function simple_condition( $setting_name_root ) {

		$conditional_fields = $this->get_conditional_logic_fields();

		$value_input = esc_js( '_gaddon_setting_' . esc_attr( $setting_name_root ) . '_value' );
		$object_type = esc_js( "simple_condition_{$setting_name_root}" );

		$str = $this->settings_select( array(
			'name'     => "{$setting_name_root}_field_id",
			'type'     => 'select',
			'choices'  => $conditional_fields,
			'class'    => 'optin_select',
			'onchange' => "jQuery('#" . esc_js( $setting_name_root ) . "_container').html(GetRuleValues('{$object_type}', 0, jQuery(this).val(), '', '{$value_input}'));"
		), false );

		$str .= $this->settings_select( array(
			'name'     => "{$setting_name_root}_operator",
			'type'     => 'select',
			'onchange' => "SetRuleProperty('{$object_type}', 0, 'operator', jQuery(this).val()); jQuery('#" . esc_js( $setting_name_root ) . "_container').html(GetRuleValues('{$object_type}', 0, jQuery('#{$setting_name_root}_field_id').val(), '', '{$value_input}'));",
			'choices'  => array(
				array(
					'value' => 'is',
					'label' => esc_html__( 'is', 'gravityforms' ),
				),
				array(
					'value' => 'isnot',
					'label' => esc_html__( 'is not', 'gravityforms' ),
				),
				array(
					'value' => '>',
					'label' => esc_html__( 'greater than', 'gravityforms' ),
				),
				array(
					'value' => '<',
					'label' => esc_html__( 'less than', 'gravityforms' ),
				),
				array(
					'value' => 'contains',
					'label' => esc_html__( 'contains', 'gravityforms' ),
				),
				array(
					'value' => 'starts_with',
					'label' => esc_html__( 'starts with', 'gravityforms' ),
				),
				array(
					'value' => 'ends_with',
					'label' => esc_html__( 'ends with', 'gravityforms' ),
				),
			),

		), false );

		$str .= sprintf( "<span id='%s_container'></span>", esc_attr( $setting_name_root ) );

		$field_id = $this->get_setting( "{$setting_name_root}_field_id" );

		$value    = $this->get_setting( "{$setting_name_root}_value" );
		$operator = $this->get_setting( "{$setting_name_root}_operator" );
		if ( empty( $operator ) ) {
			$operator = 'is';
		}

		$field_id_attribute = ! empty( $field_id ) ? $field_id : 'jQuery("#' . esc_attr( $setting_name_root ) . '_field_id").val()';

		$str .= "<script type='text/javascript'>
			var " . esc_attr( $setting_name_root ) . "_object = {'conditionalLogic':{'rules':[{'fieldId':'{$field_id}','operator':'{$operator}','value':'" . esc_attr( $value ) . "'}]}};

			jQuery(document).ready(
				function(){
					gform.addFilter( 'gform_conditional_object', 'SimpleConditionObject' );

					jQuery('#" . esc_attr( $setting_name_root ) . "_container').html(
											GetRuleValues('{$object_type}', 0, {$field_id_attribute}, '" . esc_attr( $value ) . "', '_gaddon_setting_" . esc_attr( $setting_name_root ) . "_value'));

					}
			);
			</script>";

		return $str;
	}

	/**
	 * Override this to define the array of choices which should be used to populate the Simple Condition fields drop down.
	 *
	 * Each choice should have 'label' and 'value' properties.
	 *
	 * @return array
	 */
	public function get_conditional_logic_fields() {
		return array();
	}

	/**
	 * Evaluate the rules defined for the Simple Condition field.
	 *
	 * @param string $setting_name_root The root name used as the prefix to the inputs that make up the Simple Condition field.
	 * @param array $form The form currently being processed.
	 * @param array $entry The entry currently being processed.
	 * @param array $feed The feed currently being processed or an empty array when the field is stored in the form settings.
	 *
	 * @return bool
	 */
	public function is_simple_condition_met( $setting_name_root, $form, $entry, $feed = array() ) {

		$settings = empty( $feed ) ? $this->get_form_settings( $form ) : rgar( $feed, 'meta', array() );

		$is_enabled = rgar( $settings, $setting_name_root . '_enabled' );

		if ( ! $is_enabled ) {
			// The setting is not enabled so we handle it as if the rules are met.

			return true;
		}

		// Build the logic array to be used by Gravity Forms when evaluating the rules.
		$logic = array(
			'logicType' => 'all',
			'rules'     => array(
				array(
					'fieldId'  => rgar( $settings, $setting_name_root . '_field_id' ),
					'operator' => rgar( $settings, $setting_name_root . '_operator' ),
					'value'    => rgar( $settings, $setting_name_root . '_value' ),
				),
			)
		);

		return GFCommon::evaluate_conditional_logic( $logic, $form, $entry );
	}


	//--------------  Form settings  ---------------------------------------------------

	/**
	 * Initializes form settings page
	 * Hooks up the required scripts and actions for the Form Settings page
	 */
	public function form_settings_init() {
		$view    = rgget( 'view' );
		$subview = rgget( 'subview' );
		add_filter( 'gform_form_settings_menu', array( $this, 'add_form_settings_menu' ), 10, 2 );

		if ( rgget( 'page' ) == 'gf_edit_forms' && $view == 'settings' && $subview == $this->_slug && $this->current_user_can_any( $this->_capabilities_form_settings ) ) {
			require_once( GFCommon::get_base_path() . '/tooltips.php' );
			add_action( 'gform_form_settings_page_' . $this->_slug, array( $this, 'form_settings_page' ) );
		}
	}

	/**
	 * Initializes plugin settings page
	 * Hooks up the required scripts and actions for the Plugin Settings page
	 */
	public function plugin_page_init() {

		if ( $this->current_user_can_any( $this->_capabilities_plugin_page ) ) {
			//creates the subnav left menu
			add_filter( 'gform_addon_navigation', array( $this, 'create_plugin_page_menu' ) );
		}

	}

	/**
	 * Creates plugin page menu item
	 * Target of gform_addon_navigation filter. Creates a menu item in the left nav, linking to the plugin page
	 *
	 * @param $menus - Current list of menu items
	 *
	 * @return array - Returns a new list of menu items
	 */
	public function create_plugin_page_menu( $menus ) {

		$menus[] = array( 'name' => $this->_slug, 'label' => $this->get_short_title(), 'callback' => array( $this, 'plugin_page_container' ), 'permission' => $this->_capabilities_plugin_page );

		return $menus;
	}

	/**
	 * Renders the form settings page.
	 *
	 * Not intended to be overridden or called directly by Add-Ons.
	 * Sets up the form settings page.
	 *
	 * @ignore
	 */
	public function form_settings_page() {

		GFFormSettings::page_header( $this->_title );
		?>
		<div class="gform_panel gform_panel_form_settings" id="form_settings">

			<?php
			$form = $this->get_current_form();

			$form_id = $form['id'];
			$form    = gf_apply_filters( array( 'gform_admin_pre_render', $form_id ), $form );

			if ( $this->method_is_overridden( 'form_settings' ) ) {

				//enables plugins to override settings page by implementing a form_settings() function
				$this->form_settings( $form );
			} else {

				//saves form settings if save button was pressed
				$this->maybe_save_form_settings( $form );

				//reads current form settings
				$settings = $this->get_form_settings( $form );
				$this->set_settings( $settings );

				//reading addon fields
				$sections = $this->form_settings_fields( $form );

				GFCommon::display_admin_message();

				$page_title = $this->form_settings_page_title();
				if ( empty( $page_title ) ) {
					$page_title = rgar( $sections[0], 'title' );

					//using first section title as page title, so disable section title
					$sections[0]['title'] = false;
				}
				$icon = $this->form_settings_icon();
				if ( empty( $icon ) ) {
					$icon = '<i class="fa fa-cogs"></i>';
				}

				?>
				<h3><span><?php echo $icon ?> <?php echo $page_title ?></span></h3>
				<?php

				//rendering settings based on fields and current settings
				$this->render_settings( $sections );
			}
			?>

			<script type="text/javascript">
				var form = <?php echo json_encode( $this->get_current_form() ) ?>;
			</script>
		</div>
		<?php
		GFFormSettings::page_footer();
	}

	/***
	 * Saves form settings if the submit button was pressed
	 *
	 * @param array $form The form object
	 *
	 * @return null|true|false True on success, false on error, null on no action
	 */
	public function maybe_save_form_settings( $form ) {

		if ( $this->is_save_postback() ) {

			check_admin_referer( $this->_slug . '_save_settings', '_' . $this->_slug . '_save_settings_nonce' );

			if ( ! $this->current_user_can_any( $this->_capabilities_form_settings ) ) {
				GFCommon::add_error_message( esc_html__( "You don't have sufficient permissions to update the form settings.", 'gravityforms' ) );
				return false;
			}

			// store a copy of the previous settings for cases where action would only happen if value has changed
			$this->set_previous_settings( $this->get_form_settings( $form ) );

			$settings = $this->get_posted_settings();
			$sections = $this->form_settings_fields( $form );

			$is_valid = $this->validate_settings( $sections, $settings );
			$result   = false;

			if ( $is_valid ) {
				$settings = $this->filter_settings( $sections, $settings );
				$result = $this->save_form_settings( $form, $settings );
			}

			if ( $result ) {
				GFCommon::add_message( $this->get_save_success_message( $sections ) );
			} else {
				GFCommon::add_error_message( $this->get_save_error_message( $sections ) );
			}

			return $result;
		}

	}

	/***
	 * Saves form settings to form object
	 *
	 * @param array $form
	 * @param array $settings
	 *
	 * @return true|false True on success or false on error
	 */
	public function save_form_settings( $form, $settings ) {
		$form[ $this->_slug ] = $settings;
		$result               = GFFormsModel::update_form_meta( $form['id'], $form );

		return ! ( false === $result );
	}

	/**
	 * Checks whether the current Add-On has a form settings page.
	 *
	 * @return bool
	 */
	private function has_form_settings_page() {
		return $this->method_is_overridden( 'form_settings_fields' ) || $this->method_is_overridden( 'form_settings' );
	}

	/**
	 * Custom form settings page
	 * Override this function to implement a complete custom form settings page.
	 * Before overriding this function, consider using the form_settings_fields() and specifying your field meta.
	 */
	public function form_settings( $form ) {
	}

	/**
	 * Custom form settings title
	 * Override this function to display a custom title on the Form Settings Page.
	 * By default, the first section in the configuration done in form_settings_fields() will be used as the page title.
	 * Use this function to override that behavior and add a custom page title.
	 */
	public function form_settings_page_title() {
		return '';
	}

	/**
	 * Override this function to customize the form settings icon
	 */
	public function form_settings_icon() {
		return '';
	}

	/**
	 * Checks whether the current Add-On has a plugin page.
	 *
	 * @return bool
	 */
	private function has_plugin_page() {
		return $this->method_is_overridden( 'plugin_page' );
	}

	/**
	 * Override this function to create a custom plugin page
	 */
	public function plugin_page() {
	}

	/**
	 * Override this function to customize the plugin page icon
	 */
	public function plugin_page_icon() {
		return '';
	}

	/**
	 * Override this function to customize the plugin page title
	 */
	public function plugin_page_title() {
		return $this->_title;
	}

	/**
	 * Plugin page container
	 * Target of the plugin menu left nav icon. Displays the outer plugin page markup and calls plugin_page() to render the actual page.
	 * Override plugin_page() in order to provide a custom plugin page
	 */
	public function plugin_page_container() {
		?>
		<div class="wrap">
			<?php
			$icon = $this->plugin_page_icon();
			if ( ! empty( $icon ) ) {
				?>
				<img alt="<?php echo $this->get_short_title() ?>" style="margin: 15px 7px 0pt 0pt; float: left;" src="<?php echo $icon ?>" />
			<?php
			}
			?>

			<h2 class="gf_admin_page_title"><?php echo $this->plugin_page_title() ?></h2>
			<?php

			$this->plugin_page();
			?>
		</div>
	<?php
	}

	/**
	 * Checks whether the current Add-On has a top level app menu.
	 *
	 * @return bool
	 */
	public function has_app_menu() {
		return $this->has_app_settings() || $this->method_is_overridden( 'get_app_menu_items' );
	}

	/**
	 * Creates a top level app menu. Adds the app settings page automatically if it's configured.
	 * Target of the WordPress admin_menu action.
	 * Not intended to be overridden or called directly by add-ons.
	 */
	public function create_app_menu() {

		$has_full_access = current_user_can( 'gform_full_access' );
		$min_cap         = GFCommon::current_user_can_which( $this->_capabilities_app_menu );
		if ( empty( $min_cap ) ) {
			$min_cap = 'gform_full_access';
		}

		$menu_items = $this->get_app_menu_items();

		$addon_menus = array();

		/**
		 * Filters through addon menus (filter by addon slugs)
		 *
		 * @param array $addon_menus A modifiable array of admin addon menus
		 */
		$addon_menus = apply_filters( 'gform_addon_app_navigation_' . $this->_slug, $addon_menus );

		$parent_menu = self::get_parent_menu( $menu_items, $addon_menus );

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

		// Add a top-level left nav
		$callback = isset( $parent_menu['callback'] ) ? $parent_menu['callback'] : array( $this, 'app_tab_page' );

		global $menu;
		$number = 10;
		$menu_position = '16.' . $number;
		while ( isset( $menu[$menu_position] ) ) {
			$number += 10;
			$menu_position = '16.' . $number;
		}

		/**
		 * Modify the menu position of an add-on menu
		 *
		 * @param int $menu_position The Menu position of the add-on menu
		 */
		$menu_position = apply_filters( 'gform_app_menu_position_' . $this->_slug, $menu_position );
		$this->app_hook_suffix = add_menu_page( $this->get_short_title(), $this->get_short_title(), $has_full_access ? 'gform_full_access' : $min_cap, $parent_menu['name'], $callback, $this->get_app_menu_icon(), $menu_position );

		if ( method_exists( $this, 'load_screen_options' ) ) {
			add_action( "load-$this->app_hook_suffix", array( $this, 'load_screen_options' ) );
		}

		// Adding submenu pages
		foreach ( $menu_items as $menu_item ) {
			$callback = isset( $menu_item['callback'] ) ? $menu_item['callback'] : array( $this, 'app_tab_page' );
			add_submenu_page( $parent_menu['name'], $menu_item['label'], $menu_item['label'], $has_full_access || empty( $menu_item['permission'] ) ? 'gform_full_access' : $menu_item['permission'], $menu_item['name'], $callback );
		}

		if ( is_array( $addon_menus ) ) {
			foreach ( $addon_menus as $addon_menu ) {
				add_submenu_page( $parent_menu['name'], $addon_menu['label'], $addon_menu['label'], $has_full_access ? 'gform_full_access' : $addon_menu['permission'], $addon_menu['name'], $addon_menu['callback'] );
			}
		}

		if ( $this->has_app_settings() ) {
			add_submenu_page( $parent_menu['name'], esc_html__( 'Settings', 'gravityforms' ), esc_html__( 'Settings', 'gravityforms' ), $has_full_access ? 'gform_full_access' : $this->_capabilities_app_settings, $this->_slug . '_settings', array( $this, 'app_tab_page' ) );
		}

	}

	/**
	 * Returns the parent menu item
	 *
	 * @param $menu_items
	 * @param $addon_menus
	 *
	 * @return array|bool The parent menu araray or false if none
	 */
	private function get_parent_menu( $menu_items, $addon_menus ) {
		$parent = false;
		if ( GFCommon::current_user_can_any( $this->_capabilities_app_menu ) ) {
			foreach ( $menu_items as $menu_item ) {
				if ( $this->current_user_can_any( $menu_item['permission'] ) ) {
					$parent = $menu_item;
					break;
				}
			}
		} elseif ( is_array( $addon_menus ) && sizeof( $addon_menus ) > 0 ) {
			foreach ( $addon_menus as $addon_menu ) {
				if ( $this->current_user_can_any( $addon_menu['permission'] ) ) {
					$parent = array( 'name' => $addon_menu['name'], 'callback' => $addon_menu['callback'] );
					break;
				}
			}
		} elseif ( $this->has_app_settings() && $this->current_user_can_any( $this->_capabilities_app_settings ) ) {
			$parent = array( 'name' => $this->_slug . '_settings', 'callback' => array( $this, 'app_settings' ) );
		}

		return $parent;
	}

	/**
	 * Override this function to create a top level app menu.
	 *
	 * e.g.
	 * $menu_item['name'] = 'gravitycontacts';
	 * $menu_item['label'] = __("Contacts", 'gravitycontacts');
	 * $menu_item['permission'] = 'gravitycontacts_view_contacts';
	 * $menu_item['callback'] = array($this, 'app_menu');
	 *
	 * @return array The array of menu items
	 */
	public function get_app_menu_items() {
		return array();
	}

	/**
	 * Override this function to specify a custom icon for the top level app menu.
	 * Accepts a dashicon class or a URL.
	 *
	 * @return string
	 */
	public function get_app_menu_icon() {
		return '';
	}

	/**
	 * Override this function to load custom screen options.
	 *
	 * e.g.
	 * $screen = get_current_screen();
	 * if(!is_object($screen) || $screen->id != $this->app_hook_suffix)
	 *     return;
	 *
	 * if($this->is_contact_list_page()){
	 *     $args = array(
	 *         'label' => __('Contacts per page', 'gravitycontacts'),
	 *         'default' => 20,
	 *         'option' => 'gcontacts_per_page'
	 *     );
	 * add_screen_option( 'per_page', $args );
	 */
	public function load_screen_options() {
	}

	/**
	 * Handles the rendering of app menu items that implement the tabs UI.
	 *
	 * Not intended to be overridden or called directly by add-ons.
	 */
	public function app_tab_page() {
		$page        = sanitize_text_field( rgget( 'page' ) );
		$current_tab = sanitize_text_field( rgget( 'view' ) );

		if ( $page == $this->_slug . '_settings' ) {

			$tabs = $this->get_app_settings_tabs();

		} else {

			$menu_items = $this->get_app_menu_items();

			$current_menu_item = false;
			foreach ( $menu_items as $menu_item ) {
				if ( $menu_item['name'] == $page ) {
					$current_menu_item = $menu_item;
					break;
				}
			}

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

			if ( empty( $current_menu_item['tabs'] ) ) {
				return;
			}

			$tabs = $current_menu_item['tabs'];
		}

		if ( empty( $current_tab ) ) {
			foreach ( $tabs as $tab ) {
				if ( ! isset( $tab['permission'] ) || $this->current_user_can_any( $tab['permission'] ) ) {
					$current_tab = $tab['name'];
					break;
				}
			}
		}

		if ( empty( $current_tab ) ) {
			wp_die( esc_html__( "You don't have adequate permission to view this page", 'gravityforms' ) );
		}

		foreach ( $tabs as $tab ) {
			if ( $tab['name'] == $current_tab && isset( $tab['callback'] ) && is_callable( $tab['callback'] ) ) {
				if ( isset( $tab['permission'] ) && ! $this->current_user_can_any( $tab['permission'] ) ) {
					wp_die( esc_html__( "You don't have adequate permission to view this page", 'gravityforms' ) );
				}

				$title = rgar( $tab,'title' );

				if ( empty( $title ) ) {
					$title = isset( $tab['label'] ) ? $tab['label'] : $tab['name'];
				}

				$this->app_tab_page_header( $tabs, $current_tab, $title, '' );
				call_user_func( $tab['callback'] );
				$this->app_tab_page_footer();

				return;
			}
		}

		$this->app_tab_page_header( $tabs, $current_tab, $current_tab, '' );
		/**
		 * Fires when an addon page and tab is accessed.
		 *
		 * Typically used to render settings tab content.
		 */
		$action_hook = 'gform_addon_app_' . $page . '_' . str_replace( ' ', '_', $current_tab );
		do_action( $action_hook );
		$this->app_tab_page_footer();

	}

	/**
	 * Returns the form settings for the Add-On
	 *
	 * @param $form
	 *
	 * @return array
	 */
	public function get_form_settings( $form ) {
		return rgar( $form, $this->_slug );
	}

	/**
	 * Add the form settings tab.
	 *
	 * Override this function to add the tab conditionally.
	 *
	 *
	 * @param $tabs
	 * @param $form_id
	 *
	 * @return array
	 */
	public function add_form_settings_menu( $tabs, $form_id ) {

		$tabs[] = array( 'name' => $this->_slug, 'label' => $this->get_short_title(), 'query' => array( 'fid' => null ), 'capabilities' => $this->_capabilities_form_settings );

		return $tabs;
	}

	/**
	 * Override this function to specify the settings fields to be rendered on the form settings page
	 */
	public function form_settings_fields( $form ) {
		// should return an array of sections, each section contains a title, description and an array of fields
		return array();
	}

	//--------------  Plugin Settings  ---------------------------------------------------

	public function plugin_settings_init() {
		$subview = rgget( 'subview' );
		RGForms::add_settings_page(
			array(
				'name'      => $this->_slug,
				'tab_label' => $this->get_short_title(),
				'title'     => $this->plugin_settings_title(),
				'handler'   => array( $this, 'plugin_settings_page' ),
			)
		);
		if ( rgget( 'page' ) == 'gf_settings' && $subview == $this->_slug && $this->current_user_can_any( $this->_capabilities_settings_page ) ) {
			require_once( GFCommon::get_base_path() . '/tooltips.php' );
		}

		add_filter( 'plugin_action_links', array( $this, 'plugin_settings_link' ), 10, 2 );

	}

	public function plugin_settings_link( $links, $file ) {
		if ( $file != $this->get_path() ) {
			return $links;
		}

		array_unshift( $links, '<a href="' . admin_url( 'admin.php' ) . '?page=gf_settings&subview=' . $this->_slug . '">' . esc_html__( 'Settings', 'gravityforms' ) . '</a>' );

		return $links;
	}

	/**
	 * Plugin settings page
	 */
	public function plugin_settings_page() {
		$icon = $this->plugin_settings_icon();
		if ( empty( $icon ) ) {
			$icon = '<i class="fa fa-cogs"></i>';
		}
		?>

		<h3><span><?php echo $icon ?> <?php echo $this->plugin_settings_title() ?></span></h3>

		<?php if ( $this->has_deprecated_elements() ) : ?>
		<div class="push-alert-red" style="border-left: 1px solid #E6DB55; border-right: 1px solid #E6DB55;">
			<?php esc_html_e( 'This add-on needs to be updated. Please contact the developer.', 'gravityforms' ); ?>
		</div>
		<?php endif; ?>

		<?php

		if ( $this->method_is_overridden( 'plugin_settings' ) ) {
			//enables plugins to override settings page by implementing a plugin_settings() function
			$this->plugin_settings();
		} elseif ( $this->maybe_uninstall() ) {
			?>
			<div class="push-alert-gold" style="border-left: 1px solid #E6DB55; border-right: 1px solid #E6DB55;">
				<?php printf( esc_html__( '%s has been successfully uninstalled. It can be re-activated from the %splugins page%s.', 'gravityforms'), $this->_title, "<a href='plugins.php'>", '</a>' ); ?>
			</div>
		<?php
		} else {
			//saves settings page if save button was pressed
			$this->maybe_save_plugin_settings();

			//reads main addon settings
			$settings = $this->get_plugin_settings();
			$this->set_settings( $settings );

			//reading addon fields
			$sections = $this->plugin_settings_fields();

			GFCommon::display_admin_message();

			//rendering settings based on fields and current settings
			$this->render_settings( $sections, $settings );

			//renders uninstall section
			$this->render_uninstall();

		}

	}

	public function plugin_settings_title() {
		return sprintf( esc_html__( "%s Settings", "gravityforms" ), $this->get_short_title() );
	}

	public function plugin_settings_icon() {
		return '';
	}

	/**
	 * Override this function to add a custom settings page.
	 */
	public function plugin_settings() {
	}

	/**
	 * Checks whether the current Add-On has a settings page.
	 *
	 * @return bool
	 */
	public function has_plugin_settings_page() {
		return $this->method_is_overridden( 'plugin_settings_fields' ) || $this->method_is_overridden( 'plugin_settings_page' ) || $this->method_is_overridden( 'plugin_settings' );
	}

	/**
	 * Returns the currently saved plugin settings
	 * @return mixed
	 */
	public function get_plugin_settings() {
		return get_option( 'gravityformsaddon_' . $this->_slug . '_settings' );
	}

	/**
	 * Get plugin setting
	 * Returns the plugin setting specified by the $setting_name parameter
	 *
	 * @param string $setting_name - Plugin setting to be returned
	 *
	 * @return mixed  - Returns the specified plugin setting or null if the setting doesn't exist
	 */
	public function get_plugin_setting( $setting_name ) {
		$settings = $this->get_plugin_settings();

		return isset( $settings[ $setting_name ] ) ? $settings[ $setting_name ] : null;
	}

	/**
	 * Updates plugin settings with the provided settings
	 *
	 * @param array $settings - Plugin settings to be saved
	 */
	public function update_plugin_settings( $settings ) {
		update_option( 'gravityformsaddon_' . $this->_slug . '_settings', $settings );
	}

	/**
	 * Saves the plugin settings if the submit button was pressed
	 *
	 */
	public function maybe_save_plugin_settings() {

		if ( $this->is_save_postback() ) {

			check_admin_referer( $this->_slug . '_save_settings', '_' . $this->_slug . '_save_settings_nonce' );

			if ( ! $this->current_user_can_any( $this->_capabilities_settings_page ) ) {
				GFCommon::add_error_message( esc_html__( "You don't have sufficient permissions to update the settings.", 'gravityforms' ) );
				return false;
			}

			// store a copy of the previous settings for cases where action would only happen if value has changed
			$this->set_previous_settings( $this->get_plugin_settings() );

			$settings = $this->get_posted_settings();
			$sections = $this->plugin_settings_fields();
			$is_valid = $this->validate_settings( $sections, $settings );

			if ( $is_valid ) {
				$settings = $this->filter_settings( $sections, $settings );
				$this->update_plugin_settings( $settings );
				GFCommon::add_message( $this->get_save_success_message( $sections ) );
			} else {
				GFCommon::add_error_message( $this->get_save_error_message( $sections ) );
			}
		}

	}

	/**
	 * Override this function to specify the settings fields to be rendered on the plugin settings page
	 * @return array
	 */
	public function plugin_settings_fields() {
		// should return an array of sections, each section contains a title, description and an array of fields
		return array();
	}

	//--------------  App Settings  ---------------------------------------------------

	/**
	 * Returns the tabs for the settings app menu item
	 *
	 * Not intended to be overridden or called directly by add-ons.
	 *
	 * @return array|mixed|void
	 */
	public function get_app_settings_tabs() {

		// Build left side options, always have app Settings first and Uninstall last, put add-ons in the middle

		$setting_tabs = array( array( 'name' => 'settings', 'label' => esc_html__( 'Settings', 'gravityforms' ), 'callback' => array( $this, 'app_settings_tab' ) ) );

		/**
		 * Filters the tabs within the settings menu.
		 *
		 * This filter is appended by the page slug.  Ex: gform_addon_app_settings_menu_SLUG
		 *
		 * @param array $setting_tabs Contains the information on the settings tabs.
		 */
		$setting_tabs = apply_filters( 'gform_addon_app_settings_menu_' . $this->_slug, $setting_tabs );

		if ( $this->current_user_can_uninstall() ) {
			$setting_tabs[] = array( 'name' => 'uninstall', 'label' => esc_html__( 'Uninstall', 'gravityforms' ), 'callback' => array( $this, 'app_settings_uninstall_tab' ) );
		}

		ksort( $setting_tabs, SORT_NUMERIC );

		return $setting_tabs;
	}

	/**
	 * Renders the app settings uninstall tab.
	 *
	 * Not intended to be overridden or called directly by add-ons.
	 */
	public function app_settings_uninstall_tab() {

		if ( $this->maybe_uninstall() ) {
			?>
			<div class="push-alert-gold" style="border-left: 1px solid #E6DB55; border-right: 1px solid #E6DB55;">
				<?php printf( esc_html__( '%s has been successfully uninstalled. It can be re-activated from the %splugins page%s.', 'gravityforms' ), esc_html( $this->_title ), "<a href='plugins.php'>", '</a>' ); ?>
			</div>
		<?php

		} else {
			if ( $this->current_user_can_uninstall() ) {
			?>
			<form action="" method="post">
				<?php wp_nonce_field( 'uninstall', 'gf_addon_uninstall' ) ?>
				<?php  ?>
					<h3>
						<span><i class="fa fa-times"></i> <?php printf( esc_html__( 'Uninstall %s', 'gravityforms' ), $this->get_short_title() ); ?></span>
					</h3>

					<div class="delete-alert alert_red">

						<h3>
							<i class="fa fa-exclamation-triangle gf_invalid"></i> <?php esc_html_e( 'Warning', 'gravityforms' ); ?>
						</h3>

						<div class="gf_delete_notice">
							<?php echo $this->uninstall_warning_message() ?>
						</div>

						<?php
						$uninstall_button = '<input type="submit" name="uninstall" value="' . sprintf( esc_attr__( 'Uninstall %s', 'gravityforms' ), $this->get_short_title() ) . '" class="button" onclick="return confirm(\'' . esc_js( $this->uninstall_confirm_message() ) . '\');" onkeypress="return confirm(\'' . esc_js( $this->uninstall_confirm_message() ) . '\');"/>';
						echo $uninstall_button;
						?>

					</div>
			</form>
			<?php
			}
		}
	}

	/**
	 * Renders the header for the tabs UI.
	 *
	 * @param        $tabs
	 * @param        $current_tab
	 * @param        $title
	 * @param string $message
	 */
	public function app_tab_page_header( $tabs, $current_tab, $title, $message = '' ) {

		// Print admin styles
		wp_print_styles( array( 'jquery-ui-styles', 'gform_admin' ) );

		?>

		<div class="wrap <?php echo GFCommon::get_browser_class() ?>">

		<?php if ( $message ) { ?>
			<div id="message" class="updated"><p><?php echo $message; ?></p></div>
		<?php } ?>

		<h2><?php echo esc_html( $title ) ?></h2>

		<div id="gform_tab_group" class="gform_tab_group vertical_tabs">
		<ul id="gform_tabs" class="gform_tabs">
			<?php
			foreach ( $tabs as $tab ) {
				if ( isset( $tab['permission'] ) && ! $this->current_user_can_any( $tab['permission'] ) ) {
					continue;
				}
				$label = isset( $tab['label'] ) ? $tab['label'] : $tab['name'];
				?>
				<li <?php echo urlencode( $current_tab ) == $tab['name'] ? "class='active'" : '' ?>>
					<a href="<?php echo esc_url( add_query_arg( array( 'view' => $tab['name'] ) ) ); ?>"><?php echo esc_html( $label ) ?></a>
				</li>
			<?php
			}
			?>
		</ul>

		<div id="gform_tab_container" class="gform_tab_container">
		<div class="gform_tab_content" id="tab_<?php echo esc_attr( $current_tab ) ?>">

	<?php
	}

	/**
	 * Renders the footer for the tabs UI.
	 *
	 */
	public function app_tab_page_footer() {
		?>
		</div> <!-- / gform_tab_content -->
		</div> <!-- / gform_tab_container -->
		</div> <!-- / gform_tab_group -->

		<br class="clear" style="clear: both;" />

		</div> <!-- / wrap -->

	<?php
	}

	public function app_settings_tab() {

		require_once( GFCommon::get_base_path() . '/tooltips.php' );

		$icon = $this->app_settings_icon();
		if ( empty( $icon ) ) {
			$icon = '<i class="fa fa-cogs"></i>';
		}
		?>

		<h3><span><?php echo $icon ?> <?php echo $this->app_settings_title() ?></span></h3>

		<?php

		if ( $this->method_is_overridden( 'app_settings' ) ) {
			//enables plugins to override settings page by implementing a plugin_settings() function
			$this->app_settings();
		} elseif ( $this->maybe_uninstall() ) {
			?>
			<div class="push-alert-gold" style="border-left: 1px solid #E6DB55; border-right: 1px solid #E6DB55;">
				<?php printf( esc_html__( '%s has been successfully uninstalled. It can be re-activated from the %splugins page%s.', 'gravityforms' ), esc_html( $this->_title ), "<a href='plugins.php'>", '</a>' ); ?>
			</div>
		<?php
		} else {
			//saves settings page if save button was pressed
			$this->maybe_save_app_settings();

			//reads main addon settings
			$settings = $this->get_app_settings();
			$this->set_settings( $settings );

			//reading addon fields
			$sections = $this->app_settings_fields();

			GFCommon::display_admin_message();

			//rendering settings based on fields and current settings
			$this->render_settings( $sections, $settings );

		}

	}

	/**
	 * Override this function to specific a custom app settings title
	 *
	 * @return string
	 */
	public function app_settings_title() {
		return sprintf( esc_html__( '%s Settings', 'gravityforms' ), $this->get_short_title() );
	}

	/**
	 * Override this function to specific a custom app settings icon
	 *
	 * @return string
	 */
	public function app_settings_icon() {
		return '';
	}

	/**
	 * Checks whether the current Add-On has a settings page.
	 *
	 * @return bool
	 */
	public function has_app_settings() {
		return $this->method_is_overridden( 'app_settings_fields' ) || $this->method_is_overridden( 'app_settings' );
	}

	/**
	 * Override this function to add a custom app settings page.
	 */
	public function app_settings() {
	}

	/**
	 * Returns the currently saved plugin settings
	 * @return mixed
	 */
	public function get_app_settings() {
		return get_option( 'gravityformsaddon_' . $this->_slug . '_app_settings' );
	}

	/**
	 * Get app setting
	 * Returns the app setting specified by the $setting_name parameter
	 *
	 * @param string $setting_name - Plugin setting to be returned
	 *
	 * @return mixed  - Returns the specified plugin setting or null if the setting doesn't exist
	 */
	public function get_app_setting( $setting_name ) {
		$settings = $this->get_app_settings();

		return isset( $settings[ $setting_name ] ) ? $settings[ $setting_name ] : null;
	}

	/**
	 * Updates app settings with the provided settings
	 *
	 * @param array $settings - App settings to be saved
	 */
	public function update_app_settings( $settings ) {
		update_option( 'gravityformsaddon_' . $this->_slug . '_app_settings', $settings );
	}

	/**
	 * Saves the plugin settings if the submit button was pressed
	 *
	 */
	public function maybe_save_app_settings() {

		if ( $this->is_save_postback() ) {

			check_admin_referer( $this->_slug . '_save_settings', '_' . $this->_slug . '_save_settings_nonce' );

			if ( ! $this->current_user_can_any( $this->_capabilities_app_settings ) ) {
				GFCommon::add_error_message( esc_html__( "You don't have sufficient permissions to update the settings.", 'gravityforms' ) );
				return false;
			}

			// store a copy of the previous settings for cases where action would only happen if value has changed
			$this->set_previous_settings( $this->get_app_settings() );

			$settings = $this->get_posted_settings();
			$sections = $this->app_settings_fields();
			$is_valid = $this->validate_settings( $sections, $settings );

			if ( $is_valid ) {
				$settings = $this->filter_settings( $sections, $settings );
				$this->update_app_settings( $settings );
				GFCommon::add_message( $this->get_save_success_message( $sections ) );
			} else {
				GFCommon::add_error_message( $this->get_save_error_message( $sections ) );
			}
		}

	}

	/**
	 * Override this function to specify the settings fields to be rendered on the plugin settings page
	 * @return array
	 */
	public function app_settings_fields() {
		// should return an array of sections, each section contains a title, description and an array of fields
		return array();
	}

	/**
	 * Returns an flattened array of field settings for the specified settings type ignoring sections.
	 *
	 * @param string $settings_type The settings type. e.g. 'plugin'
	 *
	 * @return array
	 */
	public function settings_fields_only( $settings_type = 'plugin' ) {

		$fields = array();

		if ( ! is_callable( array( $this, "{$settings_type}_settings_fields" ) ) ) {
			return $fields;
		}

		$sections = call_user_func( array( $this, "{$settings_type}_settings_fields" ) );

		foreach ( $sections as $section ) {
			foreach ( $section['fields'] as $field ) {
				$fields[] = $field;
			}
		}

		return $fields;
	}

	//--------------  Uninstall  ---------------

	/**
	 * Override this function to customize the markup for the uninstall section on the plugin settings page
	 */
	public function render_uninstall() {

		?>
		<form action="" method="post">
			<?php wp_nonce_field( 'uninstall', 'gf_addon_uninstall' ) ?>
			<?php if ( $this->current_user_can_uninstall() ) { ?>

				<div class="hr-divider"></div>

				<h3><span><i class="fa fa-times"></i> <?php printf( esc_html__( 'Uninstall %s Add-On', 'gravityforms' ), $this->get_short_title() ) ?></span></h3>
				<div class="delete-alert alert_red">
					<h3><i class="fa fa-exclamation-triangle gf_invalid"></i> <?php printf( esc_html__('Warning', 'gravityforms' ) ); ?></h3>
					<div class="gf_delete_notice">
						<?php echo $this->uninstall_warning_message() ?>
					</div>
					<input type="submit" name="uninstall" value="<?php esc_attr_e( 'Uninstall  Add-On', 'gravityforms' ) ?>" class="button" onclick="return confirm('<?php echo esc_js( $this->uninstall_confirm_message() ); ?>');" onkeypress="return confirm('<?php echo esc_js( $this->uninstall_confirm_message() ); ?>');">
				</div>

			<?php
			}
			?>
		</form>
	<?php
	}

	public function uninstall_warning_message() {
		return sprintf( esc_html__( '%sThis operation deletes ALL %s settings%s. If you continue, you will NOT be able to retrieve these settings.', 'gravityforms' ), '<strong>', esc_html( $this->get_short_title() ), '</strong>' );
	}

	public function uninstall_confirm_message() {
		return sprintf( __( "Warning! ALL %s settings will be deleted. This cannot be undone. 'OK' to delete, 'Cancel' to stop", 'gravityforms' ), __( $this->get_short_title() ) );
	}
	/**
	 * Not intended to be overridden or called directly by Add-Ons.
	 *
	 * @ignore
	 */
	public function maybe_uninstall() {
		if ( rgpost( 'uninstall' ) ) {
			check_admin_referer( 'uninstall', 'gf_addon_uninstall' );

			return $this->uninstall_addon();
		}

		return false;
	}

	/**
	 * Removes all settings and deactivates the Add-On.
	 *
	 * Not intended to be overridden or called directly by Add-Ons.
	 *
	 * @ignore
	 */
	public function uninstall_addon() {

		if ( ! $this->current_user_can_uninstall() ) {
			die( esc_html__( "You don't have adequate permission to uninstall this add-on: " . $this->_title, 'gravityforms' ) );
		}

		$continue = $this->uninstall();
		if ( false === $continue ) {
			return false;
		}

		global $wpdb;

		$forms        = GFFormsModel::get_forms();
		$all_form_ids = array();

		// remove entry meta
		$meta_table = version_compare( GFFormsModel::get_database_version(), '2.3-dev-1', '<' ) ? GFFormsModel::get_lead_meta_table_name() : GFFormsModel::get_entry_meta_table_name();
		remove_filter( 'query', array( 'GFForms', 'filter_query' ) );
		foreach ( $forms as $form ) {
			$all_form_ids[] = $form->id;
			$entry_meta     = $this->get_entry_meta( array(), $form->id );
			if ( is_array( $entry_meta ) ) {
				foreach ( array_keys( $entry_meta ) as $meta_key ) {
					$sql = $wpdb->prepare( "DELETE from $meta_table WHERE meta_key=%s", $meta_key );
					$wpdb->query( $sql );
				}
			}
		}
		add_filter( 'query', array( 'GFForms', 'filter_query' ) );

		//remove form settings
		if ( ! empty( $all_form_ids ) ) {
			$form_metas = GFFormsModel::get_form_meta_by_id( $all_form_ids );
			require_once( GFCommon::get_base_path() . '/form_detail.php' );
			foreach ( $form_metas as $form_meta ) {
				if ( isset( $form_meta[ $this->_slug ] ) ) {
					unset( $form_meta[ $this->_slug ] );
					$form_json = json_encode( $form_meta );
					GFFormDetail::save_form_info( $form_meta['id'], addslashes( $form_json ) );
				}
			}
		}

		//removing options
		delete_option( 'gravityformsaddon_' . $this->_slug . '_settings' );
		delete_option( 'gravityformsaddon_' . $this->_slug . '_app_settings' );
		delete_option( 'gravityformsaddon_' . $this->_slug . '_version' );


		//Deactivating plugin
		deactivate_plugins( $this->get_path() );
		update_option( 'recently_activated', array( $this->get_path() => time() ) + (array) get_option( 'recently_activated' ) );

		return true;

	}

	/**
	 * Called when the user chooses to uninstall the Add-On  - after permissions have been checked and before removing
	 * all Add-On settings and Form settings.
	 *
	 * Override this method to perform additional functions such as dropping database tables.
	 *
	 *
	 * Return false to cancel the uninstall request.
	 */
	public function uninstall() {
		return true;
	}

	//--------------  Enforce minimum GF version  ---------------------------------------------------

	/**
	 * Target for the after_plugin_row action hook. Checks whether the current version of Gravity Forms
	 * is supported and outputs a message just below the plugin info on the plugins page.
	 *
	 * Not intended to be overridden or called directly by Add-Ons.
	 *
	 * @since Unknown
	 * @since 2.4.15  Update to improve multisite updates.
	 *
	 * @param string $plugin_name The plugin filename.  Immediately overwritten.
	 * @param array  $plugin_data An array of plugin data.
	 */
	public function plugin_row( $plugin_name, $plugin_data ) {
		if ( false === $this->_enable_rg_autoupgrade && ! self::is_gravityforms_supported( $this->_min_gravityforms_version ) ) {
			$message = $this->plugin_message();
			self::display_plugin_message( $message, true );
		}

		GFForms::maybe_display_update_notification( $plugin_name, $plugin_data, $this->get_slug(), $this->_version );
	}

	/**
	 * Returns the message that will be displayed if the current version of Gravity Forms is not supported.
	 *
	 * Override this method to display a custom message.
	 */
	public function plugin_message() {
		$message = sprintf( esc_html__( 'Gravity Forms %s is required. Activate it now or %spurchase it today!%s', 'gravityforms' ), $this->_min_gravityforms_version, "<a href='https://www.gravityforms.com'>", '</a>' );

		return $message;
	}

	/**
	 * Formats and outs a message for the plugin row.
	 *
	 * Not intended to be overridden or called directly by Add-Ons.
	 *
	 * @ignore
	 *
	 * @param      $message
	 * @param bool $is_error
	 */
	public static function display_plugin_message( $message, $is_error = false ) {
		$style = $is_error ? 'style="background-color: #ffebe8;"' : '';
		echo '</tr><tr class="plugin-update-tr"><td colspan="5" class="plugin-update"><div class="update-message" ' . $style . '>' . $message . '</div></td>';
	}

	//--------------- Logging -------------------------------------------------------------

	/**
	 * Writes an error message to the Gravity Forms log. Requires the Gravity Forms logging Add-On.
	 *
	 * Not intended to be overridden by Add-Ons.
	 *
	 * @ignore
	 */
	public function log_error( $message ) {
		if ( class_exists( 'GFLogging' ) ) {
			GFLogging::include_logger();
			GFLogging::log_message( $this->_slug, $message, KLogger::ERROR );
		}
	}

	/**
	 * Writes an error message to the Gravity Forms log. Requires the Gravity Forms logging Add-On.
	 *
	 * Not intended to be overridden by Add-Ons.
	 *
	 * @ignore
	 */
	public function log_debug( $message ) {
		if ( class_exists( 'GFLogging' ) ) {
			GFLogging::include_logger();
			GFLogging::log_message( $this->_slug, $message, KLogger::DEBUG );
		}
	}

	//--------------- Locking ------------------------------------------------------------

	/**
	 * Returns the configuration for locking
	 *
	 * e.g.
	 *
	 *  array(
	 *     "object_type" => 'contact',
	 *     "capabilities" => array("gravityforms_contacts_edit_contacts"),
	 *     "redirect_url" => admin_url("admin.php?page=gf_contacts"),
	 *     "edit_url" => admin_url(sprintf("admin.php?page=gf_contacts&id=%d", $contact_id)),
	 *     "strings" => $strings
	 *     );
	 *
	 * Override this method to implement locking
	 */
	public function get_locking_config() {
		return array();
	}


	/**
	 * Returns TRUE if the current page is the edit page. Otherwise, returns FALSE
	 *
	 * Override this method to implement locking on the edit page.
	 */
	public function is_locking_edit_page() {
		return false;
	}

	/**
	 * Returns TRUE if the current page is the list page. Otherwise, returns FALSE
	 *
	 * Override this method to display locking info on the list page.
	 */
	public function is_locking_list_page() {
		return false;
	}

	/**
	 * Returns TRUE if the current page is the view page. Otherwise, returns FALSE
	 *
	 * Override this method to display locking info on the view page.
	 */
	public function is_locking_view_page() {
		return false;
	}

	/**
	 * Returns the ID of the object to be locked. E.g. Form ID
	 *
	 * Override this method to implement locking
	 */
	public function get_locking_object_id() {
		return 0;
	}

	/**
	 * Outputs information about the user currently editing the specified object
	 *
	 * @param int  $object_id The Object ID
	 * @param bool $echo      Whether to echo
	 *
	 * @return string The markup for the lock info
	 */
	public function lock_info( $object_id, $echo = true ) {
		$gf_locking = new GFAddonLocking( $this->get_locking_config(), $this );
		$lock_info  = $gf_locking->lock_info( $object_id, false );
		if ( $echo ) {
			echo $lock_info;
		}

		return $lock_info;
	}

	/**
	 * Outputs class for the row for the specified Object ID on the list page.
	 *
	 * @param int  $object_id The object ID
	 * @param bool $echo      Whether to echo
	 *
	 * @return string The markup for the class
	 */
	public function list_row_class( $object_id, $echo = true ) {
		$gf_locking = new GFAddonLocking( $this->get_locking_config(), $this );
		$class      = $gf_locking->list_row_class( $object_id, false );
		if ( $echo ) {
			echo $class;
		}

		return $class;
	}

	/**
	 * Checked whether an object is locked
	 *
	 * @param int|mixed $object_id The object ID
	 *
	 * @return bool
	 */
	public function is_object_locked( $object_id ) {
		$gf_locking = new GFAddonLocking( $this->get_locking_config(), $this );

		return $gf_locking->is_locked( $object_id );
	}

	//------------- Field Value Retrieval -------------------------------------------------

	/**
	 * Returns the value of the mapped field.
	 *
	 * @param string $setting_name
	 * @param array $form
	 * @param array $entry
	 * @param mixed $settings
	 *
	 * @return string
	 */
	public function get_mapped_field_value( $setting_name, $form, $entry, $settings = false ) {

		$field_id = $this->get_setting( $setting_name, '', $settings );

		return $this->get_field_value( $form, $entry, $field_id );
	}

	/**
	 * Returns the value of the selected field.
	 *
	 * @access private
	 *
	 * @param array $form
	 * @param array $entry
	 * @param string $field_id
	 *
	 * @return string field value
	 */
	public function get_field_value( $form, $entry, $field_id ) {

		$field_value = '';

		switch ( strtolower( $field_id ) ) {

			case 'form_title':
				$field_value = rgar( $form, 'title' );
				break;

			case 'date_created':
				$date_created = rgar( $entry, strtolower( $field_id ) );
				if ( empty( $date_created ) ) {
					//the date created may not yet be populated if this function is called during the validation phase and the entry is not yet created
					$field_value = gmdate( 'Y-m-d H:i:s' );
				} else {
					$field_value = $date_created;
				}
				break;

			case 'ip':
			case 'source_url':
			case 'id':
				$field_value = rgar( $entry, strtolower( $field_id ) );
				break;

			default:
				$field = GFFormsModel::get_field( $form, $field_id );

				if ( is_object( $field ) ) {
					$is_integer = $field_id == intval( $field_id );
					$input_type = $field->get_input_type();

					if ( $is_integer && $input_type == 'address' ) {

						$field_value = $this->get_full_address( $entry, $field_id );

					} elseif ( $is_integer && $input_type == 'name' ) {

						$field_value = $this->get_full_name( $entry, $field_id );

					} elseif ( is_callable( array( $this, "get_{$input_type}_field_value" ) ) ) {

						$field_value = call_user_func( array( $this, "get_{$input_type}_field_value" ), $entry, $field_id, $field );

					} else {

						$field_value = $field->get_value_export( $entry, $field_id );

					}
				} else {

					$field_value = rgar( $entry, $field_id );

				}

		}

		/**
		 * A generic filter allowing the field value to be overridden. Form and field id modifiers supported.
		 *
		 * @param string $field_value The value to be overridden.
		 * @param array $form The Form currently being processed.
		 * @param array $entry The Entry currently being processed.
		 * @param string $field_id The ID of the Field currently being processed.
		 * @param string $slug The add-on slug e.g. gravityformsactivecampaign.
		 *
		 * @since 1.9.15.12
		 *
		 * @return string
		 */
		$field_value = gf_apply_filters( array( 'gform_addon_field_value', $form['id'], $field_id ), $field_value, $form, $entry, $field_id, $this->_slug );

		return $this->maybe_override_field_value( $field_value, $form, $entry, $field_id );
	}

	/**
	 * Enables use of the gform_SLUG_field_value filter to override the field value. Override this function to prevent the filter being used or to implement a custom filter.
	 *
	 * @param string $field_value
	 * @param array $form
	 * @param array $entry
	 * @param string $field_id
	 *
	 * @return string
	 */
	public function maybe_override_field_value( $field_value, $form, $entry, $field_id ) {
		/* Get Add-On slug */
		$slug = str_replace( 'gravityforms', '', $this->_slug );

		return gf_apply_filters( array(
			"gform_{$slug}_field_value",
			$form['id'],
			$field_id
		), $field_value, $form, $entry, $field_id );
	}

	/**
	 * Returns the combined value of the specified Address field.
	 *
	 * @param array $entry
	 * @param string $field_id
	 *
	 * @return string
	 */
	public function get_full_address( $entry, $field_id ) {

		return GF_Fields::get( 'address' )->get_value_export( $entry, $field_id );
	}

	/**
	 * Returns the combined value of the specified Name field.
	 *
	 * @param array $entry
	 * @param string $field_id
	 *
	 * @return string
	 */
	public function get_full_name( $entry, $field_id ) {

		return GF_Fields::get( 'name' )->get_value_export( $entry, $field_id );
	}

	/**
	 * Returns the value of the specified List field.
	 *
	 * @param array $entry
	 * @param string $field_id
	 * @param GF_Field_List $field
	 *
	 * @return string
	 */
	public function get_list_field_value( $entry, $field_id, $field ) {

		return $field->get_value_export( $entry, $field_id );
	}

	/**
	 * Returns the field ID of the first field of the desired type.
	 *
	 * @access public
	 * @param string $field_type
	 * @param int $subfield_id (default: null)
	 * @param int $form_id (default: null)
	 * @return string
	 */
	public function get_first_field_by_type( $field_type, $subfield_id = null, $form_id = null, $return_first_only = true ) {

		/* Get the current form ID. */
		if ( rgblank( $form_id ) ) {

			$form_id = rgget( 'id' );

		}

		/* Get the form. */
		$form = GFAPI::get_form( $form_id );

		/* Get the request field type for the form. */
		$fields = GFAPI::get_fields_by_type( $form, array( $field_type ) );

		if ( count( $fields ) == 0 || ( count( $fields ) > 1 && $return_first_only ) ) {

			return null;

		} else {

			if ( rgblank( $subfield_id ) ) {

				return $fields[0]->id;

			} else {

				return $fields[0]->id . '.' . $subfield_id;

			}

		}

	}

	//--------------- Notes ------------------
	/**
	 * Override this function to specify a custom avatar (i.e. the payment gateway logo) for entry notes created by the Add-On
	 * @return  string - A fully qualified URL for the avatar
	 */
	public function note_avatar() {
		return false;
	}

	public function notes_avatar( $avatar, $note ) {
		if ( $note->user_name == $this->_short_title && empty( $note->user_id ) && $this->method_is_overridden( 'note_avatar', 'GFAddOn' ) ) {
			$new_avatar = $this->note_avatar();
		}

		return empty( $new_avatar ) ? $avatar : "<img alt='{$this->_short_title}' src='{$new_avatar}' class='avatar avatar-48' height='48' width='48' />";
	}

	public function add_note( $entry_id, $note, $note_type = null ) {

		$user_id   = 0;
		$user_name = $this->_short_title;

		GFFormsModel::add_note( $entry_id, $user_id, $user_name, $note, $note_type );

	}

	//--------------  Helper functions  ---------------------------------------------------

	protected final function method_is_overridden( $method_name, $base_class = 'GFAddOn' ) {
		$reflector = new ReflectionMethod( $this, $method_name );
		$name      = $reflector->getDeclaringClass()->getName();

		return $name !== $base_class;
	}

	/**
	 * Returns the url of the root folder of the current Add-On.
	 *
	 * @param string $full_path Optional. The full path the the plugin file.
	 *
	 * @return string
	 */
	public function get_base_url( $full_path = '' ) {
		if ( empty( $full_path ) ) {
			$full_path = $this->_full_path;
		}

		return plugins_url( null, $full_path );
	}

	/**
	 * Returns the url of the Add-On Framework root folder.
	 *
	 * @return string
	 */
	final public static function get_gfaddon_base_url() {
		return plugins_url( null, __FILE__ );
	}

	/**
	 * Returns the physical path of the Add-On Framework root folder.
	 *
	 * @return string
	 */
	final public static function get_gfaddon_base_path() {
		return self::_get_base_path();
	}

	/**
	 * Returns the physical path of the plugins root folder.
	 *
	 * @param string $full_path
	 *
	 * @return string
	 */
	public function get_base_path( $full_path = '' ) {
		if ( empty( $full_path ) ) {
			$full_path = $this->_full_path;
		}
		$folder = basename( dirname( $full_path ) );

		return WP_PLUGIN_DIR . '/' . $folder;
	}

	/**
	 * Returns the physical path of the Add-On Framework root folder
	 *
	 * @return string
	 */
	private static function _get_base_path() {
		$folder = basename( dirname( __FILE__ ) );

		return GFCommon::get_base_path() . '/includes/' . $folder;
	}

	/**
	 * Returns the URL of the Add-On Framework root folder
	 *
	 * @return string
	 */
	private static function _get_base_url() {
		$folder = basename( dirname( __FILE__ ) );

		return GFCommon::get_base_url() . '/includes/' . $folder;
	}

	/**
	 * Checks whether the Gravity Forms is installed.
	 *
	 * @return bool
	 */
	public function is_gravityforms_installed() {
		return class_exists( 'GFForms' );
	}

	public function table_exists( $table_name ) {

		return GFCommon::table_exists( $table_name );

	}

	/**
	 * Checks whether the current version of Gravity Forms is supported
	 *
	 * @param $min_gravityforms_version
	 *
	 * @return bool|mixed
	 */
	public function is_gravityforms_supported( $min_gravityforms_version = '' ) {
		if ( isset( $this->_min_gravityforms_version ) && empty( $min_gravityforms_version ) ) {
			$min_gravityforms_version = $this->_min_gravityforms_version;
		}

		if ( empty( $min_gravityforms_version ) ) {
			return true;
		}

		if ( class_exists( 'GFCommon' ) ) {
			$is_correct_version = version_compare( GFCommon::$version, $min_gravityforms_version, '>=' );

			return $is_correct_version;
		} else {
			return false;
		}
	}

	/**
	 * Returns this plugin's short title. Used to display the plugin title in small areas such as tabs
	 */
	public function get_short_title() {
		return isset( $this->_short_title ) ? $this->_short_title : $this->_title;
	}

	/**
	 * Return this plugin's version.
	 *
	 * @since  2.0
	 * @access public
	 *
	 * @return string
	 */
	public function get_version() {
		return $this->_version;
	}

	/**
	 * Returns the unescaped URL for the plugin settings tab associated with this plugin
	 *
	 */
	public function get_plugin_settings_url() {
		return add_query_arg( array( 'page' => 'gf_settings', 'subview' => $this->_slug ), admin_url( 'admin.php' ) );
	}

	/**
	 * Returns the current form object based on the id query var. Otherwise returns false
	 */
	public function get_current_form() {

		return rgempty( 'id', $_GET ) ? false : GFFormsModel::get_form_meta( rgget( 'id' ) );
	}

	/**
	 * Returns TRUE if the current request is a postback, otherwise returns FALSE
	 */
	public function is_postback() {
		return is_array( $_POST ) && count( $_POST ) > 0;
	}

	/**
	 * Returns TRUE if the settings "Save" button was pressed
	 */
	public function is_save_postback() {
		return ! rgempty( 'gform-settings-save' );
	}

	/**
	 * Returns TRUE if the current page is the form editor page. Otherwise, returns FALSE
	 */
	public function is_form_editor() {

		if ( rgget( 'page' ) == 'gf_edit_forms' && ! rgempty( 'id', $_GET ) && rgempty( 'view', $_GET ) ) {
			return true;
		}

		return false;
	}

	/**
	 * Returns TRUE if the current page is the form list page. Otherwise, returns FALSE
	 */
	public function is_form_list() {

		if ( rgget( 'page' ) == 'gf_edit_forms' && rgempty( 'id', $_GET ) && rgempty( 'view', $_GET ) ) {
			return true;
		}

		return false;
	}

	/**
	 * Returns TRUE if the current page is the form settings page, or a specific form settings tab (specified by the $tab parameter). Otherwise returns FALSE
	 *
	 * @param string $tab - Specifies a specific form setting page/tab
	 *
	 * @return bool
	 */
	public function is_form_settings( $tab = null ) {

		$is_form_settings = rgget( 'page' ) == 'gf_edit_forms' && rgget( 'view' ) == 'settings';
		$is_tab           = $this->_tab_matches( $tab );

		if ( $is_form_settings && $is_tab ) {
			return true;
		} else {
			return false;
		}
	}

	private function _tab_matches( $tabs ) {
		if ( $tabs == null ) {
			return true;
		}

		if ( ! is_array( $tabs ) ) {
			$tabs = array( $tabs );
		}

		$current_tab = rgempty( 'subview', $_GET ) ? 'settings' : rgget( 'subview' );

		foreach ( $tabs as $tab ) {
			if ( strtolower( $tab ) == strtolower( $current_tab ) ) {
				return true;
			}
		}
	}

	/**
	 * Returns TRUE if the current page is the plugin settings main page, or a specific plugin settings tab (specified by the $tab parameter). Otherwise returns FALSE
	 *
	 * @param string $tab - Specifies a specific plugin setting page/tab.
	 *
	 * @return bool
	 */
	public function is_plugin_settings( $tab = '' ) {

		$is_plugin_settings = rgget( 'page' ) == 'gf_settings';
		$is_tab             = $this->_tab_matches( $tab );

		if ( $is_plugin_settings && $is_tab ) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Returns TRUE if the current page is the app settings main page, or a specific apps settings tab (specified by the $tab parameter). Otherwise returns FALSE
	 *
	 * @param string $tab - Specifies a specific app setting page/tab.
	 *
	 * @return bool
	 */
	public function is_app_settings( $tab = '' ) {

		$is_app_settings = rgget( 'page' ) == $this->_slug . '_settings';
		$is_tab          = $this->_tab_matches( $tab );

		if ( $is_app_settings && $is_tab ) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Returns TRUE if the current page is the plugin page. Otherwise returns FALSE
	 * @return bool
	 */
	public function is_plugin_page() {

		return strtolower( rgget( 'page' ) ) == strtolower( $this->_slug );
	}

	/**
	 * Returns TRUE if the current page is the entry view page. Otherwise, returns FALSE
	 * @return bool
	 */
	public function is_entry_view() {
		if ( rgget( 'page' ) == 'gf_entries' && rgget( 'view' ) == 'entry' && ( ! isset( $_POST['screen_mode'] ) || rgpost( 'screen_mode' ) == 'view' ) ) {
			return true;
		}

		return false;
	}

	/**
	 * Returns TRUE if the current page is the entry edit page. Otherwise, returns FALSE
	 * @return bool
	 */
	public function is_entry_edit() {
		if ( rgget( 'page' ) == 'gf_entries' && rgget( 'view' ) == 'entry' && rgpost( 'screen_mode' ) == 'edit' ) {
			return true;
		}

		return false;
	}

	public function is_entry_list() {
		if ( rgget( 'page' ) == 'gf_entries' && ( rgget( 'view' ) == 'entries' || rgempty( 'view', $_GET ) ) ) {
			return true;
		}

		return false;
	}

	/**
	 * Returns TRUE if the current page is the results page. Otherwise, returns FALSE
	 */
	public function is_results() {
		if ( rgget( 'page' ) == 'gf_entries' && rgget( 'view' ) == 'gf_results_' . $this->_slug ) {
			return true;
		}

		return false;
	}

	/**
	 * Returns TRUE if the current page is the print page. Otherwise, returns FALSE
	 */
	public function is_print() {
		if ( rgget( 'gf_page' ) == 'print-entry' ) {
			return true;
		}

		return false;
	}

	/**
	 * Returns TRUE if the current page is the preview page. Otherwise, returns FALSE
	 */
	public function is_preview() {
		if ( rgget( 'gf_page' ) == 'preview' ) {
			return true;
		}

		return false;
	}

	public function has_deprecated_elements() {
		$deprecated = GFAddOn::get_all_deprecated_protected_methods( get_class( $this ) );
		if ( ! empty( $deprecated ) ) {
			return true;
		}

		return false;
	}

	public static function get_all_deprecated_protected_methods($add_on_class_name = ''){
		$deprecated = array();
		$deprecated = array_merge( $deprecated, self::get_deprecated_protected_methods_for_base_class( 'GFAddOn', $add_on_class_name )) ;
		$deprecated = array_merge( $deprecated, self::get_deprecated_protected_methods_for_base_class( 'GFFeedAddOn', $add_on_class_name ) ) ;
		$deprecated = array_merge( $deprecated, self::get_deprecated_protected_methods_for_base_class( 'GFPaymentAddOn', $add_on_class_name ) ) ;
		return $deprecated;
	}

	public static function get_deprecated_protected_methods_for_base_class( $base_class_name, $add_on_class_name = '' ) {
		$deprecated = array();

		if ( ! class_exists( $base_class_name ) ) {
			return $deprecated;
		}

		$base_class_names = array(
			'GFAddOn',
			'GFFeedAddOn',
			'GFPaymentAddOn'
		);

		$base_class = new ReflectionClass( $base_class_name );

		$classes = empty($add_on_class_name) ? get_declared_classes() : array( $add_on_class_name );

		foreach ( $classes as $class ) {
			if ( ! is_subclass_of( $class, $base_class_name ) || in_array( $class, $base_class_names ) ) {
				continue;
			}

			$add_on_class   = new ReflectionClass( $class );
			$add_on_methods = $add_on_class->getMethods( ReflectionMethod::IS_PROTECTED );
			foreach ( $add_on_methods as $method ) {
				$method_name               = $method->getName();
				$base_has_method           = $base_class->hasMethod( $method_name );
				$is_declared_by_base_class = $base_has_method && $base_class->getMethod( $method_name )->getDeclaringClass()->getName() == $base_class_name;
				$is_overridden             = $method->getDeclaringClass()->getName() == $class;
				if ( $is_declared_by_base_class && $is_overridden ) {
					$deprecated[] = $class . '::' . $method_name;
				}
			}
		}
		return $deprecated;
	}

	public function maybe_wp_kses( $html, $allowed_html = 'post', $allowed_protocols = array() ) {
		return GFCommon::maybe_wp_kses( $html, $allowed_html, $allowed_protocols );
	}

	/**
	 * Returns the slug for the add-on.
	 *
	 * @since 2.0
	 */
	public function get_slug() {
		return $this->_slug;
	}

	/**
	 * Returns the add-on slug with the gravityforms prefix removed.
	 *
	 * @since 2.4.18
	 *
	 * @return string
	 */
	public function get_short_slug() {
		return str_replace( 'gravityforms', '', $this->get_slug() );
	}

	/**
	 * Returns the path for the add-on.
	 *
	 * @since 2.2
	 */
	public function get_path() {
		return $this->_path;
	}

	/**
	 * Fixes the add-on _path property value, if the directory has been renamed.
	 *
	 * @since 2.4.17
	 */
	public function update_path() {
		$path_dirname = dirname( $this->_path );
		if ( $path_dirname !== '.' ) {
			$full_path_dirname = basename( dirname( $this->_full_path ) );
			if ( $path_dirname !== $full_path_dirname ) {
				$this->_path = trailingslashit( $full_path_dirname ) . basename( $this->_path );
			}
		}
	}

	/**
	 * Get all or a specific capability for Add-On.
	 *
	 * @since  2.2.5.27
	 * @access public
	 *
	 * @param string $capability Capability to return.
	 *
	 * @return string|array
	 */
	public function get_capabilities( $capability = '' ) {

		if ( rgblank( $capability ) ) {
			return $this->_capabilities;
		}

		return isset( $this->{'_capabilities_' . $capability} ) ? $this->{'_capabilities_' . $capability} : array();

	}

	/**
	 * Initializing translations.
	 *
	 * @since 2.0.7
	 */
	public function load_text_domain() {
		GFCommon::load_gf_text_domain( $this->_slug, plugin_basename( dirname( $this->_full_path ) ) );
	}

	/***
	 * Determines if the current user has the proper cabalities to uninstall this add-on
	 * Add-ons that have been network activated can only be uninstalled by a network admin.
	 *
	 * @since 2.3.1.12
	 * @access public
	 *
	 * @return bool True if current user can uninstall this add-on. False otherwise
	 */
	public function current_user_can_uninstall(){

		return GFCommon::current_user_can_uninstall( $this->_capabilities_uninstall, $this->get_path() );

	}
}
¿Qué es la limpieza dental de perros? - Clínica veterinaria


Es la eliminación del sarro y la placa adherida a la superficie de los dientes mediante un equipo de ultrasonidos que garantiza la integridad de las piezas dentales a la vez que elimina en profundidad cualquier resto de suciedad.

A continuación se procede al pulido de los dientes mediante una fresa especial que elimina la placa bacteriana y devuelve a los dientes el aspecto sano que deben tener.

Una vez terminado todo el proceso, se mantiene al perro en observación hasta que se despierta de la anestesia, bajo la atenta supervisión de un veterinario.

¿Cada cuánto tiempo tengo que hacerle una limpieza dental a mi perro?

A partir de cierta edad, los perros pueden necesitar una limpieza dental anual o bianual. Depende de cada caso. En líneas generales, puede decirse que los perros de razas pequeñas suelen acumular más sarro y suelen necesitar una atención mayor en cuanto a higiene dental.


Riesgos de una mala higiene


Los riesgos más evidentes de una mala higiene dental en los perros son los siguientes:

  • Cuando la acumulación de sarro no se trata, se puede producir una inflamación y retracción de las encías que puede descalzar el diente y provocar caídas.
  • Mal aliento (halitosis).
  • Sarro perros
  • Puede ir a más
  • Las bacterias de la placa pueden trasladarse a través del torrente circulatorio a órganos vitales como el corazón ocasionando problemas de endocarditis en las válvulas. Las bacterias pueden incluso acantonarse en huesos (La osteomielitis es la infección ósea, tanto cortical como medular) provocando mucho dolor y una artritis séptica).

¿Cómo se forma el sarro?

El sarro es la calcificación de la placa dental. Los restos de alimentos, junto con las bacterias presentes en la boca, van a formar la placa bacteriana o placa dental. Si la placa no se retira, al mezclarse con la saliva y los minerales presentes en ella, reaccionará formando una costra. La placa se calcifica y se forma el sarro.

El sarro, cuando se forma, es de color blanquecino pero a medida que pasa el tiempo se va poniendo amarillo y luego marrón.

Síntomas de una pobre higiene dental
La señal más obvia de una mala salud dental canina es el mal aliento.

Sin embargo, a veces no es tan fácil de detectar
Y hay perros que no se dejan abrir la boca por su dueño. Por ejemplo…

Recientemente nos trajeron a la clínica a un perro que parpadeaba de un ojo y decía su dueño que le picaba un lado de la cara. Tenía molestias y dificultad para comer, lo que había llevado a sus dueños a comprarle comida blanda (que suele ser un poco más cara y llevar más contenido en grasa) durante medio año. Después de una exploración oftalmológica, nos dimos cuenta de que el ojo tenía una úlcera en la córnea probablemente de rascarse . Además, el canto lateral del ojo estaba inflamado. Tenía lo que en humanos llamamos flemón pero como era un perro de pelo largo, no se le notaba a simple vista. Al abrirle la boca nos llamó la atención el ver una muela llena de sarro. Le realizamos una radiografía y encontramos una fístula que llegaba hasta la parte inferior del ojo.

Le tuvimos que extraer la muela. Tras esto, el ojo se curó completamente con unos colirios y una lentilla protectora de úlcera. Afortunadamente, la úlcera no profundizó y no perforó el ojo. Ahora el perro come perfectamente a pesar de haber perdido una muela.

¿Cómo mantener la higiene dental de tu perro?
Hay varias maneras de prevenir problemas derivados de la salud dental de tu perro.

Limpiezas de dientes en casa
Es recomendable limpiar los dientes de tu perro semanal o diariamente si se puede. Existe una gran variedad de productos que se pueden utilizar:

Pastas de dientes.
Cepillos de dientes o dedales para el dedo índice, que hacen más fácil la limpieza.
Colutorios para echar en agua de bebida o directamente sobre el diente en líquido o en spray.

En la Clínica Tus Veterinarios enseñamos a nuestros clientes a tomar el hábito de limpiar los dientes de sus perros desde que son cachorros. Esto responde a nuestro compromiso con la prevención de enfermedades caninas.

Hoy en día tenemos muchos clientes que limpian los dientes todos los días a su mascota, y como resultado, se ahorran el dinero de hacer limpiezas dentales profesionales y consiguen una mejor salud de su perro.


Limpiezas dentales profesionales de perros y gatos

Recomendamos hacer una limpieza dental especializada anualmente. La realizamos con un aparato de ultrasonidos que utiliza agua para quitar el sarro. Después, procedemos a pulir los dientes con un cepillo de alta velocidad y una pasta especial. Hacemos esto para proteger el esmalte.

La frecuencia de limpiezas dentales necesaria varía mucho entre razas. En general, las razas grandes tienen buena calidad de esmalte, por lo que no necesitan hacerlo tan a menudo e incluso pueden pasarse la vida sin requerir una limpieza. Sin embargo, razas pequeñas como el Yorkshire o el Maltés, deben hacérselas todos los años desde cachorros si se quiere conservar sus piezas dentales.

Otro factor fundamental es la calidad del pienso. Algunas marcas han diseñado croquetas que limpian la superficie del diente y de la muela al masticarse.

Ultrasonido para perros

¿Se necesita anestesia para las limpiezas dentales de perros y gatos?

La limpieza dental en perros no es una técnica que pueda practicarse sin anestesia general , aunque hay veces que los propietarios no quieren anestesiar y si tiene poco sarro y el perro es muy bueno se puede intentar…… , pero no se va a poder pulir ni acceder a todas la zona de la boca …. Además los limpiadores dentales van a irrigar agua y hay riesgo de aspiración a vías respiratorias si no se realiza una anestesia correcta con intubación traqueal . En resumen , sin anestesia no se va hacer una correcta limpieza dental.

Tampoco sirve la sedación ya que necesitamos que el animal esté totalmente quieto, y el veterinario tenga un acceso completo a todas sus piezas dentales y encías.

Alimentos para la limpieza dental

Hay que tener cierto cuidado a la hora de comprar determinados alimentos porque no todos son saludables. Algunos tienen demasiado contenido graso, que en exceso puede causar problemas cardiovasculares y obesidad.

Los mejores alimentos para los dientes son aquellos que están elaborados por empresas farmacéuticas y llevan componentes químicos con tratamientos específicos para el diente del perro. Esto implica no solo limpieza a través de la acción mecánica de morder sino también un tratamiento antibacteriano para prevenir el sarro.

Conclusión

Si eres como la mayoría de dueños, por falta de tiempo , es probable que no estés prestando la suficiente atención a la limpieza dental de tu perro. Por eso te animamos a que comiences a limpiar los dientes de tu perro y consideres atender a su higiene bucal con frecuencia.

Estas simples medidas pueden conllevar a que tu perro tenga una vida más larga y mucho más saludable.

Si te resulta imposible introducir un cepillo de dientes a tu perro en la boca, pásate con él por clínica Tus Veterinarios y te explicamos cómo hacerlo.

Necesitas hacer una limpieza dental profesional a tu mascota?
Llámanos al 622575274 o contacta con nosotros

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

¡Hola!