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