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