Re: comments enhancements

Steven Armstrong <[email protected]>
Newsgroups gmane.comp.web.pyblosxom.devel
Message-ID <[email protected]>
I also made a few local changes.

The attached patch includes Will's patch from: 
http://sourceforge.net/mailarchive/message.php?msg_id=10245717

It adds the following:

1. drop the url if it's not absolute. this prevents links like:
http://host.domain.tld/weblog/www.someone.tld

2. Secret number image thingy. Depends on my plugin 'nospam', which 
itself depends on 'session'. They can be found here: 
http://www.c-area.ch/code/pyblosxom/plugins/

3. Remember what the user entered, so in case of an error (anti-spam 
check failed), the form can be populated with the users entries.
The following variables are made available in the comment-form template: 
$cmt_title, $cmt_author, $cmt_email, $cmt_url, $cmt_body, $cmt_nospam_error

I use them like this:
<input name="author" id="author" type="text" value="$cmt_author" />
<input name="email" id="email" type="text" value="$cmt_email" />
...
<div style="color:red;">$cmt_nospam_error</div>

cheers
Steven

note: I changed Wills patch no.3 back to what it was before cause it 
fux0r'd me. Both versions are in the patch, just comment/uncomment the 
one you like.
comments.py.diff (text/plain, 12.5 KB)
Index: comments.py
===================================================================
RCS file: /cvsroot/pyblosxom/pyblosxom/contrib/plugins/comments/plugins/comments.py,v
retrieving revision 1.39
diff -u -r1.39 comments.py
--- comments.py	20 Nov 2004 08:14:18 -0000	1.39
+++ comments.py	9 Dec 2004 01:49:28 -0000
@@ -11,6 +11,9 @@
                   this defaults to datadir + "comments".
     comment_ext - the file extension used to denote a comment file.
                   this defaults to "cmt".
+    comment_draft_ext - the file extension used for new comments that have
+                        not been manually approved by you.  this defaults
+                        to comment_ext (i.e. there is no draft stage)
 
     comment_smtp_server - the smtp server to send comments notifications
                           through.
@@ -18,6 +21,9 @@
                         If you omit this, the from address will be the
                         e-mail address as input in the comment form
     comment_smtp_to - the person to send comment notifications to.
+    comment_rejected_words - the list of words that will cause automatic
+                             rejection of the comment--this is a very
+                             poor man's spam reducer.
 
 
 Comments are stored 1 per file in a parallel hierarchy to the datadir
@@ -36,15 +42,23 @@
 
 Also, for any entry that you don't want to have comments, just add
 "nocomments" to the properties of the entry.
+
+Revisions:
+	1.4 (20041209) - Steven Armstrong - integrated Will Guaraldi's patch: http://sourceforge.net/mailarchive/message.php?msg_id=10245717
+    1.3 (20041205) - Steven Armstrong - added remember/forget calls
+    1.2 (20041201) - Steven Armstrong - hacked so that comments are only shown if the QS contains ?cmt=1
+    1.1 (20041116) - Steven Armstrong - integration with nospam plugin, human verification system
+    1.0 ? original writing.
 """
+__author__ = "?"
+__version__ = "1.4 (20041209)"
+__url__ = "?"
+__description__ = "Comments Plugin"
+
 import cgi, glob, os.path, re, time, cPickle, os
 from xml.sax.saxutils import escape
 from Pyblosxom import tools
 from Pyblosxom.entries.base import EntryBase
-try:
-    from email.Utils import formatdate
-except ImportError:
-    from rfc822 import formatdate
 
 
 def cb_start(args):
@@ -59,6 +73,8 @@
         config['comment_dir'] = os.path.join(config['datadir'],'comments')
     if not config.has_key('comment_ext'):
         config['comment_ext'] = 'cmt'
+    if not config.has_key('comment_draft_ext'):
+        config['comment_draft_ext'] = config['comment_ext']
     
 def verify_installation(request):
     config = request.getConfiguration()
@@ -81,7 +97,7 @@
                 print("Missing comment SMTP property: '%s'" % i)
                 retval = 0
     
-    optional_keys = ['comment_dir', 'comment_ext']
+    optional_keys = ['comment_dir', 'comment_ext', 'comment_draft_ext']
     for i in optional_keys:
         if not config.has_key(i):
             print("missing optional property: '%s'" % i)
@@ -131,7 +147,7 @@
     
     @returns: a string containing the regular expression for comment entries
     """
-    cmtDir = os.path.join(config['comment_dir'],entry['absolute_path'])
+    cmtDir = os.path.join(config['comment_dir'], entry['absolute_path'])
     cmtExpr = os.path.join(cmtDir,entry['fn']+'-*.'+config['comment_ext'])
     return cmtExpr
 
@@ -170,12 +186,17 @@
         cmt['cmt_time'] = cmt['cmt_pubDate'] # timestamp as float for comment anchor
         cmt['cmt_pubDate'] = time.ctime(float(cmt['cmt_pubDate']))
         story.close()
+        # added new variable cmt_author_link.
+        if cmt['cmt_link'] != '':
+            cmt['cmt_author_link'] = '<a href="%s">%s</a>' % (cmt['cmt_link'], cmt['cmt_author'])
+        else:
+            cmt['cmt_author_link'] = cmt['cmt_author']
     except:
         tools.log("Couldn't read: ", filename)
         story.close()
     return cmt
 
-def writeComment(config, data, comment):
+def writeComment(request, config, data, comment):
     """
     Write a comment
     
@@ -187,32 +208,39 @@
     
     @param comment: dict containing comment info
     @type  comment: dict
+
+    @return: The success or failure of creating the comment.
+    @rtype: string
     """
     entry = data['entry_list'][0]
-    cdir = os.path.join(config['comment_dir'],entry['absolute_path'])
+    cdir = os.path.join(config['comment_dir'], entry['absolute_path'])
     cdir = os.path.normpath(cdir)
     if not os.path.isdir(cdir):
         os.makedirs(cdir)
-    cfn = os.path.join(cdir,entry['fn']+"-"+comment['pubDate']+"."+config['comment_ext'])
-     
+    cfn = os.path.join(cdir,entry['fn']+"-"+comment['pubDate']+"."+config['comment_draft_ext'])
+
+    argdict = { "request": request, "comment": comment }
+    reject = tools.run_callback("comment_reject",
+                                argdict,
+                                donefunc=lambda x:x)
+    if reject == 1:
+        return "Comment rejected."
+      
     # write comment
     cfile = None
     try :
         cfile = open(cfn, "w")
     except:
         tools.log("Couldn't open comment file %s for writing" % cfn)
-        return
+        return "Error: Couldn't open comment file for writing."
     else:
         pass
     
     def makeXMLField(name, field):
         return "<"+name+">"+cgi.escape(field[name])+"</"+name+">\n";
     try:
-        try:
-            comment[description].decode(utf-8)
-            cfile.write('<?xml version=1.0 encoding=utf-8?>\n')
-        except:
-            cfile.write('<?xml version="1.0" encoding="iso-8859-1"?>\n')
+        encoding = config.get('blog_encoding', 'iso-8859-1')
+        cfile.write('<?xml version="1.0" encoding="%s"?>\n' % encoding)
         cfile.write("<item>\n")
         cfile.write(makeXMLField('title',comment))
         cfile.write(makeXMLField('author',comment))
@@ -226,14 +254,14 @@
         tools.log("Error writing comment data for ", cfn)
         cfile.close()
         
-    #write latest pickle
+    # write latest pickle
     latest = None
     latestFilename = os.path.join(config['comment_dir'],'LATEST.cmt')
     try:
         latest = open(latestFilename,"w")
     except:
         tools.log("Couldn't open latest comment pickle for writing")
-        return
+        return "Error: Couldn't open latest comment pickle for writing."
     else:
         modTime = float(comment['pubDate'])
 
@@ -244,23 +272,31 @@
         # should log or e-mail
         if latest:
             latest.close()
-        return
+        return "Error: Problem dumping the pickle."
     
     # if the right config keys are set, notify by e-mail
-    if config.has_key('comment_smtp_server') and \
-       config.has_key('comment_smtp_to'):
+    if config.has_key('comment_smtp_server') and config.has_key('comment_smtp_to'):
+
+        # import the formatdate function which is in a different
+        # place in Python 2.3 and up.
+        try:
+            from email.Utils import formatdate
+        except ImportError:
+            from rfc822 import formatdate
+
         import smtplib
         author = escape_SMTP_commands(clean_author(comment['author']))
         description = escape_SMTP_commands(comment['description'])
+        message = [] # moved this up here cause otherwise the except: block has no access to it
+        
         if comment.has_key('email'):
             email = comment['email']
         else:
-            email = config['comment_smtp_from']
+            email = config.get('comment_smtp_from', "blah-C/[email protected]")
         try:
             server = smtplib.SMTP(config['comment_smtp_server'])
             curl = config['base_url']+'/'+entry['file_path']
 
-            message = []
             message.append("From: %s" % email)
             message.append("To: %s" % config["comment_smtp_to"])
             message.append("Date: %s" % formatdate(modTime))
@@ -273,7 +309,12 @@
             server.quit()
         except:
             tools.log("Error sending mail: %s" % message)
-            pass
+            return "Error: Problem sending notification email."
+
+    msg = "Success: Comment has been registered."
+    if config["comment_draft_ext"] != config["comment_ext"]:
+        msg = msg + "  Comment will not appear until it has been manually approved by the owner of this web-site."
+    return msg
 
 def clean_author(s):
     """
@@ -383,7 +424,42 @@
 
     return body        
         
-        
+
+_cmt_messages = {}
+def _rememberComment(form, url):
+    _cmt_messages["cmt_title"] = form['title'].value
+    _cmt_messages["cmt_author"] = form['author'].value
+    _cmt_messages["cmt_email"] = (form.has_key('email') and [form['email'].value] or [''])[0]
+    _cmt_messages["cmt_url"] = url
+    _cmt_messages["cmt_body"] = form['body'].value
+
+def _forgetComment():
+    for k, v in _cmt_messages.items():
+        _cmt_messages[k] = ""
+
+
+def _nospam_check(request, url):
+    session = request.getSession()
+    form = request.getHttp()['form']
+
+    try:
+        nospam = int(form["nospam"].value)
+        sess_nospam = int(session["nospam"])
+    except:
+        nospam = 0
+        sess_nospam = 1
+
+    if nospam != sess_nospam:
+        # remember what the user entered so we can populate the form with it, see cb_story_end
+        _cmt_messages["cmt_nospam_error"] = "Secret number did not match."
+        _rememberComment(form, url)
+        return False
+    else:
+        # forget what the user entered, see cb_story_end
+        _forgetComment()
+        return True
+
+
 def cb_prepare(args):
     """
     Handle comment related HTTP POST's.
@@ -398,23 +474,30 @@
     
     if form.has_key("title") and form.has_key("author") and form.has_key("body"):
 
-        body = form['body'].value
-        
-        body = sanitize(body)
-
         # Check if the form has a URL
         url = (form.has_key('url') and [form['url'].value] or [''])[0]
-        
-        cdict = {'title': form['title'].value, \
-                 'author' : form['author'].value, \
-                 'pubDate' : str(time.time()), \
-                 'link' : url, \
-                 'source' : '', \
-                 'description' : body }
-        if form.has_key('email'):
-            cdict['email'] = form['email'].value
-        
-        writeComment(config, data, cdict)
+        # Drop the url if it's not absolute.
+        # One could add the http:// to it instead.
+        if not "://" in url:
+            url = ""
+
+        # anti-spam check
+        if _nospam_check(request, url):
+            # only save the comment it the nospam check returns True
+
+            body = form['body'].value
+            body = sanitize(body)
+    
+            cdict = {'title': form['title'].value, \
+                     'author' : form['author'].value, \
+                     'pubDate' : str(time.time()), \
+                     'link' : url, \
+                     'source' : '', \
+                     'description' : body }
+            if form.has_key('email'):
+                cdict['email'] = form['email'].value
+            
+            data["comment_message"] = writeComment(request, config, data, cdict)
 
 
 def cb_head(args):
@@ -439,12 +522,20 @@
     entry = args['entry']
     template = args['template']
     request = args["request"]
+    form = request.getHttp()['form']
     config = request.getConfiguration()
+
     if len(renderer.getContent()) == 1 \
             and renderer.flavour.has_key('comment-story') \
+            and form.has_key("cmt") \
             and not entry.has_key("nocomments"):
         template = renderer.flavour.get('comment-story','')
+        # make this additive so that it doesn't toally fux0r me.
+        #args['template'] = args['template'] + template
+        # sar: changed this back again so that it doesn't fux0r me. Pick your poison.
         args['template'] = template
+    else:
+        _forgetComment()
 
     entry['num_comments'] = getCommentCount(entry, config)
     return template
@@ -454,9 +545,18 @@
     entry = args['entry']
     template = args['template']
     request = args["request"]
+    form = request.getHttp()['form']
+
+    # populate entry dict with the data remembered from the comment form,
+    # resp. remove the data we remembered before
+    if len(_cmt_messages) > 0:
+        for k,v in _cmt_messages.items():
+            entry[k] = v
+
     config = request.getConfiguration()
     if len(renderer.getContent()) == 1 \
             and renderer.flavour.has_key('comment-story') \
+            and form.has_key("cmt") \
             and not entry.has_key("nocomments"):
         output = []
         entry['comments'] = readComments(entry, config)
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.