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, 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_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