<?php

namespace Gravity_Forms\Gravity_Forms_Google_Analytics;

defined( 'ABSPATH' ) || die();

use GFForms;
use GFFeedAddOn;
use GFCommon;
use GFAPI;
use GFFormsModel;
use GFCache;
use Gravity_Forms\Gravity_Forms_Google_Analytics\Settings;
use WP_Error;

// Include the Gravity Forms Feed Add-On Framework.
GFForms::include_feed_addon_framework();

/**
 * Gravity Forms Google Analytics Add-On.
 *
 * @since     1.0.0
 * @package   GravityForms
 * @author    Rocketgenius
 * @copyright Copyright (c) 2019, Rocketgenius
 */
class GF_Google_Analytics extends GFFeedAddOn {

	/**
	 * Contains an instance of this class, if available.
	 *
	 * @since  1.0
	 * @var    GF_Google_Analytics $_instance If available, contains an instance of this class
	 */
	private static $_instance = null;

	/**
	 * Defines the version of the Gravity Forms Google Analytics Add-On.
	 *
	 * @since  1.0
	 * @var    string $_version Contains the version.
	 */
	protected $_version = GF_GOOGLE_ANALYTICS_VERSION;

	/**
	 * Defines the minimum Gravity Forms version required.
	 *
	 * @since  1.0
	 * @var    string $_min_gravityforms_version The minimum version required.
	 */
	protected $_min_gravityforms_version = GF_GOOGLE_ANALYTICS_MIN_GF_VERSION;

	/**
	 * Defines the plugin slug.
	 *
	 * @since  1.0
	 * @var    string $_slug The slug used for this plugin.
	 */
	protected $_slug = 'gravityformsgoogleanalytics';

	/**
	 * Defines the main plugin file.
	 *
	 * @since  1.0
	 * @var    string $_path The path to the main plugin file, relative to the plugins folder.
	 */
	protected $_path = 'gravityformsgoogleanalytics/googleanalytics.php';

	/**
	 * Defines the full path to this class file.
	 *
	 * @since  1.0
	 * @var    string $_full_path The full path.
	 */
	protected $_full_path = __FILE__;

	/**
	 * Wrapper class for plugin settings.
	 *
	 * @since 1.0
	 * @var Settings\Plugin_Settings
	 */
	private $plugin_settings;

	/**
	 * Wrapper class for form settings.
	 *
	 * @since 1.0
	 * @var Settings\Form_Settings
	 */
	private $form_settings;

	/**
	 * Defines the URL where this add-on can be found.
	 *
	 * @since  1.0
	 * @var    string The URL of the Add-On.
	 */
	protected $_url = 'https://gravityforms.com';

	/**
	 * Defines the title of this add-on.
	 *
	 * @since  1.0
	 * @var    string $_title The title of the add-on.
	 */
	protected $_title = 'Gravity Forms Google Analytics Add-On';

	/**
	 * Defines the short title of the add-on.
	 *
	 * @since  1.0
	 * @var    string $_short_title The short title.
	 */
	protected $_short_title = 'Google Analytics';

	/**
	 * Defines if Add-On should use Gravity Forms servers for update data.
	 *
	 * @since  1.0.0
	 * @access protected
	 * @var    bool
	 */
	protected $_enable_rg_autoupgrade = true;

	/**
	 * Defines the capabilities needed for the Google Analytics Add-On
	 *
	 * @since  1.0.0
	 * @access protected
	 * @var    array $_capabilities The capabilities needed for the Add-On
	 */
	protected $_capabilities = array( 'gravityforms_googleanalytics', 'gravityforms_googleanalytics_uninstall' );

	/**
	 * Defines the capability needed to access the Add-On settings page.
	 *
	 * @since  1.0.0
	 * @access protected
	 * @var    string $_capabilities_settings_page The capability needed to access the Add-On settings page.
	 */
	protected $_capabilities_settings_page = 'gravityforms_googleanalytics';

	/**
	 * Defines the capability needed to access the Add-On form settings page.
	 *
	 * @since  1.0.0
	 * @access protected
	 * @var    string $_capabilities_form_settings The capability needed to access the Add-On form settings page.
	 */
	protected $_capabilities_form_settings = 'gravityforms_googleanalytics';

	/**
	 * Defines the capability needed to uninstall the Add-On.
	 *
	 * @since  1.0.0
	 * @access protected
	 * @var    string $_capabilities_uninstall The capability needed to uninstall the Add-On.
	 */
	protected $_capabilities_uninstall = 'gravityforms_googleanalytics_uninstall';

	/**
	 * Stores the plugin's options
	 *
	 * @since 1.0.0
	 * @var array $options
	 */
	private static $options = false;

	/**
	 * Defines the category if the page is redirected.
	 *
	 * @since  1.0.0
	 *
	 * @var    string Event category for the feed.
	 */
	private $conversion_category = '';

	/**
	 * Defines the label if the page is redirected.
	 *
	 * @since  1.0.0
	 *
	 * @var    string Event label for the feed.
	 */
	private $conversion_label = '';

	/**
	 * Defines the action if the page is redirected.
	 *
	 * @since  1.0.0
	 *
	 * @var    string Event action for the feed.
	 */
	private $conversion_action = '';

	/**
	 * Saves an API instance for Google Authorization.
	 *
	 * @since  1.0.0
	 *
	 * @var    GF_Google_Analytics_API null into object is set.
	 */
	protected $api = null;

	/**
	 * Sets whether an account has a connection error (i.e., GA or GTM are not installed for an account).
	 *
	 * @since  1.0.0
	 *
	 * @var    bool True if error, false if not.
	 */
	private $connect_error = false;

	/**
	 * Version of this add-on which requires reauthentication with the API.
	 *
	 * Anytime updates are made to this class that requires a site to reauthenticate Gravity Forms with Google Analytics, this
	 * constant should be updated to the value of GFForms::$version.
	 *
	 * @since 2.0
	 *
	 * @see   GFForms::$version
	 */
	const LAST_REAUTHENTICATION_VERSION = '2.0';

	/**
	 * The handle used to register and enqueue the frontend scripts.
	 *
	 * @since 2.1
	 *
	 * @var string
	 */
	const FE_SCRIPT_HANDLE = 'gforms_google_analytics_frontend';

	/**
	 * Get an instance of this class.
	 *
	 * @since  1.0.0
	 *
	 * @return GF_Google_Analytics
	 */
	public static function get_instance() {

		if ( null === self::$_instance ) {
			self::$_instance = new self();
		}

		return self::$_instance;

	}

	/**
	 * Run add-on pre-initialization processes.
	 *
	 * @since 1.0
	 */
	public function pre_init() {
		require_once plugin_dir_path( __FILE__ ) . '/includes/settings/class-plugin-settings.php';
		require_once plugin_dir_path( __FILE__ ) . '/includes/settings/class-form-settings.php';
		$this->plugin_settings = new Settings\Plugin_Settings( $this );
		$this->form_settings   = new Settings\Form_Settings( $this );

		parent::pre_init();
	}

	/**
	 * Performs enqueuing tasks for JS, redirection, event save state, and pagination.
	 *
	 * @since  1.0.0
	 */
	public function init() {
		parent::init();
		add_action( 'gform_confirmation', array( $this, 'force_text_confirmation' ), 1000, 4 );
		add_action( 'wp_head', array( $this, 'maybe_install_analytics' ) );
		add_action( 'wp_head', array( $this, 'maybe_install_tag_manager_header' ) );
		add_action( 'admin_notices', array( $this, 'maybe_display_authentication_notice' ) );

		add_action( 'wp_body_open', array( $this, 'maybe_install_tag_manager_body' ) );
		add_filter( 'gform_ajax_submission_result', array( $this, 'set_post_ajax_submission_vars' ), 10, 1 );

		// General pagination.
		add_action( 'gform_post_paging', array( $this, 'pagination' ), 10, 3 );
	}

	/**
	 * Add Admin handlers.
	 */
	public function init_admin() {
		$this->plugin_settings->maybe_update_auth_tokens();

		parent::init_admin();
	}

	/**
	 * Add Ajax handlers.
	 */
	public function init_ajax() {
		add_action( 'wp_ajax_nopriv_get_entry_meta', array( $this, 'ajax_get_entry_meta' ) );
		add_action( 'wp_ajax_get_entry_meta', array( $this, 'ajax_get_entry_meta' ) );
		add_action( 'wp_ajax_nopriv_save_entry_meta', array( $this, 'ajax_save_entry_meta' ) );
		add_action( 'wp_ajax_save_entry_meta', array( $this, 'ajax_save_entry_meta' ) );
		add_action( 'wp_ajax_save_google_analytics_data', array( $this, 'ajax_save_google_analytics_data' ) );
		add_action( 'wp_ajax_save_google_tag_manager_data', array( $this, 'ajax_save_google_tag_manager_data' ) );
		add_action( 'wp_ajax_save_manual_configuration_data', array( $this, 'ajax_save_manual_configuration_data' ) );
		add_action( 'wp_ajax_get_ga4_data_streams', array( $this, 'ajax_get_ga4_data_streams' ) );
		add_action( 'wp_ajax_get_gtm_workspaces', array( $this, 'ajax_get_gtm_workspaces' ) );
		add_action( 'wp_ajax_get_gtm_containers', array( $this, 'ajax_get_gtm_containers' ) );
		add_action( 'wp_ajax_redirect_to_api', array( $this, 'ajax_redirect_to_api' ) );
		add_action( 'wp_ajax_disconnect_account', array( $this, 'ajax_disconnect_account' ) );
		add_action( 'wp_ajax_gf_ga_log_event_sent', array( $this, 'ajax_log_ga_event_sent' ) );
		add_action( 'wp_ajax_nopriv_gf_ga_log_event_sent', array( $this, 'ajax_log_ga_event_sent' ) );

		parent::init_ajax();
	}

	/**
	 * Initializes the Google Analytics API if credentials are valid.
	 *
	 * @since  1.0
	 *
	 * @return bool|null API initialization state. Returns null if no authentication token is provided.
	 */
	public function initialize_api() {

		// If the API is already initializes, return true.
		if ( ! is_null( $this->api ) ) {
			return true;
		}

		// Initialize Google Analytics API library.
		if ( ! class_exists( 'GF_Google_Analytics_API' ) ) {
			require_once 'includes/class-gf-google-analytics-api.php';
		}

		// Get the authentication token.
		$auth_token = self::get_options( 'auth_token' );
		$mode       = self::get_options( '', 'mode' );
		if ( empty( $mode ) ) {
			$mode = self::get_options( '', 'tempmode' );
		}
		$token        = isset( $auth_token['token'] ) ? $auth_token['token'] : '';
		$date_created = isset( $auth_token['date_created'] ) ? $auth_token['date_created'] : 0;

		// If the authentication token is not set, return null.
		if ( empty( $auth_token ) || rgblank( $token ) || 'unset' === $mode || rgblank( $mode ) ) {
			return null;
		}

		// Initialize a new Google Analytics API instance.
		$google_analytics_api = new GF_Google_Analytics_API( $this, $token );
		if ( time() > ( $date_created + 3600 ) ) { // Access token expires in 1 hour = 3600 seconds.

			// Log that authentication test failed.
			$this->log_debug( __METHOD__ . '(): API tokens expired, start refreshing.' );

			// Refresh token.
			$auth_response = $google_analytics_api->refresh_token( $auth_token['refresh'] );

			if ( ! is_wp_error( $auth_response ) ) {
				$auth_settings = array(
					'token'        => rgars( $auth_response, 'token/access_token' ),
					'refresh'      => rgars( $auth_response, 'token/refresh_token' ),
					'date_created' => rgars( $auth_response, 'token/created' ),
				);
				// Save plugin settings.
				$this->update_options( $auth_settings, 'auth_token' );
				$this->log_debug( __METHOD__ . '(): API access token has been refreshed.' );
			} else {
				$this->log_debug( __METHOD__ . '(): API access token failed to be refreshed; ' . $auth_response->get_error_message() );

				return false;
			}
		}

		// Assign Google Analytics API instance to the Add-On instance.
		$this->api = $google_analytics_api;

		return true;
	}

	/**
	 * Maybe display an admin notice if Google Analytics needs to be reauthenticated.
	 *
	 * @since 2.0
	 */
	public function maybe_display_authentication_notice() {

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

		$message = sprintf(
		/* translators: 1: reauthentication version number 2: open <a> tag, 3: close </a> tag */
			esc_html__(
				'Gravity Forms Google Analytics v%1$s requires re-authentication in order to correctly send data to Google Analytics 4. Please %2$sdisconnect and reconnect%3$s to ensure data can be sent to GA4.',
				'gravityformsgoogleanalytics'
			),
			self::LAST_REAUTHENTICATION_VERSION,
			'<a href="' . esc_attr( $this->get_plugin_settings_url() ) . '">',
			'</a>'
		)
		?>

		<div class="gf-notice notice notice-error">
			<p><?php echo wp_kses( $message, array( 'a' => array( 'href' => true ) ) ); ?></p>
		</div>
		<?php
	}

	/**
	 * Check whether this add-on needs to be reauthenticated with Google Analytics.
	 *
	 * @since 2.0
	 *
	 * @return bool
	 */
	public function requires_api_reauthentication() {
		$settings = self::get_options();

		// don't show the reauth notice if the add-on is not connected.
		if ( ! rgar( $settings, 'mode' ) ) {
			return false;
		}

		return ! empty( $settings ) && version_compare( rgar( $settings, 'reauth_version' ), self::LAST_REAUTHENTICATION_VERSION, '<' );
	}

	/**
	 * Determines if logging is enabled for Google Analytics.
	 *
	 * @since 2.0.0
	 *
	 * @return bool Returns true if logging is enabled. Returns false otherwise
	 */
	private function is_logging_enabled() {

		// Query string override to enable console logging if needed.
		if ( rgget( 'gfga_logging' ) == 1 ) {
			return true;
		}

		// Main logging setting is turned off. Do not log.
		if ( ! class_exists( 'GFLogging' ) || ! get_option( 'gform_enable_logging' ) ) {
			return false;
		}

		// Get logging setting for plugin.
		$plugin_setting = \GFLogging::get_instance()->get_plugin_setting( $this->_slug );

		return rgar( $plugin_setting, 'enable' );
	}

	/**
	 * Outputs admin scripts to handle form submission in back-end.
	 *
	 * @since  1.0.0
	 *
	 * @return array
	 */
	public function scripts() {
		$min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG || rgget( 'gform_debug' ) ? '' : '.min';

		$scripts = array(
			array(
				'handle'    => 'google_analytics_admin',
				'src'       => $this->get_base_url() . "/js/google-analytics-admin{$min}.js",
				'version'   => $this->_version,
				'deps'      => array( 'jquery', 'wp-ajax-response' ),
				'strings'   => array(
					'update_settings'     => wp_strip_all_tags( __( 'Update Settings', 'gravityformsgoogleanalytics' ) ),
					'disconnect'          => wp_strip_all_tags( __( 'Disconnecting', 'gravityformsgoogleanalytics' ) ),
					'redirect'            => wp_strip_all_tags( __( 'Redirecting...', 'gravityformsgoogleanalytics' ) ),
					'connect'             => wp_strip_all_tags( __( 'Connect', 'gravityformsgoogleanalytics' ) ),
					'connecting'          => wp_strip_all_tags( __( 'Connecting', 'gravityformsgoogleanalytics' ) ),
					'workspace_required'  => wp_strip_all_tags( __( 'You must select a Workspace', 'gravityformsgoogleanalytics' ) ),
					'view_required'       => wp_strip_all_tags( __( 'You must select a View', 'gravityformsgoogleanalytics' ) ),
					'ga_required'         => wp_strip_all_tags( __( 'You must select a Google Analytics account', 'gravityformsgoogleanalytics' ) ),
					'gtm_required'        => wp_strip_all_tags( __( 'You must select a Tag Manager Account', 'gravityformsgoogleanalytics' ) ),
					'spinner'             => GFCommon::get_base_url() . '/images/spinner.svg',
					'mode_required'       => wp_strip_all_tags( __( 'Please select a connection type to continue.', 'gravityformsgoogleanalytics' ) ),
				),
				'in_footer' => true,
				'enqueue'   => array(
					array(
						'query' => 'page=gf_settings&subview=gravityformsgoogleanalytics',
					),
				),
			),
			array(
				'handle'    => self::FE_SCRIPT_HANDLE,
				'src'       => $this->get_base_url() . "/js/google-analytics{$min}.js",
				'version'   => $this->_version,
				'deps'      => array( 'jquery', 'wp-ajax-response' ),
				'in_footer' => true,
				'enqueue'   => array(
					array( $this, 'frontend_script_callback' ),
				),
				'callback' => array( $this, 'frontend_script_strings_callback' ),
			),
		);

		return array_merge( parent::scripts(), $scripts );
	}

	/**
	 * Get Gravity API URL.
	 *
	 * @since 1.0
	 *
	 * @param string $path Path.
	 *
	 * @return string
	 */
	public function get_gravity_api_url( $path = '' ) {
		return ( defined( 'GRAVITY_API_URL' ) ? GRAVITY_API_URL : 'https://gravityapi.com/wp-json/gravityapi/v1' ) . $path;
	}

	/**
	 * Retrieve the plugin's options.
	 *
	 * Retrieve the plugin's options based on context.
	 *
	 * @since 1.0.0
	 *
	 * @param string $context      Context to retrieve options for. This is used as an array key.
	 * @param string $key          Array key to retrieve.
	 * @param bool   $force_reload Whether to retrieve cached options or forcefully retrieve from the database.
	 *
	 * @return mixed All options if no context, or associative array if context is set. Empty array if no options. String if $key is set.
	 */
	public static function get_options( $context = '', $key = false, $force_reload = false ) {
		// Try to get cached options.
		$options = self::$options;
		if ( false === $options || true === $force_reload ) {
			$options = get_option( 'gravityformsaddon_gravityformsgoogleanalytics_settings', array() );
		}

		// Store options.
		if ( ! is_array( $options ) ) {
			$options = array();
		}

		// Assign options for caching.
		self::$options = $options;

		if ( rgblank( $context ) && $key ) {
			if ( isset( $options[ $key ] ) ) {
				return $options[ $key ];
			} else {
				return '';
			}
		}

		// Attempt to get context.
		if ( ! empty( $context ) && is_string( $context ) ) {
			if ( array_key_exists( $context, $options ) ) {
				if ( false !== $key && is_string( $key ) ) {
					if ( isset( $options[ $context ][ $key ] ) ) {
						return $options[ $context ][ $key ];
					}
				} else {
					return (array) $options[ $context ];
				}
			} else {
				return array();
			}
		}

		return $options;
	}

	/**
	 * Save plugin options.
	 *
	 * Saves the plugin options based on context.  If no context is provided, updates all options.
	 *
	 * @since 1.0.0
	 *
	 * @param array  $options Associative array of plugin options.
	 * @param string $context Array key of which options to update.
	 */
	public static function update_options( $options = array(), $context = '' ) {
		$options_to_save = self::get_options();

		if ( ! empty( $context ) && is_string( $context ) ) {
			$options_to_save[ $context ] = $options;
		} else {
			$options_to_save = $options;
		}
		update_option( 'gravityformsaddon_gravityformsgoogleanalytics_settings', $options_to_save );
		self::$options = $options_to_save;
	}

	/**
	 * Save feed settings.
	 *
	 * @since 1.0.0
	 *
	 * @param int $feed_id The Feed ID.
	 * @param int $form_id The Form ID.
	 *
	 * @return int
	 */
	public function maybe_save_feed_settings( $feed_id, $form_id ) {

		if ( ! rgpost( 'gform-settings-save' ) ) {
			return $feed_id;
		}

		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.", 'gravityformsgoogleanalytics' ) );

			return $feed_id;
		}

		// Store a copy of the previous settings for cases where action would only happen if value has changed.
		$feed = $this->get_feed( $feed_id );
		$this->set_previous_settings( $feed['meta'] );

		$settings = $this->get_posted_settings();
		$sections = $this->get_feed_settings_fields();
		$settings = $this->trim_conditional_logic_vales( $settings, $form_id );

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

		if ( $is_valid ) {
			$settings = $this->filter_settings( $sections, $settings );
			$feed_id  = $this->save_feed_settings( $feed_id, $form_id, $settings );

			if ( $feed_id ) {
				GFCommon::add_message( $this->get_save_success_message( $sections ) );
			} else {
				GFCommon::add_error_message( $this->get_save_error_message( $sections ) );
			}
		} else {
			GFCommon::add_error_message( $this->get_save_error_message( $sections ) );
		}
		$redirect_url = add_query_arg(
			array(
				'page'    => 'gf_edit_forms',
				'view'    => 'settings',
				'subview' => 'gravityformsgoogleanalytics',
				'id'      => $form_id,
				'fid'     => $feed_id,
			),
			admin_url( 'admin.php' )
		);
		if ( 0 === absint( rgget( 'fid' ) ) ) {
			?>
			<script>
				setTimeout( function() {
					window.location.href = '<?php echo esc_url_raw( $redirect_url ); ?>';
				}, 1 );
			</script>
			<?php
		}
	}

	/**
	 * Installs GTAG Google Analytics if user has selected that option in settings.
	 *
	 * @since  1.0.0
	 */
	public function maybe_install_analytics() {
		$settings = self::get_options();
		if ( ! isset( $settings['ga'] ) ) {
			return;
		}
		if ( 'off' === $settings['ga'] ) {
			return;
		}

		$this->log_debug( __METHOD__ . '(): Loading Google Analytics GTAG settings: ' . print_r( $settings, true ) );

		// Attempt to get options.
		$measurement_id = sanitize_text_field( self::get_options( 'ga4_account', 'measurement_id' ) );

		// User has requested GA installation. Proceed.
		echo "\r\n";
		?>

		<!-- Google tag (gtag.js) -->
		<script async
		        src="https://www.googletagmanager.com/gtag/js?id=<?php echo esc_html( $measurement_id ); ?>"></script> <?php //phpcs:ignore ?>
		<script>
			window.dataLayer = window.dataLayer || [];

			function gtag() {
				dataLayer.push( arguments );
			}

			gtag( 'js', new Date() );

			gtag( 'config', '<?php echo esc_html( $measurement_id ); ?>' );

			<?php
			/**
			 * Action: gform_googleanalytics_install_analytics
			 *
			 * Allow custom scripting for Google Analytics GTAG
			 *
			 * @since 1.0.0
			 *
			 * @param string $gmeasurement_id Google Analytics Property ID
			 */
			do_action( 'gform_googleanalytics_install_analytics', $measurement_id );
			?>
		</script>

		<?php
	}

	/**
	 * Installs Google Tag Manager if user has selected that option in settings.
	 *
	 * @since  1.0.0
	 */
	public function maybe_install_tag_manager_header() {
		$settings = self::get_options();
		if ( ! isset( $settings['install_gtm'] ) ) {
			return;
		}
		if ( 'off' === $settings['install_gtm'] ) {
			return;
		}

		$this->log_debug( __METHOD__ . '(): Loading Google Tag Manager Installation Setting: ' . print_r( $settings, true ) );

		// Attempt to get options.
		$gtm_code = sanitize_text_field( self::get_options( 'ga4_account', 'gtm_container_id' ) );
		if ( empty( $gtm_code ) ) {
			return;
		}

		// User has requested Tag Manager installation. Proceed.
		echo "\r\n";
		?>
		<!-- Google Tag Manager -->
		<script>( function( w, d, s, l, i ) {
				w[ l ] = w[ l ] || [];
				w[ l ].push( {
					'gtm.start':
						new Date().getTime(), event: 'gtm.js'
				} );
				var f = d.getElementsByTagName( s )[ 0 ],
					j = d.createElement( s ), dl = l != 'dataLayer' ? '&l=' + l : '';
				j.async = true;
				j.src =
					'https://www.googletagmanager.com/gtm.js?id=' + i + dl;
				f.parentNode.insertBefore( j, f );
			} )( window, document, 'script', 'dataLayer', '<?php echo esc_html( $gtm_code ); ?>' );
			<?php
			/**
			 * Action: gform_googleanalytics_install_tag_manager
			 *
			 * Allow custom scripting for Google Tag Manager
			 *
			 * @since 1.0.0
			 *
			 * @param string $gtm_code Google Tag Manager ID
			 */
			do_action( 'gform_googleanalytics_install_tag_manager', $gtm_code );
			?>
		</script>
		<!-- End Google Tag Manager -->
		<?php
	}

	/**
	 * Installs Google Tag Manager if user has selected that option in settings.
	 *
	 * @since  1.0.0
	 */
	public function maybe_install_tag_manager_body() {
		$settings = self::get_options();
		if ( ! isset( $settings['install_gtm'] ) ) {
			return;
		}
		if ( 'off' === $settings['install_gtm'] ) {
			return;
		}

		$gtm_code = sanitize_text_field( self::get_options( 'account', 'container_id' ) );
		if ( empty( $gtm_code ) ) {
			return;
		}

		// User has requested Tag Manager installation. Proceed.
		?>
		<!-- Google Tag Manager (noscript) -->
		<noscript>
			<iframe src="https://www.googletagmanager.com/ns.html?id=<?php echo esc_html( $gtm_code ); ?>" height="0"
			        width="0" style="display:none;visibility:hidden"></iframe>
		</noscript>
		<!-- End Google Tag Manager (noscript) -->
		<?php
		/**
		 * Action: gform_googleanalytics_install_tag_manager
		 *
		 * Allow custom scripting for Google Tag Manager
		 *
		 * @since 1.0.0
		 *
		 * @param string $gtm_code Google Tag Manager ID
		 */
		do_action( 'gform_googleanalytics_install_tag_manager', $gtm_code );
	}

	/**
	 * Redirects to authentication screen for Google Analytics.
	 *
	 * @since  1.0.0
	 */
	public function authenticate_google_analytics() {
		$ga_options = get_option( 'gforms_google_analytics_ga' );
		$token      = isset( $ga_options['token'] ) ? $ga_options['token'] : false;
		if ( $token && ( isset( $ga_options['mode'] ) && 'ga' === $ga_options['mode'] ) ) {
			return;
		}
		$settings_mode = rgpost( '_gaddon_setting_mode' ) ? rgpost( '_gaddon_setting_mode' ) : 'gmp';
		$state         = array(
			'url'     => admin_url( 'admin.php' ),
			'page'    => 'gf_settings',
			'subview' => 'gravityformsgoogleanalytics',
			'mode'    => $settings_mode,
			'nonce'   => wp_create_nonce( 'gravityformsgoogleanalytics_ua' ),
		);
		$auth_url      = add_query_arg(
			array(
				'mode'        => $settings_mode,
				'redirect_to' => admin_url( 'admin.php' ),
				'state'       => base64_encode(
					json_encode(
						$state
					)
				),
				'license'     => GFCommon::get_key(),
			),
			$this->get_gravity_api_url( '/auth/googleanalytics' )
		);
		wp_safe_redirect( esc_url_raw( $auth_url ) );
		exit();
	}

	/**
	 * Redirects to authentication screen for Google Tag Manager.
	 *
	 * @since  1.0.0
	 */
	public function authenticate_google_tag_manager() {
		$ga_options = get_option( 'gforms_google_analytics_ga' );
		$token      = isset( $ga_options['token'] ) ? $ga_options['token'] : false;
		if ( $token && ( isset( $ga_options['mode'] ) && 'gtm' === $ga_options['mode'] ) ) {
			return;
		}
		$this->log_debug( __METHOD__ . '(): Before Authenticating With Tag Manager: ' . print_r( $ga_options, true ) );
		$state       = array(
			'url'     => admin_url( 'admin.php' ),
			'page'    => 'gf_settings',
			'subview' => 'gravityformsgoogleanalytics',
			'mode'    => 'gtm',
			'nonce'   => wp_create_nonce( 'gravityformsgoogleanalytics_ua' ),
		);
		$redirect_to = admin_url( 'admin.php' );
		$auth_url    = add_query_arg(
			array(
				'mode'        => 'gtm',
				'state'       => base64_encode(
					json_encode(
						$state
					)
				),
				'redirect_to' => $redirect_to,
				'license'     => GFCommon::get_key(),
			),
			$this->get_gravity_api_url( '/auth/googleanalytics' )
		);
		wp_safe_redirect( esc_url_raw( $auth_url ) );
		exit;
	}

	/**
	 * Returns an array with Google Analytics codes to be associated with the event. This only applies to Measurement Protocol.
	 *
	 * @since 1.0.0
	 *
	 * @param array $form  Current form object.
	 * @param array $entry Current entry object.
	 * @param array $feed  Current feed object.
	 *
	 * @return array Returns an array of Google Analytics codes.
	 */
	private function get_ga_codes( $form, $entry = null, $feed = null ) {

		$ua_ids = array( $this->get_measurement_id() );

		/**
		 * Filter: gform_googleanalytics_ua_ids
		 *
		 * Filter all outgoing UA IDs to send events to using the measurement protocol.
		 *
		 * @since 1.0.0
		 *
		 * @param array $ua_ids UA codes.
		 * @param array $form   Gravity Form form object.
		 * @param array $entry  Gravity Form Entry Object.
		 * @param array $feed   Current feed.
		 *
		 * @return array Google anaylics codes.
		 */
		return apply_filters( 'gform_googleanalytics_ua_ids', $ua_ids, $form, $entry, $feed );
	}

	/**
	 * Initialize the pagination events.
	 *
	 * @since 1.0.0
	 *
	 * @param array $form                The form arguments.
	 * @param int   $source_page_number  The original page number.
	 * @param int   $current_page_number The new page number.
	 */
	public function pagination( $form, $source_page_number, $current_page_number ) {

		// Pagination tracking not enabled. Abort.
		if ( ! rgars( $form, 'gravityformsgoogleanalytics/google_analytics_pagination' ) ) {
			return;
		}

		$mode       = self::get_options( '', 'mode' );
		$parameters = $this->get_mapped_parameters( $form['gravityformsgoogleanalytics'], 'pagination_parameters', $form, \GFFormsModel::get_current_lead( $form ) );
		$parameters = $this->maybe_replace_pagination_merge_tags( $parameters, $source_page_number, $current_page_number );
		$is_ajax    = isset( $_REQUEST['gform_ajax'] ) ? true : false;
		$event_name = $this->get_pagination_event_name( $mode, $form );


		switch ( $mode ) {
			case 'gmp':
				// Send events via server side (measurement protocol).
				$ua_codes = $this->get_ga_codes( $form );
				$this->send_measurement_protocol( $ua_codes, $parameters, $event_name, \GFFormsModel::get_current_page_url() );

				break;

			case 'ga':
				$this->log_debug( __METHOD__ . '(): Attempting to send pagination event via Google Analytics. Event Name: ' . $event_name . '. Parameters: ' . print_r( $parameters, true ) );
				$this->render_script_send_to_ga( $parameters, $event_name, $is_ajax );
				break;

			case 'gtm':
				$trigger_name = rgar( $form['gravityformsgoogleanalytics'], 'pagination_trigger' ) == 'gf_custom' ? rgar( $form['gravityformsgoogleanalytics'], 'pagination_trigger_custom' ) : rgar( $form['gravityformsgoogleanalytics'], 'pagination_trigger' );
				$this->log_debug( __METHOD__ . '(): Attempting to send pagination event via Google Tag Manager. Trigger Name: ' . $trigger_name . '. Parameters: ' . print_r( $parameters, true ) );
				$this->render_script_send_to_gtm( $parameters, $trigger_name, $is_ajax );
				break;
		}
	}

	/**
	 * Maybe replace the pagination merge tags.
	 *
	 * @since 2.2.0
	 *
	 * @param array $parameters          The pagination parameters.
	 * @param int   $source_page_number  The source page number.
	 * @param int   $current_page_number The target page number.
	 *
	 * @return array
	 */
	public function maybe_replace_pagination_merge_tags( $parameters, $source_page_number, $current_page_number ) {

		foreach ( $parameters as $key => $value ) {
			$parameters[ $key ] = str_replace( array( '{source_page_number}', '{current_page_number}' ), array( $source_page_number, $current_page_number ), $value );
		}

		return $parameters;
	}

	/**
	 * Loads the parameter list with the actual values from the form submission and query string.
	 *
	 * @since 2.0.0
	 *
	 * @param array  $settings               Settings array containing the mapped field configuration.
	 * @param string $parameter_setting_name The name of the parameter setting field.
	 * @param array  $form                   The current form object.
	 * @param array  $entry                  The current entry object.
	 *
	 * @return array An array with the mapped parameters
	 */
	private function get_mapped_parameters( $settings, $parameter_setting_name, $form, $entry ) {

		// Adding UTM values to entry.
		$entry['utm_source']   = rgget( 'utm_source' );
		$entry['utm_medium']   = rgget( 'utm_medium' );
		$entry['utm_campaign'] = rgget( 'utm_campaign' );
		$entry['utm_term']     = rgget( 'utm_term' );
		$entry['utm_content']  = rgget( 'utm_content' );

		// Loading parameters.
		return $this->get_generic_map_fields( $settings, $parameter_setting_name, $form, $entry );
	}

	/**
	 * Redirect to Gravity Forms API.
	 *
	 * @since 1.0.0
	 */
	public function ajax_redirect_to_api() {
		if ( ! wp_verify_nonce( rgpost( 'nonce' ), 'connect_google_analytics' ) || ! $this->current_user_can_any( $this->_capabilities_form_settings ) ) {
			wp_send_json_error(
				array(
					'errors'   => true,
					'redirect' => '',
				)
			);
		}

		$state = wp_create_nonce( 'gravityforms_googleanalytics_google_connect' );

		if ( get_transient( 'gravityapi_request_' . $this->get_slug() ) ) {
			delete_transient( 'gravityapi_request_' . $this->get_slug() );
		}

		set_transient( 'gravityapi_request_' . $this->get_slug(), $state, 10 * MINUTE_IN_SECONDS );
		$mode    = sanitize_text_field( rgpost( 'mode' ) );
		$options = self::get_options();

		if ( $mode == 'manual' ) {
			$redirect_url         = admin_url( 'admin.php?page=gf_settings&subview=' . $this->_slug );
			$options['is_manual'] = true;

		} else {
			$action = $mode == 'gtm' ? 'gtmselect' : 'gaselect';

			$settings_url = urlencode( admin_url( 'admin.php?page=gf_settings&subview=' . $this->_slug ) . '&action=' . $action );

			$redirect_url = add_query_arg(
				array(
					'mode'        => sanitize_text_field( rgpost( 'mode' ) ),
					'redirect_to' => $settings_url,
					'state'       => $state,
					'license'     => GFCommon::get_key(),
				),
				$this->get_gravity_api_url( '/auth/googleanalytics' )
			);

			// Temporary mode for storing what the user has selected for authorization.
			$options['tempmode'] = sanitize_text_field( rgpost( 'mode' ) );

			$this->log_debug( "Redirecting to Gravity API: {$redirect_url}" );
		}

		// Updating options.
		self::update_options( $options );

		wp_send_json_success(
			array(
				'errors'   => false,
				'redirect' => esc_url_raw( $redirect_url ),
			)
		);
	}

	/**
	 * Disconnects user from the Google Analytics API.
	 *
	 * @since 1.0.0
	 */
	public function ajax_disconnect_account() {

		// Verify nonce and capability.
		$this->verify_ajax_nonce( 'gforms_google_analytics_disconnect' );
		$this->verify_capability();

		// Deleting option. Token expires in an hour, so no need to revoke it.
		delete_option( 'gravityformsaddon_gravityformsgoogleanalytics_settings' );
		wp_send_json_success( array() );
	}

	/**
	 * Gets views for the selected account and GA code
	 *
	 * @since 2.0.0
	 */
	public function ajax_get_ga4_data_streams() {

		// Verify nonce, capability and API connection.
		$this->verify_ajax_nonce();
		$this->verify_capability();
		$this->verify_api_connection();

		// Retrieving data streams.
		$property     = sanitize_text_field( rgpost( 'property' ) );
		$data_streams = $this->api->get_data_streams( $property );
		if ( is_wp_error( $data_streams ) ) {
			$this->log_debug( __METHOD__ . '(): Error retrieving Google Analytics data streams associated with the selected property.' );
			wp_send_json_error( new WP_Error( 'google_analytics_ga_error', wp_strip_all_tags( __( 'There was an error retrieving Google Analytics data streams.', 'gravityformsgoogleanalytics' ) ) ) );
		}

		// Create select field markup.
		$html = '<br /><select name="ga_data_stream">';
		$html .= '<option value="">' . esc_html__( 'Select a data stream', 'gravityformsgoogleanalytics' ) . '</option>';
		foreach ( $data_streams['dataStreams'] as $data_stream ) {

			// Ignore data streams that are not "webStreamData".
			if ( ! isset( $data_stream['webStreamData'] ) ) {
				continue;
			}
			$html .= sprintf(
				'<option value="%s" data-data-stream-id="%s" data-data-stream-name="%s">%s</option>',
				esc_attr( $data_stream['webStreamData']['measurementId'] ),
				esc_attr( $data_stream['name'] ),
				esc_attr( $data_stream['displayName'] ),
				esc_html( $data_stream['displayName'] )
			);
		}
		$html .= '</select>';

		wp_send_json_success( $html );
	}

	/**
	 * Gets containers for the selected GTM account
	 *
	 * @since 1.1
	 */
	public function ajax_get_gtm_containers() {

		// Verify nonce, capability and API connection.
		$this->verify_ajax_nonce();
		$this->verify_capability();
		$this->verify_api_connection();

		$account_id = rgpost( 'accountId' );
		$token      = rgpost( 'token' );

		// Get containers.
		$container_response = $this->api->get_tag_manager_containers( array(), $account_id );
		if ( is_wp_error( $container_response ) ) {
			$this->log_debug( __METHOD__ . '(): Error retrieving property containers.' );
			$error = new WP_Error(
				'google_analytics_gtm_error',
				$container_response->get_error_message()
			);
			wp_send_json_error( $error );
		}

		// Output HTML.
		$success = array( 'success' => true );
		$html    = '';
		$html    .= '<br /><select name="gacontainer" id="gacontainer">';
		$html    .= '<option value="">' . esc_html__( 'Select a Container', 'gravityformsgoogleanalytics' ) . '</option>';
		foreach ( $container_response['container'] as $container ) {
			$html .= sprintf(
				'<option data-account-id="%s" data-path="%s" data-token="%s" value="%s">%s</option>',
				esc_attr( $account_id ),
				esc_attr( $container['path'] ),
				esc_attr( $token ),
				esc_attr( $container['publicId'] ),
				esc_attr( $container['publicId'] )
			);
		}
		$html .= '</select>';

		$success['body'] = $html;
		wp_send_json( $success );

	}

	/**
	 * Gets workspaces for the selected GTM account
	 *
	 * @since 1.0.0
	 */
	public function ajax_get_gtm_workspaces() {

		// Verify nonce, capability and API connection.
		$this->verify_ajax_nonce();
		$this->verify_capability();
		$this->verify_api_connection();

		// Get workspace ID.
		$response = $this->api->get_tag_manager_workspaces( array(), rgpost( 'path' ) );
		if ( is_wp_error( $response ) ) {
			$error = new WP_Error(
				'google_analytics_gtm_error',
				wp_strip_all_tags( __( 'Could not retrieve Tag Manager workspaces', 'gravityformsgoogleanalytics' ) )
			);
			wp_send_json_error( $error );
		}

		// Output HTML.
		$success = array( 'success' => true );
		$html    = '';
		$html    .= '<select name="gaworkspace">';
		$html    .= '<option value="">' . esc_html__( 'Select a Workspace', 'gravityformsgoogleanalytics' ) . '</option>';
		foreach ( $response as $index => $workspaces ) {
			foreach ( $workspaces as $workspace ) {
				$html .= sprintf(
					'<option value="%s">%s</option>',
					esc_attr( $workspace['workspaceId'] ),
					esc_html( $workspace['name'] )
				);
			}
		}
		$html .= '</select>';

		$success['body'] = $html;
		wp_send_json( $success );
	}

	/**
	 * Updates plugin settings with the provided settings. Overrides parent to correctly update needed settings.
	 *
	 * @since 1.0
	 *
	 * @param array $settings Plugin settings to be saved.
	 */
	public function update_plugin_settings( $settings ) {
		$current_settings = self::get_options();

		// Set the API authentication version.
		$current_settings['reauth_version'] = self::LAST_REAUTHENTICATION_VERSION;
		if ( is_array( $current_settings ) ) {
			$settings = array_merge( $current_settings, $settings );
		}
		update_option( 'gravityformsaddon_' . $this->_slug . '_settings', $settings );
	}

	/**
	 * Saves analytics data to settings and provides redirect callback.
	 *
	 * @since  1.0.0
	 */
	public function ajax_save_google_analytics_data() {

		// Verify nonce, capability and API connection.
		$this->verify_ajax_nonce();
		$this->verify_capability();
		$this->verify_api_connection();

		$options = $this->get_google_analytics_options( self::get_options() );

		if ( rgar( $options, 'mode' ) === 'gmp' ) {
			$api_secret = $this->get_api_secret();
			if ( is_wp_error( $api_secret ) ) {
				wp_send_json_error( $api_secret );
			}
			$options['ga4_account']['gmp_api_secret'] = $api_secret['secretValue'];
		}

		$this->log_debug( __METHOD__ . '(): Saving Google Analytics settings.' );
		$this->update_plugin_settings( $options );

		// Build redirect url and return it.
		$redirect_url = add_query_arg(
			array(
				'page'    => 'gf_settings',
				'subview' => 'gravityformsgoogleanalytics',
			),
			admin_url( 'admin.php' )
		);
		wp_send_json_success( esc_url_raw( $redirect_url ) );
	}

	/**
	 * Gets the API secret for Measurement Protocol.
	 *
	 * @since 2.0.0
	 *
	 * @return string Returns the API secret.
	 */
	private function get_api_secret() {
		$secrets = $this->api->get_api_secrets( rgpost( 'data_stream_id' ) );
		if ( rgar( $secrets, 'measurementProtocolSecrets' ) ) {
			foreach ( $secrets['measurementProtocolSecrets'] as $secret ) {
				if ( $secret['displayName'] === 'GravityFormsSecret' ) {
					return $secret;
				}
			}
		}

		return $this->api->create_api_secret( rgpost( 'data_stream_id' ) );
	}

	/**
	 * Creates an array with the Google Analytics settings to be saved.
	 *
	 * @since 2.0.0
	 *
	 * @param array $options Current options.
	 *
	 * @return array Returns an array with the settings set.
	 */
	public function get_google_analytics_options( $options ) {

		$options['auth_token']  = array(
			'token'        => sanitize_text_field( rgpost( 'token' ) ),
			'refresh'      => sanitize_text_field( rgpost( 'refresh' ) ),
			'date_created' => time(),
		);
		$options['connected']   = true;
		$options['mode']        = $options['tempmode'];
		$options['gfgamode']    = $options['tempmode'];
		$options['ga4_account'] = array(
			'account_id'       => sanitize_text_field( rgpost( 'account_id' ) ),
			'account_name'     => sanitize_text_field( rgpost( 'account_name' ) ),
			'property_id'      => sanitize_text_field( rgpost( 'property_id' ) ),
			'property_name'    => sanitize_text_field( rgpost( 'property_name' ) ),
			'data_stream_name' => sanitize_text_field( rgpost( 'data_stream_name' ) ),
			'measurement_id'   => sanitize_text_field( rgpost( 'measurement_id' ) ),
		);

		return $options;
	}

	/**
	 * Saves analytics data to settings and provides redirect callback.
	 *
	 * @since  1.0.0
	 */
	public function ajax_save_google_tag_manager_data() {

		// Verify nonce, capability and API connection.
		$this->verify_ajax_nonce();
		$this->verify_capability();
		$this->verify_api_connection();

		$options = $this->get_tag_manager_options( self::get_options() );

		// Save Google Tag Manager data.
		$this->log_debug( __METHOD__ . '(): Saving Google Tag Manager:' . print_r( $options, true ) );
		$this->update_plugin_settings( $options );

		$redirect_url = add_query_arg(
			array(
				'page'    => 'gf_settings',
				'subview' => 'gravityformsgoogleanalytics',
			),
			admin_url( 'admin.php' )
		);
		wp_send_json_success( esc_url_raw( $redirect_url ) );
	}

	/**
	 * Creates an array with Tag Manager settings to be saved.
	 *
	 * @since 2.0.0
	 *
	 * @param array $options Current options.
	 *
	 * @return array Returns an array with the settings set.
	 */
	public function get_tag_manager_options( $options ) {

		$options['auth_token']  = array(
			'token'        => sanitize_text_field( rgpost( 'token' ) ),
			'refresh'      => sanitize_text_field( rgpost( 'refresh' ) ),
			'date_created' => time(),
		);
		$options['mode']        = 'gtm';
		$options['gfgamode']    = $options['mode'];
		$options['connected']   = true;
		$options['ga4_account'] = array(
			'account_id'         => sanitize_text_field( rgpost( 'account_id' ) ),
			'account_name'       => sanitize_text_field( rgpost( 'account_name' ) ),
			'property_id'        => sanitize_text_field( rgpost( 'property_id' ) ),
			'property_name'      => sanitize_text_field( rgpost( 'property_name' ) ),
			'data_stream_name'   => sanitize_text_field( rgpost( 'data_stream_name' ) ),
			'measurement_id'     => sanitize_text_field( rgpost( 'measurement_id' ) ),
			'gtm_account_id'     => sanitize_text_field( rgpost( 'gtm_account_id' ) ),
			'gtm_account_name'   => sanitize_text_field( rgpost( 'gtm_account_name' ) ),
			'gtm_container_id'   => sanitize_text_field( rgpost( 'gtm_container' ) ),
			'gtm_workspace_id'   => sanitize_text_field( rgpost( 'gtm_workspace_id' ) ),
			'gtm_workspace_name' => sanitize_text_field( rgpost( 'gtm_workspace_name' ) ),
			'gtm_api_path'       => sanitize_text_field( rgpost( 'gtm_api_path' ) ),
		);

		return $options;
	}

	/**
	 * Saves manual configuration data to settings and provides redirect callback.
	 *
	 * @since  2.0.0
	 */
	public function ajax_save_manual_configuration_data() {

		// Verify nonce and capability.
		$this->verify_ajax_nonce();
		$this->verify_capability();

		$options = $this->get_manual_configuration_options( self::get_options() );

		$this->log_debug( __METHOD__ . '(): Saving manual configuration settings:' . print_r( $options, true ) );
		$this->update_plugin_settings( $options );

		$redirect_url = add_query_arg(
			array(
				'page'    => 'gf_settings',
				'subview' => 'gravityformsgoogleanalytics',
				'updated' => 'true',
			),
			admin_url( 'admin.php' )
		);
		wp_send_json_success( esc_url_raw( $redirect_url ) );
	}

	/**
	 * Creates an array with the manual configuration settings to be saved.
	 *
	 * @since 2.0.0
	 *
	 * @param array $options Current options.
	 *
	 * @return array Returns an array with the settings set.
	 */
	public function get_manual_configuration_options( $options ) {

		$options['mode']        = sanitize_text_field( rgpost( 'mode' ) );
		$options['connected']   = false;
		$options['ga4_account'] = array(
			'measurement_id'   => sanitize_text_field( rgpost( 'measurement_id' ) ),
			'gmp_api_secret'   => sanitize_text_field( rgpost( 'gmp_api_secret' ) ),
			'gtm_container_id' => sanitize_text_field( rgpost( 'gtm_container' ) ),
			'gtm_workspace_id' => sanitize_text_field( rgpost( 'gtm_workspace_id' ) ),
		);

		if ( rgpost( 'ga' ) !== '' ) {
			$options['ga'] = sanitize_text_field( rgpost( 'ga' ) );
		}
		if ( rgpost( 'install_gtm' ) !== '' ) {
			$options['install_gtm'] = sanitize_text_field( rgpost( 'install_gtm' ) );
		}
		if( rgpost( 'ua_tracker' ) !== '' ) {
			$options['ua_tracker'] = sanitize_text_field( rgpost( 'ua_tracker' ) );
		}

		return $options;
	}

	private function validate_ajax_nonce() {
		if ( ! wp_verify_nonce( rgar( $_REQUEST, 'nonce' ), 'gforms_google_analytics_confirmation' ) ) {
			$this->log_debug( __METHOD__ . '(): Nonce verification was not successful.' );
			wp_send_json_error( new WP_Error( 'google_analytics_nonce_error', esc_html__( 'Nonce validation failed.', 'gravityformsgoogleanalytics' ) ) );
		}
	}

	/**
	 * When a page is redirected, check if event is already sent via Ajax.
	 *
	 * @since  1.0.0
	 */
	public function ajax_get_entry_meta() {
		// Validate nonce.
		$this->validate_ajax_nonce();

		// Getting paramters.
		$entry_id = absint( rgpost( 'entry_id' ) );
		$feed_id  = absint( rgpost( 'feed_id' ) );

		// Checking if event for entry id and feed id has been sent.
		$feeds_sent = gform_get_meta( $entry_id, 'googleanalytics_feeds_sent' );
		$has_sent   = is_array( $feeds_sent ) && in_array( $feed_id, $feeds_sent );

		wp_send_json_success( array( 'event_sent' => $has_sent ) );
	}

	/**
	 * When a page is redirected, save entry meta to avoid duplicate events.
	 *
	 * @since  1.0.0
	 */
	public function ajax_save_entry_meta() {
		// Validate nonce.
		$this->validate_ajax_nonce();


		// Getting paramters.
		$entry_id = absint( rgpost( 'entry_id' ) );
		$feed_id  = absint( rgpost( 'feed_id' ) );

		// Adding current feed to meta and saving.
		$feeds_sent = gform_get_meta( $entry_id, 'googleanalytics_feeds_sent' );
		if ( ! is_array( $feeds_sent ) ) {
			$feeds_sent = array();
		}
		$feeds_sent[] = $feed_id;
		gform_update_meta( $entry_id, 'googleanalytics_feeds_sent', $feeds_sent );

		wp_send_json_success( array( 'meta_saved' => true ) );
	}

	/**
	 * Verifies nonce and sends a JSON error if unsuccessful.
	 *
	 * @since 2.0.0
	 *
	 * @param string $nonce_action The nonce action to be verified.
	 *
	 * @return void
	 */
	private function verify_ajax_nonce( $nonce_action = 'connect_google_analytics' ) {
		if ( ! wp_verify_nonce( rgpost( 'nonce' ), $nonce_action ) ) {
			$this->log_debug( __METHOD__ . '(): Nonce validation failed.' );
			wp_send_json_error( new WP_Error( 'google_analytics_ga_error', wp_strip_all_tags( __( 'Nonce validation has failed.', 'gravityformsgoogleanalytics' ) ) ) );
		}
	}

	/**
	 * Verifies Gravity Forms capabilities and sends a JSON error if user doesn't have required permission.
	 *
	 * @since 2.0.0
	 *
	 * @return void
	 */
	private function verify_capability() {
		if ( ! $this->current_user_can_any( $this->_capabilities_form_settings ) ) {
			$this->log_debug( __METHOD__ . '(): Permissions for form settings not met.' );
			wp_send_json_error( new WP_Error( 'google_analytics_ga_error', wp_strip_all_tags( __( 'User does not have required permissions to setup Google Analytics.', 'gravityformsgoogleanalytics' ) ) ) );
		}
	}

	/**
	 * Attempts to initialize Google Analytics API and sends a JSON error if unsuccessful.
	 *
	 * @since 2.0.0
	 *
	 * @return void
	 */
	private function verify_api_connection() {
		if ( ! $this->initialize_api() ) {
			$this->log_debug( __METHOD__ . '(): Unable to initialize Google Anaylics API.' );
			wp_send_json_error( new WP_Error( 'google_analytics_ga_error', wp_strip_all_tags( __( 'Unable to initialize Google Anaylics API.', 'gravityformsgoogleanalytics' ) ) ) );
		}
	}

	/**
	 * Append event data to the URL.
	 *
	 * @since  2.0.0
	 *
	 * @param string|array $confirmation Confirmation for the form, can be a page redirect.
	 * @param object       $form         Form object.
	 * @param object       $entry        Current Entry.
	 * @param bool         $ajax         Whether the form was subitted via ajax.
	 *
	 * @return string|array $confirmation
	 */
	public function force_text_confirmation( $confirmation, $form, $entry, $ajax ) {

		// Ignore blank or spam entries
		if ( empty( $entry ) || rgar( $entry, 'status' ) === 'spam' ) {
			return $confirmation;
		}

		// Ignore confirmations that are not of type redirect and submissions not associated with a GA feed.
		if ( ! isset( $confirmation['redirect'] ) || ! $this->has_feed( $form['id'] ) ) {
			return $confirmation;
		}

		// Ignore submissions associated with Measurement Protocol. Those will be handled server side and will go throught the standard flow.
		if ( self::get_options( '', 'mode' ) === 'gmp' ) {
			return $confirmation;
		}

		// Update confirmation to handle redirect.
		$processing_message = esc_html__( 'Processing... Please wait.', 'gravityformsgoogleanalytics' );

		return "<div id='gf_{$form['id']}' class='gform_anchor' tabindex='-1'></div><div id='gform_confirmation_wrapper_{$form['id']}' class='gform_confirmation_wrapper'><div id='gform_confirmation_message_{$form['id']}' class='gform_confirmation_message_{$form['id']} gform_confirmation_message'>{$processing_message}</div></div>" .
		       '<script>
					if ( window["all_ga_events_sent"] ) {
						document.location.href="' . sanitize_url( $confirmation['redirect'] ) . '";
					}
					window.parent.addEventListener("googleanalytics/all_events_sent", function(){ document.location.href="' . sanitize_url( $confirmation['redirect'] ) . '" }); 
				</script>';
	}

	/**
	 * Remove unneeded settings.
	 *
	 * @since  1.0.0
	 */
	public function uninstall() {
		parent::uninstall();

		// Delete backed up feeds as well.
		global $wpdb;
		$sql = $wpdb->prepare( "DELETE FROM {$wpdb->prefix}gf_addon_feed WHERE addon_slug=%s", 'gravityformsgoogleanalytics_ua_ga4_upgrade' );
		$wpdb->query( $sql );

		delete_option( 'gravityformsaddon_gravityformsgoogleanalytics_settings' );
		delete_option( 'gforms_google_analytics_goal_labels' );
		GFCache::delete( 'google_analytics_plugin_settings' );
		GFCache::delete( 'all_ga4_feeds_upgraded' );
	}

	/**
	 * Configures the settings for a connected option.
	 *
	 * Allows for disconnecting services and shows current status.
	 *
	 * @since  1.0.0
	 */
	public function settings_connection_method() {
		$options = self::get_options();

		if ( ! isset( $options['mode'] ) ) {
			$options['mode'] = 'unset';
		}
		$disconnect_uri  = esc_url_raw(
			add_query_arg(
				array(
					'page'    => 'gf_settings',
					'subview' => 'gravityformsgoogleanalytics',
					'action'  => 'gfgadisconnect',
					'nonce'   => wp_create_nonce( 'gforms_google_analytics_disconnect' ),
				),
				admin_url( 'admin.php' )
			)
		);
		$disconnect_link = sprintf( '<a href="%s" class="gfga-disconnect">%s</a> ', esc_url_raw( $disconnect_uri ), esc_html__( 'Disconnect', 'gravityformsgoogleanalytics' ) );
		if ( rgar( $options, 'is_manual' ) ) {
			echo esc_html__( 'Manual Configuration', 'gravityformsgoogleanalytics' ) . ' | ' . wp_kses_post( $disconnect_link );
		} elseif ( 'ga' === $options['mode'] ) {
			echo esc_html__( 'Google Analytics', 'gravityformsgoogleanalytics' ) . ' | ' . wp_kses_post( $disconnect_link );
		} elseif ( 'gtm' === $options['mode'] ) {
			echo esc_html__( 'Google Tag Manager', 'gravityformsgoogleanalytics' ) . ' | ' . wp_kses_post( $disconnect_link );
		} elseif ( 'gmp' === $options['mode'] ) {
			echo esc_html__( 'Google Measurement Protocol', 'gravityformsgoogleanalytics' ) . ' | ' . wp_kses_post( $disconnect_link );
		} else {
			echo esc_html__( 'Not connected', 'gravityformsgoogleanalytics' );
		}
	}

	/**
	 * Outputs the configured analitics account and data stream.
	 *
	 * @since  2.0.0
	 */
	public function settings_analytics_account() {
		$options = self::get_options( 'ga4_account' );
		if ( ! empty( $options['account_name'] ) ) {
			echo esc_html( $options['account_name'] ) . ' / ' . esc_html( $options['property_name'] ) . ' / ' . esc_html( $options['data_stream_name'] );
		}
	}

	/**
	 * Outputs the configured tag manager account information.
	 *
	 * @since  2.0.0
	 */
	public function settings_tag_manager_account() {
		$options = self::get_options( 'ga4_account' );
		if ( ! empty( $options['gtm_account_name'] ) ) {
			echo esc_html( $options['gtm_account_name'] ) . ' / ' . esc_html( $options['gtm_container_id'] ) . ' / ' . esc_html( $options['gtm_workspace_name'] );
		}
	}

	/**
	 * Outputs the configured tag manager create assets setting.
	 *
	 * @since  2.0.0
	 */
	public function settings_tag_manager_create_assets() {
		$options = self::get_options( 'ga4_account' );
		echo rgar( $options, 'gtm_create_assets' ) ? 'Yes' : 'No';
	}

	/**
	 * Outputs the configured measurement id
	 *
	 * @since  2.0.0
	 */
	public function settings_measurement_id() {
		$options = self::get_options( 'ga4_account' );
		if ( isset( $options['measurement_id'] ) ) {
			echo esc_html( $options['measurement_id'] );
		} else {
			esc_html_e( 'Measurement ID not specified.', 'gravityformsgoogleanalytics' );
		}
	}

	/**
	 * Adds the inline script containing the gforms_google_analytics_frontend_strings variable.
	 *
	 * @since 2.1
	 *
	 * @return void
	 */
	public function frontend_script_strings_callback() {
		static $done;
		if ( $done ) {
			return;
		}

		$strings = array(
			'ajaxurl'         => admin_url( 'admin-ajax.php' ),
			'nonce'           => wp_create_nonce( 'gforms_google_analytics_confirmation' ),
			'logging_enabled' => $this->is_logging_enabled(),
			'logging_nonce'   => wp_create_nonce( 'log_google_analytics_event_sent' ),
			'ua_tracker'      => self::get_options( '', 'ua_tracker' ),
		);

		if ( function_exists( 'wp_add_inline_script' ) ) {
			wp_add_inline_script( self::FE_SCRIPT_HANDLE, sprintf( 'var %s_strings = %s;', self::FE_SCRIPT_HANDLE, json_encode( $strings ) ), 'before' );
		} else {
			wp_localize_script( self::FE_SCRIPT_HANDLE, self::FE_SCRIPT_HANDLE . '_strings', $strings );
		}

		$done = true;
	}

	/**
	 * Check if the form has an active Google Analytics feed and mode is valid.
	 *
	 * @since  1.0.0
	 *
	 * @param array $form The form currently being processed.
	 *
	 * @return bool If the script should be enqueued.
	 */
	public function frontend_script_callback( $form ) {

		if ( is_admin() ) {
			return false;
		}

		$settings = self::get_options();

		// Check for mode setting.
		if ( ! $this->is_frontend_scripts_mode_valid( $settings ) ) {
			return false;
		}

		// Load on a redirected page. Skip if measurement protocol is selected.
		$redirect_action = rgget( 'gfaction' );
		if ( $redirect_action ) {
			return true;
		}

		if ( ! $this->has_feed( $form['id'] ) && ! rgars( $form, 'gravityformsgoogleanalytics/google_analytics_pagination' ) ) {
			return false;
		}

		return true;
	}

	/**
	 * Determines if the mode is valid for frontend scripts
	 *
	 * @param array $settings The current plugin settings.
	 *
	 * @since  1.0.0
	 *
	 * @return bool false if there is no gfgamode or it is gmp, true otherwise.
	 */
	public function is_frontend_scripts_mode_valid( $settings ) {
		$mode = rgar( $settings, 'mode' );
		if ( empty( $mode ) || $mode === 'gmp' ) {
			return false;
		}

		return true;
	}

	/**
	 * Get the tokens from the auth payload, or settings if appropriate
	 *
	 * @since  1.0.0
	 *
	 * @param array $auth_payload the auth payload returned form Google.
	 * @param array $settings     an array of plugin settings.
	 *
	 * @return bool|array false if there is no auth payload or settings array, otherwise an array of auth tokens.
	 */
	public function maybe_get_tokens_from_auth_payload( $auth_payload, $settings ) {
		if ( empty( $auth_payload ) && ! rgar( $settings, 'auth_token' ) ) {
			return false;
		}

		$auth_tokens = array();

		if ( $auth_payload ) {
			$auth_tokens['token']   = rgar( $auth_payload, 'access_token' );
			$auth_tokens['refresh'] = rgar( $auth_payload, 'refresh_token' );
		} else {
			$auth_tokens['token']   = rgar( $settings['auth_token'], 'token' );
			$auth_tokens['refresh'] = rgar( $settings['auth_token'], 'refresh' );
		}

		return $auth_tokens;
	}

	/**
	 * Displays a Google Analytics Account box.
	 *
	 * @since  1.0.0
	 */
	public function settings_ga_select_account() {

		if ( ! $this->initialize_api() ) {
			$this->log_debug( __METHOD__ . '(): Unable to initialize Google Analytics API.' );

			return false;
		}

		if ( ! $this->current_user_can_any( $this->_capabilities_form_settings ) ) {
			$this->log_debug( __METHOD__ . '(): User does not have Form Settings capability.' );

			return false;
		}

		$auth_payload = $this->plugin_settings->get_auth_payload();
		$auth_tokens  = $this->maybe_get_tokens_from_auth_payload( $auth_payload, self::get_options() );

		if ( ! $auth_tokens ) {
			$this->log_debug( __METHOD__ . '(): Unable to retrieve API Token.' );

			return false;
		}

		$response = $this->api->get_ga4_accounts();

		if ( is_wp_error( $response ) ) {
			$this->log_debug( __METHOD__ . '(): Could not retrieve Google Analytics accounts' );

			return esc_html_e( 'It appears that you do not have a Google Analytics account for the account you selected.', 'gravityformsgoogleanalytics' );
		}

		if ( ! isset( $response['accountSummaries'] ) || empty( $response['accountSummaries'] ) ) {
			$this->connect_error = true;
			$gravity_url         = admin_url( 'admin.php?page=gf_settings&subview=gravityformsgoogleanalytics' );
			?>
			<style>
                #gform-settings-save {
                    display: none;
                }
			</style>
			<p><?php esc_html_e( 'It appears that you do not have a Google Analytics account for the account you selected.', 'gravityformsgoogleanalytics' ); ?></p>
			<p><a class="button primary"
			      href="<?php echo esc_url_raw( $gravity_url ); ?>"><?php esc_html_e( 'Return to Settings', 'gravityformsgoogleanalytics' ); ?></a>
			</p>
			<?php
		} else {
			echo sprintf( '<input type="hidden" name="gfga_token" value="%s" />', esc_attr( rgar( $auth_tokens, 'token' ) ) );
			echo sprintf( '<input type="hidden" name="gfga_refresh" value="%s" />', esc_attr( rgar( $auth_tokens, 'refresh' ) ) );
			echo '<select name="gaproperty" id="gaproperty">';
			echo '<option value="">' . esc_html__( 'Select an account', 'gravityformsgoogleanalytics' ) . '</option>';
			foreach ( $response['accountSummaries'] as $account ) {
				?>
				<optgroup label="<?php echo esc_attr( $account['displayName'] ); ?>">
					<?php
					if ( ! isset( $account['propertySummaries'] ) || empty( $account['propertySummaries'] ) ) {
						printf( '<option disabled="disabled">%s</option>', esc_html__( 'This account does not have any properties...', 'gravityformsgoogleanalytics' ) );
						continue;
					}
					foreach ( $account['propertySummaries'] as $property ) {
						printf(
							'<option value="%s" data-account-id="%s" data-account-name="%s" data-property-name="%s">%s</option>',
							esc_attr( $property['property'] ),
							esc_attr( $account['account'] ),
							esc_html( $account['displayName'] ),
							esc_html( $property['displayName'] ),
							esc_html( $property['displayName'] )
						);
					}
					?>
				</optgroup>
				<?php
			}
			echo '</select>';
			?>
			<br/>
			<div id="ga-data-streams"></div>
			<?php
		}
	}


	/**
	 * Displays a Google Tag Manager box.
	 *
	 * @since  1.0.0
	 */
	public function settings_gtm_select() {
		$auth_payload = $this->plugin_settings->get_auth_payload();
		if ( $this->initialize_api() || empty( $auth_payload ) ) {
			if ( ! $this->current_user_can_any( $this->_capabilities_form_settings ) ) {
				return;
			}

			$auth_tokens = $this->maybe_get_tokens_from_auth_payload( $auth_payload, self::get_options() );

			if ( ! $auth_tokens ) {
				return false;
			}

			$token   = rgar( $auth_tokens, 'token' );
			$refresh = rgar( $auth_tokens, 'refresh' );


			$body = array();

			$response = $this->api->get_tag_manager_account( $body );
			if ( is_wp_error( $response ) ) {
				return $response->get_error_message();
			}

			if ( isset( $response['account'] ) ) {
				echo sprintf( '<input type="hidden" name="gfga_token" value="%s" />', esc_attr( $token ) );
				echo sprintf( '<input type="hidden" name="gfga_refresh" value="%s" />', esc_attr( $refresh ) );
				echo '<select name="gtmproperty" id="gtmproperty">';
				echo '<option value="">' . esc_html__( 'Select a Tag Manager Account', 'gravityformsgoogleanalytics' ) . '</option>';

				$google_tag_manager_array = array();

				foreach ( $response['account'] as $account ) {
					$google_tag_manager_array[ $account['name'] ] = array();
					?>
					<option data-account-name="<?php echo esc_attr( $account['name'] ); ?>"
					        data-account-id="<?php echo esc_attr( $account['accountId'] ); ?>"
					        data-token="<?php echo esc_attr( rgar( $auth_payload, 'access_token' ) ); ?>"
					        value="<?php echo esc_attr( $account['name'] ); ?>"><?php echo esc_attr( $account['name'] ); ?></option>
					<?php
				}

				echo '</select>';
				?>
				<br/>
				<div id="gtm-containers"></div>
				<br/>
				<div id="gtm-workspaces"></div>
				<?php

				return;
			}

			// No GTM installed - Display a message.
			$this->connect_error = true;
			$gravity_url         = admin_url( 'admin.php?page=gf_settings&subview=gravityformsgoogleanalytics' );
			?>
			<style>
                #gform-settings-save {
                    display: none;
                }
			</style>
			<p><?php esc_html_e( 'It appears that you do not have a Google Tag Manager account for the account you selected.', 'gravityformsgoogleanalytics' ); ?></p>
			<p><a class="button primary"
			      href="<?php echo esc_url_raw( $gravity_url ); ?>"><?php esc_html_e( 'Return to Settings', 'gravityformsgoogleanalytics' ); ?></a>
			</p>
			<?php
		}
	}

	/**
	 * Get Tag Manager Triggers.
	 *
	 * @since 2.0
	 *
	 * @param array  $body      The add-on's options.
	 * @param string $path      The path for the api request.
	 * @param string $workspace The workspace ID.
	 *
	 * @return array Array of triggers
	 */
	public function get_tag_manager_triggers( $body, $path, $workspace ) {
		if ( $this->initialize_api() ) {
			$triggers = $this->api->get_tag_manager_triggers( array(), $path, $workspace );

			return $triggers;
		}

		return array();
	}

	/**
	 * Get Tag Manager Variables.
	 *
	 * @since 2.0
	 *
	 * @param array  $body      The add-on's options.
	 * @param string $path      The path for the api request.
	 * @param string $workspace The workspace ID.
	 *
	 * @return array Array of variables.
	 */
	public function get_tag_manager_variables( $body, $path, $workspace ) {
		if ( $this->initialize_api() ) {
			$variables = $this->api->get_tag_manager_variables( array(), $path, $workspace );

			return $variables;
		}

		return array();
	}

	/**
	 * Sets a nonce for Google Analytics and GTM
	 *
	 * @since  1.0.0
	 */
	public function settings_nonce_connect() {
		echo sprintf( '<input type="hidden" name="gfganonce" value="%s" />', esc_attr( wp_create_nonce( 'connect_google_analytics' ) ) );
	}

	/**
	 * Sets an action variable for Google Analytics.
	 *
	 * @since  1.0.0
	 */
	public function settings_ga_action() {
		echo '<input type="hidden" name="gfgaaction" value="ga" />';
	}

	/**
	 * Sets an action variable for Google Tag Manager.
	 *
	 * @since  1.0.0
	 */
	public function settings_gtm_action() {
		echo '<input type="hidden" name="gfgaaction" value="gtm" />';
	}

	/**
	 * Sets an action variable for manual configuration.
	 *
	 * @since  1.0.0
	 */
	public function settings_manual_action() {
		echo '<input type="hidden" name="gfgaaction" value="manual" />';
	}

	/**
	 * Update auth tokens.
	 *
	 * @since  1.0.0
	 */
	public function plugin_settings_page() {

		$this->plugin_settings->maybe_update_auth_tokens();
		$this->plugin_settings->maybe_display_settings_updated();

		parent::plugin_settings_page();

	}

	/**
	 * Configures the settings which should be rendered on the add-on settings tab.
	 *
	 * @since  1.0.0
	 */
	public function plugin_settings_fields() {
		return $this->plugin_settings->get_fields();
	}

	/**
	 * Get the Google Analytics UA Code
	 *
	 * @since 1.0.0
	 *
	 * @return string/bool Returns string UA code, false otherwise
	 */
	private function get_measurement_id() {
		return self::get_options( 'ga4_account', 'measurement_id' );
	}

	/**
	 * Call form_settings from the form settings class.
	 *
	 * @since  1.0.0
	 *
	 * @param array $form The current form.
	 */
	public function form_settings( $form ) {
		return $this->form_settings->form_settings_page( $form );
	}

	/**
	 * Call form_settings_fields from the form settings class.
	 *
	 * @since  1.0.0
	 *
	 * @param array $form The current form.
	 */
	public function form_settings_fields( $form ) {
		return $this->form_settings->pagination_form_settings( $form );
	}

	/**
	 * Call feed_settings_fields from the form settings class.
	 *
	 * @since  1.0.0
	 */
	public function feed_settings_fields() {
		return $this->form_settings->get_feed_settings_fields();
	}

	/**
	 * Call feed_list_columns from the form settings class.
	 *
	 * @since  1.0.0
	 */
	public function feed_list_columns() {
		return $this->form_settings->feed_list_columns();
	}

	/**
	 * Set feed creation control.
	 *
	 * @since  1.0
	 *
	 * @return bool
	 */
	public function can_create_feed() {
		$options = self::get_options();

		if ( rgar( $options, 'is_manual' ) === true ) {
			return true;
		}

		if ( $this->initialize_api() ) {
			return true;
		}

		return false;
	}

	/**
	 * Processes the feed.
	 *
	 * @since  1.0.0
	 *
	 * @param array $feed  The feed to process.
	 * @param array $entry The entry to process.
	 * @param array $form  The form the feed is coming from.
	 */
	public function process_feed( $feed, $entry, $form ) {
		$mode       = self::get_options( '', 'mode' );
		$parameters = $this->get_mapped_parameters( $feed, 'submission_parameters', $form, $entry );
		$is_ajax    = isset( $_REQUEST['gform_ajax'] ) ? true : false;
		$event_name = $this->get_submission_event_name( $mode, $feed, $entry, $form );

		if ( defined( 'REST_REQUEST' ) && REST_REQUEST && $mode !== 'gmp' ) {
			$this->add_event_to_rest_response( $entry, $feed, $form, $mode, $parameters, $is_ajax, $event_name );

			return $entry;
		}

		switch ( $mode ) {
			case 'gmp':
				// Send events via server side (measurement protocol)
				$ua_codes = $this->get_ga_codes( $form, $entry, $feed );
				$this->send_measurement_protocol( $ua_codes, $parameters, $event_name, $entry['source_url'] );
				break;

			case 'ga':
				$this->log_debug( __METHOD__ . '(): Attempting to send event via Google Analytics. Event Name: ' . $event_name . '. Page URL: ' . $entry['source_url'] . '. Parameters: ' . print_r( $parameters, true ) );
				$this->render_script_feed_counter();
				$this->render_script_send_unique_to_ga( $entry['id'], $feed['id'], $parameters, $event_name, $is_ajax );
				break;

			case 'gtm':
				$trigger_name = rgar( $feed['meta'], 'submission_trigger' ) !== 'gf_custom' ? rgar( $feed['meta'], 'submission_trigger' ) : rgar( $feed['meta'], 'submission_trigger_custom' );
				$this->log_debug( __METHOD__ . '(): Attempting to send event via Google Tag Manager. Trigger Name: ' . $trigger_name . '. Page URL: ' . $entry['source_url'] . '. Parameters: ' . print_r( $parameters, true ) );
				$this->render_script_feed_counter();
				$this->render_script_send_unique_to_gtm( $entry['id'], $feed['id'], $parameters, $trigger_name, $is_ajax );
				break;
		}

		return $entry;
	}

	/**
	 * Adds the event data to a REST response object in lieu of echoing it in the response.
	 *
	 * @since 2.1.0
	 *
	 * @param $entry
	 * @param $feed
	 * @param $form
	 * @param $mode
	 * @param $parameters
	 * @param $is_ajax
	 * @param $event_name
	 *
	 * @return void
	 */
	protected function add_event_to_rest_response( $entry, $feed, $form, $mode, $parameters, $is_ajax, $event_name ) {
		$details = array();

		switch ( $mode ) {
			case 'ga':
				$details = array(
					'entry_id'   => $entry['id'],
					'feed_id'    => $feed['id'],
					'parameters' => $parameters,
					'event_name' => $event_name,
				);
				break;

			case 'gtm':
				$trigger_name = rgar( $feed['meta'], 'submission_trigger' ) !== 'gf_custom' ? rgar( $feed['meta'], 'submission_trigger' ) : rgar( $feed['meta'], 'submission_trigger_custom' );
				$details      = array(
					'entry_id'     => $entry['id'],
					'feed_id'      => $feed['id'],
					'parameters'   => $parameters,
					'trigger_name' => $trigger_name,
				);
				break;
		}

		add_filter( 'rest_request_after_callbacks', function ( $response ) use ( $details ) {
			$response->data['ga_event'] = $details;

			return $response;
		} );
	}

	/***
	 * Determines the name of the event to be sent to Google Analytics or Measurement Protocol.
	 *
	 * @since 2.0.0
	 *
	 * @param string $mode  Google analytics connection mode. "ga" for Analytics, "gtm" for Tag Manager, "gmp" for Management Protocol.
	 * @param array  $feed  Current feed object.
	 * @param array  $entry Current entry object.
	 * @param array  $form  Current form object.
	 *
	 * @return string Returns the event name that will be sent to track the event.
	 */
	private function get_submission_event_name( $mode, $feed, $entry, $form ) {

		/***
		 * Filters the name of the submission event to be sent to Google Analytics. This filter only applies to Google Analytics or Measurement Protocol (not Tag Manger)
		 *
		 * @since 2.0.0
		 *
		 * @param string $event_name Event name being filtered.
		 * @param string $mode       Google analytics connection mode. "ga" for Analytics, "gtm" for Tag Manager, "gmp" for Management Protocol.
		 * @param array  $feed       Current feed object.
		 * @param array  $entry      Current entry object.
		 * @param array  $form       Current form object.
		 */
		return apply_filters( 'gform_googleanalytics_submission_event_name', 'gforms_submission', $mode, $feed, $entry, $form );
	}

	/***
	 * Determines the name of the pagination event to be sent to Google Analytics or Measurement Protocol.
	 *
	 * @since 2.0.0
	 *
	 * @param string $mode Google analytics connection mode. "ga" for Analytics and "gmp" for Measurement Protocol.
	 * @param array  $form Current form object.
	 *
	 * @return string Returns the event name.
	 */
	private function get_pagination_event_name( $mode, $form ) {

		/***
		 * Filters the name of the pagination event to be sent to Google Analytics. This filter only applies to Google Analytics or Measurement Protocol (not Tag Manger)
		 *
		 * @since 2.0.0
		 *
		 * @param string $event_name Event name being filtered.
		 * @param string $mode       Google analytics connection mode. "ga" for Analytics, "gtm" for Tag Manager, "gmp" for Management Protocol.
		 * @param array  $form       Current form object.
		 */
		return apply_filters( 'gform_googleanalytics_pagination_event_name', 'gforms_pagination', $mode, $form );
	}

	/**
	 * Renders the script to send an event to Google Analytics, making sure the event is not sent if it has been sent before.
	 *
	 * @since 2.0
	 *
	 * @param int    $entry_id   The current entry id.
	 * @param int    $feed_id    The current feed id.
	 * @param array  $parameters An array of event parameters to be sent.
	 * @param string $event_name The event name to be recorded in Google Analytics.
	 * @param bool   $is_ajax    Wether or not the form submission was done with AJAX enabled.
	 *
	 */
	private function render_script_send_unique_to_ga( $entry_id, $feed_id, $parameters, $event_name, $is_ajax ) {

		$body = "window.parent.GF_Google_Analytics.send_unique_to_ga( '" . esc_js( $entry_id ) . "', '" . esc_js( $feed_id ) . "',  " . wp_json_encode( $parameters ) . ", '" . esc_js( $event_name ) . "' );";
		$this->render_script_wrapper( $body, $is_ajax );
	}

	/**
	 * Renders the script to send an event to Google Tag Manager, making sure the event is not sent if it has been sent before.
	 *
	 * @since 2.0
	 *
	 * @param int    $entry_id     The current entry id.
	 * @param int    $feed_id      The current feed id.
	 * @param array  $parameters   An array of event parameters to be sent.
	 * @param string $trigger_name The trigger name to be associated with this event in Tag Manager.
	 * @param bool   $is_ajax      Wether or not the form submission was done with AJAX enabled.
	 *
	 */
	private function render_script_send_unique_to_gtm( $entry_id, $feed_id, $parameters, $trigger_name, $is_ajax ) {

		$body = "window.parent.GF_Google_Analytics.send_unique_to_gtm( '" . esc_js( $entry_id ) . "', '" . esc_js( $feed_id ) . "',  " . wp_json_encode( $parameters ) . ", '" . esc_js( $trigger_name ) . "' );";
		$this->render_script_wrapper( $body, $is_ajax );
	}

	/**
	 * Renders the script to send an event to Google Analytics.
	 *
	 * @since 2.0
	 *
	 * @param array  $parameters An array of event parameters to be sent.
	 * @param string $event_name The event name to be recorded in Google Analytics.
	 * @param bool   $is_ajax    Wether or not the form submission was done with AJAX enabled.
	 *
	 */
	private function render_script_send_to_ga( $parameters, $event_name, $is_ajax ) {

		$body = 'window.parent.GF_Google_Analytics.send_to_ga( ' . wp_json_encode( $parameters ) . ', "' . esc_js( $event_name ) . '" );';
		$this->render_script_wrapper( $body, $is_ajax );
	}

	/**
	 * Renders the script to send an event to Google Tag Manager.
	 *
	 * @since 2.0
	 *
	 * @param array  $parameters   An array of event parameters to be sent.
	 * @param string $trigger_name The trigger name to be associated with this event in Tag Manager.
	 * @param bool   $is_ajax      Wether or not the form submission was done with AJAX enabled.
	 *
	 */
	private function render_script_send_to_gtm( $parameters, $trigger_name, $is_ajax ) {

		$body = 'window.parent.GF_Google_Analytics.send_to_gtm( ' . wp_json_encode( $parameters ) . ', "' . esc_js( $trigger_name ) . '" );';
		$this->render_script_wrapper( $body, $is_ajax );
	}

	/**
	 * Renders the wrapper script for the specified $script_body script, taking AJAX into account.
	 *
	 * @since 2.0
	 *
	 * @param string $script_body The script body to be wrapped.
	 * @param bool   $is_ajax     Wether or not the form submission was done with AJAX enabled.
	 */
	private function render_script_wrapper( $script_body, $is_ajax ) {
		if ( rgpost( 'gform_submission_method' ) === 'ajax' ) {
			return;
		}
		?>
		<script>
			<?php
			if ( $is_ajax ) {
				echo $script_body;
			} else {
			// For non-AJAX forms, script body must be wrapped inside the googleanalytics/script_loaded so that it is
			// executed after the google analytics script is loaded.
			?>
			window.addEventListener( "googleanalytics/script_loaded", function() {
				<?php
				echo $script_body;
				?>
			} );
			<?php
			}
			?>
		</script>
		<?php
	}

	/**
	 * Renders the script that will count how many feeds were executed. This is needed so that an event can be fired after all the feeds are sent.
	 *
	 * @since 2.0
	 */
	private function render_script_feed_counter() {
		// New ajax can't just write to the page.
		if ( rgpost( 'gform_submission_method' ) === 'ajax' ) {
			return;
		}
		?>
		<script>
			window.parent[ 'ga_feed_count' ] = window.parent[ 'ga_feed_count' ] ? window.parent[ 'ga_feed_count' ] + 1 : 1;
		</script>
		<?php
	}

	/**
	 * Sends an event via the Measurement Protocol.
	 *
	 * @since 2.0
	 *
	 * @param array  $ga_codes   An array of GA Codes to receive the event. Each GA code here will receive the event.
	 * @param array  $parameters An array of event parameters to be sent.
	 * @param string $event_name The event name to be recorded in Google Analytics.
	 * @param string $page_url   The current page URL.
	 */
	private function send_measurement_protocol( $ga_codes, $parameters, $event_name, $page_url ) {

		// Initialize the measurement protocol.
		if ( ! class_exists( 'GF_Google_Analytics_Measurement_Protocol' ) ) {
			include_once 'includes/class-gf-google-analytics-measurement-protocol.php';
		}
		$event      = new GF_Google_Analytics_Measurement_Protocol();
		$api_secret = self::get_options( 'ga4_account', 'gmp_api_secret' );
		$event->init( $api_secret, $event_name );

		// Set document variables.
		$event->set_document_path( str_replace( home_url(), '', $page_url ) );
		$event->set_document_location( esc_url( $page_url ) );
		$event_url_parsed = wp_parse_url( home_url() );
		$event->set_document_host( $event_url_parsed['host'] );

		// Set IP address
		$event->set_user_ip_address( \GFFormsModel::get_ip() );

		// Set document title.
		global $post;
		$document_title = isset( $post ) && isset( $post->post_title ) ? sanitize_text_field( $post->post_title ) : esc_html__( 'No title found', 'gravityformsgoogleanalytics' );
		$event->set_document_title( $document_title );

		// Set submission parameters.
		$event->set_params( $parameters );

		// Begin sending events using the measurement protocol.
		foreach ( $ga_codes as $code ) {

			$response = $event->send( $code );

			if ( is_wp_error( $response ) ) {
				$this->log_debug( __METHOD__ . '(): Failed to send event to Google Analytics via Measurement Protocol. Secret (last 4): XXXXXX' . substr( $api_secret, - 4 ) . '. GA code: ' . $code . '. Event Name: ' . $event_name . '. Page URL: ' . $page_url . '. Parameters: ' . print_r( $event->get_params(), true ) );
			} else {
				$this->log_debug( __METHOD__ . '(): Successfully sent event to Google Analytics via Measurement Protocol. Secret (last 4): XXXXXX' . substr( $api_secret, - 4 ) . '. GA code: ' . $code . '. Event Name: ' . $event_name . '. Page URL: ' . $page_url . '. Parameters: ' . print_r( $event->get_params(), true ) );
			}
		}
	}

	/**
	 * Retrieves the value for the feed item.
	 *
	 * @since 1.0.0
	 *
	 * @param mixed $feed   The Feed item.
	 * @param mixed $column The Feed column name.
	 *
	 * @return string The column value.
	 */
	public function get_column_value( $feed, $column ) {

		$value = '';

		if ( empty( $value ) ) {
			if ( isset( $feed[ $column ] ) ) {
				$value = $feed[ $column ];
			} elseif ( isset( $feed['meta'][ $column ] ) ) {
				$value = $feed['meta'][ $column ];
			}
		}

		return $value;
	}

	/**
	 * Get the menu icon for this plugin.
	 *
	 * @since 1.0
	 *
	 * @return string the class for the plugin menu icon.
	 */
	public function get_menu_icon() {
		return $this->is_gravityforms_supported( '2.5-beta-4' ) ? 'gform-icon--analytics' : 'dashicons-admin-generic';
	}

	/**
	 * Log if an event is successfully sent.
	 *
	 * @since 2.1.0
	 */
	public function ajax_log_ga_event_sent() {
		$this->verify_ajax_nonce( 'log_google_analytics_event_sent' );
		$connection = rgpost( 'connection' );
		$parameters = rgpost( 'parameters' );
		if ( $connection === 'ga' ) {
			$event_name = rgpost( 'eventName' );
			$this->log_debug( __METHOD__ . '(): Completed sending event via Google Analytics. Event Name: ' . $event_name . '. Parameters: ' . print_r( $parameters, true ) );
		} elseif ( $connection = 'gtm' ) {
			$trigger_name = rgpost( 'triggerName' );
			$this->log_debug( __METHOD__ . '(): Completed sending event via Google Tag Manager. Trigger Name: ' . $trigger_name . '. Parameters: ' . print_r( $parameters, true ) );
		}
	}

	/**
	 * Adds the feed data to the submission results allowing the frontend to send
	 * the Google Analytics event.
	 *
	 * @since 2.3.1
	 *
	 * @param array $submissionResults The submission results from the ajax submission.
	 * @return array The submission results with the feed data added.
	 */
	public function set_post_ajax_submission_vars( $submissionResults ) {
		if ( ! rgar( $submissionResults, 'is_valid' ) || rgar( $submissionResults, 'is_spam' ) ) {
			return $submissionResults;
		}

		$formId = rgars( $submissionResults, 'form/id' );
		$form   = rgar( $submissionResults, 'form' );

		if ( ! rgar( $submissionResults, 'entry_id' ) ) {
			// Pagination tracking not enabled. Abort.
			if ( ! rgars( $form, 'gravityformsgoogleanalytics/google_analytics_pagination' ) ) {
				return $submissionResults;
			}

			$mode                = self::get_options( '', 'mode' );
			$source_page_number  = rgar( $submissionResults, 'source_page_number' );
			$current_page_number = rgar( $submissionResults, 'page_number' );
			$parameters          = $this->get_mapped_parameters( $form['gravityformsgoogleanalytics'], 'pagination_parameters', $form, \GFFormsModel::get_current_lead( $form ) );
			$parameters          = $this->maybe_replace_pagination_merge_tags( $parameters, $source_page_number, $current_page_number );
			$event_name          = $this->get_pagination_event_name( $mode, $form );

			$submissionResults['ajax_pagination'] = [
				'form_id'    => (string) $formId,
				'parameters' => $parameters,
				'eventName'  => $event_name,
				'mode'       => $mode,
			];

			$submission_type   = rgar( $submissionResults, 'submission_type' );
			$submission_method = rgpost( 'gform_submission_method' );

			if ( $mode === 'gmp' && 'ajax' === $submission_method && ( $submission_type === 'next' || $submission_type === 'previous' ) ) {
				$ua_codes = $this->get_ga_codes( $form );
				$this->send_measurement_protocol( $ua_codes, $parameters, $event_name, rgpost( 'current_page_url' ) );
			}
			return $submissionResults;
		}

		$feeds = $this->get_feeds( $formId );

		if ( empty( $feeds ) ) {
			return $submissionResults;
		}

		$entry = GFAPI::get_entry( rgar( $submissionResults, 'entry_id' ) );

		if ( is_wp_error( $entry ) ) {
			$this->log_debug( __METHOD__ . '(): Unable to retrieve entry. Entry ID: ' . rgar( $submissionResults, 'entry_id' ) );
			return $submissionResults;
		}

		$mode = self::get_options( '', 'mode' );

		foreach ( $feeds as $feed ) {
			if ( ! rgar( $feed, 'is_active' ) || ! $this->is_feed_condition_met( $feed, $form, $entry ) ) {
				continue;
			}

			$parameters                        = $this->get_mapped_parameters( $feed, 'submission_parameters', $form, $entry );
			$submissionResults['ajax_feeds'][] = [
				'form_id'    => (string) $formId,
				'feed_id'    => rgar( $feed, 'id' ),
				'parameters' => $parameters,
				'eventName'  => $this->get_submission_event_name( 'ga', $feed, $entry, $form ),
				'mode'       => $mode,
			];
		}

		return $submissionResults;
	}
}
