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, "content
test
") + + body = "
test
" + content = "content" + + result = prepend_html_content(body, content) + result = re.sub(r'[\s\t]', '', result) + self.assertEqual(result, "content
test
") + + body = """ + +
test
+ + """ + + result = prepend_html_content(body, content) + result = re.sub(r'[\s\t]', '', result) + self.assertEqual(result, "content
test
") + + 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_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 #----------------------------------------------------------