[mailman3] Drop mailman2 configuration
Signed-off-by: Yohann D'ANELLO <ynerant@crans.org>certbot_on_virtu
							parent
							
								
									23a02adf11
								
							
						
					
					
						commit
						b74d5e0bf1
					
				| 
						 | 
				
			
			@ -7,67 +7,6 @@ loc_certbot:
 | 
			
		|||
    certname: crans.org
 | 
			
		||||
    domains: "*.crans.org"
 | 
			
		||||
 | 
			
		||||
loc_nginx:
 | 
			
		||||
  service_name: mailman
 | 
			
		||||
  default_server: lists.crans.org
 | 
			
		||||
  default_ssl_server: lists.crans.org
 | 
			
		||||
  auth_passwd:
 | 
			
		||||
    Stop: "$apr1$NXaV5H7Q$J3ora3Jo5h775Y1nm93PN1"
 | 
			
		||||
  deploy_robots_file: true
 | 
			
		||||
  servers:
 | 
			
		||||
    - server_name:
 | 
			
		||||
      - lists.crans.org
 | 
			
		||||
      ssl: crans.org
 | 
			
		||||
      root: "/usr/lib/cgi-bin/mailman/"
 | 
			
		||||
      index:
 | 
			
		||||
        - index.htm
 | 
			
		||||
        - index.html
 | 
			
		||||
      locations:
 | 
			
		||||
        - filter: "/error/"
 | 
			
		||||
          params:
 | 
			
		||||
            - "internal"
 | 
			
		||||
            - "alias /var/www/html/"
 | 
			
		||||
        - filter: "/create"
 | 
			
		||||
          params:
 | 
			
		||||
            - "default_type text/html"
 | 
			
		||||
            - "alias /etc/mailman/create.html"
 | 
			
		||||
        - filter: "~ ^/$"
 | 
			
		||||
          params:
 | 
			
		||||
            - "return 302 https://lists.crans.org/listinfo"
 | 
			
		||||
        - filter: "/"
 | 
			
		||||
          params:
 | 
			
		||||
            - "include \"/etc/nginx/snippets/fastcgi-mailman.conf\""
 | 
			
		||||
        - filter: "~ ^/listinfo"
 | 
			
		||||
          params:
 | 
			
		||||
            - "satisfy any"
 | 
			
		||||
            - "include \"/etc/nginx/snippets/fastcgi-mailman.conf\""
 | 
			
		||||
            - "allow 185.230.76.0/22"
 | 
			
		||||
            - "allow 2a0c:700:0::/40"
 | 
			
		||||
            - "deny all"
 | 
			
		||||
            - "auth_basic \"On n'aime pas les spambots, donc on a mis un mot de passe. Le login est Stop et le mot de passe est Spam.\""
 | 
			
		||||
            - "auth_basic_user_file /etc/nginx/passwd"
 | 
			
		||||
            - "error_page 401 /error/401.html"
 | 
			
		||||
        - filter: "~ ^/admin"
 | 
			
		||||
          params:
 | 
			
		||||
            - "satisfy any"
 | 
			
		||||
            - "include \"/etc/nginx/snippets/fastcgi-mailman.conf\""
 | 
			
		||||
            - "allow 185.230.76.0/22"
 | 
			
		||||
            - "allow 2a0c:700:0::/40"
 | 
			
		||||
            - "deny all"
 | 
			
		||||
            - "auth_basic \"On n'aime pas les spambots, donc on a mis un mot de passe. Le login est Stop et le mot de passe est Spam.\""
 | 
			
		||||
            - "auth_basic_user_file /etc/nginx/passwd"
 | 
			
		||||
            - "error_page 401 /error/401.html"
 | 
			
		||||
        - filter: "/images/mailman"
 | 
			
		||||
          params:
 | 
			
		||||
            - "alias /usr/share/images/mailman"
 | 
			
		||||
        - filter: "/robots.txt"
 | 
			
		||||
          params:
 | 
			
		||||
            - "alias /var/www/robots.txt"
 | 
			
		||||
        - filter: "/archives"
 | 
			
		||||
          params:
 | 
			
		||||
            - "alias /var/lib/mailman/archives/public"
 | 
			
		||||
            - "autoindex on"
 | 
			
		||||
 | 
			
		||||
glob_mailman3:
 | 
			
		||||
  site_owner: root@crans.org
 | 
			
		||||
  database:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										3
									
								
								hosts
								
								
								
								
							
							
						
						
									
										3
									
								
								hosts
								
								
								
								
							| 
						 | 
				
			
			@ -89,9 +89,6 @@ sputnik.adm.crans.org
 | 
			
		|||
[linx]
 | 
			
		||||
linx.adm.crans.org
 | 
			
		||||
 | 
			
		||||
[mailman]
 | 
			
		||||
redisdead.adm.crans.org
 | 
			
		||||
 | 
			
		||||
[mailman]
 | 
			
		||||
mailman.adm.crans.org
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,25 +1,5 @@
 | 
			
		|||
#!/usr/bin/env ansible-playbook
 | 
			
		||||
---
 | 
			
		||||
# Deploy Mailman
 | 
			
		||||
- hosts: redisdead.adm.crans.org
 | 
			
		||||
  vars:
 | 
			
		||||
    mailman:
 | 
			
		||||
      site_list: "nounou"
 | 
			
		||||
      default_url: "https://lists.crans.org/"
 | 
			
		||||
      default_host: "lists.crans.org"
 | 
			
		||||
      default_language: "fr"
 | 
			
		||||
      custom_logo: "crans_icon_dark.svg"
 | 
			
		||||
      custom_logo_name: "crans.svg"
 | 
			
		||||
      custom_logo_url: "https://www.crans.org/"
 | 
			
		||||
      custom_logo_alt: "CRANS"
 | 
			
		||||
    spamassassin: "SpamAssassin_crans"
 | 
			
		||||
    smtphost: "smtp.adm.crans.org"
 | 
			
		||||
    mynetworks: ['138.231.0.0/16', '185.230.76.0/22', '2a0c:700:0::/40']
 | 
			
		||||
    nginx: '{{ glob_nginx | default({}) | combine(loc_nginx | default({})) }}'
 | 
			
		||||
  roles:
 | 
			
		||||
    - mailman
 | 
			
		||||
    - nginx
 | 
			
		||||
 | 
			
		||||
# Deploy Mailman3
 | 
			
		||||
- hosts: mailman
 | 
			
		||||
  vars:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +0,0 @@
 | 
			
		|||
---
 | 
			
		||||
- name: Reload mailman
 | 
			
		||||
  systemd:
 | 
			
		||||
    name: mailman
 | 
			
		||||
    state: reloaded
 | 
			
		||||
| 
						 | 
				
			
			@ -1,47 +0,0 @@
 | 
			
		|||
---
 | 
			
		||||
- name: Install mailman and SpamAssassin
 | 
			
		||||
  apt:
 | 
			
		||||
    update_cache: true
 | 
			
		||||
    name:
 | 
			
		||||
      - mailman
 | 
			
		||||
      - spamassassin
 | 
			
		||||
  register: apt_result
 | 
			
		||||
  retries: 3
 | 
			
		||||
  until: apt_result is succeeded
 | 
			
		||||
 | 
			
		||||
- name: Deploy mailman config
 | 
			
		||||
  template:
 | 
			
		||||
    src: "mailman/{{ item }}.j2"
 | 
			
		||||
    dest: "/etc/mailman/{{ item }}"
 | 
			
		||||
    mode: 0755
 | 
			
		||||
  loop:
 | 
			
		||||
    - mm_cfg.py
 | 
			
		||||
    - create.html
 | 
			
		||||
  notify: Reload mailman
 | 
			
		||||
 | 
			
		||||
- name: Deploy mailman snippet
 | 
			
		||||
  template:
 | 
			
		||||
    src: "nginx/snippets/fastcgi-mailman.conf.j2"
 | 
			
		||||
    dest: "/etc/nginx/snippets/fastcgi-mailman.conf"
 | 
			
		||||
    owner: root
 | 
			
		||||
    group: root
 | 
			
		||||
    mode: 0644
 | 
			
		||||
 | 
			
		||||
# Fanciness
 | 
			
		||||
- name: Deploy custom logo
 | 
			
		||||
  copy:
 | 
			
		||||
    src: "{{ mailman.custom_logo }}"
 | 
			
		||||
    dest: "/usr/share/images/mailman/{{ mailman.custom_logo_name }}"
 | 
			
		||||
 | 
			
		||||
- name: Deploy custom logo
 | 
			
		||||
  template:
 | 
			
		||||
    src: usr/lib/mailman/Mailman/htmlformat.py.j2
 | 
			
		||||
    dest: /usr/lib/mailman/Mailman/htmlformat.py
 | 
			
		||||
    mode: 0755
 | 
			
		||||
  notify: Reload mailman
 | 
			
		||||
 | 
			
		||||
- name: Indicate role in motd
 | 
			
		||||
  template:
 | 
			
		||||
    src: update-motd.d/05-mailman.j2
 | 
			
		||||
    dest: /etc/update-motd.d/05-mailman
 | 
			
		||||
    mode: 0755
 | 
			
		||||
| 
						 | 
				
			
			@ -1,13 +0,0 @@
 | 
			
		|||
{{ ansible_header | comment('xml') }}
 | 
			
		||||
 | 
			
		||||
<html>
 | 
			
		||||
<head>
 | 
			
		||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
 | 
			
		||||
<title>Creation de mailing list</title>
 | 
			
		||||
</head>
 | 
			
		||||
 | 
			
		||||
<body>
 | 
			
		||||
<h1>Creation de mailing list</h1>
 | 
			
		||||
Il faut s'adresser a nounou arobase crans point org.
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,226 +0,0 @@
 | 
			
		|||
{{ ansible_header | comment }}
 | 
			
		||||
# -*- python -*-
 | 
			
		||||
 | 
			
		||||
# Copyright (C) 1998,1999,2000 by the Free Software Foundation, Inc.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software; you can redistribute it and/or
 | 
			
		||||
# modify it under the terms of the GNU General Public License
 | 
			
		||||
# as published by the Free Software Foundation; either version 2
 | 
			
		||||
# of the License, or (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# This program is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program; if not, write to the Free Software
 | 
			
		||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 | 
			
		||||
# 02110-1301 USA
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
"""This is the module which takes your site-specific settings.
 | 
			
		||||
 | 
			
		||||
From a raw distribution it should be copied to mm_cfg.py.  If you
 | 
			
		||||
already have an mm_cfg.py, be careful to add in only the new settings
 | 
			
		||||
you want.  The complete set of distributed defaults, with annotation,
 | 
			
		||||
are in ./Defaults.  In mm_cfg, override only those you want to
 | 
			
		||||
change, after the
 | 
			
		||||
 | 
			
		||||
  from Defaults import *
 | 
			
		||||
 | 
			
		||||
line (see below).
 | 
			
		||||
 | 
			
		||||
Note that these are just default settings - many can be overridden via the
 | 
			
		||||
admin and user interfaces on a per-list or per-user basis.
 | 
			
		||||
 | 
			
		||||
Note also that some of the settings are resolved against the active list
 | 
			
		||||
setting by using the value as a format string against the
 | 
			
		||||
list-instance-object's dictionary - see the distributed value of
 | 
			
		||||
DEFAULT_MSG_FOOTER for an example."""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#######################################################
 | 
			
		||||
#    Here's where we get the distributed defaults.    #
 | 
			
		||||
 | 
			
		||||
from Defaults import *
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#####
 | 
			
		||||
# General system-wide defaults
 | 
			
		||||
#####
 | 
			
		||||
 | 
			
		||||
# Should image logos be used?  Set this to 0 to disable image logos from "our
 | 
			
		||||
# sponsors" and just use textual links instead (this will also disable the
 | 
			
		||||
# shortcut "favicon").  Otherwise, this should contain the URL base path to
 | 
			
		||||
# the logo images (and must contain the trailing slash)..  If you want to
 | 
			
		||||
# disable Mailman's logo footer altogther, hack
 | 
			
		||||
# Mailman/htmlformat.py:MailmanLogo(), which also contains the hardcoded links
 | 
			
		||||
# and image names.
 | 
			
		||||
IMAGE_LOGOS = '/images/mailman/'
 | 
			
		||||
 | 
			
		||||
#-------------------------------------------------------------
 | 
			
		||||
# The name of the list Mailman uses to send password reminders
 | 
			
		||||
# and similar. Don't change if you want mailman-owner to be
 | 
			
		||||
# a valid local part.
 | 
			
		||||
MAILMAN_SITE_LIST = '{{ mailman.site_list }}'
 | 
			
		||||
 | 
			
		||||
DEFAULT_URL= '{{ mailman.default_url }}'
 | 
			
		||||
DEFAULT_URL_PATTERN = 'https://%s/'
 | 
			
		||||
add_virtualhost(DEFAULT_URL_HOST, DEFAULT_EMAIL_HOST)
 | 
			
		||||
 | 
			
		||||
#-------------------------------------------------------------
 | 
			
		||||
# Default domain for email addresses of newly created MLs
 | 
			
		||||
DEFAULT_EMAIL_HOST = '{{ mailman.default_host }}'
 | 
			
		||||
#-------------------------------------------------------------
 | 
			
		||||
# Default host for web interface of newly created MLs
 | 
			
		||||
DEFAULT_URL_HOST   = '{{ mailman.default_host }}'
 | 
			
		||||
#-------------------------------------------------------------
 | 
			
		||||
# Required when setting any of its arguments.
 | 
			
		||||
add_virtualhost(DEFAULT_URL_HOST, DEFAULT_EMAIL_HOST)
 | 
			
		||||
 | 
			
		||||
#-------------------------------------------------------------
 | 
			
		||||
# Do we send monthly reminders?
 | 
			
		||||
DEFAULT_SEND_REMINDERS = No
 | 
			
		||||
 | 
			
		||||
# Normally when a site administrator authenticates to a web page with the site
 | 
			
		||||
# password, they get a cookie which authorizes them as the list admin.  It
 | 
			
		||||
# makes me nervous to hand out site auth cookies because if this cookie is
 | 
			
		||||
# cracked or intercepted, the intruder will have access to every list on the
 | 
			
		||||
# site.  OTOH, it's dang handy to not have to re-authenticate to every list on
 | 
			
		||||
# the site.  Set this value to Yes to allow site admin cookies.
 | 
			
		||||
ALLOW_SITE_ADMIN_COOKIES = Yes
 | 
			
		||||
 | 
			
		||||
#####
 | 
			
		||||
# Archive defaults
 | 
			
		||||
#####
 | 
			
		||||
 | 
			
		||||
PUBLIC_ARCHIVE_URL = '{{ mailman.default_url }}archives/%(listname)s'
 | 
			
		||||
 | 
			
		||||
# Are archives on or off by default?
 | 
			
		||||
DEFAULT_ARCHIVE = Off
 | 
			
		||||
 | 
			
		||||
# Are archives public or private by default?
 | 
			
		||||
# 0=public, 1=private
 | 
			
		||||
DEFAULT_ARCHIVE_PRIVATE = 1
 | 
			
		||||
 | 
			
		||||
# Pipermail assumes that messages bodies contain US-ASCII text.
 | 
			
		||||
# Change this option to define a different character set to be used as
 | 
			
		||||
# the default character set for the archive.  The term "character set"
 | 
			
		||||
# is used in MIME to refer to a method of converting a sequence of
 | 
			
		||||
# octets into a sequence of characters.  If you change the default
 | 
			
		||||
# charset, you might need to add it to VERBATIM_ENCODING below.
 | 
			
		||||
DEFAULT_CHARSET = 'utf-8'
 | 
			
		||||
 | 
			
		||||
# Most character set encodings require special HTML entity characters to be
 | 
			
		||||
# quoted, otherwise they won't look right in the Pipermail archives.  However
 | 
			
		||||
# some character sets must not quote these characters so that they can be
 | 
			
		||||
# rendered properly in the browsers.  The primary issue is multi-byte
 | 
			
		||||
# encodings where the octet 0x26 does not always represent the & character.
 | 
			
		||||
# This variable contains a list of such characters sets which are not
 | 
			
		||||
# HTML-quoted in the archives.
 | 
			
		||||
VERBATIM_ENCODING = ['utf-8']
 | 
			
		||||
 | 
			
		||||
#####
 | 
			
		||||
# General defaults
 | 
			
		||||
#####
 | 
			
		||||
 | 
			
		||||
# The default language for this server.  Whenever we can't figure out the list
 | 
			
		||||
# context or user context, we'll fall back to using this language.  See
 | 
			
		||||
# LC_DESCRIPTIONS below for legal values.
 | 
			
		||||
DEFAULT_SERVER_LANGUAGE = '{{ mailman.default_language }}'
 | 
			
		||||
 | 
			
		||||
# How many members to display at a time on the admin cgi to unsubscribe them
 | 
			
		||||
# or change their options?
 | 
			
		||||
DEFAULT_ADMIN_MEMBER_CHUNKSIZE = 50
 | 
			
		||||
 | 
			
		||||
# set this variable to Yes to allow list owners to delete their own mailing
 | 
			
		||||
# lists.  You may not want to give them this power, in which case, setting
 | 
			
		||||
# this variable to No instead requires list removal to be done by the site
 | 
			
		||||
# administrator, via the command line script bin/rmlist.
 | 
			
		||||
#OWNERS_CAN_DELETE_THEIR_OWN_LISTS = No
 | 
			
		||||
 | 
			
		||||
# Set this variable to Yes to allow list owners to set the "personalized"
 | 
			
		||||
# flags on their mailing lists.  Turning these on tells Mailman to send
 | 
			
		||||
# separate email messages to each user instead of batching them together for
 | 
			
		||||
# delivery to the MTA.  This gives each member a more personalized message,
 | 
			
		||||
# but can have a heavy impact on the performance of your system.
 | 
			
		||||
#OWNERS_CAN_ENABLE_PERSONALIZATION = No
 | 
			
		||||
 | 
			
		||||
#####
 | 
			
		||||
# List defaults.  NOTE: Changing these values does NOT change the
 | 
			
		||||
# configuration of an existing list.  It only defines the default for new
 | 
			
		||||
# lists you subsequently create.
 | 
			
		||||
#####
 | 
			
		||||
 | 
			
		||||
# Should a list, by default be advertised?  What is the default maximum number
 | 
			
		||||
# of explicit recipients allowed?  What is the default maximum message size
 | 
			
		||||
# allowed?
 | 
			
		||||
DEFAULT_LIST_ADVERTISED = Yes
 | 
			
		||||
 | 
			
		||||
# {header-name: regexp} spam filtering - we include some for example sake.
 | 
			
		||||
DEFAULT_BOUNCE_MATCHING_HEADERS = """
 | 
			
		||||
# Les lignes commencant par # sont des commentairtes.
 | 
			
		||||
#from: .*-owner@yahoogroups.com
 | 
			
		||||
#from: .*@uplinkpro.com
 | 
			
		||||
#from: .*@coolstats.comic.com
 | 
			
		||||
#from: .*@trafficmagnet.com
 | 
			
		||||
#from: .*@hotmail.com
 | 
			
		||||
#X-Reject: 450
 | 
			
		||||
#X-Reject: 554
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
# Mailman can be configured to strip any existing Reply-To: header, or simply
 | 
			
		||||
# extend any existing Reply-To: with one based on the above setting.
 | 
			
		||||
DEFAULT_FIRST_STRIP_REPLY_TO = Yes
 | 
			
		||||
 | 
			
		||||
# SUBSCRIBE POLICY
 | 
			
		||||
# 0 - open list (only when ALLOW_OPEN_SUBSCRIBE is set to 1) **
 | 
			
		||||
# 1 - confirmation required for subscribes
 | 
			
		||||
# 2 - admin approval required for subscribes
 | 
			
		||||
# 3 - both confirmation and admin approval required
 | 
			
		||||
#
 | 
			
		||||
# ** please do not choose option 0 if you are not allowing open
 | 
			
		||||
# subscribes (next variable)
 | 
			
		||||
DEFAULT_SUBSCRIBE_POLICY = 3
 | 
			
		||||
 | 
			
		||||
# Is the list owner notified of subscribes/unsubscribes?
 | 
			
		||||
DEFAULT_ADMIN_NOTIFY_MCHANGES = Yes
 | 
			
		||||
 | 
			
		||||
# Do we send monthly reminders?
 | 
			
		||||
DEFAULT_SEND_REMINDERS = No
 | 
			
		||||
 | 
			
		||||
# What should happen to non-member posts which do not match explicit
 | 
			
		||||
# non-member actions?
 | 
			
		||||
# 0 = Accept
 | 
			
		||||
# 1 = Hold
 | 
			
		||||
# 2 = Reject
 | 
			
		||||
# 3 = Discard
 | 
			
		||||
DEFAULT_GENERIC_NONMEMBER_ACTION = 1
 | 
			
		||||
 | 
			
		||||
# Use spamassassin automatically
 | 
			
		||||
GLOBAL_PIPELINE.insert(5, '{{ spamassassin }}')
 | 
			
		||||
# Discard messages with score higher than ...
 | 
			
		||||
SPAMASSASSIN_DISCARD_SCORE = 8
 | 
			
		||||
# Hold in moderation messages with score higher than ...
 | 
			
		||||
SPAMASSASSIN_HOLD_SCORE = 2.1
 | 
			
		||||
 | 
			
		||||
# Add SpamAssassin administration interface on gui
 | 
			
		||||
# To make it work, you need to edit Gui/__init__.py
 | 
			
		||||
# with
 | 
			
		||||
# from SpamAssassin import SpamAssassin
 | 
			
		||||
ADMIN_CATEGORIES.append("spamassassin")
 | 
			
		||||
 | 
			
		||||
# Add header to keep
 | 
			
		||||
PLAIN_DIGEST_KEEP_HEADERS.append('X-Spam-Score')
 | 
			
		||||
 | 
			
		||||
# configure MTA
 | 
			
		||||
MTA = 'Postfix'
 | 
			
		||||
SMTPHOST = '{{ smtphost }}'
 | 
			
		||||
SMTP_MAX_RCPTS = 50
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
POSTFIX_STYLE_VIRTUAL_DOMAINS = ["{{ mailman.default_host }}"]
 | 
			
		||||
 | 
			
		||||
# Note - if you're looking for something that is imported from mm_cfg, but you
 | 
			
		||||
# didn't find it above, it's probably in /usr/lib/mailman/Mailman/Defaults.py.
 | 
			
		||||
| 
						 | 
				
			
			@ -1,18 +0,0 @@
 | 
			
		|||
{{ ansible_header | comment }}
 | 
			
		||||
 | 
			
		||||
# regex to split $uri to $fastcgi_script_name and $fastcgi_path
 | 
			
		||||
fastcgi_split_path_info (^/[^/]*)(.*)$;
 | 
			
		||||
 | 
			
		||||
# check that the PHP script exists before passing it
 | 
			
		||||
try_files $fastcgi_script_name =404;
 | 
			
		||||
 | 
			
		||||
# Bypass the fact that try_files resets $fastcgi_path_info
 | 
			
		||||
# see: http://trac.nginx.org/nginx/ticket/321
 | 
			
		||||
set $path_info $fastcgi_path_info;
 | 
			
		||||
fastcgi_param PATH_INFO $path_info;
 | 
			
		||||
 | 
			
		||||
# Let NGINX handle errors
 | 
			
		||||
fastcgi_intercept_errors on;
 | 
			
		||||
 | 
			
		||||
include /etc/nginx/fastcgi.conf;
 | 
			
		||||
fastcgi_pass unix:/var/run/fcgiwrap.socket;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,3 +0,0 @@
 | 
			
		|||
#!/usr/bin/tail +14
 | 
			
		||||
{{ ansible_header | comment }}
 | 
			
		||||
[0m> [38;5;82mMailman[0m a été déployé sur cette machine. Voir [38;5;6m/etc/mailman/[0m et [38;5;6m/var/lib/mailman/[0m.
 | 
			
		||||
| 
						 | 
				
			
			@ -1,742 +0,0 @@
 | 
			
		|||
{{ ansible_header | comment }}
 | 
			
		||||
# Copyright (C) 1998-2018 by the Free Software Foundation, Inc.
 | 
			
		||||
#
 | 
			
		||||
# This program is free software; you can redistribute it and/or
 | 
			
		||||
# modify it under the terms of the GNU General Public License
 | 
			
		||||
# as published by the Free Software Foundation; either version 2
 | 
			
		||||
# of the License, or (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# This program is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program; if not, write to the Free Software
 | 
			
		||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
 | 
			
		||||
# USA.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
"""Library for program-based construction of an HTML documents.
 | 
			
		||||
 | 
			
		||||
Encapsulate HTML formatting directives in classes that act as containers
 | 
			
		||||
for python and, recursively, for nested HTML formatting objects.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Eventually could abstract down to HtmlItem, which outputs an arbitrary html
 | 
			
		||||
# object given start / end tags, valid options, and a value.  Ug, objects
 | 
			
		||||
# shouldn't be adding their own newlines.  The next object should.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import types
 | 
			
		||||
 | 
			
		||||
from Mailman import mm_cfg
 | 
			
		||||
from Mailman import Utils
 | 
			
		||||
from Mailman.i18n import _, get_translation
 | 
			
		||||
 | 
			
		||||
from Mailman.CSRFcheck import csrf_token
 | 
			
		||||
 | 
			
		||||
SPACE = ' '
 | 
			
		||||
EMPTYSTRING = ''
 | 
			
		||||
NL = '\n'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Format an arbitrary object.
 | 
			
		||||
def HTMLFormatObject(item, indent):
 | 
			
		||||
    "Return a presentation of an object, invoking their Format method if any."
 | 
			
		||||
    if type(item) == type(''):
 | 
			
		||||
        return item
 | 
			
		||||
    elif not hasattr(item, "Format"):
 | 
			
		||||
        return `item`
 | 
			
		||||
    else:
 | 
			
		||||
        return item.Format(indent)
 | 
			
		||||
 | 
			
		||||
def CaseInsensitiveKeyedDict(d):
 | 
			
		||||
    result = {}
 | 
			
		||||
    for (k,v) in d.items():
 | 
			
		||||
        result[k.lower()] = v
 | 
			
		||||
    return result
 | 
			
		||||
 | 
			
		||||
# Given references to two dictionaries, copy the second dictionary into the
 | 
			
		||||
# first one.
 | 
			
		||||
def DictMerge(destination, fresh_dict):
 | 
			
		||||
    for (key, value) in fresh_dict.items():
 | 
			
		||||
        destination[key] = value
 | 
			
		||||
 | 
			
		||||
class Table:
 | 
			
		||||
    def __init__(self, **table_opts):
 | 
			
		||||
        self.cells = []
 | 
			
		||||
        self.cell_info = {}
 | 
			
		||||
        self.row_info = {}
 | 
			
		||||
        self.opts = table_opts
 | 
			
		||||
 | 
			
		||||
    def AddOptions(self, opts):
 | 
			
		||||
        DictMerge(self.opts, opts)
 | 
			
		||||
 | 
			
		||||
    # Sets all of the cells.  It writes over whatever cells you had there
 | 
			
		||||
    # previously.
 | 
			
		||||
 | 
			
		||||
    def SetAllCells(self, cells):
 | 
			
		||||
        self.cells = cells
 | 
			
		||||
 | 
			
		||||
    # Add a new blank row at the end
 | 
			
		||||
    def NewRow(self):
 | 
			
		||||
        self.cells.append([])
 | 
			
		||||
 | 
			
		||||
    # Add a new blank cell at the end
 | 
			
		||||
    def NewCell(self):
 | 
			
		||||
        self.cells[-1].append('')
 | 
			
		||||
 | 
			
		||||
    def AddRow(self, row):
 | 
			
		||||
        self.cells.append(row)
 | 
			
		||||
 | 
			
		||||
    def AddCell(self, cell):
 | 
			
		||||
        self.cells[-1].append(cell)
 | 
			
		||||
 | 
			
		||||
    def AddCellInfo(self, row, col, **kws):
 | 
			
		||||
        kws = CaseInsensitiveKeyedDict(kws)
 | 
			
		||||
        if not self.cell_info.has_key(row):
 | 
			
		||||
            self.cell_info[row] = { col : kws }
 | 
			
		||||
        elif self.cell_info[row].has_key(col):
 | 
			
		||||
            DictMerge(self.cell_info[row], kws)
 | 
			
		||||
        else:
 | 
			
		||||
            self.cell_info[row][col] = kws
 | 
			
		||||
 | 
			
		||||
    def AddRowInfo(self, row, **kws):
 | 
			
		||||
        kws = CaseInsensitiveKeyedDict(kws)
 | 
			
		||||
        if not self.row_info.has_key(row):
 | 
			
		||||
            self.row_info[row] = kws
 | 
			
		||||
        else:
 | 
			
		||||
            DictMerge(self.row_info[row], kws)
 | 
			
		||||
 | 
			
		||||
    # What's the index for the row we just put in?
 | 
			
		||||
    def GetCurrentRowIndex(self):
 | 
			
		||||
        return len(self.cells)-1
 | 
			
		||||
 | 
			
		||||
    # What's the index for the col we just put in?
 | 
			
		||||
    def GetCurrentCellIndex(self):
 | 
			
		||||
        return len(self.cells[-1])-1
 | 
			
		||||
 | 
			
		||||
    def ExtractCellInfo(self, info):
 | 
			
		||||
        valid_mods = ['align', 'valign', 'nowrap', 'rowspan', 'colspan',
 | 
			
		||||
                      'bgcolor']
 | 
			
		||||
        output = ''
 | 
			
		||||
 | 
			
		||||
        for (key, val) in info.items():
 | 
			
		||||
            if not key in valid_mods:
 | 
			
		||||
                continue
 | 
			
		||||
            if key == 'nowrap':
 | 
			
		||||
                output = output + ' NOWRAP'
 | 
			
		||||
                continue
 | 
			
		||||
            else:
 | 
			
		||||
                output = output + ' %s="%s"' % (key.upper(), val)
 | 
			
		||||
 | 
			
		||||
        return output
 | 
			
		||||
 | 
			
		||||
    def ExtractRowInfo(self, info):
 | 
			
		||||
        valid_mods = ['align', 'valign', 'bgcolor']
 | 
			
		||||
        output = ''
 | 
			
		||||
 | 
			
		||||
        for (key, val) in info.items():
 | 
			
		||||
            if not key in valid_mods:
 | 
			
		||||
                continue
 | 
			
		||||
            output = output + ' %s="%s"' % (key.upper(), val)
 | 
			
		||||
 | 
			
		||||
        return output
 | 
			
		||||
 | 
			
		||||
    def ExtractTableInfo(self, info):
 | 
			
		||||
        valid_mods = ['align', 'width', 'border', 'cellspacing', 'cellpadding',
 | 
			
		||||
                      'bgcolor']
 | 
			
		||||
 | 
			
		||||
        output = ''
 | 
			
		||||
 | 
			
		||||
        for (key, val) in info.items():
 | 
			
		||||
            if not key in valid_mods:
 | 
			
		||||
                continue
 | 
			
		||||
            if key == 'border' and val == None:
 | 
			
		||||
                output = output + ' BORDER'
 | 
			
		||||
                continue
 | 
			
		||||
            else:
 | 
			
		||||
                output = output + ' %s="%s"' % (key.upper(), val)
 | 
			
		||||
 | 
			
		||||
        return output
 | 
			
		||||
 | 
			
		||||
    def FormatCell(self, row, col, indent):
 | 
			
		||||
        try:
 | 
			
		||||
            my_info = self.cell_info[row][col]
 | 
			
		||||
        except:
 | 
			
		||||
            my_info = None
 | 
			
		||||
 | 
			
		||||
        output = '\n' + ' '*indent + '<td'
 | 
			
		||||
        if my_info:
 | 
			
		||||
            output = output + self.ExtractCellInfo(my_info)
 | 
			
		||||
        item = self.cells[row][col]
 | 
			
		||||
        item_format = HTMLFormatObject(item, indent+4)
 | 
			
		||||
        output = '%s>%s</td>' % (output, item_format)
 | 
			
		||||
        return output
 | 
			
		||||
 | 
			
		||||
    def FormatRow(self, row, indent):
 | 
			
		||||
        try:
 | 
			
		||||
            my_info = self.row_info[row]
 | 
			
		||||
        except:
 | 
			
		||||
            my_info = None
 | 
			
		||||
 | 
			
		||||
        output = '\n' + ' '*indent + '<tr'
 | 
			
		||||
        if my_info:
 | 
			
		||||
            output = output + self.ExtractRowInfo(my_info)
 | 
			
		||||
        output = output + '>'
 | 
			
		||||
 | 
			
		||||
        for i in range(len(self.cells[row])):
 | 
			
		||||
            output = output + self.FormatCell(row, i, indent + 2)
 | 
			
		||||
 | 
			
		||||
        output = output + '\n' + ' '*indent + '</tr>'
 | 
			
		||||
 | 
			
		||||
        return output
 | 
			
		||||
 | 
			
		||||
    def Format(self, indent=0):
 | 
			
		||||
        output = '\n' + ' '*indent + '<table'
 | 
			
		||||
        output = output + self.ExtractTableInfo(self.opts)
 | 
			
		||||
        output = output + '>'
 | 
			
		||||
 | 
			
		||||
        for i in range(len(self.cells)):
 | 
			
		||||
            output = output + self.FormatRow(i, indent + 2)
 | 
			
		||||
 | 
			
		||||
        output = output + '\n' + ' '*indent + '</table>\n'
 | 
			
		||||
 | 
			
		||||
        return output
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Link:
 | 
			
		||||
    def __init__(self, href, text, target=None):
 | 
			
		||||
        self.href = href
 | 
			
		||||
        self.text = text
 | 
			
		||||
        self.target = target
 | 
			
		||||
 | 
			
		||||
    def Format(self, indent=0):
 | 
			
		||||
        texpr = ""
 | 
			
		||||
        if self.target != None:
 | 
			
		||||
            texpr = ' target="%s"' % self.target
 | 
			
		||||
        return '<a href="%s"%s>%s</a>' % (HTMLFormatObject(self.href, indent),
 | 
			
		||||
                                          texpr,
 | 
			
		||||
                                          HTMLFormatObject(self.text, indent))
 | 
			
		||||
 | 
			
		||||
class FontSize:
 | 
			
		||||
    """FontSize is being deprecated - use FontAttr(..., size="...") instead."""
 | 
			
		||||
    def __init__(self, size, *items):
 | 
			
		||||
        self.items = list(items)
 | 
			
		||||
        self.size = size
 | 
			
		||||
 | 
			
		||||
    def Format(self, indent=0):
 | 
			
		||||
        output = '<font size="%s">' % self.size
 | 
			
		||||
        for item in self.items:
 | 
			
		||||
            output = output + HTMLFormatObject(item, indent)
 | 
			
		||||
        output = output + '</font>'
 | 
			
		||||
        return output
 | 
			
		||||
 | 
			
		||||
class FontAttr:
 | 
			
		||||
    """Present arbitrary font attributes."""
 | 
			
		||||
    def __init__(self, *items, **kw):
 | 
			
		||||
        self.items = list(items)
 | 
			
		||||
        self.attrs = kw
 | 
			
		||||
 | 
			
		||||
    def Format(self, indent=0):
 | 
			
		||||
        seq = []
 | 
			
		||||
        for k, v in self.attrs.items():
 | 
			
		||||
            seq.append('%s="%s"' % (k, v))
 | 
			
		||||
        output = '<font %s>' % SPACE.join(seq)
 | 
			
		||||
        for item in self.items:
 | 
			
		||||
            output = output + HTMLFormatObject(item, indent)
 | 
			
		||||
        output = output + '</font>'
 | 
			
		||||
        return output
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Container:
 | 
			
		||||
    def __init__(self, *items):
 | 
			
		||||
        if not items:
 | 
			
		||||
            self.items = []
 | 
			
		||||
        else:
 | 
			
		||||
            self.items = items
 | 
			
		||||
 | 
			
		||||
    def AddItem(self, obj):
 | 
			
		||||
        self.items.append(obj)
 | 
			
		||||
 | 
			
		||||
    def Format(self, indent=0):
 | 
			
		||||
        output = []
 | 
			
		||||
        for item in self.items:
 | 
			
		||||
            output.append(HTMLFormatObject(item, indent))
 | 
			
		||||
        return EMPTYSTRING.join(output)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Label(Container):
 | 
			
		||||
    align = 'right'
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *items):
 | 
			
		||||
        Container.__init__(self, *items)
 | 
			
		||||
 | 
			
		||||
    def Format(self, indent=0):
 | 
			
		||||
        return ('<div align="%s">' % self.align) + \
 | 
			
		||||
               Container.Format(self, indent) + \
 | 
			
		||||
               '</div>'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# My own standard document template.  YMMV.
 | 
			
		||||
# something more abstract would be more work to use...
 | 
			
		||||
 | 
			
		||||
class Document(Container):
 | 
			
		||||
    title = None
 | 
			
		||||
    language = None
 | 
			
		||||
    bgcolor = mm_cfg.WEB_BG_COLOR
 | 
			
		||||
    suppress_head = 0
 | 
			
		||||
 | 
			
		||||
    def set_language(self, lang=None):
 | 
			
		||||
        self.language = lang
 | 
			
		||||
 | 
			
		||||
    def set_bgcolor(self, color):
 | 
			
		||||
        self.bgcolor = color
 | 
			
		||||
 | 
			
		||||
    def SetTitle(self, title):
 | 
			
		||||
        self.title = title
 | 
			
		||||
 | 
			
		||||
    def Format(self, indent=0, **kws):
 | 
			
		||||
        charset = 'us-ascii'
 | 
			
		||||
        if self.language and Utils.IsLanguage(self.language):
 | 
			
		||||
            charset = Utils.GetCharSet(self.language)
 | 
			
		||||
        output = ['Content-Type: text/html; charset=%s' % charset]
 | 
			
		||||
        output.append('Cache-control: no-cache\n')
 | 
			
		||||
        if not self.suppress_head:
 | 
			
		||||
            kws.setdefault('bgcolor', self.bgcolor)
 | 
			
		||||
            tab = ' ' * indent
 | 
			
		||||
            output.extend([tab,
 | 
			
		||||
                           '<HTML>',
 | 
			
		||||
                           '<HEAD>'
 | 
			
		||||
                           ])
 | 
			
		||||
            if mm_cfg.IMAGE_LOGOS:
 | 
			
		||||
                output.append('<LINK REL="SHORTCUT ICON" HREF="%s">' %
 | 
			
		||||
                              (mm_cfg.IMAGE_LOGOS + mm_cfg.SHORTCUT_ICON))
 | 
			
		||||
            # Hit all the bases
 | 
			
		||||
            output.append('<META http-equiv="Content-Type" '
 | 
			
		||||
                          'content="text/html; charset=%s">' % charset)
 | 
			
		||||
            if self.title:
 | 
			
		||||
                output.append('%s<TITLE>%s</TITLE>' % (tab, self.title))
 | 
			
		||||
            # Add CSS to visually hide some labeling text but allow screen
 | 
			
		||||
            # readers to read it.
 | 
			
		||||
            output.append("""\
 | 
			
		||||
<style type="text/css">
 | 
			
		||||
    div.hidden
 | 
			
		||||
        {position:absolute;
 | 
			
		||||
        left:-10000px;
 | 
			
		||||
        top:auto;
 | 
			
		||||
        width:1px;
 | 
			
		||||
        height:1px;
 | 
			
		||||
        overflow:hidden;}
 | 
			
		||||
</style>
 | 
			
		||||
""")
 | 
			
		||||
            if mm_cfg.WEB_HEAD_ADD:
 | 
			
		||||
                output.append(mm_cfg.WEB_HEAD_ADD)
 | 
			
		||||
            output.append('%s</HEAD>' % tab)
 | 
			
		||||
            quals = []
 | 
			
		||||
            # Default link colors
 | 
			
		||||
            if mm_cfg.WEB_VLINK_COLOR:
 | 
			
		||||
                kws.setdefault('vlink', mm_cfg.WEB_VLINK_COLOR)
 | 
			
		||||
            if mm_cfg.WEB_ALINK_COLOR:
 | 
			
		||||
                kws.setdefault('alink', mm_cfg.WEB_ALINK_COLOR)
 | 
			
		||||
            if mm_cfg.WEB_LINK_COLOR:
 | 
			
		||||
                kws.setdefault('link', mm_cfg.WEB_LINK_COLOR)
 | 
			
		||||
            for k, v in kws.items():
 | 
			
		||||
                quals.append('%s="%s"' % (k, v))
 | 
			
		||||
            output.append('%s<BODY %s' % (tab, SPACE.join(quals)))
 | 
			
		||||
            # Language direction
 | 
			
		||||
            direction = Utils.GetDirection(self.language)
 | 
			
		||||
            output.append('dir="%s">' % direction)
 | 
			
		||||
        # Always do this...
 | 
			
		||||
        output.append(Container.Format(self, indent))
 | 
			
		||||
        if not self.suppress_head:
 | 
			
		||||
            output.append('%s</BODY>' % tab)
 | 
			
		||||
            output.append('%s</HTML>' % tab)
 | 
			
		||||
        return NL.join(output)
 | 
			
		||||
 | 
			
		||||
    def addError(self, errmsg, tag=None):
 | 
			
		||||
        if tag is None:
 | 
			
		||||
            tag = _('Error: ')
 | 
			
		||||
        self.AddItem(Header(3, Bold(FontAttr(
 | 
			
		||||
            _(tag), color=mm_cfg.WEB_ERROR_COLOR, size='+2')).Format() +
 | 
			
		||||
                            Italic(errmsg).Format()))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HeadlessDocument(Document):
 | 
			
		||||
    """Document without head section, for templates that provide their own."""
 | 
			
		||||
    suppress_head = 1
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class StdContainer(Container):
 | 
			
		||||
    def Format(self, indent=0):
 | 
			
		||||
        # If I don't start a new I ignore indent
 | 
			
		||||
        output = '<%s>' % self.tag
 | 
			
		||||
        output = output + Container.Format(self, indent)
 | 
			
		||||
        output = '%s</%s>' % (output, self.tag)
 | 
			
		||||
        return output
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class QuotedContainer(Container):
 | 
			
		||||
    def Format(self, indent=0):
 | 
			
		||||
        # If I don't start a new I ignore indent
 | 
			
		||||
        output = '<%s>%s</%s>' % (
 | 
			
		||||
            self.tag,
 | 
			
		||||
            Utils.websafe(Container.Format(self, indent)),
 | 
			
		||||
            self.tag)
 | 
			
		||||
        return output
 | 
			
		||||
 | 
			
		||||
class Header(StdContainer):
 | 
			
		||||
    def __init__(self, num, *items):
 | 
			
		||||
        self.items = items
 | 
			
		||||
        self.tag = 'h%d' % num
 | 
			
		||||
 | 
			
		||||
class Address(StdContainer):
 | 
			
		||||
    tag = 'address'
 | 
			
		||||
 | 
			
		||||
class Underline(StdContainer):
 | 
			
		||||
    tag = 'u'
 | 
			
		||||
 | 
			
		||||
class Bold(StdContainer):
 | 
			
		||||
    tag = 'strong'
 | 
			
		||||
 | 
			
		||||
class Italic(StdContainer):
 | 
			
		||||
    tag = 'em'
 | 
			
		||||
 | 
			
		||||
class Preformatted(QuotedContainer):
 | 
			
		||||
    tag = 'pre'
 | 
			
		||||
 | 
			
		||||
class Subscript(StdContainer):
 | 
			
		||||
    tag = 'sub'
 | 
			
		||||
 | 
			
		||||
class Superscript(StdContainer):
 | 
			
		||||
    tag = 'sup'
 | 
			
		||||
 | 
			
		||||
class Strikeout(StdContainer):
 | 
			
		||||
    tag = 'strike'
 | 
			
		||||
 | 
			
		||||
class Center(StdContainer):
 | 
			
		||||
    tag = 'center'
 | 
			
		||||
 | 
			
		||||
class Form(Container):
 | 
			
		||||
    def __init__(self, action='', method='POST', encoding=None,
 | 
			
		||||
                       mlist=None, contexts=None, user=None, *items):
 | 
			
		||||
        apply(Container.__init__, (self,) +  items)
 | 
			
		||||
        self.action = action
 | 
			
		||||
        self.method = method
 | 
			
		||||
        self.encoding = encoding
 | 
			
		||||
        self.mlist = mlist
 | 
			
		||||
        self.contexts = contexts
 | 
			
		||||
        self.user = user
 | 
			
		||||
 | 
			
		||||
    def set_action(self, action):
 | 
			
		||||
        self.action = action
 | 
			
		||||
 | 
			
		||||
    def Format(self, indent=0):
 | 
			
		||||
        spaces = ' ' * indent
 | 
			
		||||
        encoding = ''
 | 
			
		||||
        if self.encoding:
 | 
			
		||||
            encoding = 'enctype="%s"' % self.encoding
 | 
			
		||||
        output = '\n%s<FORM action="%s" method="%s" %s>\n' % (
 | 
			
		||||
            spaces, self.action, self.method, encoding)
 | 
			
		||||
        if self.mlist:
 | 
			
		||||
            output = output + \
 | 
			
		||||
                '<input type="hidden" name="csrf_token" value="%s">\n' \
 | 
			
		||||
                % csrf_token(self.mlist, self.contexts, self.user)
 | 
			
		||||
        output = output + Container.Format(self, indent+2)
 | 
			
		||||
        output = '%s\n%s</FORM>\n' % (output, spaces)
 | 
			
		||||
        return output
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class InputObj:
 | 
			
		||||
    def __init__(self, name, ty, value, checked, **kws):
 | 
			
		||||
        self.name = name
 | 
			
		||||
        self.type = ty
 | 
			
		||||
        self.value = value
 | 
			
		||||
        self.checked = checked
 | 
			
		||||
        self.kws = kws
 | 
			
		||||
 | 
			
		||||
    def Format(self, indent=0):
 | 
			
		||||
        charset = get_translation().charset() or 'us-ascii'
 | 
			
		||||
        output = ['<INPUT name="%s" type="%s" value="%s"' %
 | 
			
		||||
                  (self.name, self.type, self.value)]
 | 
			
		||||
        for item in self.kws.items():
 | 
			
		||||
            output.append('%s="%s"' % item)
 | 
			
		||||
        if self.checked:
 | 
			
		||||
            output.append('CHECKED')
 | 
			
		||||
        output.append('>')
 | 
			
		||||
        ret = SPACE.join(output)
 | 
			
		||||
        if self.type == 'TEXT' and isinstance(ret, unicode):
 | 
			
		||||
            ret = ret.encode(charset, 'xmlcharrefreplace')
 | 
			
		||||
        return ret
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SubmitButton(InputObj):
 | 
			
		||||
    def __init__(self, name, button_text):
 | 
			
		||||
        InputObj.__init__(self, name, "SUBMIT", button_text, checked=0)
 | 
			
		||||
 | 
			
		||||
class PasswordBox(InputObj):
 | 
			
		||||
    def __init__(self, name, value='', size=mm_cfg.TEXTFIELDWIDTH):
 | 
			
		||||
        InputObj.__init__(self, name, "PASSWORD", value, checked=0, size=size)
 | 
			
		||||
 | 
			
		||||
class TextBox(InputObj):
 | 
			
		||||
    def __init__(self, name, value='', size=mm_cfg.TEXTFIELDWIDTH):
 | 
			
		||||
        if isinstance(value, str):
 | 
			
		||||
            safevalue = Utils.websafe(value)
 | 
			
		||||
        else:
 | 
			
		||||
            safevalue = value
 | 
			
		||||
        InputObj.__init__(self, name, "TEXT", safevalue, checked=0, size=size)
 | 
			
		||||
 | 
			
		||||
class Hidden(InputObj):
 | 
			
		||||
    def __init__(self, name, value=''):
 | 
			
		||||
        InputObj.__init__(self, name, 'HIDDEN', value, checked=0)
 | 
			
		||||
 | 
			
		||||
class TextArea:
 | 
			
		||||
    def __init__(self, name, text='', rows=None, cols=None, wrap='soft',
 | 
			
		||||
                 readonly=0):
 | 
			
		||||
        if isinstance(text, str):
 | 
			
		||||
            # Double escape HTML entities in non-readonly areas.
 | 
			
		||||
            doubleescape = not readonly
 | 
			
		||||
            safetext = Utils.websafe(text, doubleescape)
 | 
			
		||||
        else:
 | 
			
		||||
            safetext = text
 | 
			
		||||
        self.name = name
 | 
			
		||||
        self.text = safetext
 | 
			
		||||
        self.rows = rows
 | 
			
		||||
        self.cols = cols
 | 
			
		||||
        self.wrap = wrap
 | 
			
		||||
        self.readonly = readonly
 | 
			
		||||
 | 
			
		||||
    def Format(self, indent=0):
 | 
			
		||||
        charset = get_translation().charset() or 'us-ascii'
 | 
			
		||||
        output = '<TEXTAREA NAME=%s' % self.name
 | 
			
		||||
        if self.rows:
 | 
			
		||||
            output += ' ROWS=%s' % self.rows
 | 
			
		||||
        if self.cols:
 | 
			
		||||
            output += ' COLS=%s' % self.cols
 | 
			
		||||
        if self.wrap:
 | 
			
		||||
            output += ' WRAP=%s' % self.wrap
 | 
			
		||||
        if self.readonly:
 | 
			
		||||
            output += ' READONLY'
 | 
			
		||||
        output += '>%s</TEXTAREA>' % self.text
 | 
			
		||||
        if isinstance(output, unicode):
 | 
			
		||||
            output = output.encode(charset, 'xmlcharrefreplace')
 | 
			
		||||
        return output
 | 
			
		||||
 | 
			
		||||
class FileUpload(InputObj):
 | 
			
		||||
    def __init__(self, name, rows=None, cols=None, **kws):
 | 
			
		||||
        apply(InputObj.__init__, (self, name, 'FILE', '', 0), kws)
 | 
			
		||||
 | 
			
		||||
class RadioButton(InputObj):
 | 
			
		||||
    def __init__(self, name, value, checked=0, **kws):
 | 
			
		||||
        apply(InputObj.__init__, (self, name, 'RADIO', value, checked), kws)
 | 
			
		||||
 | 
			
		||||
class CheckBox(InputObj):
 | 
			
		||||
    def __init__(self, name, value, checked=0, **kws):
 | 
			
		||||
        apply(InputObj.__init__, (self, name, "CHECKBOX", value, checked), kws)
 | 
			
		||||
 | 
			
		||||
class VerticalSpacer:
 | 
			
		||||
    def __init__(self, size=10):
 | 
			
		||||
        self.size = size
 | 
			
		||||
    def Format(self, indent=0):
 | 
			
		||||
        output = '<spacer type="vertical" height="%d">' % self.size
 | 
			
		||||
        return output
 | 
			
		||||
 | 
			
		||||
class WidgetArray:
 | 
			
		||||
    Widget = None
 | 
			
		||||
 | 
			
		||||
    def __init__(self, name, button_names, checked, horizontal, values):
 | 
			
		||||
        self.name = name
 | 
			
		||||
        self.button_names = button_names
 | 
			
		||||
        self.checked = checked
 | 
			
		||||
        self.horizontal = horizontal
 | 
			
		||||
        self.values = values
 | 
			
		||||
        assert len(values) == len(button_names)
 | 
			
		||||
        # Don't assert `checked' because for RadioButtons it is a scalar while
 | 
			
		||||
        # for CheckedBoxes it is a vector.  Subclasses will assert length.
 | 
			
		||||
 | 
			
		||||
    def ischecked(self, i):
 | 
			
		||||
        raise NotImplemented
 | 
			
		||||
 | 
			
		||||
    def Format(self, indent=0):
 | 
			
		||||
        t = Table(cellspacing=5)
 | 
			
		||||
        items = []
 | 
			
		||||
        for i, name, value in zip(range(len(self.button_names)),
 | 
			
		||||
                                  self.button_names,
 | 
			
		||||
                                  self.values):
 | 
			
		||||
            ischecked = (self.ischecked(i))
 | 
			
		||||
            item = ('<label>' +
 | 
			
		||||
                    self.Widget(self.name, value, ischecked).Format() +
 | 
			
		||||
                    name + '</label>')
 | 
			
		||||
            items.append(item)
 | 
			
		||||
            if not self.horizontal:
 | 
			
		||||
                t.AddRow(items)
 | 
			
		||||
                items = []
 | 
			
		||||
        if self.horizontal:
 | 
			
		||||
            t.AddRow(items)
 | 
			
		||||
        return t.Format(indent)
 | 
			
		||||
 | 
			
		||||
class RadioButtonArray(WidgetArray):
 | 
			
		||||
    Widget = RadioButton
 | 
			
		||||
 | 
			
		||||
    def __init__(self, name, button_names, checked=None, horizontal=1,
 | 
			
		||||
                 values=None):
 | 
			
		||||
        if values is None:
 | 
			
		||||
            values = range(len(button_names))
 | 
			
		||||
        # BAW: assert checked is a scalar...
 | 
			
		||||
        WidgetArray.__init__(self, name, button_names, checked, horizontal,
 | 
			
		||||
                             values)
 | 
			
		||||
 | 
			
		||||
    def ischecked(self, i):
 | 
			
		||||
        return self.checked == i
 | 
			
		||||
 | 
			
		||||
class CheckBoxArray(WidgetArray):
 | 
			
		||||
    Widget = CheckBox
 | 
			
		||||
 | 
			
		||||
    def __init__(self, name, button_names, checked=None, horizontal=0,
 | 
			
		||||
                 values=None):
 | 
			
		||||
        if checked is None:
 | 
			
		||||
            checked = [0] * len(button_names)
 | 
			
		||||
        else:
 | 
			
		||||
            assert len(checked) == len(button_names)
 | 
			
		||||
        if values is None:
 | 
			
		||||
            values = range(len(button_names))
 | 
			
		||||
        WidgetArray.__init__(self, name, button_names, checked, horizontal,
 | 
			
		||||
                             values)
 | 
			
		||||
 | 
			
		||||
    def ischecked(self, i):
 | 
			
		||||
        return self.checked[i]
 | 
			
		||||
 | 
			
		||||
class UnorderedList(Container):
 | 
			
		||||
    def Format(self, indent=0):
 | 
			
		||||
        spaces = ' ' * indent
 | 
			
		||||
        output = '\n%s<ul>\n' % spaces
 | 
			
		||||
        for item in self.items:
 | 
			
		||||
            output = output + '%s<li>%s\n' % \
 | 
			
		||||
                     (spaces, HTMLFormatObject(item, indent + 2))
 | 
			
		||||
        output = output + '%s</ul>\n' % spaces
 | 
			
		||||
        return output
 | 
			
		||||
 | 
			
		||||
class OrderedList(Container):
 | 
			
		||||
    def Format(self, indent=0):
 | 
			
		||||
        spaces = ' ' * indent
 | 
			
		||||
        output = '\n%s<ol>\n' % spaces
 | 
			
		||||
        for item in self.items:
 | 
			
		||||
            output = output + '%s<li>%s\n' % \
 | 
			
		||||
                     (spaces, HTMLFormatObject(item, indent + 2))
 | 
			
		||||
        output = output + '%s</ol>\n' % spaces
 | 
			
		||||
        return output
 | 
			
		||||
 | 
			
		||||
class DefinitionList(Container):
 | 
			
		||||
    def Format(self, indent=0):
 | 
			
		||||
        spaces = ' ' * indent
 | 
			
		||||
        output = '\n%s<dl>\n' % spaces
 | 
			
		||||
        for dt, dd in self.items:
 | 
			
		||||
            output = output + '%s<dt>%s\n<dd>%s\n' % \
 | 
			
		||||
                     (spaces, HTMLFormatObject(dt, indent+2),
 | 
			
		||||
                      HTMLFormatObject(dd, indent+2))
 | 
			
		||||
        output = output + '%s</dl>\n' % spaces
 | 
			
		||||
        return output
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Logo constants
 | 
			
		||||
#
 | 
			
		||||
# These are the URLs which the image logos link to.  The Mailman home page now
 | 
			
		||||
# points at the gnu.org site instead of the www.list.org mirror.
 | 
			
		||||
#
 | 
			
		||||
from mm_cfg import MAILMAN_URL
 | 
			
		||||
PYTHON_URL  = 'http://www.python.org/'
 | 
			
		||||
GNU_URL     = 'http://www.gnu.org/'
 | 
			
		||||
CUSTOM_URL  = '{{ mailman.custom_logo_url }}'
 | 
			
		||||
 | 
			
		||||
# The names of the image logo files.  These are concatentated onto
 | 
			
		||||
# mm_cfg.IMAGE_LOGOS (not urljoined).
 | 
			
		||||
DELIVERED_BY = 'mailman.jpg'
 | 
			
		||||
PYTHON_POWERED = 'PythonPowered.png'
 | 
			
		||||
GNU_HEAD = 'gnu-head-tiny.jpg'
 | 
			
		||||
CUSTOM_LOGO = '{{ mailman.custom_logo_name }}'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def MailmanLogo():
 | 
			
		||||
    t = Table(border=0, width='100%')
 | 
			
		||||
 | 
			
		||||
    version = mm_cfg.VERSION
 | 
			
		||||
    mmlink = _("Delivered by Mailman")
 | 
			
		||||
    pylink = _("Python Powered")
 | 
			
		||||
    gnulink = _("GNU's Not Unix")
 | 
			
		||||
    customlink = _("{{ mailman.custom_logo_alt }}")
 | 
			
		||||
    if mm_cfg.SITE_LINK:
 | 
			
		||||
        sitelink = mm_cfg.SITE_TEXT
 | 
			
		||||
 | 
			
		||||
    if mm_cfg.IMAGE_LOGOS:
 | 
			
		||||
        def logo(file, alt, base=mm_cfg.IMAGE_LOGOS):
 | 
			
		||||
            return '<img src="%s" alt="%s" border="0" />' % \
 | 
			
		||||
              (base + file, alt)
 | 
			
		||||
        mmlink = logo(DELIVERED_BY, mmlink)
 | 
			
		||||
        pylink = logo(PYTHON_POWERED, pylink)
 | 
			
		||||
        gnulink = logo(GNU_HEAD, gnulink)
 | 
			
		||||
        customlink = logo(CUSTOM_LOGO, customlink)
 | 
			
		||||
        if mm_cfg.SITE_LINK:
 | 
			
		||||
            sitelink = logo(mm_cfg.SITE_LOGO, sitelink, "")
 | 
			
		||||
 | 
			
		||||
    mmlink = Link(MAILMAN_URL, mmlink + _('<br>version %(version)s'))
 | 
			
		||||
    pylink = Link(PYTHON_URL, pylink)
 | 
			
		||||
    gnulink = Link(GNU_URL, gnulink)
 | 
			
		||||
    customlink = Link(CUSTOM_URL, customlink)
 | 
			
		||||
    links = [mmlink, pylink, gnulink, customlink]
 | 
			
		||||
    if mm_cfg.SITE_LINK:
 | 
			
		||||
        if mm_cfg.SITE_URL:
 | 
			
		||||
            sitelink = Link(mm_cfg.SITE_URL, sitelink)
 | 
			
		||||
        links.append(sitelink)
 | 
			
		||||
    t.AddRow(links)
 | 
			
		||||
    return t
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SelectOptions:
 | 
			
		||||
   def __init__(self, varname, values, legend,
 | 
			
		||||
                selected=0, size=1, multiple=None):
 | 
			
		||||
      self.varname  = varname
 | 
			
		||||
      self.values   = values
 | 
			
		||||
      self.legend   = legend
 | 
			
		||||
      self.size     = size
 | 
			
		||||
      self.multiple = multiple
 | 
			
		||||
      # we convert any type to tuple, commas are needed
 | 
			
		||||
      if not multiple:
 | 
			
		||||
         if type(selected) == types.IntType:
 | 
			
		||||
             self.selected = (selected,)
 | 
			
		||||
         elif type(selected) == types.TupleType:
 | 
			
		||||
             self.selected = (selected[0],)
 | 
			
		||||
         elif type(selected) == types.ListType:
 | 
			
		||||
             self.selected = (selected[0],)
 | 
			
		||||
         else:
 | 
			
		||||
             self.selected = (0,)
 | 
			
		||||
 | 
			
		||||
   def Format(self, indent=0):
 | 
			
		||||
      spaces = " " * indent
 | 
			
		||||
      items  = min( len(self.values), len(self.legend) )
 | 
			
		||||
 | 
			
		||||
      # jcrey: If there is no argument, we return nothing to avoid errors
 | 
			
		||||
      if items == 0:
 | 
			
		||||
          return ""
 | 
			
		||||
 | 
			
		||||
      text = "\n" + spaces + "<Select name=\"%s\"" % self.varname
 | 
			
		||||
      if self.size > 1:
 | 
			
		||||
          text = text + " size=%d" % self.size
 | 
			
		||||
      if self.multiple:
 | 
			
		||||
          text = text + " multiple"
 | 
			
		||||
      text = text + ">\n"
 | 
			
		||||
 | 
			
		||||
      for i in range(items):
 | 
			
		||||
          if i in self.selected:
 | 
			
		||||
              checked = " Selected"
 | 
			
		||||
          else:
 | 
			
		||||
              checked = ""
 | 
			
		||||
 | 
			
		||||
          opt = " <option value=\"%s\"%s> %s </option>" % (
 | 
			
		||||
              self.values[i], checked, self.legend[i])
 | 
			
		||||
          text = text + spaces + opt + "\n"
 | 
			
		||||
 | 
			
		||||
      return text + spaces + '</Select>'
 | 
			
		||||
		Loading…
	
		Reference in New Issue