Пример #1
0
 def _render_property(self, name, mode, context, props):
     repos, revs = props[name]
     def link(rev):
         chgset = repos.get_changeset(rev)
         return tag.a(rev, class_="changeset",
                      title=shorten_line(chgset.message),
                      href=context.href.changeset(rev, repos.reponame))
     if name == 'Parents' and len(revs) == 2: # merge
         new = context.resource.id
         parent_links = [
                 (link(rev), ' (',
                  tag.a('diff', title=_("Diff against this parent "
                        "(show the changes merged from the other parents)"),
                        href=context.href.changeset(new, repos.reponame, 
                                                    old=rev)), ')')
                        for rev in revs]
         return tag([(parent, ', ') for parent in parent_links[:-1]],
                    parent_links[-1], tag.br(),
                    tag.span(tag_("Note: this is a %(merge)s changeset, "
                                  "the changes displayed below correspond "
                                  "to the merge itself.",
                                  merge=tag.strong('merge')),
                             class_='hint'), tag.br(), 
                    # TODO: only keep chunks present in both parents 
                    #       (conflicts) or in none (extra changes)
                    # tag.span('No changes means the merge was clean.',
                    #         class_='hint'), tag.br(), 
                    tag.span(tag_("Use the %(diff)s links above to see all "
                                  "the changes relative to each parent.",
                                  diff=tag.tt('(diff)')),
                             class_='hint'))
     return tag([tag(link(rev), ', ') for rev in revs[:-1]],
                link(revs[-1]))
Пример #2
0
    def _provider_failure(self, exc, req, ep, current_filters, all_filters):
        """Raise a TracError exception explaining the failure of a provider.

        At the same time, the message will contain a link to the timeline
        without the filters corresponding to the guilty event provider `ep`.
        """
        self.log.error('Timeline event provider failed: %s',
                       exception_to_unicode(exc, traceback=True))

        ep_kinds = dict((f[0], f[1])
                        for f in ep.get_timeline_filters(req) or [])
        ep_filters = set(ep_kinds.keys())
        current_filters = set(current_filters)
        other_filters = set(current_filters) - ep_filters
        if not other_filters:
            other_filters = set(all_filters) -  ep_filters
        args = [(a, req.args.get(a)) for a in ('from', 'format', 'max',
                                               'daysback')]
        href = req.href.timeline(args + [(f, 'on') for f in other_filters])
        # TRANSLATOR: ...want to see the 'other kinds of events' from... (link)
        other_events = tag.a(_('other kinds of events'), href=href)
        raise TracError(tag(
            tag.p(tag_("Event provider %(name)s failed for filters "
                       "%(kinds)s: ",
                       name=tag.tt(ep.__class__.__name__),
                       kinds=', '.join('"%s"' % ep_kinds[f] for f in
                                       current_filters & ep_filters)),
                  tag.b(exception_to_unicode(exc)), class_='message'),
            tag.p(tag_("You may want to see the %(other_events)s from the "
                       "Timeline or notify your Trac administrator about the "
                       "error (detailed information was written to the log).",
                       other_events=other_events))))
Пример #3
0
 def _do_repositories(self, req, category, page):
     # Check that the setttings have been set.
     parentpath = self.config.get('svnadmin', 'parent_path')
     client = self.config.get('svnadmin', 'svn_client_location')
     admin = self.config.get('svnadmin', 'svnadmin_location')
     if not parentpath or not client or not admin:
         add_warning(req, _('You must provide settings before continuing.'))
         req.redirect(req.href.admin(category, 'config'))
     
     data = {}
     
     svn_provider = self.env[SvnRepositoryProvider]
     db_provider = self.env[DbRepositoryProvider]
     
     if req.method == 'POST':
         # Add a repository
         if svn_provider and req.args.get('add_repos'):
             name = req.args.get('name')
             dir = os.path.join(parentpath, name)
             if name is None or name == "" or not dir:
                 add_warning(req, _('Missing arguments to add a repository.'))
             elif self._check_dir(req, dir):
                 try:
                     svn_provider.add_repository(name)
                     db_provider.add_repository(name, dir, 'svn')
                     add_notice(req, _('The repository "%(name)s" has been '
                                       'added.', name=name))
                     resync = tag.tt('trac-admin $ENV repository resync '
                                     '"%s"' % name)
                     msg = tag_('You should now run %(resync)s to '
                                'synchronize Trac with the repository.',
                                resync=resync)
                     add_notice(req, msg)
                     cset_added = tag.tt('trac-admin $ENV changeset '
                                         'added "%s" $REV' % name)
                     msg = tag_('You should also set up a post-commit hook '
                                'on the repository to call %(cset_added)s '
                                'for each committed changeset.',
                                cset_added=cset_added)
                     add_notice(req, msg)
                     req.redirect(req.href.admin(category, page))
                 except TracError, why:
                     add_warning(req, str(why))
         
         # Remove repositories
         elif svn_provider and req.args.get('remove'):
             sel = req.args.getlist('sel')
             if sel:
                 for name in sel:
                     svn_provider.remove_repository(name)
                     db_provider.remove_repository(name)
                 add_notice(req, _('The selected repositories have '
                                   'been removed.'))
                 req.redirect(req.href.admin(category, page))
             add_warning(req, _('No repositories were selected.'))
Пример #4
0
    def __get__(self, instance, owner):
        if instance is None:
            return self
        order = ListOption.__get__(self, instance, owner)
        components = []
        implementing_classes = []
        for impl in self.xtnpt.extensions(instance):
            implementing_classes.append(impl.__class__.__name__)
            if self.include_missing or impl.__class__.__name__ in order:
                components.append(impl)
        not_found = sorted(set(order) - set(implementing_classes))
        if not_found:
            raise ConfigurationError(
                tag_("Cannot find implementation(s) of the %(interface)s "
                     "interface named %(implementation)s. Please check "
                     "that the Component is enabled or update the option "
                     "%(option)s in trac.ini.",
                     interface=tag.tt(self.xtnpt.interface.__name__),
                     implementation=tag(
                         (', ' if idx != 0 else None, tag.tt(impl))
                         for idx, impl in enumerate(not_found)),
                     option=tag.tt("[%s] %s" % (self.section, self.name))))

        def compare(x, y):
            x, y = x.__class__.__name__, y.__class__.__name__
            if x not in order:
                return int(y in order)
            if y not in order:
                return -int(x in order)
            return cmp(order.index(x), order.index(y))
        components.sort(compare)
        return components
Пример #5
0
    def _do_save(self, req, page):
        if not page.exists:
            req.perm(page.resource).require('WIKI_CREATE')
        else:
            req.perm(page.resource).require('WIKI_MODIFY')

        if 'WIKI_ADMIN' in req.perm(page.resource):
            # Modify the read-only flag if it has been changed and the user is
            # WIKI_ADMIN
            page.readonly = int('readonly' in req.args)

        try:
            page.save(get_reporter_id(req, 'author'), req.args.get('comment'),
                      req.remote_addr)
            href = req.href.wiki(page.name, action='diff',
                                 version=page.version)
            add_notice(req, tag_("Your changes have been saved in version "
                                 "%(version)s (%(diff)s).",
                                 version=page.version,
                                 diff=tag.a(_("diff"), href=href)))
            req.redirect(get_resource_url(self.env, page.resource, req.href,
                                          version=None))
        except TracError:
            add_warning(req, _("Page not modified, showing latest version."))
            return self._render_view(req, page)
Пример #6
0
    def _render_source(self, context, stream, annotations, marks=None):
        from trac.web.chrome import add_warning
        annotators, labels, titles = {}, {}, {}
        for annotator in self.annotators:
            atype, alabel, atitle = annotator.get_annotation_type()
            if atype in annotations:
                labels[atype] = alabel
                titles[atype] = atitle
                annotators[atype] = annotator
        annotations = [a for a in annotations if a in annotators]

        if isinstance(stream, list):
            stream = HTMLParser(StringIO(u'\n'.join(stream)))
        elif isinstance(stream, unicode):
            text = stream
            def linesplitter():
                for line in text.splitlines(True):
                    yield TEXT, line, (None, -1, -1)
            stream = linesplitter()

        annotator_datas = []
        for a in annotations:
            annotator = annotators[a]
            try:
                data = (annotator, annotator.get_annotation_data(context))
            except TracError, e:
                self.log.warning("Can't use annotator '%s': %s", a, e.message)
                add_warning(context.req, tag.strong(
                    tag_("Can't use %(annotator)s annotator: %(error)s",
                         annotator=tag.em(a), error=tag.pre(e.message))))
                data = (None, None)
            annotator_datas.append(data)
Пример #7
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(author=req.authname)
        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")
            tn = BatchTicketNotifyEmail(self.env)
            try:
                tn.notify(retargeted_tickets, new_values, comment, None,
                          req.authname)
            except Exception, 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, resid, subject, author=None):
        self.subject = subject
        config = self.config['notification']
        if not config.getbool('smtp_enabled'):
            return
        from_email, from_name = '', ''
        if author and config.getbool('smtp_from_author'):
            from_email = self.get_smtp_address(author)
            if from_email:
                from_name = self.name_map.get(author, '')
                if not from_name:
                    mo = self.longaddr_re.search(author)
                    if mo:
                        from_name = mo.group(1)
        if not from_email:
            from_email = config.get('smtp_from')
            from_name = config.get('smtp_from_name') or self.env.project_name
        self.replyto_email = config.get('smtp_replyto')
        self.from_email = from_email or self.replyto_email
        self.from_name = from_name
        if not self.from_email and not self.replyto_email:
            message = tag(
                tag.p(_('Unable to send email due to identity crisis.')),
                # convert explicitly to `Fragment` to avoid breaking message
                # when passing `LazyProxy` object to `Fragment`
                tag.p(to_fragment(tag_(
                    "Neither %(from_)s nor %(reply_to)s are specified in the "
                    "configuration.",
                    from_=tag.strong("[notification] smtp_from"),
                    reply_to=tag.strong("[notification] smtp_replyto")))))
            raise TracError(message, _("SMTP Notification Error"))

        Notify.notify(self, resid)
Пример #9
0
    def send(self, from_addr, recipients, message):
        # Ensure the message complies with RFC2822: use CRLF line endings
        message = fix_eol(message, CRLF)

        self.log.info("Sending notification through SMTP at %s:%d to %s",
                      self.smtp_server, self.smtp_port, recipients)
        try:
            server = smtplib.SMTP(self.smtp_server, self.smtp_port)
        except smtplib.socket.error as e:
            raise ConfigurationError(
                tag_("SMTP server connection error (%(error)s). Please "
                     "modify %(option1)s or %(option2)s in your "
                     "configuration.",
                     error=to_unicode(e),
                     option1=tag.code("[notification] smtp_server"),
                     option2=tag.code("[notification] smtp_port")))
        # server.set_debuglevel(True)
        if self.use_tls:
            server.ehlo()
            if 'starttls' not in server.esmtp_features:
                raise TracError(_("TLS enabled but server does not support"
                                  " TLS"))
            server.starttls()
            server.ehlo()
        if self.smtp_user:
            server.login(self.smtp_user.encode('utf-8'),
                         self.smtp_password.encode('utf-8'))
        start = time.time()
        resp = sendmail(server, from_addr, recipients, message)
        t = time.time() - start
        if t > 5:
            self.log.warning("Slow mail submission (%.2f s), "
                             "check your mail setup", t)
        if self.use_tls:
            # avoid false failure detection when the server closes
            # the SMTP connection with TLS enabled
            import socket
            try:
                server.quit()
            except socket.sslerror:
                pass
        else:
            server.quit()

        msg = email.message_from_string(message)
        ticket_id = int(msg['x-trac-ticket-id'])
        msgid = msg['message-id']
        aws_re = r'^email-smtp\.([a-z0-9-]+)\.amazonaws\.com$'
        m = re.match(aws_re, self.smtp_server)
        if m:
            parts = resp.split()
            if len(parts) == 2 and parts[0] == 'Ok':
                region = m.group(1)
                msgid = '<%s@%s.amazonses.com>' % (parts[1], region)
        with self.env.db_transaction as db:
            cursor = db.cursor()
            cursor.execute("""
                INSERT OR IGNORE INTO messageid (ticket,messageid)
                VALUES (%s, %s)
                """, (ticket_id, msgid))
Пример #10
0
    def _do_login(self, req):
        """Log the remote user in.

        This function expects to be called when the remote user name
        is available. The user name is inserted into the `auth_cookie`
        table and a cookie identifying the user on subsequent requests
        is sent back to the client.

        If the Authenticator was created with `ignore_case` set to
        true, then the authentication name passed from the web server
        in req.remote_user will be converted to lower case before
        being used. This is to avoid problems on installations
        authenticating against Windows which is not case sensitive
        regarding user names and domain names
        """
        if not req.remote_user:
            # TRANSLATOR: ... refer to the 'installation documentation'. (link)
            inst_doc = tag.a(_('installation documentation'),
                             title=_("Configuring Authentication"),
                             href=req.href.wiki('TracInstall')
                                  + "#ConfiguringAuthentication")
            raise TracError(tag_("Authentication information not available. "
                                 "Please refer to the %(inst_doc)s.",
                                 inst_doc=inst_doc))
        remote_user = req.remote_user
        if self.ignore_case:
            remote_user = remote_user.lower()

        if req.authname not in ('anonymous', remote_user):
            raise TracError(_('Already logged in as %(user)s.',
                              user=req.authname))

        with self.env.db_transaction as db:
            # Delete cookies older than 10 days
            db("DELETE FROM auth_cookie WHERE time < %s",
               (int(time.time()) - 86400 * 10,))
            # Insert a new cookie if we haven't already got one
            cookie = None
            trac_auth = req.incookie.get('trac_auth')
            if trac_auth is not None:
                name = self._cookie_to_name(req, trac_auth)
                cookie = trac_auth.value if name == remote_user else None
            if cookie is None:
                cookie = hex_entropy()
                db("""
                    INSERT INTO auth_cookie (cookie, name, ipnr, time)
                         VALUES (%s, %s, %s, %s)
                   """, (cookie, remote_user, req.remote_addr,
                         int(time.time())))
        req.authname = remote_user
        req.outcookie['trac_auth'] = cookie
        req.outcookie['trac_auth']['path'] = self.auth_cookie_path \
                                             or req.base_path or '/'
        if self.env.secure_cookies:
            req.outcookie['trac_auth']['secure'] = True
        if sys.version_info >= (2, 6):
            req.outcookie['trac_auth']['httponly'] = True
        if self.auth_cookie_lifetime > 0:
            req.outcookie['trac_auth']['expires'] = self.auth_cookie_lifetime
Пример #11
0
 def render_timeline_event(self, context, field, event):
     url, title, desc = event[3]
     if field == 'url':
         return url
     elif field == 'title':
         return tag_('%(page)s created', page=tag.em(title))
     elif field == 'description':
         return tag(desc)
Пример #12
0
    def _render_source(self, context, stream, annotations):
        from trac.web.chrome import add_warning

        annotators, labels, titles = {}, {}, {}
        for annotator in self.annotators:
            atype, alabel, atitle = annotator.get_annotation_type()
            if atype in annotations:
                labels[atype] = alabel
                titles[atype] = atitle
                annotators[atype] = annotator
        annotations = [a for a in annotations if a in annotators]

        if isinstance(stream, list):
            stream = HTMLParser(StringIO(u"\n".join(stream)))
        elif isinstance(stream, unicode):
            text = stream

            def linesplitter():
                for line in text.splitlines(True):
                    yield TEXT, line, (None, -1, -1)

            stream = linesplitter()

        annotator_datas = []
        for a in annotations:
            annotator = annotators[a]
            try:
                data = (annotator, annotator.get_annotation_data(context))
            except TracError as e:
                self.log.warning("Can't use annotator '%s': %s", a, e)
                add_warning(
                    context.req,
                    tag.strong(
                        tag_("Can't use %(annotator)s annotator: %(error)s", annotator=tag.em(a), error=tag.pre(e))
                    ),
                )
                data = None, None
            annotator_datas.append(data)

        def _head_row():
            return tag.tr(
                [tag.th(labels[a], class_=a, title=titles[a]) for a in annotations]
                + [tag.th(u"\xa0", class_="content")]
            )

        def _body_rows():
            for idx, line in enumerate(_group_lines(stream)):
                row = tag.tr()
                for annotator, data in annotator_datas:
                    if annotator:
                        annotator.annotate_row(context, row, idx + 1, line, data)
                    else:
                        row.append(tag.td())
                row.append(tag.td(line))
                yield row

        return tag.table(class_="code")(tag.thead(_head_row()), tag.tbody(_body_rows()))
Пример #13
0
 def render_timeline_event(self, context, field, event):
     milestone, description = event[3]
     if field == 'url':
         return context.href.milestone(milestone.id)
     elif field == 'title':
         return tag_('Milestone %(name)s completed',
                     name=tag.em(milestone.id))
     elif field == 'description':
         return format_to(self.env, None, context.child(resource=milestone),
                          description)
Пример #14
0
 def render_timeline_event(self, context, field, event):
     wiki_page, comment = event[3]
     if field == 'url':
         return context.href.wiki(wiki_page.id, version=wiki_page.version)
     elif field == 'title':
         name = tag.em(get_resource_name(self.env, wiki_page))
         if wiki_page.version > 1:
             return tag_('%(page)s edited', page=name)
         else:
             return tag_('%(page)s created', page=name)
     elif field == 'description':
         markup = format_to(self.env, None,
                            context.child(resource=wiki_page), comment)
         if wiki_page.version > 1:
             diff_href = context.href.wiki(
                 wiki_page.id, version=wiki_page.version, action='diff')
             markup = tag(markup,
                          ' (', tag.a(_('diff'), href=diff_href), ')')
         return markup
Пример #15
0
 def validate(self, relation):
     rls = RelationsSystem(self.env)
     existing_relations = rls._select_relations(resource_type=relation.type,
                                                destination=relation.destination)
     if existing_relations:
         raise ValidationError(
             tag_("Another resource is already related to %(destination)s "
                  "with %(relation)s relation.",
                  destination=tag.em(relation.destination),
                  relation=tag.b(self.render_relation_type(relation.type)))
         )
Пример #16
0
def _dispatch_request(req, env, env_error):
    resp = []

    # fixup env.abs_href if `[trac] base_url` was not specified
    if env and not env.abs_href.base:
        env._abs_href = req.abs_href

    try:
        if not env and env_error:
            raise HTTPInternalError(env_error)
        try:
            dispatcher = RequestDispatcher(env)
            dispatcher.dispatch(req)
        except RequestDone:
            pass
        resp = req._response or []

    except HTTPException, e:
        # This part is a bit more complex than it should be.
        # See trac/web/api.py for the definition of HTTPException subclasses.
        if env:
            env.log.warn(exception_to_unicode(e))
        try:
            # We try to get localized error messages here, 
            # but we should ignore secondary errors
            title = _('Error')
            if e.reason:
                if title.lower() in e.reason.lower():
                    title = e.reason
                else:
                    title = _('Error: %(message)s', message=e.reason)
        except:
            title = 'Error'
        # The message is based on the e.detail, which can be an Exception
        # object, but not a TracError one: when creating HTTPException,
        # a TracError.message is directly assigned to e.detail
        if isinstance(e.detail, Exception): # not a TracError
            message = exception_to_unicode(e.detail)
        elif isinstance(e.detail, Fragment): # markup coming from a TracError
            message = e.detail
        else:
            message = to_unicode(e.detail)
        data = {'title': title, 'type': 'TracError', 'message': message,
                'frames': [], 'traceback': None}
        if e.code == 403 and req.authname == 'anonymous':
            # TRANSLATOR: ... not logged in, you may want to 'do so' now (link)
            do_so = tag.a(_("do so"), href=req.href.login())
            req.chrome['notices'].append(
                tag_("You are currently not logged in. You may want to "
                     "%(do_so)s now.", do_so=do_so))
        try:
            req.send_error(sys.exc_info(), status=e.code, env=env, data=data)
        except RequestDone:
            pass
Пример #17
0
    def process_request(self, req):
        req.perm.assert_permission('TICKET_BATCH_MODIFY')

        comment = req.args.get('batchmod_value_comment', '')
        action = req.args.get('action')

        try:
            new_values = self._get_new_ticket_values(req)
        except TracError, e:
            new_values = None
            add_warning(req, tag_("The changes could not be saved: "
                                  "%(message)s", message=to_unicode(e)))
Пример #18
0
def get_existing_node(req, repos, path, rev):
    try: 
        return repos.get_node(path, rev) 
    except NoSuchNode, e:
        # TRANSLATOR: You can 'search' in the repository history... (link)
        search_a = tag.a(_("search"), 
                         href=req.href.log(path, rev=rev, mode='path_history'))
        raise ResourceNotFound(tag(
            tag.p(e.message, class_="message"), 
            tag.p(tag_("You can %(search)s in the repository history to see "
                       "if that path existed but was later removed",
                       search=search_a))))
Пример #19
0
    def _validate(self, req, page):
        valid = True

        # Validate page size
        if len(req.args.get('text', '')) > self.max_size:
            add_warning(req, _("The wiki page is too long (must be less "
                               "than %(num)s characters)",
                               num=self.max_size))
            valid = False

        # Give the manipulators a pass at post-processing the page
        for manipulator in self.page_manipulators:
            for field, message in manipulator.validate_wiki_page(req, page):
                valid = False
                if field:
                    add_warning(req, tag_("The Wiki page field '%(field)s'"
                                          " is invalid: %(message)s",
                                          field=field, message=message))
                else:
                    add_warning(req, tag_("Invalid Wiki page: %(message)s",
                                          message=message))
        return valid
Пример #20
0
 def generate_prefix(prefix):
     intertrac = intertracs[prefix]
     if isinstance(intertrac, basestring):
         yield tag.tr(tag.td(tag.strong(prefix)),
                      tag.td(tag_("Alias for %(name)s",
                                  name=tag.strong(intertrac))))
     else:
         url = intertrac.get('url', '')
         if url:
             title = intertrac.get('title', url)
             yield tag.tr(tag.td(tag.a(tag.strong(prefix),
                                       href=url + '/timeline')),
                          tag.td(tag.a(title, href=url)))
Пример #21
0
    def _do_login(self, req):
        """Log the remote user in.

        This function expects to be called when the remote user name is
        available. The user name is inserted into the `auth_cookie` table and a
        cookie identifying the user on subsequent requests is sent back to the
        client.

        If the Authenticator was created with `ignore_case` set to true, then 
        the authentication name passed from the web server in req.remote_user
        will be converted to lower case before being used. This is to avoid
        problems on installations authenticating against Windows which is not
        case sensitive regarding user names and domain names
        """
        if not req.remote_user:
            # TRANSLATOR: ... refer to the 'installation documentation'. (link)
            inst_doc = tag.a(
                _("installation documentation"),
                title=_("Configuring Authentication"),
                href=req.href.wiki("TracInstall") + "#ConfiguringAuthentication",
            )
            raise TracError(
                tag_(
                    "Authentication information not available. " "Please refer to the %(inst_doc)s.", inst_doc=inst_doc
                )
            )
        remote_user = req.remote_user
        if self.ignore_case:
            remote_user = remote_user.lower()

        assert req.authname in ("anonymous", remote_user), _("Already logged in as %(user)s.", user=req.authname)

        cookie = hex_entropy()

        @self.env.with_transaction()
        def store_session_cookie(db):
            cursor = db.cursor()
            # Delete cookies older than 10 days
            cursor.execute("DELETE FROM auth_cookie WHERE time < %s", (int(time.time()) - 86400 * 10,))
            cursor.execute(
                "INSERT INTO auth_cookie (cookie,name,ipnr,time) " "VALUES (%s, %s, %s, %s)",
                (cookie, remote_user, req.remote_addr, int(time.time())),
            )

        req.authname = remote_user
        req.outcookie["trac_auth"] = cookie
        req.outcookie["trac_auth"]["path"] = self.auth_cookie_path or req.base_path or "/"
        if self.env.secure_cookies:
            req.outcookie["trac_auth"]["secure"] = True
        if self.auth_cookie_lifetime > 0:
            req.outcookie["trac_auth"]["expires"] = self.auth_cookie_lifetime
Пример #22
0
    def get_search_results(self, req, terms, filters):
        """Overriding search results for Tickets"""
        if not "ticket" in filters:
            return
        ticket_realm = Resource("ticket")
        with self.env.db_query as db:
            sql, args = search_to_sql(
                db, ["summary", "keywords", "description", "reporter", "cc", db.cast("id", "text")], terms
            )
            sql2, args2 = search_to_sql(db, ["newvalue"], terms)
            sql3, args3 = search_to_sql(db, ["value"], terms)
            ticketsystem = TicketSystem(self.env)
            if req.args.get("product"):
                productsql = "product='%s' AND" % req.args.get("product")
            else:
                productsql = ""

            for summary, desc, author, type, tid, ts, status, resolution in db(
                """SELECT summary, description, reporter, type, id,
                                 time, status, resolution 
                          FROM ticket
                          WHERE (%s id IN (
                              SELECT id FROM ticket WHERE %s
                            UNION
                              SELECT ticket FROM ticket_change
                              WHERE field='comment' AND %s
                            UNION
                              SELECT ticket FROM ticket_custom WHERE %s
                          ))
                          """
                % (productsql, sql, sql2, sql3),
                args + args2 + args3,
            ):
                t = ticket_realm(id=tid)
                if "TICKET_VIEW" in req.perm(t):
                    yield (
                        req.href.ticket(tid),
                        tag_(
                            "%(title)s: %(message)s",
                            title=tag.span(get_resource_shortname(self.env, t), class_=status),
                            message=ticketsystem.format_summary(summary, status, resolution, type),
                        ),
                        from_utimestamp(ts),
                        author,
                        shorten_result(desc, terms),
                    )

        # Attachments
        for result in AttachmentModule(self.env).get_search_results(req, ticket_realm, terms):
            yield result
Пример #23
0
def _send_user_error(req, env, e):
    # See trac/web/api.py for the definition of HTTPException subclasses.
    if env:
        env.log.warn('[%s] %s', req.remote_addr, exception_to_unicode(e))
    data = {'title': e.title, 'type': 'TracError', 'message': e.message,
            'frames': [], 'traceback': None}
    if e.code == 403 and req.authname == 'anonymous':
        # TRANSLATOR: ... not logged in, you may want to 'do so' now (link)
        do_so = tag.a(_("do so"), href=req.href.login())
        add_notice(req, tag_("You are currently not logged in. You may want "
                             "to %(do_so)s now.", do_so=do_so))
    try:
        req.send_error(sys.exc_info(), status=e.code, env=env, data=data)
    except RequestDone:
        pass
Пример #24
0
    def render_ticket_action_control(self, req, ticket, action):
        this_action = ConfigurableTicketWorkflow(self.env).actions[action]
        status = this_action['newstate']
        operations = this_action['operations']

        control = []  # default to nothing
        hints = []
        if 'set_evaluation' in operations:
            evaluations = self._get_evaluation_options()
            if not evaluations:
                raise TracError(_('Your workflow attempts to set an evaluation '
                                  'but none is defined (configuration issue, '
                                  'please contact your Trac admin).'))
            id = 'action_%s_evaluate_evaluation' % action
            if len(evaluations) == 1:
                evaluation = tag.input(type='hidden', id=id, name=id,
                                       value=evaluations[0])
                control.append(tag_('as %(evaluation)s',
                                    evaluation=tag(evaluations[0],
                                                   evaluation)))
                hints.append(_('The evaluation will be set to %(name)s',
                               name=evaluations[0]))
            else:
                selected_option = 1
                control.append(tag_('as %(evaluation)s',
                                    evaluation=tag.select(
                                        [tag.option(x, value=x,
                                                    selected=(x == selected_option or None))
                                            for x in evaluations],
                                        id=id, name=id)))
                hints.append(_('The evaluation will be set'))
        #if 'del_evaluation' in operations:
        #    hints.append(_('The evaluation will be deleted'))

        return (this_action['name'], tag(*control), '. '.join(hints) + '.'
                if hints else '')
Пример #25
0
    def get_delete_event(self, req, download_str, dir_or_file, type_to_can_view,
                         show_downloads_events, show_files_events, filename, path):
        target_path = '/' + path
        target_type = download_str
        can_view_target = type_to_can_view[target_type]
        if not can_view_target:
            return
        if target_type == 'download':
            if not show_downloads_events:
                return
            event_class = 'downloadsevent-rm'
        else:
            if not show_files_events:
                return
            event_class = 'filesevent-rm'

        parent_path = get_normalized_relative_path(
            'base_path_not_used', os.path.join(target_path[1:], '..'),
            assume_relative_path=True)
        if dir_or_file == 'dir':
            title = _("Directory %(filename)s deleted", filename=filename)
            description = tag_("Directory %(filename)s deleted from %(target_path)s",
                filename=filename, target_path=tag.a(target_path,
                    href=req.href.files(parent_path)))
        else:
            if target_type == 'download':
                title = _("Download %(filename)s deleted", filename=filename)
                description = tag_("Download %(filename)s deleted from %(target_path)s",
                    filename=filename, target_path=tag.a(target_path,
                        href=req.href.files(parent_path)))
            else:
                title = _("File %(filename)s deleted", filename=filename)
                description = tag_("File %(filename)s deleted from %(target_path)s",
                    filename=filename, target_path=tag.a(target_path,
                        href=req.href.files(parent_path)))
        return event_class, title, description
Пример #26
0
 def __get__(self, instance, owner):
     if instance is None:
         return self
     value = Option.__get__(self, instance, owner)
     for impl in self.xtnpt.extensions(instance):
         if impl.__class__.__name__ == value:
             return impl
     raise ConfigurationError(
         tag_("Cannot find an implementation of the %(interface)s "
              "interface named %(implementation)s. Please check "
              "that the Component is enabled or update the option "
              "%(option)s in trac.ini.",
              interface=tag.tt(self.xtnpt.interface.__name__),
              implementation=tag.tt(value),
              option=tag.tt("[%s] %s" % (self.section, self.name))))
Пример #27
0
    def get_search_results(self, req, terms, filters):
        """Return the entry  whose 'keyword' or 'text' tag contains
        one or more word among the terms.
        """

        if 'crashdump' not in filters:
            return

        self.log.debug('search for %s and %s', terms, filters)

        #ticket_realm = Resource(self.realm)
        with self.env.db_query as db:
            sql, args = search_to_sql(db, ['summary', 'keywords',
                                           'description', 'reporter', 'cc',
                                           'applicationname', 'applicationfile',
                                           'uploadhostname', 'uploadusername',
                                           'crashhostname', 'crashusername',
                                           'systemname',
                                           'uuid',
                                           db.cast('id', 'text')], terms)
            for id, uuid, summary, description, reporter, type, \
                crashhostname, crashusername, applicationname, applicationfile, systemname, \
                crashtime, reporttime, status, resolution in \
                    db("""SELECT id, uuid, summary, description, reporter, type,
                                 crashhostname, crashusername, applicationname, applicationfile, systemname,
                                 crashtime, reporttime, status, resolution
                          FROM crashdump
                          WHERE id IN (
                              SELECT id FROM crashdump WHERE %s
                          )
                          """ % (sql), args):
                if 'TICKET_VIEW' in req.perm:

                    # The events returned by this function must be tuples of the form (href, title, date, author, excerpt).
                    full_desc = '%s on %s (%s)' % (
                            applicationname if applicationname else applicationfile,
                            '%s@%s' % (crashusername, crashhostname),
                            systemname
                        )
                    #excerpt = shorten_result(full_desc, terms)
                    excerpt = full_desc
                    yield (req.href('crash', uuid),
                           tag_("%(title)s: %(uuid)s",
                                uuid=uuid,
                                title='CrashId#%i' % id,
                                ),
                           from_utimestamp(crashtime), reporter,
                           excerpt)
Пример #28
0
    def send(self, from_addr, recipients, message):
        # Ensure the message complies with RFC2822: use CRLF line endings
        message = fix_eol(message, CRLF)

        self.log.info("Sending notification through SMTP at %s:%d to %s",
                      self.smtp_server, self.smtp_port, recipients)
        try:
            server = smtplib.SMTP(self.smtp_server, self.smtp_port)
        except smtplib.socket.error, e:
            raise ConfigurationError(
                tag_("SMTP server connection error (%(error)s). Please "
                     "modify %(option1)s or %(option2)s in your "
                     "configuration.",
                     error=to_unicode(e),
                     option1=tag.tt("[notification] smtp_server"),
                     option2=tag.tt("[notification] smtp_port")))
Пример #29
0
    def send(self, from_addr, recipients, message):
        # Use native line endings in message
        message = fix_eol(message, os.linesep)

        self.log.info("Sending notification through sendmail at %s to %s",
                      self.sendmail_path, recipients)
        cmdline = [self.sendmail_path, "-i", "-f", from_addr]
        cmdline.extend(recipients)
        self.log.debug("Sendmail command line: %s", cmdline)
        try:
            child = Popen(cmdline, bufsize=-1, stdin=PIPE, stdout=PIPE,
                          stderr=PIPE, close_fds=close_fds)
        except OSError, e:
            raise ConfigurationError(
                tag_("Sendmail error (%(error)s). Please modify %(option)s "
                     "in your configuration.",
                     error=to_unicode(e),
                     option=tag.tt("[notification] sendmail_path")))
Пример #30
0
    def send(self, from_addr, recipients, message):
        # Ensure the message complies with RFC2822: use CRLF line endings
        message = fix_eol(message, CRLF)

        self.log.info("Sending notification through SMTP at %s:%d to %s",
                      self.smtp_server, self.smtp_port, recipients)
        try:
            server = smtplib.SMTP(self.smtp_server, self.smtp_port)
        except smtplib.socket.error as e:
            raise ConfigurationError(
                tag_("SMTP server connection error (%(error)s). Please "
                     "modify %(option1)s or %(option2)s in your "
                     "configuration.",
                     error=to_unicode(e),
                     option1=tag.code("[notification] smtp_server"),
                     option2=tag.code("[notification] smtp_port")))
        # server.set_debuglevel(True)
        if self.use_tls:
            server.ehlo()
            if 'starttls' not in server.esmtp_features:
                raise TracError(_("TLS enabled but server does not support"
                                  " TLS"))
            server.starttls()
            server.ehlo()
        if self.smtp_user:
            server.login(self.smtp_user.encode('utf-8'),
                         self.smtp_password.encode('utf-8'))
        start = time.time()
        server.sendmail(from_addr, recipients, message)
        t = time.time() - start
        if t > 5:
            self.log.warning("Slow mail submission (%.2f s), "
                             "check your mail setup", t)
        if self.use_tls:
            # avoid false failure detection when the server closes
            # the SMTP connection with TLS enabled
            import socket
            try:
                server.quit()
            except socket.sslerror:
                pass
        else:
            server.quit()
Пример #31
0
    def send(self, from_addr, recipients, message):
        # Use native line endings in message
        message = fix_eol(message, os.linesep)

        self.log.info("Sending notification through sendmail at %s to %s",
                      self.sendmail_path, recipients)
        cmdline = [self.sendmail_path, '-i', '-f', from_addr] + recipients
        self.log.debug("Sendmail command line: %s", cmdline)
        try:
            child = Popen(cmdline, bufsize=-1, stdin=PIPE, stdout=PIPE,
                          stderr=PIPE, close_fds=close_fds)
        except OSError as e:
            raise ConfigurationError(
                tag_("Sendmail error (%(error)s). Please modify %(option)s "
                     "in your configuration.",
                     error=to_unicode(e),
                     option=tag.code("[notification] sendmail_path")))
        out, err = child.communicate(message)
        if child.returncode or err:
            raise Exception("Sendmail failed with (%s, %s), command: '%s'"
                            % (child.returncode, err.strip(), cmdline))
Пример #32
0
    def _get_valid_default_handler(self, req):
        # Use default_handler from the Session if it is a valid value.
        name = req.session.get('default_handler')
        handler = self._request_handlers.get(name)
        if handler and not is_valid_default_handler(handler):
            handler = None

        if not handler:
            # Use default_handler from project configuration.
            handler = self.default_handler
            if not is_valid_default_handler(handler):
                raise ConfigurationError(
                    tag_(
                        "%(handler)s is not a valid default handler. Please "
                        "update %(option)s through the %(page)s page or by "
                        "directly editing trac.ini.",
                        handler=tag.code(handler.__class__.__name__),
                        option=tag.code("[trac] default_handler"),
                        page=tag.a(_("Basic Settings"),
                                   href=req.href.admin('general/basics'))))
        return handler
Пример #33
0
    def __init__(self, path, log=None, params={}):
        self.cnx = None
        if path != ':memory:':
            if not os.access(path, os.F_OK):
                raise ConfigurationError(
                    _('Database "%(path)s" not found.', path=path))

            dbdir = os.path.dirname(path)
            if not os.access(path, os.R_OK + os.W_OK) or \
                    not os.access(dbdir, os.R_OK + os.W_OK):
                raise ConfigurationError(
                    tag_(
                        "The user %(user)s requires read _and_ write permissions "
                        "to the database file %(path)s and the directory it is "
                        "located in.",
                        user=tag.code(getuser()),
                        path=tag.code(path)))

        self._active_cursors = weakref.WeakKeyDictionary()
        timeout = int(params.get('timeout', 10.0))
        self._eager = params.get('cursor', 'eager') == 'eager'
        # eager is default, can be turned off by specifying ?cursor=
        cnx = sqlite.connect(path,
                             detect_types=sqlite.PARSE_DECLTYPES,
                             isolation_level=None,
                             check_same_thread=sqlite_version < (3, 3, 1),
                             timeout=timeout)
        # load extensions
        extensions = params.get('extensions', [])
        if len(extensions) > 0:
            cnx.enable_load_extension(True)
            for ext in extensions:
                cnx.load_extension(ext)
            cnx.enable_load_extension(False)

        with closing(cnx.cursor()) as cursor:
            _set_journal_mode(cursor, params.get('journal_mode'))
            set_synchronous(cursor, params.get('synchronous'))
        cnx.isolation_level = 'DEFERRED'
        ConnectionWrapper.__init__(self, cnx, log)
Пример #34
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(author=req.authname)
        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")
            tn = BatchTicketNotifyEmail(self.env)
            try:
                tn.notify(retargeted_tickets, new_values, comment, None,
                          req.authname)
            except Exception, 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)))
Пример #35
0
    def _filter_resolutions(self, req, items):
        for item in items:
            if item[0] != 'resolve':
                yield item
                return

            resolutions = [
                val.name for val in Resolution.select(self.env)
                if int(val.value) > 0
            ]
            ts = TicketSystem(self.env)
            selected_option = req.args.get('action_resolve_resolve_resolution',
                                           ts.default_resolution)
            control = tag.select([
                tag.option(x, value=x, selected=(x == selected_option or None))
                for x in resolutions
            ],
                                 id='action_resolve_resolve_resolution',
                                 name='action_resolve_resolve_resolution')

            yield ('resolve', tag_('as %(resolution)s',
                                   resolution=control), item[2])
Пример #36
0
    def getint(self, name, default=None, min=None, max=None):
        """Return the value as an integer. Raise an `HTTPBadRequest`
        exception if an exception occurs while converting the value
        to an integer.

        :param name: the name of the request parameter
        :keyword default: the value to return if the parameter is not
                          specified
        :keyword min: lower bound to which the value is limited
        :keyword max: upper bound to which the value is limited

        :since: 1.2
        """
        if name not in self:
            return default
        value = as_int(self[name], None, min, max)
        if value is None:
            raise HTTPBadRequest(
                tag_("Invalid value for request argument "
                     "%(name)s.",
                     name=tag.em(name)))
        return value
Пример #37
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.values[field] if field in t.values 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)
     try:
         tn = BatchTicketNotifyEmail(self.env)
         tn.notify(selected_tickets, new_values, comment, action,
                   req.authname)
     except Exception, 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)))
Пример #38
0
Файл: batch.py Проект: t2y/trac
    def process_request(self, req):
        req.perm.assert_permission('TICKET_BATCH_MODIFY')

        comment = req.args.get('batchmod_value_comment', '')
        action = req.args.get('action')

        try:
            new_values = self._get_new_ticket_values(req)
        except TracError as e:
            new_values = None
            add_warning(
                req,
                tag_("The changes could not be saved: "
                     "%(message)s",
                     message=to_unicode(e)))

        if new_values is not None:
            selected_tickets = self._get_selected_tickets(req)
            self._save_ticket_changes(req, selected_tickets, new_values,
                                      comment, action)

        #Always redirect back to the query page we came from.
        req.redirect(req.session['query_href'])
Пример #39
0
def _send_user_error(req, env, e):
    # See trac/web/api.py for the definition of HTTPException subclasses.
    if env:
        env.log.warning('[%s] %s, %r, referrer %r', req.remote_addr,
                        exception_to_unicode(e), req,
                        req.environ.get('HTTP_REFERER'))
    data = {
        'title': e.title,
        'type': 'TracError',
        'message': e.message,
        'frames': [],
        'traceback': None
    }
    if e.code == 403 and not req.is_authenticated:
        # TRANSLATOR: ... not logged in, you may want to 'do so' now (link)
        do_so = tag.a(_("do so"), href=req.href.login())
        add_notice(
            req,
            tag_(
                "You are currently not logged in. You may want "
                "to %(do_so)s now.",
                do_so=do_so))
    _send_error(req, sys.exc_info(), status=e.code, env=env, data=data)
Пример #40
0
    def _do_save(self, req, page):
        if not page.exists:
            req.perm(page.resource).require('WIKI_CREATE')
        else:
            req.perm(page.resource).require('WIKI_MODIFY')

        if 'WIKI_CHANGE_READONLY' in req.perm(page.resource):
            # Modify the read-only flag if it has been changed and the user is
            # WIKI_ADMIN
            page.readonly = int('readonly' in req.args)

        try:
            page.save(get_reporter_id(req, 'author'), req.args.get('comment'))
        except TracError:
            add_warning(req, _("Page not modified, showing latest version."))
            return self._render_view(req, page)

        href = req.href.wiki(page.name, action='diff', version=page.version)
        add_notice(req, tag_("Your changes have been saved in version "
                             "%(version)s (%(diff)s).", version=page.version,
                             diff=tag.a(_("diff"), href=href)))
        req.redirect(get_resource_url(self.env, page.resource, req.href,
                                      version=None))
Пример #41
0
    def _render_source(self, context, stream, annotations, marks=None):
        from trac.web.chrome import add_warning
        annotators, labels, titles = {}, {}, {}
        for annotator in self.annotators:
            atype, alabel, atitle = annotator.get_annotation_type()
            if atype in annotations:
                labels[atype] = alabel
                titles[atype] = atitle
                annotators[atype] = annotator
        annotations = [a for a in annotations if a in annotators]

        if isinstance(stream, list):
            stream = HTMLParser(StringIO(u'\n'.join(stream)))
        elif isinstance(stream, unicode):
            text = stream

            def linesplitter():
                for line in text.splitlines(True):
                    yield TEXT, line, (None, -1, -1)

            stream = linesplitter()

        annotator_datas = []
        for a in annotations:
            annotator = annotators[a]
            try:
                data = (annotator, annotator.get_annotation_data(context))
            except TracError, e:
                self.log.warning("Can't use annotator '%s': %s", a, e.message)
                add_warning(
                    context.req,
                    tag.strong(
                        tag_("Can't use %(annotator)s annotator: %(error)s",
                             annotator=tag.em(a),
                             error=tag.pre(e.message))))
                data = (None, None)
            annotator_datas.append(data)
Пример #42
0
def _send_user_error(req, env, e):
    # See trac/web/api.py for the definition of HTTPException subclasses.
    if env:
        env.log.warn('[%s] %s' % (req.remote_addr, exception_to_unicode(e)))
    try:
        # We first try to get localized error messages here, but we
        # should ignore secondary errors if the main error was also
        # due to i18n issues
        title = _('Error')
        if e.reason:
            if title.lower() in e.reason.lower():
                title = e.reason
            else:
                title = _('Error: %(message)s', message=e.reason)
    except Exception:
        title = 'Error'
    # The message is based on the e.detail, which can be an Exception
    # object, but not a TracError one: when creating HTTPException,
    # a TracError.message is directly assigned to e.detail
    if isinstance(e.detail, Exception): # not a TracError
        message = exception_to_unicode(e.detail)
    elif isinstance(e.detail, Fragment): # markup coming from a TracError
        message = e.detail
    else:
        message = to_unicode(e.detail)
    data = {'title': title, 'type': 'TracError', 'message': message,
            'frames': [], 'traceback': None}
    if e.code == 403 and req.authname == 'anonymous':
        # TRANSLATOR: ... not logged in, you may want to 'do so' now (link)
        do_so = tag.a(_("do so"), href=req.href.login())
        req.chrome['notices'].append(
            tag_("You are currently not logged in. You may want to "
                 "%(do_so)s now.", do_so=do_so))
    try:
        req.send_error(sys.exc_info(), status=e.code, env=env, data=data)
    except RequestDone:
        pass
Пример #43
0
    def _process_fork_request(self, req, data):
        """Fork an existing repository."""
        rm = RepositoryManager(self.env)
        origin_name = req.args.get('local_origin', req.args.get('reponame'))

        if self.restrict_forks and origin_name:
            name = req.authname + '/' + origin_name
            if not 'REPOSITORY_ADMIN' in req.perm:
                if rm.get_repository(name):
                    req.redirect(req.href.browser(name))
                req.args['local_name'] = name

        local_fork = self._get_repository_data_from_request(req, 'local_')
        local_fork['origin'] = origin_name

        if req.args.get('fork_local'):
            origin = self._get_checked_repository(req, local_fork['origin'],
                                                  False, 'REPOSITORY_FORK')
            local_fork.update({'type': origin.type})
            self._create(req, local_fork, rm.fork_local)

        repo_link = tag.a(origin_name, href=req.href.browser(origin_name))
        data.update({'title': tag_("Fork Repository %(link)s", link=repo_link),
                     'local_fork': local_fork})
Пример #44
0
def _send_user_error(req, env, e):
    # See trac/web/api.py for the definition of HTTPException subclasses.
    if env:
        env.log.warn('[%s] %s', req.remote_addr, exception_to_unicode(e))
    data = {
        'title': e.title,
        'type': 'TracError',
        'message': e.message,
        'frames': [],
        'traceback': None
    }
    if e.code == 403 and req.authname == 'anonymous':
        # TRANSLATOR: ... not logged in, you may want to 'do so' now (link)
        do_so = tag.a(_("do so"), href=req.href.login())
        add_notice(
            req,
            tag_(
                "You are currently not logged in. You may want "
                "to %(do_so)s now.",
                do_so=do_so))
    try:
        req.send_error(sys.exc_info(), status=e.code, env=env, data=data)
    except RequestDone:
        pass
Пример #45
0
    def notify(self, resid, subject, author=None):
        self.subject = subject
        config = self.config['notification']
        if not config.getbool('smtp_enabled'):
            return
        from_email, from_name = '', ''
        if author and config.getbool('smtp_from_author'):
            from_email = self.get_smtp_address(author)
            if from_email:
                from_name = self.name_map.get(author, '')
                if not from_name:
                    mo = self.longaddr_re.search(author)
                    if mo:
                        from_name = mo.group(1)
        if not from_email:
            from_email = config.get('smtp_from')
            from_name = config.get('smtp_from_name') or self.env.project_name
        self.replyto_email = config.get('smtp_replyto')
        self.from_email = from_email or self.replyto_email
        self.from_name = from_name
        if not self.from_email and not self.replyto_email:
            message = tag(
                tag.p(_('Unable to send email due to identity crisis.')),
                # convert explicitly to `Fragment` to avoid breaking message
                # when passing `LazyProxy` object to `Fragment`
                tag.p(
                    to_fragment(
                        tag_(
                            "Neither %(from_)s nor %(reply_to)s are specified in the "
                            "configuration.",
                            from_=tag.strong("[notification] smtp_from"),
                            reply_to=tag.strong(
                                "[notification] smtp_replyto")))))
            raise TracError(message, _("SMTP Notification Error"))

        Notify.notify(self, resid)
Пример #46
0
    def render_admin_panel(self, req, cat, page, path_info):
        log_type = self.env.log_type
        log_level = self.env.log_level
        log_file = self.env.log_file
        log_dir = self.env.log_dir

        log_types = [
            dict(name='none',
                 label=_("None"),
                 selected=log_type == 'none',
                 disabled=False),
            dict(name='stderr',
                 label=_("Console"),
                 selected=log_type == 'stderr',
                 disabled=False),
            dict(name='file',
                 label=_("File"),
                 selected=log_type == 'file',
                 disabled=False),
            dict(name='syslog',
                 label=_("Syslog"),
                 selected=log_type in ('unix', 'syslog'),
                 disabled=os.name != 'posix'),
            dict(name='eventlog',
                 label=_("Windows event log"),
                 selected=log_type in ('winlog', 'eventlog', 'nteventlog'),
                 disabled=os.name != 'nt'),
        ]

        if req.method == 'POST':
            changed = False

            new_type = req.args.get('log_type')
            if new_type not in [t['name'] for t in log_types]:
                raise TracError(_("Unknown log type %(type)s", type=new_type),
                                _("Invalid log type"))
            new_file = req.args.get('log_file', 'trac.log')
            if not new_file:
                raise TracError(_("You must specify a log file"),
                                _("Missing field"))
            new_level = req.args.get('log_level')
            if new_level not in LOG_LEVELS:
                raise TracError(
                    _("Unknown log level %(level)s", level=new_level),
                    _("Invalid log level"))

            # Create logger to be sure the configuration is valid.
            new_file_path = new_file
            if not os.path.isabs(new_file_path):
                new_file_path = os.path.join(self.env.log_dir, new_file)
            try:
                logger, handler = \
                    self.env.create_logger(new_type, new_file_path, new_level,
                                           self.env.log_format)
            except Exception as e:
                add_warning(
                    req,
                    tag_(
                        "Changes not saved. Logger configuration "
                        "error: %(error)s. Inspect the log for more "
                        "information.",
                        error=tag.code(exception_to_unicode(e))))
                self.log.error("Logger configuration error: %s",
                               exception_to_unicode(e, traceback=True))
            else:
                handler.close()
                if new_type != log_type:
                    self.config.set('logging', 'log_type', new_type)
                    changed = True
                    log_type = new_type

                if log_type == 'none':
                    self.config.remove('logging', 'log_level')
                    changed = True
                else:
                    if new_level != log_level:
                        self.config.set('logging', 'log_level', new_level)
                        changed = True
                        log_level = new_level

                if log_type == 'file':
                    if new_file != log_file:
                        self.config.set('logging', 'log_file', new_file)
                        changed = True
                        log_file = new_file
                else:
                    self.config.remove('logging', 'log_file')
                    changed = True

            if changed:
                _save_config(self.config, req, self.log),
            req.redirect(req.href.admin(cat, page))

        data = {
            'type': log_type,
            'types': log_types,
            'level': log_level,
            'levels': LOG_LEVELS,
            'file': log_file,
            'dir': log_dir
        }
        return 'admin_logging.html', {'log': data}
Пример #47
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
Пример #48
0
    def _do_login(self, req):
        """Log the remote user in.

        This function expects to be called when the remote user name
        is available. The user name is inserted into the `auth_cookie`
        table and a cookie identifying the user on subsequent requests
        is sent back to the client.

        If the Authenticator was created with `ignore_case` set to
        true, then the authentication name passed from the web server
        in req.remote_user will be converted to lower case before
        being used. This is to avoid problems on installations
        authenticating against Windows which is not case sensitive
        regarding user names and domain names
        """
        if not req.remote_user:
            # TRANSLATOR: ... refer to the 'installation documentation'. (link)
            inst_doc = tag.a(_("installation documentation"),
                             title=_("Configuring Authentication"),
                             href=req.href.wiki('TracInstall') +
                             "#ConfiguringAuthentication")
            raise TracError(
                tag_(
                    "Authentication information not available. "
                    "Please refer to the %(inst_doc)s.",
                    inst_doc=inst_doc))
        remote_user = req.remote_user
        if self.ignore_case:
            remote_user = remote_user.lower()

        if req.authname not in ('anonymous', remote_user):
            raise TracError(
                _("Already logged in as %(user)s.", user=req.authname))

        with self.env.db_transaction as db:
            # Delete cookies older than 10 days
            db("DELETE FROM auth_cookie WHERE time < %s",
               (int(time.time()) - 86400 * 10, ))
            # Insert a new cookie if we haven't already got one
            cookie = None
            trac_auth = req.incookie.get('trac_auth')
            if trac_auth is not None:
                name = self._cookie_to_name(req, trac_auth)
                cookie = trac_auth.value if name == remote_user else None
            if cookie is None:
                cookie = hex_entropy()
                db(
                    """
                    INSERT INTO auth_cookie (cookie, name, ipnr, time)
                         VALUES (%s, %s, %s, %s)
                   """,
                    (cookie, remote_user, req.remote_addr, int(time.time())))
        req.authname = remote_user
        req.outcookie['trac_auth'] = cookie
        if self.auth_cookie_domain:
            req.outcookie['trac_auth']['domain'] = self.auth_cookie_domain
        req.outcookie['trac_auth']['path'] = self.auth_cookie_path \
                                             or req.base_path or '/'
        if self.env.secure_cookies:
            req.outcookie['trac_auth']['secure'] = True
        req.outcookie['trac_auth']['httponly'] = True
        if self.auth_cookie_lifetime > 0:
            req.outcookie['trac_auth']['expires'] = self.auth_cookie_lifetime
Пример #49
0
                'title': title, 'description': description,
                'max': limit, 'args': args, 'show_args_form': False,
                'message': None, 'paginator': None,
                'report_href': report_href,
                }

        res = None
        with self.env.db_query as db:
            res = self.execute_paginated_report(req, db, id, sql, args, limit,
                                                offset)

        if len(res) == 2:
             e, sql = res
             data['message'] = \
                 tag_("Report execution failed: %(error)s %(sql)s",
                      error=tag.pre(exception_to_unicode(e)),
                      sql=tag(tag.hr(),
                              tag.pre(sql, style="white-space: pre")))
             return 'report_view.html', data, None

        cols, results, num_items, missing_args, limit_offset = res
        need_paginator = limit > 0 and limit_offset
        need_reorder = limit_offset is None
        results = [list(row) for row in results]
        numrows = len(results)

        paginator = None
        if need_paginator:
            paginator = Paginator(results, page - 1, limit, num_items)
            data['paginator'] = paginator
            if paginator.has_next_page:
                add_link(req, 'next', report_href(page=page + 1),
Пример #50
0
    def process_request(self, req):
        parent_id = None
        parent_realm = req.args.get('realm')
        path = req.args.get('path')
        filename = None

        if not parent_realm or not path:
            raise HTTPBadRequest(_('Bad request'))
        if parent_realm == 'attachment':
            raise TracError(
                tag_("%(realm)s is not a valid parent realm",
                     realm=tag.code(parent_realm)))

        parent_realm = Resource(parent_realm)
        action = req.args.get('action', 'view')
        if action == 'new':
            parent_id = path.rstrip('/')
        else:
            last_slash = path.rfind('/')
            if last_slash == -1:
                parent_id, filename = path, ''
            else:
                parent_id, filename = path[:last_slash], path[last_slash + 1:]

        parent = parent_realm(id=parent_id)
        if not resource_exists(self.env, parent):
            raise ResourceNotFound(
                _("Parent resource %(parent)s doesn't exist",
                  parent=get_resource_name(self.env, parent)))

        # Link the attachment page to parent resource
        parent_name = get_resource_name(self.env, parent)
        parent_url = get_resource_url(self.env, parent, req.href)
        add_link(req, 'up', parent_url, parent_name)
        add_ctxtnav(req, _('Back to %(parent)s', parent=parent_name),
                    parent_url)

        if not filename:  # there's a trailing '/'
            if req.args.get('format') == 'zip':
                self._download_as_zip(req, parent)
            elif action != 'new':
                return self._render_list(req, parent)

        attachment = Attachment(self.env, parent.child(self.realm, filename))

        if req.method == 'POST':
            if action == 'new':
                data = self._do_save(req, attachment)
            elif action == 'delete':
                self._do_delete(req, attachment)
            else:
                raise HTTPBadRequest(_("Invalid request arguments."))
        elif action == 'delete':
            data = self._render_confirm_delete(req, attachment)
        elif action == 'new':
            data = self._render_form(req, attachment)
        else:
            data = self._render_view(req, attachment)

        add_stylesheet(req, 'common/css/code.css')
        return 'attachment.html', data, None
Пример #51
0
    def _do_save(self, req, attachment):
        req.perm(attachment.resource).require('ATTACHMENT_CREATE')
        parent_resource = attachment.resource.parent

        if 'cancel' in req.args:
            req.redirect(get_resource_url(self.env, parent_resource, req.href))

        upload = req.args.getfirst('attachment')
        if not hasattr(upload, 'filename') or not upload.filename:
            raise TracError(_("No file uploaded"))
        if hasattr(upload.file, 'fileno'):
            size = os.fstat(upload.file.fileno())[6]
        else:
            upload.file.seek(0, 2)  # seek to end of file
            size = upload.file.tell()
            upload.file.seek(0)
        if size == 0:
            raise TracError(_("Can't upload empty file"))

        # Maximum attachment size (in bytes)
        max_size = self.max_size
        if 0 <= max_size < size:
            raise TracError(
                _("Maximum attachment size: %(num)s",
                  num=pretty_size(max_size)), _("Upload failed"))

        filename = _normalized_filename(upload.filename)
        if not filename:
            raise TracError(_("No file uploaded"))
        # Now the filename is known, update the attachment resource
        attachment.filename = filename
        attachment.description = req.args.get('description', '')
        attachment.author = get_reporter_id(req, 'author')
        attachment.ipnr = req.remote_addr

        # Validate attachment
        valid = True
        for manipulator in self.manipulators:
            for field, message in manipulator.validate_attachment(
                    req, attachment):
                valid = False
                if field:
                    add_warning(
                        req,
                        tag_(
                            "Attachment field %(field)s is invalid: "
                            "%(message)s",
                            field=tag.strong(field),
                            message=message))
                else:
                    add_warning(
                        req,
                        tag_("Invalid attachment: %(message)s",
                             message=message))
        if not valid:
            # Display the attach form with pre-existing data
            # NOTE: Local file path not known, file field cannot be repopulated
            add_warning(req, _('Note: File must be selected again.'))
            data = self._render_form(req, attachment)
            data['is_replace'] = req.args.get('replace')
            return data

        if req.args.get('replace'):
            try:
                old_attachment = Attachment(self.env,
                                            attachment.resource(id=filename))
                if not (req.authname and req.authname != 'anonymous'
                        and old_attachment.author == req.authname) \
                   and 'ATTACHMENT_DELETE' \
                                        not in req.perm(attachment.resource):
                    raise PermissionError(msg=_(
                        "You don't have permission to "
                        "replace the attachment %(name)s. You can only "
                        "replace your own attachments. Replacing other's "
                        "attachments requires ATTACHMENT_DELETE permission.",
                        name=filename))
                if (not attachment.description.strip()
                        and old_attachment.description):
                    attachment.description = old_attachment.description
                old_attachment.delete()
            except TracError:
                pass  # don't worry if there's nothing to replace
        attachment.insert(filename, upload.file, size)

        req.redirect(
            get_resource_url(self.env, attachment.resource(id=None), req.href))
Пример #52
0
    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 comment is invalid: "
                             "%(message)s",
                             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)))
Пример #53
0
    def render_admin_panel(self, req, category, page, path_info):
        req.perm.require('VERSIONCONTROL_ADMIN')

        # Retrieve info for all repositories
        rm = RepositoryManager(self.env)
        all_repos = rm.get_all_repositories()
        db_provider = self.env[DbRepositoryProvider]

        if path_info:
            # Detail view
            reponame = path_info if not is_default(path_info) else ''
            info = all_repos.get(reponame)
            if info is None:
                raise TracError(
                    _("Repository '%(repo)s' not found", repo=path_info))
            if req.method == 'POST':
                if req.args.get('cancel'):
                    req.redirect(req.href.admin(category, page))

                elif db_provider and req.args.get('save'):
                    # Modify repository
                    changes = {}
                    for field in db_provider.repository_attrs:
                        value = normalize_whitespace(req.args.get(field))
                        if (value is not None or field == 'hidden') \
                                and value != info.get(field):
                            changes[field] = value
                    if 'dir' in changes \
                            and not self._check_dir(req, changes['dir']):
                        changes = {}
                    if changes:
                        db_provider.modify_repository(reponame, changes)
                        add_notice(req, _('Your changes have been saved.'))
                    name = req.args.get('name')
                    resync = tag.tt('trac-admin $ENV repository resync "%s"' %
                                    (name or '(default)'))
                    if 'dir' in changes:
                        msg = tag_(
                            'You should now run %(resync)s to '
                            'synchronize Trac with the repository.',
                            resync=resync)
                        add_notice(req, msg)
                    elif 'type' in changes:
                        msg = tag_(
                            'You may have to run %(resync)s to '
                            'synchronize Trac with the repository.',
                            resync=resync)
                        add_notice(req, msg)
                    if name and name != path_info and not 'alias' in info:
                        cset_added = tag.tt('trac-admin $ENV changeset '
                                            'added "%s" $REV' %
                                            (name or '(default)'))
                        msg = tag_(
                            'You will need to update your post-commit '
                            'hook to call %(cset_added)s with the new '
                            'repository name.',
                            cset_added=cset_added)
                        add_notice(req, msg)
                    if changes:
                        req.redirect(req.href.admin(category, page))

            Chrome(self.env).add_wiki_toolbars(req)
            data = {'view': 'detail', 'reponame': reponame}

        else:
            # List view
            if req.method == 'POST':
                # Add a repository
                if db_provider and req.args.get('add_repos'):
                    name = req.args.get('name')
                    type_ = req.args.get('type')
                    # Avoid errors when copy/pasting paths
                    dir = normalize_whitespace(req.args.get('dir', ''))
                    if name is None or type_ is None or not dir:
                        add_warning(
                            req, _('Missing arguments to add a '
                                   'repository.'))
                    elif self._check_dir(req, dir):
                        db_provider.add_repository(name, dir, type_)
                        name = name or '(default)'
                        add_notice(
                            req,
                            _('The repository "%(name)s" has been '
                              'added.',
                              name=name))
                        resync = tag.tt('trac-admin $ENV repository resync '
                                        '"%s"' % name)
                        msg = tag_(
                            'You should now run %(resync)s to '
                            'synchronize Trac with the repository.',
                            resync=resync)
                        add_notice(req, msg)
                        cset_added = tag.tt('trac-admin $ENV changeset '
                                            'added "%s" $REV' % name)
                        msg = tag_(
                            'You should also set up a post-commit hook '
                            'on the repository to call %(cset_added)s '
                            'for each committed changeset.',
                            cset_added=cset_added)
                        add_notice(req, msg)
                        req.redirect(req.href.admin(category, page))

                # Add a repository alias
                elif db_provider and req.args.get('add_alias'):
                    name = req.args.get('name')
                    alias = req.args.get('alias')
                    if name is not None and alias is not None:
                        db_provider.add_alias(name, alias)
                        add_notice(
                            req,
                            _('The alias "%(name)s" has been '
                              'added.',
                              name=name or '(default)'))
                        req.redirect(req.href.admin(category, page))
                    add_warning(req, _('Missing arguments to add an '
                                       'alias.'))

                # Refresh the list of repositories
                elif req.args.get('refresh'):
                    req.redirect(req.href.admin(category, page))

                # Remove repositories
                elif db_provider and req.args.get('remove'):
                    sel = req.args.getlist('sel')
                    if sel:
                        for name in sel:
                            db_provider.remove_repository(name)
                        add_notice(
                            req,
                            _('The selected repositories have '
                              'been removed.'))
                        req.redirect(req.href.admin(category, page))
                    add_warning(req, _('No repositories were selected.'))

            data = {'view': 'list'}

        # Find repositories that are editable
        db_repos = {}
        if db_provider is not None:
            db_repos = dict(db_provider.get_repositories())

        # Prepare common rendering data
        repositories = dict(
            (reponame,
             self._extend_info(reponame, info.copy(), reponame in db_repos))
            for (reponame, info) in all_repos.iteritems())
        types = sorted([''] + rm.get_supported_types())
        data.update({
            'types': types,
            'default_type': rm.repository_type,
            'repositories': repositories
        })

        return 'admin_repositories.html', data
Пример #54
0
    def process_request(self, req):
        realm = req.args['realm']
        id_ = req.args['id']

        if not which(self.dot_path):
            raise TracError(
                _("Path to dot executable is invalid: %(path)s",
                  path=self.dot_path))

        # Urls to generate the depgraph for a ticket is /depgraph/ticketnum
        # Urls to generate the depgraph for a milestone is
        # /depgraph/milestone/milestone_name

        # List of tickets to generate the depgraph.
        if realm == 'milestone':
            # We need to query the list of tickets in the milestone
            query = Query(self.env, constraints={'milestone': [id_]}, max=0)
            tkt_ids = [fields['id'] for fields in query.execute(req)]
        else:
            tid = as_int(id_, None)
            if tid is None:
                raise TracError(
                    tag_("%(id)s is not a valid ticket id.", id=html.tt(id_)))
            tkt_ids = [tid]

        # The summary argument defines whether we place the ticket id or
        # its summary in the node's label
        label_summary = 0
        if 'summary' in req.args:
            label_summary = int(req.args.get('summary'))

        g = self._build_graph(req, tkt_ids, label_summary=label_summary)
        if req.path_info.endswith('/depgraph.png') or 'format' in req.args:
            format_ = req.args.get('format')
            if format_ == 'text':
                # In case g.__str__ returns unicode, convert it in ascii
                req.send(
                    to_unicode(g).encode('ascii', 'replace'), 'text/plain')
            elif format_ == 'debug':
                import pprint

                req.send(
                    pprint.pformat(
                        [TicketLinks(self.env, tkt_id) for tkt_id in tkt_ids]),
                    'text/plain')
            elif format_ is not None:
                if format_ in self.acceptable_formats:
                    req.send(g.render(self.dot_path, format_), 'text/plain')
                else:
                    raise TracError(
                        _("The %(format)s format is not allowed.",
                          format=format_))

            if self.use_gs:
                ps = g.render(self.dot_path, 'ps2')
                gs = subprocess.Popen([
                    self.gs_path, '-q', '-dTextAlphaBits=4',
                    '-dGraphicsAlphaBits=4', '-sDEVICE=png16m',
                    '-sOutputFile=%stdout%', '-'
                ],
                                      stdin=subprocess.PIPE,
                                      stdout=subprocess.PIPE,
                                      stderr=subprocess.PIPE)
                img, err = gs.communicate(ps)
                if err:
                    self.log.debug('MasterTickets: Error from gs: %s', err)
            else:
                img = g.render(self.dot_path)
            req.send(img, 'image/png')
        else:
            data = {}

            # Add a context link to enable/disable labels in nodes.
            if label_summary:
                add_ctxtnav(req, 'Without labels',
                            req.href(req.path_info, summary=0))
            else:
                add_ctxtnav(req, 'With labels',
                            req.href(req.path_info, summary=1))

            if realm == 'milestone':
                add_ctxtnav(req, 'Back to Milestone: %s' % id_,
                            req.href.milestone(id_))
                data['milestone'] = id_
            else:
                data['ticket'] = id_
                add_ctxtnav(req, 'Back to Ticket #%s' % id_,
                            req.href.ticket(id_))
            data['graph'] = g
            data['graph_render'] = functools.partial(g.render, self.dot_path)
            data['use_gs'] = self.use_gs

            return 'depgraph.html', data, None
Пример #55
0
    def expand_macro(self, formatter, name, content):
        req = formatter.req
        query_string, kwargs, format = self.parse_args(content)
        if query_string:
            query_string += '&'
        query_string += '&'.join('%s=%s' % item
                                 for item in kwargs.iteritems())

        env = ProductEnvironment.lookup_global_env(self.env)
        query = ProductQuery.from_string(env, query_string)

        if format == 'count':
            cnt = query.count(req)
            return tag.span(cnt, title='%d tickets for which %s' %
                            (cnt, query_string), class_='query_count')

        tickets = query.execute(req)

        if format == 'table':
            data = query.template_data(formatter.context, tickets,
                                       req=formatter.context.req)

            add_stylesheet(req, 'common/css/report.css')

            return Chrome(env).render_template(
                req, 'query_results.html', data, None, fragment=True)

        if format == 'progress':
            from trac.ticket.roadmap import (RoadmapModule,
                                             apply_ticket_permissions,
                                             get_ticket_stats,
                                             grouped_stats_data)

            add_stylesheet(req, 'common/css/roadmap.css')

            def query_href(extra_args, group_value = None):
                q = ProductQuery.from_string(env, query_string)
                if q.group:
                    extra_args[q.group] = group_value
                    q.group = None
                for constraint in q.constraints:
                    constraint.update(extra_args)
                if not q.constraints:
                    q.constraints.append(extra_args)
                return q.get_href(formatter.context)
            chrome = Chrome(env)
            tickets = apply_ticket_permissions(env, req, tickets)
            stats_provider = RoadmapModule(env).stats_provider
            by = query.group
            if not by:
                stat = get_ticket_stats(stats_provider, tickets)
                data = {
                    'stats': stat,
                    'stats_href': query_href(stat.qry_args),
                    'interval_hrefs': [query_href(interval['qry_args'])
                                       for interval in stat.intervals],
                    'legend': True,
                }
                return tag.div(
                    chrome.render_template(req, 'progress_bar.html', data,
                                           None, fragment=True),
                    class_='trac-progress')

            def per_group_stats_data(gstat, group_name):
                return {
                    'stats': gstat,
                    'stats_href': query_href(gstat.qry_args,  group_name),
                    'interval_hrefs': [query_href(interval['qry_args'],
                                                  group_name)
                                       for interval in gstat.intervals],
                    'percent': '%d / %d' % (gstat.done_count,
                                            gstat.count),
                    'legend': False,
                }

            groups = grouped_stats_data(env, stats_provider, tickets, by,
                                        per_group_stats_data)
            data = {
                'groups': groups, 'grouped_by': by,
                'summary': _("Ticket completion status for each %(group)s",
                             group=by),
            }
            return tag.div(
                chrome.render_template(req, 'progress_bar_grouped.html', data,
                                       None, fragment=True),
                class_='trac-groupprogress')

        # Formats above had their own permission checks, here we need to
        # do it explicitly:

        tickets = [t for t in tickets
                   if 'TICKET_VIEW' in req.perm('ticket', t['id'])]

        if not tickets:
            return tag.span(_("No results"), class_='query_no_results')

        # Cache resolved href targets
        hrefcache = {}

        def ticket_anchor(ticket):
            try:
                pvalue = ticket.get('product') or GLOBAL_PRODUCT
                envhref = hrefcache[pvalue]
            except KeyError:
                try:
                    env = lookup_product_env(self.env, prefix= pvalue,
                                             name=pvalue)
                except LookupError:
                    return tag.a('#%s' % ticket['id'], 
                                 class_='missing product')
                hrefcache[pvalue] = envhref = resolve_product_href(
                         to_env=env, at_env=self.env)
            return tag.a('#%s' % ticket['id'],
                         class_=ticket['status'],
                         href=envhref.ticket(int(ticket['id'])),
                         title=shorten_line(ticket['summary']))

        def ticket_groups():
            groups = []
            for v, g in groupby(tickets, lambda t: t[query.group]):
                q = ProductQuery.from_string(env, query_string)
                # produce the hint for the group
                q.group = q.groupdesc = None
                order = q.order
                q.order = None
                title = _("%(groupvalue)s %(groupname)s tickets matching "
                          "%(query)s", groupvalue=v, groupname=query.group,
                          query=q.to_string())
                # produce the href for the query corresponding to the group
                for constraint in q.constraints:
                    constraint[str(query.group)] = v
                q.order = order
                href = q.get_href(formatter.context)
                groups.append((v, [t for t in g], href, title))
            return groups

        if format == 'compact':
            if query.group:
                groups = [(v, ' ',
                           tag.a('#%s' % u',\u200b'.join(str(t['id'])
                                                         for t in g),
                                 href=href, class_='query', title=title))
                          for v, g, href, title in ticket_groups()]
                return tag(groups[0], [(', ', g) for g in groups[1:]])
            else:
                alist = [ticket_anchor(ticket) for ticket in tickets]
                return tag.span(alist[0], *[(', ', a) for a in alist[1:]])
        else:
            if query.group:
                return tag.div(
                    [(tag.p(tag_('%(groupvalue)s %(groupname)s tickets:',
                                 groupvalue=tag.a(v, href=href, class_='query',
                                                  title=title),
                                 groupname=query.group)),
                      tag.dl([(tag.dt(ticket_anchor(t)),
                               tag.dd(t['summary'])) for t in g],
                             class_='wiki compact'))
                     for v, g, href, title in ticket_groups()])
            else:
                return tag.div(tag.dl([(tag.dt(ticket_anchor(ticket)),
                                        tag.dd(ticket['summary']))
                                       for ticket in tickets],
                                      class_='wiki compact'))
Пример #56
0
    def render_ticket_action_control(self, req, ticket, action):

        self.log.debug('render_ticket_action_control: action "%s"', action)

        this_action = self.actions[action]
        status = this_action['newstate']
        operations = this_action['operations']
        ticket_owner = ticket._old.get('owner', ticket['owner'])
        ticket_status = ticket._old.get('status', ticket['status'])
        author = get_reporter_id(req, 'author')
        author_info = partial(Chrome(self.env).authorinfo, req,
                              resource=ticket.resource)
        format_author = partial(Chrome(self.env).format_author, req,
                                resource=ticket.resource)
        formatted_current_owner = author_info(ticket_owner)
        exists = ticket_status is not None

        ticket_system = TicketSystem(self.env)
        control = []  # default to nothing
        hints = []
        if 'reset_workflow' in operations:
            control.append(_("from invalid state"))
            hints.append(_("Current state no longer exists"))
        if 'del_owner' in operations:
            hints.append(_("The ticket will be disowned"))
        if 'set_owner' in operations or 'may_set_owner' in operations:
            owners = self.get_allowed_owners(req, ticket, this_action)

            if 'set_owner' in operations:
                default_owner = author
            elif 'may_set_owner' in operations:
                if not exists:
                    default_owner = ticket_system.default_owner
                else:
                    default_owner = ticket_owner or None
                if owners is not None and default_owner not in owners:
                    owners.insert(0, default_owner)
            else:
                # Protect against future modification for case that another
                # operation is added to the outer conditional
                raise AssertionError(operations)

            id = 'action_%s_reassign_owner' % action

            if not owners:
                owner = req.args.get(id, default_owner)
                control.append(
                    tag_("to %(owner)s",
                         owner=tag.input(type='text', id=id, name=id,
                                         value=owner)))
                if not exists or ticket_owner is None:
                    hints.append(_("The owner will be the specified user"))
                else:
                    hints.append(tag_("The owner will be changed from "
                                      "%(current_owner)s to the specified "
                                      "user",
                                      current_owner=formatted_current_owner))
            elif len(owners) == 1:
                owner = tag.input(type='hidden', id=id, name=id,
                                  value=owners[0])
                formatted_new_owner = author_info(owners[0])
                control.append(tag_("to %(owner)s",
                                    owner=tag(formatted_new_owner, owner)))
                if not exists or ticket_owner is None:
                    hints.append(tag_("The owner will be %(new_owner)s",
                                      new_owner=formatted_new_owner))
                elif ticket['owner'] != owners[0]:
                    hints.append(tag_("The owner will be changed from "
                                      "%(current_owner)s to %(new_owner)s",
                                      current_owner=formatted_current_owner,
                                      new_owner=formatted_new_owner))
            else:
                selected_owner = req.args.get(id, default_owner)
                control.append(tag_("to %(owner)s", owner=tag.select(
                    [tag.option(label, value=value if value is not None else '',
                                selected=(value == selected_owner or None))
                     for label, value in sorted((format_author(owner), owner)
                                                for owner in owners)],
                    id=id, name=id)))
                if not exists or ticket_owner is None:
                    hints.append(_("The owner will be the selected user"))
                else:
                    hints.append(tag_("The owner will be changed from "
                                      "%(current_owner)s to the selected user",
                                      current_owner=formatted_current_owner))
        elif 'set_owner_to_self' in operations:
            formatted_author = author_info(author)
            if not exists or ticket_owner is None:
                hints.append(tag_("The owner will be %(new_owner)s",
                                  new_owner=formatted_author))
            elif ticket_owner != author:
                hints.append(tag_("The owner will be changed from "
                                  "%(current_owner)s to %(new_owner)s",
                                  current_owner=formatted_current_owner,
                                  new_owner=formatted_author))
            elif ticket_status != status:
                hints.append(tag_("The owner will remain %(current_owner)s",
                                  current_owner=formatted_current_owner))
        if 'set_resolution' in operations:
            resolutions = [r.name for r in Resolution.select(self.env)]
            if 'set_resolution' in this_action:
                valid_resolutions = set(resolutions)
                resolutions = this_action['set_resolution']
                if any(x not in valid_resolutions for x in resolutions):
                    raise ConfigurationError(_(
                        "Your workflow attempts to set a resolution but uses "
                        "undefined resolutions (configuration issue, please "
                        "contact your Trac admin)."))
            if not resolutions:
                raise ConfigurationError(_(
                    "Your workflow attempts to set a resolution but none is "
                    "defined (configuration issue, please contact your Trac "
                    "admin)."))
            id = 'action_%s_resolve_resolution' % action
            if len(resolutions) == 1:
                resolution = tag.input(type='hidden', id=id, name=id,
                                       value=resolutions[0])
                control.append(tag_("as %(resolution)s",
                                    resolution=tag(resolutions[0],
                                                   resolution)))
                hints.append(tag_("The resolution will be set to %(name)s",
                                  name=resolutions[0]))
            else:
                selected_option = req.args.get(id,
                                               ticket_system.default_resolution)
                control.append(tag_("as %(resolution)s",
                                    resolution=tag.select(
                    [tag.option(x, value=x,
                                selected=(x == selected_option or None))
                     for x in resolutions],
                    id=id, name=id)))
                hints.append(_("The resolution will be set"))
        if 'del_resolution' in operations:
            hints.append(_("The resolution will be deleted"))
        if 'leave_status' in operations:
            control.append(tag_("as %(status)s", status=ticket_status))
            if len(operations) == 1:
                hints.append(tag_("The owner will remain %(current_owner)s",
                                  current_owner=formatted_current_owner)
                             if ticket_owner else
                             _("The ticket will remain with no owner"))
        elif not operations:
            if status != '*':
                if ticket['status'] is None:
                    hints.append(tag_("The status will be '%(name)s'",
                                      name=status))
                else:
                    hints.append(tag_("Next status will be '%(name)s'",
                                      name=status))
        return (this_action['label'], tag(separated(control, ' ')),
                tag(separated(hints, '. ', '.') if hints else ''))
Пример #57
0
    def expand_macro(self, formatter, name, content, args=None):
        if content is not None:
            content = content.strip()
        if not args and not content:
            raw_actions = self.config.options('ticket-workflow')
        else:
            is_macro = args is None
            if is_macro:
                kwargs = parse_args(content)[1]
                file = kwargs.get('file')
            else:
                file = args.get('file')
                if not file and not content:
                    raise ProcessorError("Invalid argument(s).")

            if file:
                print(file)
                text = RepositoryManager(self.env).read_file_by_path(file)
                if text is None:
                    raise ProcessorError(
                        tag_("The file %(file)s does not exist.",
                             file=tag.code(file)))
            elif is_macro:
                text = '\n'.join(line.lstrip() for line in content.split(';'))
            else:
                text = content

            if '[ticket-workflow]' not in text:
                text = '[ticket-workflow]\n' + text
            parser = RawConfigParser()
            try:
                parser.readfp(io.StringIO(text))
            except ParsingError as e:
                if is_macro:
                    raise MacroError(exception_to_unicode(e))
                else:
                    raise ProcessorError(exception_to_unicode(e))
            raw_actions = list(parser.items('ticket-workflow'))
        actions = parse_workflow_config(raw_actions)
        states = list(
            {state for action in actions.itervalues()
                   for state in action['oldstates']} |
            {action['newstate'] for action in actions.itervalues()})
        action_labels = [attrs['label'] for attrs in actions.values()]
        action_names = list(actions)
        edges = []
        for name, action in actions.items():
            new_index = states.index(action['newstate'])
            name_index = action_names.index(name)
            for old_state in action['oldstates']:
                old_index = states.index(old_state)
                edges.append((old_index, new_index, name_index))

        args = args or {}
        width = args.get('width', 800)
        height = args.get('height', 600)
        graph = {'nodes': states, 'actions': action_labels, 'edges': edges,
                 'width': width, 'height': height}
        graph_id = '%012x' % id(graph)
        req = formatter.req
        add_script(req, 'common/js/excanvas.js', ie_if='IE')
        add_script(req, 'common/js/workflow_graph.js')
        add_script_data(req, {'graph_%s' % graph_id: graph})
        return tag(
            tag.div('', class_='trac-workflow-graph trac-noscript',
                    id='trac-workflow-graph-%s' % graph_id,
                    style="display:inline-block;width:%spx;height:%spx" %
                          (width, height)),
            tag.noscript(
                tag.div(_("Enable JavaScript to display the workflow graph."),
                        class_='system-message')))
Пример #58
0
    def render_property_diff(self, name, old_context, old_props, new_context,
                             new_props, options):
        # Build 5 columns table showing modifications on merge sources
        # || source || added || removed || added (ni) || removed (ni) ||
        # || source || removed                                        ||
        rm = RepositoryManager(self.env)
        repos = rm.get_repository(old_context.resource.parent.id)

        def parse_sources(props):
            sources = {}
            value = props[name]
            lines = value.splitlines() if name == 'svn:mergeinfo' \
                                       else value.split()
            for line in lines:
                path, revs = line.split(':', 1)
                spath = _path_within_scope(repos.scope, path)
                if spath is not None:
                    inheritable, non_inheritable = _partition_inheritable(revs)
                    sources[spath] = (set(Ranges(inheritable)),
                                      set(Ranges(non_inheritable)))
            return sources

        old_sources = parse_sources(old_props)
        new_sources = parse_sources(new_props)
        # Go through new sources, detect modified ones or added ones
        blocked = name.endswith('blocked')
        added_label = [_("merged: "), _("blocked: ")][blocked]
        removed_label = [_("reverse-merged: "), _("un-blocked: ")][blocked]
        added_ni_label = _("marked as non-inheritable: ")
        removed_ni_label = _("unmarked as non-inheritable: ")

        sources = []
        changed_revs = {}
        changed_nodes = []
        for spath, (new_revs, new_revs_ni) in new_sources.iteritems():
            new_spath = spath not in old_sources
            if new_spath:
                old_revs = old_revs_ni = set()
            else:
                old_revs, old_revs_ni = old_sources.pop(spath)
            added = new_revs - old_revs
            removed = old_revs - new_revs
            # unless new revisions differ from old revisions
            if not added and not removed:
                continue
            added_ni = new_revs_ni - old_revs_ni
            removed_ni = old_revs_ni - new_revs_ni
            revs = sorted(added | removed | added_ni | removed_ni)
            try:
                node = repos.get_node(spath, revs[-1])
                changed_nodes.append((node, revs[0]))
            except NoSuchNode:
                pass
            sources.append(
                (spath, new_spath, added, removed, added_ni, removed_ni))
        if changed_nodes:
            changed_revs = repos._get_changed_revs(changed_nodes)

        def revs_link(revs, context):
            if revs:
                revs = to_ranges(revs)
                return _get_revs_link(revs.replace(',', u',\u200b'), context,
                                      spath, revs)

        modified_sources = []
        for spath, new_spath, added, removed, added_ni, removed_ni in sources:
            if spath in changed_revs:
                revs = set(changed_revs[spath])
                added &= revs
                removed &= revs
                added_ni &= revs
                removed_ni &= revs
            if added or removed:
                if new_spath:
                    status = _(" (added)")
                else:
                    status = None
                modified_sources.append(
                    (spath, [_get_source_link(spath, new_context),
                             status], added
                     and tag(added_label, revs_link(added, new_context)),
                     removed
                     and tag(removed_label, revs_link(removed, old_context)),
                     added_ni
                     and tag(added_ni_label, revs_link(added_ni, new_context)),
                     removed_ni and tag(removed_ni_label,
                                        revs_link(removed_ni, old_context))))
        # Go through remaining old sources, those were deleted
        removed_sources = []
        for spath, old_revs in old_sources.iteritems():
            removed_sources.append(
                (spath, _get_source_link(spath, old_context)))
        if modified_sources or removed_sources:
            modified_sources.sort()
            removed_sources.sort()
            changes = tag.table(tag.tbody([
                tag.tr(tag.td(c) for c in cols[1:])
                for cols in modified_sources
            ], [
                tag.tr(tag.td(src), tag.td(_('removed'), colspan=4))
                for spath, src in removed_sources
            ]),
                                class_='props')
        else:
            changes = tag.em(_(' (with no actual effect on merging)'))
        return tag.li(tag_('Property %(prop)s changed', prop=tag.strong(name)),
                      changes)
Пример #59
0
    def render_admin_panel(self, req, category, page, path_info):
        # Retrieve info for all repositories
        rm = RepositoryManager(self.env)
        all_repos = rm.get_all_repositories()
        db_provider = self.env[DbRepositoryProvider]

        if path_info:
            # Detail view
            reponame = path_info if not is_default(path_info) else ''
            info = all_repos.get(reponame)
            if info is None:
                raise TracError(
                    _("Repository '%(repo)s' not found", repo=path_info))
            if req.method == 'POST':
                if req.args.get('cancel'):
                    req.redirect(req.href.admin(category, page))

                elif db_provider and req.args.get('save'):
                    # Modify repository
                    changes = {}
                    valid = True
                    for field in db_provider.repository_attrs:
                        value = normalize_whitespace(req.args.get(field))
                        if (value is not None
                            or field in ('hidden', 'sync_per_request')) \
                                and value != info.get(field):
                            changes[field] = value
                    if 'dir' in changes and not \
                            self._check_dir(req, changes['dir']):
                        valid = False
                    if valid and changes:
                        db_provider.modify_repository(reponame, changes)
                        add_notice(req, _('Your changes have been saved.'))
                        name = req.args.get('name')
                        pretty_name = name or '(default)'
                        resync = tag.code('trac-admin "%s" repository resync '
                                          '"%s"' %
                                          (self.env.path, pretty_name))
                        if 'dir' in changes:
                            msg = tag_(
                                'You should now run %(resync)s to '
                                'synchronize Trac with the repository.',
                                resync=resync)
                            add_notice(req, msg)
                        elif 'type' in changes:
                            msg = tag_(
                                'You may have to run %(resync)s to '
                                'synchronize Trac with the repository.',
                                resync=resync)
                            add_notice(req, msg)
                        if name and name != path_info and 'alias' not in info:
                            cset_added = tag.code('trac-admin "%s" changeset '
                                                  'added "%s" $REV' %
                                                  (self.env.path, pretty_name))
                            msg = tag_(
                                'You will need to update your '
                                'post-commit hook to call '
                                '%(cset_added)s with the new '
                                'repository name.',
                                cset_added=cset_added)
                            add_notice(req, msg)
                    if valid:
                        req.redirect(req.href.admin(category, page))

            chrome = Chrome(self.env)
            chrome.add_wiki_toolbars(req)
            chrome.add_auto_preview(req)
            data = {'view': 'detail', 'reponame': reponame}

        else:
            # List view
            if req.method == 'POST':
                # Add a repository
                if db_provider and req.args.get('add_repos'):
                    name = req.args.get('name')
                    pretty_name = name or '(default)'
                    if name in all_repos:
                        raise TracError(
                            _('The repository "%(name)s" already '
                              'exists.',
                              name=pretty_name))
                    type_ = req.args.get('type')
                    # Avoid errors when copy/pasting paths
                    dir = normalize_whitespace(req.args.get('dir', ''))
                    if name is None or type_ is None or not dir:
                        add_warning(
                            req, _('Missing arguments to add a '
                                   'repository.'))
                    elif self._check_dir(req, dir):
                        db_provider.add_repository(name, dir, type_)
                        add_notice(
                            req,
                            _('The repository "%(name)s" has been '
                              'added.',
                              name=pretty_name))
                        resync = tag.code('trac-admin "%s" repository resync '
                                          '"%s"' %
                                          (self.env.path, pretty_name))
                        msg = tag_(
                            'You should now run %(resync)s to '
                            'synchronize Trac with the repository.',
                            resync=resync)
                        add_notice(req, msg)
                        cset_added = tag.code('trac-admin "%s" changeset '
                                              'added "%s" $REV' %
                                              (self.env.path, pretty_name))
                        doc = tag.a(_("documentation"),
                                    href=req.href.wiki('TracRepositoryAdmin') +
                                    '#Synchronization')
                        msg = tag_(
                            'You should also set up a post-commit hook '
                            'on the repository to call %(cset_added)s '
                            'for each committed changeset. See the '
                            '%(doc)s for more information.',
                            cset_added=cset_added,
                            doc=doc)
                        add_notice(req, msg)

                # Add a repository alias
                elif db_provider and req.args.get('add_alias'):
                    name = req.args.get('name')
                    pretty_name = name or '(default)'
                    alias = req.args.get('alias')
                    if name is not None and alias is not None:
                        try:
                            db_provider.add_alias(name, alias)
                        except self.env.db_exc.IntegrityError:
                            raise TracError(
                                _('The alias "%(name)s" already '
                                  'exists.',
                                  name=pretty_name))
                        add_notice(
                            req,
                            _('The alias "%(name)s" has been '
                              'added.',
                              name=pretty_name))
                    else:
                        add_warning(req,
                                    _('Missing arguments to add an '
                                      'alias.'))

                # Refresh the list of repositories
                elif req.args.get('refresh'):
                    pass

                # Remove repositories
                elif db_provider and req.args.get('remove'):
                    sel = req.args.getlist('sel')
                    if sel:
                        for name in sel:
                            db_provider.remove_repository(name)
                        add_notice(
                            req,
                            _('The selected repositories have '
                              'been removed.'))
                    else:
                        add_warning(req, _('No repositories were selected.'))

                req.redirect(req.href.admin(category, page))

            data = {'view': 'list'}

        # Find repositories that are editable
        db_repos = {}
        if db_provider is not None:
            db_repos = dict(db_provider.get_repositories())

        # Prepare common rendering data
        repositories = {
            reponame: self._extend_info(reponame, info.copy(), reponame
                                        in db_repos)
            for (reponame, info) in all_repos.iteritems()
        }
        types = sorted([''] + rm.get_supported_types())
        data.update({
            'types':
            types,
            'default_type':
            rm.default_repository_type,
            'repositories':
            repositories,
            'can_add_alias':
            any('alias' not in info for info in repositories.itervalues())
        })

        return 'admin_repositories.html', data
Пример #60
0
    def _render_view(self, req, id):
        """Retrieve the report results and pre-process them for rendering."""
        r = Report(self.env, id)
        title, description, sql = r.title, r.description, r.query

        # If this is a saved custom query, redirect to the query module
        #
        # A saved query is either an URL query (?... or query:?...),
        # or a query language expression (query:...).
        #
        # It may eventually contain newlines, for increased clarity.
        #
        query = ''.join(line.strip() for line in sql.splitlines())
        if query and (query[0] == '?' or query.startswith('query:?')):
            query = query if query[0] == '?' else query[6:]
            report_id = 'report=%s' % id
            if 'report=' in query:
                if report_id not in query:
                    err = _('When specified, the report number should be '
                            '"%(num)s".', num=id)
                    req.redirect(req.href.report(id, action='edit', error=err))
            else:
                if query[-1] != '?':
                    query += '&'
                query += report_id
            req.redirect(req.href.query() + quote_query_string(query))
        elif query.startswith('query:'):
            from trac.ticket.query import Query, QuerySyntaxError
            try:
                query = Query.from_string(self.env, query[6:], report=id)
            except QuerySyntaxError as e:
                req.redirect(req.href.report(id, action='edit',
                                             error=to_unicode(e)))
            else:
                req.redirect(query.get_href(req.href))

        format = req.args.get('format')
        if format == 'sql':
            self._send_sql(req, id, title, description, sql)

        title = '{%i} %s' % (id, title)

        report_resource = Resource(self.realm, id)
        req.perm(report_resource).require('REPORT_VIEW')
        context = web_context(req, report_resource)

        page = req.args.getint('page', 1)
        default_max = {'rss': self.items_per_page_rss,
                       'csv': 0, 'tab': 0}.get(format, self.items_per_page)
        max = req.args.getint('max')
        limit = as_int(max, default_max, min=0)  # explict max takes precedence
        offset = (page - 1) * limit

        sort_col = req.args.get('sort', '')
        asc = req.args.getint('asc', 0, min=0, max=1)
        args = {}

        def report_href(**kwargs):
            """Generate links to this report preserving user variables,
            and sorting and paging variables.
            """
            params = args.copy()
            if sort_col:
                params['sort'] = sort_col
            if page != 1:
                params['page'] = page
            if max != default_max:
                params['max'] = max
            params.update(kwargs)
            params['asc'] = 1 if params.get('asc', asc) else None
            return req.href.report(id, params)

        data = {'action': 'view',
                'report': {'id': id, 'resource': report_resource},
                'context': context, 'title': title, 'description': description,
                'max': limit, 'args': args, 'show_args_form': False,
                'message': None, 'paginator': None,
                'report_href': report_href}

        try:
            args = self.get_var_args(req)
            sql = self.get_default_var_args(args, sql)
        except ValueError as e:
            data['message'] = _("Report failed: %(error)s", error=e)
            return 'report_view.html', data, None
        data.update({'args': args, 'title': sub_vars(title, args),
                     'description': sub_vars(description or '', args)})

        try:
            res = self.execute_paginated_report(req, id, sql, args, limit,
                                                offset)
        except TracError as e:
            data['message'] = _("Report failed: %(error)s", error=e)
        else:
            if len(res) == 2:
                e, sql = res
                data['message'] = \
                    tag_("Report execution failed: %(error)s %(sql)s",
                         error=tag.pre(exception_to_unicode(e)),
                         sql=tag(tag.hr(),
                                 tag.pre(sql, style="white-space: pre")))
        if data['message']:
            return 'report_view.html', data, None

        cols, results, num_items, missing_args, limit_offset = res
        need_paginator = limit > 0 and limit_offset
        need_reorder = limit_offset is None
        results = [list(row) for row in results]
        numrows = len(results)

        paginator = None
        if need_paginator:
            paginator = Paginator(results, page - 1, limit, num_items)
            data['paginator'] = paginator
            if paginator.has_next_page:
                add_link(req, 'next', report_href(page=page + 1),
                         _('Next Page'))
            if paginator.has_previous_page:
                add_link(req, 'prev', report_href(page=page - 1),
                         _('Previous Page'))

            pagedata = []
            shown_pages = paginator.get_shown_pages(21)
            for p in shown_pages:
                pagedata.append([report_href(page=p), None, str(p),
                                 _('Page %(num)d', num=p)])
            fields = ['href', 'class', 'string', 'title']
            paginator.shown_pages = [dict(zip(fields, p)) for p in pagedata]
            paginator.current_page = {'href': None, 'class': 'current',
                                      'string': str(paginator.page + 1),
                                      'title': None}
            numrows = paginator.num_items

        # Place retrieved columns in groups, according to naming conventions
        #  * _col_ means fullrow, i.e. a group with one header
        #  * col_ means finish the current group and start a new one

        field_labels = TicketSystem(self.env).get_ticket_field_labels()

        header_groups = [[]]
        for idx, col in enumerate(cols):
            if col in field_labels:
                title = field_labels[col]
            else:
                title = col.strip('_').capitalize()
            header = {
                'col': col,
                'title': title,
                'hidden': False,
                'asc': None,
            }

            if col == sort_col:
                if asc:
                    data['asc'] = asc
                data['sort'] = sort_col
                header['asc'] = bool(asc)
                if not paginator and need_reorder:
                    # this dict will have enum values for sorting
                    # and will be used in sortkey(), if non-empty:
                    sort_values = {}
                    if sort_col in ('status', 'resolution', 'priority',
                                    'severity'):
                        # must fetch sort values for that columns
                        # instead of comparing them as strings
                        with self.env.db_query as db:
                            for name, value in db(
                                    "SELECT name, %s FROM enum WHERE type=%%s"
                                    % db.cast('value', 'int'),
                                    (sort_col,)):
                                sort_values[name] = value

                    def sortkey(row):
                        val = row[idx]
                        # check if we have sort_values, then use them as keys.
                        if sort_values:
                            return sort_values.get(val)
                        # otherwise, continue with string comparison:
                        if isinstance(val, basestring):
                            val = val.lower()
                        return val
                    results = sorted(results, key=sortkey, reverse=not asc)

            header_group = header_groups[-1]

            if col.startswith('__') and col.endswith('__'):  # __col__
                header['hidden'] = True
            elif col[0] == '_' and col[-1] == '_':           # _col_
                header_group = []
                header_groups.append(header_group)
                header_groups.append([])
            elif col[0] == '_':                              # _col
                header['hidden'] = True
            elif col[-1] == '_':                             # col_
                header_groups.append([])
            header_group.append(header)

        # Structure the rows and cells:
        #  - group rows according to __group__ value, if defined
        #  - group cells the same way headers are grouped
        chrome = Chrome(self.env)
        row_groups = []
        authorized_results = []
        prev_group_value = None
        for row_idx, result in enumerate(results):
            col_idx = 0
            cell_groups = []
            row = {'cell_groups': cell_groups}
            realm = TicketSystem.realm
            parent_realm = ''
            parent_id = ''
            email_cells = []
            for header_group in header_groups:
                cell_group = []
                for header in header_group:
                    value = cell_value(result[col_idx])
                    cell = {'value': value, 'header': header,
                            'index': col_idx}
                    col = header['col']
                    col_idx += 1
                    # Detect and create new group
                    if col == '__group__' and value != prev_group_value:
                        prev_group_value = value
                        # Brute force handling of email in group by header
                        row_groups.append(
                            (value and chrome.format_author(req, value), []))
                    # Other row properties
                    row['__idx__'] = row_idx
                    if col in self._html_cols:
                        row[col] = value
                    if col in ('report', 'ticket', 'id', '_id'):
                        row['id'] = value
                    # Special casing based on column name
                    col = col.strip('_')
                    if col in ('reporter', 'cc', 'owner'):
                        email_cells.append(cell)
                    elif col == 'realm':
                        realm = value
                    elif col == 'parent_realm':
                        parent_realm = value
                    elif col == 'parent_id':
                        parent_id = value
                    cell_group.append(cell)
                cell_groups.append(cell_group)
            if parent_realm:
                resource = Resource(realm, row.get('id'),
                                    parent=Resource(parent_realm, parent_id))
            else:
                resource = Resource(realm, row.get('id'))
            # FIXME: for now, we still need to hardcode the realm in the action
            if resource.realm.upper() + '_VIEW' not in req.perm(resource):
                continue
            authorized_results.append(result)
            if email_cells:
                for cell in email_cells:
                    emails = chrome.format_emails(context.child(resource),
                                                  cell['value'])
                    result[cell['index']] = cell['value'] = emails
            row['resource'] = resource
            if row_groups:
                row_group = row_groups[-1][1]
            else:
                row_group = []
                row_groups = [(None, row_group)]
            row_group.append(row)

        data.update({'header_groups': header_groups,
                     'row_groups': row_groups,
                     'numrows': numrows})

        if format == 'rss':
            data['context'] = web_context(req, report_resource,
                                          absurls=True)
            return 'report.rss', data, 'application/rss+xml'
        elif format == 'csv':
            filename = 'report_%s.csv' % id if id else 'report.csv'
            self._send_csv(req, cols, authorized_results, mimetype='text/csv',
                           filename=filename)
        elif format == 'tab':
            filename = 'report_%s.tsv' % id if id else 'report.tsv'
            self._send_csv(req, cols, authorized_results, '\t',
                           mimetype='text/tab-separated-values',
                           filename=filename)
        else:
            p = page if max is not None else None
            add_link(req, 'alternate',
                     auth_link(req, report_href(format='rss', page=None)),
                     _('RSS Feed'), 'application/rss+xml', 'rss')
            add_link(req, 'alternate', report_href(format='csv', page=p),
                     _('Comma-delimited Text'), 'text/plain')
            add_link(req, 'alternate', report_href(format='tab', page=p),
                     _('Tab-delimited Text'), 'text/plain')
            if 'REPORT_SQL_VIEW' in req.perm(self.realm, id):
                add_link(req, 'alternate',
                         req.href.report(id=id, format='sql'),
                         _('SQL Query'), 'text/plain')

            # reuse the session vars of the query module so that
            # the query navigation links on the ticket can be used to
            # navigate report results as well
            try:
                req.session['query_tickets'] = \
                    ' '.join(str(int(row['id']))
                             for rg in row_groups for row in rg[1])
                req.session['query_href'] = \
                    req.session['query_href'] = report_href()
                # Kludge: we have to clear the other query session
                # variables, but only if the above succeeded
                for var in ('query_constraints', 'query_time'):
                    if var in req.session:
                        del req.session[var]
            except (ValueError, KeyError):
                pass
            if set(data['args']) - {'USER'}:
                data['show_args_form'] = True
                # Add values of all select-type ticket fields for autocomplete.
                fields = TicketSystem(self.env).get_ticket_fields()
                arg_values = {}
                for arg in set(data['args']) - {'USER'}:
                    attrs = fields.by_name(arg.lower())
                    if attrs and 'options' in attrs:
                        arg_values[attrs['name']] = attrs['options']
                if arg_values:
                    add_script_data(req, arg_values=arg_values)
                    Chrome(self.env).add_jquery_ui(req)
            if missing_args:
                add_warning(req, _(
                    'The following arguments are missing: %(args)s',
                    args=", ".join(missing_args)))
            return 'report_view.html', data, None