# Copyright (C) 1998,1999,2000,2001 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. """Process and produce the list-administration options forms. """ import os import cgi import types import sha from mimelib.address import unquote from Mailman import mm_cfg from Mailman import Utils from Mailman import MailList from Mailman import Errors from Mailman import MailCommandHandler from Mailman.htmlformat import * from Mailman.Cgi import Auth from Mailman.Logging.Syslog import syslog CATEGORIES = [] NL = '\n' def main(): """Process and produce list options form. CGI input indicates that we're returning from submission of some new settings, which is processed before producing the new version. """ global CATEGORIES doc = Document() parts = Utils.GetPathPieces() if not parts: FormatAdminOverview() return # get the list object listname = parts[0].lower() try: mlist = MailList.MailList(listname) except Errors.MMListError, e: FormatAdminOverview(_('No such list %s') % listname) syslog('error', 'Someone tried to access the admin interface for a ' 'non-existent list: %s' % listname) return try: if len(parts) == 1: category = 'general' category_suffix = '' else: category = parts[1] category_suffix = category # If the user is not authenticated, we're done. cgidata = cgi.FieldStorage(keep_blank_values=1) try: Auth.authenticate(mlist, cgidata) except Auth.NotLoggedInError, e: Auth.loginpage(mlist, 'admin', e.message) return # Is this a log-out request? if category == 'logout': print mlist.ZapCookie('admin') Auth.loginpage(mlist, 'admin', frontpage=1) return os.environ['LANG'] = mlist.preferred_language CATEGORIES = [('general', _("General Options")), ('members', _("Membership Management")), ('privacy', _("Privacy Options")), ('nondigest', _("Regular-member (non-digest) Options")), ('digest', _("Digest-member Options")), ('bounce', _("Bounce Options")), ('archive', _("Archival Options")), ('gateway', _("Mail-News and News-Mail gateways")), ] if category not in map(lambda x: x[0], CATEGORIES): category = 'general' # is the request for variable details? varhelp = None if cgidata.has_key('VARHELP'): varhelp = cgidata['VARHELP'].value elif cgidata.has_key('request_login') and \ os.environ.get('QUERY_STRING'): # POST methods, even if their actions have a query string, don't # get put into FieldStorage's keys :-( qs = cgi.parse_qs(os.environ['QUERY_STRING']).get('VARHELP') if qs and type(qs) == types.ListType: varhelp = qs[0] if varhelp: FormatOptionHelp(doc, varhelp, mlist) print doc.Format(bgcolor="#ffffff") return # BAW: This doesn't appear to do anything. The pairs variable isn't # used anywhere. Perhaps this was meant as some incomplete error # checking on the value? if cgidata.has_key('bounce_matching_headers'): pairs = mlist.parse_matching_header_opt() if len(cgidata.keys()): ChangeOptions(mlist, category, cgidata, doc) mlist.CheckValues() # Sanity checks if not mlist.digestable and not mlist.nondigestable: AddErrorMessage(doc, _('''You have turned off delivery of both digest and non-digest messages. This is an incompatible state of affairs. You must turn on either digest delivery or non-digest delivery or your mailing list will basically be unusable.''')) if not mlist.digestable and len(mlist.GetDigestMembers()): AddErrorMessage(doc, _('''You have digest members, but digests are turned off. Those people will not receive mail.''')) if not mlist.nondigestable and len(mlist.GetMembers()): AddErrorMessage(doc, _('''You have regular list members but non-digestified mail is turned off. They will receive mail until you fix this problem.''')) FormatConfiguration(doc, mlist, category, category_suffix, cgidata) print doc.Format(bgcolor="#ffffff") finally: mlist.Save() mlist.Unlock() # Form Production: def FormatAdminOverview(error=None): "Present a general welcome and itemize the (public) lists." doc = Document() default_hostname = mm_cfg.DEFAULT_HOST_NAME legend = _('%(default_hostname)s mailing lists - Admin Links') doc.SetTitle(legend) table = Table(border=0, width="100%") table.AddRow([Center(Header(2, legend))]) table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 0, colspan=2, bgcolor="#99ccff") advertised = [] names = Utils.list_names() names.sort() for n in names: l = MailList.MailList(n, lock=0) if l.advertised: advertised.append(l) os.environ['LANG'] = mm_cfg.DEFAULT_SERVER_LANGUAGE if error: greeting = FontAttr(error, color="ff5060", size="+1") else: greeting = _("Welcome!") if not advertised: welcome_items = (greeting, _("
" " There currently are no publicly-advertised "), Link(mm_cfg.MAILMAN_URL, "mailman"), _(" mailing lists on %s.") % mm_cfg.DEFAULT_HOST_NAME, ) else: welcome_items = ( greeting, _("
" " Below is the collection of publicly-advertised "), Link(mm_cfg.MAILMAN_URL, "mailman"), _(" mailing lists on %(default_hostname)s."), (_(' Click on a list name to visit the configuration pages' ' for that list.') ) ) mailman_owner = mm_cfg.MAILMAN_OWNER extra = error and _('right ') or '' welcome_items = (welcome_items + (_(" To visit the administrators configuration page for" " an unadvertised list, open a URL similar to this") + (_(" one, but with a '/' and the %(extra)slist name " 'appended.
')) + _(" General list information can be found at "), Link(Utils.ScriptURL('listinfo'), _('the mailing list overview page')), "." + _("
(Send questions and comments to "), Link('mailto:%(mailman_owner)s', mailman_owner), ".)
" ) ) table.AddRow([apply(Container, welcome_items)]) table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 0, colspan=2) if advertised: table.AddRow([' ', ' ']) table.AddRow([Bold(_("List")), Bold(_("Description"))]) for l in advertised: table.AddRow( [Link(l.GetScriptURL('admin'), Bold(l.real_name)), l.description or Italic(_('[no description available]')), ]) doc.AddItem(table) doc.AddItem('
') form.AddItem(FormatOptionsSection(category, mlist, cgi_data)) if category == 'general': form.AddItem(Center(FormatPasswordStuff())) form.AddItem("
") form.AddItem(Center(FormatSubmit())) form.AddItem(mlist.GetMailmanFooter()) def FormatOptionsSection(category, mlist, cgi_data): """Produce the category-specific options table.""" if category == 'members': # Special case for members section. return FormatMembershipOptions(mlist, cgi_data) options = GetConfigOptions(mlist, category) big_table = Table(cellspacing=3, cellpadding=4) # Get and portray the text label for the category. for k, v in CATEGORIES: if k == category: label = v big_table.AddRow([Center(Header(2, label))]) big_table.AddCellInfo(max(big_table.GetCurrentRowIndex(), 0), 0, colspan=2, bgcolor="#99ccff") def ColHeader(big_table = big_table): big_table.AddRow([Center(Bold(_('Description'))), Center(Bold(_('Value')))]) big_table.AddCellInfo(max(big_table.GetCurrentRowIndex(), 0), 0, width="15%") big_table.AddCellInfo(max(big_table.GetCurrentRowIndex(), 0), 1, width="85%") did_col_header = 0 for item in options: if type(item) == types.StringType: # The very first banner option (string in an options list) is # treated as a general description, while any others are # treated as section headers - centered and italicized... if did_col_header: item = "
" % (varname, category, descr)) doc.AddItem("%s
" % elaboration) form = Form("%s/%s" % (mlist.GetScriptURL('admin'), category)) valtab = Table(cellspacing=3, cellpadding=4) AddOptionsTableItem(valtab, item, category, mlist, detailsp=0) form.AddItem(valtab) form.AddItem('
')
form.AddItem(Center(FormatSubmit()))
doc.AddItem(Center(form))
doc.AddItem(_("""Warning: changing this option here
could cause other screens to be out-of-sync. Be sure to reload any other
pages that are displaying this option for this mailing list. You can
also """))
doc.AddItem(Link('%s/%s' % (mlist.GetScriptURL('admin'), category),
_('return to the %(category)s options page.')))
doc.AddItem('')
doc.AddItem(mlist.GetMailmanFooter())
def GetItemCharacteristics(table_entry):
"""Break out the components of an item description from its table entry:
0 option-var name
1 type
2 entry size
3 ?dependancies?
4 Brief description
5 Optional description elaboration"""
if len(table_entry) == 5:
elaboration = None
varname, kind, params, dependancies, descr = table_entry
elif len(table_entry) == 6:
varname, kind, params, dependancies, descr, elaboration = table_entry
else:
raise ValueError, (_("Badly formed options entry:\n %s")
% table_entry)
return (varname, kind, params, dependancies, descr, elaboration)
def GetItemGuiValue(mlist, kind, varname, params):
"""Return a representation of an item's settings."""
if kind == mm_cfg.Radio or kind == mm_cfg.Toggle:
#
# if we are sending returning the option for subscribe
# policy and this site doesn't allow open subscribes,
# then we have to alter the value of mlist.subscribe_policy
# as passed to RadioButtonArray in order to compensate
# for the fact that there is one fewer option. correspondingly,
# we alter the value back in the change options function -scott
#
# TBD: this is an ugly ugly hack.
if varname[0] == '_':
checked = 0
else:
checked = getattr(mlist, varname)
if varname == 'subscribe_policy' and not mm_cfg.ALLOW_OPEN_SUBSCRIBE:
checked = checked - 1
return RadioButtonArray(varname, params, checked)
elif (kind == mm_cfg.String or kind == mm_cfg.Email or
kind == mm_cfg.Host or kind == mm_cfg.Number):
return TextBox(varname, getattr(mlist, varname), params)
elif kind == mm_cfg.Text:
if params:
r, c = params
else:
r, c = None, None
val = getattr(mlist, varname)
if not val:
val = ''
return TextArea(varname, val, r, c)
elif kind == mm_cfg.EmailList:
if params:
r, c = params
else:
r, c = None, None
res = NL.join(getattr(mlist, varname))
return TextArea(varname, res, r, c, wrap='off')
elif kind == mm_cfg.FileUpload:
# like a text area, but also with uploading
if params:
r, c = params
else:
r, c = None, None
val = getattr(mlist, varname)
if not val:
val = ''
container = Container()
container.AddItem(_('Enter the text below, or...
'))
container.AddItem(TextArea(varname, val, r, c))
container.AddItem(_('
...specify a file to upload
'))
container.AddItem(FileUpload(varname+'_upload', r, c))
return container
# jcrey - new to deal with language
elif kind == mm_cfg.Select:
if params:
values, legend, selected = params
else:
values = mlist.GetAvailableLanguages()
legend = map(_, map(Utils.GetLanguageDescr, values))
selected = values.index(mlist.preferred_language)
return SelectOptions(varname, values, legend, selected)
def GetItemGuiDescr(mlist, category, varname, descr, detailsp):
"""Return the item's description, with link to details.
Details are not included if this is a VARHELP page, because that /is/ the
details page!
"""
if detailsp:
text = Container('
To View other sections, " "click on the appropriate range listed below")) chunk_indices = range(len(chunks)) chunk_indices.remove(chunk) buttons = [] for ci in chunk_indices: start, end = chunks[ci][0], chunks[ci][-1] url = mlist.GetScriptURL('admin') buttons.append("" + _("from %s to %s") + "" % (url, ci, start, end)) buttons = apply(UnorderedList, tuple(buttons)) footer = footer + buttons.Format() + "
" else: all.sort() footer = "
" for member in all: mtext = '%s' % ( mlist.GetOptionsURL(member, obscure=1), mlist.GetUserSubscribedAddress(member)) cells = [mtext + "" % (member), Center(CheckBox(member + "_subscribed", "on", 1).Format())] for opt in ("hide", "nomail", "ack", "notmetoo"): if mlist.GetUserOption(member,MailCommandHandler.option_info[opt]): value = "on" checked = 1 else: value = "off" checked = 0 box = CheckBox("%s_%s" % (member, opt), value, checked) cells.append(Center(box.Format())) if mlist.members.has_key(member): cells.append(Center(CheckBox(member + "_digest", "off", 0).Format())) else: cells.append(Center(CheckBox(member + "_digest", "on", 1).Format())) if mlist.GetUserOption(member,MailCommandHandler.option_info['plain']): value = 'on' checked = 1 else: value = 'off' checked = 0 cells.append(Center(CheckBox('%s_plain' % member, value, checked))) user_table.AddRow(cells) # format preferred user's language pl = mlist.GetPreferredLanguage(member) ListLangs = mlist.GetAvailableLanguages() LangDescr = map(_, map(Utils.GetLanguageDescr, ListLangs)) try: selected = ListLangs.index(pl) except: selected = 0 cells.append(Center(SelectOptions(member + '_language' , ListLangs, LangDescr, selected).Format())) container.AddItem(Center(user_table)) legend = UnorderedList() legend.AddItem(_('subscr -- Is the member subscribed?')) legend.AddItem(_("hide -- Is the member's address " "concealed on the list of subscribers?")) legend.AddItem(_('nomail -- Is delivery to the member disabled?')) legend.AddItem(_('ack -- ' 'Does the member get acknowledgements of their posts?')) legend.AddItem(_('not metoo -- ' 'Does the member avoid copies of their own posts?')) legend.AddItem(_('digest -- ' 'Does the member get messages in digests? ' '(otherwise, individual messages)')) legend.AddItem( _('plain -- ' 'If getting digests, does the member get plain text digests? ' '(otherwise, MIME)')) legend.AddItem(_("language --" "Language preferred by the user")) container.AddItem(legend.Format()) container.AddItem(footer) t = Table(width="90%") t.AddRow([Center(Header(4, _("Mass Subscribe Members")))]) t.AddCellInfo(t.GetCurrentRowIndex(), t.GetCurrentCellIndex(), bgcolor="#cccccc", colspan=8) if mlist.send_welcome_msg: nochecked = 0 yeschecked = 1 else: nochecked = 1 yeschecked = 0 t.AddRow([("1." + _("Send Welcome message to this batch? ") + RadioButton("send_welcome_msg_to_this_batch", 0, nochecked).Format() + _(" no ") + RadioButton("send_welcome_msg_to_this_batch", 1, yeschecked).Format() + _(" yes "))]) t.AddRow(["2." + _("Enter one address per line:") + "
"]) container.AddItem(Center(t)) container.AddItem(Center(TextArea(name='subscribees', rows=10,cols=60,wrap=None))) return container def FormatPasswordStuff(): change_pw_table = Table(bgcolor="#99cccc", border=0, cellspacing=0, cellpadding=2, valign="top") change_pw_table.AddRow( [Bold(Center(_('To Change The Administrator Password')))]) change_pw_table.AddCellInfo(0, 0, align="left", colspan=2) old = Table(bgcolor="#99cccc", border=1, cellspacing=0, cellpadding=2, valign="top") old.AddRow(['
real_name attribute not changed! It must differ from the list's name by case only.
""")) continue setattr(mlist, property, value) # # mass subscription processing for members category # if cgi_info.has_key('subscribees'): name_text = cgi_info['subscribees'].value name_text = name_text.replace('\r', '') names = filter(None, [unquote(n.strip()) for n in name_text.split(NL)]) send_welcome_msg = int( cgi_info["send_welcome_msg_to_this_batch"].value) digest = 0 if not mlist.digestable: digest = 0 if not mlist.nondigestable: digest = 1 subscribe_errors = [] subscribe_success = [] result = mlist.ApprovedAddMembers(names, None, digest, None, send_welcome_msg) for name in result.keys(): if result[name] is None: subscribe_success.append(name) else: # `name' was not subscribed, find out why. On failures, # result[name] is set from sys.exc_info()[:2] e, v = result[name] if e is Errors.MMAlreadyAMember: subscribe_errors.append((name, _('Already a member'))) elif e is Errors.MMBadEmailError: if name == '': name = '<blank line>' subscribe_errors.append( (name, _("Bad/Invalid email address"))) elif e is Errors.MMHostileAddress: subscribe_errors.append( (name, _("Hostile Address (illegal characters)"))) if subscribe_success: document.AddItem(Header(5, _("Successfully Subscribed:"))) document.AddItem(apply(UnorderedList, tuple((subscribe_success)))) document.AddItem("
") # ApprovedAddMembers will already have saved the list for us. if subscribe_errors: document.AddItem(Header(5, _("Error Subscribing:"))) items = map(lambda x: "%s -- %s" % (x[0], x[1]), subscribe_errors) document.AddItem(apply(UnorderedList, tuple((items)))) document.AddItem("
") # # do the user options for members category # if cgi_info.has_key('user'): user = cgi_info["user"] if type(user) is type([]): users = [] for ui in range(len(user)): users.append(user[ui].value) else: users = [user.value] errors = [] for user in users: if not cgi_info.has_key('%s_subscribed' % (user)): try: mlist.DeleteMember(user) except Errors.MMNoSuchUserError: errors.append((user, _('Not subscribed'))) continue value = cgi_info.has_key('%s_digest' % user) try: mlist.SetUserDigest(user, value, force=1) except (Errors.MMNotAMemberError, Errors.MMAlreadyDigested, Errors.MMAlreadyUndigested): pass if cgi_info.has_key(user+'_language'): newlang = cgi_info[user+'_language'].value oldlang = mlist.GetPreferredLanguage(user) if newlang <> oldlang: mlist.SetPreferredLanguage(user, newlang) for opt in ("hide", "nomail", "ack", "notmetoo", "plain"): opt_code = MailCommandHandler.option_info[opt] if cgi_info.has_key("%s_%s" % (user, opt)): mlist.SetUserOption(user, opt_code, 1, save_list=0) else: mlist.SetUserOption(user, opt_code, 0, save_list=0) if errors: document.AddItem(Header(5, _("Error Unsubscribing:"))) items = map(lambda x: "%s -- %s" % (x[0], x[1]), errors) document.AddItem(apply(UnorderedList, tuple((items)))) document.AddItem("
") def AddErrorMessage(doc, errmsg, tag='Warning: ', *args): doc.AddItem(Header(3, Bold(FontAttr( _(tag), color="#ff0000", size="+2")).Format() + Italic(errmsg % args).Format())) def GetConfigOptions(mlist, category): return mlist.GetConfigInfo()[category]