Index: src/main/java/info/magnolia/module/commenting/frontend/action/PageComments.java =================================================================== --- src/main/java/info/magnolia/module/commenting/frontend/action/PageComments.java (revision 37701) +++ src/main/java/info/magnolia/module/commenting/frontend/action/PageComments.java (working copy) @@ -15,9 +15,11 @@ package info.magnolia.module.commenting.frontend.action; import info.magnolia.cms.core.Content; +import info.magnolia.cms.core.SystemProperty; import info.magnolia.cms.core.search.Query; import info.magnolia.cms.core.search.QueryManager; import info.magnolia.cms.security.SecurityUtil; +import info.magnolia.cms.util.NodeDataUtil; import info.magnolia.context.MgnlContext; import info.magnolia.module.commenting.CommentingModule; import info.magnolia.module.forum.DefaultForumManager; @@ -28,22 +30,32 @@ import info.magnolia.module.forum.frontend.action.ThreadView; import info.magnolia.module.templating.RenderableDefinition; import info.magnolia.module.templating.RenderingModel; +import net.sf.akismet.Akismet; +import nl.vpro.magnolia.module.vproforum.setup.VproForumModule; import org.apache.commons.lang.StringUtils; import javax.jcr.RepositoryException; +import javax.servlet.http.HttpServletRequest; import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; /** - * @author had - * @version $Revision: $ ($Author: $) + * VPRO's version of info.magnolia.module.commenting.frontend.action.PageComments. + * Look for 'vpro' to see what we changed to store extra request info in the forum node. */ +@SuppressWarnings({"JavaDoc", "UnusedDeclaration"}) public class PageComments extends ThreadView { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(PageComments.class); + private static final String HEADER_X_FORWARDED_FOR = "X-FORWARDED-FOR"; + private boolean enableSpamFiltering = true; + public PageComments(Content content, RenderableDefinition renderable, RenderingModel parent, ForumManager forumManager, ForumConfiguration config) { super(content, renderable, parent, forumManager, config); + if (SystemProperty.hasProperty("enable.spam.filtering")) { + enableSpamFiltering = SystemProperty.getBooleanProperty("enable.spam.filtering"); + } } public PageComments(Content content, RenderableDefinition renderable, RenderingModel parent) { @@ -86,68 +98,148 @@ threadId = null; } - if (threadId == null && messageText == null) { - // empty and never commented on yet - setMessages(new PagedResult(new ArrayList(), 0)); - threadId = "null"; - return "success"; + /* + this happens when: + - the form was committed on a page with no comments yet. + - a page is rendered with no comments yet. + the latter may occur quite often, so it's not bad to step out quickly, as both form processing and + comments page generation is not necessary. + but it would be nice to make a distinction between the two cases, because the latter does not need special + treatment. + You can distinguish between the two cases by checking for the request param 'command'. + */ + if (noFormToProcessAndNoCommentsToShow(messageText)) { + return exit(); } - final String userId = MgnlContext.getUser().getName(); + if (!StringUtils.isBlank(messageText) && !isSpam(messageText)) { + Content message = createNewMessageContent(messageTitle, messageText, inReplyTo); + if (message != null) { + setValidated(message); + storeExtraFormfieldsAsProperties(message); + } + } + } catch (RepositoryException e) { + throw new RuntimeException(e); + } + return super.execute(); + } - Content message = null; - if (threadId == null) { - setupForumIdFromContent(); - if (forumId == null) { - String forumName = (String) getDefinition().getParameters().get("forumName"); + private String exit() { + setMessages(new PagedResult(new ArrayList(), 0)); + threadId = "null"; + return "success"; + } - if (forumName == null) { - throw new RuntimeException("Define /modules/yourModule/paragraphs/" + getDefinition().getName() + "/parameters/forumName to specify default forum for comments"); - } - forumId = forumManager.getForumId(forumName); - } - if (forumId == null) { - throw new IllegalStateException("No forum id given"); - } - // page might not have title specified ... use page name as a title in that case - String title = StringUtils.defaultIfEmpty(getRoot().getContent().getTitle(), getRoot().getContent().getName()); - thread = forumManager.createThread(forumId, title, messageTitle, messageText, userId, SecurityUtil.isAnonymous()); - threadId = thread.getUUID(); - thread.createNodeData("reference", getRoot().getContent().getUUID()); - thread.save(); + private boolean noFormToProcessAndNoCommentsToShow(String messageText) { + return threadId == null && messageText == null; + } - message = thread.getNodeData("firstMessage").getReferencedContent(); - - } else if (messageText != null) { - message = forumManager.replyToThread(threadId, inReplyTo, messageTitle, messageText, userId, SecurityUtil.isAnonymous()); + @SuppressWarnings({"unchecked"}) + private void storeExtraFormfieldsAsProperties(Content message) throws RepositoryException { + // store extra properties defined in page comment form. + Content props = message.getContent("properties"); + Enumeration names = MgnlContext.getWebContext().getRequest().getParameterNames(); + while (names.hasMoreElements()) { + String paramName = names.nextElement(); + if ("threadId".equals(paramName) || "messageTitle".equals(paramName) || "messageText".equals(paramName) || "inReplyTo".equals(paramName) || "targetPage".equals(paramName)) { + // skip params already used/saved elsewhere + continue; } + String val = param(paramName); + if (val != null) { + props.setNodeData(paramName, val); + } + } + props.save(); + } - if (message != null) { - // store extra properties defined in page comment form. - Content props = message.getChildByName("properties"); - Enumeration names = MgnlContext.getWebContext().getRequest().getParameterNames(); - while (names.hasMoreElements()) { - String paramName = names.nextElement(); - if ("threadId".equals(paramName) || "messageTitle".equals(paramName) || "messageText".equals(paramName) || "inReplyTo".equals(paramName) || "targetPage".equals(paramName)) { - // skip params already used/saved elsewhere - continue; - } - String val = param(paramName); - if (val != null) { - props.createNodeData(paramName, val); - } - } - props.save(); + private void setValidated(Content message) throws RepositoryException { + // added by vpro: was checked for spam, so set validated as true + NodeDataUtil.getOrCreateAndSet(message, "validated", true); + message.save(); + } + + private Content createNewMessageContent(String messageTitle, String messageText, String inReplyTo) throws RepositoryException { + Content message; + final String userId = MgnlContext.getUser().getName(); + if (threadId == null) { + resolveForumId(); + + // page might not have title specified ... use page name as a title in that case + String newForumThreadTitle = StringUtils.defaultIfEmpty(getRoot().getContent().getTitle(), getRoot().getContent().getName()); + thread = forumManager.createThread(forumId, newForumThreadTitle, messageTitle, messageText, userId, SecurityUtil.isAnonymous()); + threadId = thread.getUUID(); + thread.setNodeData("reference", getRoot().getContent().getUUID()); + thread.save(); + + message = thread.getNodeData("firstMessage").getReferencedContent(); + + } else { + message = forumManager.replyToThread(threadId, inReplyTo, messageTitle, messageText, userId, SecurityUtil.isAnonymous()); + } + return message; + } + + private void resolveForumId() throws RepositoryException, IllegalStateException { + setupForumIdFromContent(); + if (forumId == null) { + resolveForumFromParagraphDefenition(); + } + if (forumId == null) { + throw new IllegalStateException("No forum id given"); + } + } + + private void resolveForumFromParagraphDefenition() throws RepositoryException { + String forumName = (String) getDefinition().getParameters().get("forumName"); + + if (forumName == null) { + throw new RuntimeException("Define /modules/yourModule/paragraphs/" + getDefinition().getName() + "/parameters/forumName to specify default forum for comments"); + } + forumId = forumManager.getForumId(forumName); + } + + // [added by vpro] + + /** + * Get the clients ipaddress from the request. + * + * @param request + * @return + */ + private String getClientIp(HttpServletRequest request) { + String clientIp = request.getRemoteAddr(); + + // look for ipaddress in X-Forwarded-For header + String xForwardedFor = request.getHeader(HEADER_X_FORWARDED_FOR); + if (StringUtils.isNotEmpty(xForwardedFor)) { + String[] split = StringUtils.split(xForwardedFor, ","); + if (split.length >= 1) { + clientIp = split[0]; } - } catch (RepositoryException e) { - //log.error("RepositoryException: "+e.getMessage(),e); - throw new RuntimeException(e); // TODO - //return "error"; } - return super.execute(); + + return clientIp; } + // [added by vpro] + /** + * Gets the servername from an url. E.g. "http://blabla.com/abc -> http://blabla.com" + * + * @param requestUrl + * @return + */ + private String getServerName(String requestUrl) { + int thirdSlashPosition = requestUrl.indexOf("/", "https://".length()); + if (thirdSlashPosition == -1) { + thirdSlashPosition = requestUrl.length(); + } + return StringUtils.substring(requestUrl, 0, thirdSlashPosition); + } + + /** * Checks if current user is allowed to post on the current thread. */ public boolean isAllowedToPost() { @@ -159,4 +251,41 @@ } } + /** + * Check the contents of a string for spam. Added by vpro. + * + * @param message + * @return + */ + private boolean isSpam(String message) { + if (enableSpamFiltering) { + // get information from request, use defaults if not known + String requestUrl = MgnlContext.getWebContext().getRequest().getRequestURL().toString(); + String commentHostUrl = getServerName(requestUrl); + if (StringUtils.isBlank(commentHostUrl)) { + commentHostUrl = VproForumModule.getInstance().getAkismetDefaultServername(); + } + String remoteAddr = getClientIp(MgnlContext.getWebContext().getRequest()); + if (StringUtils.isBlank(remoteAddr)) { + remoteAddr = VproForumModule.getInstance().getAkismetDefaultIpaddress(); + } + + // verify Akismet key + Akismet akismet = new Akismet(VproForumModule.getInstance().getAkismetKey(), + commentHostUrl); + boolean verifyAPIKey = akismet.verifyAPIKey(); + log.debug("Verify akismet key: " + verifyAPIKey); + + // ask Akismet if this is spam + boolean isSpam = akismet.commentCheck(remoteAddr, VproForumModule.getInstance().getAkismetUserAgent(), + null, null, null, null, null, null, message, null); + log.debug("Is this (" + message + ") spam: " + isSpam); + + return isSpam; + } else { + log.warn("Spam filtering disabled!! Development only"); + return false; + } + } + }