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 tag, 3: close 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,
'',
''
)
?>
array( 'href' => true ) ) ); ?>
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' ) ) ) {
?>
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";
?>
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";
?>
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 = '
';
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 .= '
';
$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 .= '';
$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 "" .
'';
}
/**
* 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( '%s ', 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' );
?>
', esc_attr( rgar( $auth_tokens, 'token' ) ) );
echo sprintf( '', esc_attr( rgar( $auth_tokens, 'refresh' ) ) );
echo '