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