[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) <qsm@odoo.com>
This commit is contained in:
Romain Derie
2020-01-29 14:53:38 +00:00
committed by qsm-odoo
parent 3bce5277e3
commit 3028e9a864
14 changed files with 407 additions and 15 deletions
+126
View File
@@ -107,6 +107,132 @@
</record>
</data>
<template id="cookie_policy" name="Cookie Policy">
<t t-call="website.layout">
<div id="wrap" class="oe_structure">
<section class="pt8 pb8">
<div class="container">
<h2 class="pt16">Cookie Policy</h2>
<p>
Cookies are small bits of text sent by our servers to your computer or device when you access our services.
They are stored in your browser and later sent back to our servers so that we can provide contextual content.
Without cookies, using the web would be a much more frustrating experience.
We use them to support your activities on our website. For example, your session (so you don't have to login again) or your shopping cart.
<br/>
Cookies are also used to help us understand your preferences based on previous or current activity on our website (the pages you have
visited), your language and country, which enables us to provide you with improved services.
We also use cookies to help us compile aggregate data about site traffic and site interaction so that we can offer
better site experiences and tools in the future.
</p>
<p>
Here is an overview of the cookies that may be stored on your device when you visit our website:
</p>
<div class="table-responsive">
<table class="small table table-bordered text-center">
<thead class="thead-light">
<tr>
<th scope="col" style="width: 20%">Category of Cookie</th>
<th scope="col" style="width: 50%; min-width: 200px;">Purpose</th>
<th scope="col" style="width: 30%">Examples</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<p>Session &amp; Security</p>
</td>
<td>
<p>
Authenticate users, protect user data and allow the website to deliver the services users expects,
such as maintaining the content of their cart, or allowing file uploads.
</p>
<p>The website will not work properly if you reject or discard those cookies.</p>
</td>
<td>
session_id (Odoo)<br/>
fileToken (Odoo)
</td>
</tr>
<tr>
<td>
<p>Preferences</p>
</td>
<td>
<p>Remember information about the preferred look or behavior of the website, such as your preferred language or region.</p>
<p>Your experience may be degraded if you discard those cookies, but the website will still work.</p>
</td>
<td>
frontend_lang (Odoo)
</td>
</tr>
<tr>
<td>Interaction History</td>
<td>
<p>
Users to collect information about your interactions with the website, the pages you've seen,
and any specific marketing campaign that brought you to the website.
</p>
<p>We may not be able to provide the best service to you if you reject those cookies, but the website will work.</p>
</td>
<td>
im_livechat_previous_operator_pid (Odoo)<br/>
utm_campaign (Odoo)<br/>
utm_source (Odoo)<br/>
utm_medium (Odoo)
</td>
</tr>
<tr>
<td>
<p>Advertising &amp; Marketing</p>
</td>
<td>
<p>
Used to make advertising more engaging to users and more valuable to publishers and advertisers,
such as providing more relevant ads when you visit other websites that display ads or to improve reporting on ad campaign performance.
</p>
<p>Note that some third-party services may install additional cookies on your browser in order to identify you.</p>
<p>
You may opt-out of a third-party's use of cookies by visiting the <a href="https://optout.networkadvertising.org/?c=1" rel="nofollow">Network Advertising Initiative opt-out page</a>.
The website will still work if you reject or discard those cookies.
</p>
</td>
<td>
__gads (Google)<br/>
__gac (Google)
</td>
</tr>
<tr>
<td>
<p>Analytics</p>
</td>
<td>
<p>
Understand how visitors engage with our website, via Google Analytics.
Learn more about <a href="https://developers.google.com/analytics/resources/concepts/gaConceptsCookies?hl=en">Analytics cookies and privacy information.</a>
</p>
<p>The website will still work if you reject or discard those cookies.</p>
</td>
<td>
_ga (Google)<br/>
_gat (Google)<br/>
_gid (Google)<br/>
_gac_* (Google)
</td>
</tr>
</tbody>
</table>
</div>
<p>
You can choose to have your computer warn you each time a cookie is being sent, or you can choose to turn off all cookies.
Each browser is a little different, so look at your browser's Help menu to learn the correct way to modify your cookies.
</p>
<p>We do not currently support Do Not Track signals, as there is no industry standard for compliance.</p>
</div>
</section>
</div>
</t>
</template>
<data noupdate="1">
<record id="homepage_page" model="website.page">
<field name="is_published">True</field>
@@ -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)
+21
View File
@@ -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
@@ -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);
});
@@ -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, ...).
@@ -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();
},
});
@@ -1,7 +1,8 @@
// s_popup
.s_popup_main {
.s_popup_content {
min-height: var(--o-we-toolbar-height);
// keep lower than <p> 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
@@ -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});
});
@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="website.cookies_bar.text_title">
<div class="o_cookies_bar_text_title">
<p>Respecting your privacy is our priority.</p>
</div>
</t>
<t t-name="website.cookies_bar.text_primary">
<div class="o_cookies_bar_text_primary">
We use cookies to provide you a better user experience.
</div>
</t>
<t t-name="website.cookies_bar.text_secondary">
<div class="o_cookies_bar_text_secondary">
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.
<br/>
By clicking on this banner, you give us permission to collect data.
</div>
</t>
<t t-name="website.cookies_bar.discrete">
<section class="pt4 pb4">
<div class="container text-600 d-flex align-items-center">
<div class="mb0">
<t t-call="website.cookies_bar.text_primary"/>
</div>
<a href="/cookie-policy" class="o_cookies_bar_text_policy btn btn-link btn-sm text-nowrap ml-auto">Cookie Policy</a>
<a href="#" role="button" class="js_close_popup o_cookies_bar_text_button btn btn-primary btn-sm text-nowrap">I agree</a>
</div>
</section>
</t>
<t t-name="website.cookies_bar.classic">
<section class="pt24 pb24">
<div class="container text-600 d-flex align-items-center">
<div>
<div class="h3 text-900">
<t t-call="website.cookies_bar.text_title"/>
</div>
<div class="small">
<t t-call="website.cookies_bar.text_primary"/>
</div>
<div class="small">
<t t-call="website.cookies_bar.text_secondary"/>
</div>
</div>
<div class="ml-auto">
<a href="#" role="button" class="js_close_popup o_cookies_bar_text_button btn btn-primary btn-block text-nowrap">I agree</a>
<a href="/cookie-policy" class="o_cookies_bar_text_policy btn btn-link btn-sm text-nowrap">Cookie Policy</a>
</div>
</div>
</section>
</t>
<t t-name="website.cookies_bar.popup">
<section class="p-5">
<div class="container text-800 text-center">
<img t-attf-src="/web/image/website/#{websiteId}/logo/250x250" class="img img-fluid" alt="Website Logo"/>
<br/><br/>
<div class="h3">
<t t-call="website.cookies_bar.text_title"/>
</div>
<br/>
<div class="mb0 text-left">
<t t-call="website.cookies_bar.text_primary"/>
</div>
<div class="text-left">
<t t-call="website.cookies_bar.text_secondary"/>
</div>
<br/>
<a href="#" role="button" class="js_close_popup o_cookies_bar_text_button btn btn-primary btn-lg">Cookie Policy</a>
<br/>
<a href="/cookie-policy" class="o_cookies_bar_text_policy btn btn-link">I agree</a>
</div>
</section>
</t>
</templates>
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="website.res_config_settings.cookies_modal_main">
<main role="alert">
<p>
<b>Cookie bars may significantly impair the experience</b> 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.
</p>
<p>
For session cookies, authentification and analytics*,
<b>you do not need to ask for the consent</b> (see e.g. Opinion
04/2012 on Cookie Consent Exemption by the EU Art.29 WP).
</p>
<p>
* provided that your analytics is anonymized, which is not the
case by default with Google Analytics.
</p>
</main>
</t>
</templates>
@@ -127,7 +127,18 @@
</div>
</div>
</div>
<div class="col-12 col-lg-6 o_setting_box" id="website_cookies_bar_setting">
<div class="o_setting_left_pane">
<field name="website_cookies_bar" widget="website_cookiesbar_field"/>
</div>
<div class="o_setting_right_pane">
<label for="website_cookies_bar"/>
<span class="fa fa-lg fa-globe" title="Values set here are website-specific." groups="website.group_multi_website"/>
<div class="text-muted">
Display a customizable cookies bar on your website.
</div>
</div>
</div>
<div class="col-12 col-lg-6 o_setting_box" id="cdn_setting" title="A CDN helps you serve your website’s content with high availability and high performance to any visitor wherever they are located." groups="base.group_no_one">
<div class="o_setting_left_pane">
<field name="cdn_activated"/>
+9 -6
View File
@@ -4,10 +4,10 @@
<template id="s_popup" name="Popup">
<div class="s_popup o_snippet_invisible">
<div class='modal s_popup_main popup_backdrop s_popup_center o_one_page bg-black-25 '
data-show-after="5000" data-display="afterDelay" data-consents-delay="7">
data-show-after="5000" data-display="afterDelay" data-consents-duration="7">
<div class='modal-dialog modal-dialog-centered s_popup_frame modal-md'>
<div class='oe_structure modal-content s_popup_content oe_img_bg oe_custom_bg' style="background-color: #FFFFFF">
<div class='s_popup_close o_we_no_overlay o_not_editable'>&amp;times;</div>
<div class='js_close_popup s_popup_close o_we_no_overlay o_not_editable'>&amp;times;</div>
<section class="s_banner parallax pt96 pb96">
<span class="s_parallax_bg oe_img_bg oe_custom_bg" style="background-image: url('/web/image/website.s_banner_default_image');"></span>
<div class="container">
@@ -37,24 +37,27 @@
<div data-js="SnippetPopup" data-selector=".s_popup" data-target=".s_popup_main">
<we-select string="Position">
<we-button data-set-layout="modal" data-name="position_centered">Center</we-button>
<we-button data-set-layout="fixed">Bottom</we-button>
<we-button data-set-layout="fixed" data-name="position_bottom">Bottom</we-button>
</we-select>
<we-select string="Size" data-apply-to=".s_popup_frame">
<we-button data-select-class="modal-sm">Small</we-button>
<we-button data-select-class="modal-md">Medium</we-button>
<we-button data-select-class="modal-md" data-name="s_popup_size_md">Medium</we-button>
<we-button data-select-class="modal-lg">Large</we-button>
<we-button data-select-class="modal-xl">Big</we-button>
<we-button data-select-class="s_popup_full">Full</we-button>
<we-button data-select-class="s_popup_full" data-name="s_popup_size_full">Full</we-button>
</we-select>
<we-colorpicker string="Backdrop Color" data-select-style="true"
data-css-property="background-color" data-color-prefix="bg-" data-dependencies="position_centered"/>
</div>
<div data-js="SnippetPopup" data-selector=".s_popup" data-exclude="#website_cookies_bar" data-target=".s_popup_main">
<we-colorpicker string="Text Color" data-select-style="true" data-css-property="color" data-color-prefix="text-"/>
<we-select string="Display" data-attribute-name="display" data-attribute-default-value="always">
<we-button data-select-data-attribute="afterDelay" data-name="show_delay">Delay</we-button>
<we-button data-select-data-attribute="mouseExit">On Exit</we-button>
</we-select>
<we-input string="Delay" data-select-data-attribute="" data-attribute-name="showAfter" data-unit="s" data-save-unit="ms" data-dependencies="show_delay"/>
<we-input string="Period between consents" data-select-data-attribute="7days" data-attribute-name="consentsDelay" data-unit="days"/>
<t t-set="unit_popup_duration">days</t>
<we-input string="Hide For" title="Once the user closes the popup, it won't be shown again for that period of time." t-attf-data-select-data-attribute="7#{unit_popup_duration}" data-attribute-name="consentsDuration" t-att-data-unit="unit_popup_duration"/>
<we-select string="Show on" data-no-preview="true">
<we-button data-move-block="moveToBody">This page</we-button>
<we-button data-move-block="moveToFooter">All pages</we-button>
@@ -467,5 +467,14 @@
data-name="scroll_button_opt"/>
</we-row>
</div>
<!-- Cookies Bar -->
<div data-selector="#website_cookies_bar" data-js="CookiesBar" data-target=".s_popup_main">
<we-select string="Layout" class="o_we_inline">
<we-button data-select-class="o_cookies_discrete" data-select-layout="discrete" data-trigger="position_bottom,s_popup_size_full">Discrete</we-button>
<we-button data-select-class="o_cookies_classic" data-select-layout="classic" data-trigger="position_bottom,s_popup_size_full">Classic</we-button>
<we-button data-select-class="o_cookies_popup" data-select-layout="popup" data-trigger="position_centered,s_popup_size_md">Popup</we-button>
</we-select>
</div>
</template>
</odoo>
@@ -271,6 +271,31 @@
</xpath>
</template>
<template id="cookies_bar" inherit_id="website.footer_custom" name="Cookies Bar">
<xpath expr="//div[@id='footer']" position="before">
<div id="website_cookies_bar" t-if="website.cookies_bar" class="s_popup o_snippet_invisible" data-name="Cookies Bar">
<div class='s_popup_main s_popup_fixed popup_backdrop d-none o_cookies_discrete'
data-show-after="500" data-display="afterDelay" data-consents-duration="999">
<div class='s_popup_frame s_popup_full'>
<div class='oe_structure s_popup_content bg-white'>
<section class="pt4 pb4">
<div class="container text-600 d-flex align-items-center">
<div class="mb0">
<div class="o_cookies_bar_text_primary">
We use cookies to provide you a better user experience.
</div>
</div>
<a href="/cookie-policy" class="o_cookies_bar_text_policy btn btn-link btn-sm text-nowrap ml-auto">Cookie Policy</a>
<a href="#" role="button" class="js_close_popup o_cookies_bar_text_button btn btn-primary btn-sm text-nowrap">I agree</a>
</div>
</section>
</div>
</div>
</div>
</div>
</xpath>
</template>
<template id="language_selector" name="Language Selector">
<ul class="js_language_selector mb0 list-inline" t-if="(request and request.is_frontend_multilang and len(languages) &gt; 1) or (website and (editable or translatable))">
<li class="list-inline-item">