forked from CGM_Public/pretix_original
New mechanism to transfer cookie consent from the widget (Z#23181715) (#4875)
* Cookie consent: Add separate storage layer for widget * Widget: Move cookie consent out of widget_data * Add consent parameter to forms
This commit is contained in:
@@ -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', '')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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', {
|
||||
+ '<input type="hidden" name="_voucher_code" :value="$root.voucher_code" v-if="$root.voucher_code">'
|
||||
+ '<input type="hidden" name="subevent" :value="$root.subevent" />'
|
||||
+ '<input type="hidden" name="widget_data" :value="$root.widget_data_json" />'
|
||||
+ '<input v-if="$root.consent_parameter_value" type="hidden" name="consent" :value="$root.consent_parameter_value" />'
|
||||
|
||||
// Error message
|
||||
+ '<div class="pretix-widget-error-message" v-if="$root.error">{{ $root.error }}</div>'
|
||||
@@ -1072,6 +1075,7 @@ Vue.component('pretix-widget-event-form', {
|
||||
+ '</div>'
|
||||
+ '<input type="hidden" name="subevent" :value="$root.subevent" />'
|
||||
+ '<input type="hidden" name="widget_data" :value="$root.widget_data_json" />'
|
||||
+ '<input v-if="$root.consent_parameter_value" type="hidden" name="consent" :value="$root.consent_parameter_value" />'
|
||||
+ '<input type="hidden" name="locale" value="' + lang + '" />'
|
||||
+ '<div class="pretix-widget-voucher-button-wrap">'
|
||||
+ '<button @click="$parent.redeem">' + strings.redeem + '</button>'
|
||||
@@ -1706,6 +1710,7 @@ Vue.component('pretix-button', {
|
||||
+ '<input type="hidden" name="subevent" :value="$root.subevent" />'
|
||||
+ '<input type="hidden" name="locale" :value="$root.lang" />'
|
||||
+ '<input type="hidden" name="widget_data" :value="$root.widget_data_json" />'
|
||||
+ '<input v-if="$root.consent_parameter_value" type="hidden" name="consent" :value="$root.consent_parameter_value" />'
|
||||
+ '<input type="hidden" v-for="item in $root.items" :name="item.item" :value="item.count" />'
|
||||
+ '<button class="pretix-button" @click="buy" v-html="$root.button_text"></button>'
|
||||
+ '</form>'
|
||||
@@ -1923,6 +1928,7 @@ var shared_root_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 {
|
||||
@@ -2033,8 +2039,26 @@ var shared_root_computed = {
|
||||
}
|
||||
return has_priced || cnt_items > 1;
|
||||
},
|
||||
consent_parameter_value: function () {
|
||||
if (typeof this.widget_data["consent"] !== "undefined") {
|
||||
return encodeURIComponent(this.widget_data["consent"]);
|
||||
}
|
||||
return "";
|
||||
},
|
||||
consent_parameter: function () {
|
||||
if (typeof this.widget_data["consent"] !== "undefined") {
|
||||
return "&consent=" + encodeURIComponent(this.widget_data["consent"]);
|
||||
}
|
||||
return "";
|
||||
},
|
||||
widget_data_json: function () {
|
||||
return JSON.stringify(this.widget_data);
|
||||
var cloned_data = Object.assign({}, this.widget_data);
|
||||
if (typeof cloned_data["consent"] !== "undefined") {
|
||||
// Remove consent as we pass it differently. We still keep it as widget_data in the input to avoid breaking
|
||||
// the JS API of the widget.
|
||||
delete cloned_data["consent"];
|
||||
}
|
||||
return JSON.stringify(cloned_data);
|
||||
},
|
||||
additionalURLParams: function () {
|
||||
if (!window.location.search.indexOf('utm_')) {
|
||||
|
||||
Reference in New Issue
Block a user