Compare commits

...
Author SHA1 Message Date
Richard Schreiber eb61bcdd4e Fix email preview curly brackets mismatch 2024-05-29 16:11:05 +02:00
Raphael Michel 1566f54764 VAT ID validation: Fix crash with invalid Norwegian IDs (PRETIXEU-A3J) 2024-05-29 09:31:58 +02:00
Richard Schreiber 9d380557e1 SEO improvements - add h1.sr-only if only header-image is used
* add hidden h1 with event-title if header-image only

* add event-title to alt-attribute of header-image

* add hidden setting for google_site_verification
2024-05-28 09:18:15 +02:00
Martin Gross 5758e0dd68 PPv2 APM: Create referenced PPObjects for APM Orders; enable webhooks to capture them (#3958) 2024-05-27 13:45:37 +02:00
Martin Gross b4629e24a5 Downgrade requests to 2.31.* again while waiting for 2.33.3 release 2024-05-27 12:11:40 +02:00
7 changed files with 96 additions and 37 deletions
+1 -1
View File
@@ -91,7 +91,7 @@ dependencies = [
"qrcode==7.4.*",
"redis==5.0.*",
"reportlab==4.2.*",
"requests==2.32.*",
"requests==2.31.*",
"sentry-sdk==1.45.*",
"sepaxml==2.6.*",
"slimit",
+4 -1
View File
@@ -62,7 +62,10 @@ class VATIDTemporaryError(VATIDError):
def _validate_vat_id_NO(vat_id, country_code):
# Inspired by vat_moss library
vat_id = vat_moss.id.normalize(vat_id)
try:
vat_id = vat_moss.id.normalize(vat_id)
except ValueError:
raise VATIDFinalError(error_messages['invalid'])
if not vat_id or len(vat_id) < 3 or not re.match('^\\d{9}MVA$', vat_id[2:]):
raise VATIDFinalError(error_messages['invalid'])
+31 -12
View File
@@ -749,21 +749,40 @@ class MailSettingsPreview(EventPermissionRequiredMixin, View):
if preview_item not in MailSettingsForm.base_context:
return HttpResponseBadRequest(_('invalid item'))
regex = r"^" + re.escape(preview_item) + r"_(?P<idx>[\d]+)$"
re_escape_single_bracket = r"((\{)[^\}]+\{|\}[^\{]+(\})|(\{)[^\}]*$|^[^\{]*(\}))"
msgs = {}
for k, v in request.POST.items():
# only accept allowed fields
matched = re.search(regex, k)
if matched is not None:
idx = matched.group('idx')
if idx in self.supported_locale:
with language(self.supported_locale[idx], self.request.event.settings.region):
if k.startswith('mail_subject_'):
msgs[self.supported_locale[idx]] = format_map(bleach.clean(v), self.placeholders(preview_item))
else:
msgs[self.supported_locale[idx]] = markdown_compile_email(
format_map(v, self.placeholders(preview_item))
)
if not k.startswith(preview_item + "_"):
continue
idx = k[len(preview_item)+1:]
if idx in self.supported_locale:
cleaned_v = ""
test_v = v.replace("{{", "__").replace("}}", "__")
while True:
match = re.search(re_escape_single_bracket, test_v)
if not match:
cleaned_v += v
break
if "{" in match.groups():
# replace first occurrence of { with {{, but keep trailing { in testing string
match_end = match.end()-1
cleaned_v += v[:match_end].replace("{", "{{", 1)
else:
# replace last occurrence of } with }}
match_end = match.end()
cleaned_v += "}}".join(v[:match_end].rsplit("}", 1))
v = v[match_end:]
test_v = test_v[match_end:]
with language(self.supported_locale[idx], self.request.event.settings.region):
if k.startswith('mail_subject_'):
msgs[self.supported_locale[idx]] = format_map(bleach.clean(cleaned_v), self.placeholders(preview_item))
else:
msgs[self.supported_locale[idx]] = markdown_compile_email(
format_map(cleaned_v, self.placeholders(preview_item))
)
return JsonResponse({
'item': preview_item,
+11
View File
@@ -30,6 +30,7 @@ from django import forms
from django.conf import settings
from django.contrib import messages
from django.core.cache import cache
from django.db import transaction
from django.http import HttpRequest
from django.template.loader import get_template
from django.templatetags.static import static
@@ -54,6 +55,7 @@ from pretix.base.models import Event, Order, OrderPayment, OrderRefund, Quota
from pretix.base.payment import BasePaymentProvider, PaymentException
from pretix.base.services.mail import SendMailException
from pretix.base.settings import SettingsSandbox
from pretix.helpers import OF_SELF
from pretix.helpers.urls import build_absolute_uri as build_global_uri
from pretix.multidomain.urlreverse import build_absolute_uri, eventreverse
from pretix.plugins.paypal2.client.core.environment import (
@@ -585,6 +587,9 @@ class PaypalMethod(BasePaymentProvider):
},
})
response = self.client.execute(paymentreq)
if payment:
ReferencedPayPalObject.objects.get_or_create(order=payment.order, payment=payment, reference=response.result.id)
except IOError as e:
if "RESOURCE_NOT_FOUND" in str(e):
messages.error(request, _('Your payment has failed due to a known issue within PayPal. Please try '
@@ -617,7 +622,13 @@ class PaypalMethod(BasePaymentProvider):
}
return template.render(ctx)
@transaction.atomic
def execute_payment(self, request: HttpRequest, payment: OrderPayment):
payment = OrderPayment.objects.select_for_update(of=OF_SELF).get(pk=payment.pk)
if payment.state == OrderPayment.PAYMENT_STATE_CONFIRMED:
logger.warning('payment is already confirmed; possible return-view/webhook race-condition')
return
try:
if request.session.get('payment_paypal_oid', '') == '':
raise PaymentException(_('We were unable to process your payment. See below for details on how to '
+27 -21
View File
@@ -477,29 +477,35 @@ def webhook(request, *args, **kwargs):
amount=payment.amount - known_sum
)
elif payment.state in (OrderPayment.PAYMENT_STATE_PENDING, OrderPayment.PAYMENT_STATE_CREATED,
OrderPayment.PAYMENT_STATE_CANCELED, OrderPayment.PAYMENT_STATE_FAILED) \
and sale['status'] == 'COMPLETED':
any_captures = False
all_captures_completed = True
for purchaseunit in sale['purchase_units']:
for capture in purchaseunit['payments']['captures']:
try:
ReferencedPayPalObject.objects.get_or_create(order=payment.order, payment=payment,
reference=capture['id'])
except ReferencedPayPalObject.MultipleObjectsReturned:
pass
OrderPayment.PAYMENT_STATE_CANCELED, OrderPayment.PAYMENT_STATE_FAILED):
if sale['status'] == 'COMPLETED':
any_captures = False
all_captures_completed = True
for purchaseunit in sale['purchase_units']:
for capture in purchaseunit['payments']['captures']:
try:
ReferencedPayPalObject.objects.get_or_create(order=payment.order, payment=payment,
reference=capture['id'])
except ReferencedPayPalObject.MultipleObjectsReturned:
pass
if capture['status'] not in ('COMPLETED', 'REFUNDED', 'PARTIALLY_REFUNDED'):
all_captures_completed = False
else:
any_captures = True
if any_captures and all_captures_completed:
if capture['status'] not in ('COMPLETED', 'REFUNDED', 'PARTIALLY_REFUNDED'):
all_captures_completed = False
else:
any_captures = True
if any_captures and all_captures_completed:
try:
payment.info = json.dumps(sale.dict())
payment.save(update_fields=['info'])
payment.confirm()
except Quota.QuotaExceededException:
pass
elif sale['status'] == 'APPROVED':
request.session['payment_paypal_oid'] = payment.info_data['id']
try:
payment.info = json.dumps(sale.dict())
payment.save(update_fields=['info'])
payment.confirm()
except Quota.QuotaExceededException:
pass
payment.payment_provider.execute_payment(request, payment)
except PaymentException as e:
logger.exception('PayPal2 - Could not capture/execute_payment from Webhook: {}'.format(str(e)))
return HttpResponse(status=200)
@@ -19,6 +19,9 @@
{% if social_image %}
<meta property="og:image" content="{{ social_image }}" />
{% endif %}
{% if event.settings.google_site_verification %}
<meta name="google-site-verification" content="{{ event.settings.google_site_verification }}" />
{% endif %}
{{ block.super }}
{% endblock %}
{% block above %}
@@ -68,15 +71,23 @@
{% block page %}
<div class="page-header{% if event_logo %} pager-header-with-logo{% endif %}{% if event_logo and event_logo_image_large %} logo-large{% endif %}">
<div class="{% if not event_logo or not event_logo_image_large %}pull-left flip{% endif %}">
{% if event_logo and not event_logo_show_title %}
<h1 class="sr-only">
{{ event.name }}
{% if request.event.settings.show_dates_on_frontpage and not event.has_subevents %}
<small>{{ event.get_date_range_display_as_html }}</small>
{% endif %}
</h1>
{% endif %}
{% if event_logo and event_logo_image_large %}
<a href="{% eventurl event "presale:event.index" cart_namespace=cart_namespace|default_if_none:"" %}"
aria-label="{% trans 'Homepage' %}" title="{% trans 'Homepage' %}">
<img src="{{ event_logo|thumb:'1170x5000' }}" alt="" class="event-logo" />
<img src="{{ event_logo|thumb:'1170x5000' }}" alt="{{ event.name }}" class="event-logo" />
</a>
{% elif event_logo %}
<a href="{% eventurl event "presale:event.index" cart_namespace=cart_namespace|default_if_none:"" %}"
aria-label="{% trans 'Homepage' %}" title="{% trans 'Homepage' %}">
<img src="{{ event_logo|thumb:'5000x120' }}" alt="" class="event-logo" />
<img src="{{ event_logo|thumb:'5000x120' }}" alt="{{ event.name }}" class="event-logo" />
</a>
{% else %}
<h1>
@@ -12,6 +12,10 @@
<meta name="robots" content="noindex, nofollow">
{% endif %}
<meta property="og:type" content="website" />
{% if organizer.settings.google_site_verification %}
<meta name="google-site-verification" content="{{ organizer.settings.google_site_verification }}" />
{% endif %}
{{ block.super }}
{% endblock %}
{% block above %}
@@ -39,6 +43,11 @@
{% block page %}
<div class="page-header{% if organizer_logo %} pager-header-with-logo{% endif %}{% if organizer_logo and organizer.settings.organizer_logo_image_large %} logo-large{% endif %}">
<div class="{% if not organizer_logo or not organizer.settings.organizer_logo_image_large %}pull-left flip{% endif %}">
{% if organizer_logo %}
<h1 class="sr-only">
{{ organizer.name }}
</h1>
{% endif %}
{% if organizer_logo and organizer.settings.organizer_logo_image_large %}
<a href="{% eventurl organizer "presale:organizer.index" %}" title="{{ organizer.name }}">
<img src="{{ organizer_logo|thumb:'1170x5000' }}" alt="{{ organizer.name }}"