From 3028e9a8649ae5d9b23a1bdb989b903645ede5fe Mon Sep 17 00:00:00 2001 From: Romain Derie Date: Fri, 22 Nov 2019 13:20:23 +0000 Subject: [PATCH] [IMP] website: introduce cookies bar on website With this commit, it is now possible to activate a cookies bar on a website through the res.settings. The cookies bar can be edited in the frontend by entering edit mode. The cookies bar will appear until the user clicks on 'I agree'. Part of https://github.com/odoo/odoo/pull/41294 task-2087003 closes odoo/odoo#41294 Signed-off-by: Quentin Smetz (qsm) --- addons/website/data/website_data.xml | 126 ++++++++++++++++++ addons/website/models/res_config_settings.py | 1 + addons/website/models/website.py | 21 +++ .../src/js/backend/res_config_settings.js | 41 +++++- .../static/src/js/editor/snippets.options.js | 58 ++++++++ .../static/src/snippets/s_popup/000.js | 8 +- .../static/src/snippets/s_popup/000.scss | 3 +- .../static/src/snippets/s_popup/options.js | 5 +- .../static/src/xml/website.cookies_bar.xml | 75 +++++++++++ .../src/xml/website.res_config_settings.xml | 22 +++ .../views/res_config_settings_views.xml | 13 +- addons/website/views/snippets/s_popup.xml | 15 ++- addons/website/views/snippets/snippets.xml | 9 ++ addons/website/views/website_templates.xml | 25 ++++ 14 files changed, 407 insertions(+), 15 deletions(-) create mode 100644 addons/website/static/src/xml/website.cookies_bar.xml create mode 100644 addons/website/static/src/xml/website.res_config_settings.xml diff --git a/addons/website/data/website_data.xml b/addons/website/data/website_data.xml index 2304a8b3a7e..d24e2683fde 100644 --- a/addons/website/data/website_data.xml +++ b/addons/website/data/website_data.xml @@ -107,6 +107,132 @@ + + True diff --git a/addons/website/models/res_config_settings.py b/addons/website/models/res_config_settings.py index 85449fd23c2..054ec2d019c 100644 --- a/addons/website/models/res_config_settings.py +++ b/addons/website/models/res_config_settings.py @@ -28,6 +28,7 @@ class ResConfigSettings(models.TransientModel): website_default_lang_code = fields.Char('Default language code', related='website_id.default_lang_id.code', readonly=False) specific_user_account = fields.Boolean(related='website_id.specific_user_account', readonly=False, help='Are newly created user accounts website specific') + website_cookies_bar = fields.Boolean(related='website_id.cookies_bar', readonly=False) google_analytics_key = fields.Char('Google Analytics Key', related='website_id.google_analytics_key', readonly=False) google_management_client_id = fields.Char('Google Client ID', related='website_id.google_management_client_id', readonly=False) diff --git a/addons/website/models/website.py b/addons/website/models/website.py index f9a6a9f4c77..6de22c8f241 100644 --- a/addons/website/models/website.py +++ b/addons/website/models/website.py @@ -59,6 +59,7 @@ class Website(models.Model): language_ids = fields.Many2many('res.lang', 'website_lang_rel', 'website_id', 'lang_id', 'Languages', default=_active_languages) default_lang_id = fields.Many2one('res.lang', string="Default Language", default=_default_language, required=True) auto_redirect_lang = fields.Boolean('Autoredirect Language', default=True, help="Should users be redirected to their browser's language") + cookies_bar = fields.Boolean('Cookies Bar', help="Display a customizable cookies bar on your website.") def _default_social_facebook(self): return self.env.ref('base.main_company').social_facebook @@ -168,6 +169,26 @@ class Website(models.Model): if 'cdn_activated' in values or 'cdn_url' in values or 'cdn_filters' in values: # invalidate the caches from static node at compile time self.env['ir.qweb'].clear_caches() + + if 'cookies_bar' in values: + if values['cookies_bar']: + cookies_view = self.env.ref('website.cookie_policy', raise_if_not_found=False) + if cookies_view: + cookies_view.with_context(website_id=self.id).write({'website_id': self.id}) + specific_cook_view = self.with_context(website_id=self.id).viewref('website.cookie_policy') + self.env['website.page'].create({ + 'is_published': True, + 'website_indexed': False, + 'url': '/cookie-policy', + 'website_id': self.id, + 'view_id': specific_cook_view.id, + }) + else: + self.env['website.page'].search([ + ('website_id', '=', self.id), + ('url', '=', '/cookie-policy'), + ]).unlink() + return result @api.model diff --git a/addons/website/static/src/js/backend/res_config_settings.js b/addons/website/static/src/js/backend/res_config_settings.js index 47a52579447..ad9373d26f1 100644 --- a/addons/website/static/src/js/backend/res_config_settings.js +++ b/addons/website/static/src/js/backend/res_config_settings.js @@ -1,7 +1,14 @@ odoo.define('website.settings', function (require) { -var BaseSettingController = require('base.settings').Controller; -var FormController = require('web.FormController'); +const BaseSettingController = require('base.settings').Controller; +const core = require('web.core'); +const Dialog = require('web.Dialog'); +const FieldBoolean = require('web.basic_fields').FieldBoolean; +const fieldRegistry = require('web.field_registry'); +const FormController = require('web.FormController'); + +const QWeb = core.qweb; +const _t = core._t; BaseSettingController.include({ @@ -33,4 +40,34 @@ BaseSettingController.include({ }, }); +const WebsiteCookiesbarField = FieldBoolean.extend({ + xmlDependencies: ['/website/static/src/xml/website.res_config_settings.xml'], + + _onChange: function () { + const checked = this.$input[0].checked; + if (!checked) { + return this._setValue(checked); + } + + const cancelCallback = () => this.$input[0].checked = !checked; + Dialog.confirm(this, null, { + title: _t("Please confirm"), + $content: QWeb.render('website.res_config_settings.cookies_modal_main'), + buttons: [{ + text: 'Do not activate', + classes: 'btn-primary', + close: true, + click: cancelCallback, + }, + { + text: 'Activate anyway', + close: true, + click: () => this._setValue(checked), + }], + cancel_callback: cancelCallback, + }); + }, +}); + +fieldRegistry.add('website_cookiesbar_field', WebsiteCookiesbarField); }); diff --git a/addons/website/static/src/js/editor/snippets.options.js b/addons/website/static/src/js/editor/snippets.options.js index 9765bc57473..0ef07ccc12e 100644 --- a/addons/website/static/src/js/editor/snippets.options.js +++ b/addons/website/static/src/js/editor/snippets.options.js @@ -1,6 +1,7 @@ odoo.define('website.editor.snippets.options', function (require) { 'use strict'; +require('website.s_popup_options'); var core = require('web.core'); var Dialog = require('web.Dialog'); const wUtils = require('website.utils'); @@ -913,6 +914,63 @@ options.registry.anchor = options.Class.extend({ }, }); +options.registry.CookiesBar = options.registry.SnippetPopup.extend({ + xmlDependencies: (options.registry.SnippetPopup.prototype.xmlDependencies || []).concat( + ['/website/static/src/xml/website.cookies_bar.xml'] + ), + + //-------------------------------------------------------------------------- + // Options + //-------------------------------------------------------------------------- + + /** + * Change the cookies bar layout. + * + * @see this.selectClass for parameters + */ + selectLayout: function (previewMode, widgetValue, params) { + let websiteId; + this.trigger_up('context_get', { + callback: function (ctx) { + websiteId = ctx['website_id']; + }, + }); + + const $template = $(qweb.render(`website.cookies_bar.${widgetValue}`, { + websiteId: websiteId, + })); + + const $content = this.$target.find('.s_popup_content'); + const selectorsToKeep = [ + '.o_cookies_bar_text_button', + '.o_cookies_bar_text_policy', + '.o_cookies_bar_text_title', + '.o_cookies_bar_text_primary', + '.o_cookies_bar_text_secondary', + ]; + + if (this.$savedSelectors === undefined) { + this.$savedSelectors = []; + } + + for (const selector of selectorsToKeep) { + const $currentLayoutEls = $content.find(selector).contents(); + const $newLayoutEl = $template.find(selector); + if ($currentLayoutEls.length) { + // save value before change, eg 'title' is not inside 'discrete' template + // but we want to preserve it in case of select another layout later + this.$savedSelectors[selector] = $currentLayoutEls; + } + const $savedSelector = this.$savedSelectors[selector]; + if ($newLayoutEl.length && $savedSelector && $savedSelector.length) { + $newLayoutEl.empty().append($savedSelector); + } + } + + $content.empty().append($template); + }, +}); + /** * Allows edition of 'cover_properties' in website models which have such * fields (blogs, posts, events, ...). diff --git a/addons/website/static/src/snippets/s_popup/000.js b/addons/website/static/src/snippets/s_popup/000.js index 0e7983002f0..16820dcc988 100644 --- a/addons/website/static/src/snippets/s_popup/000.js +++ b/addons/website/static/src/snippets/s_popup/000.js @@ -8,14 +8,14 @@ const utils = require('web.utils'); const PopupWidget = publicWidget.Widget.extend({ selector: '.s_popup', events: { - 'click .s_popup_close': '_onCloseClick', + 'click .js_close_popup': '_onCloseClick', }, /** * @override */ start: function () { - if (!utils.get_cookie(this.$target.attr('id'))) { + if (!utils.get_cookie(this.$el.attr('id'))) { this._bindPopup(); } return this._super(...arguments); @@ -77,8 +77,8 @@ const PopupWidget = publicWidget.Widget.extend({ * @private */ _onCloseClick: function () { - const nbDays = this.$target.find('.s_popup_main').data('consentsDelay'); - utils.set_cookie(this.$target.attr('id'), true, nbDays * 24 * 60 * 60); + const nbDays = this.$el.find('.s_popup_main').data('consentsDuration'); + utils.set_cookie(this.$el.attr('id'), true, nbDays * 24 * 60 * 60); this._hidePopup(); }, }); diff --git a/addons/website/static/src/snippets/s_popup/000.scss b/addons/website/static/src/snippets/s_popup/000.scss index bb14c100322..71566512868 100644 --- a/addons/website/static/src/snippets/s_popup/000.scss +++ b/addons/website/static/src/snippets/s_popup/000.scss @@ -1,7 +1,8 @@ // s_popup .s_popup_main { .s_popup_content { - min-height: var(--o-we-toolbar-height); + // keep lower than

height (cookies bar) + min-height: $o-font-size-base * $o-line-height-base; box-shadow: $modal-content-box-shadow-sm-up; .container { // keep margin when fixed bottom diff --git a/addons/website/static/src/snippets/s_popup/options.js b/addons/website/static/src/snippets/s_popup/options.js index 4c43041c614..842cc284c83 100644 --- a/addons/website/static/src/snippets/s_popup/options.js +++ b/addons/website/static/src/snippets/s_popup/options.js @@ -8,7 +8,10 @@ options.registry.SnippetPopup = options.Class.extend({ * @override */ start: function () { - this.$target.find('.s_popup_close').on('click', () => { + // Note: the link are excluded here so that internal modal buttons do + // not close the popup as we want to allow edition of those buttons. + this.$target.find('.js_close_popup:not(a, .btn)').on('click', ev => { + ev.stopPropagation(); this.onTargetHide(); this.trigger_up('snippet_option_visibility_update', {show: false}); }); diff --git a/addons/website/static/src/xml/website.cookies_bar.xml b/addons/website/static/src/xml/website.cookies_bar.xml new file mode 100644 index 00000000000..8f43ccb3698 --- /dev/null +++ b/addons/website/static/src/xml/website.cookies_bar.xml @@ -0,0 +1,75 @@ + + + +

+

Respecting your privacy is our priority.

+
+ + +
+ We use cookies to provide you a better user experience. +
+
+ +
+ 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. +
+
+ + +
+
+
+ +
+ Cookie Policy + I agree +
+
+
+ +
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ +
+
+ Website Logo +

+
+ +
+
+
+ +
+
+ +
+
+ Cookie Policy +
+ I agree +
+
+
+ diff --git a/addons/website/static/src/xml/website.res_config_settings.xml b/addons/website/static/src/xml/website.res_config_settings.xml new file mode 100644 index 00000000000..6b6d48172bd --- /dev/null +++ b/addons/website/static/src/xml/website.res_config_settings.xml @@ -0,0 +1,22 @@ + + + +
+

+ Cookie bars may significantly impair the experience of + your visitors. We recommend you avoid them unless you have + verified with a legal advisor that you absolutely need cookie + consent in your country. +

+

+ For session cookies, authentification and analytics*, + you do not need to ask for the consent (see e.g. Opinion + 04/2012 on Cookie Consent Exemption by the EU Art.29 WP). +

+

+ * provided that your analytics is anonymized, which is not the + case by default with Google Analytics. +

+
+
+
diff --git a/addons/website/views/res_config_settings_views.xml b/addons/website/views/res_config_settings_views.xml index 6a32d2b55a7..870eb8cd59e 100644 --- a/addons/website/views/res_config_settings_views.xml +++ b/addons/website/views/res_config_settings_views.xml @@ -127,7 +127,18 @@ - +
+
+ +
+
+
+
diff --git a/addons/website/views/snippets/s_popup.xml b/addons/website/views/snippets/s_popup.xml index 3ca662e8ff6..ea0b13a1c14 100644 --- a/addons/website/views/snippets/s_popup.xml +++ b/addons/website/views/snippets/s_popup.xml @@ -4,10 +4,10 @@ diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml index b40c459e081..dd6092435dc 100644 --- a/addons/website/views/website_templates.xml +++ b/addons/website/views/website_templates.xml @@ -271,6 +271,31 @@ + +