[FIX] mail: replace local links in notification emails

Since notification emails are not mail templates anymore they do not go
through the mail_template.render_post_process() method that converts
local links to absolute links. This means that buttons and images were
not correctly displayed in notification emails. Indeed those still had
local links.

This commit fixes that by moving the method directly in mail.thread and
calling it accordingly when rendering the notification email. Note that
the method has been changed in order to use regex instead of html
parser. It is done based on work already done in mass mailing application.
Indeed using parsers is complex and time consuming and may create
changes in the html structure that are not intended.

Next step in master is to go through various link modifications in
mass mailing module to see how to handle them properly. Maybe.
This commit is contained in:
Thibault Delavallée
2018-02-12 13:07:41 +01:00
parent 94f2f6241b
commit b1b9896c46
5 changed files with 38 additions and 41 deletions
+1 -38
View File
@@ -313,46 +313,9 @@ class MailTemplate(models.Model):
# RENDERING
# ----------------------------------------
@api.model
def _replace_local_links(self, html):
""" Post-processing of html content to replace local links to absolute
links, using web.base.url as base url. """
if not html:
return html
# form a tree
root = lxml.html.fromstring(html)
if not len(root) and root.text is None and root.tail is None:
html = u'<div>%s</div>' % html
root = lxml.html.fromstring(html, encoding='unicode')
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
base = urls.url_parse(base_url)
def _process_link(url):
new_url = urls.url_parse(url)
if new_url.scheme and (new_url.netloc or new_url.scheme == 'mailto'):
return url
return new_url.replace(scheme=base.scheme, netloc=base.netloc).to_url()
# check all nodes, replace :
# - img src -> check URL
# - a href -> check URL
for node in root.iter():
if node.tag == 'a' and node.get('href'):
node.set('href', _process_link(node.get('href')))
elif node.tag == 'img' and not node.get('src', 'data').startswith(u'data'):
node.set('src', _process_link(node.get('src')))
html = lxml.html.tostring(root, pretty_print=False, method='html', encoding='unicode')
# this is ugly, but lxml/etree tostring want to put everything in a 'div' that breaks the editor -> remove that
if html.startswith(u'<div>') and html.endswith(u'</div>'):
html = html[5:-6]
return html
@api.model
def render_post_process(self, html):
html = self._replace_local_links(html)
html = self.env['mail.thread']._replace_local_links(html)
return html
@api.model
+34 -1
View File
@@ -23,9 +23,10 @@ from email.message import Message
from email.utils import formataddr
from lxml import etree
from werkzeug import url_encode
from werkzeug import urls
from odoo import _, api, exceptions, fields, models, tools
from odoo.tools import pycompat
from odoo.tools import pycompat, ustr
from odoo.tools.safe_eval import safe_eval
@@ -374,6 +375,38 @@ class MailThread(models.AbstractModel):
# Technical methods / wrappers / tools
# ------------------------------------------------------
def _replace_local_links(self, html, base_url=None):
""" Replace local links by absolute links. It is required in various
cases, for example when sending emails on chatter or sending mass
mailings. It replaces
* href of links (mailto will not match the regex)
* src of images (base64 hardcoded data will not match the regex)
* styling using url like background-image: url
It is done using regex because it is shorten than using an html parser
to create a potentially complex soupe and hope to have a result that
has not been harmed.
"""
if not html:
return html
html = ustr(html)
def _sub_relative2absolute(match):
# compute here to do it only if really necessary + cache will ensure it is done only once
# if not base_url
if not _sub_relative2absolute.base_url:
_sub_relative2absolute.base_url = self.env["ir.config_parameter"].sudo().get_param("web.base.url")
return match.group(1) + urls.url_join(_sub_relative2absolute.base_url, match.group(2))
_sub_relative2absolute.base_url = base_url
html = re.sub(r"""(<img(?=\s)[^>]*\ssrc=")(/[^/][^"]+)""", _sub_relative2absolute, html)
html = re.sub(r"""(<a(?=\s)[^>]*\shref=")(/[^/][^"]+)""", _sub_relative2absolute, html)
html = re.sub(r"""(<[^>]+\bstyle="[^"]+\burl\('?)(/[^/'][^'")]+)""", _sub_relative2absolute, html)
return html
@api.model
def _garbage_collect_attachments(self):
""" Garbage collect lost mail attachments. Those are attachments
+1
View File
@@ -185,6 +185,7 @@ class Partner(models.Model):
'subject': message.subject or (message.record_name and 'Re: %s' % message.record_name),
'body': base_template.render(template_ctx, engine='ir.qweb'),
}
fol_values['body'] = self.env['mail.thread']._replace_local_links(fol_values['body'])
# send email
new_emails, new_recipients_nbr = self._notify_send(fol_values['body'], fol_values['subject'], recipient_template_values['recipients'], **base_mail_values)
# update notifications
+1 -1
View File
@@ -742,7 +742,7 @@ class MassMailing(models.Model):
raise UserError(_('Please select recipients.'))
# Convert links in absolute URLs before the application of the shortener
mailing.body_html = self.env['mail.template']._replace_local_links(mailing.body_html)
mailing.body_html = self.env['mail.thread']._replace_local_links(mailing.body_html)
composer_values = {
'author_id': author_id,
+1 -1
View File
@@ -20,7 +20,7 @@ class TestMassMailing(models.TransientModel):
test_emails = tools.email_split(self.email_to)
for test_mail in test_emails:
# Convert links in absolute URLs before the application of the shortener
mailing.write({'body_html': self.env['mail.template']._replace_local_links(mailing.body_html)})
mailing.write({'body_html': self.env['mail.thread']._replace_local_links(mailing.body_html)})
mail_values = {
'email_from': mailing.email_from,
'reply_to': mailing.reply_to,