Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 194 additions & 0 deletions mail_hide_inline_attachments/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
==============================
Mail - Hide Inline Attachments
==============================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:96103eaaa45f84a91e31f2a1d29942d30ccc480f01199926c1df3171c12daf1d
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fsocial-lightgray.png?logo=github
:target: https://github.com/OCA/social/tree/16.0/mail_hide_inline_attachments
:alt: OCA/social
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/social-16-0/social-16-0-mail_hide_inline_attachments
: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=16.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

This module hides inline images from emails in the attachment list of records that
inherit from ``mail.thread`` and also from the ``attachment_ids`` field of the
``mail.message`` model.

When an email contains embedded (inline) images, they are converted into attachments by
Odoo and become visible both in the email body and in the attachment list of the record
and message. This module filters these inline images so they appear only in the email
body.

Features
--------

- Automatically detects attachments that are referenced inline in message bodies
through:
- CID (Content-ID) references in ``<img>`` tags
- ``data-filename`` attributes in ``<img>`` tags
- ``/web/image/{id}`` URLs in the ``src`` attribute of ``<img>`` tags
- Filters these attachments from the record's attachment list (via ``mail.thread``)
- Filters these attachments from the ``attachment_ids`` field of ``mail.message``
- Images remain visible in the email body
- Works with all models that inherit from ``mail.thread``

How it works
------------

The module intercepts attachment processing at two points:

1. **During message creation** (``mail.thread._message_post_process_attachments``):
Inline attachments are unlinked from the record (``res_model`` and ``res_id`` are
cleared), but remain linked to the message.
2. **During message formatting** (``mail.message._message_format``): Inline attachments
are filtered from the attachment list returned to the web client.


**Table of contents**

.. contents::
:local:

Configuration
=============

Configuration
=============

This module does not require additional configuration. It works automatically after
installation.

Inline attachment detection
----------------------------

The module detects inline attachments through three methods:

1. **CID (Content-ID)**: When an ``<img>`` tag has ``src="cid:xxx"``, the attachment with
that CID is considered inline
2. **data-filename**: When an ``<img>`` tag has the ``data-filename="filename"`` attribute,
the attachment with that name is considered inline
3. **URL /web/image/{id}**: When an ``<img>`` tag has ``src="/web/image/123"``, the
attachment with ID 123 is considered inline

All these methods are automatically checked during message processing.


Usage
=====

Usage
=====

After installation, the module works automatically. No additional configuration is
required.

Behavior
--------

When an email is received or sent with inline images:

1. Images are processed normally and appear in the email body
2. Corresponding attachments are created in the system
3. Inline attachments are automatically filtered and do not appear:
- In the record's attachment list (chatter)
- In the ``attachment_ids`` field of ``mail.message``
4. Only attachments that are not inline images remain visible in the attachment list

Example
-------

If an email contains:

- 1 inline image (company logo)
- 2 normal attachments (PDF and DOCX)

The result will be:

- The inline image appears only in the email body
- The 2 normal attachments appear in the record's and message's attachment list


Known issues / Roadmap
======================

Roadmap
=======

Planned future improvements:

- Support for other types of inline content besides images
- Configuration option to disable inline attachment filtering
- Improvements in inline attachment detection for edge cases
- Support for inline attachments in threaded replies


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 to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/social/issues/new?body=module:%20mail_hide_inline_attachments%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

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

Credits
=======

Authors
~~~~~~~

* KMEE

Contributors
~~~~~~~~~~~~

Contributors
============

- `KMEE <https://www.kmee.com.br>`_:
- Luis Felipe Miléo <[email protected]>


Maintainers
~~~~~~~~~~~

This module is maintained by the OCA.

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

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.

.. |maintainer-mileo| image:: https://github.com/mileo.png?size=40px
:target: https://github.com/mileo
:alt: mileo

Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:

|maintainer-mileo|

This module is part of the `OCA/social <https://github.com/OCA/social/tree/16.0/mail_hide_inline_attachments>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
4 changes: 4 additions & 0 deletions mail_hide_inline_attachments/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright (C) 2024 - KMEE
# License LGPL-3 - See http://www.gnu.org/licenses/lgpl-3.0.html

from . import models
20 changes: 20 additions & 0 deletions mail_hide_inline_attachments/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright (C) 2024 - KMEE
# License LGPL-3 - See http://www.gnu.org/licenses/lgpl-3.0.html

{
"name": "Mail - Hide Inline Attachments",
"summary": "Hide inline email images from attachment list",
"version": "16.0.1.0.0",
"category": "Discuss",
"author": "KMEE, Odoo Community Association (OCA)",
"maintainers": ["mileo"],
"website": "https://github.com/OCA/social",
"license": "LGPL-3",
"depends": [
"mail",
],
"data": [],
"installable": True,
"auto_install": False,
"application": False,
}
5 changes: 5 additions & 0 deletions mail_hide_inline_attachments/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Copyright (C) 2024 - KMEE
# License LGPL-3 - See http://www.gnu.org/licenses/lgpl-3.0.html

from . import mail_thread
from . import mail_message
90 changes: 90 additions & 0 deletions mail_hide_inline_attachments/models/mail_message.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Copyright (C) 2024 - KMEE
# License LGPL-3 - See http://www.gnu.org/licenses/lgpl-3.0.html

import logging
import re

import lxml.html

from odoo import models
from odoo.tools import ustr

_logger = logging.getLogger(__name__)


class MailMessage(models.Model):
_inherit = "mail.message"

def _get_inline_attachment_ids(self):
"""Identifica IDs de anexos que são referenciados inline no body.

Retorna um set com os IDs dos anexos que aparecem como imagens
inline no body da mensagem (referências /web/image/{id}).
"""
self.ensure_one()
inline_attachment_ids = set()

if not self.body:
return inline_attachment_ids

try:
root = lxml.html.fromstring(ustr(self.body))
for node in root.iter("img"):
src = node.get("src", "")
# Pattern matches:
# - /web/image/{id}
# - /web/image/{id}?access_token=...
# - /web/image/{id}/...
matches = re.findall(r"/web/image/(\d+)", src)
for mid in matches:
inline_attachment_ids.add(int(mid))
except Exception as e:
_logger.warning(
"Erro ao processar body para detectar anexos inline "
"na mensagem %d: %s",
self.id,
e,
)

return inline_attachment_ids

def _message_format(self, fnames, format_reply=True, legacy=False):
"""Override para filtrar anexos inline do campo attachment_ids.

Anexos que são referenciados inline no body (imagens) não devem
aparecer na lista de anexos do mail.message.
"""
vals_list = super()._message_format(
fnames, format_reply=format_reply, legacy=legacy
)

# Processa em batch para melhor performance
message_ids = [vals["id"] for vals in vals_list]
messages = self.browse(message_ids)

for vals in vals_list:
message_id = vals["id"]
message = messages.filtered(lambda m, mid=message_id: m.id == mid)
if not message:
continue

inline_attachment_ids = message._get_inline_attachment_ids()

if inline_attachment_ids and vals.get("attachment_ids"):
# Filtra anexos inline da lista de anexos formatados
filtered_attachments = [
att
for att in vals["attachment_ids"]
if att.get("id") not in inline_attachment_ids
]
vals["attachment_ids"] = filtered_attachments
_logger.debug(
"Filtrados %d anexos inline da mensagem %d "
"(IDs: %s). Restaram %d anexos.",
len(inline_attachment_ids),
message.id,
sorted(inline_attachment_ids),
len(filtered_attachments),
)

return vals_list
Loading