Attachment 'PageComment2-098.py'
Download 1 # -*- coding: iso-8859-1 -*-
2 """
3 PageComment2.py Version 0.98 April 17, 2006
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://www.silee.net/
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
23 - pagename: the page name which the comments are retrieved for. by default the page itself.
24 If the user has no 'read' ACL for that page, it does not allow to insert/view comments.
25 e.g., pagename=AnotherPage
26
27 - section: the section name of the page. The comments in different sections are managed in separated sub pages.
28 Section name should be alphanumeric format ([a-zA-Z0-9] in regular expression).
29 If not, all the non-alphanumric characters are removed.
30 e.g., section=1, section=News, section=Opinion
31
32 - inputonly: shows input form only. list of the comments are shown to admin users only.
33 - inputonly=0; default, all list is shown to all users including anonymous users
34 - inputonly=1; shown to admin users only (who has the page delete privilege)
35
36 - commentonly: shows the list of comments only.
37 - commentonly=0; default, both of the list and input form will be shown
38 - commentonly=1; only the list of comments will be shown
39
40 - countonly: returns the number of the comments posted to this page
41 - countonly=0; default, normal form (input form; list of comments)
42 - countonly=1; just return the number of comments.
43 e.g., 'There are [[PageComments(countonly=1)]] comments here'
44
45 - rows: the # of rows of the textarea. default 4. e.g., rows=4
46
47 - cols: the # of columns of the textarea. default 60. e.g., cols=60
48
49 - maxlength: limitation on # of characters for comment text. default 0 (no limit). e.g., maxlength=500
50
51 - newerfirst: order of the list of comments.
52 - newerfirst=0: default, newer ones are listed at the end
53 - newerfirst=1: newer ones are listed at the top
54
55 - commentfirst: shows comment list before the input form.
56 - commentfirst=0: default, the input form first
57 - commentfirst=1: comment list first
58
59 - articleview: shows comment list in an article view.
60 - articleview=0: default, list in table view
61 - articleview=1: list in article view
62
63 - tablewidth: the width of the table format for PageComment2, default '' (none).
64 e.g., tablewidth=600, tablewidth=100%
65
66 - smileylist: shows smiley options with drop-down list box
67 - smileylist=0: default, a part of the smiley in radio button
68 - smileylist=1: smiley in drop-down list box
69
70 - nosmiley: shows no smiley
71 - nosmiley=0: default, shows smiley selection
72 - nosmiley=1: no smiley selection
73
74 - notify: notifies to the subscribers of the page which includes the macro when a comment is added
75 - notify=0: default, notification disabled
76 - notify=1: notification enabled
77
78 - encryptpass: encrypts entered password
79 - encryptpass=0: default, the password is stored in plain text
80 - encryptpass=1: the password is stored in encrypted format
81
82 - markup: enables wiki markup in the comment text except some specified macros.
83 - markup=0: default, use of wiki markup in the text is disabled
84 - markup=1: use of wiki markup in the text is enabled and preview button is activated
85
86 Change Log
87
88 - April 17, 2006 - Version 0.98
89 - fixed a bug on revision history
90 - added a despam action
91
92 - Jan. 05, 2006 - Version 0.97
93 - added features:
94 - mail notification
95 - password encryption
96 - wiki markup support with preview
97 - remember author name last used
98 - administrative actions (delete without password) are allowed to those who has WRITE acl.
99
100 - Nov. 29, 2005 - Version 0.96
101 - some format parameters are added
102 - random password feature is added
103
104 - Nov. 20, 2005 - Version 0.95
105 - some minor bugs are fixed
106
107 - Nov. 20, 2005 - Version 0.94
108 - some parameters are added
109 - some minor bugs are fixed
110
111 - Nov. 19, 2005 - Version 0.92
112 - some minor bugs are fixed
113 - 'olderfirst' parameter replaced with 'newerfirst'
114
115 - Nov. 19, 2005 - Version 0.91
116 - some parameters are added
117 - validates smiley markup
118 - modified view
119
120 - Nov. 18, 2005 - Version 0.90 (Release 2)
121 - No text data file support any more: Comment is stored in the sub wiki page.
122 - (does not compatible with Release 1: PageComment.py)
123 - Custom icon (smiley) can be inserted
124 - Pre-fill the name input field with his/her login name
125 - Logs at add/remove comments
126 - Added some parameters
127
128 - Oct. 08, 2005 - Version 0.82
129 - Changed the directory the data file stored to be secured
130
131 - Oct. 07, 2005 - Version 0.81
132 - Unicode encoding related bugs in deletecomment function are patched.
133 - Instruction bugs are patched.
134
135 - Oct. 06, 2005 - Version 0.80
136 - The initial version is released.
137
138
139 Notes
140
141 - 'Gallery.py' developed by Simon Ryan has inspired this macro.
142 - Thanks to many of the MoinMoin users for valuable comments.
143 - Visit http://moinmoin.wikiwikiweb.de/MacroMarket/PageComment2 for more detail
144
145 """
146
147 from MoinMoin import config, wikiutil
148 import StringIO, time, re
149 from MoinMoin.Page import Page
150 from MoinMoin.PageEditor import PageEditor
151 from MoinMoin.parser import wiki
152
153
154 class Globs:
155 # A quick place to plonk those shared variables
156
157 adminmsg = ''
158 datapagename = ''
159 pagename = ''
160 curpagename = ''
161 cursubname = ''
162 admin = ''
163 macro = ''
164 defaultacl = ''
165 defaulticon = ''
166 formid = 0
167 smileys = []
168
169 class Params:
170
171 rows = 0
172 cols = 0
173 maxlength = 0
174 newerfirst = 0
175 tablewidth = ''
176 commentfirst = 0
177 pagename = ''
178 commentonly = 0
179 inputonly = 0
180 countonly = 0
181 section = ''
182 articleview = 0
183 notify = 0
184 encryptpass = 0
185 markup = 0
186
187
188 def execute(macro, args):
189
190 # INITIALIZATION ----------------------------------------
191 getparams(args)
192 setglobalvalues(macro)
193
194 # internal variables
195 request = macro.request
196 _ = request.getText
197
198 if not Globs.pagename == Globs.curpagename:
199 if not macro.request.user.may.read(Globs.pagename):
200 return macro.formatter.rawHTML(u'PageComment: %s' % _('You are not allowed to view this page.'))
201 elif not Page(request, Globs.pagename).exists():
202 return macro.formatter.rawHTML(u'PageComment: %s' % _('This page is already deleted or was never created!'))
203
204
205 if Params.countonly:
206 html = len(fetchcomments())
207 return macro.formatter.rawHTML('%s' % html)
208
209 datapagename = Globs.datapagename
210
211 # form vals
212 comicon = Globs.defaulticon
213 comauthor = ''
214 comtext = ''
215 compasswd = ''
216 comrev = 0
217 comautopass = ''
218 commentpreview = ''
219 commarkup = ''
220
221 addcommand = u'addcomment%d' % Globs.formid
222 delcommand = u'delcomment%d' % Globs.formid
223
224 action = macro.form.get('commentaction', [''])[0]
225
226 if action == addcommand:
227
228 # process form input for comment add
229 form_fields = {'comicon': Globs.defaulticon, 'comauthor': '', 'comtext': '', 'compasswd': '', 'comrev': 0, 'autopasswd': '', 'button_save': '', 'button_preview': '', 'commarkup%d' % Globs.formid: '0'}
230 required_fields = {'comauthor': _('Name'), 'comtext': _('Text'), 'compasswd': _('Password'), 'comrev': 'Rev. #'}
231
232 formvals, missingfields = getforminput(macro.form, form_fields, required_fields)
233
234 comicon = formvals['comicon']
235 comauthor = formvals['comauthor']
236 comtext = formvals['comtext']
237 compasswd = formvals['compasswd']
238 comrev = int(formvals['comrev'])
239 comautopass = formvals['autopasswd']
240 btnsave = formvals['button_save']
241 btnpreview = formvals['button_preview']
242 commarkup = formvals['commarkup%d' % Globs.formid]
243
244 if not len(missingfields) == len(required_fields):
245 if not missingfields:
246
247 # check input
248 if comicon and (not comicon in config.smileys.keys()):
249 message('Please use smiley markup only')
250
251 elif Params.maxlength and (len(comtext) > Params.maxlength):
252 message('Comment text is limited to %d characters. (%d characters now)' % (Params.maxlength, len(comtext)) )
253
254 elif not comtext.strip() or comtext == u'Add your comment':
255 message('Please fill the comment text')
256
257 ## PREVIEW
258 elif btnpreview:
259 commentpreview = previewcomment(comicon, comauthor, comtext, commarkup)
260
261 ## ADD
262 elif btnsave:
263 flag = addcomment(macro, comicon, comauthor, comtext, compasswd, comrev, comautopass, commarkup)
264
265 if flag:
266 comicon = Globs.defaulticon
267 comauthor = ''
268 comtext = ''
269 compasswd = ''
270 comrev = 0
271 commentpreview = ''
272 commarkup = ''
273
274 ## ERROR
275 else:
276 message( 'What do you want?' )
277
278 else:
279 message( _('Required attribute "%(attrname)s" missing') % { 'attrname': u', '.join(missingfields) } )
280
281 elif action == delcommand:
282
283 # process form input for comment delete
284 form_fields = {'delkey': '', 'delpasswd': ''}
285 required_fields = {'delkey': 'Comment Key', 'delpasswd': 'Password'}
286
287 formvals, missingfields = getforminput(macro.form, form_fields, required_fields)
288
289 delkey = formvals['delkey']
290 delpasswd = formvals['delpasswd']
291
292 if not len(missingfields) == len(required_fields):
293 if not missingfields:
294 deletecomment(macro, delkey, delpasswd)
295 else:
296 message( _('Required attribute "%(attrname)s" missing') % { 'attrname': u', '.join(missingfields) } )
297
298 # format output
299 html = []
300
301 html.append(u'<div id="pagecomment">')
302 html.append(u'<a name="pagecomment%d"></a>' % Globs.formid)
303
304 html.append(u'<table border="0" class="pagecomment" %s>' % Params.tablewidth)
305
306 if Globs.adminmsg:
307 html.append(u'<tr><td colspan="5" style="border-width: 0px;">')
308 html.append(u'<font color="#aa0000">%s</font>' % Globs.adminmsg)
309 html.append(u'</td></tr>')
310
311 commentlisthtml = showcommentsection()
312 commentformhtml = commentformsection(comauthor, comtext, compasswd, comicon, comrev, comautopass, commarkup)
313
314 if Params.commentfirst:
315 if commentpreview:
316 html.append(commentpreview)
317
318 html.append(commentlisthtml)
319 html.append(u'<tr><td colspan="5" class="commentblankline" style="border-width: 0px; height: 20px;"></td></tr>')
320 html.append(commentformhtml)
321 else:
322 html.append(commentformhtml)
323 html.append(u'<tr><td colspan="5" class="commentblankline" style="border-width: 0px; height: 20px;"></td></tr>')
324 if commentpreview:
325 html.append(commentpreview)
326
327 html.append(commentlisthtml)
328
329 if Globs.debugmsg:
330 html.append(u'<tr><td colspan="5" style="border-width: 0px;">')
331 html.append(u'<font color="#aa0000">%s</font>' % Globs.debugmsg)
332 html.append(u'</td></tr>')
333
334 html.append(u'</table>')
335
336 if Globs.customscript:
337 html.append(u'%s' % Globs.customscript)
338
339 html.append(u'</div>')
340
341 return macro.formatter.rawHTML(u'\n'.join(html))
342
343
344 def commentformsection(comauthor, comtext, compasswd, comicon, comrev, autopass, commarkup):
345 html = []
346
347 if not Params.commentonly:
348 html.append(u'<tr><td style="border-width: 1px; margin: 10px 0 10px 0;" colspan="5">')
349 #html.append(u'<table class="commentform"><tr><td style="border-width: 1px;">')
350 html.append(commentform(comauthor, comtext, compasswd, comicon, comrev, autopass, commarkup))
351 #html.append(u'</td></tr></table>')
352 html.append(u'</td></tr>')
353
354 return u'\n'.join(html)
355
356
357 def showcommentsection():
358 html = []
359 if (not Params.inputonly) or Globs.admin:
360 html.append(deleteform())
361 html.append(showcomment())
362 else:
363 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>')
364
365 return u'\n'.join(html)
366
367 def getforminput(form, inputfields, requiredfields):
368
369 formvals = {}
370 missingfields = []
371
372 for item in inputfields.keys():
373 formvals[item] = form.get(item, [inputfields[item]])[0]
374 if (not formvals[item]) and (item in requiredfields):
375 missingfields.append(requiredfields[item])
376
377 return formvals, missingfields
378
379 def getparams(args):
380 # process arguments
381
382 params = {}
383 if args:
384 # Arguments are comma delimited key=value pairs
385 sargs = args.split(',')
386
387 for item in sargs:
388 sitem = item.split('=')
389
390 if len(sitem) == 2:
391 key, value = sitem[0], sitem[1]
392 params[key.strip()] = value.strip()
393
394 Params.pagename = params.get('pagename', '')
395
396 Params.section = params.get('section', '')
397 if Params.section:
398 Params.section = getescapedsectionname(Params.section)
399
400 try:
401 Params.inputonly = int(params.get('inputonly', 0))
402 except ValueError:
403 Params.inputonly = 0
404
405 try:
406 Params.commentonly = int(params.get('commentonly', 0))
407 except ValueError:
408 Params.commentonly = 0
409
410 try:
411 Params.countonly = int(params.get('countonly', 0))
412 except ValueError:
413 Params.countonly = 0
414
415 try:
416 Params.newerfirst = int(params.get('newerfirst', 0))
417 except ValueError:
418 Params.newerfirst = 0
419
420 try:
421 Params.commentfirst = int(params.get('commentfirst', 0))
422 except ValueError:
423 Params.commentfirst = 0
424
425 try:
426 Params.articleview = int(params.get('articleview', 0))
427 except ValueError:
428 Params.articleview = 0
429
430 try:
431 Params.smileylist = int(params.get('smileylist', 0))
432 except ValueError:
433 Params.smileylist = 0
434
435 try:
436 Params.nosmiley = int(params.get('nosmiley', 0))
437 except ValueError:
438 Params.nosmiley = 0
439
440 try:
441 Params.rows = int(params.get('rows', 4))
442 except ValueError:
443 Params.rows = 4
444
445 try:
446 Params.cols = int(params.get('cols', 60))
447 except ValueError:
448 Params.cols = 60
449
450 try:
451 Params.maxlength = int(params.get('maxlength', 0))
452 except ValueError:
453 Params.maxlength = 0
454
455 try:
456 Params.notify = int(params.get('notify', 0))
457 except ValueError:
458 Params.notify = 0
459
460 try:
461 Params.encryptpass = int(params.get('encryptpass', 0))
462 except ValueError:
463 Params.encryptpass = 0
464
465 try:
466 Params.markup = int(params.get('markup', 0))
467 except ValueError:
468 Params.markup = 0
469
470 Params.tablewidth = params.get('tablewidth', '')
471 if Params.tablewidth:
472 Params.tablewidth = ' width="%s" ' % Params.tablewidth
473
474 def setglobalvalues(macro):
475
476 # Global variables
477 Globs.macro = macro
478 Globs.defaultacl = u'#acl All:'
479 Globs.adminmsg = ''
480 Globs.debugmsg = ''
481 Globs.customscript = ''
482 Globs.defaulticon = ''
483 request = macro.request
484
485 # ADD SMILEYS HERE TO BE USED:
486 Globs.smileys = [':)', ':))', ':(', ';)', ':\\', '|)', 'X-(', 'B)']
487
488 if Params.markup:
489
490 # ADD MACROS HERE TO ALLOW TO BE USED IN THE TEXT:
491 Globs.macroallowed = [ 'BR', 'Date', 'DateTime', 'MailTo', 'Icon' ]
492
493 from MoinMoin import wikimacro
494 macronames = wikimacro.getNames(request.cfg)
495
496 for names in Globs.macroallowed:
497 macronames.remove(names)
498
499 # ADD REGEX PATTERN HERE TO MAKE IT FORBIDDEN TO USE IN MARKUP:
500 Globs.markupforbidden = {
501 #ur'(^\s*)((?P<hmarker>=+)\s.*\s(?P=hmarker))( $)': r'\1`\2`\4',
502 #ur'(?P<rule>-{4,})': r'`\1`',
503 ur'(?P<macro>\[\[(%(macronames)s)(?:\(.*?\))?\]\])' % { 'macronames': u'|'.join(macronames) } : r'`\1`'
504 }
505
506 Globs.curpagename = macro.formatter.page.page_name
507
508 if Params.pagename:
509 Globs.pagename = Params.pagename
510 else:
511 Globs.pagename = Globs.curpagename
512
513 Globs.cursubname = Globs.curpagename.split('/')[-1]
514 Globs.datapagename = u'%s/%s%s' % (Globs.pagename, 'PageCommentData', Params.section)
515
516 try:
517 #if request.user.may.delete(Globs.pagename):
518 if request.user.may.write(Globs.pagename):
519 Globs.admin = 'true'
520 else:
521 Globs.admin = ''
522 except AttributeError:
523 Globs.admin = ''
524 pass
525
526 # set form id
527
528 if not hasattr(request, 'pgformid'):
529 request.pgformid = 0
530
531 request.pgformid += 1
532 Globs.formid = request.pgformid
533
534
535 def message(astring):
536 Globs.adminmsg = u'PageComment: %s\n' % astring
537
538 def debug(astring):
539 Globs.debugmsg += u'%s\n<br>' % astring
540
541
542 def commentform(tmpauthor, tmptext, tmppasswd, tmpicon, comrev, tmpautopass, tmpmarkup):
543 # A form for posting a new comment
544 request = Globs.macro.request
545 datapagename = Globs.datapagename
546 _ = request.getText
547
548 cellstyle = u'border-width: 0px; vertical-align: middle; font-size: 0.9em;'
549
550 pg = Page( request, datapagename )
551
552 if pg.exists():
553 comrev = pg.current_rev()
554 else:
555 comrev = 0
556
557 if not Params.nosmiley:
558 if not Params.smileylist:
559 iconlist = getsmileymarkupradio(tmpicon)
560 else:
561 iconlist = getsmileymarkuplist(tmpicon)
562 else:
563 iconlist = ''
564
565 initName = ''
566 initPass = ''
567 initText = ''
568
569 if not (request.user.valid or tmpauthor):
570
571 tmpauthor = getAuthorFromCookie()
572
573 if not tmpauthor:
574
575 import socket
576 host = request.remote_addr
577
578 try:
579 hostname = socket.gethostbyaddr(host)[0]
580 except socket.error:
581 hostname = host
582
583 tmpauthor = hostname.split('.')[0]
584
585 initName = tmpauthor
586
587 if not tmppasswd:
588 tmppasswd = nicepass()
589 initPass = tmppasswd
590 elif tmpautopass and tmpautopass == tmppasswd:
591 tmppasswd = nicepass()
592 initPass = tmppasswd
593
594 if not tmptext:
595 tmptext = u'Add your comment'
596 initText = tmptext
597 elif tmptext and tmptext == u'Add your comment':
598 initText = tmptext
599
600 previewbutton = ''
601 markupcheckbox = ''
602
603 if Params.markup:
604 if not (tmpmarkup == '0'):
605 markupchecked = "checked"
606 else:
607 markupchecked = ''
608
609 previewbutton = '<br><input type="submit" name="button_preview" value="%s" style="color: #ff7777; font-size: 9pt; width: 6em; ">' % _('Preview')
610 markupcheckbox = '<input type="checkbox" name="commarkup%d" value="1" %s> Markup' % (Globs.formid, markupchecked)
611
612
613 if request.user.valid:
614 html1 = [
615 u'<input type="hidden" value="%s" name="comauthor">' % request.user.name,
616 u'<input type="hidden" value="*" name="compasswd">',
617 ]
618 authorJavascriptCode = ''
619 onSubmitCode = ''
620 else:
621 html1 = [
622 u'<input type="text" style="font-size: 9pt;" size="6" maxlength="20" name="comauthor" value="%(author)s" onfocus="if (this.value==\'%(msg)s\') {this.value=\'\';};" onblur="if (this.value==\'\') {this.value=\'%(msg)s\';};">' % { 'msg': wikiutil.escape(initName), 'cellstyle': cellstyle, 'author': wikiutil.escape(tmpauthor) },
623 u'<input type="password" style="font-size: 9pt;" size="4" maxlength="10" name="compasswd" value="%(passwd)s" onfocus="if (this.value==\'%(msg)s\') {this.value=\'\';};" onblur="if (this.value==\'\') {this.value=\'%(msg)s\';};">' % { 'msg': wikiutil.escape(initPass), 'passwd': wikiutil.escape(tmppasswd) },
624 u'<input type="hidden" value="%s" name="autopasswd">' % wikiutil.escape(initPass),
625 ]
626
627 authorJavascriptCode = """
628 <script language="javascript">
629 <!--
630 function setCookie(name, value) {
631 var today = new Date();
632 var expire = new Date(today.getTime() + 60*60*24*365*1000);
633 document.cookie = name + "=" + encodeURIComponent(value) + "; expires=" + expire.toGMTString() + "; path=%s";
634 }
635 //-->
636 </script>""" % request.getScriptname()
637
638 onSubmitCode = 'onSubmit="setCookie(\'PG2AUTHOR\', this.comauthor.value);"'
639
640 html1 = u'\n'.join(html1)
641 scripthtml = u'onfocus="if (this.value==\'%(msg)s\') {this.value=\'\';};" onblur="if (this.value==\'\') {this.value=\'%(msg)s\';};"' % {'msg': wikiutil.escape(initText) }
642
643 page_url = wikiutil.quoteWikinameURL(Globs.cursubname)
644
645 html2 = [
646 u'%s' % authorJavascriptCode,
647 u'<form action="%s#pagecomment%d" name="comment" METHOD="POST" %s>' % (page_url, Globs.formid, onSubmitCode),
648 u'<table class="addcommentform">',
649 u'<tr>',
650 u'<td style="%s"><textarea name="comtext" rows="%d" cols="%d" style="font-size: 9pt;" ' % (cellstyle, Params.rows, Params.cols),
651 u'%s>%s</textarea></td>' % (scripthtml, wikiutil.escape(tmptext)),
652 u'<td style="%s vertical-align: bottom;"><input type="submit" name="button_save" value="%s" style="font-size: 9pt; width: 6em; height:3em; ">%s</td>' % (cellstyle, _('Save'), previewbutton),
653 u'</tr>',
654 u'<tr><td style="%s">' % cellstyle,
655 u'%s' % html1,
656 u'%s' % iconlist,
657 u'</td>',
658 u'<td style="%s text-align: right; font-size: 9pt;">%s</td>' % (cellstyle, markupcheckbox),
659 u'</tr>',
660 u'</table>',
661 u'<input type="hidden" name="action" value="show" >',
662 u'<input type="hidden" name="comrev" value="%s">' % comrev,
663 u'<input type="hidden" name="commentaction" value="addcomment%d">' % Globs.formid,
664 u'</form>',
665 ]
666
667
668 return u'\n'.join(html2)
669
670 def addcomment(macro, comicon, comauthor, comtext, compasswd, comrev, comautopass, commarkup):
671 # Add a comment with inputs
672
673 request = Globs.macro.request
674 cfg = request.cfg
675 _ = request.getText
676
677 datapagename = Globs.datapagename
678
679 pg = PageEditor( request, datapagename )
680 pagetext = pg.get_raw_body()
681
682 # HACK for despam
683 try:
684 if not request.user.may.save( pg, comtext, pg.current_rev()):
685 #message("No permission to save this text.")
686 #return 0
687 pass
688
689 except pg.SaveError, msg:
690 message(msg)
691 return 0
692
693 comtext = convertdelimeter(comtext)
694
695 if request.user.valid:
696 comloginuser = 'TRUE'
697 comauthor = request.user.name
698 else:
699 comloginuser = ''
700 comauthor = convertdelimeter(comauthor)
701
702 orgcompasswd = compasswd
703
704 if Params.encryptpass:
705 from MoinMoin import user
706 compasswd = user.encodePassword(compasswd)
707
708 newcomment = [
709 u'{{{',
710 u'%s,%s' % (comicon, commarkup),
711 u'%s' % comauthor,
712 u'%s' % time.strftime(cfg.datetime_fmt, time.localtime(time.time())),
713 u'',
714 u'%s' % comtext,
715 u'}}}',
716 u'##PASSWORD %s' % compasswd,
717 u'##LOGINUSER %s' % comloginuser,
718 ]
719
720 newpagetext = u'%s\n\n%s' % (pagetext, u'\n'.join(newcomment))
721
722 if not pg.exists():
723 action = 'SAVENEW'
724 defaultacl = Globs.defaultacl
725 warnmessages = '\'\'\'\'\'DO NOT EDIT THIS PAGE!!\'\'\' This page is automatically generated by Page``Comment macro.\'\'\n----'
726 newpagetext = u'%s\n%s\n%s' % (defaultacl, warnmessages, newpagetext)
727 else:
728 action = 'SAVE'
729
730 newpagetext = pg.normalizeText( newpagetext )
731
732 comment = u'PageComment modification at %s' % Globs.curpagename
733 pg._write_file(newpagetext, action, comment)
734
735 comment = u'New comment by "%s"' % comauthor
736
737 trivial = 0
738 addLogEntry(request, 'COMNEW', Globs.curpagename, comment)
739
740 #msg = _('Thank you for your changes. Your attention to detail is appreciated.')
741 msg = _('The commnet is added.')
742
743 # send notification mails
744 if Params.notify:
745 msg = msg + commentNotify(comment, trivial, comtext)
746
747 if comautopass and comautopass == orgcompasswd:
748 msg2 = u'<i>You did not enter a password. A random password has been generated for you: <b>%s</b></i>' % comautopass
749 msg = u'%s%s' % (msg, msg2)
750
751 message(msg)
752 return 1
753
754
755 def previewcomment(comicon, comauthor, comtext, commarkup):
756 request = Globs.macro.request
757 _ = request.getText
758 cfg = request.cfg
759
760 # normalize text
761 lines = comtext.splitlines()
762 if not lines[-1] == u'':
763 # '' will make newline after join
764 lines.append(u'')
765
766 comtext = u'\n'.join(lines)
767
768 #comtext = convertdelimeter(comtext)
769 #comauthor = convertdelimeter(comauthor)
770
771 if Params.articleview:
772 cellstyle = u'border-width: 1px; border-bottom-width: 0px; border-color: #ff7777; background-color: #eeeeee; vertical-align: top; font-size: 9pt;'
773 htmlcomment = [
774 u'<tr><td colspan="5" class="commenttext" style="%(cellstyle)s">%(text)s</td></tr>',
775 u'<tr><td colspan="5" class="commentauthor" style="border-color: #ff7777; border-width: 1px; border-top-width: 0px; text-align: right; font-size: 8pt; color: #999999;">Posted by <b>%(author)s</b> %(icon)s at %(date)s %(delform)s</td></tr>',
776 u'<tr><td colspan="5" class="commentblankline" style="border-width: 0px; height: 20px;"></td></tr>',
777 ]
778
779 else:
780 cellstyle = u'border-width: 0px; background-color: #ffeeee; border-top-width: 1px; vertical-align: top; font-size: 9pt;'
781 htmlcomment = [
782 u'<tr><td class="commenticon" style="%(cellstyle)s">%(icon)s</td>',
783 u'<td class="commentauthor" style="%(cellstyle)s">%(author)s</td>',
784 u'<td style="%(cellstyle)s width: 10px;"> </td>',
785 u'<td class="commenttext" style="%(cellstyle)s">%(text)s</td>',
786 u'<td class="commentdate" style="%(cellstyle)s text-align: right; font-size: 8pt; " nowrap>%(date)s%(delform)s</td></tr>',
787 ]
788
789 htmlcommentitem = u'\n'.join(htmlcomment) % {
790 'cellstyle': cellstyle,
791 'icon': getsmiley(comicon),
792 'author': converttext(comauthor),
793 'text': converttext(comtext, commarkup),
794 'date': time.strftime(cfg.datetime_fmt, time.localtime(time.time())),
795 'delform': ''
796 }
797
798 return htmlcommentitem
799
800 def showcomment():
801
802 request = Globs.macro.request
803 _ = request.getText
804
805 commentlist = fetchcomments()
806
807 if Params.newerfirst:
808 commentlist.reverse()
809
810 html = []
811 cur_index = 0
812
813 if Params.articleview:
814 cellstyle = u'border-width: 0px; background-color: #eeeeee; vertical-align: top; font-size: 9pt;'
815 htmlcomment = [
816 u'<tr><td colspan="5" class="commenttext" style="%(cellstyle)s">%(text)s</td></tr>',
817 u'<tr><td colspan="5" class="commentauthor" style="text-align: right; border-width: 0px; font-size: 8pt; color: #999999;">Posted by <b>%(author)s</b> %(icon)s at %(date)s %(delform)s</td></tr>',
818 u'<tr><td colspan="5" class="commentblankline" style="border-width: 0px; height: 20px;"></td></tr>',
819 ]
820
821 else:
822 cellstyle = u'border-width: 0px; border-top-width: 1px; vertical-align: top; font-size: 9pt;'
823 htmlcomment = [
824 u'<tr><td class="commenticon" style="%(cellstyle)s">%(icon)s</td>',
825 u'<td class="commentauthor" style="%(cellstyle)s">%(author)s</td>',
826 u'<td style="%(cellstyle)s width: 10px;"> </td>',
827 u'<td class="commenttext" style="%(cellstyle)s">%(text)s</td>',
828 u'<td class="commentdate" style="%(cellstyle)s text-align: right; font-size: 8pt; " nowrap>%(date)s%(delform)s</td></tr>',
829 ]
830
831 htmlcommentdel_admin = [
832 u' <font style="font-size: 8pt;">',
833 u'<a style="color: #aa0000;" href="javascript: requesttodeleteadmin%(formid)d(document.delform%(formid)d, \'%(key)s\');" title="%(msg)s">X</a>',
834 u'</font>',
835 ]
836
837 htmlcommentdel_guest = [
838 u' <font style="font-size: 8pt;">',
839 u'<a style="color: #aa0000;" href="javascript: requesttodelete%(formid)d(document.delform%(formid)d, \'%(key)s\');" title="%(msg)s">X</a>',
840 u'</font>',
841 ]
842
843 for item in commentlist:
844 if Globs.admin or (item['loginuser'] and request.user.valid and request.user.name == item['name']):
845 htmlcommentdel = htmlcommentdel_admin
846 elif item['loginuser']:
847 htmlcommentdel = ''
848 else:
849 htmlcommentdel = htmlcommentdel_guest
850
851 htmlcommentdel = u'\n'.join(htmlcommentdel) % {
852 'formid': Globs.formid,
853 'key': item['key'],
854 'msg': _('Delete')
855 }
856
857 htmlcommentitem = u'\n'.join(htmlcomment) % {
858 'cellstyle': cellstyle,
859 'icon': getsmiley(item['icon']),
860 'author': converttext(item['name']),
861 'text': converttext(item['text'], item['markup']),
862 'date': item['date'],
863 'delform': htmlcommentdel
864 }
865
866 html.append(htmlcommentitem)
867
868 return u'\n'.join(html)
869
870 def getescapedsectionname(targettext):
871 regex = r'\W'
872 pattern = re.compile(regex, re.UNICODE)
873 sectionname = pattern.sub('', targettext)
874
875 return sectionname
876
877
878 def getsmiley(markup):
879
880 if markup in config.smileys.keys():
881 formatter = Globs.macro.formatter
882 return formatter.smiley(markup)
883 else:
884 return ''
885
886
887 def converttext(targettext, markup='0'):
888 # Converts some special characters of html to plain-text style
889 # What else to handle?
890
891 if Params.markup and markup == '1':
892 targettext = getMarkupText(targettext)
893 else:
894 # targettext = targettext.strip()
895 targettext = targettext.replace(u'&', '&')
896 targettext = targettext.replace(u'>', '>')
897 targettext = targettext.replace(u'<', '<')
898 targettext = targettext.replace(u'\n', '<br>')
899 targettext = targettext.replace(u'"', '"')
900 targettext = targettext.replace(u'\t', ' ')
901 targettext = targettext.replace(u' ', ' ')
902
903 return targettext
904
905 def convertdelimeter(targettext, reverse=0):
906 # Converts delimeter to other string to avoid a crash
907
908 if reverse:
909 targettext = targettext.replace(u'{_{_{', u'{{{')
910 targettext = targettext.replace(u'}_}_}', u'}}}')
911
912 else:
913 targettext = targettext.replace(u'{{{', u'{_{_{')
914 targettext = targettext.replace(u'}}}', u'}_}_}')
915
916 return targettext
917
918
919 def deleteform():
920 # Javascript codes for deleting or restoring a comment
921
922 request = Globs.macro.request
923 _ = request.getText
924
925 htmlresult = []
926
927 html = [
928 '<script language="javascript">',
929 '<!--',
930 ]
931 htmlresult.append(u'\n'.join(html))
932
933 html = [
934 ' function requesttodeleteadmin%d(delform, comkey) {' % Globs.formid,
935 ' if (confirm("%s")) {;' % _('Really delete this comment?'),
936 ' delform.delkey.value = comkey;',
937 ' delform.delpasswd.value = "****";',
938 ' delform.submit();',
939 ' }',
940 ' }',
941 ' function requesttodelete%d(delform, comkey) {' % Globs.formid,
942 ' var passwd = prompt("%s:", "");' % _('Please specify a password!'),
943 ' if(!(passwd == "" || passwd == null)) {',
944 ' delform.delkey.value = comkey;',
945 ' delform.delpasswd.value = passwd;',
946 ' delform.submit();',
947 ' }',
948 ' }',
949 ]
950
951 htmlresult.append(u'\n'.join(html))
952
953 page_url = wikiutil.quoteWikinameURL(Globs.cursubname)
954
955 html = [
956 '//-->',
957 '</script>',
958 '<form name="delform%d" action="%s#pagecomment%d" METHOD="post">' % (Globs.formid, page_url, Globs.formid),
959 '<input type="hidden" value="show" name="action">',
960 '<input name="delpasswd" type="hidden" value="****">',
961 '<input name="delkey" type="hidden" value="">',
962 '<input type="hidden" name="commentaction" value="delcomment%s">' % Globs.formid,
963 '</form>',
964 ]
965 htmlresult.append(u'\n'.join(html))
966
967 return u'\n'.join(htmlresult)
968
969
970 def filtercomment(index='', name='', passwd=''):
971
972 # filter by index
973 if index:
974 filteredlist1 = fetchcomments(index, index)
975 else:
976 filteredlist1 = fetchcomments()
977
978 # filter by name
979 filteredlist2 = []
980 if name:
981 for item in filteredlist1:
982 if name == item['name']:
983 filteredlist2.append(item)
984 else:
985 filteredlist2 = filteredlist1
986
987 # filter by password
988 filteredlist3 = []
989 if passwd:
990 for item in filteredlist2:
991 if passwd == item['passwd']:
992 filteredlist3.append(item)
993 else:
994 filteredlist3 = filteredlist2
995
996 return filteredlist3
997
998
999 def fetchcomments(startindex=1, endindex=9999):
1000
1001 commentlist = []
1002
1003 request = Globs.macro.request
1004 formatter = Globs.macro.formatter
1005 datapagename = Globs.datapagename
1006
1007 pg = Page( request, datapagename )
1008 pagetext = pg.get_raw_body()
1009
1010 regex = ur"""
1011 ^[\{]{3}\n
1012 ^(?P<icon>[^\n]*)\n
1013 ^(?P<name>[^\n]*)\n
1014 ^(?P<date>[^\n]*)\n\n
1015 ^(?P<text>
1016 \s*.*?
1017 (?=[\}]{3})
1018 )[\}]{3}[\n]*
1019 ^[#]{2}PASSWORD[ ](?P<passwd>[^\n]*)[\n]*
1020 ^[#]{2}LOGINUSER[ ](?P<loginuser>[^\n]*)[\n]*"""
1021
1022 pattern = re.compile(regex, re.UNICODE + re.MULTILINE + re.VERBOSE + re.DOTALL)
1023 commentitems = pattern.findall(pagetext)
1024
1025 cur_index = 0
1026
1027 for item in commentitems:
1028 comment = {}
1029 cur_index += 1
1030
1031 if cur_index < startindex:
1032 continue
1033
1034 comment['index'] = cur_index
1035
1036 custom_fields = item[0].split(',')
1037
1038 comment['icon'] = custom_fields[0]
1039
1040 if len(custom_fields) > 1:
1041 comment['markup'] = custom_fields[1].strip()
1042 else:
1043 comment['markup'] = ''
1044
1045 comment['name'] = convertdelimeter(item[1], 1)
1046 comment['date'] = item[2]
1047 comment['text'] = convertdelimeter(item[3], 1)
1048 comment['passwd'] = item[4]
1049 comment['loginuser'] = item[5]
1050
1051 # experimental
1052 comment['key'] = comment['date'].strip()
1053
1054 commentlist.append(comment)
1055
1056 if cur_index >= endindex:
1057 break
1058
1059 return commentlist
1060
1061 def deletecomment(macro, delkey, delpasswd):
1062 # Deletes a comment with given index and password
1063
1064 request = Globs.macro.request
1065 formatter = Globs.macro.formatter
1066 datapagename = Globs.datapagename
1067 _ = request.getText
1068
1069 if Params.encryptpass:
1070 from MoinMoin import user
1071 delpasswd = user.encodePassword(delpasswd)
1072
1073 pg = PageEditor( request, datapagename )
1074 pagetext = pg.get_raw_body()
1075
1076 regex = ur"""
1077 (?P<comblock>
1078 ^[\{]{3}\n
1079 ^(?P<icon>[^\n]*)\n
1080 ^(?P<name>[^\n]*)\n
1081 ^(?P<date>[^\n]*)[\n]+
1082 ^(?P<text>
1083 \s*.*?
1084 (?=[\}]{3})
1085 )[\}]{3}[\n]*
1086 ^[#]{2}PASSWORD[ ](?P<passwd>[^\n]*)[\n]*
1087 ^[#]{2}LOGINUSER[ ](?P<loginuser>[^\n]*)[\n$]*
1088 )"""
1089
1090 pattern = re.compile(regex, re.UNICODE + re.MULTILINE + re.VERBOSE + re.DOTALL)
1091 commentitems = pattern.findall(pagetext)
1092
1093 for item in commentitems:
1094
1095 if delkey == item[3].strip():
1096 comauthor = item[2]
1097 if Globs.admin or (request.user.valid and request.user.name == comauthor) or delpasswd == item[5]:
1098 newpagetext = pagetext.replace(item[0], '', 1)
1099
1100 action = 'SAVE'
1101 comment = 'Deleted comment by "%s"' % comauthor
1102 trivial = 1
1103 pg._write_file(newpagetext, action, u'PageComment modification at %s' % Globs.curpagename)
1104 addLogEntry(request, 'COMDEL', Globs.curpagename, comment)
1105
1106 msg = _('The comment is deleted.')
1107
1108 # send notification mails
1109 if Params.notify:
1110 msg = msg + commentNotify(comment, trivial)
1111
1112 message(msg)
1113
1114 return
1115 else:
1116 message(_('Sorry, wrong password.'))
1117 return
1118
1119 message(_('No such comment'))
1120
1121
1122 def getAuthorFromCookie():
1123
1124 import Cookie
1125 request = Globs.macro.request
1126 cookieauthor = ''
1127
1128 try:
1129 cookie = Cookie.SimpleCookie(request.saved_cookie)
1130 except Cookie.CookieError:
1131 # ignore invalid cookies
1132 cookie = None
1133
1134 if cookie and cookie.has_key('PG2AUTHOR'):
1135 cookieauthor = cookie['PG2AUTHOR'].value
1136
1137 cookieauthor = decodeURI(cookieauthor)
1138
1139 return cookieauthor
1140
1141
1142 def commentNotify(comment, trivial, comtext=''):
1143
1144 request = Globs.macro.request
1145
1146 if hasattr(request.cfg, 'mail_enabled'):
1147 mail_enabled = request.cfg.mail_enabled
1148 elif hasattr(request.cfg, 'mail_smarthost'):
1149 mail_enabled = request.cfg.mail_smarthost
1150 else:
1151 mail_enabled = ''
1152
1153 if not mail_enabled:
1154 return ''
1155
1156 _ = request.getText
1157 pg = PageEditor( request, Globs.pagename )
1158
1159 subscribers = pg.getSubscribers(request, return_users=1, trivial=trivial)
1160 if subscribers:
1161 # get a list of old revisions, and append a diff
1162
1163 # send email to all subscribers
1164 results = [_('Status of sending notification mails:')]
1165 for lang in subscribers.keys():
1166 emails = map(lambda u: u.email, subscribers[lang])
1167 names = map(lambda u: u.name, subscribers[lang])
1168 mailok, status = sendNotification(pg, comtext, comment, emails, lang, trivial)
1169 recipients = ", ".join(names)
1170 results.append(_('[%(lang)s] %(recipients)s: %(status)s') % {
1171 'lang': lang, 'recipients': recipients, 'status': status})
1172
1173 # Return mail sent results. Ignore trivial - we don't have
1174 # to lie. If mail was sent, just tell about it.
1175 return '<p>\n%s\n</p> ' % '<br>'.join(results)
1176
1177 # No mail sent, no message.
1178 return ''
1179
1180 def sendNotification(pg, comtext, comment, emails, email_lang, trivial):
1181
1182 from MoinMoin import util, user
1183 request = Globs.macro.request
1184
1185 _ = lambda s, formatted=True, r=request, l=email_lang: r.getText(s, formatted=formatted, lang=l)
1186
1187 mailBody = _("Dear Wiki user,\n\n"
1188 'You have subscribed to a wiki page or wiki category on "%(sitename)s" for change notification.\n\n'
1189 "The following page has been changed by %(editor)s:\n"
1190 "%(pagelink)s\n\n", formatted=False) % {
1191 'editor': pg.uid_override or user.getUserIdentification(request),
1192 'pagelink': pg.request.getQualifiedURL(pg.url(request)),
1193 'sitename': pg.cfg.sitename or request.getBaseURL(),
1194 }
1195
1196 if comment:
1197 mailBody = mailBody + \
1198 _("The comment on the change is:\n%(comment)s\n\n", formatted=False) % {'comment': comment}
1199
1200 # append comment text
1201 if comtext:
1202 mailBody = mailBody + "%s\n%s\n" % (("-" * 78), comtext)
1203
1204 return util.mail.sendmail(request, emails,
1205 _('[%(sitename)s] %(trivial)sUpdate of "%(pagename)s" by %(username)s', formatted=False) % {
1206 'trivial' : (trivial and _("Trivial ", formatted=False)) or "",
1207 'sitename': pg.cfg.sitename or "Wiki",
1208 'pagename': pg.page_name,
1209 'username': pg.uid_override or user.getUserIdentification(request),
1210 },
1211 mailBody, mail_from=pg.cfg.mail_from)
1212
1213
1214
1215 def decodeURI(quotedstring):
1216
1217 try:
1218 unquotedstring = wikiutil.url_unquote(quotedstring)
1219 except AttributeError:
1220 # for compatibility with old versions
1221 unquotedstring = url_unquote(quotedstring)
1222
1223 return unquotedstring
1224
1225
1226 def url_unquote(s, want_unicode=True):
1227 """
1228 From moinmoin 1.5
1229
1230 Wrapper around urllib.unquote doing the encoding/decoding as usually wanted:
1231
1232 @param s: the string to unquote (can be str or unicode, if it is unicode,
1233 config.charset is used to encode it before calling urllib)
1234 @param want_unicode: for the less usual case that you want to get back
1235 str and not unicode, set this to False.
1236 Default is True.
1237 """
1238 import urllib
1239
1240 if isinstance(s, unicode):
1241 s = s.encode(config.charset) # ascii would also work
1242 s = urllib.unquote(s)
1243 if want_unicode:
1244 s = s.decode(config.charset)
1245 return s
1246
1247
1248 def addLogEntry(request, action, pagename, msg):
1249 # Add an entry to the edit log on adding comments.
1250 from MoinMoin.logfile import editlog
1251 t = wikiutil.timestamp2version(time.time())
1252 msg = unicode(msg)
1253
1254 pg = Page( request, pagename )
1255 #rev = pg.current_rev()
1256 rev = 99999999
1257
1258 # TODO: for now we simply write 2 logs, maybe better use some multilog stuff
1259 # Write to global log
1260 log = editlog.EditLog(request)
1261 log.add(request, t, rev, action, pagename, request.remote_addr, '', msg)
1262
1263 # Write to local log
1264 log = editlog.EditLog(request, rootpagename=pagename)
1265 log.add(request, t, rev, action, pagename, request.remote_addr, '', msg)
1266
1267 def getsmileymarkuplist(defaulticon):
1268
1269 html = [
1270 u'Smiley: <select name="comicon">',
1271 u' <option value=""></option>',
1272 ]
1273
1274 for smiley in config.smileys.keys():
1275 if defaulticon.strip() == smiley:
1276 html.append(u' <option selected>%s</option>' % wikiutil.escape(smiley))
1277 else:
1278 html.append(u' <option>%s</option>' % wikiutil.escape(smiley))
1279
1280 html.append(u'</select>')
1281
1282 return u'\n'.join(html)
1283
1284 def getsmileymarkupradio(defaulticon):
1285
1286 smileys = Globs.smileys
1287 html = []
1288
1289 for smiley in smileys:
1290 if defaulticon.strip() == smiley:
1291 html.append(u'<input type="radio" name="comicon" value="%s" checked>%s ' % (wikiutil.escape(smiley), getsmiley(smiley)) )
1292 else:
1293 html.append(u'<input type="radio" name="comicon" value="%s">%s ' % (wikiutil.escape(smiley), getsmiley(smiley)) )
1294
1295 html.append(u'</select>')
1296
1297 return u'\n'.join(html)
1298
1299
1300 def getMarkupText(lines):
1301 request = Globs.macro.request
1302 formatter = Globs.macro.formatter
1303
1304 markup = Globs.markupforbidden
1305
1306 for regex in markup.keys():
1307 pattern = re.compile(regex, re.UNICODE + re.VERBOSE + re.MULTILINE)
1308 lines, nchanges = pattern.subn(markup[regex], lines)
1309
1310 #if nchanges:
1311 # debug(regex)
1312
1313 out = StringIO.StringIO()
1314 request.redirect(out)
1315 wikiizer = wiki.Parser(lines, request)
1316 wikiizer.format(formatter)
1317 targettext = out.getvalue()
1318 request.redirect()
1319 del out
1320
1321 return targettext
1322
1323
1324 def nicepass(alpha=3,numeric=1):
1325 """
1326 returns a human-readble password (say rol86din instead of
1327 a difficult to remember K8Yn9muL )
1328 """
1329 import string
1330 import random
1331 vowels = ['a','e','i','o','u']
1332 consonants = [a for a in string.ascii_lowercase if a not in vowels]
1333 digits = string.digits
1334
1335 ####utility functions
1336 def a_part(slen):
1337 ret = ''
1338 for i in range(slen):
1339 if i%2 ==0:
1340 randid = random.randint(0,20) #number of consonants
1341 ret += consonants[randid]
1342 else:
1343 randid = random.randint(0,4) #number of vowels
1344 ret += vowels[randid]
1345 return ret
1346
1347 def n_part(slen):
1348 ret = ''
1349 for i in range(slen):
1350 randid = random.randint(0,9) #number of digits
1351 ret += digits[randid]
1352 return ret
1353
1354 ####
1355 fpl = alpha/2
1356 if alpha % 2 :
1357 fpl = int(alpha/2) + 1
1358 lpl = alpha - fpl
1359
1360 start = a_part(fpl)
1361 mid = n_part(numeric)
1362 end = a_part(lpl)
1363
1364 # return "%s%s%s" % (start,mid,end)
1365 return "%s%s%s" % (start,end,mid)
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.