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