Merge branch 'master-hansa-05072018' into 'master-patch-july-2018'

Master hansa 05072018

See merge request flectra-hq/flectra!100
This commit is contained in:
Parthiv Patel 2018-07-13 11:21:23 +00:00
commit e313b2a500
42 changed files with 958 additions and 88 deletions

View File

@ -9,7 +9,7 @@ class AccountRegisterPayments(models.TransientModel):
_inherit = "account.register.payments"
check_amount_in_words = fields.Char(string="Amount in Words")
check_manual_sequencing = fields.Boolean(related='journal_id.check_manual_sequencing')
check_manual_sequencing = fields.Boolean(related='journal_id.check_manual_sequencing', readonly=1)
# Note: a check_number == 0 means that it will be attributed when the check is printed
check_number = fields.Integer(string="Check Number", readonly=True, copy=False, default=0,
help="Number of the check corresponding to this payment. If your pre-printed check are not already numbered, "
@ -42,7 +42,7 @@ class AccountPayment(models.Model):
_inherit = "account.payment"
check_amount_in_words = fields.Char(string="Amount in Words")
check_manual_sequencing = fields.Boolean(related='journal_id.check_manual_sequencing')
check_manual_sequencing = fields.Boolean(related='journal_id.check_manual_sequencing', readonly=1)
check_number = fields.Integer(string="Check Number", readonly=True, copy=False,
help="The selected journal is configured to print check numbers. If your pre-printed check paper already has numbers "
"or if the current numbering is wrong, you can change it in the journal configuration page.")

View File

@ -5,7 +5,7 @@ import logging
from flectra import api, fields, models
from flectra import tools, _
from flectra.exceptions import ValidationError
from flectra.exceptions import ValidationError, AccessError
from flectra.modules.module import get_module_resource
_logger = logging.getLogger(__name__)

View File

@ -14,7 +14,7 @@ class MailAlias(models.AbstractModel):
_inherit = 'mail.alias.mixin'
def _alias_check_contact_on_record(self, record, message, message_dict, alias):
if alias.alias_contact == 'employees' and record.ids:
if alias.alias_contact == 'employees':
email_from = tools.decode_message_header(message, 'From')
email_address = tools.email_split(email_from)[0]
employee = self.env['hr.employee'].search([('work_email', 'ilike', email_address)], limit=1)

View File

@ -38,11 +38,18 @@ var GreetingMessage = Widget.extend({
if (this.next_action != 'hr_attendance.hr_attendance_action_kiosk_mode' && this.next_action.tag != 'hr_attendance_kiosk_mode') {
this.activeBarcode = false;
}
this.attendance = action.attendance;
// We receive the check in/out times in UTC
// This widget only deals with display, which should be in browser's TimeZone
this.attendance.check_in = this.attendance.check_in && moment.utc(this.attendance.check_in).local();
this.attendance.check_out = this.attendance.check_out && moment.utc(this.attendance.check_out).local();
this.previous_attendance_change_date = action.previous_attendance_change_date && moment.utc(action.previous_attendance_change_date).local();
// check in/out times displayed in the greeting message template.
this.attendance.check_in_time = (new Date((new Date(this.attendance.check_in)).valueOf() - (new Date()).getTimezoneOffset()*60*1000)).toTimeString().slice(0,8);
this.attendance.check_out_time = this.attendance.check_out && (new Date((new Date(this.attendance.check_out)).valueOf() - (new Date()).getTimezoneOffset()*60*1000)).toTimeString().slice(0,8);
this.previous_attendance_change_date = action.previous_attendance_change_date;
this.format_time = 'HH:mm:ss';
this.attendance.check_in_time = this.attendance.check_in && this.attendance.check_in.format(this.format_time);
this.attendance.check_out_time = this.attendance.check_out && this.attendance.check_out.format(this.format_time);
this.employee_name = action.employee_name;
},
@ -57,13 +64,13 @@ var GreetingMessage = Widget.extend({
welcome_message: function() {
var self = this;
var now = new Date((new Date(this.attendance.check_in)).valueOf() - (new Date()).getTimezoneOffset()*60*1000);
var now = this.attendance.check_in.clone();
this.return_to_main_menu = setTimeout( function() { self.do_action(self.next_action, {clear_breadcrumbs: true}); }, 5000);
if (now.getHours() < 5) {
if (now.hours() < 5) {
this.$('.o_hr_attendance_message_message').append(_t("Good night"));
} else if (now.getHours() < 12) {
if (now.getHours() < 8 && Math.random() < 0.3) {
} else if (now.hours() < 12) {
if (now.hours() < 8 && Math.random() < 0.3) {
if (Math.random() < 0.75) {
this.$('.o_hr_attendance_message_message').append(_t("The early bird catches the worm"));
} else {
@ -72,16 +79,16 @@ var GreetingMessage = Widget.extend({
} else {
this.$('.o_hr_attendance_message_message').append(_t("Good morning"));
}
} else if (now.getHours() < 17){
} else if (now.hours() < 17){
this.$('.o_hr_attendance_message_message').append(_t("Good afternoon"));
} else if (now.getHours() < 23){
} else if (now.hours() < 23){
this.$('.o_hr_attendance_message_message').append(_t("Good evening"));
} else {
this.$('.o_hr_attendance_message_message').append(_t("Good night"));
}
if(this.previous_attendance_change_date){
var last_check_out_date = new Date((new Date(this.previous_attendance_change_date)).valueOf() - (new Date()).getTimezoneOffset()*60*1000);
if(now.valueOf() - last_check_out_date.valueOf() > 1000*60*60*24*7){
var last_check_out_date = this.previous_attendance_change_date.clone();
if(now - last_check_out_date > 24*7*60*60*1000){
this.$('.o_hr_attendance_random_message').html(_t("Glad to have you back, it's been a while!"));
} else {
if(Math.random() < 0.02){
@ -93,33 +100,33 @@ var GreetingMessage = Widget.extend({
farewell_message: function() {
var self = this;
var now = new Date((new Date(this.attendance.check_out)).valueOf() - (new Date()).getTimezoneOffset()*60*1000);
var now = this.attendance.check_out.clone();
this.return_to_main_menu = setTimeout( function() { self.do_action(self.next_action, {clear_breadcrumbs: true}); }, 5000);
if(this.previous_attendance_change_date){
var last_check_in_date = new Date((new Date(this.previous_attendance_change_date)).valueOf() - (new Date()).getTimezoneOffset()*60*1000);
if(now.valueOf() - last_check_in_date.valueOf() > 1000*60*60*12){
var last_check_in_date = this.previous_attendance_change_date.clone();
if(now - last_check_in_date > 1000*60*60*12){
this.$('.o_hr_attendance_warning_message').append(_t("Warning! Last check in was over 12 hours ago.<br/>If this isn't right, please contact Human Resources."));
clearTimeout(this.return_to_main_menu);
this.activeBarcode = false;
} else if(now.valueOf() - last_check_in_date.valueOf() > 1000*60*60*8){
} else if(now - last_check_in_date > 1000*60*60*8){
this.$('.o_hr_attendance_random_message').html(_t("Another good day's work! See you soon!"));
}
}
if (now.getHours() < 12) {
if (now.hours() < 12) {
this.$('.o_hr_attendance_message_message').append(_t("Have a good day!"));
} else if (now.getHours() < 14) {
} else if (now.hours() < 14) {
this.$('.o_hr_attendance_message_message').append(_t("Have a nice lunch!"));
if (Math.random() < 0.05) {
this.$('.o_hr_attendance_random_message').html(_t("Eat breakfast as a king, lunch as a merchant and supper as a beggar"));
} else if (Math.random() < 0.06) {
this.$('.o_hr_attendance_random_message').html(_t("An apple a day keeps the doctor away"));
}
} else if (now.getHours() < 17) {
} else if (now.hours() < 17) {
this.$('.o_hr_attendance_message_message').append(_t("Have a good afternoon"));
} else {
if (now.getHours() < 18 && Math.random() < 0.2) {
if (now.hours() < 18 && Math.random() < 0.2) {
this.$('.o_hr_attendance_message_message').append(_t("Early to bed and early to rise, makes a man healthy, wealthy and wise"));
} else {
this.$('.o_hr_attendance_message_message').append(_t("Have a good evening"));

View File

@ -1,6 +1,7 @@
flectra.define('hr_attendance.kiosk_mode', function (require) {
"use strict";
var ajax = require('web.ajax');
var core = require('web.core');
var Widget = require('web.Widget');
var Session = require('web.session');
@ -27,6 +28,8 @@ var KioskMode = Widget.extend({
self.$el.html(QWeb.render("HrAttendanceKioskMode", {widget: self}));
self.start_clock();
});
// Make a RPC call every day to keep the session alive
self._interval = window.setInterval(this._callServer.bind(this), (60*60*1000*24));
return $.when(def, this._super.apply(this, arguments));
},
@ -55,8 +58,15 @@ var KioskMode = Widget.extend({
destroy: function () {
core.bus.off('barcode_scanned', this, this._onBarcodeScanned);
clearInterval(this.clock_start);
clearInterval(this._interval);
this._super.apply(this, arguments);
},
_callServer: function () {
// Make a call to the database to avoid the auto close of the session
return ajax.rpc("/web/webclient/version_info", {});
},
});
core.action_registry.add('hr_attendance_kiosk_mode', KioskMode);

View File

@ -67,7 +67,7 @@ class Contract(models.Model):
resource_calendar_id = fields.Many2one(
'resource.calendar', 'Working Schedule',
default=lambda self: self.env['res.company']._company_default_get().resource_calendar_id.id)
wage = fields.Monetary('Wage', digits=(16, 2), required=True, help="Employee's monthly gross wage.")
wage = fields.Monetary('Wage', digits=(16, 2), required=True, track_visibility="onchange", help="Employee's monthly gross wage.")
advantages = fields.Text('Advantages')
notes = fields.Text('Notes')
state = fields.Selection([

View File

@ -370,7 +370,9 @@ class HrExpense(models.Model):
product = default_product
else:
expense_description = expense_description.replace(product_code.group(), '')
product = self.env['product.product'].search([('default_code', 'ilike', product_code.group(1))]) or default_product
products = self.env['product.product'].search([('default_code', 'ilike', product_code.group(1))]) or default_product
product = products.filtered(lambda p: p.default_code == product_code.group(1)) or products[0]
account = product.product_tmpl_id._get_product_accounts()['expense']
pattern = '[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?'
# Match the last occurence of a float in the string
@ -397,6 +399,8 @@ class HrExpense(models.Model):
'unit_amount': price,
'company_id': employee.company_id.id,
})
if account:
custom_values['account_id'] = account.id
return super(HrExpense, self).message_new(msg_dict, custom_values)
class HrExpenseSheet(models.Model):
@ -512,7 +516,7 @@ class HrExpenseSheet(models.Model):
@api.onchange('employee_id')
def _onchange_employee_id(self):
self.address_id = self.employee_id.address_home_id
self.address_id = self.employee_id.sudo().address_home_id
self.department_id = self.employee_id.department_id
@api.one
@ -599,4 +603,4 @@ class HrExpenseSheet(models.Model):
def _check_payment_mode(self):
payment_mode = set(self.expense_line_ids.mapped('payment_mode'))
if len(payment_mode) > 1:
raise ValidationError(_('You cannot report expenses with different payment modes.'))
raise ValidationError(_('You cannot report expenses with different payment modes.'))

View File

@ -82,6 +82,7 @@
<tbody>
<tr>
<td style="padding-top:10px;font-size: 12px;">
<div>Sent by ${object.company_id.name}</div>
% if 'website_url' in object.job_id and object.job_id.website_url:
<div>
Discover <a href="/jobs" style="text-decoration:none;color:#717188;">our others jobs</a>.

View File

@ -36,7 +36,7 @@
<span t-field="l.user_id.partner_id.name"/>
</td>
<td >
<span t-field="l.name"/>
<span t-field="l.name" t-options="{'widget': 'text'}"/>
</td>
<td t-if="show_task or show_project">
<t t-if="show_project"><span t-field="l.project_id.name"/></t>

View File

@ -2,6 +2,7 @@
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from datetime import date, datetime, timedelta
import pytz
from flectra import api, exceptions, fields, models, _
@ -77,7 +78,7 @@ class MailActivity(models.Model):
summary = fields.Char('Summary')
note = fields.Html('Note')
feedback = fields.Html('Feedback')
date_deadline = fields.Date('Due Date', index=True, required=True, default=fields.Date.today)
date_deadline = fields.Date('Due Date', index=True, required=True, default=fields.Date.context_today)
# description
user_id = fields.Many2one(
'res.users', 'Assigned to',
@ -108,8 +109,16 @@ class MailActivity(models.Model):
@api.depends('date_deadline')
def _compute_state(self):
today = date.today()
today_default = date.today()
for record in self.filtered(lambda activity: activity.date_deadline):
today = today_default
tz = record.user_id.sudo().tz
if tz:
today_utc = pytz.UTC.localize(datetime.utcnow())
today_tz = today_utc.astimezone(pytz.timezone(tz))
today = date(year=today_tz.year, month=today_tz.month, day=today_tz.day)
date_deadline = fields.Date.from_string(record.date_deadline)
diff = (date_deadline - today)
if diff.days == 0:
@ -132,7 +141,8 @@ class MailActivity(models.Model):
@api.onchange('recommended_activity_type_id')
def _onchange_recommended_activity_type_id(self):
self.activity_type_id = self.recommended_activity_type_id
if self.recommended_activity_type_id:
self.activity_type_id = self.recommended_activity_type_id
@api.multi
def _check_access(self, operation):

View File

@ -505,7 +505,15 @@ class Channel(models.Model):
partners_to_add = partners - channel.channel_partner_ids
channel.write({'channel_last_seen_partner_ids': [(0, 0, {'partner_id': partner_id}) for partner_id in partners_to_add.ids]})
for partner in partners_to_add:
notification = _('<div class="o_mail_notification">joined <a href="#" class="o_channel_redirect" data-oe-id="%s">#%s</a></div>') % (self.id, self.name,)
if partner.id != self.env.user.partner_id.id:
notification = _('<div class="o_mail_notification">%(author)s invited %(new_partner)s to <a href="#" class="o_channel_redirect" data-oe-id="%(channel_id)s">#%(channel_name)s</a></div>') % {
'author': self.env.user.display_name,
'new_partner': partner.display_name,
'channel_id': channel.id,
'channel_name': channel.name,
}
else:
notification = _('<div class="o_mail_notification">joined <a href="#" class="o_channel_redirect" data-oe-id="%s">#%s</a></div>') % (channel.id, channel.name,)
self.message_post(body=notification, message_type="notification", subtype="mail.mt_comment", author_id=partner.id)
# broadcast the channel header to the added partner
@ -674,7 +682,7 @@ class Channel(models.Model):
msg += _(" This channel is private. People must be invited to join it.")
else:
channel_partners = self.env['mail.channel.partner'].search([('partner_id', '!=', partner.id), ('channel_id', '=', self.id)])
msg = _("You are in a private conversation with <b>@%s</b>.") % channel_partners[0].partner_id.name
msg = _("You are in a private conversation with <b>@%s</b>.") % (channel_partners[0].partner_id.name if channel_partners else _('Anonymous'))
msg += _("""<br><br>
You can mention someone by typing <b>@username</b>, this will grab its attention.<br>
You can mention a channel by typing <b>#channel</b>.<br>

View File

@ -5,6 +5,7 @@ import logging
import re
from email.utils import formataddr
from openerp.http import request
from flectra import _, api, fields, models, modules, SUPERUSER_ID, tools
from flectra.exceptions import UserError, AccessError
@ -299,11 +300,12 @@ class Message(models.Model):
# 2. Attachments as SUPERUSER, because could receive msg and attachments for doc uid cannot see
attachments_data = attachments.sudo().read(['id', 'datas_fname', 'name', 'mimetype'])
safari = request and request.httprequest.user_agent.browser == 'safari'
attachments_tree = dict((attachment['id'], {
'id': attachment['id'],
'filename': attachment['datas_fname'],
'name': attachment['name'],
'mimetype': attachment['mimetype'],
'mimetype': 'application/octet-stream' if safari and 'video' in attachment['mimetype'] else attachment['mimetype'],
}) for attachment in attachments_data)
# 3. Tracking values
@ -422,7 +424,18 @@ class Message(models.Model):
subtype_ids = [msg['subtype_id'][0] for msg in message_values if msg['subtype_id']]
subtypes = self.env['mail.message.subtype'].sudo().browse(subtype_ids).read(['internal', 'description'])
subtypes_dict = dict((subtype['id'], subtype) for subtype in subtypes)
# fetch notification status
notif_dict = {}
notifs = self.env['mail.notification'].sudo().search([('mail_message_id', 'in', list(mid for mid in message_tree)), ('is_read', '=', False)])
for notif in notifs:
mid = notif.mail_message_id.id
if not notif_dict.get(mid):
notif_dict[mid] = {'partner_id': list()}
notif_dict[mid]['partner_id'].append(notif.res_partner_id.id)
for message in message_values:
message['needaction_partner_ids'] = notif_dict.get(message['id'], dict()).get('partner_id', [])
message['is_note'] = message['subtype_id'] and subtypes_dict[message['subtype_id'][0]]['internal']
message['subtype_description'] = message['subtype_id'] and subtypes_dict[message['subtype_id'][0]]['description']
if message['model'] and self.env[message['model']]._original_module:

View File

@ -126,7 +126,8 @@ class MailThread(models.AbstractModel):
followers = self.env['mail.followers'].sudo().search([
('res_model', '=', self._name),
('partner_id', operator, operand)])
return [('id', 'in', followers.mapped('res_id'))]
# using read() below is much faster than followers.mapped('res_id')
return [('id', 'in', [res['res_id'] for res in followers.read(['res_id'])])]
@api.model
def _search_follower_channels(self, operator, operand):
@ -139,7 +140,8 @@ class MailThread(models.AbstractModel):
followers = self.env['mail.followers'].sudo().search([
('res_model', '=', self._name),
('channel_id', operator, operand)])
return [('id', 'in', followers.mapped('res_id'))]
# using read() below is much faster than followers.mapped('res_id')
return [('id', 'in', [res['res_id'] for res in followers.read(['res_id'])])]
@api.multi
@api.depends('message_follower_ids')
@ -149,7 +151,8 @@ class MailThread(models.AbstractModel):
('res_id', 'in', self.ids),
('partner_id', '=', self.env.user.partner_id.id),
])
following_ids = followers.mapped('res_id')
# using read() below is much faster than followers.mapped('res_id')
following_ids = [res['res_id'] for res in followers.read(['res_id'])]
for record in self:
record.message_is_follower = record.id in following_ids
@ -161,9 +164,11 @@ class MailThread(models.AbstractModel):
])
# Cases ('message_is_follower', '=', True) or ('message_is_follower', '!=', False)
if (operator == '=' and operand) or (operator == '!=' and not operand):
return [('id', 'in', followers.mapped('res_id'))]
# using read() below is much faster than followers.mapped('res_id')
return [('id', 'in', [res['res_id'] for res in followers.read(['res_id'])])]
else:
return [('id', 'not in', followers.mapped('res_id'))]
# using read() below is much faster than followers.mapped('res_id')
return [('id', 'not in', [res['res_id'] for res in followers.read(['res_id'])])]
@api.multi
def _get_message_unread(self):
@ -1373,7 +1378,13 @@ class MailThread(models.AbstractModel):
located in tools. """
if not body:
return body, attachments
root = lxml.html.fromstring(body)
try:
root = lxml.html.fromstring(body)
except ValueError:
# In case the email client sent XHTML, fromstring will fail because 'Unicode strings
# with encoding declaration are not supported'.
root = lxml.html.fromstring(body.encode('utf-8'))
postprocessed = False
to_remove = []
for node in root.iter():

View File

@ -58,6 +58,8 @@ class Users(models.Model):
# create a welcome message
user._create_welcome_message()
# Auto-subscribe to channels
self.env['mail.channel'].search([('group_ids', 'in', user.groups_id.ids)])._subscribe_users()
return user
@api.multi
@ -127,16 +129,19 @@ class Users(models.Model):
def activity_user_count(self):
query = """SELECT m.id, count(*), act.res_model as model,
CASE
WHEN now()::date - act.date_deadline::date = 0 Then 'today'
WHEN now()::date - act.date_deadline::date > 0 Then 'overdue'
WHEN now()::date - act.date_deadline::date < 0 Then 'planned'
WHEN %(today)s::date - act.date_deadline::date = 0 Then 'today'
WHEN %(today)s::date - act.date_deadline::date > 0 Then 'overdue'
WHEN %(today)s::date - act.date_deadline::date < 0 Then 'planned'
END AS states
FROM mail_activity AS act
JOIN ir_model AS m ON act.res_model_id = m.id
WHERE user_id = %s
WHERE user_id = %(user_id)s
GROUP BY m.id, states, act.res_model;
"""
self.env.cr.execute(query, [self.env.uid])
self.env.cr.execute(query, {
'today': fields.Date.context_today(self),
'user_id': self.env.uid,
})
activity_data = self.env.cr.dictfetchall()
model_ids = [a['id'] for a in activity_data]
model_names = {n[0]:n[1] for n in self.env['ir.model'].browse(model_ids).name_get()}

View File

@ -32,6 +32,7 @@ function _readActivities(self, ids) {
model: 'mail.activity',
method: 'read',
args: [ids],
context: (self.record && self.record.getContext()) || self.getSession().user_context,
}).then(function (activities) {
// convert create_date and date_deadline to moments
_.each(activities, function (activity) {
@ -108,6 +109,7 @@ var AbstractActivityField = AbstractField.extend({
method: 'action_feedback',
args: [[id]],
kwargs: {feedback: feedback},
context: this.record.getContext(),
});
},
_scheduleActivity: function (id, previous_activity_type_id, callback) {

View File

@ -149,7 +149,16 @@ function make_message (data) {
_.each(_.keys(emoji_substitutions), function (key) {
var escaped_key = String(key).replace(/([.*+?=^!:${}()|[\]\/\\])/g, '\\$1');
var regexp = new RegExp("(?:^|\\s|<[a-z]*>)(" + escaped_key + ")(?=\\s|$|</[a-z]*>)", "g");
var msg_bak = msg.body;
msg.body = msg.body.replace(regexp, ' <span class="o_mail_emoji">'+emoji_substitutions[key]+'</span> ');
// Idiot-proof limit. If the user had the amazing idea of copy-pasting thousands of emojis,
// the image rendering can lead to memory overflow errors on some browsers (e.g. Chrome).
// Set an arbitrary limit to 200 from which we simply don't replace them (anyway, they are
// already replaced by the unicode counterpart).
if (_.str.count(msg.body, 'o_mail_emoji') > 200) {
msg.body = msg_bak;
}
});
function property_descr(channel) {

View File

@ -16,7 +16,8 @@ var HEIGHT_FOLDED = '34px';
return Widget.extend({
template: "mail.ChatWindow",
custom_events: {
escape_pressed: '_onEscapePressed'
escape_pressed: '_onEscapePressed',
document_viewer_closed: '_onDocumentViewerClose',
},
events: {
'click .o_chat_composer': '_onComposerClick',
@ -180,6 +181,9 @@ return Widget.extend({
}
this.focus_input();
},
_onDocumentViewerClose: function (ev) {
this.focus_input();
},
/**
* @private
*/

View File

@ -514,6 +514,9 @@ var BasicComposer = Widget.extend(chat_mixin, {
on_click_add_attachment: function () {
this.$('input.o_input_file').click();
this.$input.focus();
// set ignoreEscape to avoid escape_pressed event when file selector dialog is opened
// when user press escape to cancel file selector dialog then escape_pressed event should not be trigerred
this.ignoreEscape = true;
},
setState: function (state) {
@ -565,6 +568,8 @@ var BasicComposer = Widget.extend(chat_mixin, {
if (this.mention_manager.is_open()) {
event.stopPropagation();
this.mention_manager.reset_suggestions();
} else if (this.ignoreEscape) {
this.ignoreEscape = false;
} else {
this.trigger_up("escape_pressed");
}
@ -789,6 +794,7 @@ var BasicComposer = Widget.extend(chat_mixin, {
* @param {MouseEvent} event
*/
_onAttachmentView: function (event) {
event.stopPropagation();
var activeAttachmentID = $(event.currentTarget).data('id');
var attachments = this.get('attachment_ids');
if (activeAttachmentID) {

View File

@ -25,6 +25,7 @@ var DocumentViewer = Widget.extend({
'DOMMouseScroll .o_viewer_content': '_onScroll', // Firefox
'mousewheel .o_viewer_content': '_onScroll', // Chrome, Safari, IE
'keydown': '_onKeydown',
'keyup': '_onKeyUp',
'mousedown .o_viewer_img': '_onStartDrag',
'mousemove .o_viewer_content': '_onDrag',
'mouseup .o_viewer_content': '_onEndDrag'
@ -152,6 +153,7 @@ var DocumentViewer = Widget.extend({
_onClose: function (e) {
e.preventDefault();
this.$el.modal('hide');
this.trigger_up('document_viewer_closed');
},
/**
* When popup close complete destroyed modal even DOM footprint too
@ -232,6 +234,20 @@ var DocumentViewer = Widget.extend({
break;
}
},
/**
* Close popup on ESCAPE keyup
*
* @private
* @param {KeyEvent} e
*/
_onKeyUp: function (e) {
switch (e.which) {
case $.ui.keyCode.ESCAPE:
e.preventDefault();
this._onClose(e);
break;
}
},
/**
* @private
* @param {MouseEvent} e

View File

@ -122,7 +122,7 @@ var MessagingMenu = Widget.extend({
if (channelID === 'channel_inbox') {
var resID = $(event.currentTarget).data('res_id');
var resModel = $(event.currentTarget).data('res_model');
if (resModel && resID) {
if (resModel && resModel !== 'mail.channel' && resID) {
this.do_action({
type: 'ir.actions.act_window',
res_model: resModel,
@ -130,7 +130,11 @@ var MessagingMenu = Widget.extend({
res_id: resID
});
} else {
this.do_action('mail.mail_channel_action_client_chat', {clear_breadcrumbs: true})
var clientChatOptions = {clear_breadcrumbs: true};
if (resModel && resModel === 'mail.channel' && resID) {
clientChatOptions.active_id = resID;
}
this.do_action('mail.mail_channel_action_client_chat', clientChatOptions)
.then(function () {
self.trigger_up('hide_app_switcher');
core.bus.trigger('change_menu_section', chat_manager.get_discuss_menu_id());

View File

@ -328,6 +328,7 @@ var Thread = Widget.extend({
* @param {MouseEvent} event
*/
_onAttachmentView: function (event) {
event.stopPropagation();
var activeAttachmentID = $(event.currentTarget).data('id');
if (activeAttachmentID) {
var attachmentViewer = new DocumentViewer(this, this.attachments, activeAttachmentID);

View File

@ -60,7 +60,7 @@ function _parse_and_transform(nodes, transform_function) {
// Suggested URL Javascript regex of http://stackoverflow.com/questions/3809401/what-is-a-good-regular-expression-to-match-a-url
// Adapted to make http(s):// not required if (and only if) www. is given. So `should.notmatch` does not match.
var url_regexp = /\b(?:https?:\/\/|(www\.))[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,13}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/gi;
var url_regexp = /\b(?:https?:\/\/\d{1,3}(?:\.\d{1,3}){3}|(?:https?:\/\/|(?:www\.))[-a-z0-9@:%._\+~#=]{2,256}\.[a-z]{2,13})\b(?:[-a-z0-9@:%_\+.~#?&'$//=]*)/gi;
function linkify(text, attrs) {
attrs = attrs || {};
if (attrs.target === undefined) {
@ -70,7 +70,7 @@ function linkify(text, attrs) {
return key + '="' + _.escape(value) + '"';
}).join(' ');
return text.replace(url_regexp, function (url) {
var href = (!/^(f|ht)tps?:\/\//i.test(url)) ? "http://" + url : url;
var href = (!/^https?:\/\//i.test(url)) ? "http://" + url : url;
return '<a ' + attrs + ' href="' + href + '">' + url + '</a>';
});
}
@ -92,6 +92,7 @@ function strip_html (node, transform_children) {
function inline (node, transform_children) {
if (node.nodeType === 3) return node.data;
if (node.nodeType === 8) return "";
if (node.tagName === "BR") return " ";
if (node.tagName.match(/^(A|P|DIV|PRE|BLOCKQUOTE)$/)) return transform_children();
node.innerHTML = transform_children();
@ -100,16 +101,23 @@ function inline (node, transform_children) {
// Parses text to find email: Tagada <address@mail.fr> -> [Tagada, address@mail.fr] or False
function parse_email (text) {
var result = text.match(/(.*)<(.*@.*)>/);
if (result) {
return [_.str.trim(result[1]), _.str.trim(result[2])];
if (text){
var result = text.match(/(.*)<(.*@.*)>/);
if (result) {
return [_.str.trim(result[1]), _.str.trim(result[2])];
}
result = text.match(/(.*@.*)/);
if (result) {
return [_.str.trim(result[1]), _.str.trim(result[1])];
}
return [text, false];
}
result = text.match(/(.*@.*)/);
/* result = text.match(/(.*@.*)/);
if (result) {
return [_.str.trim(result[1]), _.str.trim(result[1])];
}
return [text, false];
}
}*/
// Replaces textarea text into html text (add <p>, <a>)
// TDE note : should be done server-side, in Python -> use mail.compose.message ?

View File

@ -51,7 +51,7 @@
border: none;
}
.o_composer_button_full_composer {
.o-position-absolute(0, 0);
.o-position-absolute(auto, 0);
}
@media (max-width: @screen-xs-max) {
.o_composer_button_send {
@ -136,7 +136,7 @@
&.o_chat_inline_composer {
.o_composer_container {
.o-flex(1, 0, auto);
.o-flex(1, 1, auto);
}
.o_composer {
padding: @o-mail-chatter-gap @o-mail-chatter-gap 0 @o-mail-chatter-gap;
@ -339,6 +339,7 @@
}
.o_modal_fullscreen {
z-index: @o-chat-window-zindex + 1; // To overlap chat window
.o_viewer_content {
width: 100%;
height: 100%;

View File

@ -107,7 +107,7 @@
&.o_mail_note {
background-color: @mail-thread-note;
padding-left: @grid-gutter-width*0.3;
border-bottom: 1px solid @gray-lighter-dark;
border-bottom: 1px solid @gray-lighter-darker;
}
.o_mail_subject {

View File

@ -76,7 +76,7 @@
</t>
<t t-name="DocumentViewer">
<div class="modal o_modal_fullscreen" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal o_modal_fullscreen" tabindex="-1" data-keyboard="false" role="dialog" aria-hidden="true">
<t t-call="DocumentViewer.Content"/>
<t t-if="widget.attachment.length != 1">

View File

@ -50,6 +50,84 @@ QUnit.module('mail', {}, function () {
parent.destroy();
});
QUnit.test('open document viewer and close using ESCAPE key should reset focus to chat window', function (assert) {
assert.expect(6);
function createParent(params) {
var widget = new Widget();
testUtils.addMockEnvironment(widget, params);
return widget;
}
var messages = [{
attachment_ids: [{
filename: 'image1.jpg',
id:1,
mimetype: 'image/jpeg',
name: 'Test Image 1',
url: '/web/content/1?download=true'
}],
author_id: ["1", "John Doe"],
body: "A message",
date: moment("2016-12-20 09:35:40"),
displayed_author: "John Doe",
id: 1,
is_note: false,
is_starred: false,
model: 'partner',
res_id: 2
}];
var parent = createParent({
mockRPC: function (route, args) {
if(_.str.contains(route, '/mail/attachment/preview/') ||
_.str.contains(route, '/web/static/lib/pdfjs/web/viewer.html')){
var canvas = document.createElement('canvas');
return $.when(canvas.toDataURL());
}
return this._super.apply(this, arguments);
},
data: {},
});
var chatWindow = new ChatWindow(parent, 1, "user", false, messages.length, {});
chatWindow.appendTo($('#qunit-fixture'));
chatWindow.render(messages);
testUtils.intercept(chatWindow, 'get_messages', function(event) {
event.stopPropagation();
var requested_msgs = _.filter(messages, function (msg) {
return _.contains(event.data.options.ids, msg.id);
});
event.data.callback($.when(requested_msgs));
}, true);
testUtils.intercept(chatWindow, 'get_bus', function(event) {
event.stopPropagation();
event.data.callback(new Bus());
}, true);
chatWindow.on('document_viewer_closed', null, function () {
assert.ok(true, "chat window should trigger a close document viewer event");
});
assert.strictEqual(chatWindow.$('.o_thread_message .o_attachment').length, 1,
"there should be three attachment on message");
// click on first image attachement
chatWindow.$('.o_thread_message .o_attachment .o_image_box .o_image_overlay').first().click();
// check focus is on document viewer popup and then press escape to close it
assert.strictEqual(document.activeElement, $('.o_modal_fullscreen')[0], "Modal popup should have focus");
assert.strictEqual($('.o_modal_fullscreen img.o_viewer_img[src*="/web/image/1?unique=1"]').length, 1,
"Modal popup should open with first image src");
// trigger ESCAPE keyup on document viewer popup
var upKeyEvent = jQuery.Event("keyup", {which: 27});
$('.o_modal_fullscreen').trigger(upKeyEvent);
assert.strictEqual(document.activeElement, chatWindow.$input[0],
"input should be focused");
var upKeyEvent = jQuery.Event( "keyup", {which: 27});
chatWindow.$('.o_composer_input').trigger(upKeyEvent);
assert.strictEqual(chatWindow.folded, false, "Closed chat Window");
parent.destroy();
});
QUnit.test('chat window\'s input can still be focused when the UI is blocked', function (assert) {
assert.expect(2);

View File

@ -8,7 +8,7 @@ QUnit.module('mail', {}, function () {
QUnit.module('Mail utils');
QUnit.test('add_link utility function', function (assert) {
assert.expect(7);
assert.expect(15);
var testInputs = {
'http://admin:password@example.com:8/%2020': true,
@ -24,6 +24,7 @@ QUnit.test('add_link utility function', function (assert) {
var output = utils.parse_and_transform(content, utils.add_link);
if (willLinkify) {
assert.strictEqual(output.indexOf('<a '), 0, "There should be a link");
assert.strictEqual(output.indexOf('</a>'), (output.length - 4), "Link should match the whole text");
} else {
assert.strictEqual(output.indexOf('<a '), -1, "There should be no link");
}

View File

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import socket
@ -10,7 +10,7 @@ from flectra.tools import mute_logger
MAIL_TEMPLATE = """Return-Path: <whatever-2a840@postmaster.twitter.com>
To: {to}
cc: {cc}
Received: by mail1.flectra.com (Postfix, from userid 10002)
Received: by mail1.openerp.com (Postfix, from userid 10002)
id 5DF9ABFB2A; Fri, 10 Aug 2012 16:16:39 +0200 (CEST)
From: {email_from}
Subject: {subject}
@ -51,7 +51,7 @@ Content-Transfer-Encoding: quoted-printable
MAIL_TEMPLATE_PLAINTEXT = """Return-Path: <whatever-2a840@postmaster.twitter.com>
To: {to}
Received: by mail1.flectra.com (Postfix, from userid 10002)
Received: by mail1.openerp.com (Postfix, from userid 10002)
id 5DF9ABFB2A; Fri, 10 Aug 2012 16:16:39 +0200 (CEST)
From: Sylvie Lelitre <test.sylvie.lelitre@agrolait.com>
Subject: {subject}
@ -78,7 +78,7 @@ X-Spam-Status: No, score=-2.6 required=5.0 tests=BAYES_00,FREEMAIL_FROM,
HTML_MESSAGE,RCVD_IN_DNSWL_LOW autolearn=unavailable version=3.3.1
Received: from mail-ie0-f173.google.com (mail-ie0-f173.google.com [209.85.223.173])
by mail1.grosbedon.com (Postfix) with ESMTPS id 9BBD7BFAAA
for <raoul@flectra.fr>; Fri, 23 Aug 2013 13:17:55 +0200 (CEST)
for <raoul@openerp.fr>; Fri, 23 Aug 2013 13:17:55 +0200 (CEST)
Received: by mail-ie0-f173.google.com with SMTP id qd12so575130ieb.4
for <raoul@grosbedon.fr>; Fri, 23 Aug 2013 04:17:54 -0700 (PDT)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
@ -298,6 +298,630 @@ AAAAACwAAAAAAgACAAAEA3DJFQA7
--001a11416b9e9b229a05272b7052--
"""
MAIL_EML_ATTACHMENT = """Subject: Re: test attac
From: Anon <anon@flectrahq.com>
To: anon@gmail.com
References: <f3b9f8f8-28fa-2543-cab2-7aa68f679ebb@flectrahq.com>
Message-ID: <cb7eaf62-58dc-2017-148c-305d0c78892f@flectrahq.com>
Date: Wed, 14 Mar 2018 14:26:58 +0100
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101
Thunderbird/52.6.0
MIME-Version: 1.0
In-Reply-To: <f3b9f8f8-28fa-2543-cab2-7aa68f679ebb@flectrahq.com>
Content-Type: multipart/mixed;
boundary="------------A6B5FD5F68F4D73ECD739009"
Content-Language: en-US
This is a multi-part message in MIME format.
--------------A6B5FD5F68F4D73ECD739009
Content-Type: text/plain; charset=utf-8; format=flowed
Content-Transfer-Encoding: 7bit
On 14/03/18 14:20, Anon wrote:
> Some nice content
>
--------------A6B5FD5F68F4D73ECD739009
Content-Type: message/rfc822;
name="original_msg.eml"
Content-Transfer-Encoding: 8bit
Content-Disposition: attachment;
filename="original_msg.eml"
Delivered-To: anon2@gmail1.openerp.com
Received: by 10.46.1.170 with SMTP id f42csp2379722lji;
Mon, 5 Mar 2018 01:19:23 -0800 (PST)
X-Google-Smtp-Source: AG47ELsYTlAcblMxfnaEENQuF+MFoac5Q07wieyw0cybq/qOX4+DmayqoQILkiWT+NiTOcnr/ACO
X-Received: by 10.28.154.213 with SMTP id c204mr7237750wme.64.1520241563503;
Mon, 05 Mar 2018 01:19:23 -0800 (PST)
ARC-Seal: i=1; a=rsa-sha256; t=1520241563; cv=none;
d=google.com; s=arc-20160816;
b=BqgMSbqmbpYW1ZtfGTVjj/654MBmabw4XadNZEaI96hDaub6N6cP8Guu3PoxscI9os
0OLYVP1s/B+Vv9rIzulCwHyHsgnX+aTxGYepTDN6x8SA9Qeb9aQoNSVvQLryTAoGpaFr
vXhw8aPWyr28edE03TDFA/s7X65Bf6dV5zJdMiUPVqGkfYfcTHMf3nDER5vk8vQj7tve
Cfyy0h9vLU9RSEtdFwmlEkLmgT9NQ3GDf0jQ97eMXPgR2q6duCPoMcz15KlWOno53xgH
EiV7aIZ5ZMN/m+/2xt3br/ubJ5euFojWhDnHUZoaqd08TCSQPd4fFCCx75MjDeCnwYMn
iKSg==
ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816;
h=content-language:mime-version:user-agent:date:message-id:subject
:from:to:dkim-signature:arc-authentication-results;
bh=/UIFqhjCCbwBLsI4w7YY98QH6G/wxe+2W4bbMDCskjM=;
b=Wv5jt+usnSgWI96GaZWUN8/VKl1drueDpU/4gkyX/iK4d6S4CuSDjwYAc3guz/TjeW
GoKCqT30IGZoStpXQbuLry7ezXNK+Fp8MJKN2n/x5ClJWHxIsxIGlP2QC3TO8RI0P5o0
GXG9izW93q1ubkdPJFt3unSjjwSYf5XVQAZQtRm9xKjqA+lbtFbsnbjJ4wgYBURnD8ma
Qxb2xsxXDelaZvtdlzHRDn5SEkbqhcCclEYw6oRLpVQFZeYtPxcCleVybtj2owJxdaLp
7wXuo/gpYe6E2cPuS2opei8AzjEhYTNzlYXTPvaoxCCTTjfGTaPv22TeRDehuIXngSEl
Nmmw==
ARC-Authentication-Results: i=1; mx.google.com;
dkim=pass header.i=@flectrahq.com header.s=mail header.b=MCzhjB9b;
spf=pass (google.com: domain of soup@flectrahq.com designates 149.202.180.44 as permitted sender) smtp.mailfrom=soup@flectrahq.com;
dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=flectrahq.com
Return-Path: <soup@flectrahq.com>
Received: from mail2.flectrahq.com (mail2.flectrahq.com. [149.202.180.44])
by mx.google.com with ESMTPS id y4si4279200wmy.148.2018.03.05.01.19.22
(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);
Mon, 05 Mar 2018 01:19:23 -0800 (PST)
Received-SPF: pass (google.com: domain of soup@flectrahq.com designates 149.202.180.44 as permitted sender) client-ip=149.202.180.44;
Authentication-Results: mx.google.com;
dkim=pass header.i=@flectrahq.com header.s=mail header.b=MCzhjB9b;
spf=pass (google.com: domain of soup@flectrahq.com designates 149.202.180.44 as permitted sender) smtp.mailfrom=soup@flectrahq.com;
dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=flectrahq.com
Received: from [10.10.31.24] (unknown [91.183.114.50])
(Authenticated sender: soup)
by mail2.flectrahq.com (Postfix) with ESMTPSA id 7B571A4085
for <what@flectrahq.com>; Mon, 5 Mar 2018 10:19:21 +0100 (CET)
DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=flectrahq.com; s=mail;
t=1520241562; bh=L2r7Sp/vjogIdM1k8H9zDGDjnhKolsTTLLjndnFC4Jc=;
h=To:From:Subject:Date:From;
b=MCzhjB9bnsrJ3uKjq+GjujFxmtrq3fc7Vv7Vg2C72EPKnkxgqy6yPjWKtXbBlaiT3
YjKI24aiSQlOeOPQiqFgiDzeqqemNDp+CRuhoYz1Vbz+ESRaHtkWRLb7ZjvohS2k7e
RTq7tUxY2nUL2YrNHV7DFYtJVBwiTuyLP6eAiJdE=
To: what@flectrahq.com
From: Soup <soup@flectrahq.com>
Subject: =?UTF-8?Q?Soupe_du_jour_:_Pois_cass=c3=a9s?=
Message-ID: <a05d8334-7b7c-df68-c96a-4a88ed19f31b@flectrahq.com>
Date: Mon, 5 Mar 2018 10:19:21 +0100
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101
Thunderbird/52.6.0
MIME-Version: 1.0
Content-Type: multipart/alternative;
boundary="------------1F2D18B1129FC2F0B9EECF50"
Content-Language: en-US
X-Spam-Status: No, score=-1.2 required=5.0 tests=ALL_TRUSTED,BAYES_00,
HTML_IMAGE_ONLY_08,HTML_MESSAGE,T_REMOTE_IMAGE autolearn=no
autolearn_force=no version=3.4.0
X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on mail2.flectrahq.com
This is a multi-part message in MIME format.
--------------1F2D18B1129FC2F0B9EECF50
Content-Type: text/plain; charset=utf-8; format=flowed
Content-Transfer-Encoding: 8bit
Résultat de recherche d'images pour "dessin la princesse au petit pois"
--
Soup
Odoo S.A.
Chaussée de Namur, 40
B-1367 Grand Rosière
Web: http://www.flectrahq.com
--------------1F2D18B1129FC2F0B9EECF50
Content-Type: text/html; charset=utf-8
Content-Transfer-Encoding: 8bit
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
</head>
<body text="#000000" bgcolor="#FFFFFF">
<p><img
src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQjCNAadd3NDM8g9w0P_-gAVYrrqC0wmBNYKGsTZ2Pst5SsNxTRnA"
alt="Résultat de recherche d'images pour &quot;dessin la
princesse au petit pois&quot;"></p>
<pre class="moz-signature" cols="72">--
Soup
Odoo S.A.
Chaussée de Namur, 40
B-1367 Grand Rosière
Web: <a class="moz-txt-link-freetext" href="http://www.flectrahq.com">http://www.flectrahq.com</a> </pre>
</body>
</html>
--------------1F2D18B1129FC2F0B9EECF50--
--------------A6B5FD5F68F4D73ECD739009--"""
MAIL_XHTML = """Return-Path: <xxxx@xxxx.com>
Received: from xxxx.internal (xxxx.xxxx.internal [1.1.1.1])
by xxxx (xxxx 1.1.1-111-g972eecc-slipenbois) with LMTPA;
Fri, 13 Apr 2018 22:11:52 -0400
X-Cyrus-Session-Id: sloti35d1t38-1111111-11111111111-5-11111111111111111111
X-Sieve: CMU Sieve 1.0
X-Spam-known-sender: no ("Email failed DMARC policy for domain"); in-addressbook
X-Spam-score: 0.0
X-Spam-hits: ALL_TRUSTED -1, BAYES_00 -1.9, FREEMAIL_FROM 0.001,
HTML_FONT_LOW_CONTRAST 0.001, HTML_MESSAGE 0.001, SPF_SOFTFAIL 0.665,
LANGUAGES en, BAYES_USED global, SA_VERSION 1.1.0
X-Spam-source: IP='1.1.1.1', Host='unk', Country='unk', FromHeader='com',
MailFrom='com'
X-Spam-charsets: plain='utf-8', html='utf-8'
X-IgnoreVacation: yes ("Email failed DMARC policy for domain")
X-Resolved-to: catchall@xxxx.xxxx
X-Delivered-to: catchall@xxxx.xxxx
X-Mail-from: xxxx@xxxx.com
Received: from mx4 ([1.1.1.1])
by xxxx.internal (LMTPProxy); Fri, 13 Apr 2018 22:11:52 -0400
Received: from xxxx.xxxx.com (localhost [127.0.0.1])
by xxxx.xxxx.internal (Postfix) with ESMTP id E1111C1111;
Fri, 13 Apr 2018 22:11:51 -0400 (EDT)
Received: from xxxx.xxxx.internal (localhost [127.0.0.1])
by xxxx.xxxx.com (Authentication Milter) with ESMTP
id BBDD1111D1A;
Fri, 13 Apr 2018 22:11:51 -0400
ARC-Authentication-Results: i=1; xxxx.xxxx.com; arc=none (no signatures found);
dkim=pass (2048-bit rsa key sha256) header.d=xxxx.com header.i=@xxxx.com header.b=P1aaAAaa x-bits=2048 x-keytype=rsa x-algorithm=sha256 x-selector=fm2;
dmarc=fail (p=none,d=none) header.from=xxxx.com;
iprev=pass policy.iprev=1.1.1.1 (out1-smtp.xxxx.com);
spf=softfail smtp.mailfrom=xxxx@xxxx.com smtp.helo=out1-smtp.xxxx.com;
x-aligned-from=pass (Address match);
x-cm=none score=0;
x-ptr=pass x-ptr-helo=out1-smtp.xxxx.com x-ptr-lookup=out1-smtp.xxxx.com;
x-return-mx=pass smtp.domain=xxxx.com smtp.result=pass smtp_is_org_domain=yes header.domain=xxxx.com header.result=pass header_is_org_domain=yes;
x-tls=pass version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128;
x-vs=clean score=0 state=0
Authentication-Results: xxxx.xxxx.com;
arc=none (no signatures found);
dkim=pass (2048-bit rsa key sha256) header.d=xxxx.com header.i=@xxxx.com header.b=P1awJPiy x-bits=2048 x-keytype=rsa x-algorithm=sha256 x-selector=fm2;
dmarc=fail (p=none,d=none) header.from=xxxx.com;
iprev=pass policy.iprev=66.111.4.25 (out1-smtp.xxxx.com);
spf=softfail smtp.mailfrom=xxxx@xxxx.com smtp.helo=out1-smtp.xxxx.com;
x-aligned-from=pass (Address match);
x-cm=none score=0;
x-ptr=pass x-ptr-helo=out1-smtp.xxxx.com x-ptr-lookup=out1-smtp.xxxx.com;
x-return-mx=pass smtp.domain=xxxx.com smtp.result=pass smtp_is_org_domain=yes header.domain=xxxx.com header.result=pass header_is_org_domain=yes;
x-tls=pass version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128;
x-vs=clean score=0 state=0
X-ME-VSCategory: clean
X-ME-CMScore: 0
X-ME-CMCategory: none
Received-SPF: softfail
(gmail.com ... _spf.xxxx.com: Sender is not authorized by default to use 'xxxx@xxxx.com' in 'mfrom' identity, however domain is not currently prepared for false failures (mechanism '~all' matched))
receiver=xxxx.xxxx.com;
identity=mailfrom;
envelope-from="xxxx@xxxx.com";
helo=out1-smtp.xxxx.com;
client-ip=1.1.1.1
Received: from xxxx.xxxx.internal (gateway1.xxxx.internal [1.1.1.1])
(using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits))
(No client certificate requested)
by xxxx.xxxx.internal (Postfix) with ESMTPS;
Fri, 13 Apr 2018 22:11:51 -0400 (EDT)
Received: from compute3.internal (xxxx.xxxx.internal [10.202.2.43])
by xxxx.xxxx.internal (Postfix) with ESMTP id 8BD5B21BBD;
Fri, 13 Apr 2018 22:11:51 -0400 (EDT)
Received: from xxxx ([10.202.2.163])
by xxxx.internal (MEProxy); Fri, 13 Apr 2018 22:11:51 -0400
X-ME-Sender: <xms:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa>
Received: from [1.1.1.1] (unknown [1.1.1.1])
by mail.xxxx.com (Postfix) with ESMTPA id BF5E1111D
for <catchall@xxxx.xxxx>; Fri, 13 Apr 2018 22:11:50 -0400 (EDT)
From: "xxxx xxxx" <xxxx@xxxx.com>
To: "xxxx" <catchall@xxxx.xxxx>
Subject: Re: xxxx (Ref PO1)
Date: Sat, 14 Apr 2018 02:11:42 +0000
Message-Id: <em67f5c44a-xxxx-xxxx-xxxx-69f56d618a94@wswin7hg4n4l1ce>
In-Reply-To: <829228111124527.1111111602.256611118262939-openerp-129-xxxx.xxxx@ip-1-1-1-1>
References: <867911111953277.1523671337.187951111160400-openerp-129-xxxx.xxxx@ip-1-1-1-1>
<867911111953277.1523671337.256611118262939-openerp-129-xxxx.xxxx@ip-1-1-1-1>
Reply-To: "xxxx xxxx" <xxxx@xxxx.com>
User-Agent: eM_Client/7.0.26687.0
Mime-Version: 1.0
Content-Type: multipart/alternative;
boundary="------=_MB48E455BD-1111-42EC-1111-886CDF48905E"
--------=_MB48E455BD-1111-42EC-1111-886CDF48905E
Content-Type: text/plain; format=flowed; charset=utf-8
Content-Transfer-Encoding: quoted-printable
xxxx
------ Original Message ------
From: "xxxx" <xxxx@xxxx.com>
To: "xxxx" <xxxx@xxxx.com>
Sent: 4/13/2018 7:06:43 PM
Subject: xxxx
>xxxx
--------=_MB48E455BD-1111-42EC-1111-886CDF48905E
Content-Type: text/html; charset=utf-8
Content-Transfer-Encoding: quoted-printable
<?xml version=3D"1.0" encoding=3D"utf-16"?><html><head><style type=3D"text/=
css"><!--blockquote.cite
{margin-left: 5px; margin-right: 0px; padding-left: 10px; padding-right:=
0px; border-left-width: 1px; border-left-style: solid; border-left-color:=
rgb(204, 204, 204);}
blockquote.cite2
{margin-left: 5px; margin-right: 0px; padding-left: 10px; padding-right:=
0px; border-left-width: 1px; border-left-style: solid; border-left-color:=
rgb(204, 204, 204); margin-top: 3px; padding-top: 0px;}
a img
{border: 0px;}
body
{font-family: Tahoma; font-size: 12pt;}
--></style></head><body><div>this is a reply to PO200109 from emClient</div=
><div id=3D"signature_old"><div style=3D"font-family: Tahoma; font-size:=
12 pt;">-- <br /><span><span class=3D"__postbox-detected-content __postbox=
-detected-address" style=3D"TEXT-DECORATION: underline; COLOR: rgb(115,133,=
172); PADDING-BOTTOM: 0pt; PADDING-TOP: 0pt; PADDING-LEFT: 0pt; DISPLAY:=
inline; PADDING-RIGHT: 0pt" __postbox-detected-content=3D"__postbox-detect=
ed-address"></span>xxxx<br />xxxx<br /><b=
r />xxxx</span></=
div></div><div><br /></div><div><br /></div><div><br /></div>
<div>------ Original Message ------</div>
<div>From: "xxxx" &lt;<a href=3D"mailto:xxxx@xxxx.com">xxxx=
@xxxx.com</a>&gt;</div>
<div>To: "xxxx" &lt;<a href=3D"mailto:xxxx@xxxx.com">a=
xxxx@xxxx.com</a>&gt;</div>
<div>Sent: 4/13/2018 7:06:43 PM</div>
<div>Subject: xxxx</div><div><br /></div=
>
<div id=3D"x00b4101ba6e64ce"><blockquote cite=3D"829228972724527.1523671602=
.256660938262939-openerp-129-xxxx.xxxx@ip-1-1-1-1" type=3D"cite"=
class=3D"cite2">
<table border=3D"0" width=3D"100%" cellpadding=3D"0" bgcolor=3D"#ededed"=
style=3D"padding: 20px; background-color: #ededed" summary=3D"o_mail_notif=
ication">
<tbody>
<!-- HEADER -->
<tr>
<td align=3D"center" style=3D"min-width: 590px;">
<table width=3D"590" border=3D"0" cellpadding=3D=
"0" bgcolor=3D"#875A7B" style=3D"min-width: 590px; background-color: rgb(13=
5,90,123); padding: 20px;">
<tbody><tr>
<td valign=3D"middle">
<span style=3D"font-size:20px; color:whit=
e; font-weight: bold;">
mangez des saucisses
</span>
</td>
<td valign=3D"middle" align=3D"right">
<img src=3D"http://erp.xxxx.xxxx/logo.png=
" style=3D"padding: 0px; margin: 0px; height: auto; width: 80px;" alt=3D=
"xxxx" />
</td>
</tr>
</tbody></table>
</td>
</tr>
<!-- CONTENT -->
<tr>
<td align=3D"center" style=3D"min-width: 590px;">
<table width=3D"590" border=3D"0" cellpadding=3D=
"0" bgcolor=3D"#ffffff" style=3D"min-width: 590px; background-color: rgb(25=
5, 255, 255); padding: 20px;">
<tbody>
<tr><td valign=3D"top" style=3D"font-family:A=
rial,Helvetica,sans-serif; color: #555; font-size: 14px;">
<p style=3D"margin: 0px 0px 9px 0px; font-s=
ize: 13px; font-family: &quot;Lucida Grande&quot;, Helvetica, Verdana, Aria=
l, sans-serif">xxxx.=20
,</p>
<p style=3D"margin: 0px 0px 9px 0px; font-size: 13px; font-family: &quot;Lu=
cida Grande&quot;, Helvetica, Verdana, Arial, sans-serif">
xxxx.
</p>
<p style=3D"margin: 0px 0px 9px 0px; font-size: 13px; font-family: &quot;Lu=
cida Grande&quot;, Helvetica, Verdana, Arial, sans-serif">You can reply =
to this email if you have any questions.</p>
<p style=3D"margin: 0px 0px 9px 0px; font-size: 13px; font-family: &quot;Lu=
cida Grande&quot;, Helvetica, Verdana, Arial, sans-serif">Thank you,</p>
</td>
</tr></tbody>
</table>
</td>
</tr>
<!-- FOOTER -->
<tr>
<td align=3D"center" style=3D"min-width: 590px;">
<table width=3D"590" border=3D"0" cellpadding=3D=
"0" bgcolor=3D"#875A7B" style=3D"min-width: 590px; background-color: rgb(13=
5,90,123); padding: 20px;">
<tbody><tr>
<td valign=3D"middle" align=3D"left" style=
=3D"color: #fff; padding-top: 10px; padding-bottom: 10px; font-size: 12px;"=
>
xxxx<br />
+1-801-980-4240
</td>
<td valign=3D"middle" align=3D"right" style=
=3D"color: #fff; padding-top: 10px; padding-bottom: 10px; font-size: 12px;"=
>
<a href=3D"http://erp.xxxx.xxxx/info@xxxx-a=
aa.com" style=3D"text-decoration:none; color: white;">info@aust-mfg.com</a>=
<br />
<a href=3D"http://www.xxxx=
.com" style=3D"text-decoration:none; color: white;">
http://www.xxxx.com
</a>
</td>
</tr>
</tbody></table>
</td>
</tr>
<tr>
<td align=3D"center">
Powered by <a href=3D"https://www.flectrahq.com">Odo=
o</a>.
</td>
</tr>
</tbody>
</table>
=20
<pre style=3D"white-space: pre-wrap">xxxx.
</pre>
</blockquote></div>
</body></html>
--------=_MB48E455BD-2850-42EC-B1CA-886CDF48905E--"""
MAIL_XHTML = """Return-Path: <xxxx@xxxx.com>
Received: from xxxx.internal (xxxx.xxxx.internal [1.1.1.1])
by xxxx (xxxx 1.1.1-111-g972eecc-slipenbois) with LMTPA;
Fri, 13 Apr 2018 22:11:52 -0400
X-Cyrus-Session-Id: sloti35d1t38-1111111-11111111111-5-11111111111111111111
X-Sieve: CMU Sieve 1.0
X-Spam-known-sender: no ("Email failed DMARC policy for domain"); in-addressbook
X-Spam-score: 0.0
X-Spam-hits: ALL_TRUSTED -1, BAYES_00 -1.9, FREEMAIL_FROM 0.001,
HTML_FONT_LOW_CONTRAST 0.001, HTML_MESSAGE 0.001, SPF_SOFTFAIL 0.665,
LANGUAGES en, BAYES_USED global, SA_VERSION 1.1.0
X-Spam-source: IP='1.1.1.1', Host='unk', Country='unk', FromHeader='com',
MailFrom='com'
X-Spam-charsets: plain='utf-8', html='utf-8'
X-IgnoreVacation: yes ("Email failed DMARC policy for domain")
X-Resolved-to: catchall@xxxx.xxxx
X-Delivered-to: catchall@xxxx.xxxx
X-Mail-from: xxxx@xxxx.com
Received: from mx4 ([1.1.1.1])
by xxxx.internal (LMTPProxy); Fri, 13 Apr 2018 22:11:52 -0400
Received: from xxxx.xxxx.com (localhost [127.0.0.1])
by xxxx.xxxx.internal (Postfix) with ESMTP id E1111C1111;
Fri, 13 Apr 2018 22:11:51 -0400 (EDT)
Received: from xxxx.xxxx.internal (localhost [127.0.0.1])
by xxxx.xxxx.com (Authentication Milter) with ESMTP
id BBDD1111D1A;
Fri, 13 Apr 2018 22:11:51 -0400
ARC-Authentication-Results: i=1; xxxx.xxxx.com; arc=none (no signatures found);
dkim=pass (2048-bit rsa key sha256) header.d=xxxx.com header.i=@xxxx.com header.b=P1aaAAaa x-bits=2048 x-keytype=rsa x-algorithm=sha256 x-selector=fm2;
dmarc=fail (p=none,d=none) header.from=xxxx.com;
iprev=pass policy.iprev=1.1.1.1 (out1-smtp.xxxx.com);
spf=softfail smtp.mailfrom=xxxx@xxxx.com smtp.helo=out1-smtp.xxxx.com;
x-aligned-from=pass (Address match);
x-cm=none score=0;
x-ptr=pass x-ptr-helo=out1-smtp.xxxx.com x-ptr-lookup=out1-smtp.xxxx.com;
x-return-mx=pass smtp.domain=xxxx.com smtp.result=pass smtp_is_org_domain=yes header.domain=xxxx.com header.result=pass header_is_org_domain=yes;
x-tls=pass version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128;
x-vs=clean score=0 state=0
Authentication-Results: xxxx.xxxx.com;
arc=none (no signatures found);
dkim=pass (2048-bit rsa key sha256) header.d=xxxx.com header.i=@xxxx.com header.b=P1awJPiy x-bits=2048 x-keytype=rsa x-algorithm=sha256 x-selector=fm2;
dmarc=fail (p=none,d=none) header.from=xxxx.com;
iprev=pass policy.iprev=66.111.4.25 (out1-smtp.xxxx.com);
spf=softfail smtp.mailfrom=xxxx@xxxx.com smtp.helo=out1-smtp.xxxx.com;
x-aligned-from=pass (Address match);
x-cm=none score=0;
x-ptr=pass x-ptr-helo=out1-smtp.xxxx.com x-ptr-lookup=out1-smtp.xxxx.com;
x-return-mx=pass smtp.domain=xxxx.com smtp.result=pass smtp_is_org_domain=yes header.domain=xxxx.com header.result=pass header_is_org_domain=yes;
x-tls=pass version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128;
x-vs=clean score=0 state=0
X-ME-VSCategory: clean
X-ME-CMScore: 0
X-ME-CMCategory: none
Received-SPF: softfail
(gmail.com ... _spf.xxxx.com: Sender is not authorized by default to use 'xxxx@xxxx.com' in 'mfrom' identity, however domain is not currently prepared for false failures (mechanism '~all' matched))
receiver=xxxx.xxxx.com;
identity=mailfrom;
envelope-from="xxxx@xxxx.com";
helo=out1-smtp.xxxx.com;
client-ip=1.1.1.1
Received: from xxxx.xxxx.internal (gateway1.xxxx.internal [1.1.1.1])
(using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits))
(No client certificate requested)
by xxxx.xxxx.internal (Postfix) with ESMTPS;
Fri, 13 Apr 2018 22:11:51 -0400 (EDT)
Received: from compute3.internal (xxxx.xxxx.internal [10.202.2.43])
by xxxx.xxxx.internal (Postfix) with ESMTP id 8BD5B21BBD;
Fri, 13 Apr 2018 22:11:51 -0400 (EDT)
Received: from xxxx ([10.202.2.163])
by xxxx.internal (MEProxy); Fri, 13 Apr 2018 22:11:51 -0400
X-ME-Sender: <xms:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa>
Received: from [1.1.1.1] (unknown [1.1.1.1])
by mail.xxxx.com (Postfix) with ESMTPA id BF5E1111D
for <catchall@xxxx.xxxx>; Fri, 13 Apr 2018 22:11:50 -0400 (EDT)
From: "xxxx xxxx" <xxxx@xxxx.com>
To: "xxxx" <catchall@xxxx.xxxx>
Subject: Re: xxxx (Ref PO1)
Date: Sat, 14 Apr 2018 02:11:42 +0000
Message-Id: <em67f5c44a-xxxx-xxxx-xxxx-69f56d618a94@wswin7hg4n4l1ce>
In-Reply-To: <829228111124527.1111111602.256611118262939-openerp-129-xxxx.xxxx@ip-1-1-1-1>
References: <867911111953277.1523671337.187951111160400-openerp-129-xxxx.xxxx@ip-1-1-1-1>
<867911111953277.1523671337.256611118262939-openerp-129-xxxx.xxxx@ip-1-1-1-1>
Reply-To: "xxxx xxxx" <xxxx@xxxx.com>
User-Agent: eM_Client/7.0.26687.0
Mime-Version: 1.0
Content-Type: multipart/alternative;
boundary="------=_MB48E455BD-1111-42EC-1111-886CDF48905E"
--------=_MB48E455BD-1111-42EC-1111-886CDF48905E
Content-Type: text/plain; format=flowed; charset=utf-8
Content-Transfer-Encoding: quoted-printable
xxxx
------ Original Message ------
From: "xxxx" <xxxx@xxxx.com>
To: "xxxx" <xxxx@xxxx.com>
Sent: 4/13/2018 7:06:43 PM
Subject: xxxx
>xxxx
--------=_MB48E455BD-1111-42EC-1111-886CDF48905E
Content-Type: text/html; charset=utf-8
Content-Transfer-Encoding: quoted-printable
<?xml version=3D"1.0" encoding=3D"utf-16"?><html><head><style type=3D"text/=
css"><!--blockquote.cite
{margin-left: 5px; margin-right: 0px; padding-left: 10px; padding-right:=
0px; border-left-width: 1px; border-left-style: solid; border-left-color:=
rgb(204, 204, 204);}
blockquote.cite2
{margin-left: 5px; margin-right: 0px; padding-left: 10px; padding-right:=
0px; border-left-width: 1px; border-left-style: solid; border-left-color:=
rgb(204, 204, 204); margin-top: 3px; padding-top: 0px;}
a img
{border: 0px;}
body
{font-family: Tahoma; font-size: 12pt;}
--></style></head><body><div>this is a reply to PO200109 from emClient</div=
><div id=3D"signature_old"><div style=3D"font-family: Tahoma; font-size:=
12 pt;">-- <br /><span><span class=3D"__postbox-detected-content __postbox=
-detected-address" style=3D"TEXT-DECORATION: underline; COLOR: rgb(115,133,=
172); PADDING-BOTTOM: 0pt; PADDING-TOP: 0pt; PADDING-LEFT: 0pt; DISPLAY:=
inline; PADDING-RIGHT: 0pt" __postbox-detected-content=3D"__postbox-detect=
ed-address"></span>xxxx<br />xxxx<br /><b=
r />xxxx</span></=
div></div><div><br /></div><div><br /></div><div><br /></div>
<div>------ Original Message ------</div>
<div>From: "xxxx" &lt;<a href=3D"mailto:xxxx@xxxx.com">xxxx=
@xxxx.com</a>&gt;</div>
<div>To: "xxxx" &lt;<a href=3D"mailto:xxxx@xxxx.com">a=
xxxx@xxxx.com</a>&gt;</div>
<div>Sent: 4/13/2018 7:06:43 PM</div>
<div>Subject: xxxx</div><div><br /></div=
>
<div id=3D"x00b4101ba6e64ce"><blockquote cite=3D"829228972724527.1523671602=
.256660938262939-openerp-129-xxxx.xxxx@ip-1-1-1-1" type=3D"cite"=
class=3D"cite2">
<table border=3D"0" width=3D"100%" cellpadding=3D"0" bgcolor=3D"#ededed"=
style=3D"padding: 20px; background-color: #ededed" summary=3D"o_mail_notif=
ication">
<tbody>
<!-- HEADER -->
<tr>
<td align=3D"center" style=3D"min-width: 590px;">
<table width=3D"590" border=3D"0" cellpadding=3D=
"0" bgcolor=3D"#875A7B" style=3D"min-width: 590px; background-color: rgb(13=
5,90,123); padding: 20px;">
<tbody><tr>
<td valign=3D"middle">
<span style=3D"font-size:20px; color:whit=
e; font-weight: bold;">
mangez des saucisses
</span>
</td>
<td valign=3D"middle" align=3D"right">
<img src=3D"http://erp.xxxx.xxxx/logo.png=
" style=3D"padding: 0px; margin: 0px; height: auto; width: 80px;" alt=3D=
"xxxx" />
</td>
</tr>
</tbody></table>
</td>
</tr>
<!-- CONTENT -->
<tr>
<td align=3D"center" style=3D"min-width: 590px;">
<table width=3D"590" border=3D"0" cellpadding=3D=
"0" bgcolor=3D"#ffffff" style=3D"min-width: 590px; background-color: rgb(25=
5, 255, 255); padding: 20px;">
<tbody>
<tr><td valign=3D"top" style=3D"font-family:A=
rial,Helvetica,sans-serif; color: #555; font-size: 14px;">
<p style=3D"margin: 0px 0px 9px 0px; font-s=
ize: 13px; font-family: &quot;Lucida Grande&quot;, Helvetica, Verdana, Aria=
l, sans-serif">xxxx.=20
,</p>
<p style=3D"margin: 0px 0px 9px 0px; font-size: 13px; font-family: &quot;Lu=
cida Grande&quot;, Helvetica, Verdana, Arial, sans-serif">
xxxx.
</p>
<p style=3D"margin: 0px 0px 9px 0px; font-size: 13px; font-family: &quot;Lu=
cida Grande&quot;, Helvetica, Verdana, Arial, sans-serif">You can reply =
to this email if you have any questions.</p>
<p style=3D"margin: 0px 0px 9px 0px; font-size: 13px; font-family: &quot;Lu=
cida Grande&quot;, Helvetica, Verdana, Arial, sans-serif">Thank you,</p>
</td>
</tr></tbody>
</table>
</td>
</tr>
<!-- FOOTER -->
<tr>
<td align=3D"center" style=3D"min-width: 590px;">
<table width=3D"590" border=3D"0" cellpadding=3D=
"0" bgcolor=3D"#875A7B" style=3D"min-width: 590px; background-color: rgb(13=
5,90,123); padding: 20px;">
<tbody><tr>
<td valign=3D"middle" align=3D"left" style=
=3D"color: #fff; padding-top: 10px; padding-bottom: 10px; font-size: 12px;"=
>
xxxx<br />
+1-801-980-4240
</td>
<td valign=3D"middle" align=3D"right" style=
=3D"color: #fff; padding-top: 10px; padding-bottom: 10px; font-size: 12px;"=
>
<a href=3D"http://erp.xxxx.xxxx/info@xxxx-a=
aa.com" style=3D"text-decoration:none; color: white;">info@aust-mfg.com</a>=
<br />
<a href=3D"http://www.xxxx=
.com" style=3D"text-decoration:none; color: white;">
http://www.xxxx.com
</a>
</td>
</tr>
</tbody></table>
</td>
</tr>
<tr>
<td align=3D"center">
Powered by <a href=3D"https://www.flectrahq.com">Odo=
o</a>.
</td>
</tr>
</tbody>
</table>
=20
<pre style=3D"white-space: pre-wrap">xxxx.
</pre>
</blockquote></div>
</body></html>
--------=_MB48E455BD-2850-42EC-B1CA-886CDF48905E--"""
class TestMailgateway(TestMail):
@ -327,7 +951,7 @@ class TestMailgateway(TestMail):
'subject': 'Public Discussion',
'message_type': 'email',
'author_id': self.partner_1.id,
'message_id': '<123456-flectra-%s-mail.test@%s>' % (self.test_public.id, socket.gethostname()),
'message_id': '<123456-openerp-%s-mail.test@%s>' % (self.test_public.id, socket.gethostname()),
})
@mute_logger('flectra.addons.mail.models.mail_thread')
@ -366,6 +990,12 @@ class TestMailgateway(TestMail):
self.assertEqual(res['body'], '')
self.assertEqual(res['attachments'][0][0], 'thetruth.pdf')
@mute_logger('flectra.addons.mail.models.mail_thread')
def test_message_parse_xhtml(self):
""" Test that the parsing of mail with embedded emails as eml(msg) which generates empty attachments, can be processed.
"""
self.env['mail.thread'].message_process('mail.channel', MAIL_XHTML)
@mute_logger('flectra.addons.mail.models.mail_thread')
def test_message_process_cid(self):
new_groups = self.format_and_process(MAIL_MULTIPART_IMAGE, subject='My Frogs', to='groups@example.com')
@ -528,13 +1158,7 @@ class TestMailgateway(TestMail):
'message_process: email should be sent to Sylvie')
# TODO : the author of a message post on mail.test should not be added as follower
# FAIL ON 'message_process: after reply, group should have 2 followers') ` AssertionError: res.partner(104,) != res.partner(104, 105) : message_process: after reply, group should have 2 followers
# Test: author (and not recipient) added as follower
# self.assertEqual(self.test_public.message_partner_ids, self.partner_1 | self.partner_2,
# 'message_process: after reply, group should have 2 followers')
# self.assertEqual(self.test_public.message_channel_ids, self.env['mail.test'],
# 'message_process: after reply, group should have 2 followers (0 channels)')
@mute_logger('flectra.addons.mail.models.mail_thread', 'flectra.models')
def test_message_process_in_reply_to(self):
@ -569,6 +1193,7 @@ class TestMailgateway(TestMail):
MAIL_TEMPLATE, to='erroneous@example.com',
extra='References: <2233@a.com>\r\n\t<3edss_dsa@b.com> %s' % self.fake_email.message_id,
msg_id='<1198923581.41972151344608186800.JavaMail.4@agrolait.com>')
self.assertEqual(len(self.test_public.message_ids), 2, 'message_process: group should contain one new message')
self.assertEqual(len(self.fake_email.child_ids), 1, 'message_process: new message should be children of the existing one')
@ -581,10 +1206,10 @@ class TestMailgateway(TestMail):
extra='References: <2233@a.com>\r\n\t<3edss_dsa@b.com> %s' % self.fake_email.message_id,
msg_id='<1198923581.41972151344608186800.JavaMail.4@agrolait.com>',
target_model='mail.channel')
self.assertEqual(len(self.test_public.message_ids), 1, 'message_process: group should not contain new message')
self.assertEqual(len(self.fake_email.child_ids), 0, 'message_process: original email should not contain childs')
self.assertEqual(res_test.name, 'My Dear Forward')
self.assertEqual(len(res_test.message_ids), 1)
self.assertEqual(len(self.test_public.message_ids), 2, 'message_process: group should not contain new message')
self.assertEqual(len(self.fake_email.child_ids), 1, 'message_process: original email should not contain childs')
self.assertEqual(len(res_test.message_ids), 0)
@mute_logger('flectra.addons.mail.models.mail_thread', 'flectra.models')
def test_message_process_references_forward_cc(self):
@ -606,11 +1231,11 @@ class TestMailgateway(TestMail):
self.format_and_process,
MAIL_TEMPLATE, email_from='valid.lelitre@agrolait.com',
to='noone@example.com', subject='spam',
extra='In-Reply-To: <12321321-flectra-%d-mail.test@%s>' % (self.test_public.id, socket.gethostname()),
extra='In-Reply-To: <12321321-openerp-%d-mail.test@%s>' % (self.test_public.id, socket.gethostname()),
msg_id='<1198923581.41972151344608186802.JavaMail.diff1@agrolait.com>')
# when 6.1 messages are present, compat mode is available
# Flectra 10 update: compat mode has been removed and should not work anymore
# Odoo 10 update: compat mode has been removed and should not work anymore
self.fake_email.write({'message_id': False})
# Do: compat mode accepts partial-matching emails
self.assertRaises(
@ -619,16 +1244,16 @@ class TestMailgateway(TestMail):
MAIL_TEMPLATE, email_from='other5@gmail.com',
msg_id='<1.2.JavaMail.new@agrolait.com>',
to='noone@example.com>', subject='spam',
extra='In-Reply-To: <12321321-flectra-%d-mail.test@%s>' % (self.test_public.id, socket.gethostname()))
extra='In-Reply-To: <12321321-openerp-%d-mail.test@%s>' % (self.test_public.id, socket.gethostname()))
# 3''. 6.1 compat mode should not work if hostname does not match!
# Flectra 10 update: compat mode has been removed and should not work anymore and does not depend from hostname
# Odoo 10 update: compat mode has been removed and should not work anymore and does not depend from hostname
self.assertRaises(ValueError,
self.format_and_process,
MAIL_TEMPLATE, email_from='other5@gmail.com',
msg_id='<1.3.JavaMail.new@agrolait.com>',
to='noone@example.com>', subject='spam',
extra='In-Reply-To: <12321321-flectra-%d-mail.test@neighbor.com>' % self.test_public.id)
extra='In-Reply-To: <12321321-openerp-%d-mail.test@neighbor.com>' % self.test_public.id)
# Test created messages
self.assertEqual(len(self.test_public.message_ids), 1)

View File

@ -23,7 +23,7 @@
</div>
</div>
</div>
<div class="col-xs-12 col-md-6 o_setting_box" title="Using your own email server is required to send/receive emails in Community and Enterprise versions. Online users already benefit from a ready-to-use email server (@mycompany.flectra.com).">
<div class="col-xs-12 col-md-6 o_setting_box" title="Using your own email server is required to send/receive emails in Community versions. Online users already benefit from a ready-to-use email server (@mycompany.flectrahq.com).">
<div class="o_setting_left_pane">
<field name="default_external_email_server"/>
</div>

View File

@ -11,6 +11,11 @@ from flectra.tools import consteq
class MassMailController(http.Controller):
@http.route(['/unsubscribe_from_list'], type='http', website=True, multilang=False, auth='public')
def unsubscribe_placeholder_link(self, **post):
"""Dummy route so placeholder is not prefixed by language, MUST have multilang=False"""
raise werkzeug.exceptions.NotFound()
@http.route(['/mail/mailing/<int:mailing_id>/unsubscribe'], type='http', website=True, auth='public')
def mailing(self, mailing_id, email=None, res_id=None, token="", **post):
mailing = request.env['mail.mass_mailing'].sudo().browse(mailing_id)

View File

@ -92,7 +92,7 @@ class MailMail(models.Model):
def send_get_email_dict(self, partner=None):
# TDE: temporary addition (mail was parameter) due to semi-new-API
res = super(MailMail, self).send_get_email_dict(partner)
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url').rstrip('/')
if self.mailing_id and res.get('body') and res.get('email_to'):
emails = tools.email_split(res.get('email_to')[0])
email_to = emails and emails[0] or False

View File

@ -681,6 +681,7 @@ class MassMailing(models.Model):
'mass_mailing_id': mailing.id,
'mailing_list_ids': [(4, l.id) for l in mailing.contact_list_ids],
'no_auto_thread': mailing.reply_to_mode != 'thread',
'template_id': None,
}
if mailing.reply_to_mode == 'email':
composer_values['reply_to'] = mailing.reply_to

View File

@ -38,7 +38,7 @@ FieldTextHtml.include({
var datarecord = this._super();
if (this.model === 'mail.mass_mailing') {
// these fields can potentially get very long, let's remove them
datarecord = _.omit(datarecord, ['mailing_domain', 'contact_list_ids', 'body_html']);
datarecord = _.omit(datarecord, ['mailing_domain', 'contact_list_ids', 'body_html', 'attachment_ids']);
}
return datarecord;
},

View File

@ -46,6 +46,11 @@
background-color: @o_mm_def_color_alpha;
}
td {
// Default browser style but needed so that alignment works on some mail
// clients (see transcoder)
text-align: inherit;
}
// ===== Layout =====
.o_layout {

View File

@ -26,7 +26,7 @@ class TestMassMailing(models.TransientModel):
'reply_to': mailing.reply_to,
'email_to': test_mail,
'subject': mailing.name,
'body_html': mailing.body_html,
'body_html': tools.html_sanitize(mailing.body_html, sanitize_attributes=True, sanitize_style=True, strip_classes=True),
'notification': True,
'mailing_id': mailing.id,
'attachment_ids': [(4, attachment.id) for attachment in mailing.attachment_ids],

View File

@ -10,7 +10,9 @@
name="action_mass_mailing_attendees"
icon="fa-envelope-o"
attrs="{'invisible': [('seats_expected', '=', 0)]}">
Mail Attendees
<div class="o_field_widget o_stat_info">
<span class="o_stat_text">Mail Attendees</span>
</div>
</button>
</button>
</field>

View File

@ -16,7 +16,7 @@ class Event(models.Model):
view_mode='form',
target='current',
context=dict(
default_mailing_model=self.env.ref('website_event_track.model_event_track').id,
default_mailing_model_id=self.env.ref('website_event_track.model_event_track').id,
default_mailing_domain="[('event_id', 'in', %s), ('stage_id.is_cancel', '!=', True)]" % self.ids,
),
)

View File

@ -100,7 +100,7 @@ class PortalChatter(http.Controller):
raise Forbidden()
# Non-employee see only messages with not internal subtype (aka, no internal logs)
if not request.env['res.users'].has_group('base.group_user'):
domain = expression.AND([['&', '&', ('subtype_id', '!=', False), ('subtype_id.internal', '=', False)], domain])
domain = expression.AND([['&', ('subtype_id', '!=', False), ('subtype_id.internal', '=', False)], domain])
Message = request.env['mail.message'].sudo()
return {
'messages': Message.search(domain, limit=limit, offset=offset).portal_message_format(),

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from . import ir_http
from . import mail_thread
from . import mail_message
from . import portal_mixin

View File

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from flectra import api, fields, models
from flectra.osv import expression
class IrHttp(models.AbstractModel):
_inherit = 'ir.http'
@classmethod
def _get_translation_frontend_modules_domain(cls):
domain = super(IrHttp, cls)._get_translation_frontend_modules_domain()
return expression.OR([domain, [('name', '=', 'portal')]])

View File

@ -10,6 +10,7 @@
is_website_user: <t t-esc="json.dumps(request.env.user._is_public())"/>,
user_id: <t t-esc="json.dumps(request.env.user.id)" />,
is_frontend: true,
translationURL: '/website/translations',
};
</script>
@ -194,6 +195,21 @@
</li>
</ul>
</div>
<div t-if="searchbar_groupby" class="dropdown pull-right mr4">
<button id="portal_searchbar_groupby" class="o_portal_search_panel_fixed_width btn btn-default" type="button" data-toggle="dropdown">
<span class="fa fa-bars fa-lg" />
<span class='hidden-xs hidden-sm hidden-md' t-esc="searchbar_groupby[groupby].get('label', 'None')"/>
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="portal_searchbar_groupby">
<li t-foreach="searchbar_groupby" t-as="option" t-att-class="groupby == option and 'active'">
<a t-att-href="default_url + '?' + keep_query('*', groupby=option)">
<span t-esc="searchbar_groupby[option].get('label')"/>
</a>
</li>
</ul>
</div>
</template>
<template id="portal_contact" name="Contact">

View File

@ -140,7 +140,7 @@ class PortalWizardUser(models.TransientModel):
if wizard_user.partner_id.company_id:
company_id = wizard_user.partner_id.company_id.id
else:
company_id = self.env['res.company']._company_default_get('res.users')
company_id = self.env['res.company']._company_default_get('res.users').id
user_portal = wizard_user.sudo().with_context(company_id=company_id)._create_user()
else:
user_portal = user