Attachment 'PageComment2-090.py'
Download 1 # -*- coding: iso-8859-1 -*-
2 """
3 PageComment2.py Version 0.90 Nov. 18, 2005
4
5 This macro gives a form to post a new comment to the page and shows a list of the posted comments.
6
7 @copyright: 2005 by Seungik Lee <seungiklee<at>gmail.com> http://cds.icu.ac.kr/~silee/
8 @license: GPL
9
10 Usage: [[PageComment2]]
11
12 Features:
13
14 Simple usage, just put [[PageComment2]] on any page.
15 Lets anonymous users post a new comment with an input form.
16 Shows a list of the posted comments.
17 Support for comment deletion by given password.
18 Support for administrative action, e.g.,
19 - to delete a comment without entering a given password
20
21 Parameters:
22 public: if the list of comments is shown to public users
23 - public=1; default, all list is shown to all users including anonymous users
24 - public=0; shown to only admin users (who has the page delete privilege)
25
26 countonly: returns the number of the comments posted to this page
27 - countonly=0; default, normal form (input form; list of comments)
28 - countonly=1; just return the number of comments. e.g., 'There are [[PageComments(countonly=1)]] comments here'
29
30 Change Log
31
32 Nov. 18, 2005 - Version 0.90 (Release 2)
33 - No text data file support any more: Comment is stored in the sub wiki page.
34 - (does not compatible with Release 1: PageComment.py)
35 - Custom icon (smiley) can be inserted
36 - Pre-fill the name input field with his/her login name
37 - Logs at add/remove comments
38 - Added some parameters
39 Oct. 08, 2005 - Version 0.82
40 - Changed the directory the data file stored to be secured
41
42 Oct. 07, 2005 - Version 0.81
43 - Unicode encoding related bugs in deletecomment function are patched.
44 - Instruction bugs are patched.
45
46 Oct. 06, 2005 - Version 0.80
47 - The initial version is released.
48
49
50 Notes
51
52 'Gallery.py' developed by Simon Ryan has inspired this macro.
53
54
55 """
56
57 from MoinMoin import wikiutil
58 import StringIO, time, re
59 from MoinMoin.Page import Page
60 from MoinMoin.PageEditor import PageEditor
61 from MoinMoin.parser import wiki
62
63
64 class Globs:
65 # A quick place to plonk those shared variables
66 adminmsg = ''
67 datapagename = ''
68 pagename = ''
69 subname = ''
70 admin = ''
71 macro = ''
72 defaultacl = ''
73 defaulticon = ':)'
74
75 class Params:
76 public = 1
77 countonly = 0
78
79 def execute(macro, args):
80
81 # INITIALIZATION ----------------------------------------
82 setglobalvalues(macro)
83
84 getparams(args)
85
86 if Params.countonly:
87 html = len(fetchcomments())
88 return macro.formatter.rawHTML('%s' % html)
89
90 # internal variables
91 request = macro.request
92 datapagename = Globs.datapagename
93
94 _ = request.getText
95
96 # form vals
97 comicon = Globs.defaulticon
98 comauthor = ''
99 comtext = ''
100 compasswd = ''
101 comrev = 0
102
103 action = macro.form.get('commentaction', [''])[0]
104
105 if action == 'addcomment':
106
107 # process form input for comment add
108 form_fields = {'comicon': Globs.defaulticon, 'comauthor': '', 'comtext': '', 'compasswd': '', 'comrev': 0}
109 required_fields = {'comauthor': _('Name'), 'comtext': _('Text'), 'compasswd': _('Password'), 'comrev': 'Rev. #'}
110
111 formvals, missingfields = getforminput(macro.form, form_fields, required_fields)
112
113 comicon = formvals['comicon']
114 comauthor = formvals['comauthor']
115 comtext = formvals['comtext']
116 compasswd = formvals['compasswd']
117 comrev = formvals['comrev']
118
119 if not len(missingfields) == len(required_fields):
120 if not missingfields:
121 addcomment(macro, comicon, comauthor, comtext, compasswd, comrev)
122
123 comicon = Globs.defaulticon
124 comauthor = ''
125 comtext = ''
126 compasswd = ''
127 comrev = ''
128 else:
129 message( _('Required attribute "%(attrname)s" missing') % { 'attrname': u', '.join(missingfields) } )
130
131 elif action == 'delcomment':
132
133 # process form input for comment delete
134 form_fields = {'delkey': '', 'delpasswd': ''}
135 required_fields = {'delkey': 'Comment Key', 'delpasswd': 'Password'}
136
137 formvals, missingfields = getforminput(macro.form, form_fields, required_fields)
138
139 delkey = formvals['delkey']
140 delpasswd = formvals['delpasswd']
141
142 if not len(missingfields) == len(required_fields):
143 if not missingfields:
144 deletecomment(macro, delkey, delpasswd)
145 else:
146 message( _('Required attribute "%(attrname)s" missing') % { 'attrname': u', '.join(missingfields) } )
147
148 # format output
149 html = []
150
151 html.append(u'<a name="pagecomment">')
152 html.append(u'<table class="pagecomment">')
153 html.append(u'<tr><td style="border: 0px;">')
154 html.append(u'<font style="color: #aa0000;">%s</font>' % Globs.adminmsg)
155 html.append(commentform(comauthor, comtext, compasswd, comicon, comrev))
156 html.append(u'</td></tr>')
157
158 if Params.public or Globs.admin:
159 html.append(deleteform())
160 html.append(u'<tr><td style="border: 0px;">')
161 html.append(showcomment())
162 html.append(u'</td></tr>')
163 else:
164 html.append(u'<tr><td style="text-align: center; border: 0px; font-size: 0.8em; color: #aaaaaa;">(The posted comments are shown to administrators only.)</td></tr>')
165
166 html.append(u'</table>')
167
168 return macro.formatter.rawHTML(u'\n'.join(html))
169
170 def getforminput(form, inputfields, requiredfields):
171
172 formvals = {}
173 missingfields = []
174
175 for item in inputfields.keys():
176 if (not form.has_key(item)) and (item in requiredfields):
177 missingfields.append(requiredfields[item])
178 formvals[item] = form.get(item, [inputfields[item]])[0]
179
180 return formvals, missingfields
181
182 def getparams(args):
183 # process arguments
184
185 params = {}
186 if args:
187 # Arguments are comma delimited key=value pairs
188 sargs = args.split(',')
189
190 for item in sargs:
191 sitem = item.split('=')
192
193 if len(sitem) == 2:
194 key, value = sitem[0], sitem[1]
195 params[key.strip()] = value.strip()
196
197 try:
198 Params.public = int(params.get('public', 1))
199 except ValueError:
200 Params.public = 1
201
202 try:
203 Params.countonly = int(params.get('countonly', 0))
204 except ValueError:
205 Params.countonly = 0
206
207 def setglobalvalues(macro):
208
209 # Global variables
210 Globs.pagename = macro.formatter.page.page_name
211 Globs.subname = Globs.pagename.split('/')[-1]
212 Globs.macro = macro
213 Globs.datapagename = u'%s/%s' % (Globs.pagename, 'PageCommentData')
214 Globs.defaultacl = u'#acl All:'
215
216 # Figure out if we have delete privs
217 try:
218 if macro.request.user.may.delete(Globs.datapagename):
219 Globs.admin = 'true'
220 except AttributeError:
221 pass
222
223
224 def message(astring):
225 Globs.adminmsg += u'%s<br>\n' % astring
226
227
228 def commentform(tmpauthor, tmptext, tmppasswd, tmpicon, comrev):
229 # A form for posting a new comment
230 request = Globs.macro.request
231 datapagename = Globs.datapagename
232 _ = request.getText
233
234 pg = Page( request, datapagename )
235
236 if pg.exists():
237 comrev = pg.current_rev()
238 else:
239 comrev = 0
240
241 if request.user.valid:
242 html1 = [
243 u'<input type="hidden" value="%s" name="comauthor">' % request.user.name,
244 u'<input type="hidden" value="*" name="compasswd">',
245 u'<tr><td>%s: %s</td>' % (_('Name'), request.user.name),
246 u'<td>%s: ****</td>' % _('Password'),
247 ]
248 else:
249 html1 = [
250 u'<tr><td>%s: <input type="text" size="10" maxlength="20" name="comauthor" value="%s"></td>' % (_('Name'), tmpauthor),
251 u'<td>%s: <input type="password" size="6" maxlength="10" name="compasswd" value="%s"></td>' % (_('Password'), tmppasswd),
252 ]
253
254 html1 = u'\n'.join(html1)
255 html2 = [
256 u'<div id="commentform">',
257 u'<form action="%s#pagecomment" name="comment" METHOD="POST">' % Globs.subname,
258 u'<table class="addcommentform">',
259 u'%s' % html1,
260 u'<td>Smiley: <input type="text" size="4" maxlength="4" name="comicon" value="%s"></td></tr>' % tmpicon,
261 u'<tr><td colspan="3"><textarea name="comtext" rows="2" cols="60">%s</textarea></td></tr>' % tmptext,
262 u'<tr><td colspan="3" align="center"><input type="submit" value="%s"></td></tr>' % _('Save'),
263 u'</table>',
264 u'<input type="hidden" value="show" name="action">',
265 u'<input type="hidden" value="%s" name="comrev">' % comrev,
266 u'<input type="hidden" value="addcomment" name="commentaction">',
267 u'</form>',
268 u'</div>',
269 ]
270
271
272 return u'\n'.join(html2)
273
274 def addcomment(macro, comicon, comauthor, comtext, compasswd, comrev):
275 # Add a comment with inputs
276
277 request = Globs.macro.request
278 cfg = request.cfg
279 _ = request.getText
280
281 datapagename = Globs.datapagename
282
283 pg = PageEditor( request, datapagename )
284 pagetext = pg.get_raw_body()
285
286 comicon = convertdelimiter(comicon)
287 comtext = convertdelimiter(comtext)
288
289 if request.user.valid:
290 comloginuser = 'TRUE'
291 comauthor = request.user.name
292 else:
293 comloginuser = ''
294 comauthor = convertdelimiter(comauthor)
295
296 newcomment = [
297 u'{{{',
298 u'%s' % comicon,
299 u'%s' % comauthor,
300 u'%s' % time.strftime(cfg.datetime_fmt, time.localtime(time.time())),
301 u'',
302 u'%s' % comtext.strip(),
303 u'}}}',
304 u'##PASSWORD %s' % compasswd,
305 u'##LOGINUSER %s' % comloginuser,
306 ]
307
308 newpagetext = u'%s\n\n%s' % (pagetext, u'\n'.join(newcomment))
309
310 if not pg.exists():
311 action = 'SAVENEW'
312 defaultacl = Globs.defaultacl
313 newpagetext = u'%s\n%s' % (defaultacl, newpagetext)
314 else:
315 action = 'SAVE'
316
317 newpagetext = pg.normalizeText( newpagetext )
318
319 comment = 'New comment by "%s"' % comauthor
320 pg._write_file(newpagetext, action, u'Modified by PageComment macro')
321 addLogEntry(request, 'COMNEW', Globs.pagename, comment)
322
323 # message(_('The comment is added'))
324 message(_('Thank you for your changes. Your attention to detail is appreciated.'))
325
326
327 def showcomment():
328
329 request = Globs.macro.request
330 _ = request.getText
331
332 commentlist = fetchcomments()
333
334 html = []
335 cur_index = 0
336 linestyle = u'border-width: 0px; border-top-width: 1px; vertical-align: top; font-size: 0.9em;'
337
338 html.append(u'<div id="commentlist"><table width="100%" class="commentlist">')
339
340 for item in commentlist:
341 if Globs.admin or (item['loginuser'] and request.user.valid and request.user.name == item['name']):
342 htmlcommentdel = [
343 u' <font style="font-size: 0.9em;">',
344 u'<a style="color: #aa0000;" href="javascript: requesttodeleteadmin(\'%s\');" title="%s">X</a>' % (item['key'], _('Delete')),
345 u'</font>',
346 ]
347 elif item['loginuser']:
348 htmlcommentdel = []
349
350 else:
351 htmlcommentdel = [
352 u' <font style="font-size: 0.9em;">',
353 u'<a style="color: #aa0000;" href="javascript: requesttodelete(\'%s\');" title="%s">X</a>' % (item['key'], _('Delete')),
354 u'</font>',
355 ]
356
357 htmlcomment = [
358 u'<tr><td class="commenticon" style="%s width: 20px;">%s</td>' % (linestyle, getparsedtext(item['icon'])),
359 u'<td class="commentauthor" style="%s"' % linestyle,
360 u'>%s</td>' % converttext(item['name']),
361 u'<td class="commenttext" style="%s">%s</td>' % (linestyle, converttext(item['text'])),
362 u'<td class="commentdate" style="%s text-align: right; font-size: 0.8em; line-height: 10px;">%s %s</td></tr>' % (linestyle, item['date'], u''.join(htmlcommentdel)),
363 ]
364
365 html.append(u'\n'.join(htmlcomment))
366
367 html.append(u'</table></div>')
368
369 return u'\n'.join(html)
370
371
372 def getparsedtext(targettext):
373 request = Globs.macro.request
374 formatter = Globs.macro.formatter
375
376 out = StringIO.StringIO()
377 request.redirect(out)
378 wikiizer = wiki.Parser(targettext, request)
379 wikiizer.format(formatter)
380 parsedtext = out.getvalue()
381 request.redirect()
382 del out
383
384 return parsedtext
385
386
387 def converttext(targettext):
388 # Converts some special characters of html to plain-text style
389 # What else to handle?
390
391 targettext = targettext.strip()
392 targettext = targettext.replace(u'&', '&')
393 targettext = targettext.replace(u'>', '>')
394 targettext = targettext.replace(u'<', '<')
395 targettext = targettext.replace(u'\n', '<br>')
396 targettext = targettext.replace(u'"', '"')
397 targettext = targettext.replace(u'\t', ' ')
398 targettext = targettext.replace(u' ', ' ')
399
400 return targettext
401
402 def convertdelimiter(targettext):
403 # Converts delimeter to other string to avoid a crash
404
405 targettext = targettext.replace('{{{', '{ { {')
406 targettext = targettext.replace('}}}', '} } }')
407
408 return targettext
409
410
411 def deleteform():
412 # Javascript codes for deleting or restoring a comment
413
414 request = Globs.macro.request
415 _ = request.getText
416
417 htmlresult = []
418
419 html = [
420 '<script language="javascript">',
421 '<!--',
422 ]
423 htmlresult.append(u'\n'.join(html))
424
425 html = [
426 ' function requesttodeleteadmin(comkey) {',
427 ' if (confirm("%s")) {;' % _('Really delete this page?'),
428 ' document.delform.delkey.value = comkey;',
429 ' document.delform.delpasswd.value = "****";',
430 ' document.delform.submit();',
431 ' }',
432 ' }',
433 ' function requesttodelete(comkey) {',
434 ' var passwd = prompt("%s:", "");' % _('Please specify a password!'),
435 ' if(!(passwd == "" || passwd == null)) {',
436 ' document.delform.delkey.value = comkey;',
437 ' document.delform.delpasswd.value = passwd;',
438 ' document.delform.submit();',
439 ' }',
440 ' }',
441 ]
442
443 htmlresult.append(u'\n'.join(html))
444
445 html = [
446 '//-->',
447 '</script>',
448 '<form name="delform" action="%s#pagecomment" METHOD="post">' % Globs.subname,
449 '<input type=hidden value="show" name="action">',
450 '<input name="delpasswd" type="hidden" value="****">',
451 '<input name="delkey" type="hidden" value="">',
452 '<input type="hidden" name="commentaction" value="delcomment">',
453 '</form>',
454 ]
455 htmlresult.append(u'\n'.join(html))
456
457 return u'\n'.join(htmlresult)
458
459
460 def filtercomment(index='', name='', passwd=''):
461
462 # filter by index
463 if index:
464 filteredlist1 = fetchcomments(index, index)
465 else:
466 filteredlist1 = fetchcomments()
467
468 # filter by name
469 filteredlist2 = []
470 if name:
471 for item in filteredlist1:
472 if name == item['name']:
473 filteredlist2.append(item)
474 else:
475 filteredlist2 = filteredlist1
476
477 # filter by password
478 filteredlist3 = []
479 if passwd:
480 for item in filteredlist2:
481 if passwd == item['passwd']:
482 filteredlist3.append(item)
483 else:
484 filteredlist3 = filteredlist2
485
486 return filteredlist3
487
488
489 def fetchcomments(startindex=1, endindex=9999):
490
491 commentlist = []
492
493 request = Globs.macro.request
494 formatter = Globs.macro.formatter
495 datapagename = Globs.datapagename
496
497 pg = Page( request, datapagename )
498 pagetext = pg.get_raw_body()
499
500 regex = r'^(#acl\s*.*)$'
501 pattern = re.compile(regex, re.UNICODE + re.MULTILINE + re.IGNORECASE)
502 pagetext = pattern.sub('', pagetext)
503
504 regex = ur"""
505 ^[\{]{3}\n
506 ^(?P<icon>[^\n]*)\n
507 ^(?P<name>[^\n]*)\n
508 ^(?P<date>[^\n]*)[\n]+
509 ^(?P<text>\s*.*?[^}]+)[\}]{3}\n
510 ^[#]{2}PASSWORD[ ](?P<passwd>[^\n]*)\n
511 ^[#]{2}LOGINUSER[ ](?P<loginuser>[^\n]*)[\n$]+"""
512
513 pattern = re.compile(regex, re.UNICODE + re.MULTILINE + re.VERBOSE)
514 commentitems = pattern.findall(pagetext)
515
516 cur_index = 0
517 for item in commentitems:
518 comment = {}
519 cur_index += 1
520
521 if cur_index < startindex:
522 continue
523
524 comment['index'] = cur_index
525 comment['icon'] = item[0]
526 comment['name'] = item[1]
527 comment['text'] = item[3]
528 comment['date'] = item[2]
529 comment['passwd'] = item[4]
530 comment['loginuser'] = item[5]
531
532 # experimental
533 comment['key'] = comment['date'].strip()
534
535 commentlist.append(comment)
536
537 if cur_index >= endindex:
538 break
539
540 return commentlist
541
542 def deletecomment(macro, delkey, delpasswd):
543 # Deletes a comment with given index and password
544
545 request = Globs.macro.request
546 formatter = Globs.macro.formatter
547 datapagename = Globs.datapagename
548 _ = request.getText
549
550 pg = PageEditor( request, datapagename )
551 pagetext = pg.get_raw_body()
552
553 regex = ur"""
554 (?P<comblock>^[\{]{3}\n
555 ^(?P<icon>[^\n]*)\n
556 ^(?P<name>[^\n]*)\n
557 ^(?P<date>[^\n]*)[\n]+
558 ^(?P<text>\s*.*?[^}]+)[\}]{3}\n
559 ^[#]{2}PASSWORD[ ](?P<passwd>[^\n]*)\n
560 ^[#]{2}LOGINUSER[ ](?P<loginuser>[^\n]*)[\n$]+)"""
561
562 pattern = re.compile(regex, re.UNICODE + re.MULTILINE + re.VERBOSE)
563 commentitems = pattern.findall(pagetext)
564
565 for item in commentitems:
566
567 if delkey == item[3].strip():
568 comauthor = item[2]
569 if Globs.admin or (request.user.valid and request.user.name == comauthor) or delpasswd == item[5]:
570 newpagetext = pagetext.replace(item[0], '', 1)
571
572 action = 'SAVE'
573 comment = 'comment deleted by "%s"' % comauthor
574 pg._write_file(newpagetext, action, u'Modified by PageComment macro')
575 addLogEntry(request, 'COMDEL', Globs.pagename, comment)
576
577 message(_('The comment is deleted'))
578
579 return
580 else:
581 message(_('Sorry, wrong password.'))
582 return
583
584 message(_('No such comment'))
585
586
587 def addLogEntry(request, action, pagename, msg):
588 # Add an entry to the edit log on adding comments.
589 from MoinMoin.logfile import editlog
590 t = wikiutil.timestamp2version(time.time())
591 msg = unicode(msg)
592
593 # TODO: for now we simply write 2 logs, maybe better use some multilog stuff
594 # Write to global log
595 log = editlog.EditLog(request)
596 log.add(request, t, 99999999, action, pagename, request.remote_addr, msg)
597
598 # Write to local log
599 log = editlog.EditLog(request, rootpagename=pagename)
600 log.add(request, t, 99999999, action, pagename, request.remote_addr, msg)
Attached Files
To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.You are not allowed to attach a file to this page.