namespace WPForms\Integrations\UsageTracking;
use WPForms\Admin\Builder\Templates;
use WPForms\Integrations\AI\Helpers as AIHelpers;
use WPForms\Integrations\IntegrationInterface;
use WPForms\Integrations\LiteConnect\Integration;
* Usage Tracker functionality to understand what's going on client's sites.
class UsageTracking implements IntegrationInterface {
* The slug that will be used to save the option of Usage Tracker.
const SETTINGS_SLUG = 'usage-tracking-enabled';
* Indicate if current integration is allowed to load.
public function allow_load(): bool {
* Whether the Usage Tracking code is allowed to be loaded.
* @param bool $var Boolean value.
return (bool) apply_filters( 'wpforms_usagetracking_is_allowed', true ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
* Whether Usage Tracking is enabled.
public function is_enabled(): bool {
* Whether the Usage Tracking is enabled.
* @param bool $var Boolean value taken from the DB.
return (bool) apply_filters( 'wpforms_integrations_usagetracking_is_enabled', wpforms_setting( self::SETTINGS_SLUG ) ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
public function load() { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks
add_filter( 'wpforms_settings_defaults', [ $this, 'settings_misc_option' ], 4 );
// Deregister the action if option is disabled.
'wpforms_settings_updated',
if ( ! $this->is_enabled() ) {
( new SendUsageTask() )->cancel();
// Register the action handler only if enabled.
if ( $this->is_enabled() ) {
'wpforms_tasks_get_tasks',
static function ( $tasks ) {
$tasks[] = SendUsageTask::class;
* Add "Allow Usage Tracking" to WPForms settings.
* @param array $settings WPForms settings.
public function settings_misc_option( $settings ) {
$settings['misc'][ self::SETTINGS_SLUG ] = [
'id' => self::SETTINGS_SLUG,
'name' => esc_html__( 'Allow Usage Tracking', 'wpforms-lite' ),
'desc' => esc_html__( 'By allowing us to track usage data, we can better help you, as we will know which WordPress configurations, themes, and plugins we should test.', 'wpforms-lite' ),
* Get the User Agent string that will be sent to the API.
public function get_user_agent(): string {
return 'WPForms/' . WPFORMS_VERSION . '; ' . get_bloginfo( 'url' );
* Get data for sending to the server.
* @noinspection PhpUndefinedConstantInspection
* @noinspection PhpUndefinedFunctionInspection
public function get_data(): array {
$theme_data = wp_get_theme();
$activated_dates = get_option( 'wpforms_activated', [] );
$first_form_date = get_option( 'wpforms_forms_first_created' );
$forms = $this->get_all_forms();
$forms_total = count( $forms );
$form_templates_total = count( $this->get_all_forms( 'wpforms-template' ) );
$entries_total = $this->get_entries_total();
$form_fields_count = $this->get_form_fields_count( $forms );
// Generic data (environment).
'php_version' => PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION,
'wp_version' => get_bloginfo( 'version' ),
'mysql_version' => $wpdb->db_version(),
'server_version' => isset( $_SERVER['SERVER_SOFTWARE'] ) ? sanitize_text_field( wp_unslash( $_SERVER['SERVER_SOFTWARE'] ) ) : '',
'is_multisite' => is_multisite(),
'is_network_activated' => $this->is_active_for_network(),
'is_wpcom' => defined( 'IS_WPCOM' ) && IS_WPCOM,
'is_wpcom_vip' => ( defined( 'WPCOM_IS_VIP_ENV' ) && WPCOM_IS_VIP_ENV ) || ( function_exists( 'wpcom_is_vip' ) && wpcom_is_vip() ),
'is_wp_cache' => defined( 'WP_CACHE' ) && WP_CACHE,
'is_wp_rest_api_enabled' => $this->is_rest_api_enabled(),
'is_user_logged_in' => is_user_logged_in(),
'sites_count' => $this->get_sites_total(),
'active_plugins' => $this->get_active_plugins(),
'theme_name' => $theme_data->get( 'Name' ),
'theme_version' => $theme_data->get( 'Version' ),
'locale' => get_locale(),
'timezone_offset' => wp_timezone_string(),
// WPForms-specific data.
'wpforms_version' => WPFORMS_VERSION,
'wpforms_license_key' => wpforms_get_license_key(),
'wpforms_license_type' => $this->get_license_type(),
'wpforms_license_status' => $this->get_license_status(),
'wpforms_is_pro' => wpforms()->is_pro(),
'wpforms_entries_avg' => $this->get_entries_avg( $forms_total, $entries_total ),
'wpforms_entries_total' => $entries_total,
'wpforms_entries_last_7days' => $this->get_entries_total( '7days' ),
'wpforms_entries_last_30days' => $this->get_entries_total( '30days' ),
'wpforms_forms_total' => $forms_total,
'wpforms_form_fields_count' => $form_fields_count,
'wpforms_form_templates_total' => $form_templates_total,
'wpforms_form_antispam_stat' => $this->get_form_antispam_stat( $forms ),
'wpforms_challenge_stats' => get_option( 'wpforms_challenge', [] ),
'wpforms_lite_installed_date' => $this->get_installed( $activated_dates, 'lite' ),
'wpforms_pro_installed_date' => $this->get_installed( $activated_dates, 'pro' ),
'wpforms_builder_opened_date' => (int) get_option( 'wpforms_builder_opened_date', 0 ),
'wpforms_settings' => $this->get_settings( $forms ),
'wpforms_integration_active' => $this->get_forms_integrations( $forms ),
'wpforms_payments_active' => $this->get_payments_active( $forms ),
'wpforms_product_quantities' => [
'payment-single' => $this->count_fields_with_setting( $forms, 'payment-single', 'enable_quantity' ),
'payment-select' => $this->count_fields_with_setting( $forms, 'payment-select', 'enable_quantity' ),
'wpforms_order_summaries' => $this->count_fields_with_setting( $forms, 'payment-total', 'summary' ),
'wpforms_multiple_confirmations' => count( $this->get_forms_with_multiple_confirmations( $forms ) ),
'wpforms_multiple_notifications' => count( $this->get_forms_with_multiple_notifications( $forms ) ),
'wpforms_ajax_form_submissions' => count( $this->get_ajax_form_submissions( $forms ) ),
'wpforms_notification_count' => wpforms()->obj( 'notifications' )->get_count(),
'wpforms_stats' => $this->get_additional_stats(),
'wpforms_ai' => AIHelpers::is_used(),
'wpforms_ai_killswitch' => AIHelpers::is_disabled(),
'wpforms_disabled_entries_count' => count( $this->get_forms_with_disabled_entries( $forms ) ),
$data = $this->add_promotion_plugin_data( $data );
if ( ! empty( $first_form_date ) ) {
$data['wpforms_forms_first_created'] = $first_form_date;
if ( $data['is_multisite'] ) {
$data['url_primary'] = network_site_url();
* Adds promotional plugin data to the provided array.
* @param array $data An array of existing data.
* @return array Modified data array with promotional plugin information added, if applicable.
private function add_promotion_plugin_data( array $data ): array {
foreach ( $plugins as $plugin ) {
$source = (string) get_option( $plugin . '_source', '' );
$date = (int) get_option( $plugin . '_date', 0 );
if ( $date && strpos( $source, 'WPForms' ) !== false ) {
$data[ 'wpforms_' . $plugin . '_date' ] = $date;
* @since 1.7.2 Clarified the license type.
* @since 1.7.9 Return only the license type, not the status.
private function get_license_type(): string {
return wpforms()->is_pro() ? wpforms_get_license_type() : 'lite';
* Get the license status.
private function get_license_status(): string {
if ( ! wpforms()->is_pro() ) {
$license_type = wpforms_get_license_type();
$license_key = wpforms_get_license_key();
return empty( $license_key ) ? 'no license' : 'not verified';
if ( wpforms_setting( 'is_expired', false, 'wpforms_license' ) ) {
if ( wpforms_setting( 'is_disabled', false, 'wpforms_license' ) ) {
if ( wpforms_setting( 'is_invalid', false, 'wpforms_license' ) ) {
// The correct type is returned in get_license_type(), so we "collapse" them here to a single value.
if ( in_array( $license_type, [ 'basic', 'plus', 'pro', 'elite', 'ultimate', 'agency' ], true ) ) {
$license_type = 'correct';
* Get all settings, except those with sensitive data.
* @since 1.9.3 Added $forms parameter.
* @param array $forms List of forms.
private function get_settings( array $forms ): array {
// Remove keys with exact names that we don't need.
$settings = array_diff_key(
get_option( 'wpforms_settings', [] ),
'stripe-test-secret-key',
'stripe-test-publishable-key',
'stripe-live-secret-key',
'stripe-live-publishable-key',
'stripe-webhooks-secret-test',
'stripe-webhooks-secret-live',
'stripe-webhooks-id-test',
'stripe-webhooks-id-live',
'square-webhooks-id-sandbox',
'square-webhooks-id-live',
'square-webhooks-secret-sandbox',
'square-webhooks-secret-live',
'authorize_net-test-api-login-id',
'authorize_net-test-transaction-key',
'authorize_net-live-api-login-id',
'authorize_net-live-transaction-key',
'square-location-id-sandbox',
'square-location-id-production',
'geolocation-google-places-api-key',
'geolocation-algolia-places-application-id',
'geolocation-algolia-places-search-only-api-key',
'geolocation-mapbox-search-access-token',
// Remove keys with a vague names that we don't need.
foreach ( $settings as $key => $value ) {
if ( strpos( $key, 'validation-' ) !== false ) {
$lite_connect_data = get_option( Integration::get_option_name() );
// If lite connect has been restored, set lite connect data.
isset( $lite_connect_data['import']['status'] ) &&
$lite_connect_data['import']['status'] === 'done'
$data['lite_connect'] = [
'restore_date' => $lite_connect_data['import']['ended_at'],
'restored_entry_count' => Integration::get_entries_count(),
// Add Dropbox Delete Local Files setting usage count.
$data['dropbox_delete_local_files_setting_count'] = $this->get_dropbox_delete_local_files_setting_count( $forms );
// Add favorite templates to the settings array.
return array_merge( $data, $this->get_favorite_templates() );
* Get the count of forms with Delete Local Files active option for Dropbox.
* @param array $forms List of forms.
private function get_dropbox_delete_local_files_setting_count( array $forms ): int {
$delete_local_files_count = 0;
foreach ( $forms as $form ) {
// Check if the Dropbox integration is configured in the form.
if ( empty( $form->post_content['providers']['dropbox'] ) ) {
// Delete Local Files option is applied for all connections if applied,
// so it's enough to check the first connection only.
$connection = current( $form->post_content['providers']['dropbox'] );
if ( ! $connection || ! isset( $connection['delete_local_files'] ) ) {
++$delete_local_files_count;
return $delete_local_files_count;
* Get the list of active plugins.
private function get_active_plugins(): array {
if ( ! function_exists( 'get_plugins' ) ) {
include ABSPATH . '/wp-admin/includes/plugin.php';
$active = is_multisite() ?
array_merge( get_option( 'active_plugins', [] ), array_flip( get_site_option( 'active_sitewide_plugins', [] ) ) ) :
get_option( 'active_plugins', [] );
$plugins = array_intersect_key( get_plugins(), array_flip( $active ) );
static function ( $plugin ) {
if ( isset( $plugin['Version'] ) ) {
return $plugin['Version'];
* @param array $activated_dates Input array with dates.
* @param string $key Input key what you want to get.
private function get_installed( array $activated_dates, string $key ) {
if ( ! empty( $activated_dates[ $key ] ) ) {
return $activated_dates[ $key ];
* Number of forms with some integrations active.
* @param array $forms List of forms.
* @return array List of forms with active integrations count.
private function get_forms_integrations( array $forms ): array {
$integrations = array_map(
static function ( $form ) {
if ( empty( $form->post_content['providers'] ) ) {
$active_integrations = [];
foreach ( $form->post_content['providers'] as $provider_slug => $connections ) {
if ( ! empty( $connections ) ) {
$active_integrations[] = $provider_slug;
return $active_integrations;