| 1 |
# vim: expandtab |
|---|
| 2 |
import re, time |
|---|
| 3 |
from StringIO import StringIO |
|---|
| 4 |
|
|---|
| 5 |
from genshi.builder import tag |
|---|
| 6 |
|
|---|
| 7 |
from trac.core import * |
|---|
| 8 |
from trac.wiki.formatter import format_to_html |
|---|
| 9 |
from trac.util import TracError |
|---|
| 10 |
from trac.util.text import to_unicode |
|---|
| 11 |
from trac.web.api import IRequestFilter, RequestDone |
|---|
| 12 |
from trac.web.chrome import add_script |
|---|
| 13 |
from trac.wiki.api import parse_args, IWikiMacroProvider |
|---|
| 14 |
from trac.wiki.macros import WikiMacroBase |
|---|
| 15 |
from trac.wiki.model import WikiPage |
|---|
| 16 |
from trac.wiki.web_ui import WikiModule |
|---|
| 17 |
|
|---|
| 18 |
from macropost.api import IMacroPoster |
|---|
| 19 |
|
|---|
| 20 |
class AddCommentMacro(WikiMacroBase): |
|---|
| 21 |
"""A macro to add comments to a page. Usage: |
|---|
| 22 |
{{{ |
|---|
| 23 |
[[AddComment]] |
|---|
| 24 |
}}} |
|---|
| 25 |
The macro accepts one optional argument that allows appending |
|---|
| 26 |
to the wiki page even though user may not have modify permission: |
|---|
| 27 |
{{{ |
|---|
| 28 |
[[AddComment(appendonly)]] |
|---|
| 29 |
}}} |
|---|
| 30 |
""" |
|---|
| 31 |
implements(IWikiMacroProvider, IRequestFilter, IMacroPoster) |
|---|
| 32 |
|
|---|
| 33 |
def expand_macro(self, formatter, name, content): |
|---|
| 34 |
|
|---|
| 35 |
args, kw = parse_args(content) |
|---|
| 36 |
req = formatter.req |
|---|
| 37 |
context = formatter.context |
|---|
| 38 |
|
|---|
| 39 |
# Prevent multiple inclusions - store a temp in req |
|---|
| 40 |
if hasattr(req, 'addcommentmacro'): |
|---|
| 41 |
raise TracError('\'AddComment\' macro cannot be included twice.') |
|---|
| 42 |
req.addcommentmacro = True |
|---|
| 43 |
|
|---|
| 44 |
# Prevent it being used outside of wiki page context |
|---|
| 45 |
resource = context.resource |
|---|
| 46 |
if not resource.realm == 'wiki': |
|---|
| 47 |
raise TracError('\'AddComment\' macro can only be used in Wiki pages.') |
|---|
| 48 |
|
|---|
| 49 |
# Setup info and defaults |
|---|
| 50 |
authname = req.authname |
|---|
| 51 |
page = WikiPage(self.env, resource) |
|---|
| 52 |
page_url = req.href.wiki(resource.id) |
|---|
| 53 |
wikipreview = req.args.get("preview", "") |
|---|
| 54 |
|
|---|
| 55 |
# Can this user add a comment to this page? |
|---|
| 56 |
appendonly = ('appendonly' in args) |
|---|
| 57 |
cancomment = False |
|---|
| 58 |
if page.readonly: |
|---|
| 59 |
if 'WIKI_ADMIN' in req.perm(resource): |
|---|
| 60 |
cancomment = True |
|---|
| 61 |
elif 'WIKI_MODIFY' in req.perm(resource): |
|---|
| 62 |
cancomment = True |
|---|
| 63 |
elif appendonly and 'WIKI_VIEW' in req.perm(resource): |
|---|
| 64 |
cancomment = True |
|---|
| 65 |
else: |
|---|
| 66 |
raise TracError('Error: Insufficient privileges to AddComment') |
|---|
| 67 |
|
|---|
| 68 |
# Get the data from the POST |
|---|
| 69 |
comment = req.args.get("addcomment", "") |
|---|
| 70 |
preview = req.args.get("previewaddcomment", "") |
|---|
| 71 |
cancel = req.args.get("canceladdcomment", "") |
|---|
| 72 |
submit = req.args.get("submitaddcomment", "") |
|---|
| 73 |
if not cancel and req.authname == 'anonymous': |
|---|
| 74 |
authname = req.args.get("authoraddcomment", authname) |
|---|
| 75 |
|
|---|
| 76 |
# Ensure [[AddComment]] is not present in comment, so that infinite |
|---|
| 77 |
# recursion does not occur. |
|---|
| 78 |
comment = to_unicode(re.sub('(^|[^!])(\[\[AddComment)', '\\1!\\2', comment)) |
|---|
| 79 |
|
|---|
| 80 |
the_preview = the_message = the_form = tag() |
|---|
| 81 |
|
|---|
| 82 |
# If we are submitting or previewing, inject comment as it should look |
|---|
| 83 |
if cancomment and comment and (preview or submit): |
|---|
| 84 |
heading = tag.h4("Comment by ", authname, " on ", |
|---|
| 85 |
to_unicode(time.strftime('%c', time.localtime())), |
|---|
| 86 |
id="commentpreview") |
|---|
| 87 |
if preview: |
|---|
| 88 |
the_preview = tag.div(heading, |
|---|
| 89 |
format_to_html(self.env, context, comment), |
|---|
| 90 |
class_="wikipage", id="preview") |
|---|
| 91 |
|
|---|
| 92 |
# Check the form_token |
|---|
| 93 |
form_ok = True |
|---|
| 94 |
if submit and req.args.get('__FORM_TOKEN','') != req.form_token: |
|---|
| 95 |
form_ok = False |
|---|
| 96 |
the_message = tag.div(tag.strong("ERROR: "), |
|---|
| 97 |
"AddComment received incorrect form token. " |
|---|
| 98 |
"Do you have cookies enabled?", |
|---|
| 99 |
class_="system-message") |
|---|
| 100 |
|
|---|
| 101 |
# When submitting, inject comment before macro |
|---|
| 102 |
if comment and submit and cancomment and form_ok: |
|---|
| 103 |
submitted = False |
|---|
| 104 |
newtext = "" |
|---|
| 105 |
for line in page.text.splitlines(): |
|---|
| 106 |
if line.find('[[AddComment') == 0: |
|---|
| 107 |
newtext += "==== Comment by %s on %s ====\n%s\n\n" % ( |
|---|
| 108 |
authname, |
|---|
| 109 |
to_unicode(time.strftime('%c', time.localtime())), |
|---|
| 110 |
comment) |
|---|
| 111 |
submitted = True |
|---|
| 112 |
newtext += line+"\n" |
|---|
| 113 |
if submitted: |
|---|
| 114 |
page.text = newtext |
|---|
| 115 |
|
|---|
| 116 |
# Let the wiki page manipulators have a look at the |
|---|
| 117 |
# submission. |
|---|
| 118 |
valid = True |
|---|
| 119 |
req.args.setdefault('comment', 'Comment added.') |
|---|
| 120 |
try: |
|---|
| 121 |
for manipulator in WikiModule(self.env).page_manipulators: |
|---|
| 122 |
for field, message in manipulator.validate_wiki_page(req, page): |
|---|
| 123 |
valid = False |
|---|
| 124 |
if field: |
|---|
| 125 |
the_message += tag.div(tag.strong("invalid field '%s': " % field), |
|---|
| 126 |
message, |
|---|
| 127 |
class_="system-message") |
|---|
| 128 |
else: |
|---|
| 129 |
the_message += tag.div(tag.strong("invalid: "), |
|---|
| 130 |
message, |
|---|
| 131 |
class_="system-message") |
|---|
| 132 |
|
|---|
| 133 |
# The TracSpamfilterPlugin does not generate messages, |
|---|
| 134 |
# but throws RejectContent. |
|---|
| 135 |
except TracError, s: |
|---|
| 136 |
valid = False |
|---|
| 137 |
the_message += tag.div(tag.strong("ERROR: "), s, class_="system-message") |
|---|
| 138 |
|
|---|
| 139 |
if valid: |
|---|
| 140 |
page.save(authname, req.args['comment'], req.environ['REMOTE_ADDR']) |
|---|
| 141 |
# We can't redirect from macro as it will raise RequestDone |
|---|
| 142 |
# which like other macro errors gets swallowed in the Formatter. |
|---|
| 143 |
# We need to re-raise it in a post_process_request instead. |
|---|
| 144 |
try: |
|---|
| 145 |
self.env.log.debug( |
|---|
| 146 |
"AddComment saved - redirecting to: %s" % page_url) |
|---|
| 147 |
req._outheaders = [] |
|---|
| 148 |
req.redirect(page_url) |
|---|
| 149 |
except RequestDone: |
|---|
| 150 |
req.addcomment_raise = True |
|---|
| 151 |
else: |
|---|
| 152 |
the_message = tag.div(tag.strong("ERROR: "), "[[AddComment]] " |
|---|
| 153 |
"macro call must be the only content on its line. " |
|---|
| 154 |
"Could not add comment.", |
|---|
| 155 |
class_="system-message") |
|---|
| 156 |
|
|---|
| 157 |
the_form = tag.form( |
|---|
| 158 |
tag.fieldset( |
|---|
| 159 |
tag.legend("Add comment"), |
|---|
| 160 |
tag.div( |
|---|
| 161 |
(wikipreview and "Page preview..." or None), |
|---|
| 162 |
tag.textarea((not cancel and comment or ""), |
|---|
| 163 |
class_="wikitext", |
|---|
| 164 |
id="addcomment", |
|---|
| 165 |
name="addcomment", |
|---|
| 166 |
cols=80, rows=5, |
|---|
| 167 |
disabled=(not cancomment and "disabled" or None)), |
|---|
| 168 |
class_="field" |
|---|
| 169 |
), |
|---|
| 170 |
(req.authname == 'anonymous' and tag.div( |
|---|
| 171 |
tag.label("Your email or username:", |
|---|
| 172 |
for_="authoraddcomment"), |
|---|
| 173 |
tag.input(id="authoraddcomment", type="text", |
|---|
| 174 |
size=30, value=authname) |
|---|
| 175 |
) or None), |
|---|
| 176 |
tag.input(type="hidden", name="__FORM_TOKEN", |
|---|
| 177 |
value=req.form_token), |
|---|
| 178 |
tag.div( |
|---|
| 179 |
tag.input(value="Add comment", type="submit", |
|---|
| 180 |
name="submitaddcomment", size=30, |
|---|
| 181 |
disabled=(not cancomment and "disabled" or None)), |
|---|
| 182 |
tag.input(value="Preview comment", type="submit", |
|---|
| 183 |
name="previewaddcomment", |
|---|
| 184 |
disabled=(not cancomment and "disabled" or None)), |
|---|
| 185 |
tag.input(value="Cancel", type="submit", |
|---|
| 186 |
name="canceladdcomment", |
|---|
| 187 |
disabled=(not cancomment and "disabled" or None)), |
|---|
| 188 |
class_="buttons" |
|---|
| 189 |
), |
|---|
| 190 |
), |
|---|
| 191 |
method="post", |
|---|
| 192 |
action=page_url+"#commenting", |
|---|
| 193 |
) |
|---|
| 194 |
|
|---|
| 195 |
if not wikipreview: |
|---|
| 196 |
# Wiki edit preview already adds this javascript file |
|---|
| 197 |
add_script(req, 'common/js/wikitoolbar.js') |
|---|
| 198 |
|
|---|
| 199 |
return tag.div(the_preview, the_message, the_form, id="commenting") |
|---|
| 200 |
|
|---|
| 201 |
# IMacroPoster method |
|---|
| 202 |
|
|---|
| 203 |
def process_macro_post(self, req): |
|---|
| 204 |
self.log.debug('AddCommentMacro: Got a POST') |
|---|
| 205 |
|
|---|
| 206 |
# IRequestFilter methods |
|---|
| 207 |
|
|---|
| 208 |
def pre_process_request(self, req, handler): |
|---|
| 209 |
return handler |
|---|
| 210 |
|
|---|
| 211 |
def post_process_request(self, req, template, data, content_type): |
|---|
| 212 |
if hasattr(req, 'addcomment_raise'): |
|---|
| 213 |
self.env.log.debug("AddCommentMacro: Re-raising RequestDone from redirect") |
|---|
| 214 |
del(req.addcomment_raise) |
|---|
| 215 |
raise RequestDone |
|---|
| 216 |
return template, data, content_type |
|---|