committed by
GitHub
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 302 additions and 0 deletions
-
80mail_embed_image/README.rst
-
4mail_embed_image/__init__.py
-
16mail_embed_image/__manifest__.py
-
4mail_embed_image/models/__init__.py
-
126mail_embed_image/models/ir_mail_server.py
-
BINmail_embed_image/static/description/icon.png
-
4mail_embed_image/tests/__init__.py
-
68mail_embed_image/tests/test_mail_embed_image.py
@ -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 |
||||
|
<https://github.com/OCA/social/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 <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_. |
||||
|
|
||||
|
Contributors |
||||
|
------------ |
||||
|
|
||||
|
* George Daramouskas <gdaramouskas@therp.nl> |
||||
|
|
||||
|
Do not contact contributors directly about help with questions or problems concerning this addon, but use the `community mailing list <mailto:community@mail.odoo.com>`_ or the `appropriate specialized mailinglist <https://odoo-community.org/groups>`_ 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. |
@ -0,0 +1,4 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2019 Therp BV <https://therp.nl> |
||||
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). |
||||
|
from . import models |
@ -0,0 +1,16 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2019 Therp BV <https://therp.nl> |
||||
|
# 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, |
||||
|
} |
@ -0,0 +1,4 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2019 Therp BV <https://therp.nl> |
||||
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). |
||||
|
from . import ir_mail_server |
@ -0,0 +1,126 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2019 Therp BV <https://therp.nl> |
||||
|
# 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 |
After Width: 128 | Height: 128 | Size: 9.2 KiB |
@ -0,0 +1,4 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2019 Therp BV <https://therp.nl> |
||||
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). |
||||
|
from . import test_mail_embed_image |
@ -0,0 +1,68 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2019 Therp BV <https://therp.nl> |
||||
|
# 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 = '<div>this is an email</div>' |
||||
|
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 = """ |
||||
|
<div> |
||||
|
this is an email |
||||
|
<img src="base64: %s"></img> |
||||
|
<img src="%s"></img> |
||||
|
<img src="%s"></img> |
||||
|
</div>""" % ( |
||||
|
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)) |
Write
Preview
Loading…
Cancel
Save
Reference in new issue