From c21e64b37a85e65e273c85286a4342948a24db89 Mon Sep 17 00:00:00 2001 From: Alan Davis Date: Wed, 12 Feb 2014 01:29:42 +0000 Subject: [PATCH] Merged HEAD-BUG-FIX (4.3/Cloud) to HEAD (4.3/Cloud) 59349: Merged V4.2-BUG-FIX (4.2.1) to HEAD-BUG-FIX (Cloud/4.3) 59348: Merged V4.1-BUG-FIX (4.1.8) to V4.2-BUG-FIX (4.2.1) 59346: MNT-9771: Merged V4.1.7 (4.1.7.1) to V4.1-BUG-FIX (4.1.8) 59313: MNT-10231 : CLONE: Version History shows incorrect creator for previous versions - Java-backed webscript CommentsPost added. Also auditable behavior is disabled around creating comment-node 59326: MNT-10231 : CLONE: Version History shows incorrect creator for previous versions - Posting activity item added to CommentsPost web-script. Also copyright message and comments added, @since changed to 4.1.7.1 59334: MNT-10231: Merged DEV to V4.1.7 (4.1.7.1) 56938: MNT-9771 : Version History shows incorrect creator for previous versions - Added a test to reproduce this issue git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@62132 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../repository/comments/comments.post.json.js | 69 ---- .../web-scripts-application-context.xml | 12 +- .../web/scripts/comments/CommentsPost.java | 371 ++++++++++++++++++ .../web/scripts/comment/CommentsApiTest.java | 32 +- 4 files changed, 413 insertions(+), 71 deletions(-) delete mode 100644 config/alfresco/templates/webscripts/org/alfresco/repository/comments/comments.post.json.js create mode 100644 source/java/org/alfresco/repo/web/scripts/comments/CommentsPost.java diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/comments/comments.post.json.js b/config/alfresco/templates/webscripts/org/alfresco/repository/comments/comments.post.json.js deleted file mode 100644 index 01d0f31978..0000000000 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/comments/comments.post.json.js +++ /dev/null @@ -1,69 +0,0 @@ - - - - -/** - * Creates a post inside the passed forum node. - */ -function addComment(node) -{ - // fetch the data required to create a comment - var title = ""; - if (json.has("title")) - { - title = json.get("title"); - } - var content = json.get("content"); - - // fetch the parent to add the node to - var commentsFolder = getOrCreateCommentsFolder(node); - - // get a unique name - var name = getUniqueChildName(commentsFolder, "comment"); - - // create the comment - var commentNode = commentsFolder.createNode(name, "fm:post"); - commentNode.mimetype = "text/html"; - commentNode.properties.title = title; - commentNode.content = content; - commentNode.save(); - - return commentNode; -} - -function main() -{ - // get requested node - var node = getRequestNode(); - if (status.getCode() != status.STATUS_OK) - { - return; - } - - var comment = addComment(node); - model.item = getCommentData(comment); - model.node = node; - - // post an activity item, but only if we've got a site - if (json.has("site") && json.has("itemTitle") && json.has("page")) - { - var siteId = json.get("site"); - if ((siteId != null) && (siteId != "")) - { - var params = jsonUtils.toObject(json.get("pageParams")), strParams = ""; - for (param in params) - { - strParams += param + "=" + encodeURIComponent(params[param]) + "&"; - } - var data = - { - title: json.get("itemTitle"), - page: json.get("page") + (strParams != "" ? "?" + strParams.substring(0, strParams.length - 1) : ""), - nodeRef: node.getNodeRef() - } - activities.postActivity("org.alfresco.comments.comment-created", siteId, "comments", jsonUtils.toJSONString(data)); - } - } -} - -main(); diff --git a/config/alfresco/web-scripts-application-context.xml b/config/alfresco/web-scripts-application-context.xml index 0e53790a9b..4d688d7ef9 100644 --- a/config/alfresco/web-scripts-application-context.xml +++ b/config/alfresco/web-scripts-application-context.xml @@ -1880,7 +1880,17 @@ - + + + + + + + + + diff --git a/source/java/org/alfresco/repo/web/scripts/comments/CommentsPost.java b/source/java/org/alfresco/repo/web/scripts/comments/CommentsPost.java new file mode 100644 index 0000000000..142e27182a --- /dev/null +++ b/source/java/org/alfresco/repo/web/scripts/comments/CommentsPost.java @@ -0,0 +1,371 @@ +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.web.scripts.comments; + +import java.io.IOException; +import java.io.Serializable; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.model.ForumModel; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.policy.BehaviourFilter; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.site.SiteModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.activities.ActivityService; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.security.AccessStatus; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.json.JSONStringer; +import org.json.JSONWriter; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; +import org.springframework.extensions.webscripts.Cache; +import org.springframework.extensions.webscripts.DeclarativeWebScript; +import org.springframework.extensions.webscripts.Status; +import org.springframework.extensions.webscripts.WebScriptException; +import org.springframework.extensions.webscripts.WebScriptRequest; + +/** + * This class is the controller for the comments.post web script. + * + * @author Sergey Scherbovich (based on existing JavaScript webscript controller) + * @since 4.1.7.1 + */ + +public class CommentsPost extends DeclarativeWebScript { + private final static String COMMENTS_TOPIC_NAME = "Comments"; + + private static Log logger = LogFactory.getLog(CommentsPost.class); + + private final static String JSON_KEY_SITE = "site"; + private final static String JSON_KEY_ITEM_TITLE = "itemTitle"; + private final static String JSON_KEY_PAGE = "page"; + private final static String JSON_KEY_TITLE = "title"; + private final static String JSON_KEY_PAGE_PARAMS = "pageParams"; + private final static String JSON_KEY_NODEREF = "nodeRef"; + private final static String JSON_KEY_CONTENT = "content"; + + private ServiceRegistry serviceRegistry; + private NodeService nodeService; + private ContentService contentService; + private PersonService personService; + private PermissionService permissionService; + private ActivityService activityService; + + private BehaviourFilter behaviourFilter; + + @Override + protected Map executeImpl(WebScriptRequest req, Status status, Cache cache) + { + // get requested node + NodeRef nodeRef = parseRequestForNodeRef(req); + // get json object from request + JSONObject json = parseJSON(req); + + /* MNT-10231, MNT-9771 fix */ + this.behaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE); + + try + { + // add a comment + NodeRef commentNodeRef = addComment(nodeRef, json); + + // generate response model for a comment node + Map model = generateModel(nodeRef, commentNodeRef); + + // post an activity item + postActivity(json, nodeRef); + + return model; + } + finally + { + this.behaviourFilter.enableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE); + } + } + + private void postActivity(JSONObject json, NodeRef nodeRef) + { + // post an activity item, but only if we've got a site + if(json.containsKey(JSON_KEY_SITE) && json.containsKey(JSON_KEY_ITEM_TITLE) && json.containsKey(JSON_KEY_PAGE)) + { + String siteId = getOrNull(json, JSON_KEY_SITE); + if (siteId != null && siteId != "") + { + try + { + org.json.JSONObject params = new org.json.JSONObject(getOrNull(json, JSON_KEY_PAGE_PARAMS)); + String strParams = ""; + + Iterator itr = params.keys(); + while (itr.hasNext()) + { + String strParam = itr.next().toString(); + strParams += strParam + "=" + params.getString(strParam) + "&"; + } + String page = getOrNull(json, JSON_KEY_PAGE) + "?" + (strParams != "" ? strParams.substring(0, strParams.length()-1) : ""); + String title = getOrNull(json, JSON_KEY_ITEM_TITLE); + String strNodeRef = nodeRef.toString(); + + JSONWriter jsonWriter = new JSONStringer().object(); + + jsonWriter.key(JSON_KEY_TITLE).value(title); + jsonWriter.key(JSON_KEY_PAGE).value(page); + jsonWriter.key(JSON_KEY_NODEREF).value(strNodeRef); + + String jsonActivityData = jsonWriter.endObject().toString(); + + activityService.postActivity("org.alfresco.comments.comment-created", siteId, "comments", jsonActivityData); + } + catch(Exception e) + { + logger.warn("Error adding comment to activities feed", e); + } + } + } + + } + + private NodeRef addComment(NodeRef nodeRef, JSONObject json) + { + // fetch the parent to add the node to + NodeRef commentsFolder = getOrCreateCommentsFolder(nodeRef); + + // get a unique name + String name = getUniqueChildName("comment"); + + // create the comment + NodeRef commentNodeRef = nodeService.createNode(commentsFolder, + ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, QName.createValidLocalName(name)), + ForumModel.TYPE_POST).getChildRef(); + + // fetch the title required to create a comment + String title = getOrNull(json, JSON_KEY_TITLE); + HashMap props = new HashMap(1, 1.0f); + props.put(ContentModel.PROP_TITLE, title != null ? title : ""); + nodeService.addProperties(commentNodeRef, props); + + ContentWriter writer = contentService.getWriter(commentNodeRef, ContentModel.PROP_CONTENT, true); + // fetch the content of a comment + String contentString = getOrNull(json, JSON_KEY_CONTENT); + + writer.setMimetype(MimetypeMap.MIMETYPE_HTML); + writer.putContent(contentString); + + return commentNodeRef; + } + + private Map generateItemValue(NodeRef commentNodeRef) + { + Map result = new HashMap(4, 1.0f); + + String creator = (String)this.nodeService.getProperty(commentNodeRef, ContentModel.PROP_CREATOR); + + Serializable created = this.nodeService.getProperty(commentNodeRef, ContentModel.PROP_CREATED); + Serializable modified = this.nodeService.getProperty(commentNodeRef, ContentModel.PROP_MODIFIED); + + boolean isUpdated = false; + if (created instanceof Date && modified instanceof Date) + { + isUpdated = ((Date)modified).getTime() - ((Date)created).getTime() > 5000; + } + + Serializable owner = this.nodeService.getProperty(commentNodeRef, ContentModel.PROP_OWNER); + String currentUser = this.serviceRegistry.getAuthenticationService().getCurrentUserName(); + + boolean isSiteManager = this.permissionService.hasPermission(commentNodeRef, SiteModel.SITE_MANAGER) == (AccessStatus.ALLOWED); + boolean isCoordinator = this.permissionService.hasPermission(commentNodeRef, PermissionService.COORDINATOR) == (AccessStatus.ALLOWED); + boolean canEditComment = isSiteManager || isCoordinator || currentUser.equals(creator) || currentUser.equals(owner); + + result.put("node", commentNodeRef); + result.put("author", this.personService.getPerson(creator)); + result.put("isUpdated", isUpdated); + result.put("canEditComment", canEditComment); + + return result; + } + + private Map generateModel(NodeRef nodeRef, NodeRef commentNodeRef) + { + Map model = new HashMap(2, 1.0f); + + model.put("node", nodeRef); + model.put("item", generateItemValue(commentNodeRef)); + + return model; + } + + private String getOrNull(JSONObject json, String key) + { + if (json.containsKey(key)) + { + return (String)json.get(key); + } + return null; + } + + private JSONObject parseJSON(WebScriptRequest req) + { + JSONObject json = null; + String contentType = req.getContentType(); + if (contentType != null && contentType.indexOf(';') != -1) + { + contentType = contentType.substring(0, contentType.indexOf(';')); + } + if (MimetypeMap.MIMETYPE_JSON.equals(contentType)) + { + JSONParser parser = new JSONParser(); + try + { + json = (JSONObject)parser.parse(req.getContent().getContent()); + } + catch (IOException io) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Invalid JSON: " + io.getMessage()); + } + catch(ParseException pe) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Invalid JSON: " + pe.getMessage()); + } + } + return json; + } + + private NodeRef getOrCreateCommentsFolder(NodeRef nodeRef) + { + NodeRef commentsFolder = getCommentsFolder(nodeRef); + // create a comment folder if it doesn't exist + if (commentsFolder == null) + { + commentsFolder = createCommentsFolder(nodeRef); + } + return commentsFolder; + } + + private NodeRef getCommentsFolder(NodeRef nodeRef) + { + if (nodeService.hasAspect(nodeRef, ForumModel.ASPECT_DISCUSSABLE)) + { + List assocs = nodeService.getChildAssocs(nodeRef, ForumModel.ASSOC_DISCUSSION, RegexQNamePattern.MATCH_ALL); + ChildAssociationRef firstAssoc = assocs.get(0); + + return nodeService.getChildByName(firstAssoc.getChildRef(), ContentModel.ASSOC_CONTAINS, COMMENTS_TOPIC_NAME); + } + else + { + return null; + } + } + + private NodeRef parseRequestForNodeRef(WebScriptRequest req) + { + Map templateVars = req.getServiceMatch().getTemplateVars(); + String storeType = templateVars.get("store_type"); + String storeId = templateVars.get("store_id"); + String nodeId = templateVars.get("id"); + + // create the NodeRef and ensure it is valid + StoreRef storeRef = new StoreRef(storeType, storeId); + return new NodeRef(storeRef, nodeId); + } + + private String getUniqueChildName(String prefix) + { + return prefix + "-" + System.currentTimeMillis(); + } + + private NodeRef createCommentsFolder(final NodeRef nodeRef) + { + NodeRef commentsFolder = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + { + public NodeRef doWork() throws Exception + { + NodeRef commentsFolder = null; + + // ALF-5240: turn off auditing round the discussion node creation to prevent + // the source document from being modified by the first user leaving a comment + behaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE); + + try + { + nodeService.addAspect(nodeRef, QName.createQName(NamespaceService.FORUMS_MODEL_1_0_URI, "discussable"), null); + nodeService.addAspect(nodeRef, QName.createQName(NamespaceService.FORUMS_MODEL_1_0_URI, "commentsRollup"), null); + List assocs = nodeService.getChildAssocs(nodeRef, QName.createQName(NamespaceService.FORUMS_MODEL_1_0_URI, "discussion"), RegexQNamePattern.MATCH_ALL); + if (assocs.size() != 0) + { + NodeRef forumFolder = assocs.get(0).getChildRef(); + + Map props = new HashMap(1, 1.0f); + props.put(ContentModel.PROP_NAME, COMMENTS_TOPIC_NAME); + commentsFolder = nodeService.createNode( + forumFolder, + ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, COMMENTS_TOPIC_NAME), + QName.createQName(NamespaceService.FORUMS_MODEL_1_0_URI, "topic"), + props).getChildRef(); + } + } + finally + { + behaviourFilter.enableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE); + } + + return commentsFolder; + } + + }, AuthenticationUtil.getSystemUserName()); + + return commentsFolder; + } + + public void setServiceRegistry(ServiceRegistry serviceRegistry) { + this.serviceRegistry = serviceRegistry; + this.nodeService = serviceRegistry.getNodeService(); + this.contentService = serviceRegistry.getContentService(); + this.personService = serviceRegistry.getPersonService(); + this.permissionService = serviceRegistry.getPermissionService(); + } + + public void setBehaviourFilter(BehaviourFilter behaviourFilter) { + this.behaviourFilter = behaviourFilter; + } + + public void setActivityService(ActivityService activityService) { + this.activityService = activityService; + } +} diff --git a/source/test-java/org/alfresco/repo/web/scripts/comment/CommentsApiTest.java b/source/test-java/org/alfresco/repo/web/scripts/comment/CommentsApiTest.java index 77e8214f36..1c4d5f2863 100644 --- a/source/test-java/org/alfresco/repo/web/scripts/comment/CommentsApiTest.java +++ b/source/test-java/org/alfresco/repo/web/scripts/comment/CommentsApiTest.java @@ -12,6 +12,7 @@ import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.permissions.PermissionReference; import org.alfresco.repo.security.permissions.PermissionServiceSPI; import org.alfresco.repo.security.permissions.impl.ModelDAO; +import org.alfresco.repo.security.authentication.MutableAuthenticationDao; import org.alfresco.repo.security.permissions.impl.SimplePermissionEntry; import org.alfresco.repo.web.scripts.BaseWebScriptTest; import org.alfresco.service.cmr.model.FileFolderService; @@ -48,6 +49,7 @@ public class CommentsApiTest extends BaseWebScriptTest private AuthenticationComponent authenticationComponent; protected PermissionServiceSPI permissionService; protected ModelDAO permissionModelDAO; + private MutableAuthenticationDao authenticationDAO; private NodeRef rootNodeRef; private NodeRef companyHomeNodeRef; @@ -57,6 +59,9 @@ public class CommentsApiTest extends BaseWebScriptTest private UserTransaction txn; + private String USER2 = "user2"; + + @Override protected void setUp() throws Exception { @@ -70,6 +75,7 @@ public class CommentsApiTest extends BaseWebScriptTest namespaceService = (NamespaceService)appContext.getBean("namespaceService"); versionService = (VersionService)appContext.getBean("versionService"); personService = (PersonService)getServer().getApplicationContext().getBean("PersonService"); + authenticationDAO = (MutableAuthenticationDao) appContext.getBean("authenticationDao"); authenticationService = (MutableAuthenticationService)getServer().getApplicationContext().getBean("AuthenticationService"); authenticationComponent = (AuthenticationComponent)getServer().getApplicationContext().getBean("authenticationComponent"); permissionService = (PermissionServiceSPI) getServer().getApplicationContext().getBean("permissionService"); @@ -98,6 +104,12 @@ public class CommentsApiTest extends BaseWebScriptTest nodeRef = fileFolderService.create(companyHomeNodeRef, "Commenty", ContentModel.TYPE_CONTENT).getNodeRef(); versionService.ensureVersioningEnabled(nodeRef, null); nodeService.setProperty(nodeRef, ContentModel.PROP_AUTO_VERSION_PROPS, true); + + if (authenticationDAO.userExists(USER2)) + { + authenticationService.deleteAuthentication(USER2); + } + authenticationService.createAuthentication(USER2, USER2.toCharArray()); createUser(USER_TEST); @@ -154,7 +166,7 @@ public class CommentsApiTest extends BaseWebScriptTest String version = versionService.getCurrentVersion(nodeRef).getVersionLabel(); return version; } - + public void testCommentDoesNotVersion() throws Exception { AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); @@ -165,6 +177,10 @@ public class CommentsApiTest extends BaseWebScriptTest assertEquals(versionBefore, versionAfter); } + /** + * MNT-9771 + * @throws Exception + */ public void testCommentPermissions() throws Exception { authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName()); @@ -217,4 +233,18 @@ public class CommentsApiTest extends BaseWebScriptTest { return permissionModelDAO.getPermissionReference(null, permission); } + + /** + * MNT-9771 + * @throws Exception + */ + public void testCommentDoesNotChangeModifier() throws Exception + { + permissionService.setPermission(nodeRef, USER2, PermissionService.ALL_PERMISSIONS, true); + String modifierBefore = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_MODIFIER); + addComment(nodeRef, USER2, 200); + String modifierAfter = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_MODIFIER); + assertEquals(modifierBefore, modifierAfter); + } + }