$95 GRAYBYTE WORDPRESS FILE MANAGER $90

SERVER : premium267.web-hosting.com #1 SMP Wed Jun 4 13:01:13 UTC 2025
SERVER IP : 69.57.162.29 | ADMIN IP 216.73.216.187
OPTIONS : CRL = ON | WGT = ON | SDO = OFF | PKEX = OFF
DEACTIVATED : NONE

/home/njinenkb/jaccul.org/wp-content/plugins/sureforms/admin/

HOME
Current File : /home/njinenkb/jaccul.org/wp-content/plugins/sureforms/admin//analytics.php
<?php
/**
 * Analytics class helps to connect BSFAnalytics.
 *
 * @package sureforms.
 */

namespace SRFM\Admin;

use SRFM\Inc\Database\Tables\Entries;
use SRFM\Inc\Helper;
use SRFM\Inc\Learn;
use SRFM\Inc\Traits\Get_Instance;

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly.
}
/**
 * Analytics class.
 *
 * @since 1.4.0
 */
class Analytics {
	use Get_Instance;

	/**
	 * BSF_Analytics_Events instance for one-time event tracking.
	 *
	 * @var \BSF_Analytics_Events|null
	 */
	private static $events = null;

	/**
	 * Class constructor.
	 *
	 * @return void
	 * @since 1.4.0
	 */
	public function __construct() {
		/*
		* BSF Analytics.
		*/
		if ( ! class_exists( 'BSF_Analytics_Loader' ) ) {
			require_once SRFM_DIR . 'inc/lib/bsf-analytics/class-bsf-analytics-loader.php';
		}

		if ( ! class_exists( 'BSF_Admin_Notices' ) ) {
			require_once SRFM_DIR . 'inc/lib/astra-notices/class-bsf-admin-notices.php';
		}

		add_filter(
			'uds_survey_allowed_screens',
			static function () {
				return [ 'plugins' ];
			}
		);

		$srfm_bsf_analytics = \BSF_Analytics_Loader::get_instance();

		$srfm_bsf_analytics->set_entity(
			[
				'sureforms' => [
					'product_name'        => 'SureForms',
					'path'                => SRFM_DIR . 'inc/lib/bsf-analytics',
					'author'              => 'SureForms',
					'time_to_display'     => '+24 hours',
					'deactivation_survey' => apply_filters(
						'srfm_deactivation_survey_data',
						[
							[
								'id'                => 'deactivation-survey-sureforms',
								'popup_logo'        => SRFM_URL . 'admin/assets/sureforms-logo.png',
								'plugin_slug'       => 'sureforms',
								'popup_title'       => 'Quick Feedback',
								'support_url'       => 'https://sureforms.com/contact/',
								'popup_description' => 'If you have a moment, please share why you are deactivating SureForms:',
								'show_on_screens'   => [ 'plugins' ],
								'plugin_version'    => SRFM_VER,
							],
						]
					),
					'hide_optin_checkbox' => true,
				],
			]
		);

		add_filter( 'bsf_core_stats', [ $this, 'add_srfm_analytics_data' ] );

		// Event tracking hooks.
		add_action( 'current_screen', [ $this, 'track_first_editor_open' ] );
		add_action( 'transition_post_status', [ $this, 'track_first_form_published' ], 10, 3 );
		add_action( 'save_post', [ $this, 'track_embed_styling_configured' ], 10, 2 );

		// Detect state-based events on admin load (dedup prevents repeat tracking).
		$this->detect_state_events();
	}

	/**
	 * Get the shared BSF_Analytics_Events instance.
	 *
	 * Uses SureForms' Helper option methods so data stays in the
	 * existing srfm_options row — zero migration required.
	 *
	 * @since 2.7.0
	 * @return \BSF_Analytics_Events
	 */
	public static function events() {
		if ( null === self::$events ) {
			if ( ! class_exists( 'BSF_Analytics_Events' ) ) {
				require_once SRFM_DIR . 'inc/lib/bsf-analytics/class-bsf-analytics-events.php';
			}

			self::$events = new \BSF_Analytics_Events(
				'sureforms',
				[
					'get'    => [ Helper::class, 'get_srfm_option' ],
					'update' => [ Helper::class, 'update_srfm_option' ],
				]
			);
		}
		return self::$events;
	}

	/**
	 * Callback function to add SureForms specific analytics data.
	 *
	 * @param array $stats_data existing stats_data.
	 * @since 1.4.0
	 * @return array
	 */
	public function add_srfm_analytics_data( $stats_data ) {
		$stats_data['plugin_data']['sureforms']                   = [
			'free_version'          => SRFM_VER,
			'site_language'         => get_locale(),
			'most_used_anti_spam'   => $this->most_used_anti_spam(),
			'user_status'           => $this->user_status(),
			'pointer_popup_clicked' => $this->pointer_popup_clicked(),
		];
		$stats_data['plugin_data']['sureforms']['numeric_values'] = [
			'total_forms'                => wp_count_posts( SRFM_FORMS_POST_TYPE )->publish ?? 0,
			'instant_forms_enabled'      => $this->instant_forms_enabled(),
			'forms_using_custom_css'     => $this->forms_using_custom_css(),
			'ai_generated_forms'         => $this->ai_generated_forms(),
			'ai_generated_payment_forms' => $this->ai_generated_forms( 'payments' ),
			'payment_forms'              => $this->get_payment_forms_count(),
			'total_entries'              => Entries::get_total_entries_by_status(),
			'restricted_forms'           => $this->get_restricted_forms(),
			'embed_styling_gb_default'   => self::embed_styling_gutenberg_count( 'default' ),
			'embed_styling_el_default'   => self::embed_styling_elementor_count( 'default' ),
			'embed_styling_br_default'   => self::embed_styling_bricks_count( 'default' ),
		];

		$stats_data['plugin_data']['sureforms'] = array_merge_recursive( $stats_data['plugin_data']['sureforms'], $this->global_settings_data() );
		// Add KPI tracking data.
		$kpi_data = $this->get_kpi_tracking_data();
		if ( ! empty( $kpi_data ) ) {
			$stats_data['plugin_data']['sureforms']['kpi_records'] = $kpi_data;
		}

		// Build learn progress snapshot.
		$this->get_learn_tracking_data();

		// Flush pending events into payload (only if any exist).
		$pending_events = self::events()->flush_pending();
		if ( ! empty( $pending_events ) ) {
			$stats_data['plugin_data']['sureforms']['events_record'] = $pending_events;
		}

		return $stats_data;
	}

	/**
	 * Return total number of forms using instant forms.
	 *
	 * @since 1.4.0
	 * @return int
	 */
	public function instant_forms_enabled() {
		$meta_query = [
			[
				'key'     => '_srfm_instant_form_settings',
				'value'   => '"enable_instant_form";b:1;',
				'compare' => 'LIKE',
			],
		];

		return $this->custom_wp_query_total_posts( $meta_query );
	}

	/**
	 * Return total number of ai generated forms.
	 *
	 * @param string $form_type Form type to check.
	 *
	 * @since 1.4.0
	 * @return int
	 */
	public function ai_generated_forms( $form_type = '' ) {
		$form_type  = empty( $form_type ) || ! is_string( $form_type ) ? '' : $form_type;
		$meta_query = [
			[
				'key'     => '_srfm_is_ai_generated',
				'value'   => '',
				'compare' => '!=', // Checks if the value is NOT empty.
			],
		];

		if ( 'payments' === $form_type ) {
			$search = 'wp:srfm/payment';
			return $this->custom_wp_query_total_posts_with_search( $meta_query, $search );
		}

		return $this->custom_wp_query_total_posts( $meta_query );
	}

	/**
	 * Return most used anti-spam type on this site.
	 *
	 * @since 1.4.4
	 * @return int
	 */
	public function most_used_anti_spam() {
		global $wpdb;

		// Attempt to get from cache first.
		$cache_key     = 'most_used_anti_spam';
		$cached_result = wp_cache_get( $cache_key, 'sureforms' );

		if ( false !== $cached_result ) {
			return $cached_result;
		}

		$meta_key = '_srfm_captcha_security_type';

		// Query to get the most used captcha type.
		// PHPCS: Ignore direct database query warning, as there is no built-in alternative.
    	// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
		$result = $wpdb->get_row(
			$wpdb->prepare(
				"
			SELECT meta_value, COUNT(meta_value) as count
			FROM {$wpdb->postmeta}
			WHERE meta_key = %s
			AND meta_value != ''
			GROUP BY meta_value
			ORDER BY count DESC
			LIMIT 1
		",
				$meta_key
			),
			ARRAY_A
		);

		$output = '';
		if ( $result && ! empty( $result['meta_value'] ) ) {
			switch ( $result['meta_value'] ) {
				case 'g-recaptcha':
					$output = 'Google reCAPTCHA';
					break;

				case 'cf-turnstile':
					$output = 'CloudFlare Turnstile';
					break;

				case 'hcaptcha':
					$output = 'hCaptcha';
					break;

				default:
					$output = '';
					break;
			}
		}

		// Store result in cache for 1 hour.
		wp_cache_set( $cache_key, $output, 'sureforms', HOUR_IN_SECONDS );

		return $output;
	}

	/**
	 * Returns total number of forms using custom css.
	 *
	 * @since 1.4.0
	 * @return int
	 */
	public function forms_using_custom_css() {
		$meta_query = [
			[
				'key'     => '_srfm_form_custom_css',
				'value'   => '',
				'compare' => '!=', // Checks if the value is NOT empty.
			],
		];

		return $this->custom_wp_query_total_posts( $meta_query );
	}

	/**
	 * Count Gutenberg srfm/form embed blocks using a specific formTheme.
	 *
	 * When formTheme is 'inherit' (the block.json default), WordPress does not
	 * serialize it in the block comment. So only 'default' and 'custom' appear.
	 *
	 * Counts individual embed blocks (not pages), since a single page can
	 * contain multiple form embeds each with different styling.
	 *
	 * @param string $theme Theme slug to count ('default' or 'custom').
	 * @since 2.7.0
	 * @return int
	 */
	public static function embed_styling_gutenberg_count( $theme ) {
		global $wpdb;

		$cache_key     = 'embed_styling_gb_' . $theme;
		$cached_result = wp_cache_get( $cache_key, 'sureforms' );

		if ( false !== $cached_result ) {
			return (int) $cached_result;
		}

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- No WP_Query alternative for cross-post-type content search with multiple LIKE conditions.
		$posts = $wpdb->get_col(
			$wpdb->prepare(
				"SELECT post_content FROM {$wpdb->posts}
				WHERE post_status = 'publish'
				AND post_content LIKE %s
				AND post_content LIKE %s",
				'%' . $wpdb->esc_like( 'wp:srfm/form' ) . '%',
				'%' . $wpdb->esc_like( '"formTheme":"' . $theme . '"' ) . '%'
			)
		);

		$count         = 0;
		$escaped_theme = preg_quote( $theme, '/' );
		foreach ( $posts as $content ) {
			$count += preg_match_all( '/<!--\s*wp:srfm\/form\s+\{[^}]*"formTheme"\s*:\s*"' . $escaped_theme . '"/', $content );
		}

		wp_cache_set( $cache_key, $count, 'sureforms', HOUR_IN_SECONDS );

		return $count;
	}

	/**
	 * Count Elementor sureforms_form widgets using a specific formTheme.
	 *
	 * Searches _elementor_data post meta for sureforms_form widgets with
	 * a non-inherit formTheme. Counts individual widgets (not pages).
	 *
	 * @param string $theme Theme slug to count ('default' or 'custom').
	 * @since 2.7.0
	 * @return int
	 */
	public static function embed_styling_elementor_count( $theme ) {
		global $wpdb;

		$cache_key     = 'embed_styling_el_' . $theme;
		$cached_result = wp_cache_get( $cache_key, 'sureforms' );

		if ( false !== $cached_result ) {
			return (int) $cached_result;
		}

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- No WP_Query alternative for post meta content search with multiple LIKE conditions.
		$meta_values = $wpdb->get_col(
			$wpdb->prepare(
				"SELECT pm.meta_value FROM {$wpdb->postmeta} pm
				INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id
				WHERE p.post_status = 'publish'
				AND pm.meta_key = '_elementor_data'
				AND pm.meta_value LIKE %s
				AND pm.meta_value LIKE %s",
				'%' . $wpdb->esc_like( 'sureforms_form' ) . '%',
				'%' . $wpdb->esc_like( '"formTheme":"' . $theme . '"' ) . '%'
			)
		);

		$count         = 0;
		$escaped_theme = preg_quote( $theme, '/' );
		foreach ( $meta_values as $json ) {
			// Count widget instances with the specific formTheme in the JSON.
			$count += preg_match_all( '/"widgetType"\s*:\s*"sureforms_form"[^}]*"formTheme"\s*:\s*"' . $escaped_theme . '"/', $json );
		}

		wp_cache_set( $cache_key, $count, 'sureforms', HOUR_IN_SECONDS );

		return $count;
	}

	/**
	 * Count Bricks sureforms elements using a specific formTheme.
	 *
	 * Searches _bricks_page_content_2 post meta (serialized PHP) for
	 * sureforms elements with a non-inherit formTheme. Counts individual elements.
	 *
	 * @param string $theme Theme slug to count ('default' or 'custom').
	 * @since 2.7.0
	 * @return int
	 */
	public static function embed_styling_bricks_count( $theme ) {
		global $wpdb;

		$cache_key     = 'embed_styling_br_' . $theme;
		$cached_result = wp_cache_get( $cache_key, 'sureforms' );

		if ( false !== $cached_result ) {
			return (int) $cached_result;
		}

		// Bricks stores element data as serialized PHP in _bricks_page_content_2.
		// In serialized format: s:9:"formTheme";s:7:"default" (for 'default' theme).
		$serialized_theme = sprintf( '"formTheme";s:%d:"%s"', strlen( $theme ), $theme );

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- No WP_Query alternative for post meta content search with multiple LIKE conditions.
		$meta_values = $wpdb->get_col(
			$wpdb->prepare(
				"SELECT pm.meta_value FROM {$wpdb->postmeta} pm
				INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id
				WHERE p.post_status = 'publish'
				AND pm.meta_key = '_bricks_page_content_2'
				AND pm.meta_value LIKE %s
				AND pm.meta_value LIKE %s",
				'%' . $wpdb->esc_like( '"sureforms"' ) . '%',
				'%' . $wpdb->esc_like( $serialized_theme ) . '%'
			)
		);

		$count                = 0;
		$escaped_serial_theme = preg_quote( $serialized_theme, '/' );
		foreach ( $meta_values as $data ) {
			$count += preg_match_all( '/' . $escaped_serial_theme . '/', $data );
		}

		wp_cache_set( $cache_key, $count, 'sureforms', HOUR_IN_SECONDS );

		return $count;
	}

	/**
	 * Return total number of restricted forms.
	 *
	 * @since 1.10.1
	 * @return int
	 */
	public function get_restricted_forms() {
		$meta_query = [
			[
				'key'     => '_srfm_form_restriction',
				'value'   => '"status":true',
				'compare' => 'LIKE',
			],
		];

		return $this->custom_wp_query_total_posts( $meta_query );
	}

	/**
	 * Generates global setting data for analytics
	 *
	 * @since 1.4.0
	 * @return array
	 */
	public function global_settings_data() {
		$global_data = [];

		$security_settings                                 = get_option( 'srfm_security_settings_options', [] );
		$global_data['boolean_values']['honeypot_enabled'] = isset( $security_settings['srfm_honeypot'] ) && true === $security_settings['srfm_honeypot'];

		$email_summary_data                                     = get_option( 'srfm_email_summary_settings_options', [] );
		$global_data['boolean_values']['email_summary_enabled'] = isset( $email_summary_data['srfm_email_summary'] ) && true === $email_summary_data['srfm_email_summary'];

		$global_data['boolean_values']['suretriggers_active'] = is_plugin_active( 'suretriggers/suretriggers.php' );

		$bsf_internal_referrer = get_option( 'bsf_product_referers', [] );
		if ( ! empty( $bsf_internal_referrer['sureforms'] ) ) {
			$global_data['internal_referer'] = $bsf_internal_referrer['sureforms'];
		} else {
			$global_data['internal_referer'] = '';
		}

		$general_settings                                    = get_option( 'srfm_general_settings_options', [] );
		$global_data['boolean_values']['ip_logging_enabled'] = ! empty( $general_settings['srfm_ip_log'] );

		$validation_messages                                        = get_option( 'srfm_default_dynamic_block_option', [] );
		$global_data['boolean_values']['custom_validation_message'] = ! empty( $validation_messages ) && is_array( $validation_messages );

		// Payment analytics - check if any payment method is enabled.
		$global_data['boolean_values']['stripe_enabled'] = $this->is_stripe_enabled();

		return $global_data;
	}

	/**
	 * Returns user status.
	 *
	 * @since 1.8.0
	 * @return string
	 */
	public function user_status() {
		// First, check if user_active is already set in srfm_options.
		if ( Helper::get_srfm_option( 'user_active', false ) ) {
			return 'active';
		}
		// Get up to 10 published SureForms.
		$forms = get_posts(
			[
				'post_type'      => SRFM_FORMS_POST_TYPE,
				'posts_per_page' => 10,
				'post_status'    => 'publish',
			]
		);
		if ( empty( $forms ) ) {
			return 'inactive';
		}
		foreach ( $forms as $form ) {
			if ( ! get_post_meta( $form->ID, '_astra_sites_imported_post', true ) ) {
				// Mark user as active in srfm_options.
				Helper::update_srfm_option( 'user_active', true );
				return 'active';
			}
		}
		return 'inactive';
	}

	/**
	 * Return pointer popup clicked status.
	 *
	 * @return string pointer click status.
	 * @since 1.8.0
	 */
	public function pointer_popup_clicked() {
		// Get both values from srfm_options.
		$accepted  = Helper::get_srfm_option( 'pointer_popup_accepted', false );
		$dismissed = Helper::get_srfm_option( 'pointer_popup_dismissed', false );
		// If neither action has occurred.
		if ( ! $accepted && ! $dismissed ) {
			return '';
		}

		// If both are set, return the most recent one.
		if ( $accepted && $dismissed ) {
			return $accepted > $dismissed ? 'accepted' : 'dismissed';
		}

		// If only one is set, return it.
		return $accepted ? 'accepted' : 'dismissed';
	}

	/**
	 * Runs a custom WP_Query to fetch the total number of posts matching the given meta query and optional search string.
	 *
	 * This function is used to count SureForms posts based on specific meta query conditions.
	 * Optionally, a search string can be included to further filter results by keyword match.
	 *
	 * @since 2.0.0
	 *
	 * @param array  $meta_query Meta query array for WP_Query.
	 * @param string $search     Optional. Search string for WP_Query. Default empty.
	 * @return int               The number of matching posts.
	 */
	public function custom_wp_query_total_posts_with_search( $meta_query = [], $search = '' ) {
		$args = [
			'post_type'      => SRFM_FORMS_POST_TYPE,
			'post_status'    => 'publish',
			'posts_per_page' => -1,
		];

		if ( ! empty( $meta_query ) && is_array( $meta_query ) ) {
			// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Meta query required as we need to fetch count of nested data.
			$args['meta_query'] = $meta_query;
		}

		// If search string is provided, add it to the query.
		if ( ! empty( $search ) ) {
			$args['s'] = sanitize_text_field( $search );
		}

		$query       = new \WP_Query( $args );
		$posts_count = $query->found_posts;

		wp_reset_postdata();

		return $posts_count;
	}

	/**
	 * Get the total number of forms that utilize payment blocks.
	 *
	 * This function searches for forms containing the payment block identifier
	 * ('wp:srfm/payment') to determine how many forms include payment capabilities.
	 *
	 * @since 2.0.0
	 * @return int The number of forms that contain payment blocks.
	 */
	public function get_payment_forms_count() {
		$search = 'wp:srfm/payment';
		// Runs a custom WP_Query to find the count of forms with payment block.
		return $this->custom_wp_query_total_posts_with_search( [], $search );
	}

	/**
	 * Track first time a user opens the form editor.
	 *
	 * @since 2.5.1
	 * @return void
	 */
	public function track_first_editor_open() {
		$screen = get_current_screen();
		if ( $screen && 'sureforms_form' === $screen->id ) {
			self::events()->track( 'first_form_editor_opened' );
		}
	}

	/**
	 * Track first time a form is published (activation event).
	 *
	 * @param string   $new_status New post status.
	 * @param string   $old_status Old post status.
	 * @param \WP_Post $post       Post object.
	 * @since 2.5.1
	 * @return void
	 */
	public function track_first_form_published( $new_status, $old_status, $post ) {
		if ( 'publish' !== $new_status || 'publish' === $old_status || SRFM_FORMS_POST_TYPE !== $post->post_type ) {
			return;
		}

		$is_ai       = ! empty( get_post_meta( $post->ID, '_srfm_is_ai_generated', true ) );
		$block_count = substr_count( $post->post_content, '<!-- wp:srfm/' );

		// Time-to-value: days between install and first form published.
		$install_time       = get_site_option( 'sureforms_usage_installed_time', 0 );
		$days_since_install = 0;
		if ( $install_time > 0 ) {
			$days_since_install = (int) floor( ( time() - $install_time ) / DAY_IN_SECONDS );
		}

		self::events()->track(
			'first_form_published',
			(string) $post->ID,
			[
				'is_ai_generated'    => (string) (int) $is_ai,
				'block_count'        => (string) $block_count,
				'days_since_install' => (string) $days_since_install,
			]
		);
	}

	/**
	 * Track embed styling configuration when a post/page is saved.
	 *
	 * Detects custom formTheme in Gutenberg blocks (post_content),
	 * Elementor widgets (_elementor_data meta), and Bricks elements
	 * (_bricks_page_content_2 meta). Re-tracks on each save by flushing
	 * the dedup flag.
	 *
	 * @param int      $post_id Post ID.
	 * @param \WP_Post $post    Post object.
	 * @since 2.7.0
	 * @return void
	 */
	public function track_embed_styling_configured( $post_id, $post ) {
		if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) {
			return;
		}

		if ( 'publish' !== $post->post_status ) {
			return;
		}

		$themes = [];
		$source = '';

		// Check Gutenberg blocks in post_content.
		if ( preg_match_all( '/<!--\s*wp:srfm\/form\s+\{[^}]*"formTheme"\s*:\s*"(?!inherit")([^"]*)"/', $post->post_content, $matches ) ) {
			$themes = array_count_values( $matches[1] );
			$source = 'gutenberg';
		}

		// Check Elementor widgets in _elementor_data meta.
		if ( empty( $themes ) ) {
			$elementor_data = get_post_meta( $post_id, '_elementor_data', true );
			if ( is_string( $elementor_data )
				&& preg_match_all( '/"widgetType"\s*:\s*"sureforms_form"[^}]*"formTheme"\s*:\s*"(?!inherit")([^"]*)"/', $elementor_data, $el_matches )
			) {
				$themes = array_count_values( $el_matches[1] );
				$source = 'elementor';
			}
		}

		// Check Bricks elements in _bricks_page_content_2 meta (serialized PHP).
		if ( empty( $themes ) ) {
			$bricks_data = get_post_meta( $post_id, '_bricks_page_content_2', true );
			if ( is_array( $bricks_data ) ) {
				$bricks_themes = self::extract_bricks_form_themes( $bricks_data );
				if ( ! empty( $bricks_themes ) ) {
					$themes = array_count_values( $bricks_themes );
					$source = 'bricks';
				}
			}
		}

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

		// Flush dedup so event is re-tracked on each meaningful save.
		self::events()->flush_pushed( [ 'embed_styling_configured' ] );

		self::events()->track(
			'embed_styling_configured',
			(string) $post_id,
			[
				'themes'      => $themes,
				'block_count' => array_sum( $themes ),
				'source'      => $source,
			]
		);
	}

	/**
	 * Extract non-inherit formTheme values from Bricks element data.
	 *
	 * Recursively walks the unserialized Bricks elements array looking for
	 * sureforms elements with custom formTheme settings.
	 *
	 * @param array<mixed> $elements Bricks elements array.
	 * @return array<string> List of formTheme values (non-inherit).
	 * @since 2.7.0
	 */
	private static function extract_bricks_form_themes( $elements ) {
		$themes = [];

		foreach ( $elements as $element ) {
			if ( ! is_array( $element ) ) {
				continue;
			}

			$name       = $element['name'] ?? '';
			$settings   = $element['settings'] ?? [];
			$form_theme = $settings['formTheme'] ?? '';

			if ( 'sureforms' === $name && ! empty( $form_theme ) && 'inherit' !== $form_theme ) {
				$themes[] = sanitize_text_field( $form_theme );
			}

			// Recurse into nested children.
			if ( ! empty( $element['children'] ) && is_array( $element['children'] ) ) {
				$themes = array_merge( $themes, self::extract_bricks_form_themes( $element['children'] ) );
			}
		}

		return $themes;
	}

	/**
	 * Check if any payment method is enabled.
	 *
	 * This function checks if any payment gateway is connected and enabled.
	 * Currently supports Stripe, but can be extended for other payment methods in the future.
	 *
	 * @since 2.0.0
	 * @return bool True if any payment method is enabled, false otherwise.
	 */
	private function is_stripe_enabled() {
		// Check if Stripe is connected.
		return class_exists( 'SRFM\Inc\Payments\Stripe\Stripe_Helper' ) && \SRFM\Inc\Payments\Stripe\Stripe_Helper::is_stripe_connected();
	}

	/**
	 * Runs custom WP_Query to fetch data as per requirement
	 *
	 * @param array $meta_query meta query array for WP_Query.
	 * @since 1.4.0
	 * @return int
	 */
	private function custom_wp_query_total_posts( $meta_query ) {

		$args = [
			'post_type'      => SRFM_FORMS_POST_TYPE,
			'post_status'    => 'publish',
			'posts_per_page' => -1,
			'meta_query'     => $meta_query, //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Meta query required as we need to fetch count of nested data.
		];

		$query       = new \WP_Query( $args );
		$posts_count = $query->found_posts;

		wp_reset_postdata();

		return $posts_count;
	}

	/**
	 * Get KPI tracking data for the last 2 days (excluding today).
	 *
	 * @since 2.4.0
	 * @return array KPI data organized by date.
	 */
	private function get_kpi_tracking_data() {
		$kpi_data = [];
		$today    = current_time( 'Y-m-d' );

		// Get data for yesterday and day before yesterday.
		for ( $i = 1; $i <= 2; $i++ ) {
			$date = gmdate( 'Y-m-d', strtotime( $today . ' -' . $i . ' days' ) );

			$kpi_data[ $date ] = [
				'numeric_values' => [
					'submissions' => $this->get_daily_submissions_count( $date ),
				],
			];
		}

		return $kpi_data;
	}

	/**
	 * Get daily submissions count for a specific date.
	 *
	 * @param string $date Date in Y-m-d format.
	 * @since 2.4.0
	 * @return int Daily submissions count.
	 */
	private function get_daily_submissions_count( $date ) {
		$start_date = $date . ' 00:00:00';
		$end_date   = $date . ' 23:59:59';

		$where_conditions = [
			[
				[
					'key'     => 'created_at',
					'compare' => '>=',
					'value'   => $start_date,
				],
				[
					'key'     => 'created_at',
					'compare' => '<=',
					'value'   => $end_date,
				],
			],
		];

		return Entries::get_instance()->get_total_count( $where_conditions );
	}

	/**
	 * Detect state-based events that can't use direct hooks.
	 * Uses dedup in self::events()->track() — safe to call repeatedly.
	 *
	 * @since 2.5.1
	 * @return void
	 */
	private function detect_state_events() {
		// plugin_activated: dedup in self::events()->track() ensures this fires only once.
		$bsf_referrers = get_option( 'bsf_product_referers', [] );
		$source        = ! empty( $bsf_referrers['sureforms'] ) ? $bsf_referrers['sureforms'] : 'self';
		self::events()->track( 'plugin_activated', SRFM_VER, [ 'source' => $source ] );

		// One-time: re-send onboarding_completed with full properties (v2).
		if ( ! Helper::get_srfm_option( 'onboarding_event_v2_flushed', false )
			&& \SRFM\Inc\Onboarding::get_instance()->get_onboarding_status() ) {
			self::events()->flush_pushed( [ 'onboarding_completed' ] );
			Helper::update_srfm_option( 'onboarding_event_v2_flushed', true );
		}

		// onboarding_completed: detect completed state with full onboarding details.
		if ( \SRFM\Inc\Onboarding::get_instance()->get_onboarding_status() ) {
			$onboarding_props     = [];
			$onboarding_analytics = Helper::get_srfm_option( 'onboarding_analytics', [] );

			if ( ! empty( $onboarding_analytics ) && is_array( $onboarding_analytics ) ) {
				if ( ! empty( $onboarding_analytics['skippedSteps'] ) && is_array( $onboarding_analytics['skippedSteps'] ) ) {
					$onboarding_props['skipped_steps'] = implode( ',', $onboarding_analytics['skippedSteps'] );
				}

				if ( isset( $onboarding_analytics['suremailInstalled'] ) ) {
					$onboarding_props['suremail_installed'] = (bool) $onboarding_analytics['suremailInstalled'] ? 'yes' : 'no';
				}

				if ( isset( $onboarding_analytics['accountConnected'] ) ) {
					$onboarding_props['account_connected'] = (bool) $onboarding_analytics['accountConnected'] ? 'yes' : 'no';
				}

				if ( isset( $onboarding_analytics['completed'] ) ) {
					$onboarding_props['completed'] = (bool) $onboarding_analytics['completed'] ? 'yes' : 'no';
				}

				if ( isset( $onboarding_analytics['exitedEarly'] ) ) {
					$onboarding_props['exited_early'] = (bool) $onboarding_analytics['exitedEarly'] ? 'yes' : 'no';
				}

				if ( ! empty( $onboarding_analytics['premiumFeatures']['selectedFeatures'] ) && is_array( $onboarding_analytics['premiumFeatures']['selectedFeatures'] ) ) {
					$premium                                       = array_filter(
						$onboarding_analytics['premiumFeatures']['selectedFeatures'],
						static function( $f ) {
							return 'ai-form-generation' !== $f && 'entries' !== $f;
						}
					);
					$onboarding_props['selected_premium_features'] = implode( ',', $premium );
					$onboarding_props['premium_features_count']    = (string) count( $premium );
				}
			}

			self::events()->track( 'onboarding_completed', '', $onboarding_props );
		}

		// stripe_connected: detect connection state.
		if ( class_exists( '\SRFM\Inc\Payments\Stripe\Stripe_Helper' )
			&& \SRFM\Inc\Payments\Stripe\Stripe_Helper::is_stripe_connected() ) {
			$mode = \SRFM\Inc\Payments\Stripe\Stripe_Helper::get_stripe_mode();
			self::events()->track( 'stripe_connected', ! empty( $mode ) ? $mode : 'live' );
		}

		// first_ai_form_generated: detect if any AI-generated form exists.
		// Guard with is_tracked() to skip the meta_query after the event is already tracked.
		if ( ! self::events()->is_tracked( 'first_ai_form_generated' ) ) {
			$ai_forms = get_posts(
				[
					'post_type'      => SRFM_FORMS_POST_TYPE,
					'post_status'    => 'any',
					'posts_per_page' => 1,
					'fields'         => 'ids',
					'meta_query'     => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Runs once per lifecycle via is_tracked guard.
						[
							'key'     => '_srfm_is_ai_generated',
							'value'   => '',
							'compare' => '!=',
						],
					],
				]
			);
			if ( ! empty( $ai_forms ) ) {
				self::events()->track( 'first_ai_form_generated' );
			}
		}

		// MCP / Abilities API first-enable events.
		$mcp_settings = get_option( 'srfm_mcp_settings_options', [] );

		if ( ! empty( $mcp_settings['srfm_abilities_api'] ) ) {
			self::events()->track( 'abilities_api_enabled' );
		}

		if ( ! empty( $mcp_settings['srfm_mcp_server'] ) ) {
			self::events()->track( 'mcp_server_enabled' );
		}
	}

	/**
	 * Get learn section progress aggregated across all users.
	 *
	 * Queries all users with srfm_learn_progress meta, counts completions
	 * per step. Called at analytics send time (bsf_core_stats filter).
	 *
	 * @since 2.8.0
	 * @return void
	 */
	private function get_learn_tracking_data() {
		// Only send when progress changed or event has never been tracked.
		$has_changed = get_transient( 'srfm_learn_progress_changed' );
		if ( ! $has_changed && self::events()->is_tracked( 'learn' ) ) {
			return;
		}

		$chapters    = Learn::get_chapters_structure();
		$step_counts = [];
		$total_steps = 0;

		// Initialize step counters from canonical structure.
		foreach ( $chapters as $chapter ) {
			if ( ! isset( $chapter['id'], $chapter['steps'] ) || ! is_array( $chapter['steps'] ) ) {
				continue;
			}
			foreach ( $chapter['steps'] as $step ) {
				if ( ! isset( $step['id'] ) ) {
					continue;
				}
				$step_counts[ $chapter['id'] . '/' . $step['id'] ] = 0;
				++$total_steps;
			}
		}

		// Query all users who have learn progress.
		$users = get_users(
			[
				'meta_key' => 'srfm_learn_progress', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Runs once per analytics send cycle; bounded by admin user count.
				'fields'   => 'ID',
			]
		);

		$users_with_progress   = 0;
		$users_fully_completed = 0;
		$max_completed         = 0;

		foreach ( $users as $user_id ) {
			$progress = get_user_meta( $user_id, 'srfm_learn_progress', true );
			if ( ! is_array( $progress ) || empty( $progress ) ) {
				continue;
			}

			$user_completed = 0;

			foreach ( $step_counts as $key => $count ) {
				[ $chapter_id, $step_id ] = explode( '/', $key );
				if ( ! empty( $progress[ $chapter_id ][ $step_id ] ) ) {
					$step_counts[ $key ] = $count + 1;
					++$user_completed;
				}
			}

			if ( $user_completed > 0 ) {
				++$users_with_progress;
			}
			if ( $user_completed === $total_steps ) {
				++$users_fully_completed;
			}
			$max_completed = max( $max_completed, $user_completed );
		}

		// Build flat properties — step keys use snake_case (hyphens → underscores).
		$properties = [];
		foreach ( $step_counts as $key => $count ) {
			$step_id = explode( '/', $key )[1];
			$properties[ str_replace( '-', '_', $step_id ) ] = (string) $count;
		}

		$properties['users_with_progress']   = (string) $users_with_progress;
		$properties['users_fully_completed'] = (string) $users_fully_completed;
		$properties['total_steps']           = (string) $total_steps;

		// Flush dedup so event re-tracks with latest snapshot.
		self::events()->flush_pushed( [ 'learn' ] );
		self::events()->track( 'learn', (string) $max_completed, $properties );

		// Clear the change flag after tracking.
		delete_transient( 'srfm_learn_progress_changed' );
	}

}


Current_dir [ WRITEABLE ] Document_root [ WRITEABLE ]


[ Back ]
NAME
SIZE
LAST TOUCH
USER
CAN-I?
FUNCTIONS
..
--
3 May 2026 12.43 PM
njinenkb / njinenkb
0755
assets
--
3 May 2026 12.43 PM
njinenkb / njinenkb
0755
admin.php
68.555 KB
3 May 2026 12.43 PM
njinenkb / njinenkb
0644
analytics.php
31.528 KB
3 May 2026 12.43 PM
njinenkb / njinenkb
0644
notice-manager.php
5.696 KB
3 May 2026 12.43 PM
njinenkb / njinenkb
0644

GRAYBYTE WORDPRESS FILE MANAGER @ 2026 CONTACT ME
Static GIF Static GIF