is_enabled() ) { return; } $last_run = get_option( self::LAST_RUN ); // Make sure we do not run it more than once a day. if ( false !== $last_run && ( time() - $last_run ) < DAY_IN_SECONDS ) { return; } wp_remote_post( $this->get_url(), array( 'timeout' => 10, 'headers' => array( 'Content-Type' => 'application/json; charset=utf-8', ), 'user-agent' => $this->get_user_agent(), 'body' => wp_json_encode( $this->get_data() ), ) ); // If we have completed successfully, recheck in 1 week. update_option( self::LAST_RUN, time() ); } /** * Gets the URL for the usage tracking api. * * @return string The URL to use for the api requests. * @since 1.0.0 */ private function get_url() { if ( defined( 'WPCONSENT_USAGE_TRACKING_URL' ) ) { return WPCONSENT_USAGE_TRACKING_URL; } return $this->url; } /** * Retrieve the data to send to the usage tracking api. * * @return array * @since 1.0.0 */ public function get_data() { global $wpdb; $theme_data = wp_get_theme(); $activated = get_option( 'wpconsent_activated', array() ); $installed_date = isset( $activated['wpconsent'] ) ? $activated['wpconsent'] : null; $data = array( // Generic data (environment). 'url' => home_url(), '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_ssl' => is_ssl(), 'is_multisite' => is_multisite(), 'sites_count' => function_exists( 'get_blog_count' ) ? (int) get_blog_count() : 1, 'active_plugins' => $this->get_active_plugins(), 'theme_name' => $theme_data->name, 'theme_version' => $theme_data->version, 'user_count' => function_exists( 'get_user_count' ) ? get_user_count() : null, 'locale' => get_locale(), 'timezone_offset' => wp_timezone_string(), // WPConsent specific data. 'wpconsent_version' => WPCONSENT_VERSION, 'wpconsent_license_key' => null, 'wpconsent_license_type' => null, 'wpconsent_is_pro' => false, 'wpconsent_lite_installed_date' => $installed_date, ); // Add WPConsent settings and data. $data = array_merge( $data, $this->get_wpconsent_data() ); return $data; } /** * Return a list of active plugins. * * @return array An array of active plugin data. * @since 1.0.0 */ private function get_active_plugins() { if ( ! function_exists( 'get_plugins' ) ) { include ABSPATH . '/wp-admin/includes/plugin.php'; } $active = get_option( 'active_plugins', array() ); $plugins = array_intersect_key( get_plugins(), array_flip( $active ) ); return array_map( static function ( $plugin ) { if ( isset( $plugin['Version'] ) ) { return $plugin['Version']; } return 'Not Set'; }, $plugins ); } /** * Get the User Agent string that will be sent to the API. * * @return string * @since 1.0.0 */ public function get_user_agent() { return 'WPConsent/' . WPCONSENT_VERSION . '; ' . get_bloginfo( 'url' ); } /** * Get the WPConsent data - settings, languages, geo rules, DNT, etc. * * @return array */ public function get_wpconsent_data() { return array_merge( $this->get_wpconsent_stats(), $this->get_settings_data(), $this->get_geo_rules_data(), $this->get_languages_data() ); } /** * Get the WPConsent settings data, filtered to remove sensitive/unnecessary data. * * @return array */ protected function get_settings_data() { $settings = wpconsent()->settings->get_options(); return array( 'wpconsent_settings' => $this->filter_settings( $settings ), ); } /** * Filter settings by removing ignored fields and processing special cases. * * @param array $settings The raw settings array. * @return array Filtered settings. */ protected function filter_settings( $settings ) { // Get the list of fields to ignore. $ignored_fields = $this->get_ignored_setting_fields(); // Start with all settings. $filtered = $settings; foreach ( $ignored_fields as $field ) { unset( $filtered[ $field ] ); } $filtered = $this->remove_language_content( $filtered ); unset( $filtered['geolocation_groups'] ); unset( $filtered['enabled_languages'] ); $filtered = $this->sanitize_settings( $filtered ); // Allow filtering. return apply_filters( 'wpconsent_usage_tracking_filtered_settings', $filtered, $settings ); } /** * Get the list of setting fields to ignore from tracking. * * @return array Array of setting keys to exclude. */ protected function get_ignored_setting_fields() { $ignored = array( // Banner content - we don't want to track user's custom messages. 'banner_message', 'accept_button_text', 'cancel_button_text', 'preferences_button_text', 'preferences_panel_title', 'preferences_panel_description', 'cookie_policy_title', 'cookie_policy_text', 'save_preferences_button_text', 'close_button_text', 'content_blocking_placeholder_text', // Banner styling - purely aesthetic, not useful for analytics. 'banner_background_color', 'banner_text_color', 'banner_accept_bg', 'banner_accept_color', 'banner_cancel_bg', 'banner_cancel_color', 'banner_preferences_bg', 'banner_preferences_color', 'button_order', // Banner logo - sensitive/personal. 'banner_logo', // Cookie table headers - content. 'cookie_table_header_name', 'cookie_table_header_description', 'cookie_table_header_duration', 'cookie_table_header_service_url', // Internal flags. 'has_auto_populated_pages', // Do Not Track settings. 'dnt_submit_text', 'dnt_field_first_name_label', 'dnt_field_last_name_label', 'dnt_field_email_label', 'dnt_field_address_label', 'dnt_field_zip_label', 'dnt_field_city_label', 'dnt_field_state_label', 'dnt_field_country_label', 'dnt_field_phone_label', ); /** * Filter the list of ignored setting fields. * * @param array $ignored Array of setting keys to exclude from tracking. */ return apply_filters( 'wpconsent_usage_tracking_ignored_settings', $ignored ); } /** * Remove language-specific content arrays from settings. * Language codes can be 2-5 characters (e.g., 'en', 'sq', 'pt_BR', 'zh_CN'). * * @param array $settings The settings array. * @return array Settings with language content removed. */ protected function remove_language_content( $settings ) { foreach ( $settings as $key => $value ) { // Language codes are typically 2-5 chars, contain letters, underscores, and hyphens. // and contain translation keys like 'banner_message', 'accept_button_text', etc. if ( $this->is_language_content_key( $key, $value ) ) { unset( $settings[ $key ] ); } } return $settings; } /** * Check if a settings key represents language content. * * @param string $key The setting key. * @param mixed $value The setting value. * @return bool True if this is language content. */ protected function is_language_content_key( $key, $value ) { // Handle numeric keys - these could be language content arrays. if ( is_numeric( $key ) ) { return true; } // Check if key looks like a language code 2-5 chars, letters/underscores (e.g., 'en', 'sq', 'pt_BR', 'zh_CN'). if ( preg_match( '/^[a-z]{2}([_-][a-z]{2,3})?$/i', $key ) ) { return true; } // Check if the value contains typical translation keys. return $this->array_contains_translation_keys( $value ); } /** * Check if an array contains translation keys. * * @param array $data The array to check. * @return bool True if the array contains translation keys. */ protected function array_contains_translation_keys( $data ) { if ( ! is_array( $data ) ) { return false; } // Check if the value contains typical translation keys. $translation_keys = array( // Banner content. 'banner_message', 'accept_button_text', 'cancel_button_text', 'preferences_button_text', 'preferences_panel_title', 'preferences_panel_description', 'content_blocking_placeholder_text', 'cookie_policy_title', ); foreach ( $translation_keys as $trans_key ) { if ( isset( $data[ $trans_key ] ) ) { return true; } } return false; } /** * Sanitize setting values for tracking. * * @param array $settings The filtered settings. * @return array Sanitized settings. */ protected function sanitize_settings( $settings ) { foreach ( $settings as $key => $value ) { // Convert page IDs to boolean (just track if configured). if ( in_array( $key, array( 'cookie_policy_page', 'dnt_page_id' ), true ) ) { $settings[ $key ] = ! empty( $value ) ? 1 : 0; continue; } // Convert manual_scan_pages array to count (don't track actual page IDs). if ( 'manual_scan_pages' === $key && is_array( $value ) ) { $settings[ $key ] = count( $value ); continue; } // Handle any other arrays (convert to JSON). if ( is_array( $value ) ) { $settings[ $key ] = wp_json_encode( $value ); } } return $settings; } /** * Get languages data for tracking. * * @return array */ protected function get_languages_data() { return array(); } /** * Get geolocation rules data for tracking. * * @return array */ protected function get_geo_rules_data() { return array(); } /** * Track WPConsent-specific data. * * @return array */ public function get_wpconsent_stats() { $wpconsent_data = array( 'wpconsent_total_categories' => 0, 'wpconsent_total_services' => 0, 'wpconsent_total_cookies' => 0, ); // Get categories and count them along with their services and cookies. $categories = wpconsent()->cookies->get_categories(); $total_categories = 0; $total_services = 0; $total_cookies = 0; foreach ( $categories as $category_slug => $category ) { $category_id = $category['id']; // Count this category (only if it has a valid ID). if ( $category_id > 0 ) { ++$total_categories; } // Get cookies for this category. $cookies = wpconsent()->cookies->get_cookies_by_category( $category_id ); // Skip if get_cookies_by_category returned an error. if ( ! is_array( $cookies ) ) { continue; } // Get services for this category. $services = wpconsent()->cookies->get_services_by_category( $category_id ); // Skip if get_services_by_category returned an error. if ( ! is_array( $services ) ) { $services = array(); } // Count cookies directly attached to this category. foreach ( $cookies as $cookie ) { // Ensure categories is an array before using it. if ( ! isset( $cookie['categories'] ) || ! is_array( $cookie['categories'] ) ) { continue; } if ( count( $cookie['categories'] ) === 1 && $cookie['categories'][0] === $category_id ) { ++$total_cookies; } } // Count services and their cookies. foreach ( $services as $service ) { ++$total_services; // Count cookies under this service. foreach ( $cookies as $cookie ) { // Ensure categories is an array before using it. if ( ! isset( $cookie['categories'] ) || ! is_array( $cookie['categories'] ) ) { continue; } if ( in_array( $service['id'], $cookie['categories'], true ) ) { ++$total_cookies; } } } } $wpconsent_data['wpconsent_total_categories'] = $total_categories; $wpconsent_data['wpconsent_total_services'] = $total_services; $wpconsent_data['wpconsent_total_cookies'] = $total_cookies; return $wpconsent_data; } }