00001
00002 """
00003 PageComment2.py Version 0.992 February , 2008
00004
00005 This macro gives a form to post a new comment to the page and shows a list of the posted comments.
00006
00007 @copyright: 2005 by Seungik Lee <seungiklee<at>gmail.com> http://www.silee.net/
00008 @license: GPL
00009
00010 Usage: <<PageComment2>>
00011
00012 Features:
00013
00014 - Simple usage, just put <<PageComment2>> on any page.
00015 - Lets anonymous users post a new comment with an input form.
00016 - Shows a list of the posted comments.
00017 - Support for comment deletion by given password.
00018 - Support for administrative action, e.g.,
00019 - to delete a comment without entering a given password
00020
00021 Parameters:
00022
00023 - pagename: the page name which the comments are retrieved for. by default the page itself.
00024 If the user has no 'read' ACL for that page, it does not allow to insert/view comments.
00025 e.g., pagename=AnotherPage
00026
00027 - section: the section name of the page. The comments in different sections are managed in separated sub pages.
00028 Section name should be alphanumeric format ([a-zA-Z0-9] in regular expression).
00029 If not, all the non-alphanumric characters are removed.
00030 e.g., section=1, section=News, section=Opinion
00031
00032 - inputonly: shows input form only. list of the comments are shown to admin users only.
00033 - inputonly=0; default, all list is shown to all users including anonymous users
00034 - inputonly=1; shown to admin users only (who has the page delete privilege)
00035
00036 - commentonly: shows the list of comments only.
00037 - commentonly=0; default, both of the list and input form will be shown
00038 - commentonly=1; only the list of comments will be shown
00039
00040 - countonly: returns the number of the comments posted to this page
00041 - countonly=0; default, normal form (input form; list of comments)
00042 - countonly=1; just return the number of comments.
00043 e.g., 'There are [[PageComments(countonly=1)]] comments here'
00044
00045 - rows: the # of rows of the textarea. default 4. e.g., rows=4
00046
00047 - cols: the # of columns of the textarea. default 60. e.g., cols=60
00048
00049 - maxlength: limitation on # of characters for comment text. default 0 (no limit). e.g., maxlength=500
00050
00051 - newerfirst: order of the list of comments.
00052 - newerfirst=0: default, newer ones are listed at the end
00053 - newerfirst=1: newer ones are listed at the top
00054
00055 - commentfirst: shows comment list before the input form.
00056 - commentfirst=0: default, the input form first
00057 - commentfirst=1: comment list first
00058
00059 - articleview: shows comment list in an article view.
00060 - articleview=0: default, list in table view
00061 - articleview=1: list in article view
00062
00063 - tablewidth: the width of the table format for PageComment2, default '' (none).
00064 e.g., tablewidth=600, tablewidth=100%
00065
00066 - smileylist: shows smiley options with drop-down list box
00067 - smileylist=0: default, a part of the smiley in radio button
00068 - smileylist=1: smiley in drop-down list box
00069
00070 - nosmiley: shows no smiley
00071 - nosmiley=0: default, shows smiley selection
00072 - nosmiley=1: no smiley selection
00073
00074 - notify: notifies to the subscribers of the page which includes the macro when a comment is added
00075 - notify=0: default, notification disabled
00076 - notify=1: notification enabled
00077
00078 - encryptpass: encrypts entered password
00079 - encryptpass=0: default, the password is stored in plain text
00080 - encryptpass=1: the password is stored in encrypted format
00081
00082 - markup: enables wiki markup in the comment text except some specified macros.
00083 - markup=0: default, use of wiki markup in the text is disabled
00084 - markup=1: use of wiki markup in the text is enabled and preview button is activated
00085
00086 Change Log
00087
00088 - April 17, 2006 - Version 0.98
00089 - fixed a bug on revision history
00090 - added a despam action
00091
00092 - Jan. 05, 2006 - Version 0.97
00093 - added features:
00094 - mail notification
00095 - password encryption
00096 - wiki markup support with preview
00097 - remember author name last used
00098 - administrative actions (delete without password) are allowed to those who has WRITE acl.
00099
00100 - Nov. 29, 2005 - Version 0.96
00101 - some format parameters are added
00102 - random password feature is added
00103
00104 - Nov. 20, 2005 - Version 0.95
00105 - some minor bugs are fixed
00106
00107 - Nov. 20, 2005 - Version 0.94
00108 - some parameters are added
00109 - some minor bugs are fixed
00110
00111 - Nov. 19, 2005 - Version 0.92
00112 - some minor bugs are fixed
00113 - 'olderfirst' parameter replaced with 'newerfirst'
00114
00115 - Nov. 19, 2005 - Version 0.91
00116 - some parameters are added
00117 - validates smiley markup
00118 - modified view
00119
00120 - Nov. 18, 2005 - Version 0.90 (Release 2)
00121 - No text data file support any more: Comment is stored in the sub wiki page.
00122 - (does not compatible with Release 1: PageComment.py)
00123 - Custom icon (smiley) can be inserted
00124 - Pre-fill the name input field with his/her login name
00125 - Logs at add/remove comments
00126 - Added some parameters
00127
00128 - Oct. 08, 2005 - Version 0.82
00129 - Changed the directory the data file stored to be secured
00130
00131 - Oct. 07, 2005 - Version 0.81
00132 - Unicode encoding related bugs in deletecomment function are patched.
00133 - Instruction bugs are patched.
00134
00135 - Oct. 06, 2005 - Version 0.80
00136 - The initial version is released.
00137
00138
00139 Notes
00140
00141 - 'Gallery.py' developed by Simon Ryan has inspired this macro.
00142 - Thanks to many of the MoinMoin users for valuable comments.
00143 - Visit http://moinmoin.wikiwikiweb.de/MacroMarket/PageComment2 for more detail
00144 - This version only works with MoinMoin version 1.6.1 and higher (until broken)
00145 No new functionality was included in the 0.99x version
00146
00147 """
00148
00149 from MoinMoin import config, wikiutil
00150 import StringIO, time, re
00151 from MoinMoin.Page import Page
00152 from MoinMoin.PageEditor import PageEditor
00153 from MoinMoin.parser import text_moin_wiki
00154 import MoinMoin.macro
00155
00156 class Globs:
00157
00158
00159 adminmsg = ''
00160 datapagename = ''
00161 pagename = ''
00162 curpagename = ''
00163 cursubname = ''
00164 admin = ''
00165 macro = ''
00166 defaultacl = ''
00167 defaulticon = ''
00168 formid = 0
00169 smileys = []
00170
00171 class Params:
00172
00173 rows = 0
00174 cols = 0
00175 maxlength = 0
00176 newerfirst = 0
00177 tablewidth = ''
00178 commentfirst = 0
00179 pagename = ''
00180 commentonly = 0
00181 inputonly = 0
00182 countonly = 0
00183 section = ''
00184 articleview = 0
00185 notify = 0
00186 encryptpass = 0
00187 markup = 0
00188
00189
00190
00191
00192
00193
00194
00195
00196
00197
00198 def execute(macro, args):
00199
00200
00201 getparams(args)
00202 setglobalvalues(macro)
00203
00204
00205 request = macro.request
00206 _ = request.getText
00207
00208 if not Globs.pagename == Globs.curpagename:
00209 if not macro.request.user.may.read(Globs.pagename):
00210 return macro.formatter.rawHTML(u'PageComment: %s' % _('You are not allowed to view this page.'))
00211 elif not Page(request, Globs.pagename).exists():
00212 return macro.formatter.rawHTML(u'PageComment: %s' % _('This page is already deleted or was never created!'))
00213
00214
00215 if Params.countonly:
00216 html = len(fetchcomments())
00217 return macro.formatter.rawHTML('%s' % html)
00218
00219 datapagename = Globs.datapagename
00220
00221
00222 comicon = Globs.defaulticon
00223 comauthor = ''
00224 comtext = ''
00225 compasswd = ''
00226 comrev = 0
00227 comautopass = ''
00228 commentpreview = ''
00229 commarkup = ''
00230
00231 addcommand = u'addcomment%d' % Globs.formid
00232 delcommand = u'delcomment%d' % Globs.formid
00233
00234 action = macro.form.get('commentaction', [''])[0]
00235
00236 if action == addcommand:
00237
00238
00239 form_fields = {'comicon': Globs.defaulticon, 'comauthor': '', 'comtext': '', 'compasswd': '', 'comrev': 0, 'autopasswd': '', 'button_save': '', 'button_preview': '', 'commarkup%d' % Globs.formid: '0'}
00240 required_fields = {'comauthor': _('Name'), 'comtext': _('Text'), 'compasswd': _('Password'), 'comrev': 'Rev. #'}
00241
00242 formvals, missingfields = getforminput(macro.form, form_fields, required_fields)
00243
00244 comicon = formvals['comicon']
00245 comauthor = formvals['comauthor']
00246 comtext = formvals['comtext']
00247 compasswd = formvals['compasswd']
00248 comrev = int(formvals['comrev'])
00249 comautopass = formvals['autopasswd']
00250 btnsave = formvals['button_save']
00251 btnpreview = formvals['button_preview']
00252 commarkup = formvals['commarkup%d' % Globs.formid]
00253
00254 if not len(missingfields) == len(required_fields):
00255 if not missingfields:
00256
00257
00258 if comicon and (not comicon in config.smileys):
00259 message('Please use smiley markup only')
00260
00261 elif Params.maxlength and (len(comtext) > Params.maxlength):
00262 message('Comment text is limited to %d characters. (%d characters now)' % (Params.maxlength, len(comtext)) )
00263
00264 elif not comtext.strip() or comtext == u'Add your comment':
00265 message('Please fill the comment text')
00266
00267
00268 elif btnpreview:
00269 commentpreview = previewcomment(comicon, comauthor, comtext, commarkup)
00270
00271
00272 elif btnsave:
00273 flag = addcomment(macro, comicon, comauthor, comtext, compasswd, comrev, comautopass, commarkup)
00274
00275 if flag:
00276 comicon = Globs.defaulticon
00277 comauthor = ''
00278 comtext = ''
00279 compasswd = ''
00280 comrev = 0
00281 commentpreview = ''
00282 commarkup = ''
00283
00284
00285 else:
00286 message( 'What do you want?' )
00287
00288 else:
00289 message( _('Required attribute "%(attrname)s" missing') % { 'attrname': u', '.join(missingfields) } )
00290
00291 elif action == delcommand:
00292
00293
00294 form_fields = {'delkey': '', 'delpasswd': ''}
00295 required_fields = {'delkey': 'Comment Key', 'delpasswd': 'Password'}
00296
00297 formvals, missingfields = getforminput(macro.form, form_fields, required_fields)
00298
00299 delkey = formvals['delkey']
00300 delpasswd = formvals['delpasswd']
00301
00302 if not len(missingfields) == len(required_fields):
00303 if not missingfields:
00304 deletecomment(macro, delkey, delpasswd)
00305 else:
00306 message( _('Required attribute "%(attrname)s" missing') % { 'attrname': u', '.join(missingfields) } )
00307
00308
00309 html = []
00310
00311 html.append(u'<div id="pagecomment">')
00312 html.append(u'<a name="pagecomment%d"></a>' % Globs.formid)
00313
00314 html.append(u'<table border="0" class="pagecomment" %s>' % Params.tablewidth)
00315
00316 if Globs.adminmsg:
00317 html.append(u'<tr><td colspan="5" style="border-width: 0px;">')
00318 html.append(u'<font color="#aa0000">%s</font>' % Globs.adminmsg)
00319 html.append(u'</td></tr>')
00320
00321 commentlisthtml = showcommentsection()
00322 commentformhtml = commentformsection(comauthor, comtext, compasswd, comicon, comrev, comautopass, commarkup)
00323
00324 if Params.commentfirst:
00325 if commentpreview:
00326 html.append(commentpreview)
00327
00328 html.append(commentlisthtml)
00329 html.append(u'<tr><td colspan="5" class="commentblankline" style="border-width: 0px; height: 20px;"></td></tr>')
00330 html.append(commentformhtml)
00331 else:
00332 html.append(commentformhtml)
00333 html.append(u'<tr><td colspan="5" class="commentblankline" style="border-width: 0px; height: 20px;"></td></tr>')
00334 if commentpreview:
00335 html.append(commentpreview)
00336
00337 html.append(commentlisthtml)
00338
00339 if Globs.debugmsg:
00340 html.append(u'<tr><td colspan="5" style="border-width: 0px;">')
00341 html.append(u'<font color="#aa0000">%s</font>' % Globs.debugmsg)
00342 html.append(u'</td></tr>')
00343
00344 html.append(u'</table>')
00345
00346 if Globs.customscript:
00347 html.append(u'%s' % Globs.customscript)
00348
00349 html.append(u'</div>')
00350
00351 return macro.formatter.rawHTML(u'\n'.join(html))
00352
00353
00354 def commentformsection(comauthor, comtext, compasswd, comicon, comrev, autopass, commarkup):
00355 html = []
00356
00357 if not Params.commentonly:
00358 html.append(u'<tr><td style="border-width: 1px; margin: 10px 0 10px 0;" colspan="5">')
00359
00360 html.append(commentform(comauthor, comtext, compasswd, comicon, comrev, autopass, commarkup))
00361
00362 html.append(u'</td></tr>')
00363
00364 return u'\n'.join(html)
00365
00366
00367 def showcommentsection():
00368 html = []
00369 if (not Params.inputonly) or Globs.admin:
00370 html.append(deleteform())
00371 html.append(showcomment())
00372 else:
00373 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>')
00374
00375 return u'\n'.join(html)
00376
00377 def getforminput(form, inputfields, requiredfields):
00378
00379 formvals = {}
00380 missingfields = []
00381
00382 for item in inputfields.keys():
00383 formvals[item] = form.get(item, [inputfields[item]])[0]
00384 if (not formvals[item]) and (item in requiredfields):
00385 missingfields.append(requiredfields[item])
00386
00387 return formvals, missingfields
00388
00389 def getparams(args):
00390
00391
00392 params = {}
00393 if args:
00394
00395 sargs = args.split(',')
00396
00397 for item in sargs:
00398 sitem = item.split('=')
00399
00400 if len(sitem) == 2:
00401 key, value = sitem[0], sitem[1]
00402 params[key.strip()] = value.strip()
00403
00404 Params.pagename = params.get('pagename', '')
00405
00406 Params.section = params.get('section', '')
00407 if Params.section:
00408 Params.section = getescapedsectionname(Params.section)
00409
00410 try:
00411 Params.inputonly = int(params.get('inputonly', 0))
00412 except ValueError:
00413 Params.inputonly = 0
00414
00415 try:
00416 Params.commentonly = int(params.get('commentonly', 0))
00417 except ValueError:
00418 Params.commentonly = 0
00419
00420 try:
00421 Params.countonly = int(params.get('countonly', 0))
00422 except ValueError:
00423 Params.countonly = 0
00424
00425 try:
00426 Params.newerfirst = int(params.get('newerfirst', 0))
00427 except ValueError:
00428 Params.newerfirst = 0
00429
00430 try:
00431 Params.commentfirst = int(params.get('commentfirst', 0))
00432 except ValueError:
00433 Params.commentfirst = 0
00434
00435 try:
00436 Params.articleview = int(params.get('articleview', 0))
00437 except ValueError:
00438 Params.articleview = 0
00439
00440 try:
00441 Params.smileylist = int(params.get('smileylist', 0))
00442 except ValueError:
00443 Params.smileylist = 0
00444
00445 try:
00446 Params.nosmiley = int(params.get('nosmiley', 0))
00447 except ValueError:
00448 Params.nosmiley = 0
00449
00450 try:
00451 Params.rows = int(params.get('rows', 4))
00452 except ValueError:
00453 Params.rows = 4
00454
00455 try:
00456 Params.cols = int(params.get('cols', 60))
00457 except ValueError:
00458 Params.cols = 60
00459
00460 try:
00461 Params.maxlength = int(params.get('maxlength', 0))
00462 except ValueError:
00463 Params.maxlength = 0
00464
00465 try:
00466 Params.notify = int(params.get('notify', 0))
00467 except ValueError:
00468 Params.notify = 0
00469
00470 try:
00471 Params.encryptpass = int(params.get('encryptpass', 0))
00472 except ValueError:
00473 Params.encryptpass = 0
00474
00475 try:
00476 Params.markup = int(params.get('markup', 0))
00477 except ValueError:
00478 Params.markup = 0
00479
00480 Params.tablewidth = params.get('tablewidth', '')
00481 if Params.tablewidth:
00482 Params.tablewidth = ' width="%s" ' % Params.tablewidth
00483
00484 def setglobalvalues(macro):
00485
00486
00487 Globs.macro = macro
00488 Globs.defaultacl = u'#acl All:'
00489 Globs.adminmsg = ''
00490 Globs.debugmsg = ''
00491 Globs.customscript = ''
00492 Globs.defaulticon = ''
00493 request = macro.request
00494
00495
00496 Globs.smileys = [':)', ':))', ':(', ';)', ':\\', '|)', 'X-(', 'B)']
00497
00498 if Params.markup:
00499
00500
00501 Globs.macroallowed = [ 'BR', 'Date', 'DateTime', 'MailTo', 'Icon' ]
00502
00503 macronames = [name for name in MoinMoin.macro.getNames(request.cfg)\
00504 if name not in Globs.macroallowed]
00505
00506
00507 Globs.markupforbidden = {
00508
00509
00510 ur'(?P<macro><<(%(macronames)s)(?:\(.*?\))?>>)' % { 'macronames': u'|'.join(macronames) } : r'`\1`'
00511 }
00512
00513 Globs.curpagename = macro.formatter.page.page_name
00514
00515 if Params.pagename:
00516 Globs.pagename = Params.pagename
00517 else:
00518 Globs.pagename = Globs.curpagename
00519
00520 Globs.cursubname = Globs.curpagename.split('/')[-1]
00521 Globs.datapagename = u'%s/%s%s' % (Globs.pagename, 'PageCommentData', Params.section)
00522
00523 try:
00524
00525 if request.user.may.write(Globs.pagename):
00526 Globs.admin = 'true'
00527 else:
00528 Globs.admin = ''
00529 except AttributeError:
00530 Globs.admin = ''
00531 pass
00532
00533
00534
00535 if not hasattr(request, 'pgformid'):
00536 request.pgformid = 0
00537
00538 request.pgformid += 1
00539 Globs.formid = request.pgformid
00540
00541
00542 def message(astring):
00543 Globs.adminmsg = u'PageComment: %s\n' % astring
00544
00545 def debug(astring):
00546 Globs.debugmsg += u'%s\n<br>' % astring
00547
00548
00549 def commentform(tmpauthor, tmptext, tmppasswd, tmpicon, comrev, tmpautopass, tmpmarkup):
00550
00551 request = Globs.macro.request
00552 datapagename = Globs.datapagename
00553 _ = request.getText
00554
00555 cellstyle = u'border-width: 0px; vertical-align: middle; font-size: 0.9em;'
00556
00557 pg = Page( request, datapagename )
00558
00559 if pg.exists():
00560 comrev = pg.current_rev()
00561 else:
00562 comrev = 0
00563
00564 if not Params.nosmiley:
00565 if not Params.smileylist:
00566 iconlist = getsmileymarkupradio(tmpicon)
00567 else:
00568 iconlist = getsmileymarkuplist(tmpicon)
00569 else:
00570 iconlist = ''
00571
00572 initName = ''
00573 initPass = ''
00574 initText = ''
00575
00576 if not (request.user.valid or tmpauthor):
00577
00578 tmpauthor = getAuthorFromCookie()
00579
00580 if not tmpauthor:
00581
00582 import socket
00583 host = request.remote_addr
00584
00585 try:
00586 hostname = socket.gethostbyaddr(host)[0]
00587 except socket.error:
00588 hostname = host
00589
00590 tmpauthor = hostname.split('.')[0]
00591
00592 initName = tmpauthor
00593
00594 if not tmppasswd:
00595 tmppasswd = nicepass()
00596 initPass = tmppasswd
00597 elif tmpautopass and tmpautopass == tmppasswd:
00598 tmppasswd = nicepass()
00599 initPass = tmppasswd
00600
00601 if not tmptext:
00602 tmptext = u'Add your comment'
00603 initText = tmptext
00604 elif tmptext and tmptext == u'Add your comment':
00605 initText = tmptext
00606
00607 previewbutton = ''
00608 markupcheckbox = ''
00609
00610 if Params.markup:
00611 if not (tmpmarkup == '0'):
00612 markupchecked = "checked"
00613 else:
00614 markupchecked = ''
00615
00616 previewbutton = '<br><input type="submit" name="button_preview" value="%s" style="color: #ff7777; font-size: 9pt; width: 6em; ">' % _('Preview')
00617 markupcheckbox = '<input type="checkbox" name="commarkup%d" value="1" %s> Markup' % (Globs.formid, markupchecked)
00618
00619
00620 if request.user.valid:
00621 html1 = [
00622 u'<input type="hidden" value="%s" name="comauthor">' % request.user.name,
00623 u'<input type="hidden" value="*" name="compasswd">',
00624 ]
00625 authorJavascriptCode = ''
00626 onSubmitCode = ''
00627 else:
00628 html1 = [
00629 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) },
00630 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) },
00631 u'<input type="hidden" value="%s" name="autopasswd">' % wikiutil.escape(initPass),
00632 ]
00633
00634 authorJavascriptCode = """
00635 <script language="javascript">
00636 <!--
00637 function setCookie(name, value) {
00638 var today = new Date();
00639 var expire = new Date(today.getTime() + 60*60*24*365*1000);
00640 document.cookie = name + "=" + encodeURIComponent(value) + "; expires=" + expire.toGMTString() + "; path=%s";
00641 }
00642 //-->
00643 </script>""" % request.getScriptname()
00644
00645 onSubmitCode = 'onSubmit="setCookie(\'PG2AUTHOR\', this.comauthor.value);"'
00646
00647 html1 = u'\n'.join(html1)
00648 scripthtml = u'onfocus="if (this.value==\'%(msg)s\') {this.value=\'\';};" onblur="if (this.value==\'\') {this.value=\'%(msg)s\';};"' % {'msg': wikiutil.escape(initText) }
00649
00650 page_url = wikiutil.quoteWikinameURL(Globs.cursubname)
00651
00652 html2 = [
00653 u'%s' % authorJavascriptCode,
00654 u'<form action="%s#pagecomment%d" name="comment" METHOD="POST" %s>' % (page_url, Globs.formid, onSubmitCode),
00655 u'<table class="addcommentform">',
00656 u'<tr>',
00657 u'<td style="%s"><textarea name="comtext" rows="%d" cols="%d" style="font-size: 9pt;" ' % (cellstyle, Params.rows, Params.cols),
00658 u'%s>%s</textarea></td>' % (scripthtml, wikiutil.escape(tmptext)),
00659 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),
00660 u'</tr>',
00661 u'<tr><td style="%s">' % cellstyle,
00662 u'%s' % html1,
00663 u'%s' % iconlist,
00664 u'</td>',
00665 u'<td style="%s text-align: right; font-size: 9pt;">%s</td>' % (cellstyle, markupcheckbox),
00666 u'</tr>',
00667 u'</table>',
00668 u'<input type="hidden" name="action" value="show" >',
00669 u'<input type="hidden" name="comrev" value="%s">' % comrev,
00670 u'<input type="hidden" name="commentaction" value="addcomment%d">' % Globs.formid,
00671 u'</form>',
00672 ]
00673
00674
00675 return u'\n'.join(html2)
00676
00677 def addcomment(macro, comicon, comauthor, comtext, compasswd, comrev, comautopass, commarkup):
00678
00679
00680 request = Globs.macro.request
00681 cfg = request.cfg
00682 _ = request.getText
00683
00684 datapagename = Globs.datapagename
00685
00686 pg = PageEditor( request, datapagename )
00687 pagetext = pg.get_raw_body()
00688
00689
00690 try:
00691 if not request.user.may.save( pg, comtext, pg.current_rev()):
00692
00693
00694 pass
00695
00696 except pg.SaveError, msg:
00697 message(msg)
00698 return 0
00699
00700 comtext = convertdelimeter(comtext)
00701
00702 if request.user.valid:
00703 comloginuser = 'TRUE'
00704 comauthor = request.user.name
00705 else:
00706 comloginuser = ''
00707 comauthor = convertdelimeter(comauthor)
00708
00709 orgcompasswd = compasswd
00710
00711 if Params.encryptpass:
00712 from MoinMoin import user
00713 compasswd = user.encodePassword(compasswd)
00714
00715 newcomment = [
00716 u'{{{',
00717 u'%s,%s' % (comicon, commarkup),
00718 u'%s' % comauthor,
00719 u'%s' % time.strftime(cfg.datetime_fmt, time.localtime(time.time())),
00720 u'',
00721 u'%s' % comtext,
00722 u'}}}',
00723 u'##PASSWORD %s' % compasswd,
00724 u'##LOGINUSER %s' % comloginuser,
00725 ]
00726
00727 newpagetext = u'%s\n\n%s' % (pagetext, u'\n'.join(newcomment))
00728
00729 if not pg.exists():
00730 action = 'SAVENEW'
00731 defaultacl = Globs.defaultacl
00732 warnmessages = '\'\'\'\'\'DO NOT EDIT THIS PAGE!!\'\'\' This page is automatically generated by Page``Comment macro.\'\'\n----'
00733 newpagetext = u'%s\n%s\n%s' % (defaultacl, warnmessages, newpagetext)
00734 else:
00735 action = 'SAVE'
00736
00737 newpagetext = pg.normalizeText( newpagetext )
00738
00739 comment = u'PageComment modification at %s' % Globs.curpagename
00740 pg._write_file(newpagetext, action, comment)
00741
00742 comment = u'New comment by "%s"' % comauthor
00743
00744 trivial = 0
00745 addLogEntry(request, 'COMNEW', Globs.curpagename, comment)
00746
00747
00748 msg = _('The comment is added.')
00749
00750
00751 if Params.notify:
00752 msg = msg + commentNotify(comment, trivial, comtext)
00753
00754 if comautopass and comautopass == orgcompasswd:
00755 msg2 = u'<i>You did not enter a password. A random password has been generated for you: <b>%s</b></i>' % comautopass
00756 msg = u'%s%s' % (msg, msg2)
00757
00758 message(msg)
00759 return 1
00760
00761
00762 def previewcomment(comicon, comauthor, comtext, commarkup):
00763 request = Globs.macro.request
00764 _ = request.getText
00765 cfg = request.cfg
00766
00767
00768 lines = comtext.splitlines()
00769 if not lines[-1] == u'':
00770
00771 lines.append(u'')
00772
00773 comtext = u'\n'.join(lines)
00774
00775
00776
00777
00778 if Params.articleview:
00779 cellstyle = u'border-width: 1px; border-bottom-width: 0px; border-color: #ff7777; background-color: #eeeeee; vertical-align: top; font-size: 9pt;'
00780 htmlcomment = [
00781 u'<tr><td colspan="5" class="commenttext" style="%(cellstyle)s">%(text)s</td></tr>',
00782 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>',
00783 u'<tr><td colspan="5" class="commentblankline" style="border-width: 0px; height: 20px;"></td></tr>',
00784 ]
00785
00786 else:
00787 cellstyle = u'border-width: 0px; background-color: #ffeeee; border-top-width: 1px; vertical-align: top; font-size: 9pt;'
00788 htmlcomment = [
00789 u'<tr><td class="commenticon" style="%(cellstyle)s">%(icon)s</td>',
00790 u'<td class="commentauthor" style="%(cellstyle)s">%(author)s</td>',
00791 u'<td style="%(cellstyle)s width: 10px;"> </td>',
00792 u'<td class="commenttext" style="%(cellstyle)s">%(text)s</td>',
00793 u'<td class="commentdate" style="%(cellstyle)s text-align: right; font-size: 8pt; " nowrap>%(date)s%(delform)s</td></tr>',
00794 ]
00795
00796 htmlcommentitem = u'\n'.join(htmlcomment) % {
00797 'cellstyle': cellstyle,
00798 'icon': getsmiley(comicon),
00799 'author': converttext(comauthor),
00800 'text': converttext(comtext, commarkup),
00801 'date': time.strftime(cfg.datetime_fmt, time.localtime(time.time())),
00802 'delform': ''
00803 }
00804
00805 return htmlcommentitem
00806
00807 def showcomment():
00808
00809 request = Globs.macro.request
00810 _ = request.getText
00811
00812 commentlist = fetchcomments()
00813
00814 if Params.newerfirst:
00815 commentlist.reverse()
00816
00817 html = []
00818 cur_index = 0
00819
00820 if Params.articleview:
00821 cellstyle = u'border-width: 0px; background-color: #eeeeee; vertical-align: top; font-size: 9pt;'
00822 htmlcomment = [
00823 u'<tr><td colspan="5" class="commenttext" style="%(cellstyle)s">%(text)s</td></tr>',
00824 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>',
00825 u'<tr><td colspan="5" class="commentblankline" style="border-width: 0px; height: 20px;"></td></tr>',
00826 ]
00827
00828 else:
00829 cellstyle = u'border-width: 0px; border-top-width: 1px; vertical-align: top; font-size: 9pt;'
00830 htmlcomment = [
00831 u'<tr><td class="commenticon" style="%(cellstyle)s">%(icon)s</td>',
00832 u'<td class="commentauthor" style="%(cellstyle)s">%(author)s</td>',
00833 u'<td style="%(cellstyle)s width: 10px;"> </td>',
00834 u'<td class="commenttext" style="%(cellstyle)s">%(text)s</td>',
00835 u'<td class="commentdate" style="%(cellstyle)s text-align: right; font-size: 8pt; " nowrap>%(date)s%(delform)s</td></tr>',
00836 ]
00837
00838 htmlcommentdel_admin = [
00839 u' <font style="font-size: 8pt;">',
00840 u'<a style="color: #aa0000;" href="javascript: requesttodeleteadmin%(formid)d(document.delform%(formid)d, \'%(key)s\');" title="%(msg)s">X</a>',
00841 u'</font>',
00842 ]
00843
00844 htmlcommentdel_guest = [
00845 u' <font style="font-size: 8pt;">',
00846 u'<a style="color: #aa0000;" href="javascript: requesttodelete%(formid)d(document.delform%(formid)d, \'%(key)s\');" title="%(msg)s">X</a>',
00847 u'</font>',
00848 ]
00849
00850 for item in commentlist:
00851 if Globs.admin or (item['loginuser'] and request.user.valid and request.user.name == item['name']):
00852 htmlcommentdel = htmlcommentdel_admin
00853 elif item['loginuser']:
00854 htmlcommentdel = ''
00855 else:
00856 htmlcommentdel = htmlcommentdel_guest
00857
00858 htmlcommentdel = u'\n'.join(htmlcommentdel) % {
00859 'formid': Globs.formid,
00860 'key': item['key'],
00861 'msg': _('Delete')
00862 }
00863
00864 htmlcommentitem = u'\n'.join(htmlcomment) % {
00865 'cellstyle': cellstyle,
00866 'icon': getsmiley(item['icon']),
00867 'author': converttext(item['name']),
00868 'text': converttext(item['text'], item['markup']),
00869 'date': item['date'],
00870 'delform': htmlcommentdel
00871 }
00872
00873 html.append(htmlcommentitem)
00874
00875 return u'\n'.join(html)
00876
00877 def getescapedsectionname(targettext):
00878 regex = r'\W'
00879 pattern = re.compile(regex, re.UNICODE)
00880 sectionname = pattern.sub('', targettext)
00881
00882 return sectionname
00883
00884
00885 def getsmiley(markup):
00886
00887 if markup in config.smileys:
00888 formatter = Globs.macro.formatter
00889 return formatter.smiley(markup)
00890 else:
00891 return ''
00892
00893
00894 def converttext(targettext, markup='0'):
00895
00896
00897
00898 if Params.markup and markup == '1':
00899 targettext = getMarkupText(targettext)
00900 else:
00901
00902 targettext = targettext.replace(u'&', '&')
00903 targettext = targettext.replace(u'>', '>')
00904 targettext = targettext.replace(u'<', '<')
00905 targettext = targettext.replace(u'\n', '<br>')
00906 targettext = targettext.replace(u'"', '"')
00907 targettext = targettext.replace(u'\t', ' ')
00908 targettext = targettext.replace(u' ', ' ')
00909
00910 return targettext
00911
00912 def convertdelimeter(targettext, reverse=0):
00913
00914
00915 if reverse:
00916 targettext = targettext.replace(u'{_{_{', u'{{{')
00917 targettext = targettext.replace(u'}_}_}', u'}}}')
00918
00919 else:
00920 targettext = targettext.replace(u'{{{', u'{_{_{')
00921 targettext = targettext.replace(u'}}}', u'}_}_}')
00922
00923 return targettext
00924
00925
00926 def deleteform():
00927
00928
00929 request = Globs.macro.request
00930 _ = request.getText
00931
00932 htmlresult = []
00933
00934 html = [
00935 '<script language="javascript">',
00936 '<!--',
00937 ]
00938 htmlresult.append(u'\n'.join(html))
00939
00940 html = [
00941 ' function requesttodeleteadmin%d(delform, comkey) {' % Globs.formid,
00942 ' if (confirm("%s")) {;' % _('Really delete this comment?'),
00943 ' delform.delkey.value = comkey;',
00944 ' delform.delpasswd.value = "****";',
00945 ' delform.submit();',
00946 ' }',
00947 ' }',
00948 ' function requesttodelete%d(delform, comkey) {' % Globs.formid,
00949 ' var passwd = prompt("%s:", "");' % _('Please specify a password!'),
00950 ' if(!(passwd == "" || passwd == null)) {',
00951 ' delform.delkey.value = comkey;',
00952 ' delform.delpasswd.value = passwd;',
00953 ' delform.submit();',
00954 ' }',
00955 ' }',
00956 ]
00957
00958 htmlresult.append(u'\n'.join(html))
00959
00960 page_url = wikiutil.quoteWikinameURL(Globs.cursubname)
00961
00962 html = [
00963 '//-->',
00964 '</script>',
00965 '<form name="delform%d" action="%s#pagecomment%d" METHOD="post">' % (Globs.formid, page_url, Globs.formid),
00966 '<input type="hidden" value="show" name="action">',
00967 '<input name="delpasswd" type="hidden" value="****">',
00968 '<input name="delkey" type="hidden" value="">',
00969 '<input type="hidden" name="commentaction" value="delcomment%s">' % Globs.formid,
00970 '</form>',
00971 ]
00972 htmlresult.append(u'\n'.join(html))
00973
00974 return u'\n'.join(htmlresult)
00975
00976
00977 def filtercomment(index='', name='', passwd=''):
00978
00979
00980 if index:
00981 filteredlist1 = fetchcomments(index, index)
00982 else:
00983 filteredlist1 = fetchcomments()
00984
00985
00986 filteredlist2 = []
00987 if name:
00988 for item in filteredlist1:
00989 if name == item['name']:
00990 filteredlist2.append(item)
00991 else:
00992 filteredlist2 = filteredlist1
00993
00994
00995 filteredlist3 = []
00996 if passwd:
00997 for item in filteredlist2:
00998 if passwd == item['passwd']:
00999 filteredlist3.append(item)
01000 else:
01001 filteredlist3 = filteredlist2
01002
01003 return filteredlist3
01004
01005
01006 def fetchcomments(startindex=1, endindex=9999):
01007
01008 commentlist = []
01009
01010 request = Globs.macro.request
01011 formatter = Globs.macro.formatter
01012 datapagename = Globs.datapagename
01013
01014 pg = Page( request, datapagename )
01015 pagetext = pg.get_raw_body()
01016
01017 regex = ur"""
01018 ^[\{]{3}\n
01019 ^(?P<icon>[^\n]*)\n
01020 ^(?P<name>[^\n]*)\n
01021 ^(?P<date>[^\n]*)\n\n
01022 ^(?P<text>
01023 \s*.*?
01024 (?=[\}]{3})
01025 )[\}]{3}[\n]*
01026 ^[#]{2}PASSWORD[ ](?P<passwd>[^\n]*)[\n]*
01027 ^[#]{2}LOGINUSER[ ](?P<loginuser>[^\n]*)[\n]*"""
01028
01029 pattern = re.compile(regex, re.UNICODE + re.MULTILINE + re.VERBOSE + re.DOTALL)
01030 commentitems = pattern.findall(pagetext)
01031
01032 cur_index = 0
01033
01034 for item in commentitems:
01035 comment = {}
01036 cur_index += 1
01037
01038 if cur_index < startindex:
01039 continue
01040
01041 comment['index'] = cur_index
01042
01043 custom_fields = item[0].split(',')
01044
01045 comment['icon'] = custom_fields[0]
01046
01047 if len(custom_fields) > 1:
01048 comment['markup'] = custom_fields[1].strip()
01049 else:
01050 comment['markup'] = ''
01051
01052 comment['name'] = convertdelimeter(item[1], 1)
01053 comment['date'] = item[2]
01054 comment['text'] = convertdelimeter(item[3], 1)
01055 comment['passwd'] = item[4]
01056 comment['loginuser'] = item[5]
01057
01058
01059 comment['key'] = comment['date'].strip()
01060
01061 commentlist.append(comment)
01062
01063 if cur_index >= endindex:
01064 break
01065
01066 return commentlist
01067
01068 def deletecomment(macro, delkey, delpasswd):
01069
01070
01071 request = Globs.macro.request
01072 formatter = Globs.macro.formatter
01073 datapagename = Globs.datapagename
01074 _ = request.getText
01075
01076 if Params.encryptpass:
01077 from MoinMoin import user
01078 delpasswd = user.encodePassword(delpasswd)
01079
01080 pg = PageEditor( request, datapagename )
01081 pagetext = pg.get_raw_body()
01082
01083 regex = ur"""
01084 (?P<comblock>
01085 ^[\{]{3}\n
01086 ^(?P<icon>[^\n]*)\n
01087 ^(?P<name>[^\n]*)\n
01088 ^(?P<date>[^\n]*)[\n]+
01089 ^(?P<text>
01090 \s*.*?
01091 (?=[\}]{3})
01092 )[\}]{3}[\n]*
01093 ^[#]{2}PASSWORD[ ](?P<passwd>[^\n]*)[\n]*
01094 ^[#]{2}LOGINUSER[ ](?P<loginuser>[^\n]*)[\n$]*
01095 )"""
01096
01097 pattern = re.compile(regex, re.UNICODE + re.MULTILINE + re.VERBOSE + re.DOTALL)
01098 commentitems = pattern.findall(pagetext)
01099
01100 for item in commentitems:
01101
01102 if delkey == item[3].strip():
01103 comauthor = item[2]
01104 if Globs.admin or (request.user.valid and request.user.name == comauthor) or delpasswd == item[5]:
01105 newpagetext = pagetext.replace(item[0], '', 1)
01106
01107 action = 'SAVE'
01108 comment = 'Deleted comment by "%s"' % comauthor
01109 trivial = 1
01110 pg._write_file(newpagetext, action, u'PageComment modification at %s' % Globs.curpagename)
01111 addLogEntry(request, 'COMDEL', Globs.curpagename, comment)
01112
01113 msg = _('The comment is deleted.')
01114
01115
01116 if Params.notify:
01117 msg = msg + commentNotify(comment, trivial)
01118
01119 message(msg)
01120
01121 return
01122 else:
01123 message(_('Sorry, wrong password.'))
01124 return
01125
01126 message(_('No such comment'))
01127
01128
01129 def getAuthorFromCookie():
01130
01131 import Cookie
01132 request = Globs.macro.request
01133 cookieauthor = ''
01134
01135 try:
01136 cookie = Cookie.SimpleCookie(request.saved_cookie)
01137 except Cookie.CookieError:
01138
01139 cookie = None
01140
01141 if cookie and cookie.has_key('PG2AUTHOR'):
01142 cookieauthor = cookie['PG2AUTHOR'].value
01143
01144 cookieauthor = decodeURI(cookieauthor)
01145
01146 return cookieauthor
01147
01148
01149 def commentNotify(comment, trivial, comtext=''):
01150
01151 request = Globs.macro.request
01152
01153 if hasattr(request.cfg, 'mail_enabled'):
01154 mail_enabled = request.cfg.mail_enabled
01155 elif hasattr(request.cfg, 'mail_smarthost'):
01156 mail_enabled = request.cfg.mail_smarthost
01157 else:
01158 mail_enabled = ''
01159
01160 if not mail_enabled:
01161 return ''
01162
01163 _ = request.getText
01164 pg = PageEditor( request, Globs.curpagename )
01165
01166 subscribers = pg.getSubscribers(request, return_users=1, trivial=trivial)
01167 if subscribers:
01168
01169
01170
01171 results = [_('Status of sending notification mails:')]
01172 for lang in subscribers.keys():
01173 emails = map(lambda u: u.email, subscribers[lang])
01174 names = map(lambda u: u.name, subscribers[lang])
01175 mailok, status = sendNotification(pg, comtext, comment, emails, lang, trivial)
01176 recipients = ", ".join(names)
01177 results.append(_('[%(lang)s] %(recipients)s: %(status)s') % {
01178 'lang': lang, 'recipients': recipients, 'status': status})
01179
01180
01181
01182 return '<p>\n%s\n</p> ' % '<br>'.join(results)
01183
01184
01185 return ''
01186
01187 def sendNotification(pg, comtext, comment, emails, email_lang, trivial):
01188
01189 from MoinMoin import util, user, mail
01190 request = Globs.macro.request
01191
01192 _ = lambda s, formatted=True, r=request, l=email_lang: r.getText(s, formatted=formatted, lang=l)
01193
01194 mailBody = _("Dear Wiki user,\n\n"
01195 'You have subscribed to a wiki page or wiki category on "%(sitename)s" for change notification.\n\n'
01196 "The following page has been changed by %(editor)s:\n"
01197 "%(pagelink)s\n\n", formatted=False) % {
01198 'editor': pg.uid_override or user.getUserIdentification(request),
01199 'pagelink': pg.request.getQualifiedURL(pg.url(request)),
01200 'sitename': pg.cfg.sitename or request.getBaseURL(),
01201 }
01202
01203 if comment:
01204 mailBody = mailBody + \
01205 _("The comment on the change is:\n%(comment)s\n\n", formatted=False) % {'comment': comment}
01206
01207
01208 if comtext:
01209 mailBody = mailBody + "%s\n%s\n" % (("-" * 78), comtext)
01210
01211 return mail.sendmail.sendmail(request, emails,
01212 _('[%(sitename)s] %(trivial)sUpdate of "%(pagename)s" by %(username)s', formatted=False) % {
01213 'trivial' : (trivial and _("Trivial ", formatted=False)) or "",
01214 'sitename': pg.cfg.sitename or "Wiki",
01215 'pagename': pg.page_name,
01216 'username': pg.uid_override or user.getUserIdentification(request),
01217 },
01218 mailBody, mail_from=pg.cfg.mail_from)
01219
01220
01221
01222 def decodeURI(quotedstring):
01223
01224 try:
01225 unquotedstring = wikiutil.url_unquote(quotedstring)
01226 except AttributeError:
01227
01228 unquotedstring = url_unquote(quotedstring)
01229
01230 return unquotedstring
01231
01232
01233 def url_unquote(s, want_unicode=True):
01234 """
01235 From moinmoin 1.5
01236
01237 Wrapper around urllib.unquote doing the encoding/decoding as usually wanted:
01238
01239 @param s: the string to unquote (can be str or unicode, if it is unicode,
01240 config.charset is used to encode it before calling urllib)
01241 @param want_unicode: for the less usual case that you want to get back
01242 str and not unicode, set this to False.
01243 Default is True.
01244 """
01245 import urllib
01246
01247 if isinstance(s, unicode):
01248 s = s.encode(config.charset)
01249 s = urllib.unquote(s)
01250 if want_unicode:
01251 s = s.decode(config.charset)
01252 return s
01253
01254
01255 def addLogEntry(request, action, pagename, msg):
01256
01257 from MoinMoin.logfile import editlog
01258 t = wikiutil.timestamp2version(time.time())
01259 msg = unicode(msg)
01260
01261 pg = Page( request, pagename )
01262
01263 rev = 99999999
01264
01265
01266
01267 log = editlog.EditLog(request)
01268 log.add(request, t, rev, action, pagename, request.remote_addr, '', msg)
01269
01270
01271 log = editlog.EditLog(request, rootpagename=pagename)
01272 log.add(request, t, rev, action, pagename, request.remote_addr, '', msg)
01273
01274 def getsmileymarkuplist(defaulticon):
01275
01276 html = [
01277 u'Smiley: <select name="comicon">',
01278 u' <option value=""></option>',
01279 ]
01280
01281 for smiley in config.smileys:
01282 if defaulticon.strip() == smiley:
01283 html.append(u' <option selected>%s</option>' % wikiutil.escape(smiley))
01284 else:
01285 html.append(u' <option>%s</option>' % wikiutil.escape(smiley))
01286
01287 html.append(u'</select>')
01288
01289 return u'\n'.join(html)
01290
01291 def getsmileymarkupradio(defaulticon):
01292
01293 smileys = Globs.smileys
01294 html = []
01295
01296 for smiley in smileys:
01297 if defaulticon.strip() == smiley:
01298 html.append(u'<input type="radio" name="comicon" value="%s" checked>%s ' % (wikiutil.escape(smiley), getsmiley(smiley)) )
01299 else:
01300 html.append(u'<input type="radio" name="comicon" value="%s">%s ' % (wikiutil.escape(smiley), getsmiley(smiley)) )
01301
01302 html.append(u'</select>')
01303
01304 return u'\n'.join(html)
01305
01306
01307 def getMarkupText(lines):
01308 request = Globs.macro.request
01309 formatter = Globs.macro.formatter
01310
01311 markup = Globs.markupforbidden
01312
01313 for regex in markup.keys():
01314 pattern = re.compile(regex, re.UNICODE + re.VERBOSE + re.MULTILINE)
01315 lines, nchanges = pattern.subn(markup[regex], lines)
01316
01317
01318
01319
01320 out = StringIO.StringIO()
01321 request.redirect(out)
01322 wikiizer = text_moin_wiki.Parser(lines, request)
01323 wikiizer.format(formatter)
01324 targettext = out.getvalue()
01325 request.redirect()
01326 del out
01327
01328 return targettext
01329
01330
01331 def nicepass(alpha=3,numeric=1):
01332 """
01333 returns a human-readble password (say rol86din instead of
01334 a difficult to remember K8Yn9muL )
01335 """
01336 import string
01337 import random
01338 vowels = ['a','e','i','o','u']
01339 consonants = [a for a in string.ascii_lowercase if a not in vowels]
01340 digits = string.digits
01341
01342
01343 def a_part(slen):
01344 ret = ''
01345 for i in range(slen):
01346 if i%2 ==0:
01347 randid = random.randint(0,20)
01348 ret += consonants[randid]
01349 else:
01350 randid = random.randint(0,4)
01351 ret += vowels[randid]
01352 return ret
01353
01354 def n_part(slen):
01355 ret = ''
01356 for i in range(slen):
01357 randid = random.randint(0,9)
01358 ret += digits[randid]
01359 return ret
01360
01361
01362 fpl = alpha/2
01363 if alpha % 2 :
01364 fpl = int(alpha/2) + 1
01365 lpl = alpha - fpl
01366
01367 start = a_part(fpl)
01368 mid = n_part(numeric)
01369 end = a_part(lpl)
01370
01371
01372 return "%s%s%s" % (start,end,mid)