diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py index 7aec4d246e3..b0560c0e989 100644 --- a/addons/mail/models/mail_thread.py +++ b/addons/mail/models/mail_thread.py @@ -1777,121 +1777,6 @@ class MailThread(models.AbstractModel): # MESSAGE POST MAIN # ------------------------------------------------------------ - def _message_post_process_attachments(self, attachments, attachment_ids, message_values): - """ Preprocess attachments for mail_thread.message_post() or mail_mail.create(). - Purpose is to - - * transfer attachments given by ``attachment_ids`` from the composer to - the record (if any); - * limit attachments manipulation when being a shared user; - * create attachments from ``attachments``. If those are linked to the - content (body) through CIDs body is updated accordingly; - - :param list(tuple(str,str), tuple(str,str, dict)) attachments : list of attachment - tuples in the form ``(name,content)`` or ``(name,content, info)`` where content - is NOT base64 encoded; - :param list attachment_ids: list of existing attachments to link to this message; - :param message_values: dictionary of values that will be used to create the - message. It is used to find back record- or content- context; - - :return dict: new values for message: 'attachment_ids' and optionally 'body' - """ - return_values = {} - body = message_values.get('body') - model = message_values['model'] - res_id = message_values['res_id'] - - m2m_attachment_ids = [] - if attachment_ids: - # taking advantage of cache looks better in this case, to check - filtered_attachment_ids = self.env['ir.attachment'].sudo().browse(attachment_ids).filtered( - lambda a: a.res_model == 'mail.compose.message' and a.create_uid.id == self._uid) - # update filtered (pending) attachments to link them to the proper record - if filtered_attachment_ids: - filtered_attachment_ids.write({'res_model': model, 'res_id': res_id}) - # prevent public and portal users from using attachments that are not theirs - if not self.env.user._is_internal(): - attachment_ids = filtered_attachment_ids.ids - - m2m_attachment_ids += [Command.link(id) for id in attachment_ids] - # Handle attachments parameter, that is a dictionary of attachments - - if attachments: # generate - cids_in_body = set() - names_in_body = set() - cid_list = [] - name_list = [] - - if body: - root = lxml.html.fromstring(tools.ustr(body)) - # first list all attachments that will be needed in body - for node in root.iter('img'): - if node.get('src', '').startswith('cid:'): - cids_in_body.add(node.get('src').split('cid:')[1]) - elif node.get('data-filename'): - names_in_body.add(node.get('data-filename')) - attachement_values_list = [] - - # generate values - for attachment in attachments: - cid = False - if len(attachment) == 2: - name, content = attachment - elif len(attachment) == 3: - name, content, info = attachment - cid = info and info.get('cid') - else: - continue - if isinstance(content, str): - content = content.encode('utf-8') - elif isinstance(content, EmailMessage): - content = content.as_bytes() - elif content is None: - continue - attachement_values = { - 'name': name, - 'datas': base64.b64encode(content), - 'type': 'binary', - 'description': name, - 'res_model': model, - 'res_id': res_id, - } - if body and (cid and cid in cids_in_body or name in names_in_body): - attachement_values['access_token'] = self.env['ir.attachment']._generate_access_token() - attachement_values_list.append(attachement_values) - # keep cid and name list synced with attachement_values_list length to match ids latter - cid_list.append(cid) - name_list.append(name) - new_attachments = self.env['ir.attachment'].create(attachement_values_list) - cid_mapping = {} - name_mapping = {} - for counter, new_attachment in enumerate(new_attachments): - cid = cid_list[counter] - if 'access_token' in attachement_values_list[counter]: - if cid: - cid_mapping[cid] = (new_attachment.id, attachement_values_list[counter]['access_token']) - name = name_list[counter] - name_mapping[name] = (new_attachment.id, attachement_values_list[counter]['access_token']) - m2m_attachment_ids.append((4, new_attachment.id)) - - # note: right know we are only taking attachments and ignoring attachment_ids. - if (cid_mapping or name_mapping) and body: - postprocessed = False - for node in root.iter('img'): - attachment_data = False - if node.get('src', '').startswith('cid:'): - cid = node.get('src').split('cid:')[1] - attachment_data = cid_mapping.get(cid) - if not attachment_data and node.get('data-filename'): - attachment_data = name_mapping.get(node.get('data-filename'), False) - if attachment_data: - node.set('src', '/web/image/%s?access_token=%s' % attachment_data) - postprocessed = True - if postprocessed: - return_values['body'] = lxml.html.tostring(root, pretty_print=False, encoding='UTF-8') - return_values['attachment_ids'] = m2m_attachment_ids - return return_values - @api.returns('mail.message', lambda value: value.id) def message_post(self, *, body='', subject=None, message_type='notification', @@ -2021,6 +1906,121 @@ class MailThread(models.AbstractModel): message and computed value are given, to try to lessen query count by using already-computed values instead of having to rebrowse things. """ + def _message_post_process_attachments(self, attachments, attachment_ids, message_values): + """ Preprocess attachments for mail_thread.message_post() or mail_mail.create(). + Purpose is to + + * transfer attachments given by ``attachment_ids`` from the composer to + the record (if any); + * limit attachments manipulation when being a shared user; + * create attachments from ``attachments``. If those are linked to the + content (body) through CIDs body is updated accordingly; + + :param list(tuple(str,str), tuple(str,str, dict)) attachments : list of attachment + tuples in the form ``(name,content)`` or ``(name,content, info)`` where content + is NOT base64 encoded; + :param list attachment_ids: list of existing attachments to link to this message; + :param message_values: dictionary of values that will be used to create the + message. It is used to find back record- or content- context; + + :return dict: new values for message: 'attachment_ids' and optionally 'body' + """ + return_values = {} + body = message_values.get('body') + model = message_values['model'] + res_id = message_values['res_id'] + + m2m_attachment_ids = [] + if attachment_ids: + # taking advantage of cache looks better in this case, to check + filtered_attachment_ids = self.env['ir.attachment'].sudo().browse(attachment_ids).filtered( + lambda a: a.res_model == 'mail.compose.message' and a.create_uid.id == self._uid) + # update filtered (pending) attachments to link them to the proper record + if filtered_attachment_ids: + filtered_attachment_ids.write({'res_model': model, 'res_id': res_id}) + # prevent public and portal users from using attachments that are not theirs + if not self.env.user._is_internal(): + attachment_ids = filtered_attachment_ids.ids + + m2m_attachment_ids += [Command.link(id) for id in attachment_ids] + # Handle attachments parameter, that is a dictionary of attachments + + if attachments: # generate + cids_in_body = set() + names_in_body = set() + cid_list = [] + name_list = [] + + if body: + root = lxml.html.fromstring(tools.ustr(body)) + # first list all attachments that will be needed in body + for node in root.iter('img'): + if node.get('src', '').startswith('cid:'): + cids_in_body.add(node.get('src').split('cid:')[1]) + elif node.get('data-filename'): + names_in_body.add(node.get('data-filename')) + attachement_values_list = [] + + # generate values + for attachment in attachments: + cid = False + if len(attachment) == 2: + name, content = attachment + elif len(attachment) == 3: + name, content, info = attachment + cid = info and info.get('cid') + else: + continue + if isinstance(content, str): + content = content.encode('utf-8') + elif isinstance(content, EmailMessage): + content = content.as_bytes() + elif content is None: + continue + attachement_values = { + 'name': name, + 'datas': base64.b64encode(content), + 'type': 'binary', + 'description': name, + 'res_model': model, + 'res_id': res_id, + } + if body and (cid and cid in cids_in_body or name in names_in_body): + attachement_values['access_token'] = self.env['ir.attachment']._generate_access_token() + attachement_values_list.append(attachement_values) + # keep cid and name list synced with attachement_values_list length to match ids latter + cid_list.append(cid) + name_list.append(name) + new_attachments = self.env['ir.attachment'].create(attachement_values_list) + cid_mapping = {} + name_mapping = {} + for counter, new_attachment in enumerate(new_attachments): + cid = cid_list[counter] + if 'access_token' in attachement_values_list[counter]: + if cid: + cid_mapping[cid] = (new_attachment.id, attachement_values_list[counter]['access_token']) + name = name_list[counter] + name_mapping[name] = (new_attachment.id, attachement_values_list[counter]['access_token']) + m2m_attachment_ids.append((4, new_attachment.id)) + + # note: right know we are only taking attachments and ignoring attachment_ids. + if (cid_mapping or name_mapping) and body: + postprocessed = False + for node in root.iter('img'): + attachment_data = False + if node.get('src', '').startswith('cid:'): + cid = node.get('src').split('cid:')[1] + attachment_data = cid_mapping.get(cid) + if not attachment_data and node.get('data-filename'): + attachment_data = name_mapping.get(node.get('data-filename'), False) + if attachment_data: + node.set('src', '/web/image/%s?access_token=%s' % attachment_data) + postprocessed = True + if postprocessed: + return_values['body'] = lxml.html.tostring(root, pretty_print=False, encoding='UTF-8') + return_values['attachment_ids'] = m2m_attachment_ids + return return_values + # ------------------------------------------------------------ # MESSAGE POST API / WRAPPERS # ------------------------------------------------------------