mirror of
https://github.com/pretix/pretix.git
synced 2026-05-10 16:04:02 +00:00
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,
|
footer_link, global_footer_link, global_html_footer, global_html_head,
|
||||||
global_html_page_header, html_footer, html_head, html_page_header,
|
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
|
from .views.theme import _get_source_cache_key
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
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['languages'] = [get_language_info(code) for code in request.event.settings.locales]
|
||||||
|
|
||||||
ctx['cookie_providers'] = get_cookie_providers(request.event, request)
|
ctx['cookie_providers'] = get_cookie_providers(request.event, request)
|
||||||
if get_or_create_cart_id(request, create=False):
|
if 'requested_consent_from_widget' in request.session:
|
||||||
c = cart_session(request)
|
# We only need to present this to the frontend once, JavaScript will then save it to localStorage/sessionStorage
|
||||||
if "widget_data" in c and c["widget_data"].get("consent"):
|
ctx['cookie_consent_from_widget'] = request.session.pop("requested_consent_from_widget").split(",")
|
||||||
ctx['cookie_consent_from_widget'] = c["widget_data"].get("consent").split(",")
|
|
||||||
|
|
||||||
if request.resolver_match:
|
if request.resolver_match:
|
||||||
ctx['cart_namespace'] = request.resolver_match.kwargs.get('cart_namespace', '')
|
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:
|
if 'iframe' in request.GET:
|
||||||
request.session['iframe_session'] = True
|
request.session['iframe_session'] = True
|
||||||
|
|
||||||
|
if request.GET.get("consent"):
|
||||||
|
request.session["requested_consent_from_widget"] = request.GET["consent"]
|
||||||
|
|
||||||
locale = request.GET.get('locale')
|
locale = request.GET.get('locale')
|
||||||
if locale and locale in [lc for lc, ll in settings.LANGUAGES]:
|
if locale and locale in [lc for lc, ll in settings.LANGUAGES]:
|
||||||
lng = locale
|
lng = locale
|
||||||
|
|||||||
@@ -4,8 +4,21 @@ $(function () {
|
|||||||
window.pretix = window.pretix || {};
|
window.pretix = window.pretix || {};
|
||||||
|
|
||||||
var storage_key = $("#cookie-consent-storage-key").text();
|
var storage_key = $("#cookie-consent-storage-key").text();
|
||||||
function update_consent(consent) {
|
var widget_consent = $("#cookie-consent-from-widget").text();
|
||||||
if (storage_key && window.localStorage) window.localStorage[storage_key] = JSON.stringify(consent);
|
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;
|
window.pretix.cookie_consent = consent;
|
||||||
|
|
||||||
// Event() is not supported by IE11, see ployfill here:
|
// Event() is not supported by IE11, see ployfill here:
|
||||||
@@ -16,44 +29,69 @@ $(function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!storage_key) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!window.localStorage) {
|
if (!window.localStorage) {
|
||||||
// Consent not supported. Even IE8 supports it, so we're on a weird embedded device.
|
// Consent not supported. Even IE8 supports it, so we're on a weird embedded device.
|
||||||
// Let's just say we don't consent then.
|
// Let's just say we don't consent then.
|
||||||
update_consent({})
|
update_consent({}, false)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var storage_val = window.localStorage[storage_key];
|
var storage_val, consent_source, save_for_session_only;
|
||||||
var show_dialog = !storage_val;
|
if (window.sessionStorage[storage_key]) {
|
||||||
var consent_checkboxes = $("#cookie-consent-details input[type=checkbox][name]");
|
// A manual input was given inside a widget. This is the user's last explicit choice and takes precedence –
|
||||||
var consent_modal = $("#cookie-consent-modal");
|
// as long as they are in the widget.
|
||||||
var widget_consent = $("#cookie-consent-from-widget").text();
|
storage_val = JSON.parse(window.sessionStorage[storage_key]);
|
||||||
if (widget_consent) {
|
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);
|
widget_consent = JSON.parse(widget_consent);
|
||||||
storage_val = {}
|
storage_val = {};
|
||||||
consent_checkboxes.each(function () {
|
consent_checkboxes.each(function () {
|
||||||
this.checked = storage_val[this.name] = widget_consent.indexOf(this.name) > -1;
|
this.checked = storage_val[this.name] = widget_consent.indexOf(this.name) > -1;
|
||||||
})
|
});
|
||||||
show_dialog = false;
|
consent_source = 'widget';
|
||||||
$("#cookie-consent-reopen").hide();
|
save_for_session_only = !!window.localStorage[storage_key];
|
||||||
} else if (storage_val) {
|
} else if (window.localStorage[storage_key]) {
|
||||||
storage_val = JSON.parse(storage_val);
|
// The user made a specific selection, let's use that.
|
||||||
consent_checkboxes.each(function () {
|
storage_val = JSON.parse(window.localStorage[storage_key]);
|
||||||
if (typeof storage_val[this.name] === "undefined") {
|
consent_source = 'localStorage';
|
||||||
// A new cookie type has been added that we haven't asked for yet
|
save_for_session_only = false;
|
||||||
show_dialog = true;
|
|
||||||
} else if (storage_val[this.name]) {
|
|
||||||
this.checked = true;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
} 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 () {
|
function _set_button_text () {
|
||||||
var btn = $("#cookie-consent-button-no");
|
var btn = $("#cookie-consent-button-no");
|
||||||
@@ -83,7 +121,8 @@ $(function () {
|
|||||||
consent[this.name] = this.checked = consent_all || this.checked;
|
consent[this.name] = this.checked = consent_all || this.checked;
|
||||||
});
|
});
|
||||||
if (consent_all) _set_button_text();
|
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);
|
consent_checkboxes.on("change", _set_button_text);
|
||||||
$("#cookie-consent-reopen").on("click", function (e) {
|
$("#cookie-consent-reopen").on("click", function (e) {
|
||||||
|
|||||||
@@ -313,9 +313,9 @@ Vue.component('availbox', {
|
|||||||
waiting_list_url: function () {
|
waiting_list_url: function () {
|
||||||
var u
|
var u
|
||||||
if (this.item.has_variations) {
|
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 {
|
} 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) {
|
if (this.$root.subevent) {
|
||||||
u += '&subevent=' + this.$root.subevent
|
u += '&subevent=' + this.$root.subevent
|
||||||
@@ -786,6 +786,7 @@ var shared_methods = {
|
|||||||
if (this.$root.additionalURLParams) {
|
if (this.$root.additionalURLParams) {
|
||||||
redirect_url += '&' + this.$root.additionalURLParams;
|
redirect_url += '&' + this.$root.additionalURLParams;
|
||||||
}
|
}
|
||||||
|
redirect_url += this.$root.consent_parameter;
|
||||||
this.$root.overlay.frame_src = redirect_url;
|
this.$root.overlay.frame_src = redirect_url;
|
||||||
},
|
},
|
||||||
voucher_open: function (voucher) {
|
voucher_open: function (voucher) {
|
||||||
@@ -797,6 +798,7 @@ var shared_methods = {
|
|||||||
if (this.$root.additionalURLParams) {
|
if (this.$root.additionalURLParams) {
|
||||||
redirect_url += '&' + this.$root.additionalURLParams;
|
redirect_url += '&' + this.$root.additionalURLParams;
|
||||||
}
|
}
|
||||||
|
redirect_url += this.$root.consent_parameter;
|
||||||
if (this.$root.useIframe) {
|
if (this.$root.useIframe) {
|
||||||
this.$root.overlay.frame_src = redirect_url;
|
this.$root.overlay.frame_src = redirect_url;
|
||||||
} else {
|
} else {
|
||||||
@@ -815,7 +817,7 @@ var shared_methods = {
|
|||||||
redirect_url += '&take_cart_id=' + this.$root.cart_id;
|
redirect_url += '&take_cart_id=' + this.$root.cart_id;
|
||||||
}
|
}
|
||||||
if (this.$root.widget_data) {
|
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) {
|
if (this.$root.additionalURLParams) {
|
||||||
redirect_url += '&' + 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="_voucher_code" :value="$root.voucher_code" v-if="$root.voucher_code">'
|
||||||
+ '<input type="hidden" name="subevent" :value="$root.subevent" />'
|
+ '<input type="hidden" name="subevent" :value="$root.subevent" />'
|
||||||
+ '<input type="hidden" name="widget_data" :value="$root.widget_data_json" />'
|
+ '<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
|
// Error message
|
||||||
+ '<div class="pretix-widget-error-message" v-if="$root.error">{{ $root.error }}</div>'
|
+ '<div class="pretix-widget-error-message" v-if="$root.error">{{ $root.error }}</div>'
|
||||||
@@ -1072,6 +1075,7 @@ Vue.component('pretix-widget-event-form', {
|
|||||||
+ '</div>'
|
+ '</div>'
|
||||||
+ '<input type="hidden" name="subevent" :value="$root.subevent" />'
|
+ '<input type="hidden" name="subevent" :value="$root.subevent" />'
|
||||||
+ '<input type="hidden" name="widget_data" :value="$root.widget_data_json" />'
|
+ '<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 + '" />'
|
+ '<input type="hidden" name="locale" value="' + lang + '" />'
|
||||||
+ '<div class="pretix-widget-voucher-button-wrap">'
|
+ '<div class="pretix-widget-voucher-button-wrap">'
|
||||||
+ '<button @click="$parent.redeem">' + strings.redeem + '</button>'
|
+ '<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="subevent" :value="$root.subevent" />'
|
||||||
+ '<input type="hidden" name="locale" :value="$root.lang" />'
|
+ '<input type="hidden" name="locale" :value="$root.lang" />'
|
||||||
+ '<input type="hidden" name="widget_data" :value="$root.widget_data_json" />'
|
+ '<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" />'
|
+ '<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>'
|
+ '<button class="pretix-button" @click="buy" v-html="$root.button_text"></button>'
|
||||||
+ '</form>'
|
+ '</form>'
|
||||||
@@ -1923,6 +1928,7 @@ var shared_root_methods = {
|
|||||||
if (this.$root.additionalURLParams) {
|
if (this.$root.additionalURLParams) {
|
||||||
redirect_url += '&' + this.$root.additionalURLParams;
|
redirect_url += '&' + this.$root.additionalURLParams;
|
||||||
}
|
}
|
||||||
|
redirect_url += this.$root.consent_parameter;
|
||||||
if (this.$root.useIframe) {
|
if (this.$root.useIframe) {
|
||||||
this.$root.overlay.frame_src = redirect_url;
|
this.$root.overlay.frame_src = redirect_url;
|
||||||
} else {
|
} else {
|
||||||
@@ -2033,8 +2039,26 @@ var shared_root_computed = {
|
|||||||
}
|
}
|
||||||
return has_priced || cnt_items > 1;
|
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 () {
|
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 () {
|
additionalURLParams: function () {
|
||||||
if (!window.location.search.indexOf('utm_')) {
|
if (!window.location.search.indexOf('utm_')) {
|
||||||
|
|||||||
Reference in New Issue
Block a user