예제 #1
0
    def send(self, torcpts, ccrcpts, mime_headers={}):
        body = self._format_body()
        public_cc = self.config.getbool('notification', 'use_public_cc')
        headers = {
            'X-Mailer': 'Trac %s, by Edgewall Software' % __version__,
            'X-Trac-Version': __version__,
            'X-Trac-Project': self.env.project_name,
            'X-URL': self.env.project_url,
            'Precedence': 'bulk',
            'Auto-Submitted': 'auto-generated',
            'Subject': self.subject,
            'From': (self.from_name,
                     self.from_email) if self.from_name else self.from_email,
            'Reply-To': self.replyto_email
        }

        def build_addresses(rcpts):
            """Format and remove invalid addresses"""
            return filter(lambda x: x,
                          [self.get_smtp_address(addr) for addr in rcpts])

        def remove_dup(rcpts, all):
            """Remove duplicates"""
            tmp = []
            for rcpt in rcpts:
                if not rcpt in all:
                    tmp.append(rcpt)
                    all.append(rcpt)
            return tmp, all

        notify_sys = NotificationSystem(self.env)
        toaddrs = build_addresses(torcpts)
        ccaddrs = build_addresses(ccrcpts)
        accaddrs = notify_sys.smtp_always_cc_list
        bccaddrs = notify_sys.smtp_always_bcc_list

        recipients = []
        toaddrs, recipients = remove_dup(toaddrs, recipients)
        ccaddrs, recipients = remove_dup(ccaddrs, recipients)
        accaddrs, recipients = remove_dup(accaddrs, recipients)
        bccaddrs, recipients = remove_dup(bccaddrs, recipients)

        # if there is not valid recipient, leave immediately
        if len(recipients) < 1:
            self.env.log.info("no recipient for a ticket notification")
            return

        pcc = accaddrs
        if public_cc:
            pcc += ccaddrs
            if toaddrs:
                headers['To'] = ', '.join(toaddrs)
        if pcc:
            headers['Cc'] = ', '.join(pcc)
        headers['Date'] = formatdate()
        msg = create_mime_text(body, 'plain', self._charset)
        self.add_headers(msg, headers)
        self.add_headers(msg, mime_headers)
        NotificationSystem(self.env).send_email(self.from_email, recipients,
                                                msg.as_string())
예제 #2
0
    def _do_delete(self, req, milestone):
        req.perm(milestone.resource).require('MILESTONE_DELETE')

        retarget_to = req.args.get('target') or None
        # Don't translate ticket comment (comment:40:ticket:5658)
        retargeted_tickets = \
            milestone.move_tickets(retarget_to, req.authname,
                "Ticket retargeted after milestone deleted")
        milestone.delete()
        add_notice(req, _('The milestone "%(name)s" has been deleted.',
                          name=milestone.name))
        if retargeted_tickets:
            add_notice(req, _('The tickets associated with milestone '
                              '"%(name)s" have been retargeted to milestone '
                              '"%(retarget)s".', name=milestone.name,
                              retarget=retarget_to))
            new_values = {'milestone': retarget_to}
            comment = _("Tickets retargeted after milestone deleted")
            event = BatchTicketChangeEvent(retargeted_tickets, None,
                                           req.authname, comment, new_values,
                                           None)
            try:
                NotificationSystem(self.env).notify(event)
            except Exception as e:
                self.log.error("Failure sending notification on ticket batch "
                               "change: %s", exception_to_unicode(e))
                add_warning(req, tag_("The changes have been saved, but an "
                                      "error occurred while sending "
                                      "notifications: %(message)s",
                                      message=to_unicode(e)))

        req.redirect(req.href.roadmap())
예제 #3
0
def create_message_id(env, targetid, from_email, time, more=None):
    """Generate a predictable, but sufficiently unique message ID.

    In case you want to set the "Message ID" header, this convenience
    function will generate one by running a hash algorithm over a number
    of properties.

    :param env: the `Environment`
    :param targetid: a string that identifies the target, like
        `NotificationEvent.target`
    :param from_email: the email address that the message is sent from
    :param time: a Python `datetime`
    :param more: a string that contains additional information that
        makes this message unique
    """
    items = [env.project_url, targetid, to_utimestamp(time)]
    if more is not None:
        items.append(more.encode('ascii', 'ignore'))
    source = b'.'.join(
        item if isinstance(item, bytes) else str(item).encode('utf-8')
        for item in items)
    hash_type = NotificationSystem(env).message_id_hash
    try:
        h = hashlib.new(hash_type)
    except:
        raise ConfigurationError(
            _("Unknown hash type '%(type)s'", type=hash_type))
    h.update(source)
    host = from_email[from_email.find('@') + 1:]
    return '<%03d.%s@%s>' % (len(source), h.hexdigest(), host)
    def apply_action_side_effects(self, req, ticket, action):
        """Add a cross-reference comment to the other ticket"""
        # TODO: This needs a lot more error checking.
        id = 'action_%s_xref' % action
        ticketnum = req.args.get(id).strip('#')
        actions = self.get_configurable_workflow().actions
        author = req.authname

        # Add a comment to the "remote" ticket to indicate this ticket is
        # related to it.
        format_string = actions[action].get(
            'xref', "Ticket %s is related "
            "to this ticket")
        comment = format_string % ('#%s' % ticket.id)
        # FIXME: we need a cnum to avoid messing up
        xticket = model.Ticket(self.env, ticketnum)
        # FIXME: We _assume_ we have sufficient permissions to comment on the
        # other ticket.
        now = datetime.now(utc)
        xticket.save_changes(author, comment, now)

        # Send notification on the other ticket
        event = TicketChangeEvent('changed', xticket, now, author)
        try:
            NotificationSystem(self.env).notify(event)
        except Exception, e:
            self.log.exception(
                "Failure sending notification on change to "
                "ticket #%s: %s", ticketnum, e)
예제 #5
0
파일: mail.py 프로젝트: minimalistduck/trac
    def _do_send(self, transport, event, message, cc_addrs, bcc_addrs):
        notify_sys = NotificationSystem(self.env)
        smtp_from = notify_sys.smtp_from
        smtp_from_name = notify_sys.smtp_from_name or self.env.project_name
        smtp_replyto = notify_sys.smtp_replyto
        if not notify_sys.use_short_addr and notify_sys.smtp_default_domain:
            if smtp_from and '@' not in smtp_from:
                smtp_from = '%s@%s' % (smtp_from,
                                       notify_sys.smtp_default_domain)
            if smtp_replyto and '@' not in smtp_replyto:
                smtp_replyto = '%s@%s' % (smtp_replyto,
                                          notify_sys.smtp_default_domain)

        headers = {}
        headers['X-Mailer'] = 'Trac %s, by Edgewall Software'\
                              % self.env.trac_version
        headers['X-Trac-Version'] = self.env.trac_version
        headers['X-Trac-Project'] = self.env.project_name
        headers['X-URL'] = self.env.project_url
        headers['X-Trac-Realm'] = event.realm
        headers['Precedence'] = 'bulk'
        headers['Auto-Submitted'] = 'auto-generated'
        if isinstance(event.target, (list, tuple)):
            targetid = ','.join(map(get_target_id, event.target))
        else:
            targetid = get_target_id(event.target)
        rootid = create_message_id(self.env, targetid, smtp_from, None,
                                   more=event.realm)
        if event.category == 'created':
            headers['Message-ID'] = rootid
        else:
            headers['Message-ID'] = create_message_id(self.env, targetid,
                                                      smtp_from, event.time,
                                                      more=event.realm)
            headers['In-Reply-To'] = rootid
            headers['References'] = rootid
        headers['Date'] = formatdate()
        headers['From'] = (smtp_from_name, smtp_from) \
                          if smtp_from_name else smtp_from
        headers['To'] = 'undisclosed-recipients: ;'
        if cc_addrs:
            headers['Cc'] = ', '.join(cc_addrs)
        if bcc_addrs:
            headers['Bcc'] = ', '.join(bcc_addrs)
        headers['Reply-To'] = smtp_replyto

        for k, v in headers.iteritems():
            set_header(message, k, v, self._charset)
        for decorator in self.decorators:
            decorator.decorate_message(event, message, self._charset)

        from_name, from_addr = parseaddr(str(message['From']))
        to_addrs = set()
        for name in ('To', 'Cc', 'Bcc'):
            values = map(str, message.get_all(name, ()))
            to_addrs.update(addr for name, addr in getaddresses(values)
                                 if addr)
        del message['Bcc']
        notify_sys.send_email(from_addr, list(to_addrs), message.as_string())
예제 #6
0
 def decorate_message(self, event, message, charset):
     if event.realm != 'ticket':
         return
     from_email = self._get_from_email(event)
     if event.category == 'batchmodify':
         tickets = sort_tickets_by_priority(self.env, event.target)
         subject = self._format_subj_batchmodify(tickets)
         targetid = ','.join(map(str, tickets))
         msgid = self._get_message_id(targetid, from_email, event.time)
     else:
         subject = self._format_subj(event)
         ticket = event.target
         targetid = '%08d' % ticket.id
         more = ticket['reporter'] or ''
         msgid = self._get_message_id(targetid, from_email, None, more)
         url = self.env.abs_href.ticket(ticket.id)
         if event.category != 'created':
             set_header(message, 'In-Reply-To', msgid, charset)
             set_header(message, 'References', msgid, charset)
             msgid = self._get_message_id(targetid, from_email, event.time,
                                          more)
             cnum = ticket.get_comment_number(event.time)
             if cnum is not None:
                 url += '#comment:%d' % cnum
         set_header(message, 'X-Trac-Ticket-ID', ticket.id, charset)
         set_header(message, 'X-Trac-Ticket-URL', url, charset)
         # When owner, reporter and updater are listed in the Cc header,
         # move the address to To header.
         if NotificationSystem(self.env).use_public_cc:
             to_addrs = set()
             matcher = RecipientMatcher(self.env)
             for rcpt in ticket['owner'], ticket['reporter'], event.author:
                 rcpt = matcher.match_recipient(rcpt)
                 if not rcpt:
                     continue
                 addr = rcpt[2]
                 if addr:
                     to_addrs.add(addr)
             if to_addrs:
                 cc_addrs = get_message_addresses(message, 'Cc')
                 to_addrs &= set(addr for name, addr in cc_addrs)
             if to_addrs:
                 cc_header = ', '.join(create_header('Cc', (name, addr),
                                                     charset)
                                       for name, addr in cc_addrs
                                       if addr not in to_addrs)
                 if cc_header:
                     set_header(message, 'Cc', cc_header, charset)
                 elif 'Cc' in message:
                     del message['Cc']
                 to_header = ', '.join(sorted(to_addrs))
                 set_header(message, 'To', to_header, charset)
     set_header(message, 'Subject', subject, charset)
     set_header(message, 'Message-ID', msgid, charset)
예제 #7
0
파일: batch.py 프로젝트: jacdevos/trac
    def _save_ticket_changes(self, req, selected_tickets, new_values, comment,
                             action):
        """Save changes to tickets."""
        valid = True
        for manipulator in self.ticket_manipulators:
            if hasattr(manipulator, 'validate_comment'):
                for message in manipulator.validate_comment(req, comment):
                    valid = False
                    add_warning(req, tag_("The ticket %(field)s is invalid: "
                                          "%(message)s",
                                          field=tag.strong(_('comment')),
                                          message=message))

        tickets = []
        for id_ in selected_tickets:
            t = Ticket(self.env, id_)
            values = self._get_updated_ticket_values(req, t, new_values)
            for ctlr in self._get_action_controllers(req, t, action):
                values.update(ctlr.get_ticket_changes(req, t, action))
            t.populate(values)
            for manipulator in self.ticket_manipulators:
                for field, message in manipulator.validate_ticket(req, t):
                    valid = False
                    if field:
                        add_warning(req, tag_("The ticket field %(field)s is "
                                              "invalid: %(message)s",
                                              field=tag.strong(field),
                                              message=message))
                    else:
                        add_warning(req, message)
            tickets.append(t)

        if not valid:
            return

        when = datetime_now(utc)
        with self.env.db_transaction:
            for t in tickets:
                t.save_changes(req.authname, comment, when=when)
                for ctlr in self._get_action_controllers(req, t, action):
                    ctlr.apply_action_side_effects(req, t, action)

        event = BatchTicketChangeEvent(selected_tickets, when,
                                       req.authname, comment, new_values,
                                       action)
        try:
            NotificationSystem(self.env).notify(event)
        except Exception as e:
            self.log.error("Failure sending notification on ticket batch"
                           "change: %s", exception_to_unicode(e))
            add_warning(req,
                        tag_("The changes have been saved, but an error "
                             "occurred while sending notifications: "
                             "%(message)s", message=to_unicode(e)))
예제 #8
0
 def _notify(self, ticket, date, author, comment):
     """Send a ticket update notification."""
     if not self.notify:
         return
     event = TicketChangeEvent('changed', ticket, date, author, comment)
     try:
         NotificationSystem(self.env).notify(event)
     except Exception as e:
         self.log.error(
             "Failure sending notification on change to "
             "ticket #%s: %s", ticket.id, exception_to_unicode(e))
예제 #9
0
    def _do_send(self, transport, event, message, cc_addrs, bcc_addrs):
        config = self.config['notification']
        smtp_from = config.get('smtp_from')
        smtp_from_name = config.get('smtp_from_name') or self.env.project_name
        smtp_reply_to = config.get('smtp_replyto')

        headers = dict()
        headers['X-Mailer'] = 'Trac %s, by Edgewall Software' % __version__
        headers['X-Trac-Version'] = __version__
        headers['X-Trac-Project'] = self.env.project_name
        headers['X-URL'] = self.env.project_url
        headers['X-Trac-Realm'] = event.realm
        headers['Precedence'] = 'bulk'
        headers['Auto-Submitted'] = 'auto-generated'
        targetid = get_target_id(event.target)
        rootid = create_message_id(self.env,
                                   targetid,
                                   smtp_from,
                                   None,
                                   more=event.realm)
        if event.category == 'created':
            headers['Message-ID'] = rootid
        else:
            headers['Message-ID'] = create_message_id(self.env,
                                                      targetid,
                                                      smtp_from,
                                                      event.time,
                                                      more=event.realm)
            headers['In-Reply-To'] = rootid
            headers['References'] = rootid
        headers['Date'] = formatdate()
        headers['From'] = (smtp_from_name, smtp_from) \
                          if smtp_from_name else smtp_from
        headers['To'] = 'undisclosed-recipients: ;'
        if cc_addrs:
            headers['Cc'] = ', '.join(cc_addrs)
        if bcc_addrs:
            headers['Bcc'] = ', '.join(bcc_addrs)
        headers['Reply-To'] = smtp_reply_to

        for k, v in headers.iteritems():
            set_header(message, k, v, self._charset)
        for decorator in self.decorators:
            decorator.decorate_message(event, message, self._charset)

        from_name, from_addr = parseaddr(str(message['From']))
        to_addrs = set()
        for name in ('To', 'Cc', 'Bcc'):
            values = map(str, message.get_all(name, ()))
            to_addrs.update(addr for name, addr in getaddresses(values)
                            if addr)
        del message['Bcc']
        NotificationSystem(self.env).send_email(from_addr, list(to_addrs),
                                                message.as_string())
예제 #10
0
    def __init__(self, env):
        super(NotifyEmail, self).__init__(env)

        self.recipient_matcher = RecipientMatcher(env)
        self.shortaddr_re = self.recipient_matcher.shortaddr_re
        self.longaddr_re = self.recipient_matcher.longaddr_re
        self._ignore_domains = self.recipient_matcher.ignore_domains
        self.name_map = self.recipient_matcher.name_map
        self.email_map = self.recipient_matcher.email_map

        notify_sys = NotificationSystem(self.env)
        self._charset = create_charset(notify_sys.mime_encoding)
예제 #11
0
 def send_notification(self, ticket, author):
     if TicketNotifyEmail:
         tn = TicketNotifyEmail(self.env)
         tn.notify(ticket, newticket=False, modtime=ticket['changetime'])
     else:
         event = TicketChangeEvent('changed', ticket, ticket['changetime'],
                                   author)
         try:
             NotificationSystem(self.env).notify(event)
         except Exception as e:
             self.log.error(
                 "Failure sending notification on change to "
                 "ticket #%s: %s", ticket.id, exception_to_unicode(e))
예제 #12
0
    def _do_send(self, id, ticket, author, origin, description):
        ticket = Ticket(self.env, ticket)
        try:
            # We send reminder only for open tickets
            if ticket['status'] != 'closed':
                reminder = self._format_reminder_text(ticket, id, author, origin, description)

                #tn = TicketReminderNotifyEmail(self.env, reminder)
                #tn.notify(ticket)
                event = TicketReminderEvent(ticket, ticket['time'], author, reminder)
                tn = NotificationSystem(self.env).notify(event)
        except Exception, e:
            self.env.log.error("Failure sending reminder notification for ticket #%s: %s", ticket.id, exception_to_unicode(e))
            print "Failure sending reminder notification for ticket #%s: %s" % (ticket.id, exception_to_unicode(e))
예제 #13
0
 def __init__(self, env):
     self.env = env
     addrfmt = EMAIL_LOOKALIKE_PATTERN
     self.notify_sys = NotificationSystem(env)
     admit_domains = self.notify_sys.admit_domains_list
     if admit_domains:
         localfmt, domainfmt = addrfmt.split('@')
         domains = '|'.join(re.escape(x) for x in admit_domains)
         addrfmt = r'%s@(?:(?:%s)|%s)' % (localfmt, domainfmt, domains)
     self.shortaddr_re = re.compile(r'\s*(%s)\s*$' % addrfmt)
     self.longaddr_re = re.compile(r'^\s*(.*)\s+<\s*(%s)\s*>\s*$' % addrfmt)
     self.ignore_domains = set(x.lower()
                               for x in self.notify_sys.ignore_domains_list)
     self.users = self.env.get_known_users(as_dict=True)
예제 #14
0
 def _notify_attachment(self, attachment, category, time):
     resource = attachment.resource.parent
     if resource.realm != 'ticket':
         return
     ticket = Ticket(self.env, resource.id)
     event = TicketChangeEvent(category, ticket, time, ticket['reporter'],
                               attachment=attachment)
     try:
         NotificationSystem(self.env).notify(event)
     except Exception as e:
         self.log.error("Failure sending notification when adding "
                        "attachment %s to ticket #%s: %s",
                        attachment.filename, ticket.id,
                        exception_to_unicode(e))
예제 #15
0
파일: mail.py 프로젝트: zxfly/trac
def create_message_id(env, targetid, from_email, time, more=None):
    """Generate a predictable, but sufficiently unique message ID."""
    items = [env.project_url.encode('utf-8'), targetid, to_utimestamp(time)]
    if more is not None:
        items.append(more.encode('ascii', 'ignore'))
    source = '.'.join(str(item) for item in items)
    hash_type = NotificationSystem(env).message_id_hash
    try:
        h = hashlib.new(hash_type)
    except:
        raise ConfigurationError(
            _("Unknown hash type '%(type)s'", type=hash_type))
    h.update(source)
    host = from_email[from_email.find('@') + 1:]
    return '<%03d.%s@%s>' % (len(source), h.hexdigest(), host)
예제 #16
0
 def __init__(self, env):
     self.env = env
     addrfmt = EMAIL_LOOKALIKE_PATTERN
     self.notify_sys = NotificationSystem(env)
     admit_domains = self.notify_sys.admit_domains_list
     if admit_domains:
         localfmt, domainfmt = addrfmt.split('@')
         domains = [domainfmt]
         domains.extend(re.escape(x) for x in admit_domains)
         addrfmt = r'%s@(?:%s)' % (localfmt, '|'.join(domains))
     self.shortaddr_re = re.compile(r'<?(%s)>?$' % addrfmt, re.IGNORECASE)
     self.longaddr_re = re.compile(r'(.*)\s+<\s*(%s)\s*>$' % addrfmt,
                                   re.IGNORECASE)
     self.ignore_domains = set(x.lower()
                               for x in self.notify_sys.ignore_domains_list)
예제 #17
0
def get_from_author(env, event):
    """Get the author name and email from a given `event`.

    The `event` parameter should be of the type `NotificationEvent`.
    If you only have the username of a Trac user, you should instead
    use the `RecipientMatcher` to find the user's details.

    The method returns a tuple that contains the name and email address
    of the user. For example: `('developer', '*****@*****.**')`.
    This tuple can be parsed by `set_header()`.
    """
    if event.author and NotificationSystem(env).smtp_from_author:
        matcher = RecipientMatcher(env)
        from_ = matcher.match_from_author(event.author)
        if from_:
            return from_
예제 #18
0
 def setUp(self):
     self.env = EnvironmentStub(
         enable=['trac.*', TestEmailSender, TestFormatter, TestSubscriber])
     config = self.env.config
     config.set('notification', 'smtp_from', '*****@*****.**')
     config.set('notification', 'smtp_enabled', 'enabled')
     config.set('notification', 'smtp_always_cc', '*****@*****.**')
     config.set('notification', 'smtp_always_bcc', '*****@*****.**')
     config.set('notification', 'email_sender', 'TestEmailSender')
     self.sender = TestEmailSender(self.env)
     self.notsys = NotificationSystem(self.env)
     with self.env.db_transaction:
         self._add_session('foo', email='*****@*****.**')
         self._add_session('bar',
                           email='*****@*****.**',
                           name=u"Bäŕ's name")
예제 #19
0
파일: prefs.py 프로젝트: rwbaumg/trac
    def expand_macro(self, formatter, name, content):
        content = content.strip() if content else ''
        name_filter = content.strip('*')
        items = {}
        for subscriber in NotificationSystem(self.env).subscribers:
            name = subscriber.__class__.__name__
            if not name_filter or name.startswith(name_filter):
                items[name] = subscriber.description()

        return tag.div(class_='trac-subscriberlist')(tag.table(class_='wiki')(
            tag.thead(tag.tr(tag.th(_("Subscriber")),
                             tag.th(_("Description")))),
            tag.tbody(
                tag.tr(tag.td(tag.code(name)),
                       tag.td(items[name]),
                       class_='odd' if idx % 2 else 'even')
                for idx, name in enumerate(sorted(items)))))
예제 #20
0
    def __init__(self, env):
        super(NotifyEmail, self).__init__(env)

        self.recipient_matcher = RecipientMatcher(env)
        self.shortaddr_re = self.recipient_matcher.shortaddr_re
        self.longaddr_re = self.recipient_matcher.longaddr_re
        self._ignore_domains = self.recipient_matcher.ignore_domains
        self.name_map = {}
        self.email_map = {}
        for username, name, email in self.env.get_known_users():
            if name:
                self.name_map[username] = name
            if email:
                self.email_map[username] = email

        notify_sys = NotificationSystem(self.env)
        self._charset = create_charset(notify_sys.mime_encoding)
예제 #21
0
 def _save_ticket_changes(self, req, selected_tickets, new_values, comment,
                          action):
     """Save all of the changes to tickets."""
     when = datetime.now(utc)
     list_fields = self._get_list_fields()
     with self.env.db_transaction as db:
         for id in selected_tickets:
             t = Ticket(self.env, int(id))
             _values = new_values.copy()
             for field in list_fields:
                 if field in new_values:
                     old = t[field] if field in t else ''
                     new = new_values[field]
                     mode = req.args.get('batchmod_value_' + field +
                                         '_mode')
                     new2 = req.args.get(
                         'batchmod_value_' + field + '_secondary', '')
                     _values[field] = self._change_list(
                         old, new, new2, mode)
             controllers = list(self._get_action_controllers(
                 req, t, action))
             for controller in controllers:
                 _values.update(
                     controller.get_ticket_changes(req, t, action))
             t.populate(_values)
             t.save_changes(req.authname, comment, when=when)
             for controller in controllers:
                 controller.apply_action_side_effects(req, t, action)
     event = BatchTicketChangeEvent(selected_tickets, when, req.authname,
                                    comment, new_values, action)
     try:
         NotificationSystem(self.env).notify(event)
     except Exception as e:
         self.log.error(
             "Failure sending notification on ticket batch"
             "change: %s", exception_to_unicode(e))
         add_warning(
             req,
             tag_(
                 "The changes have been saved, but an "
                 "error occurred while sending "
                 "notifications: %(message)s",
                 message=to_unicode(e)))
예제 #22
0
 def setUp(self):
     self.env = EnvironmentStub(enable=['trac.*', TestEmailSender,
                                        TestFormatter, TestSubscriber,
                                        TestEmailAddressResolver])
     self.config = config = self.env.config
     config.set('notification', 'smtp_from', '*****@*****.**')
     config.set('notification', 'smtp_enabled', 'enabled')
     config.set('notification', 'smtp_always_cc', '*****@*****.**')
     config.set('notification', 'smtp_always_bcc', '*****@*****.**')
     config.set('notification', 'email_sender', 'TestEmailSender')
     config.set('notification', 'email_address_resolvers',
                'SessionEmailResolver,TestEmailAddressResolver')
     self.sender = TestEmailSender(self.env)
     self.notsys = NotificationSystem(self.env)
     with self.env.db_transaction:
         self._add_session('foo', email='*****@*****.**')
         self._add_session('bar', email='*****@*****.**',
                           name="Bäŕ's name")
         self._add_session('baz', name='Baz')
         self._add_session('qux', tz='UTC')
         self._add_session('corge', email='corge-mail')
예제 #23
0
    def __init__(self, env):
        self.env = env
        addrfmt = EMAIL_LOOKALIKE_PATTERN
        notify_sys = NotificationSystem(env)
        admit_domains = notify_sys.admit_domains_list
        if admit_domains:
            localfmt, domainfmt = addrfmt.split('@')
            domains = '|'.join(re.escape(x) for x in admit_domains)
            addrfmt = r'%s@(?:(?:%s)|%s)' % (localfmt, domainfmt, domains)
        self.shortaddr_re = re.compile(r'\s*(%s)\s*$' % addrfmt)
        self.longaddr_re = re.compile(r'^\s*(.*)\s+<\s*(%s)\s*>\s*$' % addrfmt)
        self.ignore_domains = [x.lower()
                               for x in notify_sys.ignore_domains_list]

        # Get the name and email addresses of all known users
        self.name_map = {}
        self.email_map = {}
        for username, name, email in self.env.get_known_users():
            if name:
                self.name_map[username] = name
            if email:
                self.email_map[username] = email
예제 #24
0
    def save_milestone(self, req, milestone):
        # Instead of raising one single error, check all the constraints
        # and let the user fix them by going back to edit mode and showing
        # the warnings
        warnings = []

        def warn(msg):
            add_warning(req, msg)
            warnings.append(msg)

        milestone.description = req.args.get('description', '')

        if 'due' in req.args:
            duedate = req.args.get('duedate')
            milestone.due = user_time(req, parse_date, duedate,
                                      hint='datetime') \
                            if duedate else None
        else:
            milestone.due = None

        # -- check completed date
        if 'completed' in req.args:
            completed = req.args.get('completeddate', '')
            completed = user_time(req, parse_date, completed,
                                  hint='datetime') if completed else None
            if completed and completed > datetime_now(utc):
                warn(_("Completion date may not be in the future"))
        else:
            completed = None
        milestone.completed = completed

        # -- check the name
        # If the name has changed, check that the milestone doesn't already
        # exist
        # FIXME: the whole .exists business needs to be clarified
        #        (#4130) and should behave like a WikiPage does in
        #        this respect.
        new_name = req.args.get('name')
        try:
            new_milestone = Milestone(self.env, new_name)
        except ResourceNotFound:
            milestone.name = new_name
        else:
            if new_milestone.name != milestone.name:
                if new_milestone.name:
                    warn(
                        _(
                            'Milestone "%(name)s" already exists, please '
                            'choose another name.',
                            name=new_milestone.name))
                else:
                    warn(_("You must provide a name for the milestone."))

        if warnings:
            return False

        # -- actually save changes
        if milestone.exists:
            milestone.update(author=req.authname)
            if completed and 'retarget' in req.args:
                comment = req.args.get('comment', '')
                retarget_to = req.args.get('target') or None
                retargeted_tickets = \
                    milestone.move_tickets(retarget_to, req.authname,
                                           comment, exclude_closed=True)
                add_notice(
                    req,
                    _(
                        'The open tickets associated with '
                        'milestone "%(name)s" have been retargeted '
                        'to milestone "%(retarget)s".',
                        name=milestone.name,
                        retarget=retarget_to))
                new_values = {'milestone': retarget_to}
                comment = comment or \
                          _("Open tickets retargeted after milestone closed")
                event = BatchTicketChangeEvent(retargeted_tickets, None,
                                               req.authname, comment,
                                               new_values, None)
                try:
                    NotificationSystem(self.env).notify(event)
                except Exception as e:
                    self.log.error(
                        "Failure sending notification on ticket "
                        "batch change: %s", exception_to_unicode(e))
                    add_warning(
                        req,
                        tag_(
                            "The changes have been saved, but "
                            "an error occurred while sending "
                            "notifications: %(message)s",
                            message=to_unicode(e)))
            add_notice(req, _("Your changes have been saved."))
        else:
            milestone.insert()
            add_notice(
                req,
                _('The milestone "%(name)s" has been added.',
                  name=milestone.name))

        return True
예제 #25
0
파일: prefs.py 프로젝트: mugglecloud/trac
    def render_preference_panel(self, req, panel, path_info=None):
        if req.method == 'POST':
            action_arg = req.args.getfirst('action', '').split('_', 1)
            if len(action_arg) == 2:
                action, arg = action_arg
                handler = self.post_handlers.get(action)
                if handler:
                    handler(arg, req)
                    add_notice(req, _("Your preferences have been saved."))
            req.redirect(req.href.prefs('notification'))

        rules = {}
        subscribers = []
        formatters = {}
        selected_format = {}
        default_format = {}
        defaults = []

        for i in self.subscribers:
            description = i.description()
            if not description:
                continue
            if not req.session.authenticated and i.requires_authentication():
                continue
            subscribers.append({
                'class': i.__class__.__name__,
                'description': description
            })
            if hasattr(i, 'default_subscriptions'):
                defaults.extend(i.default_subscriptions())
        desc_map = dict((s['class'], s['description']) for s in subscribers)

        ns = NotificationSystem(self.env)
        for t in self._iter_transports():
            rules[t] = []
            formatters[t] = self._get_supported_styles(t)
            selected_format[t] = req.session.get('notification.format.%s' % t)
            default_format[t] = ns.get_default_format(t)
            for r in self._iter_rules(req, t):
                description = desc_map.get(r['class'])
                if description:
                    values = {'description': description}
                    values.update(
                        (key, r[key])
                        for key in ('id', 'adverb', 'class', 'priority'))
                    rules[t].append(values)

        default_rules = {}
        for r in sorted(defaults, key=itemgetter(3)):  # sort by priority
            klass, dist, format, priority, adverb = r
            default_rules.setdefault(dist, [])
            description = desc_map.get(klass)
            if description:
                default_rules[dist].append({
                    'adverb': adverb,
                    'description': description
                })

        data = {
            'rules': rules,
            'subscribers': subscribers,
            'formatters': formatters,
            'selected_format': selected_format,
            'default_format': default_format,
            'default_rules': default_rules,
            'adverbs': ('always', 'never'),
            'adverb_labels': {
                'always': _("Notify"),
                'never': _("Never notify")
            }
        }
        Chrome(self.env).add_jquery_ui(req)
        return 'prefs_notification.html', dict(data=data)
예제 #26
0
 def default_subscriptions(self):
     klass = self.__class__.__name__
     return NotificationSystem(self.env).default_subscriptions(klass)
예제 #27
0
    def distribute(self, transport, recipients, event):
        if transport != 'email':
            return
        if not self.config.getbool('notification', 'smtp_enabled'):
            self.log.debug("EmailDistributor smtp_enabled set to false")
            return

        formats = {}
        for f in self.formatters:
            for style, realm in f.get_supported_styles(transport):
                if realm == event.realm:
                    formats[style] = f
        if not formats:
            self.log.error("EmailDistributor No formats found for %s %s",
                           transport, event.realm)
            return
        self.log.debug("EmailDistributor has found the following formats "
                       "capable of handling '%s' of '%s': %s", transport,
                       event.realm, ', '.join(formats.keys()))

        notify_sys = NotificationSystem(self.env)
        always_cc = set(notify_sys.smtp_always_cc_list)
        use_public_cc = notify_sys.use_public_cc
        addresses = {}
        for sid, authed, addr, fmt in recipients:
            if fmt not in formats:
                self.log.debug("EmailDistributor format %s not available for "
                               "%s %s", fmt, transport, event.realm)
                continue

            if sid and not addr:
                for resolver in self.resolvers:
                    addr = resolver.get_address_for_session(sid, authed)
                    if addr:
                        status = 'authenticated' if authed else \
                                 'not authenticated'
                        self.log.debug("EmailDistributor found the address "
                                       "'%s' for '%s (%s)' via %s", addr, sid,
                                       status, resolver.__class__.__name__)
                        break
            if addr:
                addresses.setdefault(fmt, set()).add(addr)
                if use_public_cc or sid and sid in always_cc:
                    always_cc.add(addr)
            else:
                status = 'authenticated' if authed else 'not authenticated'
                self.log.debug("EmailDistributor was unable to find an "
                               "address for: %s (%s)", sid, status)

        outputs = {}
        failed = []
        for fmt, formatter in formats.iteritems():
            if fmt not in addresses and fmt != 'text/plain':
                continue
            try:
                outputs[fmt] = formatter.format(transport, fmt, event)
            except Exception as e:
                self.log.warn('EmailDistributor caught exception while '
                              'formatting %s to %s for %s: %s%s',
                              event.realm, fmt, transport, formatter.__class__,
                              exception_to_unicode(e, traceback=True))
                failed.append(fmt)

        # Fallback to text/plain when formatter is broken
        if failed and 'text/plain' in outputs:
            for fmt in failed:
                addresses.setdefault('text/plain', set()) \
                         .update(addresses.pop(fmt, ()))

        for fmt, addrs in addresses.iteritems():
            self.log.debug("EmailDistributor is sending event as '%s' to: %s",
                           fmt, ', '.join(addrs))
            message = self._create_message(fmt, outputs)
            if message:
                addrs = set(addrs)
                cc_addrs = sorted(addrs & always_cc)
                bcc_addrs = sorted(addrs - always_cc)
                self._do_send(transport, event, message, cc_addrs, bcc_addrs)
            else:
                self.log.warn("EmailDistributor cannot send event '%s' as "
                              "'%s': %s", event.realm, fmt, ', '.join(addrs))
예제 #28
0
파일: mail.py 프로젝트: zxfly/trac
    def distribute(self, transport, recipients, event):
        if transport != 'email':
            return
        if not self.config.getbool('notification', 'smtp_enabled'):
            self.log.debug("%s skipped because smtp_enabled set to false",
                           self.__class__.__name__)
            return

        formats = {}
        for f in self.formatters:
            for style, realm in f.get_supported_styles(transport):
                if realm == event.realm:
                    formats[style] = f
        if not formats:
            self.log.error("%s No formats found for %s %s",
                           self.__class__.__name__, transport, event.realm)
            return
        self.log.debug(
            "%s has found the following formats capable of "
            "handling '%s' of '%s': %s", self.__class__.__name__, transport,
            event.realm, ', '.join(formats))

        matcher = RecipientMatcher(self.env)
        notify_sys = NotificationSystem(self.env)
        always_cc = set(notify_sys.smtp_always_cc_list)
        addresses = {}
        for sid, auth, addr, fmt in recipients:
            if fmt not in formats:
                self.log.debug("%s format %s not available for %s %s",
                               self.__class__.__name__, fmt, transport,
                               event.realm)
                continue

            if sid and not addr:
                for resolver in self.resolvers:
                    addr = resolver.get_address_for_session(sid, auth) or None
                    if addr:
                        self.log.debug(
                            "%s found the address '%s' for '%s [%s]' via %s",
                            self.__class__.__name__, addr, sid, auth,
                            resolver.__class__.__name__)
                        break
            if sid and auth and not addr:
                addr = sid
            if notify_sys.smtp_default_domain and \
                    not notify_sys.use_short_addr and \
                    addr and matcher.nodomaddr_re.match(addr):
                addr = '%s@%s' % (addr, notify_sys.smtp_default_domain)
            if not addr:
                self.log.debug(
                    "%s was unable to find an address for "
                    "'%s [%s]'", self.__class__.__name__, sid, auth)
            elif matcher.is_email(addr) or \
                    notify_sys.use_short_addr and \
                    matcher.nodomaddr_re.match(addr):
                addresses.setdefault(fmt, set()).add(addr)
                if sid and auth and sid in always_cc:
                    always_cc.discard(sid)
                    always_cc.add(addr)
                elif notify_sys.use_public_cc:
                    always_cc.add(addr)
            else:
                self.log.debug(
                    "%s was unable to use an address '%s' for '%s "
                    "[%s]'", self.__class__.__name__, addr, sid, auth)

        outputs = {}
        failed = []
        for fmt, formatter in formats.iteritems():
            if fmt not in addresses and fmt != 'text/plain':
                continue
            try:
                outputs[fmt] = formatter.format(transport, fmt, event)
            except Exception as e:
                self.log.warning(
                    '%s caught exception while '
                    'formatting %s to %s for %s: %s%s',
                    self.__class__.__name__, event.realm, fmt, transport,
                    formatter.__class__, exception_to_unicode(e,
                                                              traceback=True))
                failed.append(fmt)

        # Fallback to text/plain when formatter is broken
        if failed and 'text/plain' in outputs:
            for fmt in failed:
                addresses.setdefault('text/plain', set()) \
                         .update(addresses.pop(fmt, ()))

        for fmt, addrs in addresses.iteritems():
            self.log.debug("%s is sending event as '%s' to: %s",
                           self.__class__.__name__, fmt, ', '.join(addrs))
            message = self._create_message(fmt, outputs)
            if message:
                addrs = set(addrs)
                cc_addrs = sorted(addrs & always_cc)
                bcc_addrs = sorted(addrs - always_cc)
                self._do_send(transport, event, message, cc_addrs, bcc_addrs)
            else:
                self.log.warning("%s cannot send event '%s' as '%s': %s",
                                 self.__class__.__name__, event.realm, fmt,
                                 ', '.join(addrs))
예제 #29
0
파일: mail.py 프로젝트: zxfly/trac
def get_from_author(env, event):
    if event.author and NotificationSystem(env).smtp_from_author:
        matcher = RecipientMatcher(env)
        from_ = matcher.match_from_author(event.author)
        if from_:
            return from_