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