diff --git a/src/pretix/presale/context.py b/src/pretix/presale/context.py index ebb939b9e..80a9a2f94 100644 --- a/src/pretix/presale/context.py +++ b/src/pretix/presale/context.py @@ -52,7 +52,6 @@ from .signals import ( footer_link, global_footer_link, global_html_footer, global_html_head, global_html_page_header, html_footer, html_head, html_page_header, ) -from .views.cart import cart_session, get_or_create_cart_id from .views.theme import _get_source_cache_key logger = logging.getLogger(__name__) @@ -157,10 +156,9 @@ def _default_context(request): ctx['languages'] = [get_language_info(code) for code in request.event.settings.locales] ctx['cookie_providers'] = get_cookie_providers(request.event, request) - if get_or_create_cart_id(request, create=False): - c = cart_session(request) - if "widget_data" in c and c["widget_data"].get("consent"): - ctx['cookie_consent_from_widget'] = c["widget_data"].get("consent").split(",") + if 'requested_consent_from_widget' in request.session: + # We only need to present this to the frontend once, JavaScript will then save it to localStorage/sessionStorage + ctx['cookie_consent_from_widget'] = request.session.pop("requested_consent_from_widget").split(",") if request.resolver_match: ctx['cart_namespace'] = request.resolver_match.kwargs.get('cart_namespace', '') diff --git a/src/pretix/presale/views/__init__.py b/src/pretix/presale/views/__init__.py index e5dd3de07..5b893c3dd 100644 --- a/src/pretix/presale/views/__init__.py +++ b/src/pretix/presale/views/__init__.py @@ -467,6 +467,9 @@ def iframe_entry_view_wrapper(view_func): if 'iframe' in request.GET: request.session['iframe_session'] = True + if request.GET.get("consent"): + request.session["requested_consent_from_widget"] = request.GET["consent"] + locale = request.GET.get('locale') if locale and locale in [lc for lc, ll in settings.LANGUAGES]: lng = locale diff --git a/src/pretix/static/pretixpresale/js/ui/cookieconsent.js b/src/pretix/static/pretixpresale/js/ui/cookieconsent.js index bc109db38..ed5ed77f1 100644 --- a/src/pretix/static/pretixpresale/js/ui/cookieconsent.js +++ b/src/pretix/static/pretixpresale/js/ui/cookieconsent.js @@ -4,8 +4,21 @@ $(function () { window.pretix = window.pretix || {}; var storage_key = $("#cookie-consent-storage-key").text(); - function update_consent(consent) { - if (storage_key && window.localStorage) window.localStorage[storage_key] = JSON.stringify(consent); + var widget_consent = $("#cookie-consent-from-widget").text(); + var consent_checkboxes = $("#cookie-consent-details input[type=checkbox][name]"); + var consent_modal = $("#cookie-consent-modal"); + + function update_consent(consent, sessionOnly) { + if (storage_key && window.sessionStorage && sessionOnly) { + if (!window.localStorage[storage_key] || window.localStorage[storage_key] !== JSON.stringify(consent)) { + // No need to write to sessionStorage if the value is identical to the one in localStorage + window.sessionStorage[storage_key] = JSON.stringify(consent); + } + } else if (storage_key && window.localStorage) { + window.localStorage[storage_key] = JSON.stringify(consent); + // When saving permanent storage, clear session storage + window.sessionStorage.removeItem(storage_key); + } window.pretix.cookie_consent = consent; // Event() is not supported by IE11, see ployfill here: @@ -16,44 +29,69 @@ $(function () { } if (!storage_key) { - update_consent(null); + // We are not on a page where the consent should run, fire the change event with empty consent but don't + // actually store anything. + update_consent(null, false); return; } if (!window.localStorage) { // Consent not supported. Even IE8 supports it, so we're on a weird embedded device. // Let's just say we don't consent then. - update_consent({}) + update_consent({}, false) return; } - var storage_val = window.localStorage[storage_key]; - var show_dialog = !storage_val; - var consent_checkboxes = $("#cookie-consent-details input[type=checkbox][name]"); - var consent_modal = $("#cookie-consent-modal"); - var widget_consent = $("#cookie-consent-from-widget").text(); - if (widget_consent) { + var storage_val, consent_source, save_for_session_only; + if (window.sessionStorage[storage_key]) { + // A manual input was given inside a widget. This is the user's last explicit choice and takes precedence – + // as long as they are in the widget. + storage_val = JSON.parse(window.sessionStorage[storage_key]); + consent_source = 'sessionStorage'; + save_for_session_only = true; + } else if (widget_consent) { + // An input was given through the widget. This takes precedence over localStorage as we need to assume the + // widget embedder is doing a correct job. If the user never visited the page without the widget, we also + // use it to prefill local storage to save the user from seeing more cookie banners. (This will stop working + // when browsers partition local storage of iframes, anyway.) If the user does have visited the page without + // the widget before and has a consent setting in localStorage, we respect the widget consent *only* within + // the widget -- hence, we save it into sessionStorage. We need to save it into sessionStorage because the + // widget_data value itself will not "survive" the entire lifetime of the tab, i.e. it is no longer present + // after the order was confirmed. widget_consent = JSON.parse(widget_consent); - storage_val = {} + storage_val = {}; consent_checkboxes.each(function () { this.checked = storage_val[this.name] = widget_consent.indexOf(this.name) > -1; - }) - show_dialog = false; - $("#cookie-consent-reopen").hide(); - } else if (storage_val) { - storage_val = JSON.parse(storage_val); - consent_checkboxes.each(function () { - if (typeof storage_val[this.name] === "undefined") { - // A new cookie type has been added that we haven't asked for yet - show_dialog = true; - } else if (storage_val[this.name]) { - this.checked = true; - } - }) + }); + consent_source = 'widget'; + save_for_session_only = !!window.localStorage[storage_key]; + } else if (window.localStorage[storage_key]) { + // The user made a specific selection, let's use that. + storage_val = JSON.parse(window.localStorage[storage_key]); + consent_source = 'localStorage'; + save_for_session_only = false; } else { - storage_val = {} + // No consent given, dialog will be shown. + storage_val = {}; + consent_source = 'new'; + save_for_session_only = false; } - update_consent(storage_val); + + var show_dialog = false; + consent_checkboxes.each(function () { + if (typeof storage_val[this.name] === "undefined") { + // A new cookie type has been added that we haven't asked for yet + if (consent_source === "widget") { + // Trust the widget, keep it as "no consent" + } else { + show_dialog = true; + } + } else if (storage_val[this.name]) { + this.checked = true; + } + }) + + update_consent(storage_val, save_for_session_only); function _set_button_text () { var btn = $("#cookie-consent-button-no"); @@ -83,7 +121,8 @@ $(function () { consent[this.name] = this.checked = consent_all || this.checked; }); if (consent_all) _set_button_text(); - update_consent(consent); + // Always save explicit consent to permanent storage + update_consent(consent, false); }); consent_checkboxes.on("change", _set_button_text); $("#cookie-consent-reopen").on("click", function (e) { diff --git a/src/pretix/static/pretixpresale/js/widget/widget.js b/src/pretix/static/pretixpresale/js/widget/widget.js index d8f922cc1..621de3042 100644 --- a/src/pretix/static/pretixpresale/js/widget/widget.js +++ b/src/pretix/static/pretixpresale/js/widget/widget.js @@ -313,9 +313,9 @@ Vue.component('availbox', { waiting_list_url: function () { var u if (this.item.has_variations) { - u = this.$root.target_url + 'w/' + widget_id + '/waitinglist/?item=' + this.item.id + '&var=' + this.variation.id + '&widget_data=' + encodeURIComponent(this.$root.widget_data_json); + u = this.$root.target_url + 'w/' + widget_id + '/waitinglist/?item=' + this.item.id + '&var=' + this.variation.id + '&widget_data=' + encodeURIComponent(this.$root.widget_data_json) + this.$root.consent_parameter; } else { - u = this.$root.target_url + 'w/' + widget_id + '/waitinglist/?item=' + this.item.id + '&widget_data=' + encodeURIComponent(this.$root.widget_data_json); + u = this.$root.target_url + 'w/' + widget_id + '/waitinglist/?item=' + this.item.id + '&widget_data=' + encodeURIComponent(this.$root.widget_data_json) + this.$root.consent_parameter; } if (this.$root.subevent) { u += '&subevent=' + this.$root.subevent @@ -786,6 +786,7 @@ var shared_methods = { if (this.$root.additionalURLParams) { redirect_url += '&' + this.$root.additionalURLParams; } + redirect_url += this.$root.consent_parameter; this.$root.overlay.frame_src = redirect_url; }, voucher_open: function (voucher) { @@ -797,6 +798,7 @@ var shared_methods = { if (this.$root.additionalURLParams) { redirect_url += '&' + this.$root.additionalURLParams; } + redirect_url += this.$root.consent_parameter; if (this.$root.useIframe) { this.$root.overlay.frame_src = redirect_url; } else { @@ -815,7 +817,7 @@ var shared_methods = { redirect_url += '&take_cart_id=' + this.$root.cart_id; } if (this.$root.widget_data) { - redirect_url += '&widget_data=' + encodeURIComponent(this.$root.widget_data_json); + redirect_url += '&widget_data=' + encodeURIComponent(this.$root.widget_data_json) + this.$root.consent_parameter; } if (this.$root.additionalURLParams) { redirect_url += '&' + this.$root.additionalURLParams; @@ -1017,6 +1019,7 @@ Vue.component('pretix-widget-event-form', { + '' + '' + '' + + '' // Error message + '
' @@ -1072,6 +1075,7 @@ Vue.component('pretix-widget-event-form', { + '' + '' + '' + + '' + '' + '