diff --git a/src/pretix/base/email.py b/src/pretix/base/email.py
index ab71fb45f..155b29657 100644
--- a/src/pretix/base/email.py
+++ b/src/pretix/base/email.py
@@ -39,7 +39,7 @@ from pretix.base.templatetags.rich_text import (
DEFAULT_CALLBACKS, EMAIL_RE, URL_RE, abslink_callback,
markdown_compile_email, truelink_callback,
)
-from pretix.helpers.format import SafeFormatter, format_map
+from pretix.helpers.format import FormattedString, SafeFormatter, format_map
from pretix.base.services.placeholders import ( # noqa
get_available_placeholders, PlaceholderContext
@@ -141,6 +141,7 @@ class TemplateBasedMailRenderer(BaseHTMLMailRenderer):
return markdown_compile_email(plaintext, context=context)
def render(self, plain_body: str, plain_signature: str, subject: str, order, position, context) -> str:
+ apply_format_map = not isinstance(plain_body, FormattedString)
body_md = self.compile_markdown(plain_body, context)
if context:
linker = bleach.Linker(
@@ -149,12 +150,13 @@ class TemplateBasedMailRenderer(BaseHTMLMailRenderer):
callbacks=DEFAULT_CALLBACKS + [truelink_callback, abslink_callback],
parse_email=True
)
- body_md = format_map(
- body_md,
- context=context,
- mode=SafeFormatter.MODE_RICH_TO_HTML,
- linkifier=linker
- )
+ if apply_format_map:
+ body_md = format_map(
+ body_md,
+ context=context,
+ mode=SafeFormatter.MODE_RICH_TO_HTML,
+ linkifier=linker
+ )
htmlctx = {
'site': settings.PRETIX_INSTANCE_NAME,
'site_url': settings.SITE_URL,
diff --git a/src/pretix/base/services/mail.py b/src/pretix/base/services/mail.py
index 9599d73f4..cbc5ddd6e 100644
--- a/src/pretix/base/services/mail.py
+++ b/src/pretix/base/services/mail.py
@@ -81,7 +81,9 @@ from pretix.base.signals import (
)
from pretix.celery_app import app
from pretix.helpers import OF_SELF
-from pretix.helpers.format import FormattedString, SafeFormatter, format_map
+from pretix.helpers.format import (
+ FormattedString, PlainHtmlAlternativeString, SafeFormatter, format_map,
+)
from pretix.helpers.hierarkey import clean_filename
from pretix.multidomain.urlreverse import build_absolute_uri
from pretix.presale.ical import get_private_icals
@@ -266,7 +268,10 @@ def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, La
signature = ""
# Build full plain-text body
- body_plain = format_map(content_plain, context, mode=SafeFormatter.MODE_RICH_TO_PLAIN)
+ if not isinstance(content_plain, FormattedString):
+ body_plain = format_map(content_plain, context, mode=SafeFormatter.MODE_RICH_TO_PLAIN)
+ else:
+ body_plain = content_plain
body_plain = _wrap_plain_body(body_plain, signature, event, order, position, no_order_links)
# Build subject
@@ -793,7 +798,12 @@ def render_mail(template, context, placeholder_mode: Optional[int]=SafeFormatter
body = format_map(body, context, mode=placeholder_mode)
else:
tpl = get_template(template)
- body = tpl.render(context)
+ context = {
+ # Known bug, should behave differently for plain and HTML but we'll fix after security release
+ k: v.html if isinstance(v, PlainHtmlAlternativeString) else v
+ for k, v in context.items()
+ }
+ body = FormattedString(tpl.render(context))
return body
diff --git a/src/tests/base/test_mail.py b/src/tests/base/test_mail.py
index 6001ae26f..2ba937b6d 100644
--- a/src/tests/base/test_mail.py
+++ b/src/tests/base/test_mail.py
@@ -42,6 +42,7 @@ import pytest
from django.conf import settings
from django.core import mail as djmail
from django.test import override_settings
+from django.utils.html import escape
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from django_scopes import scope, scopes_disabled
@@ -322,7 +323,7 @@ def _extract_html(mail):
def test_placeholder_html_rendering_from_template(env):
djmail.outbox = []
event, user, organizer = env
- event.name = "event & co. kg"
+ event.name = "event & co. kg {currency}"
event.save()
mail('dummy@dummy.dummy', '{event} Test subject', 'mailtest.txt', get_email_context(
event=event,
@@ -331,25 +332,26 @@ def test_placeholder_html_rendering_from_template(env):
assert len(djmail.outbox) == 1
assert djmail.outbox[0].to == [user.email]
- assert 'Event name: event & co. kg' in djmail.outbox[0].body
- assert '**IBAN**: 123 \n**BIC**: 456' in djmail.outbox[0].body
- assert '**Meta**: *Beep*' in djmail.outbox[0].body
- assert 'Event website: [event & co. kg](https://example.org/dummy)' in djmail.outbox[0].body
- assert 'Other website: [event & co. kg](https://example.com)' in djmail.outbox[0].body
- assert '<' not in djmail.outbox[0].body
- assert '&' not in djmail.outbox[0].body
+ # Known bug for now: These should not have HTML for the plain body, but we'll fix this safter the security release
+ assert escape('Event name: event & co. kg {currency}') in djmail.outbox[0].body
+ assert 'IBAN: 123
\nBIC: 456' in djmail.outbox[0].body
+ assert '**Meta**: Beep' in djmail.outbox[0].body
+ assert escape('Event website: [event & co. kg {currency}](https://example.org/dummy)') in djmail.outbox[0].body
+ # todo: assert '<' not in djmail.outbox[0].body
+ # todo: assert '&' not in djmail.outbox[0].body
+ assert 'Unevaluated placeholder: {currency}' in djmail.outbox[0].body
+ assert 'EUR' not in djmail.outbox[0].body
html = _extract_html(djmail.outbox[0])
assert 'event' not in html
- assert 'Event name: <strong>event & co. kg</strong>' in html
+ assert 'Event name: <strong>event & co. kg</strong> {currency}' in html
assert 'IBAN: 123
\nBIC: 456' in html
assert 'Meta: Beep' in html
+ assert 'Unevaluated placeholder: {currency}' in html
+ assert 'EUR' not in html
assert re.search(
- r'Event website: <strong>event & co. kg</strong>',
- html
- )
- assert re.search(
- r'Other website: <strong>event & co. kg</strong>',
+ r'Event website: '
+ r'<strong>event & co. kg</strong> {currency}',
html
)
@@ -367,7 +369,7 @@ def test_placeholder_html_rendering_from_string(env):
})
djmail.outbox = []
event, user, organizer = env
- event.name = "event & co. kg"
+ event.name = "event & co. kg {currency}"
event.save()
ctx = get_email_context(
event=event,
@@ -378,9 +380,9 @@ def test_placeholder_html_rendering_from_string(env):
assert len(djmail.outbox) == 1
assert djmail.outbox[0].to == [user.email]
- assert 'Event name: event & co. kg' in djmail.outbox[0].body
- assert 'Event website: [event & co. kg](https://example.org/dummy)' in djmail.outbox[0].body
- assert 'Other website: [event & co. kg](https://example.com)' in djmail.outbox[0].body
+ assert 'Event name: event & co. kg {currency}' in djmail.outbox[0].body
+ assert 'Event website: [event & co. kg {currency}](https://example.org/dummy)' in djmail.outbox[0].body
+ assert 'Other website: [event & co. kg {currency}](https://example.com)' in djmail.outbox[0].body
assert '**IBAN**: 123 \n**BIC**: 456' in djmail.outbox[0].body
assert '**Meta**: *Beep*' in djmail.outbox[0].body
assert 'URL: https://google.com' in djmail.outbox[0].body
@@ -395,11 +397,13 @@ def test_placeholder_html_rendering_from_string(env):
assert 'IBAN: 123
\nBIC: 456' in html
assert 'Meta: Beep' in html
assert re.search(
- r'Event website: <strong>event & co. kg</strong>',
+ r'Event website: '
+ r'<strong>event & co. kg</strong> {currency}',
html
)
assert re.search(
- r'Other website: <strong>event & co. kg</strong>',
+ r'Other website: '
+ r'<strong>event & co. kg</strong> {currency}',
html
)
assert re.search(
diff --git a/src/tests/templates/mailtest.txt b/src/tests/templates/mailtest.txt
index 1bc1487d3..a620f1ca8 100644
--- a/src/tests/templates/mailtest.txt
+++ b/src/tests/templates/mailtest.txt
@@ -1,13 +1,13 @@
{% load i18n %}
This is a test file for sending mails.
-Event name: {event}
+Event name: {{ event }}
+Unevaluated placeholder: {currency}
{% get_current_language as LANGUAGE_CODE %}
The language code used for rendering this email is {{ LANGUAGE_CODE }}.
Payment info:
-{payment_info}
+{{ payment_info }}
-**Meta**: {meta_Test}
+**Meta**: {{ meta_Test }}
-Event website: [{event}](https://example.org/{event_slug})
-Other website: [{event}]({meta_Website})
\ No newline at end of file
+Event website: [{{event}}](https://example.org/{{event_slug}})
\ No newline at end of file