| 1 | # -*- coding: utf-8 -*- |
|---|
| 2 | # |
|---|
| 3 | # Copyright (C) 2006 Alec Thomas <alec@swapoff.org> |
|---|
| 4 | # |
|---|
| 5 | # This software is licensed as described in the file COPYING, which |
|---|
| 6 | # you should have received as part of this distribution. |
|---|
| 7 | # |
|---|
| 8 | |
|---|
| 9 | from trac.core import * |
|---|
| 10 | from tractags.api import DefaultTagProvider, TagSystem |
|---|
| 11 | from trac.web.chrome import add_stylesheet |
|---|
| 12 | from trac.wiki.api import IWikiSyntaxProvider |
|---|
| 13 | from trac.resource import Resource, render_resource_link, get_resource_url |
|---|
| 14 | from trac.mimeview.api import Context |
|---|
| 15 | from trac.web.api import ITemplateStreamFilter |
|---|
| 16 | from trac.wiki.api import IWikiPageManipulator, IWikiChangeListener |
|---|
| 17 | from trac.util.compat import sorted |
|---|
| 18 | from genshi.builder import tag |
|---|
| 19 | from genshi.filters.transform import Transformer |
|---|
| 20 | |
|---|
| 21 | |
|---|
| 22 | class WikiTagProvider(DefaultTagProvider): |
|---|
| 23 | """Tag provider for the Wiki.""" |
|---|
| 24 | realm = 'wiki' |
|---|
| 25 | |
|---|
| 26 | def check_permission(self, perm, operation): |
|---|
| 27 | map = {'view': 'WIKI_VIEW', 'modify': 'WIKI_MODIFY'} |
|---|
| 28 | return super(WikiTagProvider, self).check_permission(perm, operation) \ |
|---|
| 29 | and map[operation] in perm |
|---|
| 30 | |
|---|
| 31 | |
|---|
| 32 | class WikiTagInterface(Component): |
|---|
| 33 | """Implement the user interface for tagging Wiki pages.""" |
|---|
| 34 | implements(ITemplateStreamFilter, IWikiPageManipulator, |
|---|
| 35 | IWikiChangeListener) |
|---|
| 36 | |
|---|
| 37 | # ITemplateStreamFilter methods |
|---|
| 38 | def filter_stream(self, req, method, filename, stream, data): |
|---|
| 39 | page_name = req.args.get('page', 'WikiStart') |
|---|
| 40 | resource = Resource('wiki', page_name) |
|---|
| 41 | if filename == 'wiki_view.html' and 'TAGS_VIEW' in req.perm(resource): |
|---|
| 42 | return self._wiki_view(req, stream) |
|---|
| 43 | elif filename == 'wiki_edit.html' and 'TAGS_MODIFY' in req.perm(resource): |
|---|
| 44 | return self._wiki_edit(req, stream) |
|---|
| 45 | return stream |
|---|
| 46 | |
|---|
| 47 | # IWikiPageManipulator methods |
|---|
| 48 | def prepare_wiki_page(self, req, page, fields): |
|---|
| 49 | pass |
|---|
| 50 | |
|---|
| 51 | def validate_wiki_page(self, req, page): |
|---|
| 52 | if req and 'TAGS_MODIFY' in req.perm(page.resource) \ |
|---|
| 53 | and req.path_info.startswith('/wiki') and 'save' in req.args: |
|---|
| 54 | if self._update_tags(req, page) and \ |
|---|
| 55 | page.text == page.old_text and \ |
|---|
| 56 | page.readonly == int('readonly' in req.args): |
|---|
| 57 | req.redirect(get_resource_url(self.env, page.resource, req.href, version=None)) |
|---|
| 58 | return [] |
|---|
| 59 | |
|---|
| 60 | # IWikiChangeListener methods |
|---|
| 61 | def wiki_page_added(self, page): |
|---|
| 62 | pass |
|---|
| 63 | |
|---|
| 64 | def wiki_page_changed(self, page, version, t, comment, author, ipnr): |
|---|
| 65 | pass |
|---|
| 66 | |
|---|
| 67 | def wiki_page_deleted(self, page): |
|---|
| 68 | tag_system = TagSystem(self.env) |
|---|
| 69 | # XXX Ugh. Hopefully this will be sufficient to full any endpoints. |
|---|
| 70 | from trac.test import Mock, MockPerm |
|---|
| 71 | req = Mock(authname='anonymous', perm=MockPerm()) |
|---|
| 72 | tag_system.delete_tags(req, page.resource) |
|---|
| 73 | |
|---|
| 74 | def wiki_page_version_deleted(self, page): |
|---|
| 75 | pass |
|---|
| 76 | |
|---|
| 77 | # Internal methods |
|---|
| 78 | def _page_tags(self, req): |
|---|
| 79 | pagename = req.args.get('page', 'WikiStart') |
|---|
| 80 | |
|---|
| 81 | tag_system = TagSystem(self.env) |
|---|
| 82 | resource = Resource('wiki', pagename) |
|---|
| 83 | tags = sorted(tag_system.get_tags(req, resource)) |
|---|
| 84 | return tags |
|---|
| 85 | |
|---|
| 86 | def _wiki_view(self, req, stream): |
|---|
| 87 | tags = self._page_tags(req) |
|---|
| 88 | if not tags: |
|---|
| 89 | return stream |
|---|
| 90 | tag_system = TagSystem(self.env) |
|---|
| 91 | add_stylesheet(req, 'tags/css/tractags.css') |
|---|
| 92 | li = [] |
|---|
| 93 | for tag_ in tags: |
|---|
| 94 | resource = Resource('tag', tag_) |
|---|
| 95 | anchor = render_resource_link(self.env, |
|---|
| 96 | Context.from_request(req, resource), resource) |
|---|
| 97 | li.append(tag.li(anchor, ' ')) |
|---|
| 98 | |
|---|
| 99 | insert = tag.ul(class_='tags')(tag.li('Tags', class_='header'), li) |
|---|
| 100 | return stream | Transformer('//div[@class="buttons"]').before(insert) |
|---|
| 101 | |
|---|
| 102 | def _update_tags(self, req, page): |
|---|
| 103 | tag_system = TagSystem(self.env) |
|---|
| 104 | newtags = tag_system.split_into_tags(req.args.get('tags', '')) |
|---|
| 105 | oldtags = tag_system.get_tags(req, page.resource) |
|---|
| 106 | |
|---|
| 107 | if oldtags != newtags: |
|---|
| 108 | tag_system.set_tags(req, page.resource, newtags) |
|---|
| 109 | return True |
|---|
| 110 | return False |
|---|
| 111 | |
|---|
| 112 | def _wiki_edit(self, req, stream): |
|---|
| 113 | insert = tag.div(class_='field')( |
|---|
| 114 | tag.label( |
|---|
| 115 | 'Tag under: (', tag.a('view all tags', href=req.href.tags()), ')', |
|---|
| 116 | tag.br(), |
|---|
| 117 | tag.input(id='tags', type='text', name='tags', size='30', |
|---|
| 118 | value=req.args.get('tags', ' '.join(self._page_tags(req)))), |
|---|
| 119 | ) |
|---|
| 120 | ) |
|---|
| 121 | return stream | Transformer('//div[@id="changeinfo1"]').append(insert) |
|---|
| 122 | |
|---|
| 123 | |
|---|
| 124 | class TagWikiSyntaxProvider(Component): |
|---|
| 125 | """Provide tag:<expr> links.""" |
|---|
| 126 | |
|---|
| 127 | implements(IWikiSyntaxProvider) |
|---|
| 128 | |
|---|
| 129 | # IWikiSyntaxProvider methods |
|---|
| 130 | def get_wiki_syntax(self): |
|---|
| 131 | yield (r'''\[tag(?:ged)?:(?P<tlpexpr>(?:'.*?'|".*?"|\S)+)\s+(?P<tlptitle>.*?]*)\]''', |
|---|
| 132 | lambda f, n, m: self._format_tagged(f, |
|---|
| 133 | m.group('tlpexpr'), |
|---|
| 134 | m.group('tlptitle'))) |
|---|
| 135 | yield (r'''(?P<tagsyn>tag(?:ged)?):(?P<texpr>(?:'.*?'|".*?"|\S)+)''', |
|---|
| 136 | lambda f, n, m: self._format_tagged(f, |
|---|
| 137 | m.group('texpr'), |
|---|
| 138 | '%s:%s' % (m.group('tagsyn'), m.group('texpr')))) |
|---|
| 139 | |
|---|
| 140 | def get_link_resolvers(self): |
|---|
| 141 | return [] |
|---|
| 142 | |
|---|
| 143 | def _format_tagged(self, formatter, target, label): |
|---|
| 144 | if label: |
|---|
| 145 | href = formatter.context.href |
|---|
| 146 | url = get_resource_url(self.env, Resource('tag', target), href) |
|---|
| 147 | return tag.a(label, href=url) |
|---|
| 148 | return render_resource_link(self.env, formatter.context, |
|---|
| 149 | Resource('tag', target)) |
|---|
| 150 | |
|---|