mirror of
https://github.com/pretix/pretix.git
synced 2026-09-18 17:04:41 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a181506e9 | ||
|
|
84df699503 | ||
|
|
aa14505d2c | ||
|
|
df69656364 | ||
|
|
82cffd1519 | ||
|
|
7fc527135d | ||
|
|
8e39b67ee9 | ||
|
|
289cbe5bfc | ||
|
|
7143b877c8 | ||
|
|
e2a78100af | ||
|
|
ca77467e48 | ||
|
|
d3cbfcd4da | ||
|
|
3648ef28d7 | ||
|
|
689b99c3aa | ||
|
|
6c40daa863 | ||
|
|
efdc83f72a | ||
|
|
8b10534c74 | ||
|
|
bcacff2ee7 | ||
|
|
47096f7f1b | ||
|
|
c240ebcb8e | ||
|
|
ce3e59e020 | ||
|
|
4a9da40028 | ||
|
|
eab1efd5f4 | ||
|
|
c7290b614e | ||
|
|
1aea5633eb | ||
|
|
86cc719651 | ||
|
|
742a69f09f | ||
|
|
c05dee7af5 | ||
|
|
e399a49c8b | ||
|
|
b9b951e56b | ||
|
|
88bb1f4008 | ||
|
|
24e7db7ddd | ||
|
|
96ed6fe1a6 | ||
|
|
d85e52c83e | ||
|
|
d9b5fa5b16 | ||
|
|
fb948cc7f4 | ||
|
|
c358f412fd | ||
|
|
b78de6386b | ||
|
|
50c00b47ae | ||
|
|
6025c7942f | ||
|
|
23a3412154 | ||
|
|
3635f6fb0c | ||
|
|
2aadfa2ce4 | ||
|
|
dd0d78242e | ||
|
|
42c5de895c | ||
|
|
e91718e73b | ||
|
|
2c15d8b074 | ||
|
|
edb4069e18 | ||
|
|
dc7d5c6029 | ||
|
|
caa6fb187b | ||
|
|
7d93cae2a9 | ||
|
|
58a58eff83 | ||
|
|
a84a02c298 |
@@ -0,0 +1,2 @@
|
|||||||
|
# Format pre-vue code with eslint where possible (2026-09-10)
|
||||||
|
d85e52c83ed3639e040372fd0052e842e4899d90
|
||||||
@@ -566,7 +566,7 @@ organizer level.
|
|||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
"imprint_url": "https://pretix.eu",
|
"region": "DE",
|
||||||
…
|
…
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -579,12 +579,14 @@ organizer level.
|
|||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
"imprint_url":
|
"region":
|
||||||
{
|
{
|
||||||
"value": "https://pretix.eu",
|
"value": "DE",
|
||||||
"label": "Imprint URL",
|
"label": "Region",
|
||||||
"readonly": false,
|
"readonly": false,
|
||||||
"help_text": "This should point e.g. to a part of your website that has your contact details and legal information."
|
"help_text": "Will be used to determine date and time formatting as well as default country for customer
|
||||||
|
addresses and phone numbers. For formatting, this takes less priority than the language and
|
||||||
|
is therefore mostly relevant for languages used in different regions globally (like English)."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
…
|
…
|
||||||
@@ -620,7 +622,7 @@ organizer level.
|
|||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
"imprint_url": "https://example.org/imprint/"
|
"region": "DE"
|
||||||
}
|
}
|
||||||
|
|
||||||
**Example response**:
|
**Example response**:
|
||||||
@@ -632,7 +634,7 @@ organizer level.
|
|||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
"imprint_url": "https://example.org/imprint/",
|
"region": "DE",
|
||||||
…
|
…
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+58
-1
@@ -8,7 +8,64 @@ import vuePug from 'eslint-plugin-vue-pug'
|
|||||||
|
|
||||||
const ignores = globalIgnores([
|
const ignores = globalIgnores([
|
||||||
'**/node_modules',
|
'**/node_modules',
|
||||||
'**/dist'
|
'**/dist',
|
||||||
|
// Vendored code
|
||||||
|
'src/pretix/static/leaflet',
|
||||||
|
'src/pretix/static/clipboard',
|
||||||
|
'src/pretix/static/cropper',
|
||||||
|
'src/pretix/static/lightbox',
|
||||||
|
'src/pretix/static/are-you-sure',
|
||||||
|
'src/pretix/static/vuejs',
|
||||||
|
'src/pretix/static/fontawesome',
|
||||||
|
'src/pretix/static/typeahead',
|
||||||
|
'src/pretix/static/moment',
|
||||||
|
'src/pretix/static/pdfjs',
|
||||||
|
'src/pretix/static/sortable',
|
||||||
|
'src/pretix/static/iframeresizer',
|
||||||
|
'src/pretix/static/bootstrap',
|
||||||
|
'src/pretix/static/d3',
|
||||||
|
'src/pretix/static/jsi18n',
|
||||||
|
'src/pretix/static/fabric',
|
||||||
|
'src/pretix/static/datetimepicker',
|
||||||
|
'src/pretix/static/charts',
|
||||||
|
'src/pretix/static/fileupload',
|
||||||
|
'src/pretix/static/seating',
|
||||||
|
'src/pretix/static/rest_framework',
|
||||||
|
'src/pretix/static/select2',
|
||||||
|
'src/pretix/static/schema',
|
||||||
|
'src/pretix/static/slider',
|
||||||
|
'src/pretix/static/jquery',
|
||||||
|
'src/pretix/static/colorpicker',
|
||||||
|
'src/pretix/static/rrule',
|
||||||
|
'src/pretix/static/pretixcontrol/js/jquery.qrcode.min.js',
|
||||||
|
'src/pretix/static/pretixpresale/js/widget/docready.js',
|
||||||
|
// Pre-vue JS code
|
||||||
|
'src/pretix/static/pretixbase/js/addressform.js',
|
||||||
|
'src/pretix/static/pretixbase/js/asynctask.js',
|
||||||
|
'src/pretix/static/pretixbase/js/details.js',
|
||||||
|
'src/pretix/static/pretixbase/js/gettextstub.js',
|
||||||
|
'src/pretix/static/pretixbase/js/i18nstring.js',
|
||||||
|
'src/pretix/static/pretixcontrol/js/menu.js',
|
||||||
|
'src/pretix/static/pretixcontrol/js/ui/editor.js',
|
||||||
|
'src/pretix/static/pretixcontrol/js/ui/geo.js',
|
||||||
|
'src/pretix/static/pretixcontrol/js/ui/main.js',
|
||||||
|
'src/pretix/static/pretixcontrol/js/ui/plugins.js',
|
||||||
|
'src/pretix/static/pretixcontrol/js/ui/subevent.js',
|
||||||
|
'src/pretix/static/pretixcontrol/js/ui/variations.js',
|
||||||
|
'src/pretix/static/pretixcontrol/js/ui/webauthn.js',
|
||||||
|
'src/pretix/static/pretixpresale/js/ui/cart.js',
|
||||||
|
'src/pretix/static/pretixpresale/js/ui/main.js',
|
||||||
|
'src/pretix/static/pretixpresale/js/ui/questions.js',
|
||||||
|
'src/pretix/static/pretixpresale/js/widget/floatformat.js',
|
||||||
|
'src/pretix/static/pretixpresale/js/widget/widget.js',
|
||||||
|
'src/pretix/plugins/banktransfer/static',
|
||||||
|
'src/pretix/plugins/paypal2/static',
|
||||||
|
'src/pretix/plugins/statistics/static',
|
||||||
|
'src/pretix/plugins/stripe/static',
|
||||||
|
// Plugin checkouts
|
||||||
|
'local',
|
||||||
|
// docs
|
||||||
|
'doc',
|
||||||
])
|
])
|
||||||
|
|
||||||
export default defineConfig([
|
export default defineConfig([
|
||||||
|
|||||||
Generated
+3
-3
@@ -4148,9 +4148,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/smol-toml": {
|
"node_modules/smol-toml": {
|
||||||
"version": "1.6.1",
|
"version": "1.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.7.1.tgz",
|
||||||
"integrity": "sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==",
|
"integrity": "sha512-PPlsspAZ4jbMBu5DMFhfUGDQLu/vrL4SyBROVS37x8ynnVmFIs1VPBz1Co8Xks3TvpIaZXmU85y4DrQ+UyVFoQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
+6
-6
@@ -37,10 +37,10 @@ dependencies = [
|
|||||||
"css-inline==0.21.*",
|
"css-inline==0.21.*",
|
||||||
"defusedcsv>=3.0.0",
|
"defusedcsv>=3.0.0",
|
||||||
"dnspython==2.*",
|
"dnspython==2.*",
|
||||||
"Django[argon2]==5.2.*",
|
"Django[argon2]==5.2.*,>=5.2.17",
|
||||||
"django-bootstrap3==26.2",
|
"django-bootstrap3==26.2",
|
||||||
"django-compressor==4.6.0",
|
"django-compressor==4.6.0",
|
||||||
"django-countries==9.0.*",
|
"django-countries==9.1.*",
|
||||||
"django-filter==26.1",
|
"django-filter==26.1",
|
||||||
"django-formset-js-improved==0.5.0.5",
|
"django-formset-js-improved==0.5.0.5",
|
||||||
"django-formtools==2.7",
|
"django-formtools==2.7",
|
||||||
@@ -56,7 +56,7 @@ dependencies = [
|
|||||||
"django-querytagger==0.0.3",
|
"django-querytagger==0.0.3",
|
||||||
"django-redis==7.0.*",
|
"django-redis==7.0.*",
|
||||||
"django-scopes==2.1.*",
|
"django-scopes==2.1.*",
|
||||||
"django-statici18n==2.7.*",
|
"django-statici18n==2.8.*",
|
||||||
"djangorestframework==3.17.*",
|
"djangorestframework==3.17.*",
|
||||||
"dnspython==2.8.*",
|
"dnspython==2.8.*",
|
||||||
"drf_ujson2==1.7.*",
|
"drf_ujson2==1.7.*",
|
||||||
@@ -75,7 +75,7 @@ dependencies = [
|
|||||||
"packaging",
|
"packaging",
|
||||||
"paypalrestsdk==1.13.*",
|
"paypalrestsdk==1.13.*",
|
||||||
"paypal-checkout-serversdk==1.0.*",
|
"paypal-checkout-serversdk==1.0.*",
|
||||||
"PyJWT==2.13.*",
|
"PyJWT==2.14.*",
|
||||||
"phonenumberslite==9.0.*",
|
"phonenumberslite==9.0.*",
|
||||||
"Pillow==12.3.*",
|
"Pillow==12.3.*",
|
||||||
"pretix-plugin-build",
|
"pretix-plugin-build",
|
||||||
@@ -84,7 +84,7 @@ dependencies = [
|
|||||||
"pycountry",
|
"pycountry",
|
||||||
"pycparser==3.0",
|
"pycparser==3.0",
|
||||||
"pycryptodome==3.23.*",
|
"pycryptodome==3.23.*",
|
||||||
"pypdf==6.5.*",
|
"pypdf==6.11.*",
|
||||||
"python-bidi==0.6.*", # Support for Arabic in reportlab
|
"python-bidi==0.6.*", # Support for Arabic in reportlab
|
||||||
"python-dateutil==2.9.*",
|
"python-dateutil==2.9.*",
|
||||||
"pytz",
|
"pytz",
|
||||||
@@ -94,7 +94,7 @@ dependencies = [
|
|||||||
"redis==7.4.*",
|
"redis==7.4.*",
|
||||||
"reportlab==5.0.*",
|
"reportlab==5.0.*",
|
||||||
"requests==2.34.*",
|
"requests==2.34.*",
|
||||||
"sentry-sdk==2.68.*",
|
"sentry-sdk==2.69.*",
|
||||||
"sepaxml==2.7.*",
|
"sepaxml==2.7.*",
|
||||||
"stripe==7.9.*",
|
"stripe==7.9.*",
|
||||||
"text-unidecode==1.*",
|
"text-unidecode==1.*",
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ from ...control.forms.filter import get_all_payment_providers
|
|||||||
from ...helpers import GroupConcat
|
from ...helpers import GroupConcat
|
||||||
from ...helpers.iter import chunked_iterable
|
from ...helpers.iter import chunked_iterable
|
||||||
from ..exporter import BaseExporter, MultiSheetListExporter
|
from ..exporter import BaseExporter, MultiSheetListExporter
|
||||||
|
from ..invoicing.transmission import get_transmission_types
|
||||||
from ..services.export import ExportError
|
from ..services.export import ExportError
|
||||||
from ..services.invoices import invoice_pdf_task
|
from ..services.invoices import invoice_pdf_task
|
||||||
from ..signals import (
|
from ..signals import (
|
||||||
@@ -197,7 +198,7 @@ class InvoiceDataExporter(InvoiceExporterMixin, MultiSheetListExporter):
|
|||||||
def iterate_sheet(self, form_data, sheet):
|
def iterate_sheet(self, form_data, sheet):
|
||||||
_ = gettext
|
_ = gettext
|
||||||
if sheet == 'invoices':
|
if sheet == 'invoices':
|
||||||
yield [
|
headers = [
|
||||||
_('Invoice number'),
|
_('Invoice number'),
|
||||||
_('Date'),
|
_('Date'),
|
||||||
_('Order code'),
|
_('Order code'),
|
||||||
@@ -230,8 +231,18 @@ class InvoiceDataExporter(InvoiceExporterMixin, MultiSheetListExporter):
|
|||||||
_('Total value (without taxes)'),
|
_('Total value (without taxes)'),
|
||||||
_('Payment matching IDs'),
|
_('Payment matching IDs'),
|
||||||
_('Payment providers'),
|
_('Payment providers'),
|
||||||
|
_('Transmission type'),
|
||||||
|
_('Transmission status'),
|
||||||
|
_('Transmission date'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
transmission_types = get_transmission_types()
|
||||||
|
for tt in transmission_types:
|
||||||
|
for c in tt.describe_info_columns():
|
||||||
|
headers.append(str(tt.verbose_name) + ': ' + str(c))
|
||||||
|
|
||||||
|
yield headers
|
||||||
|
|
||||||
p_providers = OrderPayment.objects.filter(
|
p_providers = OrderPayment.objects.filter(
|
||||||
order=OuterRef('order'),
|
order=OuterRef('order'),
|
||||||
state__in=(OrderPayment.PAYMENT_STATE_CONFIRMED, OrderPayment.PAYMENT_STATE_REFUNDED,
|
state__in=(OrderPayment.PAYMENT_STATE_CONFIRMED, OrderPayment.PAYMENT_STATE_REFUNDED,
|
||||||
@@ -242,7 +253,7 @@ class InvoiceDataExporter(InvoiceExporterMixin, MultiSheetListExporter):
|
|||||||
'm'
|
'm'
|
||||||
).order_by()
|
).order_by()
|
||||||
|
|
||||||
base_qs = self.invoices_queryset(form_data)\
|
base_qs = self.invoices_queryset(form_data)
|
||||||
|
|
||||||
qs = base_qs.select_related(
|
qs = base_qs.select_related(
|
||||||
'order', 'refers'
|
'order', 'refers'
|
||||||
@@ -280,7 +291,7 @@ class InvoiceDataExporter(InvoiceExporterMixin, MultiSheetListExporter):
|
|||||||
if mid:
|
if mid:
|
||||||
pmis.append(mid)
|
pmis.append(mid)
|
||||||
pmi = '\n'.join(pmis)
|
pmi = '\n'.join(pmis)
|
||||||
yield [
|
line = [
|
||||||
i.full_invoice_no,
|
i.full_invoice_no,
|
||||||
date_format(i.date, "SHORT_DATE_FORMAT"),
|
date_format(i.date, "SHORT_DATE_FORMAT"),
|
||||||
i.order.code,
|
i.order.code,
|
||||||
@@ -315,8 +326,20 @@ class InvoiceDataExporter(InvoiceExporterMixin, MultiSheetListExporter):
|
|||||||
', '.join([
|
', '.join([
|
||||||
str(self.providers.get(p, p)) for p in sorted(set((i.payment_providers or '').split(',')))
|
str(self.providers.get(p, p)) for p in sorted(set((i.payment_providers or '').split(',')))
|
||||||
if p and p != 'free'
|
if p and p != 'free'
|
||||||
])
|
]),
|
||||||
|
i.transmission_type_instance.verbose_name,
|
||||||
|
i.get_transmission_status_display(),
|
||||||
|
date_format(i.transmission_date, "SHORT_DATETIME_FORMAT") if i.transmission_date else "",
|
||||||
]
|
]
|
||||||
|
for tt in transmission_types:
|
||||||
|
if tt.identifier == i.transmission_type:
|
||||||
|
described = dict(tt.describe_info(i.invoice_to_transmission_info, i.invoice_to_country, i.invoice_to_is_business))
|
||||||
|
for c in tt.describe_info_columns():
|
||||||
|
line.append(described.get(c, ""))
|
||||||
|
else:
|
||||||
|
for c in tt.describe_info_columns():
|
||||||
|
line.append("")
|
||||||
|
yield line
|
||||||
elif sheet == 'lines':
|
elif sheet == 'lines':
|
||||||
yield [
|
yield [
|
||||||
_('Invoice number'),
|
_('Invoice number'),
|
||||||
|
|||||||
@@ -350,16 +350,22 @@ class WrappedPhonePrefixSelect(Select):
|
|||||||
return super().render(name, value or self.initial, *args, **kwargs)
|
return super().render(name, value or self.initial, *args, **kwargs)
|
||||||
|
|
||||||
def get_context(self, name, value, attrs):
|
def get_context(self, name, value, attrs):
|
||||||
if value and self.choices[1][0] != value:
|
# self.choices is lazy evaluated, needs to be realized to be modifiable
|
||||||
matching_choices = len([1 for p, c in self.choices if p == value])
|
choices = list(self.choices)
|
||||||
|
if value and choices[1][0] != value:
|
||||||
|
matching_choices = len([1 for p, c in choices if p == value])
|
||||||
if matching_choices > 1:
|
if matching_choices > 1:
|
||||||
# Some countries share a phone prefix, for example +1 is used all over the Americas.
|
# Some countries share a phone prefix, for example +1 is used all over the Americas.
|
||||||
# This causes a UX problem: If the default value or the existing data is +12125552368,
|
# This causes a UX problem: If the default value or the existing data is +12125552368,
|
||||||
# the widget will just show the first <option> entry with value="+1" as selected,
|
# the widget will just show the first <option> entry with value="+1" as selected,
|
||||||
# which alphabetically is America Samoa, although most numbers statistically are from
|
# which alphabetically is America Samoa, although most numbers statistically are from
|
||||||
# the US. As a workaround, we detect this case and add an aditional choice value with
|
# the US. As a workaround, we detect this case and add an additional choice value with
|
||||||
# just <option value="+1">+1</option> without an explicit country.
|
# just <option value="+1">+1</option> without an explicit country.
|
||||||
self.choices.insert(1, (value, value))
|
self.choices = [
|
||||||
|
choices[0],
|
||||||
|
(value, value),
|
||||||
|
*choices[1:],
|
||||||
|
]
|
||||||
context = super().get_context(name, value, attrs)
|
context = super().get_context(name, value, attrs)
|
||||||
return context
|
return context
|
||||||
|
|
||||||
@@ -899,7 +905,7 @@ class BaseQuestionsForm(forms.Form):
|
|||||||
field.widget.attrs['data-question-dependency-values'] = escapejson_attr(json.dumps(q.dependency_values))
|
field.widget.attrs['data-question-dependency-values'] = escapejson_attr(json.dumps(q.dependency_values))
|
||||||
if q.type != 'M':
|
if q.type != 'M':
|
||||||
field.widget.attrs['required'] = q.required and not self.all_optional
|
field.widget.attrs['required'] = q.required and not self.all_optional
|
||||||
field._required = q.required and not self.all_optional
|
field._required = q.required and not self.all_optional
|
||||||
field.required = False
|
field.required = False
|
||||||
return field
|
return field
|
||||||
|
|
||||||
@@ -1497,7 +1503,8 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
|||||||
pass # Skip re-validation if it is validated
|
pass # Skip re-validation if it is validated
|
||||||
elif self.validate_vat_id and vat_id_applicable:
|
elif self.validate_vat_id and vat_id_applicable:
|
||||||
try:
|
try:
|
||||||
normalized_id = validate_vat_id(data.get('vat_id'), str(data.get('country')))
|
requester_id = self.request.event.settings.invoice_address_from_vat_id
|
||||||
|
normalized_id = validate_vat_id(data.get('vat_id'), str(data.get('country')), requester_id)
|
||||||
self.instance.vat_id_validated = bool(normalized_id)
|
self.instance.vat_id_validated = bool(normalized_id)
|
||||||
self.instance.vat_id = data['vat_id'] = normalized_id
|
self.instance.vat_id = data['vat_id'] = normalized_id
|
||||||
except VATIDFinalError as e:
|
except VATIDFinalError as e:
|
||||||
|
|||||||
@@ -107,6 +107,9 @@ class TransmissionType:
|
|||||||
def transmission_info_to_form_data(self, transmission_info: dict) -> dict:
|
def transmission_info_to_form_data(self, transmission_info: dict) -> dict:
|
||||||
return transmission_info
|
return transmission_info
|
||||||
|
|
||||||
|
def describe_info_columns(self):
|
||||||
|
return [f.label for f in self.invoice_address_form_fields.values()]
|
||||||
|
|
||||||
def describe_info(self, transmission_info: dict, country: Country, is_business: bool):
|
def describe_info(self, transmission_info: dict, country: Country, is_business: bool):
|
||||||
form_data = self.transmission_info_to_form_data(transmission_info)
|
form_data = self.transmission_info_to_form_data(transmission_info)
|
||||||
data = []
|
data = []
|
||||||
|
|||||||
@@ -166,6 +166,7 @@ class Device(LoggedModel):
|
|||||||
)
|
)
|
||||||
security_profile = models.CharField(
|
security_profile = models.CharField(
|
||||||
max_length=190,
|
max_length=190,
|
||||||
|
verbose_name=_('Security profile'),
|
||||||
default='full',
|
default='full',
|
||||||
null=True,
|
null=True,
|
||||||
blank=False
|
blank=False
|
||||||
|
|||||||
@@ -626,47 +626,14 @@ class Order(LockModel, LoggedModel):
|
|||||||
self.save(update_fields=['last_modified'])
|
self.save(update_fields=['last_modified'])
|
||||||
|
|
||||||
def set_expires(self, now_dt=None, subevents=None):
|
def set_expires(self, now_dt=None, subevents=None):
|
||||||
now_dt = now_dt or now()
|
from pretix.base.services.payment import compute_payment_deadline
|
||||||
tz = ZoneInfo(self.event.settings.timezone)
|
|
||||||
|
|
||||||
sales_channel_suffix = "_" + self.sales_channel.identifier.replace(".", "_")
|
self.expires = compute_payment_deadline(
|
||||||
if not (mode := self.event.settings.get(f'payment_term_mode{sales_channel_suffix}')):
|
event=self.event,
|
||||||
mode = self.event.settings.get('payment_term_mode')
|
sales_channel=self.sales_channel,
|
||||||
sales_channel_suffix = ""
|
now_dt=now_dt,
|
||||||
|
subevents=subevents,
|
||||||
if mode == 'days':
|
)
|
||||||
exp_by_date = now_dt.astimezone(tz) + timedelta(days=self.event.settings.get(f'payment_term_days{sales_channel_suffix}', as_type=int))
|
|
||||||
exp_by_date = exp_by_date.astimezone(tz).replace(hour=23, minute=59, second=59, microsecond=0)
|
|
||||||
if self.event.settings.get('payment_term_weekdays'):
|
|
||||||
if exp_by_date.weekday() == 5:
|
|
||||||
exp_by_date += timedelta(days=2)
|
|
||||||
elif exp_by_date.weekday() == 6:
|
|
||||||
exp_by_date += timedelta(days=1)
|
|
||||||
elif mode == 'minutes':
|
|
||||||
exp_by_date = now_dt.astimezone(tz) + timedelta(minutes=self.event.settings.get(f'payment_term_minutes{sales_channel_suffix}', as_type=int))
|
|
||||||
else:
|
|
||||||
raise ValueError("'payment_term_mode' has an invalid value '{}'.".format(mode))
|
|
||||||
|
|
||||||
self.expires = exp_by_date
|
|
||||||
|
|
||||||
term_last = self.event.settings.get('payment_term_last', as_type=RelativeDateWrapper)
|
|
||||||
if term_last:
|
|
||||||
if self.event.has_subevents and subevents:
|
|
||||||
terms = [
|
|
||||||
term_last.datetime(se).date()
|
|
||||||
for se in subevents
|
|
||||||
]
|
|
||||||
if not terms:
|
|
||||||
return
|
|
||||||
term_last = min(terms)
|
|
||||||
else:
|
|
||||||
term_last = term_last.datetime(self.event).date()
|
|
||||||
term_last = make_aware(datetime.combine(
|
|
||||||
term_last,
|
|
||||||
time(hour=23, minute=59, second=59)
|
|
||||||
), tz)
|
|
||||||
if term_last < self.expires:
|
|
||||||
self.expires = term_last
|
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def tax_total(self):
|
def tax_total(self):
|
||||||
|
|||||||
+40
-34
@@ -801,6 +801,18 @@ def generate_compressed_addon_list(op, order, event, only_checked_in=False):
|
|||||||
return addonlist
|
return addonlist
|
||||||
|
|
||||||
|
|
||||||
|
def get_sizebox(page: pypdf.PageObject):
|
||||||
|
mediabox = page.mediabox
|
||||||
|
cropbox = page.cropbox
|
||||||
|
|
||||||
|
return pypdf.generic.RectangleObject((
|
||||||
|
max(mediabox[0], cropbox[0]),
|
||||||
|
max(mediabox[1], cropbox[1]),
|
||||||
|
min(mediabox[2], cropbox[2]),
|
||||||
|
min(mediabox[3], cropbox[3]),
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
class Renderer:
|
class Renderer:
|
||||||
|
|
||||||
def __init__(self, event, layout, background_file):
|
def __init__(self, event, layout, background_file):
|
||||||
@@ -1153,11 +1165,10 @@ class Renderer:
|
|||||||
elif o['type'] == "poweredby":
|
elif o['type'] == "poweredby":
|
||||||
self._draw_poweredby(canvas, op, o)
|
self._draw_poweredby(canvas, op, o)
|
||||||
if self.bg_pdf:
|
if self.bg_pdf:
|
||||||
page_size = (
|
first_page = self.bg_pdf.pages[0]
|
||||||
self.bg_pdf.pages[0].mediabox[2] - self.bg_pdf.pages[0].mediabox[0],
|
sizebox = get_sizebox(first_page)
|
||||||
self.bg_pdf.pages[0].mediabox[3] - self.bg_pdf.pages[0].mediabox[1]
|
page_size = (sizebox.width, sizebox.height)
|
||||||
)
|
if first_page.rotation in (90, 270):
|
||||||
if self.bg_pdf.pages[0].get('/Rotate') in (90, 270):
|
|
||||||
# swap dimensions due to pdf being rotated
|
# swap dimensions due to pdf being rotated
|
||||||
page_size = page_size[::-1]
|
page_size = page_size[::-1]
|
||||||
canvas.setPageSize(page_size)
|
canvas.setPageSize(page_size)
|
||||||
@@ -1232,9 +1243,7 @@ class Renderer:
|
|||||||
|
|
||||||
for i, page in enumerate(fg_pdf.pages):
|
for i, page in enumerate(fg_pdf.pages):
|
||||||
bg_page = self.bg_pdf.pages[i]
|
bg_page = self.bg_pdf.pages[i]
|
||||||
_correct_page_media_box(bg_page)
|
_merge_with_correct_page_media_box(output, page, bg_page)
|
||||||
page.merge_page(bg_page, over=False)
|
|
||||||
output.add_page(page)
|
|
||||||
|
|
||||||
# pdf_header is a string like "%pdf-X.X"
|
# pdf_header is a string like "%pdf-X.X"
|
||||||
if float(self.bg_pdf.pdf_header[5:]) > float(fg_pdf.pdf_header[5:]):
|
if float(self.bg_pdf.pdf_header[5:]) > float(fg_pdf.pdf_header[5:]):
|
||||||
@@ -1299,39 +1308,36 @@ def merge_background(fg_pdf: PdfWriter, bg_pdf: PdfWriter, out_file, compress):
|
|||||||
bg_pdf.write(bg_filename)
|
bg_pdf.write(bg_filename)
|
||||||
subprocess.run(pdftk_cmd, check=True, stdout=out_file)
|
subprocess.run(pdftk_cmd, check=True, stdout=out_file)
|
||||||
else:
|
else:
|
||||||
|
output = PdfWriter()
|
||||||
for i, page in enumerate(fg_pdf.pages):
|
for i, page in enumerate(fg_pdf.pages):
|
||||||
bg_page = bg_pdf.pages[i]
|
bg_page = bg_pdf.pages[i]
|
||||||
_correct_page_media_box(bg_page)
|
_merge_with_correct_page_media_box(output, page, bg_page)
|
||||||
page.merge_page(bg_page, over=False)
|
|
||||||
|
|
||||||
# pdf_header is a string like "%pdf-X.X"
|
# pdf_header is a string like "%pdf-X.X"
|
||||||
if float(bg_pdf.pdf_header[5:]) > float(fg_pdf.pdf_header[5:]):
|
output.pdf_header = (
|
||||||
fg_pdf.pdf_header = bg_pdf.pdf_header
|
bg_pdf.pdf_header
|
||||||
|
if float(bg_pdf.pdf_header[5:]) > float(fg_pdf.pdf_header[5:])
|
||||||
fg_pdf.write(out_file)
|
else fg_pdf.pdf_header
|
||||||
|
)
|
||||||
|
output.write(out_file)
|
||||||
|
|
||||||
|
|
||||||
def _correct_page_media_box(page: pypdf.PageObject):
|
def _merge_with_correct_page_media_box(output: pypdf.PdfWriter, fg_page: pypdf.PageObject, bg_page: pypdf.PageObject):
|
||||||
if page.rotation != 0:
|
"""
|
||||||
page.transfer_rotation_to_content()
|
Adds fg_page to output, merging bg_page behind it.
|
||||||
media_box = page.mediabox
|
|
||||||
|
If bg_page has a non-zero mergebox/cropbox or is rotated via /Rotate, a transformation is applied to fix this."""
|
||||||
trsf = pypdf.Transformation()
|
trsf = pypdf.Transformation()
|
||||||
if media_box.bottom != 0:
|
if bg_page.rotation != 0:
|
||||||
trsf = trsf.translate(0, -media_box.bottom)
|
trsf = trsf.rotate(-bg_page.rotation)
|
||||||
if media_box.left != 0:
|
|
||||||
trsf = trsf.translate(-media_box.left, 0)
|
mb = get_sizebox(bg_page)
|
||||||
page.add_transformation(trsf, False)
|
pt1 = trsf.apply_on(mb.lower_left)
|
||||||
for b in ["/MediaBox", "/CropBox", "/BleedBox", "/TrimBox", "/ArtBox"]:
|
pt2 = trsf.apply_on(mb.upper_right)
|
||||||
if b in page:
|
trsf = trsf.translate(-min(pt1[0], pt2[0]), -min(pt1[1], pt2[1]))
|
||||||
rr = pypdf.generic.RectangleObject(page[b])
|
|
||||||
pt1 = trsf.apply_on(rr.lower_left)
|
fg_page = output.add_page(fg_page)
|
||||||
pt2 = trsf.apply_on(rr.upper_right)
|
fg_page.merge_transformed_page(bg_page, trsf, over=False, expand=False)
|
||||||
page[pypdf.generic.NameObject(b)] = pypdf.generic.RectangleObject((
|
|
||||||
min(pt1[0], pt2[0]),
|
|
||||||
min(pt1[1], pt2[1]),
|
|
||||||
max(pt1[0], pt2[0]),
|
|
||||||
max(pt1[1], pt2[1]),
|
|
||||||
))
|
|
||||||
|
|
||||||
|
|
||||||
@deconstructible
|
@deconstructible
|
||||||
|
|||||||
@@ -1605,6 +1605,7 @@ def add_payment_to_cart_session(cart_session, provider, min_value: Decimal=None,
|
|||||||
'max_value': str(max_value) if max_value is not None else None,
|
'max_value': str(max_value) if max_value is not None else None,
|
||||||
'info_data': info_data or {},
|
'info_data': info_data or {},
|
||||||
})
|
})
|
||||||
|
cart_session['payments_postpone'] = False
|
||||||
|
|
||||||
|
|
||||||
def add_payment_to_cart(request, provider, min_value: Decimal=None, max_value: Decimal=None, info_data: dict=None):
|
def add_payment_to_cart(request, provider, min_value: Decimal=None, max_value: Decimal=None, info_data: dict=None):
|
||||||
|
|||||||
@@ -961,7 +961,7 @@ def _check_positions(event: Event, now_dt: datetime, time_machine_now_dt: dateti
|
|||||||
|
|
||||||
|
|
||||||
def _apply_rounding_and_fees(positions: List[CartPosition], payment_requests: List[dict], address: InvoiceAddress,
|
def _apply_rounding_and_fees(positions: List[CartPosition], payment_requests: List[dict], address: InvoiceAddress,
|
||||||
meta_info: dict, event: Event, require_approval=False):
|
meta_info: dict, event: Event, sales_channel: SalesChannel, require_approval=False):
|
||||||
fees = []
|
fees = []
|
||||||
# Pre-rounding, pre-fee total is used for fee calculation
|
# Pre-rounding, pre-fee total is used for fee calculation
|
||||||
total = sum([c.gross_price_before_rounding for c in positions])
|
total = sum([c.gross_price_before_rounding for c in positions])
|
||||||
@@ -1021,7 +1021,14 @@ def _apply_rounding_and_fees(positions: List[CartPosition], payment_requests: Li
|
|||||||
payments_assigned += to_pay
|
payments_assigned += to_pay
|
||||||
p['payment_amount'] = to_pay
|
p['payment_amount'] = to_pay
|
||||||
|
|
||||||
if total != payments_assigned and not require_approval:
|
allow_postponed_payment = (
|
||||||
|
require_approval or
|
||||||
|
(
|
||||||
|
sales_channel.identifier in event.settings.payment_choice_postpone_allowed_channels and not payment_requests
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if total != payments_assigned and not allow_postponed_payment:
|
||||||
raise OrderError(_("The selected payment methods do not cover the total balance."))
|
raise OrderError(_("The selected payment methods do not cover the total balance."))
|
||||||
|
|
||||||
return fees
|
return fees
|
||||||
@@ -1043,7 +1050,15 @@ def _create_order(event: Event, *, email: str, positions: List[CartPosition], no
|
|||||||
|
|
||||||
# Final calculation of fees, also performs final rounding
|
# Final calculation of fees, also performs final rounding
|
||||||
try:
|
try:
|
||||||
fees = _apply_rounding_and_fees(positions, payment_requests, address, meta_info, event, require_approval=require_approval)
|
fees = _apply_rounding_and_fees(
|
||||||
|
positions,
|
||||||
|
payment_requests,
|
||||||
|
address,
|
||||||
|
meta_info,
|
||||||
|
event,
|
||||||
|
sales_channel=sales_channel,
|
||||||
|
require_approval=require_approval
|
||||||
|
)
|
||||||
except TaxRule.SaleNotAllowed:
|
except TaxRule.SaleNotAllowed:
|
||||||
raise OrderError(error_messages['country_blocked'])
|
raise OrderError(error_messages['country_blocked'])
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
#
|
||||||
|
# This file is part of pretix (Community Edition).
|
||||||
|
#
|
||||||
|
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||||
|
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||||
|
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||||
|
#
|
||||||
|
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||||
|
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||||
|
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||||
|
# this file, see <https://pretix.eu/about/en/license>.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||||
|
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||||
|
# details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||||
|
# <https://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
from datetime import datetime, time, timedelta
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
|
from django.utils.timezone import make_aware, now
|
||||||
|
|
||||||
|
from pretix.base.models import Event, SalesChannel
|
||||||
|
from pretix.base.reldate import RelativeDateWrapper
|
||||||
|
|
||||||
|
|
||||||
|
def compute_payment_deadline(event: Event, sales_channel: SalesChannel, now_dt=None, subevents=None) -> datetime:
|
||||||
|
now_dt = now_dt or now()
|
||||||
|
tz = ZoneInfo(event.settings.timezone)
|
||||||
|
|
||||||
|
sales_channel_suffix = "_" + sales_channel.identifier.replace(".", "_")
|
||||||
|
if not (mode := event.settings.get(f'payment_term_mode{sales_channel_suffix}')):
|
||||||
|
mode = event.settings.get('payment_term_mode')
|
||||||
|
sales_channel_suffix = ""
|
||||||
|
|
||||||
|
if mode == 'days':
|
||||||
|
exp_by_date = now_dt.astimezone(tz) + timedelta(
|
||||||
|
days=event.settings.get(f'payment_term_days{sales_channel_suffix}', as_type=int))
|
||||||
|
exp_by_date = exp_by_date.astimezone(tz).replace(hour=23, minute=59, second=59, microsecond=0)
|
||||||
|
if event.settings.get('payment_term_weekdays'):
|
||||||
|
if exp_by_date.weekday() == 5:
|
||||||
|
exp_by_date += timedelta(days=2)
|
||||||
|
elif exp_by_date.weekday() == 6:
|
||||||
|
exp_by_date += timedelta(days=1)
|
||||||
|
elif mode == 'minutes':
|
||||||
|
exp_by_date = now_dt.astimezone(tz) + timedelta(
|
||||||
|
minutes=event.settings.get(f'payment_term_minutes{sales_channel_suffix}', as_type=int))
|
||||||
|
else:
|
||||||
|
raise ValueError("'payment_term_mode' has an invalid value '{}'.".format(mode))
|
||||||
|
|
||||||
|
expires = exp_by_date
|
||||||
|
|
||||||
|
term_last = event.settings.get('payment_term_last', as_type=RelativeDateWrapper)
|
||||||
|
if term_last:
|
||||||
|
if event.has_subevents and subevents:
|
||||||
|
terms = [
|
||||||
|
term_last.datetime(se).date()
|
||||||
|
for se in subevents
|
||||||
|
]
|
||||||
|
if not terms:
|
||||||
|
return expires
|
||||||
|
term_last = min(terms)
|
||||||
|
else:
|
||||||
|
term_last = term_last.datetime(event).date()
|
||||||
|
term_last = make_aware(datetime.combine(
|
||||||
|
term_last,
|
||||||
|
time(hour=23, minute=59, second=59)
|
||||||
|
), tz)
|
||||||
|
if term_last < expires:
|
||||||
|
return term_last
|
||||||
|
|
||||||
|
return expires
|
||||||
@@ -343,6 +343,66 @@ def _validate_vat_id_EU(vat_id, country_code):
|
|||||||
return vat_id
|
return vat_id
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_vat_id_EU_fallback_germany(vat_id, country_code, requester_id):
|
||||||
|
# We can skip most static validation checks because _validate_vat_id_EU always runs before
|
||||||
|
vat_id = normalize_vat_id(vat_id, country_code)
|
||||||
|
|
||||||
|
# The VIES service of the European commission is overused and down due to rate limits A LOT. There is another
|
||||||
|
# API by German BZSt, but it only works if the requester is German and the requested is not.
|
||||||
|
# https://www.bzst.de/DE/Unternehmen/Identifikationsnummern/Umsatzsteuer-Identifikationsnummer/AuslaendischeUSt-IdNr/auslaendische_ust_idnr_node.html
|
||||||
|
try:
|
||||||
|
r = requests.post(
|
||||||
|
"https://api.evatr.vies.bzst.de/app/v1/abfrage",
|
||||||
|
json={
|
||||||
|
"anfragendeUstid": requester_id,
|
||||||
|
"angefragteUstid": vat_id,
|
||||||
|
},
|
||||||
|
timeout=10,
|
||||||
|
)
|
||||||
|
d = r.json()
|
||||||
|
if r.status_code == 200:
|
||||||
|
if d['status'] in ('evatr-0000', 'evatr-2008'):
|
||||||
|
# evatr-0000: Die angefragte Ust-IdNr. ist zum Anfragezeitpunkt gültig.
|
||||||
|
# evatr-2008: Die angefragte Ust-IdNr. ist zum Anfragezeitpunkt gültig.
|
||||||
|
# Für die qualifizierte Bestätigungsanfrage liegt einer Besonderheit vor.
|
||||||
|
# Für Rückfragen wenden Sie sich an das BZSt.
|
||||||
|
return vat_id
|
||||||
|
# evatr-2002: Die angefragte USt-IdNr. ist zum Anfragezeitpunkt nicht gültig.
|
||||||
|
# Sie ist erst gültig ab dem Datum im Feld gueltigAb.
|
||||||
|
# evatr-2006: Die angefragte Ust-IdNr. ist zum Anfragezeitpunkt nicht gültig.
|
||||||
|
# Sie war gültig im Zeitraum, der durch die Werte in den Feldern gueltigAb und gueltigBis beschrieben ist.
|
||||||
|
raise VATIDFinalError(error_messages['invalid'])
|
||||||
|
elif r.status_code == 400:
|
||||||
|
if d['status'] in ('evatr-0002', 'evatr-0004', 'evatr-0008'):
|
||||||
|
# evatr-0002: Mindestens eins der Pflichtfelder ist nicht besetzt.
|
||||||
|
# evatr-0004: Die anfragende DE Ust-IdNr. ist syntaktisch falsch. Sie passt nicht in das deutsche Erzeugungsschema.
|
||||||
|
# evatr-0008: Die maximale Anzahl von qualifizierten Bestätigungsabfragen für diese Session wurde erreicht.
|
||||||
|
# Bitte starten Sie erneut mit einer einfachen Bestätigungsabfrage.
|
||||||
|
raise VATIDTemporaryError(error_messages['unavailable'])
|
||||||
|
# evatr-0005: Die angegebene angefragte Ust-IdNr. ist syntaktisch falsch.
|
||||||
|
# evatr-0012: Die angefrage USt-IdNr. ist syntaktisch falsch. Sie passt nicht in das Erzeugungsschema.
|
||||||
|
# evatr-2003: Das angegebene Länderkennzeichen der angefragten USt-IdNr. ist nicht gültig.
|
||||||
|
raise VATIDFinalError(error_messages['invalid'])
|
||||||
|
elif r.status_code == 403:
|
||||||
|
# evatr-0006: Die anfragende DE USt-IdNr. ist nicht berechtigt eine DE Ust-IdNr. anzufragen.
|
||||||
|
# evatr-0007: Fehlerhafter Aufruf.
|
||||||
|
raise VATIDTemporaryError(error_messages['unavailable'])
|
||||||
|
elif r.status_code == 404:
|
||||||
|
if d['status'] in ('evatr-2005'):
|
||||||
|
# evatr-2005: Die angegebene eigene DE Ust-IdNr. ist zum Anfragezeitpunkt nicht gültig.
|
||||||
|
raise VATIDTemporaryError(error_messages['unavailable'])
|
||||||
|
# evatr-2001: Die angefragte USt-IdNr. ist zum Anfragezeitpunkt nicht vergeben.
|
||||||
|
raise VATIDFinalError(error_messages['invalid'])
|
||||||
|
else: # 500, 503
|
||||||
|
raise VATIDTemporaryError(error_messages['unavailable'])
|
||||||
|
except requests.RequestException:
|
||||||
|
logger.exception('VAT ID checking failed for country {}'.format(country_code))
|
||||||
|
raise VATIDTemporaryError(error_messages['unavailable'])
|
||||||
|
except ValueError: # JSON parsing failed
|
||||||
|
logger.exception('VAT ID checking failed for country {}'.format(country_code))
|
||||||
|
raise VATIDTemporaryError(error_messages['unavailable'])
|
||||||
|
|
||||||
|
|
||||||
def _validate_vat_id_CH(vat_id, country_code):
|
def _validate_vat_id_CH(vat_id, country_code):
|
||||||
if vat_id[:3] != 'CHE':
|
if vat_id[:3] != 'CHE':
|
||||||
raise VATIDFinalError(error_messages['country_mismatch'])
|
raise VATIDFinalError(error_messages['country_mismatch'])
|
||||||
@@ -394,12 +454,18 @@ def _validate_vat_id_CH(vat_id, country_code):
|
|||||||
return vat_id
|
return vat_id
|
||||||
|
|
||||||
|
|
||||||
def validate_vat_id(vat_id, country_code):
|
def validate_vat_id(vat_id, country_code, requester_id=None):
|
||||||
if not vat_id:
|
if not vat_id:
|
||||||
return vat_id
|
return vat_id
|
||||||
country_code = str(country_code)
|
country_code = str(country_code)
|
||||||
if is_eu_country(country_code):
|
if is_eu_country(country_code):
|
||||||
return _validate_vat_id_EU(vat_id, country_code)
|
try:
|
||||||
|
return _validate_vat_id_EU(vat_id, country_code)
|
||||||
|
except VATIDTemporaryError:
|
||||||
|
if requester_id and requester_id.startswith("DE") and not vat_id.startswith("DE"):
|
||||||
|
return _validate_vat_id_EU_fallback_germany(vat_id, country_code, requester_id)
|
||||||
|
else:
|
||||||
|
raise
|
||||||
elif country_code == 'CH':
|
elif country_code == 'CH':
|
||||||
return _validate_vat_id_CH(vat_id, country_code)
|
return _validate_vat_id_CH(vat_id, country_code)
|
||||||
elif country_code == 'NO':
|
elif country_code == 'NO':
|
||||||
|
|||||||
+24
-11
File diff suppressed because one or more lines are too long
@@ -20,6 +20,7 @@
|
|||||||
# <https://www.gnu.org/licenses/>.
|
# <https://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
from decimal import ROUND_HALF_UP, Decimal
|
from decimal import ROUND_HALF_UP, Decimal
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from babel import Locale, UnknownLocaleError
|
from babel import Locale, UnknownLocaleError
|
||||||
from babel.numbers import format_currency
|
from babel.numbers import format_currency
|
||||||
@@ -35,32 +36,32 @@ register = template.Library()
|
|||||||
|
|
||||||
|
|
||||||
@register.filter("money")
|
@register.filter("money")
|
||||||
def money_filter(value: Decimal, arg='', hide_currency=False):
|
def money_filter(value: Optional[Decimal | float | int | str], arg='', hide_currency=False):
|
||||||
if isinstance(value, (float, int)):
|
if isinstance(value, (float, int, str)):
|
||||||
|
if value == '':
|
||||||
|
return value
|
||||||
value = Decimal(value)
|
value = Decimal(value)
|
||||||
if value is None:
|
if value is None:
|
||||||
value = Decimal('0.00')
|
value = Decimal('0.00')
|
||||||
if not isinstance(value, Decimal):
|
if not isinstance(value, Decimal):
|
||||||
if value == '':
|
|
||||||
return value
|
|
||||||
raise TypeError("Invalid data type passed to money filter: %r" % type(value))
|
raise TypeError("Invalid data type passed to money filter: %r" % type(value))
|
||||||
if not arg:
|
if not arg:
|
||||||
raise ValueError("No currency passed.")
|
raise ValueError("No currency passed.")
|
||||||
arg = arg.upper()
|
arg = arg.upper()
|
||||||
|
|
||||||
places = settings.CURRENCY_PLACES.get(arg, 2)
|
if value.normalize().as_tuple().exponent < -9:
|
||||||
rounded = value.quantize(Decimal('1') / 10 ** places, ROUND_HALF_UP)
|
# Heuristic: It's unlikely we'll ever see values of less than 0.000000001 in any currency. Therefore, if we
|
||||||
if places < 2 and rounded != value:
|
# do see them, we very likely deal with a floating point error. This happens mostly in dev mode when computations
|
||||||
# We display decimal places even if we shouldn't for this currency if rounding
|
# are made in SQLite, which uses REAL precision, but it can also happen when we naively pass a float from Python
|
||||||
# would make the numbers incorrect. If this branch executes, it's likely a bug in
|
# land to this filter (even though it should not happen).
|
||||||
# pretix, but we won't show wrong numbers!
|
value = value.quantize(Decimal('1e-9'), ROUND_HALF_UP).normalize()
|
||||||
if hide_currency:
|
|
||||||
return floatformat(value, "2g")
|
currency_places = settings.CURRENCY_PLACES.get(arg, 2)
|
||||||
else:
|
required_places = -value.normalize().as_tuple().exponent
|
||||||
return '{} {}'.format(arg, floatformat(value, "2g"))
|
render_places = max(currency_places, required_places)
|
||||||
|
|
||||||
if hide_currency:
|
if hide_currency:
|
||||||
return floatformat(value, f"{places}g")
|
return floatformat(value, f"{render_places}g")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
locale = Locale(get_babel_locale())
|
locale = Locale(get_babel_locale())
|
||||||
@@ -68,14 +69,29 @@ def money_filter(value: Decimal, arg='', hide_currency=False):
|
|||||||
locale = "en"
|
locale = "en"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return format_currency(value, arg, locale=locale)
|
return format_currency(
|
||||||
|
value,
|
||||||
|
arg,
|
||||||
|
locale=locale,
|
||||||
|
# We only allow Babel to restrict the digits to the digits defined by the currency if this does not remove any
|
||||||
|
# precision in case we have sub-currency precision (which we shouldn't have in most places, but it's still
|
||||||
|
# better than showing wrong data). Note: Weird precision effects can occur after in-database arithmetic
|
||||||
|
# on SQLite, since SQLite does not have fixed-decimal computation.
|
||||||
|
currency_digits=currency_places >= required_places,
|
||||||
|
decimal_quantization=currency_places >= required_places,
|
||||||
|
)
|
||||||
except:
|
except:
|
||||||
return '{} {}'.format(arg, floatformat(value, f"{places}g"))
|
return '{} {}'.format(arg, floatformat(value, f"{render_places}g"))
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter("money_without_currency")
|
||||||
|
def money_filter_without_currency(value: Optional[Decimal | float | int | str], arg=''):
|
||||||
|
return money_filter(value, arg, hide_currency=True)
|
||||||
|
|
||||||
|
|
||||||
@register.filter("money_numberfield")
|
@register.filter("money_numberfield")
|
||||||
def money_numberfield_filter(value: Decimal, arg=''):
|
def money_numberfield_filter(value: Optional[Decimal | float | int | str], arg=''):
|
||||||
if isinstance(value, (float, int)):
|
if isinstance(value, (float, int, str)):
|
||||||
value = Decimal(value)
|
value = Decimal(value)
|
||||||
if not isinstance(value, Decimal):
|
if not isinstance(value, Decimal):
|
||||||
raise TypeError("Invalid data type passed to money filter: %r" % type(value))
|
raise TypeError("Invalid data type passed to money filter: %r" % type(value))
|
||||||
@@ -87,15 +103,28 @@ def money_numberfield_filter(value: Decimal, arg=''):
|
|||||||
|
|
||||||
|
|
||||||
@register.filter(is_safe=True)
|
@register.filter(is_safe=True)
|
||||||
def tax_rate_format(number):
|
def tax_rate_format(number: Optional[Decimal | float | int | str]):
|
||||||
"""
|
"""
|
||||||
Display a Decimal to its significant decimal places, used for tax rates.
|
Display a Decimal to its significant decimal places, used for tax rates.
|
||||||
"""
|
"""
|
||||||
assert isinstance(number, Decimal)
|
if isinstance(number, (float, int, str)):
|
||||||
|
if number == '':
|
||||||
|
return number
|
||||||
|
number = Decimal(number)
|
||||||
|
if number is None:
|
||||||
|
number = Decimal('0.00')
|
||||||
|
if not isinstance(number, Decimal):
|
||||||
|
raise TypeError("Invalid data type passed to tax rate format filter: %r" % type(number))
|
||||||
|
if number.normalize().as_tuple().exponent < -9:
|
||||||
|
# Heuristic: It's unlikely we'll ever see values of less than 0.000000001 in any currency. Therefore, if we
|
||||||
|
# do see them, we very likely deal with a floating point error. This happens mostly in dev mode when computations
|
||||||
|
# are made in SQLite, which uses REAL precision, but it can also happen when we naively pass a float from Python
|
||||||
|
# land to this filter (even though it should not happen).
|
||||||
|
number = number.quantize(Decimal('1e-9'), ROUND_HALF_UP).normalize()
|
||||||
return mark_safe(
|
return mark_safe(
|
||||||
formats.number_format(
|
formats.number_format(
|
||||||
number.normalize(),
|
number,
|
||||||
-number.as_tuple().exponent,
|
-number.normalize().as_tuple().exponent,
|
||||||
use_l10n=True,
|
use_l10n=True,
|
||||||
force_grouping=False,
|
force_grouping=False,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -172,7 +172,9 @@ class CachedFileInput(forms.ClearableFileInput):
|
|||||||
from ...base.models import CachedFile
|
from ...base.models import CachedFile
|
||||||
v = super().value_from_datadict(data, files, name)
|
v = super().value_from_datadict(data, files, name)
|
||||||
if v is None and data.get(name + '-cachedfile'): # An explicit "[x] clear" would be False, not None
|
if v is None and data.get(name + '-cachedfile'): # An explicit "[x] clear" would be False, not None
|
||||||
return CachedFile.objects.filter(id=data[name + '-cachedfile']).first()
|
v = CachedFile.objects.filter(id=data[name + '-cachedfile']).first()
|
||||||
|
if not v.allowed_for_session(self.request):
|
||||||
|
v = None
|
||||||
return v
|
return v
|
||||||
|
|
||||||
def get_context(self, name, value, attrs):
|
def get_context(self, name, value, attrs):
|
||||||
@@ -244,6 +246,11 @@ class ExtFileField(ExtValidationMixin, SizeFileField):
|
|||||||
class CachedFileField(ExtFileField):
|
class CachedFileField(ExtFileField):
|
||||||
widget = CachedFileInput
|
widget = CachedFileInput
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.request = kwargs.pop("request", None)
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.widget.request = self.request
|
||||||
|
|
||||||
def to_python(self, data):
|
def to_python(self, data):
|
||||||
from ...base.models import CachedFile
|
from ...base.models import CachedFile
|
||||||
|
|
||||||
@@ -271,6 +278,8 @@ class CachedFileField(ExtFileField):
|
|||||||
filename=data.name,
|
filename=data.name,
|
||||||
type=data.content_type,
|
type=data.content_type,
|
||||||
)
|
)
|
||||||
|
if self.request:
|
||||||
|
cf.bind_to_session(self.request) # no salt because we want direct web access
|
||||||
cf.file.save(data.name, data.file)
|
cf.file.save(data.name, data.file)
|
||||||
cf.save()
|
cf.save()
|
||||||
data._uploaded_to = cf
|
data._uploaded_to = cf
|
||||||
@@ -294,6 +303,8 @@ class CachedFileField(ExtFileField):
|
|||||||
filename=data.name,
|
filename=data.name,
|
||||||
type=data.content_type,
|
type=data.content_type,
|
||||||
)
|
)
|
||||||
|
if self.request:
|
||||||
|
cf.bind_to_session(self.request) # no salt because we want direct web access
|
||||||
cf.file.save(data.name, data.file)
|
cf.file.save(data.name, data.file)
|
||||||
cf.save()
|
cf.save()
|
||||||
data._uploaded_to = cf
|
data._uploaded_to = cf
|
||||||
|
|||||||
@@ -855,14 +855,21 @@ class PaymentSettingsForm(EventSettingsValidationMixin, SettingsForm):
|
|||||||
'payment_term_accept_late',
|
'payment_term_accept_late',
|
||||||
'payment_pending_hidden',
|
'payment_pending_hidden',
|
||||||
'payment_explanation',
|
'payment_explanation',
|
||||||
|
'payment_choice_postpone_allowed_channels',
|
||||||
'tax_rule_payment',
|
'tax_rule_payment',
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
channels = list(self.obj.organizer.sales_channels.all())
|
||||||
|
self.fields['payment_choice_postpone_allowed_channels'].choices = [
|
||||||
|
(c.identifier, c.label) for c in channels
|
||||||
|
if c.type_instance.payment_restrictions_supported
|
||||||
|
]
|
||||||
|
|
||||||
self.term_channel_fields = {}
|
self.term_channel_fields = {}
|
||||||
for c in self.obj.organizer.sales_channels.all():
|
for c in channels:
|
||||||
if c.type_instance.payment_restrictions_supported and c.identifier != "web":
|
if c.type_instance.payment_restrictions_supported and c.identifier != "web":
|
||||||
# At the moment, it seems sufficient to allow this for the same channel types as other payment settings
|
# At the moment, it seems sufficient to allow this for the same channel types as other payment settings
|
||||||
# We can always introduce more flags later if needed
|
# We can always introduce more flags later if needed
|
||||||
|
|||||||
@@ -105,12 +105,12 @@ class GlobalSettingsForm(SettingsForm):
|
|||||||
domain=settings.SITE_URL
|
domain=settings.SITE_URL
|
||||||
)
|
)
|
||||||
)),
|
)),
|
||||||
('widget_vite_origins', forms.CharField(
|
('widget_vue2_origins', forms.CharField(
|
||||||
widget=forms.Textarea(attrs={'rows': '3'}),
|
widget=forms.Textarea(attrs={'rows': '3'}),
|
||||||
required=False,
|
required=False,
|
||||||
# Not translated on purpose, this is a temporary feature and contains too many special case words
|
# Not translated on purpose, this is a temporary feature and contains too many special case words
|
||||||
label="Vite widget origins",
|
label="Vue2 widget origins",
|
||||||
help_text="One origin per line (e.g. https://example.com). Requests from these origins will be served the new vite-based widget.",
|
help_text="One origin per line (e.g. https://example.com). Requests from these origins will be served the old vue2-based widget.",
|
||||||
))
|
))
|
||||||
])
|
])
|
||||||
responses = register_global_settings.send(self)
|
responses = register_global_settings.send(self)
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ class RRuleForm(forms.Form):
|
|||||||
('1', pgettext_lazy('rrule', 'first')),
|
('1', pgettext_lazy('rrule', 'first')),
|
||||||
('2', pgettext_lazy('rrule', 'second')),
|
('2', pgettext_lazy('rrule', 'second')),
|
||||||
('3', pgettext_lazy('rrule', 'third')),
|
('3', pgettext_lazy('rrule', 'third')),
|
||||||
|
('4', pgettext_lazy('rrule', 'fourth')),
|
||||||
('-1', pgettext_lazy('rrule', 'last')),
|
('-1', pgettext_lazy('rrule', 'last')),
|
||||||
],
|
],
|
||||||
required=False
|
required=False
|
||||||
@@ -134,6 +135,7 @@ class RRuleForm(forms.Form):
|
|||||||
('1', pgettext_lazy('rrule', 'first')),
|
('1', pgettext_lazy('rrule', 'first')),
|
||||||
('2', pgettext_lazy('rrule', 'second')),
|
('2', pgettext_lazy('rrule', 'second')),
|
||||||
('3', pgettext_lazy('rrule', 'third')),
|
('3', pgettext_lazy('rrule', 'third')),
|
||||||
|
('4', pgettext_lazy('rrule', 'fourth')),
|
||||||
('-1', pgettext_lazy('rrule', 'last')),
|
('-1', pgettext_lazy('rrule', 'last')),
|
||||||
],
|
],
|
||||||
required=False
|
required=False
|
||||||
|
|||||||
@@ -774,6 +774,7 @@ class CoreUserImpersonatedLogEntryType(UserImpersonatedLogEntryType):
|
|||||||
'pretix.user.settings.2fa.disabled': _('Two-factor authentication has been disabled.'),
|
'pretix.user.settings.2fa.disabled': _('Two-factor authentication has been disabled.'),
|
||||||
'pretix.user.settings.2fa.regenemergency': _('Your two-factor emergency codes have been regenerated.'),
|
'pretix.user.settings.2fa.regenemergency': _('Your two-factor emergency codes have been regenerated.'),
|
||||||
'pretix.user.settings.2fa.emergency': _('A two-factor emergency code has been generated.'),
|
'pretix.user.settings.2fa.emergency': _('A two-factor emergency code has been generated.'),
|
||||||
|
'pretix.user.settings.2fa.resetdrift': _('TOTP drift has been reset.'),
|
||||||
'pretix.user.settings.2fa.device.added': _('A new two-factor authentication device "{name}" has been added to '
|
'pretix.user.settings.2fa.device.added': _('A new two-factor authentication device "{name}" has been added to '
|
||||||
'your account.'),
|
'your account.'),
|
||||||
'pretix.user.settings.2fa.device.deleted': _('The two-factor authentication device "{name}" has been removed '
|
'pretix.user.settings.2fa.device.deleted': _('The two-factor authentication device "{name}" has been removed '
|
||||||
|
|||||||
@@ -109,6 +109,7 @@
|
|||||||
{% bootstrap_form_errors form layout="control" %}
|
{% bootstrap_form_errors form layout="control" %}
|
||||||
{% bootstrap_field form.tax_rule_payment layout="control" %}
|
{% bootstrap_field form.tax_rule_payment layout="control" %}
|
||||||
{% bootstrap_field form.payment_explanation layout="control" %}
|
{% bootstrap_field form.payment_explanation layout="control" %}
|
||||||
|
{% bootstrap_field form.payment_choice_postpone_allowed_channels layout="control" %}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
{% if "event.settings.payment:write" in request.eventpermset %}
|
{% if "event.settings.payment:write" in request.eventpermset %}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{% extends "pretixcontrol/base.html" %}
|
{% extends "pretixcontrol/base.html" %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load bootstrap3 %}
|
{% load bootstrap3 %}
|
||||||
|
{% load icon %}
|
||||||
{% block title %}{% trans "User" %}{% endblock %}
|
{% block title %}{% trans "User" %}{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>{% trans "User" %} {{ user.email }}</h1>
|
<h1>{% trans "User" %} {{ user.email }}</h1>
|
||||||
@@ -16,6 +17,10 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button class="btn btn-default">{% trans "Generate 2FA emergency token" %}</button>
|
<button class="btn btn-default">{% trans "Generate 2FA emergency token" %}</button>
|
||||||
</form>
|
</form>
|
||||||
|
<form action="{% url "control:users.resetdrift" id=user.pk %}" method="post" class="form-inline helper-display-inline">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button class="btn btn-default">{% trans "Reset 2FA drift" %}</button>
|
||||||
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<form action="{% url "control:users.impersonate" id=user.pk %}" method="post" class="form-inline helper-display-inline">
|
<form action="{% url "control:users.impersonate" id=user.pk %}" method="post" class="form-inline helper-display-inline">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
@@ -59,8 +64,72 @@
|
|||||||
{% bootstrap_field form.is_verified layout='control' %}
|
{% bootstrap_field form.is_verified layout='control' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% bootstrap_field form.last_login layout='control' %}
|
{% bootstrap_field form.last_login layout='control' %}
|
||||||
{% bootstrap_field form.require_2fa layout='control' %}
|
|
||||||
{% bootstrap_field form.needs_password_change layout='control' %}
|
{% bootstrap_field form.needs_password_change layout='control' %}
|
||||||
|
{% bootstrap_field form.require_2fa layout='control' %}
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-md-9 col-md-offset-3">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h3 class="panel-title">
|
||||||
|
{% trans "Available two-factor authentication methods" %}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<table class="panel-body table table-hover">
|
||||||
|
{% for d in devices %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{% if d.devicetype == 'totp' %}
|
||||||
|
TOTP
|
||||||
|
{% elif d.devicetype == 'u2f' %}
|
||||||
|
U2F
|
||||||
|
{% elif d.devicetype == 'webauthn' %}
|
||||||
|
WebAuthn
|
||||||
|
{% elif d.devicetype == 'emergency' %}
|
||||||
|
{% trans "Emergency tokens" %}
|
||||||
|
{% endif %}
|
||||||
|
{% if d.confirmed %}
|
||||||
|
{% icon "check" %}
|
||||||
|
{% else %}
|
||||||
|
{% icon "warning" %}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ d.name }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if d.throttling_failure_timestamp %}
|
||||||
|
{% blocktrans trimmed with date=d.throttling_failure_timestamp|date:"SHORT_DATETIME_FORMAT" count cnt=d.throttling_failure_count %}
|
||||||
|
1 failed attempt since {{ date }}
|
||||||
|
{% plural %}
|
||||||
|
{{ cnt }} failed attempts since {{ date }}
|
||||||
|
{% endblocktrans %}
|
||||||
|
<br>
|
||||||
|
{% endif %}
|
||||||
|
{% if d.devicetype == 'totp' %}
|
||||||
|
<small>
|
||||||
|
<code>step = {{ d.step }},
|
||||||
|
t0 = {{ d.t0 }},
|
||||||
|
digits = {{ d.digits }},
|
||||||
|
tolerance = {{ d.tolerance }},
|
||||||
|
drift = {{ d.drift }},
|
||||||
|
last_t = {{ d.last_t }}</code>
|
||||||
|
</small>
|
||||||
|
{% elif d.devicetype == 'u2f' %}
|
||||||
|
<small>
|
||||||
|
<code>sign_count = {{ d.sign_count }}</code>
|
||||||
|
</small>
|
||||||
|
{% elif d.devicetype == 'emergency' %}
|
||||||
|
<small>
|
||||||
|
<code>token_count = {{ d.token_set.count }}</code>
|
||||||
|
</small>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>{% trans "Team memberships" %}</legend>
|
<legend>{% trans "Team memberships" %}</legend>
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ urlpatterns = [
|
|||||||
re_path(r'^users/(?P<id>\d+)/impersonate$', users.UserImpersonateView.as_view(), name='users.impersonate'),
|
re_path(r'^users/(?P<id>\d+)/impersonate$', users.UserImpersonateView.as_view(), name='users.impersonate'),
|
||||||
re_path(r'^users/(?P<id>\d+)/anonymize$', users.UserAnonymizeView.as_view(), name='users.anonymize'),
|
re_path(r'^users/(?P<id>\d+)/anonymize$', users.UserAnonymizeView.as_view(), name='users.anonymize'),
|
||||||
re_path(r'^users/(?P<id>\d+)/emergencytoken$', users.UserEmergencyTokenView.as_view(), name='users.emergencytoken'),
|
re_path(r'^users/(?P<id>\d+)/emergencytoken$', users.UserEmergencyTokenView.as_view(), name='users.emergencytoken'),
|
||||||
|
re_path(r'^users/(?P<id>\d+)/resetdrift$', users.Reset2FADriftView.as_view(), name='users.resetdrift'),
|
||||||
re_path(r'^pdf/editor/webfonts.css', pdf.FontsCSSView.as_view(), name='pdf.css'),
|
re_path(r'^pdf/editor/webfonts.css', pdf.FontsCSSView.as_view(), name='pdf.css'),
|
||||||
re_path(r'^settings/?$', user.UserSettings.as_view(), name='user.settings'),
|
re_path(r'^settings/?$', user.UserSettings.as_view(), name='user.settings'),
|
||||||
re_path(r'^settings/history/$', user.UserHistoryView.as_view(), name='user.settings.history'),
|
re_path(r'^settings/history/$', user.UserHistoryView.as_view(), name='user.settings.history'),
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ from pretix.base.forms.auth import (
|
|||||||
)
|
)
|
||||||
from pretix.base.metrics import pretix_failed_logins, pretix_successful_logins
|
from pretix.base.metrics import pretix_failed_logins, pretix_successful_logins
|
||||||
from pretix.base.models import TeamInvite, U2FDevice, User, WebAuthnDevice
|
from pretix.base.models import TeamInvite, U2FDevice, User, WebAuthnDevice
|
||||||
|
from pretix.helpers import OF_SELF
|
||||||
from pretix.helpers.http import get_client_ip, redirect_to_url
|
from pretix.helpers.http import get_client_ip, redirect_to_url
|
||||||
from pretix.helpers.ratelimit import rate_limit, rate_limit_reset
|
from pretix.helpers.ratelimit import rate_limit, rate_limit_reset
|
||||||
from pretix.helpers.security import handle_login_source, session_login
|
from pretix.helpers.security import handle_login_source, session_login
|
||||||
@@ -395,15 +396,17 @@ class Recover(TemplateView):
|
|||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
if self.form.is_valid():
|
if self.form.is_valid():
|
||||||
try:
|
with transaction.atomic():
|
||||||
user = User.objects.get(id=self.request.GET.get('id'), auth_backend='native')
|
# Check token in transaction to prevent race condition
|
||||||
except User.DoesNotExist:
|
try:
|
||||||
return self.invalid('unknownuser')
|
user = User.objects.select_for_update(of=OF_SELF).get(id=self.request.GET.get('id'), auth_backend='native')
|
||||||
if not default_token_generator.check_token(user, self.request.GET.get('token')):
|
except User.DoesNotExist:
|
||||||
return self.invalid('invalid')
|
return self.invalid('unknownuser')
|
||||||
user.set_password(self.form.cleaned_data['password'])
|
if not default_token_generator.check_token(user, self.request.GET.get('token')):
|
||||||
user.needs_password_change = False
|
return self.invalid('invalid')
|
||||||
user.save()
|
user.set_password(self.form.cleaned_data['password'])
|
||||||
|
user.needs_password_change = False
|
||||||
|
user.save()
|
||||||
messages.success(request, _('You can now login using your new password.'))
|
messages.success(request, _('You can now login using your new password.'))
|
||||||
user.log_action('pretix.control.auth.user.forgot_password.recovered')
|
user.log_action('pretix.control.auth.user.forgot_password.recovered')
|
||||||
|
|
||||||
|
|||||||
@@ -163,6 +163,9 @@ class DiscountCreate(EventPermissionRequiredMixin, CreateView):
|
|||||||
i = modelcopy(self.copy_from)
|
i = modelcopy(self.copy_from)
|
||||||
i.pk = None
|
i.pk = None
|
||||||
kwargs['instance'] = i
|
kwargs['instance'] = i
|
||||||
|
kwargs["initial"]["limit_sales_channels"] = self.copy_from.limit_sales_channels.all()
|
||||||
|
kwargs["initial"]["condition_limit_products"] = self.copy_from.condition_limit_products.all()
|
||||||
|
kwargs["initial"]["benefit_limit_products"] = self.copy_from.benefit_limit_products.all()
|
||||||
else:
|
else:
|
||||||
kwargs['instance'] = Discount(event=self.request.event)
|
kwargs['instance'] = Discount(event=self.request.event)
|
||||||
|
|
||||||
|
|||||||
@@ -1646,7 +1646,8 @@ class OrderCheckVATID(OrderView):
|
|||||||
return redirect(self.get_order_url())
|
return redirect(self.get_order_url())
|
||||||
|
|
||||||
try:
|
try:
|
||||||
normalized_id = validate_vat_id(ia.vat_id, str(ia.country))
|
requester_id = self.request.event.settings.invoice_address_from_vat_id
|
||||||
|
normalized_id = validate_vat_id(ia.vat_id, str(ia.country), requester_id)
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
ia.vat_id_validated = True
|
ia.vat_id_validated = True
|
||||||
ia.vat_id = normalized_id
|
ia.vat_id = normalized_id
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ from decimal import Decimal
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.core.files import File
|
from django.core.files import File
|
||||||
from django.core.files.base import ContentFile
|
from django.core.files.base import ContentFile
|
||||||
from django.core.files.storage import default_storage
|
from django.core.files.storage import default_storage
|
||||||
@@ -193,6 +194,7 @@ class BaseEditorView(EventPermissionRequiredMixin, TemplateView):
|
|||||||
c.expires = now() + timedelta(days=7)
|
c.expires = now() + timedelta(days=7)
|
||||||
c.date = now()
|
c.date = now()
|
||||||
c.filename = 'background_preview.pdf'
|
c.filename = 'background_preview.pdf'
|
||||||
|
c.bind_to_session(request, "ticketoutput-pdf-background")
|
||||||
c.type = 'application/pdf'
|
c.type = 'application/pdf'
|
||||||
c.save()
|
c.save()
|
||||||
c.file.save('empty.pdf', ContentFile(buffer.read()))
|
c.file.save('empty.pdf', ContentFile(buffer.read()))
|
||||||
@@ -218,6 +220,7 @@ class BaseEditorView(EventPermissionRequiredMixin, TemplateView):
|
|||||||
c.expires = now() + timedelta(days=7)
|
c.expires = now() + timedelta(days=7)
|
||||||
c.date = now()
|
c.date = now()
|
||||||
c.filename = 'background_preview.pdf'
|
c.filename = 'background_preview.pdf'
|
||||||
|
c.bind_to_session(request, "ticketoutput-pdf-background")
|
||||||
c.type = 'application/pdf'
|
c.type = 'application/pdf'
|
||||||
c.file = fileobj
|
c.file = fileobj
|
||||||
c.save()
|
c.save()
|
||||||
@@ -303,5 +306,7 @@ class FontsCSSView(TemplateView):
|
|||||||
class PdfView(TemplateView):
|
class PdfView(TemplateView):
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
cf = get_object_or_404(CachedFile, id=kwargs.get("filename"), filename="background_preview.pdf")
|
cf = get_object_or_404(CachedFile, id=kwargs.get("filename"), filename="background_preview.pdf")
|
||||||
|
if not cf.allowed_for_session(request, "ticketoutput-pdf-background"):
|
||||||
|
raise PermissionDenied()
|
||||||
resp = FileResponse(cf.file, filename=cf.filename, content_type='application/pdf')
|
resp = FileResponse(cf.file, filename=cf.filename, content_type='application/pdf')
|
||||||
return resp
|
return resp
|
||||||
|
|||||||
@@ -41,15 +41,18 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
from django.views import View
|
from django.views import View
|
||||||
from django.views.generic import ListView, TemplateView
|
from django.views.generic import ListView, TemplateView
|
||||||
from django_otp.plugins.otp_static.models import StaticDevice
|
from django_otp.plugins.otp_static.models import StaticDevice
|
||||||
|
from django_otp.plugins.otp_totp.models import TOTPDevice
|
||||||
from hijack import signals
|
from hijack import signals
|
||||||
|
|
||||||
from pretix.base.auth import get_auth_backends
|
from pretix.base.auth import get_auth_backends
|
||||||
from pretix.base.models import User
|
from pretix.base.models import U2FDevice, User, WebAuthnDevice
|
||||||
from pretix.control.forms.filter import UserFilterForm
|
from pretix.control.forms.filter import UserFilterForm
|
||||||
from pretix.control.forms.users import UserEditForm
|
from pretix.control.forms.users import UserEditForm
|
||||||
from pretix.control.permissions import AdministratorPermissionRequiredMixin
|
from pretix.control.permissions import AdministratorPermissionRequiredMixin
|
||||||
from pretix.control.views import CreateView, UpdateView
|
from pretix.control.views import CreateView, UpdateView
|
||||||
from pretix.control.views.user import RecentAuthenticationRequiredMixin
|
from pretix.control.views.user import (
|
||||||
|
REAL_DEVICE_TYPES, RecentAuthenticationRequiredMixin,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_used_backend(request):
|
def get_used_backend(request):
|
||||||
@@ -107,6 +110,21 @@ class UserEditView(AdministratorPermissionRequiredMixin, RecentAuthenticationReq
|
|||||||
ctx['backend'] = (
|
ctx['backend'] = (
|
||||||
b[self.object.auth_backend].verbose_name if self.object.auth_backend in b else self.object.auth_backend
|
b[self.object.auth_backend].verbose_name if self.object.auth_backend in b else self.object.auth_backend
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ctx['devices'] = []
|
||||||
|
for dt in [*REAL_DEVICE_TYPES, StaticDevice]:
|
||||||
|
objs = list(dt.objects.filter(user=self.request.user, confirmed=True))
|
||||||
|
for obj in objs:
|
||||||
|
if dt == TOTPDevice:
|
||||||
|
obj.devicetype = 'totp'
|
||||||
|
elif dt == U2FDevice:
|
||||||
|
obj.devicetype = 'u2f'
|
||||||
|
elif dt == WebAuthnDevice:
|
||||||
|
obj.devicetype = 'webauthn'
|
||||||
|
elif dt == StaticDevice:
|
||||||
|
obj.devicetype = 'emergency'
|
||||||
|
ctx['devices'] += objs
|
||||||
|
|
||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
@@ -183,6 +201,24 @@ class UserEmergencyTokenView(AdministratorPermissionRequiredMixin, RecentAuthent
|
|||||||
return reverse('control:users.edit', kwargs=self.kwargs)
|
return reverse('control:users.edit', kwargs=self.kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class Reset2FADriftView(AdministratorPermissionRequiredMixin, RecentAuthenticationRequiredMixin, View):
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
return redirect(reverse('control:users.edit', kwargs=self.kwargs))
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
self.object = get_object_or_404(User, pk=self.kwargs.get("id"))
|
||||||
|
self.object.totpdevice_set.update(drift=0)
|
||||||
|
self.object.log_action('pretix.user.settings.2fa.resetdrift', user=self.request.user)
|
||||||
|
messages.success(request, _(
|
||||||
|
'The drift values for TOTP devices have been reset.'
|
||||||
|
))
|
||||||
|
return redirect(self.get_success_url())
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse('control:users.edit', kwargs=self.kwargs)
|
||||||
|
|
||||||
|
|
||||||
class UserAnonymizeView(AdministratorPermissionRequiredMixin, RecentAuthenticationRequiredMixin, TemplateView):
|
class UserAnonymizeView(AdministratorPermissionRequiredMixin, RecentAuthenticationRequiredMixin, TemplateView):
|
||||||
template_name = "pretixcontrol/users/anonymize.html"
|
template_name = "pretixcontrol/users/anonymize.html"
|
||||||
|
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ class VoucherList(VoucherQueryMixin, PaginationMixin, EventPermissionRequiredMix
|
|||||||
headers = [
|
headers = [
|
||||||
_('Voucher code'), _('Valid until'), _('Product'), _('Reserve quota'), _('Bypass quota'),
|
_('Voucher code'), _('Valid until'), _('Product'), _('Reserve quota'), _('Bypass quota'),
|
||||||
_('Price effect'), _('Value'), _('Tag'), _('Redeemed'), _('Maximum usages'), _('Seat'),
|
_('Price effect'), _('Value'), _('Tag'), _('Redeemed'), _('Maximum usages'), _('Seat'),
|
||||||
_('Comment')
|
_('Comment'), _('Budget'), _('Budget used')
|
||||||
]
|
]
|
||||||
writer.writerow(headers)
|
writer.writerow(headers)
|
||||||
|
|
||||||
@@ -170,7 +170,9 @@ class VoucherList(VoucherQueryMixin, PaginationMixin, EventPermissionRequiredMix
|
|||||||
str(v.redeemed),
|
str(v.redeemed),
|
||||||
str(v.max_usages),
|
str(v.max_usages),
|
||||||
str(v.seat) if v.seat else "",
|
str(v.seat) if v.seat else "",
|
||||||
str(v.comment) if v.comment else ""
|
str(v.comment) if v.comment else "",
|
||||||
|
str(v.budget) if v.budget is not None else "",
|
||||||
|
str(v.budget_used) if v.budget is not None else "",
|
||||||
]
|
]
|
||||||
writer.writerow(row)
|
writer.writerow(row)
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ from django.urls import reverse
|
|||||||
def build_absolute_uri(urlname, args=None, kwargs=None):
|
def build_absolute_uri(urlname, args=None, kwargs=None):
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
'Usage of build_absolute_uri is confusing since there are many functions with that name. '
|
'Usage of build_absolute_uri is confusing since there are many functions with that name. '
|
||||||
'Replace this usage with ',
|
'Replace this usage with mainreverse_absolute.',
|
||||||
DeprecationWarning
|
DeprecationWarning
|
||||||
)
|
)
|
||||||
return mainreverse_absolute(urlname, args, kwargs)
|
return mainreverse_absolute(urlname, args, kwargs)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: 1\n"
|
"Project-Id-Version: 1\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-08-24 15:35+0000\n"
|
"POT-Creation-Date: 2026-08-24 15:35+0000\n"
|
||||||
"PO-Revision-Date: 2026-08-25 00:00+0000\n"
|
"PO-Revision-Date: 2026-09-14 23:00+0000\n"
|
||||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||||
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix/"
|
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||||
"de/>\n"
|
"de/>\n"
|
||||||
@@ -14,7 +14,7 @@ msgstr ""
|
|||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||||
"X-Generator: Weblate 2026.8.1\n"
|
"X-Generator: Weblate 2026.9.1\n"
|
||||||
"X-Poedit-Bookmarks: -1,-1,904,-1,-1,-1,-1,-1,-1,-1\n"
|
"X-Poedit-Bookmarks: -1,-1,904,-1,-1,-1,-1,-1,-1,-1\n"
|
||||||
|
|
||||||
#: pretix/_base_settings.py
|
#: pretix/_base_settings.py
|
||||||
@@ -11708,7 +11708,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Hallo,\n"
|
"Hallo,\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Sie erhalten diese Nachricht weil Sie einen neuen Link zu Ihrer Bestellung "
|
"Sie erhalten diese Nachricht, weil Sie einen neuen Link zu Ihrer Bestellung "
|
||||||
"für\n"
|
"für\n"
|
||||||
"{event} angefordert haben.\n"
|
"{event} angefordert haben.\n"
|
||||||
"\n"
|
"\n"
|
||||||
@@ -11738,8 +11738,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Hallo,\n"
|
"Hallo,\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Sie erhalten diese Nachricht weil Sie einen neuen Link zu Ihren Bestellungen "
|
"Sie erhalten diese Nachricht, weil Sie einen neuen Link zu Ihren "
|
||||||
"für\n"
|
"Bestellungen für\n"
|
||||||
"{event} angefordert haben. Sie finden Ihre Bestellungen unter folgenden "
|
"{event} angefordert haben. Sie finden Ihre Bestellungen unter folgenden "
|
||||||
"Links:\n"
|
"Links:\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
|||||||
@@ -8,16 +8,16 @@ msgstr ""
|
|||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-08-24 15:35+0000\n"
|
"POT-Creation-Date: 2026-08-24 15:35+0000\n"
|
||||||
"PO-Revision-Date: 2026-08-24 14:30+0000\n"
|
"PO-Revision-Date: 2026-09-04 14:29+0000\n"
|
||||||
"Last-Translator: Albizuri <oier@puntu.eus>\n"
|
"Last-Translator: Albizuri <oier@puntu.eus>\n"
|
||||||
"Language-Team: Basque <https://translate.pretix.eu/projects/pretix/pretix/eu/"
|
"Language-Team: Basque <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||||
">\n"
|
"eu/>\n"
|
||||||
"Language: eu\n"
|
"Language: eu\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||||
"X-Generator: Weblate 2026.8.1\n"
|
"X-Generator: Weblate 2026.9\n"
|
||||||
|
|
||||||
#: pretix/_base_settings.py
|
#: pretix/_base_settings.py
|
||||||
msgid "English"
|
msgid "English"
|
||||||
@@ -891,7 +891,7 @@ msgstr "Produktuaren data"
|
|||||||
#: pretix/control/templates/pretixcontrol/order/index.html
|
#: pretix/control/templates/pretixcontrol/order/index.html
|
||||||
#: pretix/presale/templates/pretixpresale/event/order.html
|
#: pretix/presale/templates/pretixpresale/event/order.html
|
||||||
msgid "Order details"
|
msgid "Order details"
|
||||||
msgstr ""
|
msgstr "Eskaeraren xehetasunak"
|
||||||
|
|
||||||
#: pretix/base/datasync/sourcefields.py pretix/base/modelimport_orders.py
|
#: pretix/base/datasync/sourcefields.py pretix/base/modelimport_orders.py
|
||||||
#: pretix/control/forms/filter.py
|
#: pretix/control/forms/filter.py
|
||||||
@@ -17296,7 +17296,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/control/logdisplay.py
|
#: pretix/control/logdisplay.py
|
||||||
msgid "The order details have been changed."
|
msgid "The order details have been changed."
|
||||||
msgstr ""
|
msgstr "Eskaeraren xehetasunak aldatu dira."
|
||||||
|
|
||||||
#: pretix/control/logdisplay.py
|
#: pretix/control/logdisplay.py
|
||||||
msgid "The order has been marked as unpaid."
|
msgid "The order has been marked as unpaid."
|
||||||
@@ -23106,7 +23106,7 @@ msgstr ""
|
|||||||
#: pretix/control/templates/pretixcontrol/order/index.html
|
#: pretix/control/templates/pretixcontrol/order/index.html
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Order details: %(code)s"
|
msgid "Order details: %(code)s"
|
||||||
msgstr ""
|
msgstr "Eskaeraren xehetasunak: %(code)s"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/order/index.html
|
#: pretix/control/templates/pretixcontrol/order/index.html
|
||||||
#: pretix/control/templates/pretixcontrol/orders/index.html
|
#: pretix/control/templates/pretixcontrol/orders/index.html
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ msgstr ""
|
|||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-08-24 15:35+0000\n"
|
"POT-Creation-Date: 2026-08-24 15:35+0000\n"
|
||||||
"PO-Revision-Date: 2026-08-16 22:00+0000\n"
|
"PO-Revision-Date: 2026-09-07 18:00+0000\n"
|
||||||
"Last-Translator: Hijiri Umemoto <hijiri@umemoto.org>\n"
|
"Last-Translator: Yasunobu YesNo Kawaguchi <kawaguti@gmail.com>\n"
|
||||||
"Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/pretix/"
|
"Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||||
"ja/>\n"
|
"ja/>\n"
|
||||||
"Language: ja\n"
|
"Language: ja\n"
|
||||||
@@ -17,7 +17,7 @@ msgstr ""
|
|||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||||
"X-Generator: Weblate 2026.8.1\n"
|
"X-Generator: Weblate 2026.9\n"
|
||||||
|
|
||||||
#: pretix/_base_settings.py
|
#: pretix/_base_settings.py
|
||||||
msgid "English"
|
msgid "English"
|
||||||
@@ -93,7 +93,7 @@ msgstr "ヘブライ語"
|
|||||||
|
|
||||||
#: pretix/_base_settings.py
|
#: pretix/_base_settings.py
|
||||||
msgid "Hungarian"
|
msgid "Hungarian"
|
||||||
msgstr ""
|
msgstr "ハンガリー語"
|
||||||
|
|
||||||
#: pretix/_base_settings.py
|
#: pretix/_base_settings.py
|
||||||
msgid "Indonesian"
|
msgid "Indonesian"
|
||||||
@@ -220,7 +220,7 @@ msgstr "ターゲットURL"
|
|||||||
#: pretix/api/models.py pretix/base/models/devices.py
|
#: pretix/api/models.py pretix/base/models/devices.py
|
||||||
#: pretix/base/models/organizer.py
|
#: pretix/base/models/organizer.py
|
||||||
msgid "All events (including newly created ones)"
|
msgid "All events (including newly created ones)"
|
||||||
msgstr "すべてのイベント(最近作成されたイベントを含む)"
|
msgstr "すべてのイベント(今後作成されるものを含む)"
|
||||||
|
|
||||||
#: pretix/api/models.py pretix/base/models/devices.py
|
#: pretix/api/models.py pretix/base/models/devices.py
|
||||||
#: pretix/base/models/organizer.py
|
#: pretix/base/models/organizer.py
|
||||||
@@ -428,10 +428,8 @@ msgid "You cannot exchange a medium for a medium."
|
|||||||
msgstr "メディアを別のメディアに変更することはできません。"
|
msgstr "メディアを別のメディアに変更することはできません。"
|
||||||
|
|
||||||
#: pretix/api/views/checkin.py
|
#: pretix/api/views/checkin.py
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Product does not support medium exchange."
|
|
||||||
msgid "You cannot simulate a medium exchange."
|
msgid "You cannot simulate a medium exchange."
|
||||||
msgstr "本製品は中程度の交換に対応していません。"
|
msgstr "メディアの交換をシミュレートすることはできません。"
|
||||||
|
|
||||||
#: pretix/api/views/oauth.py pretix/control/logdisplay.py
|
#: pretix/api/views/oauth.py pretix/control/logdisplay.py
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
@@ -5691,7 +5689,7 @@ msgstr "国コード(ISO 3166-1 alpha-2)"
|
|||||||
|
|
||||||
#: pretix/base/models/items.py
|
#: pretix/base/models/items.py
|
||||||
msgid "Asked on"
|
msgid "Asked on"
|
||||||
msgstr ""
|
msgstr "質問日"
|
||||||
|
|
||||||
#: pretix/base/models/items.py pretix/base/models/organizer.py
|
#: pretix/base/models/items.py pretix/base/models/organizer.py
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -7340,12 +7338,9 @@ msgid "The payment for this invoice has already been received."
|
|||||||
msgstr "この請求書の支払いはすでに受領済みです。"
|
msgstr "この請求書の支払いはすでに受領済みです。"
|
||||||
|
|
||||||
#: pretix/base/payment.py
|
#: pretix/base/payment.py
|
||||||
#, fuzzy
|
|
||||||
#| msgid ""
|
|
||||||
#| "This payment is already being processed and can not be canceled any more."
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"This payment is already being processed and cannot be canceled any more."
|
"This payment is already being processed and cannot be canceled any more."
|
||||||
msgstr "この支払いは処理中のため、キャンセルできません。"
|
msgstr "この支払いはすでに処理中のため、キャンセルできません。"
|
||||||
|
|
||||||
#: pretix/base/payment.py
|
#: pretix/base/payment.py
|
||||||
msgid "Automatic refunds are not supported by this payment provider."
|
msgid "Automatic refunds are not supported by this payment provider."
|
||||||
@@ -8105,16 +8100,12 @@ msgid "Presale end"
|
|||||||
msgstr "前売り終了"
|
msgstr "前売り終了"
|
||||||
|
|
||||||
#: pretix/base/reldate.py
|
#: pretix/base/reldate.py
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Order email"
|
|
||||||
msgid "Order creation"
|
msgid "Order creation"
|
||||||
msgstr "注文者メール"
|
msgstr "注文の作成"
|
||||||
|
|
||||||
#: pretix/base/reldate.py
|
#: pretix/base/reldate.py
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Order expired"
|
|
||||||
msgid "Order expiry"
|
msgid "Order expiry"
|
||||||
msgstr "注文の有効期限が切れました"
|
msgstr "注文の失効"
|
||||||
|
|
||||||
#: pretix/base/reldate.py
|
#: pretix/base/reldate.py
|
||||||
msgid "before"
|
msgid "before"
|
||||||
@@ -8143,22 +8134,22 @@ msgstr "未設定"
|
|||||||
#: pretix/base/reldate.py
|
#: pretix/base/reldate.py
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "A relative date cannot be expressed as \"before\" for \"{}\""
|
msgid "A relative date cannot be expressed as \"before\" for \"{}\""
|
||||||
msgstr ""
|
msgstr "相対的な日付は、「{}」に対して「before」で表すことはできません"
|
||||||
|
|
||||||
#: pretix/base/reldate.py
|
#: pretix/base/reldate.py
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "A relative date cannot be expressed as \"after\" for \"{}\""
|
msgid "A relative date cannot be expressed as \"after\" for \"{}\""
|
||||||
msgstr ""
|
msgstr "相対的な日付は「{}」に対して「after」として表すことはできません"
|
||||||
|
|
||||||
#: pretix/base/reldate.py
|
#: pretix/base/reldate.py
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "A relative time cannot be expressed as \"before\" for \"{}\""
|
msgid "A relative time cannot be expressed as \"before\" for \"{}\""
|
||||||
msgstr ""
|
msgstr "相対時間は「{}」に対して「before」で表すことはできません"
|
||||||
|
|
||||||
#: pretix/base/reldate.py
|
#: pretix/base/reldate.py
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "A relative time cannot be expressed as \"after\" for \"{}\""
|
msgid "A relative time cannot be expressed as \"after\" for \"{}\""
|
||||||
msgstr ""
|
msgstr "相対時間は「{}」に対して「after」で表すことはできません"
|
||||||
|
|
||||||
#: pretix/base/secrets.py
|
#: pretix/base/secrets.py
|
||||||
msgid "Random (default, works with all pretix apps)"
|
msgid "Random (default, works with all pretix apps)"
|
||||||
@@ -10067,12 +10058,12 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/base/settings.py
|
#: pretix/base/settings.py
|
||||||
msgid "No dates match your criteria."
|
msgid "No dates match your criteria."
|
||||||
msgstr ""
|
msgstr "条件に合致する日付はありません。"
|
||||||
|
|
||||||
#: pretix/base/settings.py
|
#: pretix/base/settings.py
|
||||||
msgctxt "subevents"
|
msgctxt "subevents"
|
||||||
msgid "Text for empty date results"
|
msgid "Text for empty date results"
|
||||||
msgstr ""
|
msgstr "空の日付結果のテキスト"
|
||||||
|
|
||||||
#: pretix/base/settings.py
|
#: pretix/base/settings.py
|
||||||
msgctxt "subevents"
|
msgctxt "subevents"
|
||||||
@@ -10083,6 +10074,10 @@ msgid ""
|
|||||||
"touch with you to arrange further dates. We do not recommend more than one "
|
"touch with you to arrange further dates. We do not recommend more than one "
|
||||||
"or two sentences."
|
"or two sentences."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"このテキストは、カレンダーまたは日付リストが空の場合に表示されます。たとえば"
|
||||||
|
"、月に日付が含まれていない場合や、ユーザーが選択したフィルターに結果が見つか"
|
||||||
|
"らない場合です。これをご利用いただくことで、今後の日程調整のために連絡する方"
|
||||||
|
"法を宣伝できます。1文または2文以上は推奨いたしません。"
|
||||||
|
|
||||||
#: pretix/base/settings.py
|
#: pretix/base/settings.py
|
||||||
msgid "Guidance text"
|
msgid "Guidance text"
|
||||||
@@ -13902,26 +13897,20 @@ msgid ""
|
|||||||
msgstr "ギフトカードの有効期限を発行年を含めた{}年に設定しました。"
|
msgstr "ギフトカードの有効期限を発行年を含めた{}年に設定しました。"
|
||||||
|
|
||||||
#: pretix/control/forms/event.py
|
#: pretix/control/forms/event.py
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Payment date"
|
|
||||||
msgid "Payment term"
|
msgid "Payment term"
|
||||||
msgstr "支払い日"
|
msgstr "支払条件"
|
||||||
|
|
||||||
#: pretix/control/forms/event.py
|
#: pretix/control/forms/event.py
|
||||||
msgid "same as above"
|
msgid "same as above"
|
||||||
msgstr ""
|
msgstr "上記と同じ"
|
||||||
|
|
||||||
#: pretix/control/forms/event.py
|
#: pretix/control/forms/event.py
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Payment term in days"
|
|
||||||
msgid "different payment term in days"
|
msgid "different payment term in days"
|
||||||
msgstr "支払い条件(日数)"
|
msgstr "日数で表す異なる支払条件"
|
||||||
|
|
||||||
#: pretix/control/forms/event.py
|
#: pretix/control/forms/event.py
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Payment term in minutes"
|
|
||||||
msgid "different payment term in minutes"
|
msgid "different payment term in minutes"
|
||||||
msgstr "支払い期限(分単位)"
|
msgstr "分で表す異なる支払条件"
|
||||||
|
|
||||||
#: pretix/control/forms/event.py
|
#: pretix/control/forms/event.py
|
||||||
msgid "Prices including tax"
|
msgid "Prices including tax"
|
||||||
@@ -22533,28 +22522,24 @@ msgstr ""
|
|||||||
"食事を提供する場合、ユーザーに食事制限について尋ねることができる一例です。"
|
"食事を提供する場合、ユーザーに食事制限について尋ねることができる一例です。"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/items/questions.html
|
#: pretix/control/templates/pretixcontrol/items/questions.html
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Create a new question"
|
|
||||||
msgid "Create a new per-ticket question"
|
msgid "Create a new per-ticket question"
|
||||||
msgstr "新しい質問を作成"
|
msgstr "チケットごとに新しい質問を作成"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/items/questions.html
|
#: pretix/control/templates/pretixcontrol/items/questions.html
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Create a new question"
|
|
||||||
msgid "Create a new order-level question"
|
msgid "Create a new order-level question"
|
||||||
msgstr "新しい質問を作成"
|
msgstr "注文ごとに新しい質問を作成"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/items/questions.html
|
#: pretix/control/templates/pretixcontrol/items/questions.html
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Delete question"
|
|
||||||
msgid "Per-ticket questions"
|
msgid "Per-ticket questions"
|
||||||
msgstr "質問を削除"
|
msgstr "チケットごとの質問"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/items/questions.html
|
#: pretix/control/templates/pretixcontrol/items/questions.html
|
||||||
msgid ""
|
msgid ""
|
||||||
"These questions are asked for every ticket, so possibly multiple times in "
|
"These questions are asked for every ticket, so possibly multiple times in "
|
||||||
"the same order."
|
"the same order."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"これらの質問はすべてのチケットに対して尋ねられるため、同じ順序で複数回になる"
|
||||||
|
"可能性があります。"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/items/questions.html
|
#: pretix/control/templates/pretixcontrol/items/questions.html
|
||||||
msgid "Create a new question"
|
msgid "Create a new question"
|
||||||
@@ -22573,28 +22558,24 @@ msgid "All personalized products"
|
|||||||
msgstr "すべてのパーソナライズされた製品"
|
msgstr "すべてのパーソナライズされた製品"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/items/questions.html
|
#: pretix/control/templates/pretixcontrol/items/questions.html
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Include questions"
|
|
||||||
msgid "Per-order questions"
|
msgid "Per-order questions"
|
||||||
msgstr "質問を含む"
|
msgstr "注文ごとの質問"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/items/questions.html
|
#: pretix/control/templates/pretixcontrol/items/questions.html
|
||||||
msgid ""
|
msgid ""
|
||||||
"This functionality is in active development and expected to change "
|
"This functionality is in active development and expected to change "
|
||||||
"significantly over the coming months."
|
"significantly over the coming months."
|
||||||
msgstr ""
|
msgstr "この機能は現在開発が活発で、今後数か月で大幅に変化すると予想されています。"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/items/questions.html
|
#: pretix/control/templates/pretixcontrol/items/questions.html
|
||||||
msgid ""
|
msgid ""
|
||||||
"Per-order questions are currently not supported and will not be displayed in "
|
"Per-order questions are currently not supported and will not be displayed in "
|
||||||
"pretixPOS."
|
"pretixPOS."
|
||||||
msgstr ""
|
msgstr "注文ごとの質問は現在サポートされておらず、pretixPOSでは表示されません。"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/items/questions.html
|
#: pretix/control/templates/pretixcontrol/items/questions.html
|
||||||
#, fuzzy
|
|
||||||
#| msgid "The question has been reordered."
|
|
||||||
msgid "These questions are asked once per order."
|
msgid "These questions are asked once per order."
|
||||||
msgstr "質問の並び順が変更されました。"
|
msgstr "これらの質問は、注文ごとに1回ずつ尋ねられます。"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/items/quota.html
|
#: pretix/control/templates/pretixcontrol/items/quota.html
|
||||||
#: pretix/control/templates/pretixcontrol/items/quota_edit.html
|
#: pretix/control/templates/pretixcontrol/items/quota_edit.html
|
||||||
@@ -23186,10 +23167,8 @@ msgstr "(任意)"
|
|||||||
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html
|
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html
|
||||||
#: pretix/presale/templates/pretixpresale/event/checkout_questions.html
|
#: pretix/presale/templates/pretixpresale/event/checkout_questions.html
|
||||||
#: pretix/presale/templates/pretixpresale/event/order_modify.html
|
#: pretix/presale/templates/pretixpresale/event/order_modify.html
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Additional information"
|
|
||||||
msgid "Additional order information"
|
msgid "Additional order information"
|
||||||
msgstr "追加情報"
|
msgstr "追加の注文情報"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/order/delete.html
|
#: pretix/control/templates/pretixcontrol/order/delete.html
|
||||||
msgid "Delete order"
|
msgid "Delete order"
|
||||||
@@ -24856,14 +24835,13 @@ msgid "Hardware model"
|
|||||||
msgstr "ハードウェアの機種"
|
msgstr "ハードウェアの機種"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/organizers/devices.html
|
#: pretix/control/templates/pretixcontrol/organizers/devices.html
|
||||||
#, fuzzy, python-format
|
#, python-format
|
||||||
#| msgid "Begin: %(time)s"
|
|
||||||
msgid "Last seen: %(time)s"
|
msgid "Last seen: %(time)s"
|
||||||
msgstr "開始: %(time)s"
|
msgstr "最後の閲覧: %(time)s"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/organizers/devices.html
|
#: pretix/control/templates/pretixcontrol/organizers/devices.html
|
||||||
msgid "No recent contact"
|
msgid "No recent contact"
|
||||||
msgstr ""
|
msgstr "最近の連絡なし"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/organizers/devices.html
|
#: pretix/control/templates/pretixcontrol/organizers/devices.html
|
||||||
msgid "Not yet initialized"
|
msgid "Not yet initialized"
|
||||||
@@ -28220,19 +28198,14 @@ msgstr ""
|
|||||||
"に高いことを意味します。ドメインのDNS設定を更新すべきです。"
|
"に高いことを意味します。ドメインのDNS設定を更新すべきです。"
|
||||||
|
|
||||||
#: pretix/control/views/mailsetup.py
|
#: pretix/control/views/mailsetup.py
|
||||||
#, fuzzy
|
|
||||||
#| msgid ""
|
|
||||||
#| "We did not find DMARC record for your domain. This means that there is a "
|
|
||||||
#| "very high chance most of the emails will be rejected or marked as spam. "
|
|
||||||
#| "You should update the DNS settings of your domain."
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"We did not find a DMARC record for your domain. This means that there is a "
|
"We did not find a DMARC record for your domain. This means that there is a "
|
||||||
"very high chance most of the emails will be rejected or marked as spam. You "
|
"very high chance most of the emails will be rejected or marked as spam. You "
|
||||||
"should update the DNS settings of your domain."
|
"should update the DNS settings of your domain."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"お客様のドメインのDMARCレコードが見つかりませんでした。これは、ほとんどのメー"
|
"あなたのドメインのDMARCレコードが見つかりませんでした。これは、ほとんどの"
|
||||||
"ルが拒否されたりスパムとしてマークされたりする可能性が非常に高いことを意味し"
|
"メールが拒否されたかスパムとしてマークされた可能性が非常に高いことを意味しま"
|
||||||
"ます。ドメインのDNS設定を更新すべきです。"
|
"す。ドメインのDNS設定を更新すべきです。"
|
||||||
|
|
||||||
#: pretix/control/views/mailsetup.py
|
#: pretix/control/views/mailsetup.py
|
||||||
msgid "The verification code was incorrect, please try again."
|
msgid "The verification code was incorrect, please try again."
|
||||||
@@ -30906,7 +30879,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/plugins/paypal2/payment.py
|
#: pretix/plugins/paypal2/payment.py
|
||||||
msgid "Allow further payments during compliance hold"
|
msgid "Allow further payments during compliance hold"
|
||||||
msgstr ""
|
msgstr "コンプライアンス保留の間、追加の支払いを許可する"
|
||||||
|
|
||||||
#: pretix/plugins/paypal2/payment.py
|
#: pretix/plugins/paypal2/payment.py
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -30916,16 +30889,20 @@ msgid ""
|
|||||||
"attempts during that window. This might result in them being charged twice "
|
"attempts during that window. This might result in them being charged twice "
|
||||||
"if the original payment is approved."
|
"if the original payment is approved."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"PayPalの不正防止は、個々の支払いの処理をかなりの期間ブロックする可能性があり"
|
||||||
|
"ます。この期間中、支払いは「保留中」とマークされています。顧客がその期間中に"
|
||||||
|
"別の支払い試行を開始できるように許可することができます。元の支払いが承認され"
|
||||||
|
"た場合、二重に請求される可能性があります。"
|
||||||
|
|
||||||
#: pretix/plugins/paypal2/payment.py
|
#: pretix/plugins/paypal2/payment.py
|
||||||
msgid "Timeout further payment attempts"
|
msgid "Timeout further payment attempts"
|
||||||
msgstr ""
|
msgstr "追加の支払い試行がタイムアウト"
|
||||||
|
|
||||||
#: pretix/plugins/paypal2/payment.py
|
#: pretix/plugins/paypal2/payment.py
|
||||||
msgid ""
|
msgid ""
|
||||||
"Time duration in minutes after which another payment attempt is possible, "
|
"Time duration in minutes after which another payment attempt is possible, "
|
||||||
"while the last payment is still under investigation."
|
"while the last payment is still under investigation."
|
||||||
msgstr ""
|
msgstr "最後の支払いがまだ調査中である間、別の支払い試行が可能となる時間(分)。"
|
||||||
|
|
||||||
#: pretix/plugins/paypal2/payment.py
|
#: pretix/plugins/paypal2/payment.py
|
||||||
msgid "-- Automatic --"
|
msgid "-- Automatic --"
|
||||||
@@ -31190,6 +31167,10 @@ msgid ""
|
|||||||
"twice in case PayPal allows your initial payment attempt. Please contact us "
|
"twice in case PayPal allows your initial payment attempt. Please contact us "
|
||||||
"to resolve this case."
|
"to resolve this case."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"支払いはPayPalで処理されています。通常より時間がかかります。PayPalが支払いを"
|
||||||
|
"確認するまでお待ちいただくか、こちらまたは別の支払い方法で再度お支払いをお試"
|
||||||
|
"しください。PayPalが最初の支払い試行を許可した場合、二重に請求される可能性が"
|
||||||
|
"あります。この件を解決するために、弊社までご連絡ください。"
|
||||||
|
|
||||||
#: pretix/plugins/paypal2/views.py
|
#: pretix/plugins/paypal2/views.py
|
||||||
msgid ""
|
msgid ""
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-08-24 15:35+0000\n"
|
"POT-Creation-Date: 2026-08-24 15:35+0000\n"
|
||||||
"PO-Revision-Date: 2026-03-30 21:00+0000\n"
|
"PO-Revision-Date: 2026-09-02 08:35+0000\n"
|
||||||
"Last-Translator: Renne Rocha <renne@rocha.dev.br>\n"
|
"Last-Translator: Renne Rocha <renne@rocha.dev.br>\n"
|
||||||
"Language-Team: Portuguese (Brazil) <https://translate.pretix.eu/projects/"
|
"Language-Team: Portuguese (Brazil) <https://translate.pretix.eu/projects/"
|
||||||
"pretix/pretix/pt_BR/>\n"
|
"pretix/pretix/pt_BR/>\n"
|
||||||
@@ -17,7 +17,7 @@ msgstr ""
|
|||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
||||||
"X-Generator: Weblate 5.16.2\n"
|
"X-Generator: Weblate 2026.8.1\n"
|
||||||
|
|
||||||
#: pretix/_base_settings.py
|
#: pretix/_base_settings.py
|
||||||
msgid "English"
|
msgid "English"
|
||||||
@@ -93,7 +93,7 @@ msgstr "Hebraico"
|
|||||||
|
|
||||||
#: pretix/_base_settings.py
|
#: pretix/_base_settings.py
|
||||||
msgid "Hungarian"
|
msgid "Hungarian"
|
||||||
msgstr ""
|
msgstr "Húngaro"
|
||||||
|
|
||||||
#: pretix/_base_settings.py
|
#: pretix/_base_settings.py
|
||||||
msgid "Indonesian"
|
msgid "Indonesian"
|
||||||
@@ -153,7 +153,7 @@ msgstr "Espanhol (América latina)"
|
|||||||
|
|
||||||
#: pretix/_base_settings.py
|
#: pretix/_base_settings.py
|
||||||
msgid "Thai"
|
msgid "Thai"
|
||||||
msgstr ""
|
msgstr "Tailandês"
|
||||||
|
|
||||||
#: pretix/_base_settings.py
|
#: pretix/_base_settings.py
|
||||||
msgid "Turkish"
|
msgid "Turkish"
|
||||||
@@ -3022,11 +3022,12 @@ msgid ""
|
|||||||
"The field \"%(label)s\" may not contain special characters such as "
|
"The field \"%(label)s\" may not contain special characters such as "
|
||||||
"\"%(chars)s\"."
|
"\"%(chars)s\"."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"O campo \"%(label)s\" não pode conter caracteres especiais como %(chars)s\"."
|
||||||
|
|
||||||
#: pretix/base/forms/questions.py
|
#: pretix/base/forms/questions.py
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "The field \"%(label)s\" may not contain an URL (%(url)s)."
|
msgid "The field \"%(label)s\" may not contain an URL (%(url)s)."
|
||||||
msgstr ""
|
msgstr "O campo \"%(label)s\" não pode conter uma URL (%(url)s)."
|
||||||
|
|
||||||
#: pretix/base/forms/questions.py
|
#: pretix/base/forms/questions.py
|
||||||
msgctxt "phonenumber"
|
msgctxt "phonenumber"
|
||||||
@@ -8173,7 +8174,7 @@ msgstr "Permitida"
|
|||||||
#: pretix/base/permissions.py
|
#: pretix/base/permissions.py
|
||||||
msgctxt "permission_level"
|
msgctxt "permission_level"
|
||||||
msgid "Access existing events"
|
msgid "Access existing events"
|
||||||
msgstr ""
|
msgstr "Acessar eventos existentes"
|
||||||
|
|
||||||
#: pretix/base/permissions.py
|
#: pretix/base/permissions.py
|
||||||
msgctxt "permission_level"
|
msgctxt "permission_level"
|
||||||
@@ -13607,11 +13608,15 @@ msgstr "Por favor, continue em uma nova aba"
|
|||||||
#: pretix/base/templates/pretixbase/framebreak.html
|
#: pretix/base/templates/pretixbase/framebreak.html
|
||||||
msgid "For security reasons, the following step is only possible in a new tab."
|
msgid "For security reasons, the following step is only possible in a new tab."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Por razões de segurança, o passo seguinte só é possível de ser feito em uma "
|
||||||
|
"nova aba."
|
||||||
|
|
||||||
#: pretix/base/templates/pretixbase/framebreak.html
|
#: pretix/base/templates/pretixbase/framebreak.html
|
||||||
msgid ""
|
msgid ""
|
||||||
"If the new tab did not open automatically, please click the following button:"
|
"If the new tab did not open automatically, please click the following button:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Se uma nova aba não abrir automaticamente, por favor, clique no botão a "
|
||||||
|
"seguir:"
|
||||||
|
|
||||||
#: pretix/base/templates/pretixbase/framebreak.html
|
#: pretix/base/templates/pretixbase/framebreak.html
|
||||||
#: pretix/presale/templates/pretixpresale/event/cookies.html
|
#: pretix/presale/templates/pretixpresale/event/cookies.html
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ msgstr ""
|
|||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-08-24 15:35+0000\n"
|
"POT-Creation-Date: 2026-08-24 15:35+0000\n"
|
||||||
"PO-Revision-Date: 2026-08-09 06:00+0000\n"
|
"PO-Revision-Date: 2026-09-04 14:29+0000\n"
|
||||||
"Last-Translator: Julien <julien@circusiloveyou.com>\n"
|
"Last-Translator: Linnea Thelander <linnea@coeo.events>\n"
|
||||||
"Language-Team: Swedish <https://translate.pretix.eu/projects/pretix/pretix/"
|
"Language-Team: Swedish <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||||
"sv/>\n"
|
"sv/>\n"
|
||||||
"Language: sv\n"
|
"Language: sv\n"
|
||||||
@@ -17,7 +17,7 @@ msgstr ""
|
|||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||||
"X-Generator: Weblate 2026.8.1\n"
|
"X-Generator: Weblate 2026.9\n"
|
||||||
|
|
||||||
#: pretix/_base_settings.py
|
#: pretix/_base_settings.py
|
||||||
msgid "English"
|
msgid "English"
|
||||||
@@ -19170,8 +19170,9 @@ msgstr ""
|
|||||||
#: pretix/presale/templates/pretixpresale/event/order_pay.html
|
#: pretix/presale/templates/pretixpresale/event/order_pay.html
|
||||||
#: pretix/presale/templates/pretixpresale/event/order_pay_change.html
|
#: pretix/presale/templates/pretixpresale/event/order_pay_change.html
|
||||||
#: pretix/presale/templates/pretixpresale/event/position_change.html
|
#: pretix/presale/templates/pretixpresale/event/position_change.html
|
||||||
|
#, fuzzy
|
||||||
msgid "Continue"
|
msgid "Continue"
|
||||||
msgstr "Fortsätta"
|
msgstr "Fortsätt"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/auth/oauth_authorization.html
|
#: pretix/control/templates/pretixcontrol/auth/oauth_authorization.html
|
||||||
msgid "Authorize an application"
|
msgid "Authorize an application"
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ msgstr ""
|
|||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-07-06 15:52+0000\n"
|
"POT-Creation-Date: 2026-07-06 15:52+0000\n"
|
||||||
"PO-Revision-Date: 2026-08-09 06:00+0000\n"
|
"PO-Revision-Date: 2026-09-04 14:29+0000\n"
|
||||||
"Last-Translator: Julien <julien@circusiloveyou.com>\n"
|
"Last-Translator: Linnea Thelander <linnea@coeo.events>\n"
|
||||||
"Language-Team: Swedish <https://translate.pretix.eu/projects/pretix/pretix-"
|
"Language-Team: Swedish <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||||
"js/sv/>\n"
|
"js/sv/>\n"
|
||||||
"Language: sv\n"
|
"Language: sv\n"
|
||||||
@@ -17,7 +17,7 @@ msgstr ""
|
|||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||||
"X-Generator: Weblate 2026.8.1\n"
|
"X-Generator: Weblate 2026.9\n"
|
||||||
|
|
||||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js
|
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js
|
||||||
msgid "Marked as paid"
|
msgid "Marked as paid"
|
||||||
@@ -133,8 +133,9 @@ msgstr "Mercado Pago"
|
|||||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js
|
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js
|
||||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/i18n.ts
|
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/i18n.ts
|
||||||
#: pretix/static/pretixpresale/js/ui/cart.js
|
#: pretix/static/pretixpresale/js/ui/cart.js
|
||||||
|
#, fuzzy
|
||||||
msgid "Continue"
|
msgid "Continue"
|
||||||
msgstr "Fortsätta"
|
msgstr "Fortsätt"
|
||||||
|
|
||||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js
|
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js
|
||||||
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js
|
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ from django.db.models.functions import Cast, Coalesce
|
|||||||
from django.utils.timezone import make_aware
|
from django.utils.timezone import make_aware
|
||||||
from django.utils.translation import gettext as _, gettext_lazy, pgettext_lazy
|
from django.utils.translation import gettext as _, gettext_lazy, pgettext_lazy
|
||||||
from pypdf import PageObject, PdfReader, PdfWriter, Transformation
|
from pypdf import PageObject, PdfReader, PdfWriter, Transformation
|
||||||
from pypdf.generic import RectangleObject
|
|
||||||
from reportlab.lib import pagesizes
|
from reportlab.lib import pagesizes
|
||||||
from reportlab.lib.units import inch, mm
|
from reportlab.lib.units import inch, mm
|
||||||
from reportlab.pdfgen import canvas
|
from reportlab.pdfgen import canvas
|
||||||
@@ -238,15 +237,8 @@ def _render_nup_page(nup_pdf: PdfWriter, input_pages: PageObject, opt: dict) ->
|
|||||||
di = i % badges_per_page
|
di = i % badges_per_page
|
||||||
tx = opt['margins'][3] + (di % opt['cols']) * opt['offsets'][0]
|
tx = opt['margins'][3] + (di % opt['cols']) * opt['offsets'][0]
|
||||||
ty = opt['margins'][2] + (opt['rows'] - 1 - (di // opt['cols'])) * opt['offsets'][1]
|
ty = opt['margins'][2] + (opt['rows'] - 1 - (di // opt['cols'])) * opt['offsets'][1]
|
||||||
page.add_transformation(Transformation().translate(tx, ty))
|
|
||||||
page.mediabox = RectangleObject((
|
|
||||||
Decimal('%.5f' % (page.mediabox.left.as_numeric() + tx)),
|
|
||||||
Decimal('%.5f' % (page.mediabox.bottom.as_numeric() + ty)),
|
|
||||||
Decimal('%.5f' % (page.mediabox.right.as_numeric() + tx)),
|
|
||||||
Decimal('%.5f' % (page.mediabox.top.as_numeric() + ty))
|
|
||||||
))
|
|
||||||
page.trimbox = page.cropbox = page.mediabox
|
page.trimbox = page.cropbox = page.mediabox
|
||||||
nup_page.merge_page(page)
|
nup_page.merge_transformed_page(page, Transformation().translate(tx, ty))
|
||||||
return nup_page
|
return nup_page
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,183 +1,183 @@
|
|||||||
/*global $, gettext*/
|
/* global gettext */
|
||||||
var bankimport_transactionlist = {
|
var bankimport_transactionlist = {
|
||||||
|
|
||||||
_btn_click: function (e) {
|
_btn_click: function (e) {
|
||||||
console.log(e.delegateTarget);
|
console.log(e.delegateTarget)
|
||||||
var trans_id = parseInt($(e.delegateTarget).attr("name").split("_")[1]);
|
let trans_id = parseInt($(e.delegateTarget).attr('name').split('_')[1])
|
||||||
var value = $(e.delegateTarget).val();
|
let value = $(e.delegateTarget).val()
|
||||||
if (value === "discard") {
|
if (value === 'discard') {
|
||||||
bankimport_transactionlist.discard(trans_id);
|
bankimport_transactionlist.discard(trans_id)
|
||||||
} else if (value === "accept") {
|
} else if (value === 'accept') {
|
||||||
bankimport_transactionlist.accept(trans_id);
|
bankimport_transactionlist.accept(trans_id)
|
||||||
} else if (value === "retry") {
|
} else if (value === 'retry') {
|
||||||
bankimport_transactionlist.retry(trans_id);
|
bankimport_transactionlist.retry(trans_id)
|
||||||
} else if (value === "assign") {
|
} else if (value === 'assign') {
|
||||||
bankimport_transactionlist.assign(trans_id);
|
bankimport_transactionlist.assign(trans_id)
|
||||||
}
|
}
|
||||||
return false;
|
return false
|
||||||
},
|
},
|
||||||
|
|
||||||
_action: function (id, action, success) {
|
_action: function (id, action, success) {
|
||||||
$("tr[data-id=" + id + "] button").prop("disabled", true);
|
$('tr[data-id=' + id + '] button').prop('disabled', true)
|
||||||
var data = {
|
let data = {
|
||||||
"csrfmiddlewaretoken": $("[name=csrfmiddlewaretoken]").val()
|
csrfmiddlewaretoken: $('[name=csrfmiddlewaretoken]').val()
|
||||||
};
|
}
|
||||||
data["action_" + id] = action;
|
data['action_' + id] = action
|
||||||
$.ajax({
|
$.ajax({
|
||||||
"method": "POST",
|
method: 'POST',
|
||||||
"url": $(".transaction-list").attr("data-url"),
|
url: $('.transaction-list').attr('data-url'),
|
||||||
"data": data,
|
data: data,
|
||||||
"dataType": "json",
|
dataType: 'json',
|
||||||
"success": function (data) {
|
success: function (data) {
|
||||||
if (data.status == "ok") {
|
if (data.status == 'ok') {
|
||||||
$("tr[data-id=" + id + "]").removeClass("has-error");
|
$('tr[data-id=' + id + ']').removeClass('has-error')
|
||||||
if (data.comment) {
|
if (data.comment) {
|
||||||
bankimport_transactionlist.comment_reset_to_text(id, data.comment, data.plain);
|
bankimport_transactionlist.comment_reset_to_text(id, data.comment, data.plain)
|
||||||
}
|
}
|
||||||
success();
|
success()
|
||||||
} else {
|
} else {
|
||||||
$("tr[data-id=" + id + "] button").prop("disabled", false);
|
$('tr[data-id=' + id + '] button').prop('disabled', false)
|
||||||
$("tr[data-id=" + id + "] .help-block").remove();
|
$('tr[data-id=' + id + '] .help-block').remove()
|
||||||
$("tr[data-id=" + id + "]").addClass("has-error");
|
$('tr[data-id=' + id + ']').addClass('has-error')
|
||||||
$("<p>").addClass("help-block").text(data.message).appendTo($("tr[data-id=" + id + "] td.actions"));
|
$('<p>').addClass('help-block').text(data.message).appendTo($('tr[data-id=' + id + '] td.actions'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
discard: function (id) {
|
discard: function (id) {
|
||||||
bankimport_transactionlist._action(id, "discard", function () {
|
bankimport_transactionlist._action(id, 'discard', function () {
|
||||||
$("tr[data-id=" + id + "] td").remove();
|
$('tr[data-id=' + id + '] td').remove()
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
retry: function (id) {
|
retry: function (id) {
|
||||||
bankimport_transactionlist._action(id, "retry", function () {
|
bankimport_transactionlist._action(id, 'retry', function () {
|
||||||
$("tr[data-id=" + id + "] td.actions").html('').text(gettext("Marked as paid"));
|
$('tr[data-id=' + id + '] td.actions').html('').text(gettext('Marked as paid'))
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
accept: function (id) {
|
accept: function (id) {
|
||||||
bankimport_transactionlist._action(id, "accept", function () {
|
bankimport_transactionlist._action(id, 'accept', function () {
|
||||||
$("tr[data-id=" + id + "] td.actions").html('').text(gettext("Marked as paid"));
|
$('tr[data-id=' + id + '] td.actions').html('').text(gettext('Marked as paid'))
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
assign: function (id) {
|
assign: function (id) {
|
||||||
bankimport_transactionlist._action(id, "assign:" + $("tr[data-id=" + id + "] input.form-control:not(.tt-hint)").val(), function () {
|
bankimport_transactionlist._action(id, 'assign:' + $('tr[data-id=' + id + '] input.form-control:not(.tt-hint)').val(), function () {
|
||||||
$("tr[data-id=" + id + "] td.actions").html('').text(gettext("Marked as paid"));
|
$('tr[data-id=' + id + '] td.actions').html('').text(gettext('Marked as paid'))
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
comment_reset_to_text: function (id, text, plain) {
|
comment_reset_to_text: function (id, text, plain) {
|
||||||
var $box = $("tr[data-id=" + id + "] .comment-box");
|
let $box = $('tr[data-id=' + id + '] .comment-box')
|
||||||
$box[0].dataset["plain"] = plain;
|
$box[0].dataset['plain'] = plain
|
||||||
$box.html("")
|
$box.html('')
|
||||||
.append($("<strong>").text(gettext("Comment:")))
|
.append($('<strong>').text(gettext('Comment:')))
|
||||||
.append(" ")
|
.append(' ')
|
||||||
.append($("<span>").addClass("comment").append(" ").append(text))
|
.append($('<span>').addClass('comment').append(' ').append(text))
|
||||||
.append(" ")
|
.append(' ')
|
||||||
.append($("<a>").addClass("comment-modify btn btn-default btn-xs")
|
.append($('<a>').addClass('comment-modify btn btn-default btn-xs')
|
||||||
.append("<span class='fa fa-edit'></span>"));
|
.append('<span class=\'fa fa-edit\'></span>'))
|
||||||
},
|
},
|
||||||
|
|
||||||
comment_start_edit: function (e) {
|
comment_start_edit: function (e) {
|
||||||
var $box = $(e.target).closest("div");
|
let $box = $(e.target).closest('div')
|
||||||
var id = $box.closest("tr").attr("data-id");
|
let id = $box.closest('tr').attr('data-id')
|
||||||
var $inp = $("<textarea>").addClass("form-control");
|
let $inp = $('<textarea>').addClass('form-control')
|
||||||
var orig_rendered = $box.find(".comment");
|
let orig_rendered = $box.find('.comment')
|
||||||
var orig_text = $box[0].dataset.plain;
|
let orig_text = $box[0].dataset.plain
|
||||||
$inp.val(orig_text);
|
$inp.val(orig_text)
|
||||||
|
|
||||||
var $btngrp = $("<div>");
|
let $btngrp = $('<div>')
|
||||||
$btngrp.addClass("btn-group");
|
$btngrp.addClass('btn-group')
|
||||||
var $btn1 = $("<button>");
|
let $btn1 = $('<button>')
|
||||||
$btn1.attr("type", "button").addClass("btn btn-default");
|
$btn1.attr('type', 'button').addClass('btn btn-default')
|
||||||
$btn1.append("<span class='fa fa-check'></span>");
|
$btn1.append('<span class=\'fa fa-check\'></span>')
|
||||||
$btngrp.append($btn1);
|
$btngrp.append($btn1)
|
||||||
var $btn2 = $("<button>");
|
let $btn2 = $('<button>')
|
||||||
$btn2.attr("type", "button").addClass("btn btn-default");
|
$btn2.attr('type', 'button').addClass('btn btn-default')
|
||||||
$btn2.append("<span class='fa fa-close'></span>");
|
$btn2.append('<span class=\'fa fa-close\'></span>')
|
||||||
$btngrp.append($btn2);
|
$btngrp.append($btn2)
|
||||||
$box.html("").append($inp).append($btngrp);
|
$box.html('').append($inp).append($btngrp)
|
||||||
$btn1.click(function () {
|
$btn1.click(function () {
|
||||||
var text = $box.find("textarea").val();
|
let text = $box.find('textarea').val()
|
||||||
$box.find("input, textarea, button").prop("disabled", true);
|
$box.find('input, textarea, button').prop('disabled', true)
|
||||||
bankimport_transactionlist._action(id, "comment:" + text, function () {
|
bankimport_transactionlist._action(id, 'comment:' + text, function () {
|
||||||
$("tr[data-id=" + id + "] button").prop("disabled", false);
|
$('tr[data-id=' + id + '] button').prop('disabled', false)
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
$btn2.click(function () {
|
$btn2.click(function () {
|
||||||
bankimport_transactionlist.comment_reset_to_text(id, orig_rendered, orig_text);
|
bankimport_transactionlist.comment_reset_to_text(id, orig_rendered, orig_text)
|
||||||
});
|
})
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault()
|
||||||
},
|
},
|
||||||
|
|
||||||
typeahead_source: function () {
|
typeahead_source: function () {
|
||||||
return new Bloodhound({
|
return new Bloodhound({
|
||||||
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
|
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
|
||||||
queryTokenizer: Bloodhound.tokenizers.whitespace,
|
queryTokenizer: Bloodhound.tokenizers.whitespace,
|
||||||
remote: {
|
remote: {
|
||||||
url: $(".transaction-list").attr("data-url"),
|
url: $('.transaction-list').attr('data-url'),
|
||||||
prepare: function (query, settings) {
|
prepare: function (query, settings) {
|
||||||
settings.url = settings.url + '?query=' + encodeURIComponent(query);
|
settings.url = settings.url + '?query=' + encodeURIComponent(query)
|
||||||
return settings;
|
return settings
|
||||||
},
|
},
|
||||||
transform: function (object) {
|
transform: function (object) {
|
||||||
var results = object.results;
|
let results = object.results
|
||||||
var suggs = [];
|
let suggs = []
|
||||||
var reslen = results.length;
|
let reslen = results.length
|
||||||
for (var i = 0; i < reslen; i++) {
|
for (let i = 0; i < reslen; i++) {
|
||||||
suggs.push(results[i]);
|
suggs.push(results[i])
|
||||||
}
|
}
|
||||||
return suggs;
|
return suggs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
init: function () {
|
init: function () {
|
||||||
if ($(".transaction-list").length) {
|
if ($('.transaction-list').length) {
|
||||||
$(".transaction-list button").click(bankimport_transactionlist._btn_click);
|
$('.transaction-list button').click(bankimport_transactionlist._btn_click)
|
||||||
|
|
||||||
$(".transaction-list").on("click", ".comment-modify", bankimport_transactionlist.comment_start_edit);
|
$('.transaction-list').on('click', '.comment-modify', bankimport_transactionlist.comment_start_edit)
|
||||||
|
|
||||||
$(".transaction-list .form-control").typeahead(null, {
|
$('.transaction-list .form-control').typeahead(null, {
|
||||||
minLength: 2,
|
minLength: 2,
|
||||||
name: 'order-dataset',
|
name: 'order-dataset',
|
||||||
source: bankimport_transactionlist.typeahead_source(),
|
source: bankimport_transactionlist.typeahead_source(),
|
||||||
display: function (obj) {
|
display: function (obj) {
|
||||||
return obj.code;
|
return obj.code
|
||||||
},
|
},
|
||||||
templates: {
|
templates: {
|
||||||
suggestion: function (obj) {
|
suggestion: function (obj) {
|
||||||
return '<div>' + obj.code + ' (' + obj.total + ', ' + obj.status + ')</div>';
|
return '<div>' + obj.code + ' (' + obj.total + ', ' + obj.status + ')</div>'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).keypress(function (e) {
|
}).keypress(function (e) {
|
||||||
if (e.keyCode === 13) {
|
if (e.keyCode === 13) {
|
||||||
$(this).parent().parent().find("button[value=assign]").click();
|
$(this).parent().parent().find('button[value=assign]').click()
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($("[data-job-waiting]").length) {
|
if ($('[data-job-waiting]').length) {
|
||||||
window.setTimeout(bankimport_transactionlist.check_state, 750);
|
window.setTimeout(bankimport_transactionlist.check_state, 750)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
check_state: function () {
|
check_state: function () {
|
||||||
$.getJSON($("[data-job-waiting-url]").attr("data-job-waiting-url"), function (data) {
|
$.getJSON($('[data-job-waiting-url]').attr('data-job-waiting-url'), function (data) {
|
||||||
if (data.state == 'running' || data.state == 'pending') {
|
if (data.state == 'running' || data.state == 'pending') {
|
||||||
window.setTimeout(bankimport_transactionlist.check_state, 750);
|
window.setTimeout(bankimport_transactionlist.check_state, 750)
|
||||||
} else {
|
} else {
|
||||||
location.reload();
|
location.reload()
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
$(function () {
|
$(function () {
|
||||||
bankimport_transactionlist.init();
|
bankimport_transactionlist.init()
|
||||||
});
|
})
|
||||||
|
|||||||
@@ -1,350 +1,350 @@
|
|||||||
/*global $, paypal_client_id, paypal_loadingmessage, gettext */
|
/* global paypal_client_id, paypal_loadingmessage, gettext */
|
||||||
'use strict';
|
'use strict'
|
||||||
|
|
||||||
var pretixpaypal = {
|
var pretixpaypal = {
|
||||||
paypal: null,
|
paypal: null,
|
||||||
client_id: null,
|
client_id: null,
|
||||||
order_id: null,
|
order_id: null,
|
||||||
payer_id: null,
|
payer_id: null,
|
||||||
merchant_id: null,
|
merchant_id: null,
|
||||||
currency: null,
|
currency: null,
|
||||||
method: null,
|
method: null,
|
||||||
additional_disabled_funding: null,
|
additional_disabled_funding: null,
|
||||||
additional_enabled_funding: null,
|
additional_enabled_funding: null,
|
||||||
debug_buyer_country: null,
|
debug_buyer_country: null,
|
||||||
continue_button: null,
|
continue_button: null,
|
||||||
paypage: false,
|
paypage: false,
|
||||||
method_map: {
|
method_map: {
|
||||||
wallet: {
|
wallet: {
|
||||||
method: 'wallet',
|
method: 'wallet',
|
||||||
funding_source: 'paypal',
|
funding_source: 'paypal',
|
||||||
//disable_funding: null,
|
// disable_funding: null,
|
||||||
//enable_funding: 'paylater',
|
// enable_funding: 'paylater',
|
||||||
early_auth: true,
|
early_auth: true,
|
||||||
},
|
},
|
||||||
apm: {
|
apm: {
|
||||||
method: 'apm',
|
method: 'apm',
|
||||||
funding_source: null,
|
funding_source: null,
|
||||||
//disable_funding: null,
|
// disable_funding: null,
|
||||||
//enable_funding: null,
|
// enable_funding: null,
|
||||||
early_auth: false,
|
early_auth: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
apm_map: {
|
apm_map: {
|
||||||
paypal: gettext('PayPal'),
|
paypal: gettext('PayPal'),
|
||||||
venmo: gettext('Venmo'),
|
venmo: gettext('Venmo'),
|
||||||
applepay: gettext('Apple Pay'),
|
applepay: gettext('Apple Pay'),
|
||||||
itau: gettext('Itaú'),
|
itau: gettext('Itaú'),
|
||||||
credit: gettext('PayPal Credit'),
|
credit: gettext('PayPal Credit'),
|
||||||
card: gettext('Credit Card'),
|
card: gettext('Credit Card'),
|
||||||
paylater: gettext('PayPal Pay Later'),
|
paylater: gettext('PayPal Pay Later'),
|
||||||
ideal: gettext('iDEAL | Wero'),
|
ideal: gettext('iDEAL | Wero'),
|
||||||
sepa: gettext('SEPA Direct Debit'),
|
sepa: gettext('SEPA Direct Debit'),
|
||||||
bancontact: gettext('Bancontact'),
|
bancontact: gettext('Bancontact'),
|
||||||
giropay: gettext('giropay'),
|
giropay: gettext('giropay'),
|
||||||
sofort: gettext('SOFORT'),
|
sofort: gettext('SOFORT'),
|
||||||
eps: gettext('eps'),
|
eps: gettext('eps'),
|
||||||
mybank: gettext('MyBank'),
|
mybank: gettext('MyBank'),
|
||||||
p24: gettext('Przelewy24'),
|
p24: gettext('Przelewy24'),
|
||||||
verkkopankki: gettext('Verkkopankki'),
|
verkkopankki: gettext('Verkkopankki'),
|
||||||
payu: gettext('PayU'),
|
payu: gettext('PayU'),
|
||||||
blik: gettext('BLIK'),
|
blik: gettext('BLIK'),
|
||||||
trustly: gettext('Trustly'),
|
trustly: gettext('Trustly'),
|
||||||
zimpler: gettext('Zimpler'),
|
zimpler: gettext('Zimpler'),
|
||||||
maxima: gettext('Maxima'),
|
maxima: gettext('Maxima'),
|
||||||
oxxo: gettext('OXXO'),
|
oxxo: gettext('OXXO'),
|
||||||
boleto: gettext('Boleto'),
|
boleto: gettext('Boleto'),
|
||||||
wechatpay: gettext('WeChat Pay'),
|
wechatpay: gettext('WeChat Pay'),
|
||||||
mercadopago: gettext('Mercado Pago')
|
mercadopago: gettext('Mercado Pago')
|
||||||
},
|
},
|
||||||
readyToSubmitApproval: false,
|
readyToSubmitApproval: false,
|
||||||
|
|
||||||
load: function () {
|
load: function () {
|
||||||
if (pretixpaypal.paypal === null) {
|
if (pretixpaypal.paypal === null) {
|
||||||
pretixpaypal.client_id = $.trim($("#paypal_client_id").html());
|
pretixpaypal.client_id = $.trim($('#paypal_client_id').html())
|
||||||
pretixpaypal.merchant_id = $.trim($("#paypal_merchant_id").html());
|
pretixpaypal.merchant_id = $.trim($('#paypal_merchant_id').html())
|
||||||
pretixpaypal.debug_buyer_country = $.trim($("#paypal_buyer_country").html());
|
pretixpaypal.debug_buyer_country = $.trim($('#paypal_buyer_country').html())
|
||||||
pretixpaypal.continue_button = $('.checkout-button-row').closest("form").find(".checkout-button-row .btn-primary");
|
pretixpaypal.continue_button = $('.checkout-button-row').closest('form').find('.checkout-button-row .btn-primary')
|
||||||
pretixpaypal.continue_button.closest('div').append('<div id="paypal-button-container"></div>');
|
pretixpaypal.continue_button.closest('div').append('<div id="paypal-button-container"></div>')
|
||||||
pretixpaypal.additional_disabled_funding = $.trim($("#paypal_disable_funding").html());
|
pretixpaypal.additional_disabled_funding = $.trim($('#paypal_disable_funding').html())
|
||||||
pretixpaypal.additional_enabled_funding = $.trim($("#paypal_enable_funding").html());
|
pretixpaypal.additional_enabled_funding = $.trim($('#paypal_enable_funding').html())
|
||||||
pretixpaypal.paypage = Boolean($('#paypal-button-container').data('paypage'));
|
pretixpaypal.paypage = Boolean($('#paypal-button-container').data('paypage'))
|
||||||
pretixpaypal.order_id = $.trim($("#paypal_oid").html());
|
pretixpaypal.order_id = $.trim($('#paypal_oid').html())
|
||||||
pretixpaypal.currency = $("body").attr("data-currency");
|
pretixpaypal.currency = $('body').attr('data-currency')
|
||||||
pretixpaypal.locale = this.guessLocale();
|
pretixpaypal.locale = this.guessLocale()
|
||||||
}
|
}
|
||||||
|
|
||||||
$("input[name=payment][value^='paypal']").change(function () {
|
$('input[name=payment][value^=\'paypal\']').change(function () {
|
||||||
if (pretixpaypal.paypal !== null) {
|
if (pretixpaypal.paypal !== null) {
|
||||||
pretixpaypal.renderButton($(this).val());
|
pretixpaypal.renderButton($(this).val())
|
||||||
} else {
|
} else {
|
||||||
pretixpaypal.continue_button.prop("disabled", true);
|
pretixpaypal.continue_button.prop('disabled', true)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
$("input[name=payment]").not("[value^='paypal']").change(function () {
|
$('input[name=payment]').not('[value^=\'paypal\']').change(function () {
|
||||||
pretixpaypal.restore();
|
pretixpaypal.restore()
|
||||||
});
|
})
|
||||||
|
|
||||||
// If paypal is pre-selected, we must disable the continue button and handle it after SDK is loaded
|
// If paypal is pre-selected, we must disable the continue button and handle it after SDK is loaded
|
||||||
if ($("input[name=payment][value^='paypal']").is(':checked')) {
|
if ($('input[name=payment][value^=\'paypal\']').is(':checked')) {
|
||||||
pretixpaypal.continue_button.prop("disabled", true);
|
pretixpaypal.continue_button.prop('disabled', true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// We are setting the cogwheel already here, as the renderAPM() method might take some time to get loaded.
|
// We are setting the cogwheel already here, as the renderAPM() method might take some time to get loaded.
|
||||||
const apmtextselector = $("input[name=payment][value=paypal_apm]").closest("label").find(".accordion-label-text");
|
const apmtextselector = $('input[name=payment][value=paypal_apm]').closest('label').find('.accordion-label-text')
|
||||||
apmtextselector.append(' <span aria-hidden="true" class="fa fa-cog fa-spin"></span>');
|
apmtextselector.append(' <span aria-hidden="true" class="fa fa-cog fa-spin"></span>')
|
||||||
|
|
||||||
let sdk_url = 'https://www.paypal.com/sdk/js' +
|
let sdk_url = 'https://www.paypal.com/sdk/js'
|
||||||
'?client-id=' + pretixpaypal.client_id +
|
+ '?client-id=' + pretixpaypal.client_id
|
||||||
'&components=buttons,funding-eligibility' +
|
+ '&components=buttons,funding-eligibility'
|
||||||
'¤cy=' + pretixpaypal.currency;
|
+ '¤cy=' + pretixpaypal.currency
|
||||||
|
|
||||||
if (pretixpaypal.locale) {
|
if (pretixpaypal.locale) {
|
||||||
sdk_url += '&locale=' + pretixpaypal.locale;
|
sdk_url += '&locale=' + pretixpaypal.locale
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pretixpaypal.merchant_id) {
|
if (pretixpaypal.merchant_id) {
|
||||||
sdk_url += '&merchant-id=' + pretixpaypal.merchant_id;
|
sdk_url += '&merchant-id=' + pretixpaypal.merchant_id
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pretixpaypal.additional_disabled_funding) {
|
if (pretixpaypal.additional_disabled_funding) {
|
||||||
sdk_url += '&disable-funding=' + [pretixpaypal.additional_disabled_funding].filter(Boolean).join(',');
|
sdk_url += '&disable-funding=' + [pretixpaypal.additional_disabled_funding].filter(Boolean).join(',')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pretixpaypal.additional_enabled_funding) {
|
if (pretixpaypal.additional_enabled_funding) {
|
||||||
sdk_url += '&enable-funding=' + [pretixpaypal.additional_enabled_funding].filter(Boolean).join(',');
|
sdk_url += '&enable-funding=' + [pretixpaypal.additional_enabled_funding].filter(Boolean).join(',')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pretixpaypal.debug_buyer_country) {
|
if (pretixpaypal.debug_buyer_country) {
|
||||||
sdk_url += '&buyer-country=' + pretixpaypal.debug_buyer_country;
|
sdk_url += '&buyer-country=' + pretixpaypal.debug_buyer_country
|
||||||
}
|
}
|
||||||
|
|
||||||
let ppscript = document.createElement('script');
|
let ppscript = document.createElement('script')
|
||||||
let ready = false;
|
let ready = false
|
||||||
let head = document.getElementsByTagName("head")[0];
|
let head = document.getElementsByTagName('head')[0]
|
||||||
ppscript.setAttribute('src', sdk_url);
|
ppscript.setAttribute('src', sdk_url)
|
||||||
ppscript.setAttribute('data-csp-nonce', $.trim($("#csp_nonce").html()));
|
ppscript.setAttribute('data-csp-nonce', $.trim($('#csp_nonce').html()))
|
||||||
ppscript.setAttribute('data-page-type', 'checkout');
|
ppscript.setAttribute('data-page-type', 'checkout')
|
||||||
ppscript.setAttribute('data-partner-attribution-id', 'ramiioGmbH_Cart_PPCP');
|
ppscript.setAttribute('data-partner-attribution-id', 'ramiioGmbH_Cart_PPCP')
|
||||||
document.head.appendChild(ppscript);
|
document.head.appendChild(ppscript)
|
||||||
|
|
||||||
ppscript.onload = ppscript.onreadystatechange = function () {
|
ppscript.onload = ppscript.onreadystatechange = function () {
|
||||||
if (!ready && (!this.readyState || this.readyState === "loaded" || this.readyState === "complete")) {
|
if (!ready && (!this.readyState || this.readyState === 'loaded' || this.readyState === 'complete')) {
|
||||||
ready = true;
|
ready = true
|
||||||
|
|
||||||
pretixpaypal.paypal = paypal;
|
pretixpaypal.paypal = paypal
|
||||||
|
|
||||||
// Handle memory leak in IE
|
// Handle memory leak in IE
|
||||||
ppscript.onload = ppscript.onreadystatechange = null;
|
ppscript.onload = ppscript.onreadystatechange = null
|
||||||
if (head && ppscript.parentNode) {
|
if (head && ppscript.parentNode) {
|
||||||
head.removeChild(ppscript);
|
head.removeChild(ppscript)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
document.addEventListener("visibilitychange", this.onApproveSubmit);
|
document.addEventListener('visibilitychange', this.onApproveSubmit)
|
||||||
},
|
},
|
||||||
|
|
||||||
ready: function () {
|
ready: function () {
|
||||||
if ($("input[name=payment][value=paypal_apm]").length > 0) {
|
if ($('input[name=payment][value=paypal_apm]').length > 0) {
|
||||||
pretixpaypal.renderAPMs();
|
pretixpaypal.renderAPMs()
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($("input[name=payment][value^='paypal']").is(':checked')) {
|
if ($('input[name=payment][value^=\'paypal\']').is(':checked')) {
|
||||||
pretixpaypal.renderButton($("input[name=payment][value^='paypal']:checked").val());
|
pretixpaypal.renderButton($('input[name=payment][value^=\'paypal\']:checked').val())
|
||||||
} else if ($(".payment-redo-form").length) {
|
} else if ($('.payment-redo-form').length) {
|
||||||
pretixpaypal.renderButton($("input[name=payment][value^='paypal']").val());
|
pretixpaypal.renderButton($('input[name=payment][value^=\'paypal\']').val())
|
||||||
} else if ($('#paypal-button-container').data('paypage')) {
|
} else if ($('#paypal-button-container').data('paypage')) {
|
||||||
pretixpaypal.renderButton('paypal_apm');
|
pretixpaypal.renderButton('paypal_apm')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
restore: function () {
|
restore: function () {
|
||||||
// if PayPal has not been initialized, there shouldn't be anything to cleanup
|
// if PayPal has not been initialized, there shouldn't be anything to cleanup
|
||||||
if (pretixpaypal.paypal !== null) {
|
if (pretixpaypal.paypal !== null) {
|
||||||
$('#paypal-button-container').empty()
|
$('#paypal-button-container').empty()
|
||||||
pretixpaypal.continue_button.text(gettext('Continue'));
|
pretixpaypal.continue_button.text(gettext('Continue'))
|
||||||
pretixpaypal.continue_button.show();
|
pretixpaypal.continue_button.show()
|
||||||
}
|
}
|
||||||
pretixpaypal.continue_button.prop("disabled", false);
|
pretixpaypal.continue_button.prop('disabled', false)
|
||||||
},
|
},
|
||||||
|
|
||||||
renderButton: function (method) {
|
renderButton: function (method) {
|
||||||
if (method === 'paypal') {
|
if (method === 'paypal') {
|
||||||
method = "wallet"
|
method = 'wallet'
|
||||||
} else {
|
} else {
|
||||||
method = method.split('paypal_').at(-1)
|
method = method.split('paypal_').at(-1)
|
||||||
}
|
}
|
||||||
pretixpaypal.method = pretixpaypal.method_map[method];
|
pretixpaypal.method = pretixpaypal.method_map[method]
|
||||||
|
|
||||||
if (pretixpaypal.method.method === 'apm' && !pretixpaypal.paypage) {
|
if (pretixpaypal.method.method === 'apm' && !pretixpaypal.paypage) {
|
||||||
pretixpaypal.restore();
|
pretixpaypal.restore()
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
$('#paypal-button-container').empty()
|
$('#paypal-button-container').empty()
|
||||||
$('#paypal-card-container').empty()
|
$('#paypal-card-container').empty()
|
||||||
|
|
||||||
let button = pretixpaypal.paypal.Buttons({
|
let button = pretixpaypal.paypal.Buttons({
|
||||||
fundingSource: pretixpaypal.method.funding_source,
|
fundingSource: pretixpaypal.method.funding_source,
|
||||||
style: {
|
style: {
|
||||||
layout: pretixpaypal.method.early_auth ? 'horizontal' : 'vertical',
|
layout: pretixpaypal.method.early_auth ? 'horizontal' : 'vertical',
|
||||||
//color: 'white',
|
// color: 'white',
|
||||||
shape: 'rect',
|
shape: 'rect',
|
||||||
label: 'pay',
|
label: 'pay',
|
||||||
tagline: false
|
tagline: false
|
||||||
},
|
},
|
||||||
createOrder: function (data, actions) {
|
createOrder: function (data, actions) {
|
||||||
if (pretixpaypal.order_id) {
|
if (pretixpaypal.order_id) {
|
||||||
return pretixpaypal.order_id;
|
return pretixpaypal.order_id
|
||||||
}
|
}
|
||||||
|
|
||||||
// On the paypal:pay view, we already pregenerated the OID.
|
// On the paypal:pay view, we already pregenerated the OID.
|
||||||
// Since this view is also only used for APMs, we only need the XHR-calls for the Smart Payment Buttons.
|
// Since this view is also only used for APMs, we only need the XHR-calls for the Smart Payment Buttons.
|
||||||
if (pretixpaypal.paypage) {
|
if (pretixpaypal.paypage) {
|
||||||
return $("#payment_paypal_" + pretixpaypal.method.method + "_oid");
|
return $('#payment_paypal_' + pretixpaypal.method.method + '_oid')
|
||||||
} else {
|
} else {
|
||||||
var xhrurl = $("#payment_paypal_" + pretixpaypal.method.method + "_xhr").val();
|
var xhrurl = $('#payment_paypal_' + pretixpaypal.method.method + '_xhr').val()
|
||||||
}
|
}
|
||||||
|
|
||||||
return fetch(xhrurl, {
|
return fetch(xhrurl, {
|
||||||
method: 'POST'
|
method: 'POST'
|
||||||
}).then(function (res) {
|
}).then(function (res) {
|
||||||
return res.json();
|
return res.json()
|
||||||
}).then(function (data) {
|
}).then(function (data) {
|
||||||
if ('id' in data) {
|
if ('id' in data) {
|
||||||
return data.id;
|
return data.id
|
||||||
} else {
|
} else {
|
||||||
// Refreshing the page to surface the request-error message
|
// Refreshing the page to surface the request-error message
|
||||||
location.reload();
|
location.reload()
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
onApprove: function (data, actions) {
|
onApprove: function (data, actions) {
|
||||||
waitingDialog.show(gettext("Confirming your payment …"));
|
waitingDialog.show(gettext('Confirming your payment …'))
|
||||||
pretixpaypal.order_id = data.orderID;
|
pretixpaypal.order_id = data.orderID
|
||||||
pretixpaypal.payer_id = data.payerID;
|
pretixpaypal.payer_id = data.payerID
|
||||||
|
|
||||||
let method = pretixpaypal.paypage ? "wallet" : pretixpaypal.method.method;
|
let method = pretixpaypal.paypage ? 'wallet' : pretixpaypal.method.method
|
||||||
let selectorstub = "#payment_paypal_" + method;
|
let selectorstub = '#payment_paypal_' + method
|
||||||
// Insert the tokens into the form, so it gets submitted to the server
|
// Insert the tokens into the form, so it gets submitted to the server
|
||||||
$(selectorstub + "_oid").val(pretixpaypal.order_id);
|
$(selectorstub + '_oid').val(pretixpaypal.order_id)
|
||||||
$(selectorstub + "_payer").val(pretixpaypal.payer_id);
|
$(selectorstub + '_payer').val(pretixpaypal.payer_id)
|
||||||
|
|
||||||
// We are moving the submission to a separate function, which is also an EventListener, since
|
// We are moving the submission to a separate function, which is also an EventListener, since
|
||||||
// SFSafariView refuses to submit a form that is not visible. Unfortunately, that is exactly the case
|
// SFSafariView refuses to submit a form that is not visible. Unfortunately, that is exactly the case
|
||||||
// when the ticket shop is used on iOS within an SFSafariView and the PayPal payment popup has not
|
// when the ticket shop is used on iOS within an SFSafariView and the PayPal payment popup has not
|
||||||
// closed itself quickly enough.
|
// closed itself quickly enough.
|
||||||
pretixpaypal.readyToSubmitApproval = true;
|
pretixpaypal.readyToSubmitApproval = true
|
||||||
pretixpaypal.onApproveSubmit();
|
pretixpaypal.onApproveSubmit()
|
||||||
|
|
||||||
// billingToken: null
|
// billingToken: null
|
||||||
// facilitatorAccessToken: "A21AAL_fEu0gDD-sIXyOy65a6MjgSJJrhmxuPcxxUGnL5gW2DzTxiiAksfoC4x8hD-BjeY1LsFVKl7ceuO7UR1a9pQr8Q_AVw"
|
// facilitatorAccessToken: "A21AAL_fEu0gDD-sIXyOy65a6MjgSJJrhmxuPcxxUGnL5gW2DzTxiiAksfoC4x8hD-BjeY1LsFVKl7ceuO7UR1a9pQr8Q_AVw"
|
||||||
// orderID: "7RF70259NY7589848"
|
// orderID: "7RF70259NY7589848"
|
||||||
// payerID: "8M3BU92Z97VXA"
|
// payerID: "8M3BU92Z97VXA"
|
||||||
// paymentID: null
|
// paymentID: null
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
|
|
||||||
if (button.isEligible()) {
|
if (button.isEligible()) {
|
||||||
button.render('#paypal-button-container');
|
button.render('#paypal-button-container')
|
||||||
pretixpaypal.continue_button.hide();
|
pretixpaypal.continue_button.hide()
|
||||||
} else {
|
} else {
|
||||||
pretixpaypal.continue_button.text(gettext('Payment method unavailable'));
|
pretixpaypal.continue_button.text(gettext('Payment method unavailable'))
|
||||||
pretixpaypal.continue_button.show();
|
pretixpaypal.continue_button.show()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onApproveSubmit: function() {
|
onApproveSubmit: function () {
|
||||||
if (document.visibilityState === "visible" && pretixpaypal.readyToSubmitApproval === true) {
|
if (document.visibilityState === 'visible' && pretixpaypal.readyToSubmitApproval === true) {
|
||||||
let method = pretixpaypal.paypage ? "wallet" : pretixpaypal.method.method;
|
let method = pretixpaypal.paypage ? 'wallet' : pretixpaypal.method.method
|
||||||
let selectorstub = "#payment_paypal_" + method;
|
let selectorstub = '#payment_paypal_' + method
|
||||||
var $form = $(selectorstub + "_oid").closest("form");
|
let $form = $(selectorstub + '_oid').closest('form')
|
||||||
|
|
||||||
$form.get(0).submit();
|
$form.get(0).submit()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
renderAPMs: function () {
|
renderAPMs: function () {
|
||||||
pretixpaypal.restore();
|
pretixpaypal.restore()
|
||||||
let inputselector = $("input[name=payment][value=paypal_apm]");
|
let inputselector = $('input[name=payment][value=paypal_apm]')
|
||||||
let textselector = inputselector.closest("label").find('.accordion-label-text');
|
let textselector = inputselector.closest('label').find('.accordion-label-text')
|
||||||
let eligibles = [];
|
let eligibles = []
|
||||||
|
|
||||||
pretixpaypal.paypal.getFundingSources().forEach(function (fundingSource) {
|
pretixpaypal.paypal.getFundingSources().forEach(function (fundingSource) {
|
||||||
// Let's always skip PayPal, since it's always a dedicated funding source
|
// Let's always skip PayPal, since it's always a dedicated funding source
|
||||||
if (fundingSource === 'paypal') {
|
if (fundingSource === 'paypal') {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// This could also be paypal.Marks() - but they only expose images instead of cleartext...
|
// This could also be paypal.Marks() - but they only expose images instead of cleartext...
|
||||||
let button = pretixpaypal.paypal.Buttons({
|
let button = pretixpaypal.paypal.Buttons({
|
||||||
fundingSource: fundingSource
|
fundingSource: fundingSource
|
||||||
});
|
})
|
||||||
|
|
||||||
if (button.isEligible()) {
|
if (button.isEligible()) {
|
||||||
eligibles.push(gettext(pretixpaypal.apm_map[fundingSource] || fundingSource));
|
eligibles.push(gettext(pretixpaypal.apm_map[fundingSource] || fundingSource))
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
inputselector.attr('title', eligibles.join(', '));
|
inputselector.attr('title', eligibles.join(', '))
|
||||||
textselector.fadeOut(300, function () {
|
textselector.fadeOut(300, function () {
|
||||||
textselector.text(eligibles.join(', '));
|
textselector.text(eligibles.join(', '))
|
||||||
textselector.fadeIn(300);
|
textselector.fadeIn(300)
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
guessLocale: function() {
|
guessLocale: function () {
|
||||||
// This is a horrible hackjob and does not at all take into consideration the actual locale.
|
// This is a horrible hackjob and does not at all take into consideration the actual locale.
|
||||||
// Instead, we only look at the language that the shop is currently being displayed in and make
|
// Instead, we only look at the language that the shop is currently being displayed in and make
|
||||||
// that into a locale.
|
// that into a locale.
|
||||||
let allowed_locales = [
|
let allowed_locales = [
|
||||||
'en_US',
|
'en_US',
|
||||||
'ar_DZ',
|
'ar_DZ',
|
||||||
'fr_FR',
|
'fr_FR',
|
||||||
'es_ES',
|
'es_ES',
|
||||||
'zh_CN',
|
'zh_CN',
|
||||||
'de_DE',
|
'de_DE',
|
||||||
'nl_NL',
|
'nl_NL',
|
||||||
'pt_PT',
|
'pt_PT',
|
||||||
'cs_CZ',
|
'cs_CZ',
|
||||||
'da_DK',
|
'da_DK',
|
||||||
'fi_FI',
|
'fi_FI',
|
||||||
'el_GR',
|
'el_GR',
|
||||||
'hu_HU',
|
'hu_HU',
|
||||||
'id_ID',
|
'id_ID',
|
||||||
'he_IL',
|
'he_IL',
|
||||||
'it_IT',
|
'it_IT',
|
||||||
'ja_JP',
|
'ja_JP',
|
||||||
'ru_RU',
|
'ru_RU',
|
||||||
'no_NO',
|
'no_NO',
|
||||||
'pl_PL',
|
'pl_PL',
|
||||||
'sk_SK',
|
'sk_SK',
|
||||||
'sv_SE',
|
'sv_SE',
|
||||||
'th_TH',
|
'th_TH',
|
||||||
'tr_TR',
|
'tr_TR',
|
||||||
]
|
]
|
||||||
let lang = $("body").attr("data-locale").split('-')[0];
|
let lang = $('body').attr('data-locale').split('-')[0]
|
||||||
return allowed_locales.find(element => element.startsWith(lang));
|
return allowed_locales.find(element => element.startsWith(lang))
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
$(function () {
|
$(function () {
|
||||||
// This script is always loaded if paypal is enabled as a payment method, regardless of
|
// This script is always loaded if paypal is enabled as a payment method, regardless of
|
||||||
// whether it is available (it could e.g. be hidden or limited to certain countries).
|
// whether it is available (it could e.g. be hidden or limited to certain countries).
|
||||||
// We do not want to unnecessarily load the sdk.
|
// We do not want to unnecessarily load the sdk.
|
||||||
// If no paypal/paypal_apm payment option is present and we are not on
|
// If no paypal/paypal_apm payment option is present and we are not on
|
||||||
// the (APM) PayView, then we do not need the SDK.
|
// the (APM) PayView, then we do not need the SDK.
|
||||||
if (!$("input[name=payment][value^='paypal']").length && !$('#paypal-button-container').data('paypage')) {
|
if (!$('input[name=payment][value^=\'paypal\']').length && !$('#paypal-button-container').data('paypage')) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pretixpaypal.load();
|
pretixpaypal.load();
|
||||||
|
|
||||||
(async() => {
|
(async () => {
|
||||||
while(!pretixpaypal.paypal)
|
while (!pretixpaypal.paypal)
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||||
pretixpaypal.ready();
|
pretixpaypal.ready()
|
||||||
})();
|
})()
|
||||||
});
|
})
|
||||||
|
|||||||
@@ -55,6 +55,8 @@ from django_countries.fields import Country
|
|||||||
from reportlab.lib import colors
|
from reportlab.lib import colors
|
||||||
from reportlab.lib.enums import TA_CENTER
|
from reportlab.lib.enums import TA_CENTER
|
||||||
from reportlab.lib.units import mm
|
from reportlab.lib.units import mm
|
||||||
|
from reportlab.lib.utils import simpleSplit
|
||||||
|
from reportlab.pdfbase.pdfmetrics import stringWidth
|
||||||
from reportlab.pdfgen.canvas import Canvas
|
from reportlab.pdfgen.canvas import Canvas
|
||||||
from reportlab.platypus import PageBreak, Spacer, Table, TableStyle
|
from reportlab.platypus import PageBreak, Spacer, Table, TableStyle
|
||||||
|
|
||||||
@@ -226,10 +228,20 @@ class ReportlabExportMixin:
|
|||||||
def page_header(self, canvas, doc):
|
def page_header(self, canvas, doc):
|
||||||
from reportlab.lib.units import mm
|
from reportlab.lib.units import mm
|
||||||
|
|
||||||
canvas.setFont('OpenSans', 10)
|
font_name = 'OpenSans'
|
||||||
canvas.drawString(doc.leftMargin, self.pagesize[1] - 15 * mm, self.get_left_header_string())
|
font_size = 10
|
||||||
|
left_string = self.get_left_header_string()
|
||||||
|
right_string = self.get_right_header_string()
|
||||||
|
right_width = stringWidth(right_string, font_name, font_size)
|
||||||
|
max_left_width = self.pagesize[0] - doc.leftMargin - doc.rightMargin - right_width - 5 * mm
|
||||||
|
left_string_lines = simpleSplit(left_string, font_name, font_size, max_left_width)
|
||||||
|
if len(left_string_lines) > 1:
|
||||||
|
left_string = left_string_lines[0] + " …"
|
||||||
|
|
||||||
|
canvas.setFont(font_name, font_size)
|
||||||
|
canvas.drawString(doc.leftMargin, self.pagesize[1] - 15 * mm, left_string)
|
||||||
canvas.drawRightString(self.pagesize[0] - doc.rightMargin, self.pagesize[1] - 15 * mm,
|
canvas.drawRightString(self.pagesize[0] - doc.rightMargin, self.pagesize[1] - 15 * mm,
|
||||||
self.get_right_header_string())
|
right_string)
|
||||||
canvas.setStrokeColorRGB(0, 0, 0)
|
canvas.setStrokeColorRGB(0, 0, 0)
|
||||||
canvas.line(doc.leftMargin, self.pagesize[1] - 17 * mm,
|
canvas.line(doc.leftMargin, self.pagesize[1] - 17 * mm,
|
||||||
self.pagesize[0] - doc.rightMargin, self.pagesize[1] - 17 * mm)
|
self.pagesize[0] - doc.rightMargin, self.pagesize[1] - 17 * mm)
|
||||||
|
|||||||
@@ -56,18 +56,11 @@ from pretix.base.services.placeholders import FormPlaceholderMixin # noqa
|
|||||||
class BaseMailForm(FormPlaceholderMixin, forms.Form):
|
class BaseMailForm(FormPlaceholderMixin, forms.Form):
|
||||||
subject = forms.CharField(label=_("Subject"))
|
subject = forms.CharField(label=_("Subject"))
|
||||||
message = forms.CharField(label=_("Message"))
|
message = forms.CharField(label=_("Message"))
|
||||||
attachment = CachedFileField(
|
|
||||||
label=_("Attachment"),
|
|
||||||
required=False,
|
|
||||||
ext_whitelist=settings.FILE_UPLOAD_EXTENSIONS_EMAIL_ATTACHMENT,
|
|
||||||
help_text=_('Sending an attachment increases the chance of your email not arriving or being sorted into spam folders. We recommend only using PDFs '
|
|
||||||
'of no more than 2 MB in size.'),
|
|
||||||
max_size=settings.FILE_UPLOAD_MAX_SIZE_EMAIL_ATTACHMENT
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
event = self.event = kwargs.pop('event')
|
event = self.event = kwargs.pop('event')
|
||||||
context_parameters = kwargs.pop('context_parameters')
|
context_parameters = kwargs.pop('context_parameters')
|
||||||
|
request = kwargs.pop('request')
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields['subject'] = I18nFormField(
|
self.fields['subject'] = I18nFormField(
|
||||||
label=_('Subject'),
|
label=_('Subject'),
|
||||||
@@ -79,6 +72,16 @@ class BaseMailForm(FormPlaceholderMixin, forms.Form):
|
|||||||
widget=I18nMarkdownTextarea, required=True,
|
widget=I18nMarkdownTextarea, required=True,
|
||||||
locales=event.settings.get('locales'),
|
locales=event.settings.get('locales'),
|
||||||
)
|
)
|
||||||
|
self.fields['attachment'] = CachedFileField(
|
||||||
|
label=_("Attachment"),
|
||||||
|
required=False,
|
||||||
|
ext_whitelist=settings.FILE_UPLOAD_EXTENSIONS_EMAIL_ATTACHMENT,
|
||||||
|
help_text=_(
|
||||||
|
'Sending an attachment increases the chance of your email not arriving or being sorted into spam folders. We recommend only using PDFs '
|
||||||
|
'of no more than 2 MB in size.'),
|
||||||
|
max_size=settings.FILE_UPLOAD_MAX_SIZE_EMAIL_ATTACHMENT,
|
||||||
|
request=request,
|
||||||
|
)
|
||||||
self._set_field_placeholders('subject', context_parameters, rich=False)
|
self._set_field_placeholders('subject', context_parameters, rich=False)
|
||||||
self._set_field_placeholders('message', context_parameters, rich=True)
|
self._set_field_placeholders('message', context_parameters, rich=True)
|
||||||
|
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ class BaseSenderView(EventPermissionRequiredMixin, FormView):
|
|||||||
kwargs = super().get_form_kwargs()
|
kwargs = super().get_form_kwargs()
|
||||||
kwargs['event'] = self.request.event
|
kwargs['event'] = self.request.event
|
||||||
kwargs['context_parameters'] = self.context_parameters
|
kwargs['context_parameters'] = self.context_parameters
|
||||||
|
kwargs['request'] = self.request
|
||||||
if 'from_log' in self.request.GET:
|
if 'from_log' in self.request.GET:
|
||||||
try:
|
try:
|
||||||
from_log_id = self.request.GET.get('from_log')
|
from_log_id = self.request.GET.get('from_log')
|
||||||
@@ -354,9 +355,9 @@ class OrderSendView(BaseSenderView):
|
|||||||
statusq |= Q(status=Order.STATUS_PENDING, require_approval=False, valid_if_pending=True)
|
statusq |= Q(status=Order.STATUS_PENDING, require_approval=False, valid_if_pending=True)
|
||||||
orders = qs.filter(statusq)
|
orders = qs.filter(statusq)
|
||||||
|
|
||||||
opq = OrderPosition.objects.filter(
|
opq = OrderPosition.objects.with_scopes_disabled().filter(
|
||||||
Q(item_id__in=[i.pk for i in form.cleaned_data.get('items')]) | Q(Exists(
|
Q(item_id__in=[i.pk for i in form.cleaned_data.get('items')]) | Q(Exists(
|
||||||
OrderPosition.objects.filter(
|
OrderPosition.objects.with_scopes_disabled().filter(
|
||||||
addon_to_id=OuterRef('pk'),
|
addon_to_id=OuterRef('pk'),
|
||||||
item_id__in=[i.pk for i in form.cleaned_data.get('items')]
|
item_id__in=[i.pk for i in form.cleaned_data.get('items')]
|
||||||
)
|
)
|
||||||
@@ -366,36 +367,43 @@ class OrderSendView(BaseSenderView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if form.cleaned_data.get('filter_checkins'):
|
if form.cleaned_data.get('filter_checkins'):
|
||||||
ql = []
|
ci_filter = Q(pk__in=[]) # return nothing
|
||||||
|
|
||||||
if form.cleaned_data.get('not_checked_in'):
|
if form.cleaned_data.get('not_checked_in'):
|
||||||
|
consider_tickets_used_lists = list(self.request.event.checkin_lists.filter(consider_tickets_used=True).values_list("id", flat=True))
|
||||||
|
|
||||||
opq = opq.alias(
|
opq = opq.alias(
|
||||||
any_checkins=Exists(
|
any_checkins=Exists(
|
||||||
Checkin.all.filter(
|
Checkin.objects.with_scopes_disabled().filter(
|
||||||
Q(position_id=OuterRef('pk')) | Q(position__addon_to_id=OuterRef('pk')),
|
position_id=OuterRef('pk'),
|
||||||
successful=True,
|
list_id__in=consider_tickets_used_lists,
|
||||||
list__consider_tickets_used=True,
|
)
|
||||||
|
) | Exists(
|
||||||
|
Checkin.objects.with_scopes_disabled().filter(
|
||||||
|
position__addon_to_id=OuterRef('pk'),
|
||||||
|
list_id__in=consider_tickets_used_lists,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
ql.append(Q(any_checkins=False))
|
ci_filter |= Q(any_checkins=False)
|
||||||
|
|
||||||
if form.cleaned_data.get('checkin_lists'):
|
if form.cleaned_data.get('checkin_lists'):
|
||||||
opq = opq.alias(
|
opq = opq.alias(
|
||||||
matching_checkins=Exists(
|
matching_checkins=Exists(
|
||||||
Checkin.all.filter(
|
Checkin.objects.with_scopes_disabled().filter(
|
||||||
Q(position_id=OuterRef('pk')) | Q(position__addon_to_id=OuterRef('pk')),
|
position_id=OuterRef('pk'),
|
||||||
|
list_id__in=[i.pk for i in form.cleaned_data.get('checkin_lists', [])],
|
||||||
|
)
|
||||||
|
) | Exists(
|
||||||
|
Checkin.objects.with_scopes_disabled().filter(
|
||||||
|
position__addon_to_id=OuterRef('pk'),
|
||||||
list_id__in=[i.pk for i in form.cleaned_data.get('checkin_lists', [])],
|
list_id__in=[i.pk for i in form.cleaned_data.get('checkin_lists', [])],
|
||||||
successful=True
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
ql.append(Q(matching_checkins=True))
|
ci_filter |= Q(matching_checkins=True)
|
||||||
if len(ql) == 2:
|
|
||||||
opq = opq.filter(ql[0] | ql[1])
|
opq = opq.filter(ci_filter)
|
||||||
elif ql:
|
|
||||||
opq = opq.filter(ql[0])
|
|
||||||
else:
|
|
||||||
opq = opq.none()
|
|
||||||
|
|
||||||
if form.cleaned_data.get('subevent'):
|
if form.cleaned_data.get('subevent'):
|
||||||
opq = opq.filter(subevent=form.cleaned_data.get('subevent'))
|
opq = opq.filter(subevent=form.cleaned_data.get('subevent'))
|
||||||
|
|||||||
@@ -1,81 +1,81 @@
|
|||||||
/*globals $, Morris, gettext, django*/
|
/* globals Morris, django */
|
||||||
function gettext(msgid) {
|
function gettext (msgid) {
|
||||||
if (typeof django !== 'undefined' && typeof django.gettext !== 'undefined') {
|
if (typeof django !== 'undefined' && typeof django.gettext !== 'undefined') {
|
||||||
return django.gettext(msgid);
|
return django.gettext(msgid)
|
||||||
}
|
}
|
||||||
return msgid;
|
return msgid
|
||||||
}
|
}
|
||||||
$(function () {
|
$(function () {
|
||||||
$(".chart").css("height", "250px");
|
$('.chart').css('height', '250px')
|
||||||
new Morris.Area({
|
new Morris.Area({
|
||||||
element: 'obd_chart',
|
element: 'obd_chart',
|
||||||
data: JSON.parse($("#obd-data").html()),
|
data: JSON.parse($('#obd-data').html()),
|
||||||
xkey: 'date',
|
xkey: 'date',
|
||||||
ykeys: ['ordered', 'paid'],
|
ykeys: ['ordered', 'paid'],
|
||||||
labels: [gettext('Placed orders'), gettext('Paid orders')],
|
labels: [gettext('Placed orders'), gettext('Paid orders')],
|
||||||
lineColors: ['#3b1c4a', '#50a167'],
|
lineColors: ['#3b1c4a', '#50a167'],
|
||||||
smooth: false,
|
smooth: false,
|
||||||
resize: true,
|
resize: true,
|
||||||
fillOpacity: 0.3,
|
fillOpacity: 0.3,
|
||||||
behaveLikeLine: true
|
behaveLikeLine: true
|
||||||
});
|
})
|
||||||
new Morris.Area({
|
new Morris.Area({
|
||||||
element: 'abd_chart',
|
element: 'abd_chart',
|
||||||
data: JSON.parse($("#abd-data").html()),
|
data: JSON.parse($('#abd-data').html()),
|
||||||
xkey: 'date',
|
xkey: 'date',
|
||||||
ykeys: ['ordered', 'paid'],
|
ykeys: ['ordered', 'paid'],
|
||||||
labels: [gettext('Attendees (ordered)'), gettext('Attendees (paid)')],
|
labels: [gettext('Attendees (ordered)'), gettext('Attendees (paid)')],
|
||||||
lineColors: ['#3b1c4a', '#50a167'],
|
lineColors: ['#3b1c4a', '#50a167'],
|
||||||
smooth: false,
|
smooth: false,
|
||||||
resize: true,
|
resize: true,
|
||||||
fillOpacity: 0.3,
|
fillOpacity: 0.3,
|
||||||
behaveLikeLine: true
|
behaveLikeLine: true
|
||||||
});
|
})
|
||||||
new Morris.Area({
|
new Morris.Area({
|
||||||
element: 'abt_chart',
|
element: 'abt_chart',
|
||||||
data: JSON.parse($("#abt-data").html()),
|
data: JSON.parse($('#abt-data').html()),
|
||||||
xkey: 'date',
|
xkey: 'date',
|
||||||
ykeys: ['ordered', 'paid'],
|
ykeys: ['ordered', 'paid'],
|
||||||
labels: [gettext('Attendees (ordered)'), gettext('Attendees (paid)')],
|
labels: [gettext('Attendees (ordered)'), gettext('Attendees (paid)')],
|
||||||
lineColors: ['#3b1c4a', '#50a167'],
|
lineColors: ['#3b1c4a', '#50a167'],
|
||||||
smooth: false,
|
smooth: false,
|
||||||
resize: true,
|
resize: true,
|
||||||
fillOpacity: 0.3,
|
fillOpacity: 0.3,
|
||||||
behaveLikeLine: true
|
behaveLikeLine: true
|
||||||
});
|
})
|
||||||
new Morris.Area({
|
new Morris.Area({
|
||||||
element: 'rev_chart',
|
element: 'rev_chart',
|
||||||
data: JSON.parse($("#rev-data").html()),
|
data: JSON.parse($('#rev-data').html()),
|
||||||
xkey: 'date',
|
xkey: 'date',
|
||||||
ykeys: ['revenue'],
|
ykeys: ['revenue'],
|
||||||
labels: [gettext('Total revenue')],
|
labels: [gettext('Total revenue')],
|
||||||
smooth: false,
|
smooth: false,
|
||||||
resize: true,
|
resize: true,
|
||||||
lineColors: ['#3b1c4a'],
|
lineColors: ['#3b1c4a'],
|
||||||
fillOpacity: 0.3,
|
fillOpacity: 0.3,
|
||||||
preUnits: $.trim($("#currency").html()) + ' '
|
preUnits: $.trim($('#currency').html()) + ' '
|
||||||
});
|
})
|
||||||
new Morris.Bar({
|
new Morris.Bar({
|
||||||
element: 'obp_chart',
|
element: 'obp_chart',
|
||||||
data: JSON.parse($("#obp-data").html()),
|
data: JSON.parse($('#obp-data').html()),
|
||||||
xkey: 'item_short',
|
xkey: 'item_short',
|
||||||
ykeys: ['ordered', 'paid'],
|
ykeys: ['ordered', 'paid'],
|
||||||
labels: [gettext('Placed orders'), gettext('Paid orders')],
|
labels: [gettext('Placed orders'), gettext('Paid orders')],
|
||||||
barColors: ['#3b1c4a', '#50a167'],
|
barColors: ['#3b1c4a', '#50a167'],
|
||||||
hoverCallback: function (index, options, content, row) {
|
hoverCallback: function (index, options, content, row) {
|
||||||
console.log(content);
|
console.log(content)
|
||||||
var $c = $("<div>" + content + "</div>");
|
let $c = $('<div>' + content + '</div>')
|
||||||
var $label = $c.find(".morris-hover-row-label");
|
let $label = $c.find('.morris-hover-row-label')
|
||||||
$label.text(row.item);
|
$label.text(row.item)
|
||||||
var newc = $label.get(0).outerHTML;
|
let newc = $label.get(0).outerHTML
|
||||||
$c.find('.morris-hover-point').each(function (i, r) {
|
$c.find('.morris-hover-point').each(function (i, r) {
|
||||||
if ($.trim($(r).text().split("\n")[2]) !== "0") {
|
if ($.trim($(r).text().split('\n')[2]) !== '0') {
|
||||||
newc += r.outerHTML;
|
newc += r.outerHTML
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
return newc;
|
return newc
|
||||||
},
|
},
|
||||||
resize: true,
|
resize: true,
|
||||||
xLabelAngle: 30
|
xLabelAngle: 30
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|||||||
@@ -1,435 +1,435 @@
|
|||||||
/*global $, stripe_pubkey, stripe_loadingmessage, gettext */
|
/* global stripe_pubkey, stripe_loadingmessage, gettext */
|
||||||
'use strict';
|
'use strict'
|
||||||
|
|
||||||
var pretixstripe = {
|
var pretixstripe = {
|
||||||
stripe: null,
|
stripe: null,
|
||||||
elements: null,
|
elements: null,
|
||||||
card: null,
|
card: null,
|
||||||
sepa: null,
|
sepa: null,
|
||||||
affirm: null,
|
affirm: null,
|
||||||
klarna: null,
|
klarna: null,
|
||||||
paymentRequest: null,
|
paymentRequest: null,
|
||||||
paymentRequestButton: null,
|
paymentRequestButton: null,
|
||||||
|
|
||||||
'pm_request': function (method, element, kwargs = {}) {
|
pm_request: function (method, element, kwargs = {}) {
|
||||||
waitingDialog.show(gettext("Contacting Stripe …"));
|
waitingDialog.show(gettext('Contacting Stripe …'))
|
||||||
$(".stripe-errors").hide();
|
$('.stripe-errors').hide()
|
||||||
|
|
||||||
pretixstripe.stripe.createPaymentMethod(method, element, kwargs).then(function (result) {
|
pretixstripe.stripe.createPaymentMethod(method, element, kwargs).then(function (result) {
|
||||||
waitingDialog.hide();
|
waitingDialog.hide()
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
$(".stripe-errors").stop().hide().removeClass("sr-only");
|
$('.stripe-errors').stop().hide().removeClass('sr-only')
|
||||||
$(".stripe-errors").html("<div class='alert alert-danger'>" + result.error.message + "</div>");
|
$('.stripe-errors').html('<div class=\'alert alert-danger\'>' + result.error.message + '</div>')
|
||||||
$(".stripe-errors").slideDown();
|
$('.stripe-errors').slideDown()
|
||||||
} else {
|
} else {
|
||||||
var $form = $("#stripe_" + method + "_payment_method_id").closest("form");
|
let $form = $('#stripe_' + method + '_payment_method_id').closest('form')
|
||||||
// Insert the token into the form so it gets submitted to the server
|
// Insert the token into the form so it gets submitted to the server
|
||||||
$("#stripe_" + method + "_payment_method_id").val(result.paymentMethod.id);
|
$('#stripe_' + method + '_payment_method_id').val(result.paymentMethod.id)
|
||||||
if (method === 'card') {
|
if (method === 'card') {
|
||||||
$("#stripe_card_brand").val(result.paymentMethod.card.brand);
|
$('#stripe_card_brand').val(result.paymentMethod.card.brand)
|
||||||
$("#stripe_card_last4").val(result.paymentMethod.card.last4);
|
$('#stripe_card_last4').val(result.paymentMethod.card.last4)
|
||||||
}
|
}
|
||||||
if (method === 'sepa_debit') {
|
if (method === 'sepa_debit') {
|
||||||
$("#stripe_sepa_debit_last4").val(result.paymentMethod.sepa_debit.last4);
|
$('#stripe_sepa_debit_last4').val(result.paymentMethod.sepa_debit.last4)
|
||||||
}
|
}
|
||||||
// and submit
|
// and submit
|
||||||
$form.get(0).submit();
|
$form.get(0).submit()
|
||||||
}
|
}
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
waitingDialog.hide();
|
waitingDialog.hide()
|
||||||
$(".stripe-errors").stop().hide().removeClass("sr-only");
|
$('.stripe-errors').stop().hide().removeClass('sr-only')
|
||||||
$(".stripe-errors").html("<div class='alert alert-danger'>Technical error, please contact support: " + e + "</div>");
|
$('.stripe-errors').html('<div class=\'alert alert-danger\'>Technical error, please contact support: ' + e + '</div>')
|
||||||
$(".stripe-errors").slideDown();
|
$('.stripe-errors').slideDown()
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
'load': function () {
|
load: function () {
|
||||||
if (pretixstripe.stripe !== null) {
|
if (pretixstripe.stripe !== null) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
$('.stripe-container').closest("form").find(".checkout-button-row .btn-primary").prop("disabled", true);
|
$('.stripe-container').closest('form').find('.checkout-button-row .btn-primary').prop('disabled', true)
|
||||||
$.ajax(
|
$.ajax(
|
||||||
{
|
{
|
||||||
url: 'https://js.stripe.com/v3/',
|
url: 'https://js.stripe.com/v3/',
|
||||||
dataType: 'script',
|
dataType: 'script',
|
||||||
success: function () {
|
success: function () {
|
||||||
if ($.trim($("#stripe_connectedAccountId").html())) {
|
if ($.trim($('#stripe_connectedAccountId').html())) {
|
||||||
pretixstripe.stripe = Stripe($.trim($("#stripe_pubkey").html()), {
|
pretixstripe.stripe = Stripe($.trim($('#stripe_pubkey').html()), {
|
||||||
stripeAccount: $.trim($("#stripe_connectedAccountId").html()),
|
stripeAccount: $.trim($('#stripe_connectedAccountId').html()),
|
||||||
locale: $.trim($("body").attr("data-locale"))
|
locale: $.trim($('body').attr('data-locale'))
|
||||||
});
|
})
|
||||||
} else {
|
} else {
|
||||||
pretixstripe.stripe = Stripe($.trim($("#stripe_pubkey").html()), {
|
pretixstripe.stripe = Stripe($.trim($('#stripe_pubkey').html()), {
|
||||||
locale: $.trim($("body").attr("data-locale"))
|
locale: $.trim($('body').attr('data-locale'))
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
pretixstripe.elements = pretixstripe.stripe.elements();
|
pretixstripe.elements = pretixstripe.stripe.elements()
|
||||||
if ($.trim($("#stripe_merchantcountry").html()) !== "") {
|
if ($.trim($('#stripe_merchantcountry').html()) !== '') {
|
||||||
try {
|
try {
|
||||||
pretixstripe.paymentRequest = pretixstripe.stripe.paymentRequest({
|
pretixstripe.paymentRequest = pretixstripe.stripe.paymentRequest({
|
||||||
country: $("#stripe_merchantcountry").html(),
|
country: $('#stripe_merchantcountry').html(),
|
||||||
currency: $("#stripe_card_currency").val().toLowerCase(),
|
currency: $('#stripe_card_currency').val().toLowerCase(),
|
||||||
total: {
|
total: {
|
||||||
label: gettext('Total'),
|
label: gettext('Total'),
|
||||||
amount: parseInt($("#stripe_card_total").val())
|
amount: parseInt($('#stripe_card_total').val())
|
||||||
},
|
},
|
||||||
displayItems: [],
|
displayItems: [],
|
||||||
requestPayerName: false,
|
requestPayerName: false,
|
||||||
requestPayerEmail: false,
|
requestPayerEmail: false,
|
||||||
requestPayerPhone: false,
|
requestPayerPhone: false,
|
||||||
requestShipping: false,
|
requestShipping: false,
|
||||||
});
|
})
|
||||||
|
|
||||||
pretixstripe.paymentRequest.on('paymentmethod', function (ev) {
|
pretixstripe.paymentRequest.on('paymentmethod', function (ev) {
|
||||||
ev.complete('success');
|
ev.complete('success')
|
||||||
|
|
||||||
var $form = $("#stripe_card_payment_method_id").closest("form");
|
let $form = $('#stripe_card_payment_method_id').closest('form')
|
||||||
// Insert the token into the form so it gets submitted to the server
|
// Insert the token into the form so it gets submitted to the server
|
||||||
$("#stripe_card_payment_method_id").val(ev.paymentMethod.id);
|
$('#stripe_card_payment_method_id').val(ev.paymentMethod.id)
|
||||||
$("#stripe_card_brand").val(ev.paymentMethod.card.brand);
|
$('#stripe_card_brand').val(ev.paymentMethod.card.brand)
|
||||||
$("#stripe_card_last4").val(ev.paymentMethod.card.last4);
|
$('#stripe_card_last4').val(ev.paymentMethod.card.last4)
|
||||||
// and submit
|
// and submit
|
||||||
$form.get(0).submit();
|
$form.get(0).submit()
|
||||||
});
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
pretixstripe.paymentRequest = null;
|
pretixstripe.paymentRequest = null
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
pretixstripe.paymentRequest = null;
|
pretixstripe.paymentRequest = null
|
||||||
}
|
}
|
||||||
if ($("#stripe-card").length) {
|
if ($('#stripe-card').length) {
|
||||||
pretixstripe.card = pretixstripe.elements.create('card', {
|
pretixstripe.card = pretixstripe.elements.create('card', {
|
||||||
'style': {
|
style: {
|
||||||
'base': {
|
base: {
|
||||||
'fontFamily': '"Open Sans","OpenSans","Helvetica Neue",Helvetica,Arial,sans-serif',
|
fontFamily: '"Open Sans","OpenSans","Helvetica Neue",Helvetica,Arial,sans-serif',
|
||||||
'fontSize': '14px',
|
fontSize: '14px',
|
||||||
'color': '#555555',
|
color: '#555555',
|
||||||
'lineHeight': '1.42857',
|
lineHeight: '1.42857',
|
||||||
'border': '1px solid #ccc',
|
border: '1px solid #ccc',
|
||||||
'::placeholder': {
|
'::placeholder': {
|
||||||
color: 'rgba(0,0,0,0.4)',
|
color: 'rgba(0,0,0,0.4)',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'invalid': {
|
invalid: {
|
||||||
'color': 'red',
|
color: 'red',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
classes: {
|
classes: {
|
||||||
focus: 'is-focused',
|
focus: 'is-focused',
|
||||||
invalid: 'has-error',
|
invalid: 'has-error',
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
pretixstripe.card.mount("#stripe-card");
|
pretixstripe.card.mount('#stripe-card')
|
||||||
pretixstripe.card.on('ready', function () {
|
pretixstripe.card.on('ready', function () {
|
||||||
$('.stripe-container').closest("form").find(".checkout-button-row .btn-primary").prop("disabled", false);
|
$('.stripe-container').closest('form').find('.checkout-button-row .btn-primary').prop('disabled', false)
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
if ($("#stripe-sepa").length) {
|
if ($('#stripe-sepa').length) {
|
||||||
pretixstripe.sepa = pretixstripe.elements.create('iban', {
|
pretixstripe.sepa = pretixstripe.elements.create('iban', {
|
||||||
'style': {
|
style: {
|
||||||
'base': {
|
base: {
|
||||||
'fontFamily': '"Open Sans","OpenSans","Helvetica Neue",Helvetica,Arial,sans-serif',
|
fontFamily: '"Open Sans","OpenSans","Helvetica Neue",Helvetica,Arial,sans-serif',
|
||||||
'fontSize': '14px',
|
fontSize: '14px',
|
||||||
'color': '#555555',
|
color: '#555555',
|
||||||
'lineHeight': '1.42857',
|
lineHeight: '1.42857',
|
||||||
'border': '1px solid #ccc',
|
border: '1px solid #ccc',
|
||||||
'::placeholder': {
|
'::placeholder': {
|
||||||
color: 'rgba(0,0,0,0.4)',
|
color: 'rgba(0,0,0,0.4)',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'invalid': {
|
invalid: {
|
||||||
'color': 'red',
|
color: 'red',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
supportedCountries: ['SEPA'],
|
supportedCountries: ['SEPA'],
|
||||||
classes: {
|
classes: {
|
||||||
focus: 'is-focused',
|
focus: 'is-focused',
|
||||||
invalid: 'has-error',
|
invalid: 'has-error',
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
pretixstripe.sepa.on('change', function (event) {
|
pretixstripe.sepa.on('change', function (event) {
|
||||||
// List of IBAN-countries, that require the country as well as line1-property according to
|
// List of IBAN-countries, that require the country as well as line1-property according to
|
||||||
// https://stripe.com/docs/payments/sepa-debit/accept-a-payment?platform=web&ui=element#web-submit-payment
|
// https://stripe.com/docs/payments/sepa-debit/accept-a-payment?platform=web&ui=element#web-submit-payment
|
||||||
if (['AD', 'PF', 'TF', 'GI', 'GB', 'GG', 'VA', 'IM', 'JE', 'MC', 'NC', 'BL', 'PM', 'SM', 'CH', 'WF'].indexOf(event.country) > 0) {
|
if (['AD', 'PF', 'TF', 'GI', 'GB', 'GG', 'VA', 'IM', 'JE', 'MC', 'NC', 'BL', 'PM', 'SM', 'CH', 'WF'].indexOf(event.country) > 0) {
|
||||||
$("#stripe_sepa_debit_country").prop('checked', true);
|
$('#stripe_sepa_debit_country').prop('checked', true)
|
||||||
$("#stripe_sepa_debit_country").change();
|
$('#stripe_sepa_debit_country').change()
|
||||||
} else {
|
} else {
|
||||||
$("#stripe_sepa_debit_country").prop('checked', false);
|
$('#stripe_sepa_debit_country').prop('checked', false)
|
||||||
$("#stripe_sepa_debit_country").change();
|
$('#stripe_sepa_debit_country').change()
|
||||||
}
|
}
|
||||||
if (event.bankName) {
|
if (event.bankName) {
|
||||||
$("#stripe_sepa_debit_bank").val(event.bankName);
|
$('#stripe_sepa_debit_bank').val(event.bankName)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
pretixstripe.sepa.mount("#stripe-sepa");
|
pretixstripe.sepa.mount('#stripe-sepa')
|
||||||
pretixstripe.sepa.on('ready', function () {
|
pretixstripe.sepa.on('ready', function () {
|
||||||
$('.stripe-container').closest("form").find(".checkout-button-row .btn-primary").prop("disabled", false);
|
$('.stripe-container').closest('form').find('.checkout-button-row .btn-primary').prop('disabled', false)
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
if ($("#stripe-affirm").length) {
|
if ($('#stripe-affirm').length) {
|
||||||
pretixstripe.affirm = pretixstripe.elements.create('affirmMessage', {
|
pretixstripe.affirm = pretixstripe.elements.create('affirmMessage', {
|
||||||
'amount': parseInt($("#stripe_affirm_total").val()),
|
amount: parseInt($('#stripe_affirm_total').val()),
|
||||||
'currency': $("#stripe_affirm_currency").val(),
|
currency: $('#stripe_affirm_currency').val(),
|
||||||
});
|
})
|
||||||
|
|
||||||
pretixstripe.affirm.mount('#stripe-affirm');
|
pretixstripe.affirm.mount('#stripe-affirm')
|
||||||
}
|
}
|
||||||
if ($("#stripe-klarna").length) {
|
if ($('#stripe-klarna').length) {
|
||||||
try {
|
try {
|
||||||
pretixstripe.klarna = pretixstripe.elements.create('paymentMethodMessaging', {
|
pretixstripe.klarna = pretixstripe.elements.create('paymentMethodMessaging', {
|
||||||
'amount': parseInt($("#stripe_klarna_total").val()),
|
amount: parseInt($('#stripe_klarna_total').val()),
|
||||||
'currency': $("#stripe_klarna_currency").val(),
|
currency: $('#stripe_klarna_currency').val(),
|
||||||
'countryCode': $("#stripe_klarna_country").val(),
|
countryCode: $('#stripe_klarna_country').val(),
|
||||||
'paymentMethodTypes': ['klarna'],
|
paymentMethodTypes: ['klarna'],
|
||||||
});
|
})
|
||||||
|
|
||||||
pretixstripe.klarna.mount('#stripe-klarna');
|
pretixstripe.klarna.mount('#stripe-klarna')
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e)
|
||||||
$("#stripe-klarna").html("<div class='alert alert-danger'>Technical error, please contact support: " + e + "</div>");
|
$('#stripe-klarna').html('<div class=\'alert alert-danger\'>Technical error, please contact support: ' + e + '</div>')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($("#stripe-payment-request-button").length && pretixstripe.paymentRequest != null) {
|
if ($('#stripe-payment-request-button').length && pretixstripe.paymentRequest != null) {
|
||||||
pretixstripe.paymentRequestButton = pretixstripe.elements.create('paymentRequestButton', {
|
pretixstripe.paymentRequestButton = pretixstripe.elements.create('paymentRequestButton', {
|
||||||
paymentRequest: pretixstripe.paymentRequest,
|
paymentRequest: pretixstripe.paymentRequest,
|
||||||
});
|
})
|
||||||
|
|
||||||
pretixstripe.paymentRequest.canMakePayment().then(function (result) {
|
pretixstripe.paymentRequest.canMakePayment().then(function (result) {
|
||||||
if (result) {
|
if (result) {
|
||||||
pretixstripe.paymentRequestButton.mount('#stripe-payment-request-button');
|
pretixstripe.paymentRequestButton.mount('#stripe-payment-request-button')
|
||||||
$('#stripe-card-elements .stripe-or').removeClass("hidden");
|
$('#stripe-card-elements .stripe-or').removeClass('hidden')
|
||||||
$('#stripe-payment-request-button').parent().removeClass("hidden");
|
$('#stripe-payment-request-button').parent().removeClass('hidden')
|
||||||
} else {
|
} else {
|
||||||
$('#stripe-payment-request-button').hide();
|
$('#stripe-payment-request-button').hide()
|
||||||
document.getElementById('stripe-payment-request-button').style.display = 'none';
|
document.getElementById('stripe-payment-request-button').style.display = 'none'
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
},
|
},
|
||||||
'withStripe': function (callback) {
|
withStripe: function (callback) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: 'https://js.stripe.com/v3/',
|
url: 'https://js.stripe.com/v3/',
|
||||||
dataType: 'script',
|
dataType: 'script',
|
||||||
success: function () {
|
success: function () {
|
||||||
if ($.trim($("#stripe_connectedAccountId").html())) {
|
if ($.trim($('#stripe_connectedAccountId').html())) {
|
||||||
pretixstripe.stripe = Stripe($.trim($("#stripe_pubkey").html()), {
|
pretixstripe.stripe = Stripe($.trim($('#stripe_pubkey').html()), {
|
||||||
stripeAccount: $.trim($("#stripe_connectedAccountId").html()),
|
stripeAccount: $.trim($('#stripe_connectedAccountId').html()),
|
||||||
locale: $.trim($("body").attr("data-locale"))
|
locale: $.trim($('body').attr('data-locale'))
|
||||||
});
|
})
|
||||||
} else {
|
} else {
|
||||||
pretixstripe.stripe = Stripe($.trim($("#stripe_pubkey").html()), {
|
pretixstripe.stripe = Stripe($.trim($('#stripe_pubkey').html()), {
|
||||||
locale: $.trim($("body").attr("data-locale"))
|
locale: $.trim($('body').attr('data-locale'))
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
callback();
|
callback()
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
'handleAlipayAction': function (payment_intent_client_secret) {
|
handleAlipayAction: function (payment_intent_client_secret) {
|
||||||
pretixstripe.withStripe(function () {
|
pretixstripe.withStripe(function () {
|
||||||
pretixstripe.stripe.confirmAlipayPayment(
|
pretixstripe.stripe.confirmAlipayPayment(
|
||||||
payment_intent_client_secret,
|
payment_intent_client_secret,
|
||||||
{
|
{
|
||||||
return_url: window.location.href
|
return_url: window.location.href
|
||||||
}
|
}
|
||||||
).then(function (result) {
|
).then(function (result) {
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
waitingDialog.hide();
|
waitingDialog.hide()
|
||||||
$(".stripe-errors").stop().hide().removeClass("sr-only");
|
$('.stripe-errors').stop().hide().removeClass('sr-only')
|
||||||
$(".stripe-errors").html("<div class='alert alert-danger'>Technical error, please contact support: " + result.error.message + "</div>");
|
$('.stripe-errors').html('<div class=\'alert alert-danger\'>Technical error, please contact support: ' + result.error.message + '</div>')
|
||||||
$(".stripe-errors").slideDown();
|
$('.stripe-errors').slideDown()
|
||||||
} else {
|
} else {
|
||||||
waitingDialog.show(gettext("Confirming your payment …"));
|
waitingDialog.show(gettext('Confirming your payment …'))
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
'handleWechatAction': function (payment_intent_client_secret) {
|
handleWechatAction: function (payment_intent_client_secret) {
|
||||||
pretixstripe.withStripe(function () {
|
pretixstripe.withStripe(function () {
|
||||||
pretixstripe.stripe.confirmWechatPayPayment(
|
pretixstripe.stripe.confirmWechatPayPayment(
|
||||||
payment_intent_client_secret,
|
payment_intent_client_secret,
|
||||||
{
|
{
|
||||||
payment_method_options: {
|
payment_method_options: {
|
||||||
wechat_pay: {
|
wechat_pay: {
|
||||||
client: 'web',
|
client: 'web',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
).then(function (result) {
|
).then(function (result) {
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
waitingDialog.hide();
|
waitingDialog.hide()
|
||||||
$(".stripe-errors").stop().hide().removeClass("sr-only");
|
$('.stripe-errors').stop().hide().removeClass('sr-only')
|
||||||
$(".stripe-errors").html("<div class='alert alert-danger'>Technical error, please contact support: " + result.error.message + "</div>");
|
$('.stripe-errors').html('<div class=\'alert alert-danger\'>Technical error, please contact support: ' + result.error.message + '</div>')
|
||||||
$(".stripe-errors").slideDown();
|
$('.stripe-errors').slideDown()
|
||||||
} else {
|
} else {
|
||||||
waitingDialog.show(gettext("Confirming your payment …"));
|
waitingDialog.show(gettext('Confirming your payment …'))
|
||||||
location.reload();
|
location.reload()
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
'handleCardAction': function (payment_intent_client_secret) {
|
handleCardAction: function (payment_intent_client_secret) {
|
||||||
pretixstripe.withStripe(function () {
|
pretixstripe.withStripe(function () {
|
||||||
pretixstripe.stripe.handleCardAction(
|
pretixstripe.stripe.handleCardAction(
|
||||||
payment_intent_client_secret
|
payment_intent_client_secret
|
||||||
).then(function (result) {
|
).then(function (result) {
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
waitingDialog.hide();
|
waitingDialog.hide()
|
||||||
$(".stripe-errors").stop().hide().removeClass("sr-only");
|
$('.stripe-errors').stop().hide().removeClass('sr-only')
|
||||||
$(".stripe-errors").html("<div class='alert alert-danger'>Technical error, please contact support: " + result.error.message + "</div>");
|
$('.stripe-errors').html('<div class=\'alert alert-danger\'>Technical error, please contact support: ' + result.error.message + '</div>')
|
||||||
$(".stripe-errors").slideDown();
|
$('.stripe-errors').slideDown()
|
||||||
} else {
|
} else {
|
||||||
waitingDialog.show(gettext("Confirming your payment …"));
|
waitingDialog.show(gettext('Confirming your payment …'))
|
||||||
location.reload();
|
location.reload()
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
'handlePaymentRedirectAction': function (payment_intent_next_action_redirect_url) {
|
handlePaymentRedirectAction: function (payment_intent_next_action_redirect_url) {
|
||||||
waitingDialog.show(gettext("Contacting your bank …"));
|
waitingDialog.show(gettext('Contacting your bank …'))
|
||||||
|
|
||||||
let payment_intent_redirect_action_handling = $.trim($("#stripe_payment_intent_redirect_action_handling").html());
|
let payment_intent_redirect_action_handling = $.trim($('#stripe_payment_intent_redirect_action_handling').html())
|
||||||
if (payment_intent_redirect_action_handling === 'iframe') {
|
if (payment_intent_redirect_action_handling === 'iframe') {
|
||||||
let iframe = document.createElement('iframe');
|
let iframe = document.createElement('iframe')
|
||||||
iframe.src = payment_intent_next_action_redirect_url;
|
iframe.src = payment_intent_next_action_redirect_url
|
||||||
iframe.className = 'embed-responsive-item';
|
iframe.className = 'embed-responsive-item'
|
||||||
$('#scacontainer').append(iframe);
|
$('#scacontainer').append(iframe)
|
||||||
$('#scacontainer iframe').on("load", function () {
|
$('#scacontainer iframe').on('load', function () {
|
||||||
waitingDialog.hide();
|
waitingDialog.hide()
|
||||||
});
|
})
|
||||||
} else if (payment_intent_redirect_action_handling === 'redirect') {
|
} else if (payment_intent_redirect_action_handling === 'redirect') {
|
||||||
window.location.href = payment_intent_next_action_redirect_url;
|
window.location.href = payment_intent_next_action_redirect_url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
$(function () {
|
$(function () {
|
||||||
if ($("#stripe_payment_intent_SCA_status").length) {
|
if ($('#stripe_payment_intent_SCA_status').length) {
|
||||||
let payment_intent_redirect_action_handling = $.trim($("#stripe_payment_intent_redirect_action_handling").html());
|
let payment_intent_redirect_action_handling = $.trim($('#stripe_payment_intent_redirect_action_handling').html())
|
||||||
let order_status = $.trim($("#order_status").html());
|
let order_status = $.trim($('#order_status').html())
|
||||||
let order_url = $.trim($("#order_url").html())
|
let order_url = $.trim($('#order_url').html())
|
||||||
|
|
||||||
if (payment_intent_redirect_action_handling === 'iframe') {
|
if (payment_intent_redirect_action_handling === 'iframe') {
|
||||||
window.parent.postMessage('3DS-authentication-complete.' + order_status, '*');
|
window.parent.postMessage('3DS-authentication-complete.' + order_status, '*')
|
||||||
return;
|
return
|
||||||
} else if (payment_intent_redirect_action_handling === 'redirect') {
|
} else if (payment_intent_redirect_action_handling === 'redirect') {
|
||||||
waitingDialog.show(gettext("Confirming your payment …"));
|
waitingDialog.show(gettext('Confirming your payment …'))
|
||||||
|
|
||||||
if (order_status === 'p') {
|
if (order_status === 'p') {
|
||||||
window.location.href = order_url + '?paid=yes';
|
window.location.href = order_url + '?paid=yes'
|
||||||
} else {
|
} else {
|
||||||
window.location.href = order_url;
|
window.location.href = order_url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if ($("#stripe_payment_intent_next_action_redirect_url").length) {
|
} else if ($('#stripe_payment_intent_next_action_redirect_url').length) {
|
||||||
let payment_intent_next_action_redirect_url = JSON.parse($("#stripe_payment_intent_next_action_redirect_url").html());
|
let payment_intent_next_action_redirect_url = JSON.parse($('#stripe_payment_intent_next_action_redirect_url').html())
|
||||||
pretixstripe.handlePaymentRedirectAction(payment_intent_next_action_redirect_url);
|
pretixstripe.handlePaymentRedirectAction(payment_intent_next_action_redirect_url)
|
||||||
} else if ($.trim($("#stripe_payment_intent_action_type").html()) === "promptpay_display_qr_code") {
|
} else if ($.trim($('#stripe_payment_intent_action_type').html()) === 'promptpay_display_qr_code') {
|
||||||
waitingDialog.hide();
|
waitingDialog.hide()
|
||||||
} else if ($.trim($("#stripe_payment_intent_action_type").html()) === "wechat_pay_display_qr_code") {
|
} else if ($.trim($('#stripe_payment_intent_action_type').html()) === 'wechat_pay_display_qr_code') {
|
||||||
let payment_intent_client_secret = $.trim($("#stripe_payment_intent_client_secret").html());
|
let payment_intent_client_secret = $.trim($('#stripe_payment_intent_client_secret').html())
|
||||||
pretixstripe.handleWechatAction(payment_intent_client_secret);
|
pretixstripe.handleWechatAction(payment_intent_client_secret)
|
||||||
} else if ($.trim($("#stripe_payment_intent_action_type").html()) === "alipay_handle_redirect") {
|
} else if ($.trim($('#stripe_payment_intent_action_type').html()) === 'alipay_handle_redirect') {
|
||||||
let payment_intent_client_secret = $.trim($("#stripe_payment_intent_client_secret").html());
|
let payment_intent_client_secret = $.trim($('#stripe_payment_intent_client_secret').html())
|
||||||
pretixstripe.handleAlipayAction(payment_intent_client_secret);
|
pretixstripe.handleAlipayAction(payment_intent_client_secret)
|
||||||
} else if ($("#stripe_payment_intent_client_secret").length) {
|
} else if ($('#stripe_payment_intent_client_secret').length) {
|
||||||
let payment_intent_client_secret = $.trim($("#stripe_payment_intent_client_secret").html());
|
let payment_intent_client_secret = $.trim($('#stripe_payment_intent_client_secret').html())
|
||||||
pretixstripe.handleCardAction(payment_intent_client_secret);
|
pretixstripe.handleCardAction(payment_intent_client_secret)
|
||||||
}
|
}
|
||||||
|
|
||||||
$(window).on("message onmessage", function (e) {
|
$(window).on('message onmessage', function (e) {
|
||||||
if (typeof e.originalEvent.data === "string" && e.originalEvent.data.startsWith('3DS-authentication-complete.')) {
|
if (typeof e.originalEvent.data === 'string' && e.originalEvent.data.startsWith('3DS-authentication-complete.')) {
|
||||||
waitingDialog.show(gettext("Confirming your payment …"));
|
waitingDialog.show(gettext('Confirming your payment …'))
|
||||||
$('#scacontainer').hide();
|
$('#scacontainer').hide()
|
||||||
$('#continuebutton').removeClass('hidden');
|
$('#continuebutton').removeClass('hidden')
|
||||||
|
|
||||||
if (e.originalEvent.data.split('.')[1] == 'p') {
|
if (e.originalEvent.data.split('.')[1] == 'p') {
|
||||||
window.location.href = $('#continuebutton').attr('href') + '?paid=yes';
|
window.location.href = $('#continuebutton').attr('href') + '?paid=yes'
|
||||||
} else {
|
} else {
|
||||||
window.location.href = $('#continuebutton').attr('href');
|
window.location.href = $('#continuebutton').attr('href')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
if (!$(".stripe-container").length)
|
if (!$('.stripe-container').length)
|
||||||
return;
|
return
|
||||||
|
|
||||||
if (
|
if (
|
||||||
$("input[name=payment][value=stripe]").is(':checked')
|
$('input[name=payment][value=stripe]').is(':checked')
|
||||||
|| $("input[name=payment][value=stripe_sepa_debit]").is(':checked')
|
|| $('input[name=payment][value=stripe_sepa_debit]').is(':checked')
|
||||||
|| $("input[name=payment][value=stripe_affirm]").is(':checked')
|
|| $('input[name=payment][value=stripe_affirm]').is(':checked')
|
||||||
|| $("input[name=payment][value=stripe_klarna]").is(':checked')
|
|| $('input[name=payment][value=stripe_klarna]').is(':checked')
|
||||||
|| $(".payment-redo-form").length) {
|
|| $('.payment-redo-form').length) {
|
||||||
pretixstripe.load();
|
pretixstripe.load()
|
||||||
} else {
|
} else {
|
||||||
$("input[name=payment]").change(function () {
|
$('input[name=payment]').change(function () {
|
||||||
if (['stripe', 'stripe_sepa_debit', 'stripe_affirm', 'stripe_klarna'].indexOf($(this).val()) > -1) {
|
if (['stripe', 'stripe_sepa_debit', 'stripe_affirm', 'stripe_klarna'].indexOf($(this).val()) > -1) {
|
||||||
pretixstripe.load();
|
pretixstripe.load()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
$("#stripe_other_card").click(
|
$('#stripe_other_card').click(
|
||||||
function (e) {
|
function (e) {
|
||||||
$("#stripe_card_payment_method_id").val("");
|
$('#stripe_card_payment_method_id').val('')
|
||||||
$("#stripe-current-card").slideUp();
|
$('#stripe-current-card').slideUp()
|
||||||
$("#stripe-card-elements").slideDown();
|
$('#stripe-card-elements').slideDown()
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault()
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
|
|
||||||
if ($("#stripe-current-card").length) {
|
if ($('#stripe-current-card').length) {
|
||||||
$("#stripe-card-elements").hide();
|
$('#stripe-card-elements').hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
$("#stripe_other_account").click(
|
$('#stripe_other_account').click(
|
||||||
function (e) {
|
function (e) {
|
||||||
$("#stripe_sepa_debit_payment_method_id").val("");
|
$('#stripe_sepa_debit_payment_method_id').val('')
|
||||||
$("#stripe-current-account").slideUp();
|
$('#stripe-current-account').slideUp()
|
||||||
// We're using a css-selector here instead of the id-selector,
|
// We're using a css-selector here instead of the id-selector,
|
||||||
// as we're hiding Stripe Elements *and* Django form fields
|
// as we're hiding Stripe Elements *and* Django form fields
|
||||||
$('.stripe-sepa_debit-form').slideDown();
|
$('.stripe-sepa_debit-form').slideDown()
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault()
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
|
|
||||||
if ($("#stripe-current-account").length) {
|
if ($('#stripe-current-account').length) {
|
||||||
// We're using a css-selector here instead of the id-selector,
|
// We're using a css-selector here instead of the id-selector,
|
||||||
// as we're hiding Stripe Elements *and* Django form fields
|
// as we're hiding Stripe Elements *and* Django form fields
|
||||||
$('.stripe-sepa_debit-form').hide();
|
$('.stripe-sepa_debit-form').hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
$('.stripe-container').closest("form").submit(
|
$('.stripe-container').closest('form').submit(
|
||||||
function () {
|
function () {
|
||||||
if ($("input[name=card_new]").length && !$("input[name=card_new]").prop('checked')) {
|
if ($('input[name=card_new]').length && !$('input[name=card_new]').prop('checked')) {
|
||||||
return null;
|
return null
|
||||||
}
|
}
|
||||||
if (($("input[name=payment][value=stripe]").prop('checked') || $("input[name=payment][type=radio]").length === 0)
|
if (($('input[name=payment][value=stripe]').prop('checked') || $('input[name=payment][type=radio]').length === 0)
|
||||||
&& $("#stripe_card_payment_method_id").val() == "") {
|
&& $('#stripe_card_payment_method_id').val() == '') {
|
||||||
pretixstripe.pm_request('card', pretixstripe.card);
|
pretixstripe.pm_request('card', pretixstripe.card)
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (($("input[name=payment][value=stripe_sepa_debit]").prop('checked')) && $("#stripe_sepa_debit_payment_method_id").val() == "") {
|
if (($('input[name=payment][value=stripe_sepa_debit]').prop('checked')) && $('#stripe_sepa_debit_payment_method_id').val() == '') {
|
||||||
pretixstripe.pm_request('sepa_debit', pretixstripe.sepa, {
|
pretixstripe.pm_request('sepa_debit', pretixstripe.sepa, {
|
||||||
billing_details: {
|
billing_details: {
|
||||||
name: $("#id_payment_stripe_sepa_debit-accountname").val(),
|
name: $('#id_payment_stripe_sepa_debit-accountname').val(),
|
||||||
email: $("#stripe_sepa_debit_email").val(),
|
email: $('#stripe_sepa_debit_email').val(),
|
||||||
address: {
|
address: {
|
||||||
line1: $("#id_payment_stripe_sepa_debit-line1").val(),
|
line1: $('#id_payment_stripe_sepa_debit-line1').val(),
|
||||||
postal_code: $("#id_payment_stripe_sepa_debit-postal_code").val(),
|
postal_code: $('#id_payment_stripe_sepa_debit-postal_code').val(),
|
||||||
city: $("#id_payment_stripe_sepa_debit-city").val(),
|
city: $('#id_payment_stripe_sepa_debit-city').val(),
|
||||||
country: $("#id_payment_stripe_sepa_debit-country").val(),
|
country: $('#id_payment_stripe_sepa_debit-country').val(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
});
|
})
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import copy
|
|||||||
import inspect
|
import inspect
|
||||||
import uuid
|
import uuid
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
from datetime import time
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
@@ -52,6 +53,7 @@ from django.shortcuts import redirect
|
|||||||
from django.utils import translation
|
from django.utils import translation
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.html import conditional_escape
|
from django.utils.html import conditional_escape
|
||||||
|
from django.utils.timezone import now
|
||||||
from django.utils.translation import (
|
from django.utils.translation import (
|
||||||
get_language, gettext_lazy as _, pgettext_lazy,
|
get_language, gettext_lazy as _, pgettext_lazy,
|
||||||
)
|
)
|
||||||
@@ -71,6 +73,7 @@ from pretix.base.services.cart import (
|
|||||||
from pretix.base.services.cross_selling import CrossSellingService
|
from pretix.base.services.cross_selling import CrossSellingService
|
||||||
from pretix.base.services.memberships import validate_memberships_in_order
|
from pretix.base.services.memberships import validate_memberships_in_order
|
||||||
from pretix.base.services.orders import perform_order
|
from pretix.base.services.orders import perform_order
|
||||||
|
from pretix.base.services.payment import compute_payment_deadline
|
||||||
from pretix.base.services.pricing import get_price
|
from pretix.base.services.pricing import get_price
|
||||||
from pretix.base.services.tasks import EventTask
|
from pretix.base.services.tasks import EventTask
|
||||||
from pretix.base.settings import PERSON_NAME_SCHEMES
|
from pretix.base.settings import PERSON_NAME_SCHEMES
|
||||||
@@ -870,6 +873,28 @@ class QuestionsStep(CartQuestionsViewMixin, CartMixin, TemplateFlowStep):
|
|||||||
'attendee_name_parts': d
|
'attendee_name_parts': d
|
||||||
})
|
})
|
||||||
|
|
||||||
|
wd = self.cart_session.get('widget_data', {})
|
||||||
|
if wd.get('attendee-fix', '') == 'true':
|
||||||
|
for k, v in wd.items():
|
||||||
|
if v and k.startswith('attendee-name'):
|
||||||
|
o.append({
|
||||||
|
'attendee_name_parts': {
|
||||||
|
'disabled': True,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
elif v and k.startswith('email'):
|
||||||
|
o.append({
|
||||||
|
'attendee_email': {
|
||||||
|
'disabled': True,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
elif v and k.startswith('question-'):
|
||||||
|
o.append({
|
||||||
|
k[9:].upper(): {
|
||||||
|
'disabled': True,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return o
|
return o
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
@@ -1344,6 +1369,11 @@ class PaymentStep(CartMixin, TemplateFlowStep):
|
|||||||
self.request = request
|
self.request = request
|
||||||
self.request.pci_dss_payment_page = True
|
self.request.pci_dss_payment_page = True
|
||||||
|
|
||||||
|
if "postpone" in request.POST and self._allow_postpone:
|
||||||
|
self.cart_session['payments_postpone'] = True
|
||||||
|
self.cart_session['payments'] = []
|
||||||
|
return redirect_to_url(self.get_next_url(request))
|
||||||
|
|
||||||
if "remove_payment" in request.POST:
|
if "remove_payment" in request.POST:
|
||||||
self._remove_payment(request.POST["remove_payment"])
|
self._remove_payment(request.POST["remove_payment"])
|
||||||
return redirect_to_url(self.get_step_url(request))
|
return redirect_to_url(self.get_step_url(request))
|
||||||
@@ -1432,20 +1462,41 @@ class PaymentStep(CartMixin, TemplateFlowStep):
|
|||||||
ctx['providers'] = self.provider_forms
|
ctx['providers'] = self.provider_forms
|
||||||
ctx['show_fees'] = any(p['fee'] for p in self.provider_forms)
|
ctx['show_fees'] = any(p['fee'] for p in self.provider_forms)
|
||||||
|
|
||||||
if len(self.provider_forms) == 1:
|
if 'payment' in self.request.POST:
|
||||||
ctx['selected'] = self.provider_forms[0]['provider'].identifier
|
|
||||||
elif 'payment' in self.request.POST:
|
|
||||||
ctx['selected'] = self.request.POST['payment']
|
ctx['selected'] = self.request.POST['payment']
|
||||||
|
elif self.cart_session.get('payments_postpone') and self._allow_postpone:
|
||||||
|
ctx['selected'] = ''
|
||||||
|
elif len(self.provider_forms) == 1:
|
||||||
|
ctx['selected'] = self.provider_forms[0]['provider'].identifier
|
||||||
elif self.single_use_payment:
|
elif self.single_use_payment:
|
||||||
ctx['selected'] = self.single_use_payment['provider']
|
ctx['selected'] = self.single_use_payment['provider']
|
||||||
else:
|
else:
|
||||||
ctx['selected'] = ''
|
ctx['selected'] = ''
|
||||||
|
|
||||||
|
ctx['allow_postpone'] = self._allow_postpone
|
||||||
|
if self._allow_postpone:
|
||||||
|
now_dt = now()
|
||||||
|
ctx['payment_deadline'] = compute_payment_deadline(
|
||||||
|
event=self.request.event,
|
||||||
|
sales_channel=self.request.sales_channel,
|
||||||
|
subevents={p.subevent for p in ctx['cart']['raw']},
|
||||||
|
now_dt=now_dt,
|
||||||
|
)
|
||||||
|
if ctx['payment_deadline'].time() != time(hour=23, minute=59, second=59):
|
||||||
|
ctx['payment_deadline_minutes'] = int((ctx['payment_deadline'] - now_dt).total_seconds() // 60)
|
||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def _allow_postpone(self):
|
||||||
|
return self.request.sales_channel.identifier in self.request.event.settings.payment_choice_postpone_allowed_channels
|
||||||
|
|
||||||
def _is_allowed(self, prov, request):
|
def _is_allowed(self, prov, request):
|
||||||
return prov.is_allowed(request, total=self._total_order_value)
|
return prov.is_allowed(request, total=self._total_order_value)
|
||||||
|
|
||||||
def is_completed(self, request, warn=False):
|
def is_completed(self, request, warn=False):
|
||||||
|
if self.cart_session.get('payments_postpone') and self._allow_postpone:
|
||||||
|
return True
|
||||||
|
|
||||||
if not self.cart_session.get('payments'):
|
if not self.cart_session.get('payments'):
|
||||||
if warn:
|
if warn:
|
||||||
messages.error(request, _('Please select a payment method to proceed.'))
|
messages.error(request, _('Please select a payment method to proceed.'))
|
||||||
|
|||||||
@@ -128,6 +128,35 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if allow_postpone %}
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-body row">
|
||||||
|
<div class="col-md-9 col-xs-12">
|
||||||
|
{% trans "Not sure yet? You can complete your order first and then select a payment method later." %}
|
||||||
|
<br>
|
||||||
|
<span class="text-muted">
|
||||||
|
{% if current_payments %}
|
||||||
|
{% trans "To do so, please first remove the payment methods you already selected above." %}
|
||||||
|
{% elif payment_deadline_minutes %}
|
||||||
|
{% blocktrans trimmed with minutes=payment_deadline_minutes %}
|
||||||
|
Your payment needs to be completed within {{ minutes }} minutes.
|
||||||
|
{% endblocktrans %}
|
||||||
|
{% else %}
|
||||||
|
{% blocktrans trimmed with deadline=payment_deadline|date:"SHORT_DATE_FORMAT" %}
|
||||||
|
Your payment needs to be completed by {{ deadline }}.
|
||||||
|
{% endblocktrans %}
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3 col-xs-12 text-right flip">
|
||||||
|
<button name="postpone" value="on" class="btn btn-primary"
|
||||||
|
{% if current_payments %}disabled{% endif %}>
|
||||||
|
{% trans "Proceed without selection" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<div class="row checkout-button-row">
|
<div class="row checkout-button-row">
|
||||||
<div class="col-md-4 col-sm-6">
|
<div class="col-md-4 col-sm-6">
|
||||||
<a class="btn btn-block btn-default btn-lg"
|
<a class="btn btn-block btn-default btn-lg"
|
||||||
|
|||||||
@@ -23,7 +23,11 @@
|
|||||||
{% endblocktrans %} ::
|
{% endblocktrans %} ::
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% elif subevent %}
|
{% elif subevent %}
|
||||||
{{ subevent.get_date_range_display }} ::
|
{% if subevent.name|upper != event.name|upper %}
|
||||||
|
{# The |upper is a trick to force LazyI18nString→str conversion before comparison #}
|
||||||
|
{{ subevent.name }} ::
|
||||||
|
{% endif %}
|
||||||
|
{{ subevent.get_date_range_display_with_times }} ::
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
{% load escapejson %}
|
{% load escapejson %}
|
||||||
|
|
||||||
{% if payment_qr_codes %}
|
{% if payment_qr_codes %}
|
||||||
<div class="tabcontainer col-md-6 col-sm-6 hidden-xs text-center js-only blank-after">
|
<div class="tabcontainer col-md-6 col-sm-6 col-xs-12 text-center js-only blank-after">
|
||||||
<div id="banktransfer_qrcodes_tabs_content" class="tabpanels blank-after">
|
<div id="banktransfer_qrcodes_tabs_content" class="tabpanels blank-after">
|
||||||
{% for code_info in payment_qr_codes %}
|
{% for code_info in payment_qr_codes %}
|
||||||
<div id="banktransfer_qrcodes_{{ code_info.id }}"
|
<div id="banktransfer_qrcodes_{{ code_info.id }}"
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ from pretix.base.models import Customer, InvoiceAddress, Order, OrderPosition
|
|||||||
from pretix.base.services.mail import mail
|
from pretix.base.services.mail import mail
|
||||||
from pretix.base.settings import PERSON_NAME_SCHEMES
|
from pretix.base.settings import PERSON_NAME_SCHEMES
|
||||||
from pretix.base.signals import customer_created, customer_signed_in
|
from pretix.base.signals import customer_created, customer_signed_in
|
||||||
|
from pretix.helpers import OF_SELF
|
||||||
from pretix.helpers.compat import CompatDeleteView
|
from pretix.helpers.compat import CompatDeleteView
|
||||||
from pretix.helpers.http import redirect_to_url
|
from pretix.helpers.http import redirect_to_url
|
||||||
from pretix.multidomain.models import KnownDomain
|
from pretix.multidomain.models import KnownDomain
|
||||||
@@ -280,6 +281,11 @@ class SetPasswordView(FormView):
|
|||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
|
# Re-check token in transaction to prevent race condition
|
||||||
|
self.customer = Customer.objects.select_for_update(of=OF_SELF).get(pk=self.customer.pk)
|
||||||
|
if not TokenGenerator().check_token(self.customer, self.request.GET.get('token', '')):
|
||||||
|
return HttpResponseRedirect(self.get_success_url())
|
||||||
|
|
||||||
self.customer.set_password(form.cleaned_data['password'])
|
self.customer.set_password(form.cleaned_data['password'])
|
||||||
self.customer.is_verified = True
|
self.customer.is_verified = True
|
||||||
self.customer.save()
|
self.customer.save()
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ from pretix.presale.signals import seatingframe_html_head
|
|||||||
from pretix.presale.views.organizer import (
|
from pretix.presale.views.organizer import (
|
||||||
EventListMixin, add_subevents_for_days, days_for_template,
|
EventListMixin, add_subevents_for_days, days_for_template,
|
||||||
filter_qs_by_attr, filter_subevents_with_plugins, has_before_after,
|
filter_qs_by_attr, filter_subevents_with_plugins, has_before_after,
|
||||||
weeks_for_template,
|
should_hide_subevent, weeks_for_template,
|
||||||
)
|
)
|
||||||
|
|
||||||
from . import (
|
from . import (
|
||||||
@@ -443,12 +443,10 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
subevents = filter_subevents_with_plugins(list(subevents), self.request.sales_channel)
|
subevents = filter_subevents_with_plugins(list(subevents), self.request.sales_channel)
|
||||||
context['subevent_list'] = subevents
|
context['subevent_list'] = [
|
||||||
if self.request.event.settings.event_list_available_only and not voucher:
|
se for se in subevents
|
||||||
context['subevent_list'] = [
|
if not should_hide_subevent(self.request.event.settings, se, voucher)
|
||||||
se for se in subevents
|
]
|
||||||
if not se.presale_has_ended and (se.best_availability_state is None or se.best_availability_state >= Quota.AVAILABILITY_RESERVED)
|
|
||||||
]
|
|
||||||
context['visible_events'] = len(subevents) > 0
|
context['visible_events'] = len(subevents) > 0
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|||||||
@@ -601,6 +601,32 @@ def filter_subevents_with_plugins(subevents, sales_channel=None):
|
|||||||
return subevents
|
return subevents
|
||||||
|
|
||||||
|
|
||||||
|
def should_hide_subevent(settings, subevent, voucher=None):
|
||||||
|
hide = False
|
||||||
|
if settings.event_list_available_only:
|
||||||
|
hide = (
|
||||||
|
# Presale is over → the subevent is not available → hide
|
||||||
|
subevent.presale_has_ended or
|
||||||
|
# Not a single product is available on this sales channel → hide
|
||||||
|
# Note that means there could be products which are ignored for calendar availability (Quota.ignore_for_event_availability)
|
||||||
|
# or products only visible with a voucher. However, for customers with these scenarios, the event_list_available_only
|
||||||
|
# makes only very little sense as it would never do anything, so the flag can just be removed -- or the products should
|
||||||
|
# be made visible so people know why there are no products. In case a voucher is already entered on the calendar view,
|
||||||
|
# this is already respected and subevents are shown correctly.
|
||||||
|
subevent.best_availability_state is None or
|
||||||
|
(
|
||||||
|
# Sold out → hide, unless we have a voucher active that can bypass all quotas
|
||||||
|
(not voucher or not voucher.allow_ignore_quota) and
|
||||||
|
subevent.best_availability_state < Quota.AVAILABILITY_RESERVED
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if settings.event_calendar_future_only:
|
||||||
|
if (subevent.date_to or subevent.date_from) < time_machine_now():
|
||||||
|
hide = True
|
||||||
|
return hide
|
||||||
|
|
||||||
|
|
||||||
def add_subevents_for_days(qs, before, after, ebd, timezones, sales_channel, event=None, cart_namespace=None,
|
def add_subevents_for_days(qs, before, after, ebd, timezones, sales_channel, event=None, cart_namespace=None,
|
||||||
voucher=None):
|
voucher=None):
|
||||||
qs = qs.filter(active=True, is_public=True).filter(
|
qs = qs.filter(active=True, is_public=True).filter(
|
||||||
@@ -640,19 +666,8 @@ def add_subevents_for_days(qs, before, after, ebd, timezones, sales_channel, eve
|
|||||||
kwargs['cart_namespace'] = cart_namespace
|
kwargs['cart_namespace'] = cart_namespace
|
||||||
|
|
||||||
s = event.settings if event else se.event.settings
|
s = event.settings if event else se.event.settings
|
||||||
|
if should_hide_subevent(s, se, voucher):
|
||||||
if s.event_list_available_only:
|
continue
|
||||||
hide = se.presale_has_ended or (
|
|
||||||
(not voucher or not voucher.allow_ignore_quota) and
|
|
||||||
se.best_availability_state is not None and
|
|
||||||
se.best_availability_state < Quota.AVAILABILITY_RESERVED
|
|
||||||
)
|
|
||||||
if hide:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if s.event_calendar_future_only:
|
|
||||||
if (se.date_to or se.date_from) < time_machine_now():
|
|
||||||
continue
|
|
||||||
|
|
||||||
timezones.add(s.timezone)
|
timezones.add(s.timezone)
|
||||||
tz = ZoneInfo(s.timezone)
|
tz = ZoneInfo(s.timezone)
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ from pretix.presale.views.cart import get_or_create_cart_id
|
|||||||
from pretix.presale.views.organizer import (
|
from pretix.presale.views.organizer import (
|
||||||
EventListMixin, add_events_for_days, add_subevents_for_days,
|
EventListMixin, add_events_for_days, add_subevents_for_days,
|
||||||
days_for_template, filter_qs_by_attr, filter_subevents_with_plugins,
|
days_for_template, filter_qs_by_attr, filter_subevents_with_plugins,
|
||||||
weeks_for_template,
|
should_hide_subevent, weeks_for_template,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -122,19 +122,20 @@ def widget_css_etag(request, version, **kwargs):
|
|||||||
return f'{_get_source_cache_key(version)}-{request.organizer.cache.get_or_set("css_version", default=lambda: int(time.time()))}'
|
return f'{_get_source_cache_key(version)}-{request.organizer.cache.get_or_set("css_version", default=lambda: int(time.time()))}'
|
||||||
|
|
||||||
|
|
||||||
|
# use vite by default, serve old vue2-based widget only for widget_vue2_origins
|
||||||
def _use_vite(request):
|
def _use_vite(request):
|
||||||
if getattr(settings, 'PRETIX_WIDGET_VITE', False) or "beta" in request.GET:
|
if getattr(settings, 'PRETIX_WIDGET_VUE', False) or "legacy" in request.GET:
|
||||||
return True
|
return False
|
||||||
origin = request.META.get('HTTP_ORIGIN', '')
|
origin = request.META.get('HTTP_ORIGIN', '')
|
||||||
gs = GlobalSettingsObject()
|
gs = GlobalSettingsObject()
|
||||||
vite_origins = gs.settings.get('widget_vite_origins', as_type=str, default='')
|
vue_origins = gs.settings.get('widget_vue2_origins', as_type=str, default='')
|
||||||
if vite_origins and not origin:
|
if vue_origins and not origin:
|
||||||
referer = request.META.get('HTTP_REFERER', '')
|
referer = request.META.get('HTTP_REFERER', '')
|
||||||
origin = '/'.join(referer.split('/', 3)[:3])
|
origin = '/'.join(referer.split('/', 3)[:3])
|
||||||
if origin and vite_origins:
|
if origin and vue_origins:
|
||||||
origins_list = [o.strip() for o in vite_origins.strip().splitlines() if o.strip()]
|
origins_list = [o.strip() for o in vue_origins.strip().splitlines() if o.strip()]
|
||||||
return origin in origins_list
|
return origin not in origins_list
|
||||||
return False
|
return True
|
||||||
|
|
||||||
|
|
||||||
def widget_js_etag(request, version, lang, **kwargs):
|
def widget_js_etag(request, version, lang, **kwargs):
|
||||||
@@ -757,14 +758,10 @@ class WidgetAPIProductList(EventListMixin, View):
|
|||||||
evs = evs[:limit]
|
evs = evs[:limit]
|
||||||
|
|
||||||
tz = request.event.timezone
|
tz = request.event.timezone
|
||||||
if self.request.event.settings.event_list_available_only:
|
evs = [
|
||||||
evs = [
|
se for se in evs
|
||||||
se for se in evs
|
if not should_hide_subevent(self.request.event.settings, se)
|
||||||
if not se.presale_has_ended and (
|
]
|
||||||
se.best_availability_state is not None and
|
|
||||||
se.best_availability_state >= Quota.AVAILABILITY_RESERVED
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
data['events'] = [
|
data['events'] = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -913,7 +913,7 @@ VITE_DEV_SERVER_PORT = 5173
|
|||||||
VITE_DEV_SERVER = f"http://localhost:{VITE_DEV_SERVER_PORT}"
|
VITE_DEV_SERVER = f"http://localhost:{VITE_DEV_SERVER_PORT}"
|
||||||
VITE_DEV_MODE = DEBUG
|
VITE_DEV_MODE = DEBUG
|
||||||
VITE_IGNORE = False # Used to ignore `collectstatic`/`rebuild`
|
VITE_IGNORE = False # Used to ignore `collectstatic`/`rebuild`
|
||||||
PRETIX_WIDGET_VITE = os.environ.get('PRETIX_WIDGET_VITE', '') not in ('', '0')
|
PRETIX_WIDGET_VUE = os.environ.get('PRETIX_WIDGET_VUE', '') not in ('', '0')
|
||||||
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
# Reload if settings file changes
|
# Reload if settings file changes
|
||||||
|
|||||||
@@ -1,195 +1,193 @@
|
|||||||
$(function () {
|
$(function () {
|
||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
// Responses are expected to only depend on the GET parameters passed, so we can have a little client-side cache
|
// Responses are expected to only depend on the GET parameters passed, so we can have a little client-side cache
|
||||||
// to prevent fetching the same thing many times.
|
// to prevent fetching the same thing many times.
|
||||||
var responseCache = {};
|
let responseCache = {}
|
||||||
|
|
||||||
const cleanName = (name) => {
|
const cleanName = (name) => {
|
||||||
// Remove form prefix
|
// Remove form prefix
|
||||||
name = name.split("-").pop();
|
name = name.split('-').pop()
|
||||||
// Remove settings prefix
|
// Remove settings prefix
|
||||||
name = name.replace(/^invoice_address_from_/, "");
|
name = name.replace(/^invoice_address_from_/, '')
|
||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
$("[data-address-information-url]").each(function () {
|
$('[data-address-information-url]').each(function () {
|
||||||
let xhr;
|
let xhr
|
||||||
const form = $(this);
|
const form = $(this)
|
||||||
const dependencies = $(this).find("[data-trigger-address-info]");
|
const dependencies = $(this).find('[data-trigger-address-info]')
|
||||||
const loader = $("<span class='fa fa-cog fa-spin'></span>").hide().prependTo(dependencies.closest(".form-group").find("label").first())
|
const loader = $('<span class=\'fa fa-cog fa-spin\'></span>').hide().prependTo(dependencies.closest('.form-group').find('label').first())
|
||||||
const baseUrl = this.getAttribute('data-address-information-url')
|
const baseUrl = this.getAttribute('data-address-information-url')
|
||||||
const isAnyRequired = dependencies.toArray().some(function (e) { return $(e).closest(".form-group").is(".required") });
|
const isAnyRequired = dependencies.toArray().some(function (e) { return $(e).closest('.form-group').is('.required') })
|
||||||
|
|
||||||
const dependents = {
|
const dependents = {
|
||||||
'city': form.find("input[name$=city]"),
|
city: form.find('input[name$=city]'),
|
||||||
'zipcode': form.find("input[name$=zipcode]"),
|
zipcode: form.find('input[name$=zipcode]'),
|
||||||
'street': form.find("textarea[name$=street]"),
|
street: form.find('textarea[name$=street]'),
|
||||||
'state': form.find("select[name$=state]"),
|
state: form.find('select[name$=state]'),
|
||||||
'vat_id': form.find("input[name$=vat_id]"),
|
vat_id: form.find('input[name$=vat_id]'),
|
||||||
};
|
}
|
||||||
|
|
||||||
form.find("select[name*=transmission_], textarea[name*=transmission_], input[name*=transmission_]").each(function () {
|
form.find('select[name*=transmission_], textarea[name*=transmission_], input[name*=transmission_]').each(function () {
|
||||||
dependents[cleanName($(this).attr("name"))] = $(this)
|
dependents[cleanName($(this).attr('name'))] = $(this)
|
||||||
})
|
})
|
||||||
|
|
||||||
const dependentsDisabled = [];
|
const dependentsDisabled = []
|
||||||
for (var k in dependents) {
|
for (let k in dependents) {
|
||||||
if (dependents[k].prop("disabled")) {
|
if (dependents[k].prop('disabled')) {
|
||||||
dependentsDisabled.push(k);
|
dependentsDisabled.push(k)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Object.values(dependents).some((el) => el.length)) {
|
if (!Object.values(dependents).some((el) => el.length)) {
|
||||||
// No address fields found, do not create request
|
// No address fields found, do not create request
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const update_form = function (data) {
|
const update_form = function (data) {
|
||||||
var selected_state = dependents.state.prop("data-selected-value");
|
let selected_state = dependents.state.prop('data-selected-value')
|
||||||
if (selected_state) dependents.state.prop("data-selected-value", "");
|
if (selected_state) dependents.state.prop('data-selected-value', '')
|
||||||
dependents.state.find("option:not([value=''])").remove();
|
dependents.state.find('option:not([value=\'\'])').remove()
|
||||||
$.each(data.data, function (k, s) {
|
$.each(data.data, function (k, s) {
|
||||||
var o = $("<option>").attr("value", s.code).text(s.name);
|
let o = $('<option>').attr('value', s.code).text(s.name)
|
||||||
if (selected_state === s.code) o.prop("selected", true);
|
if (selected_state === s.code) o.prop('selected', true)
|
||||||
dependents.state.append(o);
|
dependents.state.append(o)
|
||||||
});
|
})
|
||||||
|
|
||||||
if (dependents.transmission_type) {
|
if (dependents.transmission_type) {
|
||||||
var selected_transmission_type = dependents.transmission_type.prop("data-selected-value");
|
let selected_transmission_type = dependents.transmission_type.prop('data-selected-value')
|
||||||
if (selected_transmission_type) dependents.transmission_type.prop("data-selected-value", "");
|
if (selected_transmission_type) dependents.transmission_type.prop('data-selected-value', '')
|
||||||
dependents.transmission_type.find("option:not([value='']):not([value='-'])").remove();
|
dependents.transmission_type.find('option:not([value=\'\']):not([value=\'-\'])').remove()
|
||||||
|
|
||||||
if (!data.transmission_type.visible) {
|
if (!data.transmission_type.visible) {
|
||||||
selected_transmission_type = "email";
|
selected_transmission_type = 'email'
|
||||||
}
|
}
|
||||||
|
|
||||||
$.each(data.transmission_types, function (k, s) {
|
$.each(data.transmission_types, function (k, s) {
|
||||||
var o = $("<option>").attr("value", s.code).text(s.name);
|
let o = $('<option>').attr('value', s.code).text(s.name)
|
||||||
if (selected_transmission_type === s.code) {
|
if (selected_transmission_type === s.code) {
|
||||||
o.prop("selected", true);
|
o.prop('selected', true)
|
||||||
}
|
}
|
||||||
dependents.transmission_type.append(o);
|
dependents.transmission_type.append(o)
|
||||||
});
|
})
|
||||||
|
}
|
||||||
|
|
||||||
}
|
for (var k in dependents) {
|
||||||
|
const options = data[k],
|
||||||
|
dependent = dependents[k]
|
||||||
|
let visible = 'visible' in options ? options.visible : true
|
||||||
|
|
||||||
for (var k in dependents) {
|
if (dependent.is('[data-display-dependency]')) {
|
||||||
const options = data[k],
|
const dependency = $(dependent.attr('data-display-dependency'))
|
||||||
dependent = dependents[k];
|
visible = visible && (
|
||||||
let visible = 'visible' in options ? options.visible : true;
|
(dependency.attr('type') === 'checkbox' || dependency.attr('type') === 'radio') ? dependency.prop('checked') : !!dependency.val()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (dependent.is("[data-display-dependency]")) {
|
if ('label' in options) {
|
||||||
const dependency = $(dependent.attr("data-display-dependency"));
|
dependent.closest('.form-group').find('.control-label').text(options.label)
|
||||||
visible = visible && (
|
}
|
||||||
(dependency.attr("type") === 'checkbox' || dependency.attr("type") === 'radio') ? dependency.prop('checked') : !!dependency.val()
|
if ('helptext_visible' in options) {
|
||||||
);
|
dependent.closest('.form-group').find('.help-block').toggle(options.helptext_visible)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('label' in options) {
|
const required = 'required' in options && visible && (
|
||||||
dependent.closest(".form-group").find(".control-label").text(options.label);
|
(options.required === 'if_any' && isAnyRequired)
|
||||||
}
|
|| (options.required === true)
|
||||||
if ('helptext_visible' in options) {
|
)
|
||||||
dependent.closest(".form-group").find(".help-block").toggle(options.helptext_visible);
|
dependent.closest('.form-group').toggle(visible).toggleClass('required', required)
|
||||||
}
|
dependent.prop('required', required)
|
||||||
|
|
||||||
const required = 'required' in options && visible && (
|
const label = dependent.closest('.form-group').find('label')
|
||||||
(options.required === 'if_any' && isAnyRequired) ||
|
const labelRequired = label.find('.label-required')
|
||||||
(options.required === true)
|
if (!required) {
|
||||||
);
|
labelRequired.remove()
|
||||||
dependent.closest(".form-group").toggle(visible).toggleClass('required', required);
|
} else if (!labelRequired.length) {
|
||||||
dependent.prop("required", required);
|
label.append('<i class="label-required">' + gettext('required') + '</i>')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (var k in dependents) dependents[k].prop('disabled', dependentsDisabled.includes(k))
|
||||||
|
loader.hide()
|
||||||
|
}
|
||||||
|
|
||||||
const label = dependent.closest(".form-group").find("label");
|
const update = function (ev) {
|
||||||
const labelRequired = label.find(".label-required");
|
dependents.state.prop('data-selected-value', dependents.state.val())
|
||||||
if (!required) {
|
if (dependents.transmission_type) {
|
||||||
labelRequired.remove();
|
dependents.transmission_type.prop('data-selected-value', dependents.transmission_type.val())
|
||||||
} else if (!labelRequired.length) {
|
}
|
||||||
label.append('<i class="label-required">' + gettext('required') + '</i>')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (var k in dependents) dependents[k].prop("disabled", dependentsDisabled.includes(k));
|
|
||||||
loader.hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
const update = function (ev) {
|
for (let k in dependents) dependents[k].prop('disabled', true)
|
||||||
dependents.state.prop("data-selected-value", dependents.state.val());
|
loader.show()
|
||||||
if (dependents.transmission_type) {
|
let url = new URL(baseUrl, location.href)
|
||||||
dependents.transmission_type.prop("data-selected-value", dependents.transmission_type.val());
|
// Address depends on all annotated fields
|
||||||
}
|
form.find('[data-trigger-address-info]').each(function () {
|
||||||
|
// Remove prefix of the form to get actual field name
|
||||||
|
if (($(this).attr('type') === 'radio' || $(this).attr('type') === 'checkbox') && !$(this).prop('checked')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
url.searchParams.append(cleanName($(this).attr('name')), $(this).val())
|
||||||
|
})
|
||||||
|
if (dependents.transmission_type) {
|
||||||
|
url.searchParams.append('transmission_type_required', !dependents.transmission_type.find('option[value=\'-\']').length)
|
||||||
|
}
|
||||||
|
|
||||||
for (var k in dependents) dependents[k].prop("disabled", true);
|
if (xhr && url in responseCache) {
|
||||||
loader.show();
|
if (responseCache[url] == xhr) {
|
||||||
var url = new URL(baseUrl, location.href);
|
// already requested this, but XHR is still running and will resolve promise
|
||||||
// Address depends on all annotated fields
|
// only re-resolve promise for JSON-data in responseCache[url]
|
||||||
form.find("[data-trigger-address-info]").each(function () {
|
return
|
||||||
// Remove prefix of the form to get actual field name
|
} else {
|
||||||
if (($(this).attr("type") === "radio" || $(this).attr("type") === "checkbox") && !$(this).prop("checked")) {
|
// abort current xhr as it is not the one we want
|
||||||
return
|
// aborting deletes responseCache[url] but async
|
||||||
}
|
xhr.abort()
|
||||||
url.searchParams.append(cleanName($(this).attr("name")), $(this).val());
|
}
|
||||||
})
|
}
|
||||||
if (dependents.transmission_type) {
|
|
||||||
url.searchParams.append("transmission_type_required", !dependents.transmission_type.find("option[value='-']").length);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (xhr && url in responseCache) {
|
if (!(url in responseCache)) {
|
||||||
if (responseCache[url] == xhr) {
|
responseCache[url] = xhr = $.ajax({
|
||||||
// already requested this, but XHR is still running and will resolve promise
|
dataType: 'json',
|
||||||
// only re-resolve promise for JSON-data in responseCache[url]
|
url: url,
|
||||||
return;
|
timeout: 3000,
|
||||||
} else {
|
})
|
||||||
// abort current xhr as it is not the one we want
|
}
|
||||||
// aborting deletes responseCache[url] but async
|
|
||||||
xhr.abort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(url in responseCache)) {
|
Promise.resolve(responseCache[url]).then(function (data) {
|
||||||
responseCache[url] = xhr = $.ajax({
|
responseCache[url] = data
|
||||||
dataType: "json",
|
update_form(data)
|
||||||
url: url,
|
}).catch(function () {
|
||||||
timeout: 3000,
|
delete responseCache[url]
|
||||||
});
|
// In case of errors, show everything and require nothing, we can still handle errors in backend
|
||||||
}
|
for (let k in dependents) {
|
||||||
|
const dependent = dependents[k],
|
||||||
|
visible = true,
|
||||||
|
required = false
|
||||||
|
|
||||||
Promise.resolve(responseCache[url]).then(function (data) {
|
dependent.closest('.form-group').toggle(visible).toggleClass('required', required)
|
||||||
responseCache[url] = data;
|
dependent.prop('required', required).prop('disabled', dependentsDisabled.includes(k))
|
||||||
update_form(data);
|
}
|
||||||
}).catch(function () {
|
}).finally(function () {
|
||||||
delete responseCache[url];
|
loader.hide()
|
||||||
// In case of errors, show everything and require nothing, we can still handle errors in backend
|
})
|
||||||
for (var k in dependents) {
|
}
|
||||||
const dependent = dependents[k],
|
update()
|
||||||
visible = true,
|
dependencies.on('change', update)
|
||||||
required = false;
|
|
||||||
|
|
||||||
dependent.closest(".form-group").toggle(visible).toggleClass('required', required);
|
if (dependents.vat_id && dependents.transmission_type && dependents.transmission_peppol_participant_id) {
|
||||||
dependent.prop("required", required).prop("disabled", dependentsDisabled.includes(k));
|
// In Belgium, the VAT ID is built from "BE" + the company ID. The Peppol ID also needs to be built
|
||||||
}
|
// from the company ID with ID scheme 0208. We can save users some knowing and typing by filling this in!
|
||||||
}).finally(function () {
|
if (!dependents.transmission_peppol_participant_id.val()) {
|
||||||
loader.hide();
|
const fill_peppol_id = function () {
|
||||||
});
|
const vatId = dependents.vat_id.val()
|
||||||
};
|
if (vatId && vatId.startsWith('BE') && dependents.transmission_type.val() === 'peppol') {
|
||||||
update();
|
dependents.transmission_peppol_participant_id.val('0208:' + vatId.substring(2).replaceAll('.', ''))
|
||||||
dependencies.on("change", update);
|
}
|
||||||
|
}
|
||||||
if (dependents.vat_id && dependents.transmission_type && dependents.transmission_peppol_participant_id) {
|
dependents.vat_id.add(dependents.transmission_type).on('change', fill_peppol_id)
|
||||||
// In Belgium, the VAT ID is built from "BE" + the company ID. The Peppol ID also needs to be built
|
dependents.transmission_peppol_participant_id.one('change', () => {
|
||||||
// from the company ID with ID scheme 0208. We can save users some knowing and typing by filling this in!
|
dependents.vat_id.add(dependents.transmission_type).unbind('change', fill_peppol_id)
|
||||||
if (!dependents.transmission_peppol_participant_id.val()) {
|
})
|
||||||
const fill_peppol_id = function () {
|
}
|
||||||
const vatId = dependents.vat_id.val();
|
}
|
||||||
if (vatId && vatId.startsWith("BE") && dependents.transmission_type.val() === "peppol") {
|
})
|
||||||
dependents.transmission_peppol_participant_id.val("0208:" + vatId.substring(2).replaceAll(".", ""))
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
dependents.vat_id.add(dependents.transmission_type).on("change", fill_peppol_id);
|
|
||||||
dependents.transmission_peppol_participant_id.one("change", () => {
|
|
||||||
dependents.vat_id.add(dependents.transmission_type).unbind("change", fill_peppol_id)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
var check = function () {
|
let check = function () {
|
||||||
$.getJSON(location.href + '&ajax=1', function (data, status) {
|
$.getJSON(location.href + '&ajax=1', function (data, _status) {
|
||||||
if (data.redirect) {
|
if (data.redirect) {
|
||||||
location.href = data.redirect;
|
location.href = data.redirect
|
||||||
} else {
|
} else {
|
||||||
window.setTimeout(check, 500);
|
window.setTimeout(check, 500)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
window.setTimeout(check, 500);
|
window.setTimeout(check, 500)
|
||||||
|
|||||||
@@ -1,359 +1,357 @@
|
|||||||
/*global $, gettext */
|
/* global gettext */
|
||||||
var async_task_id = null;
|
let async_task_id = null
|
||||||
var async_task_timeout = null;
|
let async_task_timeout = null
|
||||||
var async_task_check_url = null;
|
let async_task_check_url = null
|
||||||
var async_task_old_url = null;
|
let async_task_old_url = null
|
||||||
var async_task_is_download = false;
|
let async_task_is_download = false
|
||||||
var async_task_is_long = false;
|
let async_task_is_long = false
|
||||||
var async_task_dont_redirect = false;
|
let async_task_dont_redirect = false
|
||||||
|
|
||||||
var async_task_status_messages = {
|
let async_task_status_messages = {
|
||||||
// These are functions in order to be lazily evaluated after the gettext file is loaded
|
// These are functions in order to be lazily evaluated after the gettext file is loaded
|
||||||
long_task_started: () => gettext(
|
long_task_started: () => gettext(
|
||||||
'Your request is currently being processed. Depending on the size of your event, this might take up to ' +
|
'Your request is currently being processed. Depending on the size of your event, this might take up to '
|
||||||
'a few minutes.'
|
+ 'a few minutes.'
|
||||||
),
|
),
|
||||||
long_task_pending: () => gettext(
|
long_task_pending: () => gettext(
|
||||||
'Your request has been queued on the server and will soon be ' +
|
'Your request has been queued on the server and will soon be '
|
||||||
'processed.'
|
+ 'processed.'
|
||||||
),
|
),
|
||||||
short_task: () => gettext(
|
short_task: () => gettext(
|
||||||
'Your request arrived on the server but we still wait for it to be ' +
|
'Your request arrived on the server but we still wait for it to be '
|
||||||
'processed. If this takes longer than two minutes, please contact us or go ' +
|
+ 'processed. If this takes longer than two minutes, please contact us or go '
|
||||||
'back in your browser and try again.'
|
+ 'back in your browser and try again.'
|
||||||
)
|
)
|
||||||
};
|
|
||||||
|
|
||||||
function async_task_schedule_check(context, timeout) {
|
|
||||||
"use strict";
|
|
||||||
async_task_timeout = window.setTimeout(function() {
|
|
||||||
$.ajax(
|
|
||||||
{
|
|
||||||
'type': 'GET',
|
|
||||||
'url': async_task_check_url,
|
|
||||||
'success': async_task_check_callback,
|
|
||||||
'error': async_task_check_error,
|
|
||||||
'context': context,
|
|
||||||
'dataType': 'json'
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}, timeout);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function async_task_on_success(data) {
|
function async_task_schedule_check (context, timeout) {
|
||||||
"use strict";
|
'use strict'
|
||||||
if ((async_task_is_download && data.success) || async_task_dont_redirect) {
|
async_task_timeout = window.setTimeout(function () {
|
||||||
waitingDialog.hide();
|
$.ajax(
|
||||||
if (location.href.indexOf("async_id") !== -1) {
|
{
|
||||||
history.replaceState({}, "pretix", async_task_old_url);
|
type: 'GET',
|
||||||
}
|
url: async_task_check_url,
|
||||||
}
|
success: async_task_check_callback,
|
||||||
if (!async_task_dont_redirect) {
|
error: async_task_check_error,
|
||||||
$(window).one("pageshow", function (e) {
|
context: context,
|
||||||
// hide waitingDialog when using browser's history back
|
dataType: 'json'
|
||||||
waitingDialog.hide();
|
}
|
||||||
});
|
)
|
||||||
if (async_task_is_download && window.self !== window.top) {
|
}, timeout)
|
||||||
// if in an iframe, force to download an async_task_is_download
|
|
||||||
// e.g. pretix-reseller embeds order-page in iframe, which would cause ticket-PDFs to be displayed inline
|
|
||||||
var a = document.createElement("a");
|
|
||||||
a.href = data.redirect;
|
|
||||||
a.download = "";
|
|
||||||
a.target = "_blank";
|
|
||||||
a.click();
|
|
||||||
} else {
|
|
||||||
location.href = data.redirect;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$(this).trigger('pretix:async-task-success', data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function async_task_check_callback(data, textStatus, jqXHR) {
|
function async_task_on_success (data) {
|
||||||
"use strict";
|
'use strict'
|
||||||
if (data.ready && data.redirect) {
|
if ((async_task_is_download && data.success) || async_task_dont_redirect) {
|
||||||
async_task_on_success.call(this, data);
|
waitingDialog.hide()
|
||||||
return;
|
if (location.href.indexOf('async_id') !== -1) {
|
||||||
}
|
history.replaceState({}, 'pretix', async_task_old_url)
|
||||||
|
}
|
||||||
if (typeof data.percentage === "number") {
|
}
|
||||||
waitingDialog.setProgress(data.percentage);
|
if (!async_task_dont_redirect) {
|
||||||
}
|
$(window).one('pageshow', function (e) {
|
||||||
if (typeof data.steps === "object" && Array.isArray(data.steps)) {
|
// hide waitingDialog when using browser's history back
|
||||||
waitingDialog.setSteps(data.steps);
|
waitingDialog.hide()
|
||||||
}
|
})
|
||||||
async_task_schedule_check(this, 250);
|
if (async_task_is_download && window.self !== window.top) {
|
||||||
|
// if in an iframe, force to download an async_task_is_download
|
||||||
async_task_update_status(data);
|
// e.g. pretix-reseller embeds order-page in iframe, which would cause ticket-PDFs to be displayed inline
|
||||||
|
let a = document.createElement('a')
|
||||||
|
a.href = data.redirect
|
||||||
|
a.download = ''
|
||||||
|
a.target = '_blank'
|
||||||
|
a.click()
|
||||||
|
} else {
|
||||||
|
location.href = data.redirect
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$(this).trigger('pretix:async-task-success', data)
|
||||||
}
|
}
|
||||||
|
|
||||||
function async_task_update_status(data) {
|
function async_task_check_callback (data, textStatus, jqXHR) {
|
||||||
if (async_task_is_long) {
|
'use strict'
|
||||||
if (data.started) {
|
if (data.ready && data.redirect) {
|
||||||
waitingDialog.setStatus(async_task_status_messages.long_task_started());
|
async_task_on_success.call(this, data)
|
||||||
} else {
|
return
|
||||||
waitingDialog.setStatus(async_task_status_messages.long_task_pending());
|
}
|
||||||
}
|
|
||||||
} else {
|
if (typeof data.percentage === 'number') {
|
||||||
waitingDialog.setStatus(async_task_status_messages.short_task());
|
waitingDialog.setProgress(data.percentage)
|
||||||
}
|
}
|
||||||
|
if (typeof data.steps === 'object' && Array.isArray(data.steps)) {
|
||||||
|
waitingDialog.setSteps(data.steps)
|
||||||
|
}
|
||||||
|
async_task_schedule_check(this, 250)
|
||||||
|
|
||||||
|
async_task_update_status(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
function async_task_replace_page(target, new_html) {
|
function async_task_update_status (data) {
|
||||||
"use strict";
|
if (async_task_is_long) {
|
||||||
waitingDialog.hide();
|
if (data.started) {
|
||||||
$(target).html(new_html);
|
waitingDialog.setStatus(async_task_status_messages.long_task_started())
|
||||||
setup_basics($(target));
|
} else {
|
||||||
form_handlers($(target));
|
waitingDialog.setStatus(async_task_status_messages.long_task_pending())
|
||||||
setup_collapsible_details($(target));
|
}
|
||||||
window.setTimeout(function () { $(window).scrollTop(0) }, 200)
|
} else {
|
||||||
$(document).trigger("pretix:bind-forms");
|
waitingDialog.setStatus(async_task_status_messages.short_task())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function async_task_check_error(jqXHR, textStatus, errorThrown) {
|
function async_task_replace_page (target, new_html) {
|
||||||
"use strict";
|
'use strict'
|
||||||
var respdom = $(jqXHR.responseText);
|
waitingDialog.hide()
|
||||||
var c = respdom.filter('.container');
|
$(target).html(new_html)
|
||||||
if (jqXHR.status === 401 && jqXHR.getResponseHeader("X-Login-Url")) {
|
setup_basics($(target))
|
||||||
window.location = jqXHR.getResponseHeader("X-Login-Url") + "?next=" + encodeURIComponent(location.pathname + location.search + location.hash);
|
form_handlers($(target))
|
||||||
return;
|
setup_collapsible_details($(target))
|
||||||
}
|
window.setTimeout(function () { $(window).scrollTop(0) }, 200)
|
||||||
if (respdom.filter('form') && (respdom.filter('.has-error') || respdom.filter('.alert-danger'))) {
|
$(document).trigger('pretix:bind-forms')
|
||||||
// This is a failed form validation, let's just use it
|
|
||||||
$("body").data('ajaxing', false);
|
|
||||||
async_task_replace_page("body", jqXHR.responseText.substring(
|
|
||||||
jqXHR.responseText.indexOf("<body"),
|
|
||||||
jqXHR.responseText.indexOf("</body")
|
|
||||||
));
|
|
||||||
} else if (c.length > 0) {
|
|
||||||
// This is some kind of 500/404/403 page, show it in an overlay
|
|
||||||
$("body").data('ajaxing', false);
|
|
||||||
waitingDialog.hide();
|
|
||||||
if (location.href.indexOf("async_id") !== -1) {
|
|
||||||
history.replaceState({}, "pretix", async_task_old_url);
|
|
||||||
}
|
|
||||||
ajaxErrDialog.show(c.first().html());
|
|
||||||
} else {
|
|
||||||
if (jqXHR.status >= 400 && jqXHR.status < 500) {
|
|
||||||
$("body").data('ajaxing', false);
|
|
||||||
waitingDialog.hide();
|
|
||||||
alert(gettext('An error of type {code} occurred.').replace(/\{code\}/, jqXHR.status));
|
|
||||||
} else {
|
|
||||||
// 500 can be an application error or overload in some cases :(
|
|
||||||
waitingDialog.setStatus(gettext('We currently cannot reach the server, but we keep trying.' +
|
|
||||||
' Last error code: {code}').replace(/\{code\}/, jqXHR.status));
|
|
||||||
async_task_schedule_check(this, 5000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function async_task_callback(data, jqXHR, status) {
|
function async_task_check_error (jqXHR, textStatus, errorThrown) {
|
||||||
"use strict";
|
'use strict'
|
||||||
$("body").data('ajaxing', false);
|
let respdom = $(jqXHR.responseText)
|
||||||
if (data.redirect) {
|
let c = respdom.filter('.container')
|
||||||
async_task_on_success.call(this, data);
|
if (jqXHR.status === 401 && jqXHR.getResponseHeader('X-Login-Url')) {
|
||||||
return;
|
window.location = jqXHR.getResponseHeader('X-Login-Url') + '?next=' + encodeURIComponent(location.pathname + location.search + location.hash)
|
||||||
}
|
return
|
||||||
var check_url = new URL(data.check_url, window.location);
|
}
|
||||||
if (async_task_dont_redirect) {
|
if (respdom.filter('form') && (respdom.filter('.has-error') || respdom.filter('.alert-danger'))) {
|
||||||
check_url.searchParams.set('ajax_dont_redirect', '1');
|
// This is a failed form validation, let's just use it
|
||||||
}
|
$('body').data('ajaxing', false)
|
||||||
async_task_id = data.async_id;
|
async_task_replace_page('body', jqXHR.responseText.substring(
|
||||||
async_task_check_url = check_url.toString();
|
jqXHR.responseText.indexOf('<body'),
|
||||||
async_task_schedule_check(this, 100);
|
jqXHR.responseText.indexOf('</body')
|
||||||
|
))
|
||||||
async_task_update_status(data);
|
} else if (c.length > 0) {
|
||||||
|
// This is some kind of 500/404/403 page, show it in an overlay
|
||||||
if (location.href.indexOf("async_id") === -1) {
|
$('body').data('ajaxing', false)
|
||||||
history.pushState({}, "Waiting", async_task_check_url.replace(/ajax=1/, ''));
|
waitingDialog.hide()
|
||||||
}
|
if (location.href.indexOf('async_id') !== -1) {
|
||||||
|
history.replaceState({}, 'pretix', async_task_old_url)
|
||||||
|
}
|
||||||
|
ajaxErrDialog.show(c.first().html())
|
||||||
|
} else {
|
||||||
|
if (jqXHR.status >= 400 && jqXHR.status < 500) {
|
||||||
|
$('body').data('ajaxing', false)
|
||||||
|
waitingDialog.hide()
|
||||||
|
alert(gettext('An error of type {code} occurred.').replace(/\{code\}/, jqXHR.status))
|
||||||
|
} else {
|
||||||
|
// 500 can be an application error or overload in some cases :(
|
||||||
|
waitingDialog.setStatus(gettext('We currently cannot reach the server, but we keep trying.'
|
||||||
|
+ ' Last error code: {code}').replace(/\{code\}/, jqXHR.status))
|
||||||
|
async_task_schedule_check(this, 5000)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function async_task_error(jqXHR, textStatus, errorThrown) {
|
function async_task_callback (data, jqXHR, status) {
|
||||||
"use strict";
|
'use strict'
|
||||||
$("body").data('ajaxing', false);
|
$('body').data('ajaxing', false)
|
||||||
if (jqXHR.status === 401 && jqXHR.getResponseHeader("X-Login-Url")) {
|
if (data.redirect) {
|
||||||
window.location = jqXHR.getResponseHeader("X-Login-Url") + "?next=" + encodeURIComponent(location.pathname + location.search + location.hash);
|
async_task_on_success.call(this, data)
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
waitingDialog.hide();
|
let check_url = new URL(data.check_url, window.location)
|
||||||
if (textStatus === "timeout") {
|
if (async_task_dont_redirect) {
|
||||||
alert(gettext("The request took too long. Please try again."));
|
check_url.searchParams.set('ajax_dont_redirect', '1')
|
||||||
} else if (jqXHR.responseText.indexOf('<html') > 0) {
|
}
|
||||||
var respdom = $(jqXHR.responseText);
|
async_task_id = data.async_id
|
||||||
var c = respdom.filter('.container');
|
async_task_check_url = check_url.toString()
|
||||||
if (respdom.filter('form') && (respdom.filter('.has-error') || respdom.filter('.alert-danger'))) {
|
async_task_schedule_check(this, 100)
|
||||||
if (respdom.filter('#page-wrapper') && $('#page-wrapper').length) {
|
|
||||||
// This is a failed form validation, let's just use it
|
|
||||||
async_task_replace_page("#page-wrapper", respdom.find("#page-wrapper").html());
|
|
||||||
} else {
|
|
||||||
async_task_replace_page("body", jqXHR.responseText.substring(
|
|
||||||
jqXHR.responseText.indexOf("<body"),
|
|
||||||
jqXHR.responseText.indexOf("</body")
|
|
||||||
));
|
|
||||||
document.dispatchEvent(new Event("pretix:async-task-error"))
|
|
||||||
|
|
||||||
}
|
async_task_update_status(data)
|
||||||
|
|
||||||
} else if (c.length > 0) {
|
if (location.href.indexOf('async_id') === -1) {
|
||||||
// This is some kind of 500/404/403 page, show it in an overlay
|
history.pushState({}, 'Waiting', async_task_check_url.replace(/ajax=1/, ''))
|
||||||
ajaxErrDialog.show(c.first().html());
|
}
|
||||||
} else {
|
}
|
||||||
alert(gettext('An error of type {code} occurred.').replace(/\{code\}/, jqXHR.status));
|
|
||||||
}
|
function async_task_error (jqXHR, textStatus, errorThrown) {
|
||||||
} else {
|
'use strict'
|
||||||
if (jqXHR.status >= 400 && jqXHR.status < 500) {
|
$('body').data('ajaxing', false)
|
||||||
alert(gettext('An error of type {code} occurred.').replace(/\{code\}/, jqXHR.status));
|
if (jqXHR.status === 401 && jqXHR.getResponseHeader('X-Login-Url')) {
|
||||||
} else {
|
window.location = jqXHR.getResponseHeader('X-Login-Url') + '?next=' + encodeURIComponent(location.pathname + location.search + location.hash)
|
||||||
alert(gettext('We currently cannot reach the server. Please try again. ' +
|
return
|
||||||
'Error code: {code}').replace(/\{code\}/, jqXHR.status));
|
}
|
||||||
}
|
waitingDialog.hide()
|
||||||
}
|
if (textStatus === 'timeout') {
|
||||||
|
alert(gettext('The request took too long. Please try again.'))
|
||||||
|
} else if (jqXHR.responseText.indexOf('<html') > 0) {
|
||||||
|
let respdom = $(jqXHR.responseText)
|
||||||
|
let c = respdom.filter('.container')
|
||||||
|
if (respdom.filter('form') && (respdom.filter('.has-error') || respdom.filter('.alert-danger'))) {
|
||||||
|
if (respdom.filter('#page-wrapper') && $('#page-wrapper').length) {
|
||||||
|
// This is a failed form validation, let's just use it
|
||||||
|
async_task_replace_page('#page-wrapper', respdom.find('#page-wrapper').html())
|
||||||
|
} else {
|
||||||
|
async_task_replace_page('body', jqXHR.responseText.substring(
|
||||||
|
jqXHR.responseText.indexOf('<body'),
|
||||||
|
jqXHR.responseText.indexOf('</body')
|
||||||
|
))
|
||||||
|
document.dispatchEvent(new Event('pretix:async-task-error'))
|
||||||
|
}
|
||||||
|
} else if (c.length > 0) {
|
||||||
|
// This is some kind of 500/404/403 page, show it in an overlay
|
||||||
|
ajaxErrDialog.show(c.first().html())
|
||||||
|
} else {
|
||||||
|
alert(gettext('An error of type {code} occurred.').replace(/\{code\}/, jqXHR.status))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (jqXHR.status >= 400 && jqXHR.status < 500) {
|
||||||
|
alert(gettext('An error of type {code} occurred.').replace(/\{code\}/, jqXHR.status))
|
||||||
|
} else {
|
||||||
|
alert(gettext('We currently cannot reach the server. Please try again. '
|
||||||
|
+ 'Error code: {code}').replace(/\{code\}/, jqXHR.status))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$(function () {
|
$(function () {
|
||||||
"use strict";
|
'use strict'
|
||||||
$("body").on('submit', 'form[data-asynctask]', function (e) {
|
$('body').on('submit', 'form[data-asynctask]', function (e) {
|
||||||
// Not supported on IE, may lead to wrong results, but we don't support IE in the backend anymore
|
// Not supported on IE, may lead to wrong results, but we don't support IE in the backend anymore
|
||||||
var submitter = e.originalEvent ? e.originalEvent.submitter : null;
|
let submitter = e.originalEvent ? e.originalEvent.submitter : null
|
||||||
|
|
||||||
if (submitter && submitter.hasAttribute("data-no-asynctask")) {
|
if (submitter && submitter.hasAttribute('data-no-asynctask')) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault()
|
||||||
$(this).removeClass("dirty"); // Avoid problems with are-you-sure.js
|
$(this).removeClass('dirty') // Avoid problems with are-you-sure.js
|
||||||
if ($("body").data('ajaxing')) {
|
if ($('body').data('ajaxing')) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
async_task_id = null;
|
async_task_id = null
|
||||||
async_task_is_download = $(this).is("[data-asynctask-download]");
|
async_task_is_download = $(this).is('[data-asynctask-download]')
|
||||||
async_task_dont_redirect = $(this).is("[data-asynctask-no-redirect]");
|
async_task_dont_redirect = $(this).is('[data-asynctask-no-redirect]')
|
||||||
async_task_is_long = $(this).is("[data-asynctask-long]");
|
async_task_is_long = $(this).is('[data-asynctask-long]')
|
||||||
async_task_old_url = location.href;
|
async_task_old_url = location.href
|
||||||
$("body").data('ajaxing', true);
|
$('body').data('ajaxing', true)
|
||||||
waitingDialog.show(
|
waitingDialog.show(
|
||||||
$(this).attr("data-asynctask-headline") || gettext('We are processing your request …'),
|
$(this).attr('data-asynctask-headline') || gettext('We are processing your request …'),
|
||||||
$(this).attr("data-asynctask-text") || '',
|
$(this).attr('data-asynctask-text') || '',
|
||||||
gettext(
|
gettext(
|
||||||
'We are currently sending your request to the server. If this takes longer ' +
|
'We are currently sending your request to the server. If this takes longer '
|
||||||
'than one minute, please check your internet connection and then reload ' +
|
+ 'than one minute, please check your internet connection and then reload '
|
||||||
'this page and try again.'
|
+ 'this page and try again.'
|
||||||
)
|
)
|
||||||
);
|
)
|
||||||
|
|
||||||
var action = this.action;
|
let action = this.action
|
||||||
var formData = new FormData(this);
|
let formData = new FormData(this)
|
||||||
formData.append('ajax', '1');
|
formData.append('ajax', '1')
|
||||||
if (async_task_dont_redirect) {
|
if (async_task_dont_redirect) {
|
||||||
formData.append('ajax_dont_redirect', '1');
|
formData.append('ajax_dont_redirect', '1')
|
||||||
}
|
}
|
||||||
if (submitter && submitter.name) {
|
if (submitter && submitter.name) {
|
||||||
formData.append(submitter.name, submitter.value);
|
formData.append(submitter.name, submitter.value)
|
||||||
}
|
}
|
||||||
if (submitter && submitter.getAttribute("formaction")) {
|
if (submitter && submitter.getAttribute('formaction')) {
|
||||||
action = submitter.getAttribute("formaction");
|
action = submitter.getAttribute('formaction')
|
||||||
}
|
}
|
||||||
$.ajax(
|
$.ajax(
|
||||||
{
|
{
|
||||||
'type': 'POST',
|
type: 'POST',
|
||||||
'url': action,
|
url: action,
|
||||||
'data': formData,
|
data: formData,
|
||||||
processData: false,
|
processData: false,
|
||||||
contentType: false,
|
contentType: false,
|
||||||
'success': async_task_callback,
|
success: async_task_callback,
|
||||||
'error': async_task_error,
|
error: async_task_error,
|
||||||
'context': this,
|
context: this,
|
||||||
'dataType': 'json',
|
dataType: 'json',
|
||||||
'timeout': 60000,
|
timeout: 60000,
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
});
|
})
|
||||||
|
|
||||||
window.addEventListener("pageshow", function (evt) {
|
window.addEventListener('pageshow', function (evt) {
|
||||||
// In Safari, if you submit an async task, then get redirected, then go back,
|
// In Safari, if you submit an async task, then get redirected, then go back,
|
||||||
// Safari won't reload the HTML from disk cache but instead reuse the DOM of the
|
// Safari won't reload the HTML from disk cache but instead reuse the DOM of the
|
||||||
// previous request, thus not clearing the "loading" state.
|
// previous request, thus not clearing the "loading" state.
|
||||||
if (evt.persisted && $("body").hasClass("loading")) {
|
if (evt.persisted && $('body').hasClass('loading')) {
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
window.location.reload();
|
window.location.reload()
|
||||||
}, 10);
|
}, 10)
|
||||||
}
|
}
|
||||||
}, false);
|
}, false)
|
||||||
|
|
||||||
$("#ajaxerr").on("click", ".ajaxerr-close", ajaxErrDialog.hide);
|
$('#ajaxerr').on('click', '.ajaxerr-close', ajaxErrDialog.hide)
|
||||||
$("#loadingmodal").on("cancel", function() {
|
$('#loadingmodal').on('cancel', function () {
|
||||||
return false;
|
return false
|
||||||
});
|
})
|
||||||
$("#loadingmodal").prop("closedBy", "none");
|
$('#loadingmodal').prop('closedBy', 'none')
|
||||||
});
|
})
|
||||||
|
|
||||||
var waitingDialog = {
|
var waitingDialog = {
|
||||||
show: function (title, text, status) {
|
show: function (title, text, status) {
|
||||||
"use strict";
|
'use strict'
|
||||||
this.setTitle(title);
|
this.setTitle(title)
|
||||||
this.setText(text);
|
this.setText(text)
|
||||||
this.setStatus(status || gettext('If this takes longer than a few minutes, please contact us.'));
|
this.setStatus(status || gettext('If this takes longer than a few minutes, please contact us.'))
|
||||||
this.setProgress(null);
|
this.setProgress(null)
|
||||||
this.setSteps(null);
|
this.setSteps(null)
|
||||||
document.getElementById("loadingmodal").showModal();
|
document.getElementById('loadingmodal').showModal()
|
||||||
},
|
},
|
||||||
hide: function () {
|
hide: function () {
|
||||||
"use strict";
|
'use strict'
|
||||||
document.getElementById("loadingmodal").close();
|
document.getElementById('loadingmodal').close()
|
||||||
},
|
},
|
||||||
setTitle: function(title) {
|
setTitle: function (title) {
|
||||||
$("#loadingmodal .modal-card-title").text(title);
|
$('#loadingmodal .modal-card-title').text(title)
|
||||||
},
|
},
|
||||||
setStatus: function(statusText) {
|
setStatus: function (statusText) {
|
||||||
$("#loadingmodal p.status").text(statusText);
|
$('#loadingmodal p.status').text(statusText)
|
||||||
},
|
},
|
||||||
setText: function(text) {
|
setText: function (text) {
|
||||||
if (text)
|
if (text)
|
||||||
$("#loadingmodal .modal-card-description").text(text).show();
|
$('#loadingmodal .modal-card-description').text(text).show()
|
||||||
else
|
else
|
||||||
$("#loadingmodal .modal-card-description").hide();
|
$('#loadingmodal .modal-card-description').hide()
|
||||||
},
|
},
|
||||||
setProgress: function(percentage) {
|
setProgress: function (percentage) {
|
||||||
if (typeof percentage === 'number') {
|
if (typeof percentage === 'number') {
|
||||||
$("#loadingmodal .progress").show();
|
$('#loadingmodal .progress').show()
|
||||||
$("#loadingmodal .progress .progress-bar").css("width", percentage + "%");
|
$('#loadingmodal .progress .progress-bar').css('width', percentage + '%')
|
||||||
} else {
|
} else {
|
||||||
$("#loadingmodal .progress").hide();
|
$('#loadingmodal .progress').hide()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setSteps: function(steps) {
|
setSteps: function (steps) {
|
||||||
var $steps = $("#loadingmodal .steps");
|
let $steps = $('#loadingmodal .steps')
|
||||||
if (steps) {
|
if (steps) {
|
||||||
$steps.html("").show()
|
$steps.html('').show()
|
||||||
for (var step of steps) {
|
for (let step of steps) {
|
||||||
$steps.append(
|
$steps.append(
|
||||||
$("<span>").addClass("fa fa-fw")
|
$('<span>').addClass('fa fa-fw')
|
||||||
.toggleClass("fa-check text-success", step.done)
|
.toggleClass('fa-check text-success', step.done)
|
||||||
.toggleClass("fa-cog fa-spin text-muted", !step.done)
|
.toggleClass('fa-cog fa-spin text-muted', !step.done)
|
||||||
).append(
|
).append(
|
||||||
$("<span>").text(step.label)
|
$('<span>').text(step.label)
|
||||||
).append(
|
).append(
|
||||||
$("<br>")
|
$('<br>')
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$steps.hide();
|
$steps.hide()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
var ajaxErrDialog = {
|
var ajaxErrDialog = {
|
||||||
show: function (c) {
|
show: function (c) {
|
||||||
"use strict";
|
'use strict'
|
||||||
$("#ajaxerr").html(c);
|
$('#ajaxerr').html(c)
|
||||||
$("#ajaxerr .links").html("<a class='btn btn-default ajaxerr-close'>"
|
$('#ajaxerr .links').html('<a class=\'btn btn-default ajaxerr-close\'>'
|
||||||
+ gettext("Close message") + "</a>");
|
+ gettext('Close message') + '</a>')
|
||||||
$("body").addClass("ajaxerr has-modal-dialog");
|
$('body').addClass('ajaxerr has-modal-dialog')
|
||||||
$("#ajaxerr").prop("hidden", false);
|
$('#ajaxerr').prop('hidden', false)
|
||||||
},
|
},
|
||||||
hide: function () {
|
hide: function () {
|
||||||
"use strict";
|
'use strict'
|
||||||
$("body").removeClass("ajaxerr has-modal-dialog");
|
$('body').removeClass('ajaxerr has-modal-dialog')
|
||||||
$("#ajaxerr").prop("hidden", true);
|
$('#ajaxerr').prop('hidden', true)
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
document.querySelectorAll('a[href^="mailto:"]').forEach(function(link) {
|
document.querySelectorAll('a[href^="mailto:"]').forEach(function (link) {
|
||||||
// Replace [at] with @ and the [dot] with . in both the href and the displayed text (if needed)
|
// Replace [at] with @ and the [dot] with . in both the href and the displayed text (if needed)
|
||||||
link.href = link.href.replace('[at]', '@').replace('[dot]', '.');
|
link.href = link.href.replace('[at]', '@').replaceAll('[dot]', '.')
|
||||||
link.textContent = link.textContent.replace('[at]', '@').replace('[dot]', '.');
|
link.textContent = link.textContent.replace('[at]', '@').replaceAll('[dot]', '.')
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|||||||
@@ -1,152 +1,149 @@
|
|||||||
/*global $ */
|
|
||||||
|
|
||||||
setup_collapsible_details = function (el) {
|
setup_collapsible_details = function (el) {
|
||||||
|
el.find('.sneak-peek-trigger').each(function () {
|
||||||
|
let trigger = this
|
||||||
|
let button = this.querySelector('button')
|
||||||
|
let content = document.getElementById(button.getAttribute('aria-controls'))
|
||||||
|
if (content.scrollHeight < 200) {
|
||||||
|
trigger.remove()
|
||||||
|
content.classList.remove('sneak-peek-content')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
content.setAttribute('aria-hidden', 'true')
|
||||||
|
content.setAttribute('inert', true)
|
||||||
|
button.setAttribute('aria-expanded', 'false')
|
||||||
|
button.addEventListener('click', function (e) {
|
||||||
|
button.setAttribute('aria-expanded', 'true')
|
||||||
|
content.setAttribute('aria-hidden', 'false')
|
||||||
|
content.removeAttribute('inert')
|
||||||
|
|
||||||
el.find('.sneak-peek-trigger').each(function() {
|
content.addEventListener('transitionend', function () {
|
||||||
var trigger = this;
|
content.classList.remove('sneak-peek-content')
|
||||||
var button = this.querySelector('button');
|
content.style.removeProperty('height')
|
||||||
var content = document.getElementById(button.getAttribute('aria-controls'));
|
// we need to keep the trigger/button in the DOM to not irritate screenreaders toggling visibility
|
||||||
if (content.scrollHeight < 200) {
|
trigger.classList.add('sr-only')
|
||||||
trigger.remove();
|
}, { once: true })
|
||||||
content.classList.remove('sneak-peek-content');
|
content.style.height = content.scrollHeight + 'px'
|
||||||
return;
|
|
||||||
}
|
|
||||||
content.setAttribute('aria-hidden', 'true');
|
|
||||||
content.setAttribute('inert', true);
|
|
||||||
button.setAttribute('aria-expanded', 'false');
|
|
||||||
button.addEventListener('click', function (e) {
|
|
||||||
button.setAttribute('aria-expanded', 'true');
|
|
||||||
content.setAttribute('aria-hidden', 'false');
|
|
||||||
content.removeAttribute('inert');
|
|
||||||
|
|
||||||
content.addEventListener('transitionend', function() {
|
button.addEventListener('click', function (e) {
|
||||||
content.classList.remove('sneak-peek-content');
|
// this will be called by screenreader users if they kept focus on the button after expanding
|
||||||
content.style.removeProperty('height');
|
// we need to keep the trigger/button in the DOM to not irritate screenreaders toggling visibility
|
||||||
// we need to keep the trigger/button in the DOM to not irritate screenreaders toggling visibility
|
let expanded = button.getAttribute('aria-expanded') == 'true'
|
||||||
trigger.classList.add('sr-only');
|
button.setAttribute('aria-expanded', !expanded)
|
||||||
}, {once: true});
|
content.setAttribute('aria-hidden', expanded)
|
||||||
content.style.height = content.scrollHeight + 'px';
|
})
|
||||||
|
button.addEventListener('blur', function (e) {
|
||||||
|
// if content is visible and the user leaves the button, we can safely remove the trigger/button
|
||||||
|
if (button.getAttribute('aria-expanded') == 'true') {
|
||||||
|
trigger.remove()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, { once: true })
|
||||||
|
|
||||||
button.addEventListener('click', function (e) {
|
let container = this.closest('details.sneak-peek-container')
|
||||||
// this will be called by screenreader users if they kept focus on the button after expanding
|
if (container) {
|
||||||
// we need to keep the trigger/button in the DOM to not irritate screenreaders toggling visibility
|
function removeSneekPeakWhenClosed (e) {
|
||||||
var expanded = button.getAttribute('aria-expanded') == 'true';
|
if (e.newState == 'closed') {
|
||||||
button.setAttribute('aria-expanded', !expanded);
|
container.removeEventListener('toggle', removeSneekPeakWhenClosed)
|
||||||
content.setAttribute('aria-hidden', expanded);
|
trigger.remove()
|
||||||
});
|
content.removeAttribute('aria-hidden')
|
||||||
button.addEventListener('blur', function (e) {
|
content.removeAttribute('inert')
|
||||||
// if content is visible and the user leaves the button, we can safely remove the trigger/button
|
content.classList.remove('sneak-peek-content')
|
||||||
if (button.getAttribute('aria-expanded') == 'true') {
|
}
|
||||||
trigger.remove();
|
}
|
||||||
}
|
container.addEventListener('toggle', removeSneekPeakWhenClosed)
|
||||||
});
|
}
|
||||||
}, { once: true });
|
})
|
||||||
|
|
||||||
var container = this.closest('details.sneak-peek-container');
|
let isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]'
|
||||||
if (container) {
|
el.find('details summary').click(function (e) {
|
||||||
function removeSneekPeakWhenClosed(e) {
|
if (this.tagName !== 'A' && $(e.target).closest('a').length > 0) {
|
||||||
if (e.newState == "closed") {
|
return true
|
||||||
container.removeEventListener("toggle", removeSneekPeakWhenClosed);
|
}
|
||||||
trigger.remove();
|
let $details = $(this).closest('details')
|
||||||
content.removeAttribute('aria-hidden');
|
let isOpen = $details.prop('open')
|
||||||
content.removeAttribute('inert');
|
let $detailsNotSummary = $details.children(':not(summary)')
|
||||||
content.classList.remove('sneak-peek-content');
|
if ($detailsNotSummary.is(':animated')) {
|
||||||
}
|
e.preventDefault()
|
||||||
}
|
return false
|
||||||
container.addEventListener("toggle", removeSneekPeakWhenClosed);
|
}
|
||||||
}
|
if (isOpen) {
|
||||||
});
|
$details.removeClass('details-open')
|
||||||
|
$detailsNotSummary.stop().show().slideUp(500, function () {
|
||||||
|
$details.prop('open', false)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
$detailsNotSummary.stop().hide()
|
||||||
|
$details.prop('open', true)
|
||||||
|
$details.addClass('details-open')
|
||||||
|
$detailsNotSummary.slideDown()
|
||||||
|
}
|
||||||
|
e.preventDefault()
|
||||||
|
return false
|
||||||
|
}).keyup(function (event) {
|
||||||
|
if (32 == event.keyCode || (13 == event.keyCode && !isOpera)) {
|
||||||
|
// Space or Enter is pressed — trigger the `click` event on the `summary` element
|
||||||
|
// Opera already seems to trigger the `click` event when Enter is pressed
|
||||||
|
event.preventDefault()
|
||||||
|
$(this).click()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]';
|
$('details').each(function () {
|
||||||
el.find("details summary").click(function (e) {
|
let $details = $(this),
|
||||||
if (this.tagName !== "A" && $(e.target).closest("a").length > 0) {
|
$detailsSummary = $('summary', $details).first(),
|
||||||
return true;
|
$detailsNotSummary = $details.children(':not(summary)')
|
||||||
}
|
$details.prop('open', typeof $details.attr('open') == 'string')
|
||||||
var $details = $(this).closest("details");
|
if (!$details.prop('open')) {
|
||||||
var isOpen = $details.prop("open");
|
if ($details.find('.has-error, .alert-danger').length) {
|
||||||
var $detailsNotSummary = $details.children(':not(summary)');
|
$details.addClass('details-open')
|
||||||
if ($detailsNotSummary.is(':animated')) {
|
$details.prop('open', true)
|
||||||
e.preventDefault();
|
} else {
|
||||||
return false;
|
$detailsNotSummary.hide()
|
||||||
}
|
}
|
||||||
if (isOpen) {
|
} else {
|
||||||
$details.removeClass("details-open");
|
$details.addClass('details-open')
|
||||||
$detailsNotSummary.stop().show().slideUp(500, function () {
|
}
|
||||||
$details.prop("open", false);
|
$detailsSummary.attr({
|
||||||
});
|
role: 'button',
|
||||||
} else {
|
'aria-controls': $details.attr('id')
|
||||||
$detailsNotSummary.stop().hide();
|
}).prop('tabIndex', 0).bind('selectstart dragstart mousedown', function () {
|
||||||
$details.prop("open", true);
|
return false
|
||||||
$details.addClass("details-open");
|
})
|
||||||
$detailsNotSummary.slideDown();
|
})
|
||||||
}
|
|
||||||
e.preventDefault();
|
|
||||||
return false;
|
|
||||||
}).keyup(function (event) {
|
|
||||||
if (32 == event.keyCode || (13 == event.keyCode && !isOpera)) {
|
|
||||||
// Space or Enter is pressed — trigger the `click` event on the `summary` element
|
|
||||||
// Opera already seems to trigger the `click` event when Enter is pressed
|
|
||||||
event.preventDefault();
|
|
||||||
$(this).click();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$('details').each(function () {
|
el.find('article button[data-toggle=variations]').click(function (e) {
|
||||||
var $details = $(this),
|
let $button = $(this)
|
||||||
$detailsSummary = $('summary', $details).first(),
|
let $details = $button.closest('article')
|
||||||
$detailsNotSummary = $details.children(':not(summary)');
|
let $detailsNotSummary = $button.attr('aria-controls') ? $('#' + $button.attr('aria-controls')) : $('.variations', $details)
|
||||||
$details.prop('open', typeof $details.attr('open') == 'string');
|
let isOpen = !$detailsNotSummary.prop('hidden')
|
||||||
if (!$details.prop('open')) {
|
if ($detailsNotSummary.is(':animated')) {
|
||||||
if ($details.find(".has-error, .alert-danger").length) {
|
e.preventDefault()
|
||||||
$details.addClass("details-open");
|
return false
|
||||||
$details.prop('open', true);
|
}
|
||||||
} else {
|
|
||||||
$detailsNotSummary.hide();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$details.addClass("details-open");
|
|
||||||
}
|
|
||||||
$detailsSummary.attr({
|
|
||||||
'role': 'button',
|
|
||||||
'aria-controls': $details.attr('id')
|
|
||||||
}).prop('tabIndex', 0).bind('selectstart dragstart mousedown', function () {
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
el.find("article button[data-toggle=variations]").click(function (e) {
|
let altLabel = $button.attr('data-label-alt')
|
||||||
var $button = $(this);
|
$button.attr('data-label-alt', $button.text().trim())
|
||||||
var $details = $button.closest("article");
|
$button.find('span').text(altLabel)
|
||||||
var $detailsNotSummary = $button.attr("aria-controls") ? $('#' + $button.attr("aria-controls")) : $(".variations", $details);
|
$button.attr('aria-expanded', !isOpen)
|
||||||
var isOpen = !$detailsNotSummary.prop("hidden");
|
|
||||||
if ($detailsNotSummary.is(':animated')) {
|
|
||||||
e.preventDefault();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var altLabel = $button.attr("data-label-alt");
|
if (isOpen) {
|
||||||
$button.attr("data-label-alt", $button.text().trim());
|
$details.removeClass('details-open')
|
||||||
$button.find("span").text(altLabel);
|
$detailsNotSummary.stop().show().slideUp(500, function () {
|
||||||
$button.attr("aria-expanded", !isOpen);
|
$detailsNotSummary.prop('hidden', true)
|
||||||
|
})
|
||||||
if (isOpen) {
|
} else {
|
||||||
$details.removeClass("details-open");
|
$detailsNotSummary.prop('hidden', false).stop().hide()
|
||||||
$detailsNotSummary.stop().show().slideUp(500, function () {
|
$details.addClass('details-open')
|
||||||
$detailsNotSummary.prop("hidden", true);
|
$detailsNotSummary.slideDown()
|
||||||
});
|
}
|
||||||
} else {
|
e.preventDefault()
|
||||||
$detailsNotSummary.prop("hidden", false).stop().hide();
|
return false
|
||||||
$details.addClass("details-open");
|
})
|
||||||
$detailsNotSummary.slideDown();
|
el.find('.variations-collapsed').prop('hidden', true)
|
||||||
}
|
}
|
||||||
e.preventDefault();
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
el.find(".variations-collapsed").prop("hidden", true);
|
|
||||||
};
|
|
||||||
|
|
||||||
$(function () {
|
$(function () {
|
||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
setup_collapsible_details($("body"));
|
setup_collapsible_details($('body'))
|
||||||
});
|
})
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
['DOMContentLoaded', 'pretix:async-task-error'].forEach(function (ev) {
|
['DOMContentLoaded', 'pretix:async-task-error'].forEach(function (ev) {
|
||||||
document.addEventListener(ev, function () {
|
document.addEventListener(ev, function () {
|
||||||
document.querySelectorAll('#goback, #reload').forEach(function (element) {
|
document.querySelectorAll('#goback, #reload').forEach(function (element) {
|
||||||
const regularLoad = ev === 'DOMContentLoaded' && element.id === 'goback';
|
const regularLoad = ev === 'DOMContentLoaded' && element.id === 'goback'
|
||||||
element.addEventListener('click', regularLoad
|
element.addEventListener('click', regularLoad
|
||||||
? () => window.history.back()
|
? () => window.history.back()
|
||||||
: () => window.location.reload()
|
: () => window.location.reload()
|
||||||
);
|
)
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
// Attempt to auto-open page in new tab. Will be ignored by most browser's popup blockers anyways, though.
|
// Attempt to auto-open page in new tab. Will be ignored by most browser's popup blockers anyways, though.
|
||||||
var url = JSON.parse(document.getElementById('framebreak-url').innerText)
|
let url = JSON.parse(document.getElementById('framebreak-url').innerText)
|
||||||
window.open(url)
|
window.open(url)
|
||||||
|
|||||||
@@ -1,30 +1,29 @@
|
|||||||
// The actual gettext implementation is loaded asynchronously with the translation
|
// The actual gettext implementation is loaded asynchronously with the translation
|
||||||
function gettext(msgid) {
|
function gettext (msgid) {
|
||||||
if (typeof django !== 'undefined' && typeof django.gettext !== 'undefined') {
|
if (typeof django !== 'undefined' && typeof django.gettext !== 'undefined') {
|
||||||
return django.gettext(msgid);
|
return django.gettext(msgid)
|
||||||
}
|
}
|
||||||
return msgid;
|
return msgid
|
||||||
}
|
}
|
||||||
|
|
||||||
function ngettext(singular, plural, count) {
|
function ngettext (singular, plural, count) {
|
||||||
if (typeof django !== 'undefined' && typeof django.ngettext !== 'undefined') {
|
if (typeof django !== 'undefined' && typeof django.ngettext !== 'undefined') {
|
||||||
return django.ngettext(singular, plural, count);
|
return django.ngettext(singular, plural, count)
|
||||||
}
|
}
|
||||||
return plural;
|
return plural
|
||||||
}
|
}
|
||||||
|
|
||||||
function pgettext(context, msgid) {
|
function pgettext (context, msgid) {
|
||||||
if (typeof django !== 'undefined' && typeof django.pgettext !== 'undefined') {
|
if (typeof django !== 'undefined' && typeof django.pgettext !== 'undefined') {
|
||||||
return django.pgettext(context, msgid);
|
return django.pgettext(context, msgid)
|
||||||
}
|
}
|
||||||
return msgid;
|
return msgid
|
||||||
}
|
}
|
||||||
|
|
||||||
function interpolate(fmt, object, named) {
|
function interpolate (fmt, object, named) {
|
||||||
if (named) {
|
if (named) {
|
||||||
return fmt.replace(/%\(\w+\)s/g, function(match){return String(obj[match.slice(2,-2)])});
|
return fmt.replace(/%\(\w+\)s/g, function (match) { return String(obj[match.slice(2, -2)]) })
|
||||||
} else {
|
} else {
|
||||||
return fmt.replace(/%s/g, function(match){return String(obj.shift())});
|
return fmt.replace(/%s/g, function (match) { return String(obj.shift()) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +1,24 @@
|
|||||||
function i18nstring_localize(o) {
|
function i18nstring_localize (o) {
|
||||||
var locale = document.body.attributes['data-pretixlocale'].value
|
let locale = document.body.attributes['data-pretixlocale'].value
|
||||||
var short_locale = locale.split('-')[0]
|
let short_locale = locale.split('-')[0]
|
||||||
if (o[locale])
|
if (o[locale])
|
||||||
return o[locale]
|
return o[locale]
|
||||||
|
|
||||||
if (o[short_locale])
|
if (o[short_locale])
|
||||||
return o[short_locale]
|
return o[short_locale]
|
||||||
|
|
||||||
for (k of Object.keys(o)) {
|
for (let k of Object.keys(o)) {
|
||||||
if (k.split('-')[0] === short_locale && o[k]) {
|
if (k.split('-')[0] === short_locale && o[k]) {
|
||||||
return o[k]
|
return o[k]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (o['en'])
|
if (o['en'])
|
||||||
return o['en']
|
return o['en']
|
||||||
|
|
||||||
for (k of Object.keys(o)) {
|
for (let k of Object.keys(o)) {
|
||||||
if (o[k]) {
|
if (o[k]) {
|
||||||
return o[k]
|
return o[k]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ $(document).on('pretix:bind-forms', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (dirty) {
|
if (dirty) {
|
||||||
beforeAfterSelect.dispatchEvent(new Event('change', {bubbles: true}))
|
beforeAfterSelect.dispatchEvent(new Event('change', { bubbles: true }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
referenceSelect.addEventListener('change', updateBeforeOption)
|
referenceSelect.addEventListener('change', updateBeforeOption)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
var intId = window.setInterval(function () {
|
const intId = window.setInterval(function () {
|
||||||
$.get(location.href + '?ajax=1', function (data, status) {
|
$.get(location.href + '?ajax=1', function (data, _status) {
|
||||||
if (data === "1") {
|
if (data === '1') {
|
||||||
window.clearInterval(intId);
|
window.clearInterval(intId)
|
||||||
location.reload();
|
location.reload()
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
}, 500);
|
}, 500)
|
||||||
|
|||||||
@@ -170,13 +170,14 @@ body.has-modal-dialog .container, body.has-modal-dialog #wrapper {
|
|||||||
#lightbox-dialog {
|
#lightbox-dialog {
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
max-width: 80%;
|
max-width: 80%;
|
||||||
min-width: 24em;
|
min-width: calc(min(24em, 90%));
|
||||||
.modal-card-content {
|
.modal-card-content {
|
||||||
padding: 2.5em;
|
padding: 2.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
max-height: calc(100dvh - 60px - 5em - 5em);
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
var hiddenfield = document.querySelector("input[name=origin][type=hidden]");
|
let hiddenfield = document.querySelector('input[name=origin][type=hidden]')
|
||||||
if (hiddenfield) {
|
if (hiddenfield) {
|
||||||
hiddenfield.value = window.location.origin
|
hiddenfield.value = window.location.origin
|
||||||
}
|
}
|
||||||
async function runCheck() {
|
async function runCheck () {
|
||||||
if (document.getElementById("good_origin")) {
|
if (document.getElementById('good_origin')) {
|
||||||
if (document.getElementById("good_origin").innerText.split('').reverse().join('') !== window.location.origin) {
|
if (document.getElementById('good_origin').innerText.split('').reverse().join('') !== window.location.origin) {
|
||||||
const response = await fetch(document.getElementById("bad_origin_report_url").innerText.split('').reverse().join(''), {
|
const _response = await fetch(document.getElementById('bad_origin_report_url').innerText.split('').reverse().join(''), {
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
mode: "cors",
|
mode: 'cors',
|
||||||
referrerPolicy: "unsafe-url",
|
referrerPolicy: 'unsafe-url',
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
},
|
},
|
||||||
body: "origin=" + window.location.origin,
|
body: 'origin=' + window.location.origin,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
runCheck();
|
runCheck()
|
||||||
|
|||||||
@@ -1,118 +1,118 @@
|
|||||||
var lookup = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
let lookup = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||||
|
|
||||||
;(function (exports) {
|
;(function (exports) {
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
var Arr = (typeof Uint8Array !== 'undefined')
|
let Arr = (typeof Uint8Array !== 'undefined')
|
||||||
? Uint8Array
|
? Uint8Array
|
||||||
: Array
|
: Array
|
||||||
|
|
||||||
var PLUS = '+'.charCodeAt(0)
|
let PLUS = '+'.charCodeAt(0)
|
||||||
var SLASH = '/'.charCodeAt(0)
|
let SLASH = '/'.charCodeAt(0)
|
||||||
var NUMBER = '0'.charCodeAt(0)
|
let NUMBER = '0'.charCodeAt(0)
|
||||||
var LOWER = 'a'.charCodeAt(0)
|
let LOWER = 'a'.charCodeAt(0)
|
||||||
var UPPER = 'A'.charCodeAt(0)
|
let UPPER = 'A'.charCodeAt(0)
|
||||||
var PLUS_URL_SAFE = '-'.charCodeAt(0)
|
let PLUS_URL_SAFE = '-'.charCodeAt(0)
|
||||||
var SLASH_URL_SAFE = '_'.charCodeAt(0)
|
let SLASH_URL_SAFE = '_'.charCodeAt(0)
|
||||||
|
|
||||||
function decode (elt) {
|
function decode (elt) {
|
||||||
var code = elt.charCodeAt(0)
|
let code = elt.charCodeAt(0)
|
||||||
if (code === PLUS || code === PLUS_URL_SAFE) return 62 // '+'
|
if (code === PLUS || code === PLUS_URL_SAFE) return 62 // '+'
|
||||||
if (code === SLASH || code === SLASH_URL_SAFE) return 63 // '/'
|
if (code === SLASH || code === SLASH_URL_SAFE) return 63 // '/'
|
||||||
if (code < NUMBER) return -1 // no match
|
if (code < NUMBER) return -1 // no match
|
||||||
if (code < NUMBER + 10) return code - NUMBER + 26 + 26
|
if (code < NUMBER + 10) return code - NUMBER + 26 + 26
|
||||||
if (code < UPPER + 26) return code - UPPER
|
if (code < UPPER + 26) return code - UPPER
|
||||||
if (code < LOWER + 26) return code - LOWER + 26
|
if (code < LOWER + 26) return code - LOWER + 26
|
||||||
}
|
}
|
||||||
|
|
||||||
function b64ToByteArray (b64) {
|
function b64ToByteArray (b64) {
|
||||||
var i, j, l, tmp, placeHolders, arr
|
let i, j, l, tmp, placeHolders, arr
|
||||||
|
|
||||||
if (b64.length % 4 > 0) {
|
if (b64.length % 4 > 0) {
|
||||||
throw new Error('Invalid string. Length must be a multiple of 4')
|
throw new Error('Invalid string. Length must be a multiple of 4')
|
||||||
}
|
}
|
||||||
|
|
||||||
// the number of equal signs (place holders)
|
// the number of equal signs (place holders)
|
||||||
// if there are two placeholders, than the two characters before it
|
// if there are two placeholders, than the two characters before it
|
||||||
// represent one byte
|
// represent one byte
|
||||||
// if there is only one, then the three characters before it represent 2 bytes
|
// if there is only one, then the three characters before it represent 2 bytes
|
||||||
// this is just a cheap hack to not do indexOf twice
|
// this is just a cheap hack to not do indexOf twice
|
||||||
var len = b64.length
|
let len = b64.length
|
||||||
placeHolders = b64.charAt(len - 2) === '=' ? 2 : b64.charAt(len - 1) === '=' ? 1 : 0
|
placeHolders = b64.charAt(len - 2) === '=' ? 2 : b64.charAt(len - 1) === '=' ? 1 : 0
|
||||||
|
|
||||||
// base64 is 4/3 + up to two characters of the original data
|
// base64 is 4/3 + up to two characters of the original data
|
||||||
arr = new Arr(b64.length * 3 / 4 - placeHolders)
|
arr = new Arr(b64.length * 3 / 4 - placeHolders)
|
||||||
|
|
||||||
// if there are placeholders, only get up to the last complete 4 chars
|
// if there are placeholders, only get up to the last complete 4 chars
|
||||||
l = placeHolders > 0 ? b64.length - 4 : b64.length
|
l = placeHolders > 0 ? b64.length - 4 : b64.length
|
||||||
|
|
||||||
var L = 0
|
let L = 0
|
||||||
|
|
||||||
function push (v) {
|
function push (v) {
|
||||||
arr[L++] = v
|
arr[L++] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0, j = 0; i < l; i += 4, j += 3) {
|
for (i = 0, j = 0; i < l; i += 4, j += 3) {
|
||||||
tmp = (decode(b64.charAt(i)) << 18) | (decode(b64.charAt(i + 1)) << 12) | (decode(b64.charAt(i + 2)) << 6) | decode(b64.charAt(i + 3))
|
tmp = (decode(b64.charAt(i)) << 18) | (decode(b64.charAt(i + 1)) << 12) | (decode(b64.charAt(i + 2)) << 6) | decode(b64.charAt(i + 3))
|
||||||
push((tmp & 0xFF0000) >> 16)
|
push((tmp & 0xFF0000) >> 16)
|
||||||
push((tmp & 0xFF00) >> 8)
|
push((tmp & 0xFF00) >> 8)
|
||||||
push(tmp & 0xFF)
|
push(tmp & 0xFF)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (placeHolders === 2) {
|
if (placeHolders === 2) {
|
||||||
tmp = (decode(b64.charAt(i)) << 2) | (decode(b64.charAt(i + 1)) >> 4)
|
tmp = (decode(b64.charAt(i)) << 2) | (decode(b64.charAt(i + 1)) >> 4)
|
||||||
push(tmp & 0xFF)
|
push(tmp & 0xFF)
|
||||||
} else if (placeHolders === 1) {
|
} else if (placeHolders === 1) {
|
||||||
tmp = (decode(b64.charAt(i)) << 10) | (decode(b64.charAt(i + 1)) << 4) | (decode(b64.charAt(i + 2)) >> 2)
|
tmp = (decode(b64.charAt(i)) << 10) | (decode(b64.charAt(i + 1)) << 4) | (decode(b64.charAt(i + 2)) >> 2)
|
||||||
push((tmp >> 8) & 0xFF)
|
push((tmp >> 8) & 0xFF)
|
||||||
push(tmp & 0xFF)
|
push(tmp & 0xFF)
|
||||||
}
|
}
|
||||||
|
|
||||||
return arr
|
return arr
|
||||||
}
|
}
|
||||||
|
|
||||||
function uint8ToBase64 (uint8) {
|
function uint8ToBase64 (uint8) {
|
||||||
var i
|
let i
|
||||||
var extraBytes = uint8.length % 3 // if we have 1 byte left, pad 2 bytes
|
let extraBytes = uint8.length % 3 // if we have 1 byte left, pad 2 bytes
|
||||||
var output = ''
|
let output = ''
|
||||||
var temp, length
|
let temp, length
|
||||||
|
|
||||||
function encode (num) {
|
function encode (num) {
|
||||||
return lookup.charAt(num)
|
return lookup.charAt(num)
|
||||||
}
|
}
|
||||||
|
|
||||||
function tripletToBase64 (num) {
|
function tripletToBase64 (num) {
|
||||||
return encode(num >> 18 & 0x3F) + encode(num >> 12 & 0x3F) + encode(num >> 6 & 0x3F) + encode(num & 0x3F)
|
return encode(num >> 18 & 0x3F) + encode(num >> 12 & 0x3F) + encode(num >> 6 & 0x3F) + encode(num & 0x3F)
|
||||||
}
|
}
|
||||||
|
|
||||||
// go through the array every three bytes, we'll deal with trailing stuff later
|
// go through the array every three bytes, we'll deal with trailing stuff later
|
||||||
for (i = 0, length = uint8.length - extraBytes; i < length; i += 3) {
|
for (i = 0, length = uint8.length - extraBytes; i < length; i += 3) {
|
||||||
temp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2])
|
temp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2])
|
||||||
output += tripletToBase64(temp)
|
output += tripletToBase64(temp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// pad the end with zeros, but make sure to not forget the extra bytes
|
// pad the end with zeros, but make sure to not forget the extra bytes
|
||||||
switch (extraBytes) {
|
switch (extraBytes) {
|
||||||
case 1:
|
case 1:
|
||||||
temp = uint8[uint8.length - 1]
|
temp = uint8[uint8.length - 1]
|
||||||
output += encode(temp >> 2)
|
output += encode(temp >> 2)
|
||||||
output += encode((temp << 4) & 0x3F)
|
output += encode((temp << 4) & 0x3F)
|
||||||
output += '=='
|
output += '=='
|
||||||
break
|
break
|
||||||
case 2:
|
case 2:
|
||||||
temp = (uint8[uint8.length - 2] << 8) + (uint8[uint8.length - 1])
|
temp = (uint8[uint8.length - 2] << 8) + (uint8[uint8.length - 1])
|
||||||
output += encode(temp >> 10)
|
output += encode(temp >> 10)
|
||||||
output += encode((temp >> 4) & 0x3F)
|
output += encode((temp >> 4) & 0x3F)
|
||||||
output += encode((temp << 2) & 0x3F)
|
output += encode((temp << 2) & 0x3F)
|
||||||
output += '='
|
output += '='
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.toByteArray = b64ToByteArray
|
exports.toByteArray = b64ToByteArray
|
||||||
exports.fromByteArray = uint8ToBase64
|
exports.fromByteArray = uint8ToBase64
|
||||||
}(typeof exports === 'undefined' ? (this.base64js = {}) : exports))
|
}(typeof exports === 'undefined' ? (this.base64js = {}) : exports))
|
||||||
|
|||||||
@@ -1,33 +1,33 @@
|
|||||||
$(function() {
|
/* global gettext */
|
||||||
$('.btn-clipboard').tooltip({
|
$(function () {
|
||||||
trigger: 'click',
|
$('.btn-clipboard').tooltip({
|
||||||
placement: 'bottom'
|
trigger: 'click',
|
||||||
});
|
placement: 'bottom'
|
||||||
|
})
|
||||||
|
|
||||||
function setTooltip(btn, message) {
|
function setTooltip (btn, message) {
|
||||||
$(btn).tooltip('hide')
|
$(btn).tooltip('hide')
|
||||||
.attr('data-original-title', message)
|
.attr('data-original-title', message)
|
||||||
.tooltip('show');
|
.tooltip('show')
|
||||||
}
|
}
|
||||||
|
|
||||||
function hideTooltip(btn) {
|
function hideTooltip (btn) {
|
||||||
setTimeout(function() {
|
setTimeout(function () {
|
||||||
$(btn).tooltip('hide');
|
$(btn).tooltip('hide')
|
||||||
}, 1000);
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
var clipboard = new Clipboard('.btn-clipboard');
|
let clipboard = new Clipboard('.btn-clipboard')
|
||||||
|
|
||||||
clipboard.on('success', function(e) {
|
clipboard.on('success', function (e) {
|
||||||
if (e.text.length > 0) {
|
if (e.text.length > 0) {
|
||||||
setTooltip(e.trigger, gettext('Copied!'));
|
setTooltip(e.trigger, gettext('Copied!'))
|
||||||
hideTooltip(e.trigger);
|
hideTooltip(e.trigger)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
clipboard.on('error', function(e) {
|
|
||||||
setTooltip(e.trigger, gettext('Press Ctrl-C to copy!'));
|
|
||||||
hideTooltip(e.trigger);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
clipboard.on('error', function (e) {
|
||||||
|
setTooltip(e.trigger, gettext('Press Ctrl-C to copy!'))
|
||||||
|
hideTooltip(e.trigger)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@@ -8,79 +8,75 @@
|
|||||||
* Under MIT License
|
* Under MIT License
|
||||||
* Modified by Raphael Michel
|
* Modified by Raphael Michel
|
||||||
*/
|
*/
|
||||||
;(function($, window, document, undefined) {
|
;(function ($, window, document, undefined) {
|
||||||
|
let pluginName = 'metisMenu',
|
||||||
|
defaults = {
|
||||||
|
toggle: true,
|
||||||
|
}
|
||||||
|
|
||||||
var pluginName = "metisMenu",
|
function Plugin (element, options) {
|
||||||
defaults = {
|
this.element = $(element)
|
||||||
toggle: true,
|
this.settings = $.extend({}, defaults, options)
|
||||||
};
|
this._defaults = defaults
|
||||||
|
this._name = pluginName
|
||||||
|
this.init()
|
||||||
|
}
|
||||||
|
|
||||||
function Plugin(element, options) {
|
Plugin.prototype = {
|
||||||
this.element = $(element);
|
init: function () {
|
||||||
this.settings = $.extend({}, defaults, options);
|
let $this = this.element,
|
||||||
this._defaults = defaults;
|
$toggle = this.settings.toggle,
|
||||||
this._name = pluginName;
|
obj = this
|
||||||
this.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
Plugin.prototype = {
|
if (this.isIE() <= 9) {
|
||||||
init: function() {
|
$this.find('li.active').has('ul').children('ul').collapse('show')
|
||||||
|
$this.find('li').not('.active').has('ul').children('ul').collapse('hide')
|
||||||
|
} else {
|
||||||
|
$this.find('li.active').has('ul').children('ul').addClass('collapse in')
|
||||||
|
$this.find('li').not('.active').has('ul').children('ul').addClass('collapse')
|
||||||
|
}
|
||||||
|
|
||||||
var $this = this.element,
|
$this.find('li').has('ul').children('a.arrow').on('click' + '.' + pluginName, function (e) {
|
||||||
$toggle = this.settings.toggle,
|
e.preventDefault()
|
||||||
obj = this;
|
$(this).blur()
|
||||||
|
|
||||||
if (this.isIE() <= 9) {
|
$(this).parent('li').toggleClass('active').children('ul').collapse('toggle')
|
||||||
$this.find("li.active").has("ul").children("ul").collapse("show");
|
|
||||||
$this.find("li").not(".active").has("ul").children("ul").collapse("hide");
|
|
||||||
} else {
|
|
||||||
$this.find("li.active").has("ul").children("ul").addClass("collapse in");
|
|
||||||
$this.find("li").not(".active").has("ul").children("ul").addClass("collapse");
|
|
||||||
}
|
|
||||||
|
|
||||||
$this.find("li").has("ul").children("a.arrow").on("click" + "." + pluginName, function(e) {
|
if ($toggle) {
|
||||||
e.preventDefault();
|
$(this).parent('li').siblings().removeClass('active').children('ul.in').collapse('hide')
|
||||||
$(this).blur();
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
$(this).parent("li").toggleClass("active").children("ul").collapse("toggle");
|
isIE: function () { // https://gist.github.com/padolsey/527683
|
||||||
|
let undef,
|
||||||
|
v = 3,
|
||||||
|
div = document.createElement('div'),
|
||||||
|
all = div.getElementsByTagName('i')
|
||||||
|
|
||||||
if ($toggle) {
|
while (
|
||||||
$(this).parent("li").siblings().removeClass("active").children("ul.in").collapse("hide");
|
div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->',
|
||||||
}
|
all[0]
|
||||||
|
) {
|
||||||
|
return v > 4 ? v : undef
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
});
|
remove: function () {
|
||||||
},
|
this.element.off('.' + pluginName)
|
||||||
|
this.element.removeData(pluginName)
|
||||||
|
}
|
||||||
|
|
||||||
isIE: function() { //https://gist.github.com/padolsey/527683
|
}
|
||||||
var undef,
|
|
||||||
v = 3,
|
|
||||||
div = document.createElement("div"),
|
|
||||||
all = div.getElementsByTagName("i");
|
|
||||||
|
|
||||||
while (
|
$.fn[pluginName] = function (options) {
|
||||||
div.innerHTML = "<!--[if gt IE " + (++v) + "]><i></i><![endif]-->",
|
this.each(function () {
|
||||||
all[0]
|
let el = $(this)
|
||||||
) {
|
if (el.data(pluginName)) {
|
||||||
return v > 4 ? v : undef;
|
el.data(pluginName).remove()
|
||||||
}
|
}
|
||||||
},
|
el.data(pluginName, new Plugin(this, options))
|
||||||
|
})
|
||||||
remove: function() {
|
return this
|
||||||
this.element.off("." + pluginName);
|
}
|
||||||
this.element.removeData(pluginName);
|
})(jQuery, window, document)
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
$.fn[pluginName] = function(options) {
|
|
||||||
this.each(function () {
|
|
||||||
var el = $(this);
|
|
||||||
if (el.data(pluginName)) {
|
|
||||||
el.data(pluginName).remove();
|
|
||||||
}
|
|
||||||
el.data(pluginName, new Plugin(this, options));
|
|
||||||
});
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
})(jQuery, window, document);
|
|
||||||
|
|||||||
@@ -1,35 +1,34 @@
|
|||||||
/*global $ */
|
|
||||||
/*
|
/*
|
||||||
Based on https://github.com/BlackrockDigital/startbootstrap-sb-admin-2
|
Based on https://github.com/BlackrockDigital/startbootstrap-sb-admin-2
|
||||||
Copyright 2013-2016 Blackrock Digital LLC
|
Copyright 2013-2016 Blackrock Digital LLC
|
||||||
MIT License
|
MIT License
|
||||||
Modified by Raphael Michel
|
Modified by Raphael Michel
|
||||||
*/
|
*/
|
||||||
//Loads the correct sidebar on window load,
|
// Loads the correct sidebar on window load,
|
||||||
//collapses the sidebar on window resize.
|
// collapses the sidebar on window resize.
|
||||||
// Sets the min-height of #page-wrapper to window size
|
// Sets the min-height of #page-wrapper to window size
|
||||||
$(window).bind("load resize", function () {
|
$(window).bind('load resize', function () {
|
||||||
'use strict';
|
'use strict'
|
||||||
var topOffset = 50,
|
let topOffset = 50,
|
||||||
width = (this.window.innerWidth > 0) ? this.window.innerWidth : this.screen.width;
|
width = (this.window.innerWidth > 0) ? this.window.innerWidth : this.screen.width
|
||||||
if (width < 768) {
|
if (width < 768) {
|
||||||
$('div.navbar-collapse').addClass('collapse');
|
$('div.navbar-collapse').addClass('collapse')
|
||||||
topOffset = 100; // 2-row-menu
|
topOffset = 100 // 2-row-menu
|
||||||
} else {
|
} else {
|
||||||
$('div.navbar-collapse').removeClass('collapse');
|
$('div.navbar-collapse').removeClass('collapse')
|
||||||
}
|
}
|
||||||
|
|
||||||
var height = ((this.window.innerHeight > 0) ? this.window.innerHeight : this.screen.height) - 1;
|
let height = ((this.window.innerHeight > 0) ? this.window.innerHeight : this.screen.height) - 1
|
||||||
height = height - topOffset;
|
height = height - topOffset
|
||||||
if (height < 1) height = 1;
|
if (height < 1) height = 1
|
||||||
if (height > topOffset) {
|
if (height > topOffset) {
|
||||||
$("#page-wrapper").css("min-height", (height) + "px");
|
$('#page-wrapper').css('min-height', (height) + 'px')
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
$(function () {
|
$(function () {
|
||||||
'use strict';
|
'use strict'
|
||||||
$('ul.nav ul.nav-second-level a.active').parent().parent().addClass('in').parent().addClass('active');
|
$('ul.nav ul.nav-second-level a.active').parent().parent().addClass('in').parent().addClass('active')
|
||||||
$('#side-menu').metisMenu({
|
$('#side-menu').metisMenu({
|
||||||
'toggle': false,
|
toggle: false,
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
document.forms[0].submit();
|
document.forms[0].submit()
|
||||||
|
|||||||
@@ -1,26 +1,25 @@
|
|||||||
/*global $,gettext*/
|
/* global add_log_expand_handlers */
|
||||||
|
|
||||||
$(function () {
|
$(function () {
|
||||||
if ($("div[data-lazy-id]").length == 0) {
|
if ($('div[data-lazy-id]').length == 0) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
$.getJSON("widgets.json" + ($("select[name='subevent']").val() ? "?subevent=" + $("select[name='subevent']").val() : ""), function (data) {
|
$.getJSON('widgets.json' + ($('select[name=\'subevent\']').val() ? '?subevent=' + $('select[name=\'subevent\']').val() : ''), function (data) {
|
||||||
$.each(data.widgets, function (k, v) {
|
$.each(data.widgets, function (_k, v) {
|
||||||
$("[data-lazy-id=" + v.lazy + "]").removeClass("widget-lazy-loading");
|
$('[data-lazy-id=' + v.lazy + ']').removeClass('widget-lazy-loading')
|
||||||
$("[data-lazy-id=" + v.lazy + "] .widget").html(v.content);
|
$('[data-lazy-id=' + v.lazy + '] .widget').html(v.content)
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
$(function () {
|
$(function () {
|
||||||
if ($("#logs_target").length == 0) {
|
if ($('#logs_target').length == 0) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
$.get("dashboard/partials/logs", function (data) {
|
$.get('dashboard/partials/logs', function (data) {
|
||||||
$("#logs_target").html(data)
|
$('#logs_target').html(data)
|
||||||
add_log_expand_handlers($("#logs_target"))
|
add_log_expand_handlers($('#logs_target'))
|
||||||
});
|
})
|
||||||
$.get("dashboard/partials/warnings", function (data) {
|
$.get('dashboard/partials/warnings', function (data) {
|
||||||
$("#warnings_loading").remove()
|
$('#warnings_loading').remove()
|
||||||
$("#warnings_target").html(data)
|
$('#warnings_target').html(data)
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
/*globals $, Morris, gettext, RRule, RRuleSet*/
|
|
||||||
|
|
||||||
$(function () {
|
$(function () {
|
||||||
var update = function () {
|
let update = function () {
|
||||||
$.getJSON(location.href + '?ajax=true', {}, function (data) {
|
$.getJSON(location.href + '?ajax=true', {}, function (data) {
|
||||||
if (data.initialized) {
|
if (data.initialized) {
|
||||||
location.reload();
|
location.reload()
|
||||||
} else {
|
} else {
|
||||||
window.setTimeout(update, 500);
|
window.setTimeout(update, 500)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
window.setTimeout(update, 500);
|
window.setTimeout(update, 500)
|
||||||
});
|
})
|
||||||
|
|||||||
@@ -1,91 +1,95 @@
|
|||||||
/*global $, Sortable*/
|
/* global Sortable */
|
||||||
$(function () {
|
$(function () {
|
||||||
const allContainers = $("[data-dnd-url]");
|
const allContainers = $('[data-dnd-url]')
|
||||||
function updateAllSortButtonStates() {
|
function updateAllSortButtonStates () {
|
||||||
allContainers.each(function() { updateSortButtonState($(this)); });
|
allContainers.each(function () {
|
||||||
}
|
updateSortButtonState($(this))
|
||||||
function updateSortButtonState(container) {
|
})
|
||||||
var disabledUp = container.find(".sortable-up:disabled"),
|
}
|
||||||
firstUp = container.find(">tr[data-dnd-id] .sortable-up").first();
|
function updateSortButtonState (container) {
|
||||||
if (disabledUp.length && disabledUp.get(0) !== firstUp.get(0)) {
|
let disabledUp = container.find('.sortable-up:disabled'),
|
||||||
disabledUp.prop("disabled", false);
|
firstUp = container.find('>tr[data-dnd-id] .sortable-up').first()
|
||||||
firstUp.prop("disabled", true);
|
if (disabledUp.length && disabledUp.get(0) !== firstUp.get(0)) {
|
||||||
}
|
disabledUp.prop('disabled', false)
|
||||||
|
firstUp.prop('disabled', true)
|
||||||
|
}
|
||||||
|
|
||||||
var disabledDown = container.find(".sortable-down:disabled"),
|
let disabledDown = container.find('.sortable-down:disabled'),
|
||||||
lastDown = container.find(">tr[data-dnd-id] .sortable-down").last();
|
lastDown = container.find('>tr[data-dnd-id] .sortable-down').last()
|
||||||
if (disabledDown.length && disabledDown.get(0) !== lastDown.get(0)) {
|
if (disabledDown.length && disabledDown.get(0) !== lastDown.get(0)) {
|
||||||
disabledDown.prop("disabled", false);
|
disabledDown.prop('disabled', false)
|
||||||
lastDown.prop("disabled", true);
|
lastDown.prop('disabled', true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let didSort = false, lastClick = 0;
|
let didSort = false, lastClick = 0
|
||||||
allContainers.each(function(){
|
allContainers.each(function () {
|
||||||
const container = $(this),
|
const container = $(this),
|
||||||
url = container.data("dnd-url"),
|
url = container.data('dnd-url'),
|
||||||
handle = $('<span class="btn btn-default btn-sm dnd-sort-handle"><i class="fa fa-arrows"></i></span>');
|
handle = $('<span class="btn btn-default btn-sm dnd-sort-handle"><i class="fa fa-arrows"></i></span>')
|
||||||
|
|
||||||
container.find(".dnd-container").append(handle);
|
container.find('.dnd-container').append(handle)
|
||||||
if (!sessionStorage.dndShowMoveButtons) {
|
if (!sessionStorage.dndShowMoveButtons) {
|
||||||
container.find(".sortable-up, .sortable-down").addClass("sr-only").on("click", function () {
|
container.find('.sortable-up, .sortable-down').addClass('sr-only').on('click', function () {
|
||||||
sessionStorage.dndShowMoveButtons = 'true';
|
sessionStorage.dndShowMoveButtons = 'true'
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
if (container.find("[data-dnd-id]").length < 2 && !container.data("dnd-group")) {
|
if (container.find('[data-dnd-id]').length < 2 && !container.data('dnd-group')) {
|
||||||
handle.addClass("disabled");
|
handle.addClass('disabled')
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
function maybeShowSortButtons() {
|
function maybeShowSortButtons () {
|
||||||
if (Date.now() - lastClick < 3000) {
|
if (Date.now() - lastClick < 3000) {
|
||||||
$("[data-dnd-url] .sortable-up, [data-dnd-url] .sortable-down").removeClass("sr-only");
|
$('[data-dnd-url] .sortable-up, [data-dnd-url] .sortable-down').removeClass('sr-only')
|
||||||
updateAllSortButtonStates();
|
updateAllSortButtonStates()
|
||||||
}
|
}
|
||||||
lastClick = Date.now();
|
lastClick = Date.now()
|
||||||
}
|
}
|
||||||
container.find(".dnd-sort-handle").on("mouseup", maybeShowSortButtons);
|
container.find('.dnd-sort-handle').on('mouseup', maybeShowSortButtons)
|
||||||
const group = container.data("dnd-group");
|
const group = container.data('dnd-group')
|
||||||
const containers = group ? container.parent().find('[data-dnd-group="' + group + '"]') : container;
|
const containers = group ? container.parent().find('[data-dnd-group="' + group + '"]') : container
|
||||||
Sortable.create(container.get(0), {
|
Sortable.create(container.get(0), {
|
||||||
filter: ".sortable-disabled",
|
filter: '.sortable-disabled',
|
||||||
handle: ".dnd-sort-handle",
|
handle: '.dnd-sort-handle',
|
||||||
group: group,
|
group: group,
|
||||||
onMove: function (evt) {
|
onMove: function (evt) {
|
||||||
return evt.related.className.indexOf('sortable-disabled') === -1;
|
return evt.related.className.indexOf('sortable-disabled') === -1
|
||||||
},
|
},
|
||||||
onStart: function (evt) {
|
onStart: function () {
|
||||||
containers.addClass("sortable-dragarea");
|
containers.addClass('sortable-dragarea')
|
||||||
container.parent().addClass("sortable-sorting");
|
container.parent().addClass('sortable-sorting')
|
||||||
didSort = false;
|
didSort = false
|
||||||
},
|
},
|
||||||
onEnd: function (evt) {
|
onEnd: function () {
|
||||||
containers.removeClass("sortable-dragarea");
|
containers.removeClass('sortable-dragarea')
|
||||||
container.parent().removeClass("sortable-sorting");
|
container.parent().removeClass('sortable-sorting')
|
||||||
if (!didSort) {
|
if (!didSort) {
|
||||||
maybeShowSortButtons();
|
maybeShowSortButtons()
|
||||||
} else {
|
} else {
|
||||||
$("[data-dnd-url] .sortable-up, [data-dnd-url] .sortable-down").addClass("sr-only");
|
$('[data-dnd-url] .sortable-up, [data-dnd-url] .sortable-down').addClass('sr-only')
|
||||||
delete sessionStorage.dndShowMoveButtons;
|
delete sessionStorage.dndShowMoveButtons
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onSort: function (evt){
|
onSort: function (evt) {
|
||||||
if (evt.target !== evt.to) return;
|
if (evt.target !== evt.to) return
|
||||||
didSort = true;
|
didSort = true
|
||||||
|
|
||||||
const ids = container.find("[data-dnd-id]").toArray().map(function (e) { return e.dataset.dndId; });
|
const ids = container.find('[data-dnd-id]').toArray().map(function (e) {
|
||||||
$.ajax(
|
return e.dataset.dndId
|
||||||
{
|
})
|
||||||
'type': 'POST',
|
$.ajax(
|
||||||
'url': url,
|
{
|
||||||
'headers': {'X-CSRFToken': $("input[name=csrfmiddlewaretoken]").val()},
|
type: 'POST',
|
||||||
'data': JSON.stringify({
|
url: url,
|
||||||
ids: ids
|
headers: { 'X-CSRFToken': $('input[name=csrfmiddlewaretoken]').val() },
|
||||||
}),
|
data: JSON.stringify({
|
||||||
'contentType': "application/json",
|
ids: ids
|
||||||
'timeout': 30000
|
}),
|
||||||
}
|
contentType: 'application/json',
|
||||||
);
|
timeout: 30000
|
||||||
}
|
}
|
||||||
});
|
)
|
||||||
});
|
}
|
||||||
});
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,3 @@
|
|||||||
$(function () {
|
$(function () {
|
||||||
$("input, select, textarea").not(":disabled").focus();
|
$('input, select, textarea').not(':disabled').focus()
|
||||||
});
|
})
|
||||||
|
|||||||
@@ -1,201 +1,198 @@
|
|||||||
/*globals $*/
|
/* globals $ */
|
||||||
|
|
||||||
$(document).on("pretix:bind-forms", function () {
|
$(document).on('pretix:bind-forms', function () {
|
||||||
function cleanup(l) {
|
function cleanup (l) {
|
||||||
return $.trim(l.replace(/\n/g, ", "));
|
return $.trim(l.replace(/\n/g, ', '))
|
||||||
}
|
}
|
||||||
function combine($sel) {
|
function combine ($sel) {
|
||||||
var parts = [
|
let parts = [
|
||||||
$sel.filter("[name*=street]").val(),
|
$sel.filter('[name*=street]').val(),
|
||||||
$sel.filter("[name*=zipcode]").val(),
|
$sel.filter('[name*=zipcode]').val(),
|
||||||
$sel.filter("[name*=city]").val(),
|
$sel.filter('[name*=city]').val(),
|
||||||
$sel.filter("[name*=state]").val(),
|
$sel.filter('[name*=state]').val(),
|
||||||
$sel.filter("[name*=country]").find("option:selected").text(),
|
$sel.filter('[name*=country]').find('option:selected').text(),
|
||||||
$sel.filter("[name*=location]").val(),
|
$sel.filter('[name*=location]').val(),
|
||||||
]
|
]
|
||||||
var res = "";
|
let res = ''
|
||||||
for (var val of parts) {
|
for (let val of parts) {
|
||||||
if (val) {
|
if (val) {
|
||||||
if (res) {
|
if (res) {
|
||||||
res += ", "
|
res += ', '
|
||||||
}
|
}
|
||||||
res += val
|
res += val
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return cleanup(res)
|
return cleanup(res)
|
||||||
}
|
}
|
||||||
$(".geodata-section").each(function () {
|
$('.geodata-section').each(function () {
|
||||||
// Geocoding
|
// Geocoding
|
||||||
// detach notifications and append them to first label (should be from location)
|
// detach notifications and append them to first label (should be from location)
|
||||||
var $notifications = $(".geodata-autoupdate", this).detach().appendTo($("label", this).first());
|
let $notifications = $('.geodata-autoupdate', this).detach().appendTo($('label', this).first())
|
||||||
var $lat = $("input[name$=geo_lat]", this).first();
|
let $lat = $('input[name$=geo_lat]', this).first()
|
||||||
var $lon = $("input[name$=geo_lon]", this).first();
|
let $lon = $('input[name$=geo_lon]', this).first()
|
||||||
var lat;
|
let lat
|
||||||
var lon;
|
let lon
|
||||||
var $updateButton = $("[data-action=update]", this);
|
let $updateButton = $('[data-action=update]', this)
|
||||||
|
|
||||||
var $location;
|
let $location
|
||||||
// The .geodata-section is expected to include either...
|
// The .geodata-section is expected to include either...
|
||||||
// ... an English "location" field
|
// ... an English "location" field
|
||||||
if ($("textarea[lang=en], input[lang=en]", this).length) {
|
if ($('textarea[lang=en], input[lang=en]', this).length) {
|
||||||
$location = $("textarea[lang=en], input[lang=en], select", this).not("[name*=geo_]");
|
$location = $('textarea[lang=en], input[lang=en], select', this).not('[name*=geo_]')
|
||||||
}
|
}
|
||||||
|
|
||||||
// ... a "location" field in any other language
|
// ... a "location" field in any other language
|
||||||
if (!$location || !$location.length) {
|
if (!$location || !$location.length) {
|
||||||
var lang = $("textarea, input[type=text]", this).not("[name*=geo_]").first().attr("lang");
|
let lang = $('textarea, input[type=text]', this).not('[name*=geo_]').first().attr('lang')
|
||||||
if (lang) {
|
if (lang) {
|
||||||
$location = $("textarea[lang=" + lang + "], input[lang=" + lang + "], select", this);
|
$location = $('textarea[lang=' + lang + '], input[lang=' + lang + '], select', this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ... or a set of fields like a full address form
|
// ... or a set of fields like a full address form
|
||||||
if (!$location || !$location.length) {
|
if (!$location || !$location.length) {
|
||||||
$location = $("textarea, input, select", this).not("[name*=geo_]");
|
$location = $('textarea, input, select', this).not('[name*=geo_]')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$lat.length || !$lon.length || !$location.length) {
|
if (!$lat.length || !$lon.length || !$location.length) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var debounceLoad, debounceLatLonChange, delayUpdateDismissal;
|
let debounceLoad, debounceLatLonChange, delayUpdateDismissal
|
||||||
var touched = $lat.val() !== "";
|
let touched = $lat.val() !== ''
|
||||||
var xhr;
|
let xhr
|
||||||
var lastLocation = combine($location);
|
let lastLocation = combine($location)
|
||||||
|
|
||||||
function load() {
|
function load () {
|
||||||
window.clearTimeout(debounceLoad);
|
window.clearTimeout(debounceLoad)
|
||||||
if (xhr) {
|
if (xhr) {
|
||||||
xhr.abort();
|
xhr.abort()
|
||||||
xhr = null;
|
xhr = null
|
||||||
}
|
}
|
||||||
|
|
||||||
var q = combine($location);
|
let q = combine($location)
|
||||||
if (q === "" || q === lastLocation) return;
|
if (q === '' || q === lastLocation) return
|
||||||
|
|
||||||
lastLocation = q;
|
lastLocation = q
|
||||||
$notifications.attr("data-notify", "loading");
|
$notifications.attr('data-notify', 'loading')
|
||||||
|
|
||||||
xhr = $.getJSON('/control/geocode/?q=' + encodeURIComponent(q), function (res) {
|
xhr = $.getJSON('/control/geocode/?q=' + encodeURIComponent(q), function (res) {
|
||||||
if (!res.results || !res.results.length) {
|
if (!res.results || !res.results.length) {
|
||||||
$notifications.attr("data-notify", "error");
|
$notifications.attr('data-notify', 'error')
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
lat = res.results[0].lat;
|
lat = res.results[0].lat
|
||||||
lon = res.results[0].lon;
|
lon = res.results[0].lon
|
||||||
if ($lat.val() == lat && $lon.val() == lon) {
|
if ($lat.val() == lat && $lon.val() == lon) {
|
||||||
$notifications.attr("data-notify", "");
|
$notifications.attr('data-notify', '')
|
||||||
}
|
} else if (touched) {
|
||||||
else if (touched) {
|
$notifications.attr('data-notify', 'confirm')
|
||||||
$notifications.attr("data-notify", "confirm");
|
} else {
|
||||||
}
|
$notifications.attr('data-notify', '')
|
||||||
else {
|
$lat.val(lat)
|
||||||
$notifications.attr("data-notify", "");
|
$lon.val(lon)
|
||||||
$lat.val(lat);
|
center(13)
|
||||||
$lon.val(lon);
|
}
|
||||||
center(13);
|
})
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
$lat.add($lon).change(function () {
|
$lat.add($lon).change(function () {
|
||||||
if (this.value !== "") touched = true;
|
if (this.value !== '') touched = true
|
||||||
center(13);
|
center(13)
|
||||||
}).keyup(function () {
|
}).keyup(function () {
|
||||||
window.clearTimeout(debounceLatLonChange);
|
window.clearTimeout(debounceLatLonChange)
|
||||||
debounceLatLonChange = window.setTimeout(center, 300);
|
debounceLatLonChange = window.setTimeout(center, 300)
|
||||||
});
|
})
|
||||||
|
|
||||||
$location.change(load);
|
$location.change(load)
|
||||||
$location.keyup(function () {
|
$location.keyup(function () {
|
||||||
window.clearTimeout(debounceLoad);
|
window.clearTimeout(debounceLoad)
|
||||||
debounceLoad = window.setTimeout(load, 1000);
|
debounceLoad = window.setTimeout(load, 1000)
|
||||||
if ($notifications.attr("data-notify") == "confirm" && lastLocation !== cleanup(this.value)) $notifications.attr("data-notify", "");
|
if ($notifications.attr('data-notify') == 'confirm' && lastLocation !== cleanup(this.value)) $notifications.attr('data-notify', '')
|
||||||
});
|
})
|
||||||
|
|
||||||
$updateButton.click(function() {
|
$updateButton.click(function () {
|
||||||
$lat.val(lat);
|
$lat.val(lat)
|
||||||
$lon.val(lon).trigger("change");// change-event is needed by bulk-edit
|
$lon.val(lon).trigger('change')// change-event is needed by bulk-edit
|
||||||
touched = false;
|
touched = false
|
||||||
center(13);
|
center(13)
|
||||||
$notifications.attr("data-notify", "updated");
|
$notifications.attr('data-notify', 'updated')
|
||||||
delayUpdateDismissal = window.setTimeout(function() {
|
delayUpdateDismissal = window.setTimeout(function () {
|
||||||
if ($notifications.attr("data-notify") == "updated") $notifications.attr("data-notify", "");
|
if ($notifications.attr('data-notify') == 'updated') $notifications.attr('data-notify', '')
|
||||||
}, 2500);
|
}, 2500)
|
||||||
});
|
})
|
||||||
|
|
||||||
// Map
|
// Map
|
||||||
var $grp = $(".geodata-group", this);
|
let $grp = $('.geodata-group', this)
|
||||||
var tiles = $grp.attr("data-tiles");
|
let tiles = $grp.attr('data-tiles')
|
||||||
var attrib = $grp.attr("data-attrib");
|
let attrib = $grp.attr('data-attrib')
|
||||||
if (tiles) {
|
if (tiles) {
|
||||||
var $map = $("<div>");
|
let $map = $('<div>')
|
||||||
$grp.append($("<div>").addClass("col-md-9 col-md-offset-3").append($map));
|
$grp.append($('<div>').addClass('col-md-9 col-md-offset-3').append($map))
|
||||||
var map = L.map($map.get(0));
|
let map = L.map($map.get(0))
|
||||||
L.tileLayer(tiles, {
|
L.tileLayer(tiles, {
|
||||||
attribution: attrib,
|
attribution: attrib,
|
||||||
maxZoom: 18,
|
maxZoom: 18,
|
||||||
}).addTo(map);
|
}).addTo(map)
|
||||||
|
|
||||||
function getpoint() {
|
function getpoint () {
|
||||||
if ($lat.val() !== "" && $lon.val() !== "") {
|
if ($lat.val() !== '' && $lon.val() !== '') {
|
||||||
var p = [parseFloat($lat.val().replace(",", ".")), parseFloat($lon.val().replace(",", "."))];
|
let p = [parseFloat($lat.val().replace(',', '.')), parseFloat($lon.val().replace(',', '.'))]
|
||||||
// Clip to valid ranges. Very invalid lon/lat values can even lead to browser crashes in leaflet apparently
|
// Clip to valid ranges. Very invalid lon/lat values can even lead to browser crashes in leaflet apparently
|
||||||
if (p[0] < -90) p[0] = -90
|
if (p[0] < -90) p[0] = -90
|
||||||
if (p[0] > 90) p[0] = 90
|
if (p[0] > 90) p[0] = 90
|
||||||
if (p[1] < -180) p[1] = -180
|
if (p[1] < -180) p[1] = -180
|
||||||
if (p[1] > 180) p[1] = 180
|
if (p[1] > 180) p[1] = 180
|
||||||
return p
|
return p
|
||||||
} else {
|
} else {
|
||||||
return [0.0, 0.0];
|
return [0.0, 0.0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var marker = L.marker(getpoint(), {
|
let marker = L.marker(getpoint(), {
|
||||||
draggable: 'true',
|
draggable: 'true',
|
||||||
icon: L.icon({
|
icon: L.icon({
|
||||||
iconUrl: $grp.attr("data-icon"),
|
iconUrl: $grp.attr('data-icon'),
|
||||||
shadowUrl: $grp.attr("data-shadow"),
|
shadowUrl: $grp.attr('data-shadow'),
|
||||||
iconSize: [25, 41],
|
iconSize: [25, 41],
|
||||||
iconAnchor: [12, 41],
|
iconAnchor: [12, 41],
|
||||||
popupAnchor: [1, -34],
|
popupAnchor: [1, -34],
|
||||||
tooltipAnchor: [16, -28],
|
tooltipAnchor: [16, -28],
|
||||||
shadowSize: [41, 41]
|
shadowSize: [41, 41]
|
||||||
})
|
})
|
||||||
});
|
})
|
||||||
marker.addTo(map);
|
marker.addTo(map)
|
||||||
marker.on("dragend", function (event) {
|
marker.on('dragend', function (event) {
|
||||||
var position = marker.getLatLng();
|
let position = marker.getLatLng()
|
||||||
marker.setLatLng(position, {
|
marker.setLatLng(position, {
|
||||||
draggable: 'true'
|
draggable: 'true'
|
||||||
}).bindPopup(position).update();
|
}).bindPopup(position).update()
|
||||||
$lat.val(position.lat.toFixed(7));
|
$lat.val(position.lat.toFixed(7))
|
||||||
$lon.val(position.lng.toFixed(7));
|
$lon.val(position.lng.toFixed(7))
|
||||||
touched = true;
|
touched = true
|
||||||
center(null);
|
center(null)
|
||||||
});
|
})
|
||||||
|
|
||||||
function center(zoom) {
|
function center (zoom) {
|
||||||
if ($lat.val() !== "" && $lon.val() !== "") {
|
if ($lat.val() !== '' && $lon.val() !== '') {
|
||||||
if (zoom) {
|
if (zoom) {
|
||||||
map.setView(getpoint(), zoom);
|
map.setView(getpoint(), zoom)
|
||||||
} else {
|
} else {
|
||||||
map.panTo(getpoint());
|
map.panTo(getpoint())
|
||||||
}
|
}
|
||||||
marker.setLatLng(getpoint(), {
|
marker.setLatLng(getpoint(), {
|
||||||
draggable: 'true'
|
draggable: 'true'
|
||||||
}).bindPopup(getpoint()).update();
|
}).bindPopup(getpoint()).update()
|
||||||
} else {
|
} else {
|
||||||
map.fitWorld();
|
map.fitWorld()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
center(13);
|
center(13)
|
||||||
} else {
|
} else {
|
||||||
function center(zoom) {
|
function center (zoom) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
})
|
||||||
});
|
})
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,39 +1,39 @@
|
|||||||
$(function () {
|
$(function () {
|
||||||
hideDeselected(false);
|
hideDeselected(false)
|
||||||
|
|
||||||
function hideDeselected(animate) {
|
function hideDeselected (animate) {
|
||||||
var v = $("input[name='quota_option']:checked").val(),
|
let v = $('input[name=\'quota_option\']:checked').val(),
|
||||||
fn = animate ? 'slideDown' : 'show';
|
fn = animate ? 'slideDown' : 'show'
|
||||||
if (v === "existing") {
|
if (v === 'existing') {
|
||||||
hideAll(animate);
|
hideAll(animate)
|
||||||
$("#existing-quota-group").children()[fn]();
|
$('#existing-quota-group').children()[fn]()
|
||||||
} else if (v === "new") {
|
} else if (v === 'new') {
|
||||||
hideAll(animate);
|
hideAll(animate)
|
||||||
if ($("#id_quota_add_new_name").val() === "") {
|
if ($('#id_quota_add_new_name').val() === '') {
|
||||||
$("#id_quota_add_new_name").val($("input[name^=name_]").first().val());
|
$('#id_quota_add_new_name').val($('input[name^=name_]').first().val())
|
||||||
}
|
}
|
||||||
$("#new-quota-group").children()[fn]();
|
$('#new-quota-group').children()[fn]()
|
||||||
} else {
|
} else {
|
||||||
hideAll(animate);
|
hideAll(animate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function hideAll(animate) {
|
function hideAll (animate) {
|
||||||
var fn = animate ? 'slideUp' : 'hide';
|
let fn = animate ? 'slideUp' : 'hide'
|
||||||
$("#new-quota-group").children()[fn]();
|
$('#new-quota-group').children()[fn]()
|
||||||
$("#existing-quota-group").children()[fn]();
|
$('#existing-quota-group').children()[fn]()
|
||||||
}
|
}
|
||||||
|
|
||||||
$("input[name='quota_option']").on('change',
|
$('input[name=\'quota_option\']').on('change',
|
||||||
function () {
|
function () {
|
||||||
hideDeselected(true);
|
hideDeselected(true)
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
|
|
||||||
function toggleblock() {
|
function toggleblock () {
|
||||||
$("#new-quota-group").closest('fieldset').toggle(!$("[name=has_variations][value=on]").prop('checked'));
|
$('#new-quota-group').closest('fieldset').toggle(!$('[name=has_variations][value=on]').prop('checked'))
|
||||||
}
|
}
|
||||||
|
|
||||||
$("[name=has_variations]").change(toggleblock);
|
$('[name=has_variations]').change(toggleblock)
|
||||||
toggleblock();
|
toggleblock()
|
||||||
});
|
})
|
||||||
|
|||||||
@@ -1,75 +1,75 @@
|
|||||||
function preview_task_callback(data, jqXHR, status) {
|
/* global gettext */
|
||||||
"use strict";
|
function preview_task_callback (data, _jqXHR, _status) {
|
||||||
if (data.item) {
|
'use strict'
|
||||||
$('#' + data.item + '_panel').data('ajaxing', false);
|
if (data.item) {
|
||||||
for (var m in data.msgs){
|
$('#' + data.item + '_panel').data('ajaxing', false)
|
||||||
var target = $('div[for=' + data.item + '][lang=' + m +']');
|
for (let m in data.msgs) {
|
||||||
if (target.length === 1){
|
let target = $('div[for=' + data.item + '][lang=' + m + ']')
|
||||||
target.html(data.msgs[m]);
|
if (target.length === 1) {
|
||||||
target.find('.placeholder').tooltip();
|
target.html(data.msgs[m])
|
||||||
}
|
target.find('.placeholder').tooltip()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function preview_task_error(item) {
|
function preview_task_error (item) {
|
||||||
"use strict";
|
'use strict'
|
||||||
return function(jqXHR, textStatus, errorThrown) {
|
return function (jqXHR, textStatus, _errorThrown) {
|
||||||
$('#' + item + '_panel').data('ajaxing', false);
|
$('#' + item + '_panel').data('ajaxing', false)
|
||||||
$('#' + item + '_preview div').text(gettext('An error has occurred.'));
|
$('#' + item + '_preview div').text(gettext('An error has occurred.'))
|
||||||
if (textStatus === "timeout") {
|
if (textStatus === 'timeout') {
|
||||||
alert(gettext("The request took too long. Please try again."));
|
alert(gettext('The request took too long. Please try again.'))
|
||||||
} else {
|
} else {
|
||||||
if (jqXHR.status >= 400 && jqXHR.status < 500) {
|
if (jqXHR.status >= 400 && jqXHR.status < 500) {
|
||||||
alert(gettext('An error of type {code} occurred.').replace(/\{code\}/, jqXHR.status));
|
alert(gettext('An error of type {code} occurred.').replace(/\{code\}/, jqXHR.status))
|
||||||
} else {
|
} else {
|
||||||
alert(gettext('We currently cannot reach the server. Please try again. ' +
|
alert(gettext('We currently cannot reach the server. Please try again. '
|
||||||
'Error code: {code}').replace(/\{code\}/, jqXHR.status));
|
+ 'Error code: {code}').replace(/\{code\}/, jqXHR.status))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mail_preview_setup($el) {
|
function mail_preview_setup ($el) {
|
||||||
$el.find('.mail-preview .placeholder').tooltip();
|
$el.find('.mail-preview .placeholder').tooltip()
|
||||||
$el.find('a[type=preview]').on('click', function () {
|
$el.find('a[type=preview]').on('click', function () {
|
||||||
var itemName = $(this).closest('.preview-panel').attr('for');
|
let itemName = $(this).closest('.preview-panel').attr('for')
|
||||||
if ($('#' + itemName + '_panel').data('ajaxing') || $(this).parent('.active').length !== 0) {
|
if ($('#' + itemName + '_panel').data('ajaxing') || $(this).parent('.active').length !== 0) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// gathering data
|
// gathering data
|
||||||
var parentForm = $(this).closest('form');
|
let parentForm = $(this).closest('form')
|
||||||
var previewUrl = $(parentForm).attr('mail-preview-url');
|
let previewUrl = $(parentForm).attr('mail-preview-url')
|
||||||
var token = $(parentForm).find('input[name=csrfmiddlewaretoken]').val();
|
let token = $(parentForm).find('input[name=csrfmiddlewaretoken]').val()
|
||||||
var dataString = 'item=' + itemName + '&csrfmiddlewaretoken=' + token;
|
let dataString = 'item=' + itemName + '&csrfmiddlewaretoken=' + token
|
||||||
$('#' + itemName + '_edit textarea, #' + itemName + '_edit input').each(function () {
|
$('#' + itemName + '_edit textarea, #' + itemName + '_edit input').each(function () {
|
||||||
dataString += '&' + $(this).serialize();
|
dataString += '&' + $(this).serialize()
|
||||||
});
|
})
|
||||||
|
|
||||||
// prepare for ajax
|
// prepare for ajax
|
||||||
$('#' + itemName + '_panel').data('ajaxing', true);
|
$('#' + itemName + '_panel').data('ajaxing', true)
|
||||||
$('#' + itemName + '_preview div').text(gettext('Generating messages …'));
|
$('#' + itemName + '_preview div').text(gettext('Generating messages …'))
|
||||||
|
|
||||||
$.ajax(
|
$.ajax(
|
||||||
{
|
{
|
||||||
'type': 'POST',
|
type: 'POST',
|
||||||
'url': previewUrl,
|
url: previewUrl,
|
||||||
'data': dataString,
|
data: dataString,
|
||||||
'success': preview_task_callback,
|
success: preview_task_callback,
|
||||||
'error': preview_task_error(itemName),
|
error: preview_task_error(itemName),
|
||||||
'dataType': 'json',
|
dataType: 'json',
|
||||||
'timeout': 60000,
|
timeout: 60000,
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
|
})
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$(function () {
|
$(function () {
|
||||||
"use strict";
|
'use strict'
|
||||||
mail_preview_setup($("body"));
|
mail_preview_setup($('body'))
|
||||||
$(document).on("pretix:bind-forms", function () {
|
$(document).on('pretix:bind-forms', function () {
|
||||||
mail_preview_setup($("body"));
|
mail_preview_setup($('body'))
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,59 +1,62 @@
|
|||||||
/*global $, gettext*/
|
/* global gettext */
|
||||||
$(function () {
|
$(function () {
|
||||||
if (!$(".form-order-change").length) {
|
if (!$('.form-order-change').length) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
$(".form-order-change").each(function () {
|
$('.form-order-change').each(function () {
|
||||||
var url = $(this).attr("data-pricecalc-endpoint");
|
let url = $(this).attr('data-pricecalc-endpoint')
|
||||||
var $itemvar = $(this).find("[name*=itemvar]");
|
let $itemvar = $(this).find('[name*=itemvar]')
|
||||||
var $subevent = $(this).find("[name*=subevent]");
|
let $subevent = $(this).find('[name*=subevent]')
|
||||||
var $tax_rule = $(this).find("[name*=tax_rule]");
|
let $tax_rule = $(this).find('[name*=tax_rule]')
|
||||||
var $price = $(this).find("[name*=price]");
|
let $price = $(this).find('[name*=price]')
|
||||||
var update_price = function () {
|
let update_price = function () {
|
||||||
console.log(url);
|
console.log(url)
|
||||||
var itemvar = $itemvar.val();
|
let itemvar = $itemvar.val()
|
||||||
var item = null;
|
let item
|
||||||
var variation = null;
|
let variation = null
|
||||||
if (itemvar.indexOf("-")) {
|
if (itemvar.indexOf('-')) {
|
||||||
item = parseInt(itemvar.split("-")[0]);
|
item = parseInt(itemvar.split('-')[0])
|
||||||
variation = parseInt(itemvar.split("-")[1]);
|
variation = parseInt(itemvar.split('-')[1])
|
||||||
} else {
|
} else {
|
||||||
item = parseInt(itemvar);
|
item = parseInt(itemvar)
|
||||||
}
|
}
|
||||||
$price.closest(".field-container").append("<small class=\"loading-indicator\"><span class=\"fa fa-cog fa-spin\"></span> " +
|
$price.closest('.field-container').append('<small class="loading-indicator"><span class="fa fa-cog fa-spin"></span> '
|
||||||
gettext("Calculating default price…") + "</small>");
|
+ gettext('Calculating default price…') + '</small>')
|
||||||
$.ajax(
|
$.ajax(
|
||||||
{
|
{
|
||||||
'type': 'POST',
|
type: 'POST',
|
||||||
'url': url,
|
url: url,
|
||||||
'headers': {'X-CSRFToken': $("input[name=csrfmiddlewaretoken]").val()},
|
headers: { 'X-CSRFToken': $('input[name=csrfmiddlewaretoken]').val() },
|
||||||
'data': JSON.stringify({
|
data: JSON.stringify({
|
||||||
'item': item,
|
item: item,
|
||||||
'variation': variation,
|
variation: variation,
|
||||||
'subevent': $subevent.val(),
|
subevent: $subevent.val(),
|
||||||
'tax_rule': $tax_rule.val(),
|
tax_rule: $tax_rule.val(),
|
||||||
'locale': $("body").attr("data-pretixlocale"),
|
locale: $('body').attr('data-pretixlocale'),
|
||||||
}),
|
}),
|
||||||
'contentType': "application/json",
|
contentType: 'application/json',
|
||||||
'success': function (data) {
|
success: function (data) {
|
||||||
$price.val(data.gross_formatted);
|
$price.val(data.gross_formatted)
|
||||||
$tax_rule.val(data.tax_rule);
|
$tax_rule.val(data.tax_rule)
|
||||||
$price.closest(".field-container").find(".loading-indicator").remove();
|
$price.closest('.field-container').find('.loading-indicator').remove()
|
||||||
},
|
},
|
||||||
// 'error': …
|
// 'error': …
|
||||||
'context': this,
|
context: this,
|
||||||
'dataType': 'json',
|
dataType: 'json',
|
||||||
'timeout': 30000
|
timeout: 30000
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
$itemvar.on("change", function () { $tax_rule.val(null); update_price() });
|
$itemvar.on('change', function () {
|
||||||
$tax_rule.on("change", update_price);
|
$tax_rule.val(null)
|
||||||
$subevent.on("change", update_price).on("change", function () {
|
update_price()
|
||||||
var seat = $(this).closest(".form-order-change").find("[id$=seat]");
|
})
|
||||||
if (seat.length) {
|
$tax_rule.on('change', update_price)
|
||||||
seat.prop("required", !!$subevent.val());
|
$subevent.on('change', update_price).on('change', function () {
|
||||||
}
|
let seat = $(this).closest('.form-order-change').find('[id$=seat]')
|
||||||
});
|
if (seat.length) {
|
||||||
});
|
seat.prop('required', !!$subevent.val())
|
||||||
});
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@@ -1,43 +1,43 @@
|
|||||||
function is_sandbox_supported() {
|
function is_sandbox_supported () {
|
||||||
const iframe = document.createElement('iframe');
|
const iframe = document.createElement('iframe')
|
||||||
return 'sandbox' in iframe;
|
return 'sandbox' in iframe
|
||||||
}
|
}
|
||||||
|
|
||||||
function safe_render(url, parent) {
|
function safe_render (url, parent) {
|
||||||
// Estimate the height that prevents the user from having to scroll on two levels to see the full email
|
// Estimate the height that prevents the user from having to scroll on two levels to see the full email
|
||||||
const height = (
|
const height = (
|
||||||
Math.max(400, window.innerHeight - parent.parent().get(0).getBoundingClientRect().top - document.querySelector("footer").getBoundingClientRect().height - 20)
|
Math.max(400, window.innerHeight - parent.parent().get(0).getBoundingClientRect().top - document.querySelector('footer').getBoundingClientRect().height - 20)
|
||||||
) + "px";
|
) + 'px'
|
||||||
|
|
||||||
const iframe = (
|
const iframe = (
|
||||||
// Per the HTML spec, a data: URL in an iframe is treated as its own origin:
|
// Per the HTML spec, a data: URL in an iframe is treated as its own origin:
|
||||||
// https://github.com/whatwg/html/pull/1756
|
// https://github.com/whatwg/html/pull/1756
|
||||||
// It is unclear, if Firefox complies, and the behaviour around data URLs is quite wild:
|
// It is unclear, if Firefox complies, and the behaviour around data URLs is quite wild:
|
||||||
// https://github.com/whatwg/html/issues/12091
|
// https://github.com/whatwg/html/issues/12091
|
||||||
// Together with the sandbox attribute disallowing all JavaScript, and the fact
|
// Together with the sandbox attribute disallowing all JavaScript, and the fact
|
||||||
// that we sanitize the HTML before we even save it to the database, this should
|
// that we sanitize the HTML before we even save it to the database, this should
|
||||||
// still be the safest way to render HTML in the context of our backend.
|
// still be the safest way to render HTML in the context of our backend.
|
||||||
$("<iframe>")
|
$('<iframe>')
|
||||||
.height(height)
|
.height(height)
|
||||||
.attr("class", "html-email")
|
.attr('class', 'html-email')
|
||||||
.attr("src", url)
|
.attr('src', url)
|
||||||
.attr("sandbox", "allow-popups allow-popups-to-escape-sandbox")
|
.attr('sandbox', 'allow-popups allow-popups-to-escape-sandbox')
|
||||||
.attr("csp", "script-src 'none'; font-src 'none'; connect-src 'none'; form-action 'none'; style-src 'unsafe-inline'") // respected only by chrome
|
.attr('csp', 'script-src \'none\'; font-src \'none\'; connect-src \'none\'; form-action \'none\'; style-src \'unsafe-inline\'') // respected only by chrome
|
||||||
.prop("credentialless", true) // respected only by chrome
|
.prop('credentialless', true) // respected only by chrome
|
||||||
);
|
)
|
||||||
|
|
||||||
console.log(parent, iframe);
|
console.log(parent, iframe)
|
||||||
parent.append(iframe);
|
parent.append(iframe)
|
||||||
}
|
}
|
||||||
|
|
||||||
$(function () {
|
$(function () {
|
||||||
const script_element = $("#mail_body_html");
|
const script_element = $('#mail_body_html')
|
||||||
if (!script_element.length) return;
|
if (!script_element.length) return
|
||||||
if (!is_sandbox_supported()) {
|
if (!is_sandbox_supported()) {
|
||||||
// Browser is too old for <iframe sandbox>
|
// Browser is too old for <iframe sandbox>
|
||||||
$(script_element.parent()).text("Please switch to a modern browser to view HTML content safely.");
|
$(script_element.parent()).text('Please switch to a modern browser to view HTML content safely.')
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
safe_render(JSON.parse(script_element.html()), script_element.parent());
|
safe_render(JSON.parse(script_element.html()), script_element.parent())
|
||||||
});
|
})
|
||||||
|
|||||||
@@ -1,84 +1,89 @@
|
|||||||
$(function() {
|
/* global gettext */
|
||||||
var plugins = $(".plugin-container").toArray().map(function(el) {
|
$(function () {
|
||||||
return {
|
let plugins = $('.plugin-container').toArray().map(function (el) {
|
||||||
sortName: el.getAttribute('data-plugin-name').toLowerCase().replace(/pretix /g, ''),
|
return {
|
||||||
name: el.getAttribute('data-plugin-name').toLowerCase(),
|
sortName: el.getAttribute('data-plugin-name').toLowerCase().replace(/pretix /g, ''),
|
||||||
module: el.getAttribute('data-plugin-module').toLowerCase(),
|
name: el.getAttribute('data-plugin-name').toLowerCase(),
|
||||||
description: $(el).find('.plugin-description').text().toLowerCase(),
|
module: el.getAttribute('data-plugin-module').toLowerCase(),
|
||||||
html: el.outerHTML,
|
description: $(el).find('.plugin-description').text().toLowerCase(),
|
||||||
category: $(el).closest('[data-plugin-category]').attr('data-plugin-category'),
|
html: el.outerHTML,
|
||||||
categoryLabel: $(el).closest('[data-plugin-category]').attr('data-plugin-category-label'),
|
category: $(el).closest('[data-plugin-category]').attr('data-plugin-category'),
|
||||||
active: !!$(el).has('[data-is-active]').length,
|
categoryLabel: $(el).closest('[data-plugin-category]').attr('data-plugin-category-label'),
|
||||||
}
|
active: !!$(el).has('[data-is-active]').length,
|
||||||
});
|
}
|
||||||
function SearchMatcher(term, fields) {
|
})
|
||||||
this.searchFor = term.toLowerCase().split(/\s+/);
|
function SearchMatcher (term, fields) {
|
||||||
this.fields = fields;
|
this.searchFor = term.toLowerCase().split(/\s+/)
|
||||||
}
|
this.fields = fields
|
||||||
function inStringRanked(haystack, needle) {
|
}
|
||||||
let pos = -1, rank = 0;
|
function inStringRanked (haystack, needle) {
|
||||||
do {
|
let pos = -1, rank = 0
|
||||||
pos = haystack.indexOf(needle, pos + 1);
|
do {
|
||||||
if (pos !== -1) rank = 10;
|
pos = haystack.indexOf(needle, pos + 1)
|
||||||
if (pos === 0 || haystack.charCodeAt(pos - 1) <= 47)
|
if (pos !== -1) rank = 10
|
||||||
return 15; // string start or word start (=char before match is special char)
|
if (pos === 0 || haystack.charCodeAt(pos - 1) <= 47)
|
||||||
} while (pos !== -1);
|
return 15 // string start or word start (=char before match is special char)
|
||||||
return rank;
|
} while (pos !== -1)
|
||||||
}
|
return rank
|
||||||
SearchMatcher.prototype.isMatch = function(obj) {
|
}
|
||||||
let rank = 0;
|
SearchMatcher.prototype.isMatch = function (obj) {
|
||||||
for(let j = 0; j < this.searchFor.length; j++) {
|
let rank = 0
|
||||||
var searchFor = this.searchFor[j];
|
for (let j = 0; j < this.searchFor.length; j++) {
|
||||||
for(let i = this.fields.length - 1; i >= 0; i--) {
|
let searchFor = this.searchFor[j]
|
||||||
var result = inStringRanked(obj[this.fields[i]], searchFor);
|
for (let i = this.fields.length - 1; i >= 0; i--) {
|
||||||
if (result) {
|
let result = inStringRanked(obj[this.fields[i]], searchFor)
|
||||||
rank += (i + 1) * result;
|
if (result) {
|
||||||
break;
|
rank += (i + 1) * result
|
||||||
}
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return rank;
|
}
|
||||||
}
|
return rank
|
||||||
function strcmp(a, b) {
|
}
|
||||||
return a > b ? 1 : a < b ? -1 : 0;
|
function strcmp (a, b) {
|
||||||
}
|
return a > b ? 1 : a < b ? -1 : 0
|
||||||
var $results_box = $("#plugin_search_results");
|
}
|
||||||
var $plugin_tabs = $("#plugin_tabs");
|
let $results_box = $('#plugin_search_results')
|
||||||
var $results = $("#plugin_search_results .plugin-list");
|
let $plugin_tabs = $('#plugin_tabs')
|
||||||
function search() {
|
let $results = $('#plugin_search_results .plugin-list')
|
||||||
$results.html("");
|
function search () {
|
||||||
var value = $("#plugin_search_input").val();
|
$results.html('')
|
||||||
var only_active = $("input[name=plugin_state_filter][value=active]").prop("checked");
|
let value = $('#plugin_search_input').val()
|
||||||
if (!value && !only_active) {
|
let only_active = $('input[name=plugin_state_filter][value=active]').prop('checked')
|
||||||
$results_box.hide(); $plugin_tabs.show();
|
if (!value && !only_active) {
|
||||||
return;
|
$results_box.hide()
|
||||||
}
|
$plugin_tabs.show()
|
||||||
$results_box.show(); $plugin_tabs.hide();
|
return
|
||||||
var matcher = new SearchMatcher(value, ["description", "module", "name"]);
|
}
|
||||||
var matches = [];
|
$results_box.show()
|
||||||
for(const plugin of plugins) {
|
$plugin_tabs.hide()
|
||||||
if (only_active && !plugin.active) continue;
|
let matcher = new SearchMatcher(value, ['description', 'module', 'name'])
|
||||||
var rank = matcher.isMatch(plugin);
|
let matches = []
|
||||||
if (!rank) continue;
|
for (const plugin of plugins) {
|
||||||
matches.push([rank, plugin]);
|
if (only_active && !plugin.active)
|
||||||
}
|
continue
|
||||||
matches.sort(function (a,b) { return (b[0]-a[0]) || strcmp(a[1].sortName, b[1].sortName); })
|
let rank = matcher.isMatch(plugin)
|
||||||
$results.append(matches.map(function(res) { return $(res[1].html).prepend('<span class="pull-right">' + res[1].categoryLabel + '</span>'); }))
|
if (!rank)
|
||||||
$results.find(".panel-body, .panel, .featured-plugin, .btn-lg").removeClass("panel-body panel featured-plugin btn-lg");
|
continue
|
||||||
if (matches.length === 0) {
|
matches.push([rank, plugin])
|
||||||
$results.append(gettext("No results"));
|
}
|
||||||
}
|
matches.sort(function (a, b) { return (b[0] - a[0]) || strcmp(a[1].sortName, b[1].sortName) })
|
||||||
}
|
$results.append(matches.map(function (res) { return $(res[1].html).prepend('<span class="pull-right">' + res[1].categoryLabel + '</span>') }))
|
||||||
$("#plugin_search_input").on("input", search);
|
$results.find('.panel-body, .panel, .featured-plugin, .btn-lg').removeClass('panel-body panel featured-plugin btn-lg')
|
||||||
$("input[name=plugin_state_filter]").on("change", search);
|
if (matches.length === 0) {
|
||||||
$results_box.find("button.close").on("click", function() {
|
$results.append(gettext('No results'))
|
||||||
$("input[name=plugin_state_filter][value=all]").prop("checked", true).trigger("click");
|
}
|
||||||
$("#plugin_search_input").val("").trigger("input");
|
}
|
||||||
});
|
$('#plugin_search_input').on('input', search)
|
||||||
if (location.search) {
|
$('input[name=plugin_state_filter]').on('change', search)
|
||||||
var search = new URLSearchParams(location.search);
|
$results_box.find('button.close').on('click', function () {
|
||||||
if (search.has('q')) {
|
$('input[name=plugin_state_filter][value=all]').prop('checked', true).trigger('click')
|
||||||
$("#plugin_search_input").val(search.get("q")).trigger("input");
|
$('#plugin_search_input').val('').trigger('input')
|
||||||
}
|
})
|
||||||
}
|
if (location.search) {
|
||||||
|
var search = new URLSearchParams(location.search)
|
||||||
|
if (search.has('q')) {
|
||||||
|
$('#plugin_search_input').val(search.get('q')).trigger('input')
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,156 +1,156 @@
|
|||||||
/*global $, Morris, gettext*/
|
/* global Morris, gettext, apiGET, i18nToString */
|
||||||
$(function () {
|
$(function () {
|
||||||
// Question view
|
// Question view
|
||||||
if (!$("#question_chart").length) {
|
if (!$('#question_chart').length) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
$(".chart").css("height", "250px");
|
$('.chart').css('height', '250px')
|
||||||
var data_type = $("#question_chart").attr("data-type"),
|
let data_type = $('#question_chart').attr('data-type'),
|
||||||
data = JSON.parse($("#question-chart-data").text() || "[]"),
|
data = JSON.parse($('#question-chart-data').text() || '[]'),
|
||||||
others_sum = 0,
|
others_sum = 0,
|
||||||
max_num = 8;
|
max_num = 8
|
||||||
|
|
||||||
data = data.map(function (d) {
|
data = data.map(function (d) {
|
||||||
return {
|
return {
|
||||||
'value': d.count,
|
value: d.count,
|
||||||
'label': d.answer.length > 20 ? d.answer.substring(0, 20) + '…' : d.answer,
|
label: d.answer.length > 20 ? d.answer.substring(0, 20) + '…' : d.answer,
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
if (data_type == 'N') {
|
if (data_type == 'N') {
|
||||||
// Sort
|
// Sort
|
||||||
data.sort(function (a, b) {
|
data.sort(function (a, b) {
|
||||||
if (parseFloat(a.label) > parseFloat(b.label)) {
|
if (parseFloat(a.label) > parseFloat(b.label)) {
|
||||||
return 1;
|
return 1
|
||||||
} else if (parseFloat(a.label) < parseFloat(b.label)) {
|
} else if (parseFloat(a.label) < parseFloat(b.label)) {
|
||||||
return -1;
|
return -1
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
max_num = 20;
|
max_num = 20
|
||||||
}
|
}
|
||||||
|
|
||||||
// Limit shown options
|
// Limit shown options
|
||||||
if (data.length > max_num) {
|
if (data.length > max_num) {
|
||||||
for (var i = max_num; i < data.length; i++) {
|
for (let i = max_num; i < data.length; i++) {
|
||||||
others_sum += data[i].value;
|
others_sum += data[i].value
|
||||||
}
|
}
|
||||||
data = data.slice(0, max_num);
|
data = data.slice(0, max_num)
|
||||||
data.push({'value': others_sum, 'label': gettext('Others')});
|
data.push({ value: others_sum, label: gettext('Others') })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data_type === 'B') {
|
if (data_type === 'B') {
|
||||||
var colors;
|
let colors
|
||||||
if (data[0].answer_bool) {
|
if (data[0].answer_bool) {
|
||||||
colors = ['#50A167', '#C44F4F'];
|
colors = ['#50A167', '#C44F4F']
|
||||||
} else {
|
} else {
|
||||||
colors = ['#C44F4F', '#50A167'];
|
colors = ['#C44F4F', '#50A167']
|
||||||
}
|
}
|
||||||
new Morris.Donut({
|
new Morris.Donut({
|
||||||
element: 'question_chart',
|
element: 'question_chart',
|
||||||
data: data,
|
data: data,
|
||||||
resize: true,
|
resize: true,
|
||||||
colors: colors
|
colors: colors
|
||||||
});
|
})
|
||||||
} else if (data_type === 'C') {
|
} else if (data_type === 'C') {
|
||||||
new Morris.Donut({
|
new Morris.Donut({
|
||||||
element: 'question_chart',
|
element: 'question_chart',
|
||||||
data: data,
|
data: data,
|
||||||
resize: true,
|
resize: true,
|
||||||
colors: [
|
colors: [
|
||||||
'#7F4A91',
|
'#7F4A91',
|
||||||
'#50A167',
|
'#50A167',
|
||||||
'#FFB419',
|
'#FFB419',
|
||||||
'#5F9CD4',
|
'#5F9CD4',
|
||||||
'#C44F4F',
|
'#C44F4F',
|
||||||
'#83FFFA',
|
'#83FFFA',
|
||||||
'#FF6C38',
|
'#FF6C38',
|
||||||
'#1f5b8e',
|
'#1f5b8e',
|
||||||
'#2d683c',
|
'#2d683c',
|
||||||
]
|
]
|
||||||
});
|
})
|
||||||
} else { // M, N, S, T
|
} else { // M, N, S, T
|
||||||
new Morris.Bar({
|
new Morris.Bar({
|
||||||
element: 'question_chart',
|
element: 'question_chart',
|
||||||
data: data,
|
data: data,
|
||||||
resize: true,
|
resize: true,
|
||||||
xkey: 'label',
|
xkey: 'label',
|
||||||
ykeys: ['value'],
|
ykeys: ['value'],
|
||||||
labels: [gettext('Count')]
|
labels: [gettext('Count')]
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// N, S, T
|
// N, S, T
|
||||||
});
|
})
|
||||||
|
|
||||||
$(function () {
|
$(function () {
|
||||||
// Question editor
|
// Question editor
|
||||||
|
|
||||||
if (!$("#answer-options").length) {
|
if (!$('#answer-options').length) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Question editor
|
// Question editor
|
||||||
$("#id_type").change(question_page_toggle_view);
|
$('#id_type').change(question_page_toggle_view)
|
||||||
$("#id_required").change(question_page_toggle_view);
|
$('#id_required').change(question_page_toggle_view)
|
||||||
question_page_toggle_view();
|
question_page_toggle_view()
|
||||||
|
|
||||||
function question_page_toggle_view() {
|
function question_page_toggle_view () {
|
||||||
var show = $("#id_type").val() == "C" || $("#id_type").val() == "M";
|
let show = $('#id_type').val() == 'C' || $('#id_type').val() == 'M'
|
||||||
$("#answer-options").toggle(show);
|
$('#answer-options').toggle(show)
|
||||||
|
|
||||||
$("#valid-date").toggle($("#id_type").val() == "D");
|
$('#valid-date').toggle($('#id_type').val() == 'D')
|
||||||
$("#valid-datetime").toggle($("#id_type").val() == "W");
|
$('#valid-datetime').toggle($('#id_type').val() == 'W')
|
||||||
$("#valid-string").toggle($("#id_type").val() == "T" || $("#id_type").val() == "S");
|
$('#valid-string').toggle($('#id_type').val() == 'T' || $('#id_type').val() == 'S')
|
||||||
$("#valid-number").toggle($("#id_type").val() == "N");
|
$('#valid-number').toggle($('#id_type').val() == 'N')
|
||||||
$("#valid-file").toggle($("#id_type").val() == "F");
|
$('#valid-file').toggle($('#id_type').val() == 'F')
|
||||||
|
|
||||||
show = $("#id_type").val() == "B" && $("#id_required").prop("checked");
|
show = $('#id_type').val() == 'B' && $('#id_required').prop('checked')
|
||||||
$(".alert-required-boolean").toggle(show);
|
$('.alert-required-boolean').toggle(show)
|
||||||
}
|
}
|
||||||
|
|
||||||
var $val = $("#id_dependency_values");
|
let $val = $('#id_dependency_values')
|
||||||
var $dq = $("#id_dependency_question");
|
let $dq = $('#id_dependency_question')
|
||||||
var oldval = JSON.parse($("#dependency_value_val").text());
|
let oldval = JSON.parse($('#dependency_value_val').text())
|
||||||
function update_dependency_options() {
|
function update_dependency_options () {
|
||||||
$val.parent().find(".loading-indicator").remove();
|
$val.parent().find('.loading-indicator').remove()
|
||||||
$("#id_dependency_values option").remove();
|
$('#id_dependency_values option').remove()
|
||||||
$("#id_dependency_values").prop("required", false);
|
$('#id_dependency_values').prop('required', false)
|
||||||
|
|
||||||
var val = $dq.children("option:selected").val();
|
let val = $dq.children('option:selected').val()
|
||||||
if (!val) {
|
if (!val) {
|
||||||
$("#id_dependency_values").show();
|
$('#id_dependency_values').show()
|
||||||
$val.show();
|
$val.show()
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
$("#id_dependency_values").prop("required", true);
|
$('#id_dependency_values').prop('required', true)
|
||||||
$val.hide();
|
$val.hide()
|
||||||
$val.parent().append("<div class=\"help-block loading-indicator\"><span class=\"fa" +
|
$val.parent().append('<div class="help-block loading-indicator"><span class="fa'
|
||||||
" fa-cog fa-spin\"></span></div>");
|
+ ' fa-cog fa-spin"></span></div>')
|
||||||
|
|
||||||
// the container_type parameter is undocumented. this API is going to change in a later release.
|
// the container_type parameter is undocumented. this API is going to change in a later release.
|
||||||
apiGET('/api/v1/organizers/' + $("body").attr("data-organizer") + '/events/' + $("body").attr("data-event") + '/questions/' + val + '/?container_type=' + encodeURIComponent($dq.data('container-type')), function (data) {
|
apiGET('/api/v1/organizers/' + $('body').attr('data-organizer') + '/events/' + $('body').attr('data-event') + '/questions/' + val + '/?container_type=' + encodeURIComponent($dq.data('container-type')), function (data) {
|
||||||
if (data.type === "B") {
|
if (data.type === 'B') {
|
||||||
$val.append($("<option>").attr("value", "True").text(gettext("Yes")));
|
$val.append($('<option>').attr('value', 'True').text(gettext('Yes')))
|
||||||
$val.append($("<option>").attr("value", "False").text(gettext("No")));
|
$val.append($('<option>').attr('value', 'False').text(gettext('No')))
|
||||||
} else {
|
} else {
|
||||||
for (var i = 0; i < data.options.length; i++) {
|
for (let i = 0; i < data.options.length; i++) {
|
||||||
var opt = data.options[i];
|
let opt = data.options[i]
|
||||||
var $opt = $("<option>").attr("value", opt.identifier).text(i18nToString(opt.answer));
|
let $opt = $('<option>').attr('value', opt.identifier).text(i18nToString(opt.answer))
|
||||||
$val.append($opt);
|
$val.append($opt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (oldval) {
|
if (oldval) {
|
||||||
$val.val(oldval);
|
$val.val(oldval)
|
||||||
}
|
}
|
||||||
$val.parent().find(".loading-indicator").remove();
|
$val.parent().find('.loading-indicator').remove()
|
||||||
$val.show();
|
$val.show()
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
update_dependency_options();
|
update_dependency_options()
|
||||||
$dq.change(update_dependency_options);
|
$dq.change(update_dependency_options)
|
||||||
});
|
})
|
||||||
|
|||||||
@@ -1,50 +1,50 @@
|
|||||||
$(function () {
|
$(function () {
|
||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
var ticket_type_quota_calculation = function () {
|
let ticket_type_quota_calculation = function () {
|
||||||
var sum = 0;
|
let sum = 0
|
||||||
$("#ticket-type-formset div[data-formset-form]").each(function () {
|
$('#ticket-type-formset div[data-formset-form]').each(function () {
|
||||||
if (!$(this).find("input[name$=DELETE]").prop("checked")) {
|
if (!$(this).find('input[name$=DELETE]').prop('checked')) {
|
||||||
var val = $(this).find("input[name$=quota]").val();
|
let val = $(this).find('input[name$=quota]').val()
|
||||||
if (val === "") {
|
if (val === '') {
|
||||||
sum = "∞";
|
sum = '∞'
|
||||||
} else if (sum !== "∞") {
|
} else if (sum !== '∞') {
|
||||||
sum += parseInt(val);
|
sum += parseInt(val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
$("#total-capacity").text(sum);
|
$('#total-capacity').text(sum)
|
||||||
};
|
}
|
||||||
|
|
||||||
var toggle_payment = function () {
|
let toggle_payment = function () {
|
||||||
var any = false;
|
let any = false
|
||||||
$("#ticket-type-formset div[data-formset-form]").each(function () {
|
$('#ticket-type-formset div[data-formset-form]').each(function () {
|
||||||
if (!$(this).find("input[name$=DELETE]").prop("checked")) {
|
if (!$(this).find('input[name$=DELETE]').prop('checked')) {
|
||||||
var val = $(this).find("input[name$=default_price]").val();
|
let val = $(this).find('input[name$=default_price]').val()
|
||||||
if (/.*[1-9].*/.test(val)) {
|
if (/.*[1-9].*/.test(val)) {
|
||||||
any = true;
|
any = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
if ($("#quick-setup-step-payment:visible").length && !any) {
|
if ($('#quick-setup-step-payment:visible').length && !any) {
|
||||||
$("#quick-setup-step-payment").stop().slideUp();
|
$('#quick-setup-step-payment').stop().slideUp()
|
||||||
} else if (!$("#quick-setup-step-payment:visible").length && any) {
|
} else if (!$('#quick-setup-step-payment:visible').length && any) {
|
||||||
$("#quick-setup-step-payment").stop().slideDown();
|
$('#quick-setup-step-payment').stop().slideDown()
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
$("#ticket-type-formset").bind("formAdded", ticket_type_quota_calculation);
|
$('#ticket-type-formset').bind('formAdded', ticket_type_quota_calculation)
|
||||||
$("#ticket-type-formset").on("change keyup keydown keypress", "input", function () {
|
$('#ticket-type-formset').on('change keyup keydown keypress', 'input', function () {
|
||||||
ticket_type_quota_calculation();
|
ticket_type_quota_calculation()
|
||||||
toggle_payment();
|
toggle_payment()
|
||||||
});
|
})
|
||||||
ticket_type_quota_calculation();
|
ticket_type_quota_calculation()
|
||||||
toggle_payment();
|
toggle_payment()
|
||||||
|
|
||||||
$("#total-capacity-edit").click(function () {
|
$('#total-capacity-edit').click(function () {
|
||||||
$("#id_total_quota").val(parseInt($("#total-capacity").text()));
|
$('#id_total_quota').val(parseInt($('#total-capacity').text()))
|
||||||
$("#total-capacity").hide();
|
$('#total-capacity').hide()
|
||||||
$("#id_total_quota").closest("div").removeClass("sr-only");
|
$('#id_total_quota').closest('div').removeClass('sr-only')
|
||||||
$("#total-capacity-edit").hide();
|
$('#total-capacity-edit').hide()
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|||||||
@@ -1,43 +1,43 @@
|
|||||||
/*globals $, Morris, gettext*/
|
/* globals Morris */
|
||||||
$(function () {
|
$(function () {
|
||||||
if (!$("#quota-stats").length) {
|
if (!$('#quota-stats').length) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
$(".chart").css("height", "250px");
|
$('.chart').css('height', '250px')
|
||||||
new Morris.Donut({
|
new Morris.Donut({
|
||||||
element: 'quota_chart',
|
element: 'quota_chart',
|
||||||
data: JSON.parse($("#quota-chart-data").html()),
|
data: JSON.parse($('#quota-chart-data').html()),
|
||||||
resize: true,
|
resize: true,
|
||||||
colors: [
|
colors: [
|
||||||
'#0044CC', // paid
|
'#0044CC', // paid
|
||||||
'#0088CC', // pending
|
'#0088CC', // pending
|
||||||
'#BD362F', // vouchers
|
'#BD362F', // vouchers
|
||||||
'#F89406', // carts
|
'#F89406', // carts
|
||||||
'#51A351' // available
|
'#51A351' // available
|
||||||
]
|
]
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
$(function () {
|
$(function () {
|
||||||
if (!$("input[name=itemvars]").length) {
|
if (!$('input[name=itemvars]').length) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
var autofill = ($("#id_name").val() === "");
|
let autofill = ($('#id_name').val() === '')
|
||||||
|
|
||||||
$("#id_name").on("change keyup keydown keypress", function () {
|
$('#id_name').on('change keyup keydown keypress', function () {
|
||||||
autofill = false;
|
autofill = false
|
||||||
})
|
})
|
||||||
|
|
||||||
function do_autofill() {
|
function do_autofill () {
|
||||||
if (autofill) {
|
if (autofill) {
|
||||||
var names = [];
|
let names = []
|
||||||
$("input[name=itemvars]:checked").each(function () {
|
$('input[name=itemvars]:checked').each(function () {
|
||||||
names.push($.trim($(this).closest("label").text()))
|
names.push($.trim($(this).closest('label').text()))
|
||||||
});
|
})
|
||||||
$("#id_name").val(names.join(', '));
|
$('#id_name').val(names.join(', '))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$("input[name=itemvars]").change(do_autofill);
|
$('input[name=itemvars]').change(do_autofill)
|
||||||
do_autofill();
|
do_autofill()
|
||||||
});
|
})
|
||||||
|
|||||||
@@ -1,21 +1,19 @@
|
|||||||
/*globals $, Morris, gettext, RRule, RRuleSet*/
|
function rrule_form_toggles ($form) {
|
||||||
function rrule_form_toggles($form) {
|
let freq = $form.find('select[name*=freq]').val()
|
||||||
var freq = $form.find("select[name*=freq]").val();
|
$form.find('.repeat-yearly').toggle(freq === 'yearly')
|
||||||
$form.find(".repeat-yearly").toggle(freq === "yearly");
|
$form.find('.repeat-monthly').toggle(freq === 'monthly')
|
||||||
$form.find(".repeat-monthly").toggle(freq === "monthly");
|
$form.find('.repeat-weekly').toggle(freq === 'weekly')
|
||||||
$form.find(".repeat-weekly").toggle(freq === "weekly");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function rrule_bind_form($form) {
|
function rrule_bind_form ($form) {
|
||||||
$form.find("select[name*=freq]").change(function () {
|
$form.find('select[name*=freq]').change(function () {
|
||||||
rrule_form_toggles($form);
|
rrule_form_toggles($form)
|
||||||
});
|
})
|
||||||
rrule_form_toggles($form);
|
rrule_form_toggles($form)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$(document).on('pretix:bind-forms', function () {
|
||||||
$(document).on("pretix:bind-forms", function () {
|
$('.rrule-form').each(function () {
|
||||||
$(".rrule-form").each(function () {
|
rrule_bind_form($(this))
|
||||||
rrule_bind_form($(this));
|
})
|
||||||
});
|
})
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,226 +1,225 @@
|
|||||||
/*globals $, Morris, gettext, RRule, RRuleSet*/
|
/* globals ngettext, rrule */
|
||||||
|
|
||||||
$(document).on("pretix:bind-forms", function () {
|
$(document).on('pretix:bind-forms', function () {
|
||||||
if (!$("div[data-formset-prefix=checkinlist_set]").length) {
|
if (!$('div[data-formset-prefix=checkinlist_set]').length) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
function parse_weekday(wd) {
|
function parse_weekday (wd) {
|
||||||
map = {
|
map = {
|
||||||
'MO': 0,
|
MO: 0,
|
||||||
'TU': 1,
|
TU: 1,
|
||||||
'WE': 2,
|
WE: 2,
|
||||||
'TH': 3,
|
TH: 3,
|
||||||
'FR': 4,
|
FR: 4,
|
||||||
'SA': 5,
|
SA: 5,
|
||||||
'SU': 6
|
SU: 6
|
||||||
}
|
}
|
||||||
if (wd.indexOf(",") > 0) {
|
if (wd.indexOf(',') > 0) {
|
||||||
var wds = [];
|
let wds = []
|
||||||
$.each(wd.split(","), function (k, v) {
|
$.each(wd.split(','), function (k, v) {
|
||||||
wds.push(map[v]);
|
wds.push(map[v])
|
||||||
});
|
})
|
||||||
return wds;
|
return wds
|
||||||
} else {
|
} else {
|
||||||
return map[wd];
|
return map[wd]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RRule editor
|
// RRule editor
|
||||||
function rrule_preview() {
|
function rrule_preview () {
|
||||||
var ruleset = new rrule.RRuleSet();
|
let ruleset = new rrule.RRuleSet()
|
||||||
|
|
||||||
$(".rrule-form").each(function () {
|
$('.rrule-form').each(function () {
|
||||||
if ($(this).find("input[name$=DELETE]").prop("checked")) {
|
if ($(this).find('input[name$=DELETE]').prop('checked')) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var rule_args = {};
|
let rule_args = {}
|
||||||
var $form = $(this);
|
let $form = $(this)
|
||||||
var freq = $form.find("select[name*=freq]").val();
|
let freq = $form.find('select[name*=freq]').val()
|
||||||
if (!$form.find("input[name*=dtstart]").data("DateTimePicker")) {
|
if (!$form.find('input[name*=dtstart]').data('DateTimePicker')) {
|
||||||
// uninitialized
|
// uninitialized
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
var dtstart = $form.find("input[name*=dtstart]").data("DateTimePicker").date();
|
let dtstart = $form.find('input[name*=dtstart]').data('DateTimePicker').date()
|
||||||
dtstart = dtstart.add(dtstart.utcOffset(), 'm').add(12, 'h').utcOffset(0);
|
dtstart = dtstart.add(dtstart.utcOffset(), 'm').add(12, 'h').utcOffset(0)
|
||||||
rule_args.dtstart = dtstart.toDate();
|
rule_args.dtstart = dtstart.toDate()
|
||||||
rule_args.interval = parseInt($form.find("input[name*=interval]").val()) || 1;
|
rule_args.interval = parseInt($form.find('input[name*=interval]').val()) || 1
|
||||||
|
|
||||||
if (freq === 'yearly') {
|
if (freq === 'yearly') {
|
||||||
rule_args.freq = rrule.RRule.YEARLY;
|
rule_args.freq = rrule.RRule.YEARLY
|
||||||
|
|
||||||
var same = $form.find("input[name*=yearly_same]:checked").val();
|
var same = $form.find('input[name*=yearly_same]:checked').val()
|
||||||
if (same === "off") {
|
if (same === 'off') {
|
||||||
rule_args.bysetpos = parseInt($form.find("select[name*=yearly_bysetpos]").val());
|
rule_args.bysetpos = parseInt($form.find('select[name*=yearly_bysetpos]').val())
|
||||||
rule_args.byweekday = parse_weekday($form.find("select[name*=yearly_byweekday]").val());
|
rule_args.byweekday = parse_weekday($form.find('select[name*=yearly_byweekday]').val())
|
||||||
rule_args.bymonth = parseInt($form.find("select[name*=yearly_bymonth]").val());
|
rule_args.bymonth = parseInt($form.find('select[name*=yearly_bymonth]').val())
|
||||||
}
|
}
|
||||||
} else if (freq === 'monthly') {
|
} else if (freq === 'monthly') {
|
||||||
rule_args.freq = rrule.RRule.MONTHLY;
|
rule_args.freq = rrule.RRule.MONTHLY
|
||||||
|
|
||||||
var same = $form.find("input[name*=monthly_same]:checked").val();
|
var same = $form.find('input[name*=monthly_same]:checked').val()
|
||||||
if (same === "off") {
|
if (same === 'off') {
|
||||||
rule_args.bysetpos = parseInt($form.find("select[name*=monthly_bysetpos]").val());
|
rule_args.bysetpos = parseInt($form.find('select[name*=monthly_bysetpos]').val())
|
||||||
rule_args.byweekday = parse_weekday($form.find("select[name*=monthly_byweekday]").val());
|
rule_args.byweekday = parse_weekday($form.find('select[name*=monthly_byweekday]').val())
|
||||||
}
|
}
|
||||||
} else if (freq === 'weekly') {
|
} else if (freq === 'weekly') {
|
||||||
rule_args.freq = rrule.RRule.WEEKLY;
|
rule_args.freq = rrule.RRule.WEEKLY
|
||||||
|
|
||||||
var days = [];
|
let days = []
|
||||||
$form.find("input[name*=weekly_byweekday]:checked").each(function () {
|
$form.find('input[name*=weekly_byweekday]:checked').each(function () {
|
||||||
days.push(parse_weekday($(this).val()));
|
days.push(parse_weekday($(this).val()))
|
||||||
});
|
})
|
||||||
if (days.length !== 0) {
|
if (days.length !== 0) {
|
||||||
rule_args.byweekday = days;
|
rule_args.byweekday = days
|
||||||
}
|
}
|
||||||
} else if (freq === 'daily') {
|
} else if (freq === 'daily') {
|
||||||
rule_args.freq = rrule.RRule.DAILY;
|
rule_args.freq = rrule.RRule.DAILY
|
||||||
}
|
}
|
||||||
|
|
||||||
var end = $form.find("input[name*=end]:checked").val();
|
let end = $form.find('input[name*=end]:checked').val()
|
||||||
if (end === "count") {
|
if (end === 'count') {
|
||||||
rule_args.count = Math.max(parseInt($form.find("input[name*=count]").val()) || 1, 1);
|
rule_args.count = Math.max(parseInt($form.find('input[name*=count]').val()) || 1, 1)
|
||||||
} else {
|
} else {
|
||||||
var date = $form.find("input[name*=until]").data("DateTimePicker").date();
|
let date = $form.find('input[name*=until]').data('DateTimePicker').date()
|
||||||
if (date !== null) {
|
if (date !== null) {
|
||||||
// rrule.until is non-inclusive, whereas in pretix-backend "until" is inclusive => add 1 day
|
// rrule.until is non-inclusive, whereas in pretix-backend "until" is inclusive => add 1 day
|
||||||
// date is a Moment-object. Moment.add() mutates, but is save to do here
|
// date is a Moment-object. Moment.add() mutates, but is save to do here
|
||||||
date.add(1, 'days');
|
date.add(1, 'days')
|
||||||
rule_args.until = date;
|
rule_args.until = date
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($form.find("input[name*=exclude]").prop("checked")) {
|
if ($form.find('input[name*=exclude]').prop('checked')) {
|
||||||
ruleset.exrule(new rrule.RRule(rule_args));
|
ruleset.exrule(new rrule.RRule(rule_args))
|
||||||
$form.closest(".panel").addClass("panel-danger").removeClass("panel-default");
|
$form.closest('.panel').addClass('panel-danger').removeClass('panel-default')
|
||||||
} else {
|
} else {
|
||||||
ruleset.rrule(new rrule.RRule(rule_args));
|
ruleset.rrule(new rrule.RRule(rule_args))
|
||||||
$form.closest(".panel").addClass("panel-default").removeClass("panel-danger");
|
$form.closest('.panel').addClass('panel-default').removeClass('panel-danger')
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
var all_dates = ruleset.all();
|
let all_dates = ruleset.all()
|
||||||
var format = $("body").attr("data-longdateformat") + " (dddd)";
|
let format = $('body').attr('data-longdateformat') + ' (dddd)'
|
||||||
$("#rrule-preview").html("");
|
$('#rrule-preview').html('')
|
||||||
if (all_dates.length > 20) {
|
if (all_dates.length > 20) {
|
||||||
$("#rrule-preview").html("");
|
$('#rrule-preview').html('')
|
||||||
all_dates.slice(0, 10).forEach(function(element) {
|
all_dates.slice(0, 10).forEach(function (element) {
|
||||||
$("#rrule-preview").append($("<li>").text(moment(element).utc().format(format)));
|
$('#rrule-preview').append($('<li>').text(moment(element).utc().format(format)))
|
||||||
});
|
})
|
||||||
$("#rrule-preview").append($("<li>").text(ngettext(
|
$('#rrule-preview').append($('<li>').text(ngettext(
|
||||||
"(one more date)",
|
'(one more date)',
|
||||||
"({num} more dates)",
|
'({num} more dates)',
|
||||||
all_dates.length - 20
|
all_dates.length - 20
|
||||||
).replace(/\{num\}/g, all_dates.length - 20)));
|
).replace(/\{num\}/g, all_dates.length - 20)))
|
||||||
all_dates.slice(-10).forEach(function(element) {
|
all_dates.slice(-10).forEach(function (element) {
|
||||||
$("#rrule-preview").append($("<li>").text(moment(element).utc().format(format)));
|
$('#rrule-preview').append($('<li>').text(moment(element).utc().format(format)))
|
||||||
});
|
})
|
||||||
} else {
|
} else {
|
||||||
all_dates.forEach(function(element) {
|
all_dates.forEach(function (element) {
|
||||||
$("#rrule-preview").append($("<li>").text(moment(element).utc().format(format)));
|
$('#rrule-preview').append($('<li>').text(moment(element).utc().format(format)))
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$("#rrule-formset").on("change keydown keyup keypress dp.change", "input, select", function () {
|
$('#rrule-formset').on('change keydown keyup keypress dp.change', 'input, select', function () {
|
||||||
rrule_preview();
|
rrule_preview()
|
||||||
});
|
})
|
||||||
rrule_preview();
|
rrule_preview()
|
||||||
|
|
||||||
$("#rrule-formset").on("formAdded", "div", function (event) {rrule_bind_form($(event.target)); });
|
$('#rrule-formset').on('formAdded', 'div', function (event) { rrule_bind_form($(event.target)) })
|
||||||
|
|
||||||
// Timeslot editor
|
// Timeslot editor
|
||||||
$("#subevent_add_many_slots_go").on("click", function () {
|
$('#subevent_add_many_slots_go').on('click', function () {
|
||||||
$("#time-formset [data-formset-form]").each(function () {
|
$('#time-formset [data-formset-form]').each(function () {
|
||||||
var tf = $(this).find("[name$=time_from]").val()
|
let tf = $(this).find('[name$=time_from]').val()
|
||||||
if (!tf) {
|
if (!tf) {
|
||||||
$(this).remove();
|
$(this).remove()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
var first = $("#subevent_add_many_slots_first").data('DateTimePicker').date();
|
let first = $('#subevent_add_many_slots_first').data('DateTimePicker').date()
|
||||||
var end = $("#subevent_add_many_slots_end").data('DateTimePicker').date();
|
let end = $('#subevent_add_many_slots_end').data('DateTimePicker').date()
|
||||||
var length_m = parseFloat($("#subevent_add_many_slots_length").val()) || 0;
|
let length_m = parseFloat($('#subevent_add_many_slots_length').val()) || 0
|
||||||
var break_m = parseFloat($("#subevent_add_many_slots_break").val()) || 0;
|
let break_m = parseFloat($('#subevent_add_many_slots_break').val()) || 0
|
||||||
if (!first || !end || !length_m) {
|
if (!first || !end || !length_m) {
|
||||||
console.log("invalid", first, end, length_m)
|
console.log('invalid', first, end, length_m)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
function closure($form, time) {
|
function closure ($form, time) {
|
||||||
return function () {
|
return function () {
|
||||||
console.log("setting value", time)
|
console.log('setting value', time)
|
||||||
$form.find("[name$=time_from]").data('DateTimePicker').date(time);
|
$form.find('[name$=time_from]').data('DateTimePicker').date(time)
|
||||||
time.add(length_m, 'minutes');
|
time.add(length_m, 'minutes')
|
||||||
$form.find("[name$=time_to]").data('DateTimePicker').date(time);
|
$form.find('[name$=time_to]').data('DateTimePicker').date(time)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var pointer = first.clone();
|
let pointer = first.clone()
|
||||||
while (pointer.isBefore(end)) {
|
while (pointer.isBefore(end)) {
|
||||||
var $form = $("#time-formset").formset("getOrCreate").addForm();
|
let $form = $('#time-formset').formset('getOrCreate').addForm()
|
||||||
$form.attr("data-formset-created-at-runtime", "false"); // prevents animation
|
$form.attr('data-formset-created-at-runtime', 'false') // prevents animation
|
||||||
var time = pointer.clone();
|
let time = pointer.clone()
|
||||||
window.setTimeout(closure($form, time), 1);
|
window.setTimeout(closure($form, time), 1)
|
||||||
// jquery.formset.js only calls trigger("formAdded") after a setTimeout of 0,
|
// jquery.formset.js only calls trigger("formAdded") after a setTimeout of 0,
|
||||||
// but we need to run after that to make sure the date pickers are initialized
|
// but we need to run after that to make sure the date pickers are initialized
|
||||||
pointer.add(break_m + length_m, 'minutes');
|
pointer.add(break_m + length_m, 'minutes')
|
||||||
}
|
}
|
||||||
$("#subevent_add_many_slots").addClass("hidden");
|
$('#subevent_add_many_slots').addClass('hidden')
|
||||||
$("#subevent_add_many_slots_start").removeClass("hidden");
|
$('#subevent_add_many_slots_start').removeClass('hidden')
|
||||||
|
})
|
||||||
|
$('#subevent_add_many_slots_start').on('click', function () {
|
||||||
|
$('#subevent_add_many_slots').removeClass('hidden')
|
||||||
|
$(this).addClass('hidden')
|
||||||
|
})
|
||||||
|
|
||||||
});
|
// Hide config for products that are not for sale
|
||||||
$("#subevent_add_many_slots_start").on("click", function () {
|
function quota_form_handlers (el) {
|
||||||
$("#subevent_add_many_slots").removeClass("hidden");
|
// searchable_selection = True
|
||||||
$(this).addClass("hidden");
|
el.find('[id^="id_quotas-"]').on('select2:select select2:unselect', () => {
|
||||||
});
|
update_item_visibility()
|
||||||
|
})
|
||||||
|
// searchable_selection = False
|
||||||
|
el.find('input[id^="id_quotas-"][id*=itemvars_]').on('change', () => {
|
||||||
|
update_item_visibility()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function update_item_visibility () {
|
||||||
|
const itemvars = []
|
||||||
|
|
||||||
// Hide config for products that are not for sale
|
// searchable_selection = True
|
||||||
function quota_form_handlers(el) {
|
$('select[id^=id_quotas-][id$=-itemvars]').filter((idx, el) => {
|
||||||
// searchable_selection = True
|
return !$(el).closest('[data-formset-form]').is('[data-formset-form-deleted]')
|
||||||
el.find('[id^="id_quotas-"]').on("select2:select select2:unselect", () => {
|
}).each((_, e) => itemvars.push(...$(e).val()))
|
||||||
update_item_visibility();
|
// searchable_selection = False
|
||||||
});
|
$('input[id^=id_quotas-][id*=itemvars_]:checked').filter((idx, el) => {
|
||||||
// searchable_selection = False
|
return !$(el).closest('[data-formset-form]').is('[data-formset-form-deleted]')
|
||||||
el.find('input[id^="id_quotas-"][id*=itemvars_]').on("change", () => {
|
}).each((_, e) => itemvars.push($(e).val()))
|
||||||
update_item_visibility();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function update_item_visibility() {
|
|
||||||
const itemvars = [];
|
|
||||||
|
|
||||||
// searchable_selection = True
|
$('div[data-itemvar]').each(function (idx, e) {
|
||||||
$("select[id^=id_quotas-][id$=-itemvars]").filter((idx, el) => {
|
const el = $(e)
|
||||||
return !$(el).closest('[data-formset-form]').is('[data-formset-form-deleted]');
|
el.prop('hidden', !itemvars.includes(el.attr('data-itemvar')) && !el.find('.has-error, .alert-danger').length)
|
||||||
}).each((_, e) => itemvars.push(...$(e).val()));
|
})
|
||||||
// searchable_selection = False
|
}
|
||||||
$("input[id^=id_quotas-][id*=itemvars_]:checked").filter((idx, el) => {
|
|
||||||
return !$(el).closest('[data-formset-form]').is('[data-formset-form-deleted]');
|
|
||||||
}).each((_, e) => itemvars.push($(e).val()));
|
|
||||||
|
|
||||||
$("div[data-itemvar]").each(function (idx, e) {
|
$('[data-formset-prefix="quotas"]').on('formDeleted', 'div', () => {
|
||||||
const el = $(e);
|
update_item_visibility()
|
||||||
el.prop("hidden", !itemvars.includes(el.attr("data-itemvar")) && !el.find(".has-error, .alert-danger").length);
|
}).on('formAdded', 'div', (event) => {
|
||||||
});
|
quota_form_handlers($(event.target))
|
||||||
}
|
update_item_visibility()
|
||||||
|
})
|
||||||
|
quota_form_handlers($('body'))
|
||||||
|
update_item_visibility()
|
||||||
|
|
||||||
$('[data-formset-prefix="quotas"]').on("formDeleted", "div", () => {
|
// Auto-set name of check-in list
|
||||||
update_item_visibility();
|
let $namef = $('input[id^=id_name]').first()
|
||||||
}).on("formAdded", "div", (event) => {
|
let lastValue = $namef.val()
|
||||||
quota_form_handlers($(event.target));
|
$namef.change(function () {
|
||||||
update_item_visibility();
|
let field = $('div[data-formset-prefix=checkinlist_set] input[id$=name]').first()
|
||||||
})
|
if (field.val() === lastValue) {
|
||||||
quota_form_handlers($("body"));
|
lastValue = $(this).val()
|
||||||
update_item_visibility();
|
field.val(lastValue)
|
||||||
|
}
|
||||||
// Auto-set name of check-in list
|
})
|
||||||
var $namef = $("input[id^=id_name]").first();
|
})
|
||||||
var lastValue = $namef.val();
|
|
||||||
$namef.change(function () {
|
|
||||||
var field = $("div[data-formset-prefix=checkinlist_set] input[id$=name]").first();
|
|
||||||
if (field.val() === lastValue) {
|
|
||||||
lastValue = $(this).val();
|
|
||||||
field.val(lastValue);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,56 +1,54 @@
|
|||||||
/*globals $*/
|
|
||||||
|
|
||||||
$(function () {
|
$(function () {
|
||||||
var j = 0;
|
let j = 0
|
||||||
$(".tabbed-form").each(function () {
|
$('.tabbed-form').each(function () {
|
||||||
var $form = $(this);
|
let $form = $(this)
|
||||||
var $tabs = $("<ul>").addClass("nav nav-tabs").insertBefore($form);
|
let $tabs = $('<ul>').addClass('nav nav-tabs').insertBefore($form)
|
||||||
$form.addClass("tab-content");
|
$form.addClass('tab-content')
|
||||||
|
|
||||||
var i = 0;
|
let i = 0
|
||||||
var preselect = null;
|
let preselect = null
|
||||||
var validity_error = false;
|
let validity_error = false
|
||||||
$form.find("fieldset").each(function () {
|
$form.find('fieldset').each(function () {
|
||||||
var $fieldset = $(this);
|
let $fieldset = $(this)
|
||||||
var tid = $fieldset.attr("id");
|
let tid = $fieldset.attr('id')
|
||||||
if (!tid) tid = "tab-" + j + "-" + i;
|
if (!tid) tid = 'tab-' + j + '-' + i
|
||||||
var $tabli = $("<li>").appendTo($tabs);
|
let $tabli = $('<li>').appendTo($tabs)
|
||||||
var $tablink = $("<a>").attr("role", "tab")
|
let $tablink = $('<a>').attr('role', 'tab')
|
||||||
.attr("data-toggle", "tab")
|
.attr('data-toggle', 'tab')
|
||||||
.attr("href", "#" + tid)
|
.attr('href', '#' + tid)
|
||||||
.text($fieldset.find("legend").text())
|
.text($fieldset.find('legend').text())
|
||||||
.appendTo($tabli);
|
.appendTo($tabli)
|
||||||
if ($fieldset.find(".has-error, .alert-danger:not(.dynamic)").length > 0) {
|
if ($fieldset.find('.has-error, .alert-danger:not(.dynamic)').length > 0) {
|
||||||
$tablink.append(" ");
|
$tablink.append(' ')
|
||||||
$tablink.append($("<span>").addClass("fa fa-warning text-danger"));
|
$tablink.append($('<span>').addClass('fa fa-warning text-danger'))
|
||||||
if (preselect === null) {
|
if (preselect === null) {
|
||||||
preselect = i;
|
preselect = i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$fieldset.find("input, select, textarea").on("invalid", function () {
|
$fieldset.find('input, select, textarea').on('invalid', function () {
|
||||||
if ($tablink.find(".fa-warning").length === 0) {
|
if ($tablink.find('.fa-warning').length === 0) {
|
||||||
$tablink.append(" ");
|
$tablink.append(' ')
|
||||||
$tablink.append($("<span>").addClass("fa fa-warning text-danger"));
|
$tablink.append($('<span>').addClass('fa fa-warning text-danger'))
|
||||||
if (!validity_error) {
|
if (!validity_error) {
|
||||||
validity_error = true;
|
validity_error = true
|
||||||
$tablink.click();
|
$tablink.click()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
$fieldset.find("legend").remove();
|
$fieldset.find('legend').remove()
|
||||||
$fieldset.addClass("tab-pane").attr("id", tid);
|
$fieldset.addClass('tab-pane').attr('id', tid)
|
||||||
if (location.hash && ($fieldset.find(location.hash).length || location.hash === "#" + tid + "-open") && preselect === null) {
|
if (location.hash && ($fieldset.find(location.hash).length || location.hash === '#' + tid + '-open') && preselect === null) {
|
||||||
preselect = i;
|
preselect = i
|
||||||
}
|
}
|
||||||
i++;
|
i++
|
||||||
});
|
})
|
||||||
$tabs.find("a").get(preselect != null ? preselect : 0).click();
|
$tabs.find('a').get(preselect != null ? preselect : 0).click()
|
||||||
$tabs.find("a").on('shown.bs.tab', function (e) {
|
$tabs.find('a').on('shown.bs.tab', function (e) {
|
||||||
history.replaceState(null, null, e.target.getAttribute("href") + "-open");
|
history.replaceState(null, null, e.target.getAttribute('href') + '-open')
|
||||||
});
|
})
|
||||||
$form.closest("form").on("submit", function () {
|
$form.closest('form').on('submit', function () {
|
||||||
validity_error = false;
|
validity_error = false
|
||||||
});
|
})
|
||||||
j++;
|
j++
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|||||||
@@ -1,164 +1,163 @@
|
|||||||
/*global $,u2f */
|
|
||||||
$(function () {
|
$(function () {
|
||||||
$('.context-selector.dropdown').on('shown.bs.collapse shown.bs.dropdown', function () {
|
$('.context-selector.dropdown').on('shown.bs.collapse shown.bs.dropdown', function () {
|
||||||
$(this).parent().find("input").val("").trigger('forceRunQuery').focus();
|
$(this).parent().find('input').val('').trigger('forceRunQuery').focus()
|
||||||
});
|
})
|
||||||
$('.dropdown-menu .form-box input').click(function (e) {
|
$('.dropdown-menu .form-box input').click(function (e) {
|
||||||
e.stopPropagation();
|
e.stopPropagation()
|
||||||
});
|
})
|
||||||
|
|
||||||
$("[data-event-typeahead]").each(function () {
|
$('[data-event-typeahead]').each(function () {
|
||||||
var $container = $(this);
|
let $container = $(this)
|
||||||
var $query = $(this).find('[data-typeahead-query]').length ? $(this).find('[data-typeahead-query]') : $($(this).attr("data-typeahead-field"));
|
let $query = $(this).find('[data-typeahead-query]').length ? $(this).find('[data-typeahead-query]') : $($(this).attr('data-typeahead-field'))
|
||||||
$container.find("li:not(.query-holder)").remove();
|
$container.find('li:not(.query-holder)').remove()
|
||||||
var lastQuery = null;
|
let lastQuery = null
|
||||||
var runQueryTimeout = null;
|
let runQueryTimeout = null
|
||||||
var loadIndicatorTimeout = null;
|
let loadIndicatorTimeout = null
|
||||||
var focusOutTimeout = null;
|
let focusOutTimeout = null
|
||||||
function showLoadIndicator() {
|
function showLoadIndicator () {
|
||||||
$container.find("li:not(.query-holder)").remove();
|
$container.find('li:not(.query-holder)').remove()
|
||||||
$container.append("<li class='loading'><span class='fa fa-4x fa-cog fa-spin'></span></li>");
|
$container.append('<li class=\'loading\'><span class=\'fa fa-4x fa-cog fa-spin\'></span></li>')
|
||||||
$container.toggleClass('focused', $query.is(":focus") && $container.children().length > 0);
|
$container.toggleClass('focused', $query.is(':focus') && $container.children().length > 0)
|
||||||
}
|
}
|
||||||
function runQuery() {
|
function runQuery () {
|
||||||
var thisQuery = $query.val();
|
let thisQuery = $query.val()
|
||||||
if (thisQuery === lastQuery) return;
|
if (thisQuery === lastQuery) return
|
||||||
lastQuery = $query.val();
|
lastQuery = $query.val()
|
||||||
|
|
||||||
window.clearTimeout(loadIndicatorTimeout)
|
window.clearTimeout(loadIndicatorTimeout)
|
||||||
loadIndicatorTimeout = window.setTimeout(showLoadIndicator, 80)
|
loadIndicatorTimeout = window.setTimeout(showLoadIndicator, 80)
|
||||||
|
|
||||||
$.getJSON(
|
$.getJSON(
|
||||||
$container.attr("data-source") + "?query=" + encodeURIComponent($query.val()) + (typeof $container.attr("data-organizer") !== "undefined" ? "&organizer=" + $container.attr("data-organizer") : ""),
|
$container.attr('data-source') + '?query=' + encodeURIComponent($query.val()) + (typeof $container.attr('data-organizer') !== 'undefined' ? '&organizer=' + $container.attr('data-organizer') : ''),
|
||||||
function (data) {
|
function (data) {
|
||||||
if (thisQuery !== lastQuery) {
|
if (thisQuery !== lastQuery) {
|
||||||
// Lost race condition
|
// Lost race condition
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
window.clearTimeout(loadIndicatorTimeout);
|
window.clearTimeout(loadIndicatorTimeout)
|
||||||
$container.find("li:not(.query-holder)").remove();
|
$container.find('li:not(.query-holder)').remove()
|
||||||
$.each(data.results, function (i, res) {
|
$.each(data.results, function (_i, res) {
|
||||||
let $linkContent = $("<div>");
|
let $linkContent = $('<div>')
|
||||||
if (res.type === "organizer") {
|
if (res.type === 'organizer') {
|
||||||
$linkContent.append(
|
$linkContent.append(
|
||||||
$("<span>").addClass("event-name-full").append(
|
$('<span>').addClass('event-name-full').append(
|
||||||
$("<span>").addClass("fa fa-users fa-fw")
|
$('<span>').addClass('fa fa-users fa-fw')
|
||||||
).append(" ").append($("<div>").text(res.name).html())
|
).append(' ').append($('<div>').text(res.name).html())
|
||||||
)
|
)
|
||||||
} else if (res.type === "order" || res.type === "voucher") {
|
} else if (res.type === 'order' || res.type === 'voucher') {
|
||||||
$linkContent.append(
|
$linkContent.append(
|
||||||
$("<span>").addClass("event-name-full").append($("<div>").text(res.title).html())
|
$('<span>').addClass('event-name-full').append($('<div>').text(res.title).html())
|
||||||
).append(
|
).append(
|
||||||
$("<span>").addClass("event-organizer").append(
|
$('<span>').addClass('event-organizer').append(
|
||||||
$("<span>").addClass("fa fa-calendar fa-fw")
|
$('<span>').addClass('fa fa-calendar fa-fw')
|
||||||
).append(" ").append($("<div>").text(res.event).html())
|
).append(' ').append($('<div>').text(res.event).html())
|
||||||
)
|
)
|
||||||
} else if (res.type === "user") {
|
} else if (res.type === 'user') {
|
||||||
$linkContent.append(
|
$linkContent.append(
|
||||||
$("<span>").addClass("event-name-full").append(
|
$('<span>').addClass('event-name-full').append(
|
||||||
$("<span>").addClass("fa fa-user fa-fw")
|
$('<span>').addClass('fa fa-user fa-fw')
|
||||||
).append(" ").append($("<div>").text(res.name).html())
|
).append(' ').append($('<div>').text(res.name).html())
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
$linkContent.append(
|
$linkContent.append(
|
||||||
$("<span>").addClass("event-name-full").append($("<div>").text(res.name).html())
|
$('<span>').addClass('event-name-full').append($('<div>').text(res.name).html())
|
||||||
).append(
|
).append(
|
||||||
$("<span>").addClass("event-organizer").append(
|
$('<span>').addClass('event-organizer').append(
|
||||||
$("<span>").addClass("fa fa-users fa-fw")
|
$('<span>').addClass('fa fa-users fa-fw')
|
||||||
).append(" ").append($("<div>").text(res.organizer).html())
|
).append(' ').append($('<div>').text(res.organizer).html())
|
||||||
).append(
|
).append(
|
||||||
$("<span>").addClass("event-daterange").append(
|
$('<span>').addClass('event-daterange').append(
|
||||||
$("<span>").addClass("fa fa-calendar fa-fw")
|
$('<span>').addClass('fa fa-calendar fa-fw')
|
||||||
).append(" ").append(res.date_range)
|
).append(' ').append(res.date_range)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
$container.append(
|
$container.append(
|
||||||
$("<li>").append(
|
$('<li>').append(
|
||||||
$("<a>").attr("href", res.url).append(
|
$('<a>').attr('href', res.url).append(
|
||||||
$linkContent
|
$linkContent
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
)
|
||||||
});
|
})
|
||||||
$container.toggleClass('focused', $query.is(":focus") && $container.children().length > 0);
|
$container.toggleClass('focused', $query.is(':focus') && $container.children().length > 0)
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
$query.on("forceRunQuery", function () {
|
$query.on('forceRunQuery', function () {
|
||||||
runQuery();
|
runQuery()
|
||||||
});
|
})
|
||||||
$query.on("input", function () {
|
$query.on('input', function () {
|
||||||
if ($container.attr("data-typeahead-field") && $query.val() === "") {
|
if ($container.attr('data-typeahead-field') && $query.val() === '') {
|
||||||
$container.removeClass('focused');
|
$container.removeClass('focused')
|
||||||
$container.find("li:not(.query-holder)").remove();
|
$container.find('li:not(.query-holder)').remove()
|
||||||
lastQuery = null;
|
lastQuery = null
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
window.clearTimeout(runQueryTimeout)
|
window.clearTimeout(runQueryTimeout)
|
||||||
runQueryTimeout = window.setTimeout(runQuery, 250)
|
runQueryTimeout = window.setTimeout(runQuery, 250)
|
||||||
});
|
})
|
||||||
$query.on("keydown", function (event) {
|
$query.on('keydown', function (event) {
|
||||||
var $selected = $container.find(".active");
|
let $selected = $container.find('.active')
|
||||||
if (event.which === 13) { // enter
|
if (event.which === 13) { // enter
|
||||||
var $link = $selected.find("a");
|
let $link = $selected.find('a')
|
||||||
if ($link.length) {
|
if ($link.length) {
|
||||||
location.href = $link.attr("href");
|
location.href = $link.attr('href')
|
||||||
}
|
}
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
event.stopPropagation();
|
event.stopPropagation()
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
$container.add($query).on("keydown", function (event) {
|
$container.add($query).on('keydown', function (event) {
|
||||||
if (event.which === 27) { // escape
|
if (event.which === 27) { // escape
|
||||||
$container.removeClass('focused');
|
$container.removeClass('focused')
|
||||||
}
|
}
|
||||||
}).on("focusin", function (event) {
|
}).on('focusin', function () {
|
||||||
window.clearTimeout(focusOutTimeout);
|
window.clearTimeout(focusOutTimeout)
|
||||||
$(document.body).one("focusout", function (event) {
|
$(document.body).one('focusout', function () {
|
||||||
focusOutTimeout = window.setTimeout(function () {
|
focusOutTimeout = window.setTimeout(function () {
|
||||||
$container.removeClass('focused');
|
$container.removeClass('focused')
|
||||||
}, 100);
|
}, 100)
|
||||||
})
|
})
|
||||||
});
|
})
|
||||||
$query.on("keyup", function (event) {
|
$query.on('keyup', function (event) {
|
||||||
var $first = $container.find("li:not(.query-holder)").first();
|
let $first = $container.find('li:not(.query-holder)').first()
|
||||||
var $last = $container.find("li:not(.query-holder)").last();
|
let $last = $container.find('li:not(.query-holder)').last()
|
||||||
var $selected = $container.find(".active");
|
let $selected = $container.find('.active')
|
||||||
|
|
||||||
if (event.which === 13) { // enter
|
if (event.which === 13) { // enter
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
event.stopPropagation();
|
event.stopPropagation()
|
||||||
return true;
|
return true
|
||||||
} else if (event.which === 40) { // down
|
} else if (event.which === 40) { // down
|
||||||
var $next;
|
let $next
|
||||||
if ($selected.length === 0) {
|
if ($selected.length === 0) {
|
||||||
$next = $first;
|
$next = $first
|
||||||
} else {
|
} else {
|
||||||
$next = $selected.next();
|
$next = $selected.next()
|
||||||
}
|
}
|
||||||
if ($next.length === 0) {
|
if ($next.length === 0) {
|
||||||
$next = $first;
|
$next = $first
|
||||||
}
|
}
|
||||||
$selected.removeClass("active");
|
$selected.removeClass('active')
|
||||||
$next.addClass("active");
|
$next.addClass('active')
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
event.stopPropagation();
|
event.stopPropagation()
|
||||||
return true;
|
return true
|
||||||
} else if (event.which === 38) { // up
|
} else if (event.which === 38) { // up
|
||||||
if ($selected.length === 0) {
|
if ($selected.length === 0) {
|
||||||
$selected = $first;
|
$selected = $first
|
||||||
}
|
}
|
||||||
var $prev = $selected.prev();
|
let $prev = $selected.prev()
|
||||||
if ($prev.length === 0 || $prev.find("input").length > 0) {
|
if ($prev.length === 0 || $prev.find('input').length > 0) {
|
||||||
$prev = $last;
|
$prev = $last
|
||||||
}
|
}
|
||||||
$selected.removeClass("active");
|
$selected.removeClass('active')
|
||||||
$prev.addClass("active");
|
$prev.addClass('active')
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
event.stopPropagation();
|
event.stopPropagation()
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|||||||
@@ -1,78 +1,78 @@
|
|||||||
/*global $, Morris, gettext, formatPrice*/
|
/* global i18nToString, formatPrice */
|
||||||
$(function () {
|
$(function () {
|
||||||
// Question view
|
// Question view
|
||||||
if (!$("#item_variations").length) {
|
if (!$('#item_variations').length) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
function update_variation_summary($el) {
|
function update_variation_summary ($el) {
|
||||||
var var_names = Object.fromEntries(
|
let var_names = Object.fromEntries(
|
||||||
$el
|
$el
|
||||||
.find("input[name*=-value_]")
|
.find('input[name*=-value_]')
|
||||||
.filter(function () {
|
.filter(function () {
|
||||||
return !!this.value;
|
return !!this.value
|
||||||
})
|
})
|
||||||
.map(function () {
|
.map(function () {
|
||||||
return [[this.getAttribute("lang"), this.value]];
|
return [[this.getAttribute('lang'), this.value]]
|
||||||
})
|
})
|
||||||
.get()
|
.get()
|
||||||
);
|
)
|
||||||
var var_name = i18nToString(var_names);
|
let var_name = i18nToString(var_names)
|
||||||
var price = $el.find("input[name*=-default_price]").val();
|
let price = $el.find('input[name*=-default_price]').val()
|
||||||
if (price) {
|
if (price) {
|
||||||
var currency = $el.find("[name*=-default_price] + .input-group-addon").text();
|
let currency = $el.find('[name*=-default_price] + .input-group-addon').text()
|
||||||
price = formatPrice(price, currency);
|
price = formatPrice(price, currency)
|
||||||
}
|
}
|
||||||
|
|
||||||
$el.find(".variation-name").text(var_name);
|
$el.find('.variation-name').text(var_name)
|
||||||
$el.find(".variation-price").text(price);
|
$el.find('.variation-price').text(price)
|
||||||
$el.find(".variation-timeframe").toggleClass("variation-icon-hidden", !(
|
$el.find('.variation-timeframe').toggleClass('variation-icon-hidden', !(
|
||||||
!!$el.find("input[name$=-available_from_0]").val() ||
|
!!$el.find('input[name$=-available_from_0]').val()
|
||||||
!!$el.find("input[name$=-available_until_0]").val()
|
|| !!$el.find('input[name$=-available_until_0]').val()
|
||||||
));
|
))
|
||||||
$el.find(".variation-name").toggleClass("variation-disabled", !(
|
$el.find('.variation-name').toggleClass('variation-disabled', !(
|
||||||
!!$el.find("input[name$=-active]").prop("checked")
|
$el.find('input[name$=-active]').prop('checked')
|
||||||
));
|
))
|
||||||
$el.find(".variation-voucher").toggleClass("variation-icon-hidden", !(
|
$el.find('.variation-voucher').toggleClass('variation-icon-hidden', !(
|
||||||
!!$el.find("input[name$=-hide_without_voucher]").prop("checked")
|
$el.find('input[name$=-hide_without_voucher]').prop('checked')
|
||||||
));
|
))
|
||||||
$el.find(".variation-membership").toggleClass("variation-icon-hidden", !(
|
$el.find('.variation-membership').toggleClass('variation-icon-hidden', !(
|
||||||
!!$el.find("input[name$=-require_membership]").prop("checked")
|
$el.find('input[name$=-require_membership]').prop('checked')
|
||||||
));
|
))
|
||||||
$el.find(".variation-warning").toggleClass("hidden", !(
|
$el.find('.variation-warning').toggleClass('hidden', !(
|
||||||
$el.find(".alert-warning").length
|
$el.find('.alert-warning').length
|
||||||
));
|
))
|
||||||
$el.find(".variation-error").toggleClass("hidden", !(
|
$el.find('.variation-error').toggleClass('hidden', !(
|
||||||
$el.find(".alert-danger, .has-error").length
|
$el.find('.alert-danger, .has-error').length
|
||||||
));
|
))
|
||||||
$el.find("input[name$=-limit_sales_channels]").each(function () {
|
$el.find('input[name$=-limit_sales_channels]').each(function () {
|
||||||
$el.find(".variation-channel-" + $(this).val()).toggleClass("variation-icon-hidden", !(
|
$el.find('.variation-channel-' + $(this).val()).toggleClass('variation-icon-hidden', !(
|
||||||
(
|
(
|
||||||
$(this).closest("[data-formset-form]").find("input[name$=-all_sales_channels]").prop("checked") ||
|
$(this).closest('[data-formset-form]').find('input[name$=-all_sales_channels]').prop('checked')
|
||||||
$(this).prop("checked")
|
|| $(this).prop('checked')
|
||||||
) && (
|
) && (
|
||||||
$("input[name=all_sales_channels]").prop("checked") ||
|
$('input[name=all_sales_channels]').prop('checked')
|
||||||
$("input[name=limit_sales_channels][value=" + $(this).val() + "]").prop("checked")
|
|| $('input[name=limit_sales_channels][value=' + $(this).val() + ']').prop('checked')
|
||||||
)
|
)
|
||||||
));
|
))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
$("#item_variations [data-formset-form]").each(function () {
|
$('#item_variations [data-formset-form]').each(function () {
|
||||||
var $el = $(this);
|
let $el = $(this)
|
||||||
update_variation_summary($el);
|
update_variation_summary($el)
|
||||||
$(this).on("change dp.change", "input", function () {update_variation_summary($el)});
|
$(this).on('change dp.change', 'input', function () { update_variation_summary($el) })
|
||||||
});
|
})
|
||||||
$("input[name=limit_sales_channels] input[name=all_sales_channels]").on("change", function() {
|
$('input[name=limit_sales_channels] input[name=all_sales_channels]').on('change', function () {
|
||||||
$("#item_variations [data-formset-form]").each(function () {
|
$('#item_variations [data-formset-form]').each(function () {
|
||||||
update_variation_summary($(this));
|
update_variation_summary($(this))
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
$("#item_variations").on("formAdded", "details", function (event) {
|
$('#item_variations').on('formAdded', 'details', function (event) {
|
||||||
var $el = $(event.target);
|
let $el = $(event.target)
|
||||||
update_variation_summary($el);
|
update_variation_summary($el)
|
||||||
$(this).on("change dp.change", "input", function () {update_variation_summary($el)});
|
$(this).on('change dp.change', 'input', function () { update_variation_summary($el) })
|
||||||
setup_collapsible_details($("#item_variations"));
|
setup_collapsible_details($('#item_variations'))
|
||||||
form_handlers($(event.target));
|
form_handlers($(event.target))
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|||||||
@@ -1,32 +1,32 @@
|
|||||||
/*global $,u2f */
|
/* global base64js */
|
||||||
|
|
||||||
function b64enc(buf) {
|
function b64enc (buf) {
|
||||||
return base64js.fromByteArray(buf)
|
return base64js.fromByteArray(buf)
|
||||||
.replace(/\+/g, "-")
|
.replace(/\+/g, '-')
|
||||||
.replace(/\//g, "_")
|
.replace(/\//g, '_')
|
||||||
.replace(/=/g, "");
|
.replace(/=/g, '')
|
||||||
}
|
}
|
||||||
|
|
||||||
function b64RawEnc(buf) {
|
function b64RawEnc (buf) {
|
||||||
return base64js.fromByteArray(buf)
|
return base64js.fromByteArray(buf)
|
||||||
.replace(/\+/g, "-")
|
.replace(/\+/g, '-')
|
||||||
.replace(/\//g, "_");
|
.replace(/\//g, '_')
|
||||||
}
|
}
|
||||||
|
|
||||||
function hexEncode(buf) {
|
function hexEncode (buf) {
|
||||||
return Array.from(buf)
|
return Array.from(buf)
|
||||||
.map(function(x) {
|
.map(function (x) {
|
||||||
return ("0" + x.toString(16)).substr(-2);
|
return ('0' + x.toString(16)).substr(-2)
|
||||||
})
|
})
|
||||||
.join("");
|
.join('')
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetch_json(url, options) {
|
async function fetch_json (url, options) {
|
||||||
const response = await fetch(url, options);
|
const response = await fetch(url, options)
|
||||||
const body = await response.json();
|
const body = await response.json()
|
||||||
if (body.fail)
|
if (body.fail)
|
||||||
throw body.fail;
|
throw body.fail
|
||||||
return body;
|
return body
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -35,27 +35,26 @@ async function fetch_json(url, options) {
|
|||||||
* @param {Object} credentialCreateOptionsFromServer
|
* @param {Object} credentialCreateOptionsFromServer
|
||||||
*/
|
*/
|
||||||
const transformCredentialCreateOptions = function (credentialCreateOptionsFromServer) {
|
const transformCredentialCreateOptions = function (credentialCreateOptionsFromServer) {
|
||||||
let {challenge, user, excludeCredentials} = credentialCreateOptionsFromServer;
|
let { challenge, user, excludeCredentials } = credentialCreateOptionsFromServer
|
||||||
user.id = user.id.replace(/\_/g, "/").replace(/\-/g, "+");
|
user.id = user.id.replace(/_/g, '/').replace(/-/g, '+')
|
||||||
user.id = Uint8Array.from(atob(user.id), c => c.charCodeAt(0));
|
user.id = Uint8Array.from(atob(user.id), c => c.charCodeAt(0))
|
||||||
|
|
||||||
challenge = challenge.replace(/\_/g, "/").replace(/\-/g, "+");
|
challenge = challenge.replace(/_/g, '/').replace(/-/g, '+')
|
||||||
challenge = Uint8Array.from(atob(challenge), c => c.charCodeAt(0));
|
challenge = Uint8Array.from(atob(challenge), c => c.charCodeAt(0))
|
||||||
|
|
||||||
excludeCredentials = excludeCredentials.map(credentialDescriptor => {
|
excludeCredentials = excludeCredentials.map(credentialDescriptor => {
|
||||||
let {id} = credentialDescriptor;
|
let { id } = credentialDescriptor
|
||||||
id = id.replace(/\_/g, "/").replace(/\-/g, "+");
|
id = id.replace(/_/g, '/').replace(/-/g, '+')
|
||||||
id = Uint8Array.from(atob(id), c => c.charCodeAt(0));
|
id = Uint8Array.from(atob(id), c => c.charCodeAt(0))
|
||||||
return Object.assign({}, credentialDescriptor, {id});
|
return Object.assign({}, credentialDescriptor, { id })
|
||||||
});
|
})
|
||||||
|
|
||||||
const transformedCredentialCreateOptions = Object.assign(
|
const transformedCredentialCreateOptions = Object.assign(
|
||||||
{}, credentialCreateOptionsFromServer,
|
{}, credentialCreateOptionsFromServer,
|
||||||
{challenge, user, excludeCredentials});
|
{ challenge, user, excludeCredentials })
|
||||||
|
|
||||||
return transformedCredentialCreateOptions;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
return transformedCredentialCreateOptions
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transforms the binary data in the credential into base64 strings
|
* Transforms the binary data in the credential into base64 strings
|
||||||
@@ -63,134 +62,132 @@ const transformCredentialCreateOptions = function (credentialCreateOptionsFromSe
|
|||||||
* @param {PublicKeyCredential} newAssertion
|
* @param {PublicKeyCredential} newAssertion
|
||||||
*/
|
*/
|
||||||
const transformNewAssertionForServer = (newAssertion) => {
|
const transformNewAssertionForServer = (newAssertion) => {
|
||||||
const attObj = new Uint8Array(newAssertion.response.attestationObject);
|
const attObj = new Uint8Array(newAssertion.response.attestationObject)
|
||||||
const clientDataJSON = new Uint8Array(newAssertion.response.clientDataJSON);
|
const clientDataJSON = new Uint8Array(newAssertion.response.clientDataJSON)
|
||||||
const rawId = new Uint8Array(newAssertion.rawId);
|
const rawId = new Uint8Array(newAssertion.rawId)
|
||||||
const transports = newAssertion.response.getTransports();
|
const transports = newAssertion.response.getTransports()
|
||||||
const authenticatorAttachment = newAssertion.authenticatorAttachment;
|
const authenticatorAttachment = newAssertion.authenticatorAttachment
|
||||||
|
|
||||||
const registrationClientExtensions = newAssertion.getClientExtensionResults();
|
const registrationClientExtensions = newAssertion.getClientExtensionResults()
|
||||||
|
|
||||||
return {
|
|
||||||
id: newAssertion.id,
|
|
||||||
rawId: b64enc(rawId),
|
|
||||||
response: {
|
|
||||||
attestationObject: b64enc(attObj),
|
|
||||||
clientDataJSON: b64enc(clientDataJSON),
|
|
||||||
transports: transports,
|
|
||||||
},
|
|
||||||
type: newAssertion.type,
|
|
||||||
clientExtensionResults: JSON.stringify(registrationClientExtensions),
|
|
||||||
authenticatorAttachment: authenticatorAttachment,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: newAssertion.id,
|
||||||
|
rawId: b64enc(rawId),
|
||||||
|
response: {
|
||||||
|
attestationObject: b64enc(attObj),
|
||||||
|
clientDataJSON: b64enc(clientDataJSON),
|
||||||
|
transports: transports,
|
||||||
|
},
|
||||||
|
type: newAssertion.type,
|
||||||
|
clientExtensionResults: JSON.stringify(registrationClientExtensions),
|
||||||
|
authenticatorAttachment: authenticatorAttachment,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const transformCredentialRequestOptions = (credentialRequestOptionsFromServer) => {
|
const transformCredentialRequestOptions = (credentialRequestOptionsFromServer) => {
|
||||||
let {challenge, allowCredentials} = credentialRequestOptionsFromServer;
|
let { challenge, allowCredentials } = credentialRequestOptionsFromServer
|
||||||
|
|
||||||
challenge = challenge.replace(/\_/g, "/").replace(/\-/g, "+");
|
challenge = challenge.replace(/_/g, '/').replace(/-/g, '+')
|
||||||
challenge = Uint8Array.from(atob(challenge), c => c.charCodeAt(0));
|
challenge = Uint8Array.from(atob(challenge), c => c.charCodeAt(0))
|
||||||
|
|
||||||
allowCredentials = allowCredentials.map(credentialDescriptor => {
|
allowCredentials = allowCredentials.map(credentialDescriptor => {
|
||||||
let {id} = credentialDescriptor;
|
let { id } = credentialDescriptor
|
||||||
id = id.replace(/\_/g, "/").replace(/\-/g, "+");
|
id = id.replace(/_/g, '/').replace(/-/g, '+')
|
||||||
id = Uint8Array.from(atob(id), c => c.charCodeAt(0));
|
id = Uint8Array.from(atob(id), c => c.charCodeAt(0))
|
||||||
return Object.assign({}, credentialDescriptor, {id});
|
return Object.assign({}, credentialDescriptor, { id })
|
||||||
});
|
})
|
||||||
|
|
||||||
const transformedCredentialRequestOptions = Object.assign(
|
const transformedCredentialRequestOptions = Object.assign(
|
||||||
{},
|
{},
|
||||||
credentialRequestOptionsFromServer,
|
credentialRequestOptionsFromServer,
|
||||||
{challenge, allowCredentials});
|
{ challenge, allowCredentials })
|
||||||
|
|
||||||
return transformedCredentialRequestOptions;
|
return transformedCredentialRequestOptions
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encodes the binary data in the assertion into strings for posting to the server.
|
* Encodes the binary data in the assertion into strings for posting to the server.
|
||||||
* @param {PublicKeyCredential} newAssertion
|
* @param {PublicKeyCredential} newAssertion
|
||||||
*/
|
*/
|
||||||
const transformAssertionForServer = (newAssertion) => {
|
const transformAssertionForServer = (newAssertion) => {
|
||||||
const authData = new Uint8Array(newAssertion.response.authenticatorData);
|
const authData = new Uint8Array(newAssertion.response.authenticatorData)
|
||||||
const clientDataJSON = new Uint8Array(newAssertion.response.clientDataJSON);
|
const clientDataJSON = new Uint8Array(newAssertion.response.clientDataJSON)
|
||||||
const rawId = new Uint8Array(newAssertion.rawId);
|
const rawId = new Uint8Array(newAssertion.rawId)
|
||||||
const sig = new Uint8Array(newAssertion.response.signature);
|
const sig = new Uint8Array(newAssertion.response.signature)
|
||||||
const userHandle = new Uint8Array(newAssertion.response.userHandle);
|
const userHandle = new Uint8Array(newAssertion.response.userHandle)
|
||||||
const assertionClientExtensions = newAssertion.getClientExtensionResults();
|
const assertionClientExtensions = newAssertion.getClientExtensionResults()
|
||||||
const authenticatorAttachment = newAssertion.authenticatorAttachment;
|
const authenticatorAttachment = newAssertion.authenticatorAttachment
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: newAssertion.id,
|
id: newAssertion.id,
|
||||||
rawId: b64enc(rawId),
|
rawId: b64enc(rawId),
|
||||||
type: newAssertion.type,
|
type: newAssertion.type,
|
||||||
response: {
|
response: {
|
||||||
authenticatorData: b64RawEnc(authData),
|
authenticatorData: b64RawEnc(authData),
|
||||||
clientDataJSON: b64RawEnc(clientDataJSON),
|
clientDataJSON: b64RawEnc(clientDataJSON),
|
||||||
signature: b64RawEnc(sig),
|
signature: b64RawEnc(sig),
|
||||||
userHandle: b64RawEnc(userHandle),
|
userHandle: b64RawEnc(userHandle),
|
||||||
},
|
},
|
||||||
authenticatorAttachment: authenticatorAttachment,
|
authenticatorAttachment: authenticatorAttachment,
|
||||||
clientExtensionResults: JSON.stringify(assertionClientExtensions)
|
clientExtensionResults: JSON.stringify(assertionClientExtensions)
|
||||||
};
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const startRegister = async (e) => {
|
const startRegister = async () => {
|
||||||
const publicKeyCredentialCreateOptions = transformCredentialCreateOptions(JSON.parse($("#webauthn-enroll").text()));
|
const publicKeyCredentialCreateOptions = transformCredentialCreateOptions(JSON.parse($('#webauthn-enroll').text()))
|
||||||
|
|
||||||
// request the authenticator(s) to create a new credential keypair.
|
// request the authenticator(s) to create a new credential keypair.
|
||||||
let credential;
|
let credential
|
||||||
try {
|
try {
|
||||||
credential = await navigator.credentials.create({
|
credential = await navigator.credentials.create({
|
||||||
publicKey: publicKeyCredentialCreateOptions
|
publicKey: publicKeyCredentialCreateOptions
|
||||||
});
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
$("#webauthn-error").removeClass("hidden");
|
$('#webauthn-error').removeClass('hidden')
|
||||||
return console.error("Error creating credential:", err);
|
return console.error('Error creating credential:', err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// we now have a new credential! We now need to encode the byte arrays
|
// we now have a new credential! We now need to encode the byte arrays
|
||||||
// in the credential into strings, for posting to our server.
|
// in the credential into strings, for posting to our server.
|
||||||
const newAssertionForServer = transformNewAssertionForServer(credential);
|
const newAssertionForServer = transformNewAssertionForServer(credential)
|
||||||
|
|
||||||
$("#webauthn-response").val(JSON.stringify(newAssertionForServer));
|
$('#webauthn-response').val(JSON.stringify(newAssertionForServer))
|
||||||
$("#webauthn-form").submit();
|
$('#webauthn-form').submit()
|
||||||
};
|
}
|
||||||
|
|
||||||
|
const startLogin = async () => {
|
||||||
|
const transformedCredentialRequestOptions = transformCredentialRequestOptions(JSON.parse($('#webauthn-login').text()))
|
||||||
|
console.log(transformedCredentialRequestOptions)
|
||||||
|
|
||||||
const startLogin = async (e) => {
|
// request the authenticator to create an assertion signature using the
|
||||||
const transformedCredentialRequestOptions = transformCredentialRequestOptions(JSON.parse($("#webauthn-login").text()));
|
// credential private key
|
||||||
console.log(transformedCredentialRequestOptions);
|
let assertion
|
||||||
|
try {
|
||||||
|
assertion = await navigator.credentials.get({
|
||||||
|
publicKey: transformedCredentialRequestOptions,
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
$('#webauthn-error').removeClass('hidden')
|
||||||
|
return console.error('Error when creating credential:', err)
|
||||||
|
}
|
||||||
|
|
||||||
// request the authenticator to create an assertion signature using the
|
// we now have an authentication assertion! encode the byte arrays contained
|
||||||
// credential private key
|
// in the assertion data as strings for posting to the server
|
||||||
let assertion;
|
const transformedAssertionForServer = transformAssertionForServer(assertion)
|
||||||
try {
|
|
||||||
assertion = await navigator.credentials.get({
|
|
||||||
publicKey: transformedCredentialRequestOptions,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
$("#webauthn-error").removeClass("hidden");
|
|
||||||
return console.error("Error when creating credential:", err);
|
|
||||||
}
|
|
||||||
|
|
||||||
// we now have an authentication assertion! encode the byte arrays contained
|
// post the assertion to the server for verification.
|
||||||
// in the assertion data as strings for posting to the server
|
$('input, select, textarea').prop('required', false)
|
||||||
const transformedAssertionForServer = transformAssertionForServer(assertion);
|
$('#webauthn-response, #id_password').val(JSON.stringify(transformedAssertionForServer))
|
||||||
|
$('#webauthn-form').submit()
|
||||||
// post the assertion to the server for verification.
|
}
|
||||||
$("input, select, textarea").prop("required", false);
|
|
||||||
$("#webauthn-response, #id_password").val(JSON.stringify(transformedAssertionForServer));
|
|
||||||
$("#webauthn-form").submit();
|
|
||||||
};
|
|
||||||
|
|
||||||
$(function () {
|
$(function () {
|
||||||
$("#webauthn-progress").hide();
|
$('#webauthn-progress').hide()
|
||||||
if ($("#webauthn-enroll").length) {
|
if ($('#webauthn-enroll').length) {
|
||||||
$("#webauthn-progress").show();
|
$('#webauthn-progress').show()
|
||||||
startRegister();
|
startRegister()
|
||||||
} else if ($("#webauthn-login").length) {
|
} else if ($('#webauthn-login').length) {
|
||||||
$("#webauthn-progress").show();
|
$('#webauthn-progress').show()
|
||||||
startLogin();
|
startLogin()
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const COOKIE_NAME = "__Host-pretix_csrftoken";
|
const COOKIE_NAME = '__Host-pretix_csrftoken'
|
||||||
const RELOAD_FLAG = "csrfReloadPerformed";
|
const RELOAD_FLAG = 'csrfReloadPerformed'
|
||||||
|
|
||||||
const hasCookie = document.cookie
|
const hasCookie = document.cookie
|
||||||
.split("; ")
|
.split('; ')
|
||||||
.some((c) => c.startsWith(COOKIE_NAME + "="));
|
.some((c) => c.startsWith(COOKIE_NAME + '='))
|
||||||
|
|
||||||
if (!hasCookie && !sessionStorage.getItem(RELOAD_FLAG)) {
|
if (!hasCookie && !sessionStorage.getItem(RELOAD_FLAG)) {
|
||||||
sessionStorage.setItem(RELOAD_FLAG, "1");
|
sessionStorage.setItem(RELOAD_FLAG, '1')
|
||||||
location.reload();
|
location.reload()
|
||||||
} else if (hasCookie && sessionStorage.getItem(RELOAD_FLAG)) {
|
} else if (hasCookie && sessionStorage.getItem(RELOAD_FLAG)) {
|
||||||
sessionStorage.removeItem(RELOAD_FLAG);
|
sessionStorage.removeItem(RELOAD_FLAG)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
/*global $ */
|
|
||||||
|
|
||||||
$(function () {
|
$(function () {
|
||||||
window.addEventListener("message", (event) => {
|
window.addEventListener('message', (event) => {
|
||||||
if (event.data && event.data.__process === "popup_close") {
|
if (event.data && event.data.__process === 'popup_close') {
|
||||||
window.close()
|
window.close()
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
window.opener.postMessage(JSON.parse($("#postmessage").text()), $("#origin").text())
|
window.opener.postMessage(JSON.parse($('#postmessage').text()), $('#origin').text())
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,191 +1,191 @@
|
|||||||
/*global $,gettext,ngettext */
|
/* global gettext,ngettext,moment,django */
|
||||||
var cart = {
|
var cart = {
|
||||||
_deadline: null,
|
_deadline: null,
|
||||||
_deadline_timeout: null,
|
_deadline_timeout: null,
|
||||||
_deadline_call: 0,
|
_deadline_call: 0,
|
||||||
_time_offset: 0,
|
_time_offset: 0,
|
||||||
_prev_diff_minutes: 0,
|
_prev_diff_minutes: 0,
|
||||||
|
|
||||||
_get_now: function () {
|
_get_now: function () {
|
||||||
return moment().add(cart._time_offset, 'ms');
|
return moment().add(cart._time_offset, 'ms')
|
||||||
},
|
},
|
||||||
|
|
||||||
_calc_offset: function () {
|
_calc_offset: function () {
|
||||||
if (typeof window.performance === "undefined") {
|
if (typeof window.performance === 'undefined') {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
var perf = window.performance.timing;
|
let perf = window.performance.timing
|
||||||
var server_time = Math.round(parseFloat($("body").attr("data-now")) * 1000);
|
let server_time = Math.round(parseFloat($('body').attr('data-now')) * 1000)
|
||||||
// We use requestStart as we don't know how latency is distributed and we rather want to err on the safe side
|
// We use requestStart as we don't know how latency is distributed and we rather want to err on the safe side
|
||||||
var client_time = perf.requestStart;
|
let client_time = perf.requestStart
|
||||||
cart._time_offset = server_time - client_time;
|
cart._time_offset = server_time - client_time
|
||||||
},
|
},
|
||||||
|
|
||||||
show_expiry_notification: function () {
|
show_expiry_notification: function () {
|
||||||
document.getElementById("dialog-cart-extend").showModal();
|
document.getElementById('dialog-cart-extend').showModal()
|
||||||
cart._expiry_notified = true;
|
cart._expiry_notified = true
|
||||||
},
|
},
|
||||||
|
|
||||||
draw_deadline: function () {
|
draw_deadline: function () {
|
||||||
function pad(n, width, z) {
|
function pad (n, width, z) {
|
||||||
z = z || '0';
|
z = z || '0'
|
||||||
n = n + '';
|
n = n + ''
|
||||||
return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
|
return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n
|
||||||
}
|
}
|
||||||
|
|
||||||
cart._deadline_call++;
|
cart._deadline_call++
|
||||||
if ((typeof django === 'undefined' || typeof django.gettext === 'undefined') && cart._deadline_call < 5) {
|
if ((typeof django === 'undefined' || typeof django.gettext === 'undefined') && cart._deadline_call < 5) {
|
||||||
// Language files are not loaded yet, don't run during the first seconds
|
// Language files are not loaded yet, don't run during the first seconds
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
var now = cart._get_now();
|
let now = cart._get_now()
|
||||||
var diff_total_seconds = cart._deadline.diff(now) / 1000;
|
let diff_total_seconds = cart._deadline.diff(now) / 1000
|
||||||
var diff_minutes = Math.floor(diff_total_seconds / 60);
|
let diff_minutes = Math.floor(diff_total_seconds / 60)
|
||||||
var diff_seconds = Math.floor(diff_total_seconds % 60);
|
let diff_seconds = Math.floor(diff_total_seconds % 60)
|
||||||
|
|
||||||
if (diff_minutes < 0) {
|
if (diff_minutes < 0) {
|
||||||
$("#cart-deadline").text(gettext("The items in your cart are no longer reserved for you. You can still complete your order as long as they’re available."));
|
$('#cart-deadline').text(gettext('The items in your cart are no longer reserved for you. You can still complete your order as long as they’re available.'))
|
||||||
$("#cart-deadline-short").text(
|
$('#cart-deadline-short').text(
|
||||||
gettext("Cart expired")
|
gettext('Cart expired')
|
||||||
);
|
)
|
||||||
if (!cart._deadline_timeout) {
|
if (!cart._deadline_timeout) {
|
||||||
// no timeout => first time draw_deadline is invoked, but cart already expired => do not show dialog
|
// no timeout => first time draw_deadline is invoked, but cart already expired => do not show dialog
|
||||||
cart._expiry_notified = true;
|
cart._expiry_notified = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (diff_minutes !== cart._prev_diff_minutes) {
|
if (diff_minutes !== cart._prev_diff_minutes) {
|
||||||
if (diff_minutes == 0) {
|
if (diff_minutes == 0) {
|
||||||
$("#cart-deadline").text(gettext("Your cart is about to expire."))
|
$('#cart-deadline').text(gettext('Your cart is about to expire.'))
|
||||||
} else {
|
} else {
|
||||||
$("#cart-deadline").text(
|
$('#cart-deadline').text(
|
||||||
ngettext(
|
ngettext(
|
||||||
"The items in your cart are reserved for you for one minute.",
|
'The items in your cart are reserved for you for one minute.',
|
||||||
"The items in your cart are reserved for you for {num} minutes.",
|
'The items in your cart are reserved for you for {num} minutes.',
|
||||||
diff_minutes
|
diff_minutes
|
||||||
).replace(/\{num\}/g, diff_minutes)
|
).replace(/\{num\}/g, diff_minutes)
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
cart._prev_diff_minutes = diff_minutes;
|
cart._prev_diff_minutes = diff_minutes
|
||||||
}
|
}
|
||||||
|
|
||||||
$("#cart-deadline-short").text(
|
$('#cart-deadline-short').text(
|
||||||
pad(diff_minutes.toString(), 2) + ':' + pad(diff_seconds.toString(), 2)
|
pad(diff_minutes.toString(), 2) + ':' + pad(diff_seconds.toString(), 2)
|
||||||
);
|
)
|
||||||
|
|
||||||
cart._deadline_timeout = window.setTimeout(cart.draw_deadline, 500);
|
cart._deadline_timeout = window.setTimeout(cart.draw_deadline, 500)
|
||||||
}
|
}
|
||||||
var already_expired = diff_total_seconds <= 0;
|
let already_expired = diff_total_seconds <= 0
|
||||||
var can_extend_cart = diff_minutes < 3 && (already_expired || cart._deadline < cart._max_extend);
|
let can_extend_cart = diff_minutes < 3 && (already_expired || cart._deadline < cart._max_extend)
|
||||||
$("#cart-extend-button").toggle(can_extend_cart);
|
$('#cart-extend-button').toggle(can_extend_cart)
|
||||||
if (can_extend_cart && diff_total_seconds < 45) {
|
if (can_extend_cart && diff_total_seconds < 45) {
|
||||||
if (!cart._expiry_notified) cart.show_expiry_notification();
|
if (!cart._expiry_notified) cart.show_expiry_notification()
|
||||||
$("#dialog-cart-extend-title").text(already_expired
|
$('#dialog-cart-extend-title').text(already_expired
|
||||||
? gettext("Your cart has expired.")
|
? gettext('Your cart has expired.')
|
||||||
: gettext("Your cart is about to expire."));
|
: gettext('Your cart is about to expire.'))
|
||||||
$("#dialog-cart-extend-description").text(already_expired
|
$('#dialog-cart-extend-description').text(already_expired
|
||||||
? gettext("The items in your cart are no longer reserved for you. You can still complete your order as long as they're available.")
|
? gettext('The items in your cart are no longer reserved for you. You can still complete your order as long as they\'re available.')
|
||||||
: gettext("Do you want to renew the reservation period?"));
|
: gettext('Do you want to renew the reservation period?'))
|
||||||
$("#dialog-cart-extend .modal-card-confirm button").text(already_expired
|
$('#dialog-cart-extend .modal-card-confirm button').text(already_expired
|
||||||
? gettext("Continue")
|
? gettext('Continue')
|
||||||
: gettext("Renew reservation"));
|
: gettext('Renew reservation'))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
init: function () {
|
init: function () {
|
||||||
"use strict";
|
'use strict'
|
||||||
cart._calc_offset();
|
cart._calc_offset()
|
||||||
cart.set_deadline(
|
cart.set_deadline(
|
||||||
$("#cart-deadline").attr("data-expires"),
|
$('#cart-deadline').attr('data-expires'),
|
||||||
$("#cart-deadline").attr("data-max-expiry-extend")
|
$('#cart-deadline').attr('data-max-expiry-extend')
|
||||||
);
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
set_deadline: function (expiry, max_extend, renewed_message) {
|
set_deadline: function (expiry, max_extend, renewed_message) {
|
||||||
"use strict";
|
'use strict'
|
||||||
cart._expiry_notified = false;
|
cart._expiry_notified = false
|
||||||
cart._deadline = moment(expiry);
|
cart._deadline = moment(expiry)
|
||||||
if (cart._deadline_timeout) {
|
if (cart._deadline_timeout) {
|
||||||
window.clearTimeout(cart._deadline_timeout);
|
window.clearTimeout(cart._deadline_timeout)
|
||||||
}
|
}
|
||||||
cart._deadline_timeout = null;
|
cart._deadline_timeout = null
|
||||||
cart._max_extend = moment(max_extend);
|
cart._max_extend = moment(max_extend)
|
||||||
cart.draw_deadline();
|
cart.draw_deadline()
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
$(function () {
|
$(function () {
|
||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
if ($("#cart-deadline").length) {
|
if ($('#cart-deadline').length) {
|
||||||
cart.init();
|
cart.init()
|
||||||
$("#cart-extend-confirmation-button").hide().on("blur", function() {
|
$('#cart-extend-confirmation-button').hide().on('blur', function () {
|
||||||
$(this).hide();
|
$(this).hide()
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
$("#cart-extend-form").on("pretix:async-task-success", function(e, data) {
|
$('#cart-extend-form').on('pretix:async-task-success', function (_e, data) {
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
cart.set_deadline(data.expiry, data.max_expiry_extend);
|
cart.set_deadline(data.expiry, data.max_expiry_extend)
|
||||||
} else {
|
} else {
|
||||||
alert(data.message);
|
alert(data.message)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
// renew-button in cart-panel is clicked, show inline dialog
|
// renew-button in cart-panel is clicked, show inline dialog
|
||||||
$("#cart-extend-button").on("click", function() {
|
$('#cart-extend-button').on('click', function () {
|
||||||
$("#cart-extend-form").one("pretix:async-task-success", function(e, data) {
|
$('#cart-extend-form').one('pretix:async-task-success', function (_e, data) {
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
document.getElementById("cart-extend-confirmation-dialog").show();
|
document.getElementById('cart-extend-confirmation-dialog').show()
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
$("#cart-extend-confirmation-dialog").on("keydown", function (e) {
|
$('#cart-extend-confirmation-dialog').on('keydown', function (e) {
|
||||||
if(e.key === "Escape") {
|
if (e.key === 'Escape') {
|
||||||
this.close();
|
this.close()
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
// renew-button in modal dialog is clicked, show modal dialog
|
// renew-button in modal dialog is clicked, show modal dialog
|
||||||
$("#dialog-cart-extend form").submit(function() {
|
$('#dialog-cart-extend form').submit(function () {
|
||||||
$("#cart-extend-form").one("pretix:async-task-success", function(e, data) {
|
$('#cart-extend-form').one('pretix:async-task-success', function (_e, data) {
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
$("#dialog-cart-extended-title").text(data.message);
|
$('#dialog-cart-extended-title').text(data.message)
|
||||||
$("#dialog-cart-extended-description").text($("#cart-deadline").text());
|
$('#dialog-cart-extended-description').text($('#cart-deadline').text())
|
||||||
document.getElementById("dialog-cart-extended").showModal();
|
document.getElementById('dialog-cart-extended').showModal()
|
||||||
}
|
}
|
||||||
}).submit();
|
}).submit()
|
||||||
});
|
})
|
||||||
|
|
||||||
$(".toggle-container").each(function() {
|
$('.toggle-container').each(function () {
|
||||||
var summary = $(".toggle-summary", this);
|
let summary = $('.toggle-summary', this)
|
||||||
var content = $("> :not(.toggle-summary)", this);
|
let content = $('> :not(.toggle-summary)', this)
|
||||||
var toggle = summary.find(".toggle").on("click", function(e) {
|
let toggle = summary.find('.toggle').on('click', function () {
|
||||||
this.ariaExpanded = !this.ariaExpanded;
|
this.ariaExpanded = !this.ariaExpanded
|
||||||
if (this.classList.contains("toggle-remove")) summary.attr("hidden", true);
|
if (this.classList.contains('toggle-remove')) summary.attr('hidden', true)
|
||||||
content.show().find(":input:visible").first().focus();
|
content.show().find(':input:visible').first().focus()
|
||||||
});
|
})
|
||||||
if (toggle.attr("aria-expanded")) {
|
if (toggle.attr('aria-expanded')) {
|
||||||
content.hide();
|
content.hide()
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
$(".cart-icon-details.collapse-lines").each(function () {
|
$('.cart-icon-details.collapse-lines').each(function () {
|
||||||
var $content = $(this).find(".content");
|
let $content = $(this).find('.content')
|
||||||
var original_html = $content.html();
|
let original_html = $content.html()
|
||||||
var br_exp = /<br\s*\/?>/i;
|
let br_exp = /<br\s*\/?>/i
|
||||||
$content.text(original_html.split(br_exp).join(', '));
|
$content.text(original_html.split(br_exp).join(', '))
|
||||||
if ($content.get(0).scrollWidth > $content.get(0).offsetWidth) {
|
if ($content.get(0).scrollWidth > $content.get(0).offsetWidth) {
|
||||||
var $handler = $("<button>")
|
let $handler = $('<button>')
|
||||||
.text($(this).attr("data-expand-text"))
|
.text($(this).attr('data-expand-text'))
|
||||||
.addClass("btn btn-link collapse-handler")
|
.addClass('btn btn-link collapse-handler')
|
||||||
.attr("type", "button")
|
.attr('type', 'button')
|
||||||
.attr("aria-controls", $content.attr('id'))
|
.attr('aria-controls', $content.attr('id'))
|
||||||
.attr("aria-expanded", "false");
|
.attr('aria-expanded', 'false')
|
||||||
$handler.on("click", function (ev) {
|
$handler.on('click', function () {
|
||||||
$content.html(original_html).removeClass("content");
|
$content.html(original_html).removeClass('content')
|
||||||
$handler.attr("aria-expanded", "true").attr("aria-hidden", "true");
|
$handler.attr('aria-expanded', 'true').attr('aria-hidden', 'true')
|
||||||
$handler.hide();
|
$handler.hide()
|
||||||
});
|
})
|
||||||
$(this).append($handler);
|
$(this).append($handler)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
})
|
||||||
|
|||||||
@@ -1,145 +1,143 @@
|
|||||||
/*global $ */
|
|
||||||
|
|
||||||
$(function () {
|
$(function () {
|
||||||
window.pretix = window.pretix || {};
|
window.pretix = window.pretix || {}
|
||||||
|
|
||||||
var storage_key = $("#cookie-consent-storage-key").text();
|
let storage_key = $('#cookie-consent-storage-key').text()
|
||||||
var widget_consent = $("#cookie-consent-from-widget").text();
|
let widget_consent = $('#cookie-consent-from-widget').text()
|
||||||
var consent_checkboxes = $("#cookie-consent-details input[type=checkbox][name]");
|
let consent_checkboxes = $('#cookie-consent-details input[type=checkbox][name]')
|
||||||
var consent_modal = document.getElementById("cookie-consent-modal");
|
let consent_modal = document.getElementById('cookie-consent-modal')
|
||||||
|
|
||||||
function update_consent(consent, sessionOnly) {
|
function update_consent (consent, sessionOnly) {
|
||||||
if (storage_key && window.sessionStorage && sessionOnly) {
|
if (storage_key && window.sessionStorage && sessionOnly) {
|
||||||
if (!window.localStorage[storage_key] || window.localStorage[storage_key] !== JSON.stringify(consent)) {
|
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
|
// No need to write to sessionStorage if the value is identical to the one in localStorage
|
||||||
window.sessionStorage[storage_key] = JSON.stringify(consent);
|
window.sessionStorage[storage_key] = JSON.stringify(consent)
|
||||||
}
|
}
|
||||||
} else if (storage_key && window.localStorage) {
|
} else if (storage_key && window.localStorage) {
|
||||||
window.localStorage[storage_key] = JSON.stringify(consent);
|
window.localStorage[storage_key] = JSON.stringify(consent)
|
||||||
// When saving permanent storage, clear session storage
|
// When saving permanent storage, clear session storage
|
||||||
window.sessionStorage.removeItem(storage_key);
|
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:
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#polyfill
|
// https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#polyfill
|
||||||
var e = document.createEvent('CustomEvent');
|
let e = document.createEvent('CustomEvent')
|
||||||
e.initCustomEvent('pretix:cookie-consent:change', true, true, consent);
|
e.initCustomEvent('pretix:cookie-consent:change', true, true, consent)
|
||||||
document.dispatchEvent(e)
|
document.dispatchEvent(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!storage_key) {
|
if (!storage_key) {
|
||||||
// We are not on a page where the consent should run, fire the change event with empty consent but don't
|
// We are not on a page where the consent should run, fire the change event with empty consent but don't
|
||||||
// actually store anything.
|
// actually store anything.
|
||||||
update_consent(null, false);
|
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({}, false)
|
update_consent({}, false)
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var storage_val, consent_source, save_for_session_only;
|
let storage_val, consent_source, save_for_session_only
|
||||||
if (window.sessionStorage[storage_key]) {
|
if (window.sessionStorage[storage_key]) {
|
||||||
// A manual input was given inside a widget. This is the user's last explicit choice and takes precedence –
|
// 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.
|
// as long as they are in the widget.
|
||||||
storage_val = JSON.parse(window.sessionStorage[storage_key]);
|
storage_val = JSON.parse(window.sessionStorage[storage_key])
|
||||||
consent_source = 'sessionStorage';
|
consent_source = 'sessionStorage'
|
||||||
save_for_session_only = true;
|
save_for_session_only = true
|
||||||
} else if (widget_consent) {
|
} else if (widget_consent) {
|
||||||
// An input was given through the widget. This takes precedence over localStorage as we need to assume the
|
// 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
|
// 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
|
// 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
|
// 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 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
|
// 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
|
// 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.
|
// 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
|
||||||
});
|
})
|
||||||
consent_source = 'widget';
|
consent_source = 'widget'
|
||||||
save_for_session_only = !!window.localStorage[storage_key];
|
save_for_session_only = !!window.localStorage[storage_key]
|
||||||
} else if (window.localStorage[storage_key]) {
|
} else if (window.localStorage[storage_key]) {
|
||||||
// The user made a specific selection, let's use that.
|
// The user made a specific selection, let's use that.
|
||||||
storage_val = JSON.parse(window.localStorage[storage_key]) || {};
|
storage_val = JSON.parse(window.localStorage[storage_key]) || {}
|
||||||
consent_source = 'localStorage';
|
consent_source = 'localStorage'
|
||||||
save_for_session_only = false;
|
save_for_session_only = false
|
||||||
} else {
|
} else {
|
||||||
// No consent given, dialog will be shown.
|
// No consent given, dialog will be shown.
|
||||||
storage_val = {};
|
storage_val = {}
|
||||||
consent_source = 'new';
|
consent_source = 'new'
|
||||||
save_for_session_only = false;
|
save_for_session_only = false
|
||||||
}
|
}
|
||||||
|
|
||||||
var show_dialog = false;
|
let show_dialog = false
|
||||||
consent_checkboxes.each(function () {
|
consent_checkboxes.each(function () {
|
||||||
if (typeof storage_val[this.name] === "undefined") {
|
if (typeof storage_val[this.name] === 'undefined') {
|
||||||
// A new cookie type has been added that we haven't asked for yet
|
// A new cookie type has been added that we haven't asked for yet
|
||||||
if (consent_source === "widget") {
|
if (consent_source === 'widget') {
|
||||||
// Trust the widget, keep it as "no consent"
|
// Trust the widget, keep it as "no consent"
|
||||||
} else {
|
} else {
|
||||||
show_dialog = true;
|
show_dialog = true
|
||||||
}
|
}
|
||||||
} else if (storage_val[this.name]) {
|
} else if (storage_val[this.name]) {
|
||||||
this.checked = true;
|
this.checked = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
update_consent(storage_val, save_for_session_only);
|
update_consent(storage_val, save_for_session_only)
|
||||||
|
|
||||||
if (!consent_modal) {
|
if (!consent_modal) {
|
||||||
// Cookie consent is active, but no provider defined
|
// Cookie consent is active, but no provider defined
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
function _set_button_text () {
|
function _set_button_text () {
|
||||||
var btn = $("#cookie-consent-button-no");
|
let btn = $('#cookie-consent-button-no')
|
||||||
btn.text(
|
btn.text(
|
||||||
consent_checkboxes.filter(":checked").length ?
|
consent_checkboxes.filter(':checked').length
|
||||||
btn.attr("data-detail-text") :
|
? btn.attr('data-detail-text')
|
||||||
btn.attr("data-summary-text")
|
: btn.attr('data-summary-text')
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (consent_checkboxes.filter(":checked").length) {
|
if (consent_checkboxes.filter(':checked').length) {
|
||||||
$("#cookie-consent-details").prop("open", true).find("> *:not(summary)").show();
|
$('#cookie-consent-details').prop('open', true).find('> *:not(summary)').show()
|
||||||
}
|
}
|
||||||
|
|
||||||
_set_button_text();
|
_set_button_text()
|
||||||
if (show_dialog) {
|
if (show_dialog) {
|
||||||
consent_modal.showModal();
|
consent_modal.showModal()
|
||||||
consent_modal.addEventListener("cancel", function() {
|
consent_modal.addEventListener('cancel', function () {
|
||||||
// Dialog was initially shown, interpret Escape as „do not consent to new providers“
|
// Dialog was initially shown, interpret Escape as „do not consent to new providers“
|
||||||
var consent = {};
|
let consent = {}
|
||||||
consent_checkboxes.each(function () {
|
consent_checkboxes.each(function () {
|
||||||
consent[this.name] = storage_val[this.name] || false;
|
consent[this.name] = storage_val[this.name] || false
|
||||||
});
|
})
|
||||||
update_consent(consent, false);
|
update_consent(consent, false)
|
||||||
}, {once : true});
|
}, { once: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
consent_modal.addEventListener("close", function () {
|
consent_modal.addEventListener('close', function () {
|
||||||
if (!consent_modal.returnValue) {// ESC, do not save
|
if (!consent_modal.returnValue) { // ESC, do not save
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
var consent = {};
|
let consent = {}
|
||||||
var consent_all = consent_modal.returnValue == "yes";
|
let consent_all = consent_modal.returnValue == 'yes'
|
||||||
consent_checkboxes.each(function () {
|
consent_checkboxes.each(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, false);
|
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) {
|
||||||
consent_modal.showModal()
|
consent_modal.showModal()
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
});
|
})
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
var inIframe = function () {
|
let inIframe = function () {
|
||||||
try {
|
try {
|
||||||
return window.self !== window.top;
|
return window.self !== window.top
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
if (inIframe()) {
|
if (inIframe()) {
|
||||||
document.documentElement.classList.add('in-iframe');
|
document.documentElement.classList.add('in-iframe')
|
||||||
try {
|
try {
|
||||||
window.parent.postMessage({
|
window.parent.postMessage({
|
||||||
type: "pretix:widget:title",
|
type: 'pretix:widget:title',
|
||||||
title: document.title,
|
title: document.title,
|
||||||
}, "*");
|
}, '*')
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Could not post message to parent.", e);
|
console.error('Could not post message to parent.', e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user