Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,34 @@ All notable changes to **Tiger Core** (`webtigers/tiger-core`). Format follows

## [Unreleased]

## [1.6.3] — 2026-09-14

**Google Analytics connect flow brought in line with Google's OAuth branding and privacy guidelines.**

### Added

- **In-product Google-data notice** on the Analytics settings screen (TIGER-118). Before the connect
button, the screen now states the single scope requested (`analytics.readonly`), where the data goes
(rendered in the install's own dashboard — the WebTigers broker never sees or stores report data),
how long the connection lasts, how to revoke it, and links the privacy policy. Nine new
`analytics.*` keys in all seven locales.

### Changed

- **"Continue with Google" button follows Google's sign-in branding guidelines** (TIGER-118): the
supplied four-colour G mark as inline SVG on a neutral button, Roboto with a system fallback, light
and dark variants, hover/press states — replacing the `btn-primary` with a third-party icon-font
glyph. The remaining icon-font Google marks on the analytics screens were swapped for neutral icons.

### Fixed

- **Disconnect revokes the Google token** (TIGER-117). `Tiger_Google_Analytics::disconnect()` now
POSTs the refresh token to Google's revocation endpoint before clearing it locally, so a disconnect
ends Google's grant instead of only forgetting it on our side. Fail-soft: if Google is unreachable
the local token is still cleared and the screen reports that the grant should be revoked from the
Google Account permissions page. `DisconnectRevokeTest` exercises both paths, including a real 200
from a local HTTP server.

## [1.6.2] — 2026-09-13

**Module assets publish themselves.**
Expand Down
35 changes: 35 additions & 0 deletions library/Tiger/Google/Analytics.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class Tiger_Google_Analytics
const SCOPE = 'https://www.googleapis.com/auth/analytics.readonly';
const AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth';
const TOKEN_URL = 'https://oauth2.googleapis.com/token';
const REVOKE_URL = 'https://oauth2.googleapis.com/revoke';
const DATA_URL = 'https://analyticsdata.googleapis.com/v1beta/properties/%s:runReport';
const CACHE_TTL = 1800; // 30 min — GA data isn't real-time and the API is quota-limited

Expand Down Expand Up @@ -130,11 +131,45 @@ public static function saveOauthConfig($clientId, $clientSecret, $propertyId)
}

/** Forget the OAuth connection (drop the refresh token). Client creds + property id stay. */
/**
* Disconnect: REVOKE the grant at Google, then forget the token (TIGER-117).
*
* Forgetting alone — the previous behaviour — only made this install unable to use the grant; it
* stayed live in the user's Google account until they found it under myaccount.google.com/permissions.
* A person who clicks Disconnect reasonably believes they have withdrawn access, and the published
* privacy policy says so. Google's verification review requires the policy to match actual practice.
*
* Revocation needs only the token itself — no client secret — so the install calls Google directly
* in either mode; no broker endpoint is involved. FAIL-SOFT: if Google is unreachable the local
* token is still cleared, because leaving someone unable to disconnect would be worse. The outcome
* is returned so the screen can say which of the two things happened.
*
* @return array{revoked:bool} whether Google confirmed the revocation
*/
public static function disconnect()
{
$revoked = false;
$refresh = self::_refreshToken();
if ($refresh !== '') {
// Google returns 200 on success and on an already-invalid token; _http() yields null for
// anything non-2xx or unreachable. Either way we go on to clear locally.
$res = self::_http(static::_revokeUrl(), [
'method' => 'POST',
'headers' => ['Content-Type: application/x-www-form-urlencoded'],
'body' => http_build_query(['token' => $refresh]),
]);
$revoked = ($res !== null);
}
(new Tiger_Model_Config())->set(Tiger_Model_Config::SCOPE_GLOBAL, '', 'tiger.analytics.oauth.refresh_token_enc', '');
self::$_access = null;
@unlink(self::_cacheFile());
return ['revoked' => $revoked];
}

/** Google's revocation endpoint. A seam: tests point it at a dead port to exercise the fail-soft path. */
protected static function _revokeUrl()
{
return self::REVOKE_URL;
}

// =====================================================================================
Expand Down
2 changes: 1 addition & 1 deletion library/Tiger/Version.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@
class Tiger_Version
{
/** Current Tiger Core version. Keep in lockstep with the git tag cut for a release. */
const VERSION = '1.6.2';
const VERSION = '1.6.3';
}
10 changes: 8 additions & 2 deletions modules/analytics/controllers/AdminController.php
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,14 @@ public function disconnectAction()
{
$this->_helper->layout()->disableLayout();
$this->_helper->viewRenderer->setNoRender(true);
Tiger_Google_Analytics::disconnect();
$this->_flash('Disconnected from Google Analytics.', 'success');
// Two different outcomes, said differently: "we revoked it" and "we could not reach Google to
// revoke it — here is where you can" are not the same message (TIGER-117).
$out = Tiger_Google_Analytics::disconnect();
if (!empty($out['revoked'])) {
$this->_flash($this->view->t('analytics.disconnected'), 'success');
} else {
$this->_flash($this->view->t('analytics.disconnected_not_revoked'), 'alert');
}
$this->_redirect('/analytics/admin');
}

Expand Down
10 changes: 10 additions & 0 deletions modules/analytics/languages/de/analytics.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,16 @@
'analytics.oauth_secret_keep' => '•••••• (leer lassen, um es zu behalten)',

'analytics.view_dashboard' => 'Dashboard ansehen',
// OAuth verification requirements (TIGER-117/118)
'analytics.connect_google' => 'Weiter mit Google',
'analytics.disconnected' => 'Von Google Analytics getrennt. Die Autorisierung wurde bei Google widerrufen.',
'analytics.disconnected_not_revoked' => 'Von dieser Website getrennt, aber Google war nicht erreichbar, um die Autorisierung zu widerrufen. Sie können sie unter myaccount.google.com/permissions entfernen.',
'analytics.gdata.title' => 'Was die Verbindung mit Ihren Google-Daten macht',
'analytics.gdata.scope' => 'Nur-Lese-Zugriff auf Ihre Google-Analytics-Berichte — der Bereich analytics.readonly und sonst nichts.',
'analytics.gdata.where' => 'Berichte werden von dieser Website direkt bei Google abgerufen und nur Ihren Administratoren angezeigt.',
'analytics.gdata.retain' => 'WebTigers speichert Ihre Analysedaten nicht und sieht sie nicht; der Verbindungsdienst tauscht nur Tokens aus.',
'analytics.gdata.revoke' => 'Trennen Sie hier jederzeit, oder entziehen Sie den Zugriff in Ihrem Google-Konto.',
'analytics.gdata.policy_link' => 'Wie wir Google-Nutzerdaten behandeln — Datenschutzerklärung',
'analytics.disconnect' => 'Trennen',
'analytics.connect' => 'Google Analytics verbinden',
'analytics.connect_hint' => 'Speichert Ihre Einstellungen und öffnet dann Google zur Autorisierung.',
Expand Down
10 changes: 10 additions & 0 deletions modules/analytics/languages/en/analytics.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@
'analytics.oauth_secret_keep' => '•••••• (leave blank to keep)',

'analytics.view_dashboard' => 'View dashboard',
// OAuth verification requirements (TIGER-117/118)
'analytics.connect_google' => 'Continue with Google',
'analytics.disconnected' => 'Disconnected from Google Analytics. The authorization was revoked with Google.',
'analytics.disconnected_not_revoked' => 'Disconnected from this site, but Google could not be reached to revoke the authorization. You can remove it at myaccount.google.com/permissions.',
'analytics.gdata.title' => 'What connecting does with your Google data',
'analytics.gdata.scope' => 'Read-only access to your Google Analytics reports — the analytics.readonly scope, and nothing else.',
'analytics.gdata.where' => 'Reports are fetched by this site directly from Google and shown only to your administrators.',
'analytics.gdata.retain' => 'WebTigers does not store or see your analytics data; the connection service only exchanges tokens.',
'analytics.gdata.revoke' => 'Disconnect at any time here, or remove access in your Google Account.',
'analytics.gdata.policy_link' => 'How we handle Google user data — Privacy Policy',
'analytics.disconnect' => 'Disconnect',
'analytics.connect' => 'Connect Google Analytics',
'analytics.connect_hint' => 'Saves your settings, then opens Google to authorize.',
Expand Down
10 changes: 10 additions & 0 deletions modules/analytics/languages/es/analytics.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,16 @@
'analytics.oauth_secret_keep' => '•••••• (déjalo en blanco para conservarlo)',

'analytics.view_dashboard' => 'Ver panel',
// OAuth verification requirements (TIGER-117/118)
'analytics.connect_google' => 'Continuar con Google',
'analytics.disconnected' => 'Desconectado de Google Analytics. La autorización se revocó en Google.',
'analytics.disconnected_not_revoked' => 'Desconectado de este sitio, pero no se pudo contactar con Google para revocar la autorización. Puedes eliminarla en myaccount.google.com/permissions.',
'analytics.gdata.title' => 'Qué hace la conexión con tus datos de Google',
'analytics.gdata.scope' => 'Acceso de solo lectura a tus informes de Google Analytics — el permiso analytics.readonly y nada más.',
'analytics.gdata.where' => 'Los informes los obtiene este sitio directamente de Google y solo se muestran a tus administradores.',
'analytics.gdata.retain' => 'WebTigers no almacena ni ve tus datos de analítica; el servicio de conexión solo intercambia tokens.',
'analytics.gdata.revoke' => 'Desconéctate aquí en cualquier momento, o retira el acceso desde tu cuenta de Google.',
'analytics.gdata.policy_link' => 'Cómo tratamos los datos de usuario de Google — Política de privacidad',
'analytics.disconnect' => 'Desconectar',
'analytics.connect' => 'Conectar Google Analytics',
'analytics.connect_hint' => 'Guarda tu configuración y luego abre Google para autorizar.',
Expand Down
10 changes: 10 additions & 0 deletions modules/analytics/languages/fr/analytics.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,16 @@
'analytics.oauth_secret_keep' => '•••••• (laissez vide pour conserver)',

'analytics.view_dashboard' => 'Voir le tableau de bord',
// OAuth verification requirements (TIGER-117/118)
'analytics.connect_google' => 'Continuer avec Google',
'analytics.disconnected' => 'Déconnecté de Google Analytics. L\'autorisation a été révoquée auprès de Google.',
'analytics.disconnected_not_revoked' => 'Déconnecté de ce site, mais Google n\'a pas pu être joint pour révoquer l\'autorisation. Vous pouvez la retirer sur myaccount.google.com/permissions.',
'analytics.gdata.title' => 'Ce que la connexion fait de vos données Google',
'analytics.gdata.scope' => 'Accès en lecture seule à vos rapports Google Analytics — le périmètre analytics.readonly, et rien d\'autre.',
'analytics.gdata.where' => 'Les rapports sont récupérés par ce site directement auprès de Google et affichés uniquement à vos administrateurs.',
'analytics.gdata.retain' => 'WebTigers ne stocke ni ne voit vos données d\'analyse ; le service de connexion n\'échange que des jetons.',
'analytics.gdata.revoke' => 'Déconnectez-vous ici à tout moment, ou retirez l\'accès dans votre compte Google.',
'analytics.gdata.policy_link' => 'Comment nous traitons les données utilisateur Google — Politique de confidentialité',
'analytics.disconnect' => 'Déconnecter',
'analytics.connect' => 'Connecter Google Analytics',
'analytics.connect_hint' => 'Enregistre vos paramètres, puis ouvre Google pour autoriser.',
Expand Down
10 changes: 10 additions & 0 deletions modules/analytics/languages/hi/analytics.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,16 @@
'analytics.oauth_secret_keep' => '•••••• (बनाए रखने के लिए खाली छोड़ें)',

'analytics.view_dashboard' => 'डैशबोर्ड देखें',
// OAuth verification requirements (TIGER-117/118)
'analytics.connect_google' => 'Google के साथ जारी रखें',
'analytics.disconnected' => 'Google Analytics से डिस्कनेक्ट किया गया। Google पर प्राधिकरण निरस्त कर दिया गया।',
'analytics.disconnected_not_revoked' => 'इस साइट से डिस्कनेक्ट किया गया, लेकिन प्राधिकरण निरस्त करने के लिए Google से संपर्क नहीं हो सका। आप इसे myaccount.google.com/permissions पर हटा सकते हैं।',
'analytics.gdata.title' => 'कनेक्ट करने से आपके Google डेटा के साथ क्या होता है',
'analytics.gdata.scope' => 'आपकी Google Analytics रिपोर्टों तक केवल-पढ़ने की पहुँच — analytics.readonly स्कोप, और कुछ नहीं।',
'analytics.gdata.where' => 'रिपोर्टें यह साइट सीधे Google से लाती है और केवल आपके व्यवस्थापकों को दिखाती है।',
'analytics.gdata.retain' => 'WebTigers आपका एनालिटिक्स डेटा न संग्रहीत करता है न देखता है; कनेक्शन सेवा केवल टोकन का आदान-प्रदान करती है।',
'analytics.gdata.revoke' => 'यहाँ किसी भी समय डिस्कनेक्ट करें, या अपने Google खाते में पहुँच हटाएँ।',
'analytics.gdata.policy_link' => 'हम Google उपयोगकर्ता डेटा को कैसे संभालते हैं — गोपनीयता नीति',
'analytics.disconnect' => 'डिस्कनेक्ट करें',
'analytics.connect' => 'Google Analytics कनेक्ट करें',
'analytics.connect_hint' => 'आपकी सेटिंग्स सहेजता है, फिर अधिकृत करने के लिए Google खोलता है।',
Expand Down
10 changes: 10 additions & 0 deletions modules/analytics/languages/pt/analytics.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,16 @@
'analytics.oauth_secret_keep' => '•••••• (deixe em branco para manter)',

'analytics.view_dashboard' => 'Ver painel',
// OAuth verification requirements (TIGER-117/118)
'analytics.connect_google' => 'Continuar com o Google',
'analytics.disconnected' => 'Desconectado do Google Analytics. A autorização foi revogada no Google.',
'analytics.disconnected_not_revoked' => 'Desconectado deste site, mas não foi possível contatar o Google para revogar a autorização. Você pode removê-la em myaccount.google.com/permissions.',
'analytics.gdata.title' => 'O que a conexão faz com seus dados do Google',
'analytics.gdata.scope' => 'Acesso somente leitura aos seus relatórios do Google Analytics — o escopo analytics.readonly e nada mais.',
'analytics.gdata.where' => 'Os relatórios são obtidos por este site diretamente do Google e exibidos apenas aos seus administradores.',
'analytics.gdata.retain' => 'A WebTigers não armazena nem vê seus dados de análise; o serviço de conexão apenas troca tokens.',
'analytics.gdata.revoke' => 'Desconecte aqui a qualquer momento, ou remova o acesso na sua Conta do Google.',
'analytics.gdata.policy_link' => 'Como tratamos os dados de usuário do Google — Política de Privacidade',
'analytics.disconnect' => 'Desconectar',
'analytics.connect' => 'Conectar o Google Analytics',
'analytics.connect_hint' => 'Salva suas configurações e depois abre o Google para autorizar.',
Expand Down
10 changes: 10 additions & 0 deletions modules/analytics/languages/tlh/analytics.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,16 @@
'analytics.oauth_secret_keep' => '•••••• (leave blank to keep)',

'analytics.view_dashboard' => 'View dashboard',
// OAuth verification requirements (TIGER-117/118)
'analytics.connect_google' => 'Continue with Google',
'analytics.disconnected' => 'Disconnected from Google Analytics. The authorization was revoked with Google.',
'analytics.disconnected_not_revoked' => 'Disconnected from this site, but Google could not be reached to revoke the authorization. You can remove it at myaccount.google.com/permissions.',
'analytics.gdata.title' => 'What connecting does with your Google data',
'analytics.gdata.scope' => 'Read-only access to your Google Analytics reports — the analytics.readonly scope, and nothing else.',
'analytics.gdata.where' => 'Reports are fetched by this site directly from Google and shown only to your administrators.',
'analytics.gdata.retain' => 'WebTigers does not store or see your analytics data; the connection service only exchanges tokens.',
'analytics.gdata.revoke' => 'Disconnect at any time here, or remove access in your Google Account.',
'analytics.gdata.policy_link' => 'How we handle Google user data — Privacy Policy',
'analytics.disconnect' => 'Disconnect',
'analytics.connect' => 'Connect Google Analytics',
'analytics.connect_hint' => 'Saves your settings, then opens Google to authorize.',
Expand Down
2 changes: 1 addition & 1 deletion modules/analytics/views/scripts/admin/dashboard.phtml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<i class="fa-solid fa-chart-line fs-1 text-body-secondary mb-3 d-block"></i>
<h2 class="h5"><?= $this->t('analytics.dashboard.not_connected_title') ?></h2>
<p class="text-body-secondary"><?= $this->t('analytics.dashboard.not_connected_body') ?></p>
<a href="/analytics/admin" class="btn btn-primary"><i class="fa-brands fa-google me-2"></i><?= $this->t('analytics.dashboard.go_settings') ?></a>
<a href="/analytics/admin" class="btn btn-primary"><i class="fa-solid fa-gear me-2"></i><?= $this->t('analytics.dashboard.go_settings') ?></a>
</div>
</div>
<?php else: ?>
Expand Down
Loading
Loading