# -*- coding: iso-8859-1 -*-
"""
    PageComment2.py  Version 0.91  Nov. 19, 2005
                                                                                                           
    This macro gives a form to post a new comment to the page and shows a list of the posted comments.
                                                                                                           
    @copyright: 2005 by Seungik Lee <seungiklee<at>gmail.com>  http://cds.icu.ac.kr/~silee/
    @license: GPL

    Usage: [[PageComment2]]

    Features:
        
        Simple usage, just put [[PageComment2]] on any page.
        Lets anonymous users post a new comment with an input form.
        Shows a list of the posted comments.
        Support for comment deletion by given password.
        Support for administrative action, e.g., 
            - to delete a comment without entering a given password

    Parameters:
        public: if the list of comments is shown to public users
            - public=1; default, all list is shown to all users including anonymous users
            - public=0; shown to only admin users (who has the page delete privilege)
            
        countonly: returns the number of the comments posted to this page
            - countonly=0; default, normal form (input form; list of comments)
            - countonly=1; just return the number of comments. e.g., 'There are [[PageComments(countonly=1)]] comments here'
    
        rows: the # of rows of the textarea. default 2. e.g., rows=2
        
        cols: the # of columns of the textarea. default 60. e.g., cols=60
        
        maxlength: limitation on # of characters for comment text. default 0 (no limit). e.g., maxlength=500
        
        olderfirst: order of the list of comments. Note that it does not re-sort the existing comments.
            - olderfirst=1: default, newer ones are appended to the end
            - olderfirst=0: newer ones are inserted at the top
            
        tablewidth: the width of the table format for PageComment2, default '' (none). 
            e.g., tablewidth=600, tablewidth=100%
    
    Change Log

        Nov. 19, 2005 - Version 0.91
            - some parameters added
            - validates smiley markup
            - modified view
        
        Nov. 18, 2005 - Version 0.90 (Release 2)
            - No text data file support any more: Comment is stored in the sub wiki page.
            - (does not compatible with Release 1: PageComment.py)
            - Custom icon (smiley) can be inserted
            - Pre-fill the name input field with his/her login name
            - Logs at add/remove comments
            - Added some parameters    
        Oct. 08, 2005 - Version 0.82
            - Changed the directory the data file stored to be secured
        
        Oct. 07, 2005 - Version 0.81 
            - Unicode encoding related bugs in deletecomment function are patched. 
            - Instruction bugs are patched. 
        
        Oct. 06, 2005 - Version 0.80 
            - The initial version is released.


    Notes
        
        'Gallery.py' developed by Simon Ryan has inspired this macro.
   

"""

from MoinMoin import config, wikiutil
import StringIO, time, re
from MoinMoin.Page import Page
from MoinMoin.PageEditor import PageEditor
from MoinMoin.parser import wiki


class Globs:
    # A quick place to plonk those shared variables
    adminmsg = ''
    datapagename = ''
    pagename = ''
    subname = ''
    admin = ''
    macro = ''
    defaultacl = ''
    defaulticon = ':)'
    
class Params:
    public = 1
    countonly = 0
    rows = 2
    cols = 60
    maxlength = 0
    olderfirst = 1
    tablewidth = ''

def execute(macro, args):

    # INITIALIZATION ----------------------------------------
    setglobalvalues(macro)
    
    getparams(args)
    
    if Params.countonly:
        html = len(fetchcomments())
        return macro.formatter.rawHTML('%s' % html)
    
    # internal variables
    request = macro.request
    datapagename = Globs.datapagename
    
    _ = request.getText
    
    # form vals
    comicon = Globs.defaulticon
    comauthor = ''
    comtext = ''
    compasswd = ''
    comrev = 0
    
    action = macro.form.get('commentaction', [''])[0]
    
    if action == 'addcomment':
    
        # process form input for comment add
        form_fields = {'comicon': Globs.defaulticon, 'comauthor': '', 'comtext': '', 'compasswd': '', 'comrev': 0}
        required_fields = {'comauthor': _('Name'), 'comtext': _('Text'), 'compasswd': _('Password'), 'comrev': 'Rev. #'}
        
        formvals, missingfields = getforminput(macro.form, form_fields, required_fields)
        
        comicon = formvals['comicon']
        comauthor = formvals['comauthor']
        comtext = formvals['comtext']
        compasswd = formvals['compasswd']
        comrev = formvals['comrev']
    
        if not len(missingfields) == len(required_fields):
            if not missingfields:
                flag = addcomment(macro, comicon, comauthor, comtext, compasswd, comrev)
                
                if flag:
                    comicon = Globs.defaulticon
                    comauthor = ''
                    comtext = ''
                    compasswd = ''
                    comrev = ''
            else:
                message( _('Required attribute "%(attrname)s" missing') % { 'attrname': u', '.join(missingfields) } )
    
    elif action == 'delcomment':
    
        # process form input for comment delete
        form_fields = {'delkey': '', 'delpasswd': ''}
        required_fields = {'delkey': 'Comment Key', 'delpasswd': 'Password'}
        
        formvals, missingfields = getforminput(macro.form, form_fields, required_fields)
        
        delkey = formvals['delkey']
        delpasswd = formvals['delpasswd']
        
        if not len(missingfields) == len(required_fields):
            if not missingfields:
                deletecomment(macro, delkey, delpasswd)
            else:
                message( _('Required attribute "%(attrname)s" missing') % { 'attrname': u', '.join(missingfields) } )
    
    # format output
    html = []
    
    html.append(u'<a name="pagecomment">')
    html.append(u'<table class="pagecomment" %s>' % Params.tablewidth)
    html.append(u'<tr><td style="border-width: 0px;">')
    html.append(u'<font style="color: #aa0000;">%s</font>' % Globs.adminmsg)
    html.append(u'<table class="commentform"><tr><td style="border-width: 1px;">')
    html.append(commentform(comauthor, comtext, compasswd, comicon, comrev))
    html.append(u'</td></tr></table>')
    html.append(u'</td></tr>')
        
    if Params.public or Globs.admin:
        html.append(deleteform())
        html.append(u'<tr><td style="border: 0px;">')
        html.append(showcomment())
        html.append(u'</td></tr>')
    else:
        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>')

    html.append(u'</table>')
    
    return macro.formatter.rawHTML(u'\n'.join(html))

def getforminput(form, inputfields, requiredfields):
    
    formvals = {}
    missingfields = []
    
    for item in inputfields.keys():
        if (not form.has_key(item)) and (item in requiredfields):
            missingfields.append(requiredfields[item])
        formvals[item] = form.get(item, [inputfields[item]])[0]
        
    return formvals, missingfields

def getparams(args):
    # process arguments
    
    params = {}
    if args:
        # Arguments are comma delimited key=value pairs
        sargs = args.split(',')
    
        for item in sargs:
            sitem = item.split('=')
        
            if len(sitem) == 2:
                key, value = sitem[0], sitem[1]
                params[key.strip()] = value.strip()

    try:
        Params.public = int(params.get('public', 1))
    except ValueError:
        Params.public = 1

    try:
        Params.countonly = int(params.get('countonly', 0))
    except ValueError:
        Params.countonly = 0
        
    try:
        Params.rows = int(params.get('rows', 2))
    except ValueError:
        Params.rows = 2

    try:
        Params.cols = int(params.get('cols', 60))
    except ValueError:
        Params.cols = 60

    try:
        Params.maxlength = int(params.get('maxlength', 0))
    except ValueError:
        Params.maxlength = 0
        
    try:
        Params.olderfirst = int(params.get('olderfirst', 1))
    except ValueError:
        Params.olderfirst = 1
        
    Params.tablewidth = params.get('tablewidth', '')
    if Params.tablewidth:
        Params.tablewidth = ' width="%s" ' % Params.tablewidth

def setglobalvalues(macro):
    
    # Global variables
    Globs.pagename = macro.formatter.page.page_name
    Globs.subname = Globs.pagename.split('/')[-1]
    Globs.macro = macro
    Globs.datapagename = u'%s/%s' % (Globs.pagename, 'PageCommentData')
    Globs.defaultacl = u'#acl All:'
    
    # Figure out if we have delete privs
    try:
        if macro.request.user.may.delete(Globs.datapagename):
            Globs.admin = 'true'
    except AttributeError:
        pass


def message(astring):
    Globs.adminmsg += u'%s<br>\n' % astring


def commentform(tmpauthor, tmptext, tmppasswd, tmpicon, comrev):
    # A form for posting a new comment
    request = Globs.macro.request
    datapagename = Globs.datapagename
    _ = request.getText
    
    cellstyle = u'border-width: 0px; vertical-align: middle; font-size: 0.9em; line-height: 1em;'
    
    pg = Page( request, datapagename )
    
    if pg.exists():
        comrev = pg.current_rev()
    else:
        comrev = 0
    
    if request.user.valid:
        html1 = [
            u'<input type="hidden" value="%s" name="comauthor">' % request.user.name,
		    u'<input type="hidden" value="*" name="compasswd">',
            u'<tr><td style="%s">%s: <i>%s</i></td>' % (cellstyle, _('Name'), request.user.name),
    		u'<td style="%s">%s: ****</td>' % (cellstyle, _('Password')),
    		]
    else:
        html1 = [
            u'<tr><td style="%s">%s: <input type="text" size="10" maxlength="20" name="comauthor" value="%s"></td>' % (cellstyle, _('Name'), tmpauthor),
    		u'<td style="%s">%s: <input type="password" size="6" maxlength="10" name="compasswd" value="%s"></td>' % (cellstyle, _('Password'), tmppasswd),
    		]
    
    html1 = u'\n'.join(html1)
    html2 = [
        u'<div id="commentform">',
        u'<form action="%s#pagecomment" name="comment" METHOD="POST">' % Globs.subname,
        u'<table class="addcommentform">',
        u'%s' % html1,
		u'<td style="%s">Smiley: <input type="text" size="4" maxlength="4" name="comicon" value="%s"></td></tr>' % (cellstyle, tmpicon),
		u'<tr><td colspan="3" style="%s"><textarea name="comtext" rows="%d" cols="%d">%s</textarea></td></tr>' % (cellstyle, Params.rows, Params.cols, tmptext),
        u'<tr><td colspan="3" align="center" style="%s"><input type="submit" value="%s"></td></tr>' % (cellstyle, _('Save')),
		u'</table>',
		u'<input type="hidden" value="show" name="action">',
		u'<input type="hidden" value="%s" name="comrev">' % comrev,
		u'<input type="hidden" value="addcomment" name="commentaction">',
		u'</form>',
		u'</div>',
        ]
    
    
    return u'\n'.join(html2)
      
def addcomment(macro, comicon, comauthor, comtext, compasswd, comrev):
    # Add a comment with inputs
    
    request = Globs.macro.request
    cfg = request.cfg
    _ = request.getText
    
    # check input
    if comicon and (not comicon in config.smileys.keys()):
        message('Please use smiley markup only')
        return 0

    if Params.maxlength and (len(comtext) > Params.maxlength):
        message('Comment text is limited to %d characters. (%d characters now)' % (Params.maxlength, len(comtext)) )
        return 0
    
    datapagename = Globs.datapagename
    
    pg = PageEditor( request, datapagename )
    pagetext = pg.get_raw_body()
    
    comtext = convertdelimiter(comtext)
    
    if request.user.valid:
        comloginuser = 'TRUE'
        comauthor = request.user.name
    else:
        comloginuser = ''
        comauthor = convertdelimiter(comauthor)
    
    newcomment = [
        u'{{{',
        u'%s' % comicon,
        u'%s' % comauthor,
        u'%s' % time.strftime(cfg.datetime_fmt, time.localtime(time.time())),
        u'',
        u'%s' % comtext,
        u'}}}',
        u'##PASSWORD %s' % compasswd,
        u'##LOGINUSER %s' % comloginuser,
        ]
        
    if Params.olderfirst:
        newpagetext = u'%s\n\n%s' % (pagetext, u'\n'.join(newcomment))
    else:
        newpagetext = u'%s\n\n%s' % (u'\n'.join(newcomment), pagetext)

    if not pg.exists():
        action = 'SAVENEW'
        defaultacl = Globs.defaultacl
        warnmessages = '\'\'\'\'\'DO NOT EDIT THIS PAGE!!\'\'\' This page is automatically generated by Page``Comment2 macro.\'\'\n----'
        newpagetext = u'%s\n%s\n%s' % (defaultacl, warnmessages, newpagetext)
    else:
        action = 'SAVE'

    newpagetext = pg.normalizeText( newpagetext )
    
    comment = 'New comment by "%s"' % comauthor
    pg._write_file(newpagetext, action, u'Modified by PageComment macro')
    addLogEntry(request, 'COMNEW', Globs.pagename, comment)
    
    # message(_('The comment is added'))
    message(_('Thank you for your changes. Your attention to detail is appreciated.'))
    return 1

def showcomment():
    
    request = Globs.macro.request
    _ = request.getText
    
    commentlist = fetchcomments()
    
    html = []
    cur_index = 0
    cellstyle = u'border-width: 0px; border-top-width: 1px; vertical-align: top; font-size: 0.9em; line-height: 1em;'
    
    html.append(u'<div id="commentlist"><table width="100%" class="commentlist">')
    
    for item in commentlist:
        if Globs.admin or (item['loginuser'] and request.user.valid and request.user.name == item['name']):
            htmlcommentdel = [
                u' <font style="font-size: 0.9em;">',
                u'<a style="color: #aa0000;" href="javascript: requesttodeleteadmin(\'%s\');" title="%s">X</a>' % (item['key'], _('Delete')),
                u'</font>',
                ]
        elif item['loginuser']:
            htmlcommentdel = []

        else:
            htmlcommentdel = [
                u' <font style="font-size: 0.9em;">',
                u'<a style="color: #aa0000;" href="javascript: requesttodelete(\'%s\');" title="%s">X</a>' % (item['key'], _('Delete')),
                u'</font>',
                ]
        
        htmlcomment = [
            u'<tr><td class="commenticon" style="%s width: 20px;">%s</td>' % (cellstyle, getsmiley(item['icon'])),
            u'<td class="commentauthor" style="%s"' % cellstyle,
            u'>%s</td>' % converttext(item['name']),
            u'<td style="%s width: 10px;">&nbsp;</td>' % cellstyle,
            u'<td class="commenttext" style="%s">%s</td>' % (cellstyle, converttext(item['text'])),
            u'<td class="commentdate" style="%s text-align: right; font-size: 0.8em; ">%s%s</td></tr>' % (cellstyle, item['date'].replace(' ', '<br>'), u''.join(htmlcommentdel)),
            ]
        
        html.append(u'\n'.join(htmlcomment))
    
    html.append(u'</table></div>')
    
    return u'\n'.join(html)


def getsmiley(markup):
    
    if markup in config.smileys.keys():
        formatter = Globs.macro.formatter
        return formatter.smiley(markup)
    else:
        return ''


def converttext(targettext):
    # Converts some special characters of html to plain-text style
    # What else to handle?

    # targettext = targettext.strip()
    targettext = targettext.replace(u'&', '&amp')
    targettext = targettext.replace(u'>', '&gt;')
    targettext = targettext.replace(u'<', '&lt;')
    targettext = targettext.replace(u'\n', '<br>')
    targettext = targettext.replace(u'"', '&quot;')
    targettext = targettext.replace(u'\t', '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;')
    targettext = targettext.replace(u'  ', '&nbsp;&nbsp;')

    return targettext
    
def convertdelimiter(targettext):
    # Converts delimeter to other string to avoid a crash
    
    targettext = targettext.replace('{{{', '{ { {')
    targettext = targettext.replace('}}}', '} } }')
    
    return targettext

    
def deleteform():
    # Javascript codes for deleting or restoring a comment
    
    request = Globs.macro.request
    _ = request.getText
    
    htmlresult = []
    
    html = [
        '<script language="javascript">',
        '<!--',
        ]
    htmlresult.append(u'\n'.join(html))
           
    html = [    
        '  function requesttodeleteadmin(comkey) {',
        '      if (confirm("%s")) {;' % _('Really delete this page?'),
        '          document.delform.delkey.value = comkey;',
        '          document.delform.delpasswd.value = "****";',
        '          document.delform.submit();',
        '      }',
        '  }',
        '  function requesttodelete(comkey) {',
        '      var passwd = prompt("%s:", "");' % _('Please specify a password!'),
        '      if(!(passwd == "" || passwd == null)) {',
        '          document.delform.delkey.value = comkey;',
        '          document.delform.delpasswd.value = passwd;',
        '          document.delform.submit();',
        '      }',
        '  }',
        ]
    
    htmlresult.append(u'\n'.join(html))
                
    html = [
        '//-->',
        '</script>',
        '<form name="delform" action="%s#pagecomment" METHOD="post">' % Globs.subname,
        '<input type=hidden value="show" name="action">',
        '<input name="delpasswd" type="hidden" value="****">',
        '<input name="delkey" type="hidden" value="">',
        '<input type="hidden" name="commentaction" value="delcomment">',
        '</form>',
        ]
    htmlresult.append(u'\n'.join(html))

    return u'\n'.join(htmlresult)


def filtercomment(index='', name='', passwd=''):
    
    # filter by index
    if index:
        filteredlist1 = fetchcomments(index, index)
    else:
        filteredlist1 = fetchcomments()
    
    # filter by name
    filteredlist2 = []
    if name:
        for item in filteredlist1:
            if name == item['name']:
                filteredlist2.append(item)
    else:
        filteredlist2 = filteredlist1
    
    # filter by password
    filteredlist3 = []
    if passwd:
        for item in filteredlist2:
            if passwd == item['passwd']:
                filteredlist3.append(item)
    else:
        filteredlist3 = filteredlist2

    return filteredlist3
        

def fetchcomments(startindex=1, endindex=9999):
    
    commentlist = []
    
    request = Globs.macro.request
    formatter = Globs.macro.formatter
    datapagename = Globs.datapagename

    pg = Page( request, datapagename )
    pagetext = pg.get_raw_body()
    
    regex = r'^(#acl\s*.*)$'
    pattern = re.compile(regex, re.UNICODE + re.MULTILINE + re.IGNORECASE)
    pagetext = pattern.sub('', pagetext)
    
    regex = ur"""
^[\{]{3}\n
^(?P<icon>[^\n]*)\n
^(?P<name>[^\n]*)\n
^(?P<date>[^\n]*)[\n]+
^(?P<text>\s*.*?[^}]+)[\}]{3}\n
^[#]{2}PASSWORD[ ](?P<passwd>[^\n]*)\n
^[#]{2}LOGINUSER[ ](?P<loginuser>[^\n]*)[\n$]+"""

    pattern = re.compile(regex, re.UNICODE + re.MULTILINE + re.VERBOSE)
    commentitems = pattern.findall(pagetext)
    
    cur_index = 0
    for item in commentitems:
        comment = {}
        cur_index += 1
        
        if cur_index < startindex:
            continue
        
        comment['index'] = cur_index
        comment['icon'] = item[0]
        comment['name'] = item[1]
        comment['text'] = item[3]
        comment['date'] = item[2]
        comment['passwd'] = item[4]
        comment['loginuser'] = item[5]
        
        # experimental
        comment['key'] = comment['date'].strip()
        
        commentlist.append(comment)
        
        if cur_index >= endindex:
            break

    return commentlist

def deletecomment(macro, delkey, delpasswd):
    # Deletes a comment with given index and password
    
    request = Globs.macro.request
    formatter = Globs.macro.formatter
    datapagename = Globs.datapagename
    _ = request.getText

    pg = PageEditor( request, datapagename )
    pagetext = pg.get_raw_body()
    
    regex = ur"""
(?P<comblock>^[\{]{3}\n
^(?P<icon>[^\n]*)\n
^(?P<name>[^\n]*)\n
^(?P<date>[^\n]*)[\n]+
^(?P<text>\s*.*?[^}]+)[\}]{3}\n
^[#]{2}PASSWORD[ ](?P<passwd>[^\n]*)\n
^[#]{2}LOGINUSER[ ](?P<loginuser>[^\n]*)[\n$]+)"""

    pattern = re.compile(regex, re.UNICODE + re.MULTILINE + re.VERBOSE)
    commentitems = pattern.findall(pagetext)
    
    for item in commentitems:
        
        if delkey == item[3].strip():
            comauthor = item[2]
            if Globs.admin or (request.user.valid and request.user.name == comauthor) or delpasswd == item[5]:
                newpagetext = pagetext.replace(item[0], '', 1)
                
                action = 'SAVE'
                comment = 'comment deleted by "%s"' % comauthor
                pg._write_file(newpagetext, action, u'Modified by PageComment macro')
                addLogEntry(request, 'COMDEL', Globs.pagename, comment)
                
                message(_('The comment is deleted'))
                
                return
            else:
                message(_('Sorry, wrong password.'))
                return
                
    message(_('No such comment'))

    
def addLogEntry(request, action, pagename, msg):
    # Add an entry to the edit log on adding comments.
    from MoinMoin.logfile import editlog
    t = wikiutil.timestamp2version(time.time())
    msg = unicode(msg)

    # TODO: for now we simply write 2 logs, maybe better use some multilog stuff
    # Write to global log
    log = editlog.EditLog(request)
    log.add(request, t, 99999999, action, pagename, request.remote_addr, msg)

    # Write to local log
    log = editlog.EditLog(request, rootpagename=pagename)
    log.add(request, t, 99999999, action, pagename, request.remote_addr, msg)