Remove debian/patches
[mspang/vmailman.git] / Mailman / HTMLFormatter.py
1 # Copyright (C) 1998-2006 by the Free Software Foundation, Inc.
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
15 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
16 # USA.
17
18
19 """Routines for presentation of list-specific HTML text."""
20
21 import time
22 import re
23
24 from Mailman import mm_cfg
25 from Mailman import Utils
26 from Mailman import MemberAdaptor
27 from Mailman.htmlformat import *
28
29 from Mailman.i18n import _
30
31
32 EMPTYSTRING = ''
33 BR = '<br>'
34 NL = '\n'
35 COMMASPACE = ', '
36
37
38 \f
39 class HTMLFormatter:
40     def GetMailmanFooter(self):
41         ownertext = COMMASPACE.join([Utils.ObscureEmail(a, 1)
42                                      for a in self.owner])
43         # Remove the .Format() when htmlformat conversion is done.
44         realname = self.real_name
45         hostname = self.host_name
46         listinfo_link  = Link(self.GetScriptURL('listinfo'), realname).Format()
47         owner_link = Link('mailto:' + self.GetOwnerEmail(), ownertext).Format()
48         innertext = _('%(listinfo_link)s list run by %(owner_link)s')
49         return Container(
50             '<hr>',
51             Address(
52                 Container(
53                    innertext,
54                     '<br>',
55                     Link(self.GetScriptURL('admin'),
56                          _('%(realname)s administrative interface')),
57                     _(' (requires authorization)'),
58                     '<br>',
59                     Link(Utils.ScriptURL('listinfo'),
60                          _('Overview of all %(hostname)s mailing lists')),
61                     '<p>', MailmanLogo()))).Format()
62
63     def FormatUsers(self, digest, lang=None):
64         if lang is None:
65             lang = self.preferred_language
66         conceal_sub = mm_cfg.ConcealSubscription
67         people = []
68         if digest:
69             digestmembers = self.getDigestMemberKeys()
70             for dm in digestmembers:
71                 if not self.getMemberOption(dm, conceal_sub):
72                     people.append(dm)
73             num_concealed = len(digestmembers) - len(people)
74         else:
75             members = self.getRegularMemberKeys()
76             for m in members:
77                 if not self.getMemberOption(m, conceal_sub):
78                     people.append(m)
79             num_concealed = len(members) - len(people)
80         if num_concealed == 1:
81             concealed = _('<em>(1 private member not shown)</em>')
82         elif num_concealed > 1:
83            concealed = _(
84                '<em>(%(num_concealed)d private members not shown)</em>')
85         else:
86             concealed = ''
87         items = []
88         people.sort()
89         obscure = self.obscure_addresses
90         for person in people:
91             id = Utils.ObscureEmail(person)
92             url = self.GetOptionsURL(person, obscure=obscure)
93             if obscure:
94                 showing = Utils.ObscureEmail(person, for_text=1)
95             else:
96                 showing = person
97             got = Link(url, showing)
98             if self.getDeliveryStatus(person) <> MemberAdaptor.ENABLED:
99                 got = Italic('(', got, ')')
100             items.append(got)
101         # Just return the .Format() so this works until I finish
102         # converting everything to htmlformat...
103         return concealed + UnorderedList(*tuple(items)).Format()
104
105     def FormatOptionButton(self, option, value, user):
106         if option == mm_cfg.DisableDelivery:
107             optval = self.getDeliveryStatus(user) <> MemberAdaptor.ENABLED
108         else:
109             optval = self.getMemberOption(user, option)
110         if optval == value:
111             checked = ' CHECKED'
112         else:
113             checked = ''
114         name = {mm_cfg.DontReceiveOwnPosts      : 'dontreceive',
115                 mm_cfg.DisableDelivery          : 'disablemail',
116                 mm_cfg.DisableMime              : 'mime',
117                 mm_cfg.AcknowledgePosts         : 'ackposts',
118                 mm_cfg.Digests                  : 'digest',
119                 mm_cfg.ConcealSubscription      : 'conceal',
120                 mm_cfg.SuppressPasswordReminder : 'remind',
121                 mm_cfg.ReceiveNonmatchingTopics : 'rcvtopic',
122                 mm_cfg.DontReceiveDuplicates    : 'nodupes',
123                 }[option]
124         return '<input type=radio name="%s" value="%d"%s>' % (
125             name, value, checked)
126
127     def FormatDigestButton(self):
128         if self.digest_is_default:
129             checked = ' CHECKED'
130         else:
131             checked = ''
132         return '<input type=radio name="digest" value="1"%s>' % checked
133
134     def FormatDisabledNotice(self, user):
135         status = self.getDeliveryStatus(user)
136         reason = None
137         info = self.getBounceInfo(user)
138         if status == MemberAdaptor.BYUSER:
139             reason = _('; it was disabled by you')
140         elif status == MemberAdaptor.BYADMIN:
141             reason = _('; it was disabled by the list administrator')
142         elif status == MemberAdaptor.BYBOUNCE:
143             date = time.strftime('%d-%b-%Y',
144                                  time.localtime(Utils.midnight(info.date)))
145             reason = _('''; it was disabled due to excessive bounces.  The
146             last bounce was received on %(date)s''')
147         elif status == MemberAdaptor.UNKNOWN:
148             reason = _('; it was disabled for unknown reasons')
149         if reason:
150             note = FontSize('+1', _(
151                 'Note: your list delivery is currently disabled%(reason)s.'
152                 )).Format()
153             link = Link('#disable', _('Mail delivery')).Format()
154             mailto = Link('mailto:' + self.GetOwnerEmail(),
155                           _('the list administrator')).Format()
156             return _('''<p>%(note)s
157
158             <p>You may have disabled list delivery intentionally,
159             or it may have been triggered by bounces from your email
160             address.  In either case, to re-enable delivery, change the
161             %(link)s option below.  Contact %(mailto)s if you have any
162             questions or need assistance.''')
163         elif info and info.score > 0:
164             # Provide information about their current bounce score.  We know
165             # their membership is currently enabled.
166             score = info.score
167             total = self.bounce_score_threshold
168             return _('''<p>We have received some recent bounces from your
169             address.  Your current <em>bounce score</em> is %(score)s out of a
170             maximum of %(total)s.  Please double check that your subscribed
171             address is correct and that there are no problems with delivery to
172             this address.  Your bounce score will be automatically reset if
173             the problems are corrected soon.''')
174         else:
175             return ''
176
177     def FormatUmbrellaNotice(self, user, type):
178         addr = self.GetMemberAdminEmail(user)
179         if self.umbrella_list:
180             return _("(Note - you are subscribing to a list of mailing lists, "
181                      "so the %(type)s notice will be sent to the admin address"
182                      " for your membership, %(addr)s.)<p>")
183         else:
184             return ""
185
186     def FormatSubscriptionMsg(self):
187         msg = ''
188         also = ''
189         if self.subscribe_policy == 1:
190             msg += _('''You will be sent email requesting confirmation, to
191             prevent others from gratuitously subscribing you.''')
192         elif self.subscribe_policy == 2:
193             msg += _("""This is a closed list, which means your subscription
194             will be held for approval.  You will be notified of the list
195             moderator's decision by email.""")
196             also = _('also ')
197         elif self.subscribe_policy == 3:
198             msg += _("""You will be sent email requesting confirmation, to
199             prevent others from gratuitously subscribing you.  Once
200             confirmation is received, your request will be held for approval
201             by the list moderator.  You will be notified of the moderator's
202             decision by email.""")
203             also = _("also ")
204         if msg:
205             msg += ' '
206         if self.private_roster == 1:
207             msg += _('''This is %(also)sa private list, which means that the
208             list of members is not available to non-members.''')
209         elif self.private_roster:
210             msg += _('''This is %(also)sa hidden list, which means that the
211             list of members is available only to the list administrator.''')
212         else:
213             msg += _('''This is %(also)sa public list, which means that the
214             list of members list is available to everyone.''')
215             if self.obscure_addresses:
216                 msg += _(''' (but we obscure the addresses so they are not
217                 easily recognizable by spammers).''')
218
219         if self.umbrella_list:
220             sfx = self.umbrella_member_suffix
221             msg += _("""<p>(Note that this is an umbrella list, intended to
222             have only other mailing lists as members.  Among other things,
223             this means that your confirmation request will be sent to the
224             `%(sfx)s' account for your address.)""")
225         return msg
226
227     def FormatUndigestButton(self):
228         if self.digest_is_default:
229             checked = ''
230         else:
231             checked = ' CHECKED'
232         return '<input type=radio name="digest" value="0"%s>' % checked
233
234     def FormatMimeDigestsButton(self):
235         if self.mime_is_default_digest:
236             checked = ' CHECKED'
237         else:
238             checked = ''
239         return '<input type=radio name="mime" value="1"%s>' % checked
240
241     def FormatPlainDigestsButton(self):
242         if self.mime_is_default_digest:
243             checked = ''
244         else:
245             checked = ' CHECKED'
246         return '<input type=radio name="plain" value="1"%s>' % checked
247
248     def FormatEditingOption(self, lang):
249         if self.private_roster == 0:
250             either = _('<b><i>either</i></b> ')
251         else:
252             either = ''
253         realname = self.real_name
254
255         text = (_('''To unsubscribe from %(realname)s, get a password reminder,
256         or change your subscription options %(either)senter your subscription
257         email address:
258         <p><center> ''')
259                 + TextBox('email', size=30).Format()
260                 + '  '
261                 + SubmitButton('UserOptions',
262                                _('Unsubscribe or edit options')).Format()
263                 + Hidden('language', lang).Format()
264                 + '</center>')
265         if self.private_roster == 0:
266             text += _('''<p>... <b><i>or</i></b> select your entry from
267                       the subscribers list (see above).''')
268         text += _(''' If you leave the field blank, you will be prompted for
269         your email address''')
270         return text
271
272     def RestrictedListMessage(self, which, restriction):
273         if not restriction:
274             return ''
275         elif restriction == 1:
276             return _(
277                 '''(<i>%(which)s is only available to the list
278                 members.</i>)''')
279         else:
280             return _('''(<i>%(which)s is only available to the list
281             administrator.</i>)''')
282
283     def FormatRosterOptionForUser(self, lang):
284         return self.RosterOption(lang).Format()
285
286     def RosterOption(self, lang):
287         container = Container()
288         container.AddItem(Hidden('language', lang))
289         if not self.private_roster:
290             container.AddItem(_("Click here for the list of ")
291                               + self.real_name
292                               + _(" subscribers: "))
293             container.AddItem(SubmitButton('SubscriberRoster',
294                                            _("Visit Subscriber list")))
295         else:
296             if self.private_roster == 1:
297                 only = _('members')
298                 whom = _('Address:')
299             else:
300                 only = _('the list administrator')
301                 whom = _('Admin address:')
302             # Solicit the user and password.
303             container.AddItem(
304                 self.RestrictedListMessage(_('The subscribers list'),
305                                            self.private_roster)
306                               + _(" <p>Enter your ")
307                               + whom[:-1].lower()
308                               + _(" and password to visit"
309                               "  the subscribers list: <p><center> ")
310                               + whom
311                               + " ")
312             container.AddItem(self.FormatBox('roster-email'))
313             container.AddItem(_("Password: ")
314                               + self.FormatSecureBox('roster-pw')
315                               + "&nbsp;&nbsp;")
316             container.AddItem(SubmitButton('SubscriberRoster',
317                                            _('Visit Subscriber List')))
318             container.AddItem("</center>")
319         return container
320
321     def FormatFormStart(self, name, extra=''):
322         base_url = self.GetScriptURL(name)
323         if extra:
324             full_url = "%s/%s" % (base_url, extra)
325         else:
326             full_url = base_url
327         return ('<FORM Method=POST ACTION="%s">' % full_url)
328
329     def FormatArchiveAnchor(self):
330         return '<a href="%s">' % self.GetBaseArchiveURL()
331
332     def FormatFormEnd(self):
333         return '</FORM>'
334
335     def FormatBox(self, name, size=20, value=''):
336         if isinstance(value, str):
337             safevalue = Utils.websafe(value)
338         else:
339             safevalue = value
340         return '<INPUT type="Text" name="%s" size="%d" value="%s">' % (
341             name, size, safevalue)
342
343     def FormatSecureBox(self, name):
344         return '<INPUT type="Password" name="%s" size="15">' % name
345
346     def FormatButton(self, name, text='Submit'):
347         return '<INPUT type="Submit" name="%s" value="%s">' % (name, text)
348
349     def FormatReminder(self, lang):
350         if self.send_reminders:
351             return _('Once a month, your password will be emailed to you as'
352                      ' a reminder.')
353         return ''
354
355     def ParseTags(self, template, replacements, lang=None):
356         if lang is None:
357             charset = 'us-ascii'
358         else:
359             charset = Utils.GetCharSet(lang)
360         text = Utils.maketext(template, raw=1, lang=lang, mlist=self)
361         parts = re.split('(</?[Mm][Mm]-[^>]*>)', text)
362         i = 1
363         while i < len(parts):
364             tag = parts[i].lower()
365             if replacements.has_key(tag):
366                 repl = replacements[tag]
367                 if isinstance(repl, type(u'')):
368                     repl = repl.encode(charset, 'replace')
369                 parts[i] = repl
370             else:
371                 parts[i] = ''
372             i = i + 2
373         return EMPTYSTRING.join(parts)
374
375     # This needs to wait until after the list is inited, so let's build it
376     # when it's needed only.
377     def GetStandardReplacements(self, lang=None):
378         dmember_len = len(self.getDigestMemberKeys())
379         member_len = len(self.getRegularMemberKeys())
380         # If only one language is enabled for this mailing list, omit the
381         # language choice buttons.
382         if len(self.GetAvailableLanguages()) == 1:
383             listlangs = _(Utils.GetLanguageDescr(self.preferred_language))
384         else:
385             listlangs = self.GetLangSelectBox(lang).Format()
386         d = {
387             '<mm-mailman-footer>' : self.GetMailmanFooter(),
388             '<mm-list-name>' : self.real_name,
389             '<mm-email-user>' : self._internal_name,
390             '<mm-list-description>' : self.description,
391             '<mm-list-info>' : BR.join(self.info.split(NL)),
392             '<mm-form-end>'  : self.FormatFormEnd(),
393             '<mm-archive>'   : self.FormatArchiveAnchor(),
394             '</mm-archive>'  : '</a>',
395             '<mm-list-subscription-msg>' : self.FormatSubscriptionMsg(),
396             '<mm-restricted-list-message>' : \
397                 self.RestrictedListMessage(_('The current archive'),
398                                            self.archive_private),
399             '<mm-num-reg-users>' : `member_len`,
400             '<mm-num-digesters>' : `dmember_len`,
401             '<mm-num-members>' : (`member_len + dmember_len`),
402             '<mm-posting-addr>' : '%s' % self.GetListEmail(),
403             '<mm-request-addr>' : '%s' % self.GetRequestEmail(),
404             '<mm-owner>' : self.GetOwnerEmail(),
405             '<mm-reminder>' : self.FormatReminder(self.preferred_language),
406             '<mm-host>' : self.host_name,
407             '<mm-list-langs>' : listlangs,
408             }
409         if mm_cfg.IMAGE_LOGOS:
410             d['<mm-favicon>'] = mm_cfg.IMAGE_LOGOS + mm_cfg.SHORTCUT_ICON
411         return d
412
413     def GetAllReplacements(self, lang=None):
414         """
415         returns standard replaces plus formatted user lists in
416         a dict just like GetStandardReplacements.
417         """
418         if lang is None:
419             lang = self.preferred_language
420         d = self.GetStandardReplacements(lang)
421         d.update({"<mm-regular-users>": self.FormatUsers(0, lang),
422                   "<mm-digest-users>": self.FormatUsers(1, lang)})
423         return d
424
425     def GetLangSelectBox(self, lang=None, varname='language'):
426         if lang is None:
427             lang = self.preferred_language
428         # Figure out the available languages
429         values = self.GetAvailableLanguages()
430         legend = map(_, map(Utils.GetLanguageDescr, values))
431         try:
432             selected = values.index(lang)
433         except ValueError:
434             try:
435                 selected = values.index(self.preferred_language)
436             except ValueError:
437                 selected = mm_cfg.DEFAULT_SERVER_LANGUAGE
438         # Return the widget
439         return SelectOptions(varname, values, legend, selected)