diff --git a/addons/link_tracker/tests/common.py b/addons/link_tracker/tests/common.py
index 2d4c651dcd4..943f06ec385 100644
--- a/addons/link_tracker/tests/common.py
+++ b/addons/link_tracker/tests/common.py
@@ -23,7 +23,7 @@ class MockLinkTracker(common.BaseCase):
def _get_href_from_anchor_id(self, body, anchor_id):
html = etree.fromstring(body)
- return html.xpath("*[@id='%s']" % anchor_id)[0].attrib.get('href')
+ return html.xpath("//*[@id='%s']" % anchor_id)[0].attrib.get('href')
def _get_tracker_from_short_url(self, short_url):
code = self.env['link.tracker.code'].sudo().search([
@@ -41,7 +41,7 @@ class MockLinkTracker(common.BaseCase):
)
"""
(anchor_id, url, is_shortened) = link_info
- anchor_href = self._get_href_from_anchor_id(body, anchor_id)
+ anchor_href = self._get_href_from_anchor_id("
%s
" % body, anchor_id)
if is_shortened:
self.assertTrue('/r/' in anchor_href, '%s should be shortened: %s' % (anchor_id, anchor_href))
link_tracker = self._get_tracker_from_short_url(anchor_href)
diff --git a/addons/mail/models/mail_render_mixin.py b/addons/mail/models/mail_render_mixin.py
index 79b66e55f8b..db29d266114 100644
--- a/addons/mail/models/mail_render_mixin.py
+++ b/addons/mail/models/mail_render_mixin.py
@@ -206,6 +206,47 @@ class MailRenderMixin(models.AbstractModel):
html = self.env['mail.render.mixin']._replace_local_links(html)
return html
+ @api.model
+ def _generate_preview(self, html):
+ """ Generate a preview of html, to used for example in marketing emails.
+
+ We add the character `` (zero-width non-joiner) to fill the end of
+ the preview in order to not have the beginning of the mail at the end of
+ the preview (don't work with simple space as the content is trimmed).
+ https://litmus.com/blog/the-ultimate-guide-to-preview-text-support
+
+ :param html: html content for which we generate the preview
+ :return: html to use as preview
+ """
+ preview = ""
+ for element in re.finditer(r"\>([^<>]+)\<", html):
+ element = element.group(1).strip()
+ if element:
+ preview += " %s" % element
+ if len(preview) > 500:
+ break
+
+ html_preview = f"""
+
+ {tools.html_escape(preview)} {' ' * 500}
+
+ """ if preview else ''
+
+ return html_preview
+
+ @api.model
+ def _prepend_preview(self, html):
+ """ Prepare the email body before sending. Add the text preview at the
+ beginning of the mail. The preview text is displayed bellow the mail
+ subject of most mail client (gmail, outlook...).
+
+ See ``_generate_preview()`` for more details about the preview computation.
+
+ :param html: html content for which we want to prepend a preview
+ :return: html with preprended preview
+ """
+ return tools.prepend_html_content(html, self._generate_preview(html))
+
# ------------------------------------------------------------
# RENDERING
# ------------------------------------------------------------
diff --git a/addons/mass_mailing/models/mailing.py b/addons/mass_mailing/models/mailing.py
index 78890b1a6d8..86b6714260e 100644
--- a/addons/mass_mailing/models/mailing.py
+++ b/addons/mass_mailing/models/mailing.py
@@ -569,7 +569,7 @@ class MassMailing(models.Model):
composer_values = {
'author_id': author_id,
'attachment_ids': [(4, attachment.id) for attachment in mailing.attachment_ids],
- 'body': mailing.body_html,
+ 'body': self._prepend_preview(self.body_html),
'subject': mailing.subject,
'model': mailing.mailing_model_real,
'email_from': mailing.email_from,
diff --git a/addons/mass_mailing/wizard/mailing_mailing_test.py b/addons/mass_mailing/wizard/mailing_mailing_test.py
index 86c1ba2dbe1..e37fccd3e90 100644
--- a/addons/mass_mailing/wizard/mailing_mailing_test.py
+++ b/addons/mass_mailing/wizard/mailing_mailing_test.py
@@ -24,7 +24,8 @@ class TestMassMailing(models.TransientModel):
mass_mail_layout = self.env.ref('mass_mailing.mass_mailing_mail_layout')
for test_mail in test_emails:
# Convert links in absolute URLs before the application of the shortener
- body = self.env['mail.render.mixin']._replace_local_links(mailing.body_html)
+ body = mailing._prepend_preview(mailing.body_html)
+ body = self.env['mail.render.mixin']._replace_local_links(body)
body = tools.html_sanitize(body, sanitize_attributes=True, sanitize_style=True)
mail_values = {
'email_from': mailing.email_from,
diff --git a/odoo/addons/base/tests/test_mail.py b/odoo/addons/base/tests/test_mail.py
index 5973fd623fb..69ad772447e 100644
--- a/odoo/addons/base/tests/test_mail.py
+++ b/odoo/addons/base/tests/test_mail.py
@@ -5,6 +5,7 @@
from unittest.mock import patch
import email.policy
import email.message
+import re
import threading
from odoo.tests.common import BaseCase, SavepointCase, TransactionCase
@@ -12,6 +13,7 @@ from odoo.tools import (
is_html_empty, html_sanitize, append_content_to_html, plaintext2html,
email_split,
misc, formataddr,
+ prepend_html_content,
)
from . import test_mail_examples
@@ -334,6 +336,57 @@ class TestHtmlTools(BaseCase):
for content in valid_html_samples:
self.assertFalse(is_html_empty(content))
+ def test_prepend_html_content(self):
+ body = """
+
+
+ test
+
+
+ """
+
+ content = "content"
+
+ result = prepend_html_content(body, content)
+ result = re.sub(r'[\s\t]', '', result)
+ self.assertEqual(result, "contenttest
")
+
+ body = "test
"
+ content = "content"
+
+ result = prepend_html_content(body, content)
+ result = re.sub(r'[\s\t]', '', result)
+ self.assertEqual(result, "contenttest
")
+
+ body = """
+
+ test
+
+ """
+
+ result = prepend_html_content(body, content)
+ result = re.sub(r'[\s\t]', '', result)
+ self.assertEqual(result, "contenttest
")
+
+ body = """
+
+
+ test
+
+
+ """
+
+ content = """
+
+
+ test
+
+
+ """
+ result = prepend_html_content(body, content)
+ result = re.sub(r'[\s\t]', '', result)
+ self.assertEqual(result, "test
test
")
+
class TestEmailTools(BaseCase):
""" Test some of our generic utility functions for emails """
diff --git a/odoo/tools/mail.py b/odoo/tools/mail.py
index eed992e142f..c98a575f10a 100644
--- a/odoo/tools/mail.py
+++ b/odoo/tools/mail.py
@@ -428,6 +428,20 @@ def append_content_to_html(html, content, plaintext=True, preserve=False, contai
return '%s%s' % (html, content)
return '%s%s%s' % (html[:insert_location], content, html[insert_location:])
+
+def prepend_html_content(html_body, html_content):
+ """Prepend some HTML content at the beginning of an other HTML content."""
+ html_content = re.sub(r'(?i)(?(?:html|body|head|!\s*DOCTYPE)[^>]*>)', '', html_content)
+ html_content = html_content.strip()
+
+ insert_index = next(re.finditer(r']*>', html_body), None)
+ if insert_index is None:
+ insert_index = next(re.finditer(r']*>', html_body), None)
+
+ insert_index = insert_index.end() if insert_index else 0
+
+ return html_body[:insert_index] + html_content + html_body[insert_index:]
+
#----------------------------------------------------------
# Emails
#----------------------------------------------------------