diff --git a/addons/im_livechat/data/mail_data.xml b/addons/im_livechat/data/mail_data.xml
index 9c8468cac6f..29d1c5e2032 100644
--- a/addons/im_livechat/data/mail_data.xml
+++ b/addons/im_livechat/data/mail_data.xml
@@ -32,7 +32,7 @@
Hello, Here's a copy of your conversation with
- , on the
+ , on the
diff --git a/addons/im_livechat/models/__init__.py b/addons/im_livechat/models/__init__.py
index 65478d25ced..6abd12e36f4 100644
--- a/addons/im_livechat/models/__init__.py
+++ b/addons/im_livechat/models/__init__.py
@@ -5,6 +5,7 @@ from . import res_partner
from . import im_livechat_channel
from . import mail_channel
from . import mail_channel_partner
+from . import mail_message
from . import res_users_settings
from . import rating
from . import digest
diff --git a/addons/im_livechat/models/mail_channel.py b/addons/im_livechat/models/mail_channel.py
index 78584bbfccf..b6f6d1f7b64 100644
--- a/addons/im_livechat/models/mail_channel.py
+++ b/addons/im_livechat/models/mail_channel.py
@@ -35,10 +35,7 @@ class MailChannel(models.Model):
clicking on livechat button). So when the anonymous person is sending its FIRST message, the channel header
should be added to the notification, since the user cannot be listining to the channel.
"""
- livechat_channels = self.filtered(lambda x: x.channel_type == 'livechat')
- other_channels = self.filtered(lambda x: x.channel_type != 'livechat')
- notifications = super(MailChannel, livechat_channels)._channel_message_notifications(message.with_context(im_livechat_use_username=True)) + \
- super(MailChannel, other_channels)._channel_message_notifications(message, message_format)
+ notifications = super()._channel_message_notifications(message=message, message_format=message_format)
for channel in self:
# add uuid for private livechat channels to allow anonymous to listen
if channel.channel_type == 'livechat' and channel.public == 'private':
@@ -50,11 +47,6 @@ class MailChannel(models.Model):
notifications = self._channel_channel_notifications(unpinned_channel_partner.mapped('partner_id').ids) + notifications
return notifications
- def _channel_fetch_message(self, last_id=False, limit=20):
- """ Override to add the context of the livechat username."""
- channel = self.with_context(im_livechat_use_username=True) if self.channel_type == 'livechat' else self
- return super(MailChannel, channel)._channel_fetch_message(last_id=last_id, limit=limit)
-
def channel_info(self):
""" Extends the channel header by adding the livechat operator and the 'anonymous' profile
:rtype : list(dict)
@@ -66,12 +58,33 @@ class MailChannel(models.Model):
if channel.channel_type == 'livechat':
# add the operator id
if channel.livechat_operator_id:
- res = channel.livechat_operator_id.with_context(im_livechat_use_username=True).name_get()[0]
- channel_infos_dict[channel.id]['operator_pid'] = (res[0], res[1].replace(',', ''))
+ display_name = channel.livechat_operator_id.user_livechat_username or channel.livechat_operator_id.display_name
+ channel_infos_dict[channel.id]['operator_pid'] = (channel.livechat_operator_id.id, display_name.replace(',', ''))
# add the anonymous or partner name
channel_infos_dict[channel.id]['livechat_visitor'] = channel._channel_get_livechat_visitor_info()
return list(channel_infos_dict.values())
+ def _channel_info_format_member(self, partner, partner_info):
+ """Override to remove sensitive information in livechat."""
+ if self.channel_type == 'livechat':
+ return {
+ 'active': partner.active,
+ 'id': partner.id,
+ 'name': partner.user_livechat_username or partner.name, # for API compatibility in stable
+ 'email': False, # for API compatibility in stable
+ 'im_status': False, # for API compatibility in stable
+ 'livechat_username': partner.user_livechat_username,
+ }
+ return super()._channel_info_format_member(partner=partner, partner_info=partner_info)
+
+ def _notify_typing_partner_data(self):
+ """Override to remove name and return livechat username if applicable."""
+ data = super()._notify_typing_partner_data()
+ if self.channel_type == 'livechat' and self.env.user.partner_id.user_livechat_username:
+ data['partner_name'] = self.env.user.partner_id.user_livechat_username # for API compatibility in stable
+ data['livechat_username'] = self.env.user.partner_id.user_livechat_username
+ return data
+
def _channel_get_livechat_visitor_info(self):
self.ensure_one()
# remove active test to ensure public partner is taken into account
@@ -171,7 +184,7 @@ class MailChannel(models.Model):
mail_body = template._render(render_context, engine='ir.qweb', minimal_qcontext=True)
mail_body = self.env['mail.render.mixin']._replace_local_links(mail_body)
mail = self.env['mail.mail'].sudo().create({
- 'subject': _('Conversation with %s', self.livechat_operator_id.name),
+ 'subject': _('Conversation with %s', self.livechat_operator_id.user_livechat_username or self.livechat_operator_id.name),
'email_from': company.catchall_formatted or company.email_formatted,
'author_id': self.env.user.partner_id.id,
'email_to': email,
diff --git a/addons/im_livechat/models/mail_message.py b/addons/im_livechat/models/mail_message.py
new file mode 100644
index 00000000000..3e1d1f27993
--- /dev/null
+++ b/addons/im_livechat/models/mail_message.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import models
+
+
+class MailMessage(models.Model):
+ _inherit = 'mail.message'
+
+ def _message_format(self, fnames, format_reply=True):
+ """Override to remove email_from and to return the livechat username if applicable.
+ A third param is added to the author_id tuple in this case to be able to differentiate it
+ from the normal name in client code."""
+ vals_list = super()._message_format(fnames=fnames, format_reply=format_reply)
+ for vals in vals_list:
+ message_sudo = self.browse(vals['id']).sudo().with_prefetch(self.ids)
+ if message_sudo.model == 'mail.channel' and self.env['mail.channel'].browse(message_sudo.res_id).channel_type == 'livechat':
+ vals.pop('email_from')
+ if message_sudo.author_id.user_livechat_username:
+ vals['author_id'] = (message_sudo.author_id.id, message_sudo.author_id.user_livechat_username, message_sudo.author_id.user_livechat_username)
+ return vals_list
diff --git a/addons/im_livechat/models/res_partner.py b/addons/im_livechat/models/res_partner.py
index df2e7a0a493..b3b85cf898f 100644
--- a/addons/im_livechat/models/res_partner.py
+++ b/addons/im_livechat/models/res_partner.py
@@ -1,38 +1,14 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from odoo import models
+from odoo import api, models, fields
class Partners(models.Model):
- """ Update of res.partners class
- - override name_get to take into account the livechat username
- """
+ """Update of res.partner class to take into account the livechat username."""
_inherit = 'res.partner'
- def name_get(self):
- if self.env.context.get('im_livechat_use_username'):
- # process the ones with livechat username
- users_with_livechatname = self.env['res.users'].search([('partner_id', 'in', self.ids), ('livechat_username', '!=', False)])
- map_with_livechatname = {}
- for user in users_with_livechatname:
- map_with_livechatname[user.partner_id.id] = user.livechat_username
-
- # process the ones without livecaht username
- partner_without_livechatname = self - users_with_livechatname.mapped('partner_id')
- no_livechatname_name_get = super(Partners, partner_without_livechatname).name_get()
- map_without_livechatname = dict(no_livechatname_name_get)
-
- # restore order
- result = []
- for partner in self:
- name = map_with_livechatname.get(partner.id)
- if not name:
- name = map_without_livechatname.get(partner.id)
- result.append((partner.id, name))
- else:
- result = super(Partners, self).name_get()
- return result
+ user_livechat_username = fields.Char(compute='_compute_user_livechat_username')
def _get_channels_as_member(self):
channels = super()._get_channels_as_member()
@@ -44,3 +20,8 @@ class Partners(models.Model):
])),
])
return channels
+
+ @api.depends('user_ids.livechat_username')
+ def _compute_user_livechat_username(self):
+ for partner in self:
+ partner.user_livechat_username = next(iter(partner.user_ids.mapped('livechat_username')), False)
diff --git a/addons/im_livechat/static/src/models/message/message.js b/addons/im_livechat/static/src/models/message/message.js
new file mode 100644
index 00000000000..071f2db36ef
--- /dev/null
+++ b/addons/im_livechat/static/src/models/message/message.js
@@ -0,0 +1,26 @@
+/** @odoo-module **/
+
+import { patchModelMethods } from '@mail/model/model_core';
+import { insertAndReplace } from '@mail/model/model_field_command';
+// ensure the model definition is loaded before the patch
+import '@mail/models/message/message';
+
+patchModelMethods('Message', {
+ /**
+ * @override
+ */
+ convertData(data) {
+ const data2 = this._super(data);
+ if ('author_id' in data) {
+ if (data.author_id[2]) {
+ // flux specific for livechat, a 3rd param is livechat_username
+ // and means 2nd param (display_name) should be ignored
+ data2.author = insertAndReplace({
+ id: data.author_id[0],
+ livechat_username: data.author_id[2],
+ });
+ }
+ }
+ return data2;
+ },
+});
diff --git a/addons/im_livechat/static/src/models/messaging_notification_handler/messaging_notification_handler.js b/addons/im_livechat/static/src/models/messaging_notification_handler/messaging_notification_handler.js
index bcf44370f1f..31bdda1c8d9 100644
--- a/addons/im_livechat/static/src/models/messaging_notification_handler/messaging_notification_handler.js
+++ b/addons/im_livechat/static/src/models/messaging_notification_handler/messaging_notification_handler.js
@@ -21,7 +21,7 @@ patchRecordMethods('MessagingNotificationHandler', {
/**
* @override
*/
- _handleNotificationChannelPartnerTypingStatus({ channel_id, is_typing, partner_id, partner_name }) {
+ _handleNotificationChannelPartnerTypingStatus({ channel_id, is_typing, livechat_username, partner_id, partner_name }) {
const channel = this.messaging.models['Thread'].findFromIdentifyingData({
id: channel_id,
model: 'mail.channel',
@@ -40,11 +40,20 @@ patchRecordMethods('MessagingNotificationHandler', {
partnerId = partner_id;
partnerName = partner_name;
}
- this._super({
+ const data = {
channel_id,
is_typing,
partner_id: partnerId,
partner_name: partnerName,
- });
+ };
+ if (livechat_username) {
+ // flux specific, livechat_username is returned instead of name for livechat channels
+ delete data.partner_name; // value still present for API compatibility in stable
+ this.models['Partner'].insert({
+ id: partnerId,
+ livechat_username: livechat_username,
+ });
+ }
+ this._super(data);
},
});
diff --git a/addons/im_livechat/static/src/models/partner/partner.js b/addons/im_livechat/static/src/models/partner/partner.js
index 664ba0ed06c..db724b865c4 100644
--- a/addons/im_livechat/static/src/models/partner/partner.js
+++ b/addons/im_livechat/static/src/models/partner/partner.js
@@ -1,6 +1,8 @@
/** @odoo-module **/
-import { addModelMethods } from '@mail/model/model_core';
+import { addFields, addModelMethods, patchModelMethods } from '@mail/model/model_core';
+import { attr } from '@mail/model/model_field';
+
// ensure that the model definition is loaded before the patch
import '@mail/models/partner/partner';
@@ -14,3 +16,27 @@ addModelMethods('Partner', {
},
});
+patchModelMethods('Partner', {
+ convertData(data) {
+ const data2 = this._super(data);
+ if ('livechat_username' in data) {
+ // flux specific, if livechat username is present it means `name`,
+ // `email` and `im_status` contain `false` even though their value
+ // might actually exist. Remove them from data2 to avoid overwriting
+ // existing value (that could be known through other means).
+ delete data2.name;
+ delete data2.email;
+ delete data2.im_status;
+ data2.livechat_username = data.livechat_username;
+ }
+ return data2;
+ },
+});
+
+addFields('Partner', {
+ /**
+ * States the specific name of this partner in the context of livechat.
+ * Either a string or undefined.
+ */
+ livechat_username: attr(),
+});
diff --git a/addons/im_livechat/static/src/models/thread/thread.js b/addons/im_livechat/static/src/models/thread/thread.js
index 8c20a97a34c..78f6e5e9bed 100644
--- a/addons/im_livechat/static/src/models/thread/thread.js
+++ b/addons/im_livechat/static/src/models/thread/thread.js
@@ -77,6 +77,15 @@ patchModelMethods('Thread', {
});
patchRecordMethods('Thread', {
+ /**
+ * @override
+ */
+ getMemberName(partner) {
+ if (this.channel_type === 'livechat' && partner.livechat_username) {
+ return partner.livechat_username;
+ }
+ return this._super(partner);
+ },
/**
* @override
*/
diff --git a/addons/im_livechat/tests/test_get_mail_channel.py b/addons/im_livechat/tests/test_get_mail_channel.py
index aa48d8dabbc..9d3571b478d 100644
--- a/addons/im_livechat/tests/test_get_mail_channel.py
+++ b/addons/im_livechat/tests/test_get_mail_channel.py
@@ -9,7 +9,8 @@ class TestGetMailChannel(TransactionCase):
super(TestGetMailChannel, self).setUp()
self.operators = self.env['res.users'].create([{
'name': 'Michel',
- 'login': 'michel'
+ 'login': 'michel',
+ 'livechat_username': "Michel Operator",
}, {
'name': 'Paul',
'login': 'paul'
@@ -58,12 +59,30 @@ class TestGetMailChannel(TransactionCase):
test_user = self.env['res.users'].create({'name': 'Roger', 'login': 'roger', 'country_id': belgium.id})
# ensure visitor info are correct with anonymous
- channel_info = self.livechat_channel.with_user(public_user)._open_livechat_mail_channel(anonymous_name='Visitor 22', country_id=belgium.id)
+ operator = self.operators[0]
+ channel_info = self.livechat_channel.with_user(public_user)._open_livechat_mail_channel(anonymous_name='Visitor 22', previous_operator_id=operator.partner_id.id, country_id=belgium.id)
visitor_info = channel_info['livechat_visitor']
self.assertFalse(visitor_info['id'])
self.assertEqual(visitor_info['name'], "Visitor 22")
self.assertEqual(visitor_info['country'], (20, "Belgium"))
+ # ensure member info are hidden (in particular email and real name when livechat username is present)
+ self.assertEqual(sorted(channel_info['members'], key=lambda m: m['id']), sorted([{
+ 'active': True,
+ 'email': False,
+ 'id': operator.partner_id.id,
+ 'im_status': False,
+ 'livechat_username': 'Michel Operator',
+ 'name': 'Michel Operator',
+ }, {
+ 'active': False,
+ 'email': False,
+ 'id': public_user.partner_id.id,
+ 'im_status': False,
+ 'livechat_username': False,
+ 'name': 'Public user',
+ }], key=lambda m: m['id']))
+
# ensure visitor info are correct with real user
channel_info = self.livechat_channel.with_user(test_user)._open_livechat_mail_channel(anonymous_name='whatever', user_id=test_user.id)
visitor_info = channel_info['livechat_visitor']
@@ -74,7 +93,7 @@ class TestGetMailChannel(TransactionCase):
# ensure visitor info are correct when operator is testing himself
operator = self.operators[0]
channel_info = self.livechat_channel.with_user(operator)._open_livechat_mail_channel(anonymous_name='whatever', previous_operator_id=operator.partner_id.id, user_id=operator.id)
- self.assertEqual(channel_info['operator_pid'], (operator.partner_id.id, "Michel"))
+ self.assertEqual(channel_info['operator_pid'], (operator.partner_id.id, "Michel Operator"))
visitor_info = channel_info['livechat_visitor']
self.assertEqual(visitor_info['id'], operator.partner_id.id)
self.assertEqual(visitor_info['name'], "Michel")
@@ -100,3 +119,22 @@ class TestGetMailChannel(TransactionCase):
self.env['mail.channel'].browse(channel_info['id']).message_post(body='cc')
self.assertTrue(operator_channel_partner.is_pinned, "channel should be pinned for operator after visitor sent a message")
self.assertIn(channel_info['id'], operator_channel_partner.partner_id._get_channels_as_member().ids, "channel should be fetched by operator on new page")
+
+ def test_operator_livechat_username(self):
+ """Ensures the operator livechat_username is returned by `_channel_fetch_message`, which is
+ the method called by the public route displaying chat history."""
+ public_user = self.env.ref('base.public_user')
+ operator = self.operators[0]
+ operator.write({
+ 'email': 'michel@example.com',
+ 'livechat_username': 'Michel at your service',
+ })
+ channel_info = self.livechat_channel.with_user(public_user)._open_livechat_mail_channel(anonymous_name='whatever')
+ channel = self.env['mail.channel'].browse(channel_info['id'])
+ channel.with_user(operator).message_post(body='Hello', message_type='comment', subtype_xmlid='mail.mt_comment')
+ message_formats = channel.with_user(public_user)._channel_fetch_message()
+ self.assertEqual(len(message_formats), 1)
+ self.assertEqual(message_formats[0]['author_id'][0], operator.partner_id.id)
+ self.assertEqual(message_formats[0]['author_id'][1], operator.livechat_username)
+ self.assertEqual(message_formats[0]['author_id'][2], operator.livechat_username)
+ self.assertFalse(message_formats[0].get('email_from'), "should not send email_from to livechat user")
diff --git a/addons/mail/models/mail_channel.py b/addons/mail/models/mail_channel.py
index cdcbeccae3d..7706f88d367 100644
--- a/addons/mail/models/mail_channel.py
+++ b/addons/mail/models/mail_channel.py
@@ -771,7 +771,7 @@ class Channel(models.Model):
# avoid sending potentially a lot of members for big channels
# exclude chat and other small channels from this optimization because they are
# assumed to be smaller and it's important to know the member list for them
- info['members'] = sorted(list(partner_format_by_partner[member.partner_id] for member in members_by_channel[channel] if member.partner_id), key=lambda p: p['id'])
+ info['members'] = sorted(list(channel._channel_info_format_member(member.partner_id, partner_format_by_partner[member.partner_id]) for member in members_by_channel[channel] if member.partner_id), key=lambda p: p['id'])
info['seen_partners_info'] = sorted([{
'id': cp.id,
'partner_id': cp.partner_id.id,
@@ -789,6 +789,11 @@ class Channel(models.Model):
channel_infos.append(info)
return channel_infos
+ def _channel_info_format_member(self, partner, partner_info):
+ """Returns member information in the context of self channel."""
+ self.ensure_one()
+ return partner_info
+
def _channel_fetch_message(self, last_id=False, limit=20):
""" Return message values of the current channel.
:param last_id : last message id to start the research
@@ -1002,16 +1007,22 @@ class Channel(models.Model):
"""
notifications = []
for channel in self:
- data = {
+ data = dict({
'channel_id': channel.id,
'is_typing': is_typing,
- 'partner_id': self.env.user.partner_id.id,
- 'partner_name': self.env.user.partner_id.name,
- }
+ }, **channel._notify_typing_partner_data())
notifications.append([channel, 'mail.channel.partner/typing_status', data]) # notify backend users
notifications.append([channel.uuid, 'mail.channel.partner/typing_status', data]) # notify frontend users
self.env['bus.bus']._sendmany(notifications)
+ def _notify_typing_partner_data(self):
+ """Returns typing partner data for self channel."""
+ self.ensure_one()
+ return {
+ 'partner_id': self.env.user.partner_id.id,
+ 'partner_name': self.env.user.partner_id.name,
+ }
+
@api.model
def channel_search_to_join(self, name=None, domain=None):
""" Return the channel info of the channel the current partner can join
diff --git a/addons/mail/static/src/components/message/message.xml b/addons/mail/static/src/components/message/message.xml
index 2610dc6aa5f..a7725b26029 100644
--- a/addons/mail/static/src/components/message/message.xml
+++ b/addons/mail/static/src/components/message/message.xml
@@ -59,7 +59,12 @@
|