diff --git a/CHANGELOG.md b/CHANGELOG.md index b722ea77..1c2d6cbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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.** diff --git a/library/Tiger/Google/Analytics.php b/library/Tiger/Google/Analytics.php index ff97ef63..60c467ec 100644 --- a/library/Tiger/Google/Analytics.php +++ b/library/Tiger/Google/Analytics.php @@ -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 @@ -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; } // ===================================================================================== diff --git a/library/Tiger/Version.php b/library/Tiger/Version.php index 47bf5794..be6cc320 100644 --- a/library/Tiger/Version.php +++ b/library/Tiger/Version.php @@ -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'; } diff --git a/modules/analytics/controllers/AdminController.php b/modules/analytics/controllers/AdminController.php index de3c800f..b8bbd84e 100644 --- a/modules/analytics/controllers/AdminController.php +++ b/modules/analytics/controllers/AdminController.php @@ -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'); } diff --git a/modules/analytics/languages/de/analytics.php b/modules/analytics/languages/de/analytics.php index dac1fac3..a975d7af 100644 --- a/modules/analytics/languages/de/analytics.php +++ b/modules/analytics/languages/de/analytics.php @@ -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.', diff --git a/modules/analytics/languages/en/analytics.php b/modules/analytics/languages/en/analytics.php index bdd2fa56..a0e82b7a 100644 --- a/modules/analytics/languages/en/analytics.php +++ b/modules/analytics/languages/en/analytics.php @@ -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.', diff --git a/modules/analytics/languages/es/analytics.php b/modules/analytics/languages/es/analytics.php index 08443f8b..605f871c 100644 --- a/modules/analytics/languages/es/analytics.php +++ b/modules/analytics/languages/es/analytics.php @@ -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.', diff --git a/modules/analytics/languages/fr/analytics.php b/modules/analytics/languages/fr/analytics.php index 937c0d71..da15b6de 100644 --- a/modules/analytics/languages/fr/analytics.php +++ b/modules/analytics/languages/fr/analytics.php @@ -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.', diff --git a/modules/analytics/languages/hi/analytics.php b/modules/analytics/languages/hi/analytics.php index 7fe84f3a..f31de4b4 100644 --- a/modules/analytics/languages/hi/analytics.php +++ b/modules/analytics/languages/hi/analytics.php @@ -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 खोलता है।', diff --git a/modules/analytics/languages/pt/analytics.php b/modules/analytics/languages/pt/analytics.php index 162fd364..82e0c44b 100644 --- a/modules/analytics/languages/pt/analytics.php +++ b/modules/analytics/languages/pt/analytics.php @@ -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.', diff --git a/modules/analytics/languages/tlh/analytics.php b/modules/analytics/languages/tlh/analytics.php index 38615218..6a833ccc 100644 --- a/modules/analytics/languages/tlh/analytics.php +++ b/modules/analytics/languages/tlh/analytics.php @@ -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.', diff --git a/modules/analytics/views/scripts/admin/dashboard.phtml b/modules/analytics/views/scripts/admin/dashboard.phtml index 0266dfe9..c6952be2 100644 --- a/modules/analytics/views/scripts/admin/dashboard.phtml +++ b/modules/analytics/views/scripts/admin/dashboard.phtml @@ -25,7 +25,7 @@
= $this->t('analytics.dashboard.not_connected_body') ?>
- = $this->t('analytics.dashboard.go_settings') ?> + = $this->t('analytics.dashboard.go_settings') ?> diff --git a/modules/analytics/views/scripts/admin/index.phtml b/modules/analytics/views/scripts/admin/index.phtml index 7a04f441..624c81aa 100644 --- a/modules/analytics/views/scripts/admin/index.phtml +++ b/modules/analytics/views/scripts/admin/index.phtml @@ -48,7 +48,7 @@ $mode = ($this->ga['mode'] === 'byo') ? 'byo' : 'broker';= $this->escape($this->t('analytics.gdata.title')) ?>
+