Edgewall Software

Ticket #222: patch-emailnotify-r1120.diff

File patch-emailnotify-r1120.diff, 13.9 KB (added by cmlenz, 7 years ago)

Slightly reworked pkou's patch from ticket #1028

  • wiki-default/TracIni

     
    3232|| default_priority  || Default priority for newly created tickets || 
    3333|| default_milestone || Default milestone for newly created tickets || 
    3434|| default_component || Default component for newly created tickets || 
     35|| restrict_owner    || Select ticket owner from combo box that lists all logged users || 
    3536 
    3637See also: TracTicketsCustomFields 
    3738 
     
    3940|| max_size || Maximum allowed file size for ticket and wiki attachments || 
    4041 
    4142== [notification] == 
    42 || smtp_enabled   || Enable SMTP (email) notification (true, false) || 
    43 || smtp_server    || SMTP server to use for email notifications || 
    44 || smtp_from      || Sender address to use in notification emails || 
    45 || smtp_replyto   || Reply-To address to use in notification emails || 
    46 || smtp_always_cc || Email address(es) to always send notifications to || 
     43|| smtp_enabled           || Enable SMTP (email) notification (true, false) || 
     44|| smtp_server            || SMTP server to use for email notifications || 
     45|| smtp_from              || Sender address to use in notification emails || 
     46|| smtp_replyto           || Reply-To address to use in notification emails || 
     47|| smtp_always_cc         || Email address(es) to always send notifications to || 
     48|| always_notify_owner    || Always send notifications to ticket owners || 
    4749|| always_notify_reporter || Always send notifications to any address in the ''reporter'' field || 
    4850 
    4951See also: TracNotification 
  • wiki-default/TracNotification

     
    2121 * '''smtp_from''': Email address to use for ''Sender''-headers in notification emails. 
    2222 * '''smtp_replyto''': Email address to use for ''Reply-To''-headers in notification emails. 
    2323 * '''smtp_always_cc''': List of email addresses to always send notifications to. ''Typically used to post ticket changes to a dedicated mailing list.'' 
     24 * '''always_notify_owner''': Always send notifications to ticket owners. 
    2425 * '''always_notify_reporter''':  Always send notifications to any address in the reporter field. 
    2526 
    2627Either '''smtp_from''' or '''smtp_replyto''' (or both) ''must'' be set, otherwise Trac refuses to send notification mails. 
  • trac/db_default.py

     
    431431  ('ticket', 'default_priority', 'normal'), 
    432432  ('ticket', 'default_milestone', ''), 
    433433  ('ticket', 'default_component', 'component1'), 
     434  ('ticket', 'restrict_owner', 'false'), 
    434435  ('header_logo', 'link', 'http://trac.edgewall.com/'), 
    435436  ('header_logo', 'src', 'trac_banner.png'), 
    436437  ('header_logo', 'alt', 'Trac'), 
     
    442443  ('notification', 'smtp_enabled', 'false'), 
    443444  ('notification', 'smtp_server', 'localhost'), 
    444445  ('notification', 'smtp_always_cc', ''), 
     446  ('notification', 'always_notify_owner', 'false'), 
    445447  ('notification', 'always_notify_reporter', 'false'), 
    446448  ('notification', 'smtp_from', 'trac@localhost'), 
    447449  ('notification', 'smtp_replyto', 'trac@localhost'), 
  • trac/Session.py

     
    104104        self.sid = sid 
    105105        curs = self.db.cursor() 
    106106        curs.execute("SELECT username,var_name,var_value FROM session" 
    107                     " WHERE sid=%s", self.sid) 
     107                     " WHERE sid=%s", self.sid) 
    108108        rows = curs.fetchall() 
    109109        if (not rows                              # No session data yet 
    110110            or rows[0][0] == 'anonymous'          # Anon session 
    111111            or rows[0][0] == self.req.authname):  # Session is mine 
    112112            for u,k,v in rows: 
    113113                self.vars[k] = v 
     114            # For authenticated users: Load global settings for new session 
     115            if self.req.authname != 'anonymous': 
     116                curs.execute("SELECT var_name,var_value FROM session" 
     117                             " WHERE sid ISNULL AND username = %s", 
     118                             self.req.authname) 
     119                rows = curs.fetchall() 
     120                for k,v in rows: 
     121                    if not self.vars.has_key(k): self.vars[k] = v 
    114122            self.update_sess_time() 
    115123            self.bake_cookie() 
    116124            self.populate_hdf() 
     
    143151            curs.execute('UPDATE session SET username=%s,var_value=%s' 
    144152                         ' WHERE sid=%s AND var_name=%s', 
    145153                         self.req.authname, val, self.sid, key) 
     154        # For authenticated users: Set global settings (ignore session-specific 
     155        # attributes) 
     156        if self.req.authname != 'anonymous' and \ 
     157                not key in ['mod_time', 'last_visit']: 
     158            curs.execute("DELETE FROM session WHERE sid ISNULL AND" 
     159                         " username = %s AND var_name = %s", 
     160                         self.req.authname, key) 
     161            curs.execute("INSERT INTO session(sid,username,var_name,var_value)" 
     162                         " VALUES(NULL,%s,%s,%s)", self.req.authname, key, val) 
    146163        self.db.commit() 
    147164        self.vars[key] = val 
    148165 
    149166    def create_new_sid(self): 
    150167        self.sid = hex_entropy(24) 
     168        # For authenticated users: Load (existing) global settings for new session 
     169        if self.req.authname != 'anonymous': 
     170            curs = self.db.cursor() 
     171            curs.execute("SELECT var_name,var_value FROM session" 
     172                         " WHERE sid ISNULL AND username = %s", self.req.authname) 
     173            rows = curs.fetchall() 
     174            for k,v in rows: 
     175                if not self.vars.has_key(k): self.vars[k] = v 
    151176        self.bake_cookie() 
    152177        self.populate_hdf() 
    153178 
     
    173198        curs.execute("DELETE FROM session WHERE sid IN" 
    174199                     " (SELECT sid FROM session WHERE var_name='mod_time'" 
    175200                     "  AND var_value  < %i)", mintime) 
     201        # For authenticated users: Delete global settings when all sessions are removed 
     202        curs.execute("DELETE FROM session WHERE sid ISNULL AND username NOT IN" 
     203                     " (SELECT username FROM session WHERE NOT sid ISNULL)") 
    176204        self.db.commit() 
    177  
  • trac/Notify.py

     
    2424import time 
    2525import smtplib 
    2626import os.path 
     27import email.Utils 
    2728 
    2829import neo_cgi 
    2930import neo_cs 
     
    106107    from_email = 'trac+tickets@localhost' 
    107108    subject = '' 
    108109    server = None 
     110    useremails = {} 
    109111 
     112    def __init__(self, env, msg_template): 
     113        Notify.__init__(self, env, msg_template) 
     114        cursor = self.db.cursor() 
     115        cursor.execute("SELECT username,var_value FROM session WHERE sid ISNULL AND var_name = 'email'") 
     116        rows = cursor.fetchall() 
     117        for k,v in rows: self.useremails[k] = v 
     118 
    110119    def notify(self, resid, subject): 
    111120        self.subject = subject 
    112121 
     
    131140        Notify.notify(self, resid) 
    132141 
    133142    def get_email_addresses(self, txt): 
    134         import email.Utils 
    135         emails = [x[1] for x in  email.Utils.getaddresses([str(txt)])] 
     143        emails = [x[1] for x in email.Utils.getaddresses([str(txt), \ 
     144            self.useremails.has_key(txt) and self.useremails[txt] or ''])] 
    136145        return filter(lambda x: x.find('@') > -1, emails) 
    137146 
    138147    def begin_send(self): 
     
    279288        return txt 
    280289 
    281290    def parse_cc(self, txt): 
    282         return filter(lambda x: '@' in x, txt.replace(',', ' ').split()) 
     291        recipients = txt.replace(',', ' ').split() 
     292        additional = [] 
     293        for r in recipients: 
     294            if self.useremails.has_key(r): 
     295                additional.append(self.useremails[r]) 
     296        emails = [x[1] for x in email.Utils.getaddresses(recipients + additional)] 
     297        return filter(lambda x: '@' in x, emails) 
    283298 
    284299    def format_hdr(self): 
    285300        return '#%s: %s' % (self.ticket['id'], 
     
    295310        # is set to true 
    296311        val = self.env.get_config('notification', 'always_notify_reporter', 'false') 
    297312        notify_reporter = val.lower() in TRUE 
     313        val = self.env.get_config('notification', 'always_notify_owner', 'false') 
     314        notify_owner = val.lower() in TRUE 
    298315         
    299316        emails = self.prev_cc 
    300317        cursor = self.db.cursor() 
    301318        # Harvest email addresses from the cc field 
    302         cursor.execute('SELECT cc,reporter FROM ticket WHERE id=%s', tktid) 
     319        cursor.execute('SELECT cc,reporter,owner FROM ticket WHERE id=%s', tktid) 
    303320        row = cursor.fetchone() 
    304321        if row: 
    305322            emails += row[0] and self.parse_cc(row[0]) or [] 
    306323            if notify_reporter: 
    307324                emails += row[1] and self.get_email_addresses(row[1]) or [] 
     325            if notify_owner: 
     326                emails += row[2] and self.get_email_addresses(row[2]) or [] 
    308327 
    309328        if notify_reporter: 
    310329            cursor.execute('SELECT DISTINCT author,ticket FROM ticket_change ' 
  • trac/Ticket.py

     
    196196        return log 
    197197 
    198198 
     199def get_users(env, db): 
     200    users = [] 
     201    option = env.get_config('ticket', 'restrict_owner') 
     202    if option.lower() in util.TRUE: 
     203        cursor = db.cursor() 
     204        cursor.execute("SELECT DISTINCT s1.username, s2.var_value " 
     205                       "FROM session s1 " 
     206                       "LEFT OUTER JOIN session s2 " 
     207                       "  ON (s2.sid ISNULL AND s1.username = s2.username " 
     208                       "      AND s2.var_name = 'name') " 
     209                       "WHERE s1.sid ISNULL " 
     210                       "ORDER BY s1.username") 
     211        while 1: 
     212            row = cursor.fetchone() 
     213            if not row: 
     214                break 
     215            user = {'name': row[0]} 
     216            if row[1]: 
     217                user['label'] = '%s (%s)' % (row[0], row[1]) 
     218            users.append(user) 
     219    return users 
     220 
     221 
    199222def cmp_by_order(a, b): 
    200223    try: 
    201224        return int(a['order']) - int(b['order']) 
     
    334357                                 " ORDER BY value", 
    335358                        self.req.hdf, 'enums.severity') 
    336359 
     360        util.add_to_hdf(get_users(self.env, self.db), self.req.hdf, 
     361                        'newticket.users') 
     362 
    337363        insert_custom_fields(self.env, self.req.hdf, ticket) 
    338364 
    339365 
     
    402428        util.sql_to_hdf(self.db, "SELECT name FROM enum WHERE type='resolution'" 
    403429                                 " ORDER BY value", 
    404430                        self.req.hdf, 'enums.resolution') 
     431 
     432        util.add_to_hdf(get_users(self.env, self.db), self.req.hdf, 
     433                        'ticket.users') 
     434 
     435 
    405436        util.hdf_add_if_missing(self.req.hdf, 'ticket.components', ticket['component']) 
    406437        util.hdf_add_if_missing(self.req.hdf, 'ticket.milestones', ticket['milestone']) 
    407438        util.hdf_add_if_missing(self.req.hdf, 'ticket.versions', ticket['version']) 
  • templates/ticket.cs

     
    229229   <?cs call:hdf_select(enums.resolution, "resolve_resolution", args.resolve_resolution) ?><br /> 
    230230   <?cs call:action_radio('reassign') ?> 
    231231   <label for="reassign">reassign</label> 
    232    <label for="reassign_owner">to:</label> 
    233    <input type="text" id="reassign_owner" name="reassign_owner" size="40" value="<?cs 
    234      if:args.reassign_to ?><?cs var:args.reassign_to ?><?cs 
    235      else ?><?cs var:trac.authname ?><?cs /if ?>" /><?cs 
     232   <label for="reassign_owner">to:</label><?cs 
     233   if:args.reassign_to ?><?cs 
     234     set default_owner=args.reassign_to ?><?cs 
     235   else ?><?cs 
     236     set default_owner=trac.authname ?><?cs 
     237   /if ?><?cs 
     238   if:len(ticket.users) ?><?cs 
     239     call:hdf_select(ticket.users, "reassign_owner", default_owner) ?><?cs 
     240   else ?> 
     241     <input type="text" id="reassign_owner" name="reassign_owner" size="40" 
     242            value="<?cs var:default_owner ?>" /><?cs 
     243   /if ?><?cs 
    236244  /if ?><?cs 
    237245  if $ticket.status == "new" || $ticket.status == "assigned" || $ticket.status == "reopened" ?> 
    238246   <script type="text/javascript"> 
  • templates/macros.cs

     
    11<?cs def:hdf_select(options, name, selected) ?> 
    22 <select size="1" id="<?cs var:name ?>" name="<?cs var:name ?>"><?cs 
    3   each:option = options ?><?cs 
    4    if option.name == $selected ?> 
    5     <option selected="selected"><?cs var:option.name ?></option><?cs 
    6    else ?> 
    7     <option><?cs var:option.name ?></option><?cs 
    8    /if ?><?cs 
     3  each:option = options ?> 
     4   <option<?cs 
     5    if:option.name == selected ?> selected="selected"<?cs /if ?><?cs 
     6    if:option.label ?> value="<?cs var:option.name ?>"<?cs /if ?>><?cs 
     7    alt:option.label ?><?cs var:option.name ?><?cs /alt ?></option><?cs 
    98  /each ?> 
    109 </select><?cs 
    1110/def?> 
  • templates/newticket.cs

     
    5959   <label for="milestone">Milestone:</label><?cs 
    6060   call:hdf_select(newticket.milestones, "milestone", newticket.milestone) ?><br /> 
    6161   <label for="owner">Assign to:</label> 
     62   <?cs if:len(newticket.users) ?><?cs 
     63     call:hdf_select(newticket.users, "owner", newticket.owner) ?><br /><?cs 
     64   else ?> 
    6265   <input type="text" id="owner" name="owner" size="20" value="<?cs 
    6366     var:newticket.owner ?>" /><br /> 
     67   <?cs /if ?> 
    6468   <label for="cc">Cc:</label> 
    6569   <input type="text" id="cc" name="cc" size="30" value="<?cs var:newticket.cc ?>" /> 
    6670  </div>