Beispiel #1
0
 def _test_edit_ticket_comment(self, commenter, editor):
     ticket = self._insert_ticket(commenter)
     ticket.save_changes(commenter, comment='The comment')
     comment_resource = Resource('comment', 1, parent=ticket.resource)
     perm_cache = PermissionCache(self.env, editor, comment_resource)
     return perm_cache, comment_resource
Beispiel #2
0
 def resource(self):
     return Resource(self.realm, self.name, self._resource_version)
Beispiel #3
0
 def test_describe_tagged_resource(self):
     resource = Resource('ticket', 1)
     self.assertEquals(
         self.provider.describe_tagged_resource(self.req, resource),
         'defect: summary')
Beispiel #4
0
    def expand_macro(self, formatter, name, content):
        env = self.env
        req = formatter.req
        args, kw = parse_args(content)

        # Use macro arguments (most likely wiki macro calls).
        realms = 'realm' in kw and kw['realm'].split('|') or []
        tag_system = TagSystem(env)
        all_realms = [p.get_taggable_realm() for p in tag_system.tag_providers]
        self.all_realms = all_realms
        self.realms = realms

        if name == 'TagCloud':
            args.append(' or '.join(['realm:%s' % r for r in realms]))
            all_tags = tag_system.get_all_tags(req, ' '.join(args))
            mincount = 'mincount' in kw and kw['mincount'] or None
            return self.render_cloud(req,
                                     all_tags,
                                     caseless_sort=self.caseless_sort,
                                     mincount=mincount)
        elif name == 'ListTagged':
            if _OBSOLETE_ARGS_RE.search(content):
                data = {'warning': 'obsolete_args'}
            else:
                data = {'warning': None}
            context = formatter.context
            # Use TagsQuery arguments (most likely wiki macro calls).
            cols = 'cols' in kw and kw['cols'] or self.default_cols
            format = 'format' in kw and kw['format'] or self.default_format
            query = args and args[0].strip() or None
            if query and not realms:
                # First read query arguments (most likely a web-UI call).
                for realm in all_realms:
                    if re.search('(^|\W)realm:%s(\W|$)' % (realm), query):
                        realms = realms and realms.append(realm) or [realm]
            if not realms:
                # Apply ListTagged defaults to macro call w/o realm.
                realms = list(set(all_realms) - set(self.exclude_realms))
            if not realms:
                return ''
            else:
                self.query = query
                self.realms = realms
            query = '(%s) (%s)' % (query or '', ' or '.join(
                ['realm:%s' % (r) for r in realms]))
            env.log.debug('LISTTAGGED_QUERY: ' + query)
            query_result = tag_system.query(req, query)
            if not query_result:
                return ''

            def _link(resource):
                if resource.realm == 'tag':
                    # Keep realm selection in tag links.
                    return builder.a(resource.id,
                                     href=self.get_href(req, tag=resource))
                elif resource.realm == 'ticket':
                    # Return resource link including ticket status dependend
                    #   class to allow for common Trac ticket link style.
                    ticket = Ticket(env, resource.id)
                    return builder.a('#%s' % ticket.id,
                                     class_=ticket['status'],
                                     href=formatter.href.ticket(ticket.id),
                                     title=shorten_line(ticket['summary']))
                return render_resource_link(env, context, resource, 'compact')

            if format == 'table':
                cols = [
                    col for col in cols.split('|')
                    if col in self.supported_cols
                ]
                # Use available translations from Trac core.
                try:
                    labels = TicketSystem(env).get_ticket_field_labels()
                    labels['id'] = _('Id')
                except AttributeError:
                    # Trac 0.11 neither has the attribute nor uses i18n.
                    labels = {'id': 'Id', 'description': 'Description'}
                labels['realm'] = _('Realm')
                labels['tags'] = _('Tags')
                headers = [{'label': labels.get(col)} for col in cols]
                data.update({'cols': cols, 'headers': headers})

            results = sorted(query_result, key=lambda r: \
                             embedded_numbers(to_unicode(r[0].id)))
            results = self._paginate(req, results)
            rows = []
            for resource, tags in results:
                desc = tag_system.describe_tagged_resource(req, resource)
                tags = sorted(tags)
                if tags:
                    rendered_tags = [
                        _link(Resource('tag', tag)) for tag in tags
                    ]
                    if 'oldlist' == format:
                        resource_link = _link(resource)
                    else:
                        desc = desc or \
                               get_resource_description(env, resource,
                                                        context=context)
                        resource_link = builder.a(desc,
                                                  href=get_resource_url(
                                                      env, resource,
                                                      context.href))
                        if 'table' == format:
                            cells = []
                            for col in cols:
                                if col == 'id':
                                    cells.append(_link(resource))
                                # Don't duplicate links to resource in both.
                                elif col == 'description' and 'id' in cols:
                                    cells.append(desc)
                                elif col == 'description':
                                    cells.append(resource_link)
                                elif col == 'realm':
                                    cells.append(resource.realm)
                                elif col == 'tags':
                                    cells.append(
                                        builder([(tag, ' ')
                                                 for tag in rendered_tags]))
                            rows.append({'cells': cells})
                            continue
                rows.append({
                    'desc': desc,
                    'rendered_tags': None,
                    'resource_link': _link(resource)
                })
            data.update({
                'format': format,
                'paginator': results,
                'results': rows,
                'tags_url': req.href('tags')
            })

            # Work around a bug in trac/templates/layout.html, that causes a
            # TypeError for the wiki macro call, if we use add_link() alone.
            add_stylesheet(req, 'common/css/search.css')

            return Chrome(env).render_template(req, 'listtagged_results.html',
                                               data, 'text/html', True)
Beispiel #5
0
    def process_request(self, req):
        req.perm.require('TAGS_VIEW')

        match = re.match(r'/tags/?(.*)', req.path_info)
        tag_id = match.group(1) and match.group(1) or None
        query = req.args.get('q', '')

        # Consider only providers, that are permitted for display.
        realms = [p.get_taggable_realm() for p in self.tag_providers
                  if (not hasattr(p, 'check_permission') or \
                      p.check_permission(req.perm, 'view'))]
        if not (tag_id or query) or [r for r in realms if r in req.args] == []:
            for realm in realms:
                if not realm in self.exclude_realms:
                    req.args[realm] = 'on'
        checked_realms = [r for r in realms if r in req.args]
        if query:
            # Add permitted realms from query expression.
            checked_realms.extend(query_realms(query, realms))
        realm_args = dict(
            zip([r for r in checked_realms], ['on' for r in checked_realms]))
        # Switch between single tag and tag query expression mode.
        if tag_id and not re.match(r"""(['"]?)(\S+)\1$""", tag_id, re.UNICODE):
            # Convert complex, invalid tag ID's --> query expression.
            req.redirect(req.href.tags(realm_args, q=tag_id))
        elif query:
            single_page = re.match(r"""(['"]?)(\S+)\1$""", query, re.UNICODE)
            if single_page:
                # Convert simple query --> single tag.
                req.redirect(req.href.tags(single_page.group(2), realm_args))

        data = dict(page_title=_("Tags"), checked_realms=checked_realms)
        # Populate the TagsQuery form field.
        data['tag_query'] = tag_id and tag_id or query
        data['tag_realms'] = list(
            dict(name=realm, checked=realm in checked_realms)
            for realm in realms)
        if tag_id:
            page_name = tag_id
            page = WikiPage(self.env, page_name)
            data['tag_page'] = page

        macros = TagWikiMacros(self.env)
        if query or tag_id:
            # TRANSLATOR: The meta-nav link label.
            add_ctxtnav(req, _("Back to Cloud"), req.href.tags())
            macro = 'ListTagged'
            args = "%s,format=%s,cols=%s" % \
                   (tag_id and tag_id or query, self.default_format,
                    self.default_cols)
            data['mincount'] = None
        else:
            macro = 'TagCloud'
            mincount = as_int(req.args.get('mincount', None),
                              self.cloud_mincount)
            args = mincount and "mincount=%s" % mincount or None
            data['mincount'] = mincount
        formatter = Formatter(self.env,
                              Context.from_request(req, Resource('tag')))
        self.env.log.debug("Tag macro arguments: %s", args and args
                           or '(none)')
        try:
            # Query string without realm throws 'NotImplementedError'.
            data['tag_body'] = checked_realms and \
                               macros.expand_macro(formatter, macro, args,
                                                   realms=checked_realms) \
                               or ''
        except InvalidQuery, e:
            data['tag_query_error'] = to_unicode(e)
            data['tag_body'] = macros.expand_macro(formatter, 'TagCloud', '')
Beispiel #6
0
 def test_actor_has_email_view_for_resource_negative(self):
     format_author = Chrome(self.env).format_author
     req = MockRequest(self.env, authname='user2')
     resource = Resource('wiki', 'TracGuide')
     author = format_author(req, '*****@*****.**', resource)
     self.assertEqual(u'user@\u2026', author)
Beispiel #7
0
 def resource(self):
     return Resource('milestone', self.name) ### .version !!!
Beispiel #8
0
 def test_action_revoked(self):
     resource = Resource('wiki', 'RestrictedPage')
     self.assertEquals(
         self.check('WIKI_VIEW', 'anonymous', resource,
                    PermissionCache(self.env)), False)
Beispiel #9
0
    def process_request(self, req):
        tid = req.args.get('id')
        if not tid:
            raise TracError(_('No ticket id provided.'))

        try:
            ticket = Ticket(self.env, tid)
        except ValueError:
            raise TracError(_('Invalid ticket id.'))

        # For access to the relation management, TICKET_MODIFY is required.
        req.perm.require('TICKET_MODIFY')
        relsys = RelationsSystem(self.env)

        data = {
            'relation': {},
        }
        if req.method == 'POST':
            # for modifying the relations TICKET_MODIFY is required for
            # both the source and the destination tickets

            if 'remove' in req.args:
                rellist = req.args.get('sel')
                if rellist:
                    if isinstance(rellist, basestring):
                        rellist = [
                            rellist,
                        ]
                    self.remove_relations(req, rellist)
            elif 'add' in req.args:
                relation = dict(
                    destination=req.args.get('dest_tid', ''),
                    type=req.args.get('reltype', ''),
                    comment=req.args.get('comment', ''),
                )
                try:
                    trs = TicketRelationsSpecifics(self.env)
                    dest_ticket = trs.find_ticket(relation['destination'])
                except NoSuchTicketError:
                    data['error'] = _('Invalid ticket ID.')
                else:
                    req.perm.require('TICKET_MODIFY', Resource(dest_ticket.id))

                    try:
                        dbrel = relsys.add(ticket, dest_ticket,
                                           relation['type'],
                                           relation['comment'], req.authname)
                    except NoSuchTicketError:
                        data['error'] = _('Invalid ticket ID.')
                    except UnknownRelationType:
                        data['error'] = _('Unknown relation type.')
                    except ValidationError as ex:
                        data['error'] = ex.message
                    else:
                        # Notify
                        try:
                            self.notify_relation_changed(dbrel)
                        except Exception, e:
                            self.log.error(
                                "Failure sending notification on"
                                "creation of relation: %s",
                                exception_to_unicode(e))
                            add_warning(
                                req,
                                _(
                                    "The relation has been added, "
                                    "but an error occurred while "
                                    "sending notifications: "
                                    "%(message)s",
                                    message=to_unicode(e)))

                if 'error' in data:
                    data['relation'] = relation
            else:
                raise TracError(_('Invalid operation.'))
Beispiel #10
0
 def test_set_tags(self):
     resource = Resource('wiki', 'WikiStart')
     tags = ['tag1']
     self.req.perm = PermissionCache(self.env, username='******')
     # Shouldn't raise an error with appropriate permission.
     self.tag_s.set_tags(self.req, resource, tags)
Beispiel #11
0
 def test_action_granted(self):
     resource = Resource('wiki', 'PublicPage')
     self.assertEquals(
         self.check('WIKI_MODIFY', 'anonymous', resource,
                    PermissionCache(self.env)), True)
Beispiel #12
0
    def _do_actions(self, context, actions):
        for action in actions:
            if action == 'get-file':
                context.req.perm.require('DOWNLOADS_VIEW')

                # Get request arguments.
                download_id = context.req.args.get('id') or 0
                download_file = context.req.args.get('file')

                # Get download.
                if download_id:
                    download = self.get_download(context, download_id)
                else:
                    download = self.get_download_by_file(
                        context, download_file)

                # Check if requested download exists.
                if not download:
                    raise TracError('File not found.')

                # Check resource based permission.
                context.req.perm.require('DOWNLOADS_VIEW',
                                         Resource('downloads', download['id']))

                # Get download file path.
                path = os.path.normpath(
                    os.path.join(self.path, to_unicode(download['id']),
                                 download['file']))
                self.log.debug('path: %s' % (path, ))

                # Increase downloads count.
                new_download = {'count': download['count'] + 1}

                # Edit download.
                self.edit_download(context, download['id'], new_download)

                # Notify change listeners.
                for listener in self.change_listeners:
                    listener.download_changed(context, new_download, download)

                # Commit DB before file send.
                db = self.env.get_db_cnx()
                db.commit()

                # Guess mime type.
                file = open(path.encode('utf-8'), "r")
                file_data = file.read(1000)
                file.close()
                mimeview = Mimeview(self.env)
                mime_type = mimeview.get_mimetype(path, file_data)
                if not mime_type:
                    mime_type = 'application/octet-stream'
                if 'charset=' not in mime_type:
                    charset = mimeview.get_charset(file_data, mime_type)
                    mime_type = mime_type + '; charset=' + charset

                # Return uploaded file to request.
                context.req.send_header(
                    'Content-Disposition', 'attachment;filename="%s"' %
                    (os.path.normpath(download['file'])))
                context.req.send_header('Content-Description',
                                        download['description'])
                context.req.send_file(path.encode('utf-8'), mime_type)

            elif action == 'downloads-list':
                context.req.perm.require('DOWNLOADS_VIEW')

                self.log.debug('visible_fields: %s' % (self.visible_fields, ))

                # Get form values.
                order = context.req.args.get('order') or self.download_sort
                if context.req.args.has_key('desc'):
                    desc = context.req.args.get('desc') == '1'
                else:
                    desc = self.download_sort_direction == 'desc'

                self.data['order'] = order
                self.data['desc'] = desc
                self.data['has_tags'] = self.env.is_component_enabled(
                    'tractags.api.TagEngine')
                self.data['visible_fields'] = self.visible_fields
                self.data['title'] = self.title
                self.data['description'] = self.get_description(context)
                self.data['downloads'] = self.get_downloads(
                    context, order, desc)
                self.data['visible_fields'] = [
                    visible_field for visible_field in self.visible_fields
                ]

                # Component, versions, etc. are needed only for new download
                # add form.
                if context.req.perm.has_permission('DOWNLOADS_ADD'):
                    self.data['components'] = self.get_components(context)
                    self.data['versions'] = self.get_versions(context)
                    self.data['architectures'] = self.get_architectures(
                        context)
                    self.data['platforms'] = self.get_platforms(context)
                    self.data['types'] = self.get_types(context)

            elif action == 'admin-downloads-list':
                context.req.perm.require('DOWNLOADS_ADMIN')

                # Get form values
                order = context.req.args.get('order') or self.download_sort
                if context.req.args.has_key('desc'):
                    desc = context.req.args.get('desc') == '1'
                else:
                    desc = self.download_sort_direction == 'desc'
                download_id = int(context.req.args.get('download') or 0)

                self.data['order'] = order
                self.data['desc'] = desc
                self.data['has_tags'] = self.env.is_component_enabled(
                    'tractags.api.TagEngine')
                self.data['download'] = self.get_download(context, download_id)
                self.data['downloads'] = self.get_downloads(
                    context, order, desc)
                self.data['components'] = self.get_components(context)
                self.data['versions'] = self.get_versions(context)
                self.data['architectures'] = self.get_architectures(context)
                self.data['platforms'] = self.get_platforms(context)
                self.data['types'] = self.get_types(context)

            elif action == 'description-edit':
                context.req.perm.require('DOWNLOADS_ADMIN')

            elif action == 'description-post-edit':
                context.req.perm.require('DOWNLOADS_ADMIN')

                # Get form values.
                description = context.req.args.get('description')

                # Set new description.
                self.edit_description(context, description)

            elif action == 'downloads-post-add':
                context.req.perm.require('DOWNLOADS_ADD')

                # Get form values.
                file, filename, file_size = self._get_file_from_req(context)
                download = {
                    'file': filename,
                    'description': context.req.args.get('description'),
                    'size': file_size,
                    'time': to_timestamp(datetime.now(utc)),
                    'count': 0,
                    'author': context.req.authname,
                    'tags': context.req.args.get('tags'),
                    'component': context.req.args.get('component'),
                    'version': context.req.args.get('version'),
                    'architecture': context.req.args.get('architecture'),
                    'platform': context.req.args.get('platform'),
                    'type': context.req.args.get('type')
                }

                # Upload file to DB and file storage.
                self._add_download(context, download, file)

                # Close input file.
                file.close()

            elif action == 'downloads-post-edit':
                context.req.perm.require('DOWNLOADS_ADMIN')

                # Get form values.
                download_id = context.req.args.get('id')
                old_download = self.get_download(context, download_id)
                download = {
                    'description': context.req.args.get('description'),
                    'tags': context.req.args.get('tags'),
                    'component': context.req.args.get('component'),
                    'version': context.req.args.get('version'),
                    'architecture': context.req.args.get('architecture'),
                    'platform': context.req.args.get('platform'),
                    'type': context.req.args.get('type')
                }

                # Edit Download.
                self.edit_download(context, download_id, download)

                # Notify change listeners.
                for listener in self.change_listeners:
                    listener.download_changed(context, download, old_download)

            elif action == 'downloads-delete':
                context.req.perm.require('DOWNLOADS_ADMIN')

                # Get selected downloads.
                selection = context.req.args.get('selection')
                if isinstance(selection, (str, unicode)):
                    selection = [selection]

                # Delete download.
                if selection:
                    for download_id in selection:
                        download = self.get_download(context, download_id)
                        self.log.debug('download: %s' % (download, ))
                        self._delete_download(context, download)

            elif action == 'admin-architectures-list':
                context.req.perm.require('DOWNLOADS_ADMIN')

                # Get form values
                order = context.req.args.get('order') or self.architecture_sort
                if context.req.args.has_key('desc'):
                    desc = context.req.args.get('desc') == '1'
                else:
                    desc = self.architecture_sort_direction == 'desc'
                architecture_id = int(
                    context.req.args.get('architecture') or 0)

                # Display architectures.
                self.data['order'] = order
                self.data['desc'] = desc
                self.data['architecture'] = self.get_architecture(
                    context, architecture_id)
                self.data['architectures'] = self.get_architectures(
                    context, order, desc)

            elif action == 'architectures-post-add':
                context.req.perm.require('DOWNLOADS_ADMIN')

                # Get form values.
                architecture = {
                    'name': context.req.args.get('name'),
                    'description': context.req.args.get('description')
                }

                # Add architecture.
                self.add_architecture(context, architecture)

            elif action == 'architectures-post-edit':
                context.req.perm.require('DOWNLOADS_ADMIN')

                # Get form values.
                architecture_id = context.req.args.get('id')
                architecture = {
                    'name': context.req.args.get('name'),
                    'description': context.req.args.get('description')
                }

                # Add architecture.
                self.edit_architecture(context, architecture_id, architecture)

            elif action == 'architectures-delete':
                context.req.perm.require('DOWNLOADS_ADMIN')

                # Get selected architectures.
                selection = context.req.args.get('selection')
                if isinstance(selection, (str, unicode)):
                    selection = [selection]

                # Delete architectures.
                if selection:
                    for architecture_id in selection:
                        self.delete_architecture(context, architecture_id)

            elif action == 'admin-platforms-list':
                context.req.perm.require('DOWNLOADS_ADMIN')

                # Get form values.
                order = context.req.args.get('order') or self.platform_sort
                if context.req.args.has_key('desc'):
                    desc = context.req.args.get('desc') == '1'
                else:
                    desc = self.platform_sort_direction == 'desc'
                platform_id = int(context.req.args.get('platform') or 0)

                # Display platforms.
                self.data['order'] = order
                self.data['desc'] = desc
                self.data['platform'] = self.get_platform(context, platform_id)
                self.data['platforms'] = self.get_platforms(
                    context, order, desc)

            elif action == 'platforms-post-add':
                context.req.perm.require('DOWNLOADS_ADMIN')

                # Get form values.
                platform = {
                    'name': context.req.args.get('name'),
                    'description': context.req.args.get('description')
                }

                # Add platform.
                self.add_platform(context, platform)

            elif action == 'platforms-post-edit':
                context.req.perm.require('DOWNLOADS_ADMIN')

                # Get form values.
                platform_id = context.req.args.get('id')
                platform = {
                    'name': context.req.args.get('name'),
                    'description': context.req.args.get('description')
                }

                # Add platform.
                self.edit_platform(context, platform_id, platform)

            elif action == 'platforms-delete':
                context.req.perm.require('DOWNLOADS_ADMIN')

                # Get selected platforms.
                selection = context.req.args.get('selection')
                if isinstance(selection, (str, unicode)):
                    selection = [selection]

                # Delete platforms.
                if selection:
                    for platform_id in selection:
                        self.delete_platform(context, platform_id)

            elif action == 'admin-types-list':
                context.req.perm.require('DOWNLOADS_ADMIN')

                # Get form values
                order = context.req.args.get('order') or self.type_sort
                if context.req.args.has_key('desc'):
                    desc = context.req.args.get('desc') == '1'
                else:
                    desc = self.type_sort_direction == 'desc'
                platform_id = int(context.req.args.get('type') or 0)

                # Display platforms.
                self.data['order'] = order
                self.data['desc'] = desc
                self.data['type'] = self.get_type(context, platform_id)
                self.data['types'] = self.get_types(context, order, desc)

            elif action == 'types-post-add':
                context.req.perm.require('DOWNLOADS_ADMIN')

                # Get form values.
                type = {
                    'name': context.req.args.get('name'),
                    'description': context.req.args.get('description')
                }

                # Add type.
                self.add_type(context, type)

            elif action == 'types-post-edit':
                context.req.perm.require('DOWNLOADS_ADMIN')

                # Get form values.
                type_id = context.req.args.get('id')
                type = {
                    'name': context.req.args.get('name'),
                    'description': context.req.args.get('description')
                }

                # Add platform.
                self.edit_type(context, type_id, type)

            elif action == 'types-delete':
                context.req.perm.require('DOWNLOADS_ADMIN')

                # Get selected types.
                selection = context.req.args.get('selection')
                if isinstance(selection, (str, unicode)):
                    selection = [selection]

                # Delete types.
                if selection:
                    for type_id in selection:
                        self.delete_type(context, type_id)
Beispiel #13
0
    def expand_macro(self, formatter, name, content):
        args = None
        if content:
            content = stripws(content)
            # parse arguments
            # we expect the 1st argument to be a filename (filespec)
            args = [stripws(arg) for arg
                                 in self._split_args_re.split(content)[1::2]]
        if not args:
            return ''
        # strip unicode white-spaces and ZWSPs are copied from attachments
        # section (#10668)
        filespec = args.pop(0)

        # style information
        attr = {}
        style = {}
        link = ''
        # helper for the special case `source:`
        #
        from trac.versioncontrol.web_ui import BrowserModule
        # FIXME: somehow use ResourceSystem.get_known_realms()
        #        ... or directly trac.wiki.extract_link
        try:
            browser_links = [res[0] for res in
                             BrowserModule(self.env).get_link_resolvers()]
        except Exception:
            browser_links = []
        while args:
            arg = args.pop(0)
            if self._size_re.match(arg):
                # 'width' keyword
                attr['width'] = arg
            elif arg == 'nolink':
                link = None
            elif arg.startswith('link='):
                val = arg.split('=', 1)[1]
                elt = extract_link(self.env, formatter.context, val.strip())
                elt = find_element(elt, 'href')
                link = None
                if elt is not None:
                    link = elt.attrib.get('href')
            elif arg in ('left', 'right'):
                style['float'] = arg
            elif arg == 'center':
                style['margin-left'] = style['margin-right'] = 'auto'
                style['display'] = 'block'
                style.pop('margin', '')
            elif arg in ('top', 'bottom', 'middle'):
                style['vertical-align'] = arg
            else:
                match = self._attr_re.match(arg)
                if match:
                    key, val = match.groups()
                    if (key == 'align' and
                            val in ('left', 'right', 'center')) or \
                        (key == 'valign' and
                            val in ('top', 'middle', 'bottom')):
                        args.append(val)
                    elif key in ('margin-top', 'margin-bottom'):
                        style[key] = ' %dpx' % _arg_as_int(val, key, min=1)
                    elif key in ('margin', 'margin-left', 'margin-right') \
                             and 'display' not in style:
                        style[key] = ' %dpx' % _arg_as_int(val, key, min=1)
                    elif key == 'border':
                        style['border'] = ' %dpx solid' % _arg_as_int(val, key)
                    else:
                        m = self._quoted_re.search(val)  # unquote "..." and '...'
                        if m:
                            val = m.group(1)
                        attr[str(key)] = val  # will be used as a __call__ kwd

        if self._quoted_re.match(filespec):
            filespec = filespec.strip('\'"')
        # parse filespec argument to get realm and id if contained.
        parts = [i.strip('\'"')
                 for i in self._split_filespec_re.split(filespec)[1::2]]
        realm = parts[0] if parts else None
        url = raw_url = desc = None
        attachment = None
        interwikimap = InterWikiMap(self.env)
        if realm in ('http', 'https', 'ftp', 'data'):  # absolute
            raw_url = url = filespec
            desc = url.rsplit('?')[0]
        elif realm in interwikimap:
            url, desc = interwikimap.url(realm, ':'.join(parts[1:]))
            raw_url = url
        elif filespec.startswith('//'):       # server-relative
            raw_url = url = filespec[1:]
            desc = url.rsplit('?')[0]
        elif filespec.startswith('/'):        # project-relative
            params = ''
            if '?' in filespec:
                filespec, params = filespec.rsplit('?', 1)
            url = formatter.href(filespec)
            if params:
                url += '?' + params
            raw_url, desc = url, filespec
        elif len(parts) == 3:                 # realm:id:attachment-filename
            #                                 # or intertrac:realm:id
            realm, id, filename = parts
            intertrac_target = "%s:%s" % (id, filename)
            it = formatter.get_intertrac_url(realm, intertrac_target)
            if it:
                url, desc = it
                raw_url = url + unicode_quote('?format=raw')
            else:
                attachment = Resource(realm, id).child('attachment', filename)
        elif len(parts) == 2:
            realm, filename = parts
            if realm in browser_links:  # source:path
                # TODO: use context here as well
                rev = None
                if '@' in filename:
                    filename, rev = filename.rsplit('@', 1)
                url = formatter.href.browser(filename, rev=rev)
                raw_url = formatter.href.browser(filename, rev=rev,
                                                 format='raw')
                desc = filespec
            else:  # #ticket:attachment or WikiPage:attachment
                # FIXME: do something generic about shorthand forms...
                realm = None
                id, filename = parts
                if id and id[0] == '#':
                    realm = 'ticket'
                    id = id[1:]
                elif id == 'htdocs':
                    raw_url = url = formatter.href.chrome('site', filename)
                    desc = os.path.basename(filename)
                elif id == 'shared':
                    raw_url = url = formatter.href.chrome('shared', filename)
                    desc = os.path.basename(filename)
                else:
                    realm = 'wiki'
                if realm:
                    attachment = Resource(realm, id).child('attachment',
                                                           filename)
        elif len(parts) == 1:  # it's an attachment of the current resource
            attachment = formatter.resource.child('attachment', filespec)
        else:
            return system_message(_("No filespec given"))
        if attachment:
            try:
                desc = get_resource_summary(self.env, attachment)
            except ResourceNotFound:
                link = None
                raw_url = chrome_resource_path(formatter.context.req,
                                               'common/attachment.png')
                desc = _('No image "%(id)s" attached to %(parent)s',
                         id=attachment.id,
                         parent=get_resource_name(self.env, attachment.parent))
            else:
                if 'ATTACHMENT_VIEW' in formatter.perm(attachment):
                    url = get_resource_url(self.env, attachment,
                                           formatter.href)
                    raw_url = get_resource_url(self.env, attachment,
                                               formatter.href, format='raw')

        for key in ('title', 'alt'):
            if desc and key not in attr:
                attr[key] = desc
        if style:
            attr['style'] = '; '.join('%s:%s' % (k, escape(v))
                                      for k, v in style.items())
        if not WikiSystem(self.env).is_safe_origin(raw_url,
                                                   formatter.context.req):
            attr['crossorigin'] = 'anonymous'  # avoid password prompt
        result = tag.img(src=raw_url, **attr)
        if link is not None:
            result = tag.a(result, href=link or url,
                           style='padding:0; border:none')
        return result
Beispiel #14
0
class Node(object):
    """Represents a directory or file in the repository at a given revision."""

    DIRECTORY = "dir"
    FILE = "file"

    resource = property(lambda self: Resource('source',
                                              self.created_path,
                                              version=self.created_rev,
                                              parent=self.repos.resource))

    # created_path and created_rev properties refer to the Node "creation"
    # in the Subversion meaning of a Node in a versioned tree (see #3340).
    #
    # Those properties must be set by subclasses.
    #
    created_rev = None
    created_path = None

    def __init__(self, repos, path, rev, kind):
        assert kind in (Node.DIRECTORY, Node.FILE), \
               "Unknown node kind %s" % kind
        self.repos = repos
        self.path = to_unicode(path)
        self.rev = rev
        self.kind = kind

    def get_content(self):
        """Return a stream for reading the content of the node.

        This method will return `None` for directories.
        The returned object must support a `read([len])` method.
        """
        raise NotImplementedError

    def get_entries(self):
        """Generator that yields the immediate child entries of a directory.

        The entries are returned in no particular order.
        If the node is a file, this method returns `None`.
        """
        raise NotImplementedError

    def get_history(self, limit=None):
        """Provide backward history for this Node.
        
        Generator that yields `(path, rev, chg)` tuples, one for each revision
        in which the node was changed. This generator will follow copies and
        moves of a node (if the underlying version control system supports
        that), which will be indicated by the first element of the tuple
        (i.e. the path) changing.
        Starts with an entry for the current revision.

        :param limit: if given, yield at most ``limit`` results.
        """
        raise NotImplementedError

    def get_previous(self):
        """Return the change event corresponding to the previous revision.

        This returns a `(path, rev, chg)` tuple.
        """
        skip = True
        for p in self.get_history(2):
            if skip:
                skip = False
            else:
                return p

    def get_annotations(self):
        """Provide detailed backward history for the content of this Node.

        Retrieve an array of revisions, one `rev` for each line of content
        for that node.
        Only expected to work on (text) FILE nodes, of course.
        """
        raise NotImplementedError

    def get_properties(self):
        """Returns the properties (meta-data) of the node, as a dictionary.

        The set of properties depends on the version control system.
        """
        raise NotImplementedError

    def get_content_length(self):
        """The length in bytes of the content.

        Will be `None` for a directory.
        """
        raise NotImplementedError

    content_length = property(lambda x: x.get_content_length())

    def get_content_type(self):
        """The MIME type corresponding to the content, if known.

        Will be `None` for a directory.
        """
        raise NotImplementedError

    content_type = property(lambda x: x.get_content_type())

    def get_name(self):
        return self.path.split('/')[-1]

    name = property(lambda x: x.get_name())

    def get_last_modified(self):
        raise NotImplementedError

    last_modified = property(lambda x: x.get_last_modified())

    isdir = property(lambda x: x.kind == Node.DIRECTORY)
    isfile = property(lambda x: x.kind == Node.FILE)

    def can_view(self, perm):
        """Return True if view permission is granted on the node."""
        return (self.isdir and 'BROWSER_VIEW' or 'FILE_VIEW') \
               in perm(self.resource)
Beispiel #15
0
        elif query.startswith('query:'):
            try:
                from trac.ticket.query import Query, QuerySyntaxError
                query = Query.from_string(self.env, query[6:], report=id)
                req.redirect(query.get_href(req))
            except QuerySyntaxError, e:
                req.redirect(req.href.report(id, action='edit',
                                             error=to_unicode(e)))

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

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

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

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

        sort_col = req.args.get('sort', '')
        asc = as_bool(req.args.get('asc', 1))

        def report_href(**kwargs):
            """Generate links to this report preserving user variables,
Beispiel #16
0
 def test_resource_doesnt_exist(self):
     """Non-existent resource returns False from resource_exists."""
     r = Resource('wiki', 'WikiStart').child('attachment', 'file.txt')
     self.assertFalse(AttachmentModule(self.env).resource_exists(r))
Beispiel #17
0
 def test_actor_has_email_view_for_resource(self):
     format_author = Chrome(self.env).format_author
     req = MockRequest(self.env, authname='user2')
     resource = Resource('wiki', 'WikiStart')
     author = format_author(req, '*****@*****.**', resource)
     self.assertEqual('*****@*****.**', author)
Beispiel #18
0
    def expand_macro(self, formatter, name, content):
        # args will be null if the macro is called without parenthesis.
        if not content:
            return ''
        # parse arguments
        # we expect the 1st argument to be a filename (filespec)
        args = content.split(',')
        if len(args) == 0:
            raise Exception("No argument.")
        # strip unicode white-spaces and ZWSPs are copied from attachments
        # section (#10668)
        filespec = stripws(args.pop(0))

        # style information
        size_re = re.compile('[0-9]+(%|px)?$')
        attr_re = re.compile('(align|valign|border|width|height|alt'
                             '|margin(?:-(?:left|right|top|bottom))?'
                             '|title|longdesc|class|id|usemap)=(.+)')
        quoted_re = re.compile("(?:[\"'])(.*)(?:[\"'])$")
        attr = {}
        style = {}
        link = ''
        # helper for the special case `source:`
        #
        from trac.versioncontrol.web_ui import BrowserModule
        # FIXME: somehow use ResourceSystem.get_known_realms()
        #        ... or directly trac.wiki.extract_link
        try:
            browser_links = [
                res[0] for res in BrowserModule(self.env).get_link_resolvers()
            ]
        except Exception:
            browser_links = []
        while args:
            arg = stripws(args.pop(0))
            if size_re.match(arg):
                # 'width' keyword
                attr['width'] = arg
            elif arg == 'nolink':
                link = None
            elif arg.startswith('link='):
                val = arg.split('=', 1)[1]
                elt = extract_link(self.env, formatter.context, val.strip())
                elt = find_element(elt, 'href')
                link = None
                if elt is not None:
                    link = elt.attrib.get('href')
            elif arg in ('left', 'right'):
                style['float'] = arg
            elif arg == 'center':
                style['margin-left'] = style['margin-right'] = 'auto'
                style['display'] = 'block'
                style.pop('margin', '')
            elif arg in ('top', 'bottom', 'middle'):
                style['vertical-align'] = arg
            else:
                match = attr_re.match(arg)
                if match:
                    key, val = match.groups()
                    if (key == 'align' and
                            val in ('left', 'right', 'center')) or \
                        (key == 'valign' and \
                            val in ('top', 'middle', 'bottom')):
                        args.append(val)
                    elif key in ('margin-top', 'margin-bottom'):
                        style[key] = ' %dpx' % int(val)
                    elif key in ('margin', 'margin-left', 'margin-right') \
                             and 'display' not in style:
                        style[key] = ' %dpx' % int(val)
                    elif key == 'border':
                        style['border'] = ' %dpx solid' % int(val)
                    else:
                        m = quoted_re.search(val)  # unquote "..." and '...'
                        if m:
                            val = m.group(1)
                        attr[str(key)] = val  # will be used as a __call__ kwd

        # parse filespec argument to get realm and id if contained.
        parts = [
            i.strip('''['"]''')
            for i in self._split_filespec_re.split(filespec)
        ]
        url = raw_url = desc = None
        attachment = None
        if (parts and parts[0] in ('http', 'https', 'ftp')):  # absolute
            raw_url = url = filespec
            desc = url.rsplit('?')[0]
        elif filespec.startswith('//'):  # server-relative
            raw_url = url = filespec[1:]
            desc = url.rsplit('?')[0]
        elif filespec.startswith('/'):  # project-relative
            params = ''
            if '?' in filespec:
                filespec, params = filespec.rsplit('?', 1)
            url = formatter.href(filespec)
            if params:
                url += '?' + params
            raw_url, desc = url, filespec
        elif len(parts) == 3:  # realm:id:attachment-filename
            #                                 # or intertrac:realm:id
            realm, id, filename = parts
            intertrac_target = "%s:%s" % (id, filename)
            it = formatter.get_intertrac_url(realm, intertrac_target)
            if it:
                url, desc = it
                raw_url = url + unicode_quote('?format=raw')
            else:
                attachment = Resource(realm, id).child('attachment', filename)
        elif len(parts) == 2:
            realm, filename = parts
            if realm in browser_links:  # source:path
                # TODO: use context here as well
                rev = None
                if '@' in filename:
                    filename, rev = filename.rsplit('@', 1)
                url = formatter.href.browser(filename, rev=rev)
                raw_url = formatter.href.browser(filename,
                                                 rev=rev,
                                                 format='raw')
                desc = filespec
            else:  # #ticket:attachment or WikiPage:attachment
                # FIXME: do something generic about shorthand forms...
                realm = None
                id, filename = parts
                if id and id[0] == '#':
                    realm = 'ticket'
                    id = id[1:]
                elif id == 'htdocs':
                    raw_url = url = formatter.href.chrome('site', filename)
                    desc = os.path.basename(filename)
                elif id == 'shared':
                    raw_url = url = formatter.href.chrome('shared', filename)
                    desc = os.path.basename(filename)
                else:
                    realm = 'wiki'
                if realm:
                    attachment = Resource(realm,
                                          id).child('attachment', filename)
        elif len(parts) == 1:  # it's an attachment of the current resource
            attachment = formatter.resource.child('attachment', filespec)
        else:
            raise TracError('No filespec given')
        if attachment and 'ATTACHMENT_VIEW' in formatter.perm(attachment):
            url = get_resource_url(self.env, attachment, formatter.href)
            raw_url = get_resource_url(self.env,
                                       attachment,
                                       formatter.href,
                                       format='raw')
            try:
                desc = get_resource_summary(self.env, attachment)
            except ResourceNotFound as e:
                raw_url = formatter.href.chrome('common/attachment.png')
                desc = _('No image "%(id)s" attached to %(parent)s',
                         id=attachment.id,
                         parent=get_resource_name(self.env, attachment.parent))
        for key in ('title', 'alt'):
            if desc and not key in attr:
                attr[key] = desc
        if style:
            attr['style'] = '; '.join('%s:%s' % (k, escape(v))
                                      for k, v in style.iteritems())
        result = tag.img(src=raw_url, **attr)
        if link is not None:
            result = tag.a(result,
                           href=link or url,
                           style='padding:0; border:none')
        return result
Beispiel #19
0
            data.update(
                dict(header_groups=[headers],
                    numrows=len(data['tickets']),
                    row_groups=[(group_value,
                            [{
                                '__color__' : t['priority_value'],
                                '__idx__' : idxs.next(),
                                'cell_groups' : [[
                                        {
                                            'header' : h,
                                            'index' : hidx,
                                            'value' : t[h['col']]
                                        } \
                                    for hidx, h in enumerate(headers)]],
                                'id' : t['id'],
                                'resource' : Resource('ticket', t['id'])
                            } for t in tickets]) \
                                for group_value, tickets in data['groups'] ]))
            return 'widget_grid.html', \
                    {
                        'title' : title or _('Custom Query'),
                        'data' : data,
                        'ctxtnav' : [
                                tag.a(_('More'),
                                    href=query.get_href(req.href))],
                        'altlinks' : fakereq.chrome.get('links', {}).get('alternate')
                    }, \
                    qryctx

    render_widget = pretty_wrapper(render_widget, check_widget_name)
Beispiel #20
0
    def get_list(self, realm, wl, req, fields=None):
        db = self.env.get_db_cnx()
        cursor = db.cursor()
        context = Context.from_request(req)
        locale = getattr(req, 'locale', LC_TIME)

        ticketlist = []
        extradict = {}
        if not fields:
            fields = set(self.default_fields['ticket'])
        else:
            fields = set(fields)

        if 'changetime' in fields:
            max_changetime = datetime(1970, 1, 1, tzinfo=utc)
            min_changetime = datetime.now(utc)
        if 'time' in fields:
            max_time = datetime(1970, 1, 1, tzinfo=utc)
            min_time = datetime.now(utc)

        for sid, last_visit in wl.get_watched_resources(
                'ticket', req.authname):
            ticketdict = {}
            try:
                ticket = Ticket(self.env, sid, db)
                exists = ticket.exists
            except:
                exists = False

            if not exists:
                ticketdict['deleted'] = True
                if 'id' in fields:
                    ticketdict['id'] = sid
                    ticketdict['ID'] = '#' + sid
                if 'author' in fields:
                    ticketdict['author'] = '?'
                if 'changetime' in fields:
                    ticketdict['changedsincelastvisit'] = 1
                    ticketdict['changetime'] = '?'
                    ticketdict['ichangetime'] = 0
                if 'time' in fields:
                    ticketdict['time'] = '?'
                    ticketdict['itime'] = 0
                if 'comment' in fields:
                    ticketdict['comment'] = tag.strong(t_("deleted"),
                                                       class_='deleted')
                if 'notify' in fields:
                    ticketdict['notify'] = wl.is_notify(req, 'ticket', sid)
                if 'description' in fields:
                    ticketdict['description'] = ''
                if 'owner' in fields:
                    ticketdict['owner'] = ''
                if 'reporter' in fields:
                    ticketdict['reporter'] = ''
                ticketlist.append(ticketdict)
                continue

            render_elt = lambda x: x
            if not (Chrome(self.env).show_email_addresses or \
                    'EMAIL_VIEW' in req.perm(ticket.resource)):
                render_elt = obfuscate_email_address

            # Copy all requested fields from ticket
            if fields:
                for f in fields:
                    ticketdict[f] = ticket.values.get(f, u'')
            else:
                ticketdict = ticket.values.copy()

            changetime = ticket.time_changed
            if wl.options['attachment_changes']:
                for attachment in Attachment.select(self.env, 'ticket', sid,
                                                    db):
                    if attachment.date > changetime:
                        changetime = attachment.date
            if 'attachment' in fields:
                attachments = []
                for attachment in Attachment.select(self.env, 'ticket', sid,
                                                    db):
                    wikitext = u'[attachment:"' + u':'.join([
                        attachment.filename, 'ticket', sid
                    ]) + u'" ' + attachment.filename + u']'
                    attachments.extend([
                        tag(', '),
                        format_to_oneliner(self.env,
                                           context,
                                           wikitext,
                                           shorten=False)
                    ])
                if attachments:
                    attachments.reverse()
                    attachments.pop()
                ticketdict['attachment'] = moreless(attachments, 5)

            # Changes are special. Comment, commentnum and last author are included in them.
            if 'changes' in fields or 'author' in fields or 'comment' in fields or 'commentnum' in fields:
                changes = []
                # If there are now changes the reporter is the last author
                author = ticket.values['reporter']
                commentnum = u"0"
                comment = u""
                want_changes = 'changes' in fields
                for date, cauthor, field, oldvalue, newvalue, permanent in ticket.get_changelog(
                        changetime, db):
                    author = cauthor
                    if field == 'comment':
                        if 'commentnum' in fields:
                            ticketdict['commentnum'] = to_unicode(oldvalue)
                        if 'comment' in fields:
                            comment = to_unicode(newvalue)
                            comment = moreless(comment, 200)
                            ticketdict['comment'] = comment
                        if not want_changes:
                            break
                    else:
                        if want_changes:
                            label = self.fields['ticket'].get(field, u'')
                            if label:
                                changes.extend([
                                    tag(
                                        tag.strong(label), ' ',
                                        render_property_diff(
                                            self.env, req, ticket, field,
                                            oldvalue, newvalue)),
                                    tag('; ')
                                ])
                if want_changes:
                    # Remove the last tag('; '):
                    if changes:
                        changes.pop()
                    changes = moreless(changes, 5)
                    ticketdict['changes'] = tag(changes)

            if 'id' in fields:
                ticketdict['id'] = sid
                ticketdict['ID'] = format_to_oneliner(self.env,
                                                      context,
                                                      '#' + sid,
                                                      shorten=True)
            if 'cc' in fields:
                if render_elt == obfuscate_email_address:
                    ticketdict['cc'] = ', '.join(
                        [render_elt(c) for c in ticketdict['cc'].split(', ')])
            if 'author' in fields:
                ticketdict['author'] = render_elt(author)
            if 'changetime' in fields:
                ichangetime = to_timestamp(changetime)
                ticketdict.update(
                    changetime=format_datetime(changetime,
                                               locale=locale,
                                               tzinfo=req.tz),
                    ichangetime=ichangetime,
                    changedsincelastvisit=(last_visit < ichangetime and 1
                                           or 0),
                    changetime_delta=pretty_timedelta(changetime),
                    changetime_link=req.href.timeline(
                        precision='seconds',
                        from_=trac_format_datetime(changetime,
                                                   'iso8601',
                                                   tzinfo=req.tz)))
                if changetime > max_changetime:
                    max_changetime = changetime
                if changetime < min_changetime:
                    min_changetime = changetime
            if 'time' in fields:
                time = ticket.time_created
                ticketdict.update(time=format_datetime(time,
                                                       locale=locale,
                                                       tzinfo=req.tz),
                                  itime=to_timestamp(time),
                                  time_delta=pretty_timedelta(time),
                                  time_link=req.href.timeline(
                                      precision='seconds',
                                      from_=trac_format_datetime(
                                          time, 'iso8601', tzinfo=req.tz)))
                if time > max_time:
                    max_time = time
                if time < min_time:
                    min_time = time
            if 'description' in fields:
                description = ticket.values['description']
                description = moreless(description, 200)
                ticketdict['description'] = description
            if 'notify' in fields:
                ticketdict['notify'] = wl.is_notify(req, 'ticket', sid)
            if 'owner' in fields:
                ticketdict['owner'] = render_elt(ticket.values['owner'])
            if 'reporter' in fields:
                ticketdict['reporter'] = render_elt(ticket.values['reporter'])
            if 'tags' in fields and self.tagsystem:
                tags = []
                for t in self.tagsystem.get_tags(req, Resource('ticket', sid)):
                    tags.extend(
                        [tag.a(t, href=req.href('tags', q=t)),
                         tag(', ')])
                if tags:
                    tags.pop()
                ticketdict['tags'] = moreless(tags, 10)

            ticketlist.append(ticketdict)

        if 'changetime' in fields:
            extradict['max_changetime'] = format_datetime(max_changetime,
                                                          locale=locale,
                                                          tzinfo=req.tz)
            extradict['min_changetime'] = format_datetime(min_changetime,
                                                          locale=locale,
                                                          tzinfo=req.tz)
        if 'time' in fields:
            extradict['max_time'] = format_datetime(max_time,
                                                    locale=locale,
                                                    tzinfo=req.tz)
            extradict['min_time'] = format_datetime(min_time,
                                                    locale=locale,
                                                    tzinfo=req.tz)

        return ticketlist, extradict
Beispiel #21
0
 def _normalize_resource(self, realm_or_resource, id, version):
     if realm_or_resource:
         return Resource(realm_or_resource, id, version)
     else:
         return self._resource
Beispiel #22
0
    def _render_repository_index(self, context, all_repositories, order, desc):
        # Color scale for the age column
        timerange = custom_colorizer = None
        if self.color_scale:
            custom_colorizer = self.get_custom_colorizer()

        rm = RepositoryManager(self.env)
        repositories = []
        for reponame, repoinfo in all_repositories.iteritems():
            if not reponame or as_bool(repoinfo.get('hidden')):
                continue
            try:
                repos = rm.get_repository(reponame)
            except TracError as err:
                entry = (reponame, repoinfo, None, None,
                         exception_to_unicode(err), None)
            else:
                if repos:
                    if not repos.is_viewable(context.perm):
                        continue
                    try:
                        youngest = repos.get_changeset(repos.youngest_rev)
                    except NoSuchChangeset:
                        youngest = None
                    if self.color_scale and youngest:
                        if not timerange:
                            timerange = TimeRange(youngest.date)
                        else:
                            timerange.insert(youngest.date)
                    raw_href = self._get_download_href(context.href, repos,
                                                       None, None)
                    entry = (reponame, repoinfo, repos, youngest, None,
                             raw_href)
                else:
                    entry = (reponame, repoinfo, None, None, u"\u2013", None)
            if entry[4] is not None:  # Check permission in case of error
                root = Resource('repository', reponame).child(self.realm, '/')
                if 'BROWSER_VIEW' not in context.perm(root):
                    continue
            repositories.append(entry)

        # Ordering of repositories
        if order == 'date':
            def repo_order(args):
                reponame, repoinfo, repos, youngest, err, href = args
                return (youngest.date if youngest else to_datetime(0),
                        embedded_numbers(reponame.lower()))
        elif order == 'author':
            def repo_order(args):
                reponame, repoinfo, repos, youngest, err, href = args
                return (youngest.author.lower() if youngest else '',
                        embedded_numbers(reponame.lower()))
        else:
            def repo_order(args):
                reponame, repoinfo, repos, youngest, err, href = args
                return embedded_numbers(reponame.lower())

        repositories = sorted(repositories, key=repo_order, reverse=desc)

        return {'repositories' : repositories,
                'timerange': timerange, 'colorize_age': custom_colorizer}
Beispiel #23
0
 def test_resource_doesnt_exist(self):
     """Non-existent resource returns False from resource_exists."""
     parent = Resource('parent_realm', 'parent_id')
     self.assertTrue(resource_exists(self.env, parent))
     r = parent.child('attachment', 'file.txt')
     self.assertFalse(resource_exists(self.env, r))
Beispiel #24
0
Datei: api.py Projekt: t2y/trac
class Node(object):
    """Represents a directory or file in the repository at a given revision."""

    __metaclass__ = ABCMeta

    DIRECTORY = "dir"
    FILE = "file"

    realm = 'source'

    resource = property(lambda self: Resource(
        self.realm, self.path, version=self.rev, parent=self.repos.resource))

    # created_path and created_rev properties refer to the Node "creation"
    # in the Subversion meaning of a Node in a versioned tree (see #3340).
    #
    # Those properties must be set by subclasses.
    #
    created_rev = None
    created_path = None

    def __init__(self, repos, path, rev, kind):
        assert kind in (Node.DIRECTORY, Node.FILE), \
               "Unknown node kind %s" % kind
        self.repos = repos
        self.path = to_unicode(path)
        self.rev = rev
        self.kind = kind

    @abstractmethod
    def get_content(self):
        """Return a stream for reading the content of the node.

        This method will return `None` for directories.
        The returned object must support a `read([len])` method.
        """
        pass

    def get_processed_content(self, keyword_substitution=True, eol_hint=None):
        """Return a stream for reading the content of the node, with some
        standard processing applied.

        :param keyword_substitution: if `True`, meta-data keywords
            present in the content like ``$Rev$`` are substituted
            (which keyword are substituted and how they are
            substituted is backend specific)

        :param eol_hint: which style of line ending is expected if
            `None` was explicitly specified for the file itself in
            the version control backend (for example in Subversion,
            if it was set to ``'native'``).  It can be `None`,
            ``'LF'``, ``'CR'`` or ``'CRLF'``.
        """
        return self.get_content()

    @abstractmethod
    def get_entries(self):
        """Generator that yields the immediate child entries of a directory.

        The entries are returned in no particular order.
        If the node is a file, this method returns `None`.
        """
        pass

    @abstractmethod
    def get_history(self, limit=None):
        """Provide backward history for this Node.

        Generator that yields `(path, rev, chg)` tuples, one for each revision
        in which the node was changed. This generator will follow copies and
        moves of a node (if the underlying version control system supports
        that), which will be indicated by the first element of the tuple
        (i.e. the path) changing.
        Starts with an entry for the current revision.

        :param limit: if given, yield at most ``limit`` results.
        """
        pass

    def get_previous(self):
        """Return the change event corresponding to the previous revision.

        This returns a `(path, rev, chg)` tuple.
        """
        skip = True
        for p in self.get_history(2):
            if skip:
                skip = False
            else:
                return p

    @abstractmethod
    def get_annotations(self):
        """Provide detailed backward history for the content of this Node.

        Retrieve an array of revisions, one `rev` for each line of content
        for that node.
        Only expected to work on (text) FILE nodes, of course.
        """
        pass

    @abstractmethod
    def get_properties(self):
        """Returns the properties (meta-data) of the node, as a dictionary.

        The set of properties depends on the version control system.
        """
        pass

    @abstractmethod
    def get_content_length(self):
        """The length in bytes of the content.

        Will be `None` for a directory.
        """
        pass

    content_length = property(lambda self: self.get_content_length())

    @abstractmethod
    def get_content_type(self):
        """The MIME type corresponding to the content, if known.

        Will be `None` for a directory.
        """
        pass

    content_type = property(lambda self: self.get_content_type())

    def get_name(self):
        return self.path.split('/')[-1]

    name = property(lambda self: self.get_name())

    @abstractmethod
    def get_last_modified(self):
        pass

    last_modified = property(lambda self: self.get_last_modified())

    isdir = property(lambda self: self.kind == Node.DIRECTORY)
    isfile = property(lambda self: self.kind == Node.FILE)

    def is_viewable(self, perm):
        """Return True if view permission is granted on the node."""
        return ('BROWSER_VIEW' if self.isdir else 'FILE_VIEW') \
               in perm(self.resource)

    can_view = is_viewable  # 0.12 compatibility
Beispiel #25
0
 def test_resource_doesnt_exist(self):
     r = Resource('wiki', 'WikiStart').child('attachment', 'file.txt')
     self.assertEqual(False, AttachmentModule(self.env).resource_exists(r))
Beispiel #26
0
Datei: api.py Projekt: t2y/trac
class Changeset(object):
    """Represents a set of changes committed at once in a repository."""

    __metaclass__ = ABCMeta

    ADD = 'add'
    COPY = 'copy'
    DELETE = 'delete'
    EDIT = 'edit'
    MOVE = 'move'

    # change types which can have diff associated to them
    DIFF_CHANGES = (EDIT, COPY, MOVE)  # MERGE
    OTHER_CHANGES = (ADD, DELETE)
    ALL_CHANGES = DIFF_CHANGES + OTHER_CHANGES

    realm = 'changeset'

    resource = property(lambda self: Resource(
        self.realm, self.rev, parent=self.repos.resource))

    def __init__(self, repos, rev, message, author, date):
        self.repos = repos
        self.rev = rev
        self.message = message or ''
        self.author = author or ''
        self.date = date

    def get_properties(self):
        """Returns the properties (meta-data) of the node, as a dictionary.

        The set of properties depends on the version control system.

        Warning: this used to yield 4-elements tuple (besides `name` and
        `text`, there were `wikiflag` and `htmlclass` values).
        This is now replaced by the usage of IPropertyRenderer (see #1601).
        """
        return []

    @abstractmethod
    def get_changes(self):
        """Generator that produces a tuple for every change in the changeset.

        The tuple will contain `(path, kind, change, base_path, base_rev)`,
        where `change` can be one of Changeset.ADD, Changeset.COPY,
        Changeset.DELETE, Changeset.EDIT or Changeset.MOVE,
        and `kind` is one of Node.FILE or Node.DIRECTORY.
        The `path` is the targeted path for the `change` (which is
        the ''deleted'' path  for a DELETE change).
        The `base_path` and `base_rev` are the source path and rev for the
        action (`None` and `-1` in the case of an ADD change).
        """
        pass

    def get_branches(self):
        """Yield branches to which this changeset belong.
        Each branch is given as a pair `(name, head)`, where `name` is
        the branch name and `head` a flag set if the changeset is a head
        for this branch (i.e. if it has no children changeset).
        """
        return []

    def get_tags(self):
        """Yield tags associated with this changeset.

        .. versionadded :: 1.0
        """
        return []

    def is_viewable(self, perm):
        """Return True if view permission is granted on the changeset."""
        return 'CHANGESET_VIEW' in perm(self.resource)

    can_view = is_viewable  # 0.12 compatibility
Beispiel #27
0
 def test_create_resource(self):
     so = FullTextSearchObject(self.project, Resource('wiki', 'WikiStart'))
     self.assertEquals('wiki', so.realm)
     self.assertEquals('WikiStart', so.id)
     self.assertEquals(Resource('wiki', 'WikiStart'), so.resource)
     self.assertEquals('project1:wiki:WikiStart', so.doc_id)
Beispiel #28
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
Beispiel #29
0
    def _format_name(self, req, url):
        linkname = url
        name = ""
        missing = False

        path_info = url
        query_string = ''
        idx = path_info.find('?')
        if idx >= 0:
            path_info, query_string = path_info[:idx], path_info[idx:]
        href = req.href(path_info) + query_string

        args = arg_list_to_args(parse_arg_list(query_string.lstrip('?')))
        version = args.get('version', False)

        path = path_info.strip('/').split('/')
        realm = path[0]
        class_ = realm
        if len(path) > 1:
            resource = Resource(realm, path[1])
            if resource:
                if realm == 'ticket':
                    linkname = get_resource_shortname(self.env, resource)
                    try:
                        name = get_resource_summary(self.env, resource)
                    except ResourceNotFound:
                        missing = True
                    else:
                        from trac.ticket.model import Ticket
                        class_ = Ticket(self.env, resource.id)['status'] + \
                            ' ' + class_
                elif realm == 'milestone':
                    linkname = get_resource_name(self.env, resource)
                elif realm == 'wiki':
                    resource = Resource(realm, '/'.join(path[1:]), version)
                    linkname = get_resource_shortname(self.env, resource)
                    if version:
                        linkname += '@' + version
                elif realm == 'report':
                    linkname = "{%s}" % path[1]
                    name = self._format_report_name(path[1])
                elif realm == 'changeset':
                    rev = path[1]
                    parent = Resource('source', '/'.join(path[2:]))
                    resource = Resource(realm, rev, False, parent)
                    linkname = "[%s]" % rev
                    name = get_resource_description(self.env, resource)
                elif realm == 'browser':
                    rm = RepositoryManager(self.env)
                    reponame, repos, path = rm.get_repository_by_path('/'.join(
                        path[1:]))
                    parent = Resource('source', reponame)
                    resource = Resource('source', path, False, parent)
                    linkname = get_resource_description(self.env, resource)
                    name = get_resource_summary(self.env, resource)
                elif realm == 'attachment':
                    # Assume a file and check existence
                    parent = Resource(path[1], '/'.join(path[2:-1]))
                    resource = Resource(realm, path[-1], parent=parent)
                    linkname = get_resource_name(self.env, resource)
                    if not resource_exists(self.env, resource):
                        # Assume an attachment list page and check existence
                        parent = Resource(path[1], '/'.join(path[2:]))
                        if resource_exists(self.env, parent):
                            resource = Resource(realm, parent=parent)
                            linkname = get_resource_name(self.env, resource)
                            if not query_string:
                                # Trailing slash needed for Trac < 1.0, t:#10280
                                href += '/'
                        else:
                            # Assume it's a missing attachment
                            missing = True
                else:
                    linkname = get_resource_shortname(self.env, resource)
                    name = get_resource_summary(self.env, resource)
        elif len(path) == 1 and path[0] and path[0] != 'wiki':
            linkname = path[0].capitalize()
        else:
            class_ = 'wiki'
            linkname = 'WikiStart'

        if missing:
            href = None
            class_ = 'missing ' + realm

        return {
            'class_': class_,
            'href': href,
            'linkname': linkname,
            'name': name,
            'delete': req.href.bookmark('delete_in_page', url),
        }
Beispiel #30
0
 def _insert_attachment(self, author):
     parent_resource = Resource('parent_realm', 'parent_id')
     att = Attachment(self.env, 'parent_realm', 'parent_id')
     att.author = author
     att.insert('file.txt', io.BytesIO(), 1)
     return Resource('attachment', 'file.txt', parent=parent_resource)