~mailman-coders/mailman/2.1

1595 by Mark Sapiro
Merged and tweaked Jim P's mailman-auto-mod-verbose-members branch.
1
# Copyright (C) 1998-2016 by the Free Software Foundation, Inc.
1 by
This commit was manufactured by cvs2svn to create branch
2
#
3
# This program is free software; you can redistribute it and/or
4
# modify it under the terms of the GNU General Public License
5
# as published by the Free Software Foundation; either version 2
6
# of the License, or (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
830 by bwarsaw
A cleansing pass, almost entirely cosmetic. Such things as whitespace
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
16
# USA.
1 by
This commit was manufactured by cvs2svn to create branch
17
18
19
"""Routines which rectify an old mailing list with current structure.
20
21
The MailList.CheckVersion() method looks for an old .data_version setting in
22
the loaded structure, and if found calls the Update() routine from this
23
module, supplying the list and the state last loaded from storage.  The state
24
is necessary to distinguish from default assignments done in the .InitVars()
25
methods, before .CheckVersion() is called.
26
27
For new versions you should add sections to the UpdateOldVars() and the
28
UpdateOldUsers() sections, to preserve the sense of settings across structural
29
changes.  Note that the routines have only one pass - when .CheckVersions()
30
finds a version change it runs this routine and then updates the data_version
31
number of the list, and then does a .Save(), so the transformations won't be
32
run again until another version change is detected.
33
"""
34
35
1018 by Mark Sapiro
versions.py - Moved imports to module scope.
36
import email
37
1 by
This commit was manufactured by cvs2svn to create branch
38
from types import ListType, StringType
39
40
from Mailman import mm_cfg
41
from Mailman import Utils
42
from Mailman import Message
1018 by Mark Sapiro
versions.py - Moved imports to module scope.
43
from Mailman.Bouncer import _BounceInfo
1 by
This commit was manufactured by cvs2svn to create branch
44
from Mailman.MemberAdaptor import UNKNOWN
45
from Mailman.Logging.Syslog import syslog
46
47
48

49
def Update(l, stored_state):
50
    "Dispose of old vars and user options, mapping to new ones when suitable."
51
    ZapOldVars(l)
52
    UpdateOldUsers(l)
53
    NewVars(l)
54
    UpdateOldVars(l, stored_state)
55
    CanonicalizeUserOptions(l)
56
    NewRequestsDatabase(l)
57
58
59

60
def ZapOldVars(mlist):
61
    for name in ('num_spawns', 'filter_prog', 'clobber_date',
62
                 'public_archive_file_dir', 'private_archive_file_dir',
63
                 'archive_directory',
64
                 # Pre-2.1a4 bounce data
65
                 'minimum_removal_date',
66
                 'minimum_post_count_before_bounce_action',
67
                 'automatic_bounce_action',
68
                 'max_posts_between_bounces',
69
                 ):
70
        if hasattr(mlist, name):
71
            delattr(mlist, name)
72
73
74

75
uniqueval = []
76
def UpdateOldVars(l, stored_state):
77
    """Transform old variable values into new ones, deleting old ones.
78
    stored_state is last snapshot from file, as opposed to from InitVars()."""
79
80
    def PreferStored(oldname, newname, newdefault=uniqueval,
81
                     l=l, state=stored_state):
82
        """Use specified old value if new value is not in stored state.
83
84
        If the old attr does not exist, and no newdefault is specified, the
85
        new attr is *not* created - so either specify a default or be positive
86
        that the old attr exists - or don't depend on the new attr.
87
88
        """
89
        if hasattr(l, oldname):
90
            if not state.has_key(newname):
91
                setattr(l, newname, getattr(l, oldname))
92
            delattr(l, oldname)
93
        if not hasattr(l, newname) and newdefault is not uniqueval:
94
                setattr(l, newname, newdefault)
95
1536 by Mark Sapiro
Added character set recoding to utf-8 for list attributes for Romanian and
96
    def recode(mlist, f, t):
97
        """If the character set for a list's preferred_language has changed,
98
        attempt to recode old string values into the new character set.
99
100
        mlist is the list, f is the old charset and t is the new charset.
101
        """
102
        for x in dir(mlist):
103
            if x.startswith('_'):
104
                continue
105
            nv = doitem(getattr(mlist, x), f, t)
106
            if nv:
107
                setattr(mlist, x, nv)
108
109
    def doitem(v, f, t):
110
        """Recursively process lists, tuples and dictionary values and
111
        convert strings as needed. Return either the updated item or None
112
        if no change."""
1537 by Mark Sapiro
Cleaned the code a bit. No change in results.
113
        changed = False
1536 by Mark Sapiro
Added character set recoding to utf-8 for list attributes for Romanian and
114
        if isinstance(v, str):
1537 by Mark Sapiro
Cleaned the code a bit. No change in results.
115
            return convert(v, f, t)
1536 by Mark Sapiro
Added character set recoding to utf-8 for list attributes for Romanian and
116
        elif isinstance(v, list):
117
            for i in range(len(v)):
118
                nv = doitem(v[i], f, t)
119
                if nv:
120
                    changed = True
1537 by Mark Sapiro
Cleaned the code a bit. No change in results.
121
                    v[i] = nv
1536 by Mark Sapiro
Added character set recoding to utf-8 for list attributes for Romanian and
122
            if changed:
1537 by Mark Sapiro
Cleaned the code a bit. No change in results.
123
                return v
1536 by Mark Sapiro
Added character set recoding to utf-8 for list attributes for Romanian and
124
            else:
125
                return None
126
        elif isinstance(v, tuple):
127
            nt = ()
128
            for i in range(len(v)):
129
                nv = doitem(v[i], f, t)
130
                if nv:
131
                    changed = True
132
                    nt += (nv,)
133
                else:
134
                    nt += (v[i],)
135
            if changed:
136
                return nt
137
            else:
138
                return None
139
        elif isinstance(v, dict):
140
            for k, ov in v.items():
141
                nv = doitem(ov, f, t)
142
                if nv:
143
                    changed = True
1537 by Mark Sapiro
Cleaned the code a bit. No change in results.
144
                    v[k] = nv
1536 by Mark Sapiro
Added character set recoding to utf-8 for list attributes for Romanian and
145
            if changed:
1537 by Mark Sapiro
Cleaned the code a bit. No change in results.
146
                return v
1536 by Mark Sapiro
Added character set recoding to utf-8 for list attributes for Romanian and
147
            else:
148
                return None
149
        else:
150
            return None 
151
152
    def convert(s, f, t):
153
        """This does the actual character set conversion of the string s
154
        from charset f to charset t."""
155
156
        try:
157
            u = unicode(s, f)
158
            is_f = True
159
        except ValueError:
160
            is_f = False
161
        try:
162
            unicode(s, t)
163
            is_t = True
164
        except ValueError:
165
            is_t = False
166
        if is_f and not is_t:
167
            return u.encode(t, 'replace')
168
        else:
1537 by Mark Sapiro
Cleaned the code a bit. No change in results.
169
            return None
1536 by Mark Sapiro
Added character set recoding to utf-8 for list attributes for Romanian and
170
1 by
This commit was manufactured by cvs2svn to create branch
171
    # Migrate to 2.1b3, baw 17-Aug-2001
172
    if hasattr(l, 'dont_respond_to_post_requests'):
173
        oldval = getattr(l, 'dont_respond_to_post_requests')
174
        if not hasattr(l, 'respond_to_post_requests'):
175
            l.respond_to_post_requests = not oldval
176
        del l.dont_respond_to_post_requests
177
178
    # Migrate to 2.1b3, baw 13-Oct-2001
179
    # Basic defaults for new variables
180
    if not hasattr(l, 'default_member_moderation'):
181
        l.default_member_moderation = mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION
182
    if not hasattr(l, 'accept_these_nonmembers'):
183
        l.accept_these_nonmembers = []
184
    if not hasattr(l, 'hold_these_nonmembers'):
185
        l.hold_these_nonmembers = []
186
    if not hasattr(l, 'reject_these_nonmembers'):
187
        l.reject_these_nonmembers = []
188
    if not hasattr(l, 'discard_these_nonmembers'):
189
        l.discard_these_nonmembers = []
190
    if not hasattr(l, 'forward_auto_discards'):
191
        l.forward_auto_discards = mm_cfg.DEFAULT_FORWARD_AUTO_DISCARDS
192
    if not hasattr(l, 'generic_nonmember_action'):
193
        l.generic_nonmember_action = mm_cfg.DEFAULT_GENERIC_NONMEMBER_ACTION
194
    # Now convert what we can...  Note that the interaction between the
195
    # MM2.0.x attributes `moderated', `member_posting_only', and `posters' is
196
    # so confusing, it makes my brain really ache.  Which is why they go away
197
    # in MM2.1.  I think the best we can do semantically is the following:
198
    #
199
    # - If moderated == yes, then any sender who's address is not on the
200
    #   posters attribute would get held for approval.  If the sender was on
201
    #   the posters list, then we'd defer judgement to a later step
202
    # - If member_posting_only == yes, then members could post without holds,
203
    #   and if there were any addresses added to posters, they could also post
204
    #   without holds.
205
    # - If member_posting_only == no, then what happens depends on the value
206
    #   of the posters attribute:
207
    #       o If posters was empty, then anybody can post without their
208
    #         message being held for approval
209
    #       o If posters was non-empty, then /only/ those addresses could post
210
    #         without approval, i.e. members not on posters would have their
211
    #         messages held for approval.
212
    #
213
    # How to translate this mess to MM2.1 values?  I'm sure I got this wrong
214
    # before, but here's how we're going to do it, as of MM2.1b3.
215
    #
216
    # - We'll control member moderation through their Moderate flag, and
217
    #   non-member moderation through the generic_nonmember_action,
218
    #   hold_these_nonmembers, and accept_these_nonmembers.
219
    # - If moderated == yes then we need to troll through the addresses on
220
    #   posters, and any non-members would get added to
221
    #   accept_these_nonmembers.  /Then/ we need to troll through the
222
    #   membership and any member on posters would get their Moderate flag
223
    #   unset, while members not on posters would get their Moderate flag set.
224
    #   Then generic_nonmember_action gets set to 1 (hold) so nonmembers get
225
    #   moderated, and default_member_moderation will be set to 1 (hold) so
226
    #   new members will also get held for moderation.  We'll stop here.
227
    # - We only get to here if moderated == no.
228
    # - If member_posting_only == yes, then we'll turn off the Moderate flag
229
    #   for members.  We troll through the posters attribute and add all those
230
    #   addresses to accept_these_nonmembers.  We'll also set
231
    #   generic_nonmember_action to 1 and default_member_moderation to 0.
232
    #   We'll stop here.
233
    # - We only get to here if member_posting_only == no
234
    # - If posters is empty, then anybody could post without being held for
235
    #   approval, so we'll set generic_nonmember_action to 0 (accept), and
236
    #   we'll turn off the Moderate flag for all members.  We'll also turn off
237
    #   default_member_moderation so new members can post without approval.
238
    #   We'll stop here.
239
    # - We only get here if posters is non-empty.
240
    # - This means that /only/ the addresses on posters got to post without
241
    #   being held for approval.  So first, we troll through posters and add
242
    #   all non-members to accept_these_nonmembers.  Then we troll through the
243
    #   membership and if their address is on posters, we'll clear their
244
    #   Moderate flag, otherwise we'll set it.  We'll turn on
245
    #   default_member_moderation so new members get moderated.  We'll set
246
    #   generic_nonmember_action to 1 (hold) so all other non-members will get
247
    #   moderated.  And I think we're finally done.
248
    #
249
    # SIGH.
250
    if hasattr(l, 'moderated'):
251
        # We'll assume we're converting all these attributes at once
252
        if l.moderated:
253
            #syslog('debug', 'Case 1')
254
            for addr in l.posters:
255
                if not l.isMember(addr):
256
                    l.accept_these_nonmembers.append(addr)
257
            for member in l.getMembers():
258
                l.setMemberOption(member, mm_cfg.Moderate,
259
                                  # reset for explicitly named members
260
                                  member not in l.posters)
261
            l.generic_nonmember_action = 1
262
            l.default_member_moderation = 1
263
        elif l.member_posting_only:
264
            #syslog('debug', 'Case 2')
265
            for addr in l.posters:
266
                if not l.isMember(addr):
267
                    l.accept_these_nonmembers.append(addr)
268
            for member in l.getMembers():
269
                l.setMemberOption(member, mm_cfg.Moderate, 0)
270
            l.generic_nonmember_action = 1
271
            l.default_member_moderation = 0
272
        elif not l.posters:
273
            #syslog('debug', 'Case 3')
274
            for member in l.getMembers():
275
                l.setMemberOption(member, mm_cfg.Moderate, 0)
276
            l.generic_nonmember_action = 0
277
            l.default_member_moderation = 0
278
        else:
279
            #syslog('debug', 'Case 4')
280
            for addr in l.posters:
281
                if not l.isMember(addr):
282
                    l.accept_these_nonmembers.append(addr)
283
            for member in l.getMembers():
284
                l.setMemberOption(member, mm_cfg.Moderate,
285
                                  # reset for explicitly named members
286
                                  member not in l.posters)
287
            l.generic_nonmember_action = 1
288
            l.default_member_moderation = 1
289
        # Now get rid of the old attributes
290
        del l.moderated
291
        del l.posters
292
        del l.member_posting_only
293
    if hasattr(l, 'forbidden_posters'):
294
        # For each of the posters on this list, if they are members, toggle on
295
        # their moderation flag.  If they are not members, then add them to
296
        # hold_these_nonmembers.
297
        forbiddens = l.forbidden_posters
298
        for addr in forbiddens:
299
            if l.isMember(addr):
300
                l.setMemberOption(addr, mm_cfg.Moderate, 1)
301
            else:
302
                l.hold_these_nonmembers.append(addr)
303
        del l.forbidden_posters
304
305
    # Migrate to 1.0b6, klm 10/22/1998:
306
    PreferStored('reminders_to_admins', 'umbrella_list',
307
                 mm_cfg.DEFAULT_UMBRELLA_LIST)
308
309
    # Migrate up to 1.0b5:
310
    PreferStored('auto_subscribe', 'open_subscribe')
311
    PreferStored('closed', 'private_roster')
312
    PreferStored('mimimum_post_count_before_removal',
313
                 'mimimum_post_count_before_bounce_action')
314
    PreferStored('bad_posters', 'forbidden_posters')
315
    PreferStored('automatically_remove', 'automatic_bounce_action')
316
    if hasattr(l, "open_subscribe"):
317
        if l.open_subscribe:
318
            if mm_cfg.ALLOW_OPEN_SUBSCRIBE:
319
                l.subscribe_policy = 0
320
            else:
321
                l.subscribe_policy = 1
322
        else:
323
            l.subscribe_policy = 2      # admin approval
324
        delattr(l, "open_subscribe")
325
    if not hasattr(l, "administrivia"):
326
        setattr(l, "administrivia", mm_cfg.DEFAULT_ADMINISTRIVIA)
327
    if not hasattr(l, "admin_member_chunksize"):
328
        setattr(l, "admin_member_chunksize",
329
                mm_cfg.DEFAULT_ADMIN_MEMBER_CHUNKSIZE)
330
    #
331
    # this attribute was added then deleted, so there are a number of
332
    # cases to take care of
333
    #
334
    if hasattr(l, "posters_includes_members"):
335
        if l.posters_includes_members:
336
            if l.posters:
337
                l.member_posting_only = 1
338
        else:
339
            if l.posters:
340
                l.member_posting_only = 0
341
        delattr(l, "posters_includes_members")
342
    elif l.data_version <= 10 and l.posters:
343
        # make sure everyone gets the behavior the list used to have, but only
344
        # for really old versions of Mailman (1.0b5 or before).  Any newer
345
        # version of Mailman should not get this attribute whacked.
346
        l.member_posting_only = 0
347
    #
348
    # transfer the list data type for holding members and digest members
349
    # to the dict data type starting file format version 11
350
    #
351
    if type(l.members) is ListType:
352
        members = {}
353
        for m in l.members:
354
            members[m] = 1
355
        l.members = members
356
    if type(l.digest_members) is ListType:
357
        dmembers = {}
358
        for dm in l.digest_members:
359
            dmembers[dm] = 1
360
        l.digest_members = dmembers
361
    #
362
    # set admin_notify_mchanges
363
    #
364
    if not hasattr(l, "admin_notify_mchanges"):
365
        setattr(l, "admin_notify_mchanges",
366
                mm_cfg.DEFAULT_ADMIN_NOTIFY_MCHANGES)
367
    #
368
    # Convert the members and digest_members addresses so that the keys of
369
    # both these are always lowercased, but if there is a case difference, the
370
    # value contains the case preserved value
371
    #
372
    for k in l.members.keys():
373
        if k.lower() <> k:
374
            l.members[k.lower()] = Utils.LCDomain(k)
375
            del l.members[k]
376
        elif type(l.members[k]) == StringType and k == l.members[k].lower():
377
            # already converted
378
            pass
379
        else:
380
            l.members[k] = 0
381
    for k in l.digest_members.keys():
382
        if k.lower() <> k:
383
            l.digest_members[k.lower()] = Utils.LCDomain(k)
384
            del l.digest_members[k]
385
        elif type(l.digest_members[k]) == StringType and \
386
                 k == l.digest_members[k].lower():
387
            # already converted
388
            pass
389
        else:
390
            l.digest_members[k] = 0
1516 by Mark Sapiro
A number of changes from the unofficial 2.2 branch have been backported to
391
    #
392
    # Convert pre 2.2 topics regexps which were compiled in verbose mode
393
    # to a non-verbose equivalent.
394
    #
395
    if stored_state['data_version'] < 106 and stored_state.has_key('topics'):
396
        l.topics = []
397
        for name, pattern, description, emptyflag in stored_state['topics']:
398
            pattern = Utils.strip_verbose_pattern(pattern)
399
            l.topics.append((name, pattern, description, emptyflag))
1536 by Mark Sapiro
Added character set recoding to utf-8 for list attributes for Romanian and
400
    #
401
    # Romanian and Russian had their character sets changed in 2.1.19
402
    # to utf-8. If there are any strings in the old encoding, try to recode
403
    # them.
404
    #
405
    if stored_state['data_version'] < 108:
406
        if l.preferred_language == 'ro':
407
            if Utils.GetCharSet('ro') == 'utf-8':
408
                recode(l, 'iso-8859-2', 'utf-8')
409
        if l.preferred_language == 'ru':
410
            if Utils.GetCharSet('ru') == 'utf-8':
411
                recode(l, 'koi8-r', 'utf-8')
412
    #
1421 by Mark Sapiro
Renamed author_is_list to from_is_list.
413
    # from_is_list was called author_is_list in 2.1.16rc2 (only).
414
    PreferStored('author_is_list', 'from_is_list',
415
                 mm_cfg.DEFAULT_FROM_IS_LIST)
1 by
This commit was manufactured by cvs2svn to create branch
416
417
418

419
def NewVars(l):
420
    """Add defaults for these new variables if they don't exist."""
421
    def add_only_if_missing(attr, initval, l=l):
422
        if not hasattr(l, attr):
423
            setattr(l, attr, initval)
424
    # 1.2 beta 1, baw 18-Feb-2000
425
    # Autoresponder mixin class attributes
426
    add_only_if_missing('autorespond_postings', 0)
427
    add_only_if_missing('autorespond_admin', 0)
428
    add_only_if_missing('autorespond_requests', 0)
429
    add_only_if_missing('autoresponse_postings_text', '')
430
    add_only_if_missing('autoresponse_admin_text', '')
431
    add_only_if_missing('autoresponse_request_text', '')
432
    add_only_if_missing('autoresponse_graceperiod', 90)
433
    add_only_if_missing('postings_responses', {})
434
    add_only_if_missing('admin_responses', {})
435
    add_only_if_missing('reply_goes_to_list', '')
436
    add_only_if_missing('preferred_language', mm_cfg.DEFAULT_SERVER_LANGUAGE)
437
    add_only_if_missing('available_languages', [])
438
    add_only_if_missing('digest_volume_frequency',
439
                        mm_cfg.DEFAULT_DIGEST_VOLUME_FREQUENCY)
440
    add_only_if_missing('digest_last_sent_at', 0)
441
    add_only_if_missing('mod_password', None)
1297 by Mark Sapiro
A new list poster password has been implemented. This password may only
442
    add_only_if_missing('post_password', None)
1 by
This commit was manufactured by cvs2svn to create branch
443
    add_only_if_missing('moderator', [])
444
    add_only_if_missing('topics', [])
445
    add_only_if_missing('topics_enabled', 0)
446
    add_only_if_missing('topics_bodylines_limit', 5)
447
    add_only_if_missing('one_last_digest', {})
448
    add_only_if_missing('usernames', {})
449
    add_only_if_missing('personalize', 0)
450
    add_only_if_missing('first_strip_reply_to',
451
                        mm_cfg.DEFAULT_FIRST_STRIP_REPLY_TO)
1516 by Mark Sapiro
A number of changes from the unofficial 2.2 branch have been backported to
452
    add_only_if_missing('subscribe_auto_approval',
453
                        mm_cfg.DEFAULT_SUBSCRIBE_AUTO_APPROVAL)
1 by
This commit was manufactured by cvs2svn to create branch
454
    add_only_if_missing('unsubscribe_policy',
455
                        mm_cfg.DEFAULT_UNSUBSCRIBE_POLICY)
456
    add_only_if_missing('send_goodbye_msg', mm_cfg.DEFAULT_SEND_GOODBYE_MSG)
457
    add_only_if_missing('include_rfc2369_headers', 1)
458
    add_only_if_missing('include_list_post_header', 1)
1242.1.1 by Malte S. Stretz
Added option include_sender_header to suppress rewrite of the Sender header which confuses Outlook (formerly known as FAQ 2.3). See also <http://mail.python.org/pipermail/mailman-developers/2006-July/019040.html>. Bug #266824.
459
    add_only_if_missing('include_sender_header', 1)
1 by
This commit was manufactured by cvs2svn to create branch
460
    add_only_if_missing('bounce_score_threshold',
461
                        mm_cfg.DEFAULT_BOUNCE_SCORE_THRESHOLD)
462
    add_only_if_missing('bounce_info_stale_after',
463
                        mm_cfg.DEFAULT_BOUNCE_INFO_STALE_AFTER)
464
    add_only_if_missing('bounce_you_are_disabled_warnings',
465
                        mm_cfg.DEFAULT_BOUNCE_YOU_ARE_DISABLED_WARNINGS)
466
    add_only_if_missing(
467
        'bounce_you_are_disabled_warnings_interval',
468
        mm_cfg.DEFAULT_BOUNCE_YOU_ARE_DISABLED_WARNINGS_INTERVAL)
469
    add_only_if_missing(
470
        'bounce_unrecognized_goes_to_list_owner',
471
        mm_cfg.DEFAULT_BOUNCE_UNRECOGNIZED_GOES_TO_LIST_OWNER)
472
    add_only_if_missing(
1500 by Mark Sapiro
Implemented the ability to forward bounces to the list owner.
473
        'bounce_notify_owner_on_bounce_increment',
474
        mm_cfg.DEFAULT_BOUNCE_NOTIFY_OWNER_ON_BOUNCE_INCREMENT)
475
    add_only_if_missing(
1 by
This commit was manufactured by cvs2svn to create branch
476
        'bounce_notify_owner_on_disable',
477
        mm_cfg.DEFAULT_BOUNCE_NOTIFY_OWNER_ON_DISABLE)
478
    add_only_if_missing(
479
        'bounce_notify_owner_on_removal',
480
        mm_cfg.DEFAULT_BOUNCE_NOTIFY_OWNER_ON_REMOVAL)
481
    add_only_if_missing('ban_list', [])
482
    add_only_if_missing('filter_mime_types', mm_cfg.DEFAULT_FILTER_MIME_TYPES)
483
    add_only_if_missing('pass_mime_types', mm_cfg.DEFAULT_PASS_MIME_TYPES)
484
    add_only_if_missing('filter_content', mm_cfg.DEFAULT_FILTER_CONTENT)
485
    add_only_if_missing('convert_html_to_plaintext',
486
                        mm_cfg.DEFAULT_CONVERT_HTML_TO_PLAINTEXT)
487
    add_only_if_missing('filter_action', mm_cfg.DEFAULT_FILTER_ACTION)
488
    add_only_if_missing('delivery_status', {})
489
    # This really ought to default to mm_cfg.HOLD, but that doesn't work with
490
    # the current GUI description model.  So, 0==Hold, 1==Reject, 2==Discard
491
    add_only_if_missing('member_moderation_action', 0)
492
    add_only_if_missing('member_moderation_notice', '')
1374.1.4 by Jim Popovitch
Incorporated some feedback from Mark S.
493
    add_only_if_missing('dmarc_moderation_action', 
1479 by Mark Sapiro
Make dmarc_quarantine_moderaction_action a list setting.
494
                       mm_cfg.DEFAULT_DMARC_MODERATION_ACTION)
495
    add_only_if_missing('dmarc_quarantine_moderation_action',
496
                       mm_cfg.DEFAULT_DMARC_QUARANTINE_MODERATION_ACTION)
1600 by Mark Sapiro
Added dmarc_non_moderation_action to list settings.
497
    add_only_if_missing('dmarc_none_moderation_action',
498
                       mm_cfg.DEFAULT_DMARC_NONE_MODERATION_ACTION)
1374.1.1 by Jim Popovitch
Hold/Reject/Discard moderation support for Senders with a DMARC p=reject policy
499
    add_only_if_missing('dmarc_moderation_notice', '')
1519 by Mark Sapiro
Implemented dmarc_wrapped_message_text to optionally add an explanitory
500
    add_only_if_missing('dmarc_wrapped_message_text',
501
                       mm_cfg.DEFAULT_DMARC_WRAPPED_MESSAGE_TEXT)
1585.1.3 by jimpop at template
Improvements based on feedback from Mark Sapiro
502
    add_only_if_missing('member_verbosity_threshold', 
503
                       mm_cfg.DEFAULT_MEMBER_VERBOSITY_THRESHOLD)
1585.1.1 by jimpop at template
Auto-Moderate Verbose Members
504
    add_only_if_missing('member_verbosity_interval',
505
                       mm_cfg.DEFAULT_MEMBER_VERBOSITY_INTERVAL)
1518 by Mark Sapiro
Implemented the equivalent domains feature for list posting/moderation.
506
    add_only_if_missing('equivalent_domains', 
507
                       mm_cfg.DEFAULT_EQUIVALENT_DOMAINS)
1 by
This commit was manufactured by cvs2svn to create branch
508
    add_only_if_missing('new_member_options',
509
                        mm_cfg.DEFAULT_NEW_MEMBER_OPTIONS)
510
    # Emergency moderation flag
511
    add_only_if_missing('emergency', 0)
512
    add_only_if_missing('hold_and_cmd_autoresponses', {})
513
    add_only_if_missing('news_prefix_subject_too', 1)
514
    # Should prefixes be encoded?
515
    if Utils.GetCharSet(l.preferred_language) == 'us-ascii':
516
        encode = 0
517
    else:
518
        encode = 2
519
    add_only_if_missing('encode_ascii_prefixes', encode)
520
    add_only_if_missing('news_moderation', 0)
202 by bwarsaw
NewVars(): Add header_filter_rules if missing.
521
    add_only_if_missing('header_filter_rules', [])
463 by tkikuchi
Merging SF patches:
522
    # Scrubber in regular delivery
523
    add_only_if_missing('scrub_nondigest', 0)
524
    # ContentFilter by file extensions
525
    add_only_if_missing('filter_filename_extensions',
743 by bwarsaw
Whitespace normalization, and updates of copyright years.
526
                        mm_cfg.DEFAULT_FILTER_FILENAME_EXTENSIONS)
463 by tkikuchi
Merging SF patches:
527
    add_only_if_missing('pass_filename_extensions', [])
468 by tkikuchi
[ 790494 ] built-in automatic discard
528
    # automatic discard
529
    add_only_if_missing('max_days_to_hold', 0)
608 by tkikuchi
sourceforge patch id=1107169, re-use member_moderation_notice ...
530
    add_only_if_missing('nonmember_rejection_notice', '')
737 by tkikuchi
Introduce new attribute (collapse_alternatives) to allow HTML in
531
    # multipart/alternative collapse
743 by bwarsaw
Whitespace normalization, and updates of copyright years.
532
    add_only_if_missing('collapse_alternatives',
533
                        mm_cfg.DEFAULT_COLLAPSE_ALTERNATIVES)
1020.1.1 by Tokio Kikuchi
Add 'sibling list' feature: exclude and include lists are other mailing
534
    # exclude/include lists
535
    add_only_if_missing('regular_exclude_lists',
536
                        mm_cfg.DEFAULT_REGULAR_EXCLUDE_LISTS)
537
    add_only_if_missing('regular_include_lists',
538
                        mm_cfg.DEFAULT_REGULAR_INCLUDE_LISTS)
1345 by Mark Sapiro
Backported regular_exclude_ignore list attribute feature from 2.2 branch.
539
    add_only_if_missing('regular_exclude_ignore',
540
                        mm_cfg.DEFAULT_REGULAR_EXCLUDE_IGNORE)
1 by
This commit was manufactured by cvs2svn to create branch
541
542
543

544
def UpdateOldUsers(mlist):
545
    """Transform sense of changed user options."""
546
    # pre-1.0b11 to 1.0b11.  Force all keys in l.passwords to be lowercase
547
    passwords = {}
548
    for k, v in mlist.passwords.items():
549
        passwords[k.lower()] = v
550
    mlist.passwords = passwords
551
    # Go through all the keys in bounce_info.  If the key is not a member, or
552
    # if the data is not a _BounceInfo instance, chuck the bounce info.  We're
553
    # doing things differently now.
554
    for m in mlist.bounce_info.keys():
555
        if not mlist.isMember(m) or not isinstance(mlist.getBounceInfo(m),
556
                                                   _BounceInfo):
557
            del mlist.bounce_info[m]
558
559
560

561
def CanonicalizeUserOptions(l):
562
    """Fix up the user options."""
563
    # I want to put a flag in the list database which tells this routine to
564
    # never try to canonicalize the user options again.
565
    if getattr(l, 'useropts_version', 0) > 0:
566
        return
567
    # pre 1.0rc2 to 1.0rc3.  For all keys in l.user_options to be lowercase,
568
    # but merge options for both cases
569
    options = {}
570
    for k, v in l.user_options.items():
571
        if k is None:
572
            continue
573
        lcuser = k.lower()
574
        flags = 0
575
        if options.has_key(lcuser):
576
            flags = options[lcuser]
577
        flags |= v
578
        options[lcuser] = flags
579
    l.user_options = options
580
    # 2.1alpha3 -> 2.1alpha4.  The DisableDelivery flag is now moved into
581
    # get/setDeilveryStatus().  This must be done after the addresses are
582
    # canonicalized.
583
    for k, v in l.user_options.items():
584
        if not l.isMember(k):
585
            # There's a key in user_options that isn't associated with a real
586
            # member address.  This is likely caused by an earlier bug.
587
            del l.user_options[k]
588
            continue
589
        if l.getMemberOption(k, mm_cfg.DisableDelivery):
590
            # Convert this flag into a legacy disable
591
            l.setDeliveryStatus(k, UNKNOWN)
592
            l.setMemberOption(k, mm_cfg.DisableDelivery, 0)
593
    l.useropts_version = 1
594
595
596

597
def NewRequestsDatabase(l):
598
    """With version 1.2, we use a new pending request database schema."""
599
    r = getattr(l, 'requests', {})
600
    if not r:
601
        # no old-style requests
602
        return
603
    for k, v in r.items():
604
        if k == 'post':
605
            # This is a list of tuples with the following format
606
            #
607
            # a sequential request id integer
608
            # a timestamp float
609
            # a message tuple: (author-email-str, message-text-str)
610
            # a reason string
611
            # the subject string
612
            #
613
            # We'll re-submit this as a new HoldMessage request, but we'll
614
            # blow away the original timestamp and request id.  This means the
615
            # request will live a little longer than it possibly should have,
616
            # but that's no big deal.
617
            for p in v:
618
                author, text = p[2]
619
                reason = p[3]
992 by Mark Sapiro
/cygdrive/c/MM_bzr/log.txt
620
                msg = email.message_from_string(text, Message.Message)
1 by
This commit was manufactured by cvs2svn to create branch
621
                l.HoldMessage(msg, reason)
622
            del r[k]
623
        elif k == 'add_member':
624
            # This is a list of tuples with the following format
625
            #
626
            # a sequential request id integer
627
            # a timestamp float
628
            # a digest flag (0 == nodigest, 1 == digest)
629
            # author-email-str
630
            # password
631
            #
632
            # See the note above; the same holds true.
633
            for ign, ign, digest, addr, password in v:
167 by bwarsaw
NewRequestsDatabase(): HoldSubscription() now takes 5 arguments; the
634
                l.HoldSubscription(addr, '', password, digest,
635
                                   mm_cfg.DEFAULT_SERVER_LANGUAGE)
1 by
This commit was manufactured by cvs2svn to create branch
636
            del r[k]
637
        else:
638
            syslog('error', """\
639
VERY BAD NEWS.  Unknown pending request type `%s' found for list: %s""",
640
                   k, l.internal_name())