1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2011 Tiny SPRL (<http://tiny.be>)
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
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
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/>
20 ##############################################################################
23 from osv import fields
24 from tools.translate import _
30 #from email.header import decode_header
31 #from email.utils import parsedate
37 #_logger = logging.getLogger('mailgate')
42 #from email import Encoders
43 #from email.mime.base import MIMEBase
44 #from email.mime.multipart import MIMEMultipart
45 #from email.mime.text import MIMEText
46 #from email.header import decode_header, Header
47 #from email.utils import formatdate
53 #EMAIL_PATTERN = re.compile(r'([^()\[\] ,<:\\>@";]+@[^()\[\] ,<:\\>@";]+)') # See RFC822
54 #def extract_emails(emails_str):
56 # Returns a list of email addresses recognized in a string, ignoring the rest of the string.
57 # extract_emails('a@b.com,c@bcom, "John Doe" <d@b.com> , e@b.com') -> ['a@b.com','c@bcom', 'd@b.com', 'e@b.com']"
59 # return EMAIL_PATTERN.findall(emails_str)
62 #def extract_emails_from_dict(addresses={}):
64 # Extracts email addresses from a dictionary with comma-separated address string values, handling
65 # separately the To, CC, BCC and Reply-To addresses.
67 # :param addresses: a dictionary of addresses in the form {'To': 'a@b.com,c@bcom; d@b.com;e@b.com' , 'CC': 'e@b.com;f@b.com', ... }
68 # :return: a dictionary with a list of separate addresses for each header (To, CC, BCC), with an additional key 'all-recipients'
69 # containing all addresses for the 'To', 'CC', 'BCC' entries.
71 # result = {'all-recipients':[]}
72 # keys = ['To', 'CC', 'BCC', 'Reply-To']
74 # emails = extract_emails(addresses.get(each, u''))
75 # while u'' in emails:
77 # result[each] = emails
78 # if each != 'Reply-To':
79 # result['all-recipients'].extend(emails)
82 class email_smtp_server(osv.osv):
86 _name = "email.smtp_server"
89 'name': fields.char('Name',
90 size=64, required=True,
92 help="The Name is used as the Sender name along with the provided From Email, \
93 unless it is already specified in the From Email, e.g: John Doe <john@doe.com>",
95 'email_id': fields.char('From Email',
96 size=120, required=True,
97 help="eg: 'john@doe.com' or 'John Doe <john@doe.com>'"),
98 'smtpserver': fields.char('Server',
99 size=120, required=True,
100 help="Enter name of outgoing server, eg: smtp.yourdomain.com"),
101 'smtpport': fields.integer('SMTP Port',
102 size=64, required=True,
103 help="Enter port number, eg: 25 or 587"),
104 'smtpuname': fields.char('User Name',
105 size=120, required=False,
106 help="Specify the username if your SMTP server requires authentication, "
107 "otherwise leave it empty."),
108 'smtppass': fields.char('Password',
111 'smtptls':fields.boolean('TLS'),
112 'smtpssl':fields.boolean('SSL/TLS'),
113 'default': fields.boolean('Default', help="Only one account can be default at a time"),
117 'name':lambda self, cursor, user, context:self.pool.get( 'res.users'
118 ).read(cursor, user, user, ['name'], context)['name'],
119 'smtpport': tools.config.get('smtp_port',25),
120 'smtpserver': tools.config.get('smtp_server','localhost'),
121 'smtpssl': tools.config.get('smtp_ssl',False),
129 'Another setting already exists with this email ID !')
132 def _constraint_unique(self, cr, uid, ids, context=None):
133 default_ids = self.search(cr, uid, [('default','=',True)])
134 if len(default_ids) > 1:
141 'Error: You must be define one default smtp server account !.',
145 def name_get(self, cr, uid, ids, context=None):
146 return [(a["id"], "%s (%s)" % (a['email_id'], a['name'])) for a in self.read(cr, uid, ids, ['name', 'email_id'], context=context)]
150 def test_smtp_connection(self, cr, uid, ids, context=None):
152 Test SMTP connection works
155 for smtp_server in self.browse(cr, uid, ids, context=context):
156 smtp = tools.connect_smtp_server(smtp_server.smtpserver, smtp_server.smtpport, user_name=smtp_server.smtpuname,
157 user_password=smtp_server.smtppass, ssl=smtp_server.smtpssl, tls=smtp_server.smtptls)
161 # ignored, just a consequence of the previous exception
163 except Exception, error:
164 raise osv.except_osv(
165 _("SMTP Connection: Test failed"),
166 _("Reason: %s") % error
169 raise osv.except_osv(_("SMTP Connection: Test Successfully!"), '')
171 # def do_approval(self, cr, uid, ids, context=None):
172 # #TODO: Check if user has rights
173 # self.write(cr, uid, ids, {'state':'approved'}, context=context)
174 ## wf_service = netsvc.LocalService("workflow")
176 # def smtp_connection(self, cursor, user, id, context=None):
178 # This method should now wrap smtp_connection
180 # #This function returns a SMTP server object
181 # logger = netsvc.Logger()
182 # core_obj = self.browse(cursor, user, id, context=context)
183 # if core_obj.smtpserver and core_obj.smtpport and core_obj.state == 'approved':
185 # serv = self.get_outgoing_server(cursor, user, id, context)
186 # except Exception, error:
187 # logger.notifyChannel(_("Email Template"), netsvc.LOG_ERROR, _("Mail from Account %s failed on login. Probable Reason:Could not login to server\nError: %s") % (id, error))
189 # #Everything is complete, now return the connection
192 # logger.notifyChannel(_("Email Template"), netsvc.LOG_ERROR, _("Mail from Account %s failed. Probable Reason:Account not approved") % id)
195 ##**************************** MAIL SENDING FEATURES ***********************#
196 # def send_email(self, cr, uid, ids, addresses, subject='', body=None, payload=None, message_id=None, context=None):
197 # #TODO: Replace all this with a single email object
200 # if payload is None:
202 # if context is None:
204 # logger = netsvc.Logger()
206 # core_obj = self.browse(cr, uid, id, context)
207 # serv = self.smtp_connection(cr, uid, id)
210 # # Need a multipart/mixed wrapper for attachments if content is alternative
212 # payload_part = MIMEMultipart(_subtype='mixed')
213 # text_part = MIMEMultipart(_subtype='mixed')
214 # payload_part.attach(text_part)
216 # # otherwise a single multipart/mixed will do the whole job
217 # payload_part = text_part = MIMEMultipart(_subtype='mixed')
220 # payload_part['Subject'] = subject
221 # from_email = core_obj.email_id
222 # if '<' in from_email:
223 # # We have a structured email address, keep it untouched
224 # payload_part['From'] = Header(core_obj.email_id, 'utf-8').encode()
226 # # Plain email address, construct a structured one based on the name:
227 # sender_name = Header(core_obj.name, 'utf-8').encode()
228 # payload_part['From'] = sender_name + " <" + core_obj.email_id + ">"
229 # payload_part['Organization'] = tools.ustr(core_obj.user.company_id.name)
230 # payload_part['Date'] = formatdate()
231 # addresses_l = extract_emails_from_dict(addresses)
232 # if addresses_l['To']:
233 # payload_part['To'] = u','.join(addresses_l['To'])
234 # if addresses_l['CC']:
235 # payload_part['CC'] = u','.join(addresses_l['CC'])
236 # if addresses_l['Reply-To']:
237 # payload_part['Reply-To'] = addresses_l['Reply-To'][0]
239 # payload_part['Message-ID'] = message_id
240 # if body.get('text', False):
241 # temp_body_text = body.get('text', '')
242 # l = len(temp_body_text.replace(' ', '').replace('\r', '').replace('\n', ''))
244 # body['text'] = u'No Mail Message'
245 # # Attach parts into message container.
246 # # According to RFC 2046, the last part of a multipart message, in this case
247 # # the HTML message, is best and preferred.
248 ## if core_obj.send_pref in ('text', 'mixed', 'alternative'):
249 ## body_text = body.get('text', u'<Empty Message>')
250 ## body_text = tools.ustr(body_text)
251 ## text_part.attach(MIMEText(body_text.encode("utf-8"), _charset='UTF-8'))
252 ## if core_obj.send_pref in ('html', 'mixed', 'alternative'):
253 # html_body = body.get('html', u'')
254 # if len(html_body) == 0 or html_body == u'':
255 # html_body = body.get('text', u'<p><Empty Message></p>').replace('\n', '<br/>').replace('\r', '<br/>')
256 # html_body = tools.ustr(html_body)
257 # text_part.attach(MIMEText(html_body.encode("utf-8"), _subtype='html', _charset='UTF-8'))
259 # #Now add attachments if any, wrapping into a container multipart/mixed if needed
261 # for file in payload:
262 # part = MIMEBase('application', "octet-stream")
263 # part.set_payload(base64.decodestring(payload[file]))
264 # part.add_header('Content-Disposition', 'attachment; filename="%s"' % file)
265 # Encoders.encode_base64(part)
266 # payload_part.attach(part)
267 # except Exception, error:
268 # logger.notifyChannel(_("Email Template"), netsvc.LOG_ERROR, _("Mail from Account %s failed. Probable Reason:MIME Error\nDescription: %s") % (id, error))
269 # return {'error_msg': _("Server Send Error\nDescription: %s")%error}
271 # serv.sendmail(payload_part['From'], addresses_l['all-recipients'], payload_part.as_string())
272 # except Exception, error:
273 # logging.getLogger('email_template').error(_("Mail from Account %s failed. Probable Reason: Server Send Error\n Description: %s"), id, error, exc_info=True)
274 # return {'error_msg': _("Server Send Error\nDescription: %s")%error}
275 # #The mail sending is complete
277 # logger.notifyChannel(_("Email Template"), netsvc.LOG_INFO, _("Mail from Account %s successfully Sent.") % (id))
280 # logger.notifyChannel(_("Email Template"), netsvc.LOG_ERROR, _("Mail from Account %s failed. Probable Reason:Account not approved") % id)
281 # return {'nodestroy':True,'error_msg': _("Mail from Account %s failed. Probable Reason:Account not approved")% id}
283 # def extracttime(self, time_as_string):
287 # logger = netsvc.Logger()
288 # #The standard email dates are of format similar to:
289 # #Thu, 8 Oct 2009 09:35:42 +0200
290 # date_as_date = False
291 # convertor = {'+':1, '-':-1}
293 # time_as_string = time_as_string.replace(',', '')
294 # date_list = time_as_string.split(' ')
295 # date_temp_str = ' '.join(date_list[1:5])
296 # if len(date_list) >= 6:
297 # sign = convertor.get(date_list[5][0], False)
301 # dt = datetime.datetime.strptime(
303 # "%d %b %Y %H:%M:%S")
306 # dt = datetime.datetime.strptime(
313 # offset = datetime.timedelta(
317 # minutes=sign * int(
321 # except Exception, e2:
322 # """Looks like UT or GMT, just forget decoding"""
325 # offset = datetime.timedelta(hours=0)
327 # date_as_date = dt.strftime('%Y-%m-%d %H:%M:%S')
328 # except Exception, e:
329 # logger.notifyChannel(
330 # _("Email Template"),
331 # netsvc.LOG_WARNING,
333 # "Datetime Extraction failed.Date:%s \
338 # return date_as_date
340 # def send_receive(self, cr, uid, ids, context=None):
342 # ctx = context.copy()
343 # ctx['filters'] = [('account_id', '=', id)]
344 # self.pool.get('email.message').send_all_mail(cr, uid, [], context=ctx)
347 # def decode_header_text(self, text):
348 # """ Decode internationalized headers RFC2822.
349 # To, CC, BCC, Subject fields can contain
350 # text slices with different encodes, like:
351 # =?iso-8859-1?Q?Enric_Mart=ED?= <enricmarti@company.com>,
352 # =?Windows-1252?Q?David_G=F3mez?= <david@company.com>
353 # Sometimes they include extra " character at the beginning/
354 # end of the contact name, like:
355 # "=?iso-8859-1?Q?Enric_Mart=ED?=" <enricmarti@company.com>
356 # and decode_header() does not work well, so we use regular
357 # expressions (?= ? ? ?=) to split the text slices
361 # p = re.compile("(=\?.*?\?.\?.*?\?=)")
364 # for t2 in p.split(text):
368 # ) for (s, t) in decode_header(t2)]
376 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: