Niniejsza instrukcja opisuje proces implementacji mechanizmu logowania zgód użytkowników do zewnętrznego arkusza Google Sheets z wykorzystaniem biblioteki vanilla-cookieconsent (wersja 3.0). Rozwiązanie jest dostosowane do stron statycznych hostowanych na GitHub Pages.
Ten krok tworzy “serwer”, który będzie odbierał informacje o zgodach.
// Dozwolona domena i kategorie zgód – odrzucamy śmieciowe żądania spoza strony
var ALLOWED_SITE = 'aitrain.pl';
var ALLOWED_CATEGORIES = ['necessary', 'analytics'];
// Ochrona przed formula injection w Google Sheets: wymusza tekst, przycina
// długość i neutralizuje znaki rozpoczynające formułę (=, +, -, @).
// Bez tego ktoś mógłby wysłać np. userAgent "=IMPORTXML(...)" i wykonać
// formułę przy otwarciu arkusza.
function sanitize(value, maxLength) {
var s = String(value === null || value === undefined ? '' : value).substring(0, maxLength);
if (/^[=+\-@]/.test(s)) {
s = "'" + s;
}
return s;
}
function doPost(e) {
try {
var data = JSON.parse(e.postData.contents);
// Walidacja: tylko zgody z naszej domeny
if (data.site !== ALLOWED_SITE) {
return ContentService.createTextOutput("Ignored").setMimeType(ContentService.MimeType.TEXT);
}
// Tylko znane kategorie zgód
var accepted = (Array.isArray(data.accepted) ? data.accepted : [])
.filter(function (c) { return ALLOWED_CATEGORIES.indexOf(c) !== -1; });
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
sheet.appendRow([
new Date(),
sanitize(data.site, 100),
sanitize(data.consentId, 100),
sanitize(accepted.join(', '), 100),
sanitize(data.userAgent, 300),
sanitize(data.policyVersion, 20)
]);
return ContentService.createTextOutput("Success").setMimeType(ContentService.MimeType.TEXT);
} catch(err) {
return ContentService.createTextOutput("Error").setMimeType(ContentService.MimeType.TEXT);
}
}
Uwaga bezpieczeństwa: endpoint jest publiczny (każdy zna URL ze źródła strony), dlatego powyższy kod waliduje domenę i kategorie oraz neutralizuje próby wstrzyknięcia formuł do arkusza. Po każdej zmianie kodu w Apps Script należy ponownie wdrożyć aplikację: Wdróż -> Zarządzaj wdrożeniami -> ikona ołówka -> Wersja: Nowa wersja -> Wdróż (URL pozostaje bez zmian).
cookie-banner.html)Utwórz plik w swoim projekcie, np. w katalogu _includes/cookie-banner.html (dla Jekyll) lub po prostu jako fragment kodu do wklejenia przed zamknięciem </body>.
Wklej poniższy kod, podmieniając TWOJ_URL_APPS_SCRIPT na adres skopiowany w punkcie 1.
<!-- Vanilla CookieConsent Stylesheet -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orestbida/cookieconsent@v3.0.0/dist/cookieconsent.css">
<!-- Vanilla CookieConsent Script -->
<script src="https://cdn.jsdelivr.net/gh/orestbida/cookieconsent@v3.0.0/dist/cookieconsent.umd.js"></script>
<script>
// --- KONFIGURACJA ---
// ID Twojego Google Analytics (zostaw puste lub usuń logikę GA jeśli nie używasz)
const GA_MEASUREMENT_ID = 'G-XXXXXXXXXX';
// Wklej tutaj URL wygenerowany w Google Apps Script
const LOGGING_ENDPOINT = 'TWOJ_URL_APPS_SCRIPT';
// Data ostatniej aktualizacji Polityki Prywatności
const POLICY_VERSION = '2026-01-04';
// --- FUNKCJE POMOCNICZE ---
// Funkcja ładująca skrypt GA
function loadGoogleAnalytics() {
if (!GA_MEASUREMENT_ID) return;
if (document.querySelector('script[src*="googletagmanager.com/gtag/js"]')) return;
var s = document.createElement('script');
s.async = true;
s.src = 'https://www.googletagmanager.com/gtag/js?id=' + encodeURIComponent(GA_MEASUREMENT_ID);
document.head.appendChild(s);
var init = document.createElement('script');
init.innerHTML = "window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', '" + GA_MEASUREMENT_ID + "');";
document.head.appendChild(init);
}
// Funkcja aktualizująca Google Consent Mode
function updateConsentMode(granted) {
if (typeof gtag === 'function') {
gtag('consent', 'update', {
'ad_storage': granted ? 'granted' : 'denied',
'analytics_storage': granted ? 'granted' : 'denied',
'functionality_storage': granted ? 'granted' : 'denied',
'personalization_storage': granted ? 'granted' : 'denied',
'security_storage': 'granted'
});
}
}
// Funkcja logująca zgody do Google Sheets
const logConsent = (cookie) => {
if (!LOGGING_ENDPOINT) return;
try {
fetch(LOGGING_ENDPOINT, {
method: 'POST',
mode: 'no-cors', // Ważne dla cross-origin do Google
body: JSON.stringify({
site: window.location.hostname,
consentId: cookie.consentId,
accepted: cookie.categories,
userAgent: navigator.userAgent,
policyVersion: POLICY_VERSION
})
});
} catch (e) {
console.error('Consent logging failed', e);
}
};
// --- INICJALIZACJA COOKIE CONSENT ---
CookieConsent.run({
guiOptions: {
consentModal: {
layout: 'box',
position: 'bottom right',
equalWeightButtons: true,
flipButtons: false
},
preferencesModal: {
layout: 'box',
position: 'right',
equalWeightButtons: true,
flipButtons: false
}
},
categories: {
necessary: {
readOnly: true
},
analytics: {}
},
language: {
default: 'pl',
autoDetect: 'browser',
translations: {
pl: {
consentModal: {
title: 'Szanujemy Twoją prywatność',
description: 'Używamy plików cookie do analityki i personalizacji zgodnie z Consent Mode v2. Możesz zaakceptować wszystkie lub dostosować wybór.',
acceptAllBtn: 'Akceptuj wszystkie',
acceptNecessaryBtn: 'Odrzuć opcjonalne',
showPreferencesBtn: 'Zarządzaj opcjami'
},
preferencesModal: {
title: 'Ustawienia plików cookie',
acceptAllBtn: 'Akceptuj wszystkie',
acceptNecessaryBtn: 'Odrzuć opcjonalne',
savePreferencesBtn: 'Zapisz ustawienia',
closeIconLabel: 'Zamknij',
serviceCounterLabel: 'Usługi',
sections: [
{
title: 'Niezbędne pliki cookie',
description: 'Te pliki są wymagane do poprawnego działania strony i zapewnienia bezpieczeństwa.',
linkedCategory: 'necessary'
},
{
title: 'Analityka i personalizacja',
description: 'Pliki cookie pozwalające nam analizować ruch na stronie.',
linkedCategory: 'analytics'
}
]
}
}
}
},
// Callback podczas pierwszego wyrażenia zgody
onFirstConsent: ({ cookie }) => {
logConsent(cookie); // Logowanie
if (CookieConsent.acceptedCategory('analytics')) {
updateConsentMode(true);
loadGoogleAnalytics();
} else {
updateConsentMode(false);
}
},
// Callback podczas kolejnych wizyt (opcjonalne, tutaj logika ta sama co wyżej)
onConsent: ({ cookie }) => {
if (CookieConsent.acceptedCategory('analytics')) {
updateConsentMode(true);
loadGoogleAnalytics();
}
},
// Callback podczas zmiany ustawień przez użytkownika
onChange: ({ changedCategories, cookie }) => {
logConsent(cookie); // Logowanie zmiany
if (CookieConsent.acceptedCategory('analytics')) {
updateConsentMode(true);
loadGoogleAnalytics();
} else {
updateConsentMode(false);
}
}
});
// Obsługa przycisku w footerze otwierającego ustawienia
document.addEventListener('DOMContentLoaded', function () {
document.body.addEventListener('click', function (e) {
if (e.target.matches('[data-cc="c-settings"], [data-cc="c-settings"] *')) {
e.preventDefault();
CookieConsent.showPreferences();
}
});
});
</script>
Upewnij się, że powyższy plik jest dołączony do każdego szablonu strony, najlepiej tuż przed zamknięciem znacznika </body>.
W Jekyll (_layouts/default.html):
<body>
<!-- Treść strony -->
<footer id="footer">
<div class="container">
<div class="row">
<div class="col-md-6">
<h4 class="footer-heading">Kontakt</h4>
<p><i class="fa fa-phone"></i> +48 691 300 548</p>
<p><i class="fa fa-envelope"></i> kontakt@aitrain.pl</p>
<p><i class="fa fa-check"></i> NIP: 583-154-14-45</p>
</div>
<div class="col-md-6">
<h4 class="footer-heading">Nawigacja</h4>
<div class="footer-links">
<a href="/">Strona główna</a> |
<a href="/#services">Usługi</a> |
<a href="/#portfolio-top">Portfolio</a> |
<a href="/#trainings">Szkolenia</a> |
<a href="/#contact">Kontakt</a> |
<a href="/polityka-prywatnosci/">Polityka prywatności</a> |
<a href="#" data-cc="c-settings">Ustawienia cookies</a>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12 text-center">
<hr class="light">
<p class="copyright">© 2026 AITRAIN | Wszelkie prawa zastrzeżone</p>
</div>
</div>
</div>
</footer>
<!-- Skrypty JS -->
<!-- Vanilla CookieConsent -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orestbida/cookieconsent@v3.0.0/dist/cookieconsent.css"
integrity="sha384-9KcNFt4axT+TNOVPHpwGHOm4Kv0UcMjdVya1kl+EPBQG+Vmqtz28cnx0f5PZYcnF" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/gh/orestbida/cookieconsent@v3.0.0/dist/cookieconsent.umd.js"
integrity="sha384-zM9A7bFtKjzCrGlAqAv3bj2Rkl6zz3vc62NMGZaEtfG4TKk8Ljrx6P7ixfoLzzIl" crossorigin="anonymous"></script>
<script>
// Funkcja ładująca skrypt GA
function loadGoogleAnalytics() {
var gaId = 'G-L7R7ST44Q3';
if (!gaId) return;
// Sprawdź czy już nie załadowany
if (document.querySelector('script[src*="googletagmanager.com/gtag/js"]')) return;
var s = document.createElement('script');
s.async = true;
s.src = 'https://www.googletagmanager.com/gtag/js?id=' + encodeURIComponent(gaId);
document.head.appendChild(s);
var init = document.createElement('script');
init.innerHTML = "window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', '" + gaId + "');";
document.head.appendChild(init);
}
// Funkcja aktualizująca Consent Mode
function updateConsentMode(granted) {
if (typeof gtag === 'function') {
gtag('consent', 'update', {
'ad_storage': granted ? 'granted' : 'denied',
'analytics_storage': granted ? 'granted' : 'denied',
'functionality_storage': granted ? 'granted' : 'denied',
'personalization_storage': granted ? 'granted' : 'denied',
'security_storage': 'granted'
});
}
}
// Zawsze aktualizuj tę datę po zmianach w treści polityki prywatności
const POLICY_VERSION = '2026-06-11';
const logConsent = (cookie) => {
try {
const URL = 'https://script.google.com/macros/s/AKfycbyrnJUqxRQzZWBddD9bT6NR6nXchJQwsEooJUmt4Vjmk8_gSnY6LufQyn8z3C9uZQ37/exec';
fetch(URL, {
method: 'POST',
mode: 'no-cors',
body: JSON.stringify({
site: window.location.hostname,
consentId: cookie.consentId,
accepted: cookie.categories,
userAgent: navigator.userAgent,
policyVersion: POLICY_VERSION
})
});
} catch (e) {
// Ciche logowanie błędu, aby nie straszyć użytkownika
console.error('Consent logging failed', e);
}
};
CookieConsent.run({
guiOptions: {
consentModal: {
layout: 'box',
position: 'bottom right',
equalWeightButtons: true,
flipButtons: false
},
preferencesModal: {
layout: 'box',
position: 'right',
equalWeightButtons: true,
flipButtons: false
}
},
categories: {
necessary: {
readOnly: true
},
analytics: {}
},
language: {
default: 'pl',
autoDetect: 'browser',
translations: {
pl: {
consentModal: {
title: 'Szanujemy Twoją prywatność',
description: 'Używamy plików cookie do analityki i personalizacji zgodnie z Consent Mode v2. Możesz zaakceptować wszystkie lub dostosować wybór.',
acceptAllBtn: 'Akceptuj wszystkie',
acceptNecessaryBtn: 'Odrzuć opcjonalne',
showPreferencesBtn: 'Zarządzaj opcjami'
},
preferencesModal: {
title: 'Ustawienia plików cookie',
acceptAllBtn: 'Akceptuj wszystkie',
acceptNecessaryBtn: 'Odrzuć opcjonalne',
savePreferencesBtn: 'Zapisz ustawienia',
closeIconLabel: 'Zamknij',
serviceCounterLabel: 'Usługi',
sections: [
{
title: 'Niezbędne pliki cookie',
description: 'Te pliki są wymagane do poprawnego działania strony i zapewnienia bezpieczeństwa.',
linkedCategory: 'necessary'
},
{
title: 'Analityka i personalizacja',
description: 'Pliki cookie pozwalające nam analizować ruch na stronie i dostosowywać treści do Twoich potrzeb (Google Analytics).',
linkedCategory: 'analytics',
cookieTable: {
headers: {
name: 'Nazwa',
domain: 'Serwis',
desc: 'Opis',
expiration: 'Wygasanie'
},
body: [
{
name: '_ga',
domain: 'google-analytics.com',
desc: 'Używany do rozróżniania użytkowników.',
expiration: '2 lata'
},
{
name: '_ga_*',
domain: 'google-analytics.com',
desc: 'Przechowuje stan sesji.',
expiration: '2 lata'
}
]
}
}
]
}
}
}
},
onFirstConsent: ({ cookie }) => {
// NOWE: Logowanie zgody
logConsent(cookie);
if (CookieConsent.acceptedCategory('analytics')) {
updateConsentMode(true);
loadGoogleAnalytics();
} else {
updateConsentMode(false);
}
},
onConsent: ({ cookie }) => {
// Logika dla ponownych wejść (jeśli potrzebna, tutaj to samo co przy zmianie)
if (CookieConsent.acceptedCategory('analytics')) {
updateConsentMode(true);
loadGoogleAnalytics();
}
},
onChange: ({ changedCategories, cookie }) => {
// NOWE: Logowanie zmiany zgody
logConsent(cookie);
if (CookieConsent.acceptedCategory('analytics')) {
updateConsentMode(true);
loadGoogleAnalytics();
} else {
// Jeśli użytkownik wycofał zgodę
updateConsentMode(false);
// Uwaga: wycofanie zgody w GA nie usuwa załadowanego skryptu, ale tagi przestaną wysyłać dane user-specific.
// Można ewentualnie przeładować stronę: window.location.reload();
}
}
});
// Manual binding for setting links to ensure they work
document.addEventListener('DOMContentLoaded', function () {
document.body.addEventListener('click', function (e) {
if (e.target.matches('[data-cc="c-settings"], [data-cc="c-settings"] *')) {
e.preventDefault();
CookieConsent.showPreferences();
}
});
});
</script>
</body>
Aby użytkownik mógł w każdej chwili zmienić swoje zgody, dodaj w stopce (_includes/footer.html) link ze specjalnym atrybutem data-cc="c-settings". Skrypt banneru automatycznie wykryje kliknięcie w ten element i otworzy okno preferencji.
<a href="#" data-cc="c-settings">Ustawienia cookies</a>
To wszystko! Po publikacji strony, każde zaakceptowanie lub zmiana zgód przez użytkownika wyśle nowy wiersz do Twojego arkusza Google Sheets.
Skontaktuj się ze mną, a odpowiem tak szybko, jak to możliwe!
Telefon:
+48 691 300 548
NIP: 583-154-14-45