From 878351d840edae4779962e94da3375f46c3eb6ed Mon Sep 17 00:00:00 2001 From: Jeremy Kersten Date: Wed, 23 Jun 2021 16:24:30 +0000 Subject: [PATCH] [IMP] base, web, website, *: differentiate essential & optional cookies *: im_livechat, survey, utm, website_crm_iap_reveal, website_forum, website_livechat, website_sale, website_sale_comparison Before this commit all cookies were considered essential. This commit makes some of them optional. It also makes it possible for the website visitor to only accept the essential cookies. task-2800976 X-original-commit: 9a8a9463289a7446e9be0ef62ff895feb37a4de4 Part-of: odoo/odoo#101845 Co-authored-by: Benoit Socias --- .../src/legacy/widgets/livechat_button.js | 4 +- .../public_livechat_window.js | 5 +- .../src/public_models/livechat_button_view.js | 19 ++++---- .../public_models/public_livechat_global.js | 14 +++--- ...ic_livechat_global_notification_handler.js | 3 +- addons/survey/static/src/js/survey_form.js | 8 ++-- addons/utm/models/ir_http.py | 2 +- .../static/src/core/browser/cookie_service.js | 2 + addons/web/static/src/legacy/js/common_env.js | 6 +-- .../static/src/legacy/js/core/cookie_utils.js | 38 ++++++++++++--- .../web/static/src/legacy/js/core/session.js | 9 ++-- addons/web/static/src/legacy/js/core/utils.js | 5 +- .../src/legacy/js/public/public_root.js | 4 +- addons/website/__manifest__.py | 1 + addons/website/data/website_data.xml | 10 ++-- addons/website/models/ir_http.py | 17 +++++++ .../static/src/js/content/inject_dom.js | 4 +- .../static/src/js/editor/snippets.options.js | 1 + addons/website/static/src/js/http_cookie.js | 22 +++++++++ addons/website/static/src/scss/website.scss | 11 ----- .../static/src/snippets/s_popup/000.js | 30 ++++++++++-- .../static/src/xml/website.cookies_bar.xml | 46 +++++++++++++------ addons/website/views/website_templates.xml | 18 +++++--- .../website_crm_iap_reveal/models/ir_http.py | 2 +- .../static/src/js/website_forum.js | 3 +- .../website_livechat_chatbot_test_script.js | 10 ++-- .../src/public_models/livechat_button_view.js | 5 +- .../public_models/public_livechat_global.js | 4 +- .../src/js/website_sale_recently_viewed.js | 6 +-- .../static/src/js/website_sale_comparison.js | 8 ++-- odoo/addons/base/models/ir_http.py | 4 ++ odoo/http.py | 13 ++++++ 32 files changed, 230 insertions(+), 104 deletions(-) create mode 100644 addons/website/static/src/js/http_cookie.js diff --git a/addons/im_livechat/static/src/legacy/widgets/livechat_button.js b/addons/im_livechat/static/src/legacy/widgets/livechat_button.js index 4a5758418cf..002ce0ed1f9 100644 --- a/addons/im_livechat/static/src/legacy/widgets/livechat_button.js +++ b/addons/im_livechat/static/src/legacy/widgets/livechat_button.js @@ -1,7 +1,7 @@ /** @odoo-module **/ import time from 'web.time'; -import utils from 'web.utils'; +import {getCookie} from 'web.utils.cookies'; import Widget from 'web.Widget'; const LivechatButton = Widget.extend({ @@ -33,7 +33,7 @@ const LivechatButton = Widget.extend({ * @return {integer} operator_id.partner_id.id if the cookie is set */ _get_previous_operator_id() { - const cookie = utils.get_cookie('im_livechat_previous_operator_pid'); + const cookie = getCookie('im_livechat_previous_operator_pid'); if (cookie) { return cookie; } diff --git a/addons/im_livechat/static/src/legacy/widgets/public_livechat_window/public_livechat_window.js b/addons/im_livechat/static/src/legacy/widgets/public_livechat_window/public_livechat_window.js index 5f56aeeea6b..f826c4e3f36 100644 --- a/addons/im_livechat/static/src/legacy/widgets/public_livechat_window/public_livechat_window.js +++ b/addons/im_livechat/static/src/legacy/widgets/public_livechat_window/public_livechat_window.js @@ -4,7 +4,8 @@ import config from 'web.config'; import { _t, qweb } from 'web.core'; import Widget from 'web.Widget'; -import { set_cookie, unaccent } from 'web.utils'; +import {unaccent} from 'web.utils'; +import {setCookie} from 'web.utils.cookies'; /** * This is the widget that represent windows of livechat in the frontend. @@ -137,7 +138,7 @@ const PublicLivechatWindow = Widget.extend({ folded = !this.messaging.publicLivechatGlobal.publicLivechat.isFolded; } this.messaging.publicLivechatGlobal.publicLivechat.update({ isFolded: folded }); - set_cookie('im_livechat_session', unaccent(JSON.stringify(this.messaging.publicLivechatGlobal.publicLivechat.widget.toData()), true), 60 * 60); + setCookie('im_livechat_session', unaccent(JSON.stringify(this.messaging.publicLivechatGlobal.publicLivechat.widget.toData()), true), 60 * 60, 'required'); this.updateVisualFoldState(); }, /** diff --git a/addons/im_livechat/static/src/public_models/livechat_button_view.js b/addons/im_livechat/static/src/public_models/livechat_button_view.js index 8879a50dd35..a8e05351423 100644 --- a/addons/im_livechat/static/src/public_models/livechat_button_view.js +++ b/addons/im_livechat/static/src/public_models/livechat_button_view.js @@ -4,7 +4,8 @@ import { registerModel } from '@mail/model/model_core'; import { attr, one } from '@mail/model/model_field'; import { clear } from '@mail/model/model_field_command'; -import { get_cookie, set_cookie, unaccent } from 'web.utils'; +import {unaccent} from 'web.utils'; +import {getCookie, setCookie, deleteCookie} from 'web.utils.cookies'; registerModel({ name: 'LivechatButtonView', @@ -106,18 +107,18 @@ registerModel({ }, closeChat() { this.messaging.publicLivechatGlobal.update({ chatWindow: clear() }); - set_cookie('im_livechat_session', "", -1); // remove cookie + deleteCookie('im_livechat_session'); }, /** * Called when the visitor leaves the livechat chatter the first time (first click on X button) * this will deactivate the mail_channel, notify operator that visitor has left the channel. */ leaveSession() { - const cookie = get_cookie('im_livechat_session'); + const cookie = getCookie('im_livechat_session'); if (cookie) { const channel = JSON.parse(cookie); this.messaging.rpc({ route: '/im_livechat/visitor_leave_session', params: { uuid: channel.uuid } }); - set_cookie('im_livechat_session', "", -1); // remove cookie + deleteCookie('im_livechat_session'); } }, openChat() { @@ -152,7 +153,7 @@ registerModel({ } this.openChat(); } else if (!this.messaging.device.isSmall && this.messaging.publicLivechatGlobal.rule.action === 'auto_popup') { - const autoPopupCookie = get_cookie('im_livechat_auto_popup'); + const autoPopupCookie = getCookie('im_livechat_auto_popup'); if (!autoPopupCookie || JSON.parse(autoPopupCookie)) { this.update({ autoOpenChatTimeout: setTimeout( @@ -182,7 +183,7 @@ registerModel({ if (this.isOpeningChat) { return; } - const cookie = get_cookie('im_livechat_session'); + const cookie = getCookie('im_livechat_session'); let def; this.update({ isOpeningChat: true }); clearTimeout(this.autoOpenChatTimeout); @@ -224,12 +225,12 @@ registerModel({ this.messaging.publicLivechatGlobal.chatWindow.renderMessages(); this.messaging.publicLivechatGlobal.update({ notificationHandler: {} }); - set_cookie('im_livechat_session', unaccent(JSON.stringify(this.messaging.publicLivechatGlobal.publicLivechat.widget.toData()), true), 60 * 60); - set_cookie('im_livechat_auto_popup', JSON.stringify(false), 60 * 60); + setCookie('im_livechat_session', unaccent(JSON.stringify(this.messaging.publicLivechatGlobal.publicLivechat.widget.toData()), true), 60 * 60, 'required'); + setCookie('im_livechat_auto_popup', JSON.stringify(false), 60 * 60, 'optional'); if (this.messaging.publicLivechatGlobal.publicLivechat.operator) { const operatorPidId = this.messaging.publicLivechatGlobal.publicLivechat.operator.id; const oneWeek = 7 * 24 * 60 * 60; - set_cookie('im_livechat_previous_operator_pid', operatorPidId, oneWeek); + setCookie('im_livechat_previous_operator_pid', operatorPidId, oneWeek, 'optional'); } }); } diff --git a/addons/im_livechat/static/src/public_models/public_livechat_global.js b/addons/im_livechat/static/src/public_models/public_livechat_global.js index 221c8496a77..fb700077ce0 100644 --- a/addons/im_livechat/static/src/public_models/public_livechat_global.js +++ b/addons/im_livechat/static/src/public_models/public_livechat_global.js @@ -5,8 +5,8 @@ import { attr, many, one } from '@mail/model/model_field'; import { clear } from '@mail/model/model_field_command'; import { qweb } from 'web.core'; - -import { get_cookie, Markup, set_cookie } from 'web.utils'; +import { Markup } from 'web.utils'; +import {getCookie, setCookie, deleteCookie} from 'web.utils.cookies'; registerModel({ name: 'PublicLivechatGlobal', @@ -14,7 +14,7 @@ registerModel({ _created() { // History tracking const page = window.location.href.replace(/^.*\/\/[^/]+/, ''); - const pageHistory = get_cookie(this.LIVECHAT_COOKIE_HISTORY); + const pageHistory = getCookie(this.LIVECHAT_COOKIE_HISTORY); let urlHistory = []; if (pageHistory) { urlHistory = JSON.parse(pageHistory) || []; @@ -24,7 +24,7 @@ registerModel({ while (urlHistory.length > this.HISTORY_LIMIT) { urlHistory.shift(); } - set_cookie(this.LIVECHAT_COOKIE_HISTORY, JSON.stringify(urlHistory), 60 * 60 * 24); // 1 day cookie + setCookie(this.LIVECHAT_COOKIE_HISTORY, JSON.stringify(urlHistory), 60 * 60 * 24, 'optional'); // 1 day cookie } if (this.isAvailable) { this.willStart(); @@ -44,7 +44,7 @@ registerModel({ await this._willStartChatbot(); }, async _willStart() { - const cookie = get_cookie('im_livechat_session'); + const cookie = getCookie('im_livechat_session'); if (cookie) { const channel = JSON.parse(cookie); const history = await this.messaging.rpc({ @@ -98,7 +98,7 @@ registerModel({ }), }); } else if (this.history !== null && this.history.length !== 0) { - const sessionCookie = get_cookie('im_livechat_session'); + const sessionCookie = getCookie('im_livechat_session'); if (sessionCookie) { this.update({ sessionCookie }); } @@ -120,7 +120,7 @@ registerModel({ // -> remove cookie to force opening the popup again // -> initialize necessary state // -> batch welcome message (see '_sendWelcomeChatbotMessage') - set_cookie('im_livechat_auto_popup', '', -1); + deleteCookie('im_livechat_auto_popup'); this.update({ history: clear() }); this.update({ rule: this.livechatInit.rule }); } else if (this.chatbot.state === 'restore_session') { diff --git a/addons/im_livechat/static/src/public_models/public_livechat_global_notification_handler.js b/addons/im_livechat/static/src/public_models/public_livechat_global_notification_handler.js index 96e592c5ab2..e96418c9634 100644 --- a/addons/im_livechat/static/src/public_models/public_livechat_global_notification_handler.js +++ b/addons/im_livechat/static/src/public_models/public_livechat_global_notification_handler.js @@ -6,6 +6,7 @@ import { increment } from '@mail/model/model_field_command'; import session from 'web.session'; import utils from 'web.utils'; +import {getCookie} from 'web.utils.cookies'; registerModel({ name: 'PublicLivechatGlobalNotificationHandler', @@ -28,7 +29,7 @@ registerModel({ if (payload.id !== this.messaging.publicLivechatGlobal.publicLivechat.id) { return; } - const cookie = utils.get_cookie(this.messaging.publicLivechatGlobal.LIVECHAT_COOKIE_HISTORY); + const cookie = getCookie(this.messaging.publicLivechatGlobal.LIVECHAT_COOKIE_HISTORY); const history = cookie ? JSON.parse(cookie) : []; session.rpc('/im_livechat/history', { pid: this.messaging.publicLivechatGlobal.publicLivechat.operator.id, diff --git a/addons/survey/static/src/js/survey_form.js b/addons/survey/static/src/js/survey_form.js index eb2905a11c0..894ebbd66ca 100644 --- a/addons/survey/static/src/js/survey_form.js +++ b/addons/survey/static/src/js/survey_form.js @@ -8,7 +8,7 @@ var config = require('web.config'); var core = require('web.core'); var Dialog = require('web.Dialog'); var dom = require('web.dom'); -var utils = require('web.utils'); +const {getCookie, setCookie, deleteCookie} = require('web.utils.cookies'); var SurveyPreloadImageMixin = require('survey.preload_image_mixin'); const { SurveyImageZoomer } = require("@survey/js/survey_image_zoomer"); @@ -48,8 +48,8 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend(SurveyPreloa self.imgZoomer = false; // Add Survey cookie to retrieve the survey if you quit the page and restart the survey. - if (!utils.get_cookie('survey_' + self.options.surveyToken)) { - utils.set_cookie('survey_' + self.options.surveyToken, self.options.answerToken, 60*60*24); + if (!getCookie('survey_' + self.options.surveyToken)) { + setCookie('survey_' + self.options.surveyToken, self.options.answerToken, 60 * 60 * 24, 'optional'); } // Init fields @@ -502,7 +502,7 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend(SurveyPreloa var selectorsToFadeout = ['.o_survey_form_content']; if (options.isFinish) { selectorsToFadeout.push('.breadcrumb', '.o_survey_timer'); - utils.set_cookie('survey_' + self.options.surveyToken, '', -1); // delete cookie + deleteCookie('survey_' + self.options.surveyToken); } self.$(selectorsToFadeout.join(',')).fadeOut(this.fadeInOutDelay, function () { resolveFadeOut(); diff --git a/addons/utm/models/ir_http.py b/addons/utm/models/ir_http.py index 451078ffaa9..6059b7b5d17 100644 --- a/addons/utm/models/ir_http.py +++ b/addons/utm/models/ir_http.py @@ -16,7 +16,7 @@ class IrHttp(models.AbstractModel): domain = cls.get_utm_domain_cookies() for url_parameter, __, cookie_name in request.env['utm.mixin'].tracking_fields(): if url_parameter in request.params and request.httprequest.cookies.get(cookie_name) != request.params[url_parameter]: - response.set_cookie(cookie_name, request.params[url_parameter], domain=domain) + response.set_cookie(cookie_name, request.params[url_parameter], domain=domain, cookie_type='optional') @classmethod def _post_dispatch(cls, response): diff --git a/addons/web/static/src/core/browser/cookie_service.js b/addons/web/static/src/core/browser/cookie_service.js index 3bc89b4d094..6f835cbd8a2 100644 --- a/addons/web/static/src/core/browser/cookie_service.js +++ b/addons/web/static/src/core/browser/cookie_service.js @@ -36,6 +36,8 @@ function makeCookieService() { } let cookie = getCurrent(); function setCookie(key, value, ttl) { + // TODO When this will be used from website pages, recover the + // optional cookie mechanism. document.cookie = cookieToString(key, value, ttl); cookie = getCurrent(); } diff --git a/addons/web/static/src/legacy/js/common_env.js b/addons/web/static/src/legacy/js/common_env.js index 16c9e262837..887f14868fd 100644 --- a/addons/web/static/src/legacy/js/common_env.js +++ b/addons/web/static/src/legacy/js/common_env.js @@ -23,7 +23,7 @@ odoo.define("web.commonEnv", function (require) { const rpc = require("web.rpc"); const session = require("web.session"); const { _t } = require("web.translation"); - const utils = require("web.utils"); + const {getCookie, setCookie} = require('web.utils.cookies'); const browser = { clearInterval: window.clearInterval.bind(window), @@ -54,7 +54,7 @@ odoo.define("web.commonEnv", function (require) { return jsonRpc(...arguments); }, getCookie() { - return utils.get_cookie(...arguments); + return getCookie(...arguments); }, httpRequest(route, params = {}, readMethod = 'json') { const info = { @@ -90,7 +90,7 @@ odoo.define("web.commonEnv", function (require) { return session.rpc(query.route, query.params, options); }, setCookie() { - utils.set_cookie(...arguments); + setCookie(...arguments); }, }, session, diff --git a/addons/web/static/src/legacy/js/core/cookie_utils.js b/addons/web/static/src/legacy/js/core/cookie_utils.js index 2aeca9bd370..1696539b547 100644 --- a/addons/web/static/src/legacy/js/core/cookie_utils.js +++ b/addons/web/static/src/legacy/js/core/cookie_utils.js @@ -1,14 +1,14 @@ odoo.define('web.utils.cookies', function (require) { "use strict"; -return { +const utils = { /** * Reads the cookie described by the given name. * * @param {string} cookieName * @returns {string} */ - get_cookie(cookieName) { + getCookie(cookieName) { var cookies = document.cookie ? document.cookie.split('; ') : []; for (var i = 0, l = cookies.length; i < l; i++) { var parts = cookies[i].split('='); @@ -21,21 +21,47 @@ return { } return ""; }, + /** + * Check if cookie can be written. + * + * @param {String} type the type of the cookie + * @returns {boolean} + */ + isAllowedCookie(type) { + return true; + }, /** * Creates a cookie. * * @param {string} name the name of the cookie * @param {string} value the value stored in the cookie * @param {integer} ttl time to live of the cookie in millis. -1 to erase the cookie. + * @param {string} type the type of the cookies ('required' as default value) */ - set_cookie(name, value, ttl) { + setCookie(name, value, ttl = 31536000, type = 'required') { ttl = ttl || 24 * 60 * 60 * 365; + if (utils.isAllowedCookie(type)) { + document.cookie = [ + `${name}=${value}`, + 'path=/', + `max-age=${ttl}`, + `expires=${new Date(new Date().getTime() + ttl * 1000).toGMTString()}`, + ].join(';'); + } + }, + /** + * Deletes a cookie. + * + * @param {string} name the name of the cookie + */ + deleteCookie(name) { document.cookie = [ - `${name}=${value}`, + `${name}=`, 'path=/', - `max-age=${ttl}`, - `expires=${new Date(new Date().getTime() + ttl * 1000).toGMTString()}` + `max-age=-1`, + `expires=${new Date(new Date().getTime() - 1000).toGMTString()}`, ].join(';'); }, }; +return utils; }); diff --git a/addons/web/static/src/legacy/js/core/session.js b/addons/web/static/src/legacy/js/core/session.js index e4b276215d8..b5880b0f05c 100644 --- a/addons/web/static/src/legacy/js/core/session.js +++ b/addons/web/static/src/legacy/js/core/session.js @@ -4,7 +4,7 @@ odoo.define('web.Session', function (require) { var ajax = require('web.ajax'); var core = require('web.core'); var mixins = require('web.mixins'); -var utils = require('web.utils'); +const {setCookie} = require('web.utils.cookies'); const { session } = require('@web/session'); const { loadJS } = require('@web/core/assets'); @@ -172,11 +172,12 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, { * @param name the cookie's name * @param value the cookie's value * @param ttl the cookie's time to live, 1 year by default, set to -1 to delete + * @param type the type of the cookies ('required' as default value) */ - set_cookie: function (name, value, ttl) { + set_cookie(name, value, ttl, type = 'required') { if (!this.name) { return; } ttl = ttl || 24*60*60*365; - utils.set_cookie(this.name + '|' + name, value, ttl); + setCookie(this.name + '|' + name, value, ttl, type); }, /** * Load additional web addons of that instance and init them @@ -328,7 +329,7 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, { return a - b; } }).join(','); - utils.set_cookie('cids', hash.cids || String(main_company_id)); + setCookie('cids', hash.cids || String(main_company_id), 24 * 60 * 60 * 365, 'required'); $.bbq.pushState({'cids': hash.cids}, 0); location.reload(); }, diff --git a/addons/web/static/src/legacy/js/core/utils.js b/addons/web/static/src/legacy/js/core/utils.js index 7e04354e90e..9a654e95af1 100644 --- a/addons/web/static/src/legacy/js/core/utils.js +++ b/addons/web/static/src/legacy/js/core/utils.js @@ -8,7 +8,6 @@ odoo.define('web.utils', function (require) { */ var translation = require('web.translation'); -var cookieUtils = require('web.utils.cookies'); const { Component } = owl; @@ -316,7 +315,7 @@ function Markup(v, ...exprs) { return new _Markup(s); } -var utils = Object.assign({ +const utils = { AlreadyDefinedPatchError, UnknownPatchError, Markup, @@ -1129,7 +1128,7 @@ var utils = Object.assign({ } return curr; }, -}, cookieUtils); +}; return utils; diff --git a/addons/web/static/src/legacy/js/public/public_root.js b/addons/web/static/src/legacy/js/public/public_root.js index a755bc81964..4a07fc8317d 100644 --- a/addons/web/static/src/legacy/js/public/public_root.js +++ b/addons/web/static/src/legacy/js/public/public_root.js @@ -3,7 +3,7 @@ import dom from 'web.dom'; import legacyEnv from 'web.public_env'; import session from 'web.session'; -import utils from 'web.utils'; +import {getCookie} from 'web.utils.cookies'; import publicWidget from 'web.public.widget'; import { registry } from '@web/core/registry'; @@ -37,7 +37,7 @@ function getLang() { var html = document.documentElement; return (html.getAttribute('lang') || 'en_US').replace('-', '_'); } -var lang = utils.get_cookie('frontend_lang') || getLang(); // FIXME the cookie value should maybe be in the ctx? +const lang = getCookie('frontend_lang') || getLang(); // FIXME the cookie value should maybe be in the ctx? // momentjs don't have config for en_US, so avoid useless RPC var localeDef = lang !== 'en_US' ? loadJS('/web/webclient/locale/' + lang.replace('-', '_')) : Promise.resolve(); diff --git a/addons/website/__manifest__.py b/addons/website/__manifest__.py index da49235192c..e6ccc3e4ed4 100644 --- a/addons/website/__manifest__.py +++ b/addons/website/__manifest__.py @@ -129,6 +129,7 @@ 'website/static/src/js/post_link.js', 'website/static/src/js/plausible.js', 'website/static/src/js/user_custom_javascript.js', + 'website/static/src/js/http_cookie.js', 'website/static/src/xml/website.xml', 'website/static/src/xml/website.background.video.xml', 'website/static/src/xml/website.share.xml', diff --git a/addons/website/data/website_data.xml b/addons/website/data/website_data.xml index de829b0a375..275de9f1baf 100644 --- a/addons/website/data/website_data.xml +++ b/addons/website/data/website_data.xml @@ -228,7 +228,7 @@ -

Session & Security

+

Session & Security
(essential)

@@ -243,7 +243,7 @@ -

Preferences

+

Preferences
(essential)

Remember information about the preferred look or behavior of the website, such as your preferred language or region.

@@ -254,7 +254,7 @@ - Interaction History + Interaction History
(optional)

Used to collect information about your interactions with the website, the pages you've seen, @@ -271,7 +271,7 @@ -

Advertising & Marketing

+

Advertising & Marketing
(optional)

@@ -291,7 +291,7 @@ -

Analytics

+

Analytics
(optional)

diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py index 92a6666fe1a..78b2dc4052b 100644 --- a/addons/website/models/ir_http.py +++ b/addons/website/models/ir_http.py @@ -17,6 +17,7 @@ from odoo import api, models from odoo import SUPERUSER_ID from odoo.exceptions import AccessError from odoo.http import request +from odoo.tools.json import scriptsafe as json_scriptsafe from odoo.tools.safe_eval import safe_eval from odoo.osv.expression import FALSE_DOMAIN from odoo.addons.http_routing.models import ir_http @@ -380,6 +381,22 @@ class Http(models.AbstractModel): session_info['bundle_params']['website_id'] = request.website.id return session_info + @classmethod + def _is_allowed_cookie(cls, cookie_type): + result = super()._is_allowed_cookie(cookie_type) + if result and cookie_type == 'optional': + if not request.env['website'].get_current_website().cookies_bar: + # Cookies bar is disabled on this website + return True + accepted_cookie_types = json_scriptsafe.loads(request.httprequest.cookies.get('website_cookies_bar', '{}')) + if 'optional' in accepted_cookie_types: + return accepted_cookie_types['optional'] + return False + + # Pass-through if already forbidden for another reason or a type that + # is not restricted by the website module. + return result + class ModelConverter(ir_http.ModelConverter): diff --git a/addons/website/static/src/js/content/inject_dom.js b/addons/website/static/src/js/content/inject_dom.js index e0b1ca76eda..f96644a3958 100644 --- a/addons/website/static/src/js/content/inject_dom.js +++ b/addons/website/static/src/js/content/inject_dom.js @@ -1,6 +1,6 @@ /** @odoo-module */ -import { get_cookie } from 'web.utils.cookies'; +import {getCookie} from 'web.utils.cookies'; import { session } from '@web/session'; document.addEventListener('DOMContentLoaded', () => { @@ -13,7 +13,7 @@ document.addEventListener('DOMContentLoaded', () => { 'utm_campaign': 'utmCampaign', }; for (const [name, dsName] of Object.entries(cookieNamesToDataNames)) { - const cookie = get_cookie(`odoo_${name}`); + const cookie = getCookie(`odoo_${name}`); if (cookie) { // Remove leading and trailing " and ' htmlEl.dataset[dsName] = cookie.replace(/(^["']|["']$)/g, ''); diff --git a/addons/website/static/src/js/editor/snippets.options.js b/addons/website/static/src/js/editor/snippets.options.js index c0455dd0362..4993ad5e084 100644 --- a/addons/website/static/src/js/editor/snippets.options.js +++ b/addons/website/static/src/js/editor/snippets.options.js @@ -2617,6 +2617,7 @@ options.registry.CookiesBar = options.registry.SnippetPopup.extend({ const $content = this.$target.find('.modal-content'); const selectorsToKeep = [ '.o_cookies_bar_text_button', + '.o_cookies_bar_text_button_essential', '.o_cookies_bar_text_policy', '.o_cookies_bar_text_title', '.o_cookies_bar_text_primary', diff --git a/addons/website/static/src/js/http_cookie.js b/addons/website/static/src/js/http_cookie.js new file mode 100644 index 00000000000..34c9fb378af --- /dev/null +++ b/addons/website/static/src/js/http_cookie.js @@ -0,0 +1,22 @@ +/** @odoo-module **/ + +import cookieUtils from 'web.utils.cookies'; + +const originFunc = cookieUtils.isAllowedCookie; +cookieUtils.isAllowedCookie = (type) => { + const result = originFunc.apply(cookieUtils, [type]); + if (result && type === 'optional') { + if (!document.getElementById('cookies-consent-essential')) { + // Cookies bar is disabled on this website. + return true; + } + const consents = JSON.parse(cookieUtils.getCookie('website_cookies_bar') || '{}'); + if ('optional' in consents) { + return consents['optional']; + } + return false; + } + // Pass-through if already forbidden for another reason or a type that is + // not restricted by the website module. + return result; +}; diff --git a/addons/website/static/src/scss/website.scss b/addons/website/static/src/scss/website.scss index 9f09f6e54d4..069d53b7655 100644 --- a/addons/website/static/src/scss/website.scss +++ b/addons/website/static/src/scss/website.scss @@ -1487,17 +1487,6 @@ $ribbon-padding: 100px; flex: 1; } -// Cookies Bar -#website_cookies_bar { - .o_cookies_discrete { - .js_close_popup.o_cookies_bar_text_button, .o_cookies_bar_text_policy { - @include media-breakpoint-down(lg) { - margin-bottom: 1rem; - } - } - } -} - .o_website_btn_loading { cursor: wait; opacity: $btn-disabled-opacity; diff --git a/addons/website/static/src/snippets/s_popup/000.js b/addons/website/static/src/snippets/s_popup/000.js index 52a951910ca..a0f5a27b787 100644 --- a/addons/website/static/src/snippets/s_popup/000.js +++ b/addons/website/static/src/snippets/s_popup/000.js @@ -3,7 +3,7 @@ odoo.define('website.s_popup', function (require) { const config = require('web.config'); const publicWidget = require('web.public.widget'); -const utils = require('web.utils'); +const {getCookie, setCookie} = require('web.utils.cookies'); const PopupWidget = publicWidget.Widget.extend({ selector: '.s_popup', @@ -12,12 +12,13 @@ const PopupWidget = publicWidget.Widget.extend({ 'hide.bs.modal': '_onHideModal', 'show.bs.modal': '_onShowModal', }, + cookieValue: true, /** * @override */ start: function () { - this._popupAlreadyShown = !!utils.get_cookie(this.$el.attr('id')); + this._popupAlreadyShown = !!getCookie(this.$el.attr('id')); if (!this._popupAlreadyShown) { this._bindPopup(); } @@ -96,7 +97,7 @@ const PopupWidget = publicWidget.Widget.extend({ */ _onHideModal: function () { const nbDays = this.$el.find('.modal').data('consentsDuration'); - utils.set_cookie(this.$el.attr('id'), true, nbDays * 24 * 60 * 60); + setCookie(this.el.id, this.cookieValue, nbDays * 24 * 60 * 60, 'required'); this._popupAlreadyShown = true; this.$target.find('.media_iframe_video iframe').each((i, iframe) => { @@ -116,5 +117,28 @@ const PopupWidget = publicWidget.Widget.extend({ publicWidget.registry.popup = PopupWidget; +// Extending the popup widget with cookiebar functionality. +// This allows for refusing optional cookies for now and can be +// extended to picking which cookies categories are accepted. +publicWidget.registry.cookies_bar = PopupWidget.extend({ + selector: '#website_cookies_bar', + events: Object.assign({}, PopupWidget.prototype.events, { + 'click #cookies-consent-essential, #cookies-consent-all': '_onAcceptClick', + }), + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + * @param ev + */ + _onAcceptClick(ev) { + this.cookieValue = `{"required": true, "optional": ${ev.target.id === 'cookies-consent-all'}}`; + this._onHideModal(); + }, +}); + return PopupWidget; }); diff --git a/addons/website/static/src/xml/website.cookies_bar.xml b/addons/website/static/src/xml/website.cookies_bar.xml index e32dc4ba110..f2227b29646 100644 --- a/addons/website/static/src/xml/website.cookies_bar.xml +++ b/addons/website/static/src/xml/website.cookies_bar.xml @@ -7,25 +7,37 @@

- We use cookies to provide you a better user experience. + Allow the use of cookies from this website on this browser?

- We use them to store info about your habits on our website. It will helps us to provide you the very best experience and customize what you see.
- By clicking on this banner, you give us permission to collect data. + We use cookies to provide improved experience on this website. You can learn more about our cookies and how we use them in our Cookie Policy.

+ + Allow all cookies + + + Only allow essential cookies + +
-
-
-

We use cookies to provide you a better user experience.

+
+
+ We use cookies to provide you a better user experience on this website. + Cookie Policy
-
@@ -40,9 +52,15 @@
-
- I agree - Cookie Policy +
+
+
+ +
+
+ +
+
@@ -57,8 +75,8 @@ - Cookie Policy - I agree + +
diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml index 20551b99233..a464fc4ed2b 100644 --- a/addons/website/views/website_templates.xml +++ b/addons/website/views/website_templates.xml @@ -159,7 +159,7 @@ - +