From ed4846dd9df32aa97ba2bf73ac772428d4237f25 Mon Sep 17 00:00:00 2001 From: std-odoo Date: Tue, 26 May 2020 06:29:34 +0000 Subject: [PATCH] [IMP] mass_mailing: add preview block for mailings PURPOSE When displaying an email in a list the mail client (gmail, outlook...) computes a preview based on the content. This preview is generally not well computed as it contains a lot of garbage and tags while it should contain only relevant text. SPECIFICATIONS To build this preview, all mail clients read the content of the email. The only way to be able to customize the preview is to add an invisible HTML element at the beginning of the email with the wanted preview text. We add at the end of the preview `‌` (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. It doesn't work with simple space as the mail clients trim each HTML element content. Task ID-2172125 PR #49886 --- addons/link_tracker/tests/common.py | 4 +- addons/mail/models/mail_render_mixin.py | 41 ++++++++++++++ addons/mass_mailing/models/mailing.py | 2 +- .../wizard/mailing_mailing_test.py | 3 +- odoo/addons/base/tests/test_mail.py | 53 +++++++++++++++++++ odoo/tools/mail.py | 14 +++++ 6 files changed, 113 insertions(+), 4 deletions(-) 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 #----------------------------------------------------------