1779
by Mark Sapiro
Bump copyright dates. |
1 |
# Copyright (C) 1998-2018 by the Free Software Foundation, Inc.
|
1
by
This commit was manufactured by cvs2svn to create branch |
2 |
#
|
3 |
# This program is free software; you can redistribute it and/or
|
|
4 |
# modify it under the terms of the GNU General Public License
|
|
5 |
# as published by the Free Software Foundation; either version 2
|
|
6 |
# of the License, or (at your option) any later version.
|
|
7 |
#
|
|
8 |
# This program is distributed in the hope that it will be useful,
|
|
9 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11 |
# GNU General Public License for more details.
|
|
12 |
#
|
|
13 |
# You should have received a copy of the GNU General Public License
|
|
14 |
# along with this program; if not, write to the Free Software
|
|
830
by bwarsaw
A cleansing pass, almost entirely cosmetic. Such things as whitespace |
15 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
|
16 |
# USA.
|
|
1
by
This commit was manufactured by cvs2svn to create branch |
17 |
|
564
by bwarsaw
admin.py: |
18 |
"""Process and produce the list-administration options forms."""
|
1
by
This commit was manufactured by cvs2svn to create branch |
19 |
|
20 |
# For Python 2.1.x compatibility
|
|
21 |
from __future__ import nested_scopes |
|
22 |
||
23 |
import sys |
|
24 |
import os |
|
25 |
import re |
|
26 |
import cgi |
|
27 |
import urllib |
|
28 |
import signal |
|
29 |
from types import * |
|
30 |
||
31 |
from email.Utils import unquote, parseaddr, formataddr |
|
32 |
||
33 |
from Mailman import mm_cfg |
|
34 |
from Mailman import Utils |
|
1551
by Mark Sapiro
Implemented member address change via the admin GUI. |
35 |
from Mailman import Message |
1
by
This commit was manufactured by cvs2svn to create branch |
36 |
from Mailman import MailList |
37 |
from Mailman import Errors |
|
38 |
from Mailman import MemberAdaptor |
|
39 |
from Mailman import i18n |
|
40 |
from Mailman.UserDesc import UserDesc |
|
41 |
from Mailman.htmlformat import * |
|
42 |
from Mailman.Cgi import Auth |
|
43 |
from Mailman.Logging.Syslog import syslog |
|
1135
by Barry Warsaw
Apply Heiko Rommel's patch for hashlib deprecation warnings for bug 293178. |
44 |
from Mailman.Utils import sha_new |
1337
by Mark Sapiro
Added Tokio Kikuchi's Cross-site Request Forgery hardening to the admin UI. |
45 |
from Mailman.CSRFcheck import csrf_check |
1
by
This commit was manufactured by cvs2svn to create branch |
46 |
|
47 |
# Set up i18n
|
|
48 |
_ = i18n._ |
|
49 |
i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) |
|
1776
by Mark Sapiro
I18n for new whence reasons in admin (un)subscribe notices. |
50 |
def D_(s): |
51 |
return s |
|
1
by
This commit was manufactured by cvs2svn to create branch |
52 |
|
53 |
NL = '\n' |
|
54 |
OPTCOLUMNS = 11 |
|
55 |
||
205
by bwarsaw
get_item_gui_value(): Added a new widget HeaderFilter and associated code to |
56 |
try: |
57 |
True, False |
|
58 |
except NameError: |
|
59 |
True = 1 |
|
60 |
False = 0 |
|
61 |
||
1337
by Mark Sapiro
Added Tokio Kikuchi's Cross-site Request Forgery hardening to the admin UI. |
62 |
AUTH_CONTEXTS = (mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin) |
63 |
||
1
by
This commit was manufactured by cvs2svn to create branch |
64 |
|
65 |
||
66 |
def main(): |
|
67 |
# Try to find out which list is being administered
|
|
68 |
parts = Utils.GetPathPieces() |
|
69 |
if not parts: |
|
70 |
# None, so just do the admin overview and be done with it
|
|
71 |
admin_overview() |
|
72 |
return
|
|
73 |
# Get the list object
|
|
74 |
listname = parts[0].lower() |
|
75 |
try: |
|
76 |
mlist = MailList.MailList(listname, lock=0) |
|
77 |
except Errors.MMListError, e: |
|
78 |
# Avoid cross-site scripting attacks
|
|
79 |
safelistname = Utils.websafe(listname) |
|
1231
by Mark Sapiro
Added roster to the CGIs that return HTTP 401 status for an authentication |
80 |
# Send this with a 404 status.
|
81 |
print 'Status: 404 Not Found' |
|
1
by
This commit was manufactured by cvs2svn to create branch |
82 |
admin_overview(_('No such list <em>%(safelistname)s</em>')) |
1451
by Mark Sapiro
- Added the list name to the vette log "held message approved" entry. |
83 |
syslog('error', 'admin: No such list "%s": %s\n', |
84 |
listname, e) |
|
1
by
This commit was manufactured by cvs2svn to create branch |
85 |
return
|
86 |
# Now that we know what list has been requested, all subsequent admin
|
|
87 |
# pages are shown in that list's preferred language.
|
|
88 |
i18n.set_language(mlist.preferred_language) |
|
89 |
# If the user is not authenticated, we're done.
|
|
90 |
cgidata = cgi.FieldStorage(keep_blank_values=1) |
|
1663
by Mark Sapiro
Catch TypeError from certain defective crafted POST requests. |
91 |
try: |
1712
by Mark Sapiro
Defend against CGI requests with multiple values for the same parameter. |
92 |
cgidata.getfirst('csrf_token', '') |
1663
by Mark Sapiro
Catch TypeError from certain defective crafted POST requests. |
93 |
except TypeError: |
94 |
# Someone crafted a POST with a bad Content-Type:.
|
|
95 |
doc = Document() |
|
96 |
doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) |
|
97 |
doc.AddItem(Header(2, _("Error"))) |
|
98 |
doc.AddItem(Bold(_('Invalid options to CGI script.'))) |
|
99 |
# Send this with a 400 status.
|
|
100 |
print 'Status: 400 Bad Request' |
|
101 |
print doc.Format() |
|
102 |
return
|
|
1
by
This commit was manufactured by cvs2svn to create branch |
103 |
|
1337
by Mark Sapiro
Added Tokio Kikuchi's Cross-site Request Forgery hardening to the admin UI. |
104 |
# CSRF check
|
1340
by Mark Sapiro
Added a few more safe_params to the CSRF check. |
105 |
safe_params = ['VARHELP', 'adminpw', 'admlogin', |
1366
by Mark Sapiro
Added 'legend' to the list of CSRF safe parameters for the admin CGI. |
106 |
'letter', 'chunk', 'findmember', |
107 |
'legend'] |
|
1337
by Mark Sapiro
Added Tokio Kikuchi's Cross-site Request Forgery hardening to the admin UI. |
108 |
params = cgidata.keys() |
109 |
if set(params) - set(safe_params): |
|
1882
by Mark Sapiro
Block CSRF attack against admin or admindb pages. |
110 |
csrf_checked = csrf_check(mlist, cgidata.getfirst('csrf_token'), |
111 |
'admin') |
|
1337
by Mark Sapiro
Added Tokio Kikuchi's Cross-site Request Forgery hardening to the admin UI. |
112 |
else: |
113 |
csrf_checked = True |
|
114 |
# if password is present, void cookie to force password authentication.
|
|
1712
by Mark Sapiro
Defend against CGI requests with multiple values for the same parameter. |
115 |
if cgidata.getfirst('adminpw'): |
1337
by Mark Sapiro
Added Tokio Kikuchi's Cross-site Request Forgery hardening to the admin UI. |
116 |
os.environ['HTTP_COOKIE'] = '' |
117 |
csrf_checked = True |
|
118 |
||
1
by
This commit was manufactured by cvs2svn to create branch |
119 |
if not mlist.WebAuthenticate((mm_cfg.AuthListAdmin, |
120 |
mm_cfg.AuthSiteAdmin), |
|
1712
by Mark Sapiro
Defend against CGI requests with multiple values for the same parameter. |
121 |
cgidata.getfirst('adminpw', '')): |
1
by
This commit was manufactured by cvs2svn to create branch |
122 |
if cgidata.has_key('adminpw'): |
123 |
# This is a re-authorization attempt
|
|
124 |
msg = Bold(FontSize('+1', _('Authorization failed.'))).Format() |
|
1766.1.1
by Jim Popovitch
Improved logging of security related events |
125 |
remote = os.environ.get('HTTP_FORWARDED_FOR', |
126 |
os.environ.get('HTTP_X_FORWARDED_FOR', |
|
127 |
os.environ.get('REMOTE_ADDR', |
|
128 |
'unidentified origin'))) |
|
1768
by Mark Sapiro
Implement security log. |
129 |
syslog('security', |
130 |
'Authorization failed (admin): list=%s: remote=%s', |
|
131 |
listname, remote) |
|
1
by
This commit was manufactured by cvs2svn to create branch |
132 |
else: |
133 |
msg = '' |
|
134 |
Auth.loginpage(mlist, 'admin', msg=msg) |
|
135 |
return
|
|
136 |
||
137 |
# Which subcategory was requested? Default is `general'
|
|
138 |
if len(parts) == 1: |
|
139 |
category = 'general' |
|
140 |
subcat = None |
|
141 |
elif len(parts) == 2: |
|
142 |
category = parts[1] |
|
143 |
subcat = None |
|
144 |
else: |
|
145 |
category = parts[1] |
|
146 |
subcat = parts[2] |
|
147 |
||
148 |
# Is this a log-out request?
|
|
149 |
if category == 'logout': |
|
1293
by Mark Sapiro
Added a logout link to the admindb interface and made both admin and |
150 |
# site-wide admin should also be able to logout.
|
151 |
if mlist.AuthContextInfo(mm_cfg.AuthSiteAdmin)[0] == 'site': |
|
152 |
print mlist.ZapCookie(mm_cfg.AuthSiteAdmin) |
|
1
by
This commit was manufactured by cvs2svn to create branch |
153 |
print mlist.ZapCookie(mm_cfg.AuthListAdmin) |
154 |
Auth.loginpage(mlist, 'admin', frontpage=1) |
|
155 |
return
|
|
156 |
||
157 |
# Sanity check
|
|
158 |
if category not in mlist.GetConfigCategories().keys(): |
|
159 |
category = 'general' |
|
160 |
||
161 |
# Is the request for variable details?
|
|
162 |
varhelp = None |
|
163 |
qsenviron = os.environ.get('QUERY_STRING') |
|
164 |
parsedqs = None |
|
165 |
if qsenviron: |
|
166 |
parsedqs = cgi.parse_qs(qsenviron) |
|
167 |
if cgidata.has_key('VARHELP'): |
|
1712
by Mark Sapiro
Defend against CGI requests with multiple values for the same parameter. |
168 |
varhelp = cgidata.getfirst('VARHELP') |
1
by
This commit was manufactured by cvs2svn to create branch |
169 |
elif parsedqs: |
170 |
# POST methods, even if their actions have a query string, don't get
|
|
171 |
# put into FieldStorage's keys :-(
|
|
172 |
qs = parsedqs.get('VARHELP') |
|
173 |
if qs and isinstance(qs, ListType): |
|
174 |
varhelp = qs[0] |
|
175 |
if varhelp: |
|
176 |
option_help(mlist, varhelp) |
|
177 |
return
|
|
178 |
||
179 |
# The html page document
|
|
180 |
doc = Document() |
|
181 |
doc.set_language(mlist.preferred_language) |
|
182 |
||
183 |
# From this point on, the MailList object must be locked. However, we
|
|
184 |
# must release the lock no matter how we exit. try/finally isn't enough,
|
|
185 |
# because of this scenario: user hits the admin page which may take a long
|
|
186 |
# time to render; user gets bored and hits the browser's STOP button;
|
|
187 |
# browser shuts down socket; server tries to write to broken socket and
|
|
188 |
# gets a SIGPIPE. Under Apache 1.3/mod_cgi, Apache catches this SIGPIPE
|
|
189 |
# (I presume it is buffering output from the cgi script), then turns
|
|
190 |
# around and SIGTERMs the cgi process. Apache waits three seconds and
|
|
191 |
# then SIGKILLs the cgi process. We /must/ catch the SIGTERM and do the
|
|
192 |
# most reasonable thing we can in as short a time period as possible. If
|
|
193 |
# we get the SIGKILL we're screwed (because it's uncatchable and we'll
|
|
194 |
# have no opportunity to clean up after ourselves).
|
|
195 |
#
|
|
196 |
# This signal handler catches the SIGTERM, unlocks the list, and then
|
|
197 |
# exits the process. The effect of this is that the changes made to the
|
|
198 |
# MailList object will be aborted, which seems like the only sensible
|
|
199 |
# semantics.
|
|
200 |
#
|
|
201 |
# BAW: This may not be portable to other web servers or cgi execution
|
|
202 |
# models.
|
|
203 |
def sigterm_handler(signum, frame, mlist=mlist): |
|
204 |
# Make sure the list gets unlocked...
|
|
205 |
mlist.Unlock() |
|
206 |
# ...and ensure we exit, otherwise race conditions could cause us to
|
|
207 |
# enter MailList.Save() while we're in the unlocked state, and that
|
|
208 |
# could be bad!
|
|
209 |
sys.exit(0) |
|
210 |
||
211 |
mlist.Lock() |
|
212 |
try: |
|
213 |
# Install the emergency shutdown signal handler
|
|
214 |
signal.signal(signal.SIGTERM, sigterm_handler) |
|
215 |
||
216 |
if cgidata.keys(): |
|
1337
by Mark Sapiro
Added Tokio Kikuchi's Cross-site Request Forgery hardening to the admin UI. |
217 |
if csrf_checked: |
218 |
# There are options to change
|
|
219 |
change_options(mlist, category, subcat, cgidata, doc) |
|
220 |
else: |
|
221 |
doc.addError( |
|
222 |
_('The form lifetime has expired. (request forgery check)')) |
|
1
by
This commit was manufactured by cvs2svn to create branch |
223 |
# Let the list sanity check the changed values
|
224 |
mlist.CheckValues() |
|
225 |
# Additional sanity checks
|
|
226 |
if not mlist.digestable and not mlist.nondigestable: |
|
227 |
doc.addError( |
|
228 |
_('''You have turned off delivery of both digest and |
|
229 |
non-digest messages. This is an incompatible state of
|
|
230 |
affairs. You must turn on either digest delivery or
|
|
231 |
non-digest delivery or your mailing list will basically be
|
|
232 |
unusable.'''), tag=_('Warning: ')) |
|
233 |
||
1289
by Mark Sapiro
Refactor last change for i18n. |
234 |
dm = mlist.getDigestMemberKeys() |
235 |
if not mlist.digestable and dm: |
|
1
by
This commit was manufactured by cvs2svn to create branch |
236 |
doc.addError( |
237 |
_('''You have digest members, but digests are turned |
|
1288
by Mark Sapiro
Added a report of the affected members to the warnings issued when |
238 |
off. Those people will not receive mail.
|
1289
by Mark Sapiro
Refactor last change for i18n. |
239 |
Affected member(s) %(dm)r.'''), |
1
by
This commit was manufactured by cvs2svn to create branch |
240 |
tag=_('Warning: ')) |
1289
by Mark Sapiro
Refactor last change for i18n. |
241 |
rm = mlist.getRegularMemberKeys() |
242 |
if not mlist.nondigestable and rm: |
|
1
by
This commit was manufactured by cvs2svn to create branch |
243 |
doc.addError( |
244 |
_('''You have regular list members but non-digestified mail is |
|
1264
by Mark Sapiro
Made minor wording improvements and typo corrections in some messages. |
245 |
turned off. They will receive non-digestified mail until you
|
1289
by Mark Sapiro
Refactor last change for i18n. |
246 |
fix this problem. Affected member(s) %(rm)r.'''), |
247 |
tag=_('Warning: ')) |
|
1
by
This commit was manufactured by cvs2svn to create branch |
248 |
# Glom up the results page and print it out
|
249 |
show_results(mlist, doc, category, subcat, cgidata) |
|
250 |
print doc.Format() |
|
251 |
mlist.Save() |
|
252 |
finally: |
|
253 |
# Now be sure to unlock the list. It's okay if we get a signal here
|
|
254 |
# because essentially, the signal handler will do the same thing. And
|
|
255 |
# unlocking is unconditional, so it's not an error if we unlock while
|
|
256 |
# we're already unlocked.
|
|
257 |
mlist.Unlock() |
|
258 |
||
259 |
||
260 |
||
261 |
def admin_overview(msg=''): |
|
262 |
# Show the administrative overview page, with the list of all the lists on
|
|
263 |
# this host. msg is an optional error message to display at the top of
|
|
264 |
# the page.
|
|
265 |
#
|
|
266 |
# This page should be displayed in the server's default language, which
|
|
267 |
# should have already been set.
|
|
268 |
hostname = Utils.get_domain() |
|
269 |
legend = _('%(hostname)s mailing lists - Admin Links') |
|
270 |
# The html `document'
|
|
271 |
doc = Document() |
|
272 |
doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) |
|
273 |
doc.SetTitle(legend) |
|
274 |
# The table that will hold everything
|
|
275 |
table = Table(border=0, width="100%") |
|
276 |
table.AddRow([Center(Header(2, legend))]) |
|
277 |
table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, |
|
278 |
bgcolor=mm_cfg.WEB_HEADER_COLOR) |
|
279 |
# Skip any mailing list that isn't advertised.
|
|
280 |
advertised = [] |
|
281 |
listnames = Utils.list_names() |
|
282 |
listnames.sort() |
|
283 |
||
284 |
for name in listnames: |
|
1651
by Mark Sapiro
Catch MMUnknownListError in case list is removed after listing names. |
285 |
try: |
286 |
mlist = MailList.MailList(name, lock=0) |
|
287 |
except Errors.MMUnknownListError: |
|
288 |
# The list could have been deleted by another process.
|
|
289 |
continue
|
|
1
by
This commit was manufactured by cvs2svn to create branch |
290 |
if mlist.advertised: |
1241
by Mark Sapiro
Fixed a bug which would fail to show a list on the admin and listinfo |
291 |
if mm_cfg.VIRTUAL_HOST_OVERVIEW and ( |
292 |
mlist.web_page_url.find('/%s/' % hostname) == -1 and |
|
293 |
mlist.web_page_url.find('/%s:' % hostname) == -1): |
|
1
by
This commit was manufactured by cvs2svn to create branch |
294 |
# List is for different identity of this host - skip it.
|
295 |
continue
|
|
296 |
else: |
|
247
by bwarsaw
adminy_overview(): Richard Barrett's patch # 828811 to reduce listinfo |
297 |
advertised.append((mlist.GetScriptURL('admin'), |
298 |
mlist.real_name, |
|
1781.1.2
by Yasuhito FUTATSUKI at POEM
* apply Utils.websafe() to description string in admin.py |
299 |
Utils.websafe(mlist.GetDescription()))) |
1
by
This commit was manufactured by cvs2svn to create branch |
300 |
# Greeting depends on whether there was an error or not
|
301 |
if msg: |
|
302 |
greeting = FontAttr(msg, color="ff5060", size="+1") |
|
303 |
else: |
|
1243
by Mark Sapiro
Increased the font size of 'Welcome!'on admin overview for consistency with listinfo. |
304 |
greeting = FontAttr(_('Welcome!'), size='+2') |
1
by
This commit was manufactured by cvs2svn to create branch |
305 |
|
306 |
welcome = [] |
|
307 |
mailmanlink = Link(mm_cfg.MAILMAN_URL, _('Mailman')).Format() |
|
308 |
if not advertised: |
|
309 |
welcome.extend([ |
|
310 |
greeting, |
|
311 |
_('''<p>There currently are no publicly-advertised %(mailmanlink)s |
|
312 |
mailing lists on %(hostname)s.'''), |
|
313 |
])
|
|
314 |
else: |
|
315 |
welcome.extend([ |
|
316 |
greeting, |
|
317 |
_('''<p>Below is the collection of publicly-advertised |
|
318 |
%(mailmanlink)s mailing lists on %(hostname)s. Click on a list |
|
319 |
name to visit the configuration pages for that list.'''), |
|
320 |
])
|
|
321 |
||
322 |
creatorurl = Utils.ScriptURL('create') |
|
323 |
mailman_owner = Utils.get_site_email() |
|
324 |
extra = msg and _('right ') or '' |
|
325 |
welcome.extend([ |
|
326 |
_('''To visit the administrators configuration page for an |
|
327 |
unadvertised list, open a URL similar to this one, but with a '/' and
|
|
328 |
the %(extra)slist name appended. If you have the proper authority, |
|
329 |
you can also <a href="%(creatorurl)s">create a new mailing list</a>. |
|
330 |
||
331 |
<p>General list information can be found at '''), |
|
332 |
Link(Utils.ScriptURL('listinfo'), |
|
333 |
_('the mailing list overview page')), |
|
334 |
'.', |
|
335 |
_('<p>(Send questions and comments to '), |
|
336 |
Link('mailto:%s' % mailman_owner, mailman_owner), |
|
337 |
'.)<p>', |
|
338 |
])
|
|
339 |
||
340 |
table.AddRow([Container(*welcome)]) |
|
341 |
table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 0, colspan=2) |
|
342 |
||
343 |
if advertised: |
|
344 |
table.AddRow([' ', ' ']) |
|
345 |
table.AddRow([Bold(FontAttr(_('List'), size='+2')), |
|
346 |
Bold(FontAttr(_('Description'), size='+2')) |
|
347 |
])
|
|
348 |
highlight = 1 |
|
247
by bwarsaw
adminy_overview(): Richard Barrett's patch # 828811 to reduce listinfo |
349 |
for url, real_name, description in advertised: |
1
by
This commit was manufactured by cvs2svn to create branch |
350 |
table.AddRow( |
247
by bwarsaw
adminy_overview(): Richard Barrett's patch # 828811 to reduce listinfo |
351 |
[Link(url, Bold(real_name)), |
352 |
description or Italic(_('[no description available]'))]) |
|
1
by
This commit was manufactured by cvs2svn to create branch |
353 |
if highlight and mm_cfg.WEB_HIGHLIGHT_COLOR: |
354 |
table.AddRowInfo(table.GetCurrentRowIndex(), |
|
355 |
bgcolor=mm_cfg.WEB_HIGHLIGHT_COLOR) |
|
356 |
highlight = not highlight |
|
357 |
||
358 |
doc.AddItem(table) |
|
359 |
doc.AddItem('<hr>') |
|
360 |
doc.AddItem(MailmanLogo()) |
|
361 |
print doc.Format() |
|
362 |
||
363 |
||
364 |
||
365 |
def option_help(mlist, varhelp): |
|
366 |
# The html page document
|
|
367 |
doc = Document() |
|
368 |
doc.set_language(mlist.preferred_language) |
|
369 |
# Find out which category and variable help is being requested for.
|
|
370 |
item = None |
|
371 |
reflist = varhelp.split('/') |
|
372 |
if len(reflist) >= 2: |
|
373 |
category = subcat = None |
|
374 |
if len(reflist) == 2: |
|
375 |
category, varname = reflist |
|
376 |
elif len(reflist) == 3: |
|
377 |
category, subcat, varname = reflist |
|
378 |
options = mlist.GetConfigInfo(category, subcat) |
|
951
by msapiro
Fixed admin.py so null VARHELP category is handled (1573393). |
379 |
if options: |
380 |
for i in options: |
|
381 |
if i and i[0] == varname: |
|
382 |
item = i |
|
383 |
break
|
|
1
by
This commit was manufactured by cvs2svn to create branch |
384 |
# Print an error message if we couldn't find a valid one
|
385 |
if not item: |
|
386 |
bad = _('No valid variable name found.') |
|
387 |
doc.addError(bad) |
|
388 |
doc.AddItem(mlist.GetMailmanFooter()) |
|
389 |
print doc.Format() |
|
390 |
return
|
|
391 |
# Get the details about the variable
|
|
392 |
varname, kind, params, dependancies, description, elaboration = \ |
|
393 |
get_item_characteristics(item) |
|
394 |
# Set up the document
|
|
395 |
realname = mlist.real_name |
|
396 |
legend = _("""%(realname)s Mailing list Configuration Help |
|
397 |
<br><em>%(varname)s</em> Option""") |
|
398 |
||
399 |
header = Table(width='100%') |
|
400 |
header.AddRow([Center(Header(3, legend))]) |
|
401 |
header.AddCellInfo(header.GetCurrentRowIndex(), 0, colspan=2, |
|
402 |
bgcolor=mm_cfg.WEB_HEADER_COLOR) |
|
403 |
doc.SetTitle(_("Mailman %(varname)s List Option Help")) |
|
404 |
doc.AddItem(header) |
|
405 |
doc.AddItem("<b>%s</b> (%s): %s<p>" % (varname, category, description)) |
|
406 |
if elaboration: |
|
407 |
doc.AddItem("%s<p>" % elaboration) |
|
408 |
||
409 |
if subcat: |
|
410 |
url = '%s/%s/%s' % (mlist.GetScriptURL('admin'), category, subcat) |
|
411 |
else: |
|
412 |
url = '%s/%s' % (mlist.GetScriptURL('admin'), category) |
|
1337
by Mark Sapiro
Added Tokio Kikuchi's Cross-site Request Forgery hardening to the admin UI. |
413 |
form = Form(url, mlist=mlist, contexts=AUTH_CONTEXTS) |
1
by
This commit was manufactured by cvs2svn to create branch |
414 |
valtab = Table(cellspacing=3, cellpadding=4, width='100%') |
415 |
add_options_table_item(mlist, category, subcat, valtab, item, detailsp=0) |
|
416 |
form.AddItem(valtab) |
|
417 |
form.AddItem('<p>') |
|
418 |
form.AddItem(Center(submit_button())) |
|
419 |
doc.AddItem(Center(form)) |
|
420 |
||
421 |
doc.AddItem(_("""<em><strong>Warning:</strong> changing this option here |
|
422 |
could cause other screens to be out-of-sync. Be sure to reload any other
|
|
423 |
pages that are displaying this option for this mailing list. You can also
|
|
424 |
""")) |
|
425 |
||
426 |
adminurl = mlist.GetScriptURL('admin') |
|
427 |
if subcat: |
|
428 |
url = '%s/%s/%s' % (adminurl, category, subcat) |
|
429 |
else: |
|
430 |
url = '%s/%s' % (adminurl, category) |
|
431 |
categoryname = mlist.GetConfigCategories()[category][0] |
|
432 |
doc.AddItem(Link(url, _('return to the %(categoryname)s options page.'))) |
|
433 |
doc.AddItem('</em>') |
|
434 |
doc.AddItem(mlist.GetMailmanFooter()) |
|
435 |
print doc.Format() |
|
436 |
||
437 |
||
438 |
||
439 |
def show_results(mlist, doc, category, subcat, cgidata): |
|
440 |
# Produce the results page
|
|
441 |
adminurl = mlist.GetScriptURL('admin') |
|
442 |
categories = mlist.GetConfigCategories() |
|
443 |
label = _(categories[category][0]) |
|
444 |
||
445 |
# Set up the document's headers
|
|
446 |
realname = mlist.real_name |
|
447 |
doc.SetTitle(_('%(realname)s Administration (%(label)s)')) |
|
448 |
doc.AddItem(Center(Header(2, _( |
|
449 |
'%(realname)s mailing list administration<br>%(label)s Section')))) |
|
450 |
doc.AddItem('<hr>') |
|
451 |
# Now we need to craft the form that will be submitted, which will contain
|
|
452 |
# all the variable settings, etc. This is a bit of a kludge because we
|
|
453 |
# know that the autoreply and members categories supports file uploads.
|
|
454 |
encoding = None |
|
455 |
if category in ('autoreply', 'members'): |
|
456 |
encoding = 'multipart/form-data' |
|
457 |
if subcat: |
|
458 |
form = Form('%s/%s/%s' % (adminurl, category, subcat), |
|
1337
by Mark Sapiro
Added Tokio Kikuchi's Cross-site Request Forgery hardening to the admin UI. |
459 |
encoding=encoding, mlist=mlist, contexts=AUTH_CONTEXTS) |
1
by
This commit was manufactured by cvs2svn to create branch |
460 |
else: |
1820
by Mark Sapiro
Implemented web admin sync members. |
461 |
form = Form('%s/%s' % (adminurl, category), |
1337
by Mark Sapiro
Added Tokio Kikuchi's Cross-site Request Forgery hardening to the admin UI. |
462 |
encoding=encoding, mlist=mlist, contexts=AUTH_CONTEXTS) |
1
by
This commit was manufactured by cvs2svn to create branch |
463 |
# This holds the two columns of links
|
464 |
linktable = Table(valign='top', width='100%') |
|
465 |
linktable.AddRow([Center(Bold(_("Configuration Categories"))), |
|
466 |
Center(Bold(_("Other Administrative Activities")))]) |
|
467 |
# The `other links' are stuff in the right column.
|
|
468 |
otherlinks = UnorderedList() |
|
469 |
otherlinks.AddItem(Link(mlist.GetScriptURL('admindb'), |
|
470 |
_('Tend to pending moderator requests'))) |
|
471 |
otherlinks.AddItem(Link(mlist.GetScriptURL('listinfo'), |
|
472 |
_('Go to the general list information page'))) |
|
473 |
otherlinks.AddItem(Link(mlist.GetScriptURL('edithtml'), |
|
564
by bwarsaw
admin.py: |
474 |
_('Edit the public HTML pages and text files'))) |
1
by
This commit was manufactured by cvs2svn to create branch |
475 |
otherlinks.AddItem(Link(mlist.GetBaseArchiveURL(), |
476 |
_('Go to list archives')).Format() + |
|
477 |
'<br> <br>') |
|
478 |
# We do not allow through-the-web deletion of the site list!
|
|
479 |
if mm_cfg.OWNERS_CAN_DELETE_THEIR_OWN_LISTS and \ |
|
480 |
mlist.internal_name() <> mm_cfg.MAILMAN_SITE_LIST: |
|
481 |
otherlinks.AddItem(Link(mlist.GetScriptURL('rmlist'), |
|
482 |
_('Delete this mailing list')).Format() + |
|
483 |
_(' (requires confirmation)<br> <br>')) |
|
484 |
otherlinks.AddItem(Link('%s/logout' % adminurl, |
|
485 |
# BAW: What I really want is a blank line, but
|
|
486 |
# adding an won't do it because of the
|
|
487 |
# bullet added to the list item.
|
|
488 |
'<FONT SIZE="+2"><b>%s</b></FONT>' % |
|
489 |
_('Logout'))) |
|
490 |
# These are links to other categories and live in the left column
|
|
491 |
categorylinks_1 = categorylinks = UnorderedList() |
|
492 |
categorylinks_2 = '' |
|
493 |
categorykeys = categories.keys() |
|
494 |
half = len(categorykeys) / 2 |
|
495 |
counter = 0 |
|
496 |
subcat = None |
|
497 |
for k in categorykeys: |
|
498 |
label = _(categories[k][0]) |
|
499 |
url = '%s/%s' % (adminurl, k) |
|
500 |
if k == category: |
|
501 |
# Handle subcategories
|
|
502 |
subcats = mlist.GetConfigSubCategories(k) |
|
503 |
if subcats: |
|
504 |
subcat = Utils.GetPathPieces()[-1] |
|
505 |
for k, v in subcats: |
|
506 |
if k == subcat: |
|
507 |
break
|
|
508 |
else: |
|
509 |
# The first subcategory in the list is the default
|
|
510 |
subcat = subcats[0][0] |
|
511 |
subcat_items = [] |
|
512 |
for sub, text in subcats: |
|
513 |
if sub == subcat: |
|
514 |
text = Bold('[%s]' % text).Format() |
|
515 |
subcat_items.append(Link(url + '/' + sub, text)) |
|
516 |
categorylinks.AddItem( |
|
517 |
Bold(label).Format() + |
|
518 |
UnorderedList(*subcat_items).Format()) |
|
519 |
else: |
|
520 |
categorylinks.AddItem(Link(url, Bold('[%s]' % label))) |
|
521 |
else: |
|
522 |
categorylinks.AddItem(Link(url, label)) |
|
523 |
counter += 1 |
|
524 |
if counter >= half: |
|
525 |
categorylinks_2 = categorylinks = UnorderedList() |
|
526 |
counter = -len(categorykeys) |
|
527 |
# Make the emergency stop switch a rude solo light
|
|
528 |
etable = Table() |
|
529 |
# Add all the links to the links table...
|
|
530 |
etable.AddRow([categorylinks_1, categorylinks_2]) |
|
531 |
etable.AddRowInfo(etable.GetCurrentRowIndex(), valign='top') |
|
532 |
if mlist.emergency: |
|
533 |
label = _('Emergency moderation of all list traffic is enabled') |
|
534 |
etable.AddRow([Center( |
|
535 |
Link('?VARHELP=general/emergency', Bold(label)))]) |
|
536 |
color = mm_cfg.WEB_ERROR_COLOR |
|
537 |
etable.AddCellInfo(etable.GetCurrentRowIndex(), 0, |
|
538 |
colspan=2, bgcolor=color) |
|
539 |
linktable.AddRow([etable, otherlinks]) |
|
540 |
# ...and add the links table to the document.
|
|
541 |
form.AddItem(linktable) |
|
542 |
form.AddItem('<hr>') |
|
543 |
form.AddItem( |
|
544 |
_('''Make your changes in the following section, then submit them |
|
545 |
using the <em>Submit Your Changes</em> button below.''') |
|
546 |
+ '<p>') |
|
547 |
||
548 |
# The members and passwords categories are special in that they aren't
|
|
549 |
# defined in terms of gui elements. Create those pages here.
|
|
550 |
if category == 'members': |
|
551 |
# Figure out which subcategory we should display
|
|
552 |
subcat = Utils.GetPathPieces()[-1] |
|
1820
by Mark Sapiro
Implemented web admin sync members. |
553 |
if subcat not in ('list', 'add', 'remove', 'change', 'sync'): |
1
by
This commit was manufactured by cvs2svn to create branch |
554 |
subcat = 'list' |
555 |
# Add member category specific tables
|
|
556 |
form.AddItem(membership_options(mlist, subcat, cgidata, doc, form)) |
|
557 |
form.AddItem(Center(submit_button('setmemberopts_btn'))) |
|
558 |
# In "list" subcategory, we can also search for members
|
|
559 |
if subcat == 'list': |
|
560 |
form.AddItem('<hr>\n') |
|
561 |
table = Table(width='100%') |
|
562 |
table.AddRow([Center(Header(2, _('Additional Member Tasks')))]) |
|
563 |
table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, |
|
564 |
bgcolor=mm_cfg.WEB_HEADER_COLOR) |
|
565 |
# Add a blank separator row
|
|
566 |
table.AddRow([' ', ' ']) |
|
567 |
# Add a section to set the moderation bit for all members
|
|
568 |
table.AddRow([_("""<li>Set everyone's moderation bit, including |
|
569 |
those members not currently visible""")]) |
|
570 |
table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) |
|
571 |
table.AddRow([RadioButtonArray('allmodbit_val', |
|
572 |
(_('Off'), _('On')), |
|
573 |
mlist.default_member_moderation), |
|
574 |
SubmitButton('allmodbit_btn', _('Set'))]) |
|
575 |
form.AddItem(table) |
|
576 |
elif category == 'passwords': |
|
577 |
form.AddItem(Center(password_inputs(mlist))) |
|
578 |
form.AddItem(Center(submit_button())) |
|
579 |
else: |
|
580 |
form.AddItem(show_variables(mlist, category, subcat, cgidata, doc)) |
|
581 |
form.AddItem(Center(submit_button())) |
|
582 |
# And add the form
|
|
583 |
doc.AddItem(form) |
|
584 |
doc.AddItem(mlist.GetMailmanFooter()) |
|
585 |
||
586 |
||
587 |
||
588 |
def show_variables(mlist, category, subcat, cgidata, doc): |
|
589 |
options = mlist.GetConfigInfo(category, subcat) |
|
590 |
||
591 |
# The table containing the results
|
|
592 |
table = Table(cellspacing=3, cellpadding=4, width='100%') |
|
593 |
||
594 |
# Get and portray the text label for the category.
|
|
595 |
categories = mlist.GetConfigCategories() |
|
596 |
label = _(categories[category][0]) |
|
597 |
||
598 |
table.AddRow([Center(Header(2, label))]) |
|
599 |
table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, |
|
600 |
bgcolor=mm_cfg.WEB_HEADER_COLOR) |
|
601 |
||
602 |
# The very first item in the config info will be treated as a general
|
|
603 |
# description if it is a string
|
|
604 |
description = options[0] |
|
605 |
if isinstance(description, StringType): |
|
606 |
table.AddRow([description]) |
|
607 |
table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) |
|
608 |
options = options[1:] |
|
609 |
||
610 |
if not options: |
|
611 |
return table |
|
612 |
||
613 |
# Add the global column headers
|
|
614 |
table.AddRow([Center(Bold(_('Description'))), |
|
615 |
Center(Bold(_('Value')))]) |
|
616 |
table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 0, |
|
617 |
width='15%') |
|
618 |
table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 1, |
|
619 |
width='85%') |
|
620 |
||
621 |
for item in options: |
|
622 |
if type(item) == StringType: |
|
623 |
# The very first banner option (string in an options list) is
|
|
624 |
# treated as a general description, while any others are
|
|
625 |
# treated as section headers - centered and italicized...
|
|
626 |
table.AddRow([Center(Italic(item))]) |
|
627 |
table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) |
|
628 |
else: |
|
629 |
add_options_table_item(mlist, category, subcat, table, item) |
|
630 |
table.AddRow(['<br>']) |
|
631 |
table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) |
|
632 |
return table |
|
633 |
||
634 |
||
635 |
||
636 |
def add_options_table_item(mlist, category, subcat, table, item, detailsp=1): |
|
637 |
# Add a row to an options table with the item description and value.
|
|
638 |
varname, kind, params, extra, descr, elaboration = \ |
|
639 |
get_item_characteristics(item) |
|
640 |
if elaboration is None: |
|
641 |
elaboration = descr |
|
642 |
descr = get_item_gui_description(mlist, category, subcat, |
|
643 |
varname, descr, elaboration, detailsp) |
|
644 |
val = get_item_gui_value(mlist, category, kind, varname, params, extra) |
|
645 |
table.AddRow([descr, val]) |
|
646 |
table.AddCellInfo(table.GetCurrentRowIndex(), 0, |
|
647 |
bgcolor=mm_cfg.WEB_ADMINITEM_COLOR) |
|
648 |
table.AddCellInfo(table.GetCurrentRowIndex(), 1, |
|
649 |
bgcolor=mm_cfg.WEB_ADMINITEM_COLOR) |
|
650 |
||
651 |
||
652 |
||
653 |
def get_item_characteristics(record): |
|
654 |
# Break out the components of an item description from its description
|
|
655 |
# record:
|
|
656 |
#
|
|
657 |
# 0 -- option-var name
|
|
658 |
# 1 -- type
|
|
659 |
# 2 -- entry size
|
|
660 |
# 3 -- ?dependancies?
|
|
661 |
# 4 -- Brief description
|
|
662 |
# 5 -- Optional description elaboration
|
|
663 |
if len(record) == 5: |
|
664 |
elaboration = None |
|
665 |
varname, kind, params, dependancies, descr = record |
|
666 |
elif len(record) == 6: |
|
667 |
varname, kind, params, dependancies, descr, elaboration = record |
|
668 |
else: |
|
669 |
raise ValueError, _('Badly formed options entry:\n %(record)s') |
|
670 |
return varname, kind, params, dependancies, descr, elaboration |
|
671 |
||
672 |
||
673 |
||
674 |
def get_item_gui_value(mlist, category, kind, varname, params, extra): |
|
675 |
"""Return a representation of an item's settings."""
|
|
676 |
# Give the category a chance to return the value for the variable
|
|
677 |
value = None |
|
678 |
label, gui = mlist.GetConfigCategories()[category] |
|
679 |
if hasattr(gui, 'getValue'): |
|
680 |
value = gui.getValue(mlist, kind, varname, params) |
|
681 |
# Filter out None, and volatile attributes
|
|
682 |
if value is None and not varname.startswith('_'): |
|
683 |
value = getattr(mlist, varname) |
|
684 |
# Now create the widget for this value
|
|
685 |
if kind == mm_cfg.Radio or kind == mm_cfg.Toggle: |
|
686 |
# If we are returning the option for subscribe policy and this site
|
|
687 |
# doesn't allow open subscribes, then we have to alter the value of
|
|
688 |
# mlist.subscribe_policy as passed to RadioButtonArray in order to
|
|
689 |
# compensate for the fact that there is one fewer option.
|
|
690 |
# Correspondingly, we alter the value back in the change options
|
|
691 |
# function -scott
|
|
692 |
#
|
|
693 |
# TBD: this is an ugly ugly hack.
|
|
694 |
if varname.startswith('_'): |
|
695 |
checked = 0 |
|
696 |
else: |
|
697 |
checked = value |
|
698 |
if varname == 'subscribe_policy' and not mm_cfg.ALLOW_OPEN_SUBSCRIBE: |
|
699 |
checked = checked - 1 |
|
700 |
# For Radio buttons, we're going to interpret the extra stuff as a
|
|
701 |
# horizontal/vertical flag. For backwards compatibility, the value 0
|
|
702 |
# means horizontal, so we use "not extra" to get the parity right.
|
|
703 |
return RadioButtonArray(varname, params, checked, not extra) |
|
704 |
elif (kind == mm_cfg.String or kind == mm_cfg.Email or |
|
705 |
kind == mm_cfg.Host or kind == mm_cfg.Number): |
|
706 |
return TextBox(varname, value, params) |
|
707 |
elif kind == mm_cfg.Text: |
|
708 |
if params: |
|
709 |
r, c = params |
|
710 |
else: |
|
711 |
r, c = None, None |
|
712 |
return TextArea(varname, value or '', r, c) |
|
713 |
elif kind in (mm_cfg.EmailList, mm_cfg.EmailListEx): |
|
714 |
if params: |
|
715 |
r, c = params |
|
716 |
else: |
|
717 |
r, c = None, None |
|
718 |
res = NL.join(value) |
|
719 |
return TextArea(varname, res, r, c, wrap='off') |
|
720 |
elif kind == mm_cfg.FileUpload: |
|
721 |
# like a text area, but also with uploading
|
|
722 |
if params: |
|
723 |
r, c = params |
|
724 |
else: |
|
725 |
r, c = None, None |
|
726 |
container = Container() |
|
727 |
container.AddItem(_('<em>Enter the text below, or...</em><br>')) |
|
728 |
container.AddItem(TextArea(varname, value or '', r, c)) |
|
729 |
container.AddItem(_('<br><em>...specify a file to upload</em><br>')) |
|
730 |
container.AddItem(FileUpload(varname+'_upload', r, c)) |
|
731 |
return container |
|
732 |
elif kind == mm_cfg.Select: |
|
733 |
if params: |
|
734 |
values, legend, selected = params |
|
735 |
else: |
|
736 |
values = mlist.GetAvailableLanguages() |
|
737 |
legend = map(_, map(Utils.GetLanguageDescr, values)) |
|
738 |
selected = values.index(mlist.preferred_language) |
|
739 |
return SelectOptions(varname, values, legend, selected) |
|
740 |
elif kind == mm_cfg.Topics: |
|
741 |
# A complex and specialized widget type that allows for setting of a
|
|
742 |
# topic name, a mark button, a regexp text box, an "add after mark",
|
|
743 |
# and a delete button. Yeesh! params are ignored.
|
|
744 |
table = Table(border=0) |
|
745 |
# This adds the html for the entry widget
|
|
205
by bwarsaw
get_item_gui_value(): Added a new widget HeaderFilter and associated code to |
746 |
def makebox(i, name, pattern, desc, empty=False, table=table): |
1
by
This commit was manufactured by cvs2svn to create branch |
747 |
deltag = 'topic_delete_%02d' % i |
748 |
boxtag = 'topic_box_%02d' % i |
|
749 |
reboxtag = 'topic_rebox_%02d' % i |
|
750 |
desctag = 'topic_desc_%02d' % i |
|
751 |
wheretag = 'topic_where_%02d' % i |
|
752 |
addtag = 'topic_add_%02d' % i |
|
753 |
newtag = 'topic_new_%02d' % i |
|
754 |
if empty: |
|
755 |
table.AddRow([Center(Bold(_('Topic %(i)d'))), |
|
756 |
Hidden(newtag)]) |
|
757 |
else: |
|
758 |
table.AddRow([Center(Bold(_('Topic %(i)d'))), |
|
759 |
SubmitButton(deltag, _('Delete'))]) |
|
760 |
table.AddRow([Label(_('Topic name:')), |
|
761 |
TextBox(boxtag, value=name, size=30)]) |
|
762 |
table.AddRow([Label(_('Regexp:')), |
|
763 |
TextArea(reboxtag, text=pattern, |
|
764 |
rows=4, cols=30, wrap='off')]) |
|
765 |
table.AddRow([Label(_('Description:')), |
|
766 |
TextArea(desctag, text=desc, |
|
767 |
rows=4, cols=30, wrap='soft')]) |
|
768 |
if not empty: |
|
769 |
table.AddRow([SubmitButton(addtag, _('Add new item...')), |
|
770 |
SelectOptions(wheretag, ('before', 'after'), |
|
771 |
(_('...before this one.'), |
|
772 |
_('...after this one.')), |
|
773 |
selected=1), |
|
774 |
])
|
|
775 |
table.AddRow(['<hr>']) |
|
776 |
table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) |
|
777 |
# Now for each element in the existing data, create a widget
|
|
778 |
i = 1 |
|
779 |
data = getattr(mlist, varname) |
|
780 |
for name, pattern, desc, empty in data: |
|
781 |
makebox(i, name, pattern, desc, empty) |
|
782 |
i += 1 |
|
783 |
# Add one more non-deleteable widget as the first blank entry, but
|
|
784 |
# only if there are no real entries.
|
|
785 |
if i == 1: |
|
205
by bwarsaw
get_item_gui_value(): Added a new widget HeaderFilter and associated code to |
786 |
makebox(i, '', '', '', empty=True) |
787 |
return table |
|
788 |
elif kind == mm_cfg.HeaderFilter: |
|
789 |
# A complex and specialized widget type that allows for setting of a
|
|
790 |
# spam filter rule including, a mark button, a regexp text box, an
|
|
791 |
# "add after mark", up and down buttons, and a delete button. Yeesh!
|
|
792 |
# params are ignored.
|
|
793 |
table = Table(border=0) |
|
794 |
# This adds the html for the entry widget
|
|
795 |
def makebox(i, pattern, action, empty=False, table=table): |
|
796 |
deltag = 'hdrfilter_delete_%02d' % i |
|
797 |
reboxtag = 'hdrfilter_rebox_%02d' % i |
|
798 |
actiontag = 'hdrfilter_action_%02d' % i |
|
799 |
wheretag = 'hdrfilter_where_%02d' % i |
|
800 |
addtag = 'hdrfilter_add_%02d' % i |
|
801 |
newtag = 'hdrfilter_new_%02d' % i |
|
802 |
uptag = 'hdrfilter_up_%02d' % i |
|
803 |
downtag = 'hdrfilter_down_%02d' % i |
|
804 |
if empty: |
|
805 |
table.AddRow([Center(Bold(_('Spam Filter Rule %(i)d'))), |
|
806 |
Hidden(newtag)]) |
|
807 |
else: |
|
808 |
table.AddRow([Center(Bold(_('Spam Filter Rule %(i)d'))), |
|
809 |
SubmitButton(deltag, _('Delete'))]) |
|
810 |
table.AddRow([Label(_('Spam Filter Regexp:')), |
|
811 |
TextArea(reboxtag, text=pattern, |
|
812 |
rows=4, cols=30, wrap='off')]) |
|
813 |
values = [mm_cfg.DEFER, mm_cfg.HOLD, mm_cfg.REJECT, |
|
814 |
mm_cfg.DISCARD, mm_cfg.ACCEPT] |
|
815 |
try: |
|
816 |
checked = values.index(action) |
|
817 |
except ValueError: |
|
818 |
checked = 0 |
|
819 |
radio = RadioButtonArray( |
|
820 |
actiontag, |
|
821 |
(_('Defer'), _('Hold'), _('Reject'), |
|
822 |
_('Discard'), _('Accept')), |
|
823 |
values=values, |
|
824 |
checked=checked).Format() |
|
825 |
table.AddRow([Label(_('Action:')), radio]) |
|
826 |
if not empty: |
|
827 |
table.AddRow([SubmitButton(addtag, _('Add new item...')), |
|
828 |
SelectOptions(wheretag, ('before', 'after'), |
|
829 |
(_('...before this one.'), |
|
830 |
_('...after this one.')), |
|
831 |
selected=1), |
|
832 |
])
|
|
833 |
# BAW: IWBNI we could disable the up and down buttons for the
|
|
834 |
# first and last item respectively, but it's not easy to know
|
|
835 |
# which is the last item, so let's not worry about that for
|
|
836 |
# now.
|
|
837 |
table.AddRow([SubmitButton(uptag, _('Move rule up')), |
|
838 |
SubmitButton(downtag, _('Move rule down'))]) |
|
839 |
table.AddRow(['<hr>']) |
|
840 |
table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) |
|
841 |
# Now for each element in the existing data, create a widget
|
|
842 |
i = 1 |
|
843 |
data = getattr(mlist, varname) |
|
844 |
for pattern, action, empty in data: |
|
845 |
makebox(i, pattern, action, empty) |
|
846 |
i += 1 |
|
847 |
# Add one more non-deleteable widget as the first blank entry, but
|
|
848 |
# only if there are no real entries.
|
|
849 |
if i == 1: |
|
850 |
makebox(i, '', mm_cfg.DEFER, empty=True) |
|
1
by
This commit was manufactured by cvs2svn to create branch |
851 |
return table |
852 |
elif kind == mm_cfg.Checkbox: |
|
853 |
return CheckBoxArray(varname, *params) |
|
854 |
else: |
|
855 |
assert 0, 'Bad gui widget type: %s' % kind |
|
856 |
||
857 |
||
858 |
||
859 |
def get_item_gui_description(mlist, category, subcat, |
|
860 |
varname, descr, elaboration, detailsp): |
|
861 |
# Return the item's description, with link to details.
|
|
862 |
#
|
|
863 |
# Details are not included if this is a VARHELP page, because that /is/
|
|
864 |
# the details page!
|
|
865 |
if detailsp: |
|
866 |
if subcat: |
|
867 |
varhelp = '/?VARHELP=%s/%s/%s' % (category, subcat, varname) |
|
868 |
else: |
|
869 |
varhelp = '/?VARHELP=%s/%s' % (category, varname) |
|
870 |
if descr == elaboration: |
|
871 |
linktext = _('<br>(Edit <b>%(varname)s</b>)') |
|
872 |
else: |
|
873 |
linktext = _('<br>(Details for <b>%(varname)s</b>)') |
|
874 |
link = Link(mlist.GetScriptURL('admin') + varhelp, |
|
875 |
linktext).Format() |
|
876 |
text = Label('%s %s' % (descr, link)).Format() |
|
877 |
else: |
|
878 |
text = Label(descr).Format() |
|
879 |
if varname[0] == '_': |
|
880 |
text += Label(_('''<br><em><strong>Note:</strong> |
|
881 |
setting this value performs an immediate action but does not modify
|
|
882 |
permanent state.</em>''')).Format() |
|
883 |
return text |
|
884 |
||
885 |
||
886 |
||
887 |
def membership_options(mlist, subcat, cgidata, doc, form): |
|
888 |
# Show the main stuff
|
|
889 |
adminurl = mlist.GetScriptURL('admin', absolute=1) |
|
890 |
container = Container() |
|
891 |
header = Table(width="100%") |
|
892 |
# If we're in the list subcategory, show the membership list
|
|
893 |
if subcat == 'add': |
|
894 |
header.AddRow([Center(Header(2, _('Mass Subscriptions')))]) |
|
895 |
header.AddCellInfo(header.GetCurrentRowIndex(), 0, colspan=2, |
|
896 |
bgcolor=mm_cfg.WEB_HEADER_COLOR) |
|
897 |
container.AddItem(header) |
|
898 |
mass_subscribe(mlist, container) |
|
899 |
return container |
|
900 |
if subcat == 'remove': |
|
901 |
header.AddRow([Center(Header(2, _('Mass Removals')))]) |
|
902 |
header.AddCellInfo(header.GetCurrentRowIndex(), 0, colspan=2, |
|
903 |
bgcolor=mm_cfg.WEB_HEADER_COLOR) |
|
904 |
container.AddItem(header) |
|
905 |
mass_remove(mlist, container) |
|
906 |
return container |
|
1551
by Mark Sapiro
Implemented member address change via the admin GUI. |
907 |
if subcat == 'change': |
908 |
header.AddRow([Center(Header(2, _('Address Change')))]) |
|
909 |
header.AddCellInfo(header.GetCurrentRowIndex(), 0, colspan=2, |
|
910 |
bgcolor=mm_cfg.WEB_HEADER_COLOR) |
|
911 |
container.AddItem(header) |
|
912 |
address_change(mlist, container) |
|
913 |
return container |
|
1820
by Mark Sapiro
Implemented web admin sync members. |
914 |
if subcat == 'sync': |
915 |
header.AddRow([Center(Header(2, _('Sync Membership List')))]) |
|
916 |
header.AddCellInfo(header.GetCurrentRowIndex(), 0, colspan=2, |
|
917 |
bgcolor=mm_cfg.WEB_HEADER_COLOR) |
|
918 |
container.AddItem(header) |
|
919 |
mass_sync(mlist, container) |
|
920 |
return container |
|
1
by
This commit was manufactured by cvs2svn to create branch |
921 |
# Otherwise...
|
922 |
header.AddRow([Center(Header(2, _('Membership List')))]) |
|
923 |
header.AddCellInfo(header.GetCurrentRowIndex(), 0, colspan=2, |
|
924 |
bgcolor=mm_cfg.WEB_HEADER_COLOR) |
|
925 |
container.AddItem(header) |
|
926 |
# Add a "search for member" button
|
|
927 |
table = Table(width='100%') |
|
1792.1.1
by Yasuhito FUTATSUKI at POEM
fix python doc urls |
928 |
link = Link('https://docs.python.org/2/library/re.html' |
1156
by Mark Sapiro
Updated links to Python documentation. |
929 |
'#regular-expression-syntax', |
1
by
This commit was manufactured by cvs2svn to create branch |
930 |
_('(help)')).Format() |
931 |
table.AddRow([Label(_('Find member %(link)s:')), |
|
932 |
TextBox('findmember', |
|
1712
by Mark Sapiro
Defend against CGI requests with multiple values for the same parameter. |
933 |
value=cgidata.getfirst('findmember', '')), |
1
by
This commit was manufactured by cvs2svn to create branch |
934 |
SubmitButton('findmember_btn', _('Search...'))]) |
935 |
container.AddItem(table) |
|
936 |
container.AddItem('<hr><p>') |
|
937 |
usertable = Table(width="90%", border='2') |
|
938 |
# If there are more members than allowed by chunksize, then we split the
|
|
939 |
# membership up alphabetically. Otherwise just display them all.
|
|
940 |
chunksz = mlist.admin_member_chunksize |
|
86
by bwarsaw
Backporting from the HEAD -- updated cgi's |
941 |
# The email addresses had /better/ be ASCII, but might be encoded in the
|
942 |
# database as Unicodes.
|
|
943 |
all = [_m.encode() for _m in mlist.getMembers()] |
|
1
by
This commit was manufactured by cvs2svn to create branch |
944 |
all.sort(lambda x, y: cmp(x.lower(), y.lower())) |
945 |
# See if the query has a regular expression
|
|
1712
by Mark Sapiro
Defend against CGI requests with multiple values for the same parameter. |
946 |
regexp = cgidata.getfirst('findmember', '').strip() |
1558
by Mark Sapiro
Improved search in admin UI Membership List. |
947 |
try: |
948 |
regexp = regexp.decode(Utils.GetCharSet(mlist.preferred_language)) |
|
949 |
except UnicodeDecodeError: |
|
950 |
# This is probably a non-ascii character and an English language
|
|
951 |
# (ascii) list. Even if we didn't throw the UnicodeDecodeError,
|
|
952 |
# the input may have contained mnemonic or numeric HTML entites mixed
|
|
953 |
# with other characters. Trying to grok the real meaning out of that
|
|
954 |
# is complex and error prone, so we don't try.
|
|
955 |
pass
|
|
1
by
This commit was manufactured by cvs2svn to create branch |
956 |
if regexp: |
957 |
try: |
|
958 |
cre = re.compile(regexp, re.IGNORECASE) |
|
959 |
except re.error: |
|
960 |
doc.addError(_('Bad regular expression: ') + regexp) |
|
961 |
else: |
|
962 |
# BAW: There's got to be a more efficient way of doing this!
|
|
963 |
names = [mlist.getMemberName(s) or '' for s in all] |
|
964 |
all = [a for n, a in zip(names, all) |
|
965 |
if cre.search(n) or cre.search(a)] |
|
966 |
chunkindex = None |
|
967 |
bucket = None |
|
968 |
actionurl = None |
|
969 |
if len(all) < chunksz: |
|
970 |
members = all |
|
971 |
else: |
|
972 |
# Split them up alphabetically, and then split the alphabetical
|
|
973 |
# listing by chunks
|
|
974 |
buckets = {} |
|
975 |
for addr in all: |
|
976 |
members = buckets.setdefault(addr[0].lower(), []) |
|
977 |
members.append(addr) |
|
978 |
# Now figure out which bucket we want
|
|
979 |
bucket = None |
|
980 |
qs = {} |
|
981 |
# POST methods, even if their actions have a query string, don't get
|
|
982 |
# put into FieldStorage's keys :-(
|
|
983 |
qsenviron = os.environ.get('QUERY_STRING') |
|
984 |
if qsenviron: |
|
985 |
qs = cgi.parse_qs(qsenviron) |
|
1123
by Mark Sapiro
Fixed a bug in admin.py which would result in chunked pages of the membership |
986 |
bucket = qs.get('letter', '0')[0].lower() |
987 |
keys = buckets.keys() |
|
988 |
keys.sort() |
|
1
by
This commit was manufactured by cvs2svn to create branch |
989 |
if not bucket or not buckets.has_key(bucket): |
990 |
bucket = keys[0] |
|
991 |
members = buckets[bucket] |
|
992 |
action = adminurl + '/members?letter=%s' % bucket |
|
993 |
if len(members) <= chunksz: |
|
994 |
form.set_action(action) |
|
995 |
else: |
|
996 |
i, r = divmod(len(members), chunksz) |
|
997 |
numchunks = i + (not not r * 1) |
|
998 |
# Now chunk them up
|
|
999 |
chunkindex = 0 |
|
1000 |
if qs.has_key('chunk'): |
|
1001 |
try: |
|
1002 |
chunkindex = int(qs['chunk'][0]) |
|
1003 |
except ValueError: |
|
1004 |
chunkindex = 0 |
|
1005 |
if chunkindex < 0 or chunkindex > numchunks: |
|
1006 |
chunkindex = 0 |
|
1007 |
members = members[chunkindex*chunksz:(chunkindex+1)*chunksz] |
|
1008 |
# And set the action URL
|
|
1009 |
form.set_action(action + '&chunk=%s' % chunkindex) |
|
1010 |
# So now members holds all the addresses we're going to display
|
|
1011 |
allcnt = len(all) |
|
1012 |
if bucket: |
|
1013 |
membercnt = len(members) |
|
1014 |
usertable.AddRow([Center(Italic(_( |
|
1015 |
'%(allcnt)s members total, %(membercnt)s shown')))]) |
|
1016 |
else: |
|
1017 |
usertable.AddRow([Center(Italic(_('%(allcnt)s members total')))]) |
|
1018 |
usertable.AddCellInfo(usertable.GetCurrentRowIndex(), |
|
1019 |
usertable.GetCurrentCellIndex(), |
|
1020 |
colspan=OPTCOLUMNS, |
|
1021 |
bgcolor=mm_cfg.WEB_ADMINITEM_COLOR) |
|
1022 |
# Add the alphabetical links
|
|
1023 |
if bucket: |
|
1024 |
cells = [] |
|
1123
by Mark Sapiro
Fixed a bug in admin.py which would result in chunked pages of the membership |
1025 |
for letter in keys: |
1155
by Mark Sapiro
- Fixed the admin Membership List Find member function so the 'letter' |
1026 |
findfrag = '' |
1027 |
if regexp: |
|
1028 |
findfrag = '&findmember=' + urllib.quote(regexp) |
|
1029 |
url = adminurl + '/members?letter=' + letter + findfrag |
|
1665
by Mark Sapiro
Membership List letter links could be incorrectly rendered as Unicode. |
1030 |
if isinstance(url, unicode): |
1031 |
url = url.encode(Utils.GetCharSet(mlist.preferred_language), |
|
1032 |
errors='ignore') |
|
1
by
This commit was manufactured by cvs2svn to create branch |
1033 |
if letter == bucket: |
1034 |
show = Bold('[%s]' % letter.upper()).Format() |
|
1035 |
else: |
|
1036 |
show = letter.upper() |
|
1037 |
cells.append(Link(url, show).Format()) |
|
1038 |
joiner = ' '*2 + '\n' |
|
1039 |
usertable.AddRow([Center(joiner.join(cells))]) |
|
1040 |
usertable.AddCellInfo(usertable.GetCurrentRowIndex(), |
|
1041 |
usertable.GetCurrentCellIndex(), |
|
1042 |
colspan=OPTCOLUMNS, |
|
1043 |
bgcolor=mm_cfg.WEB_ADMINITEM_COLOR) |
|
1044 |
usertable.AddRow([Center(h) for h in (_('unsub'), |
|
1045 |
_('member address<br>member name'), |
|
1046 |
_('mod'), _('hide'), |
|
1047 |
_('nomail<br>[reason]'), |
|
1048 |
_('ack'), _('not metoo'), |
|
1049 |
_('nodupes'), |
|
1050 |
_('digest'), _('plain'), |
|
1051 |
_('language'))]) |
|
1052 |
rowindex = usertable.GetCurrentRowIndex() |
|
1053 |
for i in range(OPTCOLUMNS): |
|
1054 |
usertable.AddCellInfo(rowindex, i, bgcolor=mm_cfg.WEB_ADMINITEM_COLOR) |
|
1055 |
# Find the longest name in the list
|
|
1056 |
longest = 0 |
|
1057 |
if members: |
|
1058 |
names = filter(None, [mlist.getMemberName(s) for s in members]) |
|
1059 |
# Make the name field at least as long as the longest email address
|
|
1060 |
longest = max([len(s) for s in names + members]) |
|
1061 |
# Abbreviations for delivery status details
|
|
1062 |
ds_abbrevs = {MemberAdaptor.UNKNOWN : _('?'), |
|
1063 |
MemberAdaptor.BYUSER : _('U'), |
|
1064 |
MemberAdaptor.BYADMIN : _('A'), |
|
1065 |
MemberAdaptor.BYBOUNCE: _('B'), |
|
1066 |
}
|
|
1067 |
# Now populate the rows
|
|
1068 |
for addr in members: |
|
972
by msapiro
- CGI/admin.py |
1069 |
qaddr = urllib.quote(addr) |
1
by
This commit was manufactured by cvs2svn to create branch |
1070 |
link = Link(mlist.GetOptionsURL(addr, obscure=1), |
1071 |
mlist.getMemberCPAddress(addr)) |
|
1072 |
fullname = Utils.uncanonstr(mlist.getMemberName(addr), |
|
1073 |
mlist.preferred_language) |
|
972
by msapiro
- CGI/admin.py |
1074 |
name = TextBox(qaddr + '_realname', fullname, size=longest).Format() |
1718
by Mark Sapiro
Added text for screen readers only to checkboxes on admin Membership List. |
1075 |
cells = [Center(CheckBox(qaddr + '_unsub', 'off', 0).Format() |
1076 |
+ '<div class="hidden">' + _('unsub') + '</div>'), |
|
1
by
This commit was manufactured by cvs2svn to create branch |
1077 |
link.Format() + '<br>' + |
1078 |
name + |
|
972
by msapiro
- CGI/admin.py |
1079 |
Hidden('user', qaddr).Format(), |
1
by
This commit was manufactured by cvs2svn to create branch |
1080 |
]
|
1081 |
# Do the `mod' option
|
|
1082 |
if mlist.getMemberOption(addr, mm_cfg.Moderate): |
|
1083 |
value = 'on' |
|
1084 |
checked = 1 |
|
1085 |
else: |
|
1086 |
value = 'off' |
|
1087 |
checked = 0 |
|
972
by msapiro
- CGI/admin.py |
1088 |
box = CheckBox('%s_mod' % qaddr, value, checked) |
1718
by Mark Sapiro
Added text for screen readers only to checkboxes on admin Membership List. |
1089 |
cells.append(Center(box.Format() |
1090 |
+ '<div class="hidden">' + _('mod') + '</div>')) |
|
1091 |
# Kluge, get these translated.
|
|
1092 |
(_('hide'), _('nomail'), _('ack'), _('notmetoo'), _('nodupes')) |
|
1
by
This commit was manufactured by cvs2svn to create branch |
1093 |
for opt in ('hide', 'nomail', 'ack', 'notmetoo', 'nodupes'): |
1718
by Mark Sapiro
Added text for screen readers only to checkboxes on admin Membership List. |
1094 |
extra = '<div class="hidden">' + _(opt) + '</div>' |
1
by
This commit was manufactured by cvs2svn to create branch |
1095 |
if opt == 'nomail': |
1096 |
status = mlist.getDeliveryStatus(addr) |
|
1097 |
if status == MemberAdaptor.ENABLED: |
|
1098 |
value = 'off' |
|
1099 |
checked = 0 |
|
1100 |
else: |
|
1101 |
value = 'on' |
|
1102 |
checked = 1 |
|
1718
by Mark Sapiro
Added text for screen readers only to checkboxes on admin Membership List. |
1103 |
extra = '[%s]' % ds_abbrevs[status] + extra |
1
by
This commit was manufactured by cvs2svn to create branch |
1104 |
elif mlist.getMemberOption(addr, mm_cfg.OPTINFO[opt]): |
1105 |
value = 'on' |
|
1106 |
checked = 1 |
|
1107 |
else: |
|
1108 |
value = 'off' |
|
1109 |
checked = 0 |
|
972
by msapiro
- CGI/admin.py |
1110 |
box = CheckBox('%s_%s' % (qaddr, opt), value, checked) |
1
by
This commit was manufactured by cvs2svn to create branch |
1111 |
cells.append(Center(box.Format() + extra)) |
1112 |
# This code is less efficient than the original which did a has_key on
|
|
1113 |
# the underlying dictionary attribute. This version is slower and
|
|
1114 |
# less memory efficient. It points to a new MemberAdaptor interface
|
|
1115 |
# method.
|
|
1718
by Mark Sapiro
Added text for screen readers only to checkboxes on admin Membership List. |
1116 |
extra = '<div class="hidden">' + _('digest') + '</div>' |
1
by
This commit was manufactured by cvs2svn to create branch |
1117 |
if addr in mlist.getRegularMemberKeys(): |
1718
by Mark Sapiro
Added text for screen readers only to checkboxes on admin Membership List. |
1118 |
cells.append(Center(CheckBox(qaddr + '_digest', 'off', 0).Format() |
1119 |
+ extra)) |
|
1
by
This commit was manufactured by cvs2svn to create branch |
1120 |
else: |
1718
by Mark Sapiro
Added text for screen readers only to checkboxes on admin Membership List. |
1121 |
cells.append(Center(CheckBox(qaddr + '_digest', 'on', 1).Format() |
1122 |
+ extra)) |
|
1
by
This commit was manufactured by cvs2svn to create branch |
1123 |
if mlist.getMemberOption(addr, mm_cfg.OPTINFO['plain']): |
1124 |
value = 'on' |
|
1125 |
checked = 1 |
|
1126 |
else: |
|
1127 |
value = 'off' |
|
1128 |
checked = 0 |
|
1718
by Mark Sapiro
Added text for screen readers only to checkboxes on admin Membership List. |
1129 |
cells.append(Center(CheckBox( |
1130 |
'%s_plain' % qaddr, value, checked).Format() |
|
1131 |
+ '<div class="hidden">' + _('plain') + '</div>')) |
|
1
by
This commit was manufactured by cvs2svn to create branch |
1132 |
# User's preferred language
|
1133 |
langpref = mlist.getMemberLanguage(addr) |
|
1134 |
langs = mlist.GetAvailableLanguages() |
|
1135 |
langdescs = [_(Utils.GetLanguageDescr(lang)) for lang in langs] |
|
1136 |
try: |
|
1137 |
selected = langs.index(langpref) |
|
1138 |
except ValueError: |
|
1139 |
selected = 0 |
|
972
by msapiro
- CGI/admin.py |
1140 |
cells.append(Center(SelectOptions(qaddr + '_language', langs, |
1
by
This commit was manufactured by cvs2svn to create branch |
1141 |
langdescs, selected)).Format()) |
1142 |
usertable.AddRow(cells) |
|
1143 |
# Add the usertable and a legend
|
|
1144 |
legend = UnorderedList() |
|
1145 |
legend.AddItem( |
|
1146 |
_('<b>unsub</b> -- Click on this to unsubscribe the member.')) |
|
1147 |
legend.AddItem( |
|
1148 |
_("""<b>mod</b> -- The user's personal moderation flag. If this is |
|
1149 |
set, postings from them will be moderated, otherwise they will be
|
|
1150 |
approved.""")) |
|
1151 |
legend.AddItem( |
|
1152 |
_("""<b>hide</b> -- Is the member's address concealed on |
|
1153 |
the list of subscribers?""")) |
|
1154 |
legend.AddItem(_( |
|
1155 |
"""<b>nomail</b> -- Is delivery to the member disabled? If so, an
|
|
1156 |
abbreviation will be given describing the reason for the disabled
|
|
1157 |
delivery:
|
|
1158 |
<ul><li><b>U</b> -- Delivery was disabled by the user via their
|
|
1159 |
personal options page.
|
|
1160 |
<li><b>A</b> -- Delivery was disabled by the list
|
|
1161 |
administrators.
|
|
1162 |
<li><b>B</b> -- Delivery was disabled by the system due to
|
|
1163 |
excessive bouncing from the member's address.
|
|
1164 |
<li><b>?</b> -- The reason for disabled delivery isn't known.
|
|
1165 |
This is the case for all memberships which were disabled
|
|
1166 |
in older versions of Mailman.
|
|
1167 |
</ul>""")) |
|
1168 |
legend.AddItem( |
|
1169 |
_('''<b>ack</b> -- Does the member get acknowledgements of their |
|
1170 |
posts?''')) |
|
1171 |
legend.AddItem( |
|
1172 |
_('''<b>not metoo</b> -- Does the member want to avoid copies of their |
|
1173 |
own postings?''')) |
|
1174 |
legend.AddItem( |
|
1175 |
_('''<b>nodupes</b> -- Does the member want to avoid duplicates of the |
|
1176 |
same message?''')) |
|
1177 |
legend.AddItem( |
|
1178 |
_('''<b>digest</b> -- Does the member get messages in digests? |
|
1179 |
(otherwise, individual messages)''')) |
|
1180 |
legend.AddItem( |
|
1181 |
_('''<b>plain</b> -- If getting digests, does the member get plain |
|
1182 |
text digests? (otherwise, MIME)''')) |
|
1183 |
legend.AddItem(_("<b>language</b> -- Language preferred by the user")) |
|
1184 |
addlegend = '' |
|
1185 |
parsedqs = 0 |
|
1186 |
qsenviron = os.environ.get('QUERY_STRING') |
|
1187 |
if qsenviron: |
|
1188 |
qs = cgi.parse_qs(qsenviron).get('legend') |
|
1189 |
if qs and isinstance(qs, ListType): |
|
1190 |
qs = qs[0] |
|
1191 |
if qs == 'yes': |
|
1192 |
addlegend = 'legend=yes&' |
|
1193 |
if addlegend: |
|
1194 |
container.AddItem(legend.Format() + '<p>') |
|
1195 |
container.AddItem( |
|
1196 |
Link(adminurl + '/members/list', |
|
1197 |
_('Click here to hide the legend for this table.'))) |
|
1198 |
else: |
|
1199 |
container.AddItem( |
|
1200 |
Link(adminurl + '/members/list?legend=yes', |
|
1201 |
_('Click here to include the legend for this table.'))) |
|
1202 |
container.AddItem(Center(usertable)) |
|
1203 |
||
1204 |
# There may be additional chunks
|
|
1205 |
if chunkindex is not None: |
|
1206 |
buttons = [] |
|
1207 |
url = adminurl + '/members?%sletter=%s&' % (addlegend, bucket) |
|
1208 |
footer = _('''<p><em>To view more members, click on the appropriate |
|
1209 |
range listed below:</em>''') |
|
1210 |
chunkmembers = buckets[bucket] |
|
1211 |
last = len(chunkmembers) |
|
1212 |
for i in range(numchunks): |
|
1213 |
if i == chunkindex: |
|
1214 |
continue
|
|
1215 |
start = chunkmembers[i*chunksz] |
|
1216 |
end = chunkmembers[min((i+1)*chunksz, last)-1] |
|
1679
by Mark Sapiro
Fixed incorrect "view more members" links at the bottom of the admin |
1217 |
thisurl = url + 'chunk=%d' % i + findfrag |
1218 |
if isinstance(thisurl, unicode): |
|
1219 |
thisurl = thisurl.encode( |
|
1220 |
Utils.GetCharSet(mlist.preferred_language), |
|
1677
by Mark Sapiro
Fix unicode links in multi-page admin Membership list search results. |
1221 |
errors='ignore') |
1679
by Mark Sapiro
Fixed incorrect "view more members" links at the bottom of the admin |
1222 |
link = Link(thisurl, _('from %(start)s to %(end)s')) |
1
by
This commit was manufactured by cvs2svn to create branch |
1223 |
buttons.append(link) |
1224 |
buttons = UnorderedList(*buttons) |
|
1225 |
container.AddItem(footer + buttons.Format() + '<p>') |
|
1226 |
return container |
|
1227 |
||
1228 |
||
1229 |
||
1230 |
def mass_subscribe(mlist, container): |
|
1231 |
# MASS SUBSCRIBE
|
|
1232 |
GREY = mm_cfg.WEB_ADMINITEM_COLOR |
|
1233 |
table = Table(width='90%') |
|
1234 |
table.AddRow([ |
|
1235 |
Label(_('Subscribe these users now or invite them?')), |
|
1236 |
RadioButtonArray('subscribe_or_invite', |
|
1237 |
(_('Subscribe'), _('Invite')), |
|
1509
by Mark Sapiro
Implement a new DEFAULT_SUBSCRIBE_OR_INVITE setting to control the default |
1238 |
mm_cfg.DEFAULT_SUBSCRIBE_OR_INVITE, |
1239 |
values=(0, 1)) |
|
1
by
This commit was manufactured by cvs2svn to create branch |
1240 |
])
|
1241 |
table.AddCellInfo(table.GetCurrentRowIndex(), 0, bgcolor=GREY) |
|
1242 |
table.AddCellInfo(table.GetCurrentRowIndex(), 1, bgcolor=GREY) |
|
1243 |
table.AddRow([ |
|
1699
by Mark Sapiro
Change 'subscribees' to 'subscribers' on admin mass subscribe page. |
1244 |
Label(_('Send welcome messages to new subscribers?')), |
1
by
This commit was manufactured by cvs2svn to create branch |
1245 |
RadioButtonArray('send_welcome_msg_to_this_batch', |
1246 |
(_('No'), _('Yes')), |
|
1247 |
mlist.send_welcome_msg, |
|
1248 |
values=(0, 1)) |
|
1249 |
])
|
|
1250 |
table.AddCellInfo(table.GetCurrentRowIndex(), 0, bgcolor=GREY) |
|
1251 |
table.AddCellInfo(table.GetCurrentRowIndex(), 1, bgcolor=GREY) |
|
1252 |
table.AddRow([ |
|
1253 |
Label(_('Send notifications of new subscriptions to the list owner?')), |
|
1254 |
RadioButtonArray('send_notifications_to_list_owner', |
|
1255 |
(_('No'), _('Yes')), |
|
1256 |
mlist.admin_notify_mchanges, |
|
1257 |
values=(0,1)) |
|
1258 |
])
|
|
1259 |
table.AddCellInfo(table.GetCurrentRowIndex(), 0, bgcolor=GREY) |
|
1260 |
table.AddCellInfo(table.GetCurrentRowIndex(), 1, bgcolor=GREY) |
|
1261 |
table.AddRow([Italic(_('Enter one address per line below...'))]) |
|
1262 |
table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) |
|
1263 |
table.AddRow([Center(TextArea(name='subscribees', |
|
1264 |
rows=10, cols='70%', wrap=None))]) |
|
1265 |
table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) |
|
1266 |
table.AddRow([Italic(Label(_('...or specify a file to upload:'))), |
|
1267 |
FileUpload('subscribees_upload', cols='50')]) |
|
1268 |
container.AddItem(Center(table)) |
|
1269 |
# Invitation text
|
|
1270 |
table.AddRow([' ', ' ']) |
|
1271 |
table.AddRow([Italic(_("""Below, enter additional text to be added to the |
|
1272 |
top of your invitation or the subscription notification. Include at least
|
|
1273 |
one blank line at the end..."""))]) |
|
1274 |
table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) |
|
1275 |
table.AddRow([Center(TextArea(name='invitation', |
|
1276 |
rows=10, cols='70%', wrap=None))]) |
|
1277 |
table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) |
|
1278 |
||
1279 |
||
1280 |
||
1281 |
def mass_remove(mlist, container): |
|
1282 |
# MASS UNSUBSCRIBE
|
|
1283 |
GREY = mm_cfg.WEB_ADMINITEM_COLOR |
|
1284 |
table = Table(width='90%') |
|
1285 |
table.AddRow([ |
|
1286 |
Label(_('Send unsubscription acknowledgement to the user?')), |
|
1287 |
RadioButtonArray('send_unsub_ack_to_this_batch', |
|
1288 |
(_('No'), _('Yes')), |
|
1289 |
0, values=(0, 1)) |
|
1290 |
])
|
|
1291 |
table.AddCellInfo(table.GetCurrentRowIndex(), 0, bgcolor=GREY) |
|
1292 |
table.AddCellInfo(table.GetCurrentRowIndex(), 1, bgcolor=GREY) |
|
1293 |
table.AddRow([ |
|
1294 |
Label(_('Send notifications to the list owner?')), |
|
1295 |
RadioButtonArray('send_unsub_notifications_to_list_owner', |
|
1296 |
(_('No'), _('Yes')), |
|
1297 |
mlist.admin_notify_mchanges, |
|
1298 |
values=(0, 1)) |
|
1299 |
])
|
|
1300 |
table.AddCellInfo(table.GetCurrentRowIndex(), 0, bgcolor=GREY) |
|
1301 |
table.AddCellInfo(table.GetCurrentRowIndex(), 1, bgcolor=GREY) |
|
1302 |
table.AddRow([Italic(_('Enter one address per line below...'))]) |
|
1303 |
table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) |
|
1304 |
table.AddRow([Center(TextArea(name='unsubscribees', |
|
1305 |
rows=10, cols='70%', wrap=None))]) |
|
1306 |
table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) |
|
1307 |
table.AddRow([Italic(Label(_('...or specify a file to upload:'))), |
|
1308 |
FileUpload('unsubscribees_upload', cols='50')]) |
|
1309 |
container.AddItem(Center(table)) |
|
1310 |
||
1311 |
||
1312 |
||
1551
by Mark Sapiro
Implemented member address change via the admin GUI. |
1313 |
def address_change(mlist, container): |
1314 |
# ADDRESS CHANGE
|
|
1315 |
GREY = mm_cfg.WEB_ADMINITEM_COLOR |
|
1316 |
table = Table(width='90%') |
|
1317 |
table.AddRow([Italic(_("""To change a list member's address, enter the |
|
1318 |
member's current and new addresses below. Use the check boxes to send
|
|
1319 |
notice of the change to the old and/or new address(es)."""))]) |
|
1320 |
table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=3) |
|
1321 |
table.AddRow([ |
|
1322 |
Label(_("Member's current address")), |
|
1323 |
TextBox(name='change_from'), |
|
1324 |
CheckBox('notice_old', 'yes', 0).Format() + |
|
1325 |
' ' + |
|
1326 |
_('Send notice') |
|
1327 |
])
|
|
1328 |
table.AddCellInfo(table.GetCurrentRowIndex(), 0, bgcolor=GREY) |
|
1329 |
table.AddCellInfo(table.GetCurrentRowIndex(), 1, bgcolor=GREY) |
|
1330 |
table.AddCellInfo(table.GetCurrentRowIndex(), 2, bgcolor=GREY) |
|
1331 |
table.AddRow([ |
|
1332 |
Label(_('Address to change to')), |
|
1333 |
TextBox(name='change_to'), |
|
1334 |
CheckBox('notice_new', 'yes', 0).Format() + |
|
1335 |
' ' + |
|
1336 |
_('Send notice') |
|
1337 |
])
|
|
1338 |
table.AddCellInfo(table.GetCurrentRowIndex(), 0, bgcolor=GREY) |
|
1339 |
table.AddCellInfo(table.GetCurrentRowIndex(), 1, bgcolor=GREY) |
|
1340 |
table.AddCellInfo(table.GetCurrentRowIndex(), 2, bgcolor=GREY) |
|
1341 |
container.AddItem(Center(table)) |
|
1342 |
||
1343 |
||
1344 |
||
1820
by Mark Sapiro
Implemented web admin sync members. |
1345 |
def mass_sync(mlist, container): |
1346 |
# MASS SYNC
|
|
1347 |
table = Table(width='90%') |
|
1348 |
table.AddRow([Italic(_('Enter one address per line below...'))]) |
|
1349 |
table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) |
|
1350 |
table.AddRow([Center(TextArea(name='memberlist', |
|
1351 |
rows=10, cols='70%', wrap=None))]) |
|
1352 |
table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) |
|
1353 |
table.AddRow([Italic(Label(_('...or specify a file to upload:'))), |
|
1354 |
FileUpload('memberlist_upload', cols='50')]) |
|
1355 |
container.AddItem(Center(table)) |
|
1356 |
||
1357 |
||
1358 |
||
1
by
This commit was manufactured by cvs2svn to create branch |
1359 |
def password_inputs(mlist): |
1360 |
adminurl = mlist.GetScriptURL('admin', absolute=1) |
|
1361 |
table = Table(cellspacing=3, cellpadding=4) |
|
1362 |
table.AddRow([Center(Header(2, _('Change list ownership passwords')))]) |
|
1363 |
table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2, |
|
1364 |
bgcolor=mm_cfg.WEB_HEADER_COLOR) |
|
1365 |
table.AddRow([_("""\ |
|
1366 |
The <em>list administrators</em> are the people who have ultimate control over
|
|
1367 |
all parameters of this mailing list. They are able to change any list
|
|
1368 |
configuration variable available through these administration web pages.
|
|
1369 |
||
1370 |
<p>The <em>list moderators</em> have more limited permissions; they are not
|
|
1371 |
able to change any list configuration variable, but they are allowed to tend
|
|
1372 |
to pending administration requests, including approving or rejecting held
|
|
1373 |
subscription requests, and disposing of held postings. Of course, the
|
|
1374 |
<em>list administrators</em> can also tend to pending requests.
|
|
1375 |
||
1376 |
<p>In order to split the list ownership duties into administrators and
|
|
1377 |
moderators, you must set a separate moderator password in the fields below,
|
|
1378 |
and also provide the email addresses of the list moderators in the
|
|
1379 |
<a href="%(adminurl)s/general">general options section</a>.""")]) |
|
1380 |
table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) |
|
1381 |
# Set up the admin password table on the left
|
|
1382 |
atable = Table(border=0, cellspacing=3, cellpadding=4, |
|
1383 |
bgcolor=mm_cfg.WEB_ADMINPW_COLOR) |
|
1384 |
atable.AddRow([Label(_('Enter new administrator password:')), |
|
1385 |
PasswordBox('newpw', size=20)]) |
|
1386 |
atable.AddRow([Label(_('Confirm administrator password:')), |
|
1387 |
PasswordBox('confirmpw', size=20)]) |
|
1388 |
# Set up the moderator password table on the right
|
|
1389 |
mtable = Table(border=0, cellspacing=3, cellpadding=4, |
|
1390 |
bgcolor=mm_cfg.WEB_ADMINPW_COLOR) |
|
1391 |
mtable.AddRow([Label(_('Enter new moderator password:')), |
|
1392 |
PasswordBox('newmodpw', size=20)]) |
|
1393 |
mtable.AddRow([Label(_('Confirm moderator password:')), |
|
1394 |
PasswordBox('confirmmodpw', size=20)]) |
|
1395 |
# Add these tables to the overall password table
|
|
1396 |
table.AddRow([atable, mtable]) |
|
1297
by Mark Sapiro
A new list poster password has been implemented. This password may only |
1397 |
table.AddRow([_("""\ |
1398 |
In addition to the above passwords you may specify a password for
|
|
1399 |
pre-approving posts to the list. Either of the above two passwords can
|
|
1400 |
be used in an Approved: header or first body line pseudo-header to
|
|
1401 |
pre-approve a post that would otherwise be held for moderation. In
|
|
1402 |
addition, the password below, if set, can be used for that purpose and
|
|
1403 |
no other.""")]) |
|
1404 |
table.AddCellInfo(table.GetCurrentRowIndex(), 0, colspan=2) |
|
1405 |
# Set up the post password table
|
|
1406 |
ptable = Table(border=0, cellspacing=3, cellpadding=4, |
|
1407 |
bgcolor=mm_cfg.WEB_ADMINPW_COLOR) |
|
1408 |
ptable.AddRow([Label(_('Enter new poster password:')), |
|
1409 |
PasswordBox('newpostpw', size=20)]) |
|
1410 |
ptable.AddRow([Label(_('Confirm poster password:')), |
|
1411 |
PasswordBox('confirmpostpw', size=20)]) |
|
1412 |
table.AddRow([ptable]) |
|
1
by
This commit was manufactured by cvs2svn to create branch |
1413 |
return table |
1414 |
||
1415 |
||
1416 |
||
1417 |
def submit_button(name='submit'): |
|
1418 |
table = Table(border=0, cellspacing=0, cellpadding=2) |
|
1419 |
table.AddRow([Bold(SubmitButton(name, _('Submit Your Changes')))]) |
|
1420 |
table.AddCellInfo(table.GetCurrentRowIndex(), 0, align='middle') |
|
1421 |
return table |
|
1422 |
||
1423 |
||
1424 |
||
1425 |
def change_options(mlist, category, subcat, cgidata, doc): |
|
1780
by Mark Sapiro
Added global _ where needed. |
1426 |
global _ |
1
by
This commit was manufactured by cvs2svn to create branch |
1427 |
def safeint(formvar, defaultval=None): |
1428 |
try: |
|
1712
by Mark Sapiro
Defend against CGI requests with multiple values for the same parameter. |
1429 |
return int(cgidata.getfirst(formvar)) |
1
by
This commit was manufactured by cvs2svn to create branch |
1430 |
except (ValueError, TypeError): |
1431 |
return defaultval |
|
1432 |
confirmed = 0 |
|
1433 |
# Handle changes to the list moderator password. Do this before checking
|
|
1434 |
# the new admin password, since the latter will force a reauthentication.
|
|
1712
by Mark Sapiro
Defend against CGI requests with multiple values for the same parameter. |
1435 |
new = cgidata.getfirst('newmodpw', '').strip() |
1436 |
confirm = cgidata.getfirst('confirmmodpw', '').strip() |
|
1
by
This commit was manufactured by cvs2svn to create branch |
1437 |
if new or confirm: |
1438 |
if new == confirm: |
|
1135
by Barry Warsaw
Apply Heiko Rommel's patch for hashlib deprecation warnings for bug 293178. |
1439 |
mlist.mod_password = sha_new(new).hexdigest() |
1
by
This commit was manufactured by cvs2svn to create branch |
1440 |
# No re-authentication necessary because the moderator's
|
1441 |
# password doesn't get you into these pages.
|
|
1442 |
else: |
|
1443 |
doc.addError(_('Moderator passwords did not match')) |
|
1297
by Mark Sapiro
A new list poster password has been implemented. This password may only |
1444 |
# Handle changes to the list poster password. Do this before checking
|
1445 |
# the new admin password, since the latter will force a reauthentication.
|
|
1712
by Mark Sapiro
Defend against CGI requests with multiple values for the same parameter. |
1446 |
new = cgidata.getfirst('newpostpw', '').strip() |
1447 |
confirm = cgidata.getfirst('confirmpostpw', '').strip() |
|
1297
by Mark Sapiro
A new list poster password has been implemented. This password may only |
1448 |
if new or confirm: |
1449 |
if new == confirm: |
|
1450 |
mlist.post_password = sha_new(new).hexdigest() |
|
1451 |
# No re-authentication necessary because the poster's
|
|
1452 |
# password doesn't get you into these pages.
|
|
1453 |
else: |
|
1454 |
doc.addError(_('Poster passwords did not match')) |
|
1
by
This commit was manufactured by cvs2svn to create branch |
1455 |
# Handle changes to the list administrator password
|
1712
by Mark Sapiro
Defend against CGI requests with multiple values for the same parameter. |
1456 |
new = cgidata.getfirst('newpw', '').strip() |
1457 |
confirm = cgidata.getfirst('confirmpw', '').strip() |
|
1
by
This commit was manufactured by cvs2svn to create branch |
1458 |
if new or confirm: |
1459 |
if new == confirm: |
|
1135
by Barry Warsaw
Apply Heiko Rommel's patch for hashlib deprecation warnings for bug 293178. |
1460 |
mlist.password = sha_new(new).hexdigest() |
1
by
This commit was manufactured by cvs2svn to create branch |
1461 |
# Set new cookie
|
1462 |
print mlist.MakeCookie(mm_cfg.AuthListAdmin) |
|
1463 |
else: |
|
1464 |
doc.addError(_('Administrator passwords did not match')) |
|
1465 |
# Give the individual gui item a chance to process the form data
|
|
1466 |
categories = mlist.GetConfigCategories() |
|
1467 |
label, gui = categories[category] |
|
1468 |
# BAW: We handle the membership page special... for now.
|
|
1469 |
if category <> 'members': |
|
1470 |
gui.handleForm(mlist, category, subcat, cgidata, doc) |
|
1471 |
# mass subscription, removal processing for members category
|
|
1472 |
subscribers = '' |
|
1712
by Mark Sapiro
Defend against CGI requests with multiple values for the same parameter. |
1473 |
subscribers += cgidata.getfirst('subscribees', '') |
1474 |
subscribers += cgidata.getfirst('subscribees_upload', '') |
|
1
by
This commit was manufactured by cvs2svn to create branch |
1475 |
if subscribers: |
1476 |
entries = filter(None, [n.strip() for n in subscribers.splitlines()]) |
|
1477 |
send_welcome_msg = safeint('send_welcome_msg_to_this_batch', |
|
1478 |
mlist.send_welcome_msg) |
|
1479 |
send_admin_notif = safeint('send_notifications_to_list_owner', |
|
1480 |
mlist.admin_notify_mchanges) |
|
1481 |
# Default is to subscribe
|
|
1482 |
subscribe_or_invite = safeint('subscribe_or_invite', 0) |
|
1712
by Mark Sapiro
Defend against CGI requests with multiple values for the same parameter. |
1483 |
invitation = cgidata.getfirst('invitation', '') |
32
by bwarsaw
Backporting from trunk |
1484 |
digest = mlist.digest_is_default |
1
by
This commit was manufactured by cvs2svn to create branch |
1485 |
if not mlist.digestable: |
1486 |
digest = 0 |
|
1487 |
if not mlist.nondigestable: |
|
1488 |
digest = 1 |
|
1489 |
subscribe_errors = [] |
|
1490 |
subscribe_success = [] |
|
1491 |
# Now cruise through all the subscribees and do the deed. BAW: we
|
|
1492 |
# should limit the number of "Successfully subscribed" status messages
|
|
1493 |
# we display. Try uploading a file with 10k names -- it takes a while
|
|
1494 |
# to render the status page.
|
|
1495 |
for entry in entries: |
|
930
by bwarsaw
CVE-2006-3636. Fixes for various cross-site scripting issues. Discovery by |
1496 |
safeentry = Utils.websafe(entry) |
1
by
This commit was manufactured by cvs2svn to create branch |
1497 |
fullname, address = parseaddr(entry) |
1498 |
# Canonicalize the full name
|
|
1499 |
fullname = Utils.canonstr(fullname, mlist.preferred_language) |
|
1500 |
userdesc = UserDesc(address, fullname, |
|
1501 |
Utils.MakeRandomPassword(), |
|
1502 |
digest, mlist.preferred_language) |
|
1503 |
try: |
|
1504 |
if subscribe_or_invite: |
|
1505 |
if mlist.isMember(address): |
|
1506 |
raise Errors.MMAlreadyAMember |
|
1507 |
else: |
|
1508 |
mlist.InviteNewMember(userdesc, invitation) |
|
1509 |
else: |
|
1776
by Mark Sapiro
I18n for new whence reasons in admin (un)subscribe notices. |
1510 |
_ = D_ |
1511 |
whence = _('admin mass sub') |
|
1512 |
_ = i18n._ |
|
1
by
This commit was manufactured by cvs2svn to create branch |
1513 |
mlist.ApprovedAddMember(userdesc, send_welcome_msg, |
171
by bwarsaw
change_options(): When calling ApprovedAddMember(), pass a meaningful |
1514 |
send_admin_notif, invitation, |
1776
by Mark Sapiro
I18n for new whence reasons in admin (un)subscribe notices. |
1515 |
whence=whence) |
1
by
This commit was manufactured by cvs2svn to create branch |
1516 |
except Errors.MMAlreadyAMember: |
930
by bwarsaw
CVE-2006-3636. Fixes for various cross-site scripting issues. Discovery by |
1517 |
subscribe_errors.append((safeentry, _('Already a member'))) |
1
by
This commit was manufactured by cvs2svn to create branch |
1518 |
except Errors.MMBadEmailError: |
1519 |
if userdesc.address == '': |
|
1520 |
subscribe_errors.append((_('<blank line>'), |
|
1521 |
_('Bad/Invalid email address'))) |
|
1522 |
else: |
|
930
by bwarsaw
CVE-2006-3636. Fixes for various cross-site scripting issues. Discovery by |
1523 |
subscribe_errors.append((safeentry, |
1
by
This commit was manufactured by cvs2svn to create branch |
1524 |
_('Bad/Invalid email address'))) |
1525 |
except Errors.MMHostileAddress: |
|
1526 |
subscribe_errors.append( |
|
930
by bwarsaw
CVE-2006-3636. Fixes for various cross-site scripting issues. Discovery by |
1527 |
(safeentry, _('Hostile address (illegal characters)'))) |
791
by msapiro
Improving banned subscription logic to cover all invites, subscribes, address changes and confirmations of same. |
1528 |
except Errors.MembershipIsBanned, pattern: |
1529 |
subscribe_errors.append( |
|
930
by bwarsaw
CVE-2006-3636. Fixes for various cross-site scripting issues. Discovery by |
1530 |
(safeentry, _('Banned address (matched %(pattern)s)'))) |
1
by
This commit was manufactured by cvs2svn to create branch |
1531 |
else: |
1532 |
member = Utils.uncanonstr(formataddr((fullname, address))) |
|
1533 |
subscribe_success.append(Utils.websafe(member)) |
|
1534 |
if subscribe_success: |
|
1535 |
if subscribe_or_invite: |
|
1536 |
doc.AddItem(Header(5, _('Successfully invited:'))) |
|
1537 |
else: |
|
1538 |
doc.AddItem(Header(5, _('Successfully subscribed:'))) |
|
1539 |
doc.AddItem(UnorderedList(*subscribe_success)) |
|
1540 |
doc.AddItem('<p>') |
|
1541 |
if subscribe_errors: |
|
1542 |
if subscribe_or_invite: |
|
1543 |
doc.AddItem(Header(5, _('Error inviting:'))) |
|
1544 |
else: |
|
1545 |
doc.AddItem(Header(5, _('Error subscribing:'))) |
|
1546 |
items = ['%s -- %s' % (x0, x1) for x0, x1 in subscribe_errors] |
|
1547 |
doc.AddItem(UnorderedList(*items)) |
|
1548 |
doc.AddItem('<p>') |
|
1549 |
# Unsubscriptions
|
|
1550 |
removals = '' |
|
1551 |
if cgidata.has_key('unsubscribees'): |
|
1552 |
removals += cgidata['unsubscribees'].value |
|
1553 |
if cgidata.has_key('unsubscribees_upload') and \ |
|
1554 |
cgidata['unsubscribees_upload'].value: |
|
1555 |
removals += cgidata['unsubscribees_upload'].value |
|
1556 |
if removals: |
|
1557 |
names = filter(None, [n.strip() for n in removals.splitlines()]) |
|
1360
by Mark Sapiro
The query fragments send_unsub_notifications_to_list_owner and |
1558 |
send_unsub_notifications = safeint( |
1559 |
'send_unsub_notifications_to_list_owner', |
|
1560 |
mlist.admin_notify_mchanges) |
|
1561 |
userack = safeint( |
|
1562 |
'send_unsub_ack_to_this_batch', |
|
1563 |
mlist.send_goodbye_msg) |
|
1
by
This commit was manufactured by cvs2svn to create branch |
1564 |
unsubscribe_errors = [] |
1565 |
unsubscribe_success = [] |
|
1566 |
for addr in names: |
|
1567 |
try: |
|
1776
by Mark Sapiro
I18n for new whence reasons in admin (un)subscribe notices. |
1568 |
_ = D_ |
1569 |
whence = _('admin mass unsub') |
|
1570 |
_ = i18n._ |
|
1
by
This commit was manufactured by cvs2svn to create branch |
1571 |
mlist.ApprovedDeleteMember( |
1776
by Mark Sapiro
I18n for new whence reasons in admin (un)subscribe notices. |
1572 |
addr, whence=whence, |
1
by
This commit was manufactured by cvs2svn to create branch |
1573 |
admin_notif=send_unsub_notifications, |
1574 |
userack=userack) |
|
930
by bwarsaw
CVE-2006-3636. Fixes for various cross-site scripting issues. Discovery by |
1575 |
unsubscribe_success.append(Utils.websafe(addr)) |
1
by
This commit was manufactured by cvs2svn to create branch |
1576 |
except Errors.NotAMemberError: |
930
by bwarsaw
CVE-2006-3636. Fixes for various cross-site scripting issues. Discovery by |
1577 |
unsubscribe_errors.append(Utils.websafe(addr)) |
1
by
This commit was manufactured by cvs2svn to create branch |
1578 |
if unsubscribe_success: |
1579 |
doc.AddItem(Header(5, _('Successfully Unsubscribed:'))) |
|
1580 |
doc.AddItem(UnorderedList(*unsubscribe_success)) |
|
1581 |
doc.AddItem('<p>') |
|
1582 |
if unsubscribe_errors: |
|
1583 |
doc.AddItem(Header(3, Bold(FontAttr( |
|
1584 |
_('Cannot unsubscribe non-members:'), |
|
1585 |
color='#ff0000', size='+2')).Format())) |
|
1586 |
doc.AddItem(UnorderedList(*unsubscribe_errors)) |
|
1587 |
doc.AddItem('<p>') |
|
1551
by Mark Sapiro
Implemented member address change via the admin GUI. |
1588 |
# Address Changes
|
1589 |
if cgidata.has_key('change_from'): |
|
1712
by Mark Sapiro
Defend against CGI requests with multiple values for the same parameter. |
1590 |
change_from = cgidata.getfirst('change_from', '') |
1591 |
change_to = cgidata.getfirst('change_to', '') |
|
1551
by Mark Sapiro
Implemented member address change via the admin GUI. |
1592 |
schange_from = Utils.websafe(change_from) |
1593 |
schange_to = Utils.websafe(change_to) |
|
1594 |
success = False |
|
1595 |
msg = None |
|
1596 |
if not (change_from and change_to): |
|
1597 |
msg = _('You must provide both current and new addresses.') |
|
1598 |
elif change_from == change_to: |
|
1599 |
msg = _('Current and new addresses must be different.') |
|
1600 |
elif mlist.isMember(change_to): |
|
1601 |
# ApprovedChangeMemberAddress will just delete the old address
|
|
1602 |
# and we don't want that here.
|
|
1603 |
msg = _('%(schange_to)s is already a list member.') |
|
1604 |
else: |
|
1605 |
try: |
|
1606 |
Utils.ValidateEmail(change_to) |
|
1607 |
except (Errors.MMBadEmailError, Errors.MMHostileAddress): |
|
1608 |
msg = _('%(schange_to)s is not a valid email address.') |
|
1609 |
if msg: |
|
1610 |
doc.AddItem(Header(3, msg)) |
|
1611 |
doc.AddItem('<p>') |
|
1612 |
return
|
|
1613 |
try: |
|
1614 |
mlist.ApprovedChangeMemberAddress(change_from, change_to, False) |
|
1615 |
except Errors.NotAMemberError: |
|
1616 |
msg = _('%(schange_from)s is not a member') |
|
1617 |
except Errors.MMAlreadyAMember: |
|
1618 |
msg = _('%(schange_to)s is already a member') |
|
1619 |
except Errors.MembershipIsBanned, pat: |
|
1620 |
spat = Utils.websafe(str(pat)) |
|
1621 |
msg = _('%(schange_to)s matches banned pattern %(spat)s') |
|
1622 |
else: |
|
1623 |
msg = _('Address %(schange_from)s changed to %(schange_to)s') |
|
1624 |
success = True |
|
1625 |
doc.AddItem(Header(3, msg)) |
|
1626 |
lang = mlist.getMemberLanguage(change_to) |
|
1627 |
otrans = i18n.get_translation() |
|
1628 |
i18n.set_language(lang) |
|
1629 |
list_name = mlist.getListAddress() |
|
1630 |
text = Utils.wrap(_("""The member address %(change_from)s on the |
|
1631 |
%(list_name)s list has been changed to %(change_to)s. |
|
1632 |
""")) |
|
1633 |
subject = _('%(list_name)s address change notice.') |
|
1634 |
i18n.set_translation(otrans) |
|
1712
by Mark Sapiro
Defend against CGI requests with multiple values for the same parameter. |
1635 |
if success and cgidata.getfirst('notice_old', '') == 'yes': |
1551
by Mark Sapiro
Implemented member address change via the admin GUI. |
1636 |
# Send notice to old address.
|
1637 |
msg = Message.UserNotification(change_from, |
|
1638 |
mlist.GetOwnerEmail(), |
|
1639 |
text=text, |
|
1640 |
subject=subject, |
|
1641 |
lang=lang |
|
1642 |
)
|
|
1643 |
msg.send(mlist) |
|
1644 |
doc.AddItem(Header(3, _('Notification sent to %(schange_from)s.'))) |
|
1712
by Mark Sapiro
Defend against CGI requests with multiple values for the same parameter. |
1645 |
if success and cgidata.getfirst('notice_new', '') == 'yes': |
1551
by Mark Sapiro
Implemented member address change via the admin GUI. |
1646 |
# Send notice to new address.
|
1647 |
msg = Message.UserNotification(change_to, |
|
1648 |
mlist.GetOwnerEmail(), |
|
1649 |
text=text, |
|
1650 |
subject=subject, |
|
1651 |
lang=lang |
|
1652 |
)
|
|
1653 |
msg.send(mlist) |
|
1654 |
doc.AddItem(Header(3, _('Notification sent to %(schange_to)s.'))) |
|
1655 |
doc.AddItem('<p>') |
|
1820
by Mark Sapiro
Implemented web admin sync members. |
1656 |
|
1657 |
# sync operation
|
|
1658 |
memberlist = '' |
|
1659 |
memberlist += cgidata.getvalue('memberlist', '') |
|
1660 |
memberlist += cgidata.getvalue('memberlist_upload', '') |
|
1661 |
if memberlist: |
|
1662 |
# Browsers will convert special characters in the text box to HTML
|
|
1663 |
# entities. We need to fix those.
|
|
1664 |
def i_to_c(mo): |
|
1665 |
# Convert a matched string of digits to the corresponding unicode.
|
|
1666 |
return unichr(int(mo.group(1))) |
|
1667 |
def clean_input(x): |
|
1668 |
# Strip leading/trailing whitespace and convert numeric HTML
|
|
1669 |
# entities.
|
|
1670 |
return re.sub(r'&#(\d+);', i_to_c, x.strip()) |
|
1671 |
entries = filter(None, |
|
1672 |
[clean_input(n) for n in memberlist.splitlines()]) |
|
1673 |
lc_addresses = [parseaddr(x)[1].lower() for x in entries |
|
1674 |
if parseaddr(x)[1]] |
|
1675 |
subscribe_errors = [] |
|
1676 |
subscribe_success = [] |
|
1677 |
# First we add all the addresses that should be added to the list.
|
|
1678 |
for entry in entries: |
|
1679 |
safeentry = Utils.websafe(entry) |
|
1680 |
fullname, address = parseaddr(entry) |
|
1681 |
if mlist.isMember(address): |
|
1682 |
continue
|
|
1683 |
# Canonicalize the full name.
|
|
1684 |
fullname = Utils.canonstr(fullname, mlist.preferred_language) |
|
1685 |
userdesc = UserDesc(address, fullname, |
|
1686 |
Utils.MakeRandomPassword(), |
|
1687 |
0, mlist.preferred_language) |
|
1688 |
try: |
|
1689 |
# Add a member if not yet member.
|
|
1690 |
mlist.ApprovedAddMember(userdesc, 0, 0, 0, |
|
1691 |
whence='admin sync members') |
|
1692 |
except Errors.MMBadEmailError: |
|
1693 |
if userdesc.address == '': |
|
1694 |
subscribe_errors.append((_('<blank line>'), |
|
1695 |
_('Bad/Invalid email address'))) |
|
1696 |
else: |
|
1697 |
subscribe_errors.append((safeentry, |
|
1698 |
_('Bad/Invalid email address'))) |
|
1699 |
except Errors.MMHostileAddress: |
|
1700 |
subscribe_errors.append( |
|
1701 |
(safeentry, _('Hostile address (illegal characters)'))) |
|
1702 |
except Errors.MembershipIsBanned, pattern: |
|
1703 |
subscribe_errors.append( |
|
1704 |
(safeentry, _('Banned address (matched %(pattern)s)'))) |
|
1705 |
else: |
|
1706 |
member = Utils.uncanonstr(formataddr((fullname, address))) |
|
1707 |
subscribe_success.append(Utils.websafe(member)) |
|
1708 |
||
1709 |
# Then we remove the addresses not in our list.
|
|
1710 |
unsubscribe_errors = [] |
|
1711 |
unsubscribe_success = [] |
|
1712 |
||
1713 |
for entry in mlist.getMembers(): |
|
1714 |
# If an entry is not found in the uploaded "entries" list, then
|
|
1715 |
# remove the member.
|
|
1716 |
if not(entry in lc_addresses): |
|
1717 |
try: |
|
1718 |
mlist.ApprovedDeleteMember(entry, 0, 0) |
|
1719 |
except Errors.NotAMemberError: |
|
1720 |
# This can happen if the address is illegal (i.e. can't be
|
|
1721 |
# parsed by email.Utils.parseaddr()) but for legacy
|
|
1722 |
# reasons is in the database. Use a lower level remove to
|
|
1723 |
# get rid of this member's entry
|
|
1724 |
mlist.removeMember(entry) |
|
1725 |
else: |
|
1726 |
unsubscribe_success.append(Utils.websafe(entry)) |
|
1727 |
||
1728 |
if subscribe_success: |
|
1729 |
doc.AddItem(Header(5, _('Successfully subscribed:'))) |
|
1730 |
doc.AddItem(UnorderedList(*subscribe_success)) |
|
1731 |
doc.AddItem('<p>') |
|
1732 |
if subscribe_errors: |
|
1733 |
doc.AddItem(Header(5, _('Error subscribing:'))) |
|
1734 |
items = ['%s -- %s' % (x0, x1) for x0, x1 in subscribe_errors] |
|
1735 |
doc.AddItem(UnorderedList(*items)) |
|
1736 |
doc.AddItem('<p>') |
|
1737 |
if unsubscribe_success: |
|
1823
by Mark Sapiro
Changed new 'Successfully unsubscribed:' to existing |
1738 |
doc.AddItem(Header(5, _('Successfully Unsubscribed:'))) |
1820
by Mark Sapiro
Implemented web admin sync members. |
1739 |
doc.AddItem(UnorderedList(*unsubscribe_success)) |
1740 |
doc.AddItem('<p>') |
|
1741 |
||
1
by
This commit was manufactured by cvs2svn to create branch |
1742 |
# See if this was a moderation bit operation
|
1743 |
if cgidata.has_key('allmodbit_btn'): |
|
1360
by Mark Sapiro
The query fragments send_unsub_notifications_to_list_owner and |
1744 |
val = safeint('allmodbit_val') |
1
by
This commit was manufactured by cvs2svn to create branch |
1745 |
if val not in (0, 1): |
1746 |
doc.addError(_('Bad moderation flag value')) |
|
1747 |
else: |
|
1748 |
for member in mlist.getMembers(): |
|
1749 |
mlist.setMemberOption(member, mm_cfg.Moderate, val) |
|
1750 |
# do the user options for members category
|
|
1751 |
if cgidata.has_key('setmemberopts_btn') and cgidata.has_key('user'): |
|
1752 |
user = cgidata['user'] |
|
1753 |
if type(user) is ListType: |
|
1754 |
users = [] |
|
1755 |
for ui in range(len(user)): |
|
1756 |
users.append(urllib.unquote(user[ui].value)) |
|
1757 |
else: |
|
1758 |
users = [urllib.unquote(user.value)] |
|
1759 |
errors = [] |
|
1760 |
removes = [] |
|
1761 |
for user in users: |
|
972
by msapiro
- CGI/admin.py |
1762 |
quser = urllib.quote(user) |
1763 |
if cgidata.has_key('%s_unsub' % quser): |
|
1
by
This commit was manufactured by cvs2svn to create branch |
1764 |
try: |
1776
by Mark Sapiro
I18n for new whence reasons in admin (un)subscribe notices. |
1765 |
_ = D_ |
1766 |
whence=_('member mgt page') |
|
1767 |
_ = i18n._ |
|
1768 |
mlist.ApprovedDeleteMember(user, whence=whence) |
|
1
by
This commit was manufactured by cvs2svn to create branch |
1769 |
removes.append(user) |
1770 |
except Errors.NotAMemberError: |
|
1771 |
errors.append((user, _('Not subscribed'))) |
|
1772 |
continue
|
|
1773 |
if not mlist.isMember(user): |
|
1774 |
doc.addError(_('Ignoring changes to deleted member: %(user)s'), |
|
1775 |
tag=_('Warning: ')) |
|
1776 |
continue
|
|
972
by msapiro
- CGI/admin.py |
1777 |
value = cgidata.has_key('%s_digest' % quser) |
1
by
This commit was manufactured by cvs2svn to create branch |
1778 |
try: |
1779 |
mlist.setMemberOption(user, mm_cfg.Digests, value) |
|
1780 |
except (Errors.AlreadyReceivingDigests, |
|
1781 |
Errors.AlreadyReceivingRegularDeliveries, |
|
1782 |
Errors.CantDigestError, |
|
1783 |
Errors.MustDigestError): |
|
1784 |
# BAW: Hmm...
|
|
1785 |
pass
|
|
1786 |
||
1712
by Mark Sapiro
Defend against CGI requests with multiple values for the same parameter. |
1787 |
newname = cgidata.getfirst(quser+'_realname', '') |
1
by
This commit was manufactured by cvs2svn to create branch |
1788 |
newname = Utils.canonstr(newname, mlist.preferred_language) |
1789 |
mlist.setMemberName(user, newname) |
|
1790 |
||
1712
by Mark Sapiro
Defend against CGI requests with multiple values for the same parameter. |
1791 |
newlang = cgidata.getfirst(quser+'_language') |
1
by
This commit was manufactured by cvs2svn to create branch |
1792 |
oldlang = mlist.getMemberLanguage(user) |
20
by bwarsaw
Backporting from the trunk. |
1793 |
if Utils.IsLanguage(newlang) and newlang <> oldlang: |
1
by
This commit was manufactured by cvs2svn to create branch |
1794 |
mlist.setMemberLanguage(user, newlang) |
1795 |
||
1712
by Mark Sapiro
Defend against CGI requests with multiple values for the same parameter. |
1796 |
moderate = not not cgidata.getfirst(quser+'_mod') |
1
by
This commit was manufactured by cvs2svn to create branch |
1797 |
mlist.setMemberOption(user, mm_cfg.Moderate, moderate) |
1798 |
||
1799 |
# Set the `nomail' flag, but only if the user isn't already
|
|
1800 |
# disabled (otherwise we might change BYUSER into BYADMIN).
|
|
972
by msapiro
- CGI/admin.py |
1801 |
if cgidata.has_key('%s_nomail' % quser): |
1
by
This commit was manufactured by cvs2svn to create branch |
1802 |
if mlist.getDeliveryStatus(user) == MemberAdaptor.ENABLED: |
1803 |
mlist.setDeliveryStatus(user, MemberAdaptor.BYADMIN) |
|
1804 |
else: |
|
1805 |
mlist.setDeliveryStatus(user, MemberAdaptor.ENABLED) |
|
1806 |
for opt in ('hide', 'ack', 'notmetoo', 'nodupes', 'plain'): |
|
1807 |
opt_code = mm_cfg.OPTINFO[opt] |
|
972
by msapiro
- CGI/admin.py |
1808 |
if cgidata.has_key('%s_%s' % (quser, opt)): |
1
by
This commit was manufactured by cvs2svn to create branch |
1809 |
mlist.setMemberOption(user, opt_code, 1) |
1810 |
else: |
|
1811 |
mlist.setMemberOption(user, opt_code, 0) |
|
1812 |
# Give some feedback on who's been removed
|
|
1813 |
if removes: |
|
1814 |
doc.AddItem(Header(5, _('Successfully Removed:'))) |
|
1815 |
doc.AddItem(UnorderedList(*removes)) |
|
1816 |
doc.AddItem('<p>') |
|
1817 |
if errors: |
|
1818 |
doc.AddItem(Header(5, _("Error Unsubscribing:"))) |
|
1819 |
items = ['%s -- %s' % (x[0], x[1]) for x in errors] |
|
1820 |
doc.AddItem(apply(UnorderedList, tuple((items)))) |
|
1821 |
doc.AddItem("<p>") |