2025-07-27 19:55:19 +02:00
< ? php
/**
* BSF analytics class file .
*
* @ version 1.0 . 0
*
* @ package bsf - analytics
*/
if ( ! defined ( 'ABSPATH' ) ) {
exit ; // Exit if accessed directly.
}
if ( ! class_exists ( 'BSF_Analytics' ) ) {
/**
* BSF analytics
*/
class BSF_Analytics {
/**
* Member Variable
*
* @ var array Entities data .
*/
private $entities ;
/**
* Member Variable
*
* @ var string Usage tracking document URL
*/
public $usage_doc_link = 'https://store.brainstormforce.com/usage-tracking/?utm_source=wp_dashboard&utm_medium=general_settings&utm_campaign=usage_tracking' ;
/**
* Setup actions , load files .
*
* @ param array $args entity data for analytics .
* @ param string $analytics_path directory path to analytics library .
* @ param float $analytics_version analytics library version .
* @ since 1.0 . 0
*/
public function __construct ( $args , $analytics_path , $analytics_version ) {
// Bail when no analytics entities are registered.
if ( empty ( $args ) ) {
return ;
}
$this -> entities = $args ;
2026-03-31 11:30:59 +02:00
// Run migration from old "analytics" option names to new "usage" names.
$this -> maybe_migrate_options ();
2025-07-27 19:55:19 +02:00
define ( 'BSF_ANALYTICS_VERSION' , $analytics_version );
define ( 'BSF_ANALYTICS_URI' , $this -> get_analytics_url ( $analytics_path ) );
add_action ( 'admin_init' , array ( $this , 'handle_optin_optout' ) );
add_action ( 'admin_init' , array ( $this , 'option_notice' ) );
add_action ( 'init' , array ( $this , 'maybe_track_analytics' ), 99 );
$this -> set_actions ();
add_action ( 'admin_init' , array ( $this , 'register_usage_tracking_setting' ) );
$this -> includes ();
$this -> load_deactivation_survey_actions ();
}
/**
* Function to load the deactivation survey form actions .
*
* @ since 1.1 . 6
* @ return void
*/
public function load_deactivation_survey_actions () {
// If not in a admin area then return it.
if ( ! is_admin () ) {
return ;
}
add_filter ( 'uds_survey_vars' , array ( $this , 'add_slugs_to_uds_vars' ) );
add_action ( 'admin_footer' , array ( $this , 'load_deactivation_survey_form' ) );
}
/**
* Setup actions for admin notice style and analytics cron event .
*
* @ since 1.0 . 4
*/
public function set_actions () {
foreach ( $this -> entities as $key => $data ) {
add_action ( 'astra_notice_before_markup_' . $key . '-optin-notice' , array ( $this , 'enqueue_assets' ) );
2026-03-31 11:30:59 +02:00
add_action ( 'update_option_' . $key . '_usage_optin' , array ( $this , 'update_analytics_option_callback' ), 10 , 3 );
add_action ( 'add_option_' . $key . '_usage_optin' , array ( $this , 'add_analytics_option_callback' ), 10 , 2 );
2025-07-27 19:55:19 +02:00
}
}
/**
* BSF Analytics URL
*
* @ param string $analytics_path directory path to analytics library .
* @ return String URL of bsf - analytics directory .
* @ since 1.0 . 0
*/
public function get_analytics_url ( $analytics_path ) {
$content_dir_path = wp_normalize_path ( WP_CONTENT_DIR );
$analytics_path = wp_normalize_path ( $analytics_path );
return str_replace ( $content_dir_path , content_url (), $analytics_path );
}
/**
* Enqueue Scripts .
*
* @ since 1.0 . 0
* @ return void
*/
public function enqueue_assets () {
/**
* Load unminified if SCRIPT_DEBUG is true .
*
* Directory and Extensions .
*/
$dir_name = ( SCRIPT_DEBUG ) ? 'unminified' : 'minified' ;
$file_rtl = ( is_rtl () ) ? '-rtl' : '' ;
$css_ext = ( SCRIPT_DEBUG ) ? '.css' : '.min.css' ;
$css_uri = BSF_ANALYTICS_URI . '/assets/css/' . $dir_name . '/style' . $file_rtl . $css_ext ;
wp_enqueue_style ( 'bsf-analytics-admin-style' , $css_uri , false , BSF_ANALYTICS_VERSION , 'all' );
}
/**
* Send analytics API call .
*
* @ since 1.0 . 0
*/
public function send () {
$api_url = BSF_Analytics_Helper :: get_api_url ();
wp_remote_post (
$api_url . 'api/analytics/' ,
array (
'body' => BSF_Analytics_Stats :: instance () -> get_stats (),
'timeout' => 5 ,
'blocking' => false ,
)
);
}
/**
* Check if usage tracking is enabled .
*
* @ return bool
* @ since 1.0 . 0
*/
public function is_tracking_enabled () {
foreach ( $this -> entities as $key => $data ) {
2026-03-31 11:30:59 +02:00
$is_enabled = get_site_option ( $key . '_usage_optin' , false ) === 'yes' ? true : false ;
2025-07-27 19:55:19 +02:00
$is_enabled = $this -> is_white_label_enabled ( $key ) ? false : $is_enabled ;
if ( apply_filters ( $key . '_tracking_enabled' , $is_enabled ) ) {
return true ;
}
}
return false ;
}
/**
* Check if WHITE label is enabled for BSF products .
*
* @ param string $source source of analytics .
* @ return bool
* @ since 1.0 . 0
*/
public function is_white_label_enabled ( $source ) {
$options = apply_filters ( $source . '_white_label_options' , array () );
$is_enabled = false ;
if ( is_array ( $options ) ) {
foreach ( $options as $option ) {
if ( true === $option ) {
$is_enabled = true ;
break ;
}
}
}
return $is_enabled ;
}
/**
* Display admin notice for usage tracking .
*
* @ since 1.0 . 0
*/
public function option_notice () {
if ( ! current_user_can ( 'manage_options' ) ) {
return ;
}
if ( $this -> is_tracking_enabled () ) {
return ; // Don't need to display notice if any of our plugin already have the permission.
}
2026-03-31 11:30:59 +02:00
// If the user has opted out of tracking, don't show the notice till 7 days.
$last_displayed_time = get_site_option ( 'bsf_usage_last_displayed_time' , false );
if ( $last_displayed_time && $last_displayed_time > time () - ( 7 * DAY_IN_SECONDS ) ) {
2025-07-27 19:55:19 +02:00
return ; // Don't display the notice if it was displayed recently.
}
foreach ( $this -> entities as $key => $data ) {
$time_to_display = isset ( $data [ 'time_to_display' ] ) ? $data [ 'time_to_display' ] : '+24 hours' ;
$usage_doc_link = isset ( $data [ 'usage_doc_link' ] ) ? $data [ 'usage_doc_link' ] : $this -> usage_doc_link ;
// Don't display the notice if tracking is disabled or White Label is enabled for any of our plugins.
2026-03-31 11:30:59 +02:00
if ( false !== get_site_option ( $key . '_usage_optin' , false ) || $this -> is_white_label_enabled ( $key ) ) {
2025-07-27 19:55:19 +02:00
continue ;
}
// Show tracker consent notice after 24 hours from installed time.
if ( strtotime ( $time_to_display , $this -> get_analytics_install_time ( $key ) ) > time () ) {
continue ;
}
/* translators: %s product name */
$notice_string = sprintf (
__ (
'Help us improve %1$s and our other products!<br><br>With your permission, we\'d like to collect <strong>non-sensitive information</strong> from your website — like your PHP version and which features you use — so we can fix bugs faster, make smarter decisions, and build features that actually matter to you. <em>No personal info. Ever.</em>' , 'astra'
),
'<strong>' . esc_html ( $data [ 'product_name' ] ) . '</strong>'
);
if ( is_multisite () ) {
$notice_string .= __ ( 'This will be applicable for all sites from the network.' , 'astra' );
}
$language_dir = is_rtl () ? 'rtl' : 'ltr' ;
Astra_Notices :: add_notice (
array (
'id' => $key . '-optin-notice' ,
'type' => '' ,
'message' => sprintf (
' < div class = " notice-content " >
< div class = " notice-heading " >
% 1 $s
</ div >
< div class = " astra-notices-container " >
< a href = " %2 $s " class = " astra-notices button-primary " >
% 3 $s
</ a >
< a href = " %4 $s " data - repeat - notice - after = " %5 $s " class = " astra-notices button-secondary " >
% 6 $s
</ a >
</ div >
</ div > ' ,
/* translators: %s usage doc link */
sprintf ( $notice_string . '<span dir="%1s"><a href="%2s" target="_blank" rel="noreferrer noopener">%3s</a><span><br><br>' , $language_dir , esc_url ( $usage_doc_link ), __ ( ' Know More.' , 'astra' ) ),
esc_url (
add_query_arg (
array (
$key . '_analytics_optin' => 'yes' ,
$key . '_analytics_nonce' => wp_create_nonce ( $key . '_analytics_optin' ),
'bsf_analytics_source' => $key ,
)
)
),
__ ( 'Yes! Allow it' , 'astra' ),
esc_url (
add_query_arg (
array (
$key . '_analytics_optin' => 'no' ,
$key . '_analytics_nonce' => wp_create_nonce ( $key . '_analytics_optin' ),
'bsf_analytics_source' => $key ,
)
)
),
MONTH_IN_SECONDS ,
__ ( 'No Thanks' , 'astra' )
),
'show_if' => true ,
'repeat-notice-after' => false ,
'priority' => 18 ,
'display-with-other-notices' => true ,
)
);
return ;
}
}
/**
* Process usage tracking opt out .
*
* @ since 1.0 . 0
*/
public function handle_optin_optout () {
if ( ! current_user_can ( 'manage_options' ) ) {
return ;
}
2026-03-31 11:30:59 +02:00
// Verify nonce before accessing any $_GET data.
// The nonce key is dynamic per entity, so iterate to find a valid one.
$source = '' ;
foreach ( $this -> entities as $key => $data ) {
$nonce_key = $key . '_analytics_nonce' ;
if ( isset ( $_GET [ $nonce_key ] ) && wp_verify_nonce ( sanitize_text_field ( wp_unslash ( $_GET [ $nonce_key ] ) ), $key . '_analytics_optin' ) ) {
$source = $key ;
break ;
}
2025-07-27 19:55:19 +02:00
}
2026-03-31 11:30:59 +02:00
if ( empty ( $source ) ) {
2025-07-27 19:55:19 +02:00
return ;
}
$optin_status = isset ( $_GET [ $source . '_analytics_optin' ] ) ? sanitize_text_field ( wp_unslash ( $_GET [ $source . '_analytics_optin' ] ) ) : '' ;
if ( 'yes' === $optin_status ) {
$this -> optin ( $source );
} elseif ( 'no' === $optin_status ) {
$this -> optout ( $source );
}
wp_safe_redirect (
esc_url_raw (
remove_query_arg (
array (
$source . '_analytics_optin' ,
$source . '_analytics_nonce' ,
'bsf_analytics_source' ,
)
)
)
);
2026-03-31 11:30:59 +02:00
exit ;
2025-07-27 19:55:19 +02:00
}
/**
* Opt in to usage tracking .
*
* @ param string $source source of analytics .
* @ since 1.0 . 0
*/
private function optin ( $source ) {
2026-03-31 11:30:59 +02:00
update_site_option ( $source . '_usage_optin' , 'yes' );
2025-07-27 19:55:19 +02:00
}
/**
2026-03-31 11:30:59 +02:00
* Opt out of usage tracking .
2025-07-27 19:55:19 +02:00
*
* @ param string $source source of analytics .
* @ since 1.0 . 0
*/
private function optout ( $source ) {
2026-03-31 11:30:59 +02:00
update_site_option ( $source . '_usage_optin' , 'no' );
update_site_option ( 'bsf_usage_last_displayed_time' , time () );
2025-07-27 19:55:19 +02:00
}
/**
* Load analytics stat class .
*
* @ since 1.0 . 0
*/
private function includes () {
require_once __DIR__ . '/classes/class-bsf-analytics-helper.php' ;
require_once __DIR__ . '/class-bsf-analytics-stats.php' ;
// Loads all the modules.
require_once __DIR__ . '/modules/deactivation-survey/classes/class-deactivation-survey-feedback.php' ;
require_once __DIR__ . '/modules/utm-analytics.php' ;
}
2026-03-31 11:30:59 +02:00
/**
* Migrate old " analytics " options to new " usage " naming .
* Copies values to new options and deletes old options .
*
* @ since 1.1 . 17
*/
private function maybe_migrate_options () {
if ( get_site_option ( 'bsf_usage_migrated' ) ) {
return ;
}
// Migrate global options.
$old_last_displayed = get_site_option ( 'bsf_analytics_last_displayed_time' );
if ( false !== $old_last_displayed ) {
update_site_option ( 'bsf_usage_last_displayed_time' , $old_last_displayed );
delete_site_option ( 'bsf_analytics_last_displayed_time' );
}
// Migrate per-product options.
foreach ( $this -> entities as $key => $data ) {
$old_optin = get_site_option ( $key . '_analytics_optin' );
if ( false !== $old_optin ) {
update_site_option ( $key . '_usage_optin' , $old_optin );
delete_site_option ( $key . '_analytics_optin' );
}
$old_install_time = get_site_option ( $key . '_analytics_installed_time' );
if ( false !== $old_install_time ) {
update_site_option ( $key . '_usage_installed_time' , $old_install_time );
delete_site_option ( $key . '_analytics_installed_time' );
}
}
// Migrate transient.
$old_track = get_site_transient ( 'bsf_analytics_track' );
if ( false !== $old_track ) {
set_site_transient ( 'bsf_usage_track' , $old_track , 2 * DAY_IN_SECONDS );
delete_site_transient ( 'bsf_analytics_track' );
}
update_site_option ( 'bsf_usage_migrated' , true );
}
2025-07-27 19:55:19 +02:00
/**
* Register usage tracking option in General settings page .
*
* @ since 1.0 . 0
*/
public function register_usage_tracking_setting () {
foreach ( $this -> entities as $key => $data ) {
if ( ! apply_filters ( $key . '_tracking_enabled' , true ) || $this -> is_white_label_enabled ( $key ) ) {
return ;
}
/**
* Introducing a new key ' hide_optin_checkbox , which allows individual plugin to hide optin checkbox
* If they are providing providing in - plugin option to manage this option .
* from General > Settings page .
*
* @ since 1.1 . 14
*/
if ( ! empty ( $data [ 'hide_optin_checkbox' ] ) && true === $data [ 'hide_optin_checkbox' ] ) {
continue ;
}
$usage_doc_link = isset ( $data [ 'usage_doc_link' ] ) ? $data [ 'usage_doc_link' ] : $this -> usage_doc_link ;
$author = isset ( $data [ 'author' ] ) ? $data [ 'author' ] : 'Brainstorm Force' ;
register_setting (
'general' , // Options group.
2026-03-31 11:30:59 +02:00
$key . '_usage_optin' , // Option name/database.
2025-07-27 19:55:19 +02:00
array ( 'sanitize_callback' => array ( $this , 'sanitize_option' ) ) // sanitize callback function.
);
add_settings_field (
2026-03-31 11:30:59 +02:00
$key . '-usage-optin' , // Field ID.
2025-07-27 19:55:19 +02:00
__ ( 'Usage Tracking' , 'astra' ), // Field title.
array ( $this , 'render_settings_field_html' ), // Field callback function.
'general' ,
'default' , // Settings page slug.
array (
'type' => 'checkbox' ,
'title' => $author ,
2026-03-31 11:30:59 +02:00
'name' => $key . '_usage_optin' ,
'label_for' => $key . '-usage-optin' ,
'id' => $key . '-usage-optin' ,
2025-07-27 19:55:19 +02:00
'usage_doc_link' => $usage_doc_link ,
)
);
}
}
/**
* Sanitize Callback Function
*
* @ param bool $input Option value .
* @ since 1.0 . 0
*/
public function sanitize_option ( $input ) {
if ( ! $input || 'no' === $input ) {
return 'no' ;
}
return 'yes' ;
}
/**
* Print settings field HTML .
*
* @ param array $args arguments to field .
* @ since 1.0 . 0
*/
public function render_settings_field_html ( $args ) {
2026-03-31 11:30:59 +02:00
$is_checked = ( 'yes' === get_site_option ( $args [ 'name' ], false ) );
2025-07-27 19:55:19 +02:00
?>
< fieldset >
< label for = " <?php echo esc_attr( $args['label_for'] ); ?> " >
2026-03-31 11:30:59 +02:00
< input id = " <?php echo esc_attr( $args['id'] ); ?> " type = " checkbox " value = " 1 " name = " <?php echo esc_attr( $args['name'] ); ?> " < ? php checked ( $is_checked ); ?> >
2025-07-27 19:55:19 +02:00
< ? php
/* translators: %s Product title */
echo esc_html ( sprintf ( __ ( 'Allow %s products to track non-sensitive usage tracking data.' , 'astra' ), $args [ 'title' ] ) ); // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText
if ( is_multisite () ) {
esc_html_e ( ' This will be applicable for all sites from the network.' , 'astra' );
}
?>
</ label >
< ? php
echo wp_kses_post ( sprintf ( '<a href="%1s" target="_blank" rel="noreferrer noopener">%2s</a>' , esc_url ( $args [ 'usage_doc_link' ] ), __ ( 'Learn More.' , 'astra' ) ) );
?>
</ fieldset >
< ? php
}
/**
2026-03-31 11:30:59 +02:00
* Get analytics installed time from option .
2025-07-27 19:55:19 +02:00
*
* @ param string $source source of analytics .
* @ return string $time analytics installed time .
* @ since 1.0 . 0
*/
private function get_analytics_install_time ( $source ) {
2026-03-31 11:30:59 +02:00
$time = get_site_option ( $source . '_usage_installed_time' );
2025-07-27 19:55:19 +02:00
if ( ! $time ) {
$time = time ();
2026-03-31 11:30:59 +02:00
update_site_option ( $source . '_usage_installed_time' , $time );
2025-07-27 19:55:19 +02:00
}
return $time ;
}
/**
* Schedule / unschedule cron event on updation of option .
*
* @ param string $old_value old value of option .
* @ param string $value value of option .
* @ param string $option Option name .
* @ since 1.0 . 0
*/
public function update_analytics_option_callback ( $old_value , $value , $option ) {
if ( is_multisite () ) {
$this -> add_option_to_network ( $option , $value );
}
}
/**
* Analytics option add callback .
*
* @ param string $option Option name .
* @ param string $value value of option .
* @ since 1.0 . 0
*/
public function add_analytics_option_callback ( $option , $value ) {
if ( is_multisite () ) {
$this -> add_option_to_network ( $option , $value );
}
}
/**
* Send analytics track event if tracking is enabled .
*
* @ since 1.0 . 0
*/
public function maybe_track_analytics () {
if ( ! $this -> is_tracking_enabled () ) {
return ;
}
2026-03-31 11:30:59 +02:00
$analytics_track = get_site_transient ( 'bsf_usage_track' );
2025-07-27 19:55:19 +02:00
// If the last data sent is 2 days old i.e. transient is expired.
if ( ! $analytics_track ) {
$this -> send ();
2026-03-31 11:30:59 +02:00
set_site_transient ( 'bsf_usage_track' , true , 2 * DAY_IN_SECONDS );
2025-07-27 19:55:19 +02:00
}
}
/**
* Save analytics option to network .
*
* @ param string $option name of option .
* @ param string $value value of option .
* @ since 1.0 . 0
*/
public function add_option_to_network ( $option , $value ) {
// If action coming from general settings page.
2026-03-31 11:30:59 +02:00
if ( isset ( $_POST [ 'option_page' ] ) && 'general' === sanitize_text_field ( wp_unslash ( $_POST [ 'option_page' ] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
2025-07-27 19:55:19 +02:00
if ( get_site_option ( $option ) ) {
update_site_option ( $option , $value );
} else {
add_site_option ( $option , $value );
}
}
}
/**
* Function to load the deactivation survey form on the admin footer .
*
* This function checks if the Deactivation_Survey_Feedback class exists and if so , it loads the deactivation survey form .
* The form is configured with specific settings for plugin . Example : For CartFlows , including the source , logo , plugin slug , title , support URL , description , and the screen on which to show the form .
*
* @ since 1.1 . 6
* @ return void
*/
public function load_deactivation_survey_form () {
if ( class_exists ( 'Deactivation_Survey_Feedback' ) ) {
foreach ( $this -> entities as $key => $data ) {
// If the deactivation_survey info in available then only add the form.
if ( ! empty ( $data [ 'deactivation_survey' ] ) && is_array ( $data [ 'deactivation_survey' ] ) ) {
foreach ( $data [ 'deactivation_survey' ] as $key => $survey_args ) {
Deactivation_Survey_Feedback :: show_feedback_form (
$survey_args
);
}
}
}
}
}
/**
* Function to add plugin slugs to Deactivation Survey vars for JS operations .
*
* @ param array $vars UDS vars array .
* @ return array Modified UDS vars array with plugin slugs .
* @ since 1.1 . 6
*/
public function add_slugs_to_uds_vars ( $vars ) {
foreach ( $this -> entities as $key => $data ) {
if ( ! empty ( $data [ 'deactivation_survey' ] ) && is_array ( $data [ 'deactivation_survey' ] ) ) {
foreach ( $data [ 'deactivation_survey' ] as $key => $survey_args ) {
$vars [ '_plugin_slug' ] = isset ( $vars [ '_plugin_slug' ] ) ? array_merge ( $vars [ '_plugin_slug' ], array ( $survey_args [ 'plugin_slug' ] ) ) : array ( $survey_args [ 'plugin_slug' ] );
}
}
}
return $vars ;
}
}
}