1# Copyright (C) 2001-2018 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
18import re
19
20from Mailman import mm_cfg
21from Mailman import Utils
22from Mailman.i18n import _
23from Mailman.Logging.Syslog import syslog
24from Mailman.Gui.GUIBase import GUIBase
25
26try:
27    True, False
28except NameError:
29    True = 1
30    False = 0
31
32OR = '|'
33
34
35
36class Topics(GUIBase):
37    def GetConfigCategory(self):
38        return 'topics', _('Topics')
39
40    def GetConfigInfo(self, mlist, category, subcat=None):
41        if category <> 'topics':
42            return None
43        WIDTH = mm_cfg.TEXTFIELDWIDTH
44
45        return [
46            _('List topic keywords'),
47
48            ('topics_enabled', mm_cfg.Radio, (_('Disabled'), _('Enabled')), 0,
49             _('''Should the topic filter be enabled or disabled?'''),
50
51             _("""The topic filter categorizes each incoming email message
52             according to <a
53            href="https://docs.python.org/2/library/re.html">regular
54             expression filters</a> you specify below.  If the message's
55             <code>Subject:</code> or <code>Keywords:</code> header contains a
56             match against a topic filter, the message is logically placed
57             into a topic <em>bucket</em>.  Each user can then choose to only
58             receive messages from the mailing list for a particular topic
59             bucket (or buckets).  Any message not categorized in a topic
60             bucket registered with the user is not delivered to the list.
61
62             <p>Note that this feature only works with regular delivery, not
63             digest delivery.
64
65             <p>The body of the message can also be optionally scanned for
66             <code>Subject:</code> and <code>Keywords:</code> headers, as
67             specified by the <a
68       href="?VARHELP=topics/topics_bodylines_limit">topics_bodylines_limit</a>
69             configuration variable.""")),
70
71            ('topics_bodylines_limit', mm_cfg.Number, 5, 0,
72             _('How many body lines should the topic matcher scan?'),
73
74             _("""The topic matcher will scan this many lines of the message
75             body looking for topic keyword matches.  Body scanning stops when
76             either this many lines have been looked at, or a non-header-like
77             body line is encountered.  By setting this value to zero, no body
78             lines will be scanned (i.e. only the <code>Keywords:</code> and
79             <code>Subject:</code> headers will be scanned).  By setting this
80             value to a negative number, then all body lines will be scanned
81             until a non-header-like line is encountered.
82             """)),
83
84            ('topics', mm_cfg.Topics, 0, 0,
85             _('Topic keywords, one per line, to match against each message.'),
86
87             _("""Each topic keyword is actually a regular expression, which is
88             matched against certain parts of a mail message, specifically the
89             <code>Keywords:</code> and <code>Subject:</code> message headers.
90             Note that the first few lines of the body of the message can also
91             contain a <code>Keywords:</code> and <code>Subject:</code>
92             "header" on which matching is also performed.""")),
93
94            ]
95
96    def handleForm(self, mlist, category, subcat, cgidata, doc):
97        # MAS: Did we come from the authentication page?
98        if not cgidata.has_key('topic_box_01'):
99            return
100        topics = []
101        # We start i at 1 and keep going until we no longer find items keyed
102        # with the marked tags.
103        i = 1
104        while True:
105            deltag   = 'topic_delete_%02d' % i
106            boxtag   = 'topic_box_%02d' % i
107            reboxtag = 'topic_rebox_%02d' % i
108            desctag  = 'topic_desc_%02d' % i
109            wheretag = 'topic_where_%02d' % i
110            addtag   = 'topic_add_%02d' % i
111            newtag   = 'topic_new_%02d' % i
112            i += 1
113            # Was this a delete?  If so, we can just ignore this entry
114            if cgidata.has_key(deltag):
115                continue
116            # Get the data for the current box
117            name  = cgidata.getfirst(boxtag)
118            pattern = cgidata.getfirst(reboxtag)
119            desc  = cgidata.getfirst(desctag)
120            if name is None:
121                # We came to the end of the boxes
122                break
123            if cgidata.has_key(newtag) and (not name or not pattern):
124                # This new entry is incomplete.
125                doc.addError(_("""Topic specifications require both a name and
126                a pattern.  Incomplete topics will be ignored."""))
127                continue
128            # Make sure the pattern was a legal regular expression
129            name = Utils.websafe(name)
130            try:
131                orpattern = OR.join(pattern.splitlines())
132                re.compile(orpattern)
133            except (re.error, TypeError):
134                safepattern = Utils.websafe(orpattern)
135                doc.addError(_("""The topic pattern '%(safepattern)s' is not a
136                legal regular expression.  It will be discarded."""))
137                continue
138            # Was this an add item?
139            if cgidata.has_key(addtag):
140                # Where should the new one be added?
141                where = cgidata.getfirst(wheretag)
142                if where == 'before':
143                    # Add a new empty topics box before the current one
144                    topics.append(('', '', '', True))
145                    topics.append((name, pattern, desc, False))
146                    # Default is to add it after...
147                else:
148                    topics.append((name, pattern, desc, False))
149                    topics.append(('', '', '', True))
150            # Otherwise, just retain this one in the list
151            else:
152                topics.append((name, pattern, desc, False))
153        # Add these topics to the mailing list object, and deal with other
154        # options.
155        mlist.topics = topics
156        try:
157            mlist.topics_enabled = int(cgidata.getfirst(
158                'topics_enabled',
159                mlist.topics_enabled))
160        except ValueError:
161            # BAW: should really print a warning
162            pass
163        try:
164            mlist.topics_bodylines_limit = int(cgidata.getfirst(
165                'topics_bodylines_limit',
166                mlist.topics_bodylines_limit))
167        except ValueError:
168            # BAW: should really print a warning
169            pass
170