diff options
author | <> | 2003-01-02 05:25:50 +0000 |
---|---|---|
committer | <> | 2003-01-02 05:25:50 +0000 |
commit | b132a73f15e432eaf43310fce9196ca0c0651465 (patch) | |
tree | c15f816ba7c4de99fef510e3bd75af0890d47441 /Mailman/Gui | |
download | mailman2-b132a73f15e432eaf43310fce9196ca0c0651465.tar.gz mailman2-b132a73f15e432eaf43310fce9196ca0c0651465.tar.xz mailman2-b132a73f15e432eaf43310fce9196ca0c0651465.zip |
This commit was manufactured by cvs2svn to create branch
'Release_2_1-maint'.
Diffstat (limited to 'Mailman/Gui')
-rw-r--r-- | Mailman/Gui/.cvsignore | 1 | ||||
-rw-r--r-- | Mailman/Gui/Archive.py | 44 | ||||
-rw-r--r-- | Mailman/Gui/Autoresponse.py | 98 | ||||
-rw-r--r-- | Mailman/Gui/Bounce.py | 183 | ||||
-rw-r--r-- | Mailman/Gui/ContentFilter.py | 169 | ||||
-rw-r--r-- | Mailman/Gui/Digest.py | 160 | ||||
-rw-r--r-- | Mailman/Gui/GUIBase.py | 200 | ||||
-rw-r--r-- | Mailman/Gui/General.py | 446 | ||||
-rw-r--r-- | Mailman/Gui/Language.py | 122 | ||||
-rw-r--r-- | Mailman/Gui/Makefile.in | 69 | ||||
-rw-r--r-- | Mailman/Gui/Membership.py | 34 | ||||
-rw-r--r-- | Mailman/Gui/NonDigest.py | 130 | ||||
-rw-r--r-- | Mailman/Gui/Passwords.py | 31 | ||||
-rw-r--r-- | Mailman/Gui/Privacy.py | 398 | ||||
-rw-r--r-- | Mailman/Gui/Topics.py | 160 | ||||
-rw-r--r-- | Mailman/Gui/Usenet.py | 137 | ||||
-rw-r--r-- | Mailman/Gui/__init__.py | 32 |
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 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 <br> - 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 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 Management') + + def GetConfigSubCategories(self, category): + if category == 'members': + return [('list', _('Membership List')), + ('add', _('Mass Subscription')), + ('remove', _('Mass 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 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 rules')), + ('sender', _('Sender filters')), + ('recipient', _('Recipient filters')), + ('spam', _('Spam 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<->News 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 |