Package trac :: Package wiki :: Module web_ui

Source Code for Module trac.wiki.web_ui

  1  # -*- coding: utf-8 -*- 
  2  # 
  3  # Copyright (C) 2003-2020 Edgewall Software 
  4  # Copyright (C) 2003-2005 Jonas Borgström <[email protected]> 
  5  # Copyright (C) 2004-2005 Christopher Lenz <[email protected]> 
  6  # All rights reserved. 
  7  # 
  8  # This software is licensed as described in the file COPYING, which 
  9  # you should have received as part of this distribution. The terms 
 10  # are also available at https://trac.edgewall.org/wiki/TracLicense. 
 11  # 
 12  # This software consists of voluntary contributions made by many 
 13  # individuals. For the exact contribution history, see the revision 
 14  # history and logs, available at https://trac.edgewall.org/log/. 
 15  # 
 16  # Author: Jonas Borgström <[email protected]> 
 17  #         Christopher Lenz <[email protected]> 
 18   
 19  import pkg_resources 
 20  import re 
 21   
 22  from genshi.builder import tag 
 23   
 24  from trac.attachment import AttachmentModule, Attachment 
 25  from trac.config import IntOption 
 26  from trac.core import * 
 27  from trac.mimeview.api import IContentConverter, Mimeview 
 28  from trac.perm import IPermissionPolicy, IPermissionRequestor 
 29  from trac.resource import * 
 30  from trac.search import ISearchSource, search_to_sql, shorten_result 
 31  from trac.timeline.api import ITimelineEventProvider 
 32  from trac.util import as_int, get_reporter_id 
 33  from trac.util.datefmt import from_utimestamp, to_utimestamp 
 34  from trac.util.text import shorten_line 
 35  from trac.util.translation import _, tag_ 
 36  from trac.versioncontrol.diff import get_diff_options, diff_blocks 
 37  from trac.web.api import HTTPBadRequest, IRequestHandler 
 38  from trac.web.chrome import (Chrome, INavigationContributor, 
 39                               ITemplateProvider, add_ctxtnav, add_link, 
 40                               add_notice, add_script, add_stylesheet, 
 41                               add_warning, prevnext_nav, web_context) 
 42  from trac.wiki.api import IWikiPageManipulator, WikiSystem, validate_page_name 
 43  from trac.wiki.formatter import format_to, OneLinerFormatter 
 44  from trac.wiki.model import WikiPage 
 45   
 46   
47 -class WikiModule(Component):
48 49 implements(IContentConverter, INavigationContributor, 50 IPermissionRequestor, IRequestHandler, ITimelineEventProvider, 51 ISearchSource, ITemplateProvider) 52 53 page_manipulators = ExtensionPoint(IWikiPageManipulator) 54 55 realm = WikiSystem.realm 56 57 max_size = IntOption('wiki', 'max_size', 262144, 58 """Maximum allowed wiki page size in characters.""") 59 60 default_edit_area_height = IntOption('wiki', 'default_edit_area_height', 61 20, 62 """Default height of the textarea on the wiki edit page. 63 (//Since 1.1.5//)""") 64 65 PAGE_TEMPLATES_PREFIX = 'PageTemplates/' 66 DEFAULT_PAGE_TEMPLATE = 'DefaultPage' 67 68 # IContentConverter methods 69
71 yield ('txt', _("Plain Text"), 'txt', 'text/x-trac-wiki', 72 'text/plain', 9)
73
74 - def convert_content(self, req, mimetype, content, key):
75 return content, 'text/plain;charset=utf-8'
76 77 # INavigationContributor methods 78
79 - def get_active_navigation_item(self, req):
80 return 'wiki'
81
82 - def get_navigation_items(self, req):
83 if 'WIKI_VIEW' in req.perm(self.realm, 'WikiStart'): 84 yield ('mainnav', 'wiki', 85 tag.a(_("Wiki"), href=req.href.wiki(), accesskey=1)) 86 if 'WIKI_VIEW' in req.perm(self.realm, 'TracGuide'): 87 yield ('metanav', 'help', 88 tag.a(_("Help/Guide"), href=req.href.wiki('TracGuide'), 89 accesskey=6))
90 91 # IPermissionRequestor methods 92
93 - def get_permission_actions(self):
94 actions = ['WIKI_CREATE', 'WIKI_DELETE', 'WIKI_MODIFY', 'WIKI_RENAME', 95 'WIKI_VIEW'] 96 return actions + [('WIKI_ADMIN', actions)]
97 98 # IRequestHandler methods 99
100 - def match_request(self, req):
101 match = re.match(r'/wiki(?:/(.+))?$', req.path_info) 102 if match: 103 if match.group(1): 104 req.args['page'] = match.group(1) 105 return 1
106
107 - def process_request(self, req):
108 action = req.args.get('action', 'view') 109 pagename = req.args.get('page', 'WikiStart') 110 version = req.args.getint('version') 111 old_version = req.args.getint('old_version') 112 113 if pagename.startswith('/') or pagename.endswith('/') or \ 114 '//' in pagename: 115 pagename = re.sub(r'/{2,}', '/', pagename.strip('/')) 116 req.redirect(req.href.wiki(pagename)) 117 if not validate_page_name(pagename): 118 raise TracError(_("Invalid Wiki page name '%(name)s'", 119 name=pagename)) 120 121 page = WikiPage(self.env, pagename) 122 versioned_page = WikiPage(self.env, pagename, version) 123 124 req.perm(versioned_page.resource).require('WIKI_VIEW') 125 126 if version and versioned_page.version != version: 127 raise ResourceNotFound( 128 _('No version "%(num)s" for Wiki page "%(name)s"', 129 num=version, name=page.name)) 130 131 add_stylesheet(req, 'common/css/wiki.css') 132 133 if req.method == 'POST': 134 if action == 'edit': 135 if 'cancel' in req.args: 136 req.redirect(req.href.wiki(page.name)) 137 138 has_collision = version != page.version 139 for a in ('preview', 'diff', 'merge'): 140 if a in req.args: 141 action = a 142 break 143 versioned_page.text = req.args.get('text') 144 valid = self._validate(req, versioned_page) 145 if action == 'edit' and not has_collision and valid: 146 return self._do_save(req, versioned_page) 147 else: 148 return self._render_editor(req, page, action, 149 has_collision) 150 elif action == 'edit_comment': 151 self._do_edit_comment(req, versioned_page) 152 elif action == 'delete': 153 self._do_delete(req, versioned_page) 154 elif action == 'rename': 155 return self._do_rename(req, page) 156 elif action == 'diff': 157 style, options, diff_data = get_diff_options(req) 158 contextall = diff_data['options']['contextall'] 159 req.redirect(req.href.wiki(versioned_page.name, action='diff', 160 old_version=old_version, 161 version=version, 162 contextall=contextall or None)) 163 else: 164 raise HTTPBadRequest(_("Invalid request arguments.")) 165 elif action == 'delete': 166 return self._render_confirm_delete(req, page) 167 elif action == 'rename': 168 return self._render_confirm_rename(req, page) 169 elif action == 'edit': 170 return self._render_editor(req, page) 171 elif action == 'edit_comment': 172 return self._render_edit_comment(req, versioned_page) 173 elif action == 'diff': 174 return self._render_diff(req, versioned_page) 175 elif action == 'history': 176 return self._render_history(req, versioned_page) 177 else: 178 format = req.args.get('format') 179 if format: 180 Mimeview(self.env).send_converted(req, 'text/x-trac-wiki', 181 versioned_page.text, 182 format, versioned_page.name) 183 return self._render_view(req, versioned_page)
184 185 # ITemplateProvider methods 186
187 - def get_htdocs_dirs(self):
188 return []
189
190 - def get_templates_dirs(self):
191 return [pkg_resources.resource_filename('trac.wiki', 'templates')]
192 193 # Internal methods 194
195 - def _validate(self, req, page):
196 valid = True 197 198 # Validate page size 199 if len(req.args.get('text', '')) > self.max_size: 200 add_warning(req, _("The wiki page is too long (must be less " 201 "than %(num)s characters)", 202 num=self.max_size)) 203 valid = False 204 205 # Give the manipulators a pass at post-processing the page 206 for manipulator in self.page_manipulators: 207 for field, message in manipulator.validate_wiki_page(req, page): 208 valid = False 209 if field: 210 add_warning(req, tag_("The Wiki page field %(field)s" 211 " is invalid: %(message)s", 212 field=tag.strong(field), 213 message=message)) 214 else: 215 add_warning(req, tag_("Invalid Wiki page: %(message)s", 216 message=message)) 217 return valid
218
219 - def _page_data(self, req, page, action=''):
220 title = get_resource_summary(self.env, page.resource) 221 if action: 222 title += ' (%s)' % action 223 return {'page': page, 'action': action, 'title': title}
224
225 - def _prepare_diff(self, req, page, old_text, new_text, 226 old_version, new_version):
227 diff_style, diff_options, diff_data = get_diff_options(req) 228 diff_context = 3 229 for option in diff_options: 230 if option.startswith('-U'): 231 diff_context = int(option[2:]) 232 break 233 if diff_context < 0: 234 diff_context = None 235 diffs = diff_blocks(old_text, new_text, context=diff_context, 236 ignore_blank_lines='-B' in diff_options, 237 ignore_case='-i' in diff_options, 238 ignore_space_changes='-b' in diff_options) 239 def version_info(v, last=0): 240 return {'path': get_resource_name(self.env, page.resource), 241 # TRANSLATOR: wiki page 242 'rev': v or _("currently edited"), 243 'shortrev': v or last + 1, 244 'href': req.href.wiki(page.name, version=v) 245 if v else None}
246 changes = [{'diffs': diffs, 'props': [], 247 'new': version_info(new_version, old_version), 248 'old': version_info(old_version)}] 249 250 add_stylesheet(req, 'common/css/diff.css') 251 add_script(req, 'common/js/diff.js') 252 return diff_data, changes
253
254 - def _do_edit_comment(self, req, page):
255 req.perm(page.resource).require('WIKI_ADMIN') 256 257 if 'cancel' in req.args: 258 req.redirect(req.href.wiki(page.name, action='history')) 259 260 new_comment = req.args.get('new_comment') 261 262 page.edit_comment(new_comment) 263 add_notice(req, _("The comment of version %(version)s of the page " 264 "%(name)s has been updated.", 265 version=page.version, name=page.name)) 266 req.redirect(req.href.wiki(page.name, action='history'))
267
268 - def _do_delete(self, req, page):
269 req.perm(page.resource).require('WIKI_DELETE') 270 271 if 'cancel' in req.args: 272 req.redirect(get_resource_url(self.env, page.resource, req.href)) 273 274 version = req.args.getint('version') 275 old_version = req.args.getint('old_version', version) 276 277 with self.env.db_transaction: 278 if version and old_version and version > old_version: 279 # delete from `old_version` exclusive to `version` inclusive: 280 for v in range(old_version, version): 281 page.delete(v + 1) 282 else: 283 # only delete that `version`, or the whole page if `None` 284 page.delete(version) 285 286 if not page.exists: 287 add_notice(req, _("The page %(name)s has been deleted.", 288 name=page.name)) 289 req.redirect(req.href.wiki()) 290 else: 291 if version and old_version and version > old_version + 1: 292 add_notice(req, _("The versions %(from_)d to %(to)d of the " 293 "page %(name)s have been deleted.", 294 from_=old_version + 1, to=version, name=page.name)) 295 else: 296 add_notice(req, _("The version %(version)d of the page " 297 "%(name)s has been deleted.", 298 version=version, name=page.name)) 299 req.redirect(req.href.wiki(page.name))
300
301 - def _do_rename(self, req, page):
302 req.perm(page.resource).require('WIKI_RENAME') 303 304 if 'cancel' in req.args: 305 req.redirect(get_resource_url(self.env, page.resource, req.href)) 306 307 old_name, old_version = page.name, page.version 308 new_name = req.args.get('new_name', '') 309 new_name = re.sub(r'/{2,}', '/', new_name.strip('/')) 310 redirect = req.args.get('redirect') 311 312 # verify input parameters 313 warn = None 314 if not new_name: 315 warn = _("A new name is mandatory for a rename.") 316 elif not validate_page_name(new_name): 317 warn = _("The new name is invalid (a name which is separated " 318 "with slashes cannot be '.' or '..').") 319 elif new_name == old_name: 320 warn = _("The new name must be different from the old name.") 321 elif WikiPage(self.env, new_name).exists: 322 warn = _("The page %(name)s already exists.", name=new_name) 323 if warn: 324 add_warning(req, warn) 325 return self._render_confirm_rename(req, page, new_name) 326 327 with self.env.db_transaction as db: 328 page.rename(new_name) 329 if redirect: 330 redirection = WikiPage(self.env, old_name) 331 redirection.text = _('See [wiki:"%(name)s"].', name=new_name) 332 author = get_reporter_id(req) 333 comment = u'[wiki:"%s@%d" %s] \u2192 [wiki:"%s"].' % ( 334 new_name, old_version, old_name, new_name) 335 redirection.save(author, comment, req.remote_addr) 336 337 add_notice(req, _("The page %(old_name)s has been renamed to " 338 "%(new_name)s.", old_name=old_name, 339 new_name=new_name)) 340 if redirect: 341 add_notice(req, _("The page %(old_name)s has been recreated " 342 "with a redirect to %(new_name)s.", 343 old_name=old_name, new_name=new_name)) 344 345 req.redirect(req.href.wiki(old_name if redirect else new_name))
346
347 - def _do_save(self, req, page):
348 if not page.exists: 349 req.perm(page.resource).require('WIKI_CREATE') 350 else: 351 req.perm(page.resource).require('WIKI_MODIFY') 352 353 if 'WIKI_ADMIN' in req.perm(page.resource): 354 # Modify the read-only flag if it has been changed and the user is 355 # WIKI_ADMIN 356 page.readonly = int('readonly' in req.args) 357 358 try: 359 page.save(get_reporter_id(req, 'author'), req.args.get('comment'), 360 req.remote_addr) 361 except TracError: 362 add_warning(req, _("Page not modified, showing latest version.")) 363 return self._render_view(req, page) 364 365 href = req.href.wiki(page.name, action='diff', version=page.version) 366 add_notice(req, tag_("Your changes have been saved in version " 367 "%(version)s (%(diff)s).", version=page.version, 368 diff=tag.a(_("diff"), href=href))) 369 req.redirect(get_resource_url(self.env, page.resource, req.href, 370 version=None))
371
372 - def _render_confirm_delete(self, req, page):
373 req.perm(page.resource).require('WIKI_DELETE') 374 375 version = None 376 if 'delete_version' in req.args: 377 version = req.args.getint('version', 0) 378 old_version = req.args.getint('old_version', version) 379 380 what = 'multiple' if version and old_version \ 381 and version - old_version > 1 \ 382 else 'single' if version else 'page' 383 384 num_versions = 0 385 new_date = None 386 old_date = None 387 for v, t, author, comment, ipnr in page.get_history(): 388 if (v <= version or what == 'page') and new_date is None: 389 new_date = t 390 if (v <= old_version and what == 'multiple' or 391 num_versions > 1 and what == 'single'): 392 break 393 num_versions += 1 394 old_date = t 395 396 data = self._page_data(req, page, 'delete') 397 attachments = Attachment.select(self.env, self.realm, page.name) 398 data.update({ 399 'what': what, 'new_version': None, 'old_version': None, 400 'num_versions': num_versions, 'new_date': new_date, 401 'old_date': old_date, 'attachments': list(attachments), 402 }) 403 if version is not None: 404 data.update({'new_version': version, 'old_version': old_version}) 405 self._wiki_ctxtnav(req, page) 406 return 'wiki_delete.html', data, None
407
408 - def _render_confirm_rename(self, req, page, new_name=None):
409 req.perm(page.resource).require('WIKI_RENAME') 410 411 data = self._page_data(req, page, 'rename') 412 data['new_name'] = new_name if new_name is not None else page.name 413 self._wiki_ctxtnav(req, page) 414 return 'wiki_rename.html', data, None
415
416 - def _render_diff(self, req, page):
417 if not page.exists: 418 raise TracError(_("Version %(num)s of page \"%(name)s\" does not " 419 "exist", 420 num=req.args.get('version'), name=page.name)) 421 422 old_version = req.args.getint('old_version') 423 if old_version: 424 if old_version == page.version: 425 old_version = None 426 elif old_version > page.version: 427 # FIXME: what about reverse diffs? 428 old_version = page.resource.version 429 page = WikiPage(self.env, page.name, old_version) 430 req.perm(page.resource).require('WIKI_VIEW') 431 latest_page = WikiPage(self.env, page.name) 432 req.perm(latest_page.resource).require('WIKI_VIEW') 433 new_version = page.version 434 435 date = author = comment = ipnr = None 436 num_changes = 0 437 prev_version = next_version = None 438 for version, t, a, c, i in latest_page.get_history(): 439 if version == new_version: 440 date = t 441 author = a or 'anonymous' 442 comment = c or '--' 443 ipnr = i or '' 444 else: 445 if version < new_version: 446 num_changes += 1 447 if not prev_version: 448 prev_version = version 449 if old_version is None or version == old_version: 450 old_version = version 451 break 452 else: 453 next_version = version 454 if not old_version: 455 old_version = 0 456 old_page = WikiPage(self.env, page.name, old_version) 457 req.perm(old_page.resource).require('WIKI_VIEW') 458 459 # -- text diffs 460 old_text = old_page.text.splitlines() 461 new_text = page.text.splitlines() 462 diff_data, changes = self._prepare_diff(req, page, old_text, new_text, 463 old_version, new_version) 464 465 # -- prev/up/next links 466 if prev_version: 467 add_link(req, 'prev', req.href.wiki(page.name, action='diff', 468 version=prev_version), 469 _("Version %(num)s", num=prev_version)) 470 add_link(req, 'up', req.href.wiki(page.name, action='history'), 471 _('Page history')) 472 if next_version: 473 add_link(req, 'next', req.href.wiki(page.name, action='diff', 474 version=next_version), 475 _("Version %(num)s", num=next_version)) 476 477 data = self._page_data(req, page, 'diff') 478 data.update({ 479 'change': {'date': date, 'author': author, 'ipnr': ipnr, 480 'comment': comment}, 481 'new_version': new_version, 'old_version': old_version, 482 'latest_version': latest_page.version, 483 'num_changes': num_changes, 484 'longcol': 'Version', 'shortcol': 'v', 485 'changes': changes, 486 'diff': diff_data, 487 }) 488 prevnext_nav(req, _("Previous Change"), _("Next Change"), 489 _("Wiki History")) 490 return 'wiki_diff.html', data, None
491
492 - def _render_editor(self, req, page, action='edit', has_collision=False):
493 if has_collision: 494 if action == 'merge': 495 page = WikiPage(self.env, page.name) 496 req.perm(page.resource).require('WIKI_VIEW') 497 else: 498 action = 'collision' 499 500 if not page.exists: 501 req.perm(page.resource).require('WIKI_CREATE') 502 else: 503 req.perm(page.resource).require('WIKI_MODIFY') 504 original_text = page.text 505 comment = req.args.get('comment', '') 506 if 'text' in req.args: 507 page.text = req.args.get('text') 508 elif 'template' in req.args: 509 template = self.PAGE_TEMPLATES_PREFIX + req.args.get('template') 510 template_page = WikiPage(self.env, template) 511 if template_page and template_page.exists and \ 512 'WIKI_VIEW' in req.perm(template_page.resource): 513 page.text = template_page.text 514 elif 'version' in req.args: 515 version = req.args.getint('version') 516 old_page = WikiPage(self.env, page.name, version) 517 req.perm(page.resource).require('WIKI_VIEW') 518 page.text = old_page.text 519 comment = _("Reverted to version %(version)s.", version=version) 520 if action in ('preview', 'diff'): 521 page.readonly = 'readonly' in req.args 522 523 author = get_reporter_id(req, 'author') 524 defaults = {'editrows': str(self.default_edit_area_height)} 525 prefs = dict((key, req.session.get('wiki_%s' % key, defaults.get(key))) 526 for key in ('editrows', 'sidebyside')) 527 528 if 'from_editor' in req.args: 529 sidebyside = req.args.get('sidebyside') or None 530 if sidebyside != prefs['sidebyside']: 531 req.session.set('wiki_sidebyside', int(bool(sidebyside)), 0) 532 else: 533 sidebyside = prefs['sidebyside'] 534 535 if sidebyside: 536 editrows = max(int(prefs['editrows']), 537 len(page.text.splitlines()) + 1) 538 else: 539 editrows = req.args.get('editrows') 540 if editrows: 541 if editrows != prefs['editrows']: 542 req.session.set('wiki_editrows', editrows, 543 defaults['editrows']) 544 else: 545 editrows = prefs['editrows'] 546 547 data = self._page_data(req, page, action) 548 context = web_context(req, page.resource) 549 data.update({ 550 'context': context, 551 'author': author, 552 'comment': comment, 553 'edit_rows': editrows, 554 'sidebyside': sidebyside, 555 'scroll_bar_pos': req.args.get('scroll_bar_pos', ''), 556 'diff': None, 557 'attachments': AttachmentModule(self.env).attachment_data(context), 558 'show_readonly_checkbox': ReadonlyWikiPolicy.__name__ in 559 self.config.get('trac', 560 'permission_policies') 561 }) 562 if action in ('diff', 'merge'): 563 old_text = original_text.splitlines() if original_text else [] 564 new_text = page.text.splitlines() if page.text else [] 565 diff_data, changes = self._prepare_diff( 566 req, page, old_text, new_text, page.version, '') 567 data.update({'diff': diff_data, 'changes': changes, 568 'action': 'preview', 'merge': action == 'merge', 569 'longcol': 'Version', 'shortcol': 'v'}) 570 elif sidebyside and action != 'collision': 571 data['action'] = 'preview' 572 573 self._wiki_ctxtnav(req, page) 574 Chrome(self.env).add_wiki_toolbars(req) 575 Chrome(self.env).add_auto_preview(req) 576 add_script(req, 'common/js/folding.js') 577 return 'wiki_edit.html', data, None
578
579 - def _render_edit_comment(self, req, page):
580 req.perm(page.resource).require('WIKI_ADMIN') 581 data = self._page_data(req, page, 'edit_comment') 582 self._wiki_ctxtnav(req, page) 583 return 'wiki_edit_comment.html', data, None
584
585 - def _render_history(self, req, page):
586 """Extract the complete history for a given page. 587 588 This information is used to present a changelog/history for a given 589 page. 590 """ 591 if not page.exists: 592 raise TracError(_("Page %(name)s does not exist", name=page.name)) 593 594 data = self._page_data(req, page, 'history') 595 596 history = [] 597 for version, date, author, comment, ipnr in page.get_history(): 598 history.append({ 599 'version': version, 600 'date': date, 601 'author': author, 602 'comment': comment, 603 'ipnr': ipnr 604 }) 605 data.update({ 606 'history': history, 607 'resource': page.resource, 608 'can_edit_comment': 'WIKI_ADMIN' in req.perm(page.resource) 609 }) 610 add_ctxtnav(req, _("Back to %(wikipage)s", wikipage=page.name), 611 req.href.wiki(page.name)) 612 return 'history_view.html', data, None
613
614 - def _render_view(self, req, page):
615 version = page.resource.version 616 617 # Add registered converters 618 if page.exists: 619 for conversion in Mimeview(self.env) \ 620 .get_supported_conversions('text/x-trac-wiki'): 621 conversion_href = req.href.wiki(page.name, version=version, 622 format=conversion.key) 623 add_link(req, 'alternate', conversion_href, conversion.name, 624 conversion.in_mimetype) 625 626 data = self._page_data(req, page) 627 if page.name == 'WikiStart': 628 data['title'] = '' 629 630 ws = WikiSystem(self.env) 631 context = web_context(req, page.resource) 632 higher, related = [], [] 633 if not page.exists: 634 if 'WIKI_CREATE' not in req.perm(page.resource): 635 raise ResourceNotFound(_("Page %(name)s not found", 636 name=page.name)) 637 formatter = OneLinerFormatter(self.env, context) 638 if '/' in page.name: 639 parts = page.name.split('/') 640 for i in range(len(parts) - 2, -1, -1): 641 name = '/'.join(parts[:i] + [parts[-1]]) 642 if not ws.has_page(name): 643 higher.append(ws._format_link(formatter, 'wiki', 644 '/' + name, name, False)) 645 else: 646 name = page.name 647 name = name.lower() 648 related = [each for each in ws.pages 649 if name in each.lower() 650 and 'WIKI_VIEW' in req.perm(self.realm, each)] 651 related.sort() 652 related = [ws._format_link(formatter, 'wiki', '/' + each, each, 653 False) 654 for each in related] 655 656 latest_page = WikiPage(self.env, page.name) 657 658 prev_version = next_version = None 659 if version: 660 version = as_int(version, None) 661 if version is not None: 662 for hist in latest_page.get_history(): 663 v = hist[0] 664 if v != version: 665 if v < version: 666 if not prev_version: 667 prev_version = v 668 break 669 else: 670 next_version = v 671 672 prefix = self.PAGE_TEMPLATES_PREFIX 673 templates = [template[len(prefix):] 674 for template in ws.get_pages(prefix) 675 if 'WIKI_VIEW' in req.perm(self.realm, template)] 676 677 # -- prev/up/next links 678 if prev_version: 679 add_link(req, 'prev', 680 req.href.wiki(page.name, version=prev_version), 681 _("Version %(num)s", num=prev_version)) 682 683 parent = None 684 if version: 685 add_link(req, 'up', req.href.wiki(page.name, version=None), 686 _("View latest version")) 687 elif '/' in page.name: 688 parent = page.name[:page.name.rindex('/')] 689 add_link(req, 'up', req.href.wiki(parent, version=None), 690 _("View parent page")) 691 692 if next_version: 693 add_link(req, 'next', 694 req.href.wiki(page.name, version=next_version), 695 _('Version %(num)s', num=next_version)) 696 697 # Add ctxtnav entries 698 if version: 699 prevnext_nav(req, _("Previous Version"), _("Next Version"), 700 _("View Latest Version")) 701 else: 702 if parent: 703 add_ctxtnav(req, _('Up'), req.href.wiki(parent)) 704 self._wiki_ctxtnav(req, page) 705 706 # Plugin content validation 707 fields = {'text': page.text} 708 for manipulator in self.page_manipulators: 709 manipulator.prepare_wiki_page(req, page, fields) 710 text = fields.get('text', '') 711 712 data.update({ 713 'context': context, 714 'text': text, 715 'latest_version': latest_page.version, 716 'attachments': AttachmentModule(self.env).attachment_data(context), 717 'default_template': self.DEFAULT_PAGE_TEMPLATE, 718 'templates': templates, 719 'version': version, 720 'higher': higher, 'related': related, 721 'resourcepath_template': 'wiki_page_path.html', 722 }) 723 add_script(req, 'common/js/folding.js') 724 return 'wiki_view.html', data, None
725
726 - def _wiki_ctxtnav(self, req, page):
727 """Add the normal wiki ctxtnav entries.""" 728 if 'WIKI_VIEW' in req.perm('wiki', 'WikiStart'): 729 add_ctxtnav(req, _("Start Page"), req.href.wiki('WikiStart')) 730 if 'WIKI_VIEW' in req.perm('wiki', 'TitleIndex'): 731 add_ctxtnav(req, _("Index"), req.href.wiki('TitleIndex')) 732 if page.exists: 733 add_ctxtnav(req, _("History"), req.href.wiki(page.name, 734 action='history'))
735 736 # ITimelineEventProvider methods 737
738 - def get_timeline_filters(self, req):
739 if 'WIKI_VIEW' in req.perm: 740 yield ('wiki', _('Wiki changes'))
741
742 - def get_timeline_events(self, req, start, stop, filters):
743 if 'wiki' in filters: 744 wiki_realm = Resource(self.realm) 745 for ts, name, comment, author, version in self.env.db_query(""" 746 SELECT time, name, comment, author, version FROM wiki 747 WHERE time>=%s AND time<=%s 748 """, (to_utimestamp(start), to_utimestamp(stop))): 749 wiki_page = wiki_realm(id=name, version=version) 750 if 'WIKI_VIEW' not in req.perm(wiki_page): 751 continue 752 yield ('wiki', from_utimestamp(ts), author, 753 (wiki_page, comment)) 754 755 # Attachments 756 for event in AttachmentModule(self.env).get_timeline_events( 757 req, wiki_realm, start, stop): 758 yield event
759
760 - def render_timeline_event(self, context, field, event):
761 wiki_page, comment = event[3] 762 if field == 'url': 763 return context.href.wiki(wiki_page.id, version=wiki_page.version) 764 elif field == 'title': 765 name = tag.em(get_resource_name(self.env, wiki_page)) 766 if wiki_page.version > 1: 767 return tag_("%(page)s edited", page=name) 768 else: 769 return tag_("%(page)s created", page=name) 770 elif field == 'description': 771 markup = format_to(self.env, None, 772 context.child(resource=wiki_page), comment) 773 if wiki_page.version > 1: 774 diff_href = context.href.wiki( 775 wiki_page.id, version=wiki_page.version, action='diff') 776 markup = tag(markup, 777 " (", tag.a(_("diff"), href=diff_href), ")") 778 return markup
779 780 # ISearchSource methods 781
782 - def get_search_filters(self, req):
783 if 'WIKI_VIEW' in req.perm: 784 yield ('wiki', _('Wiki'))
785
786 - def get_search_results(self, req, terms, filters):
787 if not 'wiki' in filters: 788 return 789 with self.env.db_query as db: 790 sql_query, args = search_to_sql(db, ['w1.name', 'w1.author', 791 'w1.text'], terms) 792 wiki_realm = Resource(self.realm) 793 for name, ts, author, text in db(""" 794 SELECT w1.name, w1.time, w1.author, w1.text 795 FROM wiki w1,(SELECT name, max(version) AS ver 796 FROM wiki GROUP BY name) w2 797 WHERE w1.version = w2.ver AND w1.name = w2.name 798 AND """ + sql_query, args): 799 page = wiki_realm(id=name) 800 if 'WIKI_VIEW' in req.perm(page): 801 yield (get_resource_url(self.env, page, req.href), 802 '%s: %s' % (name, shorten_line(text)), 803 from_utimestamp(ts), author, 804 shorten_result(text, terms)) 805 806 # Attachments 807 for result in AttachmentModule(self.env).get_search_results( 808 req, wiki_realm, terms): 809 yield result
810 811
812 -class ReadonlyWikiPolicy(Component):
813 """Permission policy for the wiki that enforces the read-only attribute 814 for wiki pages.""" 815 816 implements(IPermissionPolicy) 817 818 realm = WikiSystem.realm 819 820 # IPermissionPolicy methods 821
822 - def check_permission(self, action, username, resource, perm):
823 if resource and resource.realm == self.realm and \ 824 action in ('WIKI_DELETE', 'WIKI_MODIFY', 'WIKI_RENAME'): 825 page = WikiPage(self.env, resource) 826 if page.readonly and 'WIKI_ADMIN' not in perm(resource): 827 return False
828