Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/google-analytics-dashboard-for-wp/gadwp.php
+++ b/google-analytics-dashboard-for-wp/gadwp.php
@@ -5,7 +5,7 @@
* Plugin URI: https://exactmetrics.com
* Description: Displays Google Analytics Reports and Real-Time Statistics in your Dashboard. Automatically inserts the tracking code in every page of your website.
* Author: ExactMetrics
- * Version: 9.1.2
+ * Version: 9.1.3
* Requires at least: 5.6.0
* Requires PHP: 7.2
* Author URI: https://exactmetrics.com/lite/?utm_source=liteplugin&utm_medium=pluginheader&utm_campaign=authoruri&utm_content=7%2E0%2E0
@@ -55,7 +55,7 @@
* @var string $version Plugin version.
*/
- public $version = '9.1.2';
+ public $version = '9.1.3';
/**
* Plugin file.
--- a/google-analytics-dashboard-for-wp/includes/admin/admin-assets.php
+++ b/google-analytics-dashboard-for-wp/includes/admin/admin-assets.php
@@ -285,7 +285,7 @@
'auth' => $auth_data,
'authed' => $site_auth || $ms_auth, // Boolean for admin bar compatibility
'plugin_version' => EXACTMETRICS_VERSION,
- 'wizard_url' => exactmetrics_get_onboarding_url(),
+ 'wizard_url' => exactmetrics_can_install_plugins() ? exactmetrics_get_onboarding_url() : '',
'rest_url' => get_rest_url(),
'rest_nonce' => wp_create_nonce( 'wp_rest' ),
// Direct API access (bypasses WordPress for performance).
@@ -929,7 +929,7 @@
'bearer_expires' => $bearer_expires,
// Sample data mode: when true, frontend should bypass direct API and use WP AJAX for sample data.
'sample_data_enabled' => apply_filters( 'exactmetrics_sample_data_enabled', false ),
- 'wizard_url' => is_admin() ? exactmetrics_get_onboarding_url() : '',
+ 'wizard_url' => exactmetrics_can_install_plugins() ? exactmetrics_get_onboarding_url() : '',
'addons' => $addons_active,
'addons_info' => $addons_info,
'activate_nonce' => wp_create_nonce( 'exactmetrics-activate' ),
--- a/google-analytics-dashboard-for-wp/includes/admin/class-exactmetrics-onboarding.php
+++ b/google-analytics-dashboard-for-wp/includes/admin/class-exactmetrics-onboarding.php
@@ -113,7 +113,20 @@
if ( empty( $provided_key ) || false === $stored_key || ! hash_equals( $stored_key, $provided_key ) ) {
return new WP_Error(
'exactmetrics_invalid_key',
- 'Invalid onboarding key',
+ esc_html__( 'Invalid onboarding key', 'google-analytics-dashboard-for-wp' ),
+ array( 'status' => 403 )
+ );
+ }
+
+ // Ensure the user who generated the key has plugin installation capability.
+ // Only enforce when the user ID transient is present; if it has been evicted
+ // from the object cache independently of the key transient, skip this check
+ // rather than blocking a legitimate upgrade flow.
+ $onboarding_user_id = exactmetrics_get_onboarding_user_id();
+ if ( $onboarding_user_id && ! exactmetrics_can_install_plugins( $onboarding_user_id ) ) {
+ return new WP_Error(
+ 'exactmetrics_insufficient_permissions',
+ esc_html__( 'Insufficient permissions', 'google-analytics-dashboard-for-wp' ),
array( 'status' => 403 )
);
}
--- a/google-analytics-dashboard-for-wp/includes/admin/wp-site-health.php
+++ b/google-analytics-dashboard-for-wp/includes/admin/wp-site-health.php
@@ -1,49 +0,0 @@
-<?php
-
-class ExactMetrics_WP_Site_Health {
- public function __construct() {
- add_filter( 'site_status_tests', array( $this, 'add_tests' ) );
- }
-
- public function add_tests( $tests ) {
- $tests['direct']['exactmetrics_dual_tracking'] = array(
- 'label' => __( 'ExactMetrics Dual Tracking', 'google-analytics-dashboard-for-wp' ),
- 'test' => array( $this, 'test_dual_tracking' ),
- );
-
- return $tests;
- }
-
- public function test_dual_tracking() {
-
- $has_v4_id = strlen( exactmetrics_get_v4_id() ) > 0;
-
- if ( $has_v4_id ) {
- return false;
- }
-
- $setup_link = add_query_arg( array(
- 'page' => 'exactmetrics_settings',
- 'exactmetrics-scroll' => 'exactmetrics-dual-tracking-id',
- 'exactmetrics-highlight' => 'exactmetrics-dual-tracking-id',
- ), admin_url( 'admin.php' ) );
-
- return array(
- 'label' => __( 'Enable Google Analytics 4', 'google-analytics-dashboard-for-wp' ),
- 'status' => 'critical',
- 'badge' => array(
- 'label' => __( 'ExactMetrics', 'google-analytics-dashboard-for-wp' ),
- 'color' => 'blue',
- ),
- 'description' => __( 'Starting July 1, 2023, Google's Universal Analytics (GA3) will not accept any new traffic or event data. Upgrade to Google Analytics 4 today to be prepared for the sunset.', 'google-analytics-dashboard-for-wp' ),
- 'actions' => sprintf(
- '<p><a href="%s" target="_blank" rel="noopener noreferrer">%s</a></p>',
- $setup_link,
- __( 'Set Up Dual Tracking', 'google-analytics-dashboard-for-wp' )
- ),
- 'test' => 'exactmetrics_dual_tracking',
- );
- }
-}
-
-new ExactMetrics_WP_Site_Health();
--- a/google-analytics-dashboard-for-wp/includes/connect.php
+++ b/google-analytics-dashboard-for-wp/includes/connect.php
@@ -24,7 +24,7 @@
public function hooks() {
add_action( 'wp_ajax_exactmetrics_connect_url', array( $this, 'generate_connect_url' ) );
- add_action( 'wp_ajax_nopriv_exactmetrics_connect_process', array( $this, 'process' ) );
+ add_action( 'wp_ajax_exactmetrics_connect_process', array( $this, 'process' ) );
}
/**
@@ -141,6 +141,11 @@
'</a>'
);
+ // Check for permissions.
+ if ( ! exactmetrics_can_install_plugins() ) {
+ wp_send_json_error( $error );
+ }
+
// verify params present (oth & download link).
$post_oth = ! empty( $_REQUEST['oth'] ) ? sanitize_text_field($_REQUEST['oth']) : '';
$post_url = ! empty( $_REQUEST['file'] ) ? sanitize_url($_REQUEST['file']) : '';
--- a/google-analytics-dashboard-for-wp/includes/frontend/frontend.php
+++ b/google-analytics-dashboard-for-wp/includes/frontend/frontend.php
@@ -161,6 +161,23 @@
}
/**
+ * Whether the built React admin bar bundle exists on disk.
+ *
+ * The top-level "Insights" admin bar button is interactive only when the
+ * React app is enqueued and mounts. If the bundle is missing (e.g. a
+ * release packaging gap), the rendered link has no click handler and
+ * appears broken to users. This helper lets callers gate on availability.
+ *
+ * @return bool
+ */
+function exactmetrics_admin_bar_assets_available() {
+ $version = exactmetrics_is_pro_version() ? 'pro' : 'lite';
+ $asset_file = EXACTMETRICS_PLUGIN_DIR . "{$version}/assets/admin-bar/index.asset.php";
+
+ return file_exists( $asset_file );
+}
+
+/**
* Add an admin bar menu item on the frontend.
*
* @return void
@@ -171,13 +188,26 @@
return;
}
+ // If the React admin bar bundle is missing, skip adding the button —
+ // otherwise it renders as an inert link and looks broken. The
+ // "Insights" entry under wp-logo (see em-admin.php) still works.
+ if ( ! exactmetrics_admin_bar_assets_available() ) {
+ return;
+ }
+
global $wp_admin_bar;
+ // Fallback href so the button degrades to navigation if the React
+ // app fails to mount for any reason (JS error, CSP, extension).
+ $reports_url = is_network_admin()
+ ? add_query_arg( 'page', 'exactmetrics_overview_report', network_admin_url( 'admin.php' ) )
+ : add_query_arg( 'page', 'exactmetrics_reports', admin_url( 'admin.php' ) );
+
$args = array(
'id' => 'exactmetrics_frontend_button',
'title' => '<span class="ab-icon dashicons-before dashicons-chart-bar"></span> ExactMetrics',
// Maybe allow translation?
- 'href' => '#',
+ 'href' => $reports_url,
);
if ( method_exists( $wp_admin_bar, 'add_menu' ) ) {
@@ -214,7 +244,7 @@
$version = exactmetrics_is_pro_version() ? 'pro' : 'lite';
$asset_file = EXACTMETRICS_PLUGIN_DIR . "{$version}/assets/admin-bar/index.asset.php";
- if (!file_exists($asset_file)) {
+ if ( ! exactmetrics_admin_bar_assets_available() ) {
return;
}
@@ -249,40 +279,52 @@
plugin_dir_path( EXACTMETRICS_PLUGIN_FILE ) . $version . '/languages'
);
- // Localize data (same structure as Vue version for compatibility)
- $page_title = is_singular() ? get_the_title() : exactmetrics_get_page_title();
- $site_auth = ExactMetrics()->auth->get_viewname();
- $ms_auth = is_multisite() && ExactMetrics()->auth->get_network_viewname();
-
- // Check if any of the other admin scripts are enqueued, if so, use their object.
- if ( ! wp_script_is( 'exactmetrics-vue-script' ) && ! wp_script_is( 'exactmetrics-vue-reports' ) && ! wp_script_is( 'exactmetrics-vue-widget' ) && ! wp_script_is( 'exactmetrics-vue3-custom-dashboard' ) ) {
- $reports_url = is_network_admin() ? add_query_arg( 'page', 'exactmetrics_overview_report', network_admin_url( 'admin.php' ) ) : add_query_arg( 'page', 'exactmetrics_reports', admin_url( 'admin.php' ) );
- wp_localize_script(
- 'exactmetrics-admin-bar',
- 'exactmetrics',
- array(
- 'ajax' => admin_url( 'admin-ajax.php' ),
- 'nonce' => wp_create_nonce( 'mi-admin-nonce' ),
- 'network' => is_network_admin(),
- 'assets' => plugins_url( $version . '/assets/admin-bar', EXACTMETRICS_PLUGIN_FILE ),
- 'addons_url' => is_multisite() ? network_admin_url( 'admin.php?page=exactmetrics_network#/addons' ) : admin_url( 'admin.php?page=exactmetrics_settings#/addons' ),
- 'page_id' => is_singular() ? get_the_ID() : false,
- 'page_title' => $page_title,
- 'plugin_version' => EXACTMETRICS_VERSION,
- 'shareasale_id' => exactmetrics_get_shareasale_id(),
- 'shareasale_url' => exactmetrics_get_shareasale_url( exactmetrics_get_shareasale_id(), '' ),
- 'is_admin' => is_admin(),
- 'reports_url' => $reports_url,
- 'authed' => $site_auth || $ms_auth,
- 'auth_connect_url' => is_network_admin() ? network_admin_url( 'index.php?page=exactmetrics-onboarding' ) : admin_url( 'index.php?page=exactmetrics-onboarding' ),
- 'getting_started_url' => is_multisite() ? network_admin_url( 'admin.php?page=exactmetrics_network#/about/getting-started' ) : admin_url( 'admin.php?page=exactmetrics_settings#/about/getting-started' ),
- 'wizard_url' => is_network_admin() ? network_admin_url( 'index.php?page=exactmetrics-onboarding' ) : admin_url( 'index.php?page=exactmetrics-onboarding' ),
- 'roles_manage_options' => exactmetrics_get_manage_options_roles(),
- 'user_roles' => $current_user->roles,
- 'roles_view_reports' => exactmetrics_get_option('view_reports'),
- )
- );
+ // Skip localizing the shared `exactmetrics` global if another MI app already owns it —
+ // otherwise this admin-bar payload would clobber config like relay_api_url / license / reporting_api.
+ $competing_handles = array(
+ 'exactmetrics-vue-script',
+ 'exactmetrics-vue-reports',
+ 'exactmetrics-vue-widget',
+ 'exactmetrics-vue3-custom-dashboard',
+ 'exactmetrics-vue3-reports',
+ );
+
+ foreach ( $competing_handles as $handle ) {
+ if ( wp_script_is( $handle ) ) {
+ return;
+ }
}
+
+ // Localize data (same structure as Vue version for compatibility)
+ $page_title = is_singular() ? get_the_title() : exactmetrics_get_page_title();
+ $site_auth = ExactMetrics()->auth->get_viewname();
+ $ms_auth = is_multisite() && ExactMetrics()->auth->get_network_viewname();
+ $reports_url = is_network_admin() ? add_query_arg( 'page', 'exactmetrics_overview_report', network_admin_url( 'admin.php' ) ) : add_query_arg( 'page', 'exactmetrics_reports', admin_url( 'admin.php' ) );
+ wp_localize_script(
+ 'exactmetrics-admin-bar',
+ 'exactmetrics',
+ array(
+ 'ajax' => admin_url( 'admin-ajax.php' ),
+ 'nonce' => wp_create_nonce( 'mi-admin-nonce' ),
+ 'network' => is_network_admin(),
+ 'assets' => plugins_url( $version . '/assets/admin-bar', EXACTMETRICS_PLUGIN_FILE ),
+ 'addons_url' => is_multisite() ? network_admin_url( 'admin.php?page=exactmetrics_network#/addons' ) : admin_url( 'admin.php?page=exactmetrics_settings#/addons' ),
+ 'page_id' => is_singular() ? get_the_ID() : false,
+ 'page_title' => $page_title,
+ 'plugin_version' => EXACTMETRICS_VERSION,
+ 'shareasale_id' => exactmetrics_get_shareasale_id(),
+ 'shareasale_url' => exactmetrics_get_shareasale_url( exactmetrics_get_shareasale_id(), '' ),
+ 'is_admin' => is_admin(),
+ 'reports_url' => $reports_url,
+ 'authed' => $site_auth || $ms_auth,
+ 'auth_connect_url' => is_network_admin() ? network_admin_url( 'index.php?page=exactmetrics-onboarding' ) : admin_url( 'index.php?page=exactmetrics-onboarding' ),
+ 'getting_started_url' => is_multisite() ? network_admin_url( 'admin.php?page=exactmetrics_network#/about/getting-started' ) : admin_url( 'admin.php?page=exactmetrics_settings#/about/getting-started' ),
+ 'wizard_url' => is_network_admin() ? network_admin_url( 'index.php?page=exactmetrics-onboarding' ) : admin_url( 'index.php?page=exactmetrics-onboarding' ),
+ 'roles_manage_options' => exactmetrics_get_manage_options_roles(),
+ 'user_roles' => $current_user->roles,
+ 'roles_view_reports' => exactmetrics_get_option('view_reports'),
+ )
+ );
}
add_action( 'wp_enqueue_scripts', 'exactmetrics_frontend_admin_bar_scripts' );
--- a/google-analytics-dashboard-for-wp/includes/ppc/google/class-exactmetrics-google-ads.php
+++ b/google-analytics-dashboard-for-wp/includes/ppc/google/class-exactmetrics-google-ads.php
@@ -167,6 +167,12 @@
public function reset_experience() {
check_ajax_referer('mi-admin-nonce', 'nonce');
+ if (! current_user_can('exactmetrics_save_settings')) {
+ wp_send_json_error(array(
+ 'message' => __('You do not have permission to reset the Google Ads experience.', 'google-analytics-dashboard-for-wp'),
+ ));
+ }
+
self::clear_data();
wp_send_json_success(array(
@@ -244,6 +250,12 @@
{
check_ajax_referer('mi-admin-nonce', 'nonce');
+ if (! current_user_can('exactmetrics_save_settings')) {
+ wp_send_json_error(array(
+ 'message' => __('You do not have permission to retrieve the Google Ads access token.', 'google-analytics-dashboard-for-wp'),
+ ));
+ }
+
$access_token_result = $this->get_access_token();
if (is_wp_error($access_token_result)) {
--- a/google-analytics-dashboard-for-wp/lite/assets/admin-bar/index.asset.php
+++ b/google-analytics-dashboard-for-wp/lite/assets/admin-bar/index.asset.php
@@ -0,0 +1 @@
+<?php return array('dependencies' => array('react-jsx-runtime', 'wp-element', 'wp-i18n'), 'version' => '60524db626247a37b49b');
--- a/google-analytics-dashboard-for-wp/lite/includes/popular-posts/class-popular-posts-widget.php
+++ b/google-analytics-dashboard-for-wp/lite/includes/popular-posts/class-popular-posts-widget.php
@@ -121,7 +121,7 @@
$html .= '<a href="' . esc_url($post['link']) . '">';
if ( ! empty( $theme_styles['image'] ) && ! empty( $post['image'] ) ) {
$html .= '<div class="exactmetrics-widget-popular-posts-image">';
- $html .= '<img src="' . esc_url($post['image']) . '" srcset=" ' . esc_attr($post['srcset']) . ' " alt="' . esc_attr( $post['title'] ) . '" />';
+ $html .= '<img src="' . esc_url($post['image']) . '" srcset=" ' . esc_attr($post['srcset']) . ' " alt="' . esc_attr( wp_strip_all_tags( $post['title'] ) ) . '" />';
$html .= '</div>';
}
$html .= '<div class="exactmetrics-widget-popular-posts-text">';
@@ -132,7 +132,7 @@
}
$html .= '<span class="exactmetrics-widget-popular-posts-title" ';
$html .= ! empty( $this->get_element_style( $theme, 'title', $atts ) ) ? 'style="' . esc_attr( $this->get_element_style( $theme, 'title', $atts ) ) . '"' : '';
- $html .= '>' . esc_html( $post['title'] ) . '</span>';
+ $html .= '>' . wp_kses_post( $post['title'] ) . '</span>';
$html .= '</div>'; // exactmetrics-widget-popular-posts-text.
$html .= '</a>';
$html .= '</li>';