diff --git a/src/pretix/api/serializers/organizer.py b/src/pretix/api/serializers/organizer.py index 2e49e44eea..b2c86ff56f 100644 --- a/src/pretix/api/serializers/organizer.py +++ b/src/pretix/api/serializers/organizer.py @@ -296,7 +296,14 @@ class OrganizerSettingsSerializer(SettingsSerializer): 'theme_round_borders', 'primary_font', 'organizer_logo_image_inherit', - 'organizer_logo_image' + 'organizer_logo_image', + 'privacy_url', + 'cookie_consent', + 'cookie_consent_dialog_title', + 'cookie_consent_dialog_text', + 'cookie_consent_dialog_text_secondary', + 'cookie_consent_dialog_button_yes', + 'cookie_consent_dialog_button_no', ] def __init__(self, *args, **kwargs): diff --git a/src/pretix/base/models/organizer.py b/src/pretix/base/models/organizer.py index b5c72ba624..f1f85a4b4b 100644 --- a/src/pretix/base/models/organizer.py +++ b/src/pretix/base/models/organizer.py @@ -97,10 +97,21 @@ class Organizer(LoggedModel): return self.name def save(self, *args, **kwargs): + is_new = not self.pk obj = super().save(*args, **kwargs) - self.get_cache().clear() + if is_new: + self.set_defaults() + else: + self.get_cache().clear() return obj + def set_defaults(self): + """ + This will be called after organizer creation. + This way, we can use this to introduce new default settings to pretix that do not affect existing organizers. + """ + self.settings.cookie_consent = True + def get_cache(self): """ Returns an :py:class:`ObjectRelatedCache` object. This behaves equivalent to diff --git a/src/pretix/base/settings.py b/src/pretix/base/settings.py index bd6ca615be..acfbd36179 100644 --- a/src/pretix/base/settings.py +++ b/src/pretix/base/settings.py @@ -1512,6 +1512,17 @@ DEFAULTS = { ), 'serializer_class': serializers.URLField, }, + 'privacy_url': { + 'default': None, + 'type': str, + 'form_class': forms.URLField, + 'form_kwargs': dict( + label=_("Privacy Policy URL"), + help_text=_("This should point e.g. to a part of your website that explains how you use data gathered in " + "your ticket shop."), + ), + 'serializer_class': serializers.URLField, + }, 'confirm_texts': { 'default': LazyI18nStringList(), 'type': LazyI18nStringList, @@ -2489,6 +2500,78 @@ Your {organizer} team""")) 'many years. If you keep it empty, gift cards do not have an explicit expiry date.'), ) }, + 'cookie_consent': { + 'default': 'False', + 'form_class': forms.BooleanField, + 'serializer_class': serializers.BooleanField, + 'form_kwargs': dict( + label=_("Enable cookie consent management features"), + ), + 'type': bool, + }, + 'cookie_consent_dialog_text': { + 'default': LazyI18nString.from_gettext(gettext_noop( + 'By clicking "Accept all cookies", you agree to the storing of cookies and use of similar technologies on ' + 'your device.' + )), + 'type': LazyI18nString, + 'serializer_class': I18nField, + 'form_class': I18nFormField, + 'form_kwargs': dict( + label=_("Dialog text"), + widget=I18nTextarea, + widget_kwargs={'attrs': {'rows': '3', 'data-display-dependency': '#id_settings-cookie_consent'}}, + ) + }, + 'cookie_consent_dialog_text_secondary': { + 'default': LazyI18nString.from_gettext(gettext_noop( + 'We use cookies and similar technologies to gather data that allows us to improve this website and our ' + 'offerings. If you do not agree, we will only use cookies if they are essential to providing the services ' + 'this website offers. This includes cookies used to store your cart and personal settings, cookies used for ' + 'payment processing, as well as cookies required for technical or security reasons.' + )), + 'type': LazyI18nString, + 'serializer_class': I18nField, + 'form_class': I18nFormField, + 'form_kwargs': dict( + label=_("Secondary dialog text"), + widget=I18nTextarea, + widget_kwargs={'attrs': {'rows': '3', 'data-display-dependency': '#id_settings-cookie_consent'}}, + ) + }, + 'cookie_consent_dialog_title': { + 'default': LazyI18nString.from_gettext(gettext_noop('Privacy settings')), + 'type': LazyI18nString, + 'serializer_class': I18nField, + 'form_class': I18nFormField, + 'form_kwargs': dict( + label=_('Dialog title'), + widget=I18nTextInput, + widget_kwargs={'attrs': {'data-display-dependency': '#id_settings-cookie_consent'}}, + ) + }, + 'cookie_consent_dialog_button_yes': { + 'default': LazyI18nString.from_gettext(gettext_noop('Accept all cookies')), + 'type': LazyI18nString, + 'serializer_class': I18nField, + 'form_class': I18nFormField, + 'form_kwargs': dict( + label=_('"Accept" button description'), + widget=I18nTextInput, + widget_kwargs={'attrs': {'data-display-dependency': '#id_settings-cookie_consent'}}, + ) + }, + 'cookie_consent_dialog_button_no': { + 'default': LazyI18nString.from_gettext(gettext_noop('Required cookies only')), + 'type': LazyI18nString, + 'serializer_class': I18nField, + 'form_class': I18nFormField, + 'form_kwargs': dict( + label=_('"Reject" button description'), + widget=I18nTextInput, + widget_kwargs={'attrs': {'data-display-dependency': '#id_settings-cookie_consent'}}, + ) + }, 'seating_choice': { 'default': 'True', 'form_class': forms.BooleanField, diff --git a/src/pretix/control/forms/organizer.py b/src/pretix/control/forms/organizer.py index 158db11b84..82afb94a44 100644 --- a/src/pretix/control/forms/organizer.py +++ b/src/pretix/control/forms/organizer.py @@ -307,8 +307,14 @@ class OrganizerSettingsForm(SettingsForm): 'theme_color_danger', 'theme_color_background', 'theme_round_borders', - 'primary_font' - + 'primary_font', + 'privacy_url', + 'cookie_consent', + 'cookie_consent_dialog_title', + 'cookie_consent_dialog_text', + 'cookie_consent_dialog_text_secondary', + 'cookie_consent_dialog_button_yes', + 'cookie_consent_dialog_button_no', ] organizer_logo_image = ExtFileField( diff --git a/src/pretix/control/templates/pretixcontrol/organizers/edit.html b/src/pretix/control/templates/pretixcontrol/organizers/edit.html index 7f5a082f73..34bc2520e0 100644 --- a/src/pretix/control/templates/pretixcontrol/organizers/edit.html +++ b/src/pretix/control/templates/pretixcontrol/organizers/edit.html @@ -82,6 +82,49 @@ {% bootstrap_field sform.giftcard_expiry_years layout="control" %} {% bootstrap_field sform.giftcard_length layout="control" %} +
+ {% trans "Privacy" %} + {% bootstrap_field sform.privacy_url layout="control" %} + + {% bootstrap_field sform.cookie_consent layout="control" %} + {% bootstrap_field sform.cookie_consent_dialog_title layout="control" %} + {% bootstrap_field sform.cookie_consent_dialog_text layout="control" %} + {% bootstrap_field sform.cookie_consent_dialog_text_secondary layout="control" %} + {% bootstrap_field sform.cookie_consent_dialog_button_yes layout="control" %} + {% bootstrap_field sform.cookie_consent_dialog_button_no layout="control" %} +
{% trans "Invoices" %} {% bootstrap_field sform.invoice_regenerate_allowed layout="control" %} diff --git a/src/pretix/presale/templates/pretixpresale/event/base.html b/src/pretix/presale/templates/pretixpresale/event/base.html index 64825986e2..6c75b3e105 100644 --- a/src/pretix/presale/templates/pretixpresale/event/base.html +++ b/src/pretix/presale/templates/pretixpresale/event/base.html @@ -169,6 +169,12 @@ {% if request.event.settings.contact_mail %}
  • {% trans "Contact event organizer" %}
  • {% endif %} + {% if request.event.settings.privacy_url %} +
  • {% trans "Privacy policy" %}
  • + {% endif %} + {% if request.event.settings.cookie_consent %} +
  • {% trans "Cookie settings" %}
  • + {% endif %} {% if request.event.settings.imprint_url %}
  • {% trans "Imprint" %}
  • {% endif %} diff --git a/src/pretix/presale/templates/pretixpresale/fragment_js.html b/src/pretix/presale/templates/pretixpresale/fragment_js.html index 9098c3d0a8..80af10578d 100644 --- a/src/pretix/presale/templates/pretixpresale/fragment_js.html +++ b/src/pretix/presale/templates/pretixpresale/fragment_js.html @@ -14,6 +14,7 @@ + diff --git a/src/pretix/presale/templates/pretixpresale/fragment_modals.html b/src/pretix/presale/templates/pretixpresale/fragment_modals.html index 294c056882..c59fb05e79 100644 --- a/src/pretix/presale/templates/pretixpresale/fragment_modals.html +++ b/src/pretix/presale/templates/pretixpresale/fragment_modals.html @@ -1,4 +1,6 @@ {% load i18n %} +{% load rich_text %} +{% load safelink %}
    +{% if request.organizer %} + + +{% endif %} \ No newline at end of file diff --git a/src/pretix/presale/templates/pretixpresale/organizers/base.html b/src/pretix/presale/templates/pretixpresale/organizers/base.html index 905ff154e5..50bc5d74b5 100644 --- a/src/pretix/presale/templates/pretixpresale/organizers/base.html +++ b/src/pretix/presale/templates/pretixpresale/organizers/base.html @@ -87,6 +87,12 @@ {% if not request.event and request.organizer.settings.contact_mail %}
  • {% trans "Contact event organizer" %}
  • {% endif %} + {% if not request.event and request.organizer.settings.privacy_url %} +
  • {% trans "Privacy policy" %}
  • + {% endif %} + {% if not request.event and request.organizer.settings.cookie_consent %} +
  • {% trans "Cookie settings" %}
  • + {% endif %} {% if not request.event and request.organizer.settings.imprint_url %}
  • {% trans "Imprint" %}
  • {% endif %} diff --git a/src/pretix/static/pretixpresale/js/ui/cookieconsent.js b/src/pretix/static/pretixpresale/js/ui/cookieconsent.js new file mode 100644 index 0000000000..ad283d17c1 --- /dev/null +++ b/src/pretix/static/pretixpresale/js/ui/cookieconsent.js @@ -0,0 +1,39 @@ +/*global $ */ + +window.__pretix_cookie_update_listeners = window.__pretix_cookie_update_listeners || [] + +$(function () { + var storage_key = $("#cookie-consent-storage-key").text(); + if (!window.localStorage[storage_key]) { + $("#cookie-consent-modal").show(); + } + $("#cookie-consent-button-yes").on("click", function () { + window.localStorage[storage_key] = JSON.stringify({ + 'functionality': true, + 'ad': true, + 'analytics': true, + 'social': true, + }) + for (var k of window.__pretix_cookie_update_listeners) { + k.call(this, window.localStorage[storage_key]) + } + $("#cookie-consent-modal").hide() + }) + $("#cookie-consent-button-no").on("click", function () { + window.localStorage[storage_key] = JSON.stringify({ + 'functionality': true, + 'ad': false, + 'analytics': false, + 'social': false, + }) + for (var k of window.__pretix_cookie_update_listeners) { + k.call(this, window.localStorage[storage_key]) + } + $("#cookie-consent-modal").hide() + }) + $("#cookie-consent-reopen").on("click", function (e) { + $("#cookie-consent-modal").show() + e.preventDefault() + return true + }) +}); \ No newline at end of file diff --git a/src/pretix/static/pretixpresale/scss/main.scss b/src/pretix/static/pretixpresale/scss/main.scss index 976a889e35..013143ba47 100644 --- a/src/pretix/static/pretixpresale/scss/main.scss +++ b/src/pretix/static/pretixpresale/scss/main.scss @@ -136,7 +136,7 @@ body.loading .container { font-size: 120px; color: $brand-primary; } -#loadingmodal, #ajaxerr { +#loadingmodal, #ajaxerr, #cookie-consent-modal { position: fixed; top: 0; left: 0; @@ -156,9 +156,10 @@ body.loading .container { .modal-card { margin: 50px auto 0; - top: 50px; width: 90%; max-width: 600px; + max-height: calc(100vh - 100px); + overflow-y: auto; background: white; border-radius: $border-radius-large; box-shadow: 0 7px 14px 0 rgba(78, 50, 92, 0.1),0 3px 6px 0 rgba(0,0,0,.07); @@ -178,10 +179,21 @@ body.loading .container { } } } + + &#cookie-consent-modal { + background: rgba(255, 255, 255, .5); + opacity: 1; + visibility: visible; + .modal-card-content { + margin-left: 0; + } + } } @media (max-width: 700px) { - #loadingmodal, #ajaxerr { + #loadingmodal, #ajaxerr, #cookie-consent-modal { .modal-card { + margin: 25px auto 0; + max-height: calc(100vh - 50px - 20px); .modal-card-icon { float: none; width: 100%;