[Mailman-Developers] Released: New option for mailman-cvs: reply-to munging per user

Marc MERLIN marc_news@vasoftware.com
Tue, 12 Mar 2002 02:04:35 -0800


--w7PDEPdKQumQfZlR
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

On Mon, Mar 11, 2002 at 01:18:54AM -0800, Marc MERLIN wrote:
> Per Barry's recommendation, I wrote this by  adding a flag for each user and
> duping each  list message (munged  and non  munged version) and  sending the
> right version to each user.

Just in case someone wonders why we went this way versus having a list of
munged users like you have a list of digest users, is that it would require
to change a lot more code to accomodate a third class of users.

> Since this adds some processing, I added an optimization to bypass this code
> if no one on the list requests munging.

This was the missing piece in my patch yesterday, and implementing it right,
and in an efficient way was tricky, but I'm pretty sure I nailed it now, and
did it in a way that minimizes scans of the list membership.

I think I've tested all the possible cases for this patch, and they worked
correctly.

I've also been  encouraged by the positive feedback from  the few people who
responded on  mailman-users on whether  this patch  would be useful  to them
too.

If you get  the chance, please install this on  your mailman-cvs machine (my
patch applies cleanly as of right now, can't make promises about tomorrow
:D), and let me know if you find something I overlooked.

Barry, if you have the time, let me know if you have questions about this or
if you spot things that you'd have implemented differently.

Thanks,
Marc
-- 
Microsoft is to operating systems & security ....
                                      .... what McDonalds is to gourmet cooking
  
Home page: http://marc.merlins.org/   |   Finger marc_f@merlins.org for PGP key

--w7PDEPdKQumQfZlR
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="replyto.diff.cvs"

diff -urN mailman-cvs/Mailman/Cgi/admin.py mailman-cvs.replyto/Mailman/Cgi/admin.py
--- mailman-cvs/Mailman/Cgi/admin.py	Tue Mar 12 01:48:43 2002
+++ mailman-cvs.replyto/Mailman/Cgi/admin.py	Tue Mar 12 01:50:07 2002
@@ -47,7 +47,7 @@
 i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE)
 
 NL = '\n'
-OPTCOLUMNS = 11
+OPTCOLUMNS = 12
 
 
 
@@ -869,7 +869,7 @@
                                           _('mod'), _('hide'),
                                           _('nomail<br>[reason]'),
                                           _('ack'), _('not metoo'),
-                                          _('nodupes'),
+					  _('nodupes'), _('replyto'),
                                           _('digest'), _('plain'),
                                           _('language'))])
     rowindex = usertable.GetCurrentRowIndex()
@@ -909,7 +909,7 @@
             checked = 0
         box = CheckBox('%s_mod' % addr, value, checked)
         cells.append(Center(box).Format())
-        for opt in ('hide', 'nomail', 'ack', 'notmetoo', 'nodupes'):
+	for opt in ('hide', 'nomail', 'ack', 'notmetoo', 'nodupes', 'replyto'):
             extra = ''
             if opt == 'nomail':
                 status = mlist.getDeliveryStatus(addr)
@@ -989,6 +989,8 @@
         _('''<b>nodupes</b> -- Does the member want to avoid duplicates of the
         same message?'''))
     legend.AddItem(
+        _('''<b>replyto</b> -- Does the member want to have reply-to munged to the list address? (note that it will not be active until per_user_reply_to_allowed is enabled)'''))
+    legend.AddItem(
         _('''<b>digest</b> -- Does the member get messages in digests?
         (otherwise, individual messages)'''))
     legend.AddItem(
@@ -1334,7 +1336,8 @@
                     mlist.setDeliveryStatus(user, MemberAdaptor.BYADMIN)
             else:
                 mlist.setDeliveryStatus(user, MemberAdaptor.ENABLED)
-            for opt in ('hide', 'ack', 'notmetoo', 'nodupes', 'plain'):
+	    for opt in ('hide', 'ack', 'notmetoo', 'nodupes', 'replyto', 
+								    'plain'):
                 opt_code = option_info[opt]
                 if cgidata.has_key('%s_%s' % (user, opt)):
                     mlist.setMemberOption(user, opt_code, 1)
diff -urN mailman-cvs/Mailman/Cgi/options.py mailman-cvs.replyto/Mailman/Cgi/options.py
--- mailman-cvs/Mailman/Cgi/options.py	Mon Mar 11 00:37:06 2002
+++ mailman-cvs.replyto/Mailman/Cgi/options.py	Tue Mar 12 01:50:07 2002
@@ -425,6 +425,7 @@
                            ('remind',      mm_cfg.SuppressPasswordReminder),
                            ('rcvtopic',    mm_cfg.ReceiveNonmatchingTopics),
                            ('nodupes',     mm_cfg.DontReceiveDuplicates),
+                           ('replyto',     mm_cfg.AddListReplyTo),
                            ):
             try:
                 newval = int(cgidata.getvalue(item))
@@ -504,8 +505,7 @@
         finally:
             mlist.Unlock()
         
-        # The enable/disable option and the password remind option may have
-        # their global flags sets.
+        # Several options support having their global flags sets.
         global_enable = None
         if cgidata.getvalue('deliver-globally'):
             # Yes, this is inefficient, but the list is so small it shouldn't
@@ -612,6 +612,18 @@
         mlist.FormatOptionButton(mm_cfg.DontReceiveDuplicates, 1, user))
     replacements['<mm-receive-duplicates-button>'] = (
         mlist.FormatOptionButton(mm_cfg.DontReceiveDuplicates, 0, user))
+    # Let's not tempt the user with an option we're not giving him :) 
+    # (yes, this a _very_ sleazy hack, I know :-D) -- Marc
+    if mlist.per_user_reply_to_allowed:
+	replacements['<mm-munge-replyto-hide1>'] = ( '' )
+	replacements['<mm-munge-replyto-hide2>'] = ( '' )
+    else:
+	replacements['<mm-munge-replyto-hide1>'] = ( '<!---' )
+	replacements['<mm-munge-replyto-hide2>'] = ( '--->' )
+    replacements['<mm-munge-replyto-button>'] = (
+	mlist.FormatOptionButton(mm_cfg.AddListReplyTo, 1, user))
+    replacements['<mm-dont-munge-replyto-button>'] = (
+	mlist.FormatOptionButton(mm_cfg.AddListReplyTo, 0, user))
     replacements['<mm-unsubscribe-button>'] = (
         mlist.FormatButton('unsub', _('Unsubscribe')) + '<br>' +
         CheckBox('unsubconfirm', 1, checked=0).Format() +
diff -urN mailman-cvs/Mailman/Defaults.py mailman-cvs.replyto/Mailman/Defaults.py
--- mailman-cvs/Mailman/Defaults.py	Fri Mar  8 21:09:12 2002
+++ mailman-cvs.replyto/Mailman/Defaults.py	Tue Mar 12 01:50:07 2002
@@ -754,6 +754,33 @@
 from: .*@uplinkpro.com
 """
 
+
+# Reply-To munging should really not exist. Each user can tell mailman not
+# to send them list copies when people group reply (nodupes feature), but the
+# other reason is that some users really insist on not using reply to all, and
+# absolutely want to use their reply function to reply to the list.
+# If teaching them to use their mail client properly with the aid of a baseball
+# bat is not an option, instead of forcing reply-to munging on the whole list
+# (DEFAULT_REPLY_GOES_TO_LIST), and screwing all the users who don't want it,
+# you can allow a per user reply-to setting.
+# As soon as a list member selects this, it will cause each message to be
+# duplicated in your spool (one copy with reply-to, and one copy without), and
+# it will also force a scan of the list userlist to see which user gets which
+# message. You may notice a slowdown on lists with several thousand users.
+# List admins will be allowed to turn this on and off on a per list basis,
+# but this variable takes precedence over the listmaster(s) setting
+# Set to 1 to enable
+SITEWIDE_ALLOW_PER_USER_REPLYTO_MUNGING = 1
+
+# Now, you can choose the default value for new lists (should remain '0' unless
+# you have to accomodate users who beg for the misfeature, and list server can
+# accomodate the additional load)
+DEFAULT_ALLOW_PER_USER_REPLYTO_MUNGING = 0
+
+# You can turn off the various Reply-To munging mistfeatures on a sitewide basis
+# here (the above two options should allow everyone to live without them)
+SITEWIDE_ALLOW_PER_LIST_REPLY_TO_MUNGING = 1
+
 # Mailman can be configured to "munge" Reply-To: headers for any passing
 # messages.  One the one hand, there are a lot of good reasons not to munge
 # Reply-To: but on the other, people really seem to want this feature.  See
@@ -767,7 +794,7 @@
 # Before munging Reply-To: Mailman can be configured to strip any existing
 # Reply-To: header first, or simply extend any existing Reply-To: with one
 # based on the above setting.  This is a boolean variable.
-DEFAULT_FIRST_STRIP_REPLY_TO = 1
+DEFAULT_FIRST_STRIP_REPLY_TO = 0
 
 # SUBSCRIBE POLICY
 # 0 - open list (only when ALLOW_OPEN_SUBSCRIBE is set to 1) **
@@ -1005,6 +1032,7 @@
 ReceiveNonmatchingTopics = 64
 Moderate = 128
 DontReceiveDuplicates = 256
+AddListReplyTo	    = 512
 
 # Authentication contexts.
 #
diff -urN mailman-cvs/Mailman/Defaults.py.in mailman-cvs.replyto/Mailman/Defaults.py.in
--- mailman-cvs/Mailman/Defaults.py.in	Tue Mar 12 01:48:43 2002
+++ mailman-cvs.replyto/Mailman/Defaults.py.in	Tue Mar 12 01:50:07 2002
@@ -756,6 +756,33 @@
 from: .*@uplinkpro.com
 """
 
+
+# Reply-To munging should really not exist. Each user can tell mailman not
+# to send them list copies when people group reply (nodupes feature), but the
+# other reason is that some users really insist on not using reply to all, and
+# absolutely want to use their reply function to reply to the list.
+# If teaching them to use their mail client properly with the aid of a baseball
+# bat is not an option, instead of forcing reply-to munging on the whole list
+# (DEFAULT_REPLY_GOES_TO_LIST), and screwing all the users who don't want it,
+# you can allow a per user reply-to setting.
+# As soon as a list member selects this, it will cause each message to be
+# duplicated in your spool (one copy with reply-to, and one copy without), and
+# it will also force a scan of the list userlist to see which user gets which
+# message. You may notice a slowdown on lists with several thousand users.
+# List admins will be allowed to turn this on and off on a per list basis,
+# but this variable takes precedence over the listmaster(s) setting
+# Set to 1 to enable
+SITEWIDE_ALLOW_PER_USER_REPLYTO_MUNGING = 1
+
+# Now, you can choose the default value for new lists (should remain '0' unless
+# you have to accomodate users who beg for the misfeature, and list server can
+# accomodate the additional load)
+DEFAULT_ALLOW_PER_USER_REPLYTO_MUNGING = 0
+
+# You can turn off the various Reply-To munging mistfeatures on a sitewide basis
+# here (the above two options should allow everyone to live without them)
+SITEWIDE_ALLOW_PER_LIST_REPLY_TO_MUNGING = 1
+
 # Mailman can be configured to "munge" Reply-To: headers for any passing
 # messages.  One the one hand, there are a lot of good reasons not to munge
 # Reply-To: but on the other, people really seem to want this feature.  See
@@ -1014,6 +1041,7 @@
 ReceiveNonmatchingTopics = 64
 Moderate = 128
 DontReceiveDuplicates = 256
+AddListReplyTo	    = 512
 
 # Authentication contexts.
 #
diff -urN mailman-cvs/Mailman/Gui/General.py mailman-cvs.replyto/Mailman/Gui/General.py
--- mailman-cvs/Mailman/Gui/General.py	Mon Mar 11 21:07:00 2002
+++ mailman-cvs.replyto/Mailman/Gui/General.py	Tue Mar 12 01:50:07 2002
@@ -58,7 +58,8 @@
         optvals = [mlist.new_member_options & bitfields[o] for o in OPTIONS]
         opttext = [bitdescrs[o] for o in OPTIONS]
 
-        rtn = [
+        rtn = [ ]
+	rtn.extend ( ( 
             _('''Fundamental list characteristics, including descriptive
             info and basic behaviors.'''),
 
@@ -148,9 +149,38 @@
              posted to the list, to distinguish mailing list messages in in
              mailbox summaries.  Brevity is premium here, it's ok to shorten
              long mailing list names to something more concise, as long as it
-             still identifies the mailing list.""")),
+             still identifies the mailing list.""")) 
+		) )
 
-            _('''<tt>Reply-To:</tt> header munging'''),
+        if (mm_cfg.SITEWIDE_ALLOW_PER_USER_REPLYTO_MUNGING or
+	    mm_cfg.SITEWIDE_ALLOW_PER_LIST_REPLY_TO_MUNGING ):
+	    rtn.append ( _('''<tt>Reply-To:</tt> header munging''') )
+
+
+        if (mm_cfg.SITEWIDE_ALLOW_PER_USER_REPLYTO_MUNGING ):
+	    rtn.append (
+
+            ('per_user_reply_to_allowed', mm_cfg.Radio, (_('No'), _('Yes')), 0,
+             _('''Allow users to individually set a Reply-To to the list?<BR>
+	     Note: this will create additional load, read details.'''),
+	     _("""This option allows individual users to ask the list to
+	     munge posts just for them.<BR>
+             As soon as a list member selects this, it will cause each message
+             to be duplicated in your spool (one copy with reply-to, and one
+             copy without), and it will also force a scan of the list userlist
+             to see which user gets which message. You may notice a slowdown on
+             lists with several thousand users and in this case it may be
+	     inadvisable to allow this<BR>
+	     You may also want all your users to learn to use reply to all for
+             list replies  and force  this setting off  as a  result. On the
+             flipside, it is better to enable this setting and allow a few
+	     users to munge their posts than to turn on reply-to munging for
+	     the whole list and prevent all the users from doing simple replies
+	     to sender"""))
+		)
+
+        if (mm_cfg.SITEWIDE_ALLOW_PER_LIST_REPLY_TO_MUNGING ):
+	    rtn.extend ( (
 
             ('first_strip_reply_to', mm_cfg.Radio, (_('No'), _('Yes')), 0,
              _('''Should any existing <tt>Reply-To:</tt> header found in the
@@ -227,7 +257,9 @@
 
              <p>Note that if the original message contains a
              <tt>Reply-To:</tt> header, it will not be changed.""")),
+		) )
 
+	rtn.extend ( (
             _('Umbrella list settings'),
 
             ('umbrella_list', mm_cfg.Radio, (_('No'), _('Yes')), 0,
@@ -352,8 +384,7 @@
              the mail host's exchanger address, if any.  This setting can be
              useful for selecting among alternative names of a host that has
              multiple addresses.""")),
-
-          ]
+		) )
 
         if mm_cfg.ALLOW_RFC2369_OVERRIDES:
             rtn.append(
diff -urN mailman-cvs/Mailman/HTMLFormatter.py mailman-cvs.replyto/Mailman/HTMLFormatter.py
--- mailman-cvs/Mailman/HTMLFormatter.py	Tue Mar  5 22:24:46 2002
+++ mailman-cvs.replyto/Mailman/HTMLFormatter.py	Tue Mar 12 01:50:07 2002
@@ -117,6 +117,7 @@
                 mm_cfg.SuppressPasswordReminder : 'remind',
                 mm_cfg.ReceiveNonmatchingTopics : 'rcvtopic',
                 mm_cfg.DontReceiveDuplicates    : 'nodupes',
+                mm_cfg.AddListReplyTo		: 'replyto',
                 }[option]
         return '<input type=radio name="%s" value="%d"%s>' % (
             name, value, checked)
diff -urN mailman-cvs/Mailman/Handlers/CookHeaders.py mailman-cvs.replyto/Mailman/Handlers/CookHeaders.py
--- mailman-cvs/Mailman/Handlers/CookHeaders.py	Mon Mar 11 21:07:00 2002
+++ mailman-cvs.replyto/Mailman/Handlers/CookHeaders.py	Tue Mar 12 01:50:07 2002
@@ -87,7 +87,10 @@
     # augment it.  RFC 2822 allows max one Reply-To: header so collapse them
     # if we're adding a value, otherwise don't touch it.  (Should we collapse
     # in all cases?)
-    if not fasttrack:
+    # Turning off Reply-To munging on a sitewide basis doesn't reset the list
+    # option (it just makes it go away from the interface), so we need to
+    # disable the behavior here -- Marc
+    if not fasttrack and mm_cfg.SITEWIDE_ALLOW_PER_LIST_REPLY_TO_MUNGING:
         # Set Reply-To: header to point back to this list
         replyto = []
         if mlist.reply_goes_to_list == 1:
diff -urN mailman-cvs/Mailman/Handlers/SMTPDirect.py mailman-cvs.replyto/Mailman/Handlers/SMTPDirect.py
--- mailman-cvs/Mailman/Handlers/SMTPDirect.py	Mon Mar 11 00:37:23 2002
+++ mailman-cvs.replyto/Mailman/Handlers/SMTPDirect.py	Tue Mar 12 01:50:07 2002
@@ -81,6 +81,23 @@
     if not recips:
         # Nobody to deliver to!
         return
+
+    # If the list has a reply-to/non reply-to split, the message gets queued
+    # twice, once with a Reply-To and once without. We need to weed out the
+    # members that don't match the current post configuration
+    # Let's only do this if we have to, if it's currently allowed by the list
+    # owner, and if it's allowed sitewide -- Marc
+    if mm_cfg.SITEWIDE_ALLOW_PER_USER_REPLYTO_MUNGING and \
+	mlist.per_user_reply_to_allowed and  mlist.mixed_reply_to_munged_users:
+	mungedpost=msgdata.get('replytoadded')
+	for r in recips[:]:
+	    if (mungedpost and \
+	        not mlist.getMemberOption(r, mm_cfg.AddListReplyTo)) \
+	    or \
+	       (not mungedpost and \
+		    mlist.getMemberOption(r, mm_cfg.AddListReplyTo)):
+		recips.remove(r)
+    
     # Calculate the non-VERP envelope sender.
     if mlist:
         envsender = mlist.getListAddress('bounces')
diff -urN mailman-cvs/Mailman/Handlers/ToOutgoing.py mailman-cvs.replyto/Mailman/Handlers/ToOutgoing.py
--- mailman-cvs/Mailman/Handlers/ToOutgoing.py	Sat Mar  9 19:25:01 2002
+++ mailman-cvs.replyto/Mailman/Handlers/ToOutgoing.py	Tue Mar 12 01:50:07 2002
@@ -24,6 +24,7 @@
 from Mailman import mm_cfg
 from Mailman.Queue.sbcache import get_switchboard
 
+COMMASPACE = ', '
 
 
 def process(mlist, msg, msgdata):
@@ -45,6 +46,29 @@
     else:
         # VERP every `inteval' number of times
         msgdata['verp'] = not int(mlist.post_id) % interval
+
     # And now drop the message in qfiles/out
     outq = get_switchboard(mm_cfg.OUTQUEUE_DIR)
-    outq.enqueue(msg, msgdata, listname=mlist.internal_name())
+    msgdata['replytoadded']=0
+    if (not mm_cfg.SITEWIDE_ALLOW_PER_USER_REPLYTO_MUNGING or 
+	not mlist.per_user_reply_to_allowed or
+	not mlist.all_reply_to_munged_users):
+	outq.enqueue(msg, msgdata, listname=mlist.internal_name())
+
+    if (mm_cfg.SITEWIDE_ALLOW_PER_USER_REPLYTO_MUNGING and 
+	mlist.per_user_reply_to_allowed and  
+	    (mlist.mixed_reply_to_munged_users or
+	     mlist.all_reply_to_munged_users) ):
+	msgdata['replytoadded']=1
+        # RFC 2822 allows Reply-To to be a list of addresses. Should a mail
+        # client not be happy with that, the user then has the option of not
+        # doing header munging in the first place and learning how to use the
+        # proper reply function of his mailer :-) -- Marc
+	replyto = []
+	replyto.append(mlist.GetListEmail())
+	if msg['reply-to']:
+	    replyto.append(msg['reply-to'])
+	    del msg['reply-to']
+	msg['Reply-To']=COMMASPACE.join(replyto)
+
+	outq.enqueue(msg, msgdata, listname=mlist.internal_name())
diff -urN mailman-cvs/Mailman/MailCommandHandler.py mailman-cvs.replyto/Mailman/MailCommandHandler.py
--- mailman-cvs/Mailman/MailCommandHandler.py	Tue Mar  5 22:24:48 2002
+++ mailman-cvs.replyto/Mailman/MailCommandHandler.py	Tue Mar 12 01:50:07 2002
@@ -84,6 +84,24 @@
 which have you as an explicit recipient (i.e. if you're both a member of the
 list and in either the To: or Cc: headers).""")
 
+REPLYTO = _("""
+While there are few good reasons to do this, you can ask to have list posts
+contain a reply-to pointing back to the list. It's mainly here if you really
+can't break the habit to use the reply to sender function of your mail client
+to reply to list posts. You should use the reply to all/reply to list function
+of your mail client to answer list posts (mailman is smart enough not to send
+duplicates if you reply to the list and the poster), and you can use your normal
+reply to sender function to reply just to the original post author.
+
+If this option is allowed by the list and the site owners, turning it on will
+cause list posts to contain a Reply-To: header pointing back to the list so that
+you can answer list posts with the reply function of your mail client.
+Please make note that you will then be unable to reply to the author of a
+message without typing his Email address and that many lists will not allow you
+to have a Reply-To the list, so you may be better off not changing the meaning
+of the reply function and getting used to using reply to all for lists posts
+""")
+
 option_desc = {'hide'     : HIDE,
                'nomail'   : NOMAIL,
                'ack'      : ACK,
@@ -91,6 +109,7 @@
                'digest'   : DIGEST,
                'plain'    : PLAIN,
                'nodupes'  : NODUPES,
+               'replyto'  : REPLYTO,
                }
 
 # jcrey: and then the real one
@@ -102,11 +121,20 @@
                'notmetoo': mm_cfg.DontReceiveOwnPosts,
                'digest'  : 0,
                'plain'   : mm_cfg.DisableMime,
-               'nodupes' : mm_cfg.DontReceiveDuplicates
+               'nodupes' : mm_cfg.DontReceiveDuplicates,
+               'replyto' : mm_cfg.AddListReplyTo,
                }
 
 # ordered list
-options = ('hide', 'nomail', 'ack', 'notmetoo', 'digest', 'plain', 'nodupes')
+options = ['hide', 'nomail', 'ack', 'notmetoo', 'digest', 'plain', 'nodupes']
+
+# We don't know which list the user may be on or is trying to get on, so
+# we can't check for mlist.per_user_reply_to_allowed, but we can definitely
+# check for mm_cfg.SITEWIDE_ALLOW_PER_USER_REPLYTO_MUNGING
+# The idea is not to potentially tempt the user with a feature that may not
+# be available :) -- Marc
+if mm_cfg.SITEWIDE_ALLOW_PER_USER_REPLYTO_MUNGING:
+    options.append('replyto')
 
 # strip just the outer layer of quotes
 quotecre = re.compile(r'["\'`](?P<cmd>.*)["\'`]')
diff -urN mailman-cvs/Mailman/MailList.py mailman-cvs.replyto/Mailman/MailList.py
--- mailman-cvs/Mailman/MailList.py	Tue Mar  5 22:24:48 2002
+++ mailman-cvs.replyto/Mailman/MailList.py	Tue Mar 12 01:50:07 2002
@@ -259,6 +259,16 @@
         self.usernames = {}
         self.passwords = {}
         self.new_member_options = mm_cfg.DEFAULT_NEW_MEMBER_OPTIONS
+	# if you have a least one user who wants personalized munging, each
+	# message gets dropped twice in the queue, once munged, once not.
+	# Because this requires resources, and scanning the userlist to see
+	# who gets each message requires resources too, we don't do this unless
+	# we have to (we keep track of whether we have mixed users on the list)
+	# -- Marc
+	self.mixed_reply_to_munged_users = 0
+	# Actually if all users are munged, then we go back to only dropping
+	# one copy of the mail in the spool (after munging)
+	self.all_reply_to_munged_users = 0
 
         # This stuff is configurable
         self.respond_to_post_requests = 1
@@ -270,6 +280,8 @@
                             % mm_cfg.DEFAULT_URL_HOST
         self.owner = [admin]
         self.moderator = []
+	self.per_user_reply_to_allowed = \
+			    mm_cfg.DEFAULT_ALLOW_PER_USER_REPLYTO_MUNGING
         self.reply_goes_to_list = mm_cfg.DEFAULT_REPLY_GOES_TO_LIST
         self.reply_to_address = ''
         self.first_strip_reply_to = mm_cfg.DEFAULT_FIRST_STRIP_REPLY_TO
diff -urN mailman-cvs/Mailman/OldStyleMemberships.py mailman-cvs.replyto/Mailman/OldStyleMemberships.py
--- mailman-cvs/Mailman/OldStyleMemberships.py	Tue Mar  5 22:24:48 2002
+++ mailman-cvs.replyto/Mailman/OldStyleMemberships.py	Tue Mar 12 01:50:07 2002
@@ -264,6 +264,14 @@
         assert self.__mlist.Locked()
         self.__assertIsMember(member)
         memberkey = member.lower()
+    
+	# To avoid unnecessary list rescans, let's see if the replyto or
+	# the digest status changed -- Marc
+	isregular=self.__mlist.members.has_key(memberkey)
+	isdigest=self.__mlist.digest_members.has_key(memberkey)
+	isreplyto=( self.__mlist.user_options[memberkey] & 
+			mm_cfg.AddListReplyTo )
+	
         # There's one extra gotcha we have to deal with.  If the user is
         # toggling the Digests flag, then we need to move their entry from
         # mlist.members to mlist.digest_members or vice versa.  Blarg.  Do
@@ -297,21 +305,50 @@
                 # things up so that the user receives one last digest,
                 # otherwise they may lose some email
                 self.__mlist.one_last_digest[memberkey] = cpuser
-            # We don't need to touch user_options because the digest state
-            # isn't kept as a bitfield flag.
-            return
-        # This is a bit kludgey because the semantics are that if the user has
-        # no options set (i.e. the value would be 0), then they have no entry
-        # in the user_options dict.  We use setdefault() here, and then del
-        # the entry below just to make things (questionably) cleaner.
-        self.__mlist.user_options.setdefault(memberkey, 0)
-        if value:
-            self.__mlist.user_options[memberkey] |= flag
-        else:
-            self.__mlist.user_options[memberkey] &= ~flag
-        if not self.__mlist.user_options[memberkey]:
-            del self.__mlist.user_options[memberkey]
 
+        if flag != mm_cfg.Digests:
+            # This is  a bit kludgey because  the semantics are that  if the
+            # user has no options set (i.e. the value would be 0), then they
+            # have no entry  in the user_options dict.   We use setdefault()
+            # here,  and  then del  the  entry  below  just to  make  things
+            # (questionably) cleaner.
+	    self.__mlist.user_options.setdefault(memberkey, 0)
+	    if value:
+		self.__mlist.user_options[memberkey] |= flag
+	    else:
+		self.__mlist.user_options[memberkey] &= ~flag
+	    if not self.__mlist.user_options[memberkey]:
+		del self.__mlist.user_options[memberkey]
+
+	# We need to do special processing on reply-to in case the switch
+	# causes the first munging user to appear or the last to go away
+	# In order to make the code simpler, we're only going to count
+	# who has the replyto flag set, even if they are disabled. 
+        # We might do split processing in a few cases where it's not needed,
+	# but in return, we don't have to trigger a membership scan every
+	# time a member's delivery status is changed.
+	# Let's only do a rescan if the replyto or digest status changed -- Marc
+	if ( isregular != self.__mlist.members.has_key(memberkey) or 
+	     isdigest != self.__mlist.digest_members.has_key(memberkey) or
+	    isreplyto != ( self.__mlist.user_options[memberkey] & 
+			    mm_cfg.AddListReplyTo ) ) :
+	    nomunge=0
+	    munge=0
+	    self.__mlist.mixed_reply_to_munged_users=0
+	    self.__mlist.all_reply_to_munged_users=0
+	    for member in self.__mlist.members.keys():
+		if self.__mlist.user_options[member] & mm_cfg.AddListReplyTo:
+		    munge=1
+		else:
+		    nomunge=1
+		if munge and nomunge:
+		    self.__mlist.mixed_reply_to_munged_users=1
+		    break
+
+	    if (nomunge == 0) and (munge == 1):
+		self.__mlist.all_reply_to_munged_users=1
+		
+	    
     def setMemberName(self, member, realname):
         assert self.__mlist.Locked()
         self.__assertIsMember(member)
diff -urN mailman-cvs/Mailman/Version.py mailman-cvs.replyto/Mailman/Version.py
--- mailman-cvs/Mailman/Version.py	Tue Mar 12 01:48:43 2002
+++ mailman-cvs.replyto/Mailman/Version.py	Tue Mar 12 01:50:07 2002
@@ -15,7 +15,7 @@
 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 
 # Mailman version
-VERSION = "2.1a4+"
+VERSION = "2.1a4+-replyto"
 
 # And as a hex number in the manner of PY_VERSION_HEX
 ALPHA = 0xa
@@ -36,7 +36,7 @@
                (REL_LEVEL << 4)  | (REL_SERIAL << 0))
 
 # config.pck schema version number
-DATA_FILE_VERSION = 66
+DATA_FILE_VERSION = 67
 
 # qfile/*.db schema version number
 QFILE_SCHEMA_VERSION = 3
diff -urN mailman-cvs/Mailman/versions.py mailman-cvs.replyto/Mailman/versions.py
--- mailman-cvs/Mailman/versions.py	Tue Mar 12 01:48:43 2002
+++ mailman-cvs.replyto/Mailman/versions.py	Tue Mar 12 01:50:07 2002
@@ -322,6 +322,10 @@
     add_only_if_missing('member_moderation_notice', '')
     add_only_if_missing('new_member_options',
                         mm_cfg.DEFAULT_NEW_MEMBER_OPTIONS)
+    add_only_if_missing('mixed_reply_to_munged_users', 0)
+    add_only_if_missing('all_reply_to_munged_users', 0)
+    add_only_if_missing('per_user_reply_to_allowed', \
+			 mm_cfg.DEFAULT_ALLOW_PER_USER_REPLYTO_MUNGING)
 
 
 
diff -urN mailman-cvs/templates/en/help.txt mailman-cvs.replyto/templates/en/help.txt
--- mailman-cvs/templates/en/help.txt	Tue Mar  5 22:24:56 2002
+++ mailman-cvs.replyto/templates/en/help.txt	Tue Mar 12 01:50:07 2002
@@ -85,6 +85,20 @@
             Cc: fields already or are included in multiple lists in
             one message.
 
+	replyto:
+            There are few good reasons to do this. It's mainly here if you
+            really can't break the habit to use the reply to sender function of
+            your mail client to reply to list posts. You should use the reply
+            to all/reply to list function of your mail client (mailman is smart
+            enough not to send duplicates if you reply to the list and the
+            poster) to answer list posts, and reply to sender should be to
+	    reply just to the original post author.
+            When turned on however (if allowed by the list and the site owners),
+            you can ask to have list posts contain a reply-to pointing back to
+            the list so that you can answer list posts with the reply function
+            of your mail client.
+	    Please make note that this is a bad habit and that many lists will
+	    not allow you to reply to the list without replying to all
 
     options
         Show the current values of your list options.
diff -urN mailman-cvs/templates/en/options.html mailman-cvs.replyto/templates/en/options.html
--- mailman-cvs/templates/en/options.html	Tue Mar  5 22:24:56 2002
+++ mailman-cvs.replyto/templates/en/options.html	Tue Mar 12 01:50:07 2002
@@ -300,6 +300,36 @@
         <mm-global-nodupes-button><i>Set globally</i>
         </td></tr>
 
+    <mm-munge-replyto-hide1>
+    <tr><td bgcolor="#cccccc">
+        <strong>Set Reply-To back to the list?</strong><p>
+
+                There are few good reasons to do this. It's mainly here if  
+                you really can't break the habit to use the reply to sender 
+                function of your mail client to reply to list posts. You    
+                should use the reply to all/reply to list function of your  
+                mail client to answer list posts (mailman is smart enough not to
+                send duplicates if you reply to the list and the poster), and
+                you can use your normal reply to sender function to reply just
+                to the original post author.<P>
+		
+                If this option is allowed by the list and the site
+                owners, turning it on will cause list posts to contain a
+                <tt>Reply-To:</tt> header pointing back to the list so that
+                you can answer list posts with the reply function of your mail
+                client.<BR>
+                Please make note that you will then be unable to reply to the
+		author of a message without typing his Email address and that
+		many lists will not allow you to have a Reply-To the list, so
+		you may be better off not changing the meaning of the reply
+		function and getting used to using reply to all for lists posts
+
+        </td><td bgcolor="#cccccc">
+	<mm-dont-munge-replyto-button>No
+	<mm-munge-replyto-button>Yes
+        </td></tr>
+    <mm-munge-replyto-hide2>
+
     <tr><TD colspan="2">
         <center><MM-options-Submit-button></center>
         </td></tr>

--w7PDEPdKQumQfZlR--