diff --git a/mail_embed_image/README.rst b/mail_embed_image/README.rst new file mode 100644 index 00000000..4c586898 --- /dev/null +++ b/mail_embed_image/README.rst @@ -0,0 +1,80 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: https://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +================ +mail_embed_image +================ + +This module was written to extend the functionality of ... to support ... +and allow you to ... + +Installation +============ + +To install this module, you need to: + +Follow the usual procedure for your version of Odoo. + +Configuration +============= + +To configure this module, you need to: + +There are no configuration parameters for now + + +Usage +===== + +When this module is installed all the emails that will be sent will be examined +and if they contain images with their src attribute pointing to a url, then +that will change to Content Id cid. + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/205/10.0 + + +Known issues / Roadmap +====================== + +* None so far. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smashing it by providing a detailed and welcomed feedback. + +Credits +======= + +Images +------ + +* Odoo Community Association: `Icon `_. + +Contributors +------------ + +* George Daramouskas + +Do not contact contributors directly about help with questions or problems concerning this addon, but use the `community mailing list `_ or the `appropriate specialized mailinglist `_ for help, and the bug tracker linked in `Bug Tracker`_ above for technical issues. + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit https://odoo-community.org. diff --git a/mail_embed_image/__init__.py b/mail_embed_image/__init__.py new file mode 100644 index 00000000..cff20de5 --- /dev/null +++ b/mail_embed_image/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Copyright 2019 Therp BV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from . import models diff --git a/mail_embed_image/__manifest__.py b/mail_embed_image/__manifest__.py new file mode 100644 index 00000000..f92233a2 --- /dev/null +++ b/mail_embed_image/__manifest__.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Copyright 2019 Therp BV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +{ + "name": "Mail Embed Image", + "version": "10.0.1.0.0", + "author": "Therp BV,Odoo Community Association (OCA)", + "license": "AGPL-3", + "category": "Social", + "summary": "Replace img.src's which start with http with inline cids", + "depends": [ + 'mail', + ], + "installable": True, + "application": False, +} diff --git a/mail_embed_image/models/__init__.py b/mail_embed_image/models/__init__.py new file mode 100644 index 00000000..4278fd5d --- /dev/null +++ b/mail_embed_image/models/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Copyright 2019 Therp BV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from . import ir_mail_server diff --git a/mail_embed_image/models/ir_mail_server.py b/mail_embed_image/models/ir_mail_server.py new file mode 100644 index 00000000..5cf98a3f --- /dev/null +++ b/mail_embed_image/models/ir_mail_server.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- +# Copyright 2019 Therp BV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +import uuid +from werkzeug.routing import Rule, Map +from odoo import models +from odoo.tools.image import image_resize_image +from odoo.addons.web.controllers.main import Binary +from odoo.modules import get_resource_path +from odoo.addons.base.ir.ir_mail_server import encode_header_param +from lxml.html.soupparser import fromstring +from lxml.etree import tostring +from base64 import b64encode, b64decode +from email.mime.image import MIMEImage +from email import Encoders + + +class IrMailServer(models.Model): + _inherit = 'ir.mail_server' + + def build_email( + self, + email_from, + email_to, + subject, + body, + email_cc=None, + email_bcc=None, + reply_to=None, + attachments=None, + message_id=None, + references=None, + object_id=None, + subtype='plain', + headers=None, + body_alternative=None, + subtype_alternative='plain'): + result = super(IrMailServer, self).build_email( + email_from=email_from, + email_to=email_to, + subject=subject, + body=body, + email_cc=email_cc, + email_bcc=email_bcc, + reply_to=reply_to, + attachments=attachments, + message_id=message_id, + references=references, + object_id=object_id, + subtype=subtype, + headers=headers, + body_alternative=body_alternative, + subtype_alternative=subtype_alternative, + ) + return self._build_email_replace_img_src(result, result) + + def _build_email_replace_img_src(self, email, email_part): + """ Given a message, find it's img tags and if they + are URLs, replace them with cids. + + :param email: The root email.message.Message, used for reference + :param email_part: The current part of email being examined + """ + if email_part.is_multipart(): + for part in email_part.get_payload(): + self._build_email_replace_img_src(email, part) + else: + if email_part.get_content_subtype() == 'html': + body = email_part.get_payload(decode=True) + root = fromstring(body) + for img in root.xpath( + "//img[starts-with(@src, '/web/image')]"): + base_url = self.env['ir.config_parameter'].get_param( + 'web.base.url') + response = self._get_image(base_url, img.get('src')) + cid = uuid.uuid4().hex + filename_rfc2047 = encode_header_param(cid) + part = MIMEImage(response) + part.set_param('name', filename_rfc2047) + part.add_header( + 'Content-Disposition', + 'inline', + cid=cid, + filename=filename_rfc2047, + ) + part.set_payload(response) + Encoders.encode_base64(part) + # attach the image into the email as attachment + email.attach(part) + img.set('src', 'cid:%s' % (cid)) + email_part.set_payload(b64encode(tostring(root))) + return email + + def _get_image(self, base_url, src): + """ This function emulates the operations of + odoo.addons.web.controllers.main.Binary.content_image() because + we cannot call this from here + Given an absolute url, return the raw image data. + """ + routes = Binary.content_image.routing['routes'] + map_rules = Map([Rule(x) for x in routes]) + urls = map_rules.bind(base_url) + endpoint, kwargs = urls.match(src) + status, headers, content = self.env['ir.http'].binary_content( + env=self.env, **kwargs) + height = int(kwargs.get('height', 0)) + width = int(kwargs.get('width', 0)) + if content and (height or width): + if height > 500: + height = 500 + if width > 500: + width = 500 + content = image_resize_image( + content, + (width or None, height or None), + filetype='PNG', + ) + if content: + content = b64decode(content) + else: + path_placeholder = '/web/static/src/img/placeholder.png' + with open(get_resource_path(path_placeholder)) as f: + content = f.read() + else: + content = b64decode(content) + return content diff --git a/mail_embed_image/static/description/icon.png b/mail_embed_image/static/description/icon.png new file mode 100644 index 00000000..3a0328b5 Binary files /dev/null and b/mail_embed_image/static/description/icon.png differ diff --git a/mail_embed_image/tests/__init__.py b/mail_embed_image/tests/__init__.py new file mode 100644 index 00000000..5a437961 --- /dev/null +++ b/mail_embed_image/tests/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Copyright 2019 Therp BV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from . import test_mail_embed_image diff --git a/mail_embed_image/tests/test_mail_embed_image.py b/mail_embed_image/tests/test_mail_embed_image.py new file mode 100644 index 00000000..81f3edac --- /dev/null +++ b/mail_embed_image/tests/test_mail_embed_image.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +# Copyright 2019 Therp BV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from odoo.tests.common import TransactionCase +from mock import patch +from requests import get +from base64 import b64encode +from lxml.html.soupparser import fromstring +from odoo.addons.base.ir.ir_mail_server import encode_header_param + + +class TestMailEmbedImage(TransactionCase): + + post_install = True + at_install = False + + @patch( + 'odoo.fields.html_sanitize', + side_effect=lambda *args, **kwargs: args[0], + ) + def test_mail_embed_image(self, html_sanitize): + """ The following are tested here: + 1) Create an email with no image, send it. + 2) Create an email with three images, one base64 content, one http + full url and one of the format `/web/image/.*` + + We assert that nothing has changed on 1) and only the `/web/image/.*` + has changed in 2) + """ + model_mail_mail = self.env['mail.mail'] + base_url = self.env['ir.config_parameter'].get_param('web.base.url') + image_url = base_url + '/mail_embed_image/static/description/icon.png' + image = get(image_url).content + body1 = '
this is an email
' + email1 = model_mail_mail.create({ + 'body_html': body1, + 'email_from': 'test@example.com', + 'email_to': 'test@example.com', + }) + email1.send() + self.assertEquals(email1.body_html, body1) + body2 = """ +
+ this is an email + + + +
""" % ( + b64encode(image), + image_url, + '/web/image/res.partner/1/image', + ) + email2 = model_mail_mail.create({ + 'body_html': body2, + 'email_from': 'test@example.com', + 'email_to': 'test@example.com', + }) + email2.send() + # TODO the below will not work, assert build_email called and return + # value (remove the tests above as well, they are foolish) + body2final = fromstring(email2.body_html) + self.assertEquals(len(body2final.xpath('//img')), 3) + srcs = [x.get('src') for x in body2final.xpath('//img')] + self.assertIn('base64:', srcs[0]) + self.assertIn('http:', srcs[1]) + self.assertIn('cid:', srcs[2]) + cid = srcs[2][3:] + self.assertEquals(email2.attachment_ids.name, encode_header_param(cid))