[REF] purchase: search view of purchase order and form view of merge order wizard
[odoo/odoo.git] / addons / mail_gateway / mail_gateway.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #    
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.     
19 #
20 ###########################################################################################
21
22 import re
23 import email, mimetypes
24 from email.Header import decode_header
25 from email.MIMEText import MIMEText
26 import xmlrpclib
27 import os
28 import binascii
29 import time, socket
30
31 from tools.translate import _
32
33 import tools
34 from osv import fields,osv,orm
35 from osv.orm import except_orm
36 import email
37 import netsvc
38 from poplib import POP3, POP3_SSL
39 from imaplib import IMAP4, IMAP4_SSL   
40
41 class mail_gateway_server(osv.osv):
42     _name = "mail.gateway.server"
43     _description = "Email Gateway Server"
44     _columns = {
45         'name': fields.char('Server Address',size=64,required=True ,help="IMAP/POP Address Of Email gateway Server"),
46         'login': fields.char('User',size=64,required=True,help="User Login Id of Email gateway"),
47         'password': fields.char('Password',size=64,required=True,help="User Password Of Email gateway"),
48         'server_type': fields.selection([("pop","POP"),("imap","Imap")],"Type of Server", required=True, help="Type of Email gateway Server"),
49         'port': fields.integer("Port" , help="Port Of Email gateway Server. If port is omitted, the standard POP3 port (110) is used for POP EMail Server and the standard IMAP4 port (143) is used for IMAP Sever."),
50         'ssl': fields.boolean('SSL',help ="Use Secure Authentication"),
51         'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the email gateway server without removing it."),
52     }
53     _defaults = {
54         'server_type':lambda * a:'pop',
55         'active':lambda * a:True,
56     }
57     
58     def check_duplicate(self, cr, uid, ids):
59         vals = self.read(cr, uid, ids, ['name', 'login'])[0]
60         cr.execute("select count(id) from mail_gateway_server \
61                         where name='%s' and login='%s'" % \
62                         (vals['name'], vals['login']))
63         res = cr.fetchone()
64         if res:
65             if res[0] > 1:
66                 return False
67         return True 
68
69     _constraints = [
70         (check_duplicate, 'Warning! Can\'t have duplicate server configuration!', ['name', 'login'])
71     ]
72     
73     def onchange_server_type(self, cr, uid, ids, server_type=False, ssl=False):
74         port = 0
75         if server_type == 'pop':
76             port = ssl and 995 or 110
77         elif server_type == 'imap':
78             port = ssl and 993 or 143
79         return {'value':{'port':port}}
80     
81 mail_gateway_server()
82
83
84 class mail_gateway(osv.osv):
85     _name = "mail.gateway"
86     _description = "Email Gateway"
87
88     _columns = {
89         'name': fields.char('Name',size=64,help="Name of Mail Gateway."),
90         'server_id': fields.many2one('mail.gateway.server',"Gateway Server", required=True),
91         'object_id': fields.many2one('ir.model',"Model", required=True),
92         'reply_to': fields.char('TO', size=64, help="Email address used in reply to/from of outgoing messages"),
93         'email_default': fields.char('Default eMail',size=64,help="Default eMail in case of any trouble."),        
94         'mail_history': fields.one2many("mail.gateway.history","gateway_id","History", readonly=True)
95     }
96     _defaults = {
97         'reply_to': lambda * a:tools.config.get('email_from',False)
98     }
99
100     def _fetch_mails(self, cr, uid, ids=False, context={}):
101         '''
102         Function called by the scheduler to fetch mails
103         '''
104         cr.execute('select * from mail_gateway gateway \
105                 inner join mail_gateway_server server \
106                 on server.id = gateway.server_id where server.active = True')
107         ids2 = map(lambda x: x[0], cr.fetchall() or [])
108         return self.fetch_mails(cr, uid, ids=ids2, context=context)
109
110     def parse_mail(self, cr, uid, gateway_id, email_message, context={}):
111         msg_id, res_id, note = (False, False, False)        
112         mail_history_obj = self.pool.get('mail.gateway.history')        
113         mailgateway = self.browse(cr, uid, gateway_id, context=context)
114         try :
115             msg_txt = email.message_from_string(email_message)
116             msg_id =  msg_txt['Message-ID']
117             res_id = self.msg_parse(cr, uid, gateway_id, msg_txt)            
118         except Exception, e:
119             import traceback
120             note = "Error in Parsing Mail: %s " %(str(e))            
121             netsvc.Logger().notifyChannel('Emailgate: Parsing mail:%s' % (mailgateway and (mailgateway.name or
122                      '%s (%s)'%(mailgateway.server_id.login, mailgateway.server_id.name))) or ''
123                      , netsvc.LOG_ERROR, traceback.format_exc())
124
125         mail_history_obj.create(cr, uid, {'name': msg_id, 'res_id': res_id, 'gateway_id': mailgateway.id, 'note': note})
126         return res_id,  note
127
128     def fetch_mails(self, cr, uid, ids=[], context={}):        
129         log_messages = []
130         mailgate_server = False
131         new_messages = []
132         for mailgateway in self.browse(cr, uid, ids):
133             try :
134                 mailgate_server = mailgateway.server_id
135                 if not mailgate_server.active:
136                     continue
137                 mailgate_name =  mailgateway.name or "%s (%s)" % (mailgate_server.login, mailgate_server.name)
138                 res_model = mailgateway.object_id.name
139                 log_messages.append("Mail Server : %s" % mailgate_name)
140                 log_messages.append("="*40)
141                 new_messages = []
142                 if mailgate_server.server_type == 'pop':
143                     if mailgate_server.ssl:
144                         pop_server = POP3_SSL(mailgate_server.name or 'localhost', mailgate_server.port or 995)
145                     else:
146                         pop_server = POP3(mailgate_server.name or 'localhost', mailgate_server.port or 110)
147                     pop_server.user(mailgate_server.login)
148                     pop_server.pass_(mailgate_server.password)
149                     pop_server.list()
150                     (numMsgs, totalSize) = pop_server.stat()
151                     for i in range(1, numMsgs + 1):
152                         (header, msges, octets) = pop_server.retr(i)
153                         res_id, note = self.parse_mail(cr, uid, mailgateway.id, '\n'.join(msges))
154                         log = ''
155                         if res_id:
156                             log = _('Object Successfully Created : %d of %s'% (res_id, res_model))
157                         if note:
158                             log = note
159                         log_messages.append(log)
160                         new_messages.append(i)
161                     pop_server.quit()
162
163                 elif mailgate_server.server_type == 'imap':
164                     if mailgate_server.ssl:
165                         imap_server = IMAP4_SSL(mailgate_server.name or 'localhost', mailgate_server.port or 993)
166                     else:
167                         imap_server = IMAP4(mailgate_server.name or 'localhost', mailgate_server.port or 143)
168                     imap_server.login(mailgate_server.login, mailgate_server.password)
169                     imap_server.select()
170                     typ, data = imap_server.search(None, '(UNSEEN)')
171                     for num in data[0].split():
172                         typ, data = imap_server.fetch(num, '(RFC822)')
173                         res_id, note = self.parse_mail(cr, uid, mailgateway.id, data[0][1])
174                         log = ''
175                         if res_id:
176                             log = _('Object Successfully Created/Modified: %d of %s'% (res_id, res_model))
177                         if note:
178                             log = note
179                         log_messages.append(log)
180                         new_messages.append(num)
181                     imap_server.close()
182                     imap_server.logout()
183
184             except Exception, e:
185                  import traceback
186                  log_messages.append("Error in Fetching Mail: %s " %(str(e)))                 
187                  netsvc.Logger().notifyChannel('Emailgate: Fetching mail:[%d]%s' % 
188                     (mailgate_server and mailgate_server.id or 0, mailgate_server and mailgate_server.name or ''),
189                      netsvc.LOG_ERROR, traceback.format_exc())
190
191             log_messages.append("-"*25)
192             log_messages.append("Total Read Mail: %d\n\n" %(len(new_messages)))
193         return log_messages    
194
195     def emails_get(self, email_from):
196         res = tools.email_re.search(email_from)
197         return res and res.group(1)
198
199     def partner_get(self, cr, uid, email):
200         mail = self.emails_get(email)
201         adr_ids = self.pool.get('res.partner.address').search(cr, uid, [('email', '=', mail)])
202         if not adr_ids:
203             return {}
204         adr = self.pool.get('res.partner.address').read(cr, uid, adr_ids, ['partner_id'])
205         res = {}
206         if len(adr):
207             res = {
208                 'partner_address_id': adr[0]['id'],
209                 'partner_id': adr[0].get('partner_id',False) and adr[0]['partner_id'][0] or False
210             }
211         return res
212
213     def _decode_header(self, s):
214         from email.Header import decode_header
215         s = decode_header(s)
216         return ''.join(map(lambda x:x[0].decode(x[1] or 'ascii', 'replace'), s))
217
218     def msg_new(self, cr, uid, msg, model):
219         message = self.msg_body_get(msg)
220         res_model = self.pool.get(model)
221         res_id = res_model.msg_new(cr, uid, msg)
222         if res_id:
223             attachments = message['attachment']
224
225             for attach in attachments or []:
226                 data_attach = {
227                     'name': str(attach),
228                     'datas':binascii.b2a_base64(str(attachments[attach])),
229                     'datas_fname': str(attach),
230                     'description': 'Mail attachment',
231                     'res_model': model,
232                     'res_id': res_id
233                 }
234                 self.pool.get('ir.attachment').create(cr, uid, data_attach)
235         return res_id
236
237
238     def msg_body_get(self, msg):        
239         message = {};
240         message['body'] = '';
241         message['attachment'] = {};
242         attachment = message['attachment'];
243         counter = 1;
244         def replace(match):
245             return ''        
246         for part in msg.walk():
247             if part.get_content_maintype() == 'multipart':
248                 continue
249
250             if part.get_content_maintype()=='text':
251                 buf = part.get_payload(decode=True)
252                 if buf:
253                     txt = buf.decode(part.get_charsets()[0] or 'ascii', 'replace')
254                     txt = re.sub("<(\w)>", replace, txt)
255                     txt = re.sub("<\/(\w)>", replace, txt)
256                 if txt and part.get_content_subtype() == 'plain':
257                     message['body'] += txt 
258                 elif txt and part.get_content_subtype() == 'html':                                                               
259                     message['body'] += tools.html2plaintext(txt)  
260                 
261                 filename = part.get_filename();
262                 if filename :
263                     attachment[filename] = part.get_payload(decode=True);
264                     
265             elif part.get_content_maintype()=='application' or part.get_content_maintype()=='image' or part.get_content_maintype()=='text':
266                 filename = part.get_filename();
267                 if filename :
268                     attachment[filename] = part.get_payload(decode=True);
269                 else:
270                     filename = 'attach_file'+str(counter);
271                     counter += 1;
272                     attachment[filename] = part.get_payload(decode=True);
273                 #end if
274             #end if
275             message['attachment'] = attachment
276         #end for              
277         return message
278     #end def
279
280     def msg_update(self, cr, uid, msg, res_id, res_model, user_email):        
281         if user_email and self.emails_get(user_email)==self.emails_get(self._decode_header(msg['From'])):
282             return self.msg_user(cr, uid, msg, res_id, res_model)
283         else:
284             return self.msg_partner(cr, uid, msg, res_id, res_model)
285
286     def msg_act_get(self, msg):
287         body = self.msg_body_get(msg)
288
289         # handle email body commands (ex: Set-State: Draft)
290         actions = {}
291         body_data = ''
292         for line in body['body'].split('\n'):
293             res = tools.command_re.match(line)
294             if res:
295                 actions[res.group(1).lower()] = res.group(2).lower()
296             else:
297                 body_data += line+'\n'
298         return actions, body_data
299
300     def msg_user(self, cr, uid, msg, res_id, res_model):
301         actions, body_data = self.msg_act_get(msg)        
302         data = {}
303         if 'user' in actions:
304             uids = self.pool.get('res.users').name_search(cr, uid, actions['user'])
305             if uids:
306                 data['user_id'] = uids[0][0]
307
308         res_model = self.pool.get(res_model)        
309         return res_model.msg_update(cr, uid, res_id, msg, data=data, default_act='pending')        
310
311     def msg_send(self, msg, reply_to, emails, priority=None, res_id=False):         
312         if not emails:
313             return False                
314         msg_to = [emails[0]]
315         msg_subject = msg['Subject']        
316         msg_cc = []
317         msg_body = self.msg_body_get(msg)        
318         if len(emails)>1:            
319             msg_cc = emails[1:]
320         msg_attachment = map(lambda x: (x[0], x[1]), msg_body['attachment'].items())                  
321         return tools.email_send(reply_to, msg_to, msg_subject , msg_body['body'], email_cc=msg_cc, 
322                          reply_to=reply_to, attach=msg_attachment, openobject_id=res_id, priority=priority)
323         
324
325     def msg_partner(self, cr, uid, msg, res_id, res_model):
326         res_model = self.pool.get(res_model)        
327         return res_model.msg_update(cr, uid, res_id, msg, data={}, default_act='open')      
328
329     
330
331     def msg_parse(self, cr, uid, mailgateway_id, msg):
332         mailgateway = self.browse(cr, uid, mailgateway_id)
333         res_model = mailgateway.object_id.model
334         res_str = tools.reference_re.search(msg.get('References', ''))
335         if res_str:
336             res_str = res_str.group(1)
337         else:
338             res_str = tools.res_re.search(msg.get('Subject', ''))
339             if res_str:
340                 res_str = res_str.group(1)
341
342         def msg_test(res_str):
343             emails = ('', '', '', '')
344             if not res_str:
345                 return (False, emails)  
346             res_str = int(res_str)          
347             if hasattr(self.pool.get(res_model), 'emails_get'):
348                 emails = self.pool.get(res_model).emails_get(cr, uid, [res_str])[0]
349             return (res_str, emails)
350
351         (res_id, emails) = msg_test(res_str)
352         user_email, from_email, cc_email, priority = emails
353         if res_id:
354             self.msg_update(cr, uid, msg, res_id, res_model, user_email)
355             
356         else:
357             res_id = self.msg_new(cr, uid, msg, res_model)
358             (res_id, emails) = msg_test(res_id)
359             user_email, from_email, cc_email, priority = emails
360             subject = self._decode_header(msg['subject'])
361             if msg.get('Subject', ''):
362                 del msg['Subject']
363             msg['Subject'] = '[%s] %s' %(str(res_id), subject)            
364
365         em = [user_email, from_email] + (cc_email or '').split(',')
366         emails = map(self.emails_get, filter(None, em))
367
368         mm = [self._decode_header(msg['From']), self._decode_header(msg['To'])]+self._decode_header(msg.get('Cc','')).split(',')
369         msg_mails = map(self.emails_get, filter(None, mm))
370
371         emails = filter(lambda m: m and m not in msg_mails, emails)        
372         try:
373             self.msg_send(msg, mailgateway.reply_to, emails, priority, res_id)
374             if hasattr(self.pool.get(res_model), 'msg_send'):
375                 emails = self.pool.get(res_model).msg_send(cr, uid, res_id)
376         except Exception, e:
377             if mailgateway.email_default:
378                 a = self._decode_header(msg['Subject'])
379                 del msg['Subject']
380                 msg['Subject'] = '[OpenERP-Error] ' + a
381                 self.msg_send(msg, mailgateway.reply_to, mailgateway.email_default.split(','), res_id)
382             raise e 
383         return res_id
384
385 mail_gateway()
386
387 class mail_gateway_history(osv.osv):
388     _name = "mail.gateway.history"
389     _description = "Mail Gateway History"
390     _columns = {
391         'name': fields.char('Message Id', size=64, help="Message Id in Email Server."),
392         'res_id': fields.integer("Resource ID"),        
393         'gateway_id': fields.many2one('mail.gateway',"Mail Gateway", required=True),
394         'model_id':fields.related('gateway_id', 'object_id', type='many2one', relation='ir.model', string='Model'), 
395         'note': fields.text('Notes'),
396         'create_date': fields.datetime('Created Date'),
397     }
398     _order = 'id desc'
399 mail_gateway_history()