There currently are no publicly-advertised %(mailmanlink)s - mailing lists on %(hostname)s.'''), - ]) - else: - welcome.extend([ - greeting, - _('''
Below is the collection of publicly-advertised - %(mailmanlink)s mailing lists on %(hostname)s. Click on a list - name to visit the configuration pages for that list.'''), - ]) - - creatorurl = Utils.ScriptURL('create') - mailman_owner = Utils.get_site_noreply() - extra = msg and _('right ') or '' - welcome.extend([ - _('''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. If you have the proper authority, - you can also create a new mailing list. - -
General list information can be found at '''), - Link(Utils.ScriptURL('listinfo'), - _('the mailing list overview page')), - '.', - _('
(Send questions and comments to '), - Link('mailto:%s' % mailman_owner, mailman_owner), - '.)
', - ]) - - table.AddRow([Container(*welcome)]) - table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 0, colspan=2) - - if advertised: - table.AddRow([' ', ' ']) - table.AddRow([Bold(FontAttr(_('List'), size='+2')), - Bold(FontAttr(_('Description'), size='+2')) - ]) - highlight = 1 - for url, real_name, description in advertised: - table.AddRow( - [Link(url, Bold(real_name)), - description or Italic(_('[no description available]'))]) - if highlight and config.WEB_HIGHLIGHT_COLOR: - table.AddRowInfo(table.GetCurrentRowIndex(), - bgcolor=config.WEB_HIGHLIGHT_COLOR) - highlight = not highlight - - doc.AddItem(table) - doc.AddItem('
" % (varname, category, description)) - if elaboration: - doc.AddItem("%s
" % elaboration) - - if subcat: - url = '%s/%s/%s' % (mlist.GetScriptURL('admin'), category, subcat) - else: - url = '%s/%s' % (mlist.GetScriptURL('admin'), category) - form = Form(url) - valtab = Table(cellspacing=3, cellpadding=4, width='100%') - add_options_table_item(mlist, category, subcat, valtab, item, detailsp=0) - form.AddItem(valtab) - form.AddItem('
')
- form.AddItem(Center(submit_button()))
- 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
- """))
-
- adminurl = mlist.GetScriptURL('admin')
- if subcat:
- url = '%s/%s/%s' % (adminurl, category, subcat)
- else:
- url = '%s/%s' % (adminurl, category)
- categoryname = mlist.GetConfigCategories()[category][0]
- doc.AddItem(Link(url, _('return to the %(categoryname)s options page.')))
- doc.AddItem('')
- doc.AddItem(mlist.GetMailmanFooter())
- print doc.Format()
-
-
-
-def show_results(mlist, doc, category, subcat, cgidata):
- # Produce the results page
- adminurl = mlist.GetScriptURL('admin')
- categories = mlist.GetConfigCategories()
- label = _(categories[category][0])
-
- # Set up the document's headers
- realname = mlist.real_name
- doc.SetTitle(_('%(realname)s Administration (%(label)s)'))
- doc.AddItem(Center(Header(2, _(
- '%(realname)s mailing list administration
%(label)s Section'))))
- doc.AddItem('
') - - # The members and passwords categories are special in that they aren't - # defined in terms of gui elements. Create those pages here. - if category == 'members': - # Figure out which subcategory we should display - subcat = Utils.GetPathPieces()[-1] - if subcat not in ('list', 'add', 'remove'): - subcat = 'list' - # Add member category specific tables - form.AddItem(membership_options(mlist, subcat, cgidata, doc, form)) - form.AddItem(Center(submit_button('setmemberopts_btn'))) - # In "list" subcategory, we can also search for members - if subcat == 'list': - form.AddItem('
')
- usertable = Table(width="90%", border='2')
- # If there are more members than allowed by chunksize, then we split the
- # membership up alphabetically. Otherwise just display them all.
- chunksz = mlist.admin_member_chunksize
- # The email addresses had /better/ be ASCII, but might be encoded in the
- # database as Unicodes.
- all = [_m.encode() for _m in mlist.getMembers()]
- all.sort(lambda x, y: cmp(x.lower(), y.lower()))
- # See if the query has a regular expression
- regexp = cgidata.getvalue('findmember', '').strip()
- if regexp:
- try:
- cre = re.compile(regexp, re.IGNORECASE)
- except re.error:
- doc.addError(_('Bad regular expression: ') + regexp)
- else:
- # BAW: There's got to be a more efficient way of doing this!
- names = [mlist.getMemberName(s) or '' for s in all]
- all = [a for n, a in zip(names, all)
- if cre.search(n) or cre.search(a)]
- chunkindex = None
- bucket = None
- actionurl = None
- if len(all) < chunksz:
- members = all
- else:
- # Split them up alphabetically, and then split the alphabetical
- # listing by chunks
- buckets = {}
- for addr in all:
- members = buckets.setdefault(addr[0].lower(), [])
- members.append(addr)
- # Now figure out which bucket we want
- bucket = None
- qs = {}
- # POST methods, even if their actions have a query string, don't get
- # put into FieldStorage's keys :-(
- qsenviron = os.environ.get('QUERY_STRING')
- if qsenviron:
- qs = cgi.parse_qs(qsenviron)
- bucket = qs.get('letter', 'a')[0].lower()
- if bucket not in digits + lowercase:
- bucket = None
- if not bucket or not buckets.has_key(bucket):
- keys = buckets.keys()
- keys.sort()
- bucket = keys[0]
- members = buckets[bucket]
- action = adminurl + '/members?letter=%s' % bucket
- if len(members) <= chunksz:
- form.set_action(action)
- else:
- i, r = divmod(len(members), chunksz)
- numchunks = i + (not not r * 1)
- # Now chunk them up
- chunkindex = 0
- if qs.has_key('chunk'):
- try:
- chunkindex = int(qs['chunk'][0])
- except ValueError:
- chunkindex = 0
- if chunkindex < 0 or chunkindex > numchunks:
- chunkindex = 0
- members = members[chunkindex*chunksz:(chunkindex+1)*chunksz]
- # And set the action URL
- form.set_action(action + '&chunk=%s' % chunkindex)
- # So now members holds all the addresses we're going to display
- allcnt = len(all)
- if bucket:
- membercnt = len(members)
- usertable.AddRow([Center(Italic(_(
- '%(allcnt)s members total, %(membercnt)s shown')))])
- else:
- usertable.AddRow([Center(Italic(_('%(allcnt)s members total')))])
- usertable.AddCellInfo(usertable.GetCurrentRowIndex(),
- usertable.GetCurrentCellIndex(),
- colspan=OPTCOLUMNS,
- bgcolor=config.WEB_ADMINITEM_COLOR)
- # Add the alphabetical links
- if bucket:
- cells = []
- for letter in digits + lowercase:
- if not buckets.get(letter):
- continue
- url = adminurl + '/members?letter=%s' % letter
- if letter == bucket:
- show = Bold('[%s]' % letter.upper()).Format()
- else:
- show = letter.upper()
- cells.append(Link(url, show).Format())
- joiner = ' '*2 + '\n'
- usertable.AddRow([Center(joiner.join(cells))])
- usertable.AddCellInfo(usertable.GetCurrentRowIndex(),
- usertable.GetCurrentCellIndex(),
- colspan=OPTCOLUMNS,
- bgcolor=config.WEB_ADMINITEM_COLOR)
- usertable.AddRow([Center(h) for h in (_('unsub'),
- _('member address
member name'),
- _('mod'), _('hide'),
- _('nomail
[reason]'),
- _('ack'), _('not metoo'),
- _('nodupes'),
- _('digest'), _('plain'),
- _('language'))])
- rowindex = usertable.GetCurrentRowIndex()
- for i in range(OPTCOLUMNS):
- usertable.AddCellInfo(rowindex, i, bgcolor=config.WEB_ADMINITEM_COLOR)
- # Find the longest name in the list
- longest = 0
- if members:
- names = filter(None, [mlist.getMemberName(s) for s in members])
- # Make the name field at least as long as the longest email address
- longest = max([len(s) for s in names + members])
- # Abbreviations for delivery status details
- ds_abbrevs = {MemberAdaptor.UNKNOWN : _('?'),
- MemberAdaptor.BYUSER : _('U'),
- MemberAdaptor.BYADMIN : _('A'),
- MemberAdaptor.BYBOUNCE: _('B'),
- }
- # Now populate the rows
- for addr in members:
- link = Link(mlist.GetOptionsURL(addr, obscure=1),
- mlist.getMemberCPAddress(addr))
- fullname = mlist.getMemberName(addr)
- name = TextBox(addr + '_realname', fullname, size=longest).Format()
- cells = [Center(CheckBox(addr + '_unsub', 'off', 0).Format()),
- link.Format() + '
' +
- name +
- Hidden('user', urllib.quote(addr)).Format(),
- ]
- # Do the `mod' option
- if mlist.getMemberOption(addr, config.Moderate):
- value = 'on'
- checked = 1
- else:
- value = 'off'
- checked = 0
- box = CheckBox('%s_mod' % addr, value, checked)
- cells.append(Center(box).Format())
- for opt in ('hide', 'nomail', 'ack', 'notmetoo', 'nodupes'):
- extra = ''
- if opt == 'nomail':
- status = mlist.getDeliveryStatus(addr)
- if status == MemberAdaptor.ENABLED:
- value = 'off'
- checked = 0
- else:
- value = 'on'
- checked = 1
- extra = '[%s]' % ds_abbrevs[status]
- elif mlist.getMemberOption(addr, config.OPTINFO[opt]):
- value = 'on'
- checked = 1
- else:
- value = 'off'
- checked = 0
- box = CheckBox('%s_%s' % (addr, opt), value, checked)
- cells.append(Center(box.Format() + extra))
- # This code is less efficient than the original which did a has_key on
- # the underlying dictionary attribute. This version is slower and
- # less memory efficient. It points to a new MemberAdaptor interface
- # method.
- if addr in mlist.getRegularMemberKeys():
- cells.append(Center(CheckBox(addr + '_digest', 'off', 0).Format()))
- else:
- cells.append(Center(CheckBox(addr + '_digest', 'on', 1).Format()))
- if mlist.getMemberOption(addr, config.OPTINFO['plain']):
- value = 'on'
- checked = 1
- else:
- value = 'off'
- checked = 0
- cells.append(Center(CheckBox('%s_plain' % addr, value, checked)))
- # User's preferred language
- langpref = mlist.getMemberLanguage(addr)
- langs = mlist.language_codes
- langdescs = [_(config.languges.get_description(code))
- for code in langs]
- try:
- selected = langs.index(langpref)
- except ValueError:
- selected = 0
- cells.append(Center(SelectOptions(addr + '_language', langs,
- langdescs, selected)).Format())
- usertable.AddRow(cells)
- # Add the usertable and a legend
- legend = UnorderedList()
- legend.AddItem(
- _('unsub -- Click on this to unsubscribe the member.'))
- legend.AddItem(
- _("""mod -- The user's personal moderation flag. If this is
- set, postings from them will be moderated, otherwise they will be
- approved."""))
- legend.AddItem(
- _("""hide -- Is the member's address concealed on
- the list of subscribers?"""))
- legend.AddItem(_(
- """nomail -- Is delivery to the member disabled? If so, an
- abbreviation will be given describing the reason for the disabled
- delivery:
-
') - container.AddItem( - Link(adminurl + '/members/list', - _('Click here to hide the legend for this table.'))) - else: - container.AddItem( - Link(adminurl + '/members/list?legend=yes', - _('Click here to include the legend for this table.'))) - container.AddItem(Center(usertable)) - - # There may be additional chunks - if chunkindex is not None: - buttons = [] - url = adminurl + '/members?%sletter=%s&' % (addlegend, bucket) - footer = _('''
To view more members, click on the appropriate - range listed below:''') - chunkmembers = buckets[bucket] - last = len(chunkmembers) - for i in range(numchunks): - if i == chunkindex: - continue - start = chunkmembers[i*chunksz] - end = chunkmembers[min((i+1)*chunksz, last)-1] - link = Link(url + 'chunk=%d' % i, _('from %(start)s to %(end)s')) - buttons.append(link) - buttons = UnorderedList(*buttons) - container.AddItem(footer + buttons.Format() + '
') - return container - - - -def mass_subscribe(mlist, container): - # MASS SUBSCRIBE - GREY = config.WEB_ADMINITEM_COLOR - table = Table(width='90%') - table.AddRow([ - Label(_('Subscribe these users now or invite them?')), - RadioButtonArray('subscribe_or_invite', - (_('Subscribe'), _('Invite')), - 0, values=(0, 1)) - ]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, bgcolor=GREY) - table.AddCellInfo(table.GetCurrentRowIndex(), 1, bgcolor=GREY) - table.AddRow([ - Label(_('Send welcome messages to new subscribees?')), - RadioButtonArray('send_welcome_msg_to_this_batch', - (_('No'), _('Yes')), - mlist.send_welcome_msg, - values=(0, 1)) - ]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, bgcolor=GREY) - table.AddCellInfo(table.GetCurrentRowIndex(), 1, bgcolor=GREY) - table.AddRow([ - Label(_('Send notifications of new subscriptions to the list owner?')), - RadioButtonArray('send_notifications_to_list_owner', - (_('No'), _('Yes')), - mlist.admin_notify_mchanges, - values=(0,1)) - ]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, bgcolor=GREY) - table.AddCellInfo(table.GetCurrentRowIndex(), 1, bgcolor=GREY) - table.AddRow([Italic(_('Enter one address per line below...'))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - table.AddRow([Center(TextArea(name='subscribees', - rows=10, cols='70%', wrap=None))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - table.AddRow([Italic(Label(_('...or specify a file to upload:'))), - FileUpload('subscribees_upload', cols='50')]) - container.AddItem(Center(table)) - # Invitation text - table.AddRow([' ', ' ']) - table.AddRow([Italic(_("""Below, enter additional text to be added to the - top of your invitation or the subscription notification. Include at least - one blank line at the end..."""))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - table.AddRow([Center(TextArea(name='invitation', - rows=10, cols='70%', wrap=None))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - - - -def mass_remove(mlist, container): - # MASS UNSUBSCRIBE - GREY = config.WEB_ADMINITEM_COLOR - table = Table(width='90%') - table.AddRow([ - Label(_('Send unsubscription acknowledgement to the user?')), - RadioButtonArray('send_unsub_ack_to_this_batch', - (_('No'), _('Yes')), - 0, values=(0, 1)) - ]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, bgcolor=GREY) - table.AddCellInfo(table.GetCurrentRowIndex(), 1, bgcolor=GREY) - table.AddRow([ - Label(_('Send notifications to the list owner?')), - RadioButtonArray('send_unsub_notifications_to_list_owner', - (_('No'), _('Yes')), - mlist.admin_notify_mchanges, - values=(0, 1)) - ]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, bgcolor=GREY) - table.AddCellInfo(table.GetCurrentRowIndex(), 1, bgcolor=GREY) - table.AddRow([Italic(_('Enter one address per line below...'))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - table.AddRow([Center(TextArea(name='unsubscribees', - rows=10, cols='70%', wrap=None))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - table.AddRow([Italic(Label(_('...or specify a file to upload:'))), - FileUpload('unsubscribees_upload', cols='50')]) - container.AddItem(Center(table)) - - - -def password_inputs(mlist): - adminurl = mlist.GetScriptURL('admin') - table = Table(cellspacing=3, cellpadding=4) - table.AddRow([Center(Header(2, _('Change list ownership passwords')))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, - bgcolor=config.WEB_HEADER_COLOR) - table.AddRow([_("""\ -The list administrators 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. - -
The list moderators 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 -list administrators can also tend to pending requests. - -
In order to split the list ownership duties into administrators and -moderators, you must set a separate moderator password in the fields below, -and also provide the email addresses of the list moderators in the -general options section.""")]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) - # Set up the admin password table on the left - atable = Table(border=0, cellspacing=3, cellpadding=4, - bgcolor=config.WEB_ADMINPW_COLOR) - atable.AddRow([Label(_('Enter new administrator password:')), - PasswordBox('newpw', size=20)]) - atable.AddRow([Label(_('Confirm administrator password:')), - PasswordBox('confirmpw', size=20)]) - # Set up the moderator password table on the right - mtable = Table(border=0, cellspacing=3, cellpadding=4, - bgcolor=config.WEB_ADMINPW_COLOR) - mtable.AddRow([Label(_('Enter new moderator password:')), - PasswordBox('newmodpw', size=20)]) - mtable.AddRow([Label(_('Confirm moderator password:')), - PasswordBox('confirmmodpw', size=20)]) - # Add these tables to the overall password table - table.AddRow([atable, mtable]) - return table - - - -def submit_button(name='submit'): - table = Table(border=0, cellspacing=0, cellpadding=2) - table.AddRow([Bold(SubmitButton(name, _('Submit Your Changes')))]) - table.AddCellInfo(table.GetCurrentRowIndex(), 0, align='middle') - return table - - - -def change_options(mlist, category, subcat, cgidata, doc): - def safeint(formvar, defaultval=None): - try: - return int(cgidata.getvalue(formvar)) - except (ValueError, TypeError): - return defaultval - confirmed = 0 - # Handle changes to the list moderator password. Do this before checking - # the new admin password, since the latter will force a reauthentication. - new = cgidata.getvalue('newmodpw', '').strip() - confirm = cgidata.getvalue('confirmmodpw', '').strip() - if new or confirm: - if new == confirm: - mlist.mod_password = passwords.make_secret( - new, config.PASSWORD_SCHEME) - # No re-authentication necessary because the moderator's - # password doesn't get you into these pages. - else: - doc.addError(_('Moderator passwords did not match')) - # Handle changes to the list administrator password - new = cgidata.getvalue('newpw', '').strip() - confirm = cgidata.getvalue('confirmpw', '').strip() - if new or confirm: - if new == confirm: - mlist.password = passwords.make_secret(new, config.PASSWORD_SCHEME) - # Set new cookie - print mlist.MakeCookie(config.AuthListAdmin) - else: - doc.addError(_('Administrator passwords did not match')) - # Give the individual gui item a chance to process the form data - categories = mlist.GetConfigCategories() - label, gui = categories[category] - # BAW: We handle the membership page special... for now. - if category <> 'members': - gui.handleForm(mlist, category, subcat, cgidata, doc) - # mass subscription, removal processing for members category - subscribers = '' - subscribers += cgidata.getvalue('subscribees', '') - subscribers += cgidata.getvalue('subscribees_upload', '') - if subscribers: - entries = filter(None, [n.strip() for n in subscribers.splitlines()]) - send_welcome_msg = safeint('send_welcome_msg_to_this_batch', - mlist.send_welcome_msg) - send_admin_notif = safeint('send_notifications_to_list_owner', - mlist.admin_notify_mchanges) - # Default is to subscribe - subscribe_or_invite = safeint('subscribe_or_invite', 0) - invitation = cgidata.getvalue('invitation', '') - digest = mlist.digest_is_default - if not mlist.digestable: - digest = 0 - if not mlist.nondigestable: - digest = 1 - subscribe_errors = [] - subscribe_success = [] - # Now cruise through all the subscribees and do the deed. BAW: we - # should limit the number of "Successfully subscribed" status messages - # we display. Try uploading a file with 10k names -- it takes a while - # to render the status page. - for entry in entries: - fullname, address = parseaddr(entry) - # Canonicalize the full name - fullname = Utils.canonstr(fullname, mlist.preferred_language) - userdesc = UserDesc(address, fullname, - Utils.MakeRandomPassword(), - digest, mlist.preferred_language) - try: - if subscribe_or_invite: - if mlist.isMember(address): - raise Errors.MMAlreadyAMember - else: - mlist.InviteNewMember(userdesc, invitation) - else: - mlist.ApprovedAddMember(userdesc, send_welcome_msg, - send_admin_notif, invitation, - whence='admin mass sub') - except Errors.MMAlreadyAMember: - subscribe_errors.append((entry, _('Already a member'))) - except Errors.InvalidEmailAddress: - if userdesc.address == '': - subscribe_errors.append((_('<blank line>'), - _('Bad/Invalid email address'))) - else: - subscribe_errors.append((entry, - _('Bad/Invalid email address'))) - except Errors.MembershipIsBanned, pattern: - subscribe_errors.append( - (entry, _('Banned address (matched %(pattern)s)'))) - else: - member = Utils.uncanonstr(formataddr((fullname, address))) - subscribe_success.append(Utils.websafe(member)) - if subscribe_success: - if subscribe_or_invite: - doc.AddItem(Header(5, _('Successfully invited:'))) - else: - doc.AddItem(Header(5, _('Successfully subscribed:'))) - doc.AddItem(UnorderedList(*subscribe_success)) - doc.AddItem('
') - if subscribe_errors: - if subscribe_or_invite: - doc.AddItem(Header(5, _('Error inviting:'))) - else: - doc.AddItem(Header(5, _('Error subscribing:'))) - items = ['%s -- %s' % (x0, x1) for x0, x1 in subscribe_errors] - doc.AddItem(UnorderedList(*items)) - doc.AddItem('
') - # Unsubscriptions - removals = '' - if cgidata.has_key('unsubscribees'): - removals += cgidata['unsubscribees'].value - if cgidata.has_key('unsubscribees_upload') and \ - cgidata['unsubscribees_upload'].value: - removals += cgidata['unsubscribees_upload'].value - if removals: - names = filter(None, [n.strip() for n in removals.splitlines()]) - send_unsub_notifications = int( - cgidata['send_unsub_notifications_to_list_owner'].value) - userack = int( - cgidata['send_unsub_ack_to_this_batch'].value) - unsubscribe_errors = [] - unsubscribe_success = [] - for addr in names: - try: - mlist.ApprovedDeleteMember( - addr, whence='admin mass unsub', - admin_notif=send_unsub_notifications, - userack=userack) - unsubscribe_success.append(addr) - except Errors.NotAMemberError: - unsubscribe_errors.append(addr) - if unsubscribe_success: - doc.AddItem(Header(5, _('Successfully Unsubscribed:'))) - doc.AddItem(UnorderedList(*unsubscribe_success)) - doc.AddItem('
') - if unsubscribe_errors: - doc.AddItem(Header(3, Bold(FontAttr( - _('Cannot unsubscribe non-members:'), - color='#ff0000', size='+2')).Format())) - doc.AddItem(UnorderedList(*unsubscribe_errors)) - doc.AddItem('
') - # See if this was a moderation bit operation - if cgidata.has_key('allmodbit_btn'): - val = cgidata.getvalue('allmodbit_val') - try: - val = int(val) - except VallueError: - val = None - if val not in (0, 1): - doc.addError(_('Bad moderation flag value')) - else: - for member in mlist.getMembers(): - mlist.setMemberOption(member, config.Moderate, val) - # do the user options for members category - if cgidata.has_key('setmemberopts_btn') and cgidata.has_key('user'): - user = cgidata['user'] - if isinstance(user, list): - users = [] - for ui in range(len(user)): - users.append(urllib.unquote(user[ui].value)) - else: - users = [urllib.unquote(user.value)] - errors = [] - removes = [] - for user in users: - if cgidata.has_key('%s_unsub' % user): - try: - mlist.ApprovedDeleteMember(user, whence='member mgt page') - removes.append(user) - except Errors.NotAMemberError: - errors.append((user, _('Not subscribed'))) - continue - if not mlist.isMember(user): - doc.addError(_('Ignoring changes to deleted member: %(user)s'), - tag=_('Warning: ')) - continue - value = cgidata.has_key('%s_digest' % user) - try: - mlist.setMemberOption(user, config.Digests, value) - except (Errors.AlreadyReceivingDigests, - Errors.AlreadyReceivingRegularDeliveries, - Errors.CantDigestError, - Errors.MustDigestError): - # BAW: Hmm... - pass - - newname = cgidata.getvalue(user+'_realname', '') - newname = Utils.canonstr(newname, mlist.preferred_language) - mlist.setMemberName(user, newname) - - newlang = cgidata.getvalue(user+'_language') - oldlang = mlist.getMemberLanguage(user) - if (newlang not in config.languages.enabled_codes - and newlang <> oldlang): - # Then - mlist.setMemberLanguage(user, newlang) - - moderate = not not cgidata.getvalue(user+'_mod') - mlist.setMemberOption(user, config.Moderate, moderate) - - # Set the `nomail' flag, but only if the user isn't already - # disabled (otherwise we might change BYUSER into BYADMIN). - if cgidata.has_key('%s_nomail' % user): - if mlist.getDeliveryStatus(user) == MemberAdaptor.ENABLED: - mlist.setDeliveryStatus(user, MemberAdaptor.BYADMIN) - else: - mlist.setDeliveryStatus(user, MemberAdaptor.ENABLED) - for opt in ('hide', 'ack', 'notmetoo', 'nodupes', 'plain'): - opt_code = config.OPTINFO[opt] - if cgidata.has_key('%s_%s' % (user, opt)): - mlist.setMemberOption(user, opt_code, 1) - else: - mlist.setMemberOption(user, opt_code, 0) - # Give some feedback on who's been removed - if removes: - doc.AddItem(Header(5, _('Successfully Removed:'))) - doc.AddItem(UnorderedList(*removes)) - doc.AddItem('
') - if errors: - doc.AddItem(Header(5, _("Error Unsubscribing:"))) - items = ['%s -- %s' % (x[0], x[1]) for x in errors] - doc.AddItem(apply(UnorderedList, tuple((items)))) - doc.AddItem("
")
diff --git a/src/mailman/web/Cgi/admindb.py b/src/mailman/web/Cgi/admindb.py
deleted file mode 100644
index 5d756fc78..000000000
--- a/src/mailman/web/Cgi/admindb.py
+++ /dev/null
@@ -1,813 +0,0 @@
-# Copyright (C) 1998-2009 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see
')
- # BAW: kludge to remove id from requests.db.
- try:
- mlist.HandleRequest(id, config.DISCARD)
- except Errors.LostHeldMessage:
- pass
- return
- except email.Errors.MessageParseError:
- form.AddItem(_('Message with id #%(id)d is corrupted.'))
- # BAW: Should we really delete this, or shuttle it off for site admin
- # to look more closely at?
- form.AddItem(' ')
- # BAW: kludge to remove id from requests.db.
- try:
- mlist.HandleRequest(id, config.DISCARD)
- except Errors.LostHeldMessage:
- pass
- return
- # Get the header text and the message body excerpt
- lines = []
- chars = 0
- # A negative value means, include the entire message regardless of size
- limit = config.ADMINDB_PAGE_TEXT_LIMIT
- for line in email.Iterators.body_line_iterator(msg):
- lines.append(line)
- chars += len(line)
- if chars > limit > 0:
- break
- # Negative values mean display the entire message, regardless of size
- if limit > 0:
- body = EMPTYSTRING.join(lines)[:config.ADMINDB_PAGE_TEXT_LIMIT]
- else:
- body = EMPTYSTRING.join(lines)
- # Get message charset and try encode in list charset
- mcset = msg.get_param('charset', 'us-ascii').lower()
- lcset = Utils.GetCharSet(mlist.preferred_language)
- if mcset <> lcset:
- try:
- body = unicode(body, mcset).encode(lcset)
- except (LookupError, UnicodeError, ValueError):
- pass
- hdrtxt = NL.join(['%s: %s' % (k, v) for k, v in msg.items()])
- hdrtxt = Utils.websafe(hdrtxt)
- # Okay, we've reconstituted the message just fine. Now for the fun part!
- t = Table(cellspacing=0, cellpadding=0, width='100%')
- t.AddRow([Bold(_('From:')), sender])
- row, col = t.GetCurrentRowIndex(), t.GetCurrentCellIndex()
- t.AddCellInfo(row, col-1, align='right')
- t.AddRow([Bold(_('Subject:')),
- Utils.websafe(Utils.oneline(subject, lcset))])
- t.AddCellInfo(row+1, col-1, align='right')
- t.AddRow([Bold(_('Reason:')), _(reason)])
- t.AddCellInfo(row+2, col-1, align='right')
- when = msgdata.get('received_time')
- if when:
- t.AddRow([Bold(_('Received:')), time.ctime(when)])
- t.AddCellInfo(row+2, col-1, align='right')
- # We can't use a RadioButtonArray here because horizontal placement can be
- # confusing to the user and vertical placement takes up too much
- # real-estate. This is a hack!
- buttons = Table(cellspacing="5", cellpadding="0")
- buttons.AddRow(map(lambda x, s=' '*5: s+x+s,
- (_('Defer'), _('Approve'), _('Reject'), _('Discard'))))
- buttons.AddRow([Center(RadioButton(id, config.DEFER, 1)),
- Center(RadioButton(id, config.APPROVE, 0)),
- Center(RadioButton(id, config.REJECT, 0)),
- Center(RadioButton(id, config.DISCARD, 0)),
- ])
- t.AddRow([Bold(_('Action:')), buttons])
- t.AddCellInfo(row+3, col-1, align='right')
- t.AddRow([' ',
- CheckBox('preserve-%d' % id, 'on', 0).Format() +
- ' ' + _('Preserve message for site administrator')
- ])
- t.AddRow([' ',
- CheckBox('forward-%d' % id, 'on', 0).Format() +
- ' ' + _('Additionally, forward this message to: ') +
- TextBox('forward-addr-%d' % id, size=47,
- value=mlist.GetOwnerEmail()).Format()
- ])
- notice = msgdata.get('rejection_notice', _('[No explanation given]'))
- t.AddRow([
- Bold(_('If you reject this post, ')
-
-
-
-def process_form(mlist, doc, cgidata):
- senderactions = {}
- # Sender-centric actions
- for k in cgidata.keys():
- for prefix in ('senderaction-', 'senderpreserve-', 'senderforward-',
- 'senderforwardto-', 'senderfilterp-', 'senderfilter-',
- 'senderclearmodp-', 'senderbanp-'):
- if k.startswith(prefix):
- action = k[:len(prefix)-1]
- sender = unquote_plus(k[len(prefix):])
- value = cgidata.getvalue(k)
- senderactions.setdefault(sender, {})[action] = value
- # discard-all-defers
- try:
- discardalldefersp = cgidata.getvalue('discardalldefersp', 0)
- except ValueError:
- discardalldefersp = 0
- for sender in senderactions.keys():
- actions = senderactions[sender]
- # Handle what to do about all this sender's held messages
- try:
- action = int(actions.get('senderaction', config.DEFER))
- except ValueError:
- action = config.DEFER
- if action == config.DEFER and discardalldefersp:
- action = config.DISCARD
- if action in (config.DEFER, config.APPROVE,
- config.REJECT, config.DISCARD):
- preserve = actions.get('senderpreserve', 0)
- forward = actions.get('senderforward', 0)
- forwardaddr = actions.get('senderforwardto', '')
- comment = _('No reason given')
- bysender = helds_by_sender(mlist)
- for id in bysender.get(sender, []):
- try:
- mlist.HandleRequest(id, action, comment, preserve,
- forward, forwardaddr)
- except (KeyError, Errors.LostHeldMessage):
- # That's okay, it just means someone else has already
- # updated the database while we were staring at the page,
- # so just ignore it
- continue
- # Now see if this sender should be added to one of the nonmember
- # sender filters.
- if actions.get('senderfilterp', 0):
- try:
- which = int(actions.get('senderfilter'))
- except ValueError:
- # Bogus form
- which = 'ignore'
- if which == config.ACCEPT:
- mlist.accept_these_nonmembers.append(sender)
- elif which == config.HOLD:
- mlist.hold_these_nonmembers.append(sender)
- elif which == config.REJECT:
- mlist.reject_these_nonmembers.append(sender)
- elif which == config.DISCARD:
- mlist.discard_these_nonmembers.append(sender)
- # Otherwise, it's a bogus form, so ignore it
- # And now see if we're to clear the member's moderation flag.
- if actions.get('senderclearmodp', 0):
- try:
- mlist.setMemberOption(sender, config.Moderate, 0)
- except Errors.NotAMemberError:
- # This person's not a member any more. Oh well.
- pass
- # And should this address be banned?
- if actions.get('senderbanp', 0):
- if sender not in mlist.ban_list:
- mlist.ban_list.append(sender)
- # Now, do message specific actions
- banaddrs = []
- erroraddrs = []
- for k in cgidata.keys():
- formv = cgidata[k]
- if isinstance(formv, list):
- continue
- try:
- v = int(formv.value)
- request_id = int(k)
- except ValueError:
- continue
- if v not in (config.DEFER, config.APPROVE, config.REJECT,
- config.DISCARD, config.SUBSCRIBE, config.UNSUBSCRIBE,
- config.ACCEPT, config.HOLD):
- continue
- # Get the action comment and reasons if present.
- commentkey = 'comment-%d' % request_id
- preservekey = 'preserve-%d' % request_id
- forwardkey = 'forward-%d' % request_id
- forwardaddrkey = 'forward-addr-%d' % request_id
- bankey = 'ban-%d' % request_id
- # Defaults
- comment = _('[No reason given]')
- preserve = 0
- forward = 0
- forwardaddr = ''
- if cgidata.has_key(commentkey):
- comment = cgidata[commentkey].value
- if cgidata.has_key(preservekey):
- preserve = cgidata[preservekey].value
- if cgidata.has_key(forwardkey):
- forward = cgidata[forwardkey].value
- if cgidata.has_key(forwardaddrkey):
- forwardaddr = cgidata[forwardaddrkey].value
- # Should we ban this address? Do this check before handling the
- # request id because that will evict the record.
- requestsdb = config.db.get_list_requests(mlist)
- if cgidata.getvalue(bankey):
- key, data = requestsdb.get_record(request_id)
- sender = data['sender']
- if sender not in mlist.ban_list:
- mlist.ban_list.append(sender)
- # Handle the request id
- try:
- mlist.HandleRequest(request_id, v, comment,
- preserve, forward, forwardaddr)
- except (KeyError, Errors.LostHeldMessage):
- # That's okay, it just means someone else has already updated the
- # database while we were staring at the page, so just ignore it
- continue
- except Errors.MMAlreadyAMember, v:
- erroraddrs.append(v)
- except Errors.MembershipIsBanned, pattern:
- data = requestsdb.get_record(request_id)
- sender = data['sender']
- banaddrs.append((sender, pattern))
- # save the list and print the results
- doc.AddItem(Header(2, _('Database Updated...')))
- if erroraddrs:
- for addr in erroraddrs:
- doc.AddItem(`addr` + _(' is already a member') + ' Note that confirmation strings expire approximately
- %(days)s days after the initial subscription request. If your
- confirmation has expired, please try to re-submit your subscription.
- Otherwise, re-enter your confirmation
- string.''')
-
- content = mlist.pend_confirm(cookie, expunge=False)
- if content is None:
- bad_confirmation(doc, badconfirmstr)
- doc.AddItem(mlist.GetMailmanFooter())
- print doc.Format()
- return
-
- try:
- if content[0] == Pending.SUBSCRIPTION:
- if cgidata.getvalue('cancel'):
- subscription_cancel(mlist, doc, cookie)
- elif cgidata.getvalue('submit'):
- subscription_confirm(mlist, doc, cookie, cgidata)
- else:
- subscription_prompt(mlist, doc, cookie, content[1])
- elif content[0] == Pending.UNSUBSCRIPTION:
- try:
- if cgidata.getvalue('cancel'):
- unsubscription_cancel(mlist, doc, cookie)
- elif cgidata.getvalue('submit'):
- unsubscription_confirm(mlist, doc, cookie)
- else:
- unsubscription_prompt(mlist, doc, cookie, *content[1:])
- except Errors.NotAMemberError:
- doc.addError(_("""The address requesting unsubscription is not
- a member of the mailing list. Perhaps you have already been
- unsubscribed, e.g. by the list administrator?"""))
- # Expunge this record from the pending database.
- expunge(mlist, cookie)
- elif content[0] == Pending.CHANGE_OF_ADDRESS:
- if cgidata.getvalue('cancel'):
- addrchange_cancel(mlist, doc, cookie)
- elif cgidata.getvalue('submit'):
- addrchange_confirm(mlist, doc, cookie)
- else:
- # Watch out for users who have unsubscribed themselves in the
- # meantime!
- try:
- addrchange_prompt(mlist, doc, cookie, *content[1:])
- except Errors.NotAMemberError:
- doc.addError(_("""The address requesting to be changed has
- been subsequently unsubscribed. This request has been
- cancelled."""))
- # Expunge this record from the pending database.
- expunge(mlist, cookie)
- elif content[0] == Pending.HELD_MESSAGE:
- if cgidata.getvalue('cancel'):
- heldmsg_cancel(mlist, doc, cookie)
- elif cgidata.getvalue('submit'):
- heldmsg_confirm(mlist, doc, cookie)
- else:
- heldmsg_prompt(mlist, doc, cookie, *content[1:])
- elif content[0] == Pending.RE_ENABLE:
- if cgidata.getvalue('cancel'):
- reenable_cancel(mlist, doc, cookie)
- elif cgidata.getvalue('submit'):
- reenable_confirm(mlist, doc, cookie)
- else:
- reenable_prompt(mlist, doc, cookie, *content[1:])
- else:
- bad_confirmation(doc, _('System error, bad content: %(content)s'))
- except Errors.MMBadConfirmation:
- bad_confirmation(doc, badconfirmstr)
-
- doc.AddItem(mlist.GetMailmanFooter())
- print doc.Format()
-
-
-
-def bad_confirmation(doc, extra=''):
- title = _('Bad confirmation string')
- doc.SetTitle(title)
- doc.AddItem(Header(3, Bold(FontAttr(title, color='#ff0000', size='+2'))))
- doc.AddItem(extra)
-
-
-def expunge(mlist, cookie):
- # Expunge this record from the list's pending database. This requires
- # that the list lock be acquired, however the list doesn't need to be
- # saved because this operation doesn't touch the config.pck file.
- mlist.Lock()
- try:
- mlist.pend_confirm(cookie, expunge=True)
- finally:
- mlist.Unlock()
-
-
-
-def ask_for_cookie(mlist, doc, extra=''):
- title = _('Enter confirmation cookie')
- doc.SetTitle(title)
- form = Form(mlist.GetScriptURL('confirm'))
- table = Table(border=0, width='100%')
- table.AddRow([Center(Bold(FontAttr(title, size='+1')))])
- table.AddCellInfo(table.GetCurrentRowIndex(), 0,
- colspan=2, bgcolor=config.WEB_HEADER_COLOR)
-
- if extra:
- table.AddRow([Bold(FontAttr(extra, size='+1'))])
- table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2)
-
- # Add cookie entry box
- table.AddRow([_("""Please enter the confirmation string
- (i.e. cookie) that you received in your email message, in the box
- below. Then hit the Submit button to proceed to the next
- confirmation step.""")])
- table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2)
- table.AddRow([Label(_('Confirmation string:')),
- TextBox('cookie')])
- table.AddRow([Center(SubmitButton('submit_cookie', _('Submit')))])
- table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2)
- form.AddItem(table)
- doc.AddItem(form)
- doc.AddItem(mlist.GetMailmanFooter())
- print doc.Format()
-
-
-
-def subscription_prompt(mlist, doc, cookie, userdesc):
- email = userdesc.address
- password = userdesc.password
- digest = userdesc.digest
- lang = userdesc.language
- name = Utils.uncanonstr(userdesc.fullname, lang)
- i18n.set_language(lang)
- doc.set_language(lang)
- title = _('Confirm subscription request')
- doc.SetTitle(title)
-
- form = Form(mlist.GetScriptURL('confirm'))
- table = Table(border=0, width='100%')
- table.AddRow([Center(Bold(FontAttr(title, size='+1')))])
- table.AddCellInfo(table.GetCurrentRowIndex(), 0,
- colspan=2, bgcolor=config.WEB_HEADER_COLOR)
-
- listname = mlist.real_name
- # This is the normal, no-confirmation required results text.
- #
- # We do things this way so we don't have to reformat this paragraph, which
- # would mess up translations. If you modify this text for other reasons,
- # please refill the paragraph, and clean up the logic.
- result = _("""Your confirmation is required in order to complete the
- subscription request to the mailing list %(listname)s. Your
- subscription settings are shown below; make any necessary changes and hit
- Subscribe to complete the confirmation process. Once you've
- confirmed your subscription request, you will be shown your account
- options page which you can use to further customize your membership
- options.
-
- Note: your password will be emailed to you once your subscription is
- confirmed. You can change it by visiting your personal options page.
-
- Or hit Cancel my subscription request if you no longer want to
- subscribe to this list.""") + ' Note: your password will be emailed to you once your subscription
- is confirmed. You can change it by visiting your personal options
- page.
-
- Or, if you've changed your mind and do not want to subscribe to
- this mailing list, you can hit Cancel my subscription
- request.""") + ' You can now
- proceed to your membership login
- page.'''))
- mlist.Save()
- finally:
- mlist.Unlock()
-
-
-
-def unsubscription_cancel(mlist, doc, cookie):
- # Expunge this record from the pending database
- expunge(mlist, cookie)
- doc.AddItem(_('You have canceled your unsubscription request.'))
-
-
-
-def unsubscription_confirm(mlist, doc, cookie):
- # See the comment in admin.py about the need for the signal
- # handler.
- def sigterm_handler(signum, frame, mlist=mlist):
- mlist.Unlock()
- sys.exit(0)
-
- mlist.Lock()
- try:
- try:
- # Do this in two steps so we can get the preferred language for
- # the user who is unsubscribing.
- op, addr = mlist.pend_confirm(cookie, expunge=False)
- lang = mlist.getMemberLanguage(addr)
- i18n.set_language(lang)
- doc.set_language(lang)
- op, addr = mlist.ProcessConfirmation(cookie)
- except Errors.NotAMemberError:
- bad_confirmation(doc, _('''Invalid confirmation string. It is
- possible that you are attempting to confirm a request for an
- address that has already been unsubscribed.'''))
- else:
- # The response
- listname = mlist.real_name
- title = _('Unsubscription request confirmed')
- listinfourl = mlist.GetScriptURL('listinfo')
- doc.SetTitle(title)
- doc.AddItem(Header(3, Bold(FontAttr(title, size='+2'))))
- doc.AddItem(_("""\
- You have successfully unsubscribed from the %(listname)s mailing
- list. You can now visit the list's main
- information page."""))
- mlist.Save()
- finally:
- mlist.Unlock()
-
-
-
-def unsubscription_prompt(mlist, doc, cookie, addr):
- title = _('Confirm unsubscription request')
- doc.SetTitle(title)
- lang = mlist.getMemberLanguage(addr)
- i18n.set_language(lang)
- doc.set_language(lang)
-
- form = Form(mlist.GetScriptURL('confirm'))
- table = Table(border=0, width='100%')
- table.AddRow([Center(Bold(FontAttr(title, size='+1')))])
- table.AddCellInfo(table.GetCurrentRowIndex(), 0,
- colspan=2, bgcolor=config.WEB_HEADER_COLOR)
-
- listname = mlist.real_name
- fullname = mlist.getMemberName(addr)
- if fullname is None:
- fullname = _('Not available')
- else:
- fullname = Utils.uncanonstr(fullname, lang)
- table.AddRow([_("""Your confirmation is required in order to complete the
- unsubscription request from the mailing list %(listname)s. You
- are currently subscribed with
-
- Or hit Cancel and discard to cancel this unsubscription
- request.""") + ' Or hit Cancel and discard to cancel this change of address
- request.""") + ' Or hit the Continue awaiting approval button to continue to
- allow the list moderator to approve or reject the message.''')
- + ' You also need to enter the email address of the initial list owner.
- Once the list is created, the list owner will be given notification, along
- with the initial list password. The list owner will then be able to
- modify the password and add or remove additional list owners.
-
- If you want Mailman to automatically generate the initial list admin
- password, click on `Yes' in the autogenerate field below, and leave the
- initial list password fields empty.
-
- You must have the proper authorization to create new mailing lists.
- Each site should have a list creator's password, which you can
- enter in the field at the bottom. Note that the site administrator's
- password can also be used for authentication.
- """)])
- # Build the form for the necessary input
- GREY = config.WEB_ADMINITEM_COLOR
- form = Form(Utils.ScriptURL('create'))
- ftable = Table(border=0, cols='2', width='100%',
- cellspacing=3, cellpadding=4)
-
- ftable.AddRow([Center(Italic(_('List Identity')))])
- ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2)
-
- safelistname = Utils.websafe(cgidata.getvalue('listname', ''))
- ftable.AddRow([Label(_('Name of list:')),
- TextBox('listname', safelistname)])
- ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY)
- ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY)
-
- safeowner = Utils.websafe(cgidata.getvalue('owner', ''))
- ftable.AddRow([Label(_('Initial list owner address:')),
- TextBox('owner', safeowner)])
- ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY)
- ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY)
-
- try:
- autogen = bool(int(cgidata.getvalue('autogen', '0')))
- except ValueError:
- autogen = False
- ftable.AddRow([Label(_('Auto-generate initial list password?')),
- RadioButtonArray('autogen', (_('No'), _('Yes')),
- checked=autogen,
- values=(0, 1))])
- ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY)
- ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY)
-
- safepasswd = Utils.websafe(cgidata.getvalue('password', ''))
- ftable.AddRow([Label(_('Initial list password:')),
- PasswordBox('password', safepasswd)])
- ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY)
- ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY)
-
- safeconfirm = Utils.websafe(cgidata.getvalue('confirm', ''))
- ftable.AddRow([Label(_('Confirm initial password:')),
- PasswordBox('confirm', safeconfirm)])
- ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY)
- ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY)
-
- try:
- notify = bool(int(cgidata.getvalue('notify', '1')))
- except ValueError:
- notify = True
- try:
- moderate = bool(int(cgidata.getvalue('moderate',
- config.DEFAULT_DEFAULT_MEMBER_MODERATION)))
- except ValueError:
- moderate = config.DEFAULT_DEFAULT_MEMBER_MODERATION
-
- ftable.AddRow([Center(Italic(_('List Characteristics')))])
- ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2)
-
- ftable.AddRow([
- Label(_("""Should new members be quarantined before they
- are allowed to post unmoderated to this list? Answer Yes to hold
- new member postings for moderator approval by default.""")),
- RadioButtonArray('moderate', (_('No'), _('Yes')),
- checked=moderate,
- values=(0,1))])
- ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY)
- ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY)
- # Create the table of initially supported languages, sorted on the long
- # name of the language.
- revmap = {}
- for key, (name, charset) in config.LC_DESCRIPTIONS.items():
- revmap[_(name)] = key
- langnames = revmap.keys()
- langnames.sort()
- langs = []
- for name in langnames:
- langs.append(revmap[name])
- try:
- langi = langs.index(config.DEFAULT_SERVER_LANGUAGE)
- except ValueError:
- # Someone must have deleted the servers's preferred language. Could
- # be other trouble lurking!
- langi = 0
- # BAW: we should preserve the list of checked languages across form
- # invocations.
- checked = [0] * len(langs)
- checked[langi] = 1
- deflang = _(
- config.languages.get_description(config.DEFAULT_SERVER_LANGUAGE))
- ftable.AddRow([Label(_(
- """Initial list of supported languages. Note that if you do not
- select at least one initial language, the list will use the server
- default language of $deflang""")),
- CheckBoxArray('langs',
- [_(config.languges.get_description(code))
- for code in langs],
- checked=checked,
- values=langs)])
- ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY)
- ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY)
-
- ftable.AddRow([Label(_('Send "list created" email to list owner?')),
- RadioButtonArray('notify', (_('No'), _('Yes')),
- checked=notify,
- values=(0, 1))])
- ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY)
- ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY)
-
- ftable.AddRow([' ')
- doc.AddItem(' ' + _('When you are done making changes...'))
- form.AddItem(SubmitButton('submit', _('Submit Changes')))
- doc.AddItem(form)
-
-
-
-def ChangeHTML(mlist, cgi_info, template_name, doc):
- if not cgi_info.has_key('html_code'):
- doc.AddItem(Header(3,_("Can't have empty html page.")))
- doc.AddItem(Header(3,_("HTML Unchanged.")))
- doc.AddItem(' There currently are no publicly-advertised
- %(mailmanlink)s mailing lists on %(hostname)s.'''))
- else:
- welcome.append(
- _(''' Below is a listing of all the public mailing lists on
- %(hostname)s. Click on a list name to get more information about
- the list, or to subscribe, unsubscribe, and change the preferences
- on your subscription.'''))
-
- # set up some local variables
- adj = msg and _('right') or ''
- siteowner = Utils.get_site_noreply()
- welcome.extend(
- (_(''' To visit the general information page for an unadvertised list,
- open a URL similar to this one, but with a '/' and the %(adj)s
- list name appended.
- List administrators, you can visit '''),
- Link(Utils.ScriptURL('admin'),
- _('the list admin overview page')),
- _(''' to find the management interface for your list.
- If you are having trouble using the lists, please contact '''),
- Link('mailto:' + siteowner, siteowner),
- '. '))
-
- table.AddRow([apply(Container, welcome)])
- table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 0, colspan=2)
-
- if advertised:
- table.AddRow([' ', ' '])
- table.AddRow([Bold(FontAttr(_('List'), size='+2')),
- Bold(FontAttr(_('Description'), size='+2'))
- ])
- highlight = 1
- for url, real_name, description in advertised:
- table.AddRow(
- [Link(url, Bold(real_name)),
- description or Italic(_('[no description available]'))])
- if highlight and config.WEB_HIGHLIGHT_COLOR:
- table.AddRowInfo(table.GetCurrentRowIndex(),
- bgcolor=config.WEB_HIGHLIGHT_COLOR)
- highlight = not highlight
-
- doc.AddItem(table)
- doc.AddItem(' Important: From this point on, you must have
- cookies enabled in your browser, otherwise none of your changes will take
- effect.
- """)])
- # Password and login button
- ptable = Table(width='50%', border=0, cellspacing=4, cellpadding=5)
- if user is None:
- ptable.AddRow([Label(_('Email address:')),
- TextBox('email', size=20)])
- else:
- ptable.AddRow([Hidden('email', user)])
- ptable.AddRow([Label(_('Password:')),
- PasswordBox('password', size=20)])
- ptable.AddRow([Center(SubmitButton('login', _('Log in')))])
- ptable.AddCellInfo(ptable.GetCurrentRowIndex(), 0, colspan=2)
- table.AddRow([Center(ptable)])
- # Unsubscribe section
- table.AddRow([Center(Header(2, _('Unsubscribe')))])
- table.AddCellInfo(table.GetCurrentRowIndex(), 0,
- bgcolor=config.WEB_HEADER_COLOR)
-
- table.AddRow([_("""By clicking on the Unsubscribe button, a
- confirmation message will be emailed to you. This message will have a
- link that you should click on to complete the removal process (you can
- also confirm by email; see the instructions in the confirmation
- message).""")])
-
- table.AddRow([Center(SubmitButton('login-unsub', _('Unsubscribe')))])
- # Password reminder section
- table.AddRow([Center(Header(2, _('Password reminder')))])
- table.AddCellInfo(table.GetCurrentRowIndex(), 0,
- bgcolor=config.WEB_HEADER_COLOR)
-
- table.AddRow([_("""By clicking on the Remind button, your
- password will be emailed to you.""")])
-
- table.AddRow([Center(SubmitButton('login-remind', _('Remind')))])
- # Finish up glomming together the login page
- form.AddItem(table)
- doc.AddItem(form)
- doc.AddItem(mlist.GetMailmanFooter())
-
-
-
-def lists_of_member(mlist, user):
- hostname = mlist.host_name
- onlists = []
- for listname in config.list_manager.names:
- # The current list will always handle things in the mainline
- if listname == mlist.internal_name():
- continue
- glist = MailList.MailList(listname, lock=0)
- if glist.host_name <> hostname:
- continue
- if not glist.isMember(user):
- continue
- onlists.append(glist)
- return onlists
-
-
-
-def change_password(mlist, user, newpw):
- # Must own the list lock!
- mlist.Lock()
- try:
- # Change the user's password. The password must already have been
- # compared to the confirmpw and otherwise been vetted for
- # acceptability.
- mlist.setMemberPassword(user, newpw)
- mlist.Save()
- finally:
- mlist.Unlock()
-
-
-
-def global_options(mlist, user, globalopts):
- # Is there anything to do?
- for attr in dir(globalopts):
- if attr.startswith('_'):
- continue
- if getattr(globalopts, attr) is not None:
- break
- else:
- return
-
- def sigterm_handler(signum, frame, mlist=mlist):
- # Make sure the list gets unlocked...
- mlist.Unlock()
- # ...and ensure we exit, otherwise race conditions could cause us to
- # enter MailList.Save() while we're in the unlocked state, and that
- # could be bad!
- sys.exit(0)
-
- # Must own the list lock!
- mlist.Lock()
- try:
- if globalopts.enable is not None:
- mlist.setDeliveryStatus(user, globalopts.enable)
-
- if globalopts.remind is not None:
- mlist.setMemberOption(user, config.SuppressPasswordReminder,
- globalopts.remind)
-
- if globalopts.nodupes is not None:
- mlist.setMemberOption(user, config.DontReceiveDuplicates,
- globalopts.nodupes)
-
- if globalopts.mime is not None:
- mlist.setMemberOption(user, config.DisableMime, globalopts.mime)
-
- mlist.Save()
- finally:
- mlist.Unlock()
-
-
-
-def topic_details(mlist, doc, user, cpuser, userlang, varhelp):
- # Find out which topic the user wants to get details of
- reflist = varhelp.split('/')
- name = None
- topicname = _(' Note that no warning will be sent to your list members and after this
- action, any subsequent messages sent to the mailing list, or any of its
- administrative addreses will bounce.
-
- You also have the option of removing the archives for this mailing list
- at this time. It is almost always recommended that you do
- not remove the archives, since they serve as the
- historical record of your mailing list.
-
- For your safety, you will be asked to reconfirm the list password.
- """)])
- GREY = config.WEB_ADMINITEM_COLOR
- form = Form(mlist.GetScriptURL('rmlist'))
- ftable = Table(border=0, cols='2', width='100%',
- cellspacing=3, cellpadding=4)
-
- ftable.AddRow([Label(_('List password:')), PasswordBox('password')])
- ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY)
- ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY)
-
- ftable.AddRow([Label(_('Also delete archives?')),
- RadioButtonArray('delarchives', (_('No'), _('Yes')),
- checked=0, values=(0, 1))])
- ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY)
- ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY)
-
- ftable.AddRow([Center(Link(
- mlist.GetScriptURL('admin'),
- _('Cancel and return to list administration')))])
- ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2)
-
- ftable.AddRow([Center(SubmitButton('doit', _('Delete this list')))])
- ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2)
- form.AddItem(ftable)
- table.AddRow([form])
- doc.AddItem(table)
diff --git a/src/mailman/web/Cgi/roster.py b/src/mailman/web/Cgi/roster.py
deleted file mode 100644
index 2351d6915..000000000
--- a/src/mailman/web/Cgi/roster.py
+++ /dev/null
@@ -1,130 +0,0 @@
-# Copyright (C) 1998-2009 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see '
-
-# Set up i18n
-_ = i18n._
-i18n.set_language(config.DEFAULT_SERVER_LANGUAGE)
-
-log = logging.getLogger('mailman.error')
-mlog = logging.getLogger('mailman.mischief')
-
-
-
-def main():
- doc = Document()
- doc.set_language(config.DEFAULT_SERVER_LANGUAGE)
-
- parts = Utils.GetPathPieces()
- if not parts:
- doc.AddItem(Header(2, _("Error")))
- doc.AddItem(Bold(_('Invalid options to CGI script')))
- print doc.Format()
- return
-
- listname = parts[0].lower()
- try:
- mlist = MailList.MailList(listname, lock=0)
- except Errors.MMListError, e:
- # Avoid cross-site scripting attacks
- safelistname = Utils.websafe(listname)
- doc.AddItem(Header(2, _("Error")))
- doc.AddItem(Bold(_('No such list %(safelistname)s')))
- print doc.Format()
- log.error('No such list "%s": %s\n', listname, e)
- return
-
- # See if the form data has a preferred language set, in which case, use it
- # for the results. If not, use the list's preferred language.
- cgidata = cgi.FieldStorage()
- language = cgidata.getvalue('language')
- if language not in config.languages.enabled_codes:
- language = mlist.preferred_language
- i18n.set_language(language)
- doc.set_language(language)
-
- mlist.Lock()
- try:
- process_form(mlist, doc, cgidata, language)
- mlist.Save()
- finally:
- mlist.Unlock()
-
-
-
-def process_form(mlist, doc, cgidata, lang):
- listowner = mlist.GetOwnerEmail()
- realname = mlist.real_name
- results = []
-
- # The email address being subscribed, required
- email = cgidata.getvalue('email', '')
- if not email:
- results.append(_('You must supply a valid email address.'))
-
- fullname = cgidata.getvalue('fullname', '')
- # Canonicalize the full name
- fullname = Utils.canonstr(fullname, lang)
- # Who was doing the subscribing?
- remote = os.environ.get('REMOTE_HOST',
- os.environ.get('REMOTE_ADDR',
- 'unidentified origin'))
- # Was an attempt made to subscribe the list to itself?
- if email == mlist.GetListEmail():
- mlog.error('Attempt to self subscribe %s: %s', email, remote)
- results.append(_('You may not subscribe a list to itself!'))
- # If the user did not supply a password, generate one for him
- password = cgidata.getvalue('pw')
- confirmed = cgidata.getvalue('pw-conf')
-
- if password is None and confirmed is None:
- password = Utils.MakeRandomPassword()
- elif password is None or confirmed is None:
- results.append(_('If you supply a password, you must confirm it.'))
- elif password <> confirmed:
- results.append(_('Your passwords did not match.'))
-
- # Get the digest option for the subscription.
- digestflag = cgidata.getvalue('digest')
- if digestflag:
- try:
- digest = int(digestflag)
- except ValueError:
- digest = 0
- else:
- digest = mlist.digest_is_default
-
- # Sanity check based on list configuration. BAW: It's actually bogus that
- # the page allows you to set the digest flag if you don't really get the
- # choice. :/
- if not mlist.digestable:
- digest = 0
- elif not mlist.nondigestable:
- digest = 1
-
- if results:
- print_results(mlist, ERRORSEP.join(results), doc, lang)
- return
-
- # If this list has private rosters, we have to be careful about the
- # message that gets printed, otherwise the subscription process can be
- # used to mine for list members. It may be inefficient, but it's still
- # possible, and that kind of defeats the purpose of private rosters.
- # We'll use this string for all successful or unsuccessful subscription
- # results.
- if mlist.private_roster == 0:
- # Public rosters
- privacy_results = ''
- else:
- privacy_results = _("""\
-Your subscription request has been received, and will soon be acted upon.
-Depending on the configuration of this mailing list, your subscription request
-may have to be first confirmed by you via email, or approved by the list
-moderator. If confirmation is required, you will soon get a confirmation
-email which contains further instructions.""")
-
- try:
- userdesc = UserDesc(email, fullname, password, digest, lang)
- mlist.AddMember(userdesc, remote)
- results = ''
- # Check for all the errors that mlist.AddMember can throw options on the
- # web page for this cgi
- except Errors.MembershipIsBanned:
- results = _("""The email address you supplied is banned from this
- mailing list. If you think this restriction is erroneous, please
- contact the list owners at %(listowner)s.""")
- except Errors.InvalidEmailAddress:
- results = _('The email address you supplied is not valid.')
- except Errors.MMSubscribeNeedsConfirmation:
- # Results string depends on whether we have private rosters or not
- if privacy_results:
- results = privacy_results
- else:
- results = _("""\
-Confirmation from your email address is required, to prevent anyone from
-subscribing you without permission. Instructions are being sent to you at
-%(email)s. Please note your subscription will not start until you confirm
-your subscription.""")
- except Errors.MMNeedApproval, x:
- # Results string depends on whether we have private rosters or not
- if privacy_results:
- results = privacy_results
- else:
- # We need to interpolate into x
- x = _(x)
- results = _("""\
-Your subscription request was deferred because %(x)s. Your request has been
-forwarded to the list moderator. You will receive email informing you of the
-moderator's decision when they get to your request.""")
- except Errors.MMAlreadyAMember:
- # Results string depends on whether we have private rosters or not
- if not privacy_results:
- results = _('You are already subscribed.')
- else:
- results = privacy_results
- # This could be a membership probe. For safety, let the user know
- # a probe occurred. BAW: should we inform the list moderator?
- listaddr = mlist.GetListEmail()
- # Set the language for this email message to the member's language.
- mlang = mlist.getMemberLanguage(email)
- with i18n.using_language(mlang):
- msg = Message.UserNotification(
- mlist.getMemberCPAddress(email),
- mlist.GetBouncesEmail(),
- _('Mailman privacy alert'),
- _("""\
-An attempt was made to subscribe your address to the mailing list
-%(listaddr)s. You are already subscribed to this mailing list.
-
-Note that the list membership is not public, so it is possible that a bad
-person was trying to probe the list for its membership. This would be a
-privacy violation if we let them do this, but we didn't.
-
-If you submitted the subscription request and forgot that you were already
-subscribed to the list, then you can ignore this message. If you suspect that
-an attempt is being made to covertly discover whether you are a member of this
-list, and you are worried about your privacy, then feel free to send a message
-to the list administrator at %(listowner)s.
-"""), lang=mlang)
- msg.send(mlist)
- # These shouldn't happen unless someone's tampering with the form
- except Errors.MMCantDigestError:
- results = _('This list does not support digest delivery.')
- except Errors.MMMustDigestError:
- results = _('This list only supports digest delivery.')
- else:
- # Everything's cool. Our return string actually depends on whether
- # this list has private rosters or not
- if privacy_results:
- results = privacy_results
- else:
- results = _("""\
-You have been successfully subscribed to the %(realname)s mailing list.""")
- # Show the results
- print_results(mlist, results, doc, lang)
-
-
-
-def print_results(mlist, results, doc, lang):
- # The bulk of the document will come from the options.html template, which
- # includes its own html armor (head tags, etc.). Suppress the head that
- # Document() derived pages get automatically.
- doc.suppress_head = 1
-
- replacements = mlist.GetStandardReplacements(lang)
- replacements[' If you would like to help us identify the problem,
-please email a copy of this page to the webmaster for this site with
-a description of what happened. Thanks!
-
- Please inform the webmaster for this site of this
-problem. Printing of traceback and other system information has been
-explicitly inhibited, but the webmaster can find this information in the
-Mailman error logs.'''
- return htfp.getvalue()
-
-
-
-def print_environment(log=None):
- try:
- import os
- except ImportError:
- os = None
-
- if log:
- outfp = StringIO()
-
- # Write some information about our Python executable to the log file.
- print >> outfp, '[----- Python Information -----]'
- print >> outfp, 'sys.version =', sys.version
- print >> outfp, 'sys.executable =', sys.executable
- print >> outfp, 'sys.prefix =', sys.prefix
- print >> outfp, 'sys.exec_prefix =', sys.exec_prefix
- print >> outfp, 'sys.path =', sys.exec_prefix
- print >> outfp, 'sys.platform =', sys.platform
-
- # Write the same information to the HTML sink.
- htfp = StringIO()
- if not STEALTH_MODE:
- print >> htfp, """\
-
-
-In the text fields below, string interpolation is performed with
-the following key/value substitutions:
- For each text field, you can either enter the text directly into the text
-box, or you can specify a file on your local system to upload as the text."""),
-
- ('autorespond_postings', config.Toggle, (_('No'), _('Yes')), 0,
- _('''Should Mailman send an auto-response to mailing list
- posters?''')),
-
- ('autoresponse_postings_text', config.FileUpload,
- (6, WIDTH), 0,
- _('Auto-response text to send to mailing list posters.')),
-
- ('autorespond_admin', config.Toggle, (_('No'), _('Yes')), 0,
- _('''Should Mailman send an auto-response to emails sent to the
- -owner address?''')),
-
- ('autoresponse_admin_text', config.FileUpload,
- (6, WIDTH), 0,
- _('Auto-response text to send to -owner emails.')),
-
- ('autorespond_requests', config.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', config.FileUpload,
- (6, WIDTH), 0,
- _('Auto-response text to send to -request emails.')),
-
- ('autoresponse_graceperiod', config.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/src/mailman/web/Gui/Bounce.py b/src/mailman/web/Gui/Bounce.py
deleted file mode 100644
index a2f6f887a..000000000
--- a/src/mailman/web/Gui/Bounce.py
+++ /dev/null
@@ -1,195 +0,0 @@
-# Copyright (C) 2001-2009 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see When a bounce is received, Mailman tries to extract two pieces
- of information from the message: the address of the member the
- message was intended for, and the severity of the problem causing
- the bounce. The severity can be either hard or
- soft meaning either a fatal error occurred, or a
- transient error occurred. When in doubt, a hard severity is used.
-
- If no member address can be extracted from the bounce, then the
- bounce is usually discarded. Otherwise, each member is assigned a
- bounce score and every time we encounter a bounce from
- this member we increment the score. Hard bounces increment by 1
- while soft bounces increment by 0.5. We only increment the bounce
- score once per day, so even if we receive ten hard bounces from a
- member per day, their score will increase by only 1 for that day.
-
- When a member's bounce score is greater than the
- bounce score
- threshold, the subscription is disabled. Once disabled, the
- member will not receive any postings from the list until their
- membership is explicitly re-enabled (either by the list
- administrator or the user). However, they will receive occasional
- reminders that their membership has been disabled, and these
- reminders will include information about how to re-enable their
- membership.
-
- You can control both the
- number
- of reminders the member will receive and the
- frequency with which these reminders are sent.
-
- There is one other important configuration variable; after a
- certain period of time -- during which no bounces from the member
- are received -- the bounce information is
- considered
- stale and discarded. Thus by adjusting this value, and the
- score threshold, you can control how quickly bouncing members are
- disabled. You should tune both of these to the frequency and
- traffic volume of your list."""),
-
- _('Bounce detection sensitivity'),
-
- ('bounce_processing', config.Toggle, (_('No'), _('Yes')), 0,
- _('Should Mailman perform automatic bounce processing?'),
- _("""By setting this value to No, you disable all
- automatic bounce processing for this list, however bounce
- messages will still be discarded so that the list administrator
- isn't inundated with them.""")),
-
- ('bounce_score_threshold', config.Number, 5, 0,
- _("""The maximum member bounce score before the member's
- subscription is disabled. This value can be a floating point
- number."""),
- _("""Each subscriber is assigned a bounce score, as a floating
- point number. Whenever Mailman receives a bounce from a list
- member, that member's score is incremented. Hard bounces (fatal
- errors) increase the score by 1, while soft bounces (temporary
- errors) increase the score by 0.5. Only one bounce per day
- counts against a member's score, so even if 10 bounces are
- received for a member on the same day, their score will increase
- by just 1.
-
- This variable describes the upper limit for a member's bounce
- score, above which they are automatically disabled, but not
- removed from the mailing list.""")),
-
- ('bounce_info_stale_after', config.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', config.Number, 5, 0,
- _("""How many Your Membership Is Disabled warnings a
- disabled member should get before their address is removed from
- the mailing list. Set to 0 to immediately remove an address from
- the list once their bounce score exceeds the threshold. This
- value must be an integer.""")),
-
- ('bounce_you_are_disabled_warnings_interval', config.Number, 5, 0,
- _("""The number of days between sending the Your Membership
- Is Disabled warnings. This value must be an integer.""")),
-
- _('Notifications'),
-
- ('bounce_unrecognized_goes_to_list_owner', config.Toggle,
- (_('No'), _('Yes')), 0,
- _('''Should Mailman send you, the list owner, any bounce messages
- that failed to be detected by the bounce processor? Yes
- is recommended.'''),
- _("""While Mailman's bounce detector is fairly robust, it's
- impossible to detect every bounce format in the world. You
- should keep this variable set to Yes for two reasons: 1)
- If this really is a permanent bounce from one of your members,
- you should probably manually remove them from your list, and 2)
- you might want to send the message on to the Mailman developers
- so that this new format can be added to its known set.
-
- If you really can't be bothered, then set this variable to
- No and all non-detected bounces will be discarded
- without further processing.
-
- Note: This setting will also affect all messages sent
- to your list's -admin address. This address is deprecated and
- should never be used, but some people may still send mail to this
- address. If this happens, and this variable is set to
- No those messages too will get discarded. You may want
- to set up an
- autoresponse
- message for email to the -owner and -admin address.""")),
-
- ('bounce_notify_owner_on_disable', config.Toggle,
- (_('No'), _('Yes')), 0,
- _("""Should Mailman notify you, the list owner, when bounces
- cause a member's subscription to be disabled?"""),
- _("""By setting this value to No, you turn off
- notification messages that are normally sent to the list owners
- when a member's delivery is disabled due to excessive bounces.
- An attempt to notify the member will always be made.""")),
-
- ('bounce_notify_owner_on_removal', config.Toggle,
- (_('No'), _('Yes')), 0,
- _("""Should Mailman notify you, the list owner, when bounces
- cause a member to be unsubscribed?"""),
- _("""By setting this value to No, you turn off
- notification messages that are normally sent to the list owners
- when a member is unsubscribed due to excessive bounces. An
- attempt to notify the member will always be made.""")),
-
- ]
-
- def _setValue(self, mlist, property, val, doc):
- # Do value conversion from web representation to internal
- # representation.
- try:
- if property == 'bounce_processing':
- val = int(val)
- elif property == 'bounce_score_threshold':
- val = float(val)
- elif property == 'bounce_info_stale_after':
- val = config.days(int(val))
- elif property == 'bounce_you_are_disabled_warnings':
- val = int(val)
- elif property == 'bounce_you_are_disabled_warnings_interval':
- val = config.days(int(val))
- elif property == 'bounce_notify_owner_on_disable':
- val = int(val)
- elif property == 'bounce_notify_owner_on_removal':
- val = int(val)
- except ValueError:
- doc.addError(
- _("""Bad value for %(property)s: %(val)s"""),
- tag = _('Error: '))
- return
- GUIBase._setValue(self, mlist, property, val, doc)
-
- def getValue(self, mlist, kind, varname, params):
- if varname not in ('bounce_info_stale_after',
- 'bounce_you_are_disabled_warnings_interval'):
- return None
- return int(getattr(mlist, varname) / config.days(1))
diff --git a/src/mailman/web/Gui/ContentFilter.py b/src/mailman/web/Gui/ContentFilter.py
deleted file mode 100644
index 09817f4aa..000000000
--- a/src/mailman/web/Gui/ContentFilter.py
+++ /dev/null
@@ -1,199 +0,0 @@
-# Copyright (C) 2002-2009 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see Content filtering works like this: when a message is
- received by the list and you have enabled content filtering, the
- individual attachments are first compared to the
- filter
- types. If the attachment type matches an entry in the filter
- types, it is discarded.
-
- Then, if there are pass types
- defined, any attachment type that does not match a
- pass type is also discarded. If there are no pass types defined,
- this check is skipped.
-
- After this initial filtering, any multipart
- attachments that are empty are removed. If the outer message is
- left empty after this filtering, then the whole message is
- discarded.
-
- Then, each multipart/alternative section will
- be replaced by just the first alternative that is non-empty after
- filtering if
- collapse_alternatives is enabled.
-
- Finally, any text/html parts that are left in the
- message may be converted to text/plain if
- convert_html_to_plaintext is enabled and the site is
- configured to allow these conversions."""),
-
- ('filter_content', config.Radio, (_('No'), _('Yes')), 0,
- _("""Should Mailman filter the content of list traffic according
- to the settings below?""")),
-
- ('filter_mime_types', config.Text, (10, WIDTH), 0,
- _("""Remove message attachments that have a matching content
- type."""),
-
- _("""Use this option to remove each message attachment that
- matches one of these content types. Each line should contain a
- string naming a MIME type/subtype,
- e.g. image/gif. Leave off the subtype to remove all
- parts with a matching major content type, e.g. image.
-
- Blank lines are ignored.
-
- See also pass_mime_types for a content type whitelist.""")),
-
- ('pass_mime_types', config.Text, (10, WIDTH), 0,
- _("""Remove message attachments that don't have a matching
- content type. Leave this field blank to skip this filter
- test."""),
-
- _("""Use this option to remove each message attachment that does
- not have a matching content type. Requirements and formats are
- exactly like filter_mime_types.
-
- Note: if you add entries to this list but don't add
- multipart to this list, any messages with attachments
- will be rejected by the pass filter.""")),
-
- ('filter_filename_extensions', config.Text, (10, WIDTH), 0,
- _("""Remove message attachments that have a matching filename
- extension."""),),
-
- ('pass_filename_extensions', config.Text, (10, WIDTH), 0,
- _("""Remove message attachments that don't have a matching
- filename extension. Leave this field blank to skip this filter
- test."""),),
-
- ('collapse_alternatives', config.Radio, (_('No'), _('Yes')), 0,
- _("""Should Mailman collapse multipart/alternative to its
- first part content?""")),
-
- ('convert_html_to_plaintext', config.Radio, (_('No'), _('Yes')), 0,
- _("""Should Mailman convert text/html parts to plain
- text? This conversion happens after MIME attachments have been
- stripped.""")),
-
- ('filter_action', config.Radio, tuple(actions), 0,
-
- _("""Action to take when a message matches the content filtering
- rules."""),
-
- _("""One of these actions is take when the message matches one of
- the content filtering rules, meaning, the top-level
- content type matches one of the filter_mime_types, or the top-level content type does
- not match one of the
- pass_mime_types, or if after filtering the subparts of the
- message, the message ends up empty.
-
- Note this action is not taken if after filtering the message
- still contains content. In that case the message is always
- forwarded on to the list membership.
-
- When messages are discarded, a log entry is written
- containing the Message-ID of the discarded message. When
- messages are rejected or forwarded to the list owner, a reason
- for the rejection is included in the bounce message to the
- original author. When messages are preserved, they are saved in
- a special queue directory on disk for the site administrator to
- view (and possibly rescue) but otherwise discarded. This last
- option is only available if enabled by the site
- administrator.""")),
- ]
-
- def _setValue(self, mlist, property, val, doc):
- if property in ('filter_mime_types', 'pass_mime_types'):
- types = []
- for spectype in [s.strip() for s in val.splitlines()]:
- ok = 1
- slashes = spectype.count('/')
- if slashes == 0 and not spectype:
- ok = 0
- elif slashes == 1:
- maintype, subtype = [s.strip().lower()
- for s in spectype.split('/')]
- if not maintype or not subtype:
- ok = 0
- elif slashes > 1:
- ok = 0
- if not ok:
- doc.addError(_('Bad MIME type ignored: %(spectype)s'))
- else:
- types.append(spectype.strip().lower())
- if property == 'filter_mime_types':
- mlist.filter_mime_types = types
- elif property == 'pass_mime_types':
- mlist.pass_mime_types = types
- elif property in ('filter_filename_extensions',
- 'pass_filename_extensions'):
- fexts = []
- for ext in [s.strip() for s in val.splitlines()]:
- fexts.append(ext.lower())
- if property == 'filter_filename_extensions':
- mlist.filter_filename_extensions = fexts
- elif property == 'pass_filename_extensions':
- mlist.pass_filename_extensions = fexts
- 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)
- if property == 'filter_filename_extensions':
- return NL.join(mlist.filter_filename_extensions)
- if property == 'pass_filename_extensions':
- return NL.join(mlist.pass_filename_extensions)
- return None
diff --git a/src/mailman/web/Gui/Digest.py b/src/mailman/web/Gui/Digest.py
deleted file mode 100644
index 821d0684f..000000000
--- a/src/mailman/web/Gui/Digest.py
+++ /dev/null
@@ -1,161 +0,0 @@
-# Copyright (C) 1998-2009 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see However, some lists can benefit from a more personalized
-## approach. In this case, Mailman crafts a new message for
-## each member on the digest delivery list. Turning this on
-## adds a few more expansion variables that can be included in
-## the message header
-## and message footer
-## but it may degrade the performance of your site as
-## a whole.
-
-## You need to carefully consider whether the trade-off is
-## worth it, or whether there are other ways to accomplish what
-## you want. You should also carefully monitor your system load
-## to make sure it is acceptable.
-
-## These additional substitution variables will be available
-## for your headers and footers, when this feature is enabled:
-
-## 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 The list moderators 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 list
- administrators can also tend to pending requests.
-
- In order to split the list ownership duties into
- administrators and moderators, you must
- set a separate moderator password,
- and also provide the email
- addresses of the list moderators. Note that the field you
- are changing here specifies the list administrators.''')),
-
- ('moderator', config.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 list administrators 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.
-
- The list moderators 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 list
- administrators can also tend to pending requests.
-
- In order to split the list ownership duties into
- administrators and moderators, you must
- set a separate moderator password,
- 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', config.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', config.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 except 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', config.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.
- You can also add a sequencial number by %%d substitution
- directive. eg.; [listname %%d] -> [listname 123]
- (listname %%05d) -> (listname 00123)
- """)),
-
- ('anonymous_list', config.Radio, (_('No'), _('Yes')), 0,
- _("""Hide the sender of a message, replacing it with the list
- address (Removes From, Sender and Reply-To fields)""")),
-
- _('''Reply-To: header munging'''),
-
- ('first_strip_reply_to', config.Radio, (_('No'), _('Yes')), 0,
- _('''Should any existing Reply-To: header found in the
- original message be stripped? If so, this will be done
- regardless of whether an explict Reply-To: header is
- added by Mailman or not.''')),
-
- ('reply_goes_to_list', config.Radio,
- (_('Poster'), _('This list'), _('Explicit address')), 0,
- _('''Where are replies to list messages directed?
- Poster is strongly recommended for most mailing
- lists.'''),
-
- # Details for reply_goes_to_list
- _("""This option controls what Mailman does to the
- Reply-To: header in messages flowing through this
- mailing list. When set to Poster, no Reply-To:
- header is added by Mailman, although if one is present in the
- original message, it is not stripped. Setting this value to
- either This list or Explicit address causes
- Mailman to insert a specific Reply-To: header in all
- messages, overriding the header in the original message if
- necessary (Explicit address inserts the value of reply_to_address).
-
- There are many reasons not to introduce or override the
- Reply-To: header. One is that some posters depend on
- their own Reply-To: settings to convey their valid
- return address. Another is that modifying Reply-To:
- makes it much more difficult to send private replies. See `Reply-To'
- Munging Considered Harmful for a general discussion of this
- issue. See Reply-To
- Munging Considered Useful for a dissenting opinion.
-
- 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 Explicit address and set the
- Reply-To: address below to point to the parallel
- list.""")),
-
- ('reply_to_address', config.Email, WIDTH, 0,
- _('Explicit Reply-To: header.'),
- # Details for reply_to_address
- _("""This is the address set in the Reply-To: header
- when the reply_goes_to_list
- option is set to Explicit address.
-
- There are many reasons not to introduce or override the
- Reply-To: header. One is that some posters depend on
- their own Reply-To: settings to convey their valid
- return address. Another is that modifying Reply-To:
- makes it much more difficult to send private replies. See `Reply-To'
- Munging Considered Harmful for a general discussion of this
- issue. See Reply-To
- Munging Considered Useful for a dissenting opinion.
-
- 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 Reply-To: address here. You
- must also specify Explicit address in the
- reply_goes_to_list
- variable.
-
- Note that if the original message contains a
- Reply-To: header, it will not be changed.""")),
-
- _('Umbrella list settings'),
-
- ('umbrella_list', config.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', config.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', config.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', config.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.
-
- Note that this text will be wrapped, according to the
- following rules:
- 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', config.Radio,
- (_('No'), _('Yes')), 0,
- _('Should postings include the List-Post: header?'),
- _("""The List-Post: header is one of the headers
- recommended by
- RFC 2369.
- However for some announce-only 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 List-Post: header is misleading.
- Select No to disable the inclusion of this header. (This
- does not affect the inclusion of the other List-*:
- headers.)"""))
- )
-
- # Discard held messages after this number of days
- rtn.append(
- ('max_days_to_hold', config.Number, 7, 0,
- _("""Discard held messages older than this number of days.
- Use 0 for no automatic discarding."""))
- )
-
- 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(_("""real_name 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 = config.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 _escape(self, property, value):
- # The 'info' property allows HTML, but lets sanitize it to avoid XSS
- # exploits. Everything else should be fully escaped.
- if property <> 'info':
- return GUIBase._escape(self, property, value)
- # Sanitize tags but nothing else. Not the best
- # solution, but expedient.
- return re.sub(r'<([/]?script.*?)>', r'<\1>', value)
-
- 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/src/mailman/web/Gui/Language.py b/src/mailman/web/Gui/Language.py
deleted file mode 100644
index 05824be5e..000000000
--- a/src/mailman/web/Gui/Language.py
+++ /dev/null
@@ -1,128 +0,0 @@
-# Copyright (C) 2001-2009 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see Note however, that if your mailing list receives both encoded
- and unencoded subject headers, you might want to choose As
- needed. 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, prop, val, doc):
- # If we're changing the list's preferred language, change the I18N
- # context as well
- if prop == 'preferred_language':
- i18n.set_language(val)
- doc.set_language(val)
- # Language codes must be wrapped
- if prop == 'available_languages':
- mlist.set_languages(*val)
- else:
- GUIBase._setValue(self, mlist, prop, val, doc)
-
- def getValue(self, mlist, kind, varname, params):
- if varname == 'available_languages':
- # Unwrap Language instances, to return just the code
- return [language.code for language in mlist.available_languages]
- # Returning None tells the infrastructure to use getattr
- return None
diff --git a/src/mailman/web/Gui/Membership.py b/src/mailman/web/Gui/Membership.py
deleted file mode 100644
index bbfdb438b..000000000
--- a/src/mailman/web/Gui/Membership.py
+++ /dev/null
@@ -1,34 +0,0 @@
-# Copyright (C) 2001-2009 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see 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.
-
- Select No to disable personalization and send
- messages to the members in batches. Select Yes to
- personalize deliveries and allow additional substitution
- variables in message headers and footers (see below). In
- addition, by selecting Full Personalization, the
- When personalization is enabled, a few more expansion
- variables that can be included in the message header and
- message footer.
-
- These additional substitution variables will be available
- for your headers and footers, when this feature is enabled:
-
- (*) 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. (*) 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. 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', config.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', config.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', config.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')
- 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.
-
- Member postings are held for moderation if their
- moderation flag is turned on. You can control whether
- member postings are moderated by default or not.
-
- Non-member postings can be automatically
- accepted,
- held for
- moderation,
- rejected (bounced), or
- discarded,
- 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
- general
- non-member rules.
-
- In the text boxes below, add one address per line; start the
- line with a ^ character to designate a Python regular expression. When entering backslashes, do so
- as if you were using Python raw strings (i.e. you generally just
- use a single backslash).
-
- Note that non-regexp matches are always done first."""),
-
- _('Member filters'),
-
- ('default_member_moderation', config.Radio, (_('No'), _('Yes')),
- 0, _('By default, should new list member postings be moderated?'),
-
- _("""Each list member has a moderation flag 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.
-
- 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
- membership management
- screens.""")),
-
- ('member_moderation_action', config.Radio,
- (_('Hold'), _('Reject'), _('Discard')), 0,
- _("""Action to take when a moderated member posts to the
- list."""),
- _(""" Add member addresses one per line; start the line with a ^
- character to designate a regular expression match.""")),
-
- ('discard_these_nonmembers', config.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 receive copies of auto-discarded messages..
-
- Add member addresses one per line; start the line with a ^
- character to designate a regular expression match.""")),
-
- ('generic_nonmember_action', config.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
- accepted,
- held,
- rejected (bounced), and
- discarded addresses. If no match is found, then this action
- is taken.""")),
-
- ('forward_auto_discards', config.Radio, (_('No'), _('Yes')), 0,
- _("""Should messages from non-members, which are automatically
- discarded, be forwarded to the list moderator?""")),
-
- ('nonmember_rejection_notice', config.Text, (10, WIDTH), 1,
- _("""Text to include in any rejection notice to be sent to
- non-members who post to this list. This notice can include
- the list's owner address by %%(listowner)s and replaces the
- internally crafted default message.""")),
-
- ]
-
- recip_rtn = [
- _("""This section allows you to configure various filters based on
- the recipient of the message."""),
-
- _('Recipient filters'),
-
- ('require_explicit_destination', config.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.
-
- The cost is that the list will not accept unhindered any
- postings relayed from other addresses, unless
-
- 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.
-
- 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', config.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.
- """),
-
- _('Header filters'),
-
- ('header_filter_rules', config.HeaderFilter, 0, 0,
- _('Filter rules to match against the headers of a message.'),
-
- _("""Each header filter rule has two parts, a list of regular
- expressions, one per line, and an action to take. Mailman
- matches the message's headers against every regular expression in
- the rule and if any match, the message is rejected, held, or
- discarded based on the action you specify. Use Defer to
- temporarily disable a rule.
-
- You can have more than one filter rule for your list. In that
- case, each rule is matched in turn, with processing stopped after
- the first match.
-
- Note that headers are collected from all the attachments
- (except for the mailman administrivia message) and
- matched against the regular expressions. With this feature,
- you can effectively sort out messages with dangerous file
- types or file name extensions.""")),
-
- _('Legacy anti-spam filters'),
-
- ('bounce_matching_headers', config.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.
-
- For example: 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):
- # Ignore any hdrfilter_* form variables
- if property.startswith('hdrfilter_'):
- return
- # 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 config.ALLOW_OPEN_SUBSCRIBE:
- val += 1
- setattr(mlist, property, val)
-
- # We need to handle the header_filter_rules widgets specially, but
- # everything else can be done by the base class's handleForm() method.
- # However, to do this we need an awful hack. _setValue() and
- # _getValidValue() will essentially ignore any hdrfilter_* form variables.
- # TK: we should call this function only in subcat == 'spam'
- def _handleForm(self, mlist, category, subcat, cgidata, doc):
- # TK: If there is no hdrfilter_* in cgidata, we should not touch
- # the header filter rules.
- if not cgidata.has_key('hdrfilter_rebox_01'):
- return
- # First deal with
- rules = []
- # We start i at 1 and keep going until we no longer find items keyed
- # with the marked tags.
- i = 1
- downi = None
- while True:
- deltag = 'hdrfilter_delete_%02d' % i
- reboxtag = 'hdrfilter_rebox_%02d' % i
- actiontag = 'hdrfilter_action_%02d' % i
- wheretag = 'hdrfilter_where_%02d' % i
- addtag = 'hdrfilter_add_%02d' % i
- newtag = 'hdrfilter_new_%02d' % i
- uptag = 'hdrfilter_up_%02d' % i
- downtag = 'hdrfilter_down_%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
- pattern = cgidata.getvalue(reboxtag)
- try:
- action = int(cgidata.getvalue(actiontag))
- # We'll get a TypeError when the actiontag is missing and the
- # .getvalue() call returns None.
- except (ValueError, TypeError):
- action = config.DEFER
- if pattern is None:
- # We came to the end of the boxes
- break
- if cgidata.has_key(newtag) and not pattern:
- # This new entry is incomplete.
- if i == 2:
- # OK it is the first.
- continue
- doc.addError(_("""Header filter rules require a pattern.
- Incomplete filter rules will be ignored."""))
- continue
- # Make sure the pattern was a legal regular expression
- try:
- re.compile(pattern)
- except (re.error, TypeError):
- safepattern = Utils.websafe(pattern)
- doc.addError(_("""The header filter rule pattern
- '%(safepattern)s' is not a legal regular expression. This
- rule will be ignored."""))
- 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 rule box before the current one
- rules.append(('', config.DEFER, True))
- rules.append((pattern, action, False))
- # Default is to add it after...
- else:
- rules.append((pattern, action, False))
- rules.append(('', config.DEFER, True))
- # Was this an up movement?
- elif cgidata.has_key(uptag):
- # As long as this one isn't the first rule, move it up
- if rules:
- rules.insert(-1, (pattern, action, False))
- else:
- rules.append((pattern, action, False))
- # Was this the down movement?
- elif cgidata.has_key(downtag):
- downi = i - 2
- rules.append((pattern, action, False))
- # Otherwise, just retain this one in the list
- else:
- rules.append((pattern, action, False))
- # Move any down button filter rule
- if downi is not None:
- rule = rules[downi]
- del rules[downi]
- rules.insert(downi+1, rule)
- mlist.header_filter_rules = rules
-
- def handleForm(self, mlist, category, subcat, cgidata, doc):
- if subcat == 'spam':
- self._handleForm(mlist, category, subcat, cgidata, doc)
- # Everything else is dealt with by the base handler
- GUIBase.handleForm(self, mlist, category, subcat, cgidata, doc)
diff --git a/src/mailman/web/Gui/Topics.py b/src/mailman/web/Gui/Topics.py
deleted file mode 100644
index 00df988be..000000000
--- a/src/mailman/web/Gui/Topics.py
+++ /dev/null
@@ -1,162 +0,0 @@
-# Copyright (C) 2001-2009 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see Note that this feature only works with regular delivery, not
- digest delivery.
-
- The body of the message can also be optionally scanned for
- If the newsgroup is moderated, you can set this mailing list
- up to be the moderation address for the newsgroup. By selecting
- Moderated, 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.
-
- Note that if the message has an Approved 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.
-
- Finally, if the newsgroup is moderated, but you want to have
- an open posting policy anyway, you should select Open list,
- moderated group. The effect of this is to use the normal
- Mailman moderation facilities, but to add an Approved
- header to all messages that are gatewayed to Usenet.""")),
-
- ('news_prefix_subject_too', config.Toggle, (_('No'), _('Yes')), 0,
- _('Prefix Subject: headers on postings gated to news?'),
- _("""Mailman prefixes Subject: headers with
- text you can
- customize and normally, this prefix shows up in messages
- gatewayed to Usenet. You can set this option to No to
- disable the prefix on gated messages. Of course, if you turn off
- normal Subject: prefixes, they won't be prefixed for
- gated messages either.""")),
-
- _('Mass catch up'),
-
- ('_mass_catchup', config.Toggle, (_('No'), _('Yes')), 0,
- _('Should Mailman perform a catchup 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
- read. 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
- news server field and
- the linked
- newsgroup fields are filled in."""))
- # And reset these values
- mlist.gateway_to_news = 0
- mlist.gateway_to_mail = 0
diff --git a/src/mailman/web/Gui/__init__.py b/src/mailman/web/Gui/__init__.py
deleted file mode 100644
index 2e12526c0..000000000
--- a/src/mailman/web/Gui/__init__.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright (C) 2001-2009 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see ', MailmanLogo()))).Format()
-
- def FormatUsers(self, digest, lang=None):
- if lang is None:
- lang = self.preferred_language
- conceal_sub = Defaults.ConcealSubscription
- people = []
- if digest:
- digestmembers = self.getDigestMemberKeys()
- for dm in digestmembers:
- if not self.getMemberOption(dm, conceal_sub):
- people.append(dm)
- num_concealed = len(digestmembers) - len(people)
- else:
- members = self.getRegularMemberKeys()
- for m in members:
- if not self.getMemberOption(m, conceal_sub):
- people.append(m)
- num_concealed = len(members) - len(people)
- if num_concealed == 1:
- concealed = _('(1 private member not shown)')
- elif num_concealed > 1:
- concealed = _(
- '(%(num_concealed)d private members not shown)')
- else:
- concealed = ''
- items = []
- people.sort()
- obscure = self.obscure_addresses
- for person in people:
- id = Utils.ObscureEmail(person)
- url = self.GetOptionsURL(person, obscure=obscure)
- if obscure:
- showing = Utils.ObscureEmail(person, for_text=1)
- else:
- showing = person
- got = Link(url, showing)
- if self.getDeliveryStatus(person) <> MemberAdaptor.ENABLED:
- got = Italic('(', got, ')')
- items.append(got)
- # Just return the .Format() so this works until I finish
- # converting everything to htmlformat...
- return concealed + UnorderedList(*tuple(items)).Format()
-
- def FormatOptionButton(self, option, value, user):
- if option == Defaults.DisableDelivery:
- optval = self.getDeliveryStatus(user) <> MemberAdaptor.ENABLED
- else:
- optval = self.getMemberOption(user, option)
- if optval == value:
- checked = ' CHECKED'
- else:
- checked = ''
- name = {
- Defaults.DontReceiveOwnPosts : 'dontreceive',
- Defaults.DisableDelivery : 'disablemail',
- Defaults.DisableMime : 'mime',
- Defaults.AcknowledgePosts : 'ackposts',
- Defaults.Digests : 'digest',
- Defaults.ConcealSubscription : 'conceal',
- Defaults.SuppressPasswordReminder : 'remind',
- Defaults.ReceiveNonmatchingTopics : 'rcvtopic',
- Defaults.DontReceiveDuplicates : 'nodupes',
- }[option]
- return '' % (
- name, value, checked)
-
- def FormatDigestButton(self):
- if self.digest_is_default:
- checked = ' CHECKED'
- else:
- checked = ''
- return '' % checked
-
- def FormatDisabledNotice(self, user):
- status = self.getDeliveryStatus(user)
- reason = None
- info = self.getBounceInfo(user)
- if status == MemberAdaptor.BYUSER:
- reason = _('; it was disabled by you')
- elif status == MemberAdaptor.BYADMIN:
- reason = _('; it was disabled by the list administrator')
- elif status == MemberAdaptor.BYBOUNCE:
- date = time.strftime('%d-%b-%Y',
- time.localtime(Utils.midnight(info.date)))
- reason = _('''; it was disabled due to excessive bounces. The
- last bounce was received on %(date)s''')
- elif status == MemberAdaptor.UNKNOWN:
- reason = _('; it was disabled for unknown reasons')
- if reason:
- note = FontSize('+1', _(
- 'Note: your list delivery is currently disabled%(reason)s.'
- )).Format()
- link = Link('#disable', _('Mail delivery')).Format()
- mailto = Link('mailto:' + self.GetOwnerEmail(),
- _('the list administrator')).Format()
- return _(''' %(note)s
-
- You may have disabled list delivery intentionally,
- or it may have been triggered by bounces from your email
- address. In either case, to re-enable delivery, change the
- %(link)s option below. Contact %(mailto)s if you have any
- questions or need assistance.''')
- elif info and info.score > 0:
- # Provide information about their current bounce score. We know
- # their membership is currently enabled.
- score = info.score
- total = self.bounce_score_threshold
- return _(''' We have received some recent bounces from your
- address. Your current bounce score is %(score)s out of a
- maximum of %(total)s. Please double check that your subscribed
- address is correct and that there are no problems with delivery to
- this address. Your bounce score will be automatically reset if
- the problems are corrected soon.''')
- else:
- return ''
-
- def FormatUmbrellaNotice(self, user, type):
- addr = self.GetMemberAdminEmail(user)
- if self.umbrella_list:
- return _("(Note - you are subscribing to a list of mailing lists, "
- "so the %(type)s notice will be sent to the admin address"
- " for your membership, %(addr)s.) ")
- else:
- return ""
-
- def FormatSubscriptionMsg(self):
- msg = ''
- also = ''
- if self.subscribe_policy == 1:
- msg += _('''You will be sent email requesting confirmation, to
- prevent others from gratuitously subscribing you.''')
- elif self.subscribe_policy == 2:
- msg += _("""This is a closed list, which means your subscription
- will be held for approval. You will be notified of the list
- moderator's decision by email.""")
- also = _('also ')
- elif self.subscribe_policy == 3:
- msg += _("""You will be sent email requesting confirmation, to
- prevent others from gratuitously subscribing you. Once
- confirmation is received, your request will be held for approval
- by the list moderator. You will be notified of the moderator's
- decision by email.""")
- also = _("also ")
- if msg:
- msg += ' '
- if self.private_roster == 1:
- msg += _('''This is %(also)sa private list, which means that the
- list of members is not available to non-members.''')
- elif self.private_roster:
- msg += _('''This is %(also)sa hidden list, which means that the
- list of members is available only to the list administrator.''')
- else:
- msg += _('''This is %(also)sa public list, which means that the
- list of members list is available to everyone.''')
- if self.obscure_addresses:
- msg += _(''' (but we obscure the addresses so they are not
- easily recognizable by spammers).''')
-
- if self.umbrella_list:
- sfx = self.umbrella_member_suffix
- msg += _(""" (Note that this is an umbrella list, intended to
- have only other mailing lists as members. Among other things,
- this means that your confirmation request will be sent to the
- `%(sfx)s' account for your address.)""")
- return msg
-
- def FormatUndigestButton(self):
- if self.digest_is_default:
- checked = ''
- else:
- checked = ' CHECKED'
- return '' % checked
-
- def FormatMimeDigestsButton(self):
- if self.mime_is_default_digest:
- checked = ' CHECKED'
- else:
- checked = ''
- return '' % checked
-
- def FormatPlainDigestsButton(self):
- if self.mime_is_default_digest:
- checked = ''
- else:
- checked = ' CHECKED'
- return '' % checked
-
- def FormatEditingOption(self, lang):
- if self.private_roster == 0:
- either = _('either ')
- else:
- either = ''
- realname = self.real_name
-
- text = (_('''To unsubscribe from %(realname)s, get a password reminder,
- or change your subscription options %(either)senter your subscription
- email address:
- ... or select your entry from
- the subscribers list (see above).''')
- text += _(''' If you leave the field blank, you will be prompted for
- your email address''')
- return text
-
- def RestrictedListMessage(self, which, restriction):
- if not restriction:
- return ''
- elif restriction == 1:
- return _(
- '''(%(which)s is only available to the list
- members.)''')
- else:
- return _('''(%(which)s is only available to the list
- administrator.)''')
-
- def FormatRosterOptionForUser(self, lang):
- return self.RosterOption(lang).Format()
-
- def RosterOption(self, lang):
- container = Container()
- container.AddItem(Hidden('language', lang))
- if not self.private_roster:
- container.AddItem(_("Click here for the list of ")
- + self.real_name
- + _(" subscribers: "))
- container.AddItem(SubmitButton('SubscriberRoster',
- _("Visit Subscriber list")))
- else:
- if self.private_roster == 1:
- only = _('members')
- whom = _('Address:')
- else:
- only = _('the list administrator')
- whom = _('Admin address:')
- # Solicit the user and password.
- container.AddItem(
- self.RestrictedListMessage(_('The subscribers list'),
- self.private_roster)
- + _(" Enter your ")
- + whom[:-1].lower()
- + _(" and password to visit"
- " the subscribers list: There currently are no publicly-advertised %(mailmanlink)s
+ mailing lists on %(hostname)s.'''),
+ ])
+ else:
+ welcome.extend([
+ greeting,
+ _(''' Below is the collection of publicly-advertised
+ %(mailmanlink)s mailing lists on %(hostname)s. Click on a list
+ name to visit the configuration pages for that list.'''),
+ ])
+
+ creatorurl = Utils.ScriptURL('create')
+ mailman_owner = Utils.get_site_noreply()
+ extra = msg and _('right ') or ''
+ welcome.extend([
+ _('''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. If you have the proper authority,
+ you can also create a new mailing list.
+
+ General list information can be found at '''),
+ Link(Utils.ScriptURL('listinfo'),
+ _('the mailing list overview page')),
+ '.',
+ _(' (Send questions and comments to '),
+ Link('mailto:%s' % mailman_owner, mailman_owner),
+ '.) ',
+ ])
+
+ table.AddRow([Container(*welcome)])
+ table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 0, colspan=2)
+
+ if advertised:
+ table.AddRow([' ', ' '])
+ table.AddRow([Bold(FontAttr(_('List'), size='+2')),
+ Bold(FontAttr(_('Description'), size='+2'))
+ ])
+ highlight = 1
+ for url, real_name, description in advertised:
+ table.AddRow(
+ [Link(url, Bold(real_name)),
+ description or Italic(_('[no description available]'))])
+ if highlight and config.WEB_HIGHLIGHT_COLOR:
+ table.AddRowInfo(table.GetCurrentRowIndex(),
+ bgcolor=config.WEB_HIGHLIGHT_COLOR)
+ highlight = not highlight
+
+ doc.AddItem(table)
+ doc.AddItem(' " % (varname, category, description))
+ if elaboration:
+ doc.AddItem("%s " % elaboration)
+
+ if subcat:
+ url = '%s/%s/%s' % (mlist.GetScriptURL('admin'), category, subcat)
+ else:
+ url = '%s/%s' % (mlist.GetScriptURL('admin'), category)
+ form = Form(url)
+ valtab = Table(cellspacing=3, cellpadding=4, width='100%')
+ add_options_table_item(mlist, category, subcat, valtab, item, detailsp=0)
+ form.AddItem(valtab)
+ form.AddItem(' ')
+ form.AddItem(Center(submit_button()))
+ 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
+ """))
+
+ adminurl = mlist.GetScriptURL('admin')
+ if subcat:
+ url = '%s/%s/%s' % (adminurl, category, subcat)
+ else:
+ url = '%s/%s' % (adminurl, category)
+ categoryname = mlist.GetConfigCategories()[category][0]
+ doc.AddItem(Link(url, _('return to the %(categoryname)s options page.')))
+ doc.AddItem('')
+ doc.AddItem(mlist.GetMailmanFooter())
+ print doc.Format()
+
+
+
+def show_results(mlist, doc, category, subcat, cgidata):
+ # Produce the results page
+ adminurl = mlist.GetScriptURL('admin')
+ categories = mlist.GetConfigCategories()
+ label = _(categories[category][0])
+
+ # Set up the document's headers
+ realname = mlist.real_name
+ doc.SetTitle(_('%(realname)s Administration (%(label)s)'))
+ doc.AddItem(Center(Header(2, _(
+ '%(realname)s mailing list administration ')
+
+ # The members and passwords categories are special in that they aren't
+ # defined in terms of gui elements. Create those pages here.
+ if category == 'members':
+ # Figure out which subcategory we should display
+ subcat = Utils.GetPathPieces()[-1]
+ if subcat not in ('list', 'add', 'remove'):
+ subcat = 'list'
+ # Add member category specific tables
+ form.AddItem(membership_options(mlist, subcat, cgidata, doc, form))
+ form.AddItem(Center(submit_button('setmemberopts_btn')))
+ # In "list" subcategory, we can also search for members
+ if subcat == 'list':
+ form.AddItem(' ')
+ usertable = Table(width="90%", border='2')
+ # If there are more members than allowed by chunksize, then we split the
+ # membership up alphabetically. Otherwise just display them all.
+ chunksz = mlist.admin_member_chunksize
+ # The email addresses had /better/ be ASCII, but might be encoded in the
+ # database as Unicodes.
+ all = [_m.encode() for _m in mlist.getMembers()]
+ all.sort(lambda x, y: cmp(x.lower(), y.lower()))
+ # See if the query has a regular expression
+ regexp = cgidata.getvalue('findmember', '').strip()
+ if regexp:
+ try:
+ cre = re.compile(regexp, re.IGNORECASE)
+ except re.error:
+ doc.addError(_('Bad regular expression: ') + regexp)
+ else:
+ # BAW: There's got to be a more efficient way of doing this!
+ names = [mlist.getMemberName(s) or '' for s in all]
+ all = [a for n, a in zip(names, all)
+ if cre.search(n) or cre.search(a)]
+ chunkindex = None
+ bucket = None
+ actionurl = None
+ if len(all) < chunksz:
+ members = all
+ else:
+ # Split them up alphabetically, and then split the alphabetical
+ # listing by chunks
+ buckets = {}
+ for addr in all:
+ members = buckets.setdefault(addr[0].lower(), [])
+ members.append(addr)
+ # Now figure out which bucket we want
+ bucket = None
+ qs = {}
+ # POST methods, even if their actions have a query string, don't get
+ # put into FieldStorage's keys :-(
+ qsenviron = os.environ.get('QUERY_STRING')
+ if qsenviron:
+ qs = cgi.parse_qs(qsenviron)
+ bucket = qs.get('letter', 'a')[0].lower()
+ if bucket not in digits + lowercase:
+ bucket = None
+ if not bucket or not buckets.has_key(bucket):
+ keys = buckets.keys()
+ keys.sort()
+ bucket = keys[0]
+ members = buckets[bucket]
+ action = adminurl + '/members?letter=%s' % bucket
+ if len(members) <= chunksz:
+ form.set_action(action)
+ else:
+ i, r = divmod(len(members), chunksz)
+ numchunks = i + (not not r * 1)
+ # Now chunk them up
+ chunkindex = 0
+ if qs.has_key('chunk'):
+ try:
+ chunkindex = int(qs['chunk'][0])
+ except ValueError:
+ chunkindex = 0
+ if chunkindex < 0 or chunkindex > numchunks:
+ chunkindex = 0
+ members = members[chunkindex*chunksz:(chunkindex+1)*chunksz]
+ # And set the action URL
+ form.set_action(action + '&chunk=%s' % chunkindex)
+ # So now members holds all the addresses we're going to display
+ allcnt = len(all)
+ if bucket:
+ membercnt = len(members)
+ usertable.AddRow([Center(Italic(_(
+ '%(allcnt)s members total, %(membercnt)s shown')))])
+ else:
+ usertable.AddRow([Center(Italic(_('%(allcnt)s members total')))])
+ usertable.AddCellInfo(usertable.GetCurrentRowIndex(),
+ usertable.GetCurrentCellIndex(),
+ colspan=OPTCOLUMNS,
+ bgcolor=config.WEB_ADMINITEM_COLOR)
+ # Add the alphabetical links
+ if bucket:
+ cells = []
+ for letter in digits + lowercase:
+ if not buckets.get(letter):
+ continue
+ url = adminurl + '/members?letter=%s' % letter
+ if letter == bucket:
+ show = Bold('[%s]' % letter.upper()).Format()
+ else:
+ show = letter.upper()
+ cells.append(Link(url, show).Format())
+ joiner = ' '*2 + '\n'
+ usertable.AddRow([Center(joiner.join(cells))])
+ usertable.AddCellInfo(usertable.GetCurrentRowIndex(),
+ usertable.GetCurrentCellIndex(),
+ colspan=OPTCOLUMNS,
+ bgcolor=config.WEB_ADMINITEM_COLOR)
+ usertable.AddRow([Center(h) for h in (_('unsub'),
+ _('member address ')
+ container.AddItem(
+ Link(adminurl + '/members/list',
+ _('Click here to hide the legend for this table.')))
+ else:
+ container.AddItem(
+ Link(adminurl + '/members/list?legend=yes',
+ _('Click here to include the legend for this table.')))
+ container.AddItem(Center(usertable))
+
+ # There may be additional chunks
+ if chunkindex is not None:
+ buttons = []
+ url = adminurl + '/members?%sletter=%s&' % (addlegend, bucket)
+ footer = _(''' To view more members, click on the appropriate
+ range listed below:''')
+ chunkmembers = buckets[bucket]
+ last = len(chunkmembers)
+ for i in range(numchunks):
+ if i == chunkindex:
+ continue
+ start = chunkmembers[i*chunksz]
+ end = chunkmembers[min((i+1)*chunksz, last)-1]
+ link = Link(url + 'chunk=%d' % i, _('from %(start)s to %(end)s'))
+ buttons.append(link)
+ buttons = UnorderedList(*buttons)
+ container.AddItem(footer + buttons.Format() + ' ')
+ return container
+
+
+
+def mass_subscribe(mlist, container):
+ # MASS SUBSCRIBE
+ GREY = config.WEB_ADMINITEM_COLOR
+ table = Table(width='90%')
+ table.AddRow([
+ Label(_('Subscribe these users now or invite them?')),
+ RadioButtonArray('subscribe_or_invite',
+ (_('Subscribe'), _('Invite')),
+ 0, values=(0, 1))
+ ])
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0, bgcolor=GREY)
+ table.AddCellInfo(table.GetCurrentRowIndex(), 1, bgcolor=GREY)
+ table.AddRow([
+ Label(_('Send welcome messages to new subscribees?')),
+ RadioButtonArray('send_welcome_msg_to_this_batch',
+ (_('No'), _('Yes')),
+ mlist.send_welcome_msg,
+ values=(0, 1))
+ ])
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0, bgcolor=GREY)
+ table.AddCellInfo(table.GetCurrentRowIndex(), 1, bgcolor=GREY)
+ table.AddRow([
+ Label(_('Send notifications of new subscriptions to the list owner?')),
+ RadioButtonArray('send_notifications_to_list_owner',
+ (_('No'), _('Yes')),
+ mlist.admin_notify_mchanges,
+ values=(0,1))
+ ])
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0, bgcolor=GREY)
+ table.AddCellInfo(table.GetCurrentRowIndex(), 1, bgcolor=GREY)
+ table.AddRow([Italic(_('Enter one address per line below...'))])
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2)
+ table.AddRow([Center(TextArea(name='subscribees',
+ rows=10, cols='70%', wrap=None))])
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2)
+ table.AddRow([Italic(Label(_('...or specify a file to upload:'))),
+ FileUpload('subscribees_upload', cols='50')])
+ container.AddItem(Center(table))
+ # Invitation text
+ table.AddRow([' ', ' '])
+ table.AddRow([Italic(_("""Below, enter additional text to be added to the
+ top of your invitation or the subscription notification. Include at least
+ one blank line at the end..."""))])
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2)
+ table.AddRow([Center(TextArea(name='invitation',
+ rows=10, cols='70%', wrap=None))])
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2)
+
+
+
+def mass_remove(mlist, container):
+ # MASS UNSUBSCRIBE
+ GREY = config.WEB_ADMINITEM_COLOR
+ table = Table(width='90%')
+ table.AddRow([
+ Label(_('Send unsubscription acknowledgement to the user?')),
+ RadioButtonArray('send_unsub_ack_to_this_batch',
+ (_('No'), _('Yes')),
+ 0, values=(0, 1))
+ ])
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0, bgcolor=GREY)
+ table.AddCellInfo(table.GetCurrentRowIndex(), 1, bgcolor=GREY)
+ table.AddRow([
+ Label(_('Send notifications to the list owner?')),
+ RadioButtonArray('send_unsub_notifications_to_list_owner',
+ (_('No'), _('Yes')),
+ mlist.admin_notify_mchanges,
+ values=(0, 1))
+ ])
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0, bgcolor=GREY)
+ table.AddCellInfo(table.GetCurrentRowIndex(), 1, bgcolor=GREY)
+ table.AddRow([Italic(_('Enter one address per line below...'))])
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2)
+ table.AddRow([Center(TextArea(name='unsubscribees',
+ rows=10, cols='70%', wrap=None))])
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2)
+ table.AddRow([Italic(Label(_('...or specify a file to upload:'))),
+ FileUpload('unsubscribees_upload', cols='50')])
+ container.AddItem(Center(table))
+
+
+
+def password_inputs(mlist):
+ adminurl = mlist.GetScriptURL('admin')
+ table = Table(cellspacing=3, cellpadding=4)
+ table.AddRow([Center(Header(2, _('Change list ownership passwords')))])
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2,
+ bgcolor=config.WEB_HEADER_COLOR)
+ table.AddRow([_("""\
+The list administrators 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.
+
+ The list moderators 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
+list administrators can also tend to pending requests.
+
+ In order to split the list ownership duties into administrators and
+moderators, you must set a separate moderator password in the fields below,
+and also provide the email addresses of the list moderators in the
+general options section.""")])
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2)
+ # Set up the admin password table on the left
+ atable = Table(border=0, cellspacing=3, cellpadding=4,
+ bgcolor=config.WEB_ADMINPW_COLOR)
+ atable.AddRow([Label(_('Enter new administrator password:')),
+ PasswordBox('newpw', size=20)])
+ atable.AddRow([Label(_('Confirm administrator password:')),
+ PasswordBox('confirmpw', size=20)])
+ # Set up the moderator password table on the right
+ mtable = Table(border=0, cellspacing=3, cellpadding=4,
+ bgcolor=config.WEB_ADMINPW_COLOR)
+ mtable.AddRow([Label(_('Enter new moderator password:')),
+ PasswordBox('newmodpw', size=20)])
+ mtable.AddRow([Label(_('Confirm moderator password:')),
+ PasswordBox('confirmmodpw', size=20)])
+ # Add these tables to the overall password table
+ table.AddRow([atable, mtable])
+ return table
+
+
+
+def submit_button(name='submit'):
+ table = Table(border=0, cellspacing=0, cellpadding=2)
+ table.AddRow([Bold(SubmitButton(name, _('Submit Your Changes')))])
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0, align='middle')
+ return table
+
+
+
+def change_options(mlist, category, subcat, cgidata, doc):
+ def safeint(formvar, defaultval=None):
+ try:
+ return int(cgidata.getvalue(formvar))
+ except (ValueError, TypeError):
+ return defaultval
+ confirmed = 0
+ # Handle changes to the list moderator password. Do this before checking
+ # the new admin password, since the latter will force a reauthentication.
+ new = cgidata.getvalue('newmodpw', '').strip()
+ confirm = cgidata.getvalue('confirmmodpw', '').strip()
+ if new or confirm:
+ if new == confirm:
+ mlist.mod_password = passwords.make_secret(
+ new, config.PASSWORD_SCHEME)
+ # No re-authentication necessary because the moderator's
+ # password doesn't get you into these pages.
+ else:
+ doc.addError(_('Moderator passwords did not match'))
+ # Handle changes to the list administrator password
+ new = cgidata.getvalue('newpw', '').strip()
+ confirm = cgidata.getvalue('confirmpw', '').strip()
+ if new or confirm:
+ if new == confirm:
+ mlist.password = passwords.make_secret(new, config.PASSWORD_SCHEME)
+ # Set new cookie
+ print mlist.MakeCookie(config.AuthListAdmin)
+ else:
+ doc.addError(_('Administrator passwords did not match'))
+ # Give the individual gui item a chance to process the form data
+ categories = mlist.GetConfigCategories()
+ label, gui = categories[category]
+ # BAW: We handle the membership page special... for now.
+ if category <> 'members':
+ gui.handleForm(mlist, category, subcat, cgidata, doc)
+ # mass subscription, removal processing for members category
+ subscribers = ''
+ subscribers += cgidata.getvalue('subscribees', '')
+ subscribers += cgidata.getvalue('subscribees_upload', '')
+ if subscribers:
+ entries = filter(None, [n.strip() for n in subscribers.splitlines()])
+ send_welcome_msg = safeint('send_welcome_msg_to_this_batch',
+ mlist.send_welcome_msg)
+ send_admin_notif = safeint('send_notifications_to_list_owner',
+ mlist.admin_notify_mchanges)
+ # Default is to subscribe
+ subscribe_or_invite = safeint('subscribe_or_invite', 0)
+ invitation = cgidata.getvalue('invitation', '')
+ digest = mlist.digest_is_default
+ if not mlist.digestable:
+ digest = 0
+ if not mlist.nondigestable:
+ digest = 1
+ subscribe_errors = []
+ subscribe_success = []
+ # Now cruise through all the subscribees and do the deed. BAW: we
+ # should limit the number of "Successfully subscribed" status messages
+ # we display. Try uploading a file with 10k names -- it takes a while
+ # to render the status page.
+ for entry in entries:
+ fullname, address = parseaddr(entry)
+ # Canonicalize the full name
+ fullname = Utils.canonstr(fullname, mlist.preferred_language)
+ userdesc = UserDesc(address, fullname,
+ Utils.MakeRandomPassword(),
+ digest, mlist.preferred_language)
+ try:
+ if subscribe_or_invite:
+ if mlist.isMember(address):
+ raise Errors.MMAlreadyAMember
+ else:
+ mlist.InviteNewMember(userdesc, invitation)
+ else:
+ mlist.ApprovedAddMember(userdesc, send_welcome_msg,
+ send_admin_notif, invitation,
+ whence='admin mass sub')
+ except Errors.MMAlreadyAMember:
+ subscribe_errors.append((entry, _('Already a member')))
+ except Errors.InvalidEmailAddress:
+ if userdesc.address == '':
+ subscribe_errors.append((_('<blank line>'),
+ _('Bad/Invalid email address')))
+ else:
+ subscribe_errors.append((entry,
+ _('Bad/Invalid email address')))
+ except Errors.MembershipIsBanned, pattern:
+ subscribe_errors.append(
+ (entry, _('Banned address (matched %(pattern)s)')))
+ else:
+ member = Utils.uncanonstr(formataddr((fullname, address)))
+ subscribe_success.append(Utils.websafe(member))
+ if subscribe_success:
+ if subscribe_or_invite:
+ doc.AddItem(Header(5, _('Successfully invited:')))
+ else:
+ doc.AddItem(Header(5, _('Successfully subscribed:')))
+ doc.AddItem(UnorderedList(*subscribe_success))
+ doc.AddItem(' ')
+ if subscribe_errors:
+ if subscribe_or_invite:
+ doc.AddItem(Header(5, _('Error inviting:')))
+ else:
+ doc.AddItem(Header(5, _('Error subscribing:')))
+ items = ['%s -- %s' % (x0, x1) for x0, x1 in subscribe_errors]
+ doc.AddItem(UnorderedList(*items))
+ doc.AddItem(' ')
+ # Unsubscriptions
+ removals = ''
+ if cgidata.has_key('unsubscribees'):
+ removals += cgidata['unsubscribees'].value
+ if cgidata.has_key('unsubscribees_upload') and \
+ cgidata['unsubscribees_upload'].value:
+ removals += cgidata['unsubscribees_upload'].value
+ if removals:
+ names = filter(None, [n.strip() for n in removals.splitlines()])
+ send_unsub_notifications = int(
+ cgidata['send_unsub_notifications_to_list_owner'].value)
+ userack = int(
+ cgidata['send_unsub_ack_to_this_batch'].value)
+ unsubscribe_errors = []
+ unsubscribe_success = []
+ for addr in names:
+ try:
+ mlist.ApprovedDeleteMember(
+ addr, whence='admin mass unsub',
+ admin_notif=send_unsub_notifications,
+ userack=userack)
+ unsubscribe_success.append(addr)
+ except Errors.NotAMemberError:
+ unsubscribe_errors.append(addr)
+ if unsubscribe_success:
+ doc.AddItem(Header(5, _('Successfully Unsubscribed:')))
+ doc.AddItem(UnorderedList(*unsubscribe_success))
+ doc.AddItem(' ')
+ if unsubscribe_errors:
+ doc.AddItem(Header(3, Bold(FontAttr(
+ _('Cannot unsubscribe non-members:'),
+ color='#ff0000', size='+2')).Format()))
+ doc.AddItem(UnorderedList(*unsubscribe_errors))
+ doc.AddItem(' ')
+ # See if this was a moderation bit operation
+ if cgidata.has_key('allmodbit_btn'):
+ val = cgidata.getvalue('allmodbit_val')
+ try:
+ val = int(val)
+ except VallueError:
+ val = None
+ if val not in (0, 1):
+ doc.addError(_('Bad moderation flag value'))
+ else:
+ for member in mlist.getMembers():
+ mlist.setMemberOption(member, config.Moderate, val)
+ # do the user options for members category
+ if cgidata.has_key('setmemberopts_btn') and cgidata.has_key('user'):
+ user = cgidata['user']
+ if isinstance(user, list):
+ users = []
+ for ui in range(len(user)):
+ users.append(urllib.unquote(user[ui].value))
+ else:
+ users = [urllib.unquote(user.value)]
+ errors = []
+ removes = []
+ for user in users:
+ if cgidata.has_key('%s_unsub' % user):
+ try:
+ mlist.ApprovedDeleteMember(user, whence='member mgt page')
+ removes.append(user)
+ except Errors.NotAMemberError:
+ errors.append((user, _('Not subscribed')))
+ continue
+ if not mlist.isMember(user):
+ doc.addError(_('Ignoring changes to deleted member: %(user)s'),
+ tag=_('Warning: '))
+ continue
+ value = cgidata.has_key('%s_digest' % user)
+ try:
+ mlist.setMemberOption(user, config.Digests, value)
+ except (Errors.AlreadyReceivingDigests,
+ Errors.AlreadyReceivingRegularDeliveries,
+ Errors.CantDigestError,
+ Errors.MustDigestError):
+ # BAW: Hmm...
+ pass
+
+ newname = cgidata.getvalue(user+'_realname', '')
+ newname = Utils.canonstr(newname, mlist.preferred_language)
+ mlist.setMemberName(user, newname)
+
+ newlang = cgidata.getvalue(user+'_language')
+ oldlang = mlist.getMemberLanguage(user)
+ if (newlang not in config.languages.enabled_codes
+ and newlang <> oldlang):
+ # Then
+ mlist.setMemberLanguage(user, newlang)
+
+ moderate = not not cgidata.getvalue(user+'_mod')
+ mlist.setMemberOption(user, config.Moderate, moderate)
+
+ # Set the `nomail' flag, but only if the user isn't already
+ # disabled (otherwise we might change BYUSER into BYADMIN).
+ if cgidata.has_key('%s_nomail' % user):
+ if mlist.getDeliveryStatus(user) == MemberAdaptor.ENABLED:
+ mlist.setDeliveryStatus(user, MemberAdaptor.BYADMIN)
+ else:
+ mlist.setDeliveryStatus(user, MemberAdaptor.ENABLED)
+ for opt in ('hide', 'ack', 'notmetoo', 'nodupes', 'plain'):
+ opt_code = config.OPTINFO[opt]
+ if cgidata.has_key('%s_%s' % (user, opt)):
+ mlist.setMemberOption(user, opt_code, 1)
+ else:
+ mlist.setMemberOption(user, opt_code, 0)
+ # Give some feedback on who's been removed
+ if removes:
+ doc.AddItem(Header(5, _('Successfully Removed:')))
+ doc.AddItem(UnorderedList(*removes))
+ doc.AddItem(' ')
+ if errors:
+ doc.AddItem(Header(5, _("Error Unsubscribing:")))
+ items = ['%s -- %s' % (x[0], x[1]) for x in errors]
+ doc.AddItem(apply(UnorderedList, tuple((items))))
+ doc.AddItem(" ")
diff --git a/src/web/Cgi/admindb.py b/src/web/Cgi/admindb.py
new file mode 100644
index 000000000..5d756fc78
--- /dev/null
+++ b/src/web/Cgi/admindb.py
@@ -0,0 +1,813 @@
+# Copyright (C) 1998-2009 by the Free Software Foundation, Inc.
+#
+# This file is part of GNU Mailman.
+#
+# GNU Mailman 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 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Mailman 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
+# GNU Mailman. If not, see ')
+ # BAW: kludge to remove id from requests.db.
+ try:
+ mlist.HandleRequest(id, config.DISCARD)
+ except Errors.LostHeldMessage:
+ pass
+ return
+ except email.Errors.MessageParseError:
+ form.AddItem(_('Message with id #%(id)d is corrupted.'))
+ # BAW: Should we really delete this, or shuttle it off for site admin
+ # to look more closely at?
+ form.AddItem(' ')
+ # BAW: kludge to remove id from requests.db.
+ try:
+ mlist.HandleRequest(id, config.DISCARD)
+ except Errors.LostHeldMessage:
+ pass
+ return
+ # Get the header text and the message body excerpt
+ lines = []
+ chars = 0
+ # A negative value means, include the entire message regardless of size
+ limit = config.ADMINDB_PAGE_TEXT_LIMIT
+ for line in email.Iterators.body_line_iterator(msg):
+ lines.append(line)
+ chars += len(line)
+ if chars > limit > 0:
+ break
+ # Negative values mean display the entire message, regardless of size
+ if limit > 0:
+ body = EMPTYSTRING.join(lines)[:config.ADMINDB_PAGE_TEXT_LIMIT]
+ else:
+ body = EMPTYSTRING.join(lines)
+ # Get message charset and try encode in list charset
+ mcset = msg.get_param('charset', 'us-ascii').lower()
+ lcset = Utils.GetCharSet(mlist.preferred_language)
+ if mcset <> lcset:
+ try:
+ body = unicode(body, mcset).encode(lcset)
+ except (LookupError, UnicodeError, ValueError):
+ pass
+ hdrtxt = NL.join(['%s: %s' % (k, v) for k, v in msg.items()])
+ hdrtxt = Utils.websafe(hdrtxt)
+ # Okay, we've reconstituted the message just fine. Now for the fun part!
+ t = Table(cellspacing=0, cellpadding=0, width='100%')
+ t.AddRow([Bold(_('From:')), sender])
+ row, col = t.GetCurrentRowIndex(), t.GetCurrentCellIndex()
+ t.AddCellInfo(row, col-1, align='right')
+ t.AddRow([Bold(_('Subject:')),
+ Utils.websafe(Utils.oneline(subject, lcset))])
+ t.AddCellInfo(row+1, col-1, align='right')
+ t.AddRow([Bold(_('Reason:')), _(reason)])
+ t.AddCellInfo(row+2, col-1, align='right')
+ when = msgdata.get('received_time')
+ if when:
+ t.AddRow([Bold(_('Received:')), time.ctime(when)])
+ t.AddCellInfo(row+2, col-1, align='right')
+ # We can't use a RadioButtonArray here because horizontal placement can be
+ # confusing to the user and vertical placement takes up too much
+ # real-estate. This is a hack!
+ buttons = Table(cellspacing="5", cellpadding="0")
+ buttons.AddRow(map(lambda x, s=' '*5: s+x+s,
+ (_('Defer'), _('Approve'), _('Reject'), _('Discard'))))
+ buttons.AddRow([Center(RadioButton(id, config.DEFER, 1)),
+ Center(RadioButton(id, config.APPROVE, 0)),
+ Center(RadioButton(id, config.REJECT, 0)),
+ Center(RadioButton(id, config.DISCARD, 0)),
+ ])
+ t.AddRow([Bold(_('Action:')), buttons])
+ t.AddCellInfo(row+3, col-1, align='right')
+ t.AddRow([' ',
+ CheckBox('preserve-%d' % id, 'on', 0).Format() +
+ ' ' + _('Preserve message for site administrator')
+ ])
+ t.AddRow([' ',
+ CheckBox('forward-%d' % id, 'on', 0).Format() +
+ ' ' + _('Additionally, forward this message to: ') +
+ TextBox('forward-addr-%d' % id, size=47,
+ value=mlist.GetOwnerEmail()).Format()
+ ])
+ notice = msgdata.get('rejection_notice', _('[No explanation given]'))
+ t.AddRow([
+ Bold(_('If you reject this post, ')
+
+
+
+def process_form(mlist, doc, cgidata):
+ senderactions = {}
+ # Sender-centric actions
+ for k in cgidata.keys():
+ for prefix in ('senderaction-', 'senderpreserve-', 'senderforward-',
+ 'senderforwardto-', 'senderfilterp-', 'senderfilter-',
+ 'senderclearmodp-', 'senderbanp-'):
+ if k.startswith(prefix):
+ action = k[:len(prefix)-1]
+ sender = unquote_plus(k[len(prefix):])
+ value = cgidata.getvalue(k)
+ senderactions.setdefault(sender, {})[action] = value
+ # discard-all-defers
+ try:
+ discardalldefersp = cgidata.getvalue('discardalldefersp', 0)
+ except ValueError:
+ discardalldefersp = 0
+ for sender in senderactions.keys():
+ actions = senderactions[sender]
+ # Handle what to do about all this sender's held messages
+ try:
+ action = int(actions.get('senderaction', config.DEFER))
+ except ValueError:
+ action = config.DEFER
+ if action == config.DEFER and discardalldefersp:
+ action = config.DISCARD
+ if action in (config.DEFER, config.APPROVE,
+ config.REJECT, config.DISCARD):
+ preserve = actions.get('senderpreserve', 0)
+ forward = actions.get('senderforward', 0)
+ forwardaddr = actions.get('senderforwardto', '')
+ comment = _('No reason given')
+ bysender = helds_by_sender(mlist)
+ for id in bysender.get(sender, []):
+ try:
+ mlist.HandleRequest(id, action, comment, preserve,
+ forward, forwardaddr)
+ except (KeyError, Errors.LostHeldMessage):
+ # That's okay, it just means someone else has already
+ # updated the database while we were staring at the page,
+ # so just ignore it
+ continue
+ # Now see if this sender should be added to one of the nonmember
+ # sender filters.
+ if actions.get('senderfilterp', 0):
+ try:
+ which = int(actions.get('senderfilter'))
+ except ValueError:
+ # Bogus form
+ which = 'ignore'
+ if which == config.ACCEPT:
+ mlist.accept_these_nonmembers.append(sender)
+ elif which == config.HOLD:
+ mlist.hold_these_nonmembers.append(sender)
+ elif which == config.REJECT:
+ mlist.reject_these_nonmembers.append(sender)
+ elif which == config.DISCARD:
+ mlist.discard_these_nonmembers.append(sender)
+ # Otherwise, it's a bogus form, so ignore it
+ # And now see if we're to clear the member's moderation flag.
+ if actions.get('senderclearmodp', 0):
+ try:
+ mlist.setMemberOption(sender, config.Moderate, 0)
+ except Errors.NotAMemberError:
+ # This person's not a member any more. Oh well.
+ pass
+ # And should this address be banned?
+ if actions.get('senderbanp', 0):
+ if sender not in mlist.ban_list:
+ mlist.ban_list.append(sender)
+ # Now, do message specific actions
+ banaddrs = []
+ erroraddrs = []
+ for k in cgidata.keys():
+ formv = cgidata[k]
+ if isinstance(formv, list):
+ continue
+ try:
+ v = int(formv.value)
+ request_id = int(k)
+ except ValueError:
+ continue
+ if v not in (config.DEFER, config.APPROVE, config.REJECT,
+ config.DISCARD, config.SUBSCRIBE, config.UNSUBSCRIBE,
+ config.ACCEPT, config.HOLD):
+ continue
+ # Get the action comment and reasons if present.
+ commentkey = 'comment-%d' % request_id
+ preservekey = 'preserve-%d' % request_id
+ forwardkey = 'forward-%d' % request_id
+ forwardaddrkey = 'forward-addr-%d' % request_id
+ bankey = 'ban-%d' % request_id
+ # Defaults
+ comment = _('[No reason given]')
+ preserve = 0
+ forward = 0
+ forwardaddr = ''
+ if cgidata.has_key(commentkey):
+ comment = cgidata[commentkey].value
+ if cgidata.has_key(preservekey):
+ preserve = cgidata[preservekey].value
+ if cgidata.has_key(forwardkey):
+ forward = cgidata[forwardkey].value
+ if cgidata.has_key(forwardaddrkey):
+ forwardaddr = cgidata[forwardaddrkey].value
+ # Should we ban this address? Do this check before handling the
+ # request id because that will evict the record.
+ requestsdb = config.db.get_list_requests(mlist)
+ if cgidata.getvalue(bankey):
+ key, data = requestsdb.get_record(request_id)
+ sender = data['sender']
+ if sender not in mlist.ban_list:
+ mlist.ban_list.append(sender)
+ # Handle the request id
+ try:
+ mlist.HandleRequest(request_id, v, comment,
+ preserve, forward, forwardaddr)
+ except (KeyError, Errors.LostHeldMessage):
+ # That's okay, it just means someone else has already updated the
+ # database while we were staring at the page, so just ignore it
+ continue
+ except Errors.MMAlreadyAMember, v:
+ erroraddrs.append(v)
+ except Errors.MembershipIsBanned, pattern:
+ data = requestsdb.get_record(request_id)
+ sender = data['sender']
+ banaddrs.append((sender, pattern))
+ # save the list and print the results
+ doc.AddItem(Header(2, _('Database Updated...')))
+ if erroraddrs:
+ for addr in erroraddrs:
+ doc.AddItem(`addr` + _(' is already a member') + ' Note that confirmation strings expire approximately
+ %(days)s days after the initial subscription request. If your
+ confirmation has expired, please try to re-submit your subscription.
+ Otherwise, re-enter your confirmation
+ string.''')
+
+ content = mlist.pend_confirm(cookie, expunge=False)
+ if content is None:
+ bad_confirmation(doc, badconfirmstr)
+ doc.AddItem(mlist.GetMailmanFooter())
+ print doc.Format()
+ return
+
+ try:
+ if content[0] == Pending.SUBSCRIPTION:
+ if cgidata.getvalue('cancel'):
+ subscription_cancel(mlist, doc, cookie)
+ elif cgidata.getvalue('submit'):
+ subscription_confirm(mlist, doc, cookie, cgidata)
+ else:
+ subscription_prompt(mlist, doc, cookie, content[1])
+ elif content[0] == Pending.UNSUBSCRIPTION:
+ try:
+ if cgidata.getvalue('cancel'):
+ unsubscription_cancel(mlist, doc, cookie)
+ elif cgidata.getvalue('submit'):
+ unsubscription_confirm(mlist, doc, cookie)
+ else:
+ unsubscription_prompt(mlist, doc, cookie, *content[1:])
+ except Errors.NotAMemberError:
+ doc.addError(_("""The address requesting unsubscription is not
+ a member of the mailing list. Perhaps you have already been
+ unsubscribed, e.g. by the list administrator?"""))
+ # Expunge this record from the pending database.
+ expunge(mlist, cookie)
+ elif content[0] == Pending.CHANGE_OF_ADDRESS:
+ if cgidata.getvalue('cancel'):
+ addrchange_cancel(mlist, doc, cookie)
+ elif cgidata.getvalue('submit'):
+ addrchange_confirm(mlist, doc, cookie)
+ else:
+ # Watch out for users who have unsubscribed themselves in the
+ # meantime!
+ try:
+ addrchange_prompt(mlist, doc, cookie, *content[1:])
+ except Errors.NotAMemberError:
+ doc.addError(_("""The address requesting to be changed has
+ been subsequently unsubscribed. This request has been
+ cancelled."""))
+ # Expunge this record from the pending database.
+ expunge(mlist, cookie)
+ elif content[0] == Pending.HELD_MESSAGE:
+ if cgidata.getvalue('cancel'):
+ heldmsg_cancel(mlist, doc, cookie)
+ elif cgidata.getvalue('submit'):
+ heldmsg_confirm(mlist, doc, cookie)
+ else:
+ heldmsg_prompt(mlist, doc, cookie, *content[1:])
+ elif content[0] == Pending.RE_ENABLE:
+ if cgidata.getvalue('cancel'):
+ reenable_cancel(mlist, doc, cookie)
+ elif cgidata.getvalue('submit'):
+ reenable_confirm(mlist, doc, cookie)
+ else:
+ reenable_prompt(mlist, doc, cookie, *content[1:])
+ else:
+ bad_confirmation(doc, _('System error, bad content: %(content)s'))
+ except Errors.MMBadConfirmation:
+ bad_confirmation(doc, badconfirmstr)
+
+ doc.AddItem(mlist.GetMailmanFooter())
+ print doc.Format()
+
+
+
+def bad_confirmation(doc, extra=''):
+ title = _('Bad confirmation string')
+ doc.SetTitle(title)
+ doc.AddItem(Header(3, Bold(FontAttr(title, color='#ff0000', size='+2'))))
+ doc.AddItem(extra)
+
+
+def expunge(mlist, cookie):
+ # Expunge this record from the list's pending database. This requires
+ # that the list lock be acquired, however the list doesn't need to be
+ # saved because this operation doesn't touch the config.pck file.
+ mlist.Lock()
+ try:
+ mlist.pend_confirm(cookie, expunge=True)
+ finally:
+ mlist.Unlock()
+
+
+
+def ask_for_cookie(mlist, doc, extra=''):
+ title = _('Enter confirmation cookie')
+ doc.SetTitle(title)
+ form = Form(mlist.GetScriptURL('confirm'))
+ table = Table(border=0, width='100%')
+ table.AddRow([Center(Bold(FontAttr(title, size='+1')))])
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0,
+ colspan=2, bgcolor=config.WEB_HEADER_COLOR)
+
+ if extra:
+ table.AddRow([Bold(FontAttr(extra, size='+1'))])
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2)
+
+ # Add cookie entry box
+ table.AddRow([_("""Please enter the confirmation string
+ (i.e. cookie) that you received in your email message, in the box
+ below. Then hit the Submit button to proceed to the next
+ confirmation step.""")])
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2)
+ table.AddRow([Label(_('Confirmation string:')),
+ TextBox('cookie')])
+ table.AddRow([Center(SubmitButton('submit_cookie', _('Submit')))])
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2)
+ form.AddItem(table)
+ doc.AddItem(form)
+ doc.AddItem(mlist.GetMailmanFooter())
+ print doc.Format()
+
+
+
+def subscription_prompt(mlist, doc, cookie, userdesc):
+ email = userdesc.address
+ password = userdesc.password
+ digest = userdesc.digest
+ lang = userdesc.language
+ name = Utils.uncanonstr(userdesc.fullname, lang)
+ i18n.set_language(lang)
+ doc.set_language(lang)
+ title = _('Confirm subscription request')
+ doc.SetTitle(title)
+
+ form = Form(mlist.GetScriptURL('confirm'))
+ table = Table(border=0, width='100%')
+ table.AddRow([Center(Bold(FontAttr(title, size='+1')))])
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0,
+ colspan=2, bgcolor=config.WEB_HEADER_COLOR)
+
+ listname = mlist.real_name
+ # This is the normal, no-confirmation required results text.
+ #
+ # We do things this way so we don't have to reformat this paragraph, which
+ # would mess up translations. If you modify this text for other reasons,
+ # please refill the paragraph, and clean up the logic.
+ result = _("""Your confirmation is required in order to complete the
+ subscription request to the mailing list %(listname)s. Your
+ subscription settings are shown below; make any necessary changes and hit
+ Subscribe to complete the confirmation process. Once you've
+ confirmed your subscription request, you will be shown your account
+ options page which you can use to further customize your membership
+ options.
+
+ Note: your password will be emailed to you once your subscription is
+ confirmed. You can change it by visiting your personal options page.
+
+ Or hit Cancel my subscription request if you no longer want to
+ subscribe to this list.""") + ' Note: your password will be emailed to you once your subscription
+ is confirmed. You can change it by visiting your personal options
+ page.
+
+ Or, if you've changed your mind and do not want to subscribe to
+ this mailing list, you can hit Cancel my subscription
+ request.""") + ' You can now
+ proceed to your membership login
+ page.'''))
+ mlist.Save()
+ finally:
+ mlist.Unlock()
+
+
+
+def unsubscription_cancel(mlist, doc, cookie):
+ # Expunge this record from the pending database
+ expunge(mlist, cookie)
+ doc.AddItem(_('You have canceled your unsubscription request.'))
+
+
+
+def unsubscription_confirm(mlist, doc, cookie):
+ # See the comment in admin.py about the need for the signal
+ # handler.
+ def sigterm_handler(signum, frame, mlist=mlist):
+ mlist.Unlock()
+ sys.exit(0)
+
+ mlist.Lock()
+ try:
+ try:
+ # Do this in two steps so we can get the preferred language for
+ # the user who is unsubscribing.
+ op, addr = mlist.pend_confirm(cookie, expunge=False)
+ lang = mlist.getMemberLanguage(addr)
+ i18n.set_language(lang)
+ doc.set_language(lang)
+ op, addr = mlist.ProcessConfirmation(cookie)
+ except Errors.NotAMemberError:
+ bad_confirmation(doc, _('''Invalid confirmation string. It is
+ possible that you are attempting to confirm a request for an
+ address that has already been unsubscribed.'''))
+ else:
+ # The response
+ listname = mlist.real_name
+ title = _('Unsubscription request confirmed')
+ listinfourl = mlist.GetScriptURL('listinfo')
+ doc.SetTitle(title)
+ doc.AddItem(Header(3, Bold(FontAttr(title, size='+2'))))
+ doc.AddItem(_("""\
+ You have successfully unsubscribed from the %(listname)s mailing
+ list. You can now visit the list's main
+ information page."""))
+ mlist.Save()
+ finally:
+ mlist.Unlock()
+
+
+
+def unsubscription_prompt(mlist, doc, cookie, addr):
+ title = _('Confirm unsubscription request')
+ doc.SetTitle(title)
+ lang = mlist.getMemberLanguage(addr)
+ i18n.set_language(lang)
+ doc.set_language(lang)
+
+ form = Form(mlist.GetScriptURL('confirm'))
+ table = Table(border=0, width='100%')
+ table.AddRow([Center(Bold(FontAttr(title, size='+1')))])
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0,
+ colspan=2, bgcolor=config.WEB_HEADER_COLOR)
+
+ listname = mlist.real_name
+ fullname = mlist.getMemberName(addr)
+ if fullname is None:
+ fullname = _('Not available')
+ else:
+ fullname = Utils.uncanonstr(fullname, lang)
+ table.AddRow([_("""Your confirmation is required in order to complete the
+ unsubscription request from the mailing list %(listname)s. You
+ are currently subscribed with
+
+ Or hit Cancel and discard to cancel this unsubscription
+ request.""") + ' Or hit Cancel and discard to cancel this change of address
+ request.""") + ' Or hit the Continue awaiting approval button to continue to
+ allow the list moderator to approve or reject the message.''')
+ + ' You also need to enter the email address of the initial list owner.
+ Once the list is created, the list owner will be given notification, along
+ with the initial list password. The list owner will then be able to
+ modify the password and add or remove additional list owners.
+
+ If you want Mailman to automatically generate the initial list admin
+ password, click on `Yes' in the autogenerate field below, and leave the
+ initial list password fields empty.
+
+ You must have the proper authorization to create new mailing lists.
+ Each site should have a list creator's password, which you can
+ enter in the field at the bottom. Note that the site administrator's
+ password can also be used for authentication.
+ """)])
+ # Build the form for the necessary input
+ GREY = config.WEB_ADMINITEM_COLOR
+ form = Form(Utils.ScriptURL('create'))
+ ftable = Table(border=0, cols='2', width='100%',
+ cellspacing=3, cellpadding=4)
+
+ ftable.AddRow([Center(Italic(_('List Identity')))])
+ ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2)
+
+ safelistname = Utils.websafe(cgidata.getvalue('listname', ''))
+ ftable.AddRow([Label(_('Name of list:')),
+ TextBox('listname', safelistname)])
+ ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY)
+ ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY)
+
+ safeowner = Utils.websafe(cgidata.getvalue('owner', ''))
+ ftable.AddRow([Label(_('Initial list owner address:')),
+ TextBox('owner', safeowner)])
+ ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY)
+ ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY)
+
+ try:
+ autogen = bool(int(cgidata.getvalue('autogen', '0')))
+ except ValueError:
+ autogen = False
+ ftable.AddRow([Label(_('Auto-generate initial list password?')),
+ RadioButtonArray('autogen', (_('No'), _('Yes')),
+ checked=autogen,
+ values=(0, 1))])
+ ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY)
+ ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY)
+
+ safepasswd = Utils.websafe(cgidata.getvalue('password', ''))
+ ftable.AddRow([Label(_('Initial list password:')),
+ PasswordBox('password', safepasswd)])
+ ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY)
+ ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY)
+
+ safeconfirm = Utils.websafe(cgidata.getvalue('confirm', ''))
+ ftable.AddRow([Label(_('Confirm initial password:')),
+ PasswordBox('confirm', safeconfirm)])
+ ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY)
+ ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY)
+
+ try:
+ notify = bool(int(cgidata.getvalue('notify', '1')))
+ except ValueError:
+ notify = True
+ try:
+ moderate = bool(int(cgidata.getvalue('moderate',
+ config.DEFAULT_DEFAULT_MEMBER_MODERATION)))
+ except ValueError:
+ moderate = config.DEFAULT_DEFAULT_MEMBER_MODERATION
+
+ ftable.AddRow([Center(Italic(_('List Characteristics')))])
+ ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2)
+
+ ftable.AddRow([
+ Label(_("""Should new members be quarantined before they
+ are allowed to post unmoderated to this list? Answer Yes to hold
+ new member postings for moderator approval by default.""")),
+ RadioButtonArray('moderate', (_('No'), _('Yes')),
+ checked=moderate,
+ values=(0,1))])
+ ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY)
+ ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY)
+ # Create the table of initially supported languages, sorted on the long
+ # name of the language.
+ revmap = {}
+ for key, (name, charset) in config.LC_DESCRIPTIONS.items():
+ revmap[_(name)] = key
+ langnames = revmap.keys()
+ langnames.sort()
+ langs = []
+ for name in langnames:
+ langs.append(revmap[name])
+ try:
+ langi = langs.index(config.DEFAULT_SERVER_LANGUAGE)
+ except ValueError:
+ # Someone must have deleted the servers's preferred language. Could
+ # be other trouble lurking!
+ langi = 0
+ # BAW: we should preserve the list of checked languages across form
+ # invocations.
+ checked = [0] * len(langs)
+ checked[langi] = 1
+ deflang = _(
+ config.languages.get_description(config.DEFAULT_SERVER_LANGUAGE))
+ ftable.AddRow([Label(_(
+ """Initial list of supported languages. Note that if you do not
+ select at least one initial language, the list will use the server
+ default language of $deflang""")),
+ CheckBoxArray('langs',
+ [_(config.languges.get_description(code))
+ for code in langs],
+ checked=checked,
+ values=langs)])
+ ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY)
+ ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY)
+
+ ftable.AddRow([Label(_('Send "list created" email to list owner?')),
+ RadioButtonArray('notify', (_('No'), _('Yes')),
+ checked=notify,
+ values=(0, 1))])
+ ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY)
+ ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY)
+
+ ftable.AddRow([' ')
+ doc.AddItem(' ' + _('When you are done making changes...'))
+ form.AddItem(SubmitButton('submit', _('Submit Changes')))
+ doc.AddItem(form)
+
+
+
+def ChangeHTML(mlist, cgi_info, template_name, doc):
+ if not cgi_info.has_key('html_code'):
+ doc.AddItem(Header(3,_("Can't have empty html page.")))
+ doc.AddItem(Header(3,_("HTML Unchanged.")))
+ doc.AddItem(' There currently are no publicly-advertised
+ %(mailmanlink)s mailing lists on %(hostname)s.'''))
+ else:
+ welcome.append(
+ _(''' Below is a listing of all the public mailing lists on
+ %(hostname)s. Click on a list name to get more information about
+ the list, or to subscribe, unsubscribe, and change the preferences
+ on your subscription.'''))
+
+ # set up some local variables
+ adj = msg and _('right') or ''
+ siteowner = Utils.get_site_noreply()
+ welcome.extend(
+ (_(''' To visit the general information page for an unadvertised list,
+ open a URL similar to this one, but with a '/' and the %(adj)s
+ list name appended.
+ List administrators, you can visit '''),
+ Link(Utils.ScriptURL('admin'),
+ _('the list admin overview page')),
+ _(''' to find the management interface for your list.
+ If you are having trouble using the lists, please contact '''),
+ Link('mailto:' + siteowner, siteowner),
+ '. '))
+
+ table.AddRow([apply(Container, welcome)])
+ table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 0, colspan=2)
+
+ if advertised:
+ table.AddRow([' ', ' '])
+ table.AddRow([Bold(FontAttr(_('List'), size='+2')),
+ Bold(FontAttr(_('Description'), size='+2'))
+ ])
+ highlight = 1
+ for url, real_name, description in advertised:
+ table.AddRow(
+ [Link(url, Bold(real_name)),
+ description or Italic(_('[no description available]'))])
+ if highlight and config.WEB_HIGHLIGHT_COLOR:
+ table.AddRowInfo(table.GetCurrentRowIndex(),
+ bgcolor=config.WEB_HIGHLIGHT_COLOR)
+ highlight = not highlight
+
+ doc.AddItem(table)
+ doc.AddItem('
please explain (optional):')),
- TextArea('comment-%d' % id, rows=4, cols=EXCERPT_WIDTH,
- text = Utils.wrap(_(notice), column=80))
- ])
- row, col = t.GetCurrentRowIndex(), t.GetCurrentCellIndex()
- t.AddCellInfo(row, col-1, align='right')
- t.AddRow([Bold(_('Message Headers:')),
- TextArea('headers-%d' % id, hdrtxt,
- rows=EXCERPT_HEIGHT, cols=EXCERPT_WIDTH, readonly=1)])
- row, col = t.GetCurrentRowIndex(), t.GetCurrentCellIndex()
- t.AddCellInfo(row, col-1, align='right')
- t.AddRow([Bold(_('Message Excerpt:')),
- TextArea('fulltext-%d' % id, Utils.websafe(body),
- rows=EXCERPT_HEIGHT, cols=EXCERPT_WIDTH, readonly=1)])
- t.AddCellInfo(row+1, col-1, align='right')
- form.AddItem(t)
- form.AddItem('
')
- if banaddrs:
- for addr, patt in banaddrs:
- doc.AddItem(_('%(addr)s is banned (matched: %(patt)s)') + '
')
diff --git a/src/mailman/web/Cgi/confirm.py b/src/mailman/web/Cgi/confirm.py
deleted file mode 100644
index 188e43068..000000000
--- a/src/mailman/web/Cgi/confirm.py
+++ /dev/null
@@ -1,834 +0,0 @@
-# Copyright (C) 2001-2009 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see
'
- if mlist.subscribe_policy in (2, 3):
- # Confirmation is required
- result = _("""Your confirmation is required in order to continue with
- the subscription request to the mailing list %(listname)s.
- Your subscription settings are shown below; make any necessary changes
- and hit Subscribe to list ... to complete the confirmation
- process. Once you've confirmed your subscription request, the
- moderator must approve or reject your membership request. You will
- receive notice of their decision.
-
-
'
- table.AddRow([result])
- table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2)
-
- table.AddRow([Label(_('Your email address:')), email])
- table.AddRow([Label(_('Your real name:')),
- TextBox('realname', name)])
-## table.AddRow([Label(_('Password:')),
-## PasswordBox('password', password)])
-## table.AddRow([Label(_('Password (confirm):')),
-## PasswordBox('pwconfirm', password)])
- # Only give them a choice to receive digests if they actually have a
- # choice
-
- Hit the Unsubscribe button below to complete the confirmation
- process.
-
-
'])
- table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2)
- table.AddRow([Hidden('cookie', cookie)])
- table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2)
- table.AddRow([SubmitButton('submit', _('Unsubscribe')),
- SubmitButton('cancel', _('Cancel and discard'))])
-
- form.AddItem(table)
- doc.AddItem(form)
-
-
-
-def addrchange_cancel(mlist, doc, cookie):
- # Expunge this record from the pending database
- expunge(mlist, cookie)
- doc.AddItem(_('You have canceled your change of address request.'))
-
-
-
-def addrchange_confirm(mlist, doc, cookie):
- # See the comment in admin.py about the need for the signal
- # handler.
- def sigterm_handler(signum, frame, mlist=mlist):
- mlist.Unlock()
- sys.exit(0)
-
- mlist.Lock()
- try:
- try:
- # Do this in two steps so we can get the preferred language for
- # the user who is unsubscribing.
- op, oldaddr, newaddr, globally = mlist.pend_confirm(
- cookie, expunge=False)
- lang = mlist.getMemberLanguage(oldaddr)
- i18n.set_language(lang)
- doc.set_language(lang)
- op, oldaddr, newaddr = mlist.ProcessConfirmation(cookie)
- except Errors.NotAMemberError:
- bad_confirmation(doc, _('''Invalid confirmation string. It is
- possible that you are attempting to confirm a request for an
- address that has already been unsubscribed.'''))
- except Errors.MembershipIsBanned:
- owneraddr = mlist.GetOwnerEmail()
- realname = mlist.real_name
- doc.addError(_("""%(newaddr)s is banned from subscribing to the
- %(realname)s list. If you think this restriction is erroneous,
- please contact the list owners at %(owneraddr)s."""))
- else:
- # The response
- listname = mlist.real_name
- title = _('Change of address request confirmed')
- optionsurl = mlist.GetOptionsURL(newaddr)
- doc.SetTitle(title)
- doc.AddItem(Header(3, Bold(FontAttr(title, size='+2'))))
- doc.AddItem(_("""\
- You have successfully changed your address on the %(listname)s
- mailing list from %(oldaddr)s to %(newaddr)s. You
- can now proceed to your membership
- login page."""))
- mlist.Save()
- finally:
- mlist.Unlock()
-
-
-
-def addrchange_prompt(mlist, doc, cookie, oldaddr, newaddr, globally):
- title = _('Confirm change of address request')
- doc.SetTitle(title)
- lang = mlist.getMemberLanguage(oldaddr)
- i18n.set_language(lang)
- doc.set_language(lang)
-
- form = Form(mlist.GetScriptURL('confirm'))
- table = Table(border=0, width='100%')
- table.AddRow([Center(Bold(FontAttr(title, size='+1')))])
- table.AddCellInfo(table.GetCurrentRowIndex(), 0,
- colspan=2, bgcolor=config.WEB_HEADER_COLOR)
-
- listname = mlist.real_name
- fullname = mlist.getMemberName(oldaddr)
- if fullname is None:
- fullname = _('Not available')
- else:
- fullname = Utils.uncanonstr(fullname, lang)
- if globally:
- globallys = _('globally')
- else:
- globallys = ''
- table.AddRow([_("""Your confirmation is required in order to complete the
- change of address request for the mailing list %(listname)s. You
- are currently subscribed with
-
-
-
- and you have requested to %(globallys)s change your email address to
-
-
-
- Hit the Change address button below to complete the confirmation
- process.
-
-
'])
- table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2)
- table.AddRow([Hidden('cookie', cookie)])
- table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2)
- table.AddRow([SubmitButton('submit', _('Change address')),
- SubmitButton('cancel', _('Cancel and discard'))])
-
- form.AddItem(table)
- doc.AddItem(form)
-
-
-
-def heldmsg_cancel(mlist, doc, cookie):
- title = _('Continue awaiting approval')
- doc.SetTitle(title)
- table = Table(border=0, width='100%')
- table.AddRow([Center(Bold(FontAttr(title, size='+1')))])
- table.AddCellInfo(table.GetCurrentRowIndex(), 0,
- bgcolor=config.WEB_HEADER_COLOR)
- # Expunge this record from the pending database.
- expunge(mlist, cookie)
- table.AddRow([_('''Okay, the list moderator will still have the
- opportunity to approve or reject this message.''')])
- doc.AddItem(table)
-
-
-
-def heldmsg_confirm(mlist, doc, cookie):
- # See the comment in admin.py about the need for the signal
- # handler.
- def sigterm_handler(signum, frame, mlist=mlist):
- mlist.Unlock()
- sys.exit(0)
-
- mlist.Lock()
- try:
- try:
- # Do this in two steps so we can get the preferred language for
- # the user who posted the message.
- op, id = mlist.pend_confirm(cookie)
- requestsdb = config.db.get_list_requests(mlist)
- key, data = requestsdb.get_record(id)
- sender = data['sender']
- msgsubject = data['msgsubject']
- subject = Utils.websafe(msgsubject)
- lang = mlist.getMemberLanguage(sender)
- i18n.set_language(lang)
- doc.set_language(lang)
- # Discard the message
- mlist.HandleRequest(id, config.DISCARD,
- _('Sender discarded message via web.'))
- except Errors.LostHeldMessage:
- bad_confirmation(doc, _('''The held message with the Subject:
- header %(subject)s could not be found. The most likely
- reason for this is that the list moderator has already approved or
- rejected the message. You were not able to cancel it in
- time.'''))
- else:
- # The response
- listname = mlist.real_name
- title = _('Posted message canceled')
- doc.SetTitle(title)
- doc.AddItem(Header(3, Bold(FontAttr(title, size='+2'))))
- doc.AddItem(_('''\
- You have successfully canceled the posting of your message with
- the Subject: header %(subject)s to the mailing list
- %(listname)s.'''))
- mlist.Save()
- finally:
- mlist.Unlock()
-
-
-
-def heldmsg_prompt(mlist, doc, cookie, id):
- title = _('Cancel held message posting')
- doc.SetTitle(title)
- form = Form(mlist.GetScriptURL('confirm'))
- table = Table(border=0, width='100%')
- table.AddRow([Center(Bold(FontAttr(title, size='+1')))])
- table.AddCellInfo(table.GetCurrentRowIndex(), 0,
- colspan=2, bgcolor=config.WEB_HEADER_COLOR)
- # Blarg. The list must be locked in order to interact with the ListAdmin
- # database, even for read-only. See the comment in admin.py about the
- # need for the signal handler.
- def sigterm_handler(signum, frame, mlist=mlist):
- mlist.Unlock()
- sys.exit(0)
- # Get the record, but watch for KeyErrors which mean the admin has already
- # disposed of this message.
- mlist.Lock()
- requestdb = config.db.get_list_requests(mlist)
- try:
- try:
- key, data = requestdb.get_record(id)
- except KeyError:
- data = None
- finally:
- mlist.Unlock()
-
- if data is None:
- bad_confirmation(doc, _("""The held message you were referred to has
- already been handled by the list administrator."""))
- return
-
- # Unpack the data and present the confirmation message
- ign, sender, msgsubject, givenreason, ign, ign = data
- # Now set the language to the sender's preferred.
- lang = mlist.getMemberLanguage(sender)
- i18n.set_language(lang)
- doc.set_language(lang)
-
- subject = Utils.websafe(msgsubject)
- reason = Utils.websafe(_(givenreason))
- listname = mlist.real_name
- table.AddRow([_('''Your confirmation is required in order to cancel the
- posting of your message to the mailing list %(listname)s:
-
-
-
- Hit the Cancel posting button to discard the posting.
-
-
'])
- table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2)
- table.AddRow([Hidden('cookie', cookie)])
- table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2)
- table.AddRow([SubmitButton('submit', _('Cancel posting')),
- SubmitButton('cancel', _('Continue awaiting approval'))])
-
- form.AddItem(table)
- doc.AddItem(form)
-
-
-
-def reenable_cancel(mlist, doc, cookie):
- # Don't actually discard this cookie, since the user may decide to
- # re-enable their membership at a future time, and we may be sending out
- # future notifications with this cookie value.
- doc.AddItem(_("""You have canceled the re-enabling of your membership. If
- we continue to receive bounces from your address, it could be deleted from
- this mailing list."""))
-
-
-
-def reenable_confirm(mlist, doc, cookie):
- # See the comment in admin.py about the need for the signal
- # handler.
- def sigterm_handler(signum, frame, mlist=mlist):
- mlist.Unlock()
- sys.exit(0)
-
- mlist.Lock()
- try:
- try:
- # Do this in two steps so we can get the preferred language for
- # the user who is unsubscribing.
- op, listname, addr = mlist.pend_confirm(cookie, expunge=False)
- lang = mlist.getMemberLanguage(addr)
- i18n.set_language(lang)
- doc.set_language(lang)
- op, addr = mlist.ProcessConfirmation(cookie)
- except Errors.NotAMemberError:
- bad_confirmation(doc, _('''Invalid confirmation string. It is
- possible that you are attempting to confirm a request for an
- address that has already been unsubscribed.'''))
- else:
- # The response
- listname = mlist.real_name
- title = _('Membership re-enabled.')
- optionsurl = mlist.GetOptionsURL(addr)
- doc.SetTitle(title)
- doc.AddItem(Header(3, Bold(FontAttr(title, size='+2'))))
- doc.AddItem(_("""\
- You have successfully re-enabled your membership in the
- %(listname)s mailing list. You can now visit your member options page.
- """))
- mlist.Save()
- finally:
- mlist.Unlock()
-
-
-
-def reenable_prompt(mlist, doc, cookie, list, member):
- title = _('Re-enable mailing list membership')
- doc.SetTitle(title)
- form = Form(mlist.GetScriptURL('confirm'))
- table = Table(border=0, width='100%')
- table.AddRow([Center(Bold(FontAttr(title, size='+1')))])
- table.AddCellInfo(table.GetCurrentRowIndex(), 0,
- colspan=2, bgcolor=config.WEB_HEADER_COLOR)
-
- lang = mlist.getMemberLanguage(member)
- i18n.set_language(lang)
- doc.set_language(lang)
-
- realname = mlist.real_name
- info = mlist.getBounceInfo(member)
- if not info:
- listinfourl = mlist.GetScriptURL('listinfo')
- # They've already be unsubscribed
- table.AddRow([_("""We're sorry, but you have already been unsubscribed
- from this mailing list. To re-subscribe, please visit the
- list information page.""")])
- return
-
- date = time.strftime('%A, %B %d, %Y',
- time.localtime(time.mktime(info.date + (0,)*6)))
- daysleft = int(info.noticesleft *
- mlist.bounce_you_are_disabled_warnings_interval /
- config.days(1))
- # BAW: for consistency this should be changed to 'fullname' or the above
- # 'fullname's should be changed to 'username'. Don't want to muck with
- # the i18n catalogs though.
- username = mlist.getMemberName(member)
- if username is None:
- username = _('not available')
- else:
- username = Utils.uncanonstr(username, lang)
-
- table.AddRow([_("""Your membership in the %(realname)s mailing list is
- currently disabled due to excessive bounces. Your confirmation is
- required in order to re-enable delivery to your address. We have the
- following information on file:
-
-
-
- Hit the Re-enable membership button to resume receiving postings
- from the mailing list. Or hit the Cancel button to defer
- re-enabling your membership.
- """)])
-
- table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2)
- table.AddRow([Hidden('cookie', cookie)])
- table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2)
- table.AddRow([SubmitButton('submit', _('Re-enable membership')),
- SubmitButton('cancel', _('Cancel'))])
-
- form.AddItem(table)
- doc.AddItem(form)
diff --git a/src/mailman/web/Cgi/create.py b/src/mailman/web/Cgi/create.py
deleted file mode 100644
index 4bb650957..000000000
--- a/src/mailman/web/Cgi/create.py
+++ /dev/null
@@ -1,400 +0,0 @@
-# Copyright (C) 2001-2009 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see
')
- # Always add the footer and print the document
- doc.AddItem(_('Return to the ') +
- Link(Utils.ScriptURL('listinfo'),
- _('general list overview')).Format())
- doc.AddItem(_('
Return to the ') +
- Link(Utils.ScriptURL('admin'),
- _('administrative list overview')).Format())
- doc.AddItem(MailmanLogo())
- print doc.Format()
-
-
-
-def process_request(doc, cgidata):
- # Lowercase the listname since this is treated as the 'internal' name.
- listname = cgidata.getvalue('listname', '').strip().lower()
- owner = cgidata.getvalue('owner', '').strip()
- try:
- autogen = bool(int(cgidata.getvalue('autogen', '0')))
- except ValueError:
- autogen = False
- try:
- notify = bool(int(cgidata.getvalue('notify', '0')))
- except ValueError:
- notify = False
- try:
- moderate = bool(int(cgidata.getvalue('moderate',
- config.DEFAULT_DEFAULT_MEMBER_MODERATION)))
- except ValueError:
- moderate = config.DEFAULT_DEFAULT_MEMBER_MODERATION
-
- password = cgidata.getvalue('password', '').strip()
- confirm = cgidata.getvalue('confirm', '').strip()
- auth = cgidata.getvalue('auth', '').strip()
- langs = cgidata.getvalue('langs', [config.DEFAULT_SERVER_LANGUAGE])
-
- if not isinstance(langs, list):
- langs = [langs]
- # Sanity checks
- safelistname = Utils.websafe(listname)
- if '@' in listname:
- request_creation(doc, cgidata,
- _('List name must not include "@": $safelistname'))
- return
- if not listname:
- request_creation(doc, cgidata,
- _('You forgot to enter the list name'))
- return
- if not owner:
- request_creation(doc, cgidata,
- _('You forgot to specify the list owner'))
- return
- if autogen:
- if password or confirm:
- request_creation(
- doc, cgidata,
- _("""Leave the initial password (and confirmation) fields
- blank if you want Mailman to autogenerate the list
- passwords."""))
- return
- password = confirm = Utils.MakeRandomPassword(
- config.ADMIN_PASSWORD_LENGTH)
- else:
- if password <> confirm:
- request_creation(doc, cgidata,
- _('Initial list passwords do not match'))
- return
- if not password:
- request_creation(
- doc, cgidata,
- # The little tag is used so that this string
- # differs from the one in bin/newlist. The former is destined
- # for the web while the latter is destined for email, so they
- # must be different entries in the message catalog.
- _('The list password cannot be empty'))
- return
- # The authorization password must be non-empty, and it must match either
- # the list creation password or the site admin password
- ok = False
- if auth:
- ok = Utils.check_global_password(auth, False)
- if not ok:
- ok = Utils.check_global_password(auth)
- if not ok:
- request_creation(
- doc, cgidata,
- _('You are not authorized to create new mailing lists'))
- return
- # Make sure the url host name matches one of our virtual domains. Then
- # calculate the list's posting address.
- url_host = Utils.get_request_domain()
- # Find the IDomain matching this url_host if there is one.
- for email_host, domain in config.domains:
- if domain.url_host == url_host:
- email_host = domain.email_host
- break
- else:
- safehostname = Utils.websafe(url_host)
- request_creation(doc, cgidata,
- _('Unknown virtual host: $safehostname'))
- return
- fqdn_listname = '%s@%s' % (listname, email_host)
- # We've got all the data we need, so go ahead and try to create the list
- mlist = MailList.MailList()
- try:
- scheme = passwords.lookup_scheme(config.PASSWORD_SCHEME.lower())
- pw = passwords.make_secret(password, scheme)
- try:
- mlist.Create(fqdn_listname, owner, pw, langs)
- except Errors.EmailAddressError, s:
- request_creation(doc, cgidata, _('Bad owner email address: $s'))
- return
- except Errors.MMListAlreadyExistsError:
- safelistname = Utils.websafe(listname)
- request_creation(doc, cgidata,
- _('List already exists: $safelistname'))
- return
- except Errors.InvalidEmailAddress, s:
- request_creation(doc, cgidata, _('Illegal list name: $s'))
- return
- except Errors.MMListError:
- request_creation(
- doc, cgidata,
- _("""Some unknown error occurred while creating the list.
- Please contact the site administrator for assistance."""))
- return
- # Initialize the host_name and web_page_url attributes, based on
- # virtual hosting settings and the request environment variables.
- mlist.default_member_moderation = moderate
- mlist.Save()
- finally:
- mlist.Unlock()
- # Now do the MTA-specific list creation tasks
- if config.MTA:
- modname = 'Mailman.MTA.' + config.MTA
- __import__(modname)
- sys.modules[modname].create(mlist, cgi=True)
- # And send the notice to the list owner.
- if notify:
- text = Utils.maketext(
- 'newlist.txt',
- {'listname' : listname,
- 'password' : password,
- 'admin_url' : mlist.GetScriptURL('admin', absolute=True),
- 'listinfo_url': mlist.GetScriptURL('listinfo', absolute=True),
- 'requestaddr' : mlist.GetRequestEmail(),
- 'siteowner' : mlist.no_reply_address,
- }, mlist=mlist)
- msg = Message.UserNotification(
- owner, mlist.no_reply_address,
- _('Your new mailing list: $listname'),
- text, mlist.preferred_language)
- msg.send(mlist)
- # Success!
- listinfo_url = mlist.GetScriptURL('listinfo')
- admin_url = mlist.GetScriptURL('admin')
- create_url = Utils.ScriptURL('create')
-
- title = _('Mailing list creation results')
- doc.SetTitle(title)
- table = Table(border=0, width='100%')
- table.AddRow([Center(Bold(FontAttr(title, size='+1')))])
- table.AddCellInfo(table.GetCurrentRowIndex(), 0,
- bgcolor=config.WEB_HEADER_COLOR)
- table.AddRow([_("""You have successfully created the mailing list
- $listname and notification has been sent to the list owner
- $owner. You can now:""")])
- ullist = UnorderedList()
- ullist.AddItem(Link(listinfo_url, _("Visit the list's info page")))
- ullist.AddItem(Link(admin_url, _("Visit the list's admin page")))
- ullist.AddItem(Link(create_url, _('Create another list')))
- table.AddRow([ullist])
- doc.AddItem(table)
-
-
-
-# Because the cgi module blows
-class Dummy:
- def getvalue(self, name, default):
- return default
-dummy = Dummy()
-
-
-
-def request_creation(doc, cgidata=dummy, errmsg=None):
- # What virtual domain are we using?
- hostname = Utils.get_request_domain()
- # Set up the document
- title = _('Create a $hostname Mailing List')
- doc.SetTitle(title)
- table = Table(border=0, width='100%')
- table.AddRow([Center(Bold(FontAttr(title, size='+1')))])
- table.AddCellInfo(table.GetCurrentRowIndex(), 0,
- bgcolor=config.WEB_HEADER_COLOR)
- # Add any error message
- if errmsg:
- table.AddRow([Header(3, Bold(
- FontAttr(_('Error: '), color='#ff0000', size='+2').Format() +
- Italic(errmsg).Format()))])
- table.AddRow([_("""You can create a new mailing list by entering the
- relevant information into the form below. The name of the mailing list
- will be used as the primary address for posting messages to the list, so
- it should be lowercased. You will not be able to change this once the
- list is created.
-
-
'])
- ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2)
- ftable.AddRow([Label(_("List creator's (authentication) password:")),
- PasswordBox('auth')])
- ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY)
- ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY)
-
- ftable.AddRow([Center(SubmitButton('doit', _('Create List'))),
- Center(SubmitButton('clear', _('Clear Form')))])
- form.AddItem(ftable)
- table.AddRow([form])
- doc.AddItem(table)
diff --git a/src/mailman/web/Cgi/edithtml.py b/src/mailman/web/Cgi/edithtml.py
deleted file mode 100644
index dfc871ec1..000000000
--- a/src/mailman/web/Cgi/edithtml.py
+++ /dev/null
@@ -1,175 +0,0 @@
-# Copyright (C) 1998-2009 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see
')
-
- link = Link(mlist.GetScriptURL('admin'),
- _('View or edit the list configuration information.'))
-
- doc.AddItem(FontSize("+1", link))
- doc.AddItem('
')
- form = Form(mlist.GetScriptURL('edithtml') + '/' + template_name)
- text = Utils.websafe(Utils.maketext(template_name, raw=1, mlist=mlist))
- form.AddItem(TextArea('html_code', text, rows=40, cols=75))
- form.AddItem('
')
- return
- code = cgi_info['html_code'].value
- code = re.sub(r'<([/]?script.*?)>', r'<\1>', code)
- langdir = os.path.join(mlist.fullpath(), mlist.preferred_language)
- # Make sure the directory exists
- Utils.makedirs(langdir)
- fp = open(os.path.join(langdir, template_name), 'w')
- try:
- fp.write(code)
- finally:
- fp.close()
- doc.AddItem(Header(3, _('HTML successfully updated.')))
- doc.AddItem('
')
diff --git a/src/mailman/web/Cgi/listinfo.py b/src/mailman/web/Cgi/listinfo.py
deleted file mode 100644
index 149be715f..000000000
--- a/src/mailman/web/Cgi/listinfo.py
+++ /dev/null
@@ -1,207 +0,0 @@
-# Copyright (C) 1998-2009 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see
')
- doc.AddItem(MailmanLogo())
- print doc.Format()
-
-
-
-def list_listinfo(mlist, lang):
- # Generate list specific listinfo
- doc = HeadlessDocument()
- doc.set_language(lang)
-
- replacements = mlist.GetStandardReplacements(lang)
-
- if not mlist.digestable or not mlist.nondigestable:
- replacements['
')
- doc.AddItem(MailmanLogo())
- print doc.Format()
- return
-
- # get the list and user's name
- listname = parts[0].lower()
- # open list
- try:
- mlist = MailList.MailList(listname, lock=0)
- except Errors.MMListError, e:
- # Avoid cross-site scripting attacks
- safelistname = Utils.websafe(listname)
- title = _('CGI script error')
- doc.SetTitle(title)
- doc.AddItem(Header(2, title))
- doc.addError(_('No such list %(safelistname)s'))
- doc.AddItem('
')
- doc.AddItem(MailmanLogo())
- print doc.Format()
- log.error('No such list "%s": %s\n', listname, e)
- return
-
- # The total contents of the user's response
- cgidata = cgi.FieldStorage(keep_blank_values=1)
-
- # Set the language for the page. If we're coming from the listinfo cgi,
- # we might have a 'language' key in the cgi data. That was an explicit
- # preference to view the page in, so we should honor that here. If that's
- # not available, use the list's default language.
- language = cgidata.getvalue('language')
- if language not in config.languages.enabled_codes:
- language = mlist.preferred_language
- i18n.set_language(language)
- doc.set_language(language)
-
- if lenparts < 2:
- user = cgidata.getvalue('email')
- if not user:
- # If we're coming from the listinfo page and we left the email
- # address field blank, it's not an error. listinfo.html names the
- # button UserOptions; we can use that as the descriminator.
- if not cgidata.getvalue('UserOptions'):
- doc.addError(_('No address given'))
- loginpage(mlist, doc, None, language)
- print doc.Format()
- return
- else:
- user = Utils.LCDomain(Utils.UnobscureEmail(SLASH.join(parts[1:])))
-
- # Avoid cross-site scripting attacks
- safeuser = Utils.websafe(user)
- try:
- Utils.ValidateEmail(user)
- except Errors.EmailAddressError:
- doc.addError(_('Illegal Email Address: %(safeuser)s'))
- loginpage(mlist, doc, None, language)
- print doc.Format()
- return
- # Sanity check the user, but only give the "no such member" error when
- # using public rosters, otherwise, we'll leak membership information.
- if not mlist.isMember(user) and mlist.private_roster == 0:
- doc.addError(_('No such member: %(safeuser)s.'))
- loginpage(mlist, doc, None, language)
- print doc.Format()
- return
-
- # Find the case preserved email address (the one the user subscribed with)
- lcuser = user.lower()
- try:
- cpuser = mlist.getMemberCPAddress(lcuser)
- except Errors.NotAMemberError:
- # This happens if the user isn't a member but we've got private rosters
- cpuser = None
- if lcuser == cpuser:
- cpuser = None
-
- # And now we know the user making the request, so set things up to for the
- # user's stored preferred language, overridden by any form settings for
- # their new language preference.
- userlang = cgidata.getvalue('language')
- if userlang not in config.languages.enabled_codes:
- userlang = mlist.getMemberLanguage(user)
- doc.set_language(userlang)
- i18n.set_language(userlang)
-
- # See if this is VARHELP on topics.
- varhelp = None
- if cgidata.has_key('VARHELP'):
- varhelp = cgidata['VARHELP'].value
- elif 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 isinstance(qs, list):
- varhelp = qs[0]
- if varhelp:
- topic_details(mlist, doc, user, cpuser, userlang, varhelp)
- return
-
- # Are we processing an unsubscription request from the login screen?
- if cgidata.has_key('login-unsub'):
- # Because they can't supply a password for unsubscribing, we'll need
- # to do the confirmation dance.
- if mlist.isMember(user):
- # We must acquire the list lock in order to pend a request.
- try:
- mlist.Lock()
- # If unsubs require admin approval, then this request has to
- # be held. Otherwise, send a confirmation.
- if mlist.unsubscribe_policy:
- mlist.HoldUnsubscription(user)
- doc.addError(_("""Your unsubscription request has been
- forwarded to the list administrator for approval."""),
- tag='')
- else:
- mlist.ConfirmUnsubscription(user, userlang)
- doc.addError(_('The confirmation email has been sent.'),
- tag='')
- mlist.Save()
- finally:
- mlist.Unlock()
- else:
- # Not a member
- if mlist.private_roster == 0:
- # Public rosters
- doc.addError(_('No such member: %(safeuser)s.'))
- else:
- mlog.error('Unsub attempt of non-member w/ private rosters: %s',
- user)
- doc.addError(_('The confirmation email has been sent.'),
- tag='')
- loginpage(mlist, doc, user, language)
- print doc.Format()
- return
-
- # Are we processing a password reminder from the login screen?
- if cgidata.has_key('login-remind'):
- if mlist.isMember(user):
- mlist.MailUserPassword(user)
- doc.addError(
- _('A reminder of your password has been emailed to you.'),
- tag='')
- else:
- # Not a member
- if mlist.private_roster == 0:
- # Public rosters
- doc.addError(_('No such member: %(safeuser)s.'))
- else:
- mlog.error(
- 'Reminder attempt of non-member w/ private rosters: %s',
- user)
- doc.addError(
- _('A reminder of your password has been emailed to you.'),
- tag='')
- loginpage(mlist, doc, user, language)
- print doc.Format()
- return
-
- # Get the password from the form.
- password = cgidata.getvalue('password', '').strip()
- # Check authentication. We need to know if the credentials match the user
- # or the site admin, because they are the only ones who are allowed to
- # change things globally. Specifically, the list admin may not change
- # values globally.
- if config.ALLOW_SITE_ADMIN_COOKIES:
- user_or_siteadmin_context = (config.AuthUser, config.AuthSiteAdmin)
- else:
- # Site and list admins are treated equal so that list admin can pass
- # site admin test. :-(
- user_or_siteadmin_context = (config.AuthUser,)
- is_user_or_siteadmin = mlist.WebAuthenticate(
- user_or_siteadmin_context, password, user)
- # Authenticate, possibly using the password supplied in the login page
- if not is_user_or_siteadmin and \
- not mlist.WebAuthenticate((config.AuthListAdmin,
- config.AuthSiteAdmin),
- password, user):
- # Not authenticated, so throw up the login page again. If they tried
- # to authenticate via cgi (instead of cookie), then print an error
- # message.
- if cgidata.has_key('password'):
- doc.addError(_('Authentication failed.'))
- # So as not to allow membership leakage, prompt for the email
- # address and the password here.
- if mlist.private_roster <> 0:
- mlog.error('Login failure with private rosters: %s', user)
- user = None
- loginpage(mlist, doc, user, language)
- print doc.Format()
- return
-
- # From here on out, the user is okay to view and modify their membership
- # options. The first set of checks does not require the list to be
- # locked.
-
- if cgidata.has_key('logout'):
- print mlist.ZapCookie(config.AuthUser, user)
- loginpage(mlist, doc, user, language)
- print doc.Format()
- return
-
- if cgidata.has_key('emailpw'):
- mlist.MailUserPassword(user)
- options_page(
- mlist, doc, user, cpuser, userlang,
- _('A reminder of your password has been emailed to you.'))
- print doc.Format()
- return
-
- if cgidata.has_key('othersubs'):
- # Only the user or site administrator can view all subscriptions.
- if not is_user_or_siteadmin:
- doc.addError(_("""The list administrator may not view the other
- subscriptions for this user."""), _('Note: '))
- options_page(mlist, doc, user, cpuser, userlang)
- print doc.Format()
- return
- hostname = mlist.host_name
- title = _('List subscriptions for %(safeuser)s on %(hostname)s')
- doc.SetTitle(title)
- doc.AddItem(Header(2, title))
- doc.AddItem(_('''Click on a link to visit your options page for the
- requested mailing list.'''))
-
- # Troll through all the mailing lists that match host_name and see if
- # the user is a member. If so, add it to the list.
- onlists = []
- for gmlist in lists_of_member(mlist, user) + [mlist]:
- url = gmlist.GetOptionsURL(user)
- link = Link(url, gmlist.real_name)
- onlists.append((gmlist.real_name, link))
- onlists.sort()
- items = OrderedList(*[link for name, link in onlists])
- doc.AddItem(items)
- print doc.Format()
- return
-
- if cgidata.has_key('change-of-address'):
- # We could be changing the user's full name, email address, or both.
- # Watch out for non-ASCII characters in the member's name.
- membername = cgidata.getvalue('fullname')
- # Canonicalize the member's name
- membername = Utils.canonstr(membername, language)
- newaddr = cgidata.getvalue('new-address')
- confirmaddr = cgidata.getvalue('confirm-address')
-
- oldname = mlist.getMemberName(user)
- set_address = set_membername = 0
-
- # See if the user wants to change their email address globally. The
- # list admin is /not/ allowed to make global changes.
- globally = cgidata.getvalue('changeaddr-globally')
- if globally and not is_user_or_siteadmin:
- doc.addError(_("""The list administrator may not change the names
- or addresses for this user's other subscriptions. However, the
- subscription for this mailing list has been changed."""),
- _('Note: '))
- globally = False
- # We will change the member's name under the following conditions:
- # - membername has a value
- # - membername has no value, but they /used/ to have a membername
- if membername and membername <> oldname:
- # Setting it to a new value
- set_membername = 1
- if not membername and oldname:
- # Unsetting it
- set_membername = 1
- # We will change the user's address if both newaddr and confirmaddr
- # are non-blank, have the same value, and aren't the currently
- # subscribed email address (when compared case-sensitively). If both
- # are blank, but membername is set, we ignore it, otherwise we print
- # an error.
- msg = ''
- if newaddr and confirmaddr:
- if newaddr <> confirmaddr:
- options_page(mlist, doc, user, cpuser, userlang,
- _('Addresses did not match!'))
- print doc.Format()
- return
- if newaddr == cpuser:
- options_page(mlist, doc, user, cpuser, userlang,
- _('You are already using that email address'))
- print doc.Format()
- return
- # If they're requesting to subscribe an address which is already a
- # member, and they're /not/ doing it globally, then refuse.
- # Otherwise, we'll agree to do it globally (with a warning
- # message) and let ApprovedChangeMemberAddress() handle already a
- # member issues.
- if mlist.isMember(newaddr):
- safenewaddr = Utils.websafe(newaddr)
- if globally:
- listname = mlist.real_name
- msg += _("""\
-The new address you requested %(newaddr)s is already a member of the
-%(listname)s mailing list, however you have also requested a global change of
-address. Upon confirmation, any other mailing list containing the address
-%(safeuser)s will be changed. """)
- # Don't return
- else:
- options_page(
- mlist, doc, user, cpuser, userlang,
- _('The new address is already a member: %(newaddr)s'))
- print doc.Format()
- return
- set_address = 1
- elif (newaddr or confirmaddr) and not set_membername:
- options_page(mlist, doc, user, cpuser, userlang,
- _('Addresses may not be blank'))
- print doc.Format()
- return
- if set_address:
- if cpuser is None:
- cpuser = user
- # Register the pending change after the list is locked
- msg += _('A confirmation message has been sent to %(newaddr)s. ')
- mlist.Lock()
- try:
- try:
- mlist.ChangeMemberAddress(cpuser, newaddr, globally)
- mlist.Save()
- finally:
- mlist.Unlock()
- except Errors.InvalidEmailAddress:
- msg = _('Invalid email address provided')
- except Errors.MMAlreadyAMember:
- msg = _('%(newaddr)s is already a member of the list.')
- except Errors.MembershipIsBanned:
- owneraddr = mlist.GetOwnerEmail()
- msg = _("""%(newaddr)s is banned from this list. If you
- think this restriction is erroneous, please contact
- the list owners at %(owneraddr)s.""")
-
- if set_membername:
- mlist.Lock()
- try:
- mlist.ChangeMemberName(user, membername, globally)
- mlist.Save()
- finally:
- mlist.Unlock()
- msg += _('Member name successfully changed. ')
-
- options_page(mlist, doc, user, cpuser, userlang, msg)
- print doc.Format()
- return
-
- if cgidata.has_key('changepw'):
- newpw = cgidata.getvalue('newpw')
- confirmpw = cgidata.getvalue('confpw')
- if not newpw or not confirmpw:
- options_page(mlist, doc, user, cpuser, userlang,
- _('Passwords may not be blank'))
- print doc.Format()
- return
- if newpw <> confirmpw:
- options_page(mlist, doc, user, cpuser, userlang,
- _('Passwords did not match!'))
- print doc.Format()
- return
-
- # See if the user wants to change their passwords globally, however
- # the list admin is /not/ allowed to change passwords globally.
- pw_globally = cgidata.getvalue('pw-globally')
- if pw_globally and not is_user_or_siteadmin:
- doc.addError(_("""The list administrator may not change the
- password for this user's other subscriptions. However, the
- password for this mailing list has been changed."""),
- _('Note: '))
- pw_globally = False
-
- mlists = [mlist]
-
- if pw_globally:
- mlists.extend(lists_of_member(mlist, user))
-
- pw = passwords.make_secret(newpw, config.PASSWORD_SCHEME)
- for gmlist in mlists:
- change_password(gmlist, user, pw)
-
- # Regenerate the cookie so a re-authorization isn't necessary
- print mlist.MakeCookie(config.AuthUser, user)
- options_page(mlist, doc, user, cpuser, userlang,
- _('Password successfully changed.'))
- print doc.Format()
- return
-
- if cgidata.has_key('unsub'):
- # Was the confirming check box turned on?
- if not cgidata.getvalue('unsubconfirm'):
- options_page(
- mlist, doc, user, cpuser, userlang,
- _('''You must confirm your unsubscription request by turning
- on the checkbox below the Unsubscribe button. You
- have not been unsubscribed!'''))
- print doc.Format()
- return
-
- mlist.Lock()
- needapproval = False
- try:
- try:
- mlist.DeleteMember(
- user, 'via the member options page', userack=1)
- except Errors.MMNeedApproval:
- needapproval = True
- mlist.Save()
- finally:
- mlist.Unlock()
- # Now throw up some results page, with appropriate links. We can't
- # drop them back into their options page, because that's gone now!
- fqdn_listname = mlist.GetListEmail()
- owneraddr = mlist.GetOwnerEmail()
- url = mlist.GetScriptURL('listinfo')
-
- title = _('Unsubscription results')
- doc.SetTitle(title)
- doc.AddItem(Header(2, title))
- if needapproval:
- doc.AddItem(_("""Your unsubscription request has been received and
- forwarded on to the list moderators for approval. You will
- receive notification once the list moderators have made their
- decision."""))
- else:
- doc.AddItem(_("""You have been successfully unsubscribed from the
- mailing list %(fqdn_listname)s. If you were receiving digest
- deliveries you may get one more digest. If you have any questions
- about your unsubscription, please contact the list owners at
- %(owneraddr)s."""))
- doc.AddItem(mlist.GetMailmanFooter())
- print doc.Format()
- return
-
- if cgidata.has_key('options-submit'):
- # Digest action flags
- digestwarn = 0
- cantdigest = 0
- mustdigest = 0
-
- newvals = []
- # First figure out which options have changed. The item names come
- # from FormatOptionButton() in HTMLFormatter.py
- for item, flag in (('digest', config.Digests),
- ('mime', config.DisableMime),
- ('dontreceive', config.DontReceiveOwnPosts),
- ('ackposts', config.AcknowledgePosts),
- ('disablemail', config.DisableDelivery),
- ('conceal', config.ConcealSubscription),
- ('remind', config.SuppressPasswordReminder),
- ('rcvtopic', config.ReceiveNonmatchingTopics),
- ('nodupes', config.DontReceiveDuplicates),
- ):
- try:
- newval = int(cgidata.getvalue(item))
- except (TypeError, ValueError):
- newval = None
-
- # Skip this option if there was a problem or it wasn't changed.
- # Note that delivery status is handled separate from the options
- # flags.
- if newval is None:
- continue
- elif flag == config.DisableDelivery:
- status = mlist.getDeliveryStatus(user)
- # Here, newval == 0 means enable, newval == 1 means disable
- if not newval and status <> MemberAdaptor.ENABLED:
- newval = MemberAdaptor.ENABLED
- elif newval and status == MemberAdaptor.ENABLED:
- newval = MemberAdaptor.BYUSER
- else:
- continue
- elif newval == mlist.getMemberOption(user, flag):
- continue
- # Should we warn about one more digest?
- if flag == config.Digests and \
- newval == 0 and mlist.getMemberOption(user, flag):
- digestwarn = 1
-
- newvals.append((flag, newval))
-
- # The user language is handled a little differently
- if userlang not in mlist.language_codes:
- newvals.append((SETLANGUAGE, mlist.preferred_language))
- else:
- newvals.append((SETLANGUAGE, userlang))
-
- # Process user selected topics, but don't make the changes to the
- # MailList object; we must do that down below when the list is
- # locked.
- topicnames = cgidata.getvalue('usertopic')
- if topicnames:
- # Some topics were selected. topicnames can actually be a string
- # or a list of strings depending on whether more than one topic
- # was selected or not.
- if not isinstance(topicnames, list):
- # Assume it was a bare string, so listify it
- topicnames = [topicnames]
- # unquote the topic names
- topicnames = [urllib.unquote_plus(n) for n in topicnames]
-
- # The standard sigterm handler (see above)
- def sigterm_handler(signum, frame, mlist=mlist):
- mlist.Unlock()
- sys.exit(0)
-
- # Now, lock the list and perform the changes
- mlist.Lock()
- try:
- # `values' is a tuple of flags and the web values
- for flag, newval in newvals:
- # Handle language settings differently
- if flag == SETLANGUAGE:
- mlist.setMemberLanguage(user, newval)
- # Handle delivery status separately
- elif flag == config.DisableDelivery:
- mlist.setDeliveryStatus(user, newval)
- else:
- try:
- mlist.setMemberOption(user, flag, newval)
- except Errors.CantDigestError:
- cantdigest = 1
- except Errors.MustDigestError:
- mustdigest = 1
- # Set the topics information.
- mlist.setMemberTopics(user, topicnames)
- mlist.Save()
- finally:
- mlist.Unlock()
-
- # A bag of attributes for the global options
- class Global:
- enable = None
- remind = None
- nodupes = None
- mime = None
- def __nonzero__(self):
- return len(self.__dict__.keys()) > 0
-
- globalopts = Global()
-
- # The enable/disable option and the password remind option may have
- # their global flags sets.
- if cgidata.getvalue('deliver-globally'):
- # Yes, this is inefficient, but the list is so small it shouldn't
- # make much of a difference.
- for flag, newval in newvals:
- if flag == config.DisableDelivery:
- globalopts.enable = newval
- break
-
- if cgidata.getvalue('remind-globally'):
- for flag, newval in newvals:
- if flag == config.SuppressPasswordReminder:
- globalopts.remind = newval
- break
-
- if cgidata.getvalue('nodupes-globally'):
- for flag, newval in newvals:
- if flag == config.DontReceiveDuplicates:
- globalopts.nodupes = newval
- break
-
- if cgidata.getvalue('mime-globally'):
- for flag, newval in newvals:
- if flag == config.DisableMime:
- globalopts.mime = newval
- break
-
- # Change options globally, but only if this is the user or site admin,
- # /not/ if this is the list admin.
- if globalopts:
- if not is_user_or_siteadmin:
- doc.addError(_("""The list administrator may not change the
- options for this user's other subscriptions. However the
- options for this mailing list subscription has been
- changed."""), _('Note: '))
- else:
- for gmlist in lists_of_member(mlist, user):
- global_options(gmlist, user, globalopts)
-
- # Now print the results
- if cantdigest:
- msg = _('''The list administrator has disabled digest delivery for
- this list, so your delivery option has not been set. However your
- other options have been set successfully.''')
- elif mustdigest:
- msg = _('''The list administrator has disabled non-digest delivery
- for this list, so your delivery option has not been set. However
- your other options have been set successfully.''')
- else:
- msg = _('You have successfully set your options.')
-
- if digestwarn:
- msg += _('You may get one last digest.')
-
- options_page(mlist, doc, user, cpuser, userlang, msg)
- print doc.Format()
- return
-
- if mlist.isMember(user):
- options_page(mlist, doc, user, cpuser, userlang)
- else:
- loginpage(mlist, doc, user, userlang)
- print doc.Format()
-
-
-
-def options_page(mlist, doc, user, cpuser, userlang, message=''):
- # The bulk of the document will come from the options.html template, which
- # includes it's own html armor (head tags, etc.). Suppress the head that
- # Document() derived pages get automatically.
- doc.suppress_head = 1
-
- if mlist.obscure_addresses:
- presentable_user = Utils.ObscureEmail(user, for_text=1)
- if cpuser is not None:
- cpuser = Utils.ObscureEmail(cpuser, for_text=1)
- else:
- presentable_user = user
-
- fullname = Utils.uncanonstr(mlist.getMemberName(user), userlang)
- if fullname:
- presentable_user += ', %s' % fullname
-
- # Do replacements
- replacements = mlist.GetStandardReplacements(userlang)
- replacements['
' +
- CheckBox('unsubconfirm', 1, checked=0).Format() +
- _('Yes, I really want to unsubscribe'))
- replacements['' + Utils.websafe(OR.join(pattern.splitlines()))
- + '
'])
- table.AddRow([Bold(Label(_('Description:'))),
- Utils.websafe(description)])
- # Make colors look nice
- for row in range(1, 4):
- table.AddCellInfo(row, 0, bgcolor=config.WEB_ADMINITEM_COLOR)
-
- options_page(mlist, doc, user, cpuser, userlang, table.Format())
- print doc.Format()
diff --git a/src/mailman/web/Cgi/private.py b/src/mailman/web/Cgi/private.py
deleted file mode 100644
index 87cad7966..000000000
--- a/src/mailman/web/Cgi/private.py
+++ /dev/null
@@ -1,190 +0,0 @@
-# Copyright (C) 1998-2009 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see
')
- doc.AddItem(MailmanLogo())
- print doc.Format()
- log.error('Bad URL specification: %s', parts)
- return
-
- listname = parts[0].lower()
- try:
- mlist = MailList.MailList(listname, lock=0)
- except Errors.MMListError, e:
- # Avoid cross-site scripting attacks
- safelistname = Utils.websafe(listname)
- title = _('No such list %(safelistname)s')
- doc.SetTitle(title)
- doc.AddItem(
- Header(3,
- Bold(FontAttr(title, color='#ff0000', size='+2'))))
- doc.AddItem('
')
- doc.AddItem(MailmanLogo())
- print doc.Format()
- log.error('No such list "%s": %s\n', listname, e)
- return
-
- # Now that we have a valid mailing list, set the language
- i18n.set_language(mlist.preferred_language)
- doc.set_language(mlist.preferred_language)
-
- # Be sure the list owners are not sneaking around!
- if not config.OWNERS_CAN_DELETE_THEIR_OWN_LISTS:
- title = _("You're being a sneaky list owner!")
- doc.SetTitle(title)
- doc.AddItem(
- Header(3, Bold(FontAttr(title, color='#ff0000', size='+2'))))
- doc.AddItem(mlist.GetMailmanFooter())
- print doc.Format()
- mlog.error('Attempt to sneakily delete a list: %s', listname)
- return
-
- if cgidata.has_key('doit'):
- process_request(doc, cgidata, mlist)
- print doc.Format()
- return
-
- request_deletion(doc, mlist)
- # Always add the footer and print the document
- doc.AddItem(mlist.GetMailmanFooter())
- print doc.Format()
-
-
-
-def process_request(doc, cgidata, mlist):
- password = cgidata.getvalue('password', '').strip()
- try:
- delarchives = int(cgidata.getvalue('delarchives', '0'))
- except ValueError:
- delarchives = 0
-
- # Removing a list is limited to the list-creator (a.k.a. list-destroyer),
- # the list-admin, or the site-admin. Don't use WebAuthenticate here
- # because we want to be sure the actual typed password is valid, not some
- # password sitting in a cookie.
- if mlist.Authenticate((config.AuthCreator,
- config.AuthListAdmin,
- config.AuthSiteAdmin),
- password) == config.UnAuthorized:
- request_deletion(
- doc, mlist,
- _('You are not authorized to delete this mailing list'))
- return
-
- # Do the MTA-specific list deletion tasks
- if config.MTA:
- modname = 'Mailman.MTA.' + config.MTA
- __import__(modname)
- sys.modules[modname].remove(mlist, cgi=1)
-
- REMOVABLES = ['lists/%s']
-
- if delarchives:
- REMOVABLES.extend(['archives/private/%s',
- 'archives/private/%s.mbox',
- 'archives/public/%s',
- 'archives/public/%s.mbox',
- ])
-
- problems = 0
- listname = mlist.internal_name()
- for dirtmpl in REMOVABLES:
- dir = os.path.join(config.VAR_DIR, dirtmpl % listname)
- if os.path.islink(dir):
- try:
- os.unlink(dir)
- except OSError, e:
- if e.errno not in (errno.EACCES, errno.EPERM): raise
- problems += 1
- log.error('link %s not deleted due to permission problems', dir)
- elif os.path.isdir(dir):
- try:
- shutil.rmtree(dir)
- except OSError, e:
- if e.errno not in (errno.EACCES, errno.EPERM): raise
- problems += 1
- log.error('directory %s not deleted due to permission problems',
- dir)
-
- title = _('Mailing list deletion results')
- doc.SetTitle(title)
- table = Table(border=0, width='100%')
- table.AddRow([Center(Bold(FontAttr(title, size='+1')))])
- table.AddCellInfo(table.GetCurrentRowIndex(), 0,
- bgcolor=config.WEB_HEADER_COLOR)
- if not problems:
- table.AddRow([_('''You have successfully deleted the mailing list
- %(listname)s.''')])
- else:
- sitelist = mlist.no_reply_address
- table.AddRow([_('''There were some problems deleting the mailing list
- %(listname)s. Contact your site administrator at %(sitelist)s
- for details.''')])
- doc.AddItem(table)
- doc.AddItem('
')
- doc.AddItem(_('Return to the ') +
- Link(Utils.ScriptURL('listinfo'),
- _('general list overview')).Format())
- doc.AddItem(_('
Return to the ') +
- Link(Utils.ScriptURL('admin'),
- _('administrative list overview')).Format())
- doc.AddItem(MailmanLogo())
-
-
-
-def request_deletion(doc, mlist, errmsg=None):
- realname = mlist.real_name
- title = _('Permanently remove mailing list %(realname)s')
- doc.SetTitle(title)
-
- table = Table(border=0, width='100%')
- table.AddRow([Center(Bold(FontAttr(title, size='+1')))])
- table.AddCellInfo(table.GetCurrentRowIndex(), 0,
- bgcolor=config.WEB_HEADER_COLOR)
-
- # Add any error message
- if errmsg:
- table.AddRow([Header(3, Bold(
- FontAttr(_('Error: '), color='#ff0000', size='+2').Format() +
- Italic(errmsg).Format()))])
-
- table.AddRow([_("""This page allows you as the list owner, to permanent
- remove this mailing list from the system. This action is not
- undoable so you should undertake it only if you are absolutely
- sure this mailing list has served its purpose and is no longer necessary.
-
- Bug in Mailman version %(VERSION)s
-We're sorry, we hit a bug!
-""" % locals()
- if not STEALTH_MODE:
- print >> htfp, '''Traceback:
'''
- exc_info = sys.exc_info()
- if traceback:
- for line in traceback.format_exception(*exc_info):
- print >> htfp, websafe(line)
- else:
- print >> htfp, '[failed to import module traceback]'
- print >> htfp, '[exc: %s, var: %s]' %\
- [websafe(x) for x in exc_info[0:2]]
- print >> htfp, '\n\n
'
- else:
- print >> htfp, '''Python information:
-
-
-
""" % (sys.version, sys.executable, sys.prefix,
- sys.exec_prefix, sys.path, sys.platform)
-
- # Write environment variables to the log file.
- if log:
- print >> outfp, '[----- Environment Variables -----]'
- if os:
- for k, v in os.environ.items():
- print >> outfp, '\t%s: %s' % (k, v)
- else:
- print >> outfp, '[failed to import module os]'
-
- # Write environment variables to the HTML sink.
- if not STEALTH_MODE:
- print >> htfp, """\
-
-Variable Value
-sys.version %s
-sys.executable %s
-sys.prefix %s
-sys.exec_prefix %s
-sys.path %s
-sys.platform %s Environment variables:
-
-
-
'
- else:
- print >> htfp, '
-"""
- if os:
- for k, v in os.environ.items():
- print >> htfp, 'Variable Value '
- print >> htfp, '' + websafe(k) + \
- ' ' + websafe(v) + \
- '
[failed to import module os]'
-
- # Dump the log output
- if log:
- log.error('%s', outfp.getvalue())
-
- return htfp.getvalue()
diff --git a/src/mailman/web/Gui/Archive.py b/src/mailman/web/Gui/Archive.py
deleted file mode 100644
index 4090e74e9..000000000
--- a/src/mailman/web/Gui/Archive.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# Copyright (C) 2001-2009 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see
-
-
-'
-
-
-
-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 (Defaults.Radio, Defaults.Toggle):
- # Let ValueErrors propagate
- return int(val)
- # String and Text widgets both just return their values verbatim
- # but convert into unicode (for 2.2)
- if wtype in (Defaults.String, Defaults.Text):
- return unicode(val, Utils.GetCharSet(mlist.preferred_language))
- # This widget contains a single email address
- if wtype == Defaults.Email:
- # BAW: We must allow blank values otherwise reply_to_address can't
- # be cleared. This is currently the only Defaults.Email type
- # widget in the interface, so watch out if we ever add any new
- # ones.
- if val:
- # Let InvalidEmailAddress 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 (Defaults.EmailList, Defaults.EmailListEx):
- # BAW: value might already be a list, if this is coming from
- # config_list input. Sigh.
- if isinstance(val, list):
- 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 == Defaults.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 == Defaults.Host:
- return val
- # This is a number, either a float or an integer
- if wtype == Defaults.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 == Defaults.Select:
- return val
- # Checkboxes return a list of the selected items, even if only one is
- # selected.
- if wtype == Defaults.Checkbox:
- if isinstance(val, list):
- return val
- return [val]
- if wtype == Defaults.FileUpload:
- return val
- if wtype == Defaults.Topics:
- return val
- if wtype == Defaults.HeaderFilter:
- 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 _escape(self, property, value):
- value = value.replace('<', '<')
- return value
-
- 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, tuple) 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], list):
- val = [self._escape(property, x.value)
- for x in cgidata[property]]
- else:
- val = self._escape(property, 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 InvalidEmailAddress
- 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 %(property)s string:
- %(bad)s
- %(property)s 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/src/mailman/web/Gui/General.py b/src/mailman/web/Gui/General.py
deleted file mode 100644
index 27ef354be..000000000
--- a/src/mailman/web/Gui/General.py
+++ /dev/null
@@ -1,464 +0,0 @@
-# Copyright (C) 2001-2009 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see
""")),
-
- ('send_welcome_msg', config.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', config.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', config.Radio, (_('No'), _('Yes')), 0,
- _('Send goodbye message to members when they are unsubscribed?')),
-
- ('admin_immed_notify', config.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', config.Radio, (_('No'), _('Yes')), 0,
- _('''Should administrator get notices of subscribes and
- unsubscribes?''')),
-
- ('respond_to_post_requests', config.Radio,
- (_('No'), _('Yes')), 0,
- _('Send mail to poster when their posting is held for approval?')
- ),
-
- _('Additional settings'),
-
- ('emergency', config.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', config.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.'''),
-
- _("""When a new member is subscribed to this list, their initial
- set of options is taken from the this variable's setting.""")),
-
- ('administrivia', config.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', config.Number, 7, 0,
- _('''Maximum length in kilobytes (KB) of a message body. Use 0
- for no limit.''')),
-
- ('host_name', config.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 config.ALLOW_RFC2369_OVERRIDES:
- rtn.append(
- ('include_rfc2369_headers', config.Radio,
- (_('No'), _('Yes')), 0,
- _("""Should messages from this mailing list include the
- RFC 2369
- (i.e. List-*) headers? Yes 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.
-
- To header of posted messages will be modified to
- include the member's address instead of the list's posting
- address.
-
-
- """))
- ])
- # 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.
- headfoot = Utils.maketext('headfoot.html', mlist=mlist, raw=1)
- if config.OWNERS_CAN_ENABLE_PERSONALIZATION:
- extra = _("""\
-When personalization is enabled
-for this list, additional substitution variables are allowed in your headers
-and footers:
-
-
-""")
- else:
- extra = ''
-
- info.extend([('msg_header', Defaults.Text, (10, WIDTH), 0,
- _('Header added to mail sent to regular list members'),
- _('''Text prepended to the top of every immediately-delivery
- message. ''') + headfoot + extra),
-
- ('msg_footer', Defaults.Text, (10, WIDTH), 0,
- _('Footer added to mail sent to regular list members'),
- _('''Text appended to the bottom of every immediately-delivery
- message. ''') + headfoot + extra),
- ])
-
- info.extend([
- ('scrub_nondigest', Defaults.Toggle, (_('No'), _('Yes')), 0,
- _('Scrub attachments of regular delivery message?'),
- _('''When you scrub attachments, they are stored in archive
- area and links are made in the message so that the member can
- access via web browser. If you want the attachments totally
- disappear, you can use content filter options.''')),
- ])
- 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/src/mailman/web/Gui/Passwords.py b/src/mailman/web/Gui/Passwords.py
deleted file mode 100644
index b2fea0fb5..000000000
--- a/src/mailman/web/Gui/Passwords.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright (C) 2001-2009 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see
'),
- _('''None - no verification steps (Not
- Recommended )
- Confirm (*) - email confirmation step required
- Require approval - require list administrator
- Approval for subscriptions
- Confirm and approve - both confirm and approve
-
-
-
- This prevents mischievous (or malicious) people
- from creating subscriptions for others without
- their consent.'''))
- else:
- sub_cfentry = ('subscribe_policy', config.Radio,
- # choices
- (_('Confirm'),
- _('Require approval'),
- _('Confirm and approve')),
- 1,
- _('What steps are required for subscription?
'),
- _('''Confirm (*) - email confirmation required
- Require approval - require list administrator
- approval for subscriptions
- Confirm and approve - both confirm and approve
-
-
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
- Archival Options section for
- separate archive-related privacy settings."""),
-
- _('Subscribing'),
- ('advertised', config.Radio, (_('No'), _('Yes')), 0,
- _('''Advertise this list when people ask what lists are on this
- machine?''')),
-
- sub_cfentry,
-
- ('subscribe_auto_approval', config.EmailListEx, (10, WIDTH), 1,
- _("""List of addresses (or regexps) whose subscriptions do not
- require approval."""),
-
- _("""When subscription requires approval, addresses in this list
- are allowed to subscribe without administrator approval. Add
- addresses one per line. You may begin a line with a ^ character
- to designate a (case insensitive) regular expression match.""")),
-
- ('unsubscribe_policy', config.Radio, (_('No'), _('Yes')), 0,
- _("""Is the list moderator's approval required for unsubscription
- requests? (No 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!).
-
-
""")),
-
- ('member_moderation_notice', config.Text, (10, WIDTH), 1,
- _("""Text to include in any
- rejection notice to
- be sent to moderated members who post to this list.""")),
-
- _('Non-member filters'),
-
- ('accept_these_nonmembers', config.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', config.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', config.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
- automatically discarded.
-
-
-
""")),
-
- ('acceptable_aliases', config.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.
-
- to: .*@public.com
says to hold all
- postings with a To: mail header containing '@public.com'
- anywhere among the addresses.
-
- Subject: or Keywords: header contains a
- match against a topic filter, the message is logically placed
- into a topic bucket. 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.
-
- Subject: and Keywords: headers, as
- specified by the topics_bodylines_limit
- configuration variable.""")),
-
- ('topics_bodylines_limit', config.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 Keywords: and
- Subject: 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', config.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
- Keywords: and Subject: message headers.
- Note that the first few lines of the body of the message can also
- contain a Keywords: and Subject:
- "header" on which matching is also performed.""")),
-
- ]
-
- def handleForm(self, mlist, category, subcat, cgidata, doc):
- # MAS: Did we come from the authentication page?
- if not cgidata.has_key('topic_box_01'):
- return
- topics = []
- # We start i at 1 and keep going until we no longer find items keyed
- # with the marked tags.
- i = 1
- while True:
- 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
- name = Utils.websafe(name)
- try:
- orpattern = OR.join(pattern.splitlines())
- re.compile(orpattern)
- except (re.error, TypeError):
- safepattern = Utils.websafe(orpattern)
- doc.addError(_("""The topic pattern '%(safepattern)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(('', '', '', True))
- topics.append((name, pattern, desc, False))
- # Default is to add it after...
- else:
- topics.append((name, pattern, desc, False))
- topics.append(('', '', '', True))
- # Otherwise, just retain this one in the list
- else:
- topics.append((name, pattern, desc, False))
- # 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/src/mailman/web/Gui/Usenet.py b/src/mailman/web/Gui/Usenet.py
deleted file mode 100644
index 9c1b50809..000000000
--- a/src/mailman/web/Gui/Usenet.py
+++ /dev/null
@@ -1,140 +0,0 @@
-# Copyright (C) 2001-2009 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see
'
-NL = '\n'
-COMMASPACE = ', '
-
-
-
-class HTMLFormatter:
- def GetMailmanFooter(self):
- ownertext = COMMASPACE.join([Utils.ObscureEmail(a, 1)
- for a in self.owner])
- # Remove the .Format() when htmlformat conversion is done.
- realname = self.real_name
- hostname = self.host_name
- listinfo_link = Link(self.GetScriptURL('listinfo'),
- realname).Format()
- owner_link = Link('mailto:' + self.GetOwnerEmail(), ownertext).Format()
- innertext = _('%(listinfo_link)s list run by %(owner_link)s')
- return Container(
- '
',
- Address(
- Container(
- innertext,
- '
',
- Link(self.GetScriptURL('admin'),
- _('%(realname)s administrative interface')),
- _(' (requires authorization)'),
- '
',
- Link(Utils.ScriptURL('listinfo'),
- _('Overview of all %(hostname)s mailing lists')),
- ''
-
- for i in range(len(self.cells[row])):
- output = output + self.FormatCell(row, i, indent + 2)
-
- output = output + '\n' + ' '*indent + ''
-
- return output
-
- def Format(self, indent=0):
- output = '\n' + ' '*indent + ' '
-
- for i in range(len(self.cells)):
- output = output + self.FormatRow(i, indent + 2)
-
- output = output + '\n' + ' '*indent + '
\n'
-
- return output
-
-
-class Link:
- def __init__(self, href, text, target=None):
- self.href = href
- self.text = text
- self.target = target
-
- def Format(self, indent=0):
- texpr = ""
- if self.target != None:
- texpr = ' target="%s"' % self.target
- return '%s' % (HTMLFormatObject(self.href, indent),
- texpr,
- HTMLFormatObject(self.text, indent))
-
-class FontSize:
- """FontSize is being deprecated - use FontAttr(..., size="...") instead."""
- def __init__(self, size, *items):
- self.items = list(items)
- self.size = size
-
- def Format(self, indent=0):
- output = '' % self.size
- for item in self.items:
- output = output + HTMLFormatObject(item, indent)
- output = output + ''
- return output
-
-class FontAttr:
- """Present arbitrary font attributes."""
- def __init__(self, *items, **kw):
- self.items = list(items)
- self.attrs = kw
-
- def Format(self, indent=0):
- seq = []
- for k, v in self.attrs.items():
- seq.append('%s="%s"' % (k, v))
- output = '' % SPACE.join(seq)
- for item in self.items:
- output = output + HTMLFormatObject(item, indent)
- output = output + ''
- return output
-
-
-class Container:
- def __init__(self, *items):
- if not items:
- self.items = []
- else:
- self.items = items
-
- def AddItem(self, obj):
- self.items.append(obj)
-
- def Format(self, indent=0):
- output = []
- for item in self.items:
- output.append(HTMLFormatObject(item, indent))
- return EMPTYSTRING.join(output)
-
-
-class Label(Container):
- align = 'right'
-
- def __init__(self, *items):
- Container.__init__(self, *items)
-
- def Format(self, indent=0):
- return ('\n' % spaces
- for item in self.items:
- output = output + '%s
\n' % spaces
- return output
-
-class OrderedList(Container):
- def Format(self, indent=0):
- spaces = ' ' * indent
- output = '\n%s\n' % spaces
- for item in self.items:
- output = output + '%s
\n' % spaces
- return output
-
-class DefinitionList(Container):
- def Format(self, indent=0):
- spaces = ' ' * indent
- output = '\n%s\n' % spaces
- for dt, dd in self.items:
- output = output + '%s
\n' % spaces
- return output
-
-
-
-# Logo constants
-#
-# These are the URLs which the image logos link to. The Mailman home page now
-# points at the gnu.org site instead of the www.list.org mirror.
-
-PYTHON_URL = 'http://www.python.org/'
-GNU_URL = 'http://www.gnu.org/'
-
-# The names of the image logo files. These are concatentated onto
-# config.IMAGE_LOGOS (not urljoined).
-DELIVERED_BY = 'mailman.jpg'
-PYTHON_POWERED = 'PythonPowered.png'
-GNU_HEAD = 'gnu-head-tiny.jpg'
-
-
-def MailmanLogo():
- t = Table(border=0, width='100%')
- if config.IMAGE_LOGOS:
- def logo(file):
- return config.IMAGE_LOGOS + file
- mmlink = '' \
- '
version %s' % (logo(DELIVERED_BY), version.VERSION)
- pylink = '' % \
- logo(PYTHON_POWERED)
- gnulink = '
' % \
- logo(GNU_HEAD)
- t.AddRow([mmlink, pylink, gnulink])
- else:
- # use only textual links
- version = version.VERSION
- mmlink = Link(config.MAILMAN_URL,
- _('Delivered by Mailman
version %(version)s'))
- pylink = Link(PYTHON_URL, _('Python Powered'))
- gnulink = Link(GNU_URL, _("Gnu's Not Unix"))
- t.AddRow([mmlink, pylink, gnulink])
- return t
-
-
-class SelectOptions:
- def __init__(self, varname, values, legend,
- selected=0, size=1, multiple=None):
- self.varname = varname
- self.values = values
- self.legend = legend
- self.size = size
- self.multiple = multiple
- # we convert any type to tuple, commas are needed
- if not multiple:
- if isinstance(selected, int):
- self.selected = (selected,)
- elif isinstance(selected, tuple):
- self.selected = (selected[0],)
- elif isinstance(selected, list):
- self.selected = (selected[0],)
- else:
- self.selected = (0,)
-
- def Format(self, indent=0):
- spaces = " " * indent
- items = min( len(self.values), len(self.legend) )
-
- # jcrey: If there is no argument, we return nothing to avoid errors
- if items == 0:
- return ""
-
- text = "\n" + spaces + "'
diff --git a/src/web/Cgi/Auth.py b/src/web/Cgi/Auth.py
new file mode 100644
index 000000000..825d972f4
--- /dev/null
+++ b/src/web/Cgi/Auth.py
@@ -0,0 +1,60 @@
+# Copyright (C) 1998-2009 by the Free Software Foundation, Inc.
+#
+# This file is part of GNU Mailman.
+#
+# GNU Mailman 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 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Mailman 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
+# GNU Mailman. If not, see
')
+ doc.AddItem(MailmanLogo())
+ print doc.Format()
+
+
+
+def option_help(mlist, varhelp):
+ # The html page document
+ doc = Document()
+ doc.set_language(mlist.preferred_language)
+ # Find out which category and variable help is being requested for.
+ item = None
+ reflist = varhelp.split('/')
+ if len(reflist) >= 2:
+ category = subcat = None
+ if len(reflist) == 2:
+ category, varname = reflist
+ elif len(reflist) == 3:
+ category, subcat, varname = reflist
+ options = mlist.GetConfigInfo(category, subcat)
+ if options:
+ for i in options:
+ if i and i[0] == varname:
+ item = i
+ break
+ # Print an error message if we couldn't find a valid one
+ if not item:
+ bad = _('No valid variable name found.')
+ doc.addError(bad)
+ doc.AddItem(mlist.GetMailmanFooter())
+ print doc.Format()
+ return
+ # Get the details about the variable
+ varname, kind, params, dependancies, description, elaboration = \
+ get_item_characteristics(item)
+ # Set up the document
+ realname = mlist.real_name
+ legend = _("""%(realname)s Mailing list Configuration Help
+
%(varname)s Option""")
+
+ header = Table(width='100%')
+ header.AddRow([Center(Header(3, legend))])
+ header.AddCellInfo(header.GetCurrentRowIndex(), 0, colspan=2,
+ bgcolor=config.WEB_HEADER_COLOR)
+ doc.SetTitle(_("Mailman %(varname)s List Option Help"))
+ doc.AddItem(header)
+ doc.AddItem("%s (%s): %s
%(label)s Section'))))
+ doc.AddItem('
')
+ # Now we need to craft the form that will be submitted, which will contain
+ # all the variable settings, etc. This is a bit of a kludge because we
+ # know that the autoreply and members categories supports file uploads.
+ encoding = None
+ if category in ('autoreply', 'members'):
+ encoding = 'multipart/form-data'
+ if subcat:
+ form = Form('%s/%s/%s' % (adminurl, category, subcat),
+ encoding=encoding)
+ else:
+ form = Form('%s/%s' % (adminurl, category), encoding=encoding)
+ # This holds the two columns of links
+ linktable = Table(valign='top', width='100%')
+ linktable.AddRow([Center(Bold(_("Configuration Categories"))),
+ Center(Bold(_("Other Administrative Activities")))])
+ # The `other links' are stuff in the right column.
+ otherlinks = UnorderedList()
+ otherlinks.AddItem(Link(mlist.GetScriptURL('admindb'),
+ _('Tend to pending moderator requests')))
+ otherlinks.AddItem(Link(mlist.GetScriptURL('listinfo'),
+ _('Go to the general list information page')))
+ otherlinks.AddItem(Link(mlist.GetScriptURL('edithtml'),
+ _('Edit the public HTML pages and text files')))
+ otherlinks.AddItem(Link(mlist.GetBaseArchiveURL(),
+ _('Go to list archives')).Format() +
+ '
')
+ if config.OWNERS_CAN_DELETE_THEIR_OWN_LISTS:
+ otherlinks.AddItem(Link(mlist.GetScriptURL('rmlist'),
+ _('Delete this mailing list')).Format() +
+ _(' (requires confirmation)
'))
+ otherlinks.AddItem(Link('%s/logout' % adminurl,
+ # BAW: What I really want is a blank line, but
+ # adding an won't do it because of the
+ # bullet added to the list item.
+ '%s' %
+ _('Logout')))
+ # These are links to other categories and live in the left column
+ categorylinks_1 = categorylinks = UnorderedList()
+ categorylinks_2 = ''
+ categorykeys = categories.keys()
+ half = len(categorykeys) / 2
+ counter = 0
+ subcat = None
+ for k in categorykeys:
+ label = _(categories[k][0])
+ url = '%s/%s' % (adminurl, k)
+ if k == category:
+ # Handle subcategories
+ subcats = mlist.GetConfigSubCategories(k)
+ if subcats:
+ subcat = Utils.GetPathPieces()[-1]
+ for k, v in subcats:
+ if k == subcat:
+ break
+ else:
+ # The first subcategory in the list is the default
+ subcat = subcats[0][0]
+ subcat_items = []
+ for sub, text in subcats:
+ if sub == subcat:
+ text = Bold('[%s]' % text).Format()
+ subcat_items.append(Link(url + '/' + sub, text))
+ categorylinks.AddItem(
+ Bold(label).Format() +
+ UnorderedList(*subcat_items).Format())
+ else:
+ categorylinks.AddItem(Link(url, Bold('[%s]' % label)))
+ else:
+ categorylinks.AddItem(Link(url, label))
+ counter += 1
+ if counter >= half:
+ categorylinks_2 = categorylinks = UnorderedList()
+ counter = -len(categorykeys)
+ # Make the emergency stop switch a rude solo light
+ etable = Table()
+ # Add all the links to the links table...
+ etable.AddRow([categorylinks_1, categorylinks_2])
+ etable.AddRowInfo(etable.GetCurrentRowIndex(), valign='top')
+ if mlist.emergency:
+ label = _('Emergency moderation of all list traffic is enabled')
+ etable.AddRow([Center(
+ Link('?VARHELP=general/emergency', Bold(label)))])
+ color = config.WEB_ERROR_COLOR
+ etable.AddCellInfo(etable.GetCurrentRowIndex(), 0,
+ colspan=2, bgcolor=color)
+ linktable.AddRow([etable, otherlinks])
+ # ...and add the links table to the document.
+ form.AddItem(linktable)
+ form.AddItem('
')
+ form.AddItem(
+ _('''Make your changes in the following section, then submit them
+ using the Submit Your Changes button below.''')
+ + '
\n')
+ table = Table(width='100%')
+ table.AddRow([Center(Header(2, _('Additional Member Tasks')))])
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2,
+ bgcolor=config.WEB_HEADER_COLOR)
+ # Add a blank separator row
+ table.AddRow([' ', ' '])
+ # Add a section to set the moderation bit for all members
+ table.AddRow([_("""
'])
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2)
+ return table
+
+
+
+def add_options_table_item(mlist, category, subcat, table, item, detailsp=1):
+ # Add a row to an options table with the item description and value.
+ varname, kind, params, extra, descr, elaboration = \
+ get_item_characteristics(item)
+ if elaboration is None:
+ elaboration = descr
+ descr = get_item_gui_description(mlist, category, subcat,
+ varname, descr, elaboration, detailsp)
+ val = get_item_gui_value(mlist, category, kind, varname, params, extra)
+ table.AddRow([descr, val])
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0,
+ bgcolor=config.WEB_ADMINITEM_COLOR)
+ table.AddCellInfo(table.GetCurrentRowIndex(), 1,
+ bgcolor=config.WEB_ADMINITEM_COLOR)
+
+
+
+def get_item_characteristics(record):
+ # Break out the components of an item description from its description
+ # record:
+ #
+ # 0 -- option-var name
+ # 1 -- type
+ # 2 -- entry size
+ # 3 -- ?dependancies?
+ # 4 -- Brief description
+ # 5 -- Optional description elaboration
+ if len(record) == 5:
+ elaboration = None
+ varname, kind, params, dependancies, descr = record
+ elif len(record) == 6:
+ varname, kind, params, dependancies, descr, elaboration = record
+ else:
+ raise ValueError, _('Badly formed options entry:\n %(record)s')
+ return varname, kind, params, dependancies, descr, elaboration
+
+
+
+def get_item_gui_value(mlist, category, kind, varname, params, extra):
+ """Return a representation of an item's settings."""
+ # Give the category a chance to return the value for the variable
+ value = None
+ label, gui = mlist.GetConfigCategories()[category]
+ if hasattr(gui, 'getValue'):
+ value = gui.getValue(mlist, kind, varname, params)
+ # Filter out None, and volatile attributes
+ if value is None and not varname.startswith('_'):
+ value = getattr(mlist, varname)
+ # Now create the widget for this value
+ if kind == config.Radio or kind == config.Toggle:
+ # If we are 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.startswith('_'):
+ checked = 0
+ else:
+ checked = value
+ if varname == 'subscribe_policy' and not config.ALLOW_OPEN_SUBSCRIBE:
+ checked = checked - 1
+ # For Radio buttons, we're going to interpret the extra stuff as a
+ # horizontal/vertical flag. For backwards compatibility, the value 0
+ # means horizontal, so we use "not extra" to get the parity right.
+ return RadioButtonArray(varname, params, checked, not extra)
+ elif (kind == config.String or kind == config.Email or
+ kind == config.Host or kind == config.Number):
+ return TextBox(varname, value, params)
+ elif kind == config.Text:
+ if params:
+ r, c = params
+ else:
+ r, c = None, None
+ return TextArea(varname, value or '', r, c)
+ elif kind in (config.EmailList, config.EmailListEx):
+ if params:
+ r, c = params
+ else:
+ r, c = None, None
+ res = NL.join(value)
+ return TextArea(varname, res, r, c, wrap='off')
+ elif kind == config.FileUpload:
+ # like a text area, but also with uploading
+ if params:
+ r, c = params
+ else:
+ r, c = None, None
+ container = Container()
+ container.AddItem(_('Enter the text below, or...
'))
+ container.AddItem(TextArea(varname, value or '', r, c))
+ container.AddItem(_('
...specify a file to upload
'))
+ container.AddItem(FileUpload(varname+'_upload', r, c))
+ return container
+ elif kind == config.Select:
+ if params:
+ values, legend, selected = params
+ else:
+ codes = mlist.language_codes
+ legend = [config.languages.get_description(code) for code in codes]
+ selected = codes.index(mlist.preferred_language)
+ return SelectOptions(varname, values, legend, selected)
+ elif kind == config.Topics:
+ # A complex and specialized widget type that allows for setting of a
+ # topic name, a mark button, a regexp text box, an "add after mark",
+ # and a delete button. Yeesh! params are ignored.
+ table = Table(border=0)
+ # This adds the html for the entry widget
+ def makebox(i, name, pattern, desc, empty=False, table=table):
+ 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
+ if empty:
+ table.AddRow([Center(Bold(_('Topic %(i)d'))),
+ Hidden(newtag)])
+ else:
+ table.AddRow([Center(Bold(_('Topic %(i)d'))),
+ SubmitButton(deltag, _('Delete'))])
+ table.AddRow([Label(_('Topic name:')),
+ TextBox(boxtag, value=name, size=30)])
+ table.AddRow([Label(_('Regexp:')),
+ TextArea(reboxtag, text=pattern,
+ rows=4, cols=30, wrap='off')])
+ table.AddRow([Label(_('Description:')),
+ TextArea(desctag, text=desc,
+ rows=4, cols=30, wrap='soft')])
+ if not empty:
+ table.AddRow([SubmitButton(addtag, _('Add new item...')),
+ SelectOptions(wheretag, ('before', 'after'),
+ (_('...before this one.'),
+ _('...after this one.')),
+ selected=1),
+ ])
+ table.AddRow(['
'])
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2)
+ # Now for each element in the existing data, create a widget
+ i = 1
+ data = getattr(mlist, varname)
+ for name, pattern, desc, empty in data:
+ makebox(i, name, pattern, desc, empty)
+ i += 1
+ # Add one more non-deleteable widget as the first blank entry, but
+ # only if there are no real entries.
+ if i == 1:
+ makebox(i, '', '', '', empty=True)
+ return table
+ elif kind == config.HeaderFilter:
+ # A complex and specialized widget type that allows for setting of a
+ # spam filter rule including, a mark button, a regexp text box, an
+ # "add after mark", up and down buttons, and a delete button. Yeesh!
+ # params are ignored.
+ table = Table(border=0)
+ # This adds the html for the entry widget
+ def makebox(i, pattern, action, empty=False, table=table):
+ deltag = 'hdrfilter_delete_%02d' % i
+ reboxtag = 'hdrfilter_rebox_%02d' % i
+ actiontag = 'hdrfilter_action_%02d' % i
+ wheretag = 'hdrfilter_where_%02d' % i
+ addtag = 'hdrfilter_add_%02d' % i
+ newtag = 'hdrfilter_new_%02d' % i
+ uptag = 'hdrfilter_up_%02d' % i
+ downtag = 'hdrfilter_down_%02d' % i
+ if empty:
+ table.AddRow([Center(Bold(_('Spam Filter Rule %(i)d'))),
+ Hidden(newtag)])
+ else:
+ table.AddRow([Center(Bold(_('Spam Filter Rule %(i)d'))),
+ SubmitButton(deltag, _('Delete'))])
+ table.AddRow([Label(_('Spam Filter Regexp:')),
+ TextArea(reboxtag, text=pattern,
+ rows=4, cols=30, wrap='off')])
+ values = [config.DEFER, config.HOLD, config.REJECT,
+ config.DISCARD, config.ACCEPT]
+ try:
+ checked = values.index(action)
+ except ValueError:
+ checked = 0
+ radio = RadioButtonArray(
+ actiontag,
+ (_('Defer'), _('Hold'), _('Reject'),
+ _('Discard'), _('Accept')),
+ values=values,
+ checked=checked).Format()
+ table.AddRow([Label(_('Action:')), radio])
+ if not empty:
+ table.AddRow([SubmitButton(addtag, _('Add new item...')),
+ SelectOptions(wheretag, ('before', 'after'),
+ (_('...before this one.'),
+ _('...after this one.')),
+ selected=1),
+ ])
+ # BAW: IWBNI we could disable the up and down buttons for the
+ # first and last item respectively, but it's not easy to know
+ # which is the last item, so let's not worry about that for
+ # now.
+ table.AddRow([SubmitButton(uptag, _('Move rule up')),
+ SubmitButton(downtag, _('Move rule down'))])
+ table.AddRow(['
'])
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2)
+ # Now for each element in the existing data, create a widget
+ i = 1
+ data = getattr(mlist, varname)
+ for pattern, action, empty in data:
+ makebox(i, pattern, action, empty)
+ i += 1
+ # Add one more non-deleteable widget as the first blank entry, but
+ # only if there are no real entries.
+ if i == 1:
+ makebox(i, '', config.DEFER, empty=True)
+ return table
+ elif kind == config.Checkbox:
+ return CheckBoxArray(varname, *params)
+ else:
+ assert 0, 'Bad gui widget type: %s' % kind
+
+
+
+def get_item_gui_description(mlist, category, subcat,
+ varname, descr, elaboration, 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:
+ if subcat:
+ varhelp = '?VARHELP=%s/%s/%s' % (category, subcat, varname)
+ else:
+ varhelp = '?VARHELP=%s/%s' % (category, varname)
+ if descr == elaboration:
+ linktext = _('
(Edit %(varname)s)')
+ else:
+ linktext = _('
(Details for %(varname)s)')
+ link = Link(mlist.GetScriptURL('admin') + varhelp,
+ linktext).Format()
+ text = Label('%s %s' % (descr, link)).Format()
+ else:
+ text = Label(descr).Format()
+ if varname[0] == '_':
+ text += Label(_('''
Note:
+ setting this value performs an immediate action but does not modify
+ permanent state.''')).Format()
+ return text
+
+
+
+def membership_options(mlist, subcat, cgidata, doc, form):
+ # Show the main stuff
+ adminurl = mlist.GetScriptURL('admin')
+ container = Container()
+ header = Table(width="100%")
+ # If we're in the list subcategory, show the membership list
+ if subcat == 'add':
+ header.AddRow([Center(Header(2, _('Mass Subscriptions')))])
+ header.AddCellInfo(header.GetCurrentRowIndex(), 0, colspan=2,
+ bgcolor=config.WEB_HEADER_COLOR)
+ container.AddItem(header)
+ mass_subscribe(mlist, container)
+ return container
+ if subcat == 'remove':
+ header.AddRow([Center(Header(2, _('Mass Removals')))])
+ header.AddCellInfo(header.GetCurrentRowIndex(), 0, colspan=2,
+ bgcolor=config.WEB_HEADER_COLOR)
+ container.AddItem(header)
+ mass_remove(mlist, container)
+ return container
+ # Otherwise...
+ header.AddRow([Center(Header(2, _('Membership List')))])
+ header.AddCellInfo(header.GetCurrentRowIndex(), 0, colspan=2,
+ bgcolor=config.WEB_HEADER_COLOR)
+ container.AddItem(header)
+ # Add a "search for member" button
+ table = Table(width='100%')
+ link = Link('http://www.python.org/doc/current/lib/re-syntax.html',
+ _('(help)')).Format()
+ table.AddRow([Label(_('Find member %(link)s:')),
+ TextBox('findmember',
+ value=cgidata.getvalue('findmember', '')),
+ SubmitButton('findmember_btn', _('Search...'))])
+ container.AddItem(table)
+ container.AddItem('
member name'),
+ _('mod'), _('hide'),
+ _('nomail
[reason]'),
+ _('ack'), _('not metoo'),
+ _('nodupes'),
+ _('digest'), _('plain'),
+ _('language'))])
+ rowindex = usertable.GetCurrentRowIndex()
+ for i in range(OPTCOLUMNS):
+ usertable.AddCellInfo(rowindex, i, bgcolor=config.WEB_ADMINITEM_COLOR)
+ # Find the longest name in the list
+ longest = 0
+ if members:
+ names = filter(None, [mlist.getMemberName(s) for s in members])
+ # Make the name field at least as long as the longest email address
+ longest = max([len(s) for s in names + members])
+ # Abbreviations for delivery status details
+ ds_abbrevs = {MemberAdaptor.UNKNOWN : _('?'),
+ MemberAdaptor.BYUSER : _('U'),
+ MemberAdaptor.BYADMIN : _('A'),
+ MemberAdaptor.BYBOUNCE: _('B'),
+ }
+ # Now populate the rows
+ for addr in members:
+ link = Link(mlist.GetOptionsURL(addr, obscure=1),
+ mlist.getMemberCPAddress(addr))
+ fullname = mlist.getMemberName(addr)
+ name = TextBox(addr + '_realname', fullname, size=longest).Format()
+ cells = [Center(CheckBox(addr + '_unsub', 'off', 0).Format()),
+ link.Format() + '
' +
+ name +
+ Hidden('user', urllib.quote(addr)).Format(),
+ ]
+ # Do the `mod' option
+ if mlist.getMemberOption(addr, config.Moderate):
+ value = 'on'
+ checked = 1
+ else:
+ value = 'off'
+ checked = 0
+ box = CheckBox('%s_mod' % addr, value, checked)
+ cells.append(Center(box).Format())
+ for opt in ('hide', 'nomail', 'ack', 'notmetoo', 'nodupes'):
+ extra = ''
+ if opt == 'nomail':
+ status = mlist.getDeliveryStatus(addr)
+ if status == MemberAdaptor.ENABLED:
+ value = 'off'
+ checked = 0
+ else:
+ value = 'on'
+ checked = 1
+ extra = '[%s]' % ds_abbrevs[status]
+ elif mlist.getMemberOption(addr, config.OPTINFO[opt]):
+ value = 'on'
+ checked = 1
+ else:
+ value = 'off'
+ checked = 0
+ box = CheckBox('%s_%s' % (addr, opt), value, checked)
+ cells.append(Center(box.Format() + extra))
+ # This code is less efficient than the original which did a has_key on
+ # the underlying dictionary attribute. This version is slower and
+ # less memory efficient. It points to a new MemberAdaptor interface
+ # method.
+ if addr in mlist.getRegularMemberKeys():
+ cells.append(Center(CheckBox(addr + '_digest', 'off', 0).Format()))
+ else:
+ cells.append(Center(CheckBox(addr + '_digest', 'on', 1).Format()))
+ if mlist.getMemberOption(addr, config.OPTINFO['plain']):
+ value = 'on'
+ checked = 1
+ else:
+ value = 'off'
+ checked = 0
+ cells.append(Center(CheckBox('%s_plain' % addr, value, checked)))
+ # User's preferred language
+ langpref = mlist.getMemberLanguage(addr)
+ langs = mlist.language_codes
+ langdescs = [_(config.languges.get_description(code))
+ for code in langs]
+ try:
+ selected = langs.index(langpref)
+ except ValueError:
+ selected = 0
+ cells.append(Center(SelectOptions(addr + '_language', langs,
+ langdescs, selected)).Format())
+ usertable.AddRow(cells)
+ # Add the usertable and a legend
+ legend = UnorderedList()
+ legend.AddItem(
+ _('unsub -- Click on this to unsubscribe the member.'))
+ legend.AddItem(
+ _("""mod -- The user's personal moderation flag. If this is
+ set, postings from them will be moderated, otherwise they will be
+ approved."""))
+ legend.AddItem(
+ _("""hide -- Is the member's address concealed on
+ the list of subscribers?"""))
+ legend.AddItem(_(
+ """nomail -- Is delivery to the member disabled? If so, an
+ abbreviation will be given describing the reason for the disabled
+ delivery:
+
"""))
+ legend.AddItem(
+ _('''ack -- Does the member get acknowledgements of their
+ posts?'''))
+ legend.AddItem(
+ _('''not metoo -- Does the member want to avoid copies of their
+ own postings?'''))
+ legend.AddItem(
+ _('''nodupes -- Does the member want to avoid duplicates of the
+ same message?'''))
+ 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"))
+ addlegend = ''
+ parsedqs = 0
+ qsenviron = os.environ.get('QUERY_STRING')
+ if qsenviron:
+ qs = cgi.parse_qs(qsenviron).get('legend')
+ if qs and isinstance(qs, list):
+ qs = qs[0]
+ if qs == 'yes':
+ addlegend = 'legend=yes&'
+ if addlegend:
+ container.AddItem(legend.Format() + '
')
+ if not (details or sender or msgid or nomessages):
+ form.AddItem(Center(
+ CheckBox('discardalldefersp', 0).Format() +
+ ' ' +
+ _('Discard all messages marked Defer')
+ ))
+ form.AddItem(Center(SubmitButton('submit', _('Submit All Data'))))
+ doc.AddItem(mlist.GetMailmanFooter())
+ print doc.Format()
+ # Commit all changes
+ mlist.Save()
+ finally:
+ mlist.Unlock()
+
+
+
+def handle_no_list(msg=''):
+ # Print something useful if no list was given.
+ doc = Document()
+ doc.set_language(config.DEFAULT_SERVER_LANGUAGE)
+
+ header = _('Mailman Administrative Database Error')
+ doc.SetTitle(header)
+ doc.AddItem(Header(2, header))
+ doc.AddItem(msg)
+ url = Utils.ScriptURL('admin')
+ link = Link(url, _('list of available mailing lists.')).Format()
+ doc.AddItem(_('You must specify a list name. Here is the %(link)s'))
+ doc.AddItem('
')
+ doc.AddItem(MailmanLogo())
+ print doc.Format()
+
+
+
+def show_pending_subs(mlist, form):
+ # Add the subscription request section
+ requestsdb = config.db.get_list_requests(mlist)
+ if requestsdb.count_of(RequestType.subscription) == 0:
+ return 0
+ form.AddItem('
')
+ form.AddItem(Center(Header(2, _('Subscription Requests'))))
+ table = Table(border=2)
+ table.AddRow([Center(Bold(_('Address/name'))),
+ Center(Bold(_('Your decision'))),
+ Center(Bold(_('Reason for refusal')))
+ ])
+ # Alphabetical order by email address
+ byaddrs = {}
+ for request in requestsdb.of_type(RequestType.subscription):
+ key, data = requestsdb.get_request(requst.id)
+ addr = data['addr']
+ byaddrs.setdefault(addr, []).append(request.id)
+ addrs = sorted(byaddrs)
+ num = 0
+ for addr, ids in byaddrs.items():
+ # Eliminate duplicates
+ for id in ids[1:]:
+ mlist.HandleRequest(id, config.DISCARD)
+ id = ids[0]
+ key, data = requestsdb.get_request(id)
+ time = data['time']
+ addr = data['addr']
+ fullname = data['fullname']
+ passwd = data['passwd']
+ digest = data['digest']
+ lang = data['lang']
+ fullname = Utils.uncanonstr(fullname, mlist.preferred_language)
+ radio = RadioButtonArray(id, (_('Defer'),
+ _('Approve'),
+ _('Reject'),
+ _('Discard')),
+ values=(config.DEFER,
+ config.SUBSCRIBE,
+ config.REJECT,
+ config.DISCARD),
+ checked=0).Format()
+ if addr not in mlist.ban_list:
+ radio += '
' + CheckBox('ban-%d' % id, 1).Format() + \
+ ' ' + _('Permanently ban from this list')
+ # While the address may be a unicode, it must be ascii
+ paddr = addr.encode('us-ascii', 'replace')
+ table.AddRow(['%s
%s' % (paddr, fullname),
+ radio,
+ TextBox('comment-%d' % id, size=40)
+ ])
+ num += 1
+ if num > 0:
+ form.AddItem(table)
+ return num
+
+
+
+def show_pending_unsubs(mlist, form):
+ # Add the pending unsubscription request section
+ lang = mlist.preferred_language
+ requestsdb = config.db.get_list_requests(mlist)
+ if requestsdb.count_of(RequestType.unsubscription) == 0:
+ return 0
+ table = Table(border=2)
+ table.AddRow([Center(Bold(_('User address/name'))),
+ Center(Bold(_('Your decision'))),
+ Center(Bold(_('Reason for refusal')))
+ ])
+ # Alphabetical order by email address
+ byaddrs = {}
+ for request in requestsdb.of_type(RequestType.unsubscription):
+ key, data = requestsdb.get_request(request.id)
+ addr = data['addr']
+ byaddrs.setdefault(addr, []).append(request.id)
+ addrs = sorted(byaddrs)
+ num = 0
+ for addr, ids in byaddrs.items():
+ # Eliminate duplicates
+ for id in ids[1:]:
+ mlist.HandleRequest(id, config.DISCARD)
+ id = ids[0]
+ key, data = requestsdb.get_record(id)
+ addr = data['addr']
+ try:
+ fullname = Utils.uncanonstr(mlist.getMemberName(addr), lang)
+ except Errors.NotAMemberError:
+ # They must have been unsubscribed elsewhere, so we can just
+ # discard this record.
+ mlist.HandleRequest(id, config.DISCARD)
+ continue
+ num += 1
+ table.AddRow(['%s
%s' % (addr, fullname),
+ RadioButtonArray(id, (_('Defer'),
+ _('Approve'),
+ _('Reject'),
+ _('Discard')),
+ values=(config.DEFER,
+ config.UNSUBSCRIBE,
+ config.REJECT,
+ config.DISCARD),
+ checked=0),
+ TextBox('comment-%d' % id, size=45)
+ ])
+ if num > 0:
+ form.AddItem('
')
+ form.AddItem(Center(Header(2, _('Unsubscription Requests'))))
+ form.AddItem(table)
+ return num
+
+
+
+def show_helds_overview(mlist, form):
+ # Sort the held messages by sender
+ bysender = helds_by_sender(mlist)
+ if not bysender:
+ return 0
+ form.AddItem('
')
+ form.AddItem(Center(Header(2, _('Held Messages'))))
+ # Add the by-sender overview tables
+ admindburl = mlist.GetScriptURL('admindb')
+ table = Table(border=0)
+ form.AddItem(table)
+ senders = bysender.keys()
+ senders.sort()
+ for sender in senders:
+ qsender = quote_plus(sender)
+ esender = Utils.websafe(sender)
+ senderurl = admindburl + '?sender=' + qsender
+ # The encompassing sender table
+ stable = Table(border=1)
+ stable.AddRow([Center(Bold(_('From:')).Format() + esender)])
+ stable.AddCellInfo(stable.GetCurrentRowIndex(), 0, colspan=2)
+ left = Table(border=0)
+ left.AddRow([_('Action to take on all these held messages:')])
+ left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2)
+ btns = hacky_radio_buttons(
+ 'senderaction-' + qsender,
+ (_('Defer'), _('Accept'), _('Reject'), _('Discard')),
+ (config.DEFER, config.APPROVE, config.REJECT, config.DISCARD),
+ (1, 0, 0, 0))
+ left.AddRow([btns])
+ left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2)
+ left.AddRow([
+ CheckBox('senderpreserve-' + qsender, 1).Format() +
+ ' ' +
+ _('Preserve messages for the site administrator')
+ ])
+ left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2)
+ left.AddRow([
+ CheckBox('senderforward-' + qsender, 1).Format() +
+ ' ' +
+ _('Forward messages (individually) to:')
+ ])
+ left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2)
+ left.AddRow([
+ TextBox('senderforwardto-' + qsender,
+ value=mlist.GetOwnerEmail())
+ ])
+ left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2)
+ # If the sender is a member and the message is being held due to a
+ # moderation bit, give the admin a chance to clear the member's mod
+ # bit. If this sender is not a member and is not already on one of
+ # the sender filters, then give the admin a chance to add this sender
+ # to one of the filters.
+ if mlist.isMember(sender):
+ if mlist.getMemberOption(sender, config.Moderate):
+ left.AddRow([
+ CheckBox('senderclearmodp-' + qsender, 1).Format() +
+ ' ' +
+ _("Clear this member's moderate flag")
+ ])
+ else:
+ left.AddRow(
+ [_('The sender is now a member of this list')])
+ left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2)
+ elif sender not in (mlist.accept_these_nonmembers +
+ mlist.hold_these_nonmembers +
+ mlist.reject_these_nonmembers +
+ mlist.discard_these_nonmembers):
+ left.AddRow([
+ CheckBox('senderfilterp-' + qsender, 1).Format() +
+ ' ' +
+ _('Add %(esender)s to one of these sender filters:')
+ ])
+ left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2)
+ btns = hacky_radio_buttons(
+ 'senderfilter-' + qsender,
+ (_('Accepts'), _('Holds'), _('Rejects'), _('Discards')),
+ (config.ACCEPT, config.HOLD, config.REJECT, config.DISCARD),
+ (0, 0, 0, 1))
+ left.AddRow([btns])
+ left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2)
+ if sender not in mlist.ban_list:
+ left.AddRow([
+ CheckBox('senderbanp-' + qsender, 1).Format() +
+ ' ' +
+ _("""Ban %(esender)s from ever subscribing to this
+ mailing list""")])
+ left.AddCellInfo(left.GetCurrentRowIndex(), 0, colspan=2)
+ right = Table(border=0)
+ right.AddRow([
+ _("""Click on the message number to view the individual
+ message, or you can """) +
+ Link(senderurl, _('view all messages from %(esender)s')).Format()
+ ])
+ right.AddCellInfo(right.GetCurrentRowIndex(), 0, colspan=2)
+ right.AddRow([' ', ' '])
+ counter = 1
+ for id in bysender[sender]:
+ key, data = requestsdb.get_record(id)
+ ptime = data['ptime']
+ sender = data['sender']
+ subject = data['subject']
+ reason = data['reason']
+ filename = data['filename']
+ msgdata = data['msgdata']
+ # BAW: This is really the size of the message pickle, which should
+ # be close, but won't be exact. Sigh, good enough.
+ try:
+ size = os.path.getsize(os.path.join(config.DATA_DIR, filename))
+ except OSError, e:
+ if e.errno <> errno.ENOENT: raise
+ # This message must have gotten lost, i.e. it's already been
+ # handled by the time we got here.
+ mlist.HandleRequest(id, config.DISCARD)
+ continue
+ dispsubj = Utils.oneline(
+ subject, Utils.GetCharSet(mlist.preferred_language))
+ t = Table(border=0)
+ t.AddRow([Link(admindburl + '?msgid=%d' % id, '[%d]' % counter),
+ Bold(_('Subject:')),
+ Utils.websafe(dispsubj)
+ ])
+ t.AddRow([' ', Bold(_('Size:')), str(size) + _(' bytes')])
+ if reason:
+ reason = _(reason)
+ else:
+ reason = _('not available')
+ t.AddRow([' ', Bold(_('Reason:')), reason])
+ # Include the date we received the message, if available
+ when = msgdata.get('received_time')
+ if when:
+ t.AddRow([' ', Bold(_('Received:')),
+ time.ctime(when)])
+ counter += 1
+ right.AddRow([t])
+ stable.AddRow([left, right])
+ table.AddRow([stable])
+ return 1
+
+
+
+def show_sender_requests(mlist, form, sender):
+ bysender = helds_by_sender(mlist)
+ if not bysender:
+ return
+ sender_ids = bysender.get(sender)
+ if sender_ids is None:
+ # BAW: should we print an error message?
+ return
+ total = len(sender_ids)
+ requestsdb = config.db.get_list_requests(mlist)
+ for i, id in enumerate(sender_ids):
+ key, data = requestsdb.get_record(id)
+ show_post_requests(mlist, id, data, total, count + 1, form)
+
+
+
+def show_message_requests(mlist, form, id):
+ requestdb = config.db.get_list_requests(mlist)
+ try:
+ id = int(id)
+ info = requestdb.get_record(id)
+ except (ValueError, KeyError):
+ # BAW: print an error message?
+ return
+ show_post_requests(mlist, id, info, 1, 1, form)
+
+
+
+def show_detailed_requests(mlist, form):
+ requestsdb = config.db.get_list_requests(mlist)
+ total = requestsdb.count_of(RequestType.held_message)
+ all = requestsdb.of_type(RequestType.held_message)
+ for i, request in enumerate(all):
+ key, data = requestdb.get_request(request.id)
+ show_post_requests(mlist, request.id, data, total, i + 1, form)
+
+
+
+def show_post_requests(mlist, id, info, total, count, form):
+ # For backwards compatibility with pre 2.0beta3
+ if len(info) == 5:
+ ptime, sender, subject, reason, filename = info
+ msgdata = {}
+ else:
+ ptime, sender, subject, reason, filename, msgdata = info
+ form.AddItem('
')
+ # Header shown on each held posting (including count of total)
+ msg = _('Posting Held for Approval')
+ if total <> 1:
+ msg += _(' (%(count)d of %(total)d)')
+ form.AddItem(Center(Header(2, msg)))
+ # We need to get the headers and part of the textual body of the message
+ # being held. The best way to do this is to use the email Parser to get
+ # an actual object, which will be easier to deal with. We probably could
+ # just do raw reads on the file.
+ try:
+ msg = readMessage(os.path.join(config.DATA_DIR, filename))
+ except IOError, e:
+ if e.errno <> errno.ENOENT:
+ raise
+ form.AddItem(_('Message with id #%(id)d was lost.'))
+ form.AddItem('
please explain (optional):')),
+ TextArea('comment-%d' % id, rows=4, cols=EXCERPT_WIDTH,
+ text = Utils.wrap(_(notice), column=80))
+ ])
+ row, col = t.GetCurrentRowIndex(), t.GetCurrentCellIndex()
+ t.AddCellInfo(row, col-1, align='right')
+ t.AddRow([Bold(_('Message Headers:')),
+ TextArea('headers-%d' % id, hdrtxt,
+ rows=EXCERPT_HEIGHT, cols=EXCERPT_WIDTH, readonly=1)])
+ row, col = t.GetCurrentRowIndex(), t.GetCurrentCellIndex()
+ t.AddCellInfo(row, col-1, align='right')
+ t.AddRow([Bold(_('Message Excerpt:')),
+ TextArea('fulltext-%d' % id, Utils.websafe(body),
+ rows=EXCERPT_HEIGHT, cols=EXCERPT_WIDTH, readonly=1)])
+ t.AddCellInfo(row+1, col-1, align='right')
+ form.AddItem(t)
+ form.AddItem('
')
+ if banaddrs:
+ for addr, patt in banaddrs:
+ doc.AddItem(_('%(addr)s is banned (matched: %(patt)s)') + '
')
diff --git a/src/web/Cgi/confirm.py b/src/web/Cgi/confirm.py
new file mode 100644
index 000000000..188e43068
--- /dev/null
+++ b/src/web/Cgi/confirm.py
@@ -0,0 +1,834 @@
+# Copyright (C) 2001-2009 by the Free Software Foundation, Inc.
+#
+# This file is part of GNU Mailman.
+#
+# GNU Mailman 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 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Mailman 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
+# GNU Mailman. If not, see
'
+ if mlist.subscribe_policy in (2, 3):
+ # Confirmation is required
+ result = _("""Your confirmation is required in order to continue with
+ the subscription request to the mailing list %(listname)s.
+ Your subscription settings are shown below; make any necessary changes
+ and hit Subscribe to list ... to complete the confirmation
+ process. Once you've confirmed your subscription request, the
+ moderator must approve or reject your membership request. You will
+ receive notice of their decision.
+
+
'
+ table.AddRow([result])
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2)
+
+ table.AddRow([Label(_('Your email address:')), email])
+ table.AddRow([Label(_('Your real name:')),
+ TextBox('realname', name)])
+## table.AddRow([Label(_('Password:')),
+## PasswordBox('password', password)])
+## table.AddRow([Label(_('Password (confirm):')),
+## PasswordBox('pwconfirm', password)])
+ # Only give them a choice to receive digests if they actually have a
+ # choice
+
+ Hit the Unsubscribe button below to complete the confirmation
+ process.
+
+
'])
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2)
+ table.AddRow([Hidden('cookie', cookie)])
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2)
+ table.AddRow([SubmitButton('submit', _('Unsubscribe')),
+ SubmitButton('cancel', _('Cancel and discard'))])
+
+ form.AddItem(table)
+ doc.AddItem(form)
+
+
+
+def addrchange_cancel(mlist, doc, cookie):
+ # Expunge this record from the pending database
+ expunge(mlist, cookie)
+ doc.AddItem(_('You have canceled your change of address request.'))
+
+
+
+def addrchange_confirm(mlist, doc, cookie):
+ # See the comment in admin.py about the need for the signal
+ # handler.
+ def sigterm_handler(signum, frame, mlist=mlist):
+ mlist.Unlock()
+ sys.exit(0)
+
+ mlist.Lock()
+ try:
+ try:
+ # Do this in two steps so we can get the preferred language for
+ # the user who is unsubscribing.
+ op, oldaddr, newaddr, globally = mlist.pend_confirm(
+ cookie, expunge=False)
+ lang = mlist.getMemberLanguage(oldaddr)
+ i18n.set_language(lang)
+ doc.set_language(lang)
+ op, oldaddr, newaddr = mlist.ProcessConfirmation(cookie)
+ except Errors.NotAMemberError:
+ bad_confirmation(doc, _('''Invalid confirmation string. It is
+ possible that you are attempting to confirm a request for an
+ address that has already been unsubscribed.'''))
+ except Errors.MembershipIsBanned:
+ owneraddr = mlist.GetOwnerEmail()
+ realname = mlist.real_name
+ doc.addError(_("""%(newaddr)s is banned from subscribing to the
+ %(realname)s list. If you think this restriction is erroneous,
+ please contact the list owners at %(owneraddr)s."""))
+ else:
+ # The response
+ listname = mlist.real_name
+ title = _('Change of address request confirmed')
+ optionsurl = mlist.GetOptionsURL(newaddr)
+ doc.SetTitle(title)
+ doc.AddItem(Header(3, Bold(FontAttr(title, size='+2'))))
+ doc.AddItem(_("""\
+ You have successfully changed your address on the %(listname)s
+ mailing list from %(oldaddr)s to %(newaddr)s. You
+ can now proceed to your membership
+ login page."""))
+ mlist.Save()
+ finally:
+ mlist.Unlock()
+
+
+
+def addrchange_prompt(mlist, doc, cookie, oldaddr, newaddr, globally):
+ title = _('Confirm change of address request')
+ doc.SetTitle(title)
+ lang = mlist.getMemberLanguage(oldaddr)
+ i18n.set_language(lang)
+ doc.set_language(lang)
+
+ form = Form(mlist.GetScriptURL('confirm'))
+ table = Table(border=0, width='100%')
+ table.AddRow([Center(Bold(FontAttr(title, size='+1')))])
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0,
+ colspan=2, bgcolor=config.WEB_HEADER_COLOR)
+
+ listname = mlist.real_name
+ fullname = mlist.getMemberName(oldaddr)
+ if fullname is None:
+ fullname = _('Not available')
+ else:
+ fullname = Utils.uncanonstr(fullname, lang)
+ if globally:
+ globallys = _('globally')
+ else:
+ globallys = ''
+ table.AddRow([_("""Your confirmation is required in order to complete the
+ change of address request for the mailing list %(listname)s. You
+ are currently subscribed with
+
+
+
+ and you have requested to %(globallys)s change your email address to
+
+
+
+ Hit the Change address button below to complete the confirmation
+ process.
+
+
'])
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2)
+ table.AddRow([Hidden('cookie', cookie)])
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2)
+ table.AddRow([SubmitButton('submit', _('Change address')),
+ SubmitButton('cancel', _('Cancel and discard'))])
+
+ form.AddItem(table)
+ doc.AddItem(form)
+
+
+
+def heldmsg_cancel(mlist, doc, cookie):
+ title = _('Continue awaiting approval')
+ doc.SetTitle(title)
+ table = Table(border=0, width='100%')
+ table.AddRow([Center(Bold(FontAttr(title, size='+1')))])
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0,
+ bgcolor=config.WEB_HEADER_COLOR)
+ # Expunge this record from the pending database.
+ expunge(mlist, cookie)
+ table.AddRow([_('''Okay, the list moderator will still have the
+ opportunity to approve or reject this message.''')])
+ doc.AddItem(table)
+
+
+
+def heldmsg_confirm(mlist, doc, cookie):
+ # See the comment in admin.py about the need for the signal
+ # handler.
+ def sigterm_handler(signum, frame, mlist=mlist):
+ mlist.Unlock()
+ sys.exit(0)
+
+ mlist.Lock()
+ try:
+ try:
+ # Do this in two steps so we can get the preferred language for
+ # the user who posted the message.
+ op, id = mlist.pend_confirm(cookie)
+ requestsdb = config.db.get_list_requests(mlist)
+ key, data = requestsdb.get_record(id)
+ sender = data['sender']
+ msgsubject = data['msgsubject']
+ subject = Utils.websafe(msgsubject)
+ lang = mlist.getMemberLanguage(sender)
+ i18n.set_language(lang)
+ doc.set_language(lang)
+ # Discard the message
+ mlist.HandleRequest(id, config.DISCARD,
+ _('Sender discarded message via web.'))
+ except Errors.LostHeldMessage:
+ bad_confirmation(doc, _('''The held message with the Subject:
+ header %(subject)s could not be found. The most likely
+ reason for this is that the list moderator has already approved or
+ rejected the message. You were not able to cancel it in
+ time.'''))
+ else:
+ # The response
+ listname = mlist.real_name
+ title = _('Posted message canceled')
+ doc.SetTitle(title)
+ doc.AddItem(Header(3, Bold(FontAttr(title, size='+2'))))
+ doc.AddItem(_('''\
+ You have successfully canceled the posting of your message with
+ the Subject: header %(subject)s to the mailing list
+ %(listname)s.'''))
+ mlist.Save()
+ finally:
+ mlist.Unlock()
+
+
+
+def heldmsg_prompt(mlist, doc, cookie, id):
+ title = _('Cancel held message posting')
+ doc.SetTitle(title)
+ form = Form(mlist.GetScriptURL('confirm'))
+ table = Table(border=0, width='100%')
+ table.AddRow([Center(Bold(FontAttr(title, size='+1')))])
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0,
+ colspan=2, bgcolor=config.WEB_HEADER_COLOR)
+ # Blarg. The list must be locked in order to interact with the ListAdmin
+ # database, even for read-only. See the comment in admin.py about the
+ # need for the signal handler.
+ def sigterm_handler(signum, frame, mlist=mlist):
+ mlist.Unlock()
+ sys.exit(0)
+ # Get the record, but watch for KeyErrors which mean the admin has already
+ # disposed of this message.
+ mlist.Lock()
+ requestdb = config.db.get_list_requests(mlist)
+ try:
+ try:
+ key, data = requestdb.get_record(id)
+ except KeyError:
+ data = None
+ finally:
+ mlist.Unlock()
+
+ if data is None:
+ bad_confirmation(doc, _("""The held message you were referred to has
+ already been handled by the list administrator."""))
+ return
+
+ # Unpack the data and present the confirmation message
+ ign, sender, msgsubject, givenreason, ign, ign = data
+ # Now set the language to the sender's preferred.
+ lang = mlist.getMemberLanguage(sender)
+ i18n.set_language(lang)
+ doc.set_language(lang)
+
+ subject = Utils.websafe(msgsubject)
+ reason = Utils.websafe(_(givenreason))
+ listname = mlist.real_name
+ table.AddRow([_('''Your confirmation is required in order to cancel the
+ posting of your message to the mailing list %(listname)s:
+
+
+
+ Hit the Cancel posting button to discard the posting.
+
+
'])
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2)
+ table.AddRow([Hidden('cookie', cookie)])
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2)
+ table.AddRow([SubmitButton('submit', _('Cancel posting')),
+ SubmitButton('cancel', _('Continue awaiting approval'))])
+
+ form.AddItem(table)
+ doc.AddItem(form)
+
+
+
+def reenable_cancel(mlist, doc, cookie):
+ # Don't actually discard this cookie, since the user may decide to
+ # re-enable their membership at a future time, and we may be sending out
+ # future notifications with this cookie value.
+ doc.AddItem(_("""You have canceled the re-enabling of your membership. If
+ we continue to receive bounces from your address, it could be deleted from
+ this mailing list."""))
+
+
+
+def reenable_confirm(mlist, doc, cookie):
+ # See the comment in admin.py about the need for the signal
+ # handler.
+ def sigterm_handler(signum, frame, mlist=mlist):
+ mlist.Unlock()
+ sys.exit(0)
+
+ mlist.Lock()
+ try:
+ try:
+ # Do this in two steps so we can get the preferred language for
+ # the user who is unsubscribing.
+ op, listname, addr = mlist.pend_confirm(cookie, expunge=False)
+ lang = mlist.getMemberLanguage(addr)
+ i18n.set_language(lang)
+ doc.set_language(lang)
+ op, addr = mlist.ProcessConfirmation(cookie)
+ except Errors.NotAMemberError:
+ bad_confirmation(doc, _('''Invalid confirmation string. It is
+ possible that you are attempting to confirm a request for an
+ address that has already been unsubscribed.'''))
+ else:
+ # The response
+ listname = mlist.real_name
+ title = _('Membership re-enabled.')
+ optionsurl = mlist.GetOptionsURL(addr)
+ doc.SetTitle(title)
+ doc.AddItem(Header(3, Bold(FontAttr(title, size='+2'))))
+ doc.AddItem(_("""\
+ You have successfully re-enabled your membership in the
+ %(listname)s mailing list. You can now visit your member options page.
+ """))
+ mlist.Save()
+ finally:
+ mlist.Unlock()
+
+
+
+def reenable_prompt(mlist, doc, cookie, list, member):
+ title = _('Re-enable mailing list membership')
+ doc.SetTitle(title)
+ form = Form(mlist.GetScriptURL('confirm'))
+ table = Table(border=0, width='100%')
+ table.AddRow([Center(Bold(FontAttr(title, size='+1')))])
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0,
+ colspan=2, bgcolor=config.WEB_HEADER_COLOR)
+
+ lang = mlist.getMemberLanguage(member)
+ i18n.set_language(lang)
+ doc.set_language(lang)
+
+ realname = mlist.real_name
+ info = mlist.getBounceInfo(member)
+ if not info:
+ listinfourl = mlist.GetScriptURL('listinfo')
+ # They've already be unsubscribed
+ table.AddRow([_("""We're sorry, but you have already been unsubscribed
+ from this mailing list. To re-subscribe, please visit the
+ list information page.""")])
+ return
+
+ date = time.strftime('%A, %B %d, %Y',
+ time.localtime(time.mktime(info.date + (0,)*6)))
+ daysleft = int(info.noticesleft *
+ mlist.bounce_you_are_disabled_warnings_interval /
+ config.days(1))
+ # BAW: for consistency this should be changed to 'fullname' or the above
+ # 'fullname's should be changed to 'username'. Don't want to muck with
+ # the i18n catalogs though.
+ username = mlist.getMemberName(member)
+ if username is None:
+ username = _('not available')
+ else:
+ username = Utils.uncanonstr(username, lang)
+
+ table.AddRow([_("""Your membership in the %(realname)s mailing list is
+ currently disabled due to excessive bounces. Your confirmation is
+ required in order to re-enable delivery to your address. We have the
+ following information on file:
+
+
+
+ Hit the Re-enable membership button to resume receiving postings
+ from the mailing list. Or hit the Cancel button to defer
+ re-enabling your membership.
+ """)])
+
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2)
+ table.AddRow([Hidden('cookie', cookie)])
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2)
+ table.AddRow([SubmitButton('submit', _('Re-enable membership')),
+ SubmitButton('cancel', _('Cancel'))])
+
+ form.AddItem(table)
+ doc.AddItem(form)
diff --git a/src/web/Cgi/create.py b/src/web/Cgi/create.py
new file mode 100644
index 000000000..4bb650957
--- /dev/null
+++ b/src/web/Cgi/create.py
@@ -0,0 +1,400 @@
+# Copyright (C) 2001-2009 by the Free Software Foundation, Inc.
+#
+# This file is part of GNU Mailman.
+#
+# GNU Mailman 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 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Mailman 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
+# GNU Mailman. If not, see
')
+ # Always add the footer and print the document
+ doc.AddItem(_('Return to the ') +
+ Link(Utils.ScriptURL('listinfo'),
+ _('general list overview')).Format())
+ doc.AddItem(_('
Return to the ') +
+ Link(Utils.ScriptURL('admin'),
+ _('administrative list overview')).Format())
+ doc.AddItem(MailmanLogo())
+ print doc.Format()
+
+
+
+def process_request(doc, cgidata):
+ # Lowercase the listname since this is treated as the 'internal' name.
+ listname = cgidata.getvalue('listname', '').strip().lower()
+ owner = cgidata.getvalue('owner', '').strip()
+ try:
+ autogen = bool(int(cgidata.getvalue('autogen', '0')))
+ except ValueError:
+ autogen = False
+ try:
+ notify = bool(int(cgidata.getvalue('notify', '0')))
+ except ValueError:
+ notify = False
+ try:
+ moderate = bool(int(cgidata.getvalue('moderate',
+ config.DEFAULT_DEFAULT_MEMBER_MODERATION)))
+ except ValueError:
+ moderate = config.DEFAULT_DEFAULT_MEMBER_MODERATION
+
+ password = cgidata.getvalue('password', '').strip()
+ confirm = cgidata.getvalue('confirm', '').strip()
+ auth = cgidata.getvalue('auth', '').strip()
+ langs = cgidata.getvalue('langs', [config.DEFAULT_SERVER_LANGUAGE])
+
+ if not isinstance(langs, list):
+ langs = [langs]
+ # Sanity checks
+ safelistname = Utils.websafe(listname)
+ if '@' in listname:
+ request_creation(doc, cgidata,
+ _('List name must not include "@": $safelistname'))
+ return
+ if not listname:
+ request_creation(doc, cgidata,
+ _('You forgot to enter the list name'))
+ return
+ if not owner:
+ request_creation(doc, cgidata,
+ _('You forgot to specify the list owner'))
+ return
+ if autogen:
+ if password or confirm:
+ request_creation(
+ doc, cgidata,
+ _("""Leave the initial password (and confirmation) fields
+ blank if you want Mailman to autogenerate the list
+ passwords."""))
+ return
+ password = confirm = Utils.MakeRandomPassword(
+ config.ADMIN_PASSWORD_LENGTH)
+ else:
+ if password <> confirm:
+ request_creation(doc, cgidata,
+ _('Initial list passwords do not match'))
+ return
+ if not password:
+ request_creation(
+ doc, cgidata,
+ # The little tag is used so that this string
+ # differs from the one in bin/newlist. The former is destined
+ # for the web while the latter is destined for email, so they
+ # must be different entries in the message catalog.
+ _('The list password cannot be empty'))
+ return
+ # The authorization password must be non-empty, and it must match either
+ # the list creation password or the site admin password
+ ok = False
+ if auth:
+ ok = Utils.check_global_password(auth, False)
+ if not ok:
+ ok = Utils.check_global_password(auth)
+ if not ok:
+ request_creation(
+ doc, cgidata,
+ _('You are not authorized to create new mailing lists'))
+ return
+ # Make sure the url host name matches one of our virtual domains. Then
+ # calculate the list's posting address.
+ url_host = Utils.get_request_domain()
+ # Find the IDomain matching this url_host if there is one.
+ for email_host, domain in config.domains:
+ if domain.url_host == url_host:
+ email_host = domain.email_host
+ break
+ else:
+ safehostname = Utils.websafe(url_host)
+ request_creation(doc, cgidata,
+ _('Unknown virtual host: $safehostname'))
+ return
+ fqdn_listname = '%s@%s' % (listname, email_host)
+ # We've got all the data we need, so go ahead and try to create the list
+ mlist = MailList.MailList()
+ try:
+ scheme = passwords.lookup_scheme(config.PASSWORD_SCHEME.lower())
+ pw = passwords.make_secret(password, scheme)
+ try:
+ mlist.Create(fqdn_listname, owner, pw, langs)
+ except Errors.EmailAddressError, s:
+ request_creation(doc, cgidata, _('Bad owner email address: $s'))
+ return
+ except Errors.MMListAlreadyExistsError:
+ safelistname = Utils.websafe(listname)
+ request_creation(doc, cgidata,
+ _('List already exists: $safelistname'))
+ return
+ except Errors.InvalidEmailAddress, s:
+ request_creation(doc, cgidata, _('Illegal list name: $s'))
+ return
+ except Errors.MMListError:
+ request_creation(
+ doc, cgidata,
+ _("""Some unknown error occurred while creating the list.
+ Please contact the site administrator for assistance."""))
+ return
+ # Initialize the host_name and web_page_url attributes, based on
+ # virtual hosting settings and the request environment variables.
+ mlist.default_member_moderation = moderate
+ mlist.Save()
+ finally:
+ mlist.Unlock()
+ # Now do the MTA-specific list creation tasks
+ if config.MTA:
+ modname = 'Mailman.MTA.' + config.MTA
+ __import__(modname)
+ sys.modules[modname].create(mlist, cgi=True)
+ # And send the notice to the list owner.
+ if notify:
+ text = Utils.maketext(
+ 'newlist.txt',
+ {'listname' : listname,
+ 'password' : password,
+ 'admin_url' : mlist.GetScriptURL('admin', absolute=True),
+ 'listinfo_url': mlist.GetScriptURL('listinfo', absolute=True),
+ 'requestaddr' : mlist.GetRequestEmail(),
+ 'siteowner' : mlist.no_reply_address,
+ }, mlist=mlist)
+ msg = Message.UserNotification(
+ owner, mlist.no_reply_address,
+ _('Your new mailing list: $listname'),
+ text, mlist.preferred_language)
+ msg.send(mlist)
+ # Success!
+ listinfo_url = mlist.GetScriptURL('listinfo')
+ admin_url = mlist.GetScriptURL('admin')
+ create_url = Utils.ScriptURL('create')
+
+ title = _('Mailing list creation results')
+ doc.SetTitle(title)
+ table = Table(border=0, width='100%')
+ table.AddRow([Center(Bold(FontAttr(title, size='+1')))])
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0,
+ bgcolor=config.WEB_HEADER_COLOR)
+ table.AddRow([_("""You have successfully created the mailing list
+ $listname and notification has been sent to the list owner
+ $owner. You can now:""")])
+ ullist = UnorderedList()
+ ullist.AddItem(Link(listinfo_url, _("Visit the list's info page")))
+ ullist.AddItem(Link(admin_url, _("Visit the list's admin page")))
+ ullist.AddItem(Link(create_url, _('Create another list')))
+ table.AddRow([ullist])
+ doc.AddItem(table)
+
+
+
+# Because the cgi module blows
+class Dummy:
+ def getvalue(self, name, default):
+ return default
+dummy = Dummy()
+
+
+
+def request_creation(doc, cgidata=dummy, errmsg=None):
+ # What virtual domain are we using?
+ hostname = Utils.get_request_domain()
+ # Set up the document
+ title = _('Create a $hostname Mailing List')
+ doc.SetTitle(title)
+ table = Table(border=0, width='100%')
+ table.AddRow([Center(Bold(FontAttr(title, size='+1')))])
+ table.AddCellInfo(table.GetCurrentRowIndex(), 0,
+ bgcolor=config.WEB_HEADER_COLOR)
+ # Add any error message
+ if errmsg:
+ table.AddRow([Header(3, Bold(
+ FontAttr(_('Error: '), color='#ff0000', size='+2').Format() +
+ Italic(errmsg).Format()))])
+ table.AddRow([_("""You can create a new mailing list by entering the
+ relevant information into the form below. The name of the mailing list
+ will be used as the primary address for posting messages to the list, so
+ it should be lowercased. You will not be able to change this once the
+ list is created.
+
+
'])
+ ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2)
+ ftable.AddRow([Label(_("List creator's (authentication) password:")),
+ PasswordBox('auth')])
+ ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, bgcolor=GREY)
+ ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY)
+
+ ftable.AddRow([Center(SubmitButton('doit', _('Create List'))),
+ Center(SubmitButton('clear', _('Clear Form')))])
+ form.AddItem(ftable)
+ table.AddRow([form])
+ doc.AddItem(table)
diff --git a/src/web/Cgi/edithtml.py b/src/web/Cgi/edithtml.py
new file mode 100644
index 000000000..dfc871ec1
--- /dev/null
+++ b/src/web/Cgi/edithtml.py
@@ -0,0 +1,175 @@
+# Copyright (C) 1998-2009 by the Free Software Foundation, Inc.
+#
+# This file is part of GNU Mailman.
+#
+# GNU Mailman 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 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Mailman 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
+# GNU Mailman. If not, see
')
+
+ link = Link(mlist.GetScriptURL('admin'),
+ _('View or edit the list configuration information.'))
+
+ doc.AddItem(FontSize("+1", link))
+ doc.AddItem('
')
+ form = Form(mlist.GetScriptURL('edithtml') + '/' + template_name)
+ text = Utils.websafe(Utils.maketext(template_name, raw=1, mlist=mlist))
+ form.AddItem(TextArea('html_code', text, rows=40, cols=75))
+ form.AddItem('
')
+ return
+ code = cgi_info['html_code'].value
+ code = re.sub(r'<([/]?script.*?)>', r'<\1>', code)
+ langdir = os.path.join(mlist.fullpath(), mlist.preferred_language)
+ # Make sure the directory exists
+ Utils.makedirs(langdir)
+ fp = open(os.path.join(langdir, template_name), 'w')
+ try:
+ fp.write(code)
+ finally:
+ fp.close()
+ doc.AddItem(Header(3, _('HTML successfully updated.')))
+ doc.AddItem('
')
diff --git a/src/web/Cgi/listinfo.py b/src/web/Cgi/listinfo.py
new file mode 100644
index 000000000..149be715f
--- /dev/null
+++ b/src/web/Cgi/listinfo.py
@@ -0,0 +1,207 @@
+# Copyright (C) 1998-2009 by the Free Software Foundation, Inc.
+#
+# This file is part of GNU Mailman.
+#
+# GNU Mailman 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 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Mailman 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
+# GNU Mailman. If not, see
')
+ doc.AddItem(MailmanLogo())
+ print doc.Format()
+
+
+
+def list_listinfo(mlist, lang):
+ # Generate list specific listinfo
+ doc = HeadlessDocument()
+ doc.set_language(lang)
+
+ replacements = mlist.GetStandardReplacements(lang)
+
+ if not mlist.digestable or not mlist.nondigestable:
+ replacements['
')
+ doc.AddItem(MailmanLogo())
+ print doc.Format()
+ return
+
+ # get the list and user's name
+ listname = parts[0].lower()
+ # open list
+ try:
+ mlist = MailList.MailList(listname, lock=0)
+ except Errors.MMListError, e:
+ # Avoid cross-site scripting attacks
+ safelistname = Utils.websafe(listname)
+ title = _('CGI script error')
+ doc.SetTitle(title)
+ doc.AddItem(Header(2, title))
+ doc.addError(_('No such list %(safelistname)s'))
+ doc.AddItem('
')
+ doc.AddItem(MailmanLogo())
+ print doc.Format()
+ log.error('No such list "%s": %s\n', listname, e)
+ return
+
+ # The total contents of the user's response
+ cgidata = cgi.FieldStorage(keep_blank_values=1)
+
+ # Set the language for the page. If we're coming from the listinfo cgi,
+ # we might have a 'language' key in the cgi data. That was an explicit
+ # preference to view the page in, so we should honor that here. If that's
+ # not available, use the list's default language.
+ language = cgidata.getvalue('language')
+ if language not in config.languages.enabled_codes:
+ language = mlist.preferred_language
+ i18n.set_language(language)
+ doc.set_language(language)
+
+ if lenparts < 2:
+ user = cgidata.getvalue('email')
+ if not user:
+ # If we're coming from the listinfo page and we left the email
+ # address field blank, it's not an error. listinfo.html names the
+ # button UserOptions; we can use that as the descriminator.
+ if not cgidata.getvalue('UserOptions'):
+ doc.addError(_('No address given'))
+ loginpage(mlist, doc, None, language)
+ print doc.Format()
+ return
+ else:
+ user = Utils.LCDomain(Utils.UnobscureEmail(SLASH.join(parts[1:])))
+
+ # Avoid cross-site scripting attacks
+ safeuser = Utils.websafe(user)
+ try:
+ Utils.ValidateEmail(user)
+ except Errors.EmailAddressError:
+ doc.addError(_('Illegal Email Address: %(safeuser)s'))
+ loginpage(mlist, doc, None, language)
+ print doc.Format()
+ return
+ # Sanity check the user, but only give the "no such member" error when
+ # using public rosters, otherwise, we'll leak membership information.
+ if not mlist.isMember(user) and mlist.private_roster == 0:
+ doc.addError(_('No such member: %(safeuser)s.'))
+ loginpage(mlist, doc, None, language)
+ print doc.Format()
+ return
+
+ # Find the case preserved email address (the one the user subscribed with)
+ lcuser = user.lower()
+ try:
+ cpuser = mlist.getMemberCPAddress(lcuser)
+ except Errors.NotAMemberError:
+ # This happens if the user isn't a member but we've got private rosters
+ cpuser = None
+ if lcuser == cpuser:
+ cpuser = None
+
+ # And now we know the user making the request, so set things up to for the
+ # user's stored preferred language, overridden by any form settings for
+ # their new language preference.
+ userlang = cgidata.getvalue('language')
+ if userlang not in config.languages.enabled_codes:
+ userlang = mlist.getMemberLanguage(user)
+ doc.set_language(userlang)
+ i18n.set_language(userlang)
+
+ # See if this is VARHELP on topics.
+ varhelp = None
+ if cgidata.has_key('VARHELP'):
+ varhelp = cgidata['VARHELP'].value
+ elif 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 isinstance(qs, list):
+ varhelp = qs[0]
+ if varhelp:
+ topic_details(mlist, doc, user, cpuser, userlang, varhelp)
+ return
+
+ # Are we processing an unsubscription request from the login screen?
+ if cgidata.has_key('login-unsub'):
+ # Because they can't supply a password for unsubscribing, we'll need
+ # to do the confirmation dance.
+ if mlist.isMember(user):
+ # We must acquire the list lock in order to pend a request.
+ try:
+ mlist.Lock()
+ # If unsubs require admin approval, then this request has to
+ # be held. Otherwise, send a confirmation.
+ if mlist.unsubscribe_policy:
+ mlist.HoldUnsubscription(user)
+ doc.addError(_("""Your unsubscription request has been
+ forwarded to the list administrator for approval."""),
+ tag='')
+ else:
+ mlist.ConfirmUnsubscription(user, userlang)
+ doc.addError(_('The confirmation email has been sent.'),
+ tag='')
+ mlist.Save()
+ finally:
+ mlist.Unlock()
+ else:
+ # Not a member
+ if mlist.private_roster == 0:
+ # Public rosters
+ doc.addError(_('No such member: %(safeuser)s.'))
+ else:
+ mlog.error('Unsub attempt of non-member w/ private rosters: %s',
+ user)
+ doc.addError(_('The confirmation email has been sent.'),
+ tag='')
+ loginpage(mlist, doc, user, language)
+ print doc.Format()
+ return
+
+ # Are we processing a password reminder from the login screen?
+ if cgidata.has_key('login-remind'):
+ if mlist.isMember(user):
+ mlist.MailUserPassword(user)
+ doc.addError(
+ _('A reminder of your password has been emailed to you.'),
+ tag='')
+ else:
+ # Not a member
+ if mlist.private_roster == 0:
+ # Public rosters
+ doc.addError(_('No such member: %(safeuser)s.'))
+ else:
+ mlog.error(
+ 'Reminder attempt of non-member w/ private rosters: %s',
+ user)
+ doc.addError(
+ _('A reminder of your password has been emailed to you.'),
+ tag='')
+ loginpage(mlist, doc, user, language)
+ print doc.Format()
+ return
+
+ # Get the password from the form.
+ password = cgidata.getvalue('password', '').strip()
+ # Check authentication. We need to know if the credentials match the user
+ # or the site admin, because they are the only ones who are allowed to
+ # change things globally. Specifically, the list admin may not change
+ # values globally.
+ if config.ALLOW_SITE_ADMIN_COOKIES:
+ user_or_siteadmin_context = (config.AuthUser, config.AuthSiteAdmin)
+ else:
+ # Site and list admins are treated equal so that list admin can pass
+ # site admin test. :-(
+ user_or_siteadmin_context = (config.AuthUser,)
+ is_user_or_siteadmin = mlist.WebAuthenticate(
+ user_or_siteadmin_context, password, user)
+ # Authenticate, possibly using the password supplied in the login page
+ if not is_user_or_siteadmin and \
+ not mlist.WebAuthenticate((config.AuthListAdmin,
+ config.AuthSiteAdmin),
+ password, user):
+ # Not authenticated, so throw up the login page again. If they tried
+ # to authenticate via cgi (instead of cookie), then print an error
+ # message.
+ if cgidata.has_key('password'):
+ doc.addError(_('Authentication failed.'))
+ # So as not to allow membership leakage, prompt for the email
+ # address and the password here.
+ if mlist.private_roster <> 0:
+ mlog.error('Login failure with private rosters: %s', user)
+ user = None
+ loginpage(mlist, doc, user, language)
+ print doc.Format()
+ return
+
+ # From here on out, the user is okay to view and modify their membership
+ # options. The first set of checks does not require the list to be
+ # locked.
+
+ if cgidata.has_key('logout'):
+ print mlist.ZapCookie(config.AuthUser, user)
+ loginpage(mlist, doc, user, language)
+ print doc.Format()
+ return
+
+ if cgidata.has_key('emailpw'):
+ mlist.MailUserPassword(user)
+ options_page(
+ mlist, doc, user, cpuser, userlang,
+ _('A reminder of your password has been emailed to you.'))
+ print doc.Format()
+ return
+
+ if cgidata.has_key('othersubs'):
+ # Only the user or site administrator can view all subscriptions.
+ if not is_user_or_siteadmin:
+ doc.addError(_("""The list administrator may not view the other
+ subscriptions for this user."""), _('Note: '))
+ options_page(mlist, doc, user, cpuser, userlang)
+ print doc.Format()
+ return
+ hostname = mlist.host_name
+ title = _('List subscriptions for %(safeuser)s on %(hostname)s')
+ doc.SetTitle(title)
+ doc.AddItem(Header(2, title))
+ doc.AddItem(_('''Click on a link to visit your options page for the
+ requested mailing list.'''))
+
+ # Troll through all the mailing lists that match host_name and see if
+ # the user is a member. If so, add it to the list.
+ onlists = []
+ for gmlist in lists_of_member(mlist, user) + [mlist]:
+ url = gmlist.GetOptionsURL(user)
+ link = Link(url, gmlist.real_name)
+ onlists.append((gmlist.real_name, link))
+ onlists.sort()
+ items = OrderedList(*[link for name, link in onlists])
+ doc.AddItem(items)
+ print doc.Format()
+ return
+
+ if cgidata.has_key('change-of-address'):
+ # We could be changing the user's full name, email address, or both.
+ # Watch out for non-ASCII characters in the member's name.
+ membername = cgidata.getvalue('fullname')
+ # Canonicalize the member's name
+ membername = Utils.canonstr(membername, language)
+ newaddr = cgidata.getvalue('new-address')
+ confirmaddr = cgidata.getvalue('confirm-address')
+
+ oldname = mlist.getMemberName(user)
+ set_address = set_membername = 0
+
+ # See if the user wants to change their email address globally. The
+ # list admin is /not/ allowed to make global changes.
+ globally = cgidata.getvalue('changeaddr-globally')
+ if globally and not is_user_or_siteadmin:
+ doc.addError(_("""The list administrator may not change the names
+ or addresses for this user's other subscriptions. However, the
+ subscription for this mailing list has been changed."""),
+ _('Note: '))
+ globally = False
+ # We will change the member's name under the following conditions:
+ # - membername has a value
+ # - membername has no value, but they /used/ to have a membername
+ if membername and membername <> oldname:
+ # Setting it to a new value
+ set_membername = 1
+ if not membername and oldname:
+ # Unsetting it
+ set_membername = 1
+ # We will change the user's address if both newaddr and confirmaddr
+ # are non-blank, have the same value, and aren't the currently
+ # subscribed email address (when compared case-sensitively). If both
+ # are blank, but membername is set, we ignore it, otherwise we print
+ # an error.
+ msg = ''
+ if newaddr and confirmaddr:
+ if newaddr <> confirmaddr:
+ options_page(mlist, doc, user, cpuser, userlang,
+ _('Addresses did not match!'))
+ print doc.Format()
+ return
+ if newaddr == cpuser:
+ options_page(mlist, doc, user, cpuser, userlang,
+ _('You are already using that email address'))
+ print doc.Format()
+ return
+ # If they're requesting to subscribe an address which is already a
+ # member, and they're /not/ doing it globally, then refuse.
+ # Otherwise, we'll agree to do it globally (with a warning
+ # message) and let ApprovedChangeMemberAddress() handle already a
+ # member issues.
+ if mlist.isMember(newaddr):
+ safenewaddr = Utils.websafe(newaddr)
+ if globally:
+ listname = mlist.real_name
+ msg += _("""\
+The new address you requested %(newaddr)s is already a member of the
+%(listname)s mailing list, however you have also requested a global change of
+address. Upon confirmation, any other mailing list containing the address
+%(safeuser)s will be changed. """)
+ # Don't return
+ else:
+ options_page(
+ mlist, doc, user, cpuser, userlang,
+ _('The new address is already a member: %(newaddr)s'))
+ print doc.Format()
+ return
+ set_address = 1
+ elif (newaddr or confirmaddr) and not set_membername:
+ options_page(mlist, doc, user, cpuser, userlang,
+ _('Addresses may not be blank'))
+ print doc.Format()
+ return
+ if set_address:
+ if cpuser is None:
+ cpuser = user
+ # Register the pending change after the list is locked
+ msg += _('A confirmation message has been sent to %(newaddr)s. ')
+ mlist.Lock()
+ try:
+ try:
+ mlist.ChangeMemberAddress(cpuser, newaddr, globally)
+ mlist.Save()
+ finally:
+ mlist.Unlock()
+ except Errors.InvalidEmailAddress:
+ msg = _('Invalid email address provided')
+ except Errors.MMAlreadyAMember:
+ msg = _('%(newaddr)s is already a member of the list.')
+ except Errors.MembershipIsBanned:
+ owneraddr = mlist.GetOwnerEmail()
+ msg = _("""%(newaddr)s is banned from this list. If you
+ think this restriction is erroneous, please contact
+ the list owners at %(owneraddr)s.""")
+
+ if set_membername:
+ mlist.Lock()
+ try:
+ mlist.ChangeMemberName(user, membername, globally)
+ mlist.Save()
+ finally:
+ mlist.Unlock()
+ msg += _('Member name successfully changed. ')
+
+ options_page(mlist, doc, user, cpuser, userlang, msg)
+ print doc.Format()
+ return
+
+ if cgidata.has_key('changepw'):
+ newpw = cgidata.getvalue('newpw')
+ confirmpw = cgidata.getvalue('confpw')
+ if not newpw or not confirmpw:
+ options_page(mlist, doc, user, cpuser, userlang,
+ _('Passwords may not be blank'))
+ print doc.Format()
+ return
+ if newpw <> confirmpw:
+ options_page(mlist, doc, user, cpuser, userlang,
+ _('Passwords did not match!'))
+ print doc.Format()
+ return
+
+ # See if the user wants to change their passwords globally, however
+ # the list admin is /not/ allowed to change passwords globally.
+ pw_globally = cgidata.getvalue('pw-globally')
+ if pw_globally and not is_user_or_siteadmin:
+ doc.addError(_("""The list administrator may not change the
+ password for this user's other subscriptions. However, the
+ password for this mailing list has been changed."""),
+ _('Note: '))
+ pw_globally = False
+
+ mlists = [mlist]
+
+ if pw_globally:
+ mlists.extend(lists_of_member(mlist, user))
+
+ pw = passwords.make_secret(newpw, config.PASSWORD_SCHEME)
+ for gmlist in mlists:
+ change_password(gmlist, user, pw)
+
+ # Regenerate the cookie so a re-authorization isn't necessary
+ print mlist.MakeCookie(config.AuthUser, user)
+ options_page(mlist, doc, user, cpuser, userlang,
+ _('Password successfully changed.'))
+ print doc.Format()
+ return
+
+ if cgidata.has_key('unsub'):
+ # Was the confirming check box turned on?
+ if not cgidata.getvalue('unsubconfirm'):
+ options_page(
+ mlist, doc, user, cpuser, userlang,
+ _('''You must confirm your unsubscription request by turning
+ on the checkbox below the Unsubscribe button. You
+ have not been unsubscribed!'''))
+ print doc.Format()
+ return
+
+ mlist.Lock()
+ needapproval = False
+ try:
+ try:
+ mlist.DeleteMember(
+ user, 'via the member options page', userack=1)
+ except Errors.MMNeedApproval:
+ needapproval = True
+ mlist.Save()
+ finally:
+ mlist.Unlock()
+ # Now throw up some results page, with appropriate links. We can't
+ # drop them back into their options page, because that's gone now!
+ fqdn_listname = mlist.GetListEmail()
+ owneraddr = mlist.GetOwnerEmail()
+ url = mlist.GetScriptURL('listinfo')
+
+ title = _('Unsubscription results')
+ doc.SetTitle(title)
+ doc.AddItem(Header(2, title))
+ if needapproval:
+ doc.AddItem(_("""Your unsubscription request has been received and
+ forwarded on to the list moderators for approval. You will
+ receive notification once the list moderators have made their
+ decision."""))
+ else:
+ doc.AddItem(_("""You have been successfully unsubscribed from the
+ mailing list %(fqdn_listname)s. If you were receiving digest
+ deliveries you may get one more digest. If you have any questions
+ about your unsubscription, please contact the list owners at
+ %(owneraddr)s."""))
+ doc.AddItem(mlist.GetMailmanFooter())
+ print doc.Format()
+ return
+
+ if cgidata.has_key('options-submit'):
+ # Digest action flags
+ digestwarn = 0
+ cantdigest = 0
+ mustdigest = 0
+
+ newvals = []
+ # First figure out which options have changed. The item names come
+ # from FormatOptionButton() in HTMLFormatter.py
+ for item, flag in (('digest', config.Digests),
+ ('mime', config.DisableMime),
+ ('dontreceive', config.DontReceiveOwnPosts),
+ ('ackposts', config.AcknowledgePosts),
+ ('disablemail', config.DisableDelivery),
+ ('conceal', config.ConcealSubscription),
+ ('remind', config.SuppressPasswordReminder),
+ ('rcvtopic', config.ReceiveNonmatchingTopics),
+ ('nodupes', config.DontReceiveDuplicates),
+ ):
+ try:
+ newval = int(cgidata.getvalue(item))
+ except (TypeError, ValueError):
+ newval = None
+
+ # Skip this option if there was a problem or it wasn't changed.
+ # Note that delivery status is handled separate from the options
+ # flags.
+ if newval is None:
+ continue
+ elif flag == config.DisableDelivery:
+ status = mlist.getDeliveryStatus(user)
+ # Here, newval == 0 means enable, newval == 1 means disable
+ if not newval and status <> MemberAdaptor.ENABLED:
+ newval = MemberAdaptor.ENABLED
+ elif newval and status == MemberAdaptor.ENABLED:
+ newval = MemberAdaptor.BYUSER
+ else:
+ continue
+ elif newval == mlist.getMemberOption(user, flag):
+ continue
+ # Should we warn about one more digest?
+ if flag == config.Digests and \
+ newval == 0 and mlist.getMemberOption(user, flag):
+ digestwarn = 1
+
+ newvals.append((flag, newval))
+
+ # The user language is handled a little differently
+ if userlang not in mlist.language_codes:
+ newvals.append((SETLANGUAGE, mlist.preferred_language))
+ else:
+ newvals.append((SETLANGUAGE, userlang))
+
+ # Process user selected topics, but don't make the changes to the
+ # MailList object; we must do that down below when the list is
+ # locked.
+ topicnames = cgidata.getvalue('usertopic')
+ if topicnames:
+ # Some topics were selected. topicnames can actually be a string
+ # or a list of strings depending on whether more than one topic
+ # was selected or not.
+ if not isinstance(topicnames, list):
+ # Assume it was a bare string, so listify it
+ topicnames = [topicnames]
+ # unquote the topic names
+ topicnames = [urllib.unquote_plus(n) for n in topicnames]
+
+ # The standard sigterm handler (see above)
+ def sigterm_handler(signum, frame, mlist=mlist):
+ mlist.Unlock()
+ sys.exit(0)
+
+ # Now, lock the list and perform the changes
+ mlist.Lock()
+ try:
+ # `values' is a tuple of flags and the web values
+ for flag, newval in newvals:
+ # Handle language settings differently
+ if flag == SETLANGUAGE:
+ mlist.setMemberLanguage(user, newval)
+ # Handle delivery status separately
+ elif flag == config.DisableDelivery:
+ mlist.setDeliveryStatus(user, newval)
+ else:
+ try:
+ mlist.setMemberOption(user, flag, newval)
+ except Errors.CantDigestError:
+ cantdigest = 1
+ except Errors.MustDigestError:
+ mustdigest = 1
+ # Set the topics information.
+ mlist.setMemberTopics(user, topicnames)
+ mlist.Save()
+ finally:
+ mlist.Unlock()
+
+ # A bag of attributes for the global options
+ class Global:
+ enable = None
+ remind = None
+ nodupes = None
+ mime = None
+ def __nonzero__(self):
+ return len(self.__dict__.keys()) > 0
+
+ globalopts = Global()
+
+ # The enable/disable option and the password remind option may have
+ # their global flags sets.
+ if cgidata.getvalue('deliver-globally'):
+ # Yes, this is inefficient, but the list is so small it shouldn't
+ # make much of a difference.
+ for flag, newval in newvals:
+ if flag == config.DisableDelivery:
+ globalopts.enable = newval
+ break
+
+ if cgidata.getvalue('remind-globally'):
+ for flag, newval in newvals:
+ if flag == config.SuppressPasswordReminder:
+ globalopts.remind = newval
+ break
+
+ if cgidata.getvalue('nodupes-globally'):
+ for flag, newval in newvals:
+ if flag == config.DontReceiveDuplicates:
+ globalopts.nodupes = newval
+ break
+
+ if cgidata.getvalue('mime-globally'):
+ for flag, newval in newvals:
+ if flag == config.DisableMime:
+ globalopts.mime = newval
+ break
+
+ # Change options globally, but only if this is the user or site admin,
+ # /not/ if this is the list admin.
+ if globalopts:
+ if not is_user_or_siteadmin:
+ doc.addError(_("""The list administrator may not change the
+ options for this user's other subscriptions. However the
+ options for this mailing list subscription has been
+ changed."""), _('Note: '))
+ else:
+ for gmlist in lists_of_member(mlist, user):
+ global_options(gmlist, user, globalopts)
+
+ # Now print the results
+ if cantdigest:
+ msg = _('''The list administrator has disabled digest delivery for
+ this list, so your delivery option has not been set. However your
+ other options have been set successfully.''')
+ elif mustdigest:
+ msg = _('''The list administrator has disabled non-digest delivery
+ for this list, so your delivery option has not been set. However
+ your other options have been set successfully.''')
+ else:
+ msg = _('You have successfully set your options.')
+
+ if digestwarn:
+ msg += _('You may get one last digest.')
+
+ options_page(mlist, doc, user, cpuser, userlang, msg)
+ print doc.Format()
+ return
+
+ if mlist.isMember(user):
+ options_page(mlist, doc, user, cpuser, userlang)
+ else:
+ loginpage(mlist, doc, user, userlang)
+ print doc.Format()
+
+
+
+def options_page(mlist, doc, user, cpuser, userlang, message=''):
+ # The bulk of the document will come from the options.html template, which
+ # includes it's own html armor (head tags, etc.). Suppress the head that
+ # Document() derived pages get automatically.
+ doc.suppress_head = 1
+
+ if mlist.obscure_addresses:
+ presentable_user = Utils.ObscureEmail(user, for_text=1)
+ if cpuser is not None:
+ cpuser = Utils.ObscureEmail(cpuser, for_text=1)
+ else:
+ presentable_user = user
+
+ fullname = Utils.uncanonstr(mlist.getMemberName(user), userlang)
+ if fullname:
+ presentable_user += ', %s' % fullname
+
+ # Do replacements
+ replacements = mlist.GetStandardReplacements(userlang)
+ replacements['
' +
+ CheckBox('unsubconfirm', 1, checked=0).Format() +
+ _('Yes, I really want to unsubscribe'))
+ replacements['