Files
ODOO4projects.com/static/ads-consent.js
T
webmaster 6d486ad954 refactor(bf): hold the Google Ads id/label in the page, keep ads-consent.js generic
Tag id + conversion label now live as data-ads-id / data-ads-send-to on the banner
element, so the served HTML carries the tag config and the script stays reusable
(missing config -> inert). Re-verified: no cookie/request before consent, cookies +
tag after Accept, conversion + form submit on click, 700 ms fallback submit with the
tag blocked.
2026-11-02 09:13:16 +00:00

174 lines
5.8 KiB
JavaScript

/* ads-consent.js — Google Ads tag, gated behind a consent banner. Page-side only.
*
* Page: /odoo-black-friday/. Companion to /static/black-friday.js (which is untouched).
* The tag ID and the conversion label are read from the banner element's data-* attributes
* (#adsConsent[data-ads-id][data-ads-send-to]), so this file stays generic and reusable and
* the page holds the one copy of the config. Without those attributes the script stays inert.
*
* WHY A GATE: the Google Ads tag is NOT cookieless — it sets the advertising cookies
* _gcl_au / _gcl_aw. So nothing loads and no request fires until the visitor accepts.
* The default is DENY. Declining genuinely prevents the tag from loading.
*
* WHAT THE CONVERSION IS: a CLICK PROXY, not a sale. It fires when a buy form on this page
* is submitted. The real purchase is measured on the checkout host (20.odoo4projects.com)
* by odoo-dev. See wiki/concepts/conversion-tracking-contract. Never label it a sale.
*
* SAFETY: a conversion tag must never swallow a buy. On submit we fire the event and submit
* the form from the event callback, with a hard timeout that submits anyway. With JavaScript
* off the native POST forms submit untouched (no banner, no conversion, no error).
*/
(function () {
"use strict";
var STORE_KEY = "o4p_ads_consent"; // "granted" | "denied"
var SUBMIT_FALLBACK_MS = 700; // hard cap: submit even if the tag never answers
var cfg = null; // { id, sendTo } — read from the banner, never hard-coded
/* ---- the visitor's stored choice ---- */
function readChoice() {
try {
return window.localStorage.getItem(STORE_KEY);
} catch (e) {
return null;
}
}
function saveChoice(value) {
try {
window.localStorage.setItem(STORE_KEY, value);
} catch (e) {
/* private mode / storage disabled: treat as no choice, banner stays up */
}
}
function isGranted() {
return readChoice() === "granted";
}
/* ---- cookie removal (only used to revoke a prior grant) ---- */
function clearCookie(name) {
var host = window.location.hostname;
var bare = host.replace(/^www\./, "");
var paths = ["/", "/odoo-black-friday/"];
var domains = ["", "; domain=" + host, "; domain=." + bare];
for (var i = 0; i < paths.length; i++) {
for (var j = 0; j < domains.length; j++) {
document.cookie =
name + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=" + paths[i] + domains[j];
}
}
}
function clearAdsCookies() {
clearCookie("_gcl_au");
clearCookie("_gcl_aw");
}
/* ---- the tag: loaded ONLY on consent ---- */
function loadGtag() {
if (!cfg || window.__o4pAdsLoaded) return;
window.__o4pAdsLoaded = true;
window.dataLayer = window.dataLayer || [];
window.gtag =
window.gtag ||
function () {
window.dataLayer.push(arguments);
};
window.gtag("js", new Date());
window.gtag("config", cfg.id);
var s = document.createElement("script");
s.async = true;
s.src = "https://www.googletagmanager.com/gtag/js?id=" + cfg.id;
document.head.appendChild(s);
}
/* ---- the conversion proxy, fired on a buy-form submit ---- */
function onSubmit(e) {
var form = e.target;
if (!form || form.tagName !== "FORM") return;
var action = form.getAttribute("action") || "";
if (action.indexOf("/shop/post_basket") === -1) return;
// Not configured, no consent, or no tag: do nothing, let the native POST go through.
if (!cfg || !isGranted() || typeof window.gtag !== "function") return;
e.preventDefault();
var submitted = false;
function submitNow() {
if (submitted) return;
submitted = true;
form.submit(); // bypasses the submit event: no loop, no re-fire
}
try {
window.gtag("event", "conversion", {
send_to: cfg.sendTo,
event_callback: submitNow,
});
} catch (err) {
submitNow();
}
// Hard fallback: a blocked / slow / failed tag must never stop the purchase.
window.setTimeout(submitNow, SUBMIT_FALLBACK_MS);
}
/* ---- the banner ---- */
function bannerEl() {
return document.getElementById("adsConsent");
}
function showBanner() {
var el = bannerEl();
if (el) el.hidden = false;
}
function hideBanner() {
var el = bannerEl();
if (el) el.hidden = true;
}
function accept() {
saveChoice("granted");
loadGtag();
hideBanner();
}
function decline() {
saveChoice("denied");
clearAdsCookies();
hideBanner();
}
function init() {
document.addEventListener("submit", onSubmit, false);
var banner = bannerEl();
if (!banner) return;
// Config lives in the page. Missing config -> stay inert (no tag, no conversion).
var id = banner.getAttribute("data-ads-id");
var sendTo = banner.getAttribute("data-ads-send-to");
if (!id || !sendTo) return;
cfg = { id: id, sendTo: sendTo };
var choice = readChoice();
if (choice === "granted") {
loadGtag(); // returning visitor who already accepted: load straight away
} else if (choice !== "denied") {
showBanner(); // first visit (or no stored choice): default deny, ask
}
var acceptBtn = document.getElementById("adsConsentAccept");
if (acceptBtn) acceptBtn.addEventListener("click", accept);
var declineBtn = document.getElementById("adsConsentDecline");
if (declineBtn) declineBtn.addEventListener("click", decline);
// A real way to change the choice later.
var opener = document.getElementById("adsConsentOpen");
if (opener) {
opener.addEventListener("click", function (e) {
e.preventDefault();
showBanner();
});
}
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
})();