def send_message(self, issueid, msgid, note, sendto, from_address=None, bcc_sendto=[], subject=None, crypt=False, add_headers={}, authid=None): '''Actually send the nominated message from this issue to the sendto recipients, with the note appended. It's possible to add headers to the message with the add_headers variable. ''' users = self.db.user messages = self.db.msg files = self.db.file if msgid is None: inreplyto = None messageid = None else: inreplyto = messages.get(msgid, 'inreplyto') messageid = messages.get(msgid, 'messageid') # make up a messageid if there isn't one (web edit) if not messageid: # this is an old message that didn't get a messageid, so # create one messageid = "<%s.%s.%s%s@%s>" % ( time.time(), b2s(base64.b32encode(random_.token_bytes(10))), self.classname, issueid, self.db.config['MAIL_DOMAIN']) if msgid is not None: messages.set(msgid, messageid=messageid) # compose title cn = self.classname title = self.get(issueid, 'title') or '%s message copy' % cn # figure author information if authid: pass elif msgid: authid = messages.get(msgid, 'author') else: authid = self.db.getuid() authname = users.get(authid, 'realname') if not authname: authname = users.get(authid, 'username', '') authaddr = users.get(authid, 'address', '') if authaddr and self.db.config.MAIL_ADD_AUTHOREMAIL: authaddr = " <%s>" % formataddr(('', authaddr)) elif authaddr: authaddr = "" # make the message body m = [''] # put in roundup's signature if self.db.config.EMAIL_SIGNATURE_POSITION == 'top': m.append(self.email_signature(issueid, msgid)) # add author information if authid and self.db.config.MAIL_ADD_AUTHORINFO: if msgid and len(self.get(issueid, 'messages')) == 1: m.append( _("New submission from %(authname)s%(authaddr)s:") % locals()) elif msgid: m.append( _("%(authname)s%(authaddr)s added the comment:") % locals()) else: m.append(_("Change by %(authname)s%(authaddr)s:") % locals()) m.append('') # add the content if msgid is not None: m.append(messages.get(msgid, 'content', '')) # get the files for this message message_files = [] if msgid: for fileid in messages.get(msgid, 'files'): # check the attachment size filesize = self.db.filesize('file', fileid, None) if filesize <= self.db.config.NOSY_MAX_ATTACHMENT_SIZE: message_files.append(fileid) else: base = self.db.config.TRACKER_WEB link = "".join((base, files.classname, fileid)) filename = files.get(fileid, 'name') m.append( _("File '%(filename)s' not attached - " "you can download it from %(link)s.") % locals()) # add the change note if note: m.append(note) # put in roundup's signature if self.db.config.EMAIL_SIGNATURE_POSITION == 'bottom': m.append(self.email_signature(issueid, msgid)) # figure the encoding charset = getattr(self.db.config, 'EMAIL_CHARSET', 'utf-8') # construct the content and convert to unicode object body = s2u('\n'.join(m)) # make sure the To line is always the same (for testing mostly) sendto.sort() # make sure we have a from address if from_address is None: from_address = self.db.config.TRACKER_EMAIL # additional bit for after the From: "name" from_tag = getattr(self.db.config, 'EMAIL_FROM_TAG', '') if from_tag: from_tag = ' ' + from_tag if subject is None: subject = '[%s%s] %s' % (cn, issueid, title) author = (authname + from_tag, from_address) # send an individual message per recipient? if self.db.config.NOSY_EMAIL_SENDING != 'single': sendto = [[address] for address in sendto] else: sendto = [sendto] # tracker sender info tracker_name = s2u(self.db.config.TRACKER_NAME) tracker_name = nice_sender_header(tracker_name, from_address, charset) # now send one or more messages # TODO: I believe we have to create a new message each time as we # can't fiddle the recipients in the message ... worth testing # and/or fixing some day first = True for sendto in sendto: # create the message mailer = Mailer(self.db.config) message = mailer.get_standard_message(multipart=message_files) # set reply-to as requested by config option # TRACKER_REPLYTO_ADDRESS replyto_config = self.db.config.TRACKER_REPLYTO_ADDRESS if replyto_config: if replyto_config == "AUTHOR": # note that authaddr at this point is already # surrounded by < >, so get the original address # from the db as nice_send_header adds < > replyto_addr = nice_sender_header( authname, users.get(authid, 'address', ''), charset) else: replyto_addr = replyto_config else: replyto_addr = tracker_name message['Reply-To'] = replyto_addr # message ids if messageid: message['Message-Id'] = messageid if inreplyto: message['In-Reply-To'] = inreplyto # Generate a header for each link or multilink to # a class that has a name attribute for propname, prop in self.getprops().items(): if not isinstance(prop, (hyperdb.Link, hyperdb.Multilink)): continue cl = self.db.getclass(prop.classname) label = None if 'name' in cl.getprops(): label = 'name' if prop.msg_header_property in cl.getprops(): label = prop.msg_header_property if prop.msg_header_property == "": # if msg_header_property is set to empty string # suppress the header entirely. You can't use # 'msg_header_property == None'. None is the # default value. label = None if not label: continue if isinstance(prop, hyperdb.Link): value = self.get(issueid, propname) if value is None: continue values = [value] else: values = self.get(issueid, propname) if not values: continue values = [cl.get(v, label) for v in values] values = ', '.join(values) header = "X-Roundup-%s-%s" % (self.classname, propname) try: values.encode('ascii') message[header] = values except UnicodeError: message[header] = Header(values, charset) # Add header for main id number to make filtering # email easier than extracting from subject line. header = "X-Roundup-%s-Id" % (self.classname) values = issueid try: values.encode('ascii') message[header] = values except UnicodeError: message[header] = Header(values, charset) # Generate additional headers for k in add_headers: v = add_headers[k] try: v.encode('ascii') message[k] = v except UnicodeError: message[k] = Header(v, charset) if not inreplyto: # Default the reply to the first message msgs = self.get(issueid, 'messages') # Assume messages are sorted by increasing message number here # If the issue is just being created, and the submitter didn't # provide a message, then msgs will be empty. if msgs and msgs[0] != msgid: inreplyto = messages.get(msgs[0], 'messageid') if inreplyto: message['In-Reply-To'] = inreplyto # attach files if message_files: # first up the text as a part part = mailer.get_standard_message() part.set_payload(body, part.get_charset()) message.attach(part) for fileid in message_files: name = files.get(fileid, 'name') mime_type = (files.get(fileid, 'type') or mimetypes.guess_type(name)[0] or 'application/octet-stream') if mime_type == 'text/plain': content = files.get(fileid, 'content') part = MIMEText('') del part['Content-Transfer-Encoding'] try: enc = content.encode('ascii') part = mailer.get_text_message('us-ascii') part.set_payload(enc) except UnicodeError: # the content cannot be 7bit-encoded. # use quoted printable # XXX stuffed if we know the charset though :( part = mailer.get_text_message('utf-8') part.set_payload(content, part.get_charset()) elif mime_type == 'message/rfc822': content = files.get(fileid, 'content') main, sub = mime_type.split('/') p = FeedParser() p.feed(content) part = MIMEBase(main, sub) part.set_payload([p.close()]) else: # some other type, so encode it content = files.get(fileid, 'binary_content') main, sub = mime_type.split('/') part = MIMEBase(main, sub) part.set_payload(content) encoders.encode_base64(part) cd = 'Content-Disposition' part[cd] = 'attachment;\n filename="%s"' % name message.attach(part) else: message.set_payload(body, message.get_charset()) if crypt: send_msg = self.encrypt_to(message, sendto) else: send_msg = message mailer.set_message_attributes(send_msg, sendto, subject, author) if crypt: send_msg['Message-Id'] = message['Message-Id'] send_msg['Reply-To'] = message['Reply-To'] if message.get('In-Reply-To'): send_msg['In-Reply-To'] = message['In-Reply-To'] if sendto: mailer.smtp_send(sendto, send_msg.as_string()) if first: if crypt: # send individual bcc mails, otherwise receivers can # deduce bcc recipients from keys in message for bcc in bcc_sendto: send_msg = self.encrypt_to(message, [bcc]) send_msg['Message-Id'] = message['Message-Id'] send_msg['Reply-To'] = message['Reply-To'] if message.get('In-Reply-To'): send_msg['In-Reply-To'] = message['In-Reply-To'] mailer.smtp_send([bcc], send_msg.as_string()) elif bcc_sendto: mailer.smtp_send(bcc_sendto, send_msg.as_string()) first = False
def send_non_roundup_mail(db, cls, issueid, msgid, sendto, cc=[], bcc=[]): """ Send mail to customer, don't use roundup change-email (nosymessage) mechanism -- so we can set different values and don't confuse the customer with roundup information. """ cn = cls.classname msg = db.msg.getnode(msgid) issue = cls.getnode(issueid) title = issue.title or '%s message copy' % cn subject = '[%s%s] %s' % (cn, issueid, title) charset = getattr(db.config, 'EMAIL_CHARSET', 'utf-8') fromaddr = None if 'customer' in cls.properties: customer = db.customer.getnode(issue.customer) if 'type' in cls.properties and 'rmafrom' in db.customer.properties: type = db.sup_type.get(issue.type, 'name') fromaddr = getattr(customer, fromprops_by_type.get(type)) if not fromaddr: fromaddr = customer.fromaddress if not fromaddr: fromaddr = db.config.TRACKER_EMAIL user = db.user.getnode(msg.author) authname = user.realname or user.username or '' author = (authname, fromaddr) m = [''] m.append(msg.content or '') body = unicode('\n'.join(m), 'utf-8').encode(charset) mailer = Mailer(db.config) message = mailer.get_standard_message(multipart=bool(msg.files)) mailer.set_message_attributes(message, sendto, subject, author) message['Message-Id'] = msg.messageid if cc: message['Cc'] = ', '.join(cc) if msg.inreplyto: message['In-Reply-To'] = msg.inreplyto if msg.files: part = mailer.get_text_message() part.set_payload(body, charset) message.attach(part) else: message.set_payload(body, charset) for f in msg.files: file = db.file.getnode(f) if file.type == 'text/plain': part = mailer.get_text_message() part.set_payload(file.content) else: type = file.type if not type: type = mimetypes.guess_type(file.name)[0] if type is None: type = 'application/octet-stream' main, sub = type.split('/') part = MIMENonMultipart(main, sub) part.set_payload(file.content) cd = 'Content-Disposition' part[cd] = 'attachment;\n filename="%s"' % file.name message.attach(part) mailer.smtp_send(sendto + cc + bcc, message.as_string())
def send_message(self, issueid, msgid, note, sendto, from_address=None, bcc_sendto=[], subject=None, crypt=False): '''Actually send the nominated message from this issue to the sendto recipients, with the note appended. ''' users = self.db.user messages = self.db.msg files = self.db.file if msgid is None: inreplyto = None messageid = None else: inreplyto = messages.get(msgid, 'inreplyto') messageid = messages.get(msgid, 'messageid') # make up a messageid if there isn't one (web edit) if not messageid: # this is an old message that didn't get a messageid, so # create one messageid = "<%s.%s.%s%s@%s>"%(time.time(), random.random(), self.classname, issueid, self.db.config.MAIL_DOMAIN) if msgid is not None: messages.set(msgid, messageid=messageid) # compose title cn = self.classname title = self.get(issueid, 'title') or '%s message copy'%cn # figure author information if msgid: authid = messages.get(msgid, 'author') else: authid = self.db.getuid() authname = users.get(authid, 'realname') if not authname: authname = users.get(authid, 'username', '') authaddr = users.get(authid, 'address', '') if authaddr and self.db.config.MAIL_ADD_AUTHOREMAIL: authaddr = " <%s>" % formataddr( ('',authaddr) ) elif authaddr: authaddr = "" # make the message body m = [''] # put in roundup's signature if self.db.config.EMAIL_SIGNATURE_POSITION == 'top': m.append(self.email_signature(issueid, msgid)) # add author information if authid and self.db.config.MAIL_ADD_AUTHORINFO: if msgid and len(self.get(issueid, 'messages')) == 1: m.append(_("New submission from %(authname)s%(authaddr)s:") % locals()) elif msgid: m.append(_("%(authname)s%(authaddr)s added the comment:") % locals()) else: m.append(_("Change by %(authname)s%(authaddr)s:") % locals()) m.append('') # add the content if msgid is not None: m.append(messages.get(msgid, 'content', '')) # get the files for this message message_files = [] if msgid : for fileid in messages.get(msgid, 'files') : # check the attachment size filesize = self.db.filesize('file', fileid, None) if filesize <= self.db.config.NOSY_MAX_ATTACHMENT_SIZE: message_files.append(fileid) else: base = self.db.config.TRACKER_WEB link = "".join((base, files.classname, fileid)) filename = files.get(fileid, 'name') m.append(_("File '%(filename)s' not attached - " "you can download it from %(link)s.") % locals()) # add the change note if note: m.append(note) # put in roundup's signature if self.db.config.EMAIL_SIGNATURE_POSITION == 'bottom': m.append(self.email_signature(issueid, msgid)) # figure the encoding charset = getattr(self.db.config, 'EMAIL_CHARSET', 'utf-8') # construct the content and convert to unicode object body = unicode('\n'.join(m), 'utf-8').encode(charset) # make sure the To line is always the same (for testing mostly) sendto.sort() # make sure we have a from address if from_address is None: from_address = self.db.config.TRACKER_EMAIL # additional bit for after the From: "name" from_tag = getattr(self.db.config, 'EMAIL_FROM_TAG', '') if from_tag: from_tag = ' ' + from_tag if subject is None: subject = '[%s%s] %s'%(cn, issueid, title) author = (authname + from_tag, from_address) # send an individual message per recipient? if self.db.config.NOSY_EMAIL_SENDING != 'single': sendto = [[address] for address in sendto] else: sendto = [sendto] # tracker sender info tracker_name = unicode(self.db.config.TRACKER_NAME, 'utf-8') tracker_name = nice_sender_header(tracker_name, from_address, charset) # now send one or more messages # TODO: I believe we have to create a new message each time as we # can't fiddle the recipients in the message ... worth testing # and/or fixing some day first = True for sendto in sendto: # create the message mailer = Mailer(self.db.config) message = mailer.get_standard_message(multipart=message_files) # set reply-to as requested by config option TRACKER_REPLYTO_ADDRESS replyto_config = self.db.config.TRACKER_REPLYTO_ADDRESS if replyto_config: if replyto_config == "AUTHOR": # note that authaddr at this point is already surrounded by < >, so # get the original address from the db as nice_send_header adds < > replyto_addr = nice_sender_header(authname, users.get(authid, 'address', ''), charset) else: replyto_addr = replyto_config else: replyto_addr = tracker_name message['Reply-To'] = replyto_addr # message ids if messageid: message['Message-Id'] = messageid if inreplyto: message['In-Reply-To'] = inreplyto # Generate a header for each link or multilink to # a class that has a name attribute for propname, prop in self.getprops().items(): if not isinstance(prop, (hyperdb.Link, hyperdb.Multilink)): continue cl = self.db.getclass(prop.classname) label = None if 'name' in cl.getprops(): label = 'name' if prop.msg_header_property in cl.getprops(): label = prop.msg_header_property if prop.msg_header_property == "": # if msg_header_property is set to empty string # suppress the header entirely. You can't use # 'msg_header_property == None'. None is the # default value. label = None if not label: continue if isinstance(prop, hyperdb.Link): value = self.get(issueid, propname) if value is None: continue values = [value] else: values = self.get(issueid, propname) if not values: continue values = [cl.get(v, label) for v in values] values = ', '.join(values) header = "X-Roundup-%s-%s"%(self.classname, propname) try: message[header] = values.encode('ascii') except UnicodeError: message[header] = Header(values, charset) if not inreplyto: # Default the reply to the first message msgs = self.get(issueid, 'messages') # Assume messages are sorted by increasing message number here # If the issue is just being created, and the submitter didn't # provide a message, then msgs will be empty. if msgs and msgs[0] != msgid: inreplyto = messages.get(msgs[0], 'messageid') if inreplyto: message['In-Reply-To'] = inreplyto # attach files if message_files: # first up the text as a part part = MIMEText(body) part.set_charset(charset) encode_quopri(part) message.attach(part) for fileid in message_files: name = files.get(fileid, 'name') mime_type = files.get(fileid, 'type') content = files.get(fileid, 'content') if mime_type == 'text/plain': try: content.decode('ascii') except UnicodeError: # the content cannot be 7bit-encoded. # use quoted printable # XXX stuffed if we know the charset though :( part = MIMEText(content) encode_quopri(part) else: part = MIMEText(content) part['Content-Transfer-Encoding'] = '7bit' elif mime_type == 'message/rfc822': main, sub = mime_type.split('/') p = FeedParser() p.feed(content) part = MIMEBase(main, sub) part.set_payload([p.close()]) else: # some other type, so encode it if not mime_type: # this should have been done when the file was saved mime_type = mimetypes.guess_type(name)[0] if mime_type is None: mime_type = 'application/octet-stream' main, sub = mime_type.split('/') part = MIMEBase(main, sub) part.set_payload(content) Encoders.encode_base64(part) cd = 'Content-Disposition' part[cd] = 'attachment;\n filename="%s"'%name message.attach(part) else: message.set_payload(body) encode_quopri(message) if crypt: send_msg = self.encrypt_to (message, sendto) else: send_msg = message mailer.set_message_attributes(send_msg, sendto, subject, author) if crypt: send_msg ['Message-Id'] = message ['Message-Id'] send_msg ['Reply-To'] = message ['Reply-To'] if message.get ('In-Reply-To'): send_msg ['In-Reply-To'] = message ['In-Reply-To'] mailer.smtp_send(sendto, send_msg.as_string()) if first: if crypt: # send individual bcc mails, otherwise receivers can # deduce bcc recipients from keys in message for bcc in bcc_sendto: send_msg = self.encrypt_to (message, [bcc]) send_msg ['Message-Id'] = message ['Message-Id'] send_msg ['Reply-To'] = message ['Reply-To'] if message.get ('In-Reply-To'): send_msg ['In-Reply-To'] = message ['In-Reply-To'] mailer.smtp_send([bcc], send_msg.as_string()) elif bcc_sendto: mailer.smtp_send(bcc_sendto, send_msg.as_string()) first = False
def send_message(self, nodeid, msgid, note, sendto, from_address=None, bcc_sendto=[]): '''Actually send the nominated message from this node to the sendto recipients, with the note appended. ''' users = self.db.user messages = self.db.msg files = self.db.file if msgid is None: inreplyto = None messageid = None else: inreplyto = messages.get(msgid, 'inreplyto') messageid = messages.get(msgid, 'messageid') # make up a messageid if there isn't one (web edit) if not messageid: # this is an old message that didn't get a messageid, so # create one messageid = "<%s.%s.%s%s@%s>" % (time.time(), random.random(), self.classname, nodeid, self.db.config.MAIL_DOMAIN) if msgid is not None: messages.set(msgid, messageid=messageid) # compose title cn = self.classname title = self.get(nodeid, 'title') or '%s message copy' % cn # figure author information if msgid: authid = messages.get(msgid, 'author') else: authid = self.db.getuid() authname = users.get(authid, 'realname') if not authname: authname = users.get(authid, 'username', '') authaddr = users.get(authid, 'address', '') if authaddr and self.db.config.MAIL_ADD_AUTHOREMAIL: authaddr = " <%s>" % straddr(('', authaddr)) elif authaddr: authaddr = "" # make the message body m = [''] # put in roundup's signature if self.db.config.EMAIL_SIGNATURE_POSITION == 'top': m.append(self.email_signature(nodeid, msgid)) # add author information if authid and self.db.config.MAIL_ADD_AUTHORINFO: if msgid and len(self.get(nodeid, 'messages')) == 1: m.append( _("New submission from %(authname)s%(authaddr)s:") % locals()) elif msgid: m.append( _("%(authname)s%(authaddr)s added the comment:") % locals()) else: m.append(_("Change by %(authname)s%(authaddr)s:") % locals()) m.append('') # add the content if msgid is not None: m.append(messages.get(msgid, 'content', '')) # get the files for this message message_files = [] if msgid: for fileid in messages.get(msgid, 'files'): # check the attachment size filename = self.db.filename('file', fileid, None) filesize = os.path.getsize(filename) if filesize <= self.db.config.NOSY_MAX_ATTACHMENT_SIZE: message_files.append(fileid) else: base = self.db.config.TRACKER_WEB link = "".join((base, files.classname, fileid)) filename = files.get(fileid, 'name') m.append( _("File '%(filename)s' not attached - " "you can download it from %(link)s.") % locals()) # add the change note if note: m.append(note) # put in roundup's signature if self.db.config.EMAIL_SIGNATURE_POSITION == 'bottom': m.append(self.email_signature(nodeid, msgid)) # encode the content as quoted-printable charset = getattr(self.db.config, 'EMAIL_CHARSET', 'utf-8') m = '\n'.join(m) if charset != 'utf-8': m = unicode(m, 'utf-8').encode(charset) content = cStringIO.StringIO(m) content_encoded = cStringIO.StringIO() quopri.encode(content, content_encoded, 0) content_encoded = content_encoded.getvalue() # make sure the To line is always the same (for testing mostly) sendto.sort() # make sure we have a from address if from_address is None: from_address = self.db.config.TRACKER_EMAIL # additional bit for after the From: "name" from_tag = getattr(self.db.config, 'EMAIL_FROM_TAG', '') if from_tag: from_tag = ' ' + from_tag subject = '[%s%s] %s' % (cn, nodeid, title) author = (authname + from_tag, from_address) # send an individual message per recipient? if self.db.config.NOSY_EMAIL_SENDING != 'single': sendto = [[address] for address in sendto] else: sendto = [sendto] # now send one or more messages # TODO: I believe we have to create a new message each time as we # can't fiddle the recipients in the message ... worth testing # and/or fixing some day first = True for sendto in sendto: # create the message mailer = Mailer(self.db.config) message, writer = mailer.get_standard_message( sendto, subject, author) # set reply-to to the tracker tracker_name = self.db.config.TRACKER_NAME if charset != 'utf-8': tracker = unicode(tracker_name, 'utf-8').encode(charset) tracker_name = encode_header(tracker_name, charset) writer.addheader('Reply-To', straddr((tracker_name, from_address))) # message ids if messageid: writer.addheader('Message-Id', messageid) if inreplyto: writer.addheader('In-Reply-To', inreplyto) # Generate a header for each link or multilink to # a class that has a name attribute for propname, prop in self.getprops().items(): if not isinstance(prop, (hyperdb.Link, hyperdb.Multilink)): continue cl = self.db.getclass(prop.classname) if not 'name' in cl.getprops(): continue if isinstance(prop, hyperdb.Link): value = self.get(nodeid, propname) if value is None: continue values = [value] else: values = self.get(nodeid, propname) if not values: continue values = [cl.get(v, 'name') for v in values] values = ', '.join(values) writer.addheader( "X-Roundup-%s-%s" % (self.classname, propname), values) if not inreplyto: # Default the reply to the first message msgs = self.get(nodeid, 'messages') # Assume messages are sorted by increasing message number here if msgs[0] != nodeid: inreplyto = messages.get(msgs[0], 'messageid') if inreplyto: writer.addheader('In-Reply-To', inreplyto) # attach files if message_files: part = writer.startmultipartbody('mixed') part = writer.nextpart() part.addheader('Content-Transfer-Encoding', 'quoted-printable') body = part.startbody('text/plain; charset=%s' % charset) body.write(content_encoded) for fileid in message_files: name = files.get(fileid, 'name') mime_type = files.get(fileid, 'type') content = files.get(fileid, 'content') part = writer.nextpart() if mime_type == 'text/plain': part.addheader('Content-Disposition', 'attachment;\n filename="%s"' % name) try: content.decode('ascii') except UnicodeError: # the content cannot be 7bit-encoded. # use quoted printable part.addheader('Content-Transfer-Encoding', 'quoted-printable') body = part.startbody('text/plain') body.write(quopri.encodestring(content)) else: part.addheader('Content-Transfer-Encoding', '7bit') body = part.startbody('text/plain') body.write(content) else: # some other type, so encode it if not mime_type: # this should have been done when the file was saved mime_type = mimetypes.guess_type(name)[0] if mime_type is None: mime_type = 'application/octet-stream' part.addheader('Content-Disposition', 'attachment;\n filename="%s"' % name) part.addheader('Content-Transfer-Encoding', 'base64') body = part.startbody(mime_type) body.write(base64.encodestring(content)) writer.lastpart() else: writer.addheader('Content-Transfer-Encoding', 'quoted-printable') body = writer.startbody('text/plain; charset=%s' % charset) body.write(content_encoded) if first: mailer.smtp_send(sendto + bcc_sendto, message) else: mailer.smtp_send(sendto, message) first = False
def send_message(self, nodeid, msgid, note, sendto, from_address=None, bcc_sendto=[]): '''Actually send the nominated message from this node to the sendto recipients, with the note appended. ''' users = self.db.user messages = self.db.msg files = self.db.file if msgid is None: inreplyto = None messageid = None else: inreplyto = messages.get(msgid, 'inreplyto') messageid = messages.get(msgid, 'messageid') # make up a messageid if there isn't one (web edit) if not messageid: # this is an old message that didn't get a messageid, so # create one messageid = "<%s.%s.%s%s@%s>"%(time.time(), random.random(), self.classname, nodeid, self.db.config.MAIL_DOMAIN) if msgid is not None: messages.set(msgid, messageid=messageid) # compose title cn = self.classname title = self.get(nodeid, 'title') or '%s message copy'%cn # figure author information if msgid: authid = messages.get(msgid, 'author') else: authid = self.db.getuid() authname = users.get(authid, 'realname') if not authname: authname = users.get(authid, 'username', '') authaddr = users.get(authid, 'address', '') if authaddr and self.db.config.MAIL_ADD_AUTHOREMAIL: authaddr = " <%s>" % straddr( ('',authaddr) ) elif authaddr: authaddr = "" # make the message body m = [''] # put in roundup's signature if self.db.config.EMAIL_SIGNATURE_POSITION == 'top': m.append(self.email_signature(nodeid, msgid)) # add author information if authid and self.db.config.MAIL_ADD_AUTHORINFO: if msgid and len(self.get(nodeid, 'messages')) == 1: m.append(_("New submission from %(authname)s%(authaddr)s:") % locals()) elif msgid: m.append(_("%(authname)s%(authaddr)s added the comment:") % locals()) else: m.append(_("Change by %(authname)s%(authaddr)s:") % locals()) m.append('') # add the content if msgid is not None: m.append(messages.get(msgid, 'content', '')) # get the files for this message message_files = [] if msgid : for fileid in messages.get(msgid, 'files') : # check the attachment size filename = self.db.filename('file', fileid, None) filesize = os.path.getsize(filename) if filesize <= self.db.config.NOSY_MAX_ATTACHMENT_SIZE: message_files.append(fileid) else: base = self.db.config.TRACKER_WEB link = "".join((base, files.classname, fileid)) filename = files.get(fileid, 'name') m.append(_("File '%(filename)s' not attached - " "you can download it from %(link)s.") % locals()) # add the change note if note: m.append(note) # put in roundup's signature if self.db.config.EMAIL_SIGNATURE_POSITION == 'bottom': m.append(self.email_signature(nodeid, msgid)) # encode the content as quoted-printable charset = getattr(self.db.config, 'EMAIL_CHARSET', 'utf-8') m = '\n'.join(m) if charset != 'utf-8': m = unicode(m, 'utf-8').encode(charset) content = cStringIO.StringIO(m) content_encoded = cStringIO.StringIO() quopri.encode(content, content_encoded, 0) content_encoded = content_encoded.getvalue() # make sure the To line is always the same (for testing mostly) sendto.sort() # make sure we have a from address if from_address is None: from_address = self.db.config.TRACKER_EMAIL # additional bit for after the From: "name" from_tag = getattr(self.db.config, 'EMAIL_FROM_TAG', '') if from_tag: from_tag = ' ' + from_tag subject = '[%s%s] %s'%(cn, nodeid, title) author = (authname + from_tag, from_address) # send an individual message per recipient? if self.db.config.NOSY_EMAIL_SENDING != 'single': sendto = [[address] for address in sendto] else: sendto = [sendto] # now send one or more messages # TODO: I believe we have to create a new message each time as we # can't fiddle the recipients in the message ... worth testing # and/or fixing some day first = True for sendto in sendto: # create the message mailer = Mailer(self.db.config) message, writer = mailer.get_standard_message(sendto, subject, author) # set reply-to to the tracker tracker_name = self.db.config.TRACKER_NAME if charset != 'utf-8': tracker = unicode(tracker_name, 'utf-8').encode(charset) tracker_name = encode_header(tracker_name, charset) writer.addheader('Reply-To', straddr((tracker_name, from_address))) # message ids if messageid: writer.addheader('Message-Id', messageid) if inreplyto: writer.addheader('In-Reply-To', inreplyto) # Generate a header for each link or multilink to # a class that has a name attribute for propname, prop in self.getprops().items(): if not isinstance(prop, (hyperdb.Link, hyperdb.Multilink)): continue cl = self.db.getclass(prop.classname) if not 'name' in cl.getprops(): continue if isinstance(prop, hyperdb.Link): value = self.get(nodeid, propname) if value is None: continue values = [value] else: values = self.get(nodeid, propname) if not values: continue values = [cl.get(v, 'name') for v in values] values = ', '.join(values) writer.addheader("X-Roundup-%s-%s" % (self.classname, propname), values) if not inreplyto: # Default the reply to the first message msgs = self.get(nodeid, 'messages') # Assume messages are sorted by increasing message number here if msgs[0] != nodeid: inreplyto = messages.get(msgs[0], 'messageid') if inreplyto: writer.addheader('In-Reply-To', inreplyto) # attach files if message_files: part = writer.startmultipartbody('mixed') part = writer.nextpart() part.addheader('Content-Transfer-Encoding', 'quoted-printable') body = part.startbody('text/plain; charset=%s'%charset) body.write(content_encoded) for fileid in message_files: name = files.get(fileid, 'name') mime_type = files.get(fileid, 'type') content = files.get(fileid, 'content') part = writer.nextpart() if mime_type == 'text/plain': part.addheader('Content-Disposition', 'attachment;\n filename="%s"'%name) try: content.decode('ascii') except UnicodeError: # the content cannot be 7bit-encoded. # use quoted printable part.addheader('Content-Transfer-Encoding', 'quoted-printable') body = part.startbody('text/plain') body.write(quopri.encodestring(content)) else: part.addheader('Content-Transfer-Encoding', '7bit') body = part.startbody('text/plain') body.write(content) else: # some other type, so encode it if not mime_type: # this should have been done when the file was saved mime_type = mimetypes.guess_type(name)[0] if mime_type is None: mime_type = 'application/octet-stream' part.addheader('Content-Disposition', 'attachment;\n filename="%s"'%name) part.addheader('Content-Transfer-Encoding', 'base64') body = part.startbody(mime_type) body.write(base64.encodestring(content)) writer.lastpart() else: writer.addheader('Content-Transfer-Encoding', 'quoted-printable') body = writer.startbody('text/plain; charset=%s'%charset) body.write(content_encoded) if first: mailer.smtp_send(sendto + bcc_sendto, message) else: mailer.smtp_send(sendto, message) first = False
def topdesk_integration(db, cl, nodeid, oldvalues): """Perform roundup-TOPDesk integration https://www.s3it.uzh.ch/help/issue630 If: * Body of the email starts with `Forwarding Incident I-[0-9]+-[0-9]+ from TOPdesk@UZH:` where `I-[0-9]+-[0-9]+` is the incident number in TOPDesk a mail should be sent to [email protected] to close the ticket. The reply email should have: * `From:` field equal to `[email protected]` * `To:` is equal to `[email protected]` * `Subject:` must contains the incident ID. Even better, we just leave the original subject and prepend Re:, as it was a reply from a regular user. Content of the email will include: Your ticket was transfered to RoundUp: http://.... and possibly the content of the ticket. """ log = db.get_logger() log.debug("TOPDesk-RoundUp integration - new issue %s created", nodeid) # Check if this issue comes from TOPDesk msgids = cl.get(nodeid, 'messages') # one and only one message should be here. if len(msgids) > 1: log.warning("Issue %s has more than one message, which is wrong. " "Only parsing first message.", nodeid) elif len(msgids) == 0: # We don't know what to do with an issue with more than one message. # Just skip it log.warning("Issue %s has no message. Unable to check if it comes " "from TOPDesk. Skipping.") return msgid = msgids[0] msg = db.msg.getnode(msgid) content = msg.content firstline = content.strip().split('\n')[0] if re_forwarded.match(firstline): # Send a reply to [email protected] # Get the URL of the roundup issue base = db.config.TRACKER_WEB if not base.endswith('/'): base = base + '/' issue_url = base + cl.classname + nodeid # Get the requestor name userid = cl.get(nodeid, 'creator') username = db.user.get(userid, 'realname') # Get the ID of the issue on TOPDesk topdesk_id = re_forwarded.search(firstline).group(1) # Mail from is fixed mail_from = '*****@*****.**' # Mail to is fixed mail_to = '*****@*****.**' # Subject is took from the original subject, that is now the # title of the issue mail_subject = 'Re: ' + cl.get(nodeid, 'title') # Build the reply message. # Please note that this message will be sent from TOPDesk to the user message = msg.content mail_body = replymessage_template % { 'mail_from': mail_from, 'mail_subject': mail_subject, 'username': username, 'issue_url': issue_url, 'message': message, 'topdeskid': topdesk_id, } try: mailer = Mailer(db.config) mailer.smtp_send([mail_to], mail_body, sender=mail_from) log.info("Sent reply to %s for issue %s (%s)." % ( mail_to, nodeid, topdesk_id)) db.addjournal(db.issue.classname, nodeid, 'topdesk-notified', {}) except Exception as ex: raise roundupdb.DetectorError( "Error sending reply message for TOPDesk issue %s " "(RoundUp issue %s): %s", topdesk_id, nodeid, ex)