diff --git a/mail_embed_image/README.rst b/mail_embed_image/README.rst index 9c3b68291d..66e641bca4 100644 --- a/mail_embed_image/README.rst +++ b/mail_embed_image/README.rst @@ -17,13 +17,13 @@ Mail Embed Image :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fsocial-lightgray.png?logo=github - :target: https://github.com/OCA/social/tree/10.0/mail_embed_image + :target: https://github.com/OCA/social/tree/16.0/mail_embed_image :alt: OCA/social .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/social-10-0/social-10-0-mail_embed_image + :target: https://translation.odoo-community.org/projects/social-16-0/social-16-0-mail_embed_image :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png - :target: https://runboat.odoo-community.org/builds?repo=OCA/social&target_branch=10.0 + :target: https://runboat.odoo-community.org/builds?repo=OCA/social&target_branch=16.0 :alt: Try me on Runboat |badge1| |badge2| |badge3| |badge4| |badge5| @@ -42,7 +42,7 @@ 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 to smash it by providing a detailed and welcomed -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -59,6 +59,7 @@ Contributors * George Daramouskas * Giovanni Francesco Capalbo +* Italo LOPES Maintainers ~~~~~~~~~~~ @@ -73,6 +74,6 @@ 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. -This module is part of the `OCA/social `_ project on GitHub. +This module is part of the `OCA/social `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/mail_embed_image/__init__.py b/mail_embed_image/__init__.py index cff20de52b..c8c7f8b183 100644 --- a/mail_embed_image/__init__.py +++ b/mail_embed_image/__init__.py @@ -1,4 +1,3 @@ -# -*- 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 index 35d0d0a502..ca23b71fdd 100644 --- a/mail_embed_image/__manifest__.py +++ b/mail_embed_image/__manifest__.py @@ -1,16 +1,15 @@ -# -*- 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.1", + "version": "16.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", - 'website': 'https://github.com/OCA/social', + "website": "https://github.com/OCA/social", "depends": [ - 'web', + "web", ], "installable": True, "application": False, diff --git a/mail_embed_image/models/__init__.py b/mail_embed_image/models/__init__.py index 4278fd5d08..02d2fee24d 100644 --- a/mail_embed_image/models/__init__.py +++ b/mail_embed_image/models/__init__.py @@ -1,4 +1,3 @@ -# -*- 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 index e7074b5fda..a98cb7561b 100644 --- a/mail_embed_image/models/ir_mail_server.py +++ b/mail_embed_image/models/ir_mail_server.py @@ -1,71 +1,40 @@ -# -*- coding: utf-8 -*- -# Copyright 2019 Therp BV -# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). -import uuid import logging -from contextlib import contextmanager -from odoo import models, http -from odoo.addons.base.ir.ir_mail_server import encode_header_param -from werkzeug.test import EnvironBuilder -from werkzeug.wrappers import Request as WerkzeugRequest -from lxml.html.soupparser import fromstring -from lxml.etree import tostring -from base64 import encodestring -import threading -from odoo.http import root as root_wsgi +import uuid +from base64 import b64encode from email.mime.image import MIMEImage +import requests +from lxml.html import fromstring, tostring -logger = logging.getLogger(__name__) +from odoo import models +_logger = logging.getLogger(__name__) -class IrMailServer(models.Model): - _inherit = 'ir.mail_server' - @contextmanager - def _fetch_image(self, path): - public_user = self.env.ref('base.public_user') - session_store = root_wsgi.session_store - session = session_store.new() - session.update({ - 'db': threading.current_thread().dbname, - 'login': public_user.login, - 'uid': public_user.id, - 'context': self.env.context, - }) - werkzeug_env = EnvironBuilder(path).get_environ() - werkzeug_request = WerkzeugRequest(werkzeug_env) - werkzeug_request.session = session - # construct an odoo request with this werkzeug request. - request = http.HttpRequest(werkzeug_request) - with request: - request._env = self.env(user=public_user) - endpoint, arguments = http.routing_map( - self.env.registry._init_modules, - False, - self.env['ir.http']._get_converters() - ).bind_to_environ( - werkzeug_env).match(return_rule=False,) - yield endpoint, arguments +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=False, - attachments=None, - message_id=None, - references=None, - object_id=False, - subtype='plain', - headers=None, - body_alternative=None, - subtype_alternative='plain', + self, + email_from, + email_to, + subject, + body, + email_cc=None, + email_bcc=None, + reply_to=False, + attachments=None, + message_id=None, + references=None, + object_id=False, + subtype="plain", + headers=None, + body_alternative=None, + subtype_alternative="plain", ): + fileparts = None + if subtype == "html": + body, fileparts = self._build_email_replace_img_src(body) result = super(IrMailServer, self).build_email( email_from=email_from, email_to=email_to, @@ -83,48 +52,41 @@ def build_email( body_alternative=body_alternative, subtype_alternative=subtype_alternative, ) - return self._build_email_replace_img_src(result) + if fileparts: + for fpart in fileparts: + result.attach(fpart) + return result - def _build_email_replace_img_src(self, email): - """ Given a message, find it's img tags and if they - are URLs, replace them with cids. - """ - for part in email.walk(): - if part.get_content_type() == 'text/html': - body = part.get_payload(decode=True) - if not body or body == '\n': - continue - root = self._build_email_process_img_body( - fromstring(body), email) - # encodestring will put a newline every 74 char - part.set_payload(encodestring(tostring(root))) - return email + def _build_email_replace_img_src(self, html_body): + """Replace img src with base64 encoded image.""" + if not html_body: + return html_body - def _build_email_process_img_body(self, root, email): - base_url = self.env['ir.config_parameter'].get_param( - 'web.base.url') - for img in root.xpath( - ".//img[starts-with(@src, '%s/web/image')]" - "| .//img[starts-with(@src, '/web/image')]" % (base_url)): - image_path = img.get('src').replace(base_url, '') - with self._fetch_image(image_path) as (endpoint, arguments): - # now go ahead and call the endpoint and fetch the data - response = endpoint.method(**arguments) - if not response or response.status_code != 200: - logger.warning('Could not get %s', img.get('src')) - continue - cid = uuid.uuid4().hex - filename_rfc2047 = encode_header_param(cid) - filepart = MIMEImage(response.data) - # TODO check if filepart exists (do not attach twice) - filepart.set_param('name', filename_rfc2047) - filepart.add_header( - 'Content-Disposition', - 'inline', - cid=cid, - filename=filename_rfc2047, - ) - # attach the image into the email as attachment - email.attach(filepart) - img.set('src', 'cid:%s' % (str(cid))) - return root + root = fromstring(html_body) + images = root.xpath("//img") + fileparts = [] + for img in images: + src = img.get("src") + if src and not src.startswith("data:") and not src.startswith("base64:"): + try: + response = requests.get(src, timeout=10) + _logger.debug("Fetching image from %s", src) + if response.status_code == 200: + cid = uuid.uuid4().hex + # convert cid to rfc2047 encoding + filename_encoded = "=?utf-8?b?%s?=" % b64encode( + cid.encode("utf-8") + ).decode("utf-8") + image_content = response.content + filepart = MIMEImage(image_content) + filepart.add_header("Content-ID", f"<{cid}>") + filepart.add_header( + "Content-Disposition", + "inline", + filename=filename_encoded, + ) + img.set("src", f"cid:{cid}") + fileparts.append(filepart) + except Exception as e: + _logger.warning("Could not get %s: %s", img.get("src"), str(e)) + return tostring(root, encoding="unicode"), fileparts diff --git a/mail_embed_image/readme/CONTRIBUTORS.rst b/mail_embed_image/readme/CONTRIBUTORS.rst index 33ea1440fe..dc3a82820b 100644 --- a/mail_embed_image/readme/CONTRIBUTORS.rst +++ b/mail_embed_image/readme/CONTRIBUTORS.rst @@ -1,2 +1,3 @@ * George Daramouskas * Giovanni Francesco Capalbo +* Italo LOPES diff --git a/mail_embed_image/static/description/index.html b/mail_embed_image/static/description/index.html index 91e860c93d..f47cf51749 100644 --- a/mail_embed_image/static/description/index.html +++ b/mail_embed_image/static/description/index.html @@ -1,4 +1,3 @@ - @@ -369,7 +368,7 @@

Mail Embed Image

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! source digest: sha256:a365995cc3558fa6f105e5354c6a4317efd6453f04a5647e0acdff4c5adb3c12 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

Beta License: AGPL-3 OCA/social Translate me on Weblate Try me on Runboat

+

Beta License: AGPL-3 OCA/social Translate me on Weblate Try me on Runboat

This module finds images attached to outgoing emails and replaces their urls with cids. This will avoid rendering issues with some email clients.

Table of contents

@@ -389,7 +388,7 @@

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 to smash it by providing a detailed and welcomed -feedback.

+feedback.

Do not contact contributors directly about support or help with technical issues.

@@ -405,6 +404,7 @@

Contributors

@@ -414,7 +414,7 @@

Maintainers

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.

-

This module is part of the OCA/social project on GitHub.

+

This module is part of the OCA/social project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

diff --git a/mail_embed_image/tests/__init__.py b/mail_embed_image/tests/__init__.py index 5a437961a8..4ad3303d0c 100644 --- a/mail_embed_image/tests/__init__.py +++ b/mail_embed_image/tests/__init__.py @@ -1,4 +1,3 @@ -# -*- 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 index 18c5b9a7c0..a2076024a4 100644 --- a/mail_embed_image/tests/test_mail_embed_image.py +++ b/mail_embed_image/tests/test_mail_embed_image.py @@ -1,11 +1,11 @@ -# -*- coding: utf-8 -*- # Copyright 2019 Therp BV # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). from base64 import b64encode -from odoo.tests import common + from lxml import html from requests import get -from ..models.ir_mail_server import IrMailServer + +from odoo.tests import common class TestMailEmbedImage(common.TransactionCase): @@ -14,51 +14,55 @@ def test_mail_embed_image(self): and then look into the result, check there were attachments created and you find xpaths like //img[src] have a cid""" # DATA - 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 - body = html.tostring(html.fromstring(""" + 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, timeout=10).content + body = html.tostring( + html.fromstring( + """
this is an email - -
""" % ( - # won't be hit because we ignore embedded images - b64encode(image), - # dito, not uploaded content - image_url, - # this we may read with the share we create below - '/web/image/res.partner/%d/image' % ( - self.env.ref('base.public_partner').id - ), - ))) - email_from = 'test@example.com' - email_to = 'test@example.com' - subject = 'test mail' + """ + % ( + # won't be hit because we ignore embedded images + b64encode(image), + # dito, not uploaded content + image_url, + ) + ) + ) + email_from = "test@example.com" + email_to = "test@example.com" + subject = "test mail" # END DATA - # given mail tests patch this method and don't restore it, we need - # to call our function somewhat clumsily - res = IrMailServer.build_email.__func__( - self.env['ir.mail_server'], - email_from, email_to, subject, - body, subtype='html', subtype_alternative='plain', + res = self.env["ir.mail_server"].build_email( + email_from, + [email_to], + subject, + body, + subtype="html", + subtype_alternative="plain", ) images_in_mail = 0 for part in res.walk(): - if part.get_content_type() == 'text/html': + if part.get_content_type() == "text/html": # we do not search in text, just in case that texts exists in # the text elsewhere (not probable, but this is better) images_in_mail += len( - html.fromstring( - part.get_payload(decode=True) - ).xpath("//img[starts-with(@src, 'cid:')]") + html.fromstring(part.get_payload(decode=True)).xpath( + "//img[starts-with(@src, 'cid:')]" + ) ) # verify 1 replaced image self.assertEqual(images_in_mail, 1) # verify 1 attachment present - self.assertEqual([ - x.get_content_type() for x in res.walk() if x.get_content_type( - ).startswith('image/')], ['image/png']) + self.assertEqual( + [ + x.get_content_type() + for x in res.walk() + if x.get_content_type().startswith("image/") + ], + ["image/png"], + ) diff --git a/setup/mail_embed_image/odoo/addons/mail_embed_image b/setup/mail_embed_image/odoo/addons/mail_embed_image new file mode 120000 index 0000000000..d2efe33fa5 --- /dev/null +++ b/setup/mail_embed_image/odoo/addons/mail_embed_image @@ -0,0 +1 @@ +../../../../mail_embed_image \ No newline at end of file diff --git a/setup/mail_embed_image/setup.py b/setup/mail_embed_image/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/mail_embed_image/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)