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