[MERGE][FIX] mail: fix notification emails not containing global links for URLs and images
See sub commits for more details. Was task 55210. #Closes #22940 .
This commit is contained in:
@@ -68,12 +68,12 @@
|
||||
<div>
|
||||
<div t-if="has_button_access" itemscope="itemscope" itemtype="http://schema.org/EmailMessage">
|
||||
<div itemprop="potentialAction" itemscope="itemscope" itemtype="http://schema.org/ViewAction">
|
||||
<link itemprop="target" href="${ctx['button_access']['url']}"/>
|
||||
<link itemprop="url" href="${ctx['button_access']['url']}"/>
|
||||
<meta itemprop="name" content="View ${ctx['model_name']}"/>
|
||||
<link itemprop="target" t-att-href="button_access['url']"/>
|
||||
<link itemprop="url" t-att-href="button_access['url']"/>
|
||||
<meta itemprop="name" t-att-content="button_access['title']"/>
|
||||
</div>
|
||||
</div>
|
||||
<div t-if="has_button_access or len(actions) > 0 or is_notification"
|
||||
<div t-if="has_button_access or len(actions) > 0 or not is_discussion"
|
||||
summary="o_mail_notification" style="padding: 0px; width:600px;">
|
||||
<table cellspacing="0" cellpadding="0" border="0" style="width: 600px; margin-top: 5px;">
|
||||
<tbody><tr>
|
||||
@@ -116,9 +116,9 @@
|
||||
<p style="color: #555555;">
|
||||
Sent by
|
||||
<a t-if="website_url" t-att-href="website_url" style="text-decoration:none; color: #875A7B;">
|
||||
<span t-esc="company_name"/>
|
||||
<span t-esc="company.name"/>
|
||||
</a>
|
||||
<span t-if="not website_url" t-esc="company_name"/>
|
||||
<span t-if="not website_url" t-esc="company.name"/>
|
||||
using
|
||||
<a target="_blank" href="https://www.odoo.com" style="text-decoration:none; color: #875A7B;">Odoo</a>.
|
||||
</p>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -73,17 +73,15 @@ class Partner(models.Model):
|
||||
if message.res_id and message.model in self.env:
|
||||
record = self.env[message.model].browse(message.res_id)
|
||||
|
||||
company = user.company_id;
|
||||
company = user.company_id
|
||||
if record and hasattr(record, 'company_id'):
|
||||
company = record.company_id;
|
||||
company_name = company.name;
|
||||
company = record.company_id
|
||||
|
||||
return {
|
||||
'message': message,
|
||||
'signature': signature,
|
||||
'website_url': website_url,
|
||||
'company': company,
|
||||
'company_name': company_name,
|
||||
'model_name': model_name,
|
||||
'record': record,
|
||||
'record_name': record_name,
|
||||
@@ -187,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
|
||||
|
||||
@@ -71,15 +71,7 @@ class MailMail(models.Model):
|
||||
new_href = href.replace(url, url + '/m/' + str(self.statistics_ids[0].id))
|
||||
body = body.replace(href, new_href)
|
||||
|
||||
# prepend <base> tag for images using absolute urls
|
||||
domain = self.env["ir.config_parameter"].sudo().get_param("web.base.url")
|
||||
base = "<base href='%s'>" % domain
|
||||
body = tools.append_content_to_html(base, body, plaintext=False, container_tag='div')
|
||||
# resolve relative image url to absolute for outlook.com
|
||||
def _sub_relative2absolute(match):
|
||||
return match.group(1) + werkzeug.urls.url_join(domain, match.group(2))
|
||||
body = re.sub('(<img(?=\s)[^>]*\ssrc=")(/[^/][^"]+)', _sub_relative2absolute, body)
|
||||
body = re.sub(r'(<[^>]+\bstyle="[^"]+\burl\(\'?)(/[^/\'][^\'")]+)', _sub_relative2absolute, body)
|
||||
body = self.env['mail.thread']._replace_local_links(body)
|
||||
|
||||
# generate tracking URL
|
||||
if self.statistics_ids:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -149,7 +149,7 @@ class TestAdvMailPerformance(TransactionCase):
|
||||
'default_res_model': 'mail.test.activity',
|
||||
})
|
||||
|
||||
with self.assertQueryCount(admin=47, emp=53): # com runbot 46 - 52 // test_mail only: 35 - 41
|
||||
with self.assertQueryCount(admin=47, emp=54): # com runbot 46 - 52 // test_mail only: 35 - 41
|
||||
model.create({
|
||||
'summary': 'Test Activity',
|
||||
'res_id': record.id,
|
||||
@@ -276,7 +276,7 @@ class TestHeavyMailPerformance(TransactionCase):
|
||||
customer_id = self.customer.id
|
||||
user_id = self.user_portal.id
|
||||
|
||||
with self.assertQueryCount(admin=325, emp=388): # test_mail only: 308 - 368
|
||||
with self.assertQueryCount(admin=325, emp=389): # test_mail only: 308 - 368
|
||||
rec = self.env['mail.test.full'].create({
|
||||
'name': 'Test',
|
||||
'umbrella_id': umbrella_id,
|
||||
|
||||
Reference in New Issue
Block a user