diff --git a/README.md b/README.md index 87fe757..fb9e960 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,40 @@ tarteaucitron.js ================ +Comply to the european cookie law is simple with the french *tarte au citron*. + +# What is this script? +The european cookie law regulates the management of cookies and you should ask your visitors their consent before exposing them to third party services. + +Clearly this script will: +- Disable all services by default, +- Display a banner on the first page view and a small one on other pages, +- Display a panel to allow or deny each services one by one, +- Activate services on the second page view if not denied, +- Store the consent in a cookie for 365 days. + +Bonus: +- Load service when user click on Allow (without reload of the page), +- Incorporate a fallback system (display a link instead of social button and a static banner instead of advertising). + +## Supported services +- Facebook +- FERank +- Google Adsense +- Google Analytics +- Google+ +- Linkedin +- Pinterest +- Twitter + +## Visitors outside the EU +In PHP for example, you can bypass all the script by setting this var `tarteaucitron.user.bypass = true;` if the visitor is not in the EU. + +## Tested on +- IE 6+ +- FF 3+ +- Safari 4+ +- Chrome 14+ +- Opera 10+ + +# Installation guide +[Visit opt-out.ferank.eu](https://opt-out.ferank.eu/) \ No newline at end of file diff --git a/css/tarteaucitron.css b/css/tarteaucitron.css new file mode 100644 index 0000000..3d17252 --- /dev/null +++ b/css/tarteaucitron.css @@ -0,0 +1,284 @@ +/*** + * Responsive layout for the control panel + */ +@media screen and (max-width:650px) { + #tarteaucitron .tarteaucitronLine .tarteaucitronName { + width: 90% !important; + } + + #tarteaucitron .tarteaucitronLine .tarteaucitronAsk { + float: left !important; + margin: 10px 15px 5px; + } +} + +@media screen and (max-width:1020px) { + #tarteaucitron { + top: 0; + } + + #tarteaucitron #tarteaucitronDisclaimer { + font-size: 14px; + } +} + +/*** + * Common value + */ +#tarteaucitron * { + zoom: 1; +} + +#tarteaucitron .clear { + clear: both; +} + +#tarteaucitron a { + color: #000; + text-decoration: underline; +} + +#tarteaucitronAlertBig a, #tarteaucitronAlertSmall a { + color: #fff; +} + +#tarteaucitron b { + font-size: 18px; + font-weight: 700; +} + +#tarteaucitron em { + cursor: pointer; + text-decoration: underline; + font-size: 12px; + font-style: italic; +} + +#tarteaucitron .tarteaucitronMore em { + cursor: text; + font-size: 14px; + text-decoration: none; +} + +/*** + * Root div added just before + */ +#tarteaucitronRoot { + left: 0; + position: absolute; + right: 0; + top: 0; + width: 100%; +} + +#tarteaucitronRoot * { + color: #333; + font-family: verdana; + font-size: 14px; +} + +/*** + * Control panel + */ +#tarteaucitronBack { + background: rgba(255, 255, 255, 0.7); + display: none; + height: 100%; + left: 0; + position: fixed; + top: 0; + width: 100%; + z-index: 2147483646; +} + +#tarteaucitron { + background: #333; + border: 0; + border-bottom: 50px solid; + display: none; + left: 0; + margin: 0 auto; + max-width: 1020px; + position: relative; + right: 0; + top: 0; + z-index: 2147483647; +} + +#tarteaucitron #tarteaucitronClosePanel { + color: #FFF; + cursor: pointer; + display: block; + text-decoration: underline; + font-family: verdana; + margin: 0px 15px 15px 0; + padding-top: 10px; + text-align: right; +} + +#tarteaucitron #tarteaucitronDisclaimer { + color: #fff; + font-size: 16px; + margin: 0 auto 30px; + max-width: 800px; + padding: 0 15px; + text-align: center; +} + +#tarteaucitron .tarteaucitronTitle { + background: rgb(81, 81, 81); + color: #FFF; + font-size: 16px; + padding: 5px 0px 5px 22px; + margin-top: 15px; +} + +#tarteaucitron .tarteaucitronLine { + background: #f5f5f5; + border-bottom: 1px solid #fff; + border-top: 1px solid #fff; + height: auto; + min-height: 100%; + overflow: hidden; + padding: 5px; +} + +#tarteaucitron .tarteaucitronLine .tarteaucitronName { + display: inline-block; + float: left; + margin-left: 15px; + width: 50%; +} + +#tarteaucitron .tarteaucitronLine .tarteaucitronAsk { + display: inline-block; + float: right; + margin: 7px 15px 0; + text-align: right; +} + +#tarteaucitron .tarteaucitronLine .tarteaucitronAsk .tarteaucitronAllow, +#tarteaucitron .tarteaucitronLine .tarteaucitronAsk .tarteaucitronDeny { + background: gray; + color: #fff; + cursor: pointer; + display: inline-block; + padding: 5px 10px; + text-align: center; + text-decoration: none; + width: auto; +} + +#tarteaucitron .tarteaucitronLine .tarteaucitronMore { + display: none; +} + + +/*** + * Big alert + */ +#tarteaucitronAlertBig { + background: #333; + color: #fff !important; + display: none; + font-size: 15px !important; + left: 0; + padding: 5px 5%; + position: fixed; + text-align: center; + top: 0; + width: 90%; + box-sizing: content-box; + z-index: 2147483645; +} + +#tarteaucitronAlertBig #tarteaucitronDisclaimerAlert { + font: 15px verdana; + color: #fff; +} + +#tarteaucitronAlertBig #tarteaucitronPersonalize { + background: #008300; + color: #fff; + cursor: pointer; + display: inline-block; + font-size: 16px; + padding: 5px 10px; + text-decoration: none; + margin-left: 7px; +} + +#tarteaucitronAlertBig #tarteaucitronCloseAlert { + font-size: 11px; + margin-left: 7px; + cursor: pointer; + color: #fff; +} + +/*** + * Small alert + */ +#tarteaucitronAlertSmall { + background: #333; + bottom: 0; + color: #fff !important; + cursor: pointer; + display: none; + font-size: 11px !important; + padding: 3px; + padding-right: 5px; + position: fixed; + right: 0; + z-index: 2147483645; +} + +#tarteaucitronAlertSmall #tarteaucitronDot { + background-color: #E63737; + border-radius: 10px; + display: inline-block; + height: 10px; + margin: 0px 5px 2px 5px; + vertical-align: middle; + width: 10px; +} + +/*** + * Fallback links for social networks + */ +.tac_share { + display: inline-block; + margin: 0; + padding: 3px 10px; + text-decoration: none; + font-size: 13px; + color: #fff !important; + text-align: center; +} + +.tac_share_twitter { + background-color: #4099ff; +} + +.tac_share_facebook { + background-color: #3b5998; +} + +.tac_share_googlep { + background-color: #D34836; +} + +.tac_share_linkedin { + background-color: #3399cc; +} + +.tac_share_pinterest { + background-color: #cb2027; +} + +.tac_share_digg { + background-color: #0093CC; +} + +.tac_share_reddit { + background-color: #2E81D5; +} \ No newline at end of file diff --git a/lang/tarteaucitron.de.js b/lang/tarteaucitron.de.js new file mode 100644 index 0000000..0c1af7d --- /dev/null +++ b/lang/tarteaucitron.de.js @@ -0,0 +1,17 @@ +/*global tarteaucitron */ +tarteaucitron.lang = { + "alertBig": "", + "alertSmall": "", + "personalize": "", + "close": "", + + "disclaimer": "", + "allow": "", + "deny": "", + "more": "", + "source": "", + + "ads": "", + "analytics": "", + "social": "" +}; \ No newline at end of file diff --git a/lang/tarteaucitron.en.js b/lang/tarteaucitron.en.js new file mode 100644 index 0000000..552e48c --- /dev/null +++ b/lang/tarteaucitron.en.js @@ -0,0 +1,17 @@ +/*global tarteaucitron */ +tarteaucitron.lang = { + "alertBig": "If you continue to browse this website, you are allowing all third-party services", + "alertSmall": "Manage Cookies", + "personalize": "Personalize", + "close": "Close", + + "disclaimer": "By allowing these third party services, you accept theirs cookies and the use of tracking technologies necessary for their proper functioning.", + "allow": "Allow", + "deny": "Deny", + "more": "Read more", + "source": "View the official website", + + "ads": "Advertising network", + "analytics": "Audience measurement", + "social": "Social networks" +}; \ No newline at end of file diff --git a/lang/tarteaucitron.es.js b/lang/tarteaucitron.es.js new file mode 100644 index 0000000..0c1af7d --- /dev/null +++ b/lang/tarteaucitron.es.js @@ -0,0 +1,17 @@ +/*global tarteaucitron */ +tarteaucitron.lang = { + "alertBig": "", + "alertSmall": "", + "personalize": "", + "close": "", + + "disclaimer": "", + "allow": "", + "deny": "", + "more": "", + "source": "", + + "ads": "", + "analytics": "", + "social": "" +}; \ No newline at end of file diff --git a/lang/tarteaucitron.fr.js b/lang/tarteaucitron.fr.js new file mode 100644 index 0000000..7d8728f --- /dev/null +++ b/lang/tarteaucitron.fr.js @@ -0,0 +1,17 @@ +/*global tarteaucitron */ +tarteaucitron.lang = { + "alertBig": "En poursuivant votre navigation, vous acceptez l'utilisation de services tiers pouvant installer des cookies", + "alertSmall": "Gestion des cookies", + "personalize": "Personnaliser", + "close": "Fermer", + + "disclaimer": "En autorisant ces services tiers, vous acceptez le dépôt et la lecture de cookies et l'utilisation de technologies de suivi nécessaires à leur bon fonctionnement.", + "allow": "Autoriser", + "deny": "Interdire", + "more": "En savoir plus", + "source": "Voir le site officiel", + + "ads": "Régies publicitaires", + "analytics": "Mesure d'audience", + "social": "Réseaux sociaux" +}; \ No newline at end of file diff --git a/lang/tarteaucitron.it.js b/lang/tarteaucitron.it.js new file mode 100644 index 0000000..0c1af7d --- /dev/null +++ b/lang/tarteaucitron.it.js @@ -0,0 +1,17 @@ +/*global tarteaucitron */ +tarteaucitron.lang = { + "alertBig": "", + "alertSmall": "", + "personalize": "", + "close": "", + + "disclaimer": "", + "allow": "", + "deny": "", + "more": "", + "source": "", + + "ads": "", + "analytics": "", + "social": "" +}; \ No newline at end of file diff --git a/tarteaucitron.js b/tarteaucitron.js new file mode 100644 index 0000000..91ab28f --- /dev/null +++ b/tarteaucitron.js @@ -0,0 +1,445 @@ +/*jslint browser: true */ +var tarteaucitron = { + "cdn": "", + "user": {}, + "lang": {}, + "services": {}, + "state": [], + "launch": [], + "init": function () { + "use strict"; + var cdn = tarteaucitron.cdn, + language = tarteaucitron.getLanguage(), + pathToLang = cdn + 'lang/tarteaucitron.' + language + '.js', + pathToServices = cdn + 'tarteaucitron.services.js', + linkElement = document.createElement('link'); + + // Step 1: load css + linkElement.rel = 'stylesheet'; + linkElement.type = 'text/css'; + linkElement.href = cdn + 'css/tarteaucitron.css'; + document.getElementsByTagName('head')[0].appendChild(linkElement); + + // Step 2: load language and services + tarteaucitron.addScript(pathToLang, '', function () { + tarteaucitron.addScript(pathToServices, '', function () { + + var body = document.body, + div = document.createElement('div'), + hostname = document.location.hostname, + hostRef = document.referrer.split('/')[2], + isNavigating = (hostRef === hostname) ? true : false, + isAutostart, + isDenied, + isAllowed, + isResponded, + cookie = tarteaucitron.cookie.read(), + s = tarteaucitron.services, + service, + html = '', + lastTitle, + phrases, + textIntro, + textMore, + alert = false, + index; + + // dedup, clean and sort job[] + function cleanArray(arr) { + var i, + len = arr.length, + out = [], + obj = {}; + + for (i = 0; i < len; i += 1) { + if (!obj[arr[i]]) { + obj[arr[i]] = {}; + if (tarteaucitron.services[arr[i]] !== undefined) { + out.push(arr[i]); + } + } + } + return out; + } + tarteaucitron.job = cleanArray(tarteaucitron.job); + tarteaucitron.job = tarteaucitron.job.sort(function (a, b) { + if (s[a].type + s[a].key > s[b].type + s[b].key) { return 1; } + if (s[a].type + s[a].key < s[b].type + s[b].key) { return -1; } + return 0; + }); + + // if bypass: load all services and exit + // for example, set tarteaucitron.user.bypass = true; + // if the user is not in europa + if (tarteaucitron.user.bypass === true) { + for (index = 0; index < tarteaucitron.job.length; index += 1) { + service = s[tarteaucitron.job[index]]; + service.js(); + } + return; + } + + // Step 3: prepare the html + html += '
'; + html += '
'; + html += '
'; + html += ' ' + tarteaucitron.lang.close; + html += '
'; + html += '
'; + html += ' ' + tarteaucitron.lang.disclaimer; + html += '
'; + html += '
'; + + for (index = 0; index < tarteaucitron.job.length; index += 1) { + service = s[tarteaucitron.job[index]]; + textIntro = service.lang[language].split(".").splice(0, 1).join("."); + phrases = service.lang[language].split("."); + phrases.splice(0, 1); + textMore = phrases.join("."); + + if (lastTitle !== service.type) { + html += '
'; + html += ' ' + tarteaucitron.lang[service.type]; + html += '
'; + + lastTitle = service.type; + } + + html += '
'; + html += '
'; + html += ' ' + service.name + '
'; + html += ' ' + textIntro + '. '; + html += ' '; + html += ' ' + tarteaucitron.lang.more; + html += ' '; + html += '
'; + html += ' ' + textMore; + html += ' '; + html += ' ' + tarteaucitron.lang.source; + html += ' '; + html += '
'; + html += '
'; + html += '
'; + html += '
'; + html += ' ' + tarteaucitron.lang.allow; + html += '
'; + html += '
'; + html += ' ' + tarteaucitron.lang.deny; + html += '
'; + html += '
'; + html += '
'; + html += '
'; + } + + html += '
'; + html += '
'; + html += '
'; + html += ' '; + html += ' ' + tarteaucitron.lang.alertBig; + html += ' '; + html += ' '; + html += ' ' + tarteaucitron.lang.personalize; + html += ' '; + html += ' '; + html += ' ' + tarteaucitron.lang.close; + html += ' '; + html += '
'; + html += '
'; + html += ' '; + html += ' ' + tarteaucitron.lang.alertSmall; + html += '
'; + + div.id = 'tarteaucitronRoot'; + body.appendChild(div, body); + div.innerHTML = html; + + // Step 4: load services + for (index = 0; index < tarteaucitron.job.length; index += 1) { + service = s[tarteaucitron.job[index]]; + isAutostart = (!service.needConsent) ? true : false; + isDenied = (cookie.indexOf(service.key + '=false') >= 0) ? true : false; + isAllowed = (cookie.indexOf(service.key + '=true') >= 0) ? true : false; + isResponded = (cookie.indexOf(service.key) >= 0) ? true : false; + + if ((!isResponded && (isAutostart || isNavigating)) || isAllowed) { + if (!isAllowed) { + tarteaucitron.cookie.create(service.key, true); + } + if (tarteaucitron.launch[service.key] !== true) { + tarteaucitron.launch[service.key] = true; + service.js(); + } + tarteaucitron.state[service.key] = true; + tarteaucitron.userInterface.color(service.key, true); + } else if (isDenied) { + if (typeof service.fallback === 'function') { + service.fallback(); + } + tarteaucitron.state[service.key] = false; + tarteaucitron.userInterface.color(service.key, false); + } else if (!isResponded) { + if (typeof service.fallback === 'function') { + service.fallback(); + } + } + + if (tarteaucitron.state[service.key] === undefined && !alert) { + alert = true; + } + } + + // Step 5: display the alert + if (alert) { + tarteaucitron.userInterface.openAlert(); + } else { + tarteaucitron.userInterface.closeAlert(); + } + if (document.location.hash === '#tarteaucitron') { + tarteaucitron.userInterface.openPanel(); + } + }); + }); + }, + "userInterface": { + "css": function (id, property, value) { + "use strict"; + document.getElementById(id).style[property] = value; + }, + "showMore": function (el) { + "use strict"; + var key = el.id.replace(/More/, ''); + tarteaucitron.userInterface.css(key + 'MoreText', 'display', 'inline'); + el.style.display = 'none'; + }, + "respond": function (el, status) { + "use strict"; + var key = el.id.replace(new RegExp("(Allow|Deni)ed", "g"), ''); + + // return if same state + if (tarteaucitron.state[key] === status) { + return; + } + + // if not already launched... launch the service + if (status === true) { + if (tarteaucitron.launch[key] !== true) { + tarteaucitron.launch[key] = true; + tarteaucitron.services[key].js(); + } + } + tarteaucitron.state[key] = status; + tarteaucitron.cookie.create(key, status); + tarteaucitron.userInterface.color(key, status); + }, + "color": function (key, status) { + "use strict"; + var gray = '#808080', + greenDark = '#1B870B', + greenLight = '#E6FFE2', + redDark = '#9C1A1A', + redLight = '#FFE2E2', + c = 'tarteaucitron', + allAllowed = true, + index; + + if (status === true) { + tarteaucitron.userInterface.css(key + 'Line', 'backgroundColor', greenLight); + tarteaucitron.userInterface.css(key + 'Allowed', 'backgroundColor', greenDark); + tarteaucitron.userInterface.css(key + 'Denied', 'backgroundColor', gray); + } else if (status === false) { + tarteaucitron.userInterface.css(key + 'Line', 'backgroundColor', redLight); + tarteaucitron.userInterface.css(key + 'Allowed', 'backgroundColor', gray); + tarteaucitron.userInterface.css(key + 'Denied', 'backgroundColor', redDark); + } + + // check if all services are allowed + for (index = 0; index < tarteaucitron.job.length; index += 1) { + if (tarteaucitron.state[tarteaucitron.job[index]] === false) { + allAllowed = false; + break; + } + } + + if (allAllowed) { + tarteaucitron.userInterface.css(c + 'Dot', 'backgroundColor', greenDark); + } else { + tarteaucitron.userInterface.css(c + 'Dot', 'backgroundColor', redDark); + } + }, + "openPanel": function () { + "use strict"; + tarteaucitron.userInterface.css('tarteaucitron', 'display', 'block'); + tarteaucitron.userInterface.css('tarteaucitronBack', 'display', 'block'); + tarteaucitron.userInterface.closeAlert(); + + // setting hash tag + document.location.hash = 'tarteaucitron'; + }, + "closePanel": function () { + "use strict"; + tarteaucitron.userInterface.css('tarteaucitron', 'display', 'none'); + tarteaucitron.userInterface.css('tarteaucitronBack', 'display', 'none'); + }, + "openAlert": function () { + "use strict"; + var c = 'tarteaucitron'; + tarteaucitron.userInterface.css(c + 'AlertSmall', 'display', 'none'); + tarteaucitron.userInterface.css(c + 'AlertBig', 'display', 'block'); + }, + "closeAlert": function () { + "use strict"; + var c = 'tarteaucitron'; + tarteaucitron.userInterface.css(c + 'AlertSmall', 'display', 'block'); + tarteaucitron.userInterface.css(c + 'AlertBig', 'display', 'none'); + } + }, + "cookie": { + "create": function (key, status) { + "use strict"; + var d = new Date(), + time = d.getTime(), + expireTime = time + 31536000000, // 365 days + regex = new RegExp("!" + key + "=(true|false)", "g"), + cookie = tarteaucitron.cookie.read().replace(regex, ""), + value = 'tarteaucitron=' + cookie + '!' + key + '=' + status; + + d.setTime(expireTime); + document.cookie = value + '; expires=' + d.toGMTString() + '; path=/;'; + }, + "read": function () { + "use strict"; + var nameEQ = "tarteaucitron=", + ca = document.cookie.split(';'), + i, + c; + + for (i = 0; i < ca.length; i += 1) { + c = ca[i]; + while (c.charAt(0) === ' ') { + c = c.substring(1, c.length); + } + if (c.indexOf(nameEQ) === 0) { + return c.substring(nameEQ.length, c.length); + } + } + return ''; + } + }, + "getLanguage": function () { + "use strict"; + if (!navigator) { return 'en'; } + + var availableLanguages = 'en,fr', + defaultLanguage = 'en', + lang = navigator.language || navigator.browserLanguage || + navigator.systemLanguage || navigator.userLang || null, + userLanguage = lang.substr(0, 2); + + if (availableLanguages.indexOf(userLanguage) === -1) { + return defaultLanguage; + } + return userLanguage; + }, + "addScript": function (url, id, callback) { + "use strict"; + var script = document.createElement('script'), + done = false; + + script.type = 'text/javascript'; + script.id = (id !== undefined) ? id : ''; + script.async = true; + script.src = url; + + if (typeof callback === 'function') { + script.onreadystatechange = script.onload = function () { + var state = script.readyState; + if (!done && (!state || /loaded|complete/.test(state))) { + done = true; + callback(); + } + }; + } + document.getElementsByTagName('head')[0].appendChild(script); + }, + "fallback": function (matchClass, content) { + "use strict"; + var elems = document.getElementsByTagName('*'), + i, + index = 0; + + for (i in elems) { + if (elems[i] !== undefined) { + for (index = 0; index < matchClass.length; index += 1) { + if ((' ' + elems[i].className + ' ') + .indexOf(' ' + matchClass[index] + ' ') > -1) { + if (typeof content === 'function') { + elems[i].innerHTML = content(elems[i]); + } else { + elems[i].innerHTML = content; + } + } + } + } + } + }, + + /*** + * Fallback function for advertising services + * + * Currently, a banner to promote tarteaucitron.js is displayed, + * fell free to change this by your own ads. + * + * Because eval() id devil, you can't pass