def render(self, client, classname, request, **options): # [ ] limit the information passed to the minimal necessary set c = context(client, self, classname, request) c.update({ 'options': options, 'gettext': lambda s: s2u(client.gettext(s)), 'ngettext': lambda s, p, n: s2u(client.ngettext(s, p, n)) }) s = self._tpl.render(c) return s if sys.version_info[0] > 2 else \ s.encode(client.STORAGE_CHARSET, )
def set_message_attributes(self, message, to, subject, author=None): ''' Add attributes to a standard output message "to" - recipients list "subject" - Subject "author" - (name, address) tuple or None for admin email Subject and author are encoded using the EMAIL_CHARSET from the config (default UTF-8). ''' # encode header values if they need to be charset = getattr(self.config, 'EMAIL_CHARSET', 'utf-8') tracker_name = s2u(self.config.TRACKER_NAME) if not author: author = (tracker_name, self.config.ADMIN_EMAIL) name = author[0] else: name = s2u(author[0]) author = nice_sender_header(name, author[1], charset) try: subject.encode('ascii') message['Subject'] = subject except UnicodeError: message['Subject'] = Header(subject, charset) message['To'] = ', '.join(to) message['From'] = author message['Date'] = formatdate(localtime=True) # add a Precedence header so autoresponders ignore us message['Precedence'] = 'bulk' # Add a unique Roundup header to help filtering try: tracker_name.encode('ascii') message['X-Roundup-Name'] = tracker_name except UnicodeError: message['X-Roundup-Name'] = Header(tracker_name, charset) # and another one to avoid loops message['X-Roundup-Loop'] = 'hello' # finally, an aid to debugging problems message['X-Roundup-Version'] = __version__
def standard_message(self, to, subject, content, author=None): """Send a standard message. Arguments: - to: a list of addresses usable by email.utils.parseaddr(). - subject: the subject as a string. - content: the body of the message as a string. - author: the sender as a (name, address) tuple All strings are assumed to be UTF-8 encoded. """ message = self.get_standard_message() self.set_message_attributes(message, to, subject, author) message.set_payload(s2u(content)) self.smtp_send(to, 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 translate(msgid, domain=None, mapping=None, default=None): result = client.translator.translate(domain, msgid, mapping=mapping, default=default) return s2u(result)