aboutsummaryrefslogtreecommitdiffstats
path: root/Mailman/Gui
diff options
context:
space:
mode:
Diffstat (limited to 'Mailman/Gui')
-rw-r--r--Mailman/Gui/.cvsignore1
-rw-r--r--Mailman/Gui/Archive.py44
-rw-r--r--Mailman/Gui/Autoresponse.py98
-rw-r--r--Mailman/Gui/Bounce.py183
-rw-r--r--Mailman/Gui/ContentFilter.py169
-rw-r--r--Mailman/Gui/Digest.py160
-rw-r--r--Mailman/Gui/GUIBase.py200
-rw-r--r--Mailman/Gui/General.py446
-rw-r--r--Mailman/Gui/Language.py122
-rw-r--r--Mailman/Gui/Makefile.in69
-rw-r--r--Mailman/Gui/Membership.py34
-rw-r--r--Mailman/Gui/NonDigest.py130
-rw-r--r--Mailman/Gui/Passwords.py31
-rw-r--r--Mailman/Gui/Privacy.py398
-rw-r--r--Mailman/Gui/Topics.py160
-rw-r--r--Mailman/Gui/Usenet.py137
-rw-r--r--Mailman/Gui/__init__.py32
17 files changed, 2414 insertions, 0 deletions
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.<p>
+
+In the text fields below, string interpolation is performed with
+the following key/value substitutions:
+<p><ul>
+ <li><b>listname</b> - <em>gets the name of the mailing list</em>
+ <li><b>listurl</b> - <em>gets the list's listinfo URL</em>
+ <li><b>requestemail</b> - <em>gets the list's -request address</em>
+ <li><b>owneremail</b> - <em>gets the list's -owner address</em>
+</ul>
+
+<p>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.
+
+ <p>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 <em>hard</em> or
+ <em>soft</em> meaning either a fatal error occurred, or a
+ transient error occurred. When in doubt, a hard severity is used.
+
+ <p>If no member address can be extracted from the bounce, then the
+ bounce is usually discarded. Otherwise, each member is assigned a
+ <em>bounce score</em> 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.
+
+ <p>When a member's bounce score is greater than the
+ <a href="?VARHELP=bounce/bounce_score_threshold">bounce score
+ threshold</a>, 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.
+
+ <p>You can control both the
+ <a href="?VARHELP=bounce/bounce_you_are_disabled_warnings">number
+ of reminders</a> the member will receive and the
+ <a href="?VARHELP=bounce/bounce_you_are_disabled_warnings_interval"
+ >frequency</a> with which these reminders are sent.
+
+ <p>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
+ <a href="?VARHELP=bounce/bounce_info_stale_after">considered
+ stale</a> 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 <em>No</em>, 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 <em>Your Membership Is Disabled</em> 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 <em>Your Membership
+ Is Disabled</em> 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? <em>Yes</em>
+ 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 <em>Yes</em> 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.
+
+ <p>If you really can't be bothered, then set this variable to
+ <em>No</em> and all non-detected bounces will be discarded
+ without further processing.
+
+ <p><b>Note:</b> 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
+ <em>No</em> those messages too will get discarded. You may want
+ to set up an
+ <a href="?VARHELP=autoreply/autoresponse_admin_text">autoresponse
+ message</a> 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 <em>No</em>, 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 <em>No</em>, 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 <a href="?VARHELP=bounce/%(property)s"
+ >%(property)s</a>: %(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&nbsp;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.
+
+ <p>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
+ <a href="?VARHELP=contentfilter/filter_mime_types">filter
+ types</a>. If the attachment type matches an entry in the filter
+ types, it is discarded.
+
+ <p>Then, if there are <a
+ href="?VARHELP=contentfilter/pass_mime_types">pass types</a>
+ defined, any attachment type that does <em>not</em> match a
+ pass type is also discarded. If there are no pass types defined,
+ this check is skipped.
+
+ <p>After this initial filtering, any <tt>multipart</tt>
+ attachments that are empty are removed. If the outer message is
+ left empty after this filtering, then the whole message is
+ discarded. Then, each <tt>multipart/alternative</tt> section will
+ be replaced by just the first alternative that is non-empty after
+ filtering.
+
+ <p>Finally, any <tt>text/html</tt> parts that are left in the
+ message may be converted to <tt>text/plain</tt> if
+ <a href="?VARHELP=contentfilter/convert_html_to_plaintext"
+ >convert_html_to_plaintext</a> 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 <tt>type/subtype</tt>,
+ e.g. <tt>image/gif</tt>. Leave off the subtype to remove all
+ parts with a matching major content type, e.g. <tt>image</tt>.
+
+ <p>Blank lines are ignored.
+
+ <p>See also <a href="?VARHELP=contentfilter/pass_mime_types"
+ >pass_mime_types</a> 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 <a href="?VARHELP=contentfilter/filter_mime_types"
+ >filter_mime_types</a>.
+
+ <p><b>Note:</b> if you add entries to this list but don't add
+ <tt>multipart</tt> 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 <tt>text/html</tt> 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 <a
+ href="?VARHELP=contentfilter/filter_mime_types"
+ >filter_mime_types</a>, or the top-level content type does
+ <strong>not</strong> match one of the
+ <a href="?VARHELP=contentfilter/pass_mime_types"
+ >pass_mime_types</a>, or if after filtering the subparts of the
+ message, the message ends up empty.
+
+ <p>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.
+
+ <p>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 <a
+## href="?VARHELP=digest/digest_personalize">read the details</a>
+## 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.
+
+## <p>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 <a href="?VARHELP=digest/digest_header">message header</a>
+## and <a href="?VARHELP=digest/digest_footer">message footer</a>
+## but it may degrade the performance of your site as
+## a whole.
+
+## <p>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.
+
+## <p>These additional substitution variables will be available
+## for your headers and footers, when this feature is enabled:
+
+## <ul><li><b>user_address</b> - The address of the user,
+## coerced to lower case.
+## <li><b>user_delivered_to</b> - The case-preserved address
+## that the user is subscribed with.
+## <li><b>user_password</b> - The user's password.
+## <li><b>user_name</b> - The user's full name.
+## <li><b>user_optionsurl</b> - The url to the user's option
+## page.
+## """))
+## ])
+
+ return info
+
+ def _setValue(self, mlist, property, val, doc):
+ # Watch for the special, immediate action attributes
+ if property == '_new_volume' and val:
+ mlist.bump_digest_volume()
+ volume = mlist.volume
+ number = mlist.next_digest_number
+ doc.AddItem(_("""The next digest will be sent as volume
+ %(volume)s, number %(number)s"""))
+ elif property == '_send_digest_now' and val:
+ status = mlist.send_digest_now()
+ if status:
+ doc.AddItem(_("""A digest has been sent."""))
+ else:
+ doc.AddItem(_("""There was no digest to send."""))
+ else:
+ # Everything else...
+ if property in ('digest_header', 'digest_footer'):
+ 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/GUIBase.py b/Mailman/Gui/GUIBase.py
new file mode 100644
index 00000000..7062235e
--- /dev/null
+++ b/Mailman/Gui/GUIBase.py
@@ -0,0 +1,200 @@
+# 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.
+
+"""Base class for all web GUI components."""
+
+import re
+from types import TupleType, ListType
+
+from Mailman import mm_cfg
+from Mailman import Utils
+from Mailman import Errors
+from Mailman.i18n import _
+
+NL = '\n'
+BADJOINER = '</code>, <code>'
+
+
+
+class GUIBase:
+ # Providing a common interface for GUI component form processing. Most
+ # GUI components won't need to override anything, but some may want to
+ # override _setValue() to provide some specialized processing for some
+ # attributes.
+ def _getValidValue(self, mlist, property, wtype, val):
+ # Coerce and validate the new value.
+ #
+ # Radio buttons and boolean toggles both have integral type
+ if wtype in (mm_cfg.Radio, mm_cfg.Toggle):
+ # Let ValueErrors propagate
+ return int(val)
+ # String and Text widgets both just return their values verbatim
+ if wtype in (mm_cfg.String, mm_cfg.Text):
+ return val
+ # This widget contains a single email address
+ if wtype == mm_cfg.Email:
+ # BAW: We must allow blank values otherwise reply_to_address can't
+ # be cleared. This is currently the only mm_cfg.Email type widget
+ # in the interface, so watch out if we ever add any new ones.
+ if val:
+ # Let MMBadEmailError and MMHostileAddress propagate
+ Utils.ValidateEmail(val)
+ return val
+ # These widget types contain lists of email addresses, one per line.
+ # The EmailListEx allows each line to contain either an email address
+ # or a regular expression
+ if wtype in (mm_cfg.EmailList, mm_cfg.EmailListEx):
+ # BAW: value might already be a list, if this is coming from
+ # config_list input. Sigh.
+ if isinstance(val, ListType):
+ return val
+ addrs = []
+ for addr in [s.strip() for s in val.split(NL)]:
+ # Discard empty lines
+ if not addr:
+ continue
+ try:
+ # This throws an exception if the address is invalid
+ Utils.ValidateEmail(addr)
+ except Errors.EmailAddressError:
+ # See if this is a context that accepts regular
+ # expressions, and that the re is legal
+ if wtype == mm_cfg.EmailListEx and addr.startswith('^'):
+ try:
+ re.compile(addr)
+ except re.error:
+ raise ValueError
+ else:
+ raise
+ addrs.append(addr)
+ return addrs
+ # This is a host name, i.e. verbatim
+ if wtype == mm_cfg.Host:
+ return val
+ # This is a number, either a float or an integer
+ if wtype == mm_cfg.Number:
+ num = -1
+ try:
+ num = int(val)
+ except ValueError:
+ # Let ValueErrors percolate up
+ num = float(val)
+ if num < 0:
+ return getattr(mlist, property)
+ return num
+ # This widget is a select box, i.e. verbatim
+ if wtype == mm_cfg.Select:
+ return val
+ # Checkboxes return a list of the selected items, even if only one is
+ # selected.
+ if wtype == mm_cfg.Checkbox:
+ if isinstance(val, ListType):
+ return val
+ return [val]
+ if wtype == mm_cfg.FileUpload:
+ return val
+ if wtype == mm_cfg.Topics:
+ return val
+ # Should never get here
+ assert 0, 'Bad gui widget type: %s' % wtype
+
+ def _setValue(self, mlist, property, val, doc):
+ # Set the value, or override to take special action on the property
+ if not property.startswith('_') and getattr(mlist, property) <> val:
+ setattr(mlist, property, val)
+
+ def _postValidate(self, mlist, doc):
+ # Validate all the attributes for this category
+ pass
+
+ def handleForm(self, mlist, category, subcat, cgidata, doc):
+ for item in self.GetConfigInfo(mlist, category, subcat):
+ # Skip descriptions and legacy non-attributes
+ if not isinstance(item, TupleType) or len(item) < 5:
+ continue
+ # Unpack the gui item description
+ property, wtype, args, deps, desc = item[0:5]
+ # BAW: I know this code is a little crufty but I wanted to
+ # reproduce the semantics of the original code in admin.py as
+ # closely as possible, for now. We can clean it up later.
+ #
+ # The property may be uploadable...
+ uploadprop = property + '_upload'
+ if cgidata.has_key(uploadprop) and cgidata[uploadprop].value:
+ val = cgidata[uploadprop].value
+ elif not cgidata.has_key(property):
+ continue
+ elif isinstance(cgidata[property], ListType):
+ val = [x.value for x in cgidata[property]]
+ else:
+ val = cgidata[property].value
+ # Coerce the value to the expected type, raising exceptions if the
+ # value is invalid
+ try:
+ val = self._getValidValue(mlist, property, wtype, val)
+ except ValueError:
+ doc.addError(_('Invalid value for variable: %(property)s'))
+ # This is the parent of MMBadEmailError and MMHostileAddress
+ except Errors.EmailAddressError:
+ doc.addError(
+ _('Bad email address for option %(property)s: %(val)s'))
+ else:
+ # Set the attribute, which will normally delegate to the mlist
+ self._setValue(mlist, property, val, doc)
+ # Do a final sweep once all the attributes have been set. This is how
+ # we can do cross-attribute assertions
+ self._postValidate(mlist, doc)
+
+ # Convenience method for handling $-string attributes
+ def _convertString(self, mlist, property, alloweds, val, doc):
+ # Is the list using $-strings?
+ dollarp = getattr(mlist, 'use_dollar_strings', 0)
+ if dollarp:
+ ids = Utils.dollar_identifiers(val)
+ else:
+ # %-strings
+ ids = Utils.percent_identifiers(val)
+ # Here's the list of allowable interpolations
+ for allowed in alloweds:
+ if ids.has_key(allowed):
+ del ids[allowed]
+ if ids:
+ # What's left are not allowed
+ badkeys = ids.keys()
+ badkeys.sort()
+ bad = BADJOINER.join(badkeys)
+ doc.addError(_(
+ """The following illegal substitution variables were
+ found in the <code>%(property)s</code> string:
+ <code>%(bad)s</code>
+ <p>Your list may not operate properly until you correct this
+ problem."""), tag=_('Warning: '))
+ return val
+ # Now if we're still using %-strings, do a roundtrip conversion and
+ # see if the converted value is the same as the new value. If not,
+ # then they probably left off a trailing `s'. We'll warn them and use
+ # the corrected string.
+ if not dollarp:
+ fixed = Utils.to_percent(Utils.to_dollar(val))
+ if fixed <> val:
+ doc.addError(_(
+ """Your <code>%(property)s</code> string appeared to
+ have some correctable problems in its new value.
+ The fixed value will be used instead. Please
+ double check that this is what you intended.
+ """))
+ return fixed
+ return val
diff --git a/Mailman/Gui/General.py b/Mailman/Gui/General.py
new file mode 100644
index 00000000..a33d1004
--- /dev/null
+++ b/Mailman/Gui/General.py
@@ -0,0 +1,446 @@
+# 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.
+
+"""MailList mixin class managing the general options.
+"""
+
+from Mailman import mm_cfg
+from Mailman import Utils
+from Mailman import Errors
+from Mailman.i18n import _
+from Mailman.Gui.GUIBase import GUIBase
+
+OPTIONS = ('hide', 'ack', 'notmetoo', 'nodupes')
+
+
+
+class General(GUIBase):
+ def GetConfigCategory(self):
+ return 'general', _('General Options')
+
+ def GetConfigInfo(self, mlist, category, subcat):
+ if category <> 'general':
+ return None
+ WIDTH = mm_cfg.TEXTFIELDWIDTH
+
+ # These are for the default_options checkboxes below.
+ bitfields = {'hide' : mm_cfg.ConcealSubscription,
+ 'ack' : mm_cfg.AcknowledgePosts,
+ 'notmetoo' : mm_cfg.DontReceiveOwnPosts,
+ 'nodupes' : mm_cfg.DontReceiveDuplicates
+ }
+ bitdescrs = {
+ 'hide' : _("Conceal the member's address"),
+ 'ack' : _("Acknowledge the member's posting"),
+ 'notmetoo' : _("Do not send a copy of a member's own post"),
+ 'nodupes' :
+ _('Filter out duplicate messages to list members (if possible)'),
+ }
+
+ optvals = [mlist.new_member_options & bitfields[o] for o in OPTIONS]
+ opttext = [bitdescrs[o] for o in OPTIONS]
+
+ rtn = [
+ _('''Fundamental list characteristics, including descriptive
+ info and basic behaviors.'''),
+
+ _('General list personality'),
+
+ ('real_name', mm_cfg.String, WIDTH, 0,
+ _('The public name of this list (make case-changes only).'),
+ _('''The capitalization of this name can be changed to make it
+ presentable in polite company as a proper noun, or to make an
+ acronym part all upper case, etc. However, the name will be
+ advertised as the email address (e.g., in subscribe confirmation
+ notices), so it should <em>not</em> be otherwise altered. (Email
+ addresses are not case sensitive, but they are sensitive to
+ almost everything else :-)''')),
+
+ ('owner', mm_cfg.EmailList, (3, WIDTH), 0,
+ _("""The list administrator email addresses. Multiple
+ administrator addresses, each on separate line is okay."""),
+
+ _('''There are two ownership roles associated with each mailing
+ list. The <em>list administrators</em> are the people who have
+ ultimate control over all parameters of this mailing list. They
+ are able to change any list configuration variable available
+ through these administration web pages.
+
+ <p>The <em>list moderators</em> have more limited permissions;
+ they are not able to change any list configuration variable, but
+ they are allowed to tend to pending administration requests,
+ including approving or rejecting held subscription requests, and
+ disposing of held postings. Of course, the <em>list
+ administrators</em> can also tend to pending requests.
+
+ <p>In order to split the list ownership duties into
+ administrators and moderators, you must
+ <a href="passwords">set a separate moderator password</a>,
+ and also provide the <a href="?VARHELP=general/moderator">email
+ addresses of the list moderators</a>. Note that the field you
+ are changing here specifies the list administrators.''')),
+
+ ('moderator', mm_cfg.EmailList, (3, WIDTH), 0,
+ _("""The list moderator email addresses. Multiple
+ moderator addresses, each on separate line is okay."""),
+
+ _('''There are two ownership roles associated with each mailing
+ list. The <em>list administrators</em> are the people who have
+ ultimate control over all parameters of this mailing list. They
+ are able to change any list configuration variable available
+ through these administration web pages.
+
+ <p>The <em>list moderators</em> have more limited permissions;
+ they are not able to change any list configuration variable, but
+ they are allowed to tend to pending administration requests,
+ including approving or rejecting held subscription requests, and
+ disposing of held postings. Of course, the <em>list
+ administrators</em> can also tend to pending requests.
+
+ <p>In order to split the list ownership duties into
+ administrators and moderators, you must
+ <a href="passwords">set a separate moderator password</a>,
+ and also provide the email addresses of the list moderators in
+ this section. Note that the field you are changing here
+ specifies the list moderators.''')),
+
+ ('description', mm_cfg.String, WIDTH, 0,
+ _('A terse phrase identifying this list.'),
+
+ _('''This description is used when the mailing list is listed with
+ other mailing lists, or in headers, and so forth. It should
+ be as succinct as you can get it, while still identifying what
+ the list is.''')),
+
+ ('info', mm_cfg.Text, (7, WIDTH), 0,
+ _('''An introductory description - a few paragraphs - about the
+ list. It will be included, as html, at the top of the listinfo
+ page. Carriage returns will end a paragraph - see the details
+ for more info.'''),
+ _("""The text will be treated as html <em>except</em> that
+ newlines will be translated to &lt;br&gt; - so you can use links,
+ preformatted text, etc, but don't put in carriage returns except
+ where you mean to separate paragraphs. And review your changes -
+ bad html (like some unterminated HTML constructs) can prevent
+ display of the entire listinfo page.""")),
+
+ ('subject_prefix', mm_cfg.String, WIDTH, 0,
+ _('Prefix for subject line of list postings.'),
+ _("""This text will be prepended to subject lines of messages
+ 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.""")),
+
+ ('anonymous_list', mm_cfg.Radio, (_('No'), _('Yes')), 0,
+ _("""Hide the sender of a message, replacing it with the list
+ address (Removes From, Sender and Reply-To fields)""")),
+
+ _('''<tt>Reply-To:</tt> header munging'''),
+
+ ('first_strip_reply_to', mm_cfg.Radio, (_('No'), _('Yes')), 0,
+ _('''Should any existing <tt>Reply-To:</tt> header found in the
+ original message be stripped? If so, this will be done
+ regardless of whether an explict <tt>Reply-To:</tt> header is
+ added by Mailman or not.''')),
+
+ ('reply_goes_to_list', mm_cfg.Radio,
+ (_('Poster'), _('This list'), _('Explicit address')), 0,
+ _('''Where are replies to list messages directed?
+ <tt>Poster</tt> is <em>strongly</em> recommended for most mailing
+ lists.'''),
+
+ # Details for reply_goes_to_list
+ _("""This option controls what Mailman does to the
+ <tt>Reply-To:</tt> header in messages flowing through this
+ mailing list. When set to <em>Poster</em>, no <tt>Reply-To:</tt>
+ header is added by Mailman, although if one is present in the
+ original message, it is not stripped. Setting this value to
+ either <em>This list</em> or <em>Explicit address</em> causes
+ Mailman to insert a specific <tt>Reply-To:</tt> header in all
+ messages, overriding the header in the original message if
+ necessary (<em>Explicit address</em> inserts the value of <a
+ href="?VARHELP=general/reply_to_address">reply_to_address</a>).
+
+ <p>There are many reasons not to introduce or override the
+ <tt>Reply-To:</tt> header. One is that some posters depend on
+ their own <tt>Reply-To:</tt> settings to convey their valid
+ return address. Another is that modifying <tt>Reply-To:</tt>
+ makes it much more difficult to send private replies. See <a
+ href="http://www.unicom.com/pw/reply-to-harmful.html">`Reply-To'
+ Munging Considered Harmful</a> for a general discussion of this
+ issue. See <a
+ href="http://www.metasystema.org/essays/reply-to-useful.mhtml">Reply-To
+ Munging Considered Useful</a> for a dissenting opinion.
+
+ <p>Some mailing lists have restricted posting privileges, with a
+ parallel list devoted to discussions. Examples are `patches' or
+ `checkin' lists, where software changes are posted by a revision
+ control system, but discussion about the changes occurs on a
+ developers mailing list. To support these types of mailing
+ lists, select <tt>Explicit address</tt> and set the
+ <tt>Reply-To:</tt> address below to point to the parallel
+ list.""")),
+
+ ('reply_to_address', mm_cfg.Email, WIDTH, 0,
+ _('Explicit <tt>Reply-To:</tt> header.'),
+ # Details for reply_to_address
+ _("""This is the address set in the <tt>Reply-To:</tt> header
+ when the <a
+ href="?VARHELP=general/reply_goes_to_list">reply_goes_to_list</a>
+ option is set to <em>Explicit address</em>.
+
+ <p>There are many reasons not to introduce or override the
+ <tt>Reply-To:</tt> header. One is that some posters depend on
+ their own <tt>Reply-To:</tt> settings to convey their valid
+ return address. Another is that modifying <tt>Reply-To:</tt>
+ makes it much more difficult to send private replies. See <a
+ href="http://www.unicom.com/pw/reply-to-harmful.html">`Reply-To'
+ Munging Considered Harmful</a> for a general discussion of this
+ issue. See <a
+ href="http://www.metasystema.org/essays/reply-to-useful.mhtml">Reply-To
+ Munging Considered Useful</a> for a dissenting opinion.
+
+ <p>Some mailing lists have restricted posting privileges, with a
+ parallel list devoted to discussions. Examples are `patches' or
+ `checkin' lists, where software changes are posted by a revision
+ control system, but discussion about the changes occurs on a
+ developers mailing list. To support these types of mailing
+ lists, specify the explicit <tt>Reply-To:</tt> address here. You
+ must also specify <tt>Explicit address</tt> in the
+ <tt>reply_goes_to_list</tt>
+ variable.
+
+ <p>Note that if the original message contains a
+ <tt>Reply-To:</tt> header, it will not be changed.""")),
+
+ _('Umbrella list settings'),
+
+ ('umbrella_list', mm_cfg.Radio, (_('No'), _('Yes')), 0,
+ _('''Send password reminders to, eg, "-owner" address instead of
+ directly to user.'''),
+
+ _("""Set this to yes when this list is intended to cascade only
+ to other mailing lists. When set, meta notices like
+ confirmations and password reminders will be directed to an
+ address derived from the member\'s address - it will have the
+ value of "umbrella_member_suffix" appended to the member's
+ account name.""")),
+
+ ('umbrella_member_suffix', mm_cfg.String, WIDTH, 0,
+ _('''Suffix for use when this list is an umbrella for other
+ lists, according to setting of previous "umbrella_list"
+ setting.'''),
+
+ _("""When "umbrella_list" is set to indicate that this list has
+ other mailing lists as members, then administrative notices like
+ confirmations and password reminders need to not be sent to the
+ member list addresses, but rather to the owner of those member
+ lists. In that case, the value of this setting is appended to
+ the member's account name for such notices. `-owner' is the
+ typical choice. This setting has no effect when "umbrella_list"
+ is "No".""")),
+
+ _('Notifications'),
+
+ ('send_reminders', mm_cfg.Radio, (_('No'), _('Yes')), 0,
+ _('''Send monthly password reminders?'''),
+
+ _('''Turn this on if you want password reminders to be sent once
+ per month to your members. Note that members may disable their
+ own individual password reminders.''')),
+
+ ('welcome_msg', mm_cfg.Text, (4, WIDTH), 0,
+ _('''List-specific text prepended to new-subscriber welcome
+ message'''),
+
+ _("""This value, if any, will be added to the front of the
+ new-subscriber welcome message. The rest of the welcome message
+ already describes the important addresses and URLs for the
+ mailing list, so you don't need to include any of that kind of
+ stuff here. This should just contain mission-specific kinds of
+ things, like etiquette policies or team orientation, or that kind
+ of thing.
+
+ <p>Note that this text will be wrapped, according to the
+ following rules:
+ <ul><li>Each paragraph is filled so that no line is longer than
+ 70 characters.
+ <li>Any line that begins with whitespace is not filled.
+ <li>A blank line separates paragraphs.
+ </ul>""")),
+
+ ('send_welcome_msg', mm_cfg.Radio, (_('No'), _('Yes')), 0,
+ _('Send welcome message to newly subscribed members?'),
+ _("""Turn this off only if you plan on subscribing people manually
+ and don't want them to know that you did so. This option is most
+ useful for transparently migrating lists from some other mailing
+ list manager to Mailman.""")),
+
+ ('goodbye_msg', mm_cfg.Text, (4, WIDTH), 0,
+ _('''Text sent to people leaving the list. If empty, no special
+ text will be added to the unsubscribe message.''')),
+
+ ('send_goodbye_msg', mm_cfg.Radio, (_('No'), _('Yes')), 0,
+ _('Send goodbye message to members when they are unsubscribed?')),
+
+ ('admin_immed_notify', mm_cfg.Radio, (_('No'), _('Yes')), 0,
+ _('''Should the list moderators get immediate notice of new
+ requests, as well as daily notices about collected ones?'''),
+
+ _('''List moderators (and list administrators) are sent daily
+ reminders of requests pending approval, like subscriptions to a
+ moderated list, or postings that are being held for one reason or
+ another. Setting this option causes notices to be sent
+ immediately on the arrival of new requests as well.''')),
+
+ ('admin_notify_mchanges', mm_cfg.Radio, (_('No'), _('Yes')), 0,
+ _('''Should administrator get notices of subscribes and
+ unsubscribes?''')),
+
+ ('respond_to_post_requests', mm_cfg.Radio,
+ (_('No'), _('Yes')), 0,
+ _('Send mail to poster when their posting is held for approval?'),
+
+ _("""Approval notices are sent when mail triggers certain of the
+ limits <em>except</em> routine list moderation and spam filters,
+ for which notices are <em>not</em> sent. This option overrides
+ ever sending the notice.""")),
+
+ _('Additional settings'),
+
+ ('emergency', mm_cfg.Toggle, (_('No'), _('Yes')), 0,
+ _('Emergency moderation of all list traffic.'),
+ _("""When this option is enabled, all list traffic is emergency
+ moderated, i.e. held for moderation. Turn this option on when
+ your list is experiencing a flamewar and you want a cooling off
+ period.""")),
+
+ ('new_member_options', mm_cfg.Checkbox,
+ (opttext, optvals, 0, OPTIONS),
+ # The description for new_member_options includes a kludge where
+ # we add a hidden field so that even when all the checkboxes are
+ # deselected, the form data will still have a new_member_options
+ # key (it will always be a list). Otherwise, we'd never be able
+ # to tell if all were deselected!
+ 0, _('''Default options for new members joining this list.<input
+ type="hidden" name="new_member_options" value="ignore">'''),
+
+ _("""When a new member is subscribed to this list, their initial
+ set of options is taken from the this variable's setting.""")),
+
+ ('administrivia', mm_cfg.Radio, (_('No'), _('Yes')), 0,
+ _('''(Administrivia filter) Check postings and intercept ones
+ that seem to be administrative requests?'''),
+
+ _("""Administrivia tests will check postings to see whether it's
+ really meant as an administrative request (like subscribe,
+ unsubscribe, etc), and will add it to the the administrative
+ requests queue, notifying the administrator of the new request,
+ in the process.""")),
+
+ ('max_message_size', mm_cfg.Number, 7, 0,
+ _('''Maximum length in kilobytes (KB) of a message body. Use 0
+ for no limit.''')),
+
+ ('host_name', mm_cfg.Host, WIDTH, 0,
+ _('Host name this list prefers for email.'),
+
+ _("""The "host_name" is the preferred name for email to
+ mailman-related addresses on this host, and generally should be
+ 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(
+ ('include_rfc2369_headers', mm_cfg.Radio,
+ (_('No'), _('Yes')), 0,
+ _("""Should messages from this mailing list include the
+ <a href="http://www.faqs.org/rfcs/rfc2369.html">RFC 2369</a>
+ (i.e. <tt>List-*</tt>) headers? <em>Yes</em> is highly
+ recommended."""),
+
+ _("""RFC 2369 defines a set of List-* headers that are
+ normally added to every message sent to the list membership.
+ These greatly aid end-users who are using standards compliant
+ mail readers. They should normally always be enabled.
+
+ <p>However, not all mail readers are standards compliant yet,
+ and if you have a large number of members who are using
+ non-compliant mail readers, they may be annoyed at these
+ headers. You should first try to educate your members as to
+ why these headers exist, and how to hide them in their mail
+ clients. As a last resort you can disable these headers, but
+ this is not recommended (and in fact, your ability to disable
+ these headers may eventually go away)."""))
+ )
+ # Suppression of List-Post: headers
+ rtn.append(
+ ('include_list_post_header', mm_cfg.Radio,
+ (_('No'), _('Yes')), 0,
+ _('Should postings include the <tt>List-Post:</tt> header?'),
+ _("""The <tt>List-Post:</tt> header is one of the headers
+ recommended by
+ <a href="http://www.faqs.org/rfcs/rfc2369.html">RFC 2369</a>.
+ However for some <em>announce-only</em> mailing lists, only a
+ very select group of people are allowed to post to the list; the
+ general membership is usually not allowed to post. For lists of
+ this nature, the <tt>List-Post:</tt> header is misleading.
+ Select <em>No</em> to disable the inclusion of this header. (This
+ does not affect the inclusion of the other <tt>List-*:</tt>
+ headers.)"""))
+ )
+
+ return rtn
+
+ def _setValue(self, mlist, property, val, doc):
+ if property == 'real_name' and \
+ val.lower() <> mlist.internal_name().lower():
+ # These values can't differ by other than case
+ doc.addError(_("""<b>real_name</b> attribute not
+ changed! It must differ from the list's name by case
+ only."""))
+ elif property == 'new_member_options':
+ newopts = 0
+ for opt in OPTIONS:
+ bitfield = mm_cfg.OPTINFO[opt]
+ if opt in val:
+ newopts |= bitfield
+ mlist.new_member_options = newopts
+ elif property == 'subject_prefix':
+ # Convert any html entities to Unicode
+ mlist.subject_prefix = Utils.canonstr(
+ val, mlist.preferred_language)
+ else:
+ GUIBase._setValue(self, mlist, property, val, doc)
+
+ def _postValidate(self, mlist, doc):
+ if not mlist.reply_to_address.strip() and \
+ mlist.reply_goes_to_list == 2:
+ # You can't go to an explicit address that is blank
+ doc.addError(_("""You cannot add a Reply-To: to an explicit
+ address if that address is blank. Resetting these values."""))
+ mlist.reply_to_address = ''
+ mlist.reply_goes_to_list = 0
+
+ def getValue(self, mlist, kind, varname, params):
+ if varname <> 'subject_prefix':
+ return None
+ # The subject_prefix may be Unicode
+ return Utils.uncanonstr(mlist.subject_prefix, mlist.preferred_language)
diff --git a/Mailman/Gui/Language.py b/Mailman/Gui/Language.py
new file mode 100644
index 00000000..bfa5185f
--- /dev/null
+++ b/Mailman/Gui/Language.py
@@ -0,0 +1,122 @@
+# 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.
+
+"""MailList mixin class managing the language options.
+"""
+
+import codecs
+
+from Mailman import mm_cfg
+from Mailman import Utils
+from Mailman import i18n
+from Mailman.Logging.Syslog import syslog
+from Mailman.Gui.GUIBase import GUIBase
+
+_ = i18n._
+
+
+
+class Language(GUIBase):
+ def GetConfigCategory(self):
+ return 'language', _('Language&nbsp;options')
+
+ def GetConfigInfo(self, mlist, category, subcat=None):
+ if category <> 'language':
+ return None
+
+ # Set things up for the language choices
+ langs = mlist.GetAvailableLanguages()
+ langnames = [_(Utils.GetLanguageDescr(L)) for L in langs]
+ try:
+ langi = langs.index(mlist.preferred_language)
+ except ValueError:
+ # Someone must have deleted the list's preferred language. Could
+ # be other trouble lurking!
+ langi = 0
+
+ # Only allow the admin to choose a language if the system has a
+ # charset for it. I think this is the best way to test for that.
+ def checkcodec(charset):
+ try:
+ codecs.lookup(charset)
+ return 1
+ except LookupError:
+ return 0
+
+ all = [key for key in mm_cfg.LC_DESCRIPTIONS.keys()
+ if checkcodec(Utils.GetCharSet(key))]
+ all.sort()
+ checked = [L in langs for L in all]
+ allnames = [_(Utils.GetLanguageDescr(L)) for L in all]
+
+ return [
+ _('Natural language (internationalization) options.'),
+
+ ('preferred_language', mm_cfg.Select,
+ (langs, langnames, langi),
+ 0,
+ _('Default language for this list.'),
+ _('''This is the default natural language for this mailing list.
+ If <a href="?VARHELP=language/available_languages">more than one
+ language</a> is supported then users will be able to select their
+ own preferences for when they interact with the list. All other
+ interactions will be conducted in the default language. This
+ applies to both web-based and email-based messages, but not to
+ email posted by list members.''')),
+
+ ('available_languages', mm_cfg.Checkbox,
+ (allnames, checked, 0, all), 0,
+ _('Languages supported by this list.'),
+
+ _('''These are all the natural languages supported by this list.
+ Note that the
+ <a href="?VARHELP=language/preferred_language">default
+ language</a> must be included.''')),
+
+ ('encode_ascii_prefixes', mm_cfg.Radio,
+ (_('Never'), _('Always'), _('As needed')), 0,
+ _("""Encode the
+ <a href="?VARHELP=general/subject_prefix">subject
+ prefix</a> even when it consists of only ASCII characters?"""),
+
+ _("""If your mailing list's default language uses a non-ASCII
+ character set and the prefix contains non-ASCII characters, the
+ prefix will always be encoded according to the relevant
+ standards. However, if your prefix contains only ASCII
+ characters, you may want to set this option to <em>Never</em> to
+ disable prefix encoding. This can make the subject headers
+ slightly more readable for users with mail readers that don't
+ properly handle non-ASCII encodings.
+
+ <p>Note however, that if your mailing list receives both encoded
+ and unencoded subject headers, you might want to choose <em>As
+ needed</em>. Using this setting, Mailman will not encode ASCII
+ prefixes when the rest of the header contains only ASCII
+ characters, but if the original header contains non-ASCII
+ characters, it will encode the prefix. This avoids an ambiguity
+ in the standards which could cause some mail readers to display
+ extra, or missing spaces between the prefix and the original
+ header.""")),
+
+ ]
+
+ def _setValue(self, mlist, property, val, doc):
+ # If we're changing the list's preferred language, change the I18N
+ # context as well
+ if property == 'preferred_language':
+ i18n.set_language(val)
+ doc.set_language(val)
+ GUIBase._setValue(self, mlist, property, val, doc)
diff --git a/Mailman/Gui/Makefile.in b/Mailman/Gui/Makefile.in
new file mode 100644
index 00000000..ea219772
--- /dev/null
+++ b/Mailman/Gui/Makefile.in
@@ -0,0 +1,69 @@
+# Copyright (C) 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.
+
+# NOTE: Makefile.in is converted into Makefile by the configure script
+# in the parent directory. Once configure has run, you can recreate
+# the Makefile by running just config.status.
+
+# Variables set by configure
+
+VPATH= @srcdir@
+srcdir= @srcdir@
+bindir= @bindir@
+prefix= @prefix@
+exec_prefix= @exec_prefix@
+
+CC= @CC@
+CHMOD= @CHMOD@
+INSTALL= @INSTALL@
+
+DEFS= @DEFS@
+
+# Customizable but not set by configure
+
+OPT= @OPT@
+CFLAGS= $(OPT) $(DEFS)
+PACKAGEDIR= $(prefix)/Mailman/Gui
+SHELL= /bin/sh
+
+MODULES= *.py
+
+# Modes for directories and executables created by the install
+# process. Default to group-writable directories but
+# user-only-writable for executables.
+DIRMODE= 775
+EXEMODE= 755
+FILEMODE= 644
+INSTALL_PROGRAM=$(INSTALL) -m $(EXEMODE)
+
+
+# Rules
+
+all:
+
+install:
+ for f in $(MODULES); \
+ do \
+ $(INSTALL) -m $(FILEMODE) $(srcdir)/$$f $(PACKAGEDIR); \
+ done
+
+finish:
+
+clean:
+
+distclean:
+ -rm *.pyc
+ -rm Makefile
diff --git a/Mailman/Gui/Membership.py b/Mailman/Gui/Membership.py
new file mode 100644
index 00000000..99e44a57
--- /dev/null
+++ b/Mailman/Gui/Membership.py
@@ -0,0 +1,34 @@
+# 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.
+
+"""MailList mixin class managing the membership pseudo-options.
+"""
+
+from Mailman.i18n import _
+
+
+
+class Membership:
+ def GetConfigCategory(self):
+ return 'members', _('Membership&nbsp;Management')
+
+ def GetConfigSubCategories(self, category):
+ if category == 'members':
+ return [('list', _('Membership&nbsp;List')),
+ ('add', _('Mass&nbsp;Subscription')),
+ ('remove', _('Mass&nbsp;Removal')),
+ ]
+ return None
diff --git a/Mailman/Gui/NonDigest.py b/Mailman/Gui/NonDigest.py
new file mode 100644
index 00000000..900f865f
--- /dev/null
+++ b/Mailman/Gui/NonDigest.py
@@ -0,0 +1,130 @@
+# 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.
+
+"""GUI component for managing the non-digest delivery options.
+"""
+
+from Mailman import mm_cfg
+from Mailman import Utils
+from Mailman.i18n import _
+from Mailman.Gui.GUIBase import GUIBase
+
+from Mailman.Gui.Digest import ALLOWEDS
+PERSONALIZED_ALLOWEDS = ('user_address', 'user_delivered_to', 'user_password',
+ 'user_name', 'user_optionsurl',
+ )
+
+
+
+class NonDigest(GUIBase):
+ def GetConfigCategory(self):
+ return 'nondigest', _('Non-digest&nbsp;options')
+
+ def GetConfigInfo(self, mlist, category, subcat=None):
+ if category <> 'nondigest':
+ return None
+ WIDTH = mm_cfg.TEXTFIELDWIDTH
+
+ info = [
+ _("Policies concerning immediately delivered list traffic."),
+
+ ('nondigestable', mm_cfg.Toggle, (_('No'), _('Yes')), 1,
+ _("""Can subscribers choose to receive mail immediately, rather
+ than in batched digests?""")),
+ ]
+
+ if mm_cfg.OWNERS_CAN_ENABLE_PERSONALIZATION:
+ info.extend([
+ ('personalize', mm_cfg.Radio,
+ (_('No'), _('Yes'), _('Full Personalization')), 1,
+
+ _('''Should Mailman personalize each non-digest delivery?
+ This is often useful for announce-only lists, but <a
+ href="?VARHELP=nondigest/personalize">read the details</a>
+ section for a discussion of important performance
+ issues.'''),
+
+ _("""Normally, Mailman sends the regular delivery 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.
+
+ <p>However, some lists can benefit from a more personalized
+ approach. In this case, Mailman crafts a new message for
+ each member on the regular delivery list. Turning this
+ feature on may degrade the performance of your site, so 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.
+
+ <p>Select <em>No</em> to disable personalization and send
+ messages to the members in batches. Select <em>Yes</em> to
+ personalize deliveries and allow additional substitution
+ variables in message headers and footers (see below). In
+ addition, by selecting <em>Full Personalization</em>, the
+ <code>To</code> header of posted messages will be modified to
+ include the member's address instead of the list's posting
+ address.
+
+ <p>When personalization is enabled, a few more expansion
+ variables that can be included in the <a
+ href="?VARHELP=nondigest/msg_header">message header</a> and
+ <a href="?VARHELP=nondigest/msg_footer">message footer</a>.
+
+ <p>These additional substitution variables will be available
+ for your headers and footers, when this feature is enabled:
+
+ <ul><li><b>user_address</b> - The address of the user,
+ coerced to lower case.
+ <li><b>user_delivered_to</b> - The case-preserved address
+ that the user is subscribed with.
+ <li><b>user_password</b> - The user's password.
+ <li><b>user_name</b> - The user's full name.
+ <li><b>user_optionsurl</b> - The url to the user's option
+ page.
+ </ul>
+ """))
+ ])
+ # BAW: for very dumb reasons, we want the `personalize' attribute to
+ # show up before the msg_header and msg_footer attrs, otherwise we'll
+ # get a bogus warning if the header/footer contains a personalization
+ # substitution variable, and we're transitioning from no
+ # personalization to personalization enabled.
+ info.extend([('msg_header', mm_cfg.Text, (10, WIDTH), 0,
+ _('Header added to mail sent to regular list members'),
+ _('''Text prepended to the top of every immediately-delivery
+ message. ''') + Utils.maketext('headfoot.html',
+ mlist=mlist, raw=1)),
+
+ ('msg_footer', mm_cfg.Text, (10, WIDTH), 0,
+ _('Footer added to mail sent to regular list members'),
+ _('''Text appended to the bottom of every immediately-delivery
+ message. ''') + Utils.maketext('headfoot.html',
+ mlist=mlist, raw=1)),
+ ])
+ return info
+
+ def _setValue(self, mlist, property, val, doc):
+ alloweds = list(ALLOWEDS)
+ if mlist.personalize:
+ alloweds.extend(PERSONALIZED_ALLOWEDS)
+ if property in ('msg_header', 'msg_footer'):
+ 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/Passwords.py b/Mailman/Gui/Passwords.py
new file mode 100644
index 00000000..a3cf6b8e
--- /dev/null
+++ b/Mailman/Gui/Passwords.py
@@ -0,0 +1,31 @@
+# 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.
+
+"""MailList mixin class managing the password pseudo-options.
+"""
+
+from Mailman.i18n import _
+from Mailman.Gui.GUIBase import GUIBase
+
+
+
+class Passwords(GUIBase):
+ def GetConfigCategory(self):
+ return 'passwords', _('Passwords')
+
+ def handleForm(self, mlist, category, subcat, cgidata, doc):
+ # Nothing more needs to be done
+ pass
diff --git a/Mailman/Gui/Privacy.py b/Mailman/Gui/Privacy.py
new file mode 100644
index 00000000..7ef50375
--- /dev/null
+++ b/Mailman/Gui/Privacy.py
@@ -0,0 +1,398 @@
+# 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.
+
+"""MailList mixin class managing the privacy options.
+"""
+
+from Mailman import mm_cfg
+from Mailman.i18n import _
+from Mailman.Gui.GUIBase import GUIBase
+
+
+
+class Privacy(GUIBase):
+ def GetConfigCategory(self):
+ return 'privacy', _('Privacy options')
+
+ def GetConfigSubCategories(self, category):
+ if category == 'privacy':
+ return [('subscribing', _('Subscription&nbsp;rules')),
+ ('sender', _('Sender&nbsp;filters')),
+ ('recipient', _('Recipient&nbsp;filters')),
+ ('spam', _('Spam&nbsp;filters')),
+ ]
+ return None
+
+ def GetConfigInfo(self, mlist, category, subcat=None):
+ if category <> 'privacy':
+ return None
+ # Pre-calculate some stuff. Technically, we shouldn't do the
+ # sub_cfentry calculation here, but it's too ugly to indent it any
+ # further, and besides, that'll mess up i18n catalogs.
+ WIDTH = mm_cfg.TEXTFIELDWIDTH
+ if mm_cfg.ALLOW_OPEN_SUBSCRIBE:
+ sub_cfentry = ('subscribe_policy', mm_cfg.Radio,
+ # choices
+ (_('None'),
+ _('Confirm'),
+ _('Require approval'),
+ _('Confirm and approve')),
+ 0,
+ _('What steps are required for subscription?<br>'),
+ _('''None - no verification steps (<em>Not
+ Recommended </em>)<br>
+ Confirm (*) - email confirmation step required <br>
+ Require approval - require list administrator
+ Approval for subscriptions <br>
+ Confirm and approve - both confirm and approve
+
+ <p>(*) when someone requests a subscription,
+ Mailman sends them a notice with a unique
+ subscription request number that they must reply to
+ in order to subscribe.<br>
+
+ This prevents mischievous (or malicious) people
+ from creating subscriptions for others without
+ their consent.'''))
+ else:
+ sub_cfentry = ('subscribe_policy', mm_cfg.Radio,
+ # choices
+ (_('Confirm'),
+ _('Require approval'),
+ _('Confirm and approve')),
+ 1,
+ _('What steps are required for subscription?<br>'),
+ _('''Confirm (*) - email confirmation required <br>
+ Require approval - require list administrator
+ approval for subscriptions <br>
+ Confirm and approve - both confirm and approve
+
+ <p>(*) when someone requests a subscription,
+ Mailman sends them a notice with a unique
+ subscription request number that they must reply to
+ in order to subscribe.<br> This prevents
+ mischievous (or malicious) people from creating
+ subscriptions for others without their consent.'''))
+
+ # some helpful values
+ admin = mlist.GetScriptURL('admin')
+
+ subscribing_rtn = [
+ _("""This section allows you to configure subscription and
+ membership exposure policy. You can also control whether this
+ list is public or not. See also the
+ <a href="%(admin)s/archive">Archival Options</a> section for
+ separate archive-related privacy settings."""),
+
+ _('Subscribing'),
+ ('advertised', mm_cfg.Radio, (_('No'), _('Yes')), 0,
+ _('''Advertise this list when people ask what lists are on this
+ machine?''')),
+
+ sub_cfentry,
+
+ ('unsubscribe_policy', mm_cfg.Radio, (_('No'), _('Yes')), 0,
+ _("""Is the list moderator's approval required for unsubscription
+ requests? (<em>No</em> is recommended)"""),
+
+ _("""When members want to leave a list, they will make an
+ unsubscription request, either via the web or via email.
+ Normally it is best for you to allow open unsubscriptions so that
+ users can easily remove themselves from mailing lists (they get
+ really upset if they can't get off lists!).
+
+ <p>For some lists though, you may want to impose moderator
+ approval before an unsubscription request is processed. Examples
+ of such lists include a corporate mailing list that all employees
+ are required to be members of.""")),
+
+ _('Ban list'),
+ ('ban_list', mm_cfg.EmailListEx, (10, WIDTH), 1,
+ _("""List of addresses which are banned from membership in this
+ mailing list."""),
+
+ _("""Addresses in this list are banned outright from subscribing
+ to this mailing list, with no further moderation required. Add
+ addresses one per line; start the line with a ^ character to
+ designate a regular expression match.""")),
+
+ _("Membership exposure"),
+ ('private_roster', mm_cfg.Radio,
+ (_('Anyone'), _('List members'), _('List admin only')), 0,
+ _('Who can view subscription list?'),
+
+ _('''When set, the list of subscribers is protected by member or
+ admin password authentication.''')),
+
+ ('obscure_addresses', mm_cfg.Radio, (_('No'), _('Yes')), 0,
+ _("""Show member addresses so they're not directly recognizable
+ as email addresses?"""),
+ _("""Setting this option causes member email addresses to be
+ transformed when they are presented on list web pages (both in
+ text and as links), so they're not trivially recognizable as
+ email addresses. The intention is to prevent the addresses
+ from being snarfed up by automated web scanners for use by
+ spammers.""")),
+ ]
+
+ adminurl = mlist.GetScriptURL('admin', absolute=1)
+ sender_rtn = [
+ _("""When a message is posted to the list, a series of
+ moderation steps are take to decide whether the a moderator must
+ first approve the message or not. This section contains the
+ controls for moderation of both member and non-member postings.
+
+ <p>Member postings are held for moderation if their
+ <b>moderation flag</b> is turned on. You can control whether
+ member postings are moderated by default or not.
+
+ <p>Non-member postings can be automatically
+ <a href="?VARHELP=privacy/sender/accept_these_nonmembers"
+ >accepted</a>,
+ <a href="?VARHELP=privacy/sender/hold_these_nonmembers">held for
+ moderation</a>,
+ <a href="?VARHELP=privacy/sender/reject_these_nonmembers"
+ >rejected</a> (bounced), or
+ <a href="?VARHELP=privacy/sender/discard_these_nonmembers"
+ >discarded</a>,
+ either individually or as a group. Any
+ posting from a non-member who is not explicitly accepted,
+ rejected, or discarded, will have their posting filtered by the
+ <a href="?VARHELP=privacy/sender/generic_nonmember_action">general
+ non-member rules</a>.
+
+ <p>In the text boxes below, add one address per line; start the
+ line with a ^ character to designate a <a href=
+ "http://www.python.org/doc/current/lib/module-re.html"
+ >Python regular expression</a>. When entering backslashes, do so
+ as if you were using Python raw strings (i.e. you generally just
+ use a single backslash).
+
+ <p>Note that non-regexp matches are always done first."""),
+
+ _('Member filters'),
+
+ ('default_member_moderation', mm_cfg.Radio, (_('No'), _('Yes')),
+ 0, _('By default, should new list member postings be moderated?'),
+
+ _("""Each list member has a <em>moderation flag</em> which says
+ whether messages from the list member can be posted directly to
+ the list, or must first be approved by the list moderator. When
+ the moderation flag is turned on, list member postings must be
+ approved first. You, the list administrator can decide whether a
+ specific individual's postings will be moderated or not.
+
+ <p>When a new member is subscribed, their initial moderation flag
+ takes its value from this option. Turn this option off to accept
+ member postings by default. Turn this option on to, by default,
+ moderate member postings first. You can always manually set an
+ individual member's moderation bit by using the
+ <a href="%(adminurl)s/members">membership management
+ screens</a>.""")),
+
+ ('member_moderation_action', mm_cfg.Radio,
+ (_('Hold'), _('Reject'), _('Discard')), 0,
+ _("""Action to take when a moderated member posts to the
+ list."""),
+ _("""<ul><li><b>Hold</b> -- this holds the message for approval
+ by the list moderators.
+
+ <p><li><b>Reject</b> -- this automatically rejects the message by
+ sending a bounce notice to the post's author. The text of the
+ bounce notice can be <a
+ href="?VARHELP=privacy/sender/member_moderation_notice"
+ >configured by you</a>.
+
+ <p><li><b>Discard</b> -- this simply discards the message, with
+ no notice sent to the post's author.
+ </ul>""")),
+
+ ('member_moderation_notice', mm_cfg.Text, (10, WIDTH), 1,
+ _("""Text to include in any
+ <a href="?VARHELP/privacy/sender/member_moderation_action"
+ >rejection notice</a> to
+ be sent to moderated members who post to this list.""")),
+
+ _('Non-member filters'),
+
+ ('accept_these_nonmembers', mm_cfg.EmailListEx, (10, WIDTH), 1,
+ _("""List of non-member addresses whose postings should be
+ automatically accepted."""),
+
+ _("""Postings from any of these non-members will be automatically
+ accepted with no further moderation applied. Add member
+ addresses one per line; start the line with a ^ character to
+ designate a regular expression match.""")),
+
+ ('hold_these_nonmembers', mm_cfg.EmailListEx, (10, WIDTH), 1,
+ _("""List of non-member addresses whose postings will be
+ immediately held for moderation."""),
+
+ _("""Postings from any of these non-members will be immediately
+ and automatically held for moderation by the list moderators.
+ The sender will receive a notification message which will allow
+ them to cancel their held message. Add member addresses one per
+ line; start the line with a ^ character to designate a regular
+ expression match.""")),
+
+ ('reject_these_nonmembers', mm_cfg.EmailListEx, (10, WIDTH), 1,
+ _("""List of non-member addresses whose postings will be
+ automatically rejected."""),
+
+ _("""Postings from any of these non-members will be automatically
+ rejected. In other words, their messages will be bounced back to
+ the sender with a notification of automatic rejection. This
+ option is not appropriate for known spam senders; their messages
+ should be
+ <a href="?VARHELP=privacy/sender/discard_these_nonmembers"
+ >automatically discarded</a>.
+
+ <p>Add member addresses one per line; start the line with a ^
+ character to designate a regular expression match.""")),
+
+ ('discard_these_nonmembers', mm_cfg.EmailListEx, (10, WIDTH), 1,
+ _("""List of non-member addresses whose postings will be
+ automatically discarded."""),
+
+ _("""Postings from any of these non-members will be automatically
+ discarded. That is, the message will be thrown away with no
+ further processing or notification. The sender will not receive
+ a notification or a bounce, however the list moderators can
+ optionally <a href="?VARHELP=privacy/sender/forward_auto_discards"
+ >receive copies of auto-discarded messages.</a>.
+
+ <p>Add member addresses one per line; start the line with a ^
+ character to designate a regular expression match.""")),
+
+ ('generic_nonmember_action', mm_cfg.Radio,
+ (_('Accept'), _('Hold'), _('Reject'), _('Discard')), 0,
+ _("""Action to take for postings from non-members for which no
+ explicit action is defined."""),
+
+ _("""When a post from a non-member is received, the message's
+ sender is matched against the list of explicitly
+ <a href="?VARHELP=privacy/sender/accept_these_nonmembers"
+ >accepted</a>,
+ <a href="?VARHELP=privacy/sender/hold_these_nonmembers">held</a>,
+ <a href="?VARHELP=privacy/sender/reject_these_nonmembers"
+ >rejected</a> (bounced), and
+ <a href="?VARHELP=privacy/sender/discard_these_nonmembers"
+ >discarded</a> addresses. If no match is found, then this action
+ is taken.""")),
+
+ ('forward_auto_discards', mm_cfg.Radio, (_('No'), _('Yes')), 0,
+ _("""Should messages from non-members, which are automatically
+ discarded, be forwarded to the list moderator?""")),
+
+ ]
+
+ recip_rtn = [
+ _("""This section allows you to configure various filters based on
+ the recipient of the message."""),
+
+ _('Recipient filters'),
+
+ ('require_explicit_destination', mm_cfg.Radio,
+ (_('No'), _('Yes')), 0,
+ _("""Must posts have list named in destination (to, cc) field
+ (or be among the acceptable alias names, specified below)?"""),
+
+ _("""Many (in fact, most) spams do not explicitly name their
+ myriad destinations in the explicit destination addresses - in
+ fact often the To: field has a totally bogus address for
+ obfuscation. The constraint applies only to the stuff in the
+ address before the '@' sign, but still catches all such spams.
+
+ <p>The cost is that the list will not accept unhindered any
+ postings relayed from other addresses, unless
+
+ <ol>
+ <li>The relaying address has the same name, or
+
+ <li>The relaying address name is included on the options that
+ specifies acceptable aliases for the list.
+
+ </ol>""")),
+
+ ('acceptable_aliases', mm_cfg.Text, (4, WIDTH), 0,
+ _("""Alias names (regexps) which qualify as explicit to or cc
+ destination names for this list."""),
+
+ _("""Alternate addresses that are acceptable when
+ `require_explicit_destination' is enabled. This option takes a
+ list of regular expressions, one per line, which is matched
+ against every recipient address in the message. The matching is
+ performed with Python's re.match() function, meaning they are
+ anchored to the start of the string.
+
+ <p>For backwards compatibility with Mailman 1.1, if the regexp
+ does not contain an `@', then the pattern is matched against just
+ the local part of the recipient address. If that match fails, or
+ if the pattern does contain an `@', then the pattern is matched
+ against the entire recipient address.
+
+ <p>Matching against the local part is deprecated; in a future
+ release, the pattern will always be matched against the entire
+ recipient address.""")),
+
+ ('max_num_recipients', mm_cfg.Number, 5, 0,
+ _('Ceiling on acceptable number of recipients for a posting.'),
+
+ _('''If a posting has this number, or more, of recipients, it is
+ held for admin approval. Use 0 for no ceiling.''')),
+ ]
+
+ spam_rtn = [
+ _("""This section allows you to configure various anti-spam
+ filters posting filters, which can help reduce the amount of spam
+ your list members end up receiving.
+ """),
+
+ _("Anti-Spam filters"),
+
+ ('bounce_matching_headers', mm_cfg.Text, (6, WIDTH), 0,
+ _('Hold posts with header value matching a specified regexp.'),
+ _("""Use this option to prohibit posts according to specific
+ header values. The target value is a regular-expression for
+ matching against the specified header. The match is done
+ disregarding letter case. Lines beginning with '#' are ignored
+ as comments.
+
+ <p>For example:<pre>to: .*@public.com </pre> says to hold all
+ postings with a <em>To:</em> mail header containing '@public.com'
+ anywhere among the addresses.
+
+ <p>Note that leading whitespace is trimmed from the regexp. This
+ can be circumvented in a number of ways, e.g. by escaping or
+ bracketing it.""")),
+ ]
+
+ if subcat == 'sender':
+ return sender_rtn
+ elif subcat == 'recipient':
+ return recip_rtn
+ elif subcat == 'spam':
+ return spam_rtn
+ else:
+ return subscribing_rtn
+
+ def _setValue(self, mlist, property, val, doc):
+ # For subscribe_policy when ALLOW_OPEN_SUBSCRIBE is true, we need to
+ # add one to the value because the page didn't present an open list as
+ # an option.
+ if property == 'subscribe_policy' and not mm_cfg.ALLOW_OPEN_SUBSCRIBE:
+ val += 1
+ setattr(mlist, property, val)
diff --git a/Mailman/Gui/Topics.py b/Mailman/Gui/Topics.py
new file mode 100644
index 00000000..310d876f
--- /dev/null
+++ b/Mailman/Gui/Topics.py
@@ -0,0 +1,160 @@
+# 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.
+
+import re
+
+from Mailman import mm_cfg
+from Mailman.i18n import _
+from Mailman.Logging.Syslog import syslog
+from Mailman.Gui.GUIBase import GUIBase
+
+
+
+class Topics(GUIBase):
+ def GetConfigCategory(self):
+ return 'topics', _('Topics')
+
+ def GetConfigInfo(self, mlist, category, subcat=None):
+ if category <> 'topics':
+ return None
+ WIDTH = mm_cfg.TEXTFIELDWIDTH
+
+ return [
+ _('List topic keywords'),
+
+ ('topics_enabled', mm_cfg.Radio, (_('Disabled'), _('Enabled')), 0,
+ _('''Should the topic filter be enabled or disabled?'''),
+
+ _("""The topic filter categorizes each incoming email message
+ according to <a
+ href="http://www.python.org/doc/current/lib/module-re.html">regular
+ expression filters</a> you specify below. If the message's
+ <code>Subject:</code> or <code>Keywords:</code> header contains a
+ match against a topic filter, the message is logically placed
+ into a topic <em>bucket</em>. Each user can then choose to only
+ receive messages from the mailing list for a particular topic
+ bucket (or buckets). Any message not categorized in a topic
+ bucket registered with the user is not delivered to the list.
+
+ <p>Note that this feature only works with regular delivery, not
+ digest delivery.
+
+ <p>The body of the message can also be optionally scanned for
+ <code>Subject:</code> and <code>Keywords:</code> headers, as
+ specified by the <a
+ href="?VARHELP=topics/topics_bodylines_limit">topics_bodylines_limit</a>
+ configuration variable.""")),
+
+ ('topics_bodylines_limit', mm_cfg.Number, 5, 0,
+ _('How many body lines should the topic matcher scan?'),
+
+ _("""The topic matcher will scan this many lines of the message
+ body looking for topic keyword matches. Body scanning stops when
+ either this many lines have been looked at, or a non-header-like
+ body line is encountered. By setting this value to zero, no body
+ lines will be scanned (i.e. only the <code>Keywords:</code> and
+ <code>Subject:</code> headers will be scanned). By setting this
+ value to a negative number, then all body lines will be scanned
+ until a non-header-like line is encountered.
+ """)),
+
+ ('topics', mm_cfg.Topics, 0, 0,
+ _('Topic keywords, one per line, to match against each message.'),
+
+ _("""Each topic keyword is actually a regular expression, which is
+ matched against certain parts of a mail message, specifically the
+ <code>Keywords:</code> and <code>Subject:</code> message headers.
+ Note that the first few lines of the body of the message can also
+ contain a <code>Keywords:</code> and <code>Subject:</code>
+ "header" on which matching is also performed.""")),
+
+ ]
+
+ def handleForm(self, mlist, category, subcat, cgidata, doc):
+ topics = []
+ # We start i at 1 and keep going until we no longer find items keyed
+ # with the marked tags.
+ i = 1
+ while 1:
+ deltag = 'topic_delete_%02d' % i
+ boxtag = 'topic_box_%02d' % i
+ reboxtag = 'topic_rebox_%02d' % i
+ desctag = 'topic_desc_%02d' % i
+ wheretag = 'topic_where_%02d' % i
+ addtag = 'topic_add_%02d' % i
+ newtag = 'topic_new_%02d' % i
+
+ i += 1
+ # Was this a delete? If so, we can just ignore this entry
+ if cgidata.has_key(deltag):
+ continue
+
+ # Get the data for the current box
+ name = cgidata.getvalue(boxtag)
+ pattern = cgidata.getvalue(reboxtag)
+ desc = cgidata.getvalue(desctag)
+
+ if name is None:
+ # We came to the end of the boxes
+ break
+
+ if cgidata.has_key(newtag) and (not name or not pattern):
+ # This new entry is incomplete.
+ doc.addError(_("""Topic specifications require both a name and
+ a pattern. Incomplete topics will be ignored."""))
+ continue
+
+ # Make sure the pattern was a legal regular expression
+ try:
+ re.compile(pattern)
+ except (re.error, TypeError):
+ doc.addError(_("""The topic pattern `%(pattern)s' is not a
+ legal regular expression. It will be discarded."""))
+ continue
+
+ # Was this an add item?
+ if cgidata.has_key(addtag):
+ # Where should the new one be added?
+ where = cgidata.getvalue(wheretag)
+ if where == 'before':
+ # Add a new empty topics box before the current one
+ topics.append(('', '', '', 1))
+ topics.append((name, pattern, desc, 0))
+ # Default is to add it after...
+ else:
+ topics.append((name, pattern, desc, 0))
+ topics.append(('', '', '', 1))
+ # Otherwise, just retain this one in the list
+ else:
+ topics.append((name, pattern, desc, 0))
+
+ # Add these topics to the mailing list object, and deal with other
+ # options.
+ mlist.topics = topics
+ try:
+ mlist.topics_enabled = int(cgidata.getvalue(
+ 'topics_enabled',
+ mlist.topics_enabled))
+ except ValueError:
+ # BAW: should really print a warning
+ pass
+ try:
+ mlist.topics_bodylines_limit = int(cgidata.getvalue(
+ 'topics_bodylines_limit',
+ mlist.topics_bodylines_limit))
+ except ValueError:
+ # BAW: should really print a warning
+ pass
diff --git a/Mailman/Gui/Usenet.py b/Mailman/Gui/Usenet.py
new file mode 100644
index 00000000..9d6b65f4
--- /dev/null
+++ b/Mailman/Gui/Usenet.py
@@ -0,0 +1,137 @@
+# 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 Usenet(GUIBase):
+ def GetConfigCategory(self):
+ return 'gateway', _('Mail&lt;-&gt;News&nbsp;gateways')
+
+ def GetConfigInfo(self, mlist, category, subcat=None):
+ if category <> 'gateway':
+ return None
+
+ WIDTH = mm_cfg.TEXTFIELDWIDTH
+ VERTICAL = 1
+
+ return [
+ _('Mail-to-News and News-to-Mail gateway services.'),
+
+ _('News server settings'),
+
+ ('nntp_host', mm_cfg.String, WIDTH, 0,
+ _('''The Internet address of the machine your News server is
+ running on.'''),
+ _('''The News server is not part of Mailman proper. You have to
+ already have access to a NNTP server, and that NNTP server has to
+ recognize the machine this mailing list runs on as a machine
+ capable of reading and posting news.''')),
+
+ ('linked_newsgroup', mm_cfg.String, WIDTH, 0,
+ _('The name of the Usenet group to gateway to and/or from.')),
+
+ ('gateway_to_news', mm_cfg.Toggle, (_('No'), _('Yes')), 0,
+ _('''Should new posts to the mailing list be sent to the
+ newsgroup?''')),
+
+ ('gateway_to_mail', mm_cfg.Toggle, (_('No'), _('Yes')), 0,
+ _('''Should new posts to the newsgroup be sent to the mailing
+ list?''')),
+
+ _('Forwarding options'),
+
+ ('news_moderation', mm_cfg.Radio,
+ (_('None'), _('Open list, moderated group'), _('Moderated')),
+ VERTICAL,
+
+ _("""The moderation policy of the newsgroup."""),
+
+ _("""This setting determines the moderation policy of the
+ newsgroup and its interaction with the moderation policy of the
+ mailing list. This only applies to the newsgroup that you are
+ gatewaying <em>to</em>, so if you are only gatewaying from
+ Usenet, or the newsgroup you are gatewaying to is not moderated,
+ set this option to <em>None</em>.
+
+ <p>If the newsgroup is moderated, you can set this mailing list
+ up to be the moderation address for the newsgroup. By selecting
+ <em>Moderated</em>, an additional posting hold will be placed in
+ the approval process. All messages posted to the mailing list
+ will have to be approved before being sent on to the newsgroup,
+ or to the mailing list membership.
+
+ <p><em>Note that if the message has an <tt>Approved</tt> header
+ with the list's administrative password in it, this hold test
+ will be bypassed, allowing privileged posters to send messages
+ directly to the list and the newsgroup.</em>
+
+ <p>Finally, if the newsgroup is moderated, but you want to have
+ an open posting policy anyway, you should select <em>Open list,
+ moderated group</em>. The effect of this is to use the normal
+ Mailman moderation facilities, but to add an <tt>Approved</tt>
+ header to all messages that are gatewayed to Usenet.""")),
+
+ ('news_prefix_subject_too', mm_cfg.Toggle, (_('No'), _('Yes')), 0,
+ _('Prefix <tt>Subject:</tt> headers on postings gated to news?'),
+ _("""Mailman prefixes <tt>Subject:</tt> headers with
+ <a href="?VARHELP=general/subject_prefix">text you can
+ customize</a> and normally, this prefix shows up in messages
+ gatewayed to Usenet. You can set this option to <em>No</em> to
+ disable the prefix on gated messages. Of course, if you turn off
+ normal <tt>Subject:</tt> prefixes, they won't be prefixed for
+ gated messages either.""")),
+
+ _('Mass catch up'),
+
+ ('_mass_catchup', mm_cfg.Toggle, (_('No'), _('Yes')), 0,
+ _('Should Mailman perform a <em>catchup</em> on the newsgroup?'),
+ _('''When you tell Mailman to perform a catchup on the newsgroup,
+ this means that you want to start gating messages to the mailing
+ list with the next new message found. All earlier messages on
+ the newsgroup will be ignored. This is as if you were reading
+ the newsgroup yourself, and you marked all current messages as
+ <em>read</em>. By catching up, your mailing list members will
+ not see any of the earlier messages.''')),
+
+ ]
+
+ def _setValue(self, mlist, property, val, doc):
+ # Watch for the special, immediate action attributes
+ if property == '_mass_catchup' and val:
+ mlist.usenet_watermark = None
+ doc.AddItem(_('Mass catchup completed'))
+ else:
+ GUIBase._setValue(self, mlist, property, val, doc)
+
+ def _postValidate(self, mlist, doc):
+ # Make sure that if we're gating, that the newsgroups and host
+ # information are not blank.
+ if mlist.gateway_to_news or mlist.gateway_to_mail:
+ # BAW: It's too expensive and annoying to ensure that both the
+ # host is valid and that the newsgroup is a valid n.g. on the
+ # server. This should be good enough.
+ if not mlist.nntp_host or not mlist.linked_newsgroup:
+ doc.addError(_("""You cannot enable gatewaying unless both the
+ <a href="?VARHELP=gateway/nntp_host">news server field</a> and
+ the <a href="?VARHELP=gateway/linked_newsgroup">linked
+ newsgroup</a> fields are filled in."""))
+ # And reset these values
+ mlist.gateway_to_news = 0
+ mlist.gateway_to_mail = 0
diff --git a/Mailman/Gui/__init__.py b/Mailman/Gui/__init__.py
new file mode 100644
index 00000000..1e79b34a
--- /dev/null
+++ b/Mailman/Gui/__init__.py
@@ -0,0 +1,32 @@
+# 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 Archive import Archive
+from Autoresponse import Autoresponse
+from Bounce import Bounce
+from Digest import Digest
+from General import General
+from Membership import Membership
+from NonDigest import NonDigest
+from Passwords import Passwords
+from Privacy import Privacy
+from Topics import Topics
+from Usenet import Usenet
+from Language import Language
+from ContentFilter import ContentFilter
+
+# Don't export this symbol outside the package
+del GUIBase