# jetpack/16.2/jetpack_vendor/automattic/jetpack-stats/src/class-main.php

Jetpack – WP Security, Backup, Speed, &amp; Growth, version 16.2. 280 lines.

- Page: https://pluginprobe.com/plugins/jetpack/16.2/code/jetpack_vendor/automattic/jetpack-stats/src/class-main.php
- Raw: https://pluginprobe.com/plugins/jetpack/16.2/raw/jetpack_vendor/automattic/jetpack-stats/src/class-main.php
- Modified: 2026-09-01T16:55:44+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/jetpack/16.2/code/jetpack_vendor/automattic/jetpack-stats/src/class-main.php#L10-L20`.

```php
<?php
/**
 * Stats Main
 *
 * @package automattic/jetpack-stats
 */

namespace Automattic\Jetpack\Stats;

use Automattic\Jetpack\Connection\Manager as Connection_Manager;
use Automattic\Jetpack\Constants;
use Automattic\Jetpack\IP\Utils as IP_Utils;
use Automattic\Jetpack\Modules;
use Automattic\Jetpack\Stats\Abilities\Stats_Abilities;
use Automattic\Jetpack\Status;
use Automattic\Jetpack\Status\Visitor;
use WP_User;

/**
 * Stats Main class.
 *
 * Entrypoint for Stats.
 *
 * @since 0.1.0
 */
class Main {
	/**
	 * Stats version.
	 * Mostly needed for backwards compatibility.
	 */
	const STATS_VERSION = '9';

	/**
	 * Singleton Main instance.
	 *
	 * @var Main
	 **/
	private static $instance = null;

	/**
	 * Initializer.
	 * Used to configure the stats package, eg when called via the Config package.
	 *
	 * @return object
	 */
	public static function init() {
		if ( null === self::$instance ) {
			self::$instance = new Main();
		}

		return self::$instance;
	}

	/**
	 * Class constructor.
	 *
	 * @return void
	 */
	private function __construct() {
		/**
		 * This avoids conflicts when running Stats package with older versions of the Jetpack plugin.
		 *
		 * On JP version 11.5-a.2 the hooks below were removed from the Jetpack plugin and it is safe
		 * to register them in the Stats package.
		 */
		$jp_plugin_version = Constants::get_constant( 'JETPACK__VERSION' );
		if ( $jp_plugin_version && version_compare( $jp_plugin_version, '11.5-a.2', '<' ) ) {
			return;
		}
		// Generate the tracking code after wp() has queried for posts.
		add_action( 'template_redirect', array( __CLASS__, 'template_redirect' ), 1 );

		add_action( 'wp_enqueue_scripts', array( __CLASS__, 'hide_smile_css' ) );

		// Map stats caps.
		add_filter( 'map_meta_cap', array( __CLASS__, 'map_meta_caps' ), 10, 3 );

		XMLRPC_Provider::init();

		/*
		 * REST_Provider only registers its routes on REST init, so defer
		 * constructing it (and autoloading the class) until a REST request is
		 * served. A closure is used because rest_api_init passes the REST server
		 * to callbacks, which would otherwise be read as REST_Provider::init()'s
		 * $new_instance argument.
		 */
		add_action(
			'rest_api_init',
			static function () {
				REST_Provider::init();
			},
			0
		);
		Transient_Cleanup::init();

		// Clean up transient cron on module deactivation.
		add_action( 'jetpack_deactivate_module_stats', array( Transient_Cleanup::class, 'unschedule_cleanup' ) );

		// Set up package version hook.
		add_filter( 'jetpack_package_versions', __NAMESPACE__ . '\Package_Version::send_package_version_to_tracker' );

		// Register WP Abilities API surface. Gated behind the
		// `jetpack_wp_abilities_enabled` filter inside Registrar::init(),
		// which defaults to false — so this call is safe to make unconditionally
		// and still opt-in per-site until the flag is flipped.
		Stats_Abilities::init();
	}

	/**
	 * Checks if filter is set and dnt is enabled.
	 *
	 * @return bool
	 */
	public static function jetpack_is_dnt_enabled() {
		/**
		 * Filter the option which decides honor DNT or not.
		 *
		 * @module stats
		 * @since-jetpack 6.1.0
		 *
		 * @param bool false Honors DNT for clients who don't want to be tracked. Defaults to false. Set to true to enable.
		 */
		if ( false === apply_filters( 'jetpack_honor_dnt_header_for_stats', false ) ) {
			return false;
		}

		foreach ( $_SERVER as $name => $value ) {
			if ( 'http_dnt' === strtolower( $name ) && 1 === (int) $value ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Maps view_stats cap to read cap as needed.
	 *
	 * @access public
	 * @param mixed $caps Caps.
	 * @param mixed $cap Cap.
	 * @param mixed $user_id User ID.
	 * @return array Possibly mapped capabilities for meta capability.
	 */
	public static function map_meta_caps( $caps, $cap, $user_id ) {
		// Map view_stats to exists.
		if ( 'view_stats' === $cap ) {
			$user        = new WP_User( $user_id );
			$stats_roles = Options::get_option( 'roles' );

			// Is any of the user's roles in the available stats roles?
			if ( is_array( $stats_roles ) && ! empty( array_intersect( $user->roles, $stats_roles ) ) ) {
				$caps = array( 'read' );
			}
		}

		return $caps;
	}

	/**
	 * Stats Template Redirect.
	 *
	 * @access public
	 * @return void
	 */
	public static function template_redirect() {
		if ( ! self::should_track() ) {
			return;
		}

		add_action( 'wp_enqueue_scripts', array( Tracking_Pixel::class, 'enqueue_stats_script' ), 101 );
		add_action( 'wp_footer', array( Tracking_Pixel::class, 'add_amp_pixel' ), 101 );
		add_action( 'web_stories_print_analytics', array( Tracking_Pixel::class, 'add_amp_pixel' ), 101 );
	}

	/**
	 * CSS to hide the tracking pixel smiley.
	 * It is now hidden for everyone (used to be visible if you had set the hide_smile option).
	 *
	 * @access public
	 * @return void
	 */
	public static function hide_smile_css() {
		if ( ! self::should_track() ) {
			return;
		}

		wp_register_style( 'jetpack-stats', false, array(), Package_Version::PACKAGE_VERSION );
		wp_enqueue_style( 'jetpack-stats' );
		wp_add_inline_style( 'jetpack-stats', 'img#wpstats{display:none}' );
	}

	/**
	 * Whether we should add the tracking pixel.
	 *
	 * @return bool
	 */
	public static function should_track() {
		global $current_user;

		// Not connected sites should not generate tracking stats.
		if ( ! ( new Connection_Manager() )->is_connected() ) {
			return false;
		}

		// If the stats module is disabled we should not generate tracking stats.
		if ( ! ( new Modules() )->is_active( 'stats' ) ) {
			return false;
		}

		// Do not generate tracking stats for feeds, robots, embeds, previews
		// or to honour the DNT headers.
		if (
			is_feed()
			|| is_robots()
			|| is_embed()
			|| is_trackback()
			|| is_preview()
			|| self::jetpack_is_dnt_enabled()
		) {
			return false;
		}

		// Sites in Safe Mode should not generate tracking stats.
		$status = new Status();
		if ( $status->in_safe_mode() ) {
			return false;
		}

		// Should we be counting this user's views?
		if ( ! empty( $current_user->ID ) ) {
			$count_roles = Options::get_option( 'count_roles' );
			if ( ! is_array( $count_roles ) || ! array_intersect( $current_user->roles, $count_roles ) ) {
				return false;
			}
		}

		/**
		 * Allow excluding specific IP addresses from being tracked in Stats.
		 * Note: for this to work well, visitors' IP addresses must:
		 * - be stored and returned properly in IP address headers;
		 * - not be impacted by any caching setup on your site.
		 *
		 * @module stats
		 *
		 * @since-jetpack 10.6
		 *
		 * @param array $excluded_ips An array of IP address strings to exclude from tracking.
		 */
		$excluded_ips = (array) apply_filters( 'jetpack_stats_excluded_ips', array() );

		/*
		 * Visitor::get_ip() returns a normalized address, so normalize the configured list the
		 * same way before comparing. Without this an entry written as `::ffff:203.0.113.5` or
		 * with uppercase IPv6 hex would never match, and the site owner's traffic would be
		 * counted with no indication why. Non-strings are dropped: they could never match the
		 * string get_ip() returns under the strict comparison below.
		 */
		$excluded_ips = array_filter(
			array_map( array( IP_Utils::class, 'clean_ip' ), array_filter( $excluded_ips, 'is_string' ) )
		);

		/*
		 * Resolving the visitor address reads request headers and, on a site with brute force
		 * protection configured, a site option, so only do it when the normalized list still
		 * holds something to compare against. The filter is unset on almost every site, which
		 * makes this the common path.
		 */
		if ( ! empty( $excluded_ips ) ) {
			// Should we be counting views for this IP address?
			$current_user_ip = ( new Visitor() )->get_ip( true );
			if ( in_array( $current_user_ip, $excluded_ips, true ) ) {
				return false;
			}
		}

		return true;
	}
}

```
