I thought I would follow up my own post explaining how I come up with the solution to get the comment workflow to work as I wanted, so at least if other run into the same problem and come looking on the list they won't find an unanswered question something I personally find very frustrating :) It is a bit of a detailed post so apologies to peoples inboxes.
The problems I had and things that I wanted to do with commenting in COREBlog2 where: 1. Creating an easy to use workflow to allow my bloggers to process comments that are submitted to their blog entries. Requirements where to allow a simple "Publish" or "Reject" from the state drop down and that this process be as simple as possible, Ideally blogger would have a specific role and not have to be made a manager in order to make the interface as simple as possible. 2. Email notifications for comments where easy to understand and worked in mozilla-thunderbird as by default they where unreadable in thunderbird due to every character being spaced apart. 3. Spam via comments and trackbacks should be reduced to an absolute minimum as to not turn off our bloggers from the process. Having them being barraged by spam and spending all their time time rejecting these would really reduce the appeal of a blog. 4. Commentors would be notified by email when their comment was approved. This was something requested by our bloggers as a courteous thing to do. 1. SETTING UP A SMOOTH WORKFLOW FOR COMMENT APPROVAL I created an editor role that had the following permissions to my editor role in the root plone site permissions tab: ATContentTypes: Add Document ATContentTypes: Add Event ATContentTypes: Add File ATContentTypes: Add Image ATContentTypes: Add Link ATContentTypes: Add News Item Access contents information Add portal content List folder contents Modify portal content Request review View I initially had real problems understanding what was happening in the permissions as they didn't seem to work, but the problems was due to me falsely assuming that all the folder objects and the articles would by default just be aquiring their permissions from the root folder but that was not the case. The article folder was not aquiring from the root in case of the "modify portal content" permission nor where the articles themselves set to aquire for that permission. You need to look at the workflow that applies to objects as that determines what permissions they are created with or at least the settings of those permissions that are managed by the workflow. There where 4 permissions managed by the cb_comments_workflow, which was just a copy of the plone_default_workflow I had made: Access contents information Change portal events Modify portal content View Each state has its own specific security settings which you can control by clicking on the "Permissions" tab for that state. For example when an object is in the "Pending" state the Reviewer role is given all the 4 permissions while when and object is in "Published" state only the Manager role has permission to edit the object. For the cb_comment_workflow I just needed 3 states: Pending (set this to the default state) Published Rejected ( a new state I created) and 3 transitions: Publish Reject Submit I deleted all other states and transitions. My bloggers are added to an Editors group which has been given the editor role. All bloggers are also placed in the Reviewers Group. REJECTING COMMENTS After thinking initially that I would have the reject transition just delete the rejected comments I thought it might be better to have a system they are kept available for historical purposes so switching them to a state where they are not visible to my bloggers or anonymous users would be a better option. One way I have thought that I could do it was to have reject be set to make the state of the comment private and private to be only viewable by the manager. This might have the added benefit of keeping the comments for historical puposes. The manger could easily go in and chose the delete option to clean out all obvious spam ones now and then but to a person who doesn't have the manager role they would effectively deleted. I changed the state that the reject transition changes to from visible to private. That works the comment disappears from the review list as it is not in the pending state but it doesn't appear to anonymous users as the private state has the view permission only allowed in Manager and Owner. The manager will see the comments as still showing in recent comments portlet but others won't. To delete the comments the manager just needs to go to http://simpson.wilderness.org.au:8081/test/blog/comments/folder_contents to see the full list of comments. The ones that have been set to private can be reviewed and cleaned out if they are spam but it might be that ones that are rejected from legitimate people who have made inappropriate comments could be kept, perhaps moved to another folder. However when I tested trying to move the comments to another folder I found that I wasn't allowed, I guess because they are strongly associated with their parent entry. What I found though was that if I rejected a comment as a user with the "Editor" role they would be shown a login page as they didn't have permission to see private comments and the default behaviour is to show the comment in its new state. I thought that the easiest way to deal with this would simply be to add a script to the reject transition that redirects the user to the blog root so they can just continue on with their review process. This turned out to be a long exercise in frustration though as even though I eventually figured out how to get a redirect to occur using the ObjectMoved method which the state_change object has it would still only work if you had permission to view objects in the private state. This is the way you can easily do redirects on a page that you have permissions: return context.REQUEST.RESPONSE.redirect(context.portal_url.getPortalPath() + '/blog') and you see it being used in various products. However this wouldn't do anything when called from a script running off a transition even when running the script logged in as manager. The following script would perform a redirect when called from a transition but only if you had rights to view the new object: ------------------------------ obj = state_change.object obj.plone_log('Beginning redirect_to script') url = obj.portal_url.getPortalObject() obj.plone_log(url) rootobj = context.portal_url.getPortalObject() id = obj.id obj.plone_log(id) newobj = state_change.new_state newid = newobj.id newobj.plone_log(newid) conid = context.id context.plone_log(conid) raise state_change.ObjectMoved(newobj, url) ------------------------------------ I also tried altering the comment view template to perform a redirect but once again the permission are such that the redirect is not called because permissions block the page before it loads the redirect. CREATING A NEW STATE AND MODIFYING COREBLOG2 Ok I had given up on the idea of making use of the private state and decided to see what happened if I created a state. I added a rejected state to the cb_commments workflow and made it the state that the reject transition moves an object to. Changing it to this state removes it from the Review list but they still display in the comment listing and the recent comment entries portlet. I then had to look at changing the COREBLog2 code to not display comments in the rejected state. There is a portlet_global_recent_comment.pt file which is not displayed in the portlets but offered a way to restrict the search of comments that are displayed. I copied this file to portlet_tws_recent_comment.pt and then added "review_state='published'" to the portal_catalog.searchResuls keywords. I also changed the style and the title to match the default Recent Comments. This recent comments portlet will now only display comments that have a state of published. Just need to swap that with the other recent comment portlet in the left slots of the properties tab for the blog. The next step was to try and stop the comments from displaying in the entry listing. It looked like the object method to do that came from the COREBlog2/content/coreblogentry.py file and was based around a catalog search again. CUSTOMISING COREBLOG2 TO ONLY SHOW PUBLISHED COMMENTS I made the following changes to 2 files to show only published comments in the list of comments displayed with an entry. I added the following to the COREBlog2/config.py file: coreblogcomment_state = 'published I then made the following changes to the COREBlog2/content/coreblogentry.py file: changed: from Products.COREBlog2.config import PROJECTNAME,\ comemnt_rel_id,trackback_rel_id,\ coreblogentry_meta_type,coreblogcomment_meta_type,\ coreblogtrackback_meta_type to from Products.COREBlog2.config import PROJECTNAME,\ comemnt_rel_id,trackback_rel_id,\ coreblogentry_meta_type,coreblogcomment_meta_type,\ coreblogtrackback_meta_type,coreblogcomment_state and changed this line in the getComment method: contentFilter = {'path':path,'portal_type':coreblogcomment_meta_type} to contentFilter = {'path':path,'portal_type':coreblogcomment_meta_type,'review_state':coreblogcomment_state} the contentFilter variable is passed into a call to the searchResults which is called via the queryCatalog in this script. Ok that caused only published comments to be shown in the comments listing for an entry but the count was still displaying all comments. To fix that I just had to change the contentFilter in the countComment method in the coreblogentry.py file to be the same as the one I had in the getComment method. I had to restart Zope after these changes to get them to load. RESTRICTING ACCESS TO COMMENTS IN THE REJECTED STATE Ok the remaining problem I have now with the comments is that I need to restrict access to the comments folder because an anonymous user can still go to .....t/blog/comments folder and see all comments except those that are private or pending. What I need to do is make it so that anonymous users don't have the right to see comments that are in an rejected state. This will also stop them being able to access these comments by search. Setting the permissions that you want on a particular state is quite easy. To do it go to the workflow that is managing the content type that you are interesting in and then click on the states tab, select the state that you want to change permissions for and then click on the Permissions tab. By default it appears that when you create a new state it is set to aquire all the permissions. I set the permissions for the rejected state so that they wheren't aquired and the manager had rights for all permissions and the Editor role had "View" and "Access Content Information" permissions. The other roles had no permissions for objects in the rejected state. You need to click on the "Update Security Settings" button in the portal_workflow page to apply any changes to permissions you make. Ok that has fixed that if anonymous goes to the comments folder now they will only see comments that are in the published state. Ok that appears to be it as far as workflowing comments goes. Bloggers now have a nice simple workflow and the anonymous user only sees content if they are published. 2. FIXING CONFIRMATION EMAIL ON COMMENT SUBMISSION The email notification that coreblog sends to people when someone submitts a comment or trackback has a number of problems: 1. There is no subject 2. The from address is not the one specified in the blog settings but the same as the to address. 3. The format of the email in thunderbird is unreadable with large spaces between every character. COREBlog doesn't use the normal mailhost that you see used in other scripts. The author has written his own method "send_mail" in order to bypass Zopes security and allow anonymous users the ability to send email by submitting a comment. What I ended up doing was changing the send_mail() method in the COREBlogTool.py. I changed the send_mail() from: def send_mail(self, body,to_addr,from_addr,subject): # # Method to bypass Zope's security. # For case of comment/trackback post by anonymous user # try: self.MailHost.send(body,to_addr, from_addr, subject) to: def send_mail(self, msg): # # Method to bypass Zope's security. # For case of comment/trackback post by anonymous user # try: self.MailHost.send(msg) I then worked on trying to get the message created properly. Ok after some stuffing around to get date field right for mozilla thunderbird people monitoring the blog now get an email that provides the comment and the comment details with a subject in the format of "[blog comment] comment title". I couldn't figure out exactly what was causing the wierd formatting in mozilla but thought it could possibly be the translate functions as they where used in the script. Here is a diff from fcomp of the original cbaddComment.cpy and my changes: ------------------------------------------------- FILE A: cbaddComment.cpy-original FILE B: cbaddComment.cpy TOTALS: 43 inserted 7 deleted 67 matched ******************** INSERT [A 15, B 15]: from DateTime import DateTime ******************** REPLACE [A 40-41, B 41-46]: from_addr = context.getNotify_to() msgbody = context.translate('comment_notify_body') ******************** WITH: from_addr = context.getNotify_from() Date = DateTime() commondate = DateTime.aCommon(Date) context.plone_log(commondate) #Get elements from the form setting them to empty strings if they don't exist ******************** REPLACE [A 50-52, B 55-66]: msgbody = msgbody % (elements) msgsubject = context.translate('comment_notify_title') mgsheader = """To: %s ******************** WITH: #Set variables to be inserted in message title = elements['title'] author = elements['author'] url = elements['url'] ip = elements['post_ip'] entryurl = elements['entry_url'] msgbody = elements['body'] msgsubject = '[blog comment] ' + title message = """ To: %s ******************** INSERT [A 56, B 70-71]: Date: %s Subject: %s ******************** REPLACE [A 57-58, B 73-94]: """ % (to_addr,from_addr) cbtool.send_mail(mgsheader+msgbody, to_addr, from_addr, msgsubject) ******************** WITH: Comment Title: %s Author: %s Url: %s Client IP: %s Entry URL: %s %s """ msg = message % ( to_addr, from_addr, commondate, msgsubject, title, author, url, ip, entryurl, msgbody ) cbtool.send_mail(msg) ------------------------------------------ 3. INSTALL ANTI-COMMENT SPAM TOOL CAPTCHA'S - First I registered an account at http://captchas.net/registration/ - They emailed me a key to use with the username I had created. - I then installed the plonecatpcha product using the latest version which you can get from http://sourceforge.net/project/showfiles.php?group_id=177298, be aware that it unpacks just the files and not the folder so you need to create the PloneCaptcha folder in your products directory and then unpack into that. - This latest version allows you to add the username and password you got from captchas.net via the properties tab in the ZMI. To do that go the root of your plone site and then click on plone_captcha. It will default to displaying the properties so just enter your username and password there. - - I had added the following to my "cbcomment_form.pt" file above the <div class="formControls"> line: <div class="field" tal:define="error errors/captcha| nothing;" tal:attributes="class python:test(error, 'field error', 'field')"> <label i18n:translate="label_captcha_help">Verification Code</label> <div class="formHelp" i18n:translate="help_plone_captcha"> This helps us prevent automated spamming. </div> <div tal:content="error">Validation error output</div> <div metal:use-macro="here/captcha/macros/edit" /> </div> as suggested in this post http://postaria.com/pipermail/coreblog-en/2006-June/000428.html - I had also added the validate_captcha to via the ZMI to the portal_skins/COREBlog2/cbcomment_preview.pt file and cbentry_view file. To save having to do this for every plone site that used the COREBlog2 product I added "validate_captcha" on to the list of validators in the metadata files for these 2 forms cbcomment_preview.cpt.metadata and cbentry_view.cpt.metadata. ** Note the .cpt stands for controller page template which gives you an associated metadata file as it lets you set up validators and actions for your page template. validators=validateComment,validate_captcha Trackback spam just poured in as soon as the blog got linked to an google. I was uncertain how useful it was myself and thought that it could just be too confusing for our bloggers so I disabled it. Setting the permissions so that anonymous users couldn't create trackback entries didn't appear to stop it. In the blog settings -> Edit Entries tab I set trackbacks to "None/Hidden" and then applied the suggested patch at http://coreblog.org/trac/coreblog2/ticket/50 now when I create a new entry trackbacks are not shown. 4. SETUP EMAIL NOTIFICATIONS FOR COMMENTERS WHEN THEIR COMMENT IS APPROVED It turned out to be quite a bit of stuffing around to get this to work as i got caught up on a couple of things that wheren't documented in any of the documentation I saw about writing scripts to send emails. This was the fact that not all methods return strings that are in a format that will be accepted by mailhost.send. With these you need to transform them using the encode() method. If you get the following error when passing a msg to mailhost.send: Error Value No message recipients designated then look for some of your variables not being encoded correctly for email and you need to use the encode method prior to passing them to mailhost.send. If you add .encode() for email = obj.email.encode() for example you can then get it accepted by mailhost.send. This is what my comment_approved script that is called after the "publish" transition: --------------------------------------------- #This script lets people know that their comment has been published from DateTime import DateTime obj = state_change.object mhost = context.MailHost from_addr = '[EMAIL PROTECTED]' #Get the email to send to make sure you encode it otherwise mailhost choke on message to_addr = obj.email.encode() #Set the date for the date header need to use commondate format for thunderbirds sake. date = DateTime() commondate = DateTime.aCommon(date) #Get the author's name name = obj.author.encode() #Get the url of the blog entry parent = obj.getParentEntry() parenturl = parent.absolute_url() #Get the title of the blog entry parentTitle = parent.Title() # the message format, %s will be filled in from data message = """ From: %s To: %s Date: %s Subject: comment on nuclear dump blog published Hi %s Your comment on my blog entry "%s" has been published see %s#comments Thank you for your input """ msg = message % ( from_addr, to_addr, commondate, name, parentTitle, parenturl ) #Log message for referece purposes context.plone_log(msg) mhost.send(msg) -------------------------------------- Ok thats was it the comment process in COREBlog2 is now nice and simple for my bloggers. cheers John -- John Habermann Internet Programmer, System Administrator The Wilderness Society Inc http://www.wilderness.org.au _______________________________________________ COREblog-en mailing list [email protected] http://postaria.com/mailman/listinfo/coreblog-en Unsubscription writing to [EMAIL PROTECTED]
