| 1 | # -*- coding: utf-8 -*- |
|---|
| 2 | """ |
|---|
| 3 | = Watchlist Plugin for Trac = |
|---|
| 4 | Plugin Website: http://trac-hacks.org/wiki/WatchlistPlugin |
|---|
| 5 | Trac website: http://trac.edgewall.org/ |
|---|
| 6 | |
|---|
| 7 | Copyright (c) 2008-2010 by Martin Scharrer <martin@scharrer-online.de> |
|---|
| 8 | All rights reserved. |
|---|
| 9 | |
|---|
| 10 | The i18n support was added by Steffen Hoffmann <hoff.st@web.de>. |
|---|
| 11 | |
|---|
| 12 | This program is free software: you can redistribute it and/or modify |
|---|
| 13 | it under the terms of the GNU General Public License as published by |
|---|
| 14 | the Free Software Foundation, either version 3 of the License, or |
|---|
| 15 | (at your option) any later version. |
|---|
| 16 | |
|---|
| 17 | This program is distributed in the hope that it will be useful, |
|---|
| 18 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 19 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 20 | GNU General Public License for more details. |
|---|
| 21 | |
|---|
| 22 | For a copy of the GNU General Public License see |
|---|
| 23 | <http://www.gnu.org/licenses/>. |
|---|
| 24 | |
|---|
| 25 | $Id: wiki.py 15264 2016-02-11 04:22:34Z rjollos $ |
|---|
| 26 | """ |
|---|
| 27 | |
|---|
| 28 | __url__ = ur"$URL: //trac-hacks.org/svn/watchlistplugin/0.12/tracwatchlist/wiki.py $"[6:-2] |
|---|
| 29 | __author__ = ur"$Author: rjollos $"[9:-2] |
|---|
| 30 | __revision__ = int("0" + ur"$Rev: 15264 $"[6:-2].strip('M')) |
|---|
| 31 | __date__ = ur"$Date: 2016-02-11 04:22:34 +0000 (Thu, 11 Feb 2016) $"[7:-2] |
|---|
| 32 | |
|---|
| 33 | from trac.core import * |
|---|
| 34 | from genshi.builder import tag |
|---|
| 35 | from trac.wiki.model import WikiPage |
|---|
| 36 | from trac.wiki.formatter import format_to_oneliner |
|---|
| 37 | from trac.util.datefmt import pretty_timedelta, \ |
|---|
| 38 | datetime, utc, to_timestamp |
|---|
| 39 | from trac.util.text import obfuscate_email_address |
|---|
| 40 | |
|---|
| 41 | from trac.util.datefmt import format_datetime as trac_format_datetime |
|---|
| 42 | from trac.web.chrome import Chrome |
|---|
| 43 | from trac.mimeview.api import Context |
|---|
| 44 | from trac.resource import Resource |
|---|
| 45 | from trac.attachment import Attachment |
|---|
| 46 | |
|---|
| 47 | from tracwatchlist.api import BasicWatchlist |
|---|
| 48 | from tracwatchlist.translation import _, N_, T_, t_, tag_, ngettext |
|---|
| 49 | from tracwatchlist.util import moreless, format_datetime, LC_TIME,\ |
|---|
| 50 | convert_to_sql_wildcards |
|---|
| 51 | |
|---|
| 52 | |
|---|
| 53 | class WikiWatchlist(BasicWatchlist): |
|---|
| 54 | """Watchlist entry for wiki pages.""" |
|---|
| 55 | realms = ['wiki'] |
|---|
| 56 | fields = {'wiki':{ |
|---|
| 57 | 'changetime': T_("Modified"), |
|---|
| 58 | 'author' : T_("Author"), |
|---|
| 59 | 'version' : T_("Version"), |
|---|
| 60 | 'diff' : T_("Diff"), |
|---|
| 61 | 'history' : T_("History"), |
|---|
| 62 | # TRANSLATOR: Abbreviated label for 'unwatch' column header. |
|---|
| 63 | # Should be a single character to not widen the column. |
|---|
| 64 | 'unwatch' : N_("U"), |
|---|
| 65 | # TRANSLATOR: Label for 'notify' column header. |
|---|
| 66 | # Should tell the user that notifications can be switched on or off |
|---|
| 67 | # with the check-boxes in this column. |
|---|
| 68 | 'notify' : N_("Notify"), |
|---|
| 69 | 'comment' : T_("Comment"), |
|---|
| 70 | |
|---|
| 71 | 'readonly' : N_("read-only"), |
|---|
| 72 | # T#RANSLATOR: IP = Internet Protocol (address) |
|---|
| 73 | #'ipnr' : N_("IP"), # Note: not supported by Trac 0.12 WikiPage class |
|---|
| 74 | }} |
|---|
| 75 | default_fields = {'wiki':[ |
|---|
| 76 | 'name', 'changetime', 'author', 'version', 'diff', |
|---|
| 77 | 'history', 'unwatch', 'notify', 'comment', |
|---|
| 78 | ]} |
|---|
| 79 | tagsystem = None |
|---|
| 80 | |
|---|
| 81 | |
|---|
| 82 | def __init__(self): |
|---|
| 83 | self.fields['wiki']['name'] = self.get_realm_label('wiki') |
|---|
| 84 | try: # Try to support the Tags Plugin |
|---|
| 85 | from tractags.api import TagSystem |
|---|
| 86 | self.tagsystem = self.env[TagSystem] |
|---|
| 87 | except ImportError, e: |
|---|
| 88 | pass |
|---|
| 89 | else: |
|---|
| 90 | if self.tagsystem: |
|---|
| 91 | self.fields['wiki']['tags'] = _("Tags") |
|---|
| 92 | |
|---|
| 93 | |
|---|
| 94 | def get_realm_label(self, realm, n_plural=1, astitle=False): |
|---|
| 95 | if astitle: |
|---|
| 96 | # TRANSLATOR: 'wiki page(s)' as title |
|---|
| 97 | return ngettext("Wiki Page", "Wiki Pages", n_plural) |
|---|
| 98 | else: |
|---|
| 99 | # TRANSLATOR: 'wiki page(s)' inside a sentence |
|---|
| 100 | return ngettext("wiki page", "wiki pages", n_plural) |
|---|
| 101 | |
|---|
| 102 | |
|---|
| 103 | def _get_sql(self, resids, fuzzy, var='name'): |
|---|
| 104 | if isinstance(resids,basestring): |
|---|
| 105 | if fuzzy: |
|---|
| 106 | resids += '*' |
|---|
| 107 | args = convert_to_sql_wildcards(resids).replace(',',' ').split() |
|---|
| 108 | sql = ' OR '.join((' ' + var + " LIKE %s ESCAPE '|' ",) * len(args)) |
|---|
| 109 | else: |
|---|
| 110 | args = list(resids) |
|---|
| 111 | if (len(args) == 1): |
|---|
| 112 | sql = ' ' + var + '=%s ' |
|---|
| 113 | else: |
|---|
| 114 | sql = ' ' + var + ' IN (' + ','.join(('%s',) * len(args)) + ') ' |
|---|
| 115 | return sql, args |
|---|
| 116 | |
|---|
| 117 | |
|---|
| 118 | def resources_exists(self, realm, resids, fuzzy=0): |
|---|
| 119 | if not resids: |
|---|
| 120 | return [] |
|---|
| 121 | sql, args = self._get_sql(resids, fuzzy) |
|---|
| 122 | if not sql: |
|---|
| 123 | return [] |
|---|
| 124 | db = self.env.get_db_cnx() |
|---|
| 125 | cursor = db.cursor() |
|---|
| 126 | cursor.execute(""" |
|---|
| 127 | SELECT DISTINCT name |
|---|
| 128 | FROM wiki |
|---|
| 129 | WHERE |
|---|
| 130 | """ + sql, args) |
|---|
| 131 | return [ unicode(v[0]) for v in cursor.fetchall() ] |
|---|
| 132 | |
|---|
| 133 | |
|---|
| 134 | def watched_resources(self, realm, resids, user, wl, fuzzy=0): |
|---|
| 135 | if not resids: |
|---|
| 136 | return [] |
|---|
| 137 | sql, args = self._get_sql(resids, fuzzy, 'resid') |
|---|
| 138 | if not sql: |
|---|
| 139 | return [] |
|---|
| 140 | db = self.env.get_db_cnx() |
|---|
| 141 | cursor = db.cursor() |
|---|
| 142 | cursor.log = self.log |
|---|
| 143 | cursor.execute(""" |
|---|
| 144 | SELECT resid |
|---|
| 145 | FROM watchlist |
|---|
| 146 | WHERE wluser=%s AND realm='wiki' AND ( |
|---|
| 147 | """ + sql + " )", [user] + args) |
|---|
| 148 | return [ unicode(v[0]) for v in cursor.fetchall() ] |
|---|
| 149 | |
|---|
| 150 | |
|---|
| 151 | def unwatched_resources(self, realm, resids, user, wl, fuzzy=0): |
|---|
| 152 | if not resids: |
|---|
| 153 | return [] |
|---|
| 154 | sql, args = self._get_sql(resids, fuzzy) |
|---|
| 155 | if not sql: |
|---|
| 156 | return [] |
|---|
| 157 | db = self.env.get_db_cnx() |
|---|
| 158 | cursor = db.cursor() |
|---|
| 159 | cursor.execute(""" |
|---|
| 160 | SELECT DISTINCT name |
|---|
| 161 | FROM wiki |
|---|
| 162 | WHERE name NOT in ( |
|---|
| 163 | SELECT resid |
|---|
| 164 | FROM watchlist |
|---|
| 165 | WHERE wluser=%s AND realm='wiki' |
|---|
| 166 | ) AND ( |
|---|
| 167 | """ + sql + " )", [user] + args) |
|---|
| 168 | return [ unicode(v[0]) for v in cursor.fetchall() ] |
|---|
| 169 | |
|---|
| 170 | |
|---|
| 171 | def get_list(self, realm, wl, req, fields=None): |
|---|
| 172 | db = self.env.get_db_cnx() |
|---|
| 173 | cursor = db.cursor() |
|---|
| 174 | user = req.authname |
|---|
| 175 | locale = getattr(req, 'locale', None) or LC_TIME |
|---|
| 176 | context = Context.from_request(req) |
|---|
| 177 | wikilist = [] |
|---|
| 178 | extradict = {} |
|---|
| 179 | if not fields: |
|---|
| 180 | fields = set(self.default_fields['wiki']) |
|---|
| 181 | else: |
|---|
| 182 | fields = set(fields) |
|---|
| 183 | |
|---|
| 184 | if 'changetime' in fields: |
|---|
| 185 | max_changetime = datetime(1970,1,1,tzinfo=utc) |
|---|
| 186 | min_changetime = datetime.now(utc) |
|---|
| 187 | |
|---|
| 188 | for name, last_visit in wl.get_watched_resources( 'wiki', req.authname ): |
|---|
| 189 | wikipage = WikiPage(self.env, name, db=db) |
|---|
| 190 | wikidict = {} |
|---|
| 191 | |
|---|
| 192 | if not wikipage.exists: |
|---|
| 193 | wikidict['deleted'] = True |
|---|
| 194 | if 'name' in fields: |
|---|
| 195 | wikidict['name'] = name |
|---|
| 196 | if 'author' in fields: |
|---|
| 197 | wikidict['author'] = '?' |
|---|
| 198 | if 'changetime' in fields: |
|---|
| 199 | wikidict['changedsincelastvisit'] = 1 |
|---|
| 200 | wikidict['changetime'] = '?' |
|---|
| 201 | wikidict['ichangetime'] = 0 |
|---|
| 202 | if 'comment' in fields: |
|---|
| 203 | wikidict['comment'] = tag.strong(t_("deleted"), class_='deleted') |
|---|
| 204 | if 'notify' in fields: |
|---|
| 205 | wikidict['notify'] = wl.is_notify(req, 'wiki', name) |
|---|
| 206 | wikilist.append(wikidict) |
|---|
| 207 | continue |
|---|
| 208 | |
|---|
| 209 | comment = wikipage.comment |
|---|
| 210 | changetime = wikipage.time |
|---|
| 211 | author = wikipage.author |
|---|
| 212 | if wl.options['attachment_changes']: |
|---|
| 213 | latest_attachment = None |
|---|
| 214 | for attachment in Attachment.select(self.env, 'wiki', name, db): |
|---|
| 215 | if attachment.date > changetime: |
|---|
| 216 | latest_attachment = attachment |
|---|
| 217 | if latest_attachment: |
|---|
| 218 | changetime = latest_attachment.date |
|---|
| 219 | author = latest_attachment.author |
|---|
| 220 | if 'comment' in fields: |
|---|
| 221 | wikitext = '[attachment:"' + ':'.join([latest_attachment.filename,'wiki',name]) + \ |
|---|
| 222 | '" ' + latest_attachment.filename + ']' |
|---|
| 223 | desc = latest_attachment.description |
|---|
| 224 | comment = tag(tag_("Attachment %(attachment)s added",\ |
|---|
| 225 | attachment=format_to_oneliner(self.env, context, wikitext, shorten=False)), |
|---|
| 226 | desc and ': ' or '.', moreless(desc,10)) |
|---|
| 227 | if 'attachment' in fields: |
|---|
| 228 | attachments = [] |
|---|
| 229 | for attachment in Attachment.select(self.env, 'wiki', name, db): |
|---|
| 230 | wikitext = '[attachment:"' + ':'.join([attachment.filename,'wiki',name]) + '" ' + attachment.filename + ']' |
|---|
| 231 | attachments.extend([tag(', '), format_to_oneliner(self.env, context, wikitext, shorten=False)]) |
|---|
| 232 | if attachments: |
|---|
| 233 | attachments.reverse() |
|---|
| 234 | attachments.pop() |
|---|
| 235 | ticketdict['attachment'] = moreless(attachments, 5) |
|---|
| 236 | if 'name' in fields: |
|---|
| 237 | wikidict['name'] = name |
|---|
| 238 | if 'author' in fields: |
|---|
| 239 | if not (Chrome(self.env).show_email_addresses or |
|---|
| 240 | 'EMAIL_VIEW' in req.perm(wikipage.resource)): |
|---|
| 241 | wikidict['author'] = obfuscate_email_address(author) |
|---|
| 242 | else: |
|---|
| 243 | wikidict['author'] = author |
|---|
| 244 | if 'version' in fields: |
|---|
| 245 | wikidict['version'] = unicode(wikipage.version) |
|---|
| 246 | if 'changetime' in fields: |
|---|
| 247 | wikidict['changetime'] = format_datetime( changetime, locale=locale, tzinfo=req.tz ) |
|---|
| 248 | wikidict['ichangetime'] = to_timestamp( changetime ) |
|---|
| 249 | wikidict['changedsincelastvisit'] = last_visit < wikidict['ichangetime'] and 1 or 0 |
|---|
| 250 | wikidict['timedelta'] = pretty_timedelta( changetime ) |
|---|
| 251 | wikidict['timeline_link'] = req.href.timeline(precision='seconds', |
|---|
| 252 | from_=trac_format_datetime ( changetime, 'iso8601', tzinfo=req.tz)) |
|---|
| 253 | if changetime > max_changetime: |
|---|
| 254 | max_changetime = changetime |
|---|
| 255 | if changetime < min_changetime: |
|---|
| 256 | min_changetime = changetime |
|---|
| 257 | if 'comment' in fields: |
|---|
| 258 | comment = moreless(comment or "", 200) |
|---|
| 259 | wikidict['comment'] = comment |
|---|
| 260 | if 'notify' in fields: |
|---|
| 261 | wikidict['notify'] = wl.is_notify(req, 'wiki', name) |
|---|
| 262 | if 'readonly' in fields: |
|---|
| 263 | wikidict['readonly'] = wikipage.readonly and t_("yes") or t_("no") |
|---|
| 264 | if 'tags' in fields and self.tagsystem: |
|---|
| 265 | tags = [] |
|---|
| 266 | for t in self.tagsystem.get_tags(req, Resource('wiki', name)): |
|---|
| 267 | tags.extend([tag.a(t,href=req.href('tags',q=t)), tag(', ')]) |
|---|
| 268 | if tags: |
|---|
| 269 | tags.pop() |
|---|
| 270 | wikidict['tags'] = moreless(tags, 10) |
|---|
| 271 | #if 'ipnr' in fields: |
|---|
| 272 | # wikidict['ipnr'] = wikipage.ipnr, # Note: Not supported by Trac 0.12 |
|---|
| 273 | wikilist.append(wikidict) |
|---|
| 274 | |
|---|
| 275 | if 'changetime' in fields: |
|---|
| 276 | extradict['max_changetime'] = format_datetime( max_changetime, locale=locale, tzinfo=req.tz ) |
|---|
| 277 | extradict['min_changetime'] = format_datetime( min_changetime, locale=locale, tzinfo=req.tz ) |
|---|
| 278 | |
|---|
| 279 | return wikilist, extradict |
|---|
| 280 | |
|---|
| 281 | # EOF |
|---|