From b132a73f15e432eaf43310fce9196ca0c0651465 Mon Sep 17 00:00:00 2001 From: <> Date: Thu, 2 Jan 2003 05:25:50 +0000 Subject: This commit was manufactured by cvs2svn to create branch 'Release_2_1-maint'. --- Mailman/Gui/.cvsignore | 1 + Mailman/Gui/Archive.py | 44 +++++ Mailman/Gui/Autoresponse.py | 98 ++++++++++ Mailman/Gui/Bounce.py | 183 ++++++++++++++++++ Mailman/Gui/ContentFilter.py | 169 ++++++++++++++++ Mailman/Gui/Digest.py | 160 ++++++++++++++++ Mailman/Gui/GUIBase.py | 200 +++++++++++++++++++ Mailman/Gui/General.py | 446 +++++++++++++++++++++++++++++++++++++++++++ Mailman/Gui/Language.py | 122 ++++++++++++ Mailman/Gui/Makefile.in | 69 +++++++ Mailman/Gui/Membership.py | 34 ++++ Mailman/Gui/NonDigest.py | 130 +++++++++++++ Mailman/Gui/Passwords.py | 31 +++ Mailman/Gui/Privacy.py | 398 ++++++++++++++++++++++++++++++++++++++ Mailman/Gui/Topics.py | 160 ++++++++++++++++ Mailman/Gui/Usenet.py | 137 +++++++++++++ Mailman/Gui/__init__.py | 32 ++++ 17 files changed, 2414 insertions(+) create mode 100644 Mailman/Gui/.cvsignore create mode 100644 Mailman/Gui/Archive.py create mode 100644 Mailman/Gui/Autoresponse.py create mode 100644 Mailman/Gui/Bounce.py create mode 100644 Mailman/Gui/ContentFilter.py create mode 100644 Mailman/Gui/Digest.py create mode 100644 Mailman/Gui/GUIBase.py create mode 100644 Mailman/Gui/General.py create mode 100644 Mailman/Gui/Language.py create mode 100644 Mailman/Gui/Makefile.in create mode 100644 Mailman/Gui/Membership.py create mode 100644 Mailman/Gui/NonDigest.py create mode 100644 Mailman/Gui/Passwords.py create mode 100644 Mailman/Gui/Privacy.py create mode 100644 Mailman/Gui/Topics.py create mode 100644 Mailman/Gui/Usenet.py create mode 100644 Mailman/Gui/__init__.py (limited to 'Mailman/Gui') diff --git a/Mailman/Gui/.cvsignore b/Mailman/Gui/.cvsignore new file mode 100644 index 00000000..f3c7a7c5 --- /dev/null +++ b/Mailman/Gui/.cvsignore @@ -0,0 +1 @@ +Makefile diff --git a/Mailman/Gui/Archive.py b/Mailman/Gui/Archive.py new file mode 100644 index 00000000..59c2fd10 --- /dev/null +++ b/Mailman/Gui/Archive.py @@ -0,0 +1,44 @@ +# Copyright (C) 2001,2002 by the Free Software Foundation, Inc. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +from Mailman import mm_cfg +from Mailman.i18n import _ +from Mailman.Gui.GUIBase import GUIBase + + + +class Archive(GUIBase): + def GetConfigCategory(self): + return 'archive', _('Archiving Options') + + def GetConfigInfo(self, mlist, category, subcat=None): + if category <> 'archive': + return None + return [ + _("List traffic archival policies."), + + ('archive', mm_cfg.Toggle, (_('No'), _('Yes')), 0, + _('Archive messages?')), + + ('archive_private', mm_cfg.Radio, (_('public'), _('private')), 0, + _('Is archive file source for public or private archival?')), + + ('archive_volume_frequency', mm_cfg.Radio, + (_('Yearly'), _('Monthly'), _('Quarterly'), + _('Weekly'), _('Daily')), + 0, + _('How often should a new archive volume be started?')), + ] diff --git a/Mailman/Gui/Autoresponse.py b/Mailman/Gui/Autoresponse.py new file mode 100644 index 00000000..3c8a71e0 --- /dev/null +++ b/Mailman/Gui/Autoresponse.py @@ -0,0 +1,98 @@ +# Copyright (C) 2001,2002 by the Free Software Foundation, Inc. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +"""Administrative GUI for the autoresponder.""" + +from Mailman import mm_cfg +from Mailman import Utils +from Mailman.i18n import _ +from Mailman.Gui.GUIBase import GUIBase + +# These are the allowable string substitution variables +ALLOWEDS = ('listname', 'listurl', 'requestemail', 'adminemail', 'owneremail') + + + +class Autoresponse(GUIBase): + def GetConfigCategory(self): + return 'autoreply', _('Auto-responder') + + def GetConfigInfo(self, mlist, category, subcat=None): + if category <> 'autoreply': + return None + WIDTH = mm_cfg.TEXTFIELDWIDTH + + return [ + _("""\ +Auto-responder characteristics.

+ +In the text fields below, string interpolation is performed with +the following key/value substitutions: +

+ +

For each text field, you can either enter the text directly into the text +box, or you can specify a file on your local system to upload as the text."""), + + ('autorespond_postings', mm_cfg.Toggle, (_('No'), _('Yes')), 0, + _('''Should Mailman send an auto-response to mailing list + posters?''')), + + ('autoresponse_postings_text', mm_cfg.FileUpload, + (6, WIDTH), 0, + _('Auto-response text to send to mailing list posters.')), + + ('autorespond_admin', mm_cfg.Toggle, (_('No'), _('Yes')), 0, + _('''Should Mailman send an auto-response to emails sent to the + -owner address?''')), + + ('autoresponse_admin_text', mm_cfg.FileUpload, + (6, WIDTH), 0, + _('Auto-response text to send to -owner emails.')), + + ('autorespond_requests', mm_cfg.Radio, + (_('No'), _('Yes, w/discard'), _('Yes, w/forward')), 0, + _('''Should Mailman send an auto-response to emails sent to the + -request address? If you choose yes, decide whether you want + Mailman to discard the original email, or forward it on to the + system as a normal mail command.''')), + + ('autoresponse_request_text', mm_cfg.FileUpload, + (6, WIDTH), 0, + _('Auto-response text to send to -request emails.')), + + ('autoresponse_graceperiod', mm_cfg.Number, 3, 0, + _('''Number of days between auto-responses to either the mailing + list or -request/-owner address from the same poster. Set to + zero (or negative) for no grace period (i.e. auto-respond to + every message).''')), + ] + + def _setValue(self, mlist, property, val, doc): + # Handle these specially because we may need to convert to/from + # external $-string representation. + if property in ('autoresponse_postings_text', + 'autoresponse_admin_text', + 'autoresponse_request_text'): + val = self._convertString(mlist, property, ALLOWEDS, val, doc) + if val is None: + # There was a problem, so don't set it + return + GUIBase._setValue(self, mlist, property, val, doc) diff --git a/Mailman/Gui/Bounce.py b/Mailman/Gui/Bounce.py new file mode 100644 index 00000000..4986cf28 --- /dev/null +++ b/Mailman/Gui/Bounce.py @@ -0,0 +1,183 @@ +# Copyright (C) 2001,2002 by the Free Software Foundation, Inc. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +from Mailman import mm_cfg +from Mailman.i18n import _ +from Mailman.mm_cfg import days +from Mailman.Gui.GUIBase import GUIBase + + + +class Bounce(GUIBase): + def GetConfigCategory(self): + return 'bounce', _('Bounce processing') + + def GetConfigInfo(self, mlist, category, subcat=None): + if category <> 'bounce': + return None + return [ + _("""These policies control the automatic bounce processing system + in Mailman. Here's an overview of how it works. + +

When a bounce is received, Mailman tries to extract two pieces + of information from the message: the address of the member the + message was intended for, and the severity of the problem causing + the bounce. The severity can be either hard or + soft meaning either a fatal error occurred, or a + transient error occurred. When in doubt, a hard severity is used. + +

If no member address can be extracted from the bounce, then the + bounce is usually discarded. Otherwise, each member is assigned a + bounce score and every time we encounter a bounce from + this member we increment the score. Hard bounces increment by 1 + while soft bounces increment by 0.5. We only increment the bounce + score once per day, so even if we receive ten hard bounces from a + member per day, their score will increase by only 1 for that day. + +

When a member's bounce score is greater than the + bounce score + threshold, the subscription is disabled. Once disabled, the + member will not receive any postings from the list until their + membership is explicitly re-enabled (either by the list + administrator or the user). However, they will receive occasional + reminders that their membership has been disabled, and these + reminders will include information about how to re-enable their + membership. + +

You can control both the + number + of reminders the member will receive and the + frequency with which these reminders are sent. + +

There is one other important configuration variable; after a + certain period of time -- during which no bounces from the member + are received -- the bounce information is + considered + stale and discarded. Thus by adjusting this value, and the + score threshold, you can control how quickly bouncing members are + disabled. You should tune both of these to the frequency and + traffic volume of your list."""), + + _('Bounce detection sensitivity'), + + ('bounce_processing', mm_cfg.Toggle, (_('No'), _('Yes')), 0, + _('Should Mailman perform automatic bounce processing?'), + _("""By setting this value to No, you disable all + automatic bounce processing for this list, however bounce + messages will still be discarded so that the list administrator + isn't inundated with them.""")), + + ('bounce_score_threshold', mm_cfg.Number, 5, 0, + _("""The maximum member bounce score before the member's + subscription is disabled. This value can be a floating point + number.""")), + + ('bounce_info_stale_after', mm_cfg.Number, 5, 0, + _("""The number of days after which a member's bounce information + is discarded, if no new bounces have been received in the + interim. This value must be an integer.""")), + + ('bounce_you_are_disabled_warnings', mm_cfg.Number, 5, 0, + _("""How many Your Membership Is Disabled warnings a + disabled member should get before their address is removed from + the mailing list. Set to 0 to immediately remove an address from + the list once their bounce score exceeds the threshold. This + value must be an integer.""")), + + ('bounce_you_are_disabled_warnings_interval', mm_cfg.Number, 5, 0, + _("""The number of days between sending the Your Membership + Is Disabled warnings. This value must be an integer.""")), + + _('Notifications'), + + ('bounce_unrecognized_goes_to_list_owner', mm_cfg.Toggle, + (_('No'), _('Yes')), 0, + _('''Should Mailman send you, the list owner, any bounce messages + that failed to be detected by the bounce processor? Yes + is recommended.'''), + _("""While Mailman's bounce detector is fairly robust, it's + impossible to detect every bounce format in the world. You + should keep this variable set to Yes for two reasons: 1) + If this really is a permanent bounce from one of your members, + you should probably manually remove them from your list, and 2) + you might want to send the message on to the Mailman developers + so that this new format can be added to its known set. + +

If you really can't be bothered, then set this variable to + No and all non-detected bounces will be discarded + without further processing. + +

Note: This setting will also affect all messages sent + to your list's -admin address. This address is deprecated and + should never be used, but some people may still send mail to this + address. If this happens, and this variable is set to + No those messages too will get discarded. You may want + to set up an + autoresponse + message for email to the -owner and -admin address.""")), + + ('bounce_notify_owner_on_disable', mm_cfg.Toggle, + (_('No'), _('Yes')), 0, + _("""Should Mailman notify you, the list owner, when bounces + cause a member's subscription to be disabled?"""), + _("""By setting this value to No, you turn off + notification messages that are normally sent to the list owners + when a member's delivery is disabled due to excessive bounces. + An attempt to notify the member will always be made.""")), + + ('bounce_notify_owner_on_removal', mm_cfg.Toggle, + (_('No'), _('Yes')), 0, + _("""Should Mailman notify you, the list owner, when bounces + cause a member to be unsubscribed?"""), + _("""By setting this value to No, you turn off + notification messages that are normally sent to the list owners + when a member is unsubscribed due to excessive bounces. An + attempt to notify the member will always be made.""")), + + ] + + def _setValue(self, mlist, property, val, doc): + # Do value conversion from web representation to internal + # representation. + try: + if property == 'bounce_processing': + val = int(val) + elif property == 'bounce_score_threshold': + val = float(val) + elif property == 'bounce_info_stale_after': + val = days(int(val)) + elif property == 'bounce_you_are_disabled_warnings': + val = int(val) + elif property == 'bounce_you_are_disabled_warnings_interval': + val = days(int(val)) + elif property == 'bounce_notify_owner_on_disable': + val = int(val) + elif property == 'bounce_notify_owner_on_removal': + val = int(val) + except ValueError: + doc.addError( + _("""Bad value for %(property)s: %(val)s"""), + tag = _('Error: ')) + return + GUIBase._setValue(self, mlist, property, val, doc) + + def getValue(self, mlist, kind, varname, params): + if varname not in ('bounce_info_stale_after', + 'bounce_you_are_disabled_warnings_interval'): + return None + return int(getattr(mlist, varname) / days(1)) diff --git a/Mailman/Gui/ContentFilter.py b/Mailman/Gui/ContentFilter.py new file mode 100644 index 00000000..cb7ed95c --- /dev/null +++ b/Mailman/Gui/ContentFilter.py @@ -0,0 +1,169 @@ +# Copyright (C) 2002 by the Free Software Foundation, Inc. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +"""GUI component managing the content filtering options. +""" + +from Mailman import mm_cfg +from Mailman.i18n import _ +from Mailman.Gui.GUIBase import GUIBase + +NL = '\n' + + + +class ContentFilter(GUIBase): + def GetConfigCategory(self): + return 'contentfilter', _('Content filtering') + + def GetConfigInfo(self, mlist, category, subcat=None): + if category <> 'contentfilter': + return None + WIDTH = mm_cfg.TEXTFIELDWIDTH + + actions = [_('Discard'), _('Reject'), _('Forward to List Owner')] + if mm_cfg.OWNERS_CAN_PRESERVE_FILTERED_MESSAGES: + actions.append(_('Preserve')) + + return [ + _("""Policies concerning the content of list traffic. + +

Content filtering works like this: when a message is + received by the list and you have enabled content filtering, the + individual attachments are first compared to the + filter + types. If the attachment type matches an entry in the filter + types, it is discarded. + +

Then, if there are pass types + defined, any attachment type that does not match a + pass type is also discarded. If there are no pass types defined, + this check is skipped. + +

After this initial filtering, any multipart + attachments that are empty are removed. If the outer message is + left empty after this filtering, then the whole message is + discarded. Then, each multipart/alternative section will + be replaced by just the first alternative that is non-empty after + filtering. + +

Finally, any text/html parts that are left in the + message may be converted to text/plain if + convert_html_to_plaintext is enabled and the site is + configured to allow these conversions."""), + + ('filter_content', mm_cfg.Radio, (_('No'), _('Yes')), 0, + _("""Should Mailman filter the content of list traffic according + to the settings below?""")), + + ('filter_mime_types', mm_cfg.Text, (10, WIDTH), 0, + _("""Remove message attachments that have a matching content + type."""), + + _("""Use this option to remove each message attachment that + matches one of these content types. Each line should contain a + string naming a MIME type/subtype, + e.g. image/gif. Leave off the subtype to remove all + parts with a matching major content type, e.g. image. + +

Blank lines are ignored. + +

See also pass_mime_types for a content type whitelist.""")), + + ('pass_mime_types', mm_cfg.Text, (10, WIDTH), 0, + _("""Remove message attachments that don't have a matching + content type. Leave this field blank to skip this filter + test."""), + + _("""Use this option to remove each message attachment that does + not have a matching content type. Requirements and formats are + exactly like filter_mime_types. + +

Note: if you add entries to this list but don't add + multipart to this list, any messages with attachments + will be rejected by the pass filter.""")), + + ('convert_html_to_plaintext', mm_cfg.Radio, (_('No'), _('Yes')), 0, + _("""Should Mailman convert text/html parts to plain + text? This conversion happens after MIME attachments have been + stripped.""")), + + ('filter_action', mm_cfg.Radio, tuple(actions), 0, + + _("""Action to take when a message matches the content filtering + rules."""), + + _("""One of these actions is take when the message matches one of + the content filtering rules, meaning, the top-level + content type matches one of the filter_mime_types, or the top-level content type does + not match one of the + pass_mime_types, or if after filtering the subparts of the + message, the message ends up empty. + +

Note this action is not taken if after filtering the message + still contains content. In that case the message is always + forwarded on to the list membership. + +

When messages are discarded, a log entry is written + containing the Message-ID of the discarded message. When + messages are rejected or forwarded to the list owner, a reason + for the rejection is included in the bounce message to the + original author. When messages are preserved, they are saved in + a special queue directory on disk for the site administrator to + view (and possibly rescue) but otherwise discarded. This last + option is only available if enabled by the site + administrator.""")), + ] + + def _setValue(self, mlist, property, val, doc): + if property in ('filter_mime_types', 'pass_mime_types'): + types = [] + for spectype in [s.strip() for s in val.splitlines()]: + ok = 1 + slashes = spectype.count('/') + if slashes == 0 and not spectype: + ok = 0 + elif slashes == 1: + maintype, subtype = [s.strip().lower() + for s in spectype.split('/')] + if not maintype or not subtype: + ok = 0 + elif slashes > 1: + ok = 0 + if not ok: + doc.addError(_('Bad MIME type ignored: %(spectype)s')) + else: + types.append(spectype.strip().lower()) + if property == 'filter_mime_types': + mlist.filter_mime_types = types + elif property == 'pass_mime_types': + mlist.pass_mime_types = types + else: + GUIBase._setValue(self, mlist, property, val, doc) + + def getValue(self, mlist, kind, property, params): + if property == 'filter_mime_types': + return NL.join(mlist.filter_mime_types) + if property == 'pass_mime_types': + return NL.join(mlist.pass_mime_types) + return None diff --git a/Mailman/Gui/Digest.py b/Mailman/Gui/Digest.py new file mode 100644 index 00000000..7eb486c7 --- /dev/null +++ b/Mailman/Gui/Digest.py @@ -0,0 +1,160 @@ +# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +"""Administrative GUI for digest deliveries.""" + +from Mailman import mm_cfg +from Mailman import Utils +from Mailman.i18n import _ + +# Intra-package import +from Mailman.Gui.GUIBase import GUIBase + +# Common b/w nondigest and digest headers & footers. Personalizations may add +# to this. +ALLOWEDS = ('real_name', 'list_name', 'host_name', 'web_page_url', + 'description', 'info', 'cgiext', '_internal_name', + ) + + + +class Digest(GUIBase): + def GetConfigCategory(self): + return 'digest', _('Digest options') + + def GetConfigInfo(self, mlist, category, subcat=None): + if category <> 'digest': + return None + WIDTH = mm_cfg.TEXTFIELDWIDTH + + info = [ + _("Batched-delivery digest characteristics."), + + ('digestable', mm_cfg.Toggle, (_('No'), _('Yes')), 1, + _('Can list members choose to receive list traffic ' + 'bunched in digests?')), + + ('digest_is_default', mm_cfg.Radio, + (_('Regular'), _('Digest')), 0, + _('Which delivery mode is the default for new users?')), + + ('mime_is_default_digest', mm_cfg.Radio, + (_('Plain'), _('MIME')), 0, + _('When receiving digests, which format is default?')), + + ('digest_size_threshhold', mm_cfg.Number, 3, 0, + _('How big in Kb should a digest be before it gets sent out?')), + # Should offer a 'set to 0' for no size threshhold. + + ('digest_send_periodic', mm_cfg.Radio, (_('No'), _('Yes')), 1, + _('Should a digest be dispatched daily when the size threshold ' + "isn't reached?")), + + ('digest_header', mm_cfg.Text, (4, WIDTH), 0, + _('Header added to every digest'), + _("Text attached (as an initial message, before the table" + " of contents) to the top of digests. ") + + Utils.maketext('headfoot.html', raw=1, mlist=mlist)), + + ('digest_footer', mm_cfg.Text, (4, WIDTH), 0, + _('Footer added to every digest'), + _("Text attached (as a final message) to the bottom of digests. ") + + Utils.maketext('headfoot.html', raw=1, mlist=mlist)), + + ('digest_volume_frequency', mm_cfg.Radio, + (_('Yearly'), _('Monthly'), _('Quarterly'), + _('Weekly'), _('Daily')), 0, + _('How often should a new digest volume be started?'), + _('''When a new digest volume is started, the volume number is + incremented and the issue number is reset to 1.''')), + + ('_new_volume', mm_cfg.Toggle, (_('No'), _('Yes')), 0, + _('Should Mailman start a new digest volume?'), + _('''Setting this option instructs Mailman to start a new volume + with the next digest sent out.''')), + + ('_send_digest_now', mm_cfg.Toggle, (_('No'), _('Yes')), 0, + _('''Should Mailman send the next digest right now, if it is not + empty?''')), + ] + +## if mm_cfg.OWNERS_CAN_ENABLE_PERSONALIZATION: +## info.extend([ +## ('digest_personalize', mm_cfg.Toggle, (_('No'), _('Yes')), 1, + +## _('''Should Mailman personalize each digest delivery? +## This is often useful for announce-only lists, but read the details +## section for a discussion of important performance +## issues.'''), + +## _("""Normally, Mailman sends the digest messages to +## the mail server in batches. This is much more efficent +## because it reduces the amount of traffic between Mailman and +## the mail server. + +##

However, some lists can benefit from a more personalized +## approach. In this case, Mailman crafts a new message for +## each member on the digest delivery list. Turning this on +## adds a few more expansion variables that can be included in +## the message header +## and message footer +## but it may degrade the performance of your site as +## a whole. + +##

You need to carefully consider whether the trade-off is +## worth it, or whether there are other ways to accomplish what +## you want. You should also carefully monitor your system load +## to make sure it is acceptable. + +##

These additional substitution variables will be available +## for your headers and footers, when this feature is enabled: + +##