* Module Name: Jetpack Stats
* Module Description: Clear, concise traffic insights right in your WordPress dashboard.
* Recommendation Order: 2
* Requires Connection: Yes
* Module Tags: Jetpack Stats, Site Stats, Recommended
* Additional Search Queries: statistics, tracking, analytics, views, traffic, stats
* @package automattic/jetpack
use Automattic\Jetpack\Admin_UI\Admin_Menu;
use Automattic\Jetpack\Connection\Client;
use Automattic\Jetpack\Redirect;
use Automattic\Jetpack\Stats\Main as Stats;
use Automattic\Jetpack\Stats\Options as Stats_Options;
use Automattic\Jetpack\Stats\Tracking_Pixel as Stats_Tracking_Pixel;
use Automattic\Jetpack\Stats\WPCOM_Stats;
use Automattic\Jetpack\Stats\XMLRPC_Provider as Stats_XMLRPC;
use Automattic\Jetpack\Stats_Admin\Admin_Post_List_Column;
use Automattic\Jetpack\Stats_Admin\Dashboard as Stats_Dashboard;
use Automattic\Jetpack\Stats_Admin\Main as Stats_Main;
use Automattic\Jetpack\Status\Host;
use Automattic\Jetpack\Tracking;
if ( ! defined( 'ABSPATH' ) ) {
if ( defined( 'STATS_DASHBOARD_SERVER' ) ) {
define( 'STATS_DASHBOARD_SERVER', 'dashboard.wordpress.com' );
* Used to test for content vs script when parsing server-generated HTML.
const STATS_BODY_MARKER = '<div id="statchart"';
const STATS_CONTENT_MARKER = '<div class="gotonewdash">';
add_action( 'jetpack_modules_loaded', 'stats_load' );
Jetpack::enable_module_configurable( __FILE__ );
// Only run the callback for those who can see the stats.
if ( is_user_logged_in() && current_user_can( 'view_stats' ) ) {
add_action( 'admin_head', 'stats_admin_bar_head', 100 );
add_action( 'wp_head', 'stats_admin_bar_head', 100 );
Admin_Post_List_Column::register();
add_action( 'jetpack_admin_menu', 'stats_admin_menu' );
add_filter( 'pre_option_db_version', 'stats_ignore_db_version' );
// Filter for adding the Jetpack plugin version to tracking stats.
add_filter( 'stats_array', 'filter_stats_array_add_jp_version' );
require_once __DIR__ . '/stats/class-jetpack-stats-upgrade-nudges.php';
add_action( 'updating_jetpack_version', array( 'Jetpack_Stats_Upgrade_Nudges', 'unset_nudges_setting' ) );
* Checks if filter is set and dnt is enabled.
function jetpack_is_dnt_enabled() {
_deprecated_function( __METHOD__, 'jetpack-11.5', 'Automattic\Jetpack\Stats\Main::jetpack_is_dnt_enabled' );
return Stats::jetpack_is_dnt_enabled();
* Prevent sparkline img requests being redirected to upgrade.php.
* See wp-admin/admin.php where it checks $wp_db_version.
* @param mixed $version Version.
* @return string $version.
function stats_ignore_db_version( $version ) {
isset( $_GET['page'] ) && 'stats' === $_GET['page'] && // phpcs:ignore WordPress.Security.NonceVerification.Recommended
isset( $_GET['chart'] ) && strpos( $_GET['chart'], 'admin-bar-hours' ) === 0 // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput
* Maps view_stats cap to read cap as needed.
* @param mixed $caps Caps.
* @param mixed $user_id User ID.
* @return array Possibly mapped capabilities for meta capability.
function stats_map_meta_caps( $caps, $cap, $user_id ) {
_deprecated_function( __METHOD__, 'jetpack-11.5', 'Automattic\Jetpack\Stats\Main::map_meta_caps' );
return Stats::map_meta_caps( $caps, $cap, $user_id );
* Stats Template Redirect.
function stats_template_redirect() {
_deprecated_function( __METHOD__, 'jetpack-11.5', 'Automattic\Jetpack\Stats\Main::template_redirect' );
Stats::template_redirect();
function stats_build_view_data() {
_deprecated_function( __METHOD__, 'jetpack-11.5', 'Automattic\Jetpack\Stats\Tracking_Pixel::build_view_data' );
return Stats_Tracking_Pixel::build_view_data();
function stats_get_options() {
_deprecated_function( __METHOD__, 'jetpack-11.5', 'Automattic\Jetpack\Stats\Options::get_options' );
return Stats_Options::get_options();
* @param mixed $option Option.
function stats_get_option( $option ) {
_deprecated_function( __METHOD__, 'jetpack-11.5', 'Automattic\Jetpack\Stats\Options::get_option' );
return Stats_Options::get_option( $option );
* @param mixed $option Option.
* @param mixed $value Value.
function stats_set_option( $option, $value ) {
_deprecated_function( __METHOD__, 'jetpack-11.5', 'Automattic\Jetpack\Stats\Options::set_option' );
return Stats_Options::set_option( $option, $value );
* @param mixed $options Options.
function stats_set_options( $options ) {
_deprecated_function( __METHOD__, 'jetpack-11.5', 'Automattic\Jetpack\Stats\Options::set_options' );
return Stats_Options::set_options( $options );
* @param mixed $options Options.
function stats_upgrade_options( $options ) {
_deprecated_function( __METHOD__, 'jetpack-11.5', 'Automattic\Jetpack\Stats\Options::upgrade_options' );
return Stats_Options::upgrade_options( $options );
function stats_admin_menu() {
// If we're at an old Stats URL, redirect to the new one.
// Don't even bother with caps, menu_page_url(), etc. Just do it.
if ( 'index.php' === $pagenow && isset( $_GET['page'] ) && 'stats' === $_GET['page'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$redirect_url = str_replace( array( '/wp-admin/index.php?', '/wp-admin/?' ), '/wp-admin/admin.php?', isset( $_SERVER['REQUEST_URI'] ) ? filter_var( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : null );
$relative_pos = strpos( $redirect_url, '/wp-admin/' );
if ( false !== $relative_pos ) {
wp_safe_redirect( admin_url( substr( $redirect_url, $relative_pos + 10 ) ) );
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( ! ( new Host() )->is_woa_site() && isset( $_GET['enable_new_stats'] ) && '1' === $_GET['enable_new_stats'] ) {
// Passing true enables Odyssey Stats.
// We're ignorning the return value for now.
Stats_Main::update_new_stats_status( true );
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( ! Stats_Options::get_option( 'enable_odyssey_stats' ) || isset( $_GET['noheader'] ) ) {
// Show old Jetpack Stats interface for:
// - When the "enable_odyssey_stats" option is disabled.
// - When being shown in the adminbar outside of wp-admin.
$hook = Admin_Menu::add_menu( __( 'Stats', 'jetpack' ), __( 'Stats', 'jetpack' ), 'view_stats', 'stats', 'jetpack_admin_ui_stats_report_page_wrapper' );
add_action( "load-$hook", 'stats_reports_load' );
// Enable the new Odyssey Stats experience.
$stats_dashboard = new Stats_Dashboard();
$hook = Admin_Menu::add_menu( __( 'Stats', 'jetpack' ), __( 'Stats', 'jetpack' ), 'view_stats', 'stats', array( $stats_dashboard, 'render' ), 1 );
add_action( "load-$hook", array( $stats_dashboard, 'admin_init' ) );
function stats_admin_path() {
return Jetpack::module_configuration_url( __FILE__ );
function stats_reports_load() {
require_once __DIR__ . '/stats/class-jetpack-stats-upgrade-nudges.php';
Jetpack_Stats_Upgrade_Nudges::init();
wp_enqueue_script( 'jquery' );
wp_enqueue_script( 'postbox' );
wp_enqueue_script( 'underscore' );
Jetpack_Admin_Page::load_wrapper_styles();
add_action( 'admin_print_styles', 'stats_reports_css' );
if ( ! empty( $_GET['nojs'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$parsed = wp_parse_url( admin_url() );
// Remember user doesn't want JS.
setcookie( 'stnojs', '1', time() + 172800, $parsed['path'], COOKIE_DOMAIN, is_ssl(), true ); // 2 days.
if ( ! empty( $_COOKIE['stnojs'] ) ) {
// Detect if JS is on. If so, remove cookie so next page load is via JS.
add_action( 'admin_print_footer_scripts', 'stats_js_remove_stnojs_cookie' );
} elseif ( ! isset( $_GET['noheader'] ) && empty( $_GET['nojs'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
// Normal page load. Load page content via JS.
add_action( 'admin_print_footer_scripts', 'stats_script_dismiss_nudge_handler' );
* JavaScript to dismiss the Odyssey nudge.
function stats_script_dismiss_nudge_handler() {
<script type="text/javascript">
function stats_odyssey_dismiss_nudge() {
// Hide the nudge UI, effectively dismissing it.
var element = document.getElementById( "stats-odyssey-nudge-main" );
element.classList.toggle( "is-hidden" );
// Note we can provide a 'postponed_for' parameter to set the delay.
// Without a parameter it defaults to 30 days which is what we want here.
let nonce = <?php echo wp_json_encode( wp_create_nonce( 'wp_rest' ) ); ?>;
let url = <?php echo wp_json_encode( rest_url( '/jetpack/v4/stats-app/stats/notices' ) ); ?>;
headers: { "x-wp-nonce": nonce },
function stats_reports_css() {
#jp-stats-wrap, #jp-stats-report-bottom {
* Detect if JS is on. If so, remove cookie so next page load is via JS.
function stats_js_remove_stnojs_cookie() {
$parsed = wp_parse_url( admin_url() );
<script type="text/javascript">
document.cookie = 'stnojs=0; expires=Wed, 9 Mar 2011 16:55:50 UTC; path=<?php echo esc_js( $parsed['path'] ); ?>';
* Jetpack Admin Page Wrapper.
function jetpack_admin_ui_stats_report_page_wrapper() {
if ( ! isset( $_GET['noheader'] ) && empty( $_GET['nojs'] ) && empty( $_COOKIE['stnojs'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
Jetpack_Admin_Page::wrap_ui( 'stats_reports_page', array( 'is-wide' => true ) );
* @param bool $main_chart_only (default: false) Main Chart Only.
function stats_reports_page( $main_chart_only = false ) {
if ( isset( $_GET['dashboard'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
stats_dashboard_widget_content();
exit( 0 ); // @phan-suppress-current-line PhanPluginUnreachableCode -- Safer to include it even though stats_dashboard_widget_content() never returns.
$blog_id = Stats_Options::get_option( 'blog_id' );
$learn_url = Redirect::get_url( 'jetpack-stats-learn-more' );
$redirect_url = admin_url( 'admin.php?page=stats&enable_new_stats=1' );
$stats_bg_url = plugins_url( 'images/odyssey-upgrade/background.png', JETPACK__PLUGIN_FILE );
$stats_bg_gradient_url = plugins_url( 'images/odyssey-upgrade/gradient.png', JETPACK__PLUGIN_FILE );
if ( ! $main_chart_only && ! isset( $_GET['noheader'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
font-size: var( --font-body );
border: 1px solid var( --jp-gray-5 );
border-left-color: var( --jp-black );
.stats-odyssey-notice--content__highlighted {
border-left-color: var( --jp-red );
.stats-odyssey-notice--content {
padding: 24px 0 24px 30px;
.stats-odyssey-notice--content-header {
.stats-odyssey-notice--content-text {
.stats-odyssey-notice--image-container {
background-image: url("<?php echo esc_url( $stats_bg_url ); ?>"), url("<?php echo esc_url( $stats_bg_gradient_url ); ?>");
.stats-odyssey-notice--close-button {
background-color: transparent;
.stats-odyssey-notice--action-bar {
.stats-odyssey-notice--primary-button {
.stats-odyssey-notice--primary-button:hover {
background-color: #3c434a;
.is-primary-link:active {
font-size: var( --font-body );
.is-secondary-link:hover {
<h1><?php esc_html_e( 'Jetpack Stats', 'jetpack' ); ?>
if ( current_user_can( 'jetpack_manage_modules' ) ) :
$i18n_headers = jetpack_get_module_i18n( 'stats' );