Code

handle missing addresses on users (sf bug 724537)
[roundup.git] / roundup / roundupdb.py
index 8fd9b287bf552ad1920678dca470a16adfc62d94..e5967431488b52fcf2c228fcc24af935b2fce83f 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: roundupdb.py,v 1.73 2002-11-05 22:59:46 richard Exp $
+# $Id: roundupdb.py,v 1.86 2003-04-27 02:24:37 richard Exp $
 
 __doc__ = """
 Extending hyperdb with types specific to issue-tracking.
@@ -24,6 +24,9 @@ Extending hyperdb with types specific to issue-tracking.
 import re, os, smtplib, socket, time, random
 import MimeWriter, cStringIO
 import base64, quopri, mimetypes
+
+from rfc2822 import encode_header
+
 # if available, use the 'email' module, otherwise fallback to 'rfc822'
 try :
     from email.Utils import formataddr as straddr
@@ -40,7 +43,8 @@ except ImportError :
             return '%s%s%s <%s>' % (quotes, name, quotes, address)
         return address
 
-import hyperdb
+from roundup import hyperdb
+from roundup.mailgw import openSMTPConnection
 
 # set to indicate to roundup not to actually _send_ email
 # this var must contain a file to write the mail to
@@ -52,6 +56,20 @@ class Database:
         that owns this connection to the hyperdatabase."""
         return self.user.lookup(self.journaltag)
 
+    def getUserTimezone(self):
+        """Return user timezone defined in 'timezone' property of user class.
+        If no such property exists return 0
+        """
+        userid = self.getuid()
+        try:
+            timezone = int(self.user.get(userid, 'timezone'))
+        except (KeyError, ValueError, TypeError):
+            # If there is no class 'user' or current user doesn't have timezone 
+            # property or that property is not numeric assume he/she lives in 
+            # Greenwich :)
+            timezone = 0
+        return timezone
+
 class MessageSendError(RuntimeError):
     pass
 
@@ -87,13 +105,16 @@ class IssueClass:
         appended to the "messages" field of the specified issue.
         """
 
-    def nosymessage(self, nodeid, msgid, oldvalues):
+    # XXX "bcc" is an optional extra here...
+    def nosymessage(self, nodeid, msgid, oldvalues, whichnosy='nosy',
+            from_address=None, cc=[]): #, bcc=[]):
         """Send a message to the members of an issue's nosy list.
 
         The message is sent only to users on the nosy list who are not
         already on the "recipients" list for the message.
         
         These users are then added to the message's "recipients" list.
+
         """
         users = self.db.user
         messages = self.db.msg
@@ -110,13 +131,32 @@ class IssueClass:
 
         # possibly send the message to the author, as long as they aren't
         # anonymous
-        if (self.db.config.MESSAGES_TO_AUTHOR == 'yes' and
-                users.get(authid, 'username') != 'anonymous'):
-            sendto.append(authid)
+        if (users.get(authid, 'username') != 'anonymous' and
+                not r.has_key(authid)):
+            if (self.db.config.MESSAGES_TO_AUTHOR == 'yes' or
+                (self.db.config.MESSAGES_TO_AUTHOR == 'new' and not oldvalues)):
+                # make sure they have an address
+                add = users.get(authid, 'address')
+                if add:
+                    # send it to them
+                    sendto.append(add)
+                    recipients.append(authid)
+
         r[authid] = 1
 
+        # now deal with cc people.
+        for cc_userid in cc :
+            if r.has_key(cc_userid):
+                continue
+            # make sure they have an address
+            add = users.get(cc_userid, 'address')
+            if add:
+                # send it to them
+                sendto.append(add)
+                recipients.append(cc_userid)
+
         # now figure the nosy people who weren't recipients
-        nosy = self.get(nodeid, 'nosy')
+        nosy = self.get(nodeid, whichnosy)
         for nosyid in nosy:
             # Don't send nosy mail to the anonymous user (that user
             # shouldn't appear in the nosy list, but just in case they
@@ -125,9 +165,12 @@ class IssueClass:
                 continue
             # make sure they haven't seen the message already
             if not r.has_key(nosyid):
-                # send it to them
-                sendto.append(nosyid)
-                recipients.append(nosyid)
+                # make sure they have an address
+                add = users.get(nosyid, 'address')
+                if add:
+                    # send it to them
+                    sendto.append(add)
+                    recipients.append(nosyid)
 
         # generate a change note
         if oldvalues:
@@ -137,19 +180,16 @@ class IssueClass:
 
         # we have new recipients
         if sendto:
-            # map userids to addresses
-            sendto = [users.get(i, 'address') for i in sendto]
-
             # update the message's recipients list
             messages.set(msgid, recipients=recipients)
 
             # send the message
-            self.send_message(nodeid, msgid, note, sendto)
+            self.send_message(nodeid, msgid, note, sendto, from_address)
 
     # backwards compatibility - don't remove
     sendmessage = nosymessage
 
-    def send_message(self, nodeid, msgid, note, sendto):
+    def send_message(self, nodeid, msgid, note, sendto, from_address=None):
         '''Actually send the nominated message from this node to the sendto
            recipients, with the note appended.
         '''
@@ -220,16 +260,27 @@ class IssueClass:
         # 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
+
         # create the message
         message = cStringIO.StringIO()
         writer = MimeWriter.MimeWriter(message)
-        writer.addheader('Subject', '[%s%s] %s'%(cn, nodeid, title))
+        writer.addheader('Subject', '[%s%s] %s'%(cn, nodeid,
+            encode_header(title)))
         writer.addheader('To', ', '.join(sendto))
-        writer.addheader('From', straddr(
-                              (authname, self.db.config.TRACKER_EMAIL) ) )
-        writer.addheader('Reply-To', straddr( 
-                                        (self.db.config.TRACKER_NAME,
-                                         self.db.config.TRACKER_EMAIL) ) )
+        writer.addheader('From', straddr((encode_header(authname) + 
+            from_tag, from_address)))
+        tracker_name = encode_header(self.db.config.TRACKER_NAME)
+        writer.addheader('Reply-To', straddr((tracker_name, from_address)))
+        writer.addheader('Date', time.strftime("%a, %d %b %Y %H:%M:%S +0000",
+            time.gmtime()))
         writer.addheader('MIME-Version', '1.0')
         if messageid:
             writer.addheader('Message-Id', messageid)
@@ -237,14 +288,17 @@ class IssueClass:
             writer.addheader('In-Reply-To', inreplyto)
 
         # add a uniquely Roundup header to help filtering
-        writer.addheader('X-Roundup-Name', self.db.config.TRACKER_NAME)
+        writer.addheader('X-Roundup-Name', tracker_name)
+
+        # avoid email loops
+        writer.addheader('X-Roundup-Loop', 'hello')
 
         # attach files
         if message_files:
             part = writer.startmultipartbody('mixed')
             part = writer.nextpart()
             part.addheader('Content-Transfer-Encoding', 'quoted-printable')
-            body = part.startbody('text/plain')
+            body = part.startbody('text/plain; charset=utf-8')
             body.write(content_encoded)
             for fileid in message_files:
                 name = files.get(fileid, 'name')
@@ -272,19 +326,19 @@ class IssueClass:
             writer.lastpart()
         else:
             writer.addheader('Content-Transfer-Encoding', 'quoted-printable')
-            body = writer.startbody('text/plain')
+            body = writer.startbody('text/plain; charset=utf-8')
             body.write(content_encoded)
 
         # now try to send the message
         if SENDMAILDEBUG:
-            open(SENDMAILDEBUG, 'w').write('FROM: %s\nTO: %s\n%s\n'%(
+            open(SENDMAILDEBUG, 'a').write('FROM: %s\nTO: %s\n%s\n'%(
                 self.db.config.ADMIN_EMAIL,
                 ', '.join(sendto),message.getvalue()))
         else:
             try:
                 # send the message as admin so bounces are sent there
                 # instead of to roundup
-                smtp = smtplib.SMTP(self.db.config.MAILHOST)
+                smtp = openSMTPConnection(self.db.config)
                 smtp.sendmail(self.db.config.ADMIN_EMAIL, sendto,
                     message.getvalue())
             except socket.error, value:
@@ -312,8 +366,8 @@ class IssueClass:
         email = straddr((self.db.config.TRACKER_NAME,
             self.db.config.TRACKER_EMAIL))
 
-        line = '_' * max(len(web), len(email))
-        return '%s\n%s\n%s\n%s'%(line, email, web, line)
+        line = '_' * max(len(web)+2, len(email))
+        return '%s\n%s\n<%s>\n%s'%(line, email, web, line)
 
 
     def generateCreateNote(self, nodeid):