diff --git a/l10n.properties b/l10n.properties index 0cf8bc0491..5ee676302f 100644 --- a/l10n.properties +++ b/l10n.properties @@ -1,6 +1,8 @@ # Branch specific configuration file for localisation scripts -MESSAGE_SEARCH_PATH="src/main/resources/alfresco/messages/admin-console*.properties src/main/resources/alfresco/messages/custommodel-restapi-messages*.properties src/main/resources/alfresco/messages/rest-framework-messages*.properties src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/admin/admin-communitysummary.get*.properties src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/admin/consoles/admin-repoconsole.get*.properties src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/admin/consoles/admin-tenantconsole.get*.properties src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/admin/consoles/admin-workflowconsole.get*.properties src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/admin/support-tools/admin-nodebrowser.get*.properties src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/audit/entry*.properties src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/links/links-delete.post*.properties src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/links/links.post*.properties src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/links/links.put*.properties src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/person/user-csv-upload.post*.properties src/main/resources/alfresco/templates/webscripts/org/alfresco/slingshot/calendar/event.get*.properties src/main/resources/alfresco/templates/webscripts/org/alfresco/slingshot/calendar/event.post*.properties src/main/resources/alfresco/templates/webscripts/org/alfresco/slingshot/calendar/event.put*.properties" + +MESSAGE_SEARCH_PATH="src/main/resources/alfresco/messages/admin-console*.properties src/main/resources/alfresco/messages/custommodel-restapi-messages*.properties src/main/resources/alfresco/messages/rest-framework-messages*.properties src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/admin/admin-communitysummary.get*.properties src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/admin/consoles/admin-repoconsole.get*.properties src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/admin/consoles/admin-tenantconsole.get*.properties src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/admin/consoles/admin-workflowconsole.get*.properties src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/admin/support-tools/admin-nodebrowser.get*.properties src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/audit/entry*.properties src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete*.properties src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete*.properties src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/links/links-delete.post*.properties src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/links/links.post*.properties src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/links/links.put*.properties src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/person/user-csv-upload.post*.properties src/main/resources/alfresco/templates/webscripts/org/alfresco/slingshot/calendar/event.get*.properties src/main/resources/alfresco/templates/webscripts/org/alfresco/slingshot/calendar/event.post*.properties src/main/resources/alfresco/templates/webscripts/org/alfresco/slingshot/calendar/event.put*.properties" + EXCLUDED_FILES="src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/audit/control.properties" diff --git a/pom.xml b/pom.xml index 25c2c26fce..95d458a382 100644 --- a/pom.xml +++ b/pom.xml @@ -37,7 +37,7 @@ ${project.build.directory}/alf_data convert - 7.125 + 7.127 8.50.1 7.21 diff --git a/src/main/java/org/alfresco/repo/web/scripts/blogs/AbstractBlogWebScript.java b/src/main/java/org/alfresco/repo/web/scripts/blogs/AbstractBlogWebScript.java new file mode 100644 index 0000000000..fa0e0d157a --- /dev/null +++ b/src/main/java/org/alfresco/repo/web/scripts/blogs/AbstractBlogWebScript.java @@ -0,0 +1,311 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.web.scripts.blogs; + +import java.io.IOException; +import java.util.Map; + +import org.alfresco.repo.activities.post.lookup.PostLookup; +import org.alfresco.repo.blog.BlogServiceImpl; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.model.Repository; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.activities.ActivityService; +import org.alfresco.service.cmr.blog.BlogPostInfo; +import org.alfresco.service.cmr.blog.BlogService; +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.site.SiteInfo; +import org.alfresco.service.cmr.site.SiteService; +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; + +/** + * @author Neil Mc Erlean + * @since 4.0 + */ +public abstract class AbstractBlogWebScript extends DeclarativeWebScript +{ + // Various common parameter strings in the blog webscripts. + protected static final String CONTAINER = "container"; + protected static final String CONTENT = "content"; + protected static final String DATA = "data"; + protected static final String DRAFT = "draft"; + protected static final String EXTERNAL_BLOG_CONFIG = "externalBlogConfig"; + protected static final String POST = "post"; + protected static final String ITEM = "item"; + protected static final String NODE = "node"; + protected static final String PAGE = "page"; + protected static final String SITE = "site"; + protected static final String TAGS = "tags"; + protected static final String TITLE = "title"; + + private static Log logger = LogFactory.getLog(AbstractBlogWebScript.class); + + // Injected services + protected Repository repository; + protected BlogService blogService; + protected NodeService nodeService; + protected SiteService siteService; + protected ActivityService activityService; + + //TODO Remove this after full refactor + protected ServiceRegistry services; + + public void setServiceRegistry(ServiceRegistry services) + { + this.services = services; + } + + public void setRepository(Repository repository) + { + this.repository = repository; + } + + public void setBlogService(BlogService blogService) + { + this.blogService = blogService; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setSiteService(SiteService siteService) + { + this.siteService = siteService; + } + + public void setActivityService(ActivityService activityService) + { + this.activityService = activityService; + } + + /** + * Generates an activity entry for the discussion item + * + * @param event One of created, updated, deleted + * @param blog Either post or reply + * @param site site + * @param req request + * @param json json + * @param nodeRef NodeRef + */ + protected void addActivityEntry(String event, BlogPostInfo blog, + SiteInfo site, WebScriptRequest req, JSONObject json, NodeRef nodeRef) + { + // We can only add activities against a site + if (site == null) + { + logger.info("Unable to add activity entry for blog " + event + " as no site given"); + return; + } + + // What page is this for? + String page = req.getParameter("page"); + if (page == null && json != null) + { + if (json.containsKey("page")) + { + page = (String)json.get("page"); + } + } + if (page == null) + { + // Default + page = "blog-postview"; + } + if (page.indexOf('?') == -1) + { + page += "?postId=" + blog.getSystemName(); + } + + // Get the title + String title = blog.getTitle(); + + try + { + JSONWriter jsonWriter = new JSONStringer() + .object() + .key(TITLE).value(title) + .key(PAGE).value(page); + + if (nodeRef != null) + { + // ALF-10182: the nodeRef needs to be included in the activity + // post to ensure read permissions are respected. + jsonWriter.key(PostLookup.JSON_NODEREF).value(nodeRef.toString()); + } + + String data = jsonWriter.endObject().toString(); + + activityService.postActivity( + "org.alfresco.blog.post-" + event, + site.getShortName(), + "blog", data); + } + catch(Exception e) + { + // Warn, but carry on + logger.warn("Error adding blog post " + event + " to activities feed", e); + } + } + + @Override + protected Map executeImpl(WebScriptRequest req, Status status, Cache cache) + { + Map templateVars = req.getServiceMatch().getTemplateVars(); + if (templateVars == null) + { + String error = "No parameters supplied"; + throw new WebScriptException(Status.STATUS_BAD_REQUEST, error); + } + + + // Parse the JSON, if supplied + 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()); + } + } + + + // Did they request it by node reference or site? + NodeRef nodeRef = null; + SiteInfo site = null; + BlogPostInfo blog = null; + + if (templateVars.containsKey("site")) + { + // Site, and Optionally Blog Post + String siteName = templateVars.get("site"); + site = siteService.getSite(siteName); + if (site == null) + { + String error = "Could not find site: " + siteName; + throw new WebScriptException(Status.STATUS_NOT_FOUND, error); + } + + // Did they give a blog post name too? + if (templateVars.containsKey("path")) + { + String name = templateVars.get("path"); + blog = blogService.getBlogPost(siteName, name); + + if (blog == null) + { + String error = "Could not find blog '" + name + "' for site '" + + site.getShortName() + "'"; + throw new WebScriptException(Status.STATUS_NOT_FOUND, error); + } + nodeRef = blog.getNodeRef(); + } + else + { + // The NodeRef is the container (if it exists) + if (siteService.hasContainer(siteName, BlogServiceImpl.BLOG_COMPONENT)) + { + nodeRef = siteService.getContainer(siteName, BlogServiceImpl.BLOG_COMPONENT); + } + } + } + else if (templateVars.containsKey("store_type") && + templateVars.containsKey("store_id") && + templateVars.containsKey("id")) + { + // NodeRef, should be a Blog Post + StoreRef store = new StoreRef( + templateVars.get("store_type"), + templateVars.get("store_id")); + + nodeRef = new NodeRef(store, templateVars.get("id")); + if (! nodeService.exists(nodeRef)) + { + String error = "Could not find node: " + nodeRef; + throw new WebScriptException(Status.STATUS_NOT_FOUND, error); + } + + // Try to build the appropriate object for it + blog = blogService.getForNodeRef(nodeRef); + + // See if it's actually attached to a site + if (blog != null) + { + NodeRef container = blog.getContainerNodeRef(); + if (container != null) + { + NodeRef maybeSite = nodeService.getPrimaryParent(container).getParentRef(); + if (maybeSite != null) + { + // Try to make it a site, will return Null if it isn't one + site = siteService.getSite(maybeSite); + } + } + } + } + else + { + String error = "Unsupported template parameters found"; + throw new WebScriptException(Status.STATUS_BAD_REQUEST, error); + } + + // Have the real work done + return executeImpl(site, nodeRef, blog, req, json, status, cache); + } + + protected abstract Map executeImpl(SiteInfo site, + NodeRef nodeRef, BlogPostInfo blog, WebScriptRequest req, + JSONObject json, Status status, Cache cache); +} diff --git a/src/main/java/org/alfresco/repo/web/scripts/blogs/BlogLibJs.java b/src/main/java/org/alfresco/repo/web/scripts/blogs/BlogLibJs.java new file mode 100644 index 0000000000..4759c7fce2 --- /dev/null +++ b/src/main/java/org/alfresco/repo/web/scripts/blogs/BlogLibJs.java @@ -0,0 +1,71 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.web.scripts.blogs; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.model.BlogIntegrationModel; +import org.alfresco.service.namespace.QName; +import org.json.simple.JSONObject; + +/** + * This class is a port of a previous JavaScript library. + * + * @author Neil Mc Erlean (based on previous JavaScript) + * @since 4.0 + * @deprecated This class should not be extended or reused as it may be refactored. + */ +public class BlogLibJs +{ + /** + * Fetches the blog properties from the json object and adds them to an array + * using the correct property names as indexes. + */ + public static Map getBlogPropertiesArray(JSONObject json) + { + Map arr = new HashMap(); + + putJSONEntryInMap(json, arr, "blogType", BlogIntegrationModel.PROP_BLOG_IMPLEMENTATION); + putJSONEntryInMap(json, arr, "blogId", BlogIntegrationModel.PROP_ID); + putJSONEntryInMap(json, arr, "blogName", BlogIntegrationModel.PROP_NAME); + putJSONEntryInMap(json, arr, "blogDescription", BlogIntegrationModel.PROP_DESCRIPTION); + putJSONEntryInMap(json, arr, "blogUrl", BlogIntegrationModel.PROP_URL); + putJSONEntryInMap(json, arr, "username", BlogIntegrationModel.PROP_USER_NAME); + putJSONEntryInMap(json, arr, "password", BlogIntegrationModel.PROP_PASSWORD); + return arr; + } + + private static void putJSONEntryInMap(JSONObject json, + Map arr, String jsonKey, QName mapKey) + { + if (json.containsKey(jsonKey)) + { + arr.put(mapKey, (Serializable)json.get(jsonKey)); + } + } +} diff --git a/src/main/java/org/alfresco/repo/web/scripts/blogs/BlogPostLibJs.java b/src/main/java/org/alfresco/repo/web/scripts/blogs/BlogPostLibJs.java new file mode 100644 index 0000000000..7e818eeee4 --- /dev/null +++ b/src/main/java/org/alfresco/repo/web/scripts/blogs/BlogPostLibJs.java @@ -0,0 +1,132 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.web.scripts.blogs; + +import java.io.Serializable; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.model.BlogIntegrationModel; +import org.alfresco.model.ContentModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * This class is a port of a previous JavaScript library. + * + * @author Neil Mc Erlean (based on previous JavaScript) + * @since 4.0 + */ +public class BlogPostLibJs +{ + //FIXME It will be refactored when the other services are ported from JavaScript to Java. + + /** + * Checks whether a blog configuration is available + * This should at some point also check whether the configuration is enabled. + * + * @param node the node that should be checked. Will check all parents if + * the node itself doesn't contain a configuration. + * @return {boolean} whether a configuration could be found. + */ + public static boolean hasExternalBlogConfiguration(NodeRef node, ServiceRegistry services) + { + if (node == null) + { + return false; + } + else if (services.getNodeService().hasAspect(node, BlogIntegrationModel.ASPECT_BLOG_DETAILS)) + { + return true; + } + else + { + return hasExternalBlogConfiguration(services.getNodeService().getPrimaryParent(node).getParentRef(), services); + } + } + + public static Map getBlogPostData(NodeRef node, ServiceRegistry services) + { + Map data = new HashMap(); + data.put("node", node); + String creator = (String)services.getNodeService().getProperty(node, ContentModel.PROP_CREATOR); + //ALF-18527 + NodeRef person = services.getPersonService().getPersonOrNull(creator); + if (person != null) + { + data.put("author", person); + } + + data.put("commentCount", CommentsLibJs.getCommentsCount(node, services)); + + // is the post published + Serializable published = services.getNodeService().getProperty(node, ContentModel.PROP_PUBLISHED); + boolean isPublished = published != null; + if (isPublished) + { + data.put("releasedDate", published); + } + + // draft + data.put("isDraft", !isPublished); + + // set the isUpdated flag + Date updatedDate = (Date) services.getNodeService().getProperty(node, ContentModel.PROP_UPDATED); + boolean isUpdated = updatedDate != null; + data.put("isUpdated", isUpdated); + if (isUpdated) + { + data.put("updatedDate", updatedDate); + } + + // fetch standard created/modified dates + data.put("createdDate", services.getNodeService().getProperty(node, ContentModel.PROP_CREATED)); + data.put("modifiedDate", services.getNodeService().getProperty(node, ContentModel.PROP_MODIFIED)); + + // does the external post require an update? + Date lastUpdate = (Date) services.getNodeService().getProperty(node, BlogIntegrationModel.PROP_LAST_UPDATE); + if (isPublished && lastUpdate != null) + { + // we either use the release or updated date + Date modifiedDate = (Date) data.get("releasedDate"); + + if (isUpdated) + { + modifiedDate = (Date) data.get("updatedDate"); + } + data.put("outOfDate", modifiedDate.getTime() - lastUpdate.getTime() > 5000L); + } + else + { + data.put("outOfDate", false); + } + + data.put("tags", services.getTaggingService().getTags(node)); + + return data; + } +} diff --git a/src/main/java/org/alfresco/repo/web/scripts/blogs/CommentsLibJs.java b/src/main/java/org/alfresco/repo/web/scripts/blogs/CommentsLibJs.java new file mode 100644 index 0000000000..1da8eea028 --- /dev/null +++ b/src/main/java/org/alfresco/repo/web/scripts/blogs/CommentsLibJs.java @@ -0,0 +1,99 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.web.scripts.blogs; + +import java.util.ArrayList; +import java.util.List; + +import org.alfresco.model.ContentModel; +import org.alfresco.model.ForumModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; + +/** + * This class is a port of a previous JavaScript library used by the blog webscript containers. + * + * @author Neil Mc Erlean (based on existing JavaScript code) + * @since 4.0 + */ +class CommentsLibJs +{ + // TODO It will likely be refactored into the Blog REST API class framework. + + private static final String COMMENTS_TOPIC_NAME = "Comments"; + + public static int getCommentsCount(NodeRef node, ServiceRegistry services) + { + return getComments(node, services).size(); + } + + /** + * Returns all comment nodes for a given node. + * @return an array of comments. + */ + public static List getComments(NodeRef node, ServiceRegistry services) + { + List result = new ArrayList(); + + NodeRef commentsFolder = getCommentsFolder(node, services); + if (commentsFolder != null) + { + List children = services.getNodeService().getChildAssocs(commentsFolder, ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL); + if (!children.isEmpty()) + { + result = children; + } + } + + return result; + } + + /** + * Returns the folder that contains all the comments. + * + * We currently use the fm:discussable aspect where we + * add a "Comments" topic to it. + */ + public static NodeRef getCommentsFolder(NodeRef node, ServiceRegistry services) + { + //FIXME These methods are from the original JavaScript. Should use the (soon to arrive) CommentService. + NodeRef result = null; + if (services.getNodeService().hasAspect(node, ForumModel.ASPECT_DISCUSSABLE)) + { + List forumFolders = services.getNodeService().getChildAssocs(node, ForumModel.ASSOC_DISCUSSION, RegexQNamePattern.MATCH_ALL); + // The JavaScript was retrieving the first child under this child-assoc so we'll do the same. + NodeRef forumFolder = forumFolders.get(0).getChildRef(); + + List topicFolder = services.getNodeService().getChildAssocs(forumFolder, ContentModel.ASSOC_CONTAINS, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, COMMENTS_TOPIC_NAME)); + result = topicFolder.isEmpty() ? null : topicFolder.get(0).getChildRef(); + } + return result; + } +} diff --git a/src/main/java/org/alfresco/repo/web/scripts/blogs/blog/BlogGet.java b/src/main/java/org/alfresco/repo/web/scripts/blogs/blog/BlogGet.java new file mode 100644 index 0000000000..6cf3bd9732 --- /dev/null +++ b/src/main/java/org/alfresco/repo/web/scripts/blogs/blog/BlogGet.java @@ -0,0 +1,81 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.web.scripts.blogs.blog; + +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.repo.web.scripts.blogs.AbstractBlogWebScript; +import org.alfresco.service.cmr.blog.BlogPostInfo; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.site.SiteInfo; +import org.json.simple.JSONObject; +import org.springframework.extensions.webscripts.Cache; +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 blog.get web script. + * + * @author Neil Mc Erlean (based on existing JavaScript webscript controllers) + * @since 4.0 + */ +public class BlogGet extends AbstractBlogWebScript +{ + @Override + protected Map executeImpl(SiteInfo site, NodeRef containerNodeRef, + BlogPostInfo blog, WebScriptRequest req, JSONObject json, Status status, Cache cache) + { + if (blog != null) + { + // They appear to have supplied a blog post itself... + // Oh well, let's hope for the best! + } + + if (containerNodeRef == null && site != null) + { + // They want to know about a blog container on a + // site where nothing has lazy-created the container + // Give them info on the site for now, should be close enough! + containerNodeRef = site.getNodeRef(); + } + + if (containerNodeRef == null) + { + // Looks like they've asked for something that isn't there + throw new WebScriptException(Status.STATUS_NOT_FOUND, "Blog Not Found"); + } + + // Build the response + // (For now, we just supply the noderef, but when we have a + // proper blog details object we'll use that) + Map model = new HashMap(); + model.put(ITEM, containerNodeRef); + + return model; + } +} \ No newline at end of file diff --git a/src/main/java/org/alfresco/repo/web/scripts/blogs/blog/BlogPut.java b/src/main/java/org/alfresco/repo/web/scripts/blogs/blog/BlogPut.java new file mode 100644 index 0000000000..e3a01ebd2b --- /dev/null +++ b/src/main/java/org/alfresco/repo/web/scripts/blogs/blog/BlogPut.java @@ -0,0 +1,104 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.web.scripts.blogs.blog; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.model.BlogIntegrationModel; +import org.alfresco.repo.blog.BlogServiceImpl; +import org.alfresco.repo.web.scripts.blogs.AbstractBlogWebScript; +import org.alfresco.repo.web.scripts.blogs.BlogLibJs; +import org.alfresco.service.cmr.blog.BlogPostInfo; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.site.SiteInfo; +import org.alfresco.service.namespace.QName; +import org.json.simple.JSONObject; +import org.springframework.extensions.webscripts.Cache; +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 blog.put web script. + * + * TODO Push most of the logic from this into the BlogService + * + * @author Neil Mc Erlean (based on existing JavaScript webscript controllers) + * @since 4.0 + */ +public class BlogPut extends AbstractBlogWebScript +{ + @Override + protected Map executeImpl(SiteInfo site, NodeRef containerNodeRef, + BlogPostInfo blog, WebScriptRequest req, JSONObject json, Status status, Cache cache) + { + if (blog != null) + { + // They appear to have supplied a blog post itself... + // Oh well, let's hope for the best! + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Blog post should not be updated via this web script."); + } + + if (site != null && containerNodeRef == null) + { + // Force the lazy creation + // This is a bit icky, but it'll have to do for now... + containerNodeRef = siteService.createContainer( + site.getShortName(), BlogServiceImpl.BLOG_COMPONENT, null, null); + } + + // Do the work + updateBlog(containerNodeRef, json); + + // Record it as done + Map model = new HashMap(); + model.put("item", containerNodeRef); + + return model; + } + + /** + * Creates a post inside the passed forum node. + */ + @SuppressWarnings("deprecation") + private void updateBlog(NodeRef node, JSONObject json) + { + Map arr = BlogLibJs.getBlogPropertiesArray(json); + + if (nodeService.hasAspect(node, BlogIntegrationModel.ASPECT_BLOG_DETAILS)) + { + Map properties = nodeService.getProperties(node); + properties.putAll(arr); + nodeService.setProperties(node, properties); + } + else + { + nodeService.addAspect(node, BlogIntegrationModel.ASPECT_BLOG_DETAILS, arr); + } + } +} diff --git a/src/main/java/org/alfresco/repo/web/scripts/blogs/post/BlogPostDelete.java b/src/main/java/org/alfresco/repo/web/scripts/blogs/post/BlogPostDelete.java new file mode 100644 index 0000000000..2c492101db --- /dev/null +++ b/src/main/java/org/alfresco/repo/web/scripts/blogs/post/BlogPostDelete.java @@ -0,0 +1,82 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.web.scripts.blogs.post; + +import java.text.MessageFormat; +import java.util.HashMap; +import java.util.Map; +import java.util.ResourceBundle; + +import org.alfresco.repo.web.scripts.blogs.AbstractBlogWebScript; +import org.alfresco.service.cmr.blog.BlogPostInfo; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.site.SiteInfo; +import org.json.simple.JSONObject; +import org.springframework.extensions.webscripts.Cache; +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 blog-posts.get web script. + * + * @author Neil Mc Erlean (based on existing JavaScript webscript controllers) + * @since 4.0 + */ +public class BlogPostDelete extends AbstractBlogWebScript +{ + protected static final String MSG_BLOG_DELETED = "blog-post.msg.deleted"; + + @Override + protected Map executeImpl(SiteInfo site, NodeRef nodeRef, + BlogPostInfo blog, WebScriptRequest req, JSONObject json, Status status, Cache cache) + { + final ResourceBundle rb = getResources(); + + if (blog == null) + { + throw new WebScriptException(Status.STATUS_NOT_FOUND, "Blog Post Not Found"); + } + + // TODO Get this from the BlogPostInfo Object + final boolean isDraftBlogPost = blogService.isDraftBlogPost(blog.getNodeRef()); + + // Have it deleted + blogService.deleteBlogPost(blog); + + // If we're in a site, and it isn't a draft, add an activity + if (site != null && !isDraftBlogPost) + { + addActivityEntry("deleted", blog, site, req, json, nodeRef); + } + + // Report it as deleted + Map model = new HashMap(); + String message = rb.getString(MSG_BLOG_DELETED); + model.put("message",MessageFormat.format(message, blog.getNodeRef())); + return model; + } +} diff --git a/src/main/java/org/alfresco/repo/web/scripts/blogs/post/BlogPostGet.java b/src/main/java/org/alfresco/repo/web/scripts/blogs/post/BlogPostGet.java new file mode 100644 index 0000000000..1e3f0f8151 --- /dev/null +++ b/src/main/java/org/alfresco/repo/web/scripts/blogs/post/BlogPostGet.java @@ -0,0 +1,88 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.web.scripts.blogs.post; + +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.repo.web.scripts.blogs.AbstractBlogWebScript; +import org.alfresco.repo.web.scripts.blogs.BlogPostLibJs; +import org.alfresco.service.cmr.blog.BlogPostInfo; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.site.SiteInfo; +import org.json.simple.JSONObject; +import org.springframework.extensions.webscripts.Cache; +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 blog-posts.get web script. + * + * @author Neil Mc Erlean (based on existing JavaScript webscript controllers) + * @since 4.0 + */ +public class BlogPostGet extends AbstractBlogWebScript +{ + @Override + protected Map executeImpl(SiteInfo site, NodeRef nodeRef, + BlogPostInfo blog, WebScriptRequest req, JSONObject json, Status status, Cache cache) + { + if (blog == null) + { + throw new WebScriptException(Status.STATUS_NOT_FOUND, "Blog Post Not Found"); + } + + // Build the response + Map model = new HashMap(); + + // TODO Fetch this from the BlogPostInfo object + NodeRef node = blog.getNodeRef(); + Map item = BlogPostLibJs.getBlogPostData(node, services); + model.put(ITEM, item); + model.put(POST, blog); + + model.put("externalBlogConfig", BlogPostLibJs.hasExternalBlogConfiguration(node, services)); + + int contentLength = -1; + String arg = req.getParameter("contentLength"); + if (arg != null) + { + try + { + contentLength = Integer.parseInt(arg); + } + catch (NumberFormatException ignored) + { + // Intentionally empty + } + } + + model.put("contentLength", contentLength); + + return model; + } +} diff --git a/src/main/java/org/alfresco/repo/web/scripts/blogs/posts/AbstractGetBlogWebScript.java b/src/main/java/org/alfresco/repo/web/scripts/blogs/posts/AbstractGetBlogWebScript.java new file mode 100644 index 0000000000..2f9a8ec077 --- /dev/null +++ b/src/main/java/org/alfresco/repo/web/scripts/blogs/posts/AbstractGetBlogWebScript.java @@ -0,0 +1,229 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.web.scripts.blogs.posts; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; +import org.alfresco.repo.web.scripts.blogs.AbstractBlogWebScript; +import org.alfresco.repo.web.scripts.blogs.BlogPostLibJs; +import org.alfresco.service.cmr.blog.BlogPostInfo; +import org.alfresco.service.cmr.blog.BlogService.RangedDateProperty; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.site.SiteInfo; +import org.alfresco.util.Pair; +import org.alfresco.util.ScriptPagingDetails; +import org.alfresco.util.UrlUtil; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.json.simple.JSONObject; +import org.springframework.extensions.surf.util.URLDecoder; +import org.springframework.extensions.webscripts.Cache; +import org.springframework.extensions.webscripts.Status; +import org.springframework.extensions.webscripts.WebScriptRequest; + +/** + * @author Neil Mc Erlean (based on existing JavaScript webscript controllers) + * @since 4.0 + */ +public abstract class AbstractGetBlogWebScript extends AbstractBlogWebScript +{ + private static final Log log = LogFactory.getLog(AbstractGetBlogWebScript.class); + + @Override + protected Map executeImpl(SiteInfo site, NodeRef nonSiteContainer, + BlogPostInfo blog, WebScriptRequest req, JSONObject json, Status status, Cache cache) + { + Map model = new HashMap(); + + // process additional parameters. + PagingRequest pagingReq = parsePagingParams(req); + pagingReq.setRequestTotalCountMax(pagingReq.getSkipCount() + pagingReq.getRequestTotalCountMax()); + + // begin and end date. + // Legacy note: these dates are URL query parameters in int form. + Date fromDate = parseDateParam(req, "fromDate"); + Date toDate = parseDateParam(req, "toDate"); + + String tag = req.getParameter("tag"); + if (tag == null || tag.length() == 0) + { + tag = null; + } + else + { + // Tags can be full unicode strings, so decode + tag = URLDecoder.decode(tag); + } + + // One webscript (blog-posts-new.get) uses a 'numdays' parameter as a 'fromDate'. + // This is a hacky solution to this special case. FIXME + if (this.getClass().equals(BlogPostsNewGet.class)) + { + // Default is for 'now' minus seven days. + final long oneDayInMilliseconds = 24 * 60 * 60 * 1000; + final long sevenDaysInMilliseconds = 7 * oneDayInMilliseconds; + fromDate = new Date(System.currentTimeMillis() - sevenDaysInMilliseconds); + + // But if there is a numdays parameter then that changes the fromDate + String numDays = req.getServiceMatch().getTemplateVars().get("numdays"); + if (numDays != null) + { + Integer numDaysInt = Integer.parseInt(numDays); + fromDate = new Date(System.currentTimeMillis() - (numDaysInt * oneDayInMilliseconds)); + } + } + + // Fetch and assign the data + PagingResults blogPostList = + getBlogPostList(site, nonSiteContainer, fromDate, toDate, tag, pagingReq); + + // We need the container for various bits + NodeRef container = nonSiteContainer; + if(container == null) + { + // Container mustn't exist yet + // Fake it with the site for permissions checking reasons + container = site.getNodeRef(); + } + + if (log.isDebugEnabled()) + { + StringBuilder msg = new StringBuilder(); + msg.append("Retrieved ").append(blogPostList.getPage().size()).append(" blog posts in page."); + log.debug(msg.toString()); + } + + createFtlModel(req, model, container, pagingReq, blogPostList); + + return model; + } + + protected void createFtlModel(WebScriptRequest req, Map model, NodeRef node, PagingRequest pagingReq, + PagingResults blogPostList) + { + Map blogPostsData = new HashMap(); + + final Pair totalResultCount = blogPostList.getTotalResultCount(); + int total = blogPostList.getPage().size(); + if (totalResultCount != null && totalResultCount.getFirst() != null) + { + total = totalResultCount.getFirst(); + } + //FIXME What to do? null + blogPostsData.put("total", total); + blogPostsData.put("pageSize", pagingReq.getMaxItems()); + blogPostsData.put("startIndex", pagingReq.getSkipCount()); + blogPostsData.put("itemCount", blogPostList.getPage().size()); + + if (total == pagingReq.getRequestTotalCountMax()) + { + blogPostsData.put("totalRecordsUpper", true); + } + else + { + blogPostsData.put("totalRecordsUpper", false); + } + + List> blogPostDataSets = new ArrayList>(blogPostList.getPage().size()); + for (BlogPostInfo postInfo : blogPostList.getPage()) + { + Map data = BlogPostLibJs.getBlogPostData(postInfo.getNodeRef(), services); + blogPostDataSets.add(data); + } + blogPostsData.put("items", blogPostDataSets); + + model.put("data", blogPostsData); + + // fetch the contentLength param + String contentLengthStr = req.getParameter("contentLength"); + int contentLength = contentLengthStr == null ? -1 : Integer.parseInt(contentLengthStr); + model.put("contentLength", contentLength); + + // assign the blog node + model.put("blog", node); + model.put("externalBlogConfig", BlogPostLibJs.hasExternalBlogConfiguration(node, services)); + } + + private PagingRequest parsePagingParams(WebScriptRequest req) + { + return ScriptPagingDetails.buildPagingRequest(req, 1000); + } + + private Date parseDateParam(WebScriptRequest req, String paramName) + { + Map templateVars = req.getServiceMatch().getTemplateVars(); + String dateStr = templateVars.get(paramName); + if(dateStr == null) + { + // Try on the parameters instead + dateStr = req.getParameter(paramName); + } + + // Parse if available + Date result = null; + if (dateStr != null) + { + result = new Date(Long.parseLong(dateStr)); + } + return result; + } + + + /** + * Fetches all posts of the given blog + */ + private PagingResults getBlogPostList(SiteInfo site, NodeRef nonSiteContainer, + Date fromDate, Date toDate, String tag, PagingRequest pagingReq) + { + // Currently we only support CannedQuery-based gets without tags: + if (tag == null || tag.trim().isEmpty()) + { + return getBlogResultsImpl(site, nonSiteContainer, fromDate, toDate, pagingReq); + } + else + { + RangedDateProperty dateRange = new RangedDateProperty(fromDate, toDate, ContentModel.PROP_CREATED); + if(site != null) + { + return blogService.findBlogPosts(site.getShortName(), dateRange, tag, pagingReq); + } + else + { + return blogService.findBlogPosts(nonSiteContainer, dateRange, tag, pagingReq); + } + } + } + + protected abstract PagingResults getBlogResultsImpl( + SiteInfo site, NodeRef nonSiteContainer, Date fromDate, Date toDate, PagingRequest pagingReq); +} diff --git a/src/main/java/org/alfresco/repo/web/scripts/blogs/posts/BlogPostsGet.java b/src/main/java/org/alfresco/repo/web/scripts/blogs/posts/BlogPostsGet.java new file mode 100644 index 0000000000..8d38f0bdc8 --- /dev/null +++ b/src/main/java/org/alfresco/repo/web/scripts/blogs/posts/BlogPostsGet.java @@ -0,0 +1,61 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.web.scripts.blogs.posts; + +import java.util.Date; + +import org.alfresco.query.EmptyPagingResults; +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; +import org.alfresco.service.cmr.blog.BlogPostInfo; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.site.SiteInfo; + +/** + * This class is the controller for the blog-posts.get web script. + * + * @author Neil Mc Erlean (based on existing JavaScript webscript controllers) + * @since 4.0 + */ +public class BlogPostsGet extends AbstractGetBlogWebScript +{ + @SuppressWarnings("deprecation") + @Override + protected PagingResults getBlogResultsImpl( + SiteInfo site, NodeRef node, Date fromDate, Date toDate, PagingRequest pagingReq) + { + // As it uses deprecated methods, this bit can be a bit hacky... + if (node == null) + { + // Site based request, but no container exists yet + return new EmptyPagingResults(); + } + + // This intentionally uses the deprecated method in the foundation service. + // In fact the method is there specifically for this class. + return blogService.getMyDraftsAndAllPublished(node, fromDate, toDate, pagingReq); + } +} diff --git a/src/main/java/org/alfresco/repo/web/scripts/blogs/posts/BlogPostsMyDraftsGet.java b/src/main/java/org/alfresco/repo/web/scripts/blogs/posts/BlogPostsMyDraftsGet.java new file mode 100644 index 0000000000..3152c8390c --- /dev/null +++ b/src/main/java/org/alfresco/repo/web/scripts/blogs/posts/BlogPostsMyDraftsGet.java @@ -0,0 +1,59 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.web.scripts.blogs.posts; + +import java.util.Date; + +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.service.cmr.blog.BlogPostInfo; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.site.SiteInfo; + +/** + * This class is the controller for the blog-posts-mydrafts.get web script. + * Based on the original JavaScript webscript controller + * + * @author Neil Mc Erlean (based on existing JavaScript webscript controllers) + * @since 4.0 + */ +public class BlogPostsMyDraftsGet extends AbstractGetBlogWebScript +{ + @Override + protected PagingResults getBlogResultsImpl(SiteInfo site, NodeRef node, Date fromDate, Date toDate, PagingRequest pagingReq) + { + String user = AuthenticationUtil.getFullyAuthenticatedUser(); + if(site != null) + { + return blogService.getDrafts(site.getShortName(), user, pagingReq); + } + else + { + return blogService.getDrafts(node, user, pagingReq); + } + } +} diff --git a/src/main/java/org/alfresco/repo/web/scripts/blogs/posts/BlogPostsMyPublishedGet.java b/src/main/java/org/alfresco/repo/web/scripts/blogs/posts/BlogPostsMyPublishedGet.java new file mode 100644 index 0000000000..d226a12fc4 --- /dev/null +++ b/src/main/java/org/alfresco/repo/web/scripts/blogs/posts/BlogPostsMyPublishedGet.java @@ -0,0 +1,59 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.web.scripts.blogs.posts; + +import java.util.Date; + +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.service.cmr.blog.BlogPostInfo; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.site.SiteInfo; + +/** + * This class is the controller for the blog-posts-mypublished.get web script. + * + * @author Neil Mc Erlean (based on existing JavaScript webscript controllers) + * @since 4.0 + */ +public class BlogPostsMyPublishedGet extends AbstractGetBlogWebScript +{ + @Override + protected PagingResults getBlogResultsImpl( + SiteInfo site, NodeRef node, Date fromDate, Date toDate, PagingRequest pagingReq) + { + String fullyAuthenticatedUser = AuthenticationUtil.getFullyAuthenticatedUser(); + if(site != null) + { + return blogService.getPublished(site.getShortName(), fromDate, toDate, fullyAuthenticatedUser, pagingReq); + } + else + { + return blogService.getPublished(node, fromDate, toDate, fullyAuthenticatedUser, pagingReq); + } + } +} diff --git a/src/main/java/org/alfresco/repo/web/scripts/blogs/posts/BlogPostsNewGet.java b/src/main/java/org/alfresco/repo/web/scripts/blogs/posts/BlogPostsNewGet.java new file mode 100644 index 0000000000..2166418dcf --- /dev/null +++ b/src/main/java/org/alfresco/repo/web/scripts/blogs/posts/BlogPostsNewGet.java @@ -0,0 +1,57 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.web.scripts.blogs.posts; + +import java.util.Date; + +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; +import org.alfresco.service.cmr.blog.BlogPostInfo; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.site.SiteInfo; + +/** + * This class is the controller for the blog-posts-publishedext.get web script. + * + * @author Neil Mc Erlean (based on existing JavaScript webscript controllers) + * @since 4.0 + */ +public class BlogPostsNewGet extends AbstractGetBlogWebScript +{ + @Override + protected PagingResults getBlogResultsImpl( + SiteInfo site, NodeRef node, Date fromDate, Date toDate, PagingRequest pagingReq) + { + if(site != null) + { + return blogService.getPublished(site.getShortName(), fromDate, toDate, null, pagingReq); + } + else + { + return blogService.getPublished(node, fromDate, toDate, null, pagingReq); + } + } +} diff --git a/src/main/java/org/alfresco/repo/web/scripts/blogs/posts/BlogPostsPerMonthGet.java b/src/main/java/org/alfresco/repo/web/scripts/blogs/posts/BlogPostsPerMonthGet.java new file mode 100644 index 0000000000..7b45916e9c --- /dev/null +++ b/src/main/java/org/alfresco/repo/web/scripts/blogs/posts/BlogPostsPerMonthGet.java @@ -0,0 +1,170 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.web.scripts.blogs.posts; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; +import org.alfresco.service.cmr.blog.BlogPostInfo; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.site.SiteInfo; +import org.springframework.extensions.webscripts.WebScriptRequest; + +/** + * This class is the controller for the blog-posts-mypublished.get web script. + * + * @author Neil Mc Erlean (based on existing JavaScript webscript controllers) + * @since 4.0 + */ +public class BlogPostsPerMonthGet extends AbstractGetBlogWebScript +{ + @Override + protected PagingResults getBlogResultsImpl( + SiteInfo site, NodeRef node, Date fromDate, Date toDate, PagingRequest pagingReq) + { + if(site != null) + { + return blogService.getPublished(site.getShortName(), fromDate, toDate, null, pagingReq); + } + else + { + return blogService.getPublished(node, fromDate, toDate, null, pagingReq); + } + } + + @Override + protected void createFtlModel(WebScriptRequest req, Map model, NodeRef node, PagingRequest pagingReq, PagingResults blogPostList) + { + model.put(DATA, getBlogPostMonths(blogPostList)); + } + + + /** + * Ported from blog-posts-per-month.get.js + */ + @SuppressWarnings("deprecation") + private Date getBeginOfMonthDate(Date date) + { + //TODO These date processing methods are copied almost verbatim from JavaScript to preserve behaviour. + // However they should be updated to use java.util.Calendar as the current implementation assumes a Gregorian calendar. + Calendar calendar = Calendar.getInstance(); + calendar.set(date.getYear(), date.getMonth(), 1); + return calendar.getTime(); + } + + /** + * Returns the date representing the last second of a month (23:59:59) + * Ported from blog-posts-per-month.get.js + */ + @SuppressWarnings("deprecation") + private Date getEndOfMonthDate(Date date) + { + Calendar calendar = Calendar.getInstance(); + calendar.set(date.getYear(), date.getMonth(), date.getDay()); + // In Gregorian calendar, this would be 31 for January, 30 for March, 28 or 29 for February. + int lastDayOfSpecifiedMonth = calendar.getActualMaximum(Calendar.DAY_OF_MONTH); + calendar.set(date.getYear(), date.getMonth(), lastDayOfSpecifiedMonth, 23, 59, 59); + + return calendar.getTime(); + } + + /** + * Create an object containing information about the month specified by date. + * Ported from blog-posts-per-month.get.js + */ + @SuppressWarnings("deprecation") + private Map getMonthDataObject(Date date) + { + Map data = new HashMap(); + data.put("year", date.getYear() + 1900); + data.put("month", date.getMonth()); + data.put("firstPostInMonth", date); + data.put("beginOfMonth", getBeginOfMonthDate(date)); + data.put("endOfMonth", getEndOfMonthDate(date)); + data.put("count", 1); + + return data; + } + + /** + * Fetches data for each month for which posts exist, plus the count of each. + * Note: If no posts could be found, this method will return the current month + * but with a count of posts equals zero. + * Ported from blog-posts-per-month.get.js + */ + @SuppressWarnings("deprecation") + private List> getBlogPostMonths(PagingResults nodes) + { + // will hold the months information + List> data = new ArrayList>(); + + // do we have posts? + if (!nodes.getPage().isEmpty()) + { + int currYear = -1; + int currMonth = -1; + Map currData = null; + for (int x = 0; x < nodes.getPage().size(); x++) + { + NodeRef node = nodes.getPage().get(x).getNodeRef(); + Date date = (Date) nodeService.getProperty(node, ContentModel.PROP_PUBLISHED); + + // is this a new month? + if (currYear != date.getYear() + 1900 || currMonth != date.getMonth()) + { + currYear = date.getYear() + 1900; + currMonth = date.getMonth(); + currData = getMonthDataObject(date); + data.add(currData); + } + // otherwise just increment the counter + else + { + Object countObj = currData.get("count"); + Integer countInt = countObj == null ? 0 : (Integer)countObj; + + currData.put("count", countInt + 1); + } + } + } + // if not, add the current month with count = 0 + else + { + Map emptyData = getMonthDataObject(new Date()); + emptyData.put("count", 0); + data.add(emptyData); + } + + return data; + } +} diff --git a/src/main/java/org/alfresco/repo/web/scripts/blogs/posts/BlogPostsPost.java b/src/main/java/org/alfresco/repo/web/scripts/blogs/posts/BlogPostsPost.java new file mode 100644 index 0000000000..0cd2843f7d --- /dev/null +++ b/src/main/java/org/alfresco/repo/web/scripts/blogs/posts/BlogPostsPost.java @@ -0,0 +1,304 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.web.scripts.blogs.posts; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.repo.web.scripts.blogs.AbstractBlogWebScript; +import org.alfresco.repo.web.scripts.blogs.BlogPostLibJs; +import org.alfresco.service.cmr.blog.BlogPostInfo; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.site.SiteInfo; +import org.alfresco.service.cmr.tagging.TaggingService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.springframework.extensions.webscripts.Cache; +import org.springframework.extensions.webscripts.Status; +import org.springframework.extensions.webscripts.WebScriptRequest; + +/** + * This class is the controller for the blog-posts.post web script. + * + * @author Neil Mc Erlean (based on existing JavaScript webscript controllers) + * @since 4.0 + */ +public class BlogPostsPost extends AbstractBlogWebScript +{ + private static final Log log = LogFactory.getLog(BlogPostsPost.class); + + // Injected services + private TaggingService taggingService; + + public void setTaggingService(TaggingService taggingService) + { + this.taggingService = taggingService; + } + + @Override + protected Map executeImpl(SiteInfo site, NodeRef nodeRef, + BlogPostInfo blog, WebScriptRequest req, JSONObject json, Status status, Cache cache) + { + Map model = new HashMap(); + + // If they're doing Path Based rather than Site Based, ensure + // that the Container is a Tag Scope + if(site == null && nodeRef != null) + { + ensureTagScope(nodeRef); + } + + // Have the Blog Post created + JsonParams jsonPostParams = parsePostParams(json); + BlogPostInfo post = createBlogPost(jsonPostParams, site, nodeRef); + + Map blogPostData = BlogPostLibJs.getBlogPostData(post.getNodeRef(), services); + model.put(ITEM, blogPostData); + model.put(EXTERNAL_BLOG_CONFIG, BlogPostLibJs.hasExternalBlogConfiguration(nodeRef, services)); + + boolean isDraft = blogPostData.get("isDraft") != null && + ((Boolean)blogPostData.get("isDraft")).booleanValue(); + if (jsonPostParams.getSite() != null && + jsonPostParams.getContainer() != null && + jsonPostParams.getPage() != null && + !isDraft) + { + addActivityEntry("created", post, site, req, json, post.getNodeRef()); + } + + return model; + } + + private JsonParams parsePostParams(JSONObject json) + { + JsonParams result = new JsonParams(); + if (json.containsKey(TITLE)) + { + result.setTitle((String)json.get(TITLE)); + } + if (json.containsKey(CONTENT)) + { + result.setContent((String)json.get(CONTENT)); + } + if (json.containsKey(DRAFT)) + { + Object draft = json.get(DRAFT); + if (draft instanceof Boolean) + { + result.setIsDraft((Boolean)draft); + } + else + { + result.setIsDraft( Boolean.parseBoolean((String)draft) ); + } + } + + // If there are no tags, this is a java.lang.String "". + // If there are any tags, it's a JSONArray of strings. One or more. + if (json.containsKey(TAGS)) + { + Object tagsObj = json.get(TAGS); + List tags = new ArrayList(); + if (tagsObj instanceof JSONArray) + { + JSONArray tagsJsonArray = (JSONArray)tagsObj; + for (int i = 0; i < tagsJsonArray.size(); i++) + { + tags.add( (String)tagsJsonArray.get(i) ); + } + } + else + { + tags.add(tagsObj.toString()); + } + result.setTags(tags); + } + if (json.containsKey(SITE)) + { + result.setSite((String)json.get(SITE)); + } + if (json.containsKey(PAGE)) + { + result.setPage((String)json.get(PAGE)); + } + if (json.containsKey(CONTAINER)) + { + result.setContainer((String)json.get(CONTAINER)); + } + + return result; + } + + /** + * Taken from JS + * @param node NodeRef + */ + private void ensureTagScope(NodeRef node) + { + if (!taggingService.isTagScope(node)) + { + taggingService.addTagScope(node); + } + + + // also check the parent (the site!) + NodeRef parent = nodeService.getPrimaryParent(node).getParentRef(); + if (!taggingService.isTagScope(parent)) + { + taggingService.addTagScope(parent); + } + } + + /** + * Creates a blog post + */ + private BlogPostInfo createBlogPost(JsonParams jsonParams, SiteInfo site, NodeRef blogNode) + { + String titleParam = jsonParams.getTitle() == null ? "" : jsonParams.getTitle(); + String contentParam = jsonParams.getContent() == null ? "" : jsonParams.getContent(); + boolean isDraftParam = jsonParams.getIsDraft(); + + if (log.isDebugEnabled()) + { + StringBuilder msg = new StringBuilder(); + msg.append("Creating blog-post '").append(titleParam).append("'"); + if (isDraftParam) + { + msg.append(" DRAFT"); + } + log.debug(msg.toString()); + } + + List tagsParam = new ArrayList(); + if (jsonParams.getTags() != null) + { + tagsParam.addAll(jsonParams.getTags()); + } + + BlogPostInfo newPostNode; + if(site != null) + { + newPostNode = blogService.createBlogPost( + site.getShortName(), titleParam, contentParam, isDraftParam); + } + else + { + newPostNode = blogService.createBlogPost( + blogNode, titleParam, contentParam, isDraftParam); + } + + // Ignore empty string tags + List nonEmptyTags = new ArrayList(); + for (String tag : tagsParam) + { + if (!tag.trim().isEmpty()) + { + nonEmptyTags.add(tag); + } + } + if (!nonEmptyTags.isEmpty()) + { + taggingService.setTags(newPostNode.getNodeRef(), nonEmptyTags); + } + + return newPostNode; + } + + /** + * A simple POJO class for the parsed JSON from the POST body. + */ + class JsonParams + { + private String title; + private String content; + private boolean isDraft = false; + private List tags; + private String site; + private String container; + private String page; + + public String getTitle() + { + return title; + } + public void setTitle(String title) + { + this.title = title; + } + public String getContent() + { + return content; + } + public void setContent(String content) + { + this.content = content; + } + public boolean getIsDraft() + { + return isDraft; + } + public void setIsDraft(boolean isDraft) + { + this.isDraft = isDraft; + } + public List getTags() + { + return tags; + } + public void setTags(List tags) + { + this.tags = tags; + } + public String getSite() + { + return site; + } + public void setSite(String site) + { + this.site = site; + } + public String getContainer() + { + return container; + } + public void setContainer(String container) + { + this.container = container; + } + public String getPage() + { + return page; + } + public void setPage(String page) + { + this.page = page; + } + } +} diff --git a/src/main/java/org/alfresco/repo/web/scripts/discussion/AbstractDiscussionWebScript.java b/src/main/java/org/alfresco/repo/web/scripts/discussion/AbstractDiscussionWebScript.java new file mode 100644 index 0000000000..11a4927e03 --- /dev/null +++ b/src/main/java/org/alfresco/repo/web/scripts/discussion/AbstractDiscussionWebScript.java @@ -0,0 +1,603 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.web.scripts.discussion; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.discussion.DiscussionServiceImpl; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.site.SiteServiceImpl; +import org.alfresco.service.cmr.activities.ActivityService; +import org.alfresco.service.cmr.discussion.DiscussionService; +import org.alfresco.service.cmr.discussion.PostInfo; +import org.alfresco.service.cmr.discussion.TopicInfo; +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.NoSuchPersonException; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.cmr.site.SiteInfo; +import org.alfresco.service.cmr.site.SiteService; +import org.alfresco.util.Pair; +import org.alfresco.util.ScriptPagingDetails; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.json.simple.JSONArray; +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; + +/** + * @author Nick Burch + * @since 4.0 + */ +public abstract class AbstractDiscussionWebScript extends DeclarativeWebScript +{ + public static final String DISCUSSIONS_SERVICE_ACTIVITY_APP_NAME = "discussions"; + + /** + * When no maximum or paging info is given, what should we use? + */ + protected static final int MAX_QUERY_ENTRY_COUNT = 1000; + + private static Log logger = LogFactory.getLog(AbstractDiscussionWebScript.class); + + protected static final String KEY_POSTDATA = "postData"; + protected static final String KEY_IS_TOPIC_POST = "isTopicPost"; + protected static final String KEY_TOPIC = "topic"; + protected static final String KEY_POST = "post"; + protected static final String KEY_CAN_EDIT = "canEdit"; + protected static final String KEY_AUTHOR = "author"; + + // Injected services + protected NodeService nodeService; + protected SiteService siteService; + protected PersonService personService; + protected ActivityService activityService; + protected DiscussionService discussionService; + protected PermissionService permissionService; + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setSiteService(SiteService siteService) + { + this.siteService = siteService; + } + + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + public void setActivityService(ActivityService activityService) + { + this.activityService = activityService; + } + + public void setDiscussionService(DiscussionService discussionService) + { + this.discussionService = discussionService; + } + + public void setPermissionService(PermissionService permissionService) + { + this.permissionService = permissionService; + } + + + protected String getOrNull(JSONObject json, String key) + { + if (json.containsKey(key)) + { + return (String)json.get(key); + } + return null; + } + + /** + * Builds up a listing Paging request, based on the arguments + * specified in the URL + */ + protected PagingRequest buildPagingRequest(WebScriptRequest req) + { + return new ScriptPagingDetails(req, MAX_QUERY_ENTRY_COUNT); + } + + protected List getTags(JSONObject json) + { + List tags = null; + if (json.containsKey("tags")) + { + // Is it "tags":"" or "tags":[...] ? + if (json.get("tags") instanceof String) + { + // This is normally an empty string, skip + String tagsS = (String)json.get("tags"); + if ("".equals(tagsS)) + { + // No tags were given + return null; + } + else + { + // Log, and treat as empty + logger.warn("Unexpected tag data: " + tagsS); + return null; + } + } + else + { + tags = new ArrayList(); + JSONArray jsTags = (JSONArray)json.get("tags"); + for (int i=0; i 0) + { + title = postTitle; + } + } + + try + { + JSONObject params = new JSONObject(); + params.put("topicId", topic.getSystemName()); + + JSONObject activity = new JSONObject(); + activity.put("title", title); + activity.put("page", page + "?topicId=" + topic.getSystemName()); + activity.put("params", params); + + activityService.postActivity( + "org.alfresco.discussions." + thing + "-" + event, + site.getShortName(), + DISCUSSIONS_SERVICE_ACTIVITY_APP_NAME, + activity.toString()); + } + catch(Exception e) + { + // Warn, but carry on + logger.warn("Error adding discussions " + thing + " " + event + " to activities feed", e); + } + } + + /** + * Is the current user allowed to edit this post? + * In order to be deemed allowed, you first need write + * permissions on the underlying node of the post. + * You then also need to either be the cm:creator of + * the post node, or a site manager + */ + protected boolean canUserEditPost(PostInfo post, SiteInfo site) + { + // Are they OK on the node? + AccessStatus canEdit = permissionService.hasPermission(post.getNodeRef(), PermissionService.WRITE); + if (canEdit == AccessStatus.ALLOWED) + { + // Only the creator and site managers may edit + String user = AuthenticationUtil.getFullyAuthenticatedUser(); + if (post.getCreator().equals(user)) + { + // It's their post + return true; + } + if (site != null) + { + String role = siteService.getMembersRole(site.getShortName(), user); + if (SiteServiceImpl.SITE_MANAGER.equals(role)) + { + // Managers may edit + return true; + } + } + } + + // If in doubt, you may not edit + return false; + } + + protected Object buildPerson(String username) + { + // Empty string needed if the user can't be found + Object noSuchPersonResponse = ""; + + if (username == null || username.length() == 0) + { + return noSuchPersonResponse; + } + + try + { + // Will turn into a Script Node needed of the person + NodeRef person = personService.getPerson(username); + return person; + } + catch(NoSuchPersonException e) + { + // This is normally caused by the person having been deleted + return noSuchPersonResponse; + } + } + + /* + * Was topicpost.lib.js getReplyPostData + * + * TODO Switch the FTL to prefer the Info object rather than the ScriptNode + */ + protected Map renderPost(PostInfo post, SiteInfo site) + { + Map item = new HashMap(); + item.put(KEY_IS_TOPIC_POST, false); + item.put(KEY_POST, post.getNodeRef()); + item.put(KEY_CAN_EDIT, canUserEditPost(post, site)); + item.put(KEY_AUTHOR, buildPerson(post.getCreator())); + return item; + } + + /* + * Was topicpost.lib.js getTopicPostData / getTopicPostDataFromTopicAndPosts + * + * TODO Switch the FTL to prefer the Info object rather than the ScriptNode + */ + protected Map renderTopic(TopicInfo topic, SiteInfo site) + { + // Fetch the primary post + PostInfo primaryPost = discussionService.getPrimaryPost(topic); + if (primaryPost == null) + { + throw new WebScriptException(Status.STATUS_PRECONDITION_FAILED, + "First (primary) post was missing from the topic, can't fetch"); + } + + // Fetch the most recent reply + PostInfo mostRecentPost = discussionService.getMostRecentPost(topic); + + // Find out how many replies there are + int numReplies; + if (mostRecentPost.getNodeRef().equals( primaryPost.getNodeRef() )) + { + // Only the one post in the topic + mostRecentPost = null; + numReplies = 0; + } + else + { + // Use this trick to get the number of posts in the topic, + // but without needing to get lots of data and objects + PagingRequest paging = new PagingRequest(1); + paging.setRequestTotalCountMax(MAX_QUERY_ENTRY_COUNT); + PagingResults posts = discussionService.listPosts(topic, paging); + + // The primary post is in the list, so exclude from the reply count + numReplies = posts.getTotalResultCount().getFirst() - 1; + } + + // Build the details + Map item = new HashMap(); + item.put(KEY_IS_TOPIC_POST, true); + item.put(KEY_TOPIC, topic.getNodeRef()); + item.put(KEY_POST, primaryPost.getNodeRef()); + item.put(KEY_CAN_EDIT, canUserEditPost(primaryPost, site)); + item.put(KEY_AUTHOR, buildPerson(topic.getCreator())); + + // The reply count is one less than all posts (first is the primary one) + item.put("totalReplyCount", numReplies); + + // Add the topic site + item.put("site", topic.getShortSiteName()); + + // We want details on the most recent post + if (mostRecentPost != null) + { + item.put("lastReply", mostRecentPost.getNodeRef()); + item.put("lastReplyBy", buildPerson(mostRecentPost.getCreator())); + } + + // Include the tags + item.put("tags", topic.getTags()); + + // All done + return item; + } + + /* + * Renders out the list of topics + * TODO Fetch the post data in one go, rather than one at a time + */ + protected Map renderTopics(PagingResults topics, + PagingRequest paging, SiteInfo site) + { + return renderTopics(topics.getPage(), topics.getTotalResultCount(), paging, site); + } + /* + * Renders out the list of topics + * TODO Fetch the post data in one go, rather than one at a time + */ + protected Map renderTopics(List topics, + Pair size, PagingRequest paging, SiteInfo site) + { + Map model = new HashMap(); + + // Paging info + model.put("total", size.getFirst()); + model.put("pageSize", paging.getMaxItems()); + model.put("startIndex", paging.getSkipCount()); + model.put("itemCount", topics.size()); + + // Data + List> items = new ArrayList>(); + for (TopicInfo topic : topics) + { + // ACE-772 fix of incorrect display of topics into "My Discussions" dashlet. + // Into "My Discussions" dashlet forum topic will be displayed only if user is a member of that site. + if (null == site && null != topic.getShortSiteName()) + { + String currentUser = AuthenticationUtil.getFullyAuthenticatedUser(); + String siteShortName = topic.getShortSiteName(); + boolean isSiteMember = siteService.isMember(siteShortName, currentUser); + + if (isSiteMember) + { + items.add(renderTopic(topic, site)); + } + } + // Display all topics on the forum of the site. + else + { + items.add(renderTopic(topic, site)); + } + } + model.put("items", items); + + // All done + return model; + } + + protected Map buildCommonModel(SiteInfo site, TopicInfo topic, + PostInfo post, WebScriptRequest req) + { + // Build the common model parts + Map model = new HashMap(); + model.put(KEY_TOPIC, topic); + model.put(KEY_POST, post); + + // Capture the site details only if site based + if (site != null) + { + model.put("siteId", site.getShortName()); + model.put("site", site); + } + + // The limit on the length of the content to be returned + int contentLength = -1; + String contentLengthS = req.getParameter("contentLength"); + if (contentLengthS != null) + { + try + { + contentLength = Integer.parseInt(contentLengthS); + } + catch (NumberFormatException e) + { + logger.info("Skipping invalid length " + contentLengthS); + } + } + model.put("contentLength", contentLength); + + // All done + return model; + } + + @Override + protected Map executeImpl(WebScriptRequest req, + Status status, Cache cache) + { + Map templateVars = req.getServiceMatch().getTemplateVars(); + if (templateVars == null) + { + String error = "No parameters supplied"; + throw new WebScriptException(Status.STATUS_BAD_REQUEST, error); + } + + + // Parse the JSON, if supplied + 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()); + } + } + + + // Did they request it by node reference or site? + NodeRef nodeRef = null; + SiteInfo site = null; + TopicInfo topic = null; + PostInfo post = null; + + if (templateVars.containsKey("site")) + { + // Site, and optionally topic + String siteName = templateVars.get("site"); + site = siteService.getSite(siteName); + if (site == null) + { + String error = "Could not find site: " + siteName; + throw new WebScriptException(Status.STATUS_NOT_FOUND, error); + } + + // Did they give a topic name too? + if (templateVars.containsKey("path")) + { + String name = templateVars.get("path"); + topic = discussionService.getTopic(site.getShortName(), name); + + if (topic == null) + { + String error = "Could not find topic '" + name + "' for site '" + + site.getShortName() + "'"; + throw new WebScriptException(Status.STATUS_NOT_FOUND, error); + } + nodeRef = topic.getNodeRef(); + } + else + { + // The NodeRef is the container (if it exists) + if (siteService.hasContainer(siteName, DiscussionServiceImpl.DISCUSSION_COMPONENT)) + { + nodeRef = siteService.getContainer(siteName, DiscussionServiceImpl.DISCUSSION_COMPONENT); + } + } + } + else if (templateVars.containsKey("store_type") && + templateVars.containsKey("store_id") && + templateVars.containsKey("id")) + { + // NodeRef, normally Topic or Discussion + StoreRef store = new StoreRef( + templateVars.get("store_type"), + templateVars.get("store_id")); + + nodeRef = new NodeRef(store, templateVars.get("id")); + if (! nodeService.exists(nodeRef)) + { + String error = "Could not find node: " + nodeRef; + throw new WebScriptException(Status.STATUS_NOT_FOUND, error); + } + + // Try to build the appropriate object for it + Pair objects = discussionService.getForNodeRef(nodeRef); + if (objects != null) + { + topic = objects.getFirst(); + post = objects.getSecond(); + } + + // See if it's actually attached to a site + if (topic != null) + { + NodeRef container = topic.getContainerNodeRef(); + if (container != null) + { + NodeRef maybeSite = nodeService.getPrimaryParent(container).getParentRef(); + if (maybeSite != null) + { + // Try to make it a site, will return Null if it isn't one + site = siteService.getSite(maybeSite); + } + } + } + } + else + { + String error = "Unsupported template parameters found"; + throw new WebScriptException(Status.STATUS_BAD_REQUEST, error); + } + + // Have the real work done + return executeImpl(site, nodeRef, topic, post, req, json, status, cache); + } + + protected abstract Map executeImpl(SiteInfo site, + NodeRef nodeRef, TopicInfo topic, PostInfo post, + WebScriptRequest req, JSONObject json, Status status, Cache cache); + +} diff --git a/src/main/java/org/alfresco/repo/web/scripts/discussion/ForumPostDelete.java b/src/main/java/org/alfresco/repo/web/scripts/discussion/ForumPostDelete.java new file mode 100644 index 0000000000..68f3350b0c --- /dev/null +++ b/src/main/java/org/alfresco/repo/web/scripts/discussion/ForumPostDelete.java @@ -0,0 +1,125 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.web.scripts.discussion; + +import java.text.MessageFormat; +import java.util.Map; +import java.util.ResourceBundle; + +import org.alfresco.service.cmr.discussion.PostInfo; +import org.alfresco.service.cmr.discussion.TopicInfo; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.site.SiteInfo; +import org.json.simple.JSONObject; +import org.springframework.extensions.webscripts.Cache; +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 forum post deleting forum-post.delete webscript. + * + * @author Nick Burch + * @since 4.0 + */ +public class ForumPostDelete extends AbstractDiscussionWebScript +{ + private static final String MSG_NODE_MARKED_REMOVED = "forum-post.msg.marked.removed"; + private static final String MSG_NODE_DELETED = "forum-post.msg.deleted"; + private static final String DELETED_POST_TEXT = "[[deleted]]"; + + @Override + protected Map executeImpl(SiteInfo site, NodeRef nodeRef, + TopicInfo topic, PostInfo post, WebScriptRequest req, JSONObject json, + Status status, Cache cache) + { + final ResourceBundle rb = getResources(); + + // Build the common model parts + Map model = buildCommonModel(site, topic, post, req); + + + // Are we deleting a topic, or a post in it? + String message = null; + if (post != null) + { + message = doDeletePost(topic, post, rb); + } + else if (topic != null) + { + message = doDeleteTopic(topic, site, req, json, rb); + } + else + { + String error = "Node was of the wrong type, only Topic and Post are supported"; + throw new WebScriptException(Status.STATUS_BAD_REQUEST, error); + } + + + // Finish the model and return + model.put("message", message); + return model; + } + + private String doDeleteTopic(TopicInfo topic, SiteInfo site, + WebScriptRequest req, JSONObject json, ResourceBundle rb) + { + // Delete the topic, which removes all its posts too + discussionService.deleteTopic(topic); + + // Add an activity entry for this if it's site based + if (site != null) + { + addActivityEntry("post", "deleted", topic, null, site, req, json); + } + + // All done + String message = rb.getString(MSG_NODE_DELETED); + + return MessageFormat.format(message, topic.getNodeRef()); + } + + /** + * We can't just delete posts with replies attached to them, + * as that breaks the reply threading. + * For that reason, we mark deleted posts with a special + * text contents. + * TODO If a post has no replies, then delete it fully + */ + private String doDeletePost(TopicInfo topic, PostInfo post, ResourceBundle rb) + { + // Set the marker text and save + post.setTitle(DELETED_POST_TEXT); + post.setContents(DELETED_POST_TEXT); + discussionService.updatePost(post); + + // Note - we don't add activity feed entries for deleted posts + // Only deleted whole topic qualify for that at the moment + + String message = rb.getString(MSG_NODE_MARKED_REMOVED); + return MessageFormat.format(message, post.getNodeRef()); + } +} diff --git a/src/main/java/org/alfresco/repo/web/scripts/discussion/ForumPostGet.java b/src/main/java/org/alfresco/repo/web/scripts/discussion/ForumPostGet.java new file mode 100644 index 0000000000..03ee838c19 --- /dev/null +++ b/src/main/java/org/alfresco/repo/web/scripts/discussion/ForumPostGet.java @@ -0,0 +1,74 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.web.scripts.discussion; + +import java.util.Map; + +import org.alfresco.service.cmr.discussion.PostInfo; +import org.alfresco.service.cmr.discussion.TopicInfo; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.site.SiteInfo; +import org.json.simple.JSONObject; +import org.springframework.extensions.webscripts.Cache; +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 discussions page fetching forum-post.get webscript. + * + * @author Nick Burch + * @since 4.0 + */ +public class ForumPostGet extends AbstractDiscussionWebScript +{ + @Override + protected Map executeImpl(SiteInfo site, NodeRef nodeRef, + TopicInfo topic, PostInfo post, WebScriptRequest req, JSONObject json, + Status status, Cache cache) + { + // Build the common model parts + Map model = buildCommonModel(site, topic, post, req); + + // Did they want just one post, or the whole of the topic? + if (post != null) + { + model.put(KEY_POSTDATA, renderPost(post, site)); + } + else if (topic != null) + { + model.put(KEY_POSTDATA, renderTopic(topic, site)); + } + else + { + String error = "Node was of the wrong type, only Topic and Post are supported"; + throw new WebScriptException(Status.STATUS_BAD_REQUEST, error); + } + + // All done + return model; + } +} diff --git a/src/main/java/org/alfresco/repo/web/scripts/discussion/ForumPostPut.java b/src/main/java/org/alfresco/repo/web/scripts/discussion/ForumPostPut.java new file mode 100644 index 0000000000..1b53e97b16 --- /dev/null +++ b/src/main/java/org/alfresco/repo/web/scripts/discussion/ForumPostPut.java @@ -0,0 +1,140 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.web.scripts.discussion; + +import java.util.List; +import java.util.Map; + +import org.alfresco.service.cmr.discussion.PostInfo; +import org.alfresco.service.cmr.discussion.TopicInfo; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.site.SiteInfo; +import org.json.simple.JSONObject; +import org.springframework.extensions.webscripts.Cache; +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 discussions page editing forum-post.put webscript. + * + * @author Nick Burch + * @since 4.0 + */ +public class ForumPostPut extends AbstractDiscussionWebScript +{ + @Override + protected Map executeImpl(SiteInfo site, NodeRef nodeRef, + TopicInfo topic, PostInfo post, WebScriptRequest req, JSONObject json, + Status status, Cache cache) + { + // Build the common model parts + Map model = buildCommonModel(site, topic, post, req); + + // Did they want to change a reply or the whole topic? + if (post != null) + { + // Update the specified post + doUpdatePost(post, post.getTopic(), req, json); + + // Add the activity entry for the reply change + addActivityEntry("reply", "updated", post.getTopic(), post, site, req, json); + + // Build the JSON for just this post + model.put(KEY_POSTDATA, renderPost(post, site)); + } + else if (topic != null) + { + // Update the primary post of the topic + post = discussionService.getPrimaryPost(topic); + if (post == null) + { + throw new WebScriptException(Status.STATUS_PRECONDITION_FAILED, + "First (primary) post was missing from the topic, can't fetch"); + } + doUpdatePost(post, topic, req, json); + + // Add the activity entry for the topic change + addActivityEntry("post", "updated", topic, null, site, req, json); + + // Build the JSON for the whole topic + model.put(KEY_POSTDATA, renderTopic(topic, site)); + } + else + { + String error = "Node was of the wrong type, only Topic and Post are supported"; + throw new WebScriptException(Status.STATUS_BAD_REQUEST, error); + } + + // All done + return model; + } + + private void doUpdatePost(PostInfo post, TopicInfo topic, WebScriptRequest req, + JSONObject json) + { + boolean updateTopic = false; + // Fetch the details from the JSON + + // Update the titles on the post and it's topic + if (json.containsKey("title")) + { + String title = (String)json.get("title"); + post.setTitle(title); + if (title.length() > 0) + { + updateTopic = true; + topic.setTitle(title); + } + } + + // Contents is on the post + if (json.containsKey("content")) + { + post.setContents((String)json.get("content")); + } + + // Tags are on the topic + if (json.containsKey("tags")) + { + topic.getTags().clear(); + + List tags = getTags(json); + if (tags != null) + { + topic.getTags().addAll(tags); + } + updateTopic = true; + } + + // Save the topic and the post + if (updateTopic == true) + { + discussionService.updateTopic(topic); + } + discussionService.updatePost(post); + } +} diff --git a/src/main/java/org/alfresco/repo/web/scripts/discussion/ForumPostRepliesGet.java b/src/main/java/org/alfresco/repo/web/scripts/discussion/ForumPostRepliesGet.java new file mode 100644 index 0000000000..35718fc934 --- /dev/null +++ b/src/main/java/org/alfresco/repo/web/scripts/discussion/ForumPostRepliesGet.java @@ -0,0 +1,111 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.web.scripts.discussion; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.alfresco.service.cmr.discussion.PostInfo; +import org.alfresco.service.cmr.discussion.PostWithReplies; +import org.alfresco.service.cmr.discussion.TopicInfo; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.site.SiteInfo; +import org.json.simple.JSONObject; +import org.springframework.extensions.webscripts.Cache; +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 discussions page creating forum-post-replies.get webscript. + * + * @author Nick Burch + * @since 4.0 + */ +public class ForumPostRepliesGet extends AbstractDiscussionWebScript +{ + @Override + protected Map executeImpl(SiteInfo site, NodeRef nodeRef, + TopicInfo topic, PostInfo post, WebScriptRequest req, JSONObject json, + Status status, Cache cache) + { + // How many levels did they want? + int levels = 1; + String levelsS = req.getParameter("levels"); + if (levelsS != null) + { + try + { + levels = Integer.parseInt(levelsS); + } + catch (NumberFormatException e) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Level depth parameter invalid"); + } + } + + // Fetch the replies + PostWithReplies replies; + if (post != null) + { + replies = discussionService.listPostReplies(post, levels); + } + else if (topic != null) + { + replies = discussionService.listPostReplies(topic, levels); + } + else + { + String error = "Node was of the wrong type, only Topic and Post are supported"; + throw new WebScriptException(Status.STATUS_BAD_REQUEST, error); + } + + // Build the common model parts + Map model = buildCommonModel(site, topic, post, req); + + // Build the JSON for the replies + model.put("data", renderReplies(replies, site).get("children")); + + // All done + return model; + } + + private Map renderReplies(PostWithReplies replies, SiteInfo site) + { + Map reply = renderPost(replies.getPost(), site); + reply.put("childCount", replies.getReplies().size()); + + List> r = new ArrayList>(); + for (PostWithReplies child : replies.getReplies()) + { + r.add(renderReplies(child, site)); + } + reply.put("children", r); + + return reply; + } +} diff --git a/src/main/java/org/alfresco/repo/web/scripts/discussion/ForumPostRepliesPost.java b/src/main/java/org/alfresco/repo/web/scripts/discussion/ForumPostRepliesPost.java new file mode 100644 index 0000000000..a0d3e38247 --- /dev/null +++ b/src/main/java/org/alfresco/repo/web/scripts/discussion/ForumPostRepliesPost.java @@ -0,0 +1,117 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.web.scripts.discussion; + +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.discussion.PostInfo; +import org.alfresco.service.cmr.discussion.TopicInfo; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.site.SiteInfo; +import org.json.simple.JSONObject; +import org.springframework.extensions.webscripts.Cache; +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 discussions page creating forum-post-replies.post webscript. + * + * @author Nick Burch + * @since 4.0 + */ +public class ForumPostRepliesPost extends AbstractDiscussionWebScript +{ + @Override + protected Map executeImpl(SiteInfo site, NodeRef nodeRef, + TopicInfo topic, PostInfo post, WebScriptRequest req, JSONObject json, + Status status, Cache cache) + { + // If they're trying to create a reply to a topic, they actually + // mean to create the reply on the primary post + if (post == null) + { + post = discussionService.getPrimaryPost(topic); + if (post == null) + { + throw new WebScriptException(Status.STATUS_PRECONDITION_FAILED, + "First (primary) post was missing from the topic, can't fetch"); + } + } + else if (topic == null) + { + String error = "Node was of the wrong type, only Topic and Post are supported"; + throw new WebScriptException(Status.STATUS_BAD_REQUEST, error); + } + + // Have the reply created + PostInfo reply = doCreatePost(post, topic, req, json); + + // Add the activity entry for the reply change + addActivityEntry("reply", "created", topic, reply, site, req, json); + + // Build the common model parts + Map model = buildCommonModel(site, topic, reply, req); + + // Build the JSON for the new reply post + model.put(KEY_POSTDATA, renderPost(reply, site)); + + // All done + return model; + } + + private PostInfo doCreatePost(PostInfo post, TopicInfo topic, WebScriptRequest req, + JSONObject json) + { + // Fetch the details from the JSON + String title = null; + if (json.containsKey("title")) + { + title = (String)json.get("title"); + } + + String contents = null; + if (json.containsKey("content")) + { + contents = (String)json.get("content"); + } + + + // Create the reply + PostInfo reply = discussionService.createReply(post, contents); + + // Set the title if needed (it normally isn't) + if (title != null && title.length() > 0) + { + nodeService.setProperty(reply.getNodeRef(), ContentModel.PROP_TITLE, title); + reply = discussionService.getPost(topic, reply.getSystemName()); + } + + // All done + return reply; + } +} diff --git a/src/main/java/org/alfresco/repo/web/scripts/discussion/ForumTopicPost.java b/src/main/java/org/alfresco/repo/web/scripts/discussion/ForumTopicPost.java new file mode 100644 index 0000000000..6bbe4394d7 --- /dev/null +++ b/src/main/java/org/alfresco/repo/web/scripts/discussion/ForumTopicPost.java @@ -0,0 +1,110 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.web.scripts.discussion; + +import java.util.List; +import java.util.Map; + +import org.alfresco.service.cmr.discussion.PostInfo; +import org.alfresco.service.cmr.discussion.TopicInfo; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.site.SiteInfo; +import org.json.simple.JSONObject; +import org.springframework.extensions.webscripts.Cache; +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 discussions page editing forum-posts.post webscript. + * + * @author Nick Burch + * @since 4.0 + */ +public class ForumTopicPost extends AbstractDiscussionWebScript +{ + @Override + protected Map executeImpl(SiteInfo site, NodeRef nodeRef, + TopicInfo topic, PostInfo post, WebScriptRequest req, JSONObject json, + Status status, Cache cache) + { + // They shouldn't be adding to an existing Post or Topic + if (topic != null || post != null) + { + String error = "Can't create a new Topic inside an existing Topic or Post"; + throw new WebScriptException(Status.STATUS_BAD_REQUEST, error); + } + + + // Grab the details of the new Topic and Post + String title = ""; + String contents = ""; + if (json.containsKey("title")) + { + title = (String)json.get("title"); + } + if (json.containsKey("content")) + { + contents = (String)json.get("content"); + } + List tags = getTags(json); + + + // Have the topic created + if (site != null) + { + topic = discussionService.createTopic(site.getShortName(), title); + } + else + { + topic = discussionService.createTopic(nodeRef, title); + } + if (tags != null && tags.size() > 0) + { + topic.getTags().clear(); + topic.getTags().addAll(tags); + discussionService.updateTopic(topic); + } + + + // Have the primary post created + post = discussionService.createPost(topic, contents); + + + // Record the activity + addActivityEntry("post", "created", topic, post, site, req, json); + + + // Build the common model parts + Map model = buildCommonModel(site, topic, post, req); + + // Build the JSON for the whole topic + model.put(KEY_POSTDATA, renderTopic(topic, site)); + + // All done + return model; + } +} diff --git a/src/main/java/org/alfresco/repo/web/scripts/discussion/ForumTopicsFilteredGet.java b/src/main/java/org/alfresco/repo/web/scripts/discussion/ForumTopicsFilteredGet.java new file mode 100644 index 0000000000..a4e9c40649 --- /dev/null +++ b/src/main/java/org/alfresco/repo/web/scripts/discussion/ForumTopicsFilteredGet.java @@ -0,0 +1,414 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.web.scripts.discussion; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Comparator; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +import org.alfresco.query.EmptyPagingResults; +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; +import org.alfresco.repo.discussion.TopicInfoImpl; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.service.cmr.discussion.PostInfo; +import org.alfresco.service.cmr.discussion.TopicInfo; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.LimitBy; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.ResultSetRow; +import org.alfresco.service.cmr.search.SearchParameters; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.site.SiteInfo; +import org.alfresco.util.ISO9075; +import org.alfresco.util.Pair; +import org.alfresco.util.ScriptPagingDetails; +import org.json.simple.JSONObject; +import org.springframework.extensions.webscripts.Cache; +import org.springframework.extensions.webscripts.Status; +import org.springframework.extensions.webscripts.WebScriptException; +import org.springframework.extensions.webscripts.WebScriptRequest; + +/** + * Gets topics matching the filters passed to it in the URL. + * + * topics = 'mine' (searches for posts by the user) or 'all' (ignores the author in the search) + * history = days in the past to search + * resultSize = the number of topics returned in the results + * + * @author Jamie Allison + */ +public class ForumTopicsFilteredGet extends AbstractDiscussionWebScript +{ + //Filter Defaults + protected static final String DEFAULT_TOPIC_AUTHOR = "mine"; + protected static final int DEFAULT_TOPIC_LATEST_POST_DAYS_AGO = 1; + protected static final int DEFAULT_MAX_RESULTS = 10; + + protected static final StoreRef SPACES_STORE = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore"); + + protected static final String SEARCH_QUERY = "TYPE:\"{http://www.alfresco.org/model/forum/1.0}post\"" + + " AND PATH:\"/app:company_home/st:sites/%s/cm:discussions/*/*\"" + + " AND @cm:created:[\"%s\" TO NOW]"; + + /** Spring-injected services */ + private SearchService searchService; + + /** + * Sets the searchService. + * + * @param searchService SearchService + */ + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + /** + * Overrides AbstractDiscussionWebScript to allow a null site + * + * @param req WebScriptRequest + * @param status Status + * @param cache Cache + * + * @return Map + */ + @Override + protected Map executeImpl(WebScriptRequest req, Status status, Cache cache) + { + Map templateVars = req.getServiceMatch().getTemplateVars(); + if (templateVars == null) + { + String error = "No parameters supplied"; + throw new WebScriptException(Status.STATUS_BAD_REQUEST, error); + } + + SiteInfo site = null; + + if (templateVars.containsKey("site")) + { + // Site, and optionally topic + String siteName = templateVars.get("site"); + site = siteService.getSite(siteName); + if (site == null) + { + String error = "Could not find site: " + siteName; + throw new WebScriptException(Status.STATUS_NOT_FOUND, error); + } + } + + // Have the real work done + return executeImpl(site, null, null, null, req, null, null, null); + } + + /** + * @param site SiteInfo + * @param nodeRef Not required. It is only included because it is overriding the parent class. + * @param topic Not required. It is only included because it is overriding the parent class. + * @param post Not required. It is only included because it is overriding the parent class. + * @param req WebScriptRequest + * @param status Not required. It is only included because it is overriding the parent class. + * @param cache Not required. It is only included because it is overriding the parent class. + * + * @return Map + */ + @Override + protected Map executeImpl(SiteInfo site, NodeRef nodeRef, TopicInfo topic, + PostInfo post, WebScriptRequest req, JSONObject json, Status status, Cache cache) + { + // They shouldn't be trying to list of an existing Post or Topic + if (topic != null || post != null) + { + String error = "Can't list Topics inside an existing Topic or Post"; + throw new WebScriptException(Status.STATUS_BAD_REQUEST, error); + } + + // Set search filter to users topics or all topics + String pAuthor = req.getParameter("topics"); + String author = DEFAULT_TOPIC_AUTHOR; + if (pAuthor != null) + { + author = pAuthor; + } + // Set the number of days in the past to search from + String pDaysAgo = req.getParameter("history"); + int daysAgo = DEFAULT_TOPIC_LATEST_POST_DAYS_AGO; + if (pDaysAgo != null) + { + try + { + daysAgo = Integer.parseInt(pDaysAgo); + } + catch (NumberFormatException e) + { + //do nothing. history has already been preset to the default value. + } + } + + // Get the complete search query + Pair searchQuery = getSearchQuery(site, author, daysAgo); + + // Get the filtered topics + PagingRequest paging = buildPagingRequest(req); + PagingResults topics = doSearch(searchQuery, false, paging); + + // Build the common model parts + Map model = buildCommonModel(site, topic, post, req); + + // Have the topics rendered + model.put("data", renderTopics(topics, paging, site)); + + // All done + return model; + } + + /** + * Do the actual search + * + * @param searchQuery Pair with query string in first and query language in second + * @param sortAscending boolean + * @param paging PagingRequest + */ + protected PagingResults doSearch(Pair searchQuery, boolean sortAscending, PagingRequest paging) + { + ResultSet resultSet = null; + PagingResults pagedResults = new EmptyPagingResults(); + + String sortOn = "@{http://www.alfresco.org/model/content/1.0}created"; + + // Setup the search parameters + SearchParameters sp = new SearchParameters(); + sp.addStore(SPACES_STORE); + sp.setQuery(searchQuery.getFirst()); + sp.setLanguage(searchQuery.getSecond()); + sp.addSort(sortOn, sortAscending); + if (paging.getMaxItems() > 0) + { + //Multiply maxItems by 10. This is to catch topics that have multiple replies and ensure that the maximum number of topics is shown. + sp.setLimit(paging.getMaxItems()*10); + sp.setLimitBy(LimitBy.FINAL_SIZE); + } + if (paging.getSkipCount() > 0) + { + sp.setSkipCount(paging.getSkipCount()); + } + + try + { + resultSet = searchService.query(sp); + pagedResults = wrap(resultSet, paging); + } + finally + { + try + { + resultSet.close(); + } + catch(Exception e) + { + //do nothing + } + } + + return pagedResults; + } + + /** + * Build the search query from the passed in parameters and SEARCH_QUERY constant + * + * @param site SiteInfo + * @param author String + * @param daysAgo int + * @return Pair with the query string in first and query language in second + */ + protected Pair getSearchQuery(SiteInfo site, String author, int daysAgo) + { + String search = String.format(SEARCH_QUERY, + (site != null ? "cm:" + ISO9075.encode(site.getShortName()) : "*"), + getDateXDaysAgo(daysAgo) + ); + + // If author equals 'mine' add cm:creator to the search query otherwise leave out + if(author.equals(DEFAULT_TOPIC_AUTHOR)) + { + search += " AND @cm:creator:\"" + AuthenticationUtil.getFullyAuthenticatedUser() + "\""; + } + + // Add the query string and language to the returned results + Pair searchQuery = new Pair(search, SearchService.LANGUAGE_FTS_ALFRESCO); + + return searchQuery; + } + + /** + * Get the date x days ago in the format 'yyyy-MM-dd' + * + * @param daysAgo int + * @return String + */ + protected String getDateXDaysAgo(int daysAgo) + { + Calendar calendar = Calendar.getInstance(); + calendar.add(Calendar.DAY_OF_MONTH, (daysAgo * -1)); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + + return sdf.format(calendar.getTime()); + } + + /** + * Builds up a listing Paging request, based on the arguments specified in the URL + * + * @param req WebScriptRequest + * @return PagingRequest + */ + @Override + protected PagingRequest buildPagingRequest(WebScriptRequest req) + { + // Grab the number of topics to return + String pResultSize = req.getParameter("resultSize"); + int resultSize = DEFAULT_MAX_RESULTS; + if (pResultSize != null) + { + try + { + resultSize = Integer.parseInt(pResultSize); + } + catch (NumberFormatException e) + { + //do nothing. ResultSize has already been preset to the default value. + } + } + return new ScriptPagingDetails(req, resultSize); + } + + /** + * Wrap up search results as {@link TopicInfo} instances + * + * @param finalResults ResultSet + * @param paging PagingRequest + */ + protected PagingResults wrap(final ResultSet finalResults, PagingRequest paging) + { + int maxItems = paging.getMaxItems(); + Comparator lastPostDesc = new Comparator() + { + @Override + public int compare(TopicInfo t1, TopicInfo t2) + { + Date t1LastPostDate = t1.getCreatedAt(); + if(discussionService.getMostRecentPost(t1) != null) + { + t1LastPostDate = discussionService.getMostRecentPost(t1).getCreatedAt(); + } + + Date t2LastPostDate = t2.getCreatedAt(); + if(discussionService.getMostRecentPost(t2) != null) + { + t2LastPostDate = discussionService.getMostRecentPost(t2).getCreatedAt(); + } + return t2LastPostDate.compareTo(t1LastPostDate); + } + }; + + final Set topics = new TreeSet(lastPostDesc); + + for (ResultSetRow row : finalResults) + { + Pair pair = discussionService.getForNodeRef(row.getNodeRef()); + TopicInfo topic = pair.getFirst(); + if(topic != null) + { + String path = nodeService.getPath(topic.getNodeRef()).toDisplayPath(nodeService, permissionService); + String site = path.split("/")[3]; + TopicInfoImpl tii = (TopicInfoImpl)topic; + tii.setShortSiteName(site); + topics.add(tii); + + if(topics.size() >= maxItems) + { + break; + } + } + } + + // Wrap + return new PagingResults() + { + @Override + public boolean hasMoreItems() + { + try + { + return finalResults.hasMore(); + } + catch(UnsupportedOperationException e) + { + // Not all search results support paging + return false; + } + } + + @Override + public Pair getTotalResultCount() + { + int skipCount = 0; + int itemsRemainingAfterThisPage = 0; + try + { + skipCount = finalResults.getStart(); + } + catch(UnsupportedOperationException e) {} + try + { + itemsRemainingAfterThisPage = finalResults.length(); + } + catch(UnsupportedOperationException e) {} + + final int totalItemsInUnpagedResultSet = skipCount + itemsRemainingAfterThisPage; + return new Pair(totalItemsInUnpagedResultSet, totalItemsInUnpagedResultSet); + } + + @Override + public List getPage() + { + return new ArrayList(topics); + } + + @Override + public String getQueryExecutionId() + { + return null; + } + }; + } +} diff --git a/src/main/java/org/alfresco/repo/web/scripts/discussion/ForumTopicsGet.java b/src/main/java/org/alfresco/repo/web/scripts/discussion/ForumTopicsGet.java new file mode 100644 index 0000000000..662e54f1c4 --- /dev/null +++ b/src/main/java/org/alfresco/repo/web/scripts/discussion/ForumTopicsGet.java @@ -0,0 +1,121 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.web.scripts.discussion; + +import java.util.Map; + +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; +import org.alfresco.service.cmr.discussion.PostInfo; +import org.alfresco.service.cmr.discussion.TopicInfo; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.site.SiteInfo; +import org.json.simple.JSONObject; +import org.springframework.extensions.surf.util.URLDecoder; +import org.springframework.extensions.webscripts.Cache; +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 discussions topics fetching forum-posts.get webscript. + * + * @author Nick Burch + * @since 4.0 + */ +public class ForumTopicsGet extends AbstractDiscussionWebScript +{ + @Override + protected Map executeImpl(SiteInfo site, NodeRef nodeRef, + TopicInfo topic, PostInfo post, WebScriptRequest req, JSONObject json, + Status status, Cache cache) + { + // They shouldn't be trying to list of an existing Post or Topic + if (topic != null || post != null) + { + String error = "Can't list Topics inside an existing Topic or Post"; + throw new WebScriptException(Status.STATUS_BAD_REQUEST, error); + } + + // Do we need to list or search? + boolean tagSearch = false; + String tag = req.getParameter("tag"); + if (tag != null && tag.length() > 0) + { + tagSearch = true; + + // Tags can be full unicode strings, so decode + tag = URLDecoder.decode(tag); + } + + // Get the topics + boolean oldestTopicsFirst = false; + PagingResults topics = null; + PagingRequest paging = buildPagingRequest(req); + if (tagSearch) + { + // Tag based is a search rather than a listing + if (site != null) + { + topics = discussionService.findTopics(site.getShortName(), null, tag, oldestTopicsFirst, paging); + } + else + { + topics = discussionService.findTopics(nodeRef, null, tag, oldestTopicsFirst, paging); + } + } + else + { + if (site != null) + { + topics = discussionService.listTopics(site.getShortName(), oldestTopicsFirst, paging); + } + else + { + topics = discussionService.listTopics(nodeRef, oldestTopicsFirst, buildPagingRequest(req)); + } + } + + + // If they did a site based search, and the component hasn't + // been created yet, use the site for the permissions checking + if (site != null && nodeRef == null) + { + nodeRef = site.getNodeRef(); + } + + + // Build the common model parts + Map model = buildCommonModel(site, topic, post, req); + model.put("forum", nodeRef); + + // Have the topics rendered + model.put("data", renderTopics(topics, paging, site)); + + // All done + return model; + } +} diff --git a/src/main/java/org/alfresco/repo/web/scripts/discussion/ForumTopicsHotGet.java b/src/main/java/org/alfresco/repo/web/scripts/discussion/ForumTopicsHotGet.java new file mode 100644 index 0000000000..579bf91a50 --- /dev/null +++ b/src/main/java/org/alfresco/repo/web/scripts/discussion/ForumTopicsHotGet.java @@ -0,0 +1,118 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.web.scripts.discussion; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; +import org.alfresco.service.cmr.discussion.PostInfo; +import org.alfresco.service.cmr.discussion.TopicInfo; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.site.SiteInfo; +import org.alfresco.util.Pair; +import org.json.simple.JSONObject; +import org.springframework.extensions.webscripts.Cache; +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 discussions topics fetching forum-posts-hot.get webscript. + * + * @author Nick Burch + * @since 4.0 + */ +public class ForumTopicsHotGet extends AbstractDiscussionWebScript +{ + protected static final int RECENT_POSTS_PERIOD_DAYS = 30; + protected static final long ONE_DAY_MS = 24*60*60*1000; + + @Override + protected Map executeImpl(SiteInfo site, NodeRef nodeRef, + TopicInfo topic, PostInfo post, WebScriptRequest req, JSONObject json, + Status status, Cache cache) + { + // They shouldn't be trying to list of an existing Post or Topic + if (topic != null || post != null) + { + String error = "Can't list Topics inside an existing Topic or Post"; + throw new WebScriptException(Status.STATUS_BAD_REQUEST, error); + } + + // Grab the date range to search over + String numDaysS = req.getParameter("numdays"); + int numDays = RECENT_POSTS_PERIOD_DAYS; + if (numDaysS != null) + { + numDays = Integer.parseInt(numDaysS); + } + + Date now = new Date(); + Date since = new Date(now.getTime() - numDays*ONE_DAY_MS); + + // Get the topics with recent replies + PagingResults> topicsAndCounts = null; + PagingRequest paging = buildPagingRequest(req); + if (site != null) + { + topicsAndCounts = discussionService.listHotTopics(site.getShortName(), since, paging); + } + else + { + topicsAndCounts = discussionService.listHotTopics(nodeRef, since, paging); + } + + // For this, we actually only need the topics, not their counts too + List topics = new ArrayList(); + for (Pair tc : topicsAndCounts.getPage()) + { + topics.add(tc.getFirst()); + } + + + // If they did a site based search, and the component hasn't + // been created yet, use the site for the permissions checking + if (site != null && nodeRef == null) + { + nodeRef = site.getNodeRef(); + } + + + // Build the common model parts + Map model = buildCommonModel(site, topic, post, req); + model.put("forum", nodeRef); + + // Have the topics rendered + model.put("data", renderTopics(topics, topicsAndCounts.getTotalResultCount(), paging, site)); + + // All done + return model; + } +} diff --git a/src/main/java/org/alfresco/repo/web/scripts/discussion/ForumTopicsMineGet.java b/src/main/java/org/alfresco/repo/web/scripts/discussion/ForumTopicsMineGet.java new file mode 100644 index 0000000000..175b49042a --- /dev/null +++ b/src/main/java/org/alfresco/repo/web/scripts/discussion/ForumTopicsMineGet.java @@ -0,0 +1,97 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.web.scripts.discussion; + +import java.util.Map; + +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.service.cmr.discussion.PostInfo; +import org.alfresco.service.cmr.discussion.TopicInfo; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.site.SiteInfo; +import org.json.simple.JSONObject; +import org.springframework.extensions.webscripts.Cache; +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 discussions topics fetching forum-posts-mine.get webscript. + * + * @author Nick Burch + * @since 4.0 + */ +public class ForumTopicsMineGet extends AbstractDiscussionWebScript +{ + @Override + protected Map executeImpl(SiteInfo site, NodeRef nodeRef, + TopicInfo topic, PostInfo post, WebScriptRequest req, JSONObject json, + Status status, Cache cache) + { + // They shouldn't be trying to list of an existing Post or Topic + if (topic != null || post != null) + { + String error = "Can't list Topics inside an existing Topic or Post"; + throw new WebScriptException(Status.STATUS_BAD_REQUEST, error); + } + + // Grab the current user to list for + String username = AuthenticationUtil.getFullyAuthenticatedUser(); + + // Get the topics for the user, oldest first + PagingResults topics = null; + PagingRequest paging = buildPagingRequest(req); + if (site != null) + { + topics = discussionService.listTopics(site.getShortName(), username, true, paging); + } + else + { + topics = discussionService.listTopics(nodeRef, username, true, paging); + } + + + // If they did a site based search, and the component hasn't + // been created yet, use the site for the permissions checking + if (site != null && nodeRef == null) + { + nodeRef = site.getNodeRef(); + } + + + // Build the common model parts + Map model = buildCommonModel(site, topic, post, req); + model.put("forum", nodeRef); + + // Have the topics rendered + model.put("data", renderTopics(topics, paging, site)); + + // All done + return model; + } +} diff --git a/src/main/java/org/alfresco/repo/web/scripts/discussion/ForumTopicsRecentGet.java b/src/main/java/org/alfresco/repo/web/scripts/discussion/ForumTopicsRecentGet.java new file mode 100644 index 0000000000..b9ea4a644d --- /dev/null +++ b/src/main/java/org/alfresco/repo/web/scripts/discussion/ForumTopicsRecentGet.java @@ -0,0 +1,109 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.web.scripts.discussion; + +import java.util.Date; +import java.util.Map; + +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; +import org.alfresco.service.cmr.discussion.PostInfo; +import org.alfresco.service.cmr.discussion.TopicInfo; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.site.SiteInfo; +import org.json.simple.JSONObject; +import org.springframework.extensions.webscripts.Cache; +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 discussions topics fetching forum-posts-new.get webscript. + * + * @author Nick Burch + * @since 4.0 + */ +public class ForumTopicsRecentGet extends AbstractDiscussionWebScript +{ + protected static final int RECENT_SEARCH_PERIOD_DAYS = 7; + protected static final long ONE_DAY_MS = 24*60*60*1000; + + @Override + protected Map executeImpl(SiteInfo site, NodeRef nodeRef, + TopicInfo topic, PostInfo post, WebScriptRequest req, JSONObject json, + Status status, Cache cache) + { + // They shouldn't be trying to list of an existing Post or Topic + if (topic != null || post != null) + { + String error = "Can't list Topics inside an existing Topic or Post"; + throw new WebScriptException(Status.STATUS_BAD_REQUEST, error); + } + + // Grab the date range to search over + String numDaysS = req.getParameter("numdays"); + int numDays = RECENT_SEARCH_PERIOD_DAYS; + if (numDaysS != null) + { + numDays = Integer.parseInt(numDaysS); + } + + Date now = new Date(); + Date from = new Date(now.getTime() - numDays*ONE_DAY_MS); + Date to = new Date(now.getTime() + ONE_DAY_MS); + + // Get the recent topics, newest first + PagingResults topics = null; + PagingRequest paging = buildPagingRequest(req); + if (site != null) + { + topics = discussionService.listTopics(site.getShortName(), from, to, false, paging); + } + else + { + topics = discussionService.listTopics(nodeRef, from, to, false, paging); + } + + + // If they did a site based search, and the component hasn't + // been created yet, use the site for the permissions checking + if (site != null && nodeRef == null) + { + nodeRef = site.getNodeRef(); + } + + + // Build the common model parts + Map model = buildCommonModel(site, topic, post, req); + model.put("forum", nodeRef); + + // Have the topics rendered + model.put("data", renderTopics(topics, paging, site)); + + // All done + return model; + } +} diff --git a/src/main/resources/alfresco/blogs/web-scripts-blog-context.xml b/src/main/resources/alfresco/blogs/web-scripts-blog-context.xml new file mode 100644 index 0000000000..c2b0334acf --- /dev/null +++ b/src/main/resources/alfresco/blogs/web-scripts-blog-context.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/alfresco/public-rest-context.xml b/src/main/resources/alfresco/public-rest-context.xml index 064d5d31ae..d5500f0e1a 100644 --- a/src/main/resources/alfresco/public-rest-context.xml +++ b/src/main/resources/alfresco/public-rest-context.xml @@ -328,7 +328,16 @@ - + + + + + + org.alfresco.discussions.reply-created + + + + diff --git a/src/main/resources/alfresco/templates/activities/org/alfresco/blog/post-created.atomentry.ftl b/src/main/resources/alfresco/templates/activities/org/alfresco/blog/post-created.atomentry.ftl new file mode 100644 index 0000000000..86fd85a509 --- /dev/null +++ b/src/main/resources/alfresco/templates/activities/org/alfresco/blog/post-created.atomentry.ftl @@ -0,0 +1,14 @@ +<#include "../slingshot-common.lib.ftl"> + + Blog post created: ${htmlTitle?xml} + + http://www.alfresco.org/rss/atom/${id} + ${xmldate(date)} + + + + + ${userName?xml} + ${userId?xml} + + \ No newline at end of file diff --git a/src/main/resources/alfresco/templates/activities/org/alfresco/blog/post-deleted.atomentry.ftl b/src/main/resources/alfresco/templates/activities/org/alfresco/blog/post-deleted.atomentry.ftl new file mode 100644 index 0000000000..81a56348ec --- /dev/null +++ b/src/main/resources/alfresco/templates/activities/org/alfresco/blog/post-deleted.atomentry.ftl @@ -0,0 +1,14 @@ +<#include "../slingshot-common.lib.ftl"> + + Blog post deleted: ${htmlTitle?xml} + + http://www.alfresco.org/rss/atom/${id} + ${xmldate(date)} + + + + + ${userName?xml} + ${userId?xml} + + \ No newline at end of file diff --git a/src/main/resources/alfresco/templates/activities/org/alfresco/blog/post-updated.atomentry.ftl b/src/main/resources/alfresco/templates/activities/org/alfresco/blog/post-updated.atomentry.ftl new file mode 100644 index 0000000000..8c9cc18456 --- /dev/null +++ b/src/main/resources/alfresco/templates/activities/org/alfresco/blog/post-updated.atomentry.ftl @@ -0,0 +1,14 @@ +<#include "../slingshot-common.lib.ftl"> + + Blog post updated: ${htmlTitle?xml} + + http://www.alfresco.org/rss/atom/${id} + ${xmldate(date)} + + + + + ${userName?xml} + ${userId?xml} + + \ No newline at end of file diff --git a/src/main/resources/alfresco/templates/activities/org/alfresco/discussions/post-created.atomentry.ftl b/src/main/resources/alfresco/templates/activities/org/alfresco/discussions/post-created.atomentry.ftl new file mode 100644 index 0000000000..b6c4583005 --- /dev/null +++ b/src/main/resources/alfresco/templates/activities/org/alfresco/discussions/post-created.atomentry.ftl @@ -0,0 +1,14 @@ +<#include "../slingshot-common.lib.ftl"> + + New discussion: ${htmlTitle?xml} + + http://www.alfresco.org/rss/atom/${id} + ${xmldate(date)} + + + + + ${userName?xml} + ${userId?xml} + + \ No newline at end of file diff --git a/src/main/resources/alfresco/templates/activities/org/alfresco/discussions/post-deleted.atomentry.ftl b/src/main/resources/alfresco/templates/activities/org/alfresco/discussions/post-deleted.atomentry.ftl new file mode 100644 index 0000000000..013510cacb --- /dev/null +++ b/src/main/resources/alfresco/templates/activities/org/alfresco/discussions/post-deleted.atomentry.ftl @@ -0,0 +1,14 @@ +<#include "../slingshot-common.lib.ftl"> + + Discussion deleted: ${htmlTitle?xml} + + http://www.alfresco.org/rss/atom/${id} + ${xmldate(date)} + + + + + ${userName?xml} + ${userId?xml} + + \ No newline at end of file diff --git a/src/main/resources/alfresco/templates/activities/org/alfresco/discussions/post-updated.atomentry.ftl b/src/main/resources/alfresco/templates/activities/org/alfresco/discussions/post-updated.atomentry.ftl new file mode 100644 index 0000000000..097dab3bdd --- /dev/null +++ b/src/main/resources/alfresco/templates/activities/org/alfresco/discussions/post-updated.atomentry.ftl @@ -0,0 +1,14 @@ +<#include "../slingshot-common.lib.ftl"> + + Discussion updated: ${htmlTitle?xml} + + http://www.alfresco.org/rss/atom/${id} + ${xmldate(date)} + + + + + ${userName?xml} + ${userId?xml} + + \ No newline at end of file diff --git a/src/main/resources/alfresco/templates/activities/org/alfresco/discussions/reply-created.atomentry.ftl b/src/main/resources/alfresco/templates/activities/org/alfresco/discussions/reply-created.atomentry.ftl new file mode 100644 index 0000000000..2c062fcdb5 --- /dev/null +++ b/src/main/resources/alfresco/templates/activities/org/alfresco/discussions/reply-created.atomentry.ftl @@ -0,0 +1,14 @@ +<#include "../slingshot-common.lib.ftl"> + + Reply added to ${(htmlTitle!'')?html?xml} + + http://www.alfresco.org/rss/atom/${id} + ${xmldate(date)} + + + + + ${userName?xml} + ${userId?xml} + + \ No newline at end of file diff --git a/src/main/resources/alfresco/templates/activities/org/alfresco/discussions/reply-updated.atomentry.ftl b/src/main/resources/alfresco/templates/activities/org/alfresco/discussions/reply-updated.atomentry.ftl new file mode 100644 index 0000000000..25d4c3503c --- /dev/null +++ b/src/main/resources/alfresco/templates/activities/org/alfresco/discussions/reply-updated.atomentry.ftl @@ -0,0 +1,14 @@ +<#include "../slingshot-common.lib.ftl"> + + Reply to ${(htmlTitle!'')?html?xml} updated + + http://www.alfresco.org/rss/atom/${id} + ${xmldate(date)} + + + + + ${userName?xml} + ${userId?xml} + + \ No newline at end of file diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/blog.lib.ftl b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/blog.lib.ftl new file mode 100644 index 0000000000..f5e782617c --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/blog.lib.ftl @@ -0,0 +1,31 @@ +<#-- + This template renders the blog data object. +--> +<#macro blogJSON item> +<#escape x as jsonUtils.encodeJSONString(x)> +{ + "qnamePath": "${item.qnamePath}", + "detailsUrl": "blog/node/${item.nodeRef?replace('://', '/')}", + "blogPostsUrl": "blog/node/${item.nodeRef?replace('://', '/')}/posts", + "type": "${item.properties["blg:blogImplementation"]!''}", + "id": "${item.properties["blg:id"]!'0'}", + "name": "${item.properties["blg:name"]!''}", + "description": "${item.properties["blg:description"]!''}", + "url": "${item.properties["blg:url"]!''}", + "username": "${item.properties["blg:userName"]!''}", + "password": "${item.properties["blg:password"]!''}", + "permissions": + { + <#if item.getParent()?? && item.getTypeShort() != "st:site" > + "create": ${(item.getParent()).hasPermission("CreateChildren")?string}, + "edit": ${(item.getParent()).hasPermission("Write")?string}, + "delete": ${(item.getParent()).hasPermission("Delete")?string} + <#else> + "create": ${item.hasPermission("CreateChildren")?string}, + "edit": ${item.hasPermission("Write")?string}, + "delete": ${item.hasPermission("Delete")?string} + + } +} + + diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/blog.lib.js b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/blog.lib.js new file mode 100644 index 0000000000..56f265d812 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/blog.lib.js @@ -0,0 +1,49 @@ + +/** Name of the blog details aspect. */ +const BLOG_DETAILS_ASPECT = "blg:blogDetails"; + +/** + * Fetches the blog properties from the json object and adds them to an array + * using the correct property names as indexes. + */ +function getBlogPropertiesArray() +{ + var arr = new Array(); + if (json.has("blogType")) + { + arr["blg:blogImplementation"] = json.get("blogType"); + } + if (json.has("blogId")) + { + arr["blg:id"] = json.get("blogId"); + } + if (json.has("blogName")) + { + arr["blg:name"] = json.get("blogName"); + } + if (json.has("blogDescription")) + { + arr["blg:description"] = json.get("blogDescription"); + } + if (json.has("blogUrl")) + { + arr["blg:url"] = json.get("blogUrl"); + } + if (json.has("username")) + { + arr["blg:userName"] = json.get("username"); + } + if (json.has("password")) + { + arr["blg:password"] = json.get("password"); + } + return arr; +} + +/** + * Returns the data object of a blog node. + */ +function getBlogData(node) +{ + return node; +} diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/blog/blog.get.desc.xml b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/blog/blog.get.desc.xml new file mode 100644 index 0000000000..318ccfff7d --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/blog/blog.get.desc.xml @@ -0,0 +1,11 @@ + + Get blog + Get the blog information. + /api/blog/site/{site}/{container}/{path} + /api/blog/site/{site}/{container} + /api/blog/node/{store_type}/{store_id}/{id} + argument + user + required + limited_support + diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/blog/blog.get.json.ftl b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/blog/blog.get.json.ftl new file mode 100644 index 0000000000..cc1616611b --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/blog/blog.get.json.ftl @@ -0,0 +1,4 @@ +<#import "../blog.lib.ftl" as blogLib/> +{ + "item" : <@blogLib.blogJSON item=item /> +} diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/blog/blog.put.desc.xml b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/blog/blog.put.desc.xml new file mode 100644 index 0000000000..c77db3c4d3 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/blog/blog.put.desc.xml @@ -0,0 +1,11 @@ + + Put blog + Updates a blog. + /api/blog/site/{site}/{container}/{path} + /api/blog/site/{site}/{container} + /api/blog/node/{store_type}/{store_id}/{id} + argument + user + required + limited_support + \ No newline at end of file diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/blog/blog.put.json.ftl b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/blog/blog.put.json.ftl new file mode 100644 index 0000000000..cc1616611b --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/blog/blog.put.json.ftl @@ -0,0 +1,4 @@ +<#import "../blog.lib.ftl" as blogLib/> +{ + "item" : <@blogLib.blogJSON item=item /> +} diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/blogpost.lib.ftl b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/blogpost.lib.ftl new file mode 100644 index 0000000000..e56121b762 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/blogpost.lib.ftl @@ -0,0 +1,116 @@ +<#import "../generic-paged-results.lib.ftl" as gen/> + +<#-- Renders a person object. --> +<#macro renderPerson person fieldName> +<#escape x as jsonUtils.encodeJSONString(x)> + "${fieldName}": + { + <#if person.assocs["cm:avatar"]??> + "avatarRef": "${person.assocs["cm:avatar"][0].nodeRef?string}", + + "username": "${person.properties["cm:userName"]}", + "firstName": "${person.properties["cm:firstName"]!""}", + "lastName": "${person.properties["cm:lastName"]!""}" + }, + + + + +<#macro addContent item> +<#escape x as jsonUtils.encodeJSONString(x)> + <#assign safecontent=stringUtils.stripUnsafeHTML(item.node.content)> + <#if (contentLength?? && contentLength > -1 && (safecontent?length > contentLength))> + "content": "${safecontent?substring(0, contentLength)}", + <#else> + "content": "${safecontent}", + + + + +<#-- + This template renders a blog post. +--> +<#macro blogpostJSON item> +<#escape x as jsonUtils.encodeJSONString(x)> +{ + "url": "blog/post/node/${item.node.nodeRef?replace('://','/')}", + "commentsUrl": "/node/${item.node.nodeRef?replace('://','/')}/comments", + "nodeRef": "${item.node.nodeRef}", + "name": "${item.node.properties.name!''}", + "title": "${item.node.properties.title!''}", + <@addContent item=item /> + <#if item.author??> + <@renderPerson person=item.author fieldName="author" /> + <#else> + "author": + { + "username": "${item.node.properties.creator}" + }, + + "createdOn": "${formatDateRFC822(item.createdDate)}", + "modifiedOn": "${formatDateRFC822(item.modifiedDate)}", + "permissions": + { + "edit": ${item.node.hasPermission("Write")?string}, + "delete": ${item.node.hasPermission("Delete")?string} + }, + "commentCount": ${item.commentCount?c}, + "tags": [<#list item.tags as x>"${x}"<#if x_has_next>, ], + <#-- draft vs internal published --> + "isDraft": ${item.isDraft?string}, + <#if (! item.isDraft)> + "releasedOn": "${formatDateRFC822(item.releasedDate)}", + + <#-- true if the post has been updated --> + "isUpdated": ${item.isUpdated?string}, + <#if (item.isUpdated)> + "updatedOn": "${formatDateRFC822(item.updatedDate)}", + + <#if (item.node.properties["blg:published"]?? && item.node.properties["blg:published"] == true)> + "publishedOn": "${formatDateRFC822(item.node.properties["blg:posted"])}", + "updatedOn": "${formatDateRFC822(item.node.properties["blg:lastUpdate"])}", + "postId": "${item.node.properties["blg:postId"]!''}", + "postLink": "${item.node.properties["blg:link"]!''}", + "outOfDate": ${item.outOfDate?string}, + + <#-- external publishing - last to make sure that we correctly end the response without a comma --> + "isPublished": ${(item.node.properties["blg:published"]!'false')?string} +} + + + +<#macro renderPostList> +{ + "metadata": + { + "blogPermissions": + { + "create": ${blog.hasPermission("CreateChildren")?string}, + "edit": ${blog.hasPermission("Write")?string}, + "delete": ${blog.hasPermission("Delete")?string} + }, + "externalBlogConfig": ${externalBlogConfig?string} + }, + "totalRecordsUpper": ${data.totalRecordsUpper?string("true","false")}, +<@gen.pagedResults data=data ; item> + <@blogpostJSON item=item /> + +} + + +<#macro renderPost> +{ + "metadata": + { + "externalBlogConfig": ${externalBlogConfig?string} + }, + "item": <@blogpostJSON item=item /> +} + + +<#function formatDateRFC822 dateItem> + <# local temp=${.locale} --> + <#setting locale="en_US"> + <#return dateItem?datetime?string("EEE, d MMM yyyy HH:mm:ss Z")> + <# setting locale=temp --> + \ No newline at end of file diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/blogpost.lib.js b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/blogpost.lib.js new file mode 100644 index 0000000000..3fd174e74d --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/blogpost.lib.js @@ -0,0 +1,130 @@ + + +const ASPECT_SYNDICATION = "cm:syndication"; +const PROP_PUBLISHED = "cm:published"; +const PROP_UPDATED = "cm:updated"; + +function setOrUpdateReleasedAndUpdatedDates(node) +{ + // make sure the syndication aspect has been added + if (!node.hasAspect(ASPECT_SYNDICATION)) + { + node.addAspect(ASPECT_SYNDICATION, []); + } + + // (re-)enable permission inheritance which got disable for draft posts + // only set if was previously draft - as only the owner/admin can do this + if (!node.inheritsPermissions()) + { + // MNT-12082 + node.removePermission("All", node.getOwner()); + node.setInheritsPermissions(true); + } + + // check whether the published date has been set + if (!node.properties[PROP_PUBLISHED]) + { + // set the published date + node.properties[PROP_PUBLISHED] = new Date(); + node.save(); + } + else + { + // set/update the updated date + node.properties[PROP_UPDATED] = new Date(); + node.save(); + } +} + +/** + * Returns the data of a blog post. + */ +function getBlogPostData(node) +{ + var data = {}; + data.node = node; + data.author = people.getPerson(node.properties["cm:creator"]); + data.commentCount = getCommentsCount(node); + + // is the post published + var isPublished = (node.properties[PROP_PUBLISHED] !== null); + if (isPublished) + { + data.releasedDate = node.properties[PROP_PUBLISHED]; + } + + // draft + data.isDraft = ! isPublished; + + // set the isUpdated flag + var isUpdated = (node.properties[PROP_UPDATED] !== null); + data.isUpdated = isUpdated; + if (isUpdated) + { + data.updatedDate = node.properties[PROP_UPDATED]; + } + + // fetch standard created/modified dates + data.createdDate = node.properties["cm:created"]; + data.modifiedDate = node.properties["cm:modified"]; + + // does the external post require an update? + if (isPublished && (node.properties["blg:lastUpdate"] !== null)) + { + // we either use the release or updated date + var modifiedDate = data.releasedDate; + if (isUpdated) + { + modifiedDate = data.updatedDate; + } + + if ((modifiedDate - node.properties["blg:lastUpdate"]) > 5000) + { + data.outOfDate = true; + } + else + { + data.outOfDate = false; + } + } + else + { + data.outOfDate = false; + } + + // tags + if (node.tags !== null) + { + data.tags = node.tags; + } + else + { + data.tags = []; + } + + return data; +} + +/** + * Checks whether a blog configuration is available + * This should at some point also check whether the configuration is enabled. + * + * @param node the node that should be checked. Will check all parents if + * the node itself doesn't contain a configuration. + * @return {boolean} whether a configuration could be found. + */ +function hasExternalBlogConfiguration(node) +{ + if (node === null || !node.hasPermission("ReadProperties")) + { + return false; + } + else if (node.hasAspect("blg:blogDetails")) + { + return true; + } + else + { + return hasExternalBlogConfiguration(node.parent); + } +} \ No newline at end of file diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete.desc.xml b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete.desc.xml new file mode 100644 index 0000000000..2b6aeb36f7 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete.desc.xml @@ -0,0 +1,10 @@ + + Delete blog post + Deletes a blog post. + /api/blog/post/site/{site}/{container}/{path} + /api/blog/post/node/{store_type}/{store_id}/{id} + argument + user + required + limited_support + \ No newline at end of file diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete.json.ftl b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete.json.ftl new file mode 100644 index 0000000000..9a22868515 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete.json.ftl @@ -0,0 +1,3 @@ +{ + "message" : "${message}" +} \ No newline at end of file diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete.properties b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete.properties new file mode 100644 index 0000000000..474e147054 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete.properties @@ -0,0 +1 @@ +blog-post.msg.deleted=Blog {0} deleted diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete_de.properties b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete_de.properties new file mode 100755 index 0000000000..7ac89a6a3a --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete_de.properties @@ -0,0 +1 @@ +blog-post.msg.deleted=Blog {0} gel\u00f6scht diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete_es.properties b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete_es.properties new file mode 100755 index 0000000000..5aadf01515 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete_es.properties @@ -0,0 +1 @@ +blog-post.msg.deleted=Blog {0} eliminado diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete_fr.properties b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete_fr.properties new file mode 100755 index 0000000000..a9cc3794ee --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete_fr.properties @@ -0,0 +1 @@ +blog-post.msg.deleted=Blog {0} supprim\u00e9 diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete_it.properties b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete_it.properties new file mode 100755 index 0000000000..528bb1f41e --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete_it.properties @@ -0,0 +1 @@ +blog-post.msg.deleted=Blog {0} eliminato diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete_ja.properties b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete_ja.properties new file mode 100755 index 0000000000..29448bdd3e --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete_ja.properties @@ -0,0 +1 @@ +blog-post.msg.deleted=\u30d6\u30ed\u30b0 "{0}" \u304c\u524a\u9664\u3055\u308c\u307e\u3057\u305f diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete_nb.properties b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete_nb.properties new file mode 100755 index 0000000000..61ea5282e3 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete_nb.properties @@ -0,0 +1 @@ +blog-post.msg.deleted=Bloggen {0} ble slettet diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete_nl.properties b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete_nl.properties new file mode 100755 index 0000000000..e391da0445 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete_nl.properties @@ -0,0 +1 @@ +blog-post.msg.deleted=Blog {0} verwijderd diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete_pt_BR.properties b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete_pt_BR.properties new file mode 100644 index 0000000000..764ef85137 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete_pt_BR.properties @@ -0,0 +1 @@ +blog-post.msg.deleted=Blog {0} exclu\u00eddo diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete_ru.properties b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete_ru.properties new file mode 100755 index 0000000000..d42a016a30 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete_ru.properties @@ -0,0 +1 @@ +blog-post.msg.deleted=\u0411\u043b\u043e\u0433 {0} \u0443\u0434\u0430\u043b\u0435\u043d diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete_zh_CN.properties b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete_zh_CN.properties new file mode 100755 index 0000000000..6cd3c84da7 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.delete_zh_CN.properties @@ -0,0 +1 @@ +blog-post.msg.deleted=\u5df2\u5220\u9664\u535a\u5ba2 {0} diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.get.desc.xml b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.get.desc.xml new file mode 100644 index 0000000000..1f95700778 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.get.desc.xml @@ -0,0 +1,10 @@ + + Get all blogs + Gets all blogs. + /api/blog/post/site/{site}/{container}/{path} + /api/blog/post/node/{store_type}/{store_id}/{id} + argument + user + required + limited_support + diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.get.json.ftl b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.get.json.ftl new file mode 100644 index 0000000000..709882b67f --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.get.json.ftl @@ -0,0 +1,2 @@ +<#import "../blogpost.lib.ftl" as blogpostLib/> +<@blogpostLib.renderPost /> \ No newline at end of file diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.put.desc.xml b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.put.desc.xml new file mode 100644 index 0000000000..d5e377c767 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.put.desc.xml @@ -0,0 +1,10 @@ + + Update blog post + Updates a blog post. + /api/blog/post/site/{site}/{container}/{path} + /api/blog/post/node/{store_type}/{store_id}/{id} + argument + user + required + limited_support + \ No newline at end of file diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.put.json.ftl b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.put.json.ftl new file mode 100644 index 0000000000..709882b67f --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.put.json.ftl @@ -0,0 +1,2 @@ +<#import "../blogpost.lib.ftl" as blogpostLib/> +<@blogpostLib.renderPost /> \ No newline at end of file diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.put.json.js b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.put.json.js new file mode 100644 index 0000000000..4c52114aba --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/post/blog-post.put.json.js @@ -0,0 +1,91 @@ + + + +/** + * Updates the draft mode part of the post + */ +function updateBlogPostDraftMode(postNode) +{ + // make sure the user doesn't try to put a non-draft + // post back into draft node + var currentDraft = (postNode.properties[PROP_PUBLISHED] == undefined); + var isDraft = json.has("draft") && json.get("draft").toString() == "true"; + + // requested draft, previously non-draft: throw an exception + if (isDraft && !currentDraft) + { + // set an error + status.setCode(status.STATUS_BAD_REQUEST, "Cannot put a published post back into draft mode"); + return null; + } + + if (!isDraft) + { + setOrUpdateReleasedAndUpdatedDates(postNode); + } +} + +/** + * Updates a blog post node + */ +function updateBlogPost(postNode) +{ + // fetch the new data + var title = ""; + if (json.has("title")) + { + title = json.get("title"); + } + var content = ""; + if (json.has("content")) + { + content = json.get("content"); + } + var tags = []; + if (json.has("tags")) + { + // get the tags JSONArray and copy it into a real javascript array object + var tmp = json.get("tags"); + for (var x=0; x < tmp.length(); x++) + { + tags.push(tmp.get(x)); + } + } + + // update the node + postNode.properties["cm:title"] = title; + postNode.mimetype = "text/html"; + postNode.content = content + postNode.tags = tags; + postNode.save(); + + updateBlogPostDraftMode(postNode); +} + +function main() +{ + // get requested node + var node = getRequestNode(); + if (status.getCode() != status.STATUS_OK) + { + return; + } + + // update blog post + updateBlogPost(node); + + model.item = getBlogPostData(node); + model.externalBlogConfig = hasExternalBlogConfiguration(node); + + if (json.has("site") && json.has("container") && json.has("page") && !model.item.isDraft) + { + var data = + { + title: model.item.node.properties.title, + page: json.get("page") + "?postId=" + model.item.node.properties.name + } + activities.postActivity("org.alfresco.blog.post-updated", json.get("site"), "blog", jsonUtils.toJSONString(data)); + } +} + +main(); diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/posts/blog-posts-mydrafts.get.desc.xml b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/posts/blog-posts-mydrafts.get.desc.xml new file mode 100644 index 0000000000..592b003de7 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/posts/blog-posts-mydrafts.get.desc.xml @@ -0,0 +1,11 @@ + + Get blog posts (draft) + Gets all draft posts for a blog. + /api/blog/site/{site}/{container}/{path}/posts/mydrafts + /api/blog/site/{site}/{container}/posts/mydrafts + /api/blog/node/{store_type}/{store_id}/{id}/posts/mydrafts + argument + user + required + limited_support + diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/posts/blog-posts-mydrafts.get.json.ftl b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/posts/blog-posts-mydrafts.get.json.ftl new file mode 100644 index 0000000000..8c931b77a3 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/posts/blog-posts-mydrafts.get.json.ftl @@ -0,0 +1,2 @@ +<#import "../blogpost.lib.ftl" as blogpostLib/> +<@blogpostLib.renderPostList /> diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/posts/blog-posts-mypublished.get.desc.xml b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/posts/blog-posts-mypublished.get.desc.xml new file mode 100644 index 0000000000..34a4911d09 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/posts/blog-posts-mypublished.get.desc.xml @@ -0,0 +1,11 @@ + + Get blog posts (published) + Gets all published posts for a blog. + /api/blog/site/{site}/{container}/{path}/posts/mypublished + /api/blog/site/{site}/{container}/posts/mypublished + /api/blog/node/{store_type}/{store_id}/{id}/posts/mypublished + argument + user + required + limited_support + diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/posts/blog-posts-mypublished.get.json.ftl b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/posts/blog-posts-mypublished.get.json.ftl new file mode 100644 index 0000000000..b955ab5451 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/posts/blog-posts-mypublished.get.json.ftl @@ -0,0 +1,2 @@ +<#import "../blogpost.lib.ftl" as blogpostLib/> +<@blogpostLib.renderPostList /> \ No newline at end of file diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/posts/blog-posts-new.get.desc.xml b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/posts/blog-posts-new.get.desc.xml new file mode 100644 index 0000000000..e113d6cd6b --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/posts/blog-posts-new.get.desc.xml @@ -0,0 +1,11 @@ + + Get blog posts for a number of days + Get all posts for a blog that were created within the specified number of days. + /api/blog/site/{site}/{container}/{path}/posts/new?numdays={numdays} + /api/blog/site/{site}/{container}/posts/new?numdays={numdays} + /api/blog/node/{store_type}/{store_id}/{id}/posts/new?numdays={numdays} + argument + user + required + limited_support + diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/posts/blog-posts-new.get.json.ftl b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/posts/blog-posts-new.get.json.ftl new file mode 100644 index 0000000000..b955ab5451 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/posts/blog-posts-new.get.json.ftl @@ -0,0 +1,2 @@ +<#import "../blogpost.lib.ftl" as blogpostLib/> +<@blogpostLib.renderPostList /> \ No newline at end of file diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/posts/blog-posts-per-month.get.desc.xml b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/posts/blog-posts-per-month.get.desc.xml new file mode 100644 index 0000000000..78989e4b9f --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/posts/blog-posts-per-month.get.desc.xml @@ -0,0 +1,11 @@ + + Get blog posts per month + Gets all months for which there are blog posts plus the number of posts in each. + /api/blog/site/{site}/{container}/{path}/postspermonth + /api/blog/site/{site}/{container}/postspermonth + /api/blog/node/{store_type}/{store_id}/{id}/postspermonth + argument + user + required + limited_support + diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/posts/blog-posts-per-month.get.json.ftl b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/posts/blog-posts-per-month.get.json.ftl new file mode 100644 index 0000000000..decf31b8dd --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/posts/blog-posts-per-month.get.json.ftl @@ -0,0 +1,17 @@ +<#escape x as jsonUtils.encodeJSONString(x)> +{ + "items" : [ + <#list data as item> + { + "firstPostInMonth" : "${item.firstPostInMonth?string("MMM dd yyyy HH:mm:ss 'GMT'Z '('zzz')'")}", + "beginOfMonth" : "${item.beginOfMonth?string("MMM dd yyyy HH:mm:ss 'GMT'Z '('zzz')'")}", + "endOfMonth" : "${item.endOfMonth?string("MMM dd yyyy HH:mm:ss 'GMT'Z '('zzz')'")}", + "year" : ${item.year?c}, + "month" : ${item.month?c}, <#-- Note: January --> + "postCount" : ${item.count?c} + } + <#if item_has_next>, + + ] +} + diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/posts/blog-posts.get.desc.xml b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/posts/blog-posts.get.desc.xml new file mode 100644 index 0000000000..66475883ae --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/posts/blog-posts.get.desc.xml @@ -0,0 +1,11 @@ + + Get blog posts + Gets all posts for a blog. + /api/blog/site/{site}/{container}/{path}/posts + /api/blog/site/{site}/{container}/posts + /api/blog/node/{store_type}/{store_id}/{id}/posts + argument + user + required + limited_support + diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/posts/blog-posts.get.json.ftl b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/posts/blog-posts.get.json.ftl new file mode 100644 index 0000000000..b955ab5451 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/posts/blog-posts.get.json.ftl @@ -0,0 +1,2 @@ +<#import "../blogpost.lib.ftl" as blogpostLib/> +<@blogpostLib.renderPostList /> \ No newline at end of file diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/posts/blog-posts.post.desc.xml b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/posts/blog-posts.post.desc.xml new file mode 100644 index 0000000000..f29672a29f --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/posts/blog-posts.post.desc.xml @@ -0,0 +1,11 @@ + + Create blog post + Creates a new blog post. + /api/blog/site/{site}/{container}/{path}/posts + /api/blog/site/{site}/{container}/posts + /api/blog/node/{store_type}/{store_id}/{id}/posts + argument + user + required + limited_support + \ No newline at end of file diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/posts/blog-posts.post.json.ftl b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/posts/blog-posts.post.json.ftl new file mode 100644 index 0000000000..709882b67f --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/blogs/posts/blog-posts.post.json.ftl @@ -0,0 +1,2 @@ +<#import "../blogpost.lib.ftl" as blogpostLib/> +<@blogpostLib.renderPost /> \ No newline at end of file diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts-filtered.get.desc.xml b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts-filtered.get.desc.xml new file mode 100644 index 0000000000..ea7bd1ff06 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts-filtered.get.desc.xml @@ -0,0 +1,10 @@ + + Get filtered forum posts + Get the discussion topics that match the filters. + /api/forum/site/{site}/discussions/posts/filtered + /api/forum/discussions/posts/filtered + argument + user + required + limited_support + diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts-filtered.get.json.ftl b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts-filtered.get.json.ftl new file mode 100644 index 0000000000..f338b40df2 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts-filtered.get.json.ftl @@ -0,0 +1,19 @@ +<#import "../post.lib.ftl" as postLib/> +<#import "../../generic-paged-results.lib.ftl" as gen/> +{ + "forumPermissions": + { + <#if forum??> + "create": ${forum.hasPermission("CreateChildren")?string}, + "edit": ${forum.hasPermission("Write")?string}, + "delete": ${forum.hasPermission("Delete")?string} + <#else> + "create": false, + "edit": false, + "delete": false + + }, +<@gen.pagedResults data=data ; item> + <@postLib.postJSON postData=item /> + +} \ No newline at end of file diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts-hot.get.desc.xml b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts-hot.get.desc.xml new file mode 100644 index 0000000000..910f5fe038 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts-hot.get.desc.xml @@ -0,0 +1,11 @@ + + Get hot forum posts + Get the hot topics in the forum. + /api/forum/site/{site}/{container}/{path}/posts/hot + /api/forum/site/{site}/{container}/posts/hot + /api/forum/node/{store_type}/{store_id}/{id}/posts/hot + argument + user + required + limited_support + diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts-hot.get.json.ftl b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts-hot.get.json.ftl new file mode 100644 index 0000000000..d60a684a3e --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts-hot.get.json.ftl @@ -0,0 +1,13 @@ +<#import "../post.lib.ftl" as postLib/> +<#import "../../generic-paged-results.lib.ftl" as gen/> +{ + "forumPermissions": + { + "create": ${forum.hasPermission("CreateChildren")?string}, + "edit": ${forum.hasPermission("Write")?string}, + "delete": ${forum.hasPermission("Delete")?string} + }, +<@gen.pagedResults data=data ; item> + <@postLib.postJSON postData=item /> + +} diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts-mine.get.desc.xml b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts-mine.get.desc.xml new file mode 100644 index 0000000000..ef849922f0 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts-mine.get.desc.xml @@ -0,0 +1,11 @@ + + Get my forum posts + Gets the forum posts created by the current user. + /api/forum/site/{site}/{container}/{path}/posts/myposts + /api/forum/site/{site}/{container}/posts/myposts + /api/forum/node/{store_type}/{store_id}/{id}/posts/myposts + argument + user + required + limited_support + diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts-mine.get.json.ftl b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts-mine.get.json.ftl new file mode 100644 index 0000000000..d60a684a3e --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts-mine.get.json.ftl @@ -0,0 +1,13 @@ +<#import "../post.lib.ftl" as postLib/> +<#import "../../generic-paged-results.lib.ftl" as gen/> +{ + "forumPermissions": + { + "create": ${forum.hasPermission("CreateChildren")?string}, + "edit": ${forum.hasPermission("Write")?string}, + "delete": ${forum.hasPermission("Delete")?string} + }, +<@gen.pagedResults data=data ; item> + <@postLib.postJSON postData=item /> + +} diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts-new.get.desc.xml b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts-new.get.desc.xml new file mode 100644 index 0000000000..629218eb72 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts-new.get.desc.xml @@ -0,0 +1,11 @@ + + Get new forum posts + Gets the forum posts for the last specified number of days. + /api/forum/site/{site}/{container}/{path}/posts/new?numdays={numdays} + /api/forum/site/{site}/{container}/posts/new?numdays={numdays} + /api/forum/node/{store_type}/{store_id}/{id}/posts/new?numdays={numdays} + argument + user + required + limited_support + diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts-new.get.json.ftl b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts-new.get.json.ftl new file mode 100644 index 0000000000..d60a684a3e --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts-new.get.json.ftl @@ -0,0 +1,13 @@ +<#import "../post.lib.ftl" as postLib/> +<#import "../../generic-paged-results.lib.ftl" as gen/> +{ + "forumPermissions": + { + "create": ${forum.hasPermission("CreateChildren")?string}, + "edit": ${forum.hasPermission("Write")?string}, + "delete": ${forum.hasPermission("Delete")?string} + }, +<@gen.pagedResults data=data ; item> + <@postLib.postJSON postData=item /> + +} diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts.get.desc.xml b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts.get.desc.xml new file mode 100644 index 0000000000..272aa32e82 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts.get.desc.xml @@ -0,0 +1,11 @@ + + Get forum posts + Gets the forum posts. + /api/forum/site/{site}/{container}/{path}/posts + /api/forum/site/{site}/{container}/posts + /api/forum/node/{store_type}/{store_id}/{id}/posts + argument + user + required + limited_support + \ No newline at end of file diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts.get.json.ftl b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts.get.json.ftl new file mode 100644 index 0000000000..6ba3e8ae02 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts.get.json.ftl @@ -0,0 +1,19 @@ +<#import "../post.lib.ftl" as postLib/> +<#import "../../generic-paged-results.lib.ftl" as gen/> +{ + "forumPermissions": + { + <#if forum.getParent()?? && forum.getTypeShort() != "st:site" > + "create": ${(forum.getParent()).hasPermission("CreateChildren")?string}, + "edit": ${(forum.getParent()).hasPermission("Write")?string}, + "delete": ${(forum.getParent()).hasPermission("Delete")?string} + <#else> + "create": ${forum.hasPermission("CreateChildren")?string}, + "edit": ${forum.hasPermission("Write")?string}, + "delete": ${forum.hasPermission("Delete")?string} + + }, +<@gen.pagedResults data=data ; item> + <@postLib.postJSON postData=item /> + +} diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts.post.desc.xml b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts.post.desc.xml new file mode 100644 index 0000000000..94aed0bf27 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts.post.desc.xml @@ -0,0 +1,11 @@ + + Add forum post + Adds a post to a forum. + /api/forum/site/{site}/{container}/{path}/posts + /api/forum/site/{site}/{container}/posts + /api/forum/node/{store_type}/{store_id}/{id}/posts + argument + user + required + limited_support + \ No newline at end of file diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts.post.json.ftl b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts.post.json.ftl new file mode 100644 index 0000000000..a3cd1be869 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts.post.json.ftl @@ -0,0 +1,4 @@ +<#import "../post.lib.ftl" as postLib /> +{ + "item": <@postLib.postJSON postData=postData /> +} \ No newline at end of file diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/post.lib.ftl b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/post.lib.ftl new file mode 100644 index 0000000000..b48ca0a66d --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/post.lib.ftl @@ -0,0 +1,124 @@ +<#-- Renders a person object. --> +<#macro renderPerson person fieldName> +<#escape x as jsonUtils.encodeJSONString(x)> + "${fieldName}": + { + <#if person?has_content> + <#if person.assocs["cm:avatar"]??> + "avatarRef": "${person.assocs["cm:avatar"][0].nodeRef?string}", + + "username": "${person.properties["cm:userName"]}", + "firstName": "${person.properties["cm:firstName"]!""}", + "lastName": "${person.properties["cm:lastName"]!""}" + + }, + + + + +<#macro addContent post> +<#escape x as jsonUtils.encodeJSONString(x)> + <#assign safecontent=stringUtils.stripUnsafeHTML(post.content)> + <#if (contentLength?? && contentLength > -1 && (safecontent?length > contentLength))> + "content": "${safecontent?substring(0, contentLength)}", + <#else> + "content": "${safecontent}", + + + + + +<#macro postJSON postData> +{ + <@postDataJSON postData=postData /> +} + + +<#macro postDataJSON postData> +<#escape x as jsonUtils.encodeJSONString(x)> + <#-- which node should be used for urls? which for the post data? --> + <#if postData.isTopicPost> + <#assign refNode=postData.topic /> + <#else> + <#assign refNode=postData.post /> + + + <#assign post=postData.post /> + + <#-- render topic post only data first --> + <#if postData.isTopicPost> + "name": "${postData.topic.name}", + "totalReplyCount": ${postData.totalReplyCount?c}, + <#if postData.lastReply??> + "lastReplyOn": "${xmldate(postData.lastReply.properties.created)}", + <#if postData.lastReplyBy??> + <@renderPerson person=postData.lastReplyBy fieldName="lastReplyBy" /> + <#else> + "lastReplyBy": + { + "username": "" + }, + + + "tags": [<#list postData.tags as x>"${x}"<#if x_has_next>, ], + "site": "${postData.site!""}", + <#else> + "name": "${post.name}", + + + <#-- data using refNode which might be the topic or the post node --> + "url": "/forum/post/node/${refNode.nodeRef.storeRef.protocol}/${refNode.nodeRef.storeRef.identifier}/${refNode.nodeRef.id}", + "repliesUrl": "/forum/post/node/${refNode.nodeRef.storeRef.protocol}/${refNode.nodeRef.storeRef.identifier}/${refNode.nodeRef.id}/replies", + "nodeRef": "${refNode.nodeRef}", + + <#-- data coming from the post node --> + "title": "${(post.properties.title!"")}", + "createdOn": "${xmldate(post.properties.created)}", + "modifiedOn": "${xmldate(post.properties.modified)}", + <#if (post.properties["cm:updated"]??)> + "isUpdated": true, + "updatedOn": "${xmldate(post.properties["cm:updated"])}", + <#else> + "isUpdated": false, + + <#if postData.author?? && postData.author?has_content> + <@renderPerson person=postData.author fieldName="author" /> + <#else> + "author": + { + "username": "${post.properties["cm:creator"]}" + }, + + <@addContent post=post /> + "replyCount": <#if post.sourceAssocs["cm:references"]??>${post.sourceAssocs["cm:references"]?size?c}<#else>0, + "permissions": + { + "edit": ${postData.canEdit?string}, + "reply": ${post.parent.hasPermission("CreateChildren")?string}, + "delete": ${post.hasPermission("Delete")?string} + } + + + + +<#-- Renders replies. + The difference is to a normal post is that the children might be + added inline in the returned JSON. +--> +<#macro repliesJSON data> +{ + <@postDataJSON postData=data /> + <#if data.children?exists> + , "children": <@repliesRootJSON children=data.children /> + +} + + +<#macro repliesRootJSON children> +[ + <#list children as child> + <@repliesJSON data=child/> + <#if child_has_next>, + +] + diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post-replies.get.desc.xml b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post-replies.get.desc.xml new file mode 100644 index 0000000000..29b9fbdbce --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post-replies.get.desc.xml @@ -0,0 +1,10 @@ + + Get forum post replies + Get the forum post replies. + /api/forum/post/site/{site}/{container}/{path}/replies + /api/forum/post/node/{store_type}/{store_id}/{id}/replies + argument + user + required + limited_support + diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post-replies.get.json.ftl b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post-replies.get.json.ftl new file mode 100644 index 0000000000..2c4163b0d7 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post-replies.get.json.ftl @@ -0,0 +1,4 @@ +<#import "../post.lib.ftl" as postLib /> +{ + "items": <@postLib.repliesRootJSON children=data /> +} diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post-replies.post.desc.xml b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post-replies.post.desc.xml new file mode 100644 index 0000000000..68fabdd713 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post-replies.post.desc.xml @@ -0,0 +1,9 @@ + + Add forum post reply + Adds a reply to a post. + /api/forum/post/node/{store_type}/{store_id}/{id}/replies + argument + user + required + limited_support + \ No newline at end of file diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post-replies.post.json.ftl b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post-replies.post.json.ftl new file mode 100644 index 0000000000..a3cd1be869 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post-replies.post.json.ftl @@ -0,0 +1,4 @@ +<#import "../post.lib.ftl" as postLib /> +{ + "item": <@postLib.postJSON postData=postData /> +} \ No newline at end of file diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete.desc.xml b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete.desc.xml new file mode 100644 index 0000000000..b179fc4052 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete.desc.xml @@ -0,0 +1,10 @@ + + Delete topic + Deletes a topic. + /api/forum/post/site/{site}/{container}/{path} + /api/forum/post/node/{store_type}/{store_id}/{id} + argument + user + required + limited_support + \ No newline at end of file diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete.json.ftl b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete.json.ftl new file mode 100644 index 0000000000..9a22868515 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete.json.ftl @@ -0,0 +1,3 @@ +{ + "message" : "${message}" +} \ No newline at end of file diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete.properties b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete.properties new file mode 100644 index 0000000000..062cb99eb9 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete.properties @@ -0,0 +1,2 @@ +forum-post.msg.deleted=Node {0} deleted +forum-post.msg.marked.removed=Node {0} marked as removed diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete_de.properties b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete_de.properties new file mode 100755 index 0000000000..33122da30d --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete_de.properties @@ -0,0 +1,2 @@ +forum-post.msg.deleted=Node {0} gel\u00f6scht +forum-post.msg.marked.removed=Node {0} als entfernt gekennzeichnet diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete_es.properties b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete_es.properties new file mode 100755 index 0000000000..b954e60984 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete_es.properties @@ -0,0 +1,2 @@ +forum-post.msg.deleted=Nodo {0} eliminado +forum-post.msg.marked.removed=Nodo {0} marcado como eliminado diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete_fr.properties b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete_fr.properties new file mode 100755 index 0000000000..d97d6c2b19 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete_fr.properties @@ -0,0 +1,2 @@ +forum-post.msg.deleted=N\u0153ud {0} supprim\u00e9 +forum-post.msg.marked.removed=N\u0153ud {0} marqu\u00e9 comme supprim\u00e9 diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete_it.properties b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete_it.properties new file mode 100755 index 0000000000..693eb2f438 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete_it.properties @@ -0,0 +1,2 @@ +forum-post.msg.deleted=Nodo {0} eliminato +forum-post.msg.marked.removed=Nodo {0} contrassegnato come rimosso diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete_ja.properties b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete_ja.properties new file mode 100755 index 0000000000..d218214116 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete_ja.properties @@ -0,0 +1,2 @@ +forum-post.msg.deleted=\u30ce\u30fc\u30c9 ''{0}'' \u304c\u524a\u9664\u3055\u308c\u307e\u3057\u305f +forum-post.msg.marked.removed=\u30ce\u30fc\u30c9 "{0}" \u304c\u524a\u9664\u6e08\u307f\u3068\u3057\u3066\u30de\u30fc\u30af\u3055\u308c\u307e\u3057\u305f diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete_nb.properties b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete_nb.properties new file mode 100755 index 0000000000..ea419fe8e4 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete_nb.properties @@ -0,0 +1,2 @@ +forum-post.msg.deleted=Slettet node {0} +forum-post.msg.marked.removed=Node {0} er merket som slettet diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete_nl.properties b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete_nl.properties new file mode 100755 index 0000000000..8a0d1d645f --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete_nl.properties @@ -0,0 +1,2 @@ +forum-post.msg.deleted=Node {0} verwijderd +forum-post.msg.marked.removed=Node {0} gemarkeerd als verwijderd diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete_pt_BR.properties b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete_pt_BR.properties new file mode 100644 index 0000000000..c1d8e916ff --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete_pt_BR.properties @@ -0,0 +1,2 @@ +forum-post.msg.deleted=N\u00f3 {0} exclu\u00eddo +forum-post.msg.marked.removed=N\u00f3 {0} marcado como removido diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete_ru.properties b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete_ru.properties new file mode 100755 index 0000000000..4d6b255554 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete_ru.properties @@ -0,0 +1,2 @@ +forum-post.msg.deleted=\u0423\u0437\u0435\u043b {0} \u0443\u0434\u0430\u043b\u0435\u043d +forum-post.msg.marked.removed=\u0423\u0437\u0435\u043b {0} \u043e\u0442\u043c\u0435\u0447\u0435\u043d \u043a\u0430\u043a \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u044b\u0439 diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete_zh_CN.properties b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete_zh_CN.properties new file mode 100755 index 0000000000..39bebedffd --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.delete_zh_CN.properties @@ -0,0 +1,2 @@ +forum-post.msg.deleted=\u5df2\u5220\u9664\u8282\u70b9 {0} +forum-post.msg.marked.removed=\u8282\u70b9 {0} \u6807\u8bb0\u4e3a\u5df2\u79fb\u9664 diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.get.desc.xml b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.get.desc.xml new file mode 100644 index 0000000000..f011745e9d --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.get.desc.xml @@ -0,0 +1,10 @@ + + Get topic details + Gets the details for a topic. + /api/forum/post/site/{site}/{container}/{path} + /api/forum/post/node/{store_type}/{store_id}/{id} + argument + user + required + limited_support + diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.get.json.ftl b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.get.json.ftl new file mode 100644 index 0000000000..a3cd1be869 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.get.json.ftl @@ -0,0 +1,4 @@ +<#import "../post.lib.ftl" as postLib /> +{ + "item": <@postLib.postJSON postData=postData /> +} \ No newline at end of file diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.put.desc.xml b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.put.desc.xml new file mode 100644 index 0000000000..c04df8dfa7 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.put.desc.xml @@ -0,0 +1,10 @@ + + Update topic + Updates a topic. + /api/forum/post/site/{site}/{container}/{path} + /api/forum/post/node/{store_type}/{store_id}/{id} + argument + user + required + limited_support + \ No newline at end of file diff --git a/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.put.json.ftl b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.put.json.ftl new file mode 100644 index 0000000000..a3cd1be869 --- /dev/null +++ b/src/main/resources/alfresco/templates/webscripts/org/alfresco/repository/discussions/posts/forum-post.put.json.ftl @@ -0,0 +1,4 @@ +<#import "../post.lib.ftl" as postLib /> +{ + "item": <@postLib.postJSON postData=postData /> +} \ No newline at end of file diff --git a/src/main/resources/alfresco/web-scripts-application-context.xml b/src/main/resources/alfresco/web-scripts-application-context.xml index 2076ba48c2..1cb6c41f62 100644 --- a/src/main/resources/alfresco/web-scripts-application-context.xml +++ b/src/main/resources/alfresco/web-scripts-application-context.xml @@ -891,6 +891,13 @@ parent="baseArchivedNodeWebScript"> + + + + + + + @@ -1323,6 +1330,89 @@ parent="abstractLinksWebScript"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/org/alfresco/AppContext04TestSuite.java b/src/test/java/org/alfresco/AppContext04TestSuite.java index 730a25440a..7d4895f3ad 100644 --- a/src/test/java/org/alfresco/AppContext04TestSuite.java +++ b/src/test/java/org/alfresco/AppContext04TestSuite.java @@ -40,7 +40,9 @@ import org.junit.runners.Suite; org.alfresco.repo.web.scripts.quickshare.QuickShareRestApiTest.class, org.alfresco.repo.web.scripts.admin.AdminWebScriptTest.class, org.alfresco.repo.web.scripts.audit.AuditWebScriptTest.class, + org.alfresco.repo.web.scripts.blogs.BlogServiceTest.class, org.alfresco.repo.web.scripts.dictionary.DictionaryRestApiTest.class, + org.alfresco.repo.web.scripts.discussion.DiscussionRestApiTest.class, org.alfresco.repo.web.scripts.activities.feed.control.FeedControlTest.class, org.alfresco.repo.web.scripts.forms.FormRestApiGet_Test.class, org.alfresco.repo.web.scripts.forms.FormRestApiJsonPost_Test.class, diff --git a/src/test/java/org/alfresco/repo/web/scripts/ReadOnlyTransactionInGetRestApiTest.java b/src/test/java/org/alfresco/repo/web/scripts/ReadOnlyTransactionInGetRestApiTest.java index a0dde35074..ebd98336ff 100644 --- a/src/test/java/org/alfresco/repo/web/scripts/ReadOnlyTransactionInGetRestApiTest.java +++ b/src/test/java/org/alfresco/repo/web/scripts/ReadOnlyTransactionInGetRestApiTest.java @@ -1,168 +1,188 @@ -/* - * #%L - * Alfresco Remote API - * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited - * %% - * This file is part of the Alfresco software. - * If the software was purchased under a paid Alfresco license, the terms of - * the paid license agreement will prevail. Otherwise, the software is - * provided under the following open source license terms: - * - * 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 . - * #L% - */ -package org.alfresco.repo.web.scripts; - -import java.util.List; - -import org.alfresco.repo.node.archive.NodeArchiveService; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.site.SiteInfo; -import org.alfresco.service.cmr.site.SiteService; -import org.alfresco.service.cmr.site.SiteVisibility; -import org.alfresco.service.transaction.TransactionService; -import org.springframework.context.ApplicationContext; -import org.springframework.extensions.webscripts.Status; -import org.springframework.extensions.webscripts.TestWebScriptServer.GetRequest; -import org.springframework.extensions.webscripts.TestWebScriptServer.Response; - -/** - * Set of tests that ensure GET REST APIs are run successfully in a read-only - * transaction (ALF-10179). - * - * Some webscripts have a side effect of creating a "container" these tests - * are to ensure this is handled gracefully in a way that allows the main - * transaction to be declared as readonly for performance reasons. - * - * @author Gavin Cornwell - * @since 4.0 - */ -public class ReadOnlyTransactionInGetRestApiTest extends BaseWebScriptTest -{ - private static final String TEST_SITE_NAME = "readOnlyTestSite"; - - private static final String URL_GET_SITE_LINKS = "/api/links/site/" + TEST_SITE_NAME + "/links?page=1&pageSize=10"; - private static final String URL_GET_SITE_LINK = "/api/links/link/site/" + TEST_SITE_NAME + "/links/123456789"; - private static final String URL_GET_SITE_TAGS = "/api/tagscopes/site/" + TEST_SITE_NAME + "/tags"; - - private SiteService siteService; - private NodeService nodeService; - private TransactionService transactionService; - private NodeArchiveService nodeArchiveService; - - private NodeRef testSiteNodeRef; - private String testSiteNodeRefString; - - private boolean logEnabled = false; - - @Override - protected void setUp() throws Exception - { - super.setUp(); - ApplicationContext appContext = getServer().getApplicationContext(); - - this.siteService = (SiteService)appContext.getBean("SiteService"); - this.nodeService = (NodeService)appContext.getBean("NodeService"); - this.transactionService = (TransactionService)appContext.getBean("TransactionService"); - this.nodeArchiveService = (NodeArchiveService)getServer().getApplicationContext().getBean("nodeArchiveService"); - - // set admin as current user - AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); - - // delete the test site if it's still hanging around from previous runs - SiteInfo site = siteService.getSite(TEST_SITE_NAME); - if (site != null) - { - siteService.deleteSite(TEST_SITE_NAME); - nodeArchiveService.purgeArchivedNode(nodeArchiveService.getArchivedNode(site.getNodeRef())); - } - - // create the test site, this should create a site but it won't have any containers created - SiteInfo siteInfo = this.siteService.createSite("collaboration", TEST_SITE_NAME, "Read Only Test Site", - "Test site for ReadOnlyTransactionRestApiTest", SiteVisibility.PUBLIC); - this.testSiteNodeRef = siteInfo.getNodeRef(); - this.testSiteNodeRefString = this.testSiteNodeRef.toString().replace("://", "/"); - - // ensure there are no containers present at the start of the test - List children = nodeService.getChildAssocs(this.testSiteNodeRef); - assertTrue("The test site should not have any containers", children.isEmpty()); - } - - /* (non-Javadoc) - * @see junit.framework.TestCase#tearDown() - */ - @Override - protected void tearDown() throws Exception - { - super.tearDown(); - - SiteInfo site = siteService.getSite(TEST_SITE_NAME); - // use retrying transaction to delete the site - this.transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() - { - @Override - public Void execute() throws Throwable - { - // delete the test site - siteService.deleteSite(TEST_SITE_NAME); - - return null; - } - }); - nodeArchiveService.purgeArchivedNode(nodeArchiveService.getArchivedNode(site.getNodeRef())); - - AuthenticationUtil.clearCurrentSecurityContext(); - } - - public void testGetSiteLinks() throws Exception - { - Response response = sendRequest(new GetRequest(URL_GET_SITE_LINKS), 200); - logResponse(response); - assertEquals(Status.STATUS_OK, response.getStatus()); - } - - public void testGetSiteLink() throws Exception - { - Response response = sendRequest(new GetRequest(URL_GET_SITE_LINK), 404); - logResponse(response); - assertEquals(Status.STATUS_NOT_FOUND, response.getStatus()); - } - - public void testGetSiteTags() throws Exception - { - Response response = sendRequest(new GetRequest(URL_GET_SITE_TAGS), 200); - logResponse(response); - assertEquals(Status.STATUS_OK, response.getStatus()); - } - - private void logResponse(Response response) - { - if (this.logEnabled) - { - try - { - System.out.println(response.getContentAsString()); - } - catch (Exception e) - { - System.err.println("Unable to log response: " + e.toString()); - } - } - } -} +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.web.scripts; + +import java.util.List; + +import org.alfresco.repo.node.archive.NodeArchiveService; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.site.SiteInfo; +import org.alfresco.service.cmr.site.SiteService; +import org.alfresco.service.cmr.site.SiteVisibility; +import org.alfresco.service.transaction.TransactionService; +import org.springframework.context.ApplicationContext; +import org.springframework.extensions.webscripts.Status; +import org.springframework.extensions.webscripts.TestWebScriptServer.GetRequest; +import org.springframework.extensions.webscripts.TestWebScriptServer.Response; + +/** + * Set of tests that ensure GET REST APIs are run successfully in a read-only + * transaction (ALF-10179). + * + * Some webscripts have a side effect of creating a "container" these tests + * are to ensure this is handled gracefully in a way that allows the main + * transaction to be declared as readonly for performance reasons. + * + * @author Gavin Cornwell + * @since 4.0 + */ +public class ReadOnlyTransactionInGetRestApiTest extends BaseWebScriptTest +{ + private static final String TEST_SITE_NAME = "readOnlyTestSite"; + + + private static final String URL_GET_SITE_FORUM_POSTS = "/api/forum/site/" + TEST_SITE_NAME + "/discussions/posts"; + private static final String URL_GET_SITE_BLOG = "/api/blog/site/" + TEST_SITE_NAME + "/blog"; + private static final String URL_GET_SITE_LINKS = "/api/links/site/" + TEST_SITE_NAME + "/links?page=1&pageSize=10"; + private static final String URL_GET_SITE_LINK = "/api/links/link/site/" + TEST_SITE_NAME + "/links/123456789"; + private static final String URL_GET_SITE_TAGS = "/api/tagscopes/site/" + TEST_SITE_NAME + "/tags"; + + private SiteService siteService; + private NodeService nodeService; + private TransactionService transactionService; + private NodeArchiveService nodeArchiveService; + + private NodeRef testSiteNodeRef; + private String testSiteNodeRefString; + + private boolean logEnabled = false; + + @Override + protected void setUp() throws Exception + { + super.setUp(); + ApplicationContext appContext = getServer().getApplicationContext(); + + this.siteService = (SiteService)appContext.getBean("SiteService"); + this.nodeService = (NodeService)appContext.getBean("NodeService"); + this.transactionService = (TransactionService)appContext.getBean("TransactionService"); + this.nodeArchiveService = (NodeArchiveService)getServer().getApplicationContext().getBean("nodeArchiveService"); + + // set admin as current user + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + + // delete the test site if it's still hanging around from previous runs + SiteInfo site = siteService.getSite(TEST_SITE_NAME); + if (site != null) + { + siteService.deleteSite(TEST_SITE_NAME); + nodeArchiveService.purgeArchivedNode(nodeArchiveService.getArchivedNode(site.getNodeRef())); + } + + // create the test site, this should create a site but it won't have any containers created + SiteInfo siteInfo = this.siteService.createSite("collaboration", TEST_SITE_NAME, "Read Only Test Site", + "Test site for ReadOnlyTransactionRestApiTest", SiteVisibility.PUBLIC); + this.testSiteNodeRef = siteInfo.getNodeRef(); + this.testSiteNodeRefString = this.testSiteNodeRef.toString().replace("://", "/"); + + // ensure there are no containers present at the start of the test + List children = nodeService.getChildAssocs(this.testSiteNodeRef); + assertTrue("The test site should not have any containers", children.isEmpty()); + } + + /* (non-Javadoc) + * @see junit.framework.TestCase#tearDown() + */ + @Override + protected void tearDown() throws Exception + { + super.tearDown(); + + SiteInfo site = siteService.getSite(TEST_SITE_NAME); + // use retrying transaction to delete the site + this.transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // delete the test site + siteService.deleteSite(TEST_SITE_NAME); + + return null; + } + }); + nodeArchiveService.purgeArchivedNode(nodeArchiveService.getArchivedNode(site.getNodeRef())); + + AuthenticationUtil.clearCurrentSecurityContext(); + } + + public void testGetSiteForumPosts() throws Exception + { + Response response = sendRequest(new GetRequest(URL_GET_SITE_FORUM_POSTS), 200); + logResponse(response); + assertEquals(Status.STATUS_OK, response.getStatus()); + } + + public void testGetSiteBlog() throws Exception + { + // TODO: Fixme - This REST API still requires a readwrite transaction to be successful + // Also add tests for all other blog GET REST APIs + + Response response = sendRequest(new GetRequest(URL_GET_SITE_BLOG), 200); + logResponse(response); + assertEquals(Status.STATUS_OK, response.getStatus()); + } + + public void testGetSiteLinks() throws Exception + { + Response response = sendRequest(new GetRequest(URL_GET_SITE_LINKS), 200); + logResponse(response); + assertEquals(Status.STATUS_OK, response.getStatus()); + } + + public void testGetSiteLink() throws Exception + { + Response response = sendRequest(new GetRequest(URL_GET_SITE_LINK), 404); + logResponse(response); + assertEquals(Status.STATUS_NOT_FOUND, response.getStatus()); + } + + public void testGetSiteTags() throws Exception + { + Response response = sendRequest(new GetRequest(URL_GET_SITE_TAGS), 200); + logResponse(response); + assertEquals(Status.STATUS_OK, response.getStatus()); + } + + private void logResponse(Response response) + { + if (this.logEnabled) + { + try + { + System.out.println(response.getContentAsString()); + } + catch (Exception e) + { + System.err.println("Unable to log response: " + e.toString()); + } + } + } +} diff --git a/src/test/java/org/alfresco/repo/web/scripts/WebScriptTestSuite.java b/src/test/java/org/alfresco/repo/web/scripts/WebScriptTestSuite.java index 6b66424c8c..a74d4d4a57 100644 --- a/src/test/java/org/alfresco/repo/web/scripts/WebScriptTestSuite.java +++ b/src/test/java/org/alfresco/repo/web/scripts/WebScriptTestSuite.java @@ -33,9 +33,11 @@ import org.alfresco.repo.web.scripts.action.RunningActionRestApiTest; import org.alfresco.repo.web.scripts.activities.feed.control.FeedControlTest; import org.alfresco.repo.web.scripts.admin.AdminWebScriptTest; import org.alfresco.repo.web.scripts.audit.AuditWebScriptTest; +import org.alfresco.repo.web.scripts.blogs.BlogServiceTest; import org.alfresco.repo.web.scripts.comment.CommentsApiTest; import org.alfresco.repo.web.scripts.custommodel.CustomModelImportTest; import org.alfresco.repo.web.scripts.dictionary.DictionaryRestApiTest; +import org.alfresco.repo.web.scripts.discussion.DiscussionRestApiTest; import org.alfresco.repo.web.scripts.facet.FacetRestApiTest; import org.alfresco.repo.web.scripts.forms.FormRestApiGet_Test; import org.alfresco.repo.web.scripts.forms.FormRestApiJsonPost_Test; @@ -78,7 +80,9 @@ public class WebScriptTestSuite extends TestSuite suite.addTestSuite( QuickShareRestApiTest.class ); suite.addTestSuite( AdminWebScriptTest.class ); suite.addTestSuite( AuditWebScriptTest.class ); + suite.addTestSuite( BlogServiceTest.class ); suite.addTestSuite( DictionaryRestApiTest.class ); + suite.addTestSuite( DiscussionRestApiTest.class ); suite.addTestSuite( FeedControlTest.class ); suite.addTestSuite( FormRestApiGet_Test.class ); suite.addTestSuite( FormRestApiJsonPost_Test.class ); diff --git a/src/test/java/org/alfresco/repo/web/scripts/blogs/AllBlogTests.java b/src/test/java/org/alfresco/repo/web/scripts/blogs/AllBlogTests.java new file mode 100644 index 0000000000..a6d725a5ec --- /dev/null +++ b/src/test/java/org/alfresco/repo/web/scripts/blogs/AllBlogTests.java @@ -0,0 +1,49 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.web.scripts.blogs; + +import org.alfresco.repo.blog.BlogServiceImplTest; +import org.alfresco.service.cmr.blog.BlogService; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +/** + * This class is a holder for the various test classes associated with the {@link BlogService}. + * It is not (at the time of writing) intended to be incorporated into the automatic build + * which will find the various test classes and run them individually. + * + * @author Neil Mc Erlean + * @since 4.0 + */ +@RunWith(Suite.class) +@Suite.SuiteClasses({ + BlogServiceImplTest.class, + BlogServiceTest.class +}) +public class AllBlogTests +{ + // Intentionally empty +} diff --git a/src/test/java/org/alfresco/repo/web/scripts/blogs/BlogServiceTest.java b/src/test/java/org/alfresco/repo/web/scripts/blogs/BlogServiceTest.java new file mode 100644 index 0000000000..0bbc1f0080 --- /dev/null +++ b/src/test/java/org/alfresco/repo/web/scripts/blogs/BlogServiceTest.java @@ -0,0 +1,998 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.web.scripts.blogs; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.activities.feed.FeedGenerator; +import org.alfresco.repo.activities.post.lookup.PostLookup; +import org.alfresco.repo.management.subsystems.ChildApplicationContextFactory; +import org.alfresco.repo.node.archive.NodeArchiveService; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.site.SiteModel; +import org.alfresco.repo.web.scripts.BaseWebScriptTest; +import org.alfresco.service.cmr.activities.ActivityService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.security.MutableAuthenticationService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.cmr.site.SiteInfo; +import org.alfresco.service.cmr.site.SiteService; +import org.alfresco.service.cmr.site.SiteVisibility; +import org.alfresco.util.GUID; +import org.alfresco.util.PropertyMap; +import org.alfresco.util.testing.category.LuceneTests; +import org.alfresco.util.testing.category.RedundantTests; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.json.JSONArray; +import org.json.JSONObject; +import org.json.JSONStringer; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.springframework.context.ApplicationContext; +import org.springframework.extensions.webscripts.Status; +import org.springframework.extensions.webscripts.TestWebScriptServer.DeleteRequest; +import org.springframework.extensions.webscripts.TestWebScriptServer.GetRequest; +import org.springframework.extensions.webscripts.TestWebScriptServer.PostRequest; +import org.springframework.extensions.webscripts.TestWebScriptServer.PutRequest; +import org.springframework.extensions.webscripts.TestWebScriptServer.Response; + +/** + * Unit Test to test Blog Web Script API + * + * @author mruflin + */ +@Category(LuceneTests.class) +public class BlogServiceTest extends BaseWebScriptTest +{ + @SuppressWarnings("unused") + private static Log logger = LogFactory.getLog(BlogServiceTest.class); + + private MutableAuthenticationService authenticationService; + private AuthenticationComponent authenticationComponent; + private PersonService personService; + private SiteService siteService; + private NodeArchiveService nodeArchiveService; + private ActivityService activityService; + private FeedGenerator feedGenerator; + private PostLookup postLookup; + + private static final String USER_ONE = "UserOneSecondToo"; + private static final String USER_TWO = "UserTwoSecondToo"; + private static final String SITE_SHORT_NAME_BLOG = "BlogSiteShortNameTest"; + private static final String COMPONENT_BLOG = "blog"; + + private static final String URL_BLOG_POST = "/api/blog/post/site/" + SITE_SHORT_NAME_BLOG + "/" + COMPONENT_BLOG + "/"; + private static final String URL_BLOG_CORE = "/api/blog/site/" + SITE_SHORT_NAME_BLOG + "/" + COMPONENT_BLOG; + private static final String URL_BLOG_POSTS = URL_BLOG_CORE + "/posts"; + private static final String URL_MY_DRAFT_BLOG_POSTS = "/api/blog/site/" + SITE_SHORT_NAME_BLOG + + "/" + COMPONENT_BLOG + "/posts/mydrafts"; + private static final String URL_MY_PUBLISHED_BLOG_POSTS = "/api/blog/site/" + SITE_SHORT_NAME_BLOG + + "/" + COMPONENT_BLOG + "/posts/mypublished"; + + private static final String URL_DELETE_COMMENT = "api/comment/node/{0}/{1}/{2}?site={3}&itemtitle={4}&page={5}"; + + private List posts; + private List drafts; + + + // General methods + + @Override + protected void setUp() throws Exception + { + super.setUp(); + + this.authenticationService = (MutableAuthenticationService)getServer().getApplicationContext().getBean("AuthenticationService"); + this.authenticationComponent = (AuthenticationComponent)getServer().getApplicationContext().getBean("authenticationComponent"); + this.personService = (PersonService)getServer().getApplicationContext().getBean("PersonService"); + this.siteService = (SiteService)getServer().getApplicationContext().getBean("SiteService"); + this.nodeArchiveService = (NodeArchiveService)getServer().getApplicationContext().getBean("nodeArchiveService"); + this.activityService = (ActivityService)getServer().getApplicationContext().getBean("activityService"); + ChildApplicationContextFactory activitiesFeed = (ChildApplicationContextFactory)getServer().getApplicationContext().getBean("ActivitiesFeed"); + ApplicationContext activitiesFeedCtx = activitiesFeed.getApplicationContext(); + this.feedGenerator = (FeedGenerator)activitiesFeedCtx.getBean("feedGenerator"); + this.postLookup = (PostLookup)activitiesFeedCtx.getBean("postLookup"); + + // Authenticate as user + this.authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName()); + + // Create test site + // - only create the site if it doesn't already exist + SiteInfo siteInfo = this.siteService.getSite(SITE_SHORT_NAME_BLOG); + if (siteInfo == null) + { + this.siteService.createSite("BlogSitePreset", SITE_SHORT_NAME_BLOG, "BlogSiteTitle", "BlogSiteDescription", SiteVisibility.PUBLIC); + } + + // Create users + createUser(USER_ONE, SiteModel.SITE_COLLABORATOR, SITE_SHORT_NAME_BLOG); + createUser(USER_TWO, SiteModel.SITE_COLLABORATOR, SITE_SHORT_NAME_BLOG); + + // Blank our lists used to track things the test creates + posts = new ArrayList(5); + drafts = new ArrayList(5); + + // Do tests as inviter user + this.authenticationComponent.setCurrentUser(USER_ONE); + } + + @Override + protected void tearDown() throws Exception + { + super.tearDown(); + + // admin user required to delete things + this.authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName()); + + SiteInfo siteInfo = this.siteService.getSite(SITE_SHORT_NAME_BLOG); + if (siteInfo != null) + { + // delete invite site + siteService.deleteSite(SITE_SHORT_NAME_BLOG); + nodeArchiveService.purgeArchivedNode(nodeArchiveService.getArchivedNode(siteInfo.getNodeRef())); + } + + + // delete the users + personService.deletePerson(USER_ONE); + if (this.authenticationService.authenticationExists(USER_ONE)) + { + this.authenticationService.deleteAuthentication(USER_ONE); + } + + personService.deletePerson(USER_TWO); + if (this.authenticationService.authenticationExists(USER_TWO)) + { + this.authenticationService.deleteAuthentication(USER_TWO); + } + } + + private void createUser(String userName, String role, String siteMembership) + { + // if user with given user name doesn't already exist then create user + if (this.authenticationService.authenticationExists(userName) == false) + { + // create user + this.authenticationService.createAuthentication(userName, "password".toCharArray()); + + // create person properties + PropertyMap personProps = new PropertyMap(); + personProps.put(ContentModel.PROP_USERNAME, userName); + personProps.put(ContentModel.PROP_FIRSTNAME, "FirstName123"); + personProps.put(ContentModel.PROP_LASTNAME, "LastName123"); + personProps.put(ContentModel.PROP_EMAIL, "FirstName123.LastName123@email.com"); + personProps.put(ContentModel.PROP_JOBTITLE, "JobTitle123"); + personProps.put(ContentModel.PROP_JOBTITLE, "Organisation123"); + + // create person node for user + this.personService.createPerson(personProps); + } + + // add the user as a member with the given role + this.siteService.setMembership(siteMembership, userName, role); + } + + + // Test helper methods + + private JSONObject getRequestObject(String title, String content, String[] tags, boolean isDraft) + throws Exception + { + JSONObject post = new JSONObject(); + if (title != null) + { + post.put("title", title); + } + if (content != null) + { + post.put("content", content); + } + if (tags != null) + { + JSONArray arr = new JSONArray(); + for (String s : tags) + { + arr.put(s); + } + post.put("tags", arr); + } + post.put("draft", isDraft); + return post; + } + + private JSONObject createPost(String title, String content, String[] tags, boolean isDraft, int expectedStatus) + throws Exception + { + JSONObject post = getRequestObject(title, content, tags, isDraft); + Response response = sendRequest(new PostRequest(URL_BLOG_POSTS, post.toString(), "application/json"), expectedStatus); + + if (expectedStatus != 200) + { + return null; + } + + //logger.debug(response.getContentAsString()); + JSONObject result = new JSONObject(response.getContentAsString()); + JSONObject item = result.getJSONObject("item"); + if (isDraft) + { + this.drafts.add(item.getString("name")); + } + else + { + this.posts.add(item.getString("name")); + } + return item; + } + + private JSONObject updatePost(String name, String title, String content, String[] tags, boolean isDraft, int expectedStatus) + throws Exception + { + JSONObject post = getRequestObject(title, content, tags, isDraft); + Response response = sendRequest(new PutRequest(URL_BLOG_POST + name, post.toString(), "application/json"), expectedStatus); + + if (expectedStatus != 200) + { + return null; + } + + JSONObject result = new JSONObject(response.getContentAsString()); + return result.getJSONObject("item"); + } + + private JSONObject getPost(String name, int expectedStatus) + throws Exception + { + Response response = sendRequest(new GetRequest(URL_BLOG_POST + name), expectedStatus); + if (expectedStatus == 200) + { + JSONObject result = new JSONObject(response.getContentAsString()); + return result.getJSONObject("item"); + } + else + { + return null; + } + } + + private String getCommentsUrl(String nodeRef) + { + return "/api/node/" + nodeRef.replace("://", "/") + "/comments"; + } + + private String getCommentUrl(String nodeRef) + { + return "/api/comment/node/" + nodeRef.replace("://", "/"); + } + + private String getDeleteCommentUrl(NodeRef commentNodeRef) + { + String itemTitle = "Test Title"; + String page = "document-details"; + + String URL = MessageFormat.format(URL_DELETE_COMMENT, new Object[] { commentNodeRef.getStoreRef().getProtocol(), + commentNodeRef.getStoreRef().getIdentifier(), commentNodeRef.getId(), SITE_SHORT_NAME_BLOG, itemTitle, page}); + return URL; + } + + private JSONObject createComment(String nodeRef, String title, String content, int expectedStatus) + throws Exception + { + JSONObject comment = new JSONObject(); + comment.put("title", title); + comment.put("content", content); + comment.put("site", SITE_SHORT_NAME_BLOG); + Response response = sendRequest(new PostRequest(getCommentsUrl(nodeRef), comment.toString(), "application/json"), expectedStatus); + + if (expectedStatus != 200) + { + return null; + } + + //logger.debug("Comment created: " + response.getContentAsString()); + JSONObject result = new JSONObject(response.getContentAsString()); + return result.getJSONObject("item"); + } + + private JSONObject updateComment(String nodeRef, String title, String content, int expectedStatus) + throws Exception + { + JSONObject comment = new JSONObject(); + comment.put("title", title); + comment.put("content", content); + Response response = sendRequest(new PutRequest(getCommentUrl(nodeRef), comment.toString(), "application/json"), expectedStatus); + + if (expectedStatus != 200) + { + return null; + } + + //logger.debug("Comment updated: " + response.getContentAsString()); + JSONObject result = new JSONObject(response.getContentAsString()); + return result.getJSONObject("item"); + } + + + // Tests + + public void testCreateDraftPost() throws Exception + { + String title = "test"; + String content = "test"; + JSONObject item = createPost(title, content, null, true, 200); + + // check that the values + assertEquals(title, item.get("title")); + assertEquals(content, item.get("content")); + assertEquals(true, item.get("isDraft")); + + // check that other user doesn't have access to the draft + this.authenticationComponent.setCurrentUser(USER_TWO); + getPost(item.getString("name"), 404); + this.authenticationComponent.setCurrentUser(USER_ONE); + + // Now we'll GET my-drafts to ensure that the post is there. + Response response = sendRequest(new GetRequest(URL_MY_DRAFT_BLOG_POSTS), 200); + JSONObject result = new JSONObject(response.getContentAsString()); + assertTrue("Wrong number of posts", result.length() > 0); + } + + /** + * @since 4.0 + */ + @Test + @Category({LuceneTests.class, RedundantTests.class}) + public void testCreateDraftPostWithTagsAndComment() throws Exception + { + String[] tags = new String[]{"foo", "bar"}; + String title = "test"; + String content = "test"; + JSONObject item = createPost(title, content, tags, true, 200); + + // check that the values + assertEquals(title, item.get("title")); + assertEquals(content, item.get("content")); + assertEquals(true, item.get("isDraft")); + JSONArray reportedTags = (JSONArray)item.get("tags"); + assertEquals("Tags size was wrong.", 2, reportedTags.length()); + List recoveredTagsList = Arrays.asList(new String[]{reportedTags.getString(0), reportedTags.getString(1)}); + assertEquals("Tags were wrong.", Arrays.asList(tags), recoveredTagsList); + + // comment on the blog post. + NodeRef blogPostNode = new NodeRef(item.getString("nodeRef")); + // Currently (mid-Swift dev) there is no Java CommentService, so we have to post a comment via the REST API. + String commentsPostUrl = "/api/node/" + blogPostNode.getStoreRef().getProtocol() + + "/" + blogPostNode.getStoreRef().getIdentifier() + "/" + + blogPostNode.getId() + "/comments"; + + String jsonToPost = new JSONStringer().object() + .key("title").value("Commented blog title") + .key("content").value("Some content.") + .endObject().toString(); + + Response response = sendRequest(new PostRequest(commentsPostUrl, jsonToPost, "application/json"), 200); + + // check that other user doesn't have access to the draft + this.authenticationComponent.setCurrentUser(USER_TWO); + getPost(item.getString("name"), 404); + this.authenticationComponent.setCurrentUser(USER_ONE); + + // Now we'll GET my-drafts to ensure that the post is there. + response = sendRequest(new GetRequest(URL_MY_DRAFT_BLOG_POSTS), 200); + JSONObject result = new JSONObject(response.getContentAsString()); + + // Ensure it reports the tag correctly on GET. + JSONArray items = result.getJSONArray("items"); + JSONArray tagsArray = items.getJSONObject(0).getJSONArray("tags"); + assertEquals("Wrong number of tags", 2, tagsArray.length()); + assertEquals("Tag wrong", tags[0], tagsArray.getString(0)); + assertEquals("Tag wrong", tags[1], tagsArray.getString(1)); + + // Ensure the comment count is accurate + assertEquals("Wrong comment count", 1, items.getJSONObject(0).getInt("commentCount")); + + // and that there is content at the commentsURL. + String commentsUrl = "/api" + items.getJSONObject(0).getString("commentsUrl"); + response = sendRequest(new GetRequest(commentsUrl), 200); + + + // Now get blog-post by tag. + // 1. No such tag + response = sendRequest(new GetRequest(URL_BLOG_POSTS + "?tag=NOSUCHTAG"), 200); + result = new JSONObject(response.getContentAsString()); + + assertEquals(0, result.getInt("total")); + + // tag created above + response = sendRequest(new GetRequest(URL_BLOG_POSTS + "?tag=foo"), 200); + result = new JSONObject(response.getContentAsString()); + + assertEquals(1, result.getInt("total")); + + //TODO More assertions on recovered node. + } + + public void testCreatePublishedPost() throws Exception + { + String title = "published"; + String content = "content"; + + JSONObject item = createPost(title, content, null, false, 200); + final String postName = item.getString("name"); + + // check the values + assertEquals(title, item.get("title")); + assertEquals(content, item.get("content")); + assertEquals(false, item.get("isDraft")); + + // check that user two has access to it as well + this.authenticationComponent.setCurrentUser(USER_TWO); + getPost(item.getString("name"), 200); + this.authenticationComponent.setCurrentUser(USER_ONE); + + // Now we'll GET my-published to ensure that the post is there. + Response response = sendRequest(new GetRequest(URL_MY_PUBLISHED_BLOG_POSTS), 200); + JSONObject result = new JSONObject(response.getContentAsString()); + + // we should have posts.size + drafts.size together + assertEquals(this.posts.size() + this.drafts.size(), result.getInt("total")); + + // Finally, we'll delete the blog-post to test the REST DELETE call. + response = sendRequest(new DeleteRequest(URL_BLOG_POST + postName), 200); + + } + + public void testCreateEmptyPost() throws Exception + { + JSONObject item = createPost(null, null, null, false, 200); + + // check the values + assertEquals("", item.get("title")); + assertEquals("", item.get("content")); + assertEquals(false, item.get("isDraft")); + + // check that user two has access to it as well + this.authenticationComponent.setCurrentUser(USER_TWO); + getPost(item.getString("name"), 200); + this.authenticationComponent.setCurrentUser(USER_ONE); + } + + public void testUpdated() throws Exception + { + JSONObject item = createPost("test", "test", null, false, 200); + String name = item.getString("name"); + assertEquals(false, item.getBoolean("isUpdated")); + + item = updatePost(name, "new title", "new content", null, false, 200); + assertEquals(true, item.getBoolean("isUpdated")); + assertEquals("new title", item.getString("title")); + assertEquals("new content", item.getString("content")); + } + + public void testUpdateWithEmptyValues() throws Exception + { + JSONObject item = createPost("test", "test", null, false, 200); + String name = item.getString("name"); + assertEquals(false, item.getBoolean("isUpdated")); + + item = updatePost(item.getString("name"), null, null, null, false, 200); + assertEquals("", item.getString("title")); + assertEquals("", item.getString("content")); + } + + public void testPublishThroughUpdate() throws Exception + { + JSONObject item = createPost("test", "test", null, true, 200); + String name = item.getString("name"); + assertEquals(true, item.getBoolean("isDraft")); + + // check that user two does not have access + this.authenticationComponent.setCurrentUser(USER_TWO); + getPost(name, 404); + this.authenticationComponent.setCurrentUser(USER_ONE); + + item = updatePost(name, "new title", "new content", null, false, 200); + assertEquals("new title", item.getString("title")); + assertEquals("new content", item.getString("content")); + assertEquals(false, item.getBoolean("isDraft")); + + // check that user two does have access + this.authenticationComponent.setCurrentUser(USER_TWO); + getPost(name, 200); + this.authenticationComponent.setCurrentUser(USER_ONE); + } + + public void testCannotDoUnpublish() throws Exception + { + JSONObject item = createPost("test", "test", null, false, 200); + String name = item.getString("name"); + assertEquals(false, item.getBoolean("isDraft")); + + item = updatePost(name, "new title", "new content", null, true, 400); // should return bad request + } + + public void testGetAll() throws Exception + { + String url = URL_BLOG_POSTS; + Response response = sendRequest(new GetRequest(url), 200); + JSONObject result = new JSONObject(response.getContentAsString()); + JSONObject blog; + + // We shouldn't have any posts at this point + assertEquals(0, this.posts.size()); + assertEquals(0, this.drafts.size()); + + assertEquals(0, result.getInt("total")); + assertEquals(0, result.getInt("startIndex")); + assertEquals(0, result.getInt("itemCount")); + assertEquals(0, result.getJSONArray("items").length()); + + // Check that the permissions are correct + JSONObject metadata = result.getJSONObject("metadata"); + JSONObject perms = metadata.getJSONObject("blogPermissions"); + assertEquals(false, metadata.getBoolean("externalBlogConfig")); + assertEquals(false, perms.getBoolean("delete")); // No container yet + assertEquals(true, perms.getBoolean("edit")); + assertEquals(true, perms.getBoolean("create")); + + + // Create a draft and a full post + String TITLE_1 = "Published"; + String TITLE_2 = "Draft"; + String TITLE_3 = "Another Published"; + createPost(TITLE_1, "Stuff", null, false, Status.STATUS_OK); + createPost(TITLE_2, "Draft Stuff", null, true, Status.STATUS_OK); + + // Check now + response = sendRequest(new GetRequest(url), 200); + result = new JSONObject(response.getContentAsString()); + assertEquals(2, result.getInt("total")); + assertEquals(0, result.getInt("startIndex")); + assertEquals(2, result.getInt("itemCount")); + assertEquals(2, result.getJSONArray("items").length()); + + // Check the core permissions + metadata = result.getJSONObject("metadata"); + perms = metadata.getJSONObject("blogPermissions"); + assertEquals(false, metadata.getBoolean("externalBlogConfig")); + assertEquals(true, perms.getBoolean("delete")); // On the container itself + assertEquals(true, perms.getBoolean("edit")); + assertEquals(true, perms.getBoolean("create")); + + // Check each one in detail, they'll come back Published + // then draft (newest first within that) + blog = result.getJSONArray("items").getJSONObject(0); + assertEquals(TITLE_1, blog.get("title")); + assertEquals(false, blog.getBoolean("isDraft")); + perms = blog.getJSONObject("permissions"); + assertEquals(true, perms.getBoolean("delete")); + assertEquals(true, perms.getBoolean("edit")); + + blog = result.getJSONArray("items").getJSONObject(1); + assertEquals(TITLE_2, blog.get("title")); + assertEquals(true, blog.getBoolean("isDraft")); + perms = blog.getJSONObject("permissions"); + assertEquals(true, perms.getBoolean("delete")); + assertEquals(true, perms.getBoolean("edit")); + + + // Add a third post + createPost(TITLE_3, "Still Stuff", null, false, Status.STATUS_OK); + + response = sendRequest(new GetRequest(url), 200); + result = new JSONObject(response.getContentAsString()); + assertEquals(3, result.getInt("total")); + assertEquals(0, result.getInt("startIndex")); + assertEquals(3, result.getInt("itemCount")); + assertEquals(3, result.getJSONArray("items").length()); + + // Published then draft, newest first + blog = result.getJSONArray("items").getJSONObject(0); + assertEquals(TITLE_3, blog.get("title")); + blog = result.getJSONArray("items").getJSONObject(1); + assertEquals(TITLE_1, blog.get("title")); + blog = result.getJSONArray("items").getJSONObject(2); + assertEquals(TITLE_2, blog.get("title")); + + + // Ensure that paging behaves properly + response = sendRequest(new GetRequest(url + "?pageSize=2&startIndex=0"), 200); + result = new JSONObject(response.getContentAsString()); + assertEquals(3, result.getInt("total")); + assertEquals(0, result.getInt("startIndex")); + assertEquals(2, result.getInt("itemCount")); + assertEquals(2, result.getJSONArray("items").length()); + + assertEquals(TITLE_3, result.getJSONArray("items").getJSONObject(0).get("title")); + assertEquals(TITLE_1, result.getJSONArray("items").getJSONObject(1).get("title")); + + + response = sendRequest(new GetRequest(url + "?pageSize=2&startIndex=1"), 200); + result = new JSONObject(response.getContentAsString()); + assertEquals(3, result.getInt("total")); + assertEquals(1, result.getInt("startIndex")); + assertEquals(2, result.getInt("itemCount")); + assertEquals(2, result.getJSONArray("items").length()); + + assertEquals(TITLE_1, result.getJSONArray("items").getJSONObject(0).get("title")); + assertEquals(TITLE_2, result.getJSONArray("items").getJSONObject(1).get("title")); + + + response = sendRequest(new GetRequest(url + "?pageSize=2&startIndex=2"), 200); + result = new JSONObject(response.getContentAsString()); + assertEquals(3, result.getInt("total")); + assertEquals(2, result.getInt("startIndex")); + assertEquals(1, result.getInt("itemCount")); + assertEquals(1, result.getJSONArray("items").length()); + + assertEquals(TITLE_2, result.getJSONArray("items").getJSONObject(0).get("title")); + + + // Switch user, check that permissions are correct + // (Drafts won't be seen) + this.authenticationComponent.setCurrentUser(USER_TWO); + + response = sendRequest(new GetRequest(url), 200); + result = new JSONObject(response.getContentAsString()); + assertEquals(2, result.getInt("total")); + assertEquals(0, result.getInt("startIndex")); + assertEquals(2, result.getInt("itemCount")); + + assertEquals(2, result.getJSONArray("items").length()); + blog = result.getJSONArray("items").getJSONObject(0); + assertEquals(TITLE_3, blog.get("title")); + assertEquals(false, blog.getBoolean("isDraft")); + perms = blog.getJSONObject("permissions"); + assertEquals(false, perms.getBoolean("delete")); + assertEquals(true, perms.getBoolean("edit")); + + blog = result.getJSONArray("items").getJSONObject(1); + assertEquals(TITLE_1, blog.get("title")); + assertEquals(false, blog.getBoolean("isDraft")); + perms = blog.getJSONObject("permissions"); + assertEquals(false, perms.getBoolean("delete")); + assertEquals(true, perms.getBoolean("edit")); + } + + public void testGetNew() throws Exception + { + String url = URL_BLOG_POSTS + "/new"; + Response response = sendRequest(new GetRequest(url), 200); + JSONObject result = new JSONObject(response.getContentAsString()); + + // we should have posts.size + assertEquals(this.posts.size(), result.getInt("total")); + } + + public void testGetDrafts() throws Exception + { + String url = URL_BLOG_POSTS + "/mydrafts"; + Response response = sendRequest(new GetRequest(URL_BLOG_POSTS), 200); + JSONObject result = new JSONObject(response.getContentAsString()); + + // we should have drafts.size resultss + assertEquals(this.drafts.size(), result.getInt("total")); + + // the second user should have zero + this.authenticationComponent.setCurrentUser(USER_TWO); + response = sendRequest(new GetRequest(url), 200); + result = new JSONObject(response.getContentAsString()); + assertEquals(0, result.getInt("total")); + this.authenticationComponent.setCurrentUser(USER_ONE); + + } + + public void testMyPublished() throws Exception + { + String url = URL_BLOG_POSTS + "/mypublished"; + Response response = sendRequest(new GetRequest(url), 200); + JSONObject result = new JSONObject(response.getContentAsString()); + + // we should have posts.size results + assertEquals(this.drafts.size(), result.getInt("total")); + + // the second user should have zero + this.authenticationComponent.setCurrentUser(USER_TWO); + response = sendRequest(new GetRequest(url), 200); + result = new JSONObject(response.getContentAsString()); + assertEquals(0, result.getInt("total")); + this.authenticationComponent.setCurrentUser(USER_ONE); + } + + public void testComments() throws Exception + { + JSONObject item = createPost("test", "test", null, false, 200); + String name = item.getString("name"); + String nodeRef = item.getString("nodeRef"); + + JSONObject commentOne = createComment(nodeRef, "comment", "content", 200); + JSONObject commentTwo = createComment(nodeRef, "comment", "content", 200); + + // fetch the comments + Response response = sendRequest(new GetRequest(getCommentsUrl(nodeRef)), 200); + JSONObject result = new JSONObject(response.getContentAsString()); + assertEquals(2, result.getInt("total")); + + // add another one + JSONObject commentThree = createComment(nodeRef, "comment", "content", 200); + + response = sendRequest(new GetRequest(getCommentsUrl(nodeRef)), 200); + result = new JSONObject(response.getContentAsString()); + assertEquals(3, result.getInt("total")); + + // delete the last comment + response = sendRequest(new DeleteRequest(getCommentUrl(commentThree.getString("nodeRef"))), 200); + + response = sendRequest(new GetRequest(getCommentsUrl(nodeRef)), 200); + result = new JSONObject(response.getContentAsString()); + assertEquals(2, result.getInt("total")); + + JSONObject commentTwoUpdated = updateComment(commentTwo.getString("nodeRef"), "new title", "new content", 200); + assertEquals("new title", commentTwoUpdated.getString("title")); + assertEquals("new content", commentTwoUpdated.getString("content")); + } + + /** + * REPO-828 (MNT-16401) + * @throws Exception + */ + public void testDeleteCommentPostActivity() throws Exception + { + this.authenticationComponent.setCurrentUser(USER_ONE); + JSONObject item = createPost("testActivity", "test", null, false, 200); + assertNotNull(item); + postLookup.execute(); + feedGenerator.execute(); + int activityNumStart = activityService.getSiteFeedEntries(SITE_SHORT_NAME_BLOG).size(); + String nodeRef = item.getString("nodeRef"); + JSONObject commentOne = createComment(nodeRef, "comment", "content", 200); + assertNotNull(item); + postLookup.execute(); + feedGenerator.execute(); + int activityNumNext = activityService.getSiteFeedEntries(SITE_SHORT_NAME_BLOG).size(); + assertEquals("The activity feeds were not generated after adding a comment", activityNumStart + 1, activityNumNext); + activityNumStart = activityNumNext; + NodeRef commentNodeRef = new NodeRef(commentOne.getString("nodeRef")); + Response resp = sendRequest(new DeleteRequest(getDeleteCommentUrl(commentNodeRef)), 200); + assertTrue(resp.getStatus() == 200); + postLookup.execute(); + feedGenerator.execute(); + activityNumNext = activityService.getSiteFeedEntries(SITE_SHORT_NAME_BLOG).size(); + assertEquals("The activity feeds were not generated after deleting a comment", activityNumStart + 1, activityNumNext); + } + + /** + * You can attach information to the blog container relating + * to integration with external blogs. + * This tests that feature + */ + public void testBlogIntegration() throws Exception + { + // Try to fetch the details on a new site + Response response = sendRequest(new GetRequest(URL_BLOG_CORE), 200); + String json = response.getContentAsString(); + JSONObject result = new JSONObject(json); + + assertEquals("No item in:\n"+json, true, result.has("item")); + JSONObject item = result.getJSONObject("item"); + + assertEquals("Missing key in: " + item, true, item.has("qnamePath")); + assertEquals("Missing key in: " + item, true, item.has("detailsUrl")); + assertEquals("Missing key in: " + item, true, item.has("blogPostsUrl")); + + // Blog properties are empty to start + assertEquals("", item.getString("type")); + assertEquals("", item.getString("name")); + assertEquals("", item.getString("description")); + assertEquals("", item.getString("url")); + assertEquals("", item.getString("username")); + assertEquals("", item.getString("password")); + + + // Have it updated + JSONObject blog = new JSONObject(); + blog.put("blogType", "wordpress"); + blog.put("blogName", "A Blog!"); + blog.put("username", "guest"); + sendRequest(new PutRequest(URL_BLOG_CORE, blog.toString(), "application/json"), Status.STATUS_OK); + + // Check again now + response = sendRequest(new GetRequest(URL_BLOG_CORE), 200); + json = response.getContentAsString(); + result = new JSONObject(json); + + assertEquals("No item in:\n"+json, true, result.has("item")); + item = result.getJSONObject("item"); + + assertEquals("Missing key in: " + item, true, item.has("qnamePath")); + assertEquals("Missing key in: " + item, true, item.has("detailsUrl")); + assertEquals("Missing key in: " + item, true, item.has("blogPostsUrl")); + + // Blog properties should now be set + assertEquals("wordpress", item.getString("type")); + assertEquals("A Blog!", item.getString("name")); + assertEquals("", item.getString("description")); + assertEquals("", item.getString("url")); + assertEquals("guest", item.getString("username")); + assertEquals("", item.getString("password")); + } + + /** + * Does some stress tests. + * + * Currently observed errors: + * 1. [repo.action.AsynchronousActionExecutionQueueImpl] Failed to execute asynchronous action: Action[ id=485211db-f117-4976-9530-ab861a19f563, node=null ] + * org.alfresco.repo.security.permissions.AccessDeniedException: Access Denied. You do not have the appropriate permissions to perform this operation. + * + * 2. JSONException, but with root cause being + * get(assocs) failed on instance of org.alfresco.repo.template.TemplateNode + * The problematic instruction: + * ---------- + * ==> if person.assocs["cm:avatar"]?? [on line 4, column 7 in org/alfresco/repository/blogs/blogpost.lib.ftl] + * + * @throws Exception + */ + public void _testTagsStressTest() throws Exception + { + final List exceptions = Collections.synchronizedList(new ArrayList()); + List threads = new ArrayList(); + + System.err.println("Creating and starting threads..."); + for (int x=0; x < 3; x++) + { + Thread t = new Thread(new Runnable() + { + public void run() + { + // set the correct user + authenticationComponent.setCurrentUser(USER_ONE); + + // now do some requests + try + { + for (int y=0; y < 3; y++) + { + off_testPostTags(); + off_testClearTags(); + } + System.err.println("------------- SUCCEEDED ---------------"); + } catch (Exception e) + { + System.err.println("------------- ERROR ---------------"); + exceptions.add(e); + e.printStackTrace(); + return; + } + }}); + + threads.add(t); + t.start(); + } + /*for (Thread t : threads) + { + t.start(); + }*/ + + for (Thread t : threads) + { + t.join(); + } + + System.err.println("------------- STACK TRACES ---------------"); + for (Exception e : exceptions) + { + e.printStackTrace(); + } + System.err.println("------------- STACK TRACES END ---------------"); + if (exceptions.size() > 0) + { + throw exceptions.get(0); + } + } + + public void off_testPostTags() throws Exception + { + String[] tags = { "first", "test" }; + JSONObject item = createPost("tagtest", "tagtest", tags, false, 200); + assertEquals(2, item.getJSONArray("tags").length()); + assertEquals("first", item.getJSONArray("tags").get(0)); + assertEquals("test", item.getJSONArray("tags").get(1)); + + item = updatePost(item.getString("name"), null, null, new String[] { "First", "Test", "Second" }, false, 200); + assertEquals(3, item.getJSONArray("tags").length()); + assertEquals("first", item.getJSONArray("tags").get(0)); + assertEquals("test", item.getJSONArray("tags").get(1)); + assertEquals("second", item.getJSONArray("tags").get(2)); + } + + public void off_testClearTags() throws Exception + { + String[] tags = { "abc", "def"}; + JSONObject item = createPost("tagtest", "tagtest", tags, false, 200); + assertEquals(2, item.getJSONArray("tags").length()); + + item = updatePost(item.getString("name"), null, null, new String[0], false, 200); + assertEquals(0, item.getJSONArray("tags").length()); + } + + /** + * Test for MNT-11964 + * @throws Exception + */ + public void testBlogPermission() throws Exception + { + this.authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName()); + + String siteName = SITE_SHORT_NAME_BLOG + GUID.generate(); + this.siteService.createSite("BlogSitePreset", siteName, "BlogSiteTitle", "BlogSiteDescription", SiteVisibility.PUBLIC); + + String userName = USER_ONE + GUID.generate(); + createUser(userName, SiteModel.SITE_COLLABORATOR, siteName); + + // Check permissions for admin + checkBlogPermissions(siteName); + + // Check permissions for user + this.authenticationComponent.setCurrentUser(userName); + checkBlogPermissions(siteName); + + // Cleanup + this.authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName()); + this.siteService.deleteSite(siteName); + + // Create a new site as user + this.authenticationComponent.setCurrentUser(userName); + siteName = SITE_SHORT_NAME_BLOG + GUID.generate(); + this.siteService.createSite("BlogSitePreset", siteName, "BlogSiteTitle", "BlogSiteDescription", SiteVisibility.PUBLIC); + + // Check permissions for user + checkBlogPermissions(siteName); + + // Check permissions for admin + this.authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName()); + checkBlogPermissions(siteName); + + // Cleanup + this.siteService.deleteSite(siteName); + this.personService.deletePerson(userName); + } + + private void checkBlogPermissions(String siteName) throws Exception + { + String url = "/api/blog/site/" + siteName + "/" + COMPONENT_BLOG; + Response response = sendRequest(new GetRequest(url), 200); + JSONObject result = new JSONObject(response.getContentAsString()); + + assertTrue("The user sould have permission to create a new blog.", result.getJSONObject("item").getJSONObject("permissions").getBoolean("create")); + } +} \ No newline at end of file diff --git a/src/test/java/org/alfresco/repo/web/scripts/calendar/CalendarRestApiTest.java b/src/test/java/org/alfresco/repo/web/scripts/calendar/CalendarRestApiTest.java index 1c048226e0..ec71468b2a 100644 --- a/src/test/java/org/alfresco/repo/web/scripts/calendar/CalendarRestApiTest.java +++ b/src/test/java/org/alfresco/repo/web/scripts/calendar/CalendarRestApiTest.java @@ -1,28 +1,28 @@ -/* - * #%L - * Alfresco Remote API - * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited - * %% - * This file is part of the Alfresco software. - * If the software was purchased under a paid Alfresco license, the terms of - * the paid license agreement will prevail. Otherwise, the software is - * provided under the following open source license terms: - * - * 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 . - * #L% - */ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ package org.alfresco.repo.web.scripts.calendar; import java.util.ArrayList; @@ -120,7 +120,7 @@ public class CalendarRestApiTest extends BaseWebScriptTest SiteInfo siteInfo = this.siteService.getSite(SITE_SHORT_NAME_CALENDAR); if (siteInfo == null) { - this.siteService.createSite("CalendarSitePreset", SITE_SHORT_NAME_CALENDAR, "CalendarSiteTitle", "CalendarSiteDescription", SiteVisibility.PUBLIC); + this.siteService.createSite("CalendarSitePreset", SITE_SHORT_NAME_CALENDAR, "CalendarSiteTitle", "BlogSiteDescription", SiteVisibility.PUBLIC); } // Ensure the calendar container is there diff --git a/src/test/java/org/alfresco/repo/web/scripts/comment/CommentsApiTest.java b/src/test/java/org/alfresco/repo/web/scripts/comment/CommentsApiTest.java index 254e0c0ae9..31fcbdd8b5 100644 --- a/src/test/java/org/alfresco/repo/web/scripts/comment/CommentsApiTest.java +++ b/src/test/java/org/alfresco/repo/web/scripts/comment/CommentsApiTest.java @@ -78,7 +78,7 @@ import org.springframework.extensions.webscripts.WebScriptException; /** * TODO: Fix the loose transaction handling. - * TODO: Rationalise with other v0 Comment REST API tests. See also ACE-5437. + * TODO: Rationalise with other v0 Comment REST API tests (eg. see BlogServiceTest ... etc). See also ACE-5437. */ public class CommentsApiTest extends BaseWebScriptTest { diff --git a/src/test/java/org/alfresco/repo/web/scripts/discussion/DiscussionRestApiTest.java b/src/test/java/org/alfresco/repo/web/scripts/discussion/DiscussionRestApiTest.java new file mode 100644 index 0000000000..2a178643fb --- /dev/null +++ b/src/test/java/org/alfresco/repo/web/scripts/discussion/DiscussionRestApiTest.java @@ -0,0 +1,1306 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.web.scripts.discussion; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.transaction.UserTransaction; + +import org.alfresco.model.ContentModel; +import org.alfresco.model.ForumModel; +import org.alfresco.repo.node.archive.NodeArchiveService; +import org.alfresco.repo.policy.BehaviourFilter; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.site.SiteModel; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.repo.web.scripts.BaseWebScriptTest; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.MutableAuthenticationService; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.cmr.site.SiteInfo; +import org.alfresco.service.cmr.site.SiteService; +import org.alfresco.service.cmr.site.SiteVisibility; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.GUID; +import org.alfresco.util.PropertyMap; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.json.JSONArray; +import org.json.JSONObject; +import org.springframework.extensions.webscripts.Status; +import org.springframework.extensions.webscripts.TestWebScriptServer.DeleteRequest; +import org.springframework.extensions.webscripts.TestWebScriptServer.GetRequest; +import org.springframework.extensions.webscripts.TestWebScriptServer.PostRequest; +import org.springframework.extensions.webscripts.TestWebScriptServer.PutRequest; +import org.springframework.extensions.webscripts.TestWebScriptServer.Response; + +/** + * Unit Test to test Discussions Web Script API + */ +public class DiscussionRestApiTest extends BaseWebScriptTest +{ + @SuppressWarnings("unused") + private static Log logger = LogFactory.getLog(DiscussionRestApiTest.class); + + private static final String DELETED_REPLY_POST_MARKER = "[[deleted]]"; + + private MutableAuthenticationService authenticationService; + private AuthenticationComponent authenticationComponent; + private TransactionService transactionService; + private BehaviourFilter policyBehaviourFilter; + private PermissionService permissionService; + private PersonService personService; + private SiteService siteService; + private NodeService nodeService; + private NodeService internalNodeService; + private NodeArchiveService nodeArchiveService; + + private static final String USER_ONE = "UserOneThird"; + private static final String USER_TWO = "UserTwoThird"; + private static final String SITE_SHORT_NAME_DISCUSSION = "DiscussionSiteShortNameThree"; + private static final String COMPONENT_DISCUSSION = "discussions"; + + private static final String URL_FORUM_SITE_POST = "/api/forum/post/site/" + SITE_SHORT_NAME_DISCUSSION + "/" + COMPONENT_DISCUSSION + "/"; + private static final String URL_FORUM_SITE_POSTS = "/api/forum/site/" + SITE_SHORT_NAME_DISCUSSION + "/" + COMPONENT_DISCUSSION + "/posts"; + private static final String URL_FORUM_NODE_POST_BASE = "/api/forum/post/node/"; // Plus node id + private static final String URL_FORUM_NODE_POSTS_BASE = "/api/forum/node/"; // Plus node id + /posts + + private List posts = new ArrayList(5); + private NodeRef FORUM_NODE; + + + // General methods + + @Override + protected void setUp() throws Exception + { + super.setUp(); + + this.authenticationService = (MutableAuthenticationService)getServer().getApplicationContext().getBean("AuthenticationService"); + this.authenticationComponent = (AuthenticationComponent)getServer().getApplicationContext().getBean("authenticationComponent"); + this.policyBehaviourFilter = (BehaviourFilter)getServer().getApplicationContext().getBean("policyBehaviourFilter"); + this.transactionService = (TransactionService)getServer().getApplicationContext().getBean("transactionService"); + this.permissionService = (PermissionService)getServer().getApplicationContext().getBean("PermissionService"); + this.personService = (PersonService)getServer().getApplicationContext().getBean("PersonService"); + this.siteService = (SiteService)getServer().getApplicationContext().getBean("SiteService"); + this.nodeService = (NodeService)getServer().getApplicationContext().getBean("NodeService"); + this.internalNodeService = (NodeService)getServer().getApplicationContext().getBean("nodeService"); + this.nodeArchiveService = (NodeArchiveService)getServer().getApplicationContext().getBean("nodeArchiveService"); + + // Authenticate as user + this.authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName()); + + // Create test site + // - only create the site if it doesn't already exist + SiteInfo siteInfo = this.siteService.getSite(SITE_SHORT_NAME_DISCUSSION); + if (siteInfo == null) + { + siteInfo = this.siteService.createSite("DiscussionSitePreset", SITE_SHORT_NAME_DISCUSSION, + "DiscussionSiteTitle", "DiscussionSiteDescription", SiteVisibility.PUBLIC); + } + final NodeRef siteNodeRef = siteInfo.getNodeRef(); + + // Create the forum + final String forumNodeName = "TestForum"; + FORUM_NODE = nodeService.getChildByName(siteInfo.getNodeRef(), ContentModel.ASSOC_CONTAINS, forumNodeName); + if (FORUM_NODE == null) + { + FORUM_NODE = transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { + @Override + public NodeRef execute() throws Throwable { + Map props = new HashMap(5); + props.put(ContentModel.PROP_NAME, forumNodeName); + props.put(ContentModel.PROP_TITLE, forumNodeName); + + return nodeService.createNode( + siteNodeRef, ContentModel.ASSOC_CONTAINS, + QName.createQName(forumNodeName), ForumModel.TYPE_FORUM, props + ).getChildRef(); + } + }); + } + + // Create users + createUser(USER_ONE, SiteModel.SITE_COLLABORATOR, SITE_SHORT_NAME_DISCUSSION); + createUser(USER_TWO, SiteModel.SITE_CONTRIBUTOR, SITE_SHORT_NAME_DISCUSSION); + + // Do tests as inviter user + this.authenticationComponent.setCurrentUser(USER_ONE); + } + + @Override + protected void tearDown() throws Exception + { + super.tearDown(); + + // admin user required to delete user + this.authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName()); + + // delete the discussions users + if(personService.personExists(USER_ONE)) + { + personService.deletePerson(USER_ONE); + } + if (this.authenticationService.authenticationExists(USER_ONE)) + { + this.authenticationService.deleteAuthentication(USER_ONE); + } + + if(personService.personExists(USER_TWO)) + { + personService.deletePerson(USER_TWO); + } + if (this.authenticationService.authenticationExists(USER_TWO)) + { + this.authenticationService.deleteAuthentication(USER_TWO); + } + + SiteInfo siteInfo = this.siteService.getSite(SITE_SHORT_NAME_DISCUSSION); + if (siteInfo != null) + { + // delete discussions test site + RetryingTransactionCallback deleteCallback = new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + siteService.deleteSite(SITE_SHORT_NAME_DISCUSSION); + return null; + } + }; + transactionService.getRetryingTransactionHelper().doInTransaction(deleteCallback); + nodeArchiveService.purgeArchivedNode(nodeArchiveService.getArchivedNode(siteInfo.getNodeRef())); + } + } + + private void createUser(String userName, String role, String siteName) + { + // if user with given user name doesn't already exist then create user + if (!this.authenticationService.authenticationExists(userName)) + { + // create user + this.authenticationService.createAuthentication(userName, "password".toCharArray()); + } + + if (!this.personService.personExists(userName)) + { + // create person properties + PropertyMap personProps = new PropertyMap(); + personProps.put(ContentModel.PROP_USERNAME, userName); + personProps.put(ContentModel.PROP_FIRSTNAME, "FirstName123"); + personProps.put(ContentModel.PROP_LASTNAME, "LastName123"); + personProps.put(ContentModel.PROP_EMAIL, "FirstName123.LastName123@email.com"); + personProps.put(ContentModel.PROP_JOBTITLE, "JobTitle123"); + personProps.put(ContentModel.PROP_JOBTITLE, "Organisation123"); + + // create person node for user + this.personService.createPerson(personProps); + } + + // add the user as a member with the given role + this.siteService.setMembership(siteName, userName, role); + + // Give the test user access to the test node + // They need to be able to read it, and create children of it + permissionService.setPermission(FORUM_NODE, userName, PermissionService.READ, true); + permissionService.setPermission(FORUM_NODE, userName, PermissionService.CREATE_CHILDREN, true); + } + + + // ----------------------------------------------------- + // Test helper methods + // ----------------------------------------------------- + + /** + * Creates a new topic+post in the test site + */ + private JSONObject createSitePost(String title, String content, int expectedStatus) + throws Exception + { + return doCreatePost(URL_FORUM_SITE_POSTS, title, content, expectedStatus); + } + + /** + * Creates a new topic+post under the given node + */ + private JSONObject createNodePost(NodeRef nodeRef, String title, String content, + int expectedStatus) throws Exception + { + return doCreatePost(getPostsUrl(nodeRef), title, content, expectedStatus); + } + + private JSONObject doCreatePost(String url, String title, String content, + int expectedStatus) throws Exception + { + JSONObject post = new JSONObject(); + post.put("title", title); + post.put("content", content); + Response response = sendRequest(new PostRequest(url, post.toString(), "application/json"), expectedStatus); + + if (expectedStatus != Status.STATUS_OK) + { + return null; + } + + JSONObject result = new JSONObject(response.getContentAsString()); + JSONObject item = result.getJSONObject("item"); + posts.add(item.getString("name")); + return item; + } + + private JSONObject updatePost(NodeRef nodeRef, String title, String content, + int expectedStatus) throws Exception + { + return doUpdatePost(getPostUrl(nodeRef), title, content, expectedStatus); + } + + private JSONObject updatePost(String name, String title, String content, + int expectedStatus) throws Exception + { + return doUpdatePost(URL_FORUM_SITE_POST + name, title, content, expectedStatus); + } + + private JSONObject doUpdatePost(String url, String title, String content, + int expectedStatus) throws Exception + { + JSONObject post = new JSONObject(); + post.put("title", title); + post.put("content", content); + Response response = sendRequest(new PutRequest(url, post.toString(), "application/json"), expectedStatus); + + if (expectedStatus != Status.STATUS_OK) + { + return null; + } + + JSONObject result = new JSONObject(response.getContentAsString()); + return result.getJSONObject("item"); + } + + private JSONObject getPost(String name, int expectedStatus) throws Exception + { + return doGetPost(URL_FORUM_SITE_POST + name, expectedStatus); + } + + private JSONObject getPost(NodeRef nodeRef, int expectedStatus) throws Exception + { + return doGetPost(getPostUrl(nodeRef), expectedStatus); + } + + private JSONObject doGetPost(String url, int expectedStatus) throws Exception + { + Response response = sendRequest(new GetRequest(url), expectedStatus); + if (expectedStatus == Status.STATUS_OK) + { + JSONObject result = new JSONObject(response.getContentAsString()); + return result.getJSONObject("item"); + } + else + { + return null; + } + } + + private JSONObject getReplies(String name, int expectedStatus) throws Exception + { + return doGetReplies(getRepliesUrl(name), expectedStatus); + } + + private JSONObject getReplies(NodeRef nodeRef, int expectedStatus) throws Exception + { + return doGetReplies(getRepliesUrl(nodeRef), expectedStatus); + } + + private JSONObject doGetReplies(String url, int expectedStatus) throws Exception + { + Response response = sendRequest(new GetRequest(url), expectedStatus); + if (expectedStatus == Status.STATUS_OK) + { + JSONObject result = new JSONObject(response.getContentAsString()); + return result; + } + else + { + return null; + } + } + + private JSONObject getPosts(String type, int expectedStatus) throws Exception + { + return doGetPosts(URL_FORUM_SITE_POSTS, type, expectedStatus); + } + + private JSONObject getPosts(NodeRef nodeRef, String type, int expectedStatus) throws Exception + { + return doGetPosts(getPostsUrl(nodeRef), type, expectedStatus); + } + + private JSONObject doGetPosts(String baseUrl, String type, int expectedStatus) throws Exception + { + String url = null; + if (type == null) + { + url = baseUrl; + } + else if (type == "limit") + { + url = baseUrl + "?pageSize=1"; + } + else if (type == "hot") + { + url = baseUrl + "/hot"; + } + else if (type == "mine") + { + url = baseUrl + "/myposts"; + } + else if (type.startsWith("new")) + { + url = baseUrl + "/" + type; + } + else + { + throw new IllegalArgumentException("Invalid search type " + type); + } + + Response response = sendRequest(new GetRequest(url), expectedStatus); + if (expectedStatus == Status.STATUS_OK) + { + JSONObject result = new JSONObject(response.getContentAsString()); + return result; + } + else + { + return null; + } + } + + private JSONObject deletePost(String name, int expectedStatus) throws Exception + { + return doDeletePost(URL_FORUM_SITE_POST + name, expectedStatus); + } + + private JSONObject deletePost(NodeRef nodeRef, int expectedStatus) throws Exception + { + return doDeletePost(getPostUrl(nodeRef), expectedStatus); + } + + private JSONObject doDeletePost(String url, int expectedStatus) throws Exception + { + Response response = sendRequest(new DeleteRequest(url), Status.STATUS_OK); + if (expectedStatus == Status.STATUS_OK) + { + return new JSONObject(response.getContentAsString()); + } + else + { + return null; + } + } + + private String getRepliesUrl(NodeRef nodeRef) + { + return getPostUrl(nodeRef) + "/replies"; + } + + private String getRepliesUrl(String postName) + { + return URL_FORUM_SITE_POST + postName + "/replies"; + } + + private String getPostUrl(NodeRef nodeRef) + { + return URL_FORUM_NODE_POST_BASE + nodeRef.toString().replace("://", "/"); + } + + private String getPostsUrl(NodeRef nodeRef) + { + return URL_FORUM_NODE_POSTS_BASE + nodeRef.toString().replace("://", "/") + "/posts"; + } + + private JSONObject createReply(NodeRef nodeRef, String title, String content, int expectedStatus) + throws Exception + { + JSONObject reply = new JSONObject(); + reply.put("title", title); + reply.put("content", content); + Response response = sendRequest(new PostRequest(getRepliesUrl(nodeRef), reply.toString(), "application/json"), expectedStatus); + + if (expectedStatus != 200) + { + return null; + } + + JSONObject result = new JSONObject(response.getContentAsString()); + return result.getJSONObject("item"); + } + + private JSONObject updateComment(NodeRef nodeRef, String title, String content, + int expectedStatus) throws Exception + { + JSONObject comment = new JSONObject(); + comment.put("title", title); + comment.put("content", content); + Response response = sendRequest(new PutRequest(getPostUrl(nodeRef), comment.toString(), "application/json"), expectedStatus); + + if (expectedStatus != Status.STATUS_OK) + { + return null; + } + + //logger.debug("Comment updated: " + response.getContentAsString()); + JSONObject result = new JSONObject(response.getContentAsString()); + return result.getJSONObject("item"); + } + + /** + * Monkeys with the created and published dates on a topic+posts + */ + private void pushCreatedDateBack(NodeRef node, int daysAgo) throws Exception + { + Date created = (Date)nodeService.getProperty(node, ContentModel.PROP_CREATED); + Date newCreated = new Date(created.getTime() - daysAgo*24*60*60*1000); + Date published = (Date)nodeService.getProperty(node, ContentModel.PROP_PUBLISHED); + if(published == null) published = created; + Date newPublished = new Date(published.getTime() - daysAgo*24*60*60*1000); + + UserTransaction txn = transactionService.getUserTransaction(); + txn.begin(); + + this.policyBehaviourFilter.disableBehaviour(ContentModel.ASPECT_AUDITABLE); + internalNodeService.setProperty(node, ContentModel.PROP_CREATED, newCreated); + internalNodeService.setProperty(node, ContentModel.PROP_MODIFIED, newCreated); + internalNodeService.setProperty(node, ContentModel.PROP_PUBLISHED, newPublished); + this.policyBehaviourFilter.enableBehaviour(ContentModel.ASPECT_AUDITABLE); + + txn.commit(); + + // Now chance something else on the node to have it re-indexed + nodeService.setProperty(node, ContentModel.PROP_CREATED, newCreated); + nodeService.setProperty(node, ContentModel.PROP_MODIFIED, newCreated); + nodeService.setProperty(node, ContentModel.PROP_PUBLISHED, newPublished); + nodeService.setProperty(node, ContentModel.PROP_DESCRIPTION, "Forced change"); + + // Finally change any children (eg if updating a topic, do the posts) + for(ChildAssociationRef ref : nodeService.getChildAssocs(node)) + { + pushCreatedDateBack(ref.getChildRef(), daysAgo); + } + } + + // ----------------------------------------------------- + // Tests + // ----------------------------------------------------- + + public void testCreateForumPost() throws Exception + { + String title = "test"; + String content = "test"; + JSONObject item = createSitePost(title, content, Status.STATUS_OK); + + // Check that the values in the response are correct + assertEquals(title, item.get("title")); + assertEquals(content, item.get("content")); + assertEquals(0, item.get("replyCount")); + assertEquals("Invalid JSON " + item, true, item.has("createdOn")); + assertEquals("Invalid JSON " + item, true, item.has("modifiedOn")); + assertEquals("Invalid JSON " + item, true, item.has("author")); + assertEquals("Invalid JSON " + item, true, item.has("permissions")); + assertEquals("Invalid JSON " + item, true, item.has("url")); + assertEquals("Invalid JSON " + item, true, item.has("repliesUrl")); + assertEquals("Invalid JSON " + item, true, item.has("nodeRef")); + + // Save some details + String name = item.getString("name"); + NodeRef nodeRef = new NodeRef(item.getString("nodeRef")); + + + // Fetch the post by name and check + item = getPost(name, Status.STATUS_OK); + + assertEquals(title, item.get("title")); + assertEquals(content, item.get("content")); + assertEquals(0, item.get("replyCount")); + assertEquals("Invalid JSON " + item, true, item.has("createdOn")); + assertEquals("Invalid JSON " + item, true, item.has("modifiedOn")); + assertEquals("Invalid JSON " + item, true, item.has("author")); + assertEquals("Invalid JSON " + item, true, item.has("permissions")); + assertEquals("Invalid JSON " + item, true, item.has("url")); + assertEquals("Invalid JSON " + item, true, item.has("repliesUrl")); + assertEquals("Invalid JSON " + item, true, item.has("nodeRef")); + + + // Fetch the post by noderef and check + item = getPost(nodeRef, Status.STATUS_OK); + + assertEquals(title, item.get("title")); + assertEquals(content, item.get("content")); + assertEquals(0, item.get("replyCount")); + assertEquals("Invalid JSON " + item, true, item.has("createdOn")); + assertEquals("Invalid JSON " + item, true, item.has("modifiedOn")); + assertEquals("Invalid JSON " + item, true, item.has("author")); + assertEquals("Invalid JSON " + item, true, item.has("permissions")); + assertEquals("Invalid JSON " + item, true, item.has("url")); + assertEquals("Invalid JSON " + item, true, item.has("repliesUrl")); + assertEquals("Invalid JSON " + item, true, item.has("nodeRef")); + + + // Create another post, this time by noderef + title = "By Node Title"; + content = "By Node Content"; + item = createNodePost(FORUM_NODE, title, content, Status.STATUS_OK); + + assertEquals(title, item.get("title")); + assertEquals(content, item.get("content")); + assertEquals(0, item.get("replyCount")); + + // Check it by noderef + nodeRef = new NodeRef(item.getString("nodeRef")); + item = getPost(nodeRef, Status.STATUS_OK); + + assertEquals(title, item.get("title")); + assertEquals(content, item.get("content")); + assertEquals(0, item.get("replyCount")); + } + + public void testUpdateForumPost() throws Exception + { + String title = "test"; + String content = "test"; + JSONObject item = createSitePost(title, content, 200); + + // check that the values + assertEquals(title, item.get("title")); + assertEquals(content, item.get("content")); + assertEquals(false, item.getBoolean("isUpdated")); + + assertEquals(true, item.has("name")); + String name = item.getString("name"); + assertEquals(true, item.has("nodeRef")); + NodeRef nodeRef = new NodeRef(item.getString("nodeRef")); + + // fetch the post by name + item = getPost(item.getString("name"), 200); + assertEquals(title, item.get("title")); + assertEquals(content, item.get("content")); + assertEquals(false, item.getBoolean("isUpdated")); + + // Fetch the post by noderef + item = getPost(nodeRef, 200); + assertEquals(title, item.get("title")); + assertEquals(content, item.get("content")); + assertEquals(false, item.getBoolean("isUpdated")); + + + // Update it by name + String title2 = "updated test"; + String content2 = "test updated"; + item = updatePost(name, title2, content2, 200); + + // Check the response + assertEquals(title2, item.get("title")); + assertEquals(content2, item.get("content")); + assertEquals(name, item.get("name")); + assertEquals(nodeRef.toString(), item.get("nodeRef")); + assertEquals(true, item.getBoolean("isUpdated")); + + // Fetch and check + item = getPost(nodeRef, 200); + assertEquals(title2, item.get("title")); + assertEquals(content2, item.get("content")); + assertEquals(name, item.get("name")); + assertEquals(nodeRef.toString(), item.get("nodeRef")); + assertEquals(true, item.getBoolean("isUpdated")); + + + // Update it again, this time by noderef + String title3 = "updated 3 test"; + String content3 = "test 3 updated"; + item = updatePost(nodeRef, title3, content3, 200); + + // Check that the values returned are correct + assertEquals(title3, item.get("title")); + assertEquals(content3, item.get("content")); + assertEquals(name, item.get("name")); + assertEquals(nodeRef.toString(), item.get("nodeRef")); + assertEquals(true, item.getBoolean("isUpdated")); + + // Fetch and re-check + item = getPost(nodeRef, 200); + assertEquals(title3, item.get("title")); + assertEquals(content3, item.get("content")); + assertEquals(name, item.get("name")); + assertEquals(nodeRef.toString(), item.get("nodeRef")); + assertEquals(true, item.getBoolean("isUpdated")); + } + + /** + * Tests that the permissions details included with topics and + * posts are correct + */ + public void testPermissions() throws Exception + { + // Create a post, and check the details on it + JSONObject item = createSitePost("test", "test", Status.STATUS_OK); + String name = item.getString("name"); + + JSONObject perms = item.getJSONObject("permissions"); + assertEquals(true, perms.getBoolean("edit")); + assertEquals(true, perms.getBoolean("reply")); + assertEquals(true, perms.getBoolean("delete")); + + // Check on a fetch too + item = getPost(name, Status.STATUS_OK); + perms = item.getJSONObject("permissions"); + assertEquals(true, perms.getBoolean("edit")); + assertEquals(true, perms.getBoolean("reply")); + assertEquals(true, perms.getBoolean("delete")); + + + // Switch to another user, see what they see + this.authenticationComponent.setCurrentUser(USER_TWO); + + item = getPost(name, Status.STATUS_OK); + perms = item.getJSONObject("permissions"); + assertEquals(false, perms.getBoolean("edit")); + assertEquals(true, perms.getBoolean("reply")); + assertEquals(false, perms.getBoolean("delete")); + + + // Remove the user from the site, see the change + this.siteService.removeMembership(SITE_SHORT_NAME_DISCUSSION, USER_TWO); + + item = getPost(name, Status.STATUS_OK); + perms = item.getJSONObject("permissions"); + assertEquals(false, perms.getBoolean("edit")); + assertEquals(false, perms.getBoolean("reply")); + assertEquals(false, perms.getBoolean("delete")); + + + // Make the site private, will vanish + SiteInfo siteInfo = siteService.getSite(SITE_SHORT_NAME_DISCUSSION); + siteInfo.setVisibility(SiteVisibility.PRIVATE); + this.authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName()); + this.siteService.updateSite(siteInfo); + this.authenticationComponent.setCurrentUser(USER_TWO); + + // On a private site we're not a member of, shouldn't be visable at all + getPost(name, Status.STATUS_NOT_FOUND); + } + + /** + * ALF-1973 - If the user who added a reply has been deleted, don't break + */ + public void testViewReplyByDeletedUser() throws Exception + { + // Create a post + JSONObject item = createSitePost("test", "test", Status.STATUS_OK); + String name = item.getString("name"); + NodeRef topicNodeRef = new NodeRef(item.getString("nodeRef")); + + // Now create a reply as a different user + this.authenticationComponent.setCurrentUser(USER_TWO); + createReply(topicNodeRef, "Reply", "By the other user", Status.STATUS_OK); + + // Should see the reply + item = getReplies(name, Status.STATUS_OK); + assertEquals(1, item.getJSONArray("items").length()); + + // Delete the user, check that the reply still shows + this.authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName()); + personService.deletePerson(USER_TWO); + this.authenticationComponent.setCurrentUser(USER_ONE); + + item = getReplies(name, Status.STATUS_OK); + assertEquals(1, item.getJSONArray("items").length()); + } + + public void testAddReply() throws Exception + { + // Create a root post + JSONObject item = createSitePost("test", "test", Status.STATUS_OK); + String topicName = item.getString("name"); + NodeRef topicNodeRef = new NodeRef(item.getString("nodeRef")); + + // Add a reply + JSONObject reply = createReply(topicNodeRef, "test", "test", Status.STATUS_OK); + NodeRef replyNodeRef = new NodeRef(reply.getString("nodeRef")); + assertEquals("test", reply.getString("title")); + assertEquals("test", reply.getString("content")); + + // Add a reply to the reply + JSONObject reply2 = createReply(replyNodeRef, "test2", "test2", 200); + NodeRef reply2NodeRef = new NodeRef(reply2.getString("nodeRef")); + assertEquals("test2", reply2.getString("title")); + assertEquals("test2", reply2.getString("content")); + + + // Check things were correctly setup. These should all be siblings + // of each other, with relations between the replies + assertEquals(ForumModel.TYPE_TOPIC, nodeService.getType(topicNodeRef)); + assertEquals(ForumModel.TYPE_POST, nodeService.getType(replyNodeRef)); + assertEquals(ForumModel.TYPE_POST, nodeService.getType(reply2NodeRef)); + assertEquals(topicNodeRef, nodeService.getPrimaryParent(replyNodeRef).getParentRef()); + assertEquals(topicNodeRef, nodeService.getPrimaryParent(reply2NodeRef).getParentRef()); + + // Reply 2 should have an assoc to Reply 1 + assertEquals(0, nodeService.getSourceAssocs(reply2NodeRef, RegexQNamePattern.MATCH_ALL).size()); + assertEquals(1, nodeService.getTargetAssocs(reply2NodeRef, RegexQNamePattern.MATCH_ALL).size()); + assertEquals(replyNodeRef, nodeService.getTargetAssocs(reply2NodeRef, RegexQNamePattern.MATCH_ALL).get(0).getTargetRef()); + + assertEquals(1, nodeService.getSourceAssocs(replyNodeRef, RegexQNamePattern.MATCH_ALL).size()); + assertEquals(1, nodeService.getTargetAssocs(replyNodeRef, RegexQNamePattern.MATCH_ALL).size()); + assertEquals(reply2NodeRef, nodeService.getSourceAssocs(replyNodeRef, RegexQNamePattern.MATCH_ALL).get(0).getSourceRef()); + + + // Fetch all replies for the post + JSONObject result = getReplies(topicNodeRef, Status.STATUS_OK); + // check the number of replies + assertEquals(1, result.getJSONArray("items").length()); + + // Check the replies by name too + result = getReplies(topicName, Status.STATUS_OK); + assertEquals(1, result.getJSONArray("items").length()); + + + // Fetch the top level post again, and check the counts there + // That post should have one direct reply, and one reply to it's reply + item = getPost(topicName, Status.STATUS_OK); + assertEquals(2, item.getInt("totalReplyCount")); + assertEquals(1, item.getInt("replyCount")); + } + + public void testUpdateReply() throws Exception + { + // Create a root post + JSONObject item = createSitePost("test", "test", Status.STATUS_OK); + String postName = item.getString("name"); + NodeRef postNodeRef = new NodeRef(item.getString("nodeRef")); + assertEquals("test", item.getString("title")); + assertEquals("test", item.getString("content")); + assertEquals(false, item.getBoolean("isUpdated")); + + + // Add a reply to it + JSONObject reply = createReply(postNodeRef, "rtest", "rtest", Status.STATUS_OK); + NodeRef replyNodeRef = new NodeRef(reply.getString("nodeRef")); + assertEquals("rtest", reply.getString("title")); + assertEquals("rtest", reply.getString("content")); + assertEquals(false, reply.getBoolean("isUpdated")); + + + // Now update the reply + JSONObject reply2 = updatePost(replyNodeRef, "test2", "test2", Status.STATUS_OK); + assertEquals("test2", reply2.getString("title")); + assertEquals("test2", reply2.getString("content")); + assertEquals(true, reply2.getBoolean("isUpdated")); + + // Fetch it to check + reply2 = getPost(replyNodeRef, Status.STATUS_OK); + assertEquals("test2", reply2.getString("title")); + assertEquals("test2", reply2.getString("content")); + assertEquals(true, reply2.getBoolean("isUpdated")); + + + // Ensure the original post wasn't changed + item = getPost(postName, Status.STATUS_OK); + assertEquals("test", item.getString("title")); + assertEquals("test", item.getString("content")); + assertEquals(false, item.getBoolean("isUpdated")); + } + + public void testDeleteToplevelPost() throws Exception + { + // Create two posts + JSONObject item1 = createSitePost("test1", "test1", Status.STATUS_OK); + JSONObject item2 = createSitePost("test2", "test2", Status.STATUS_OK); + String name1 = item1.getString("name"); + NodeRef nodeRef1 = new NodeRef(item1.getString("nodeRef")); + NodeRef nodeRef2 = new NodeRef(item2.getString("nodeRef")); + + // The node references returned correspond to the topics + assertEquals(ForumModel.TYPE_TOPIC, nodeService.getType(nodeRef1)); + assertEquals(ForumModel.TYPE_TOPIC, nodeService.getType(nodeRef2)); + + + // Delete one post by name + deletePost(name1, Status.STATUS_OK); + + // Check it went + getPost(name1, Status.STATUS_NOT_FOUND); + + + // Delete the other post by noderef + deletePost(nodeRef2, Status.STATUS_OK); + + // Check it went + getPost(nodeRef2, Status.STATUS_NOT_FOUND); + + + // Check all the nodes have gone + assertEquals(false, nodeService.exists(nodeRef1)); + assertEquals(false, nodeService.exists(nodeRef2)); + } + + public void testDeleteReplyPost() throws Exception + { + // Create a root post + JSONObject item = createSitePost("test", "test", Status.STATUS_OK); + String postName = item.getString("name"); + NodeRef postNodeRef = new NodeRef(item.getString("nodeRef")); + + // It doesn't have any replies yet + assertEquals(0, item.getInt("totalReplyCount")); + assertEquals(0, item.getInt("replyCount")); + + + // Add a reply + JSONObject reply = createReply(postNodeRef, "testR", "testR", Status.STATUS_OK); + NodeRef replyNodeRef = new NodeRef(reply.getString("nodeRef")); + String replyName = reply.getString("name"); + assertEquals("testR", reply.getString("title")); + assertEquals("testR", reply.getString("content")); + + // Fetch the reply and check + reply = getPost(replyNodeRef, Status.STATUS_OK); + assertEquals("testR", reply.getString("title")); + assertEquals("testR", reply.getString("content")); + + // Note - you can't fetch a reply by name, only by noderef + // It only works for primary posts as they share the topic name + getPost(replyName, Status.STATUS_NOT_FOUND); + + + // Check the main post, ensure the replies show up + item = getPost(postName, Status.STATUS_OK); + assertEquals(1, item.getInt("totalReplyCount")); + assertEquals(1, item.getInt("replyCount")); + + + // Delete the reply + deletePost(replyNodeRef, Status.STATUS_OK); + + // These nodes don't really get deleted at the moment + // Due to threading, we just add special marker text + // TODO Really we should probably delete posts with no attached replies + reply = getPost(replyNodeRef, Status.STATUS_OK); + assertEquals(DELETED_REPLY_POST_MARKER, reply.get("title")); + assertEquals(DELETED_REPLY_POST_MARKER, reply.get("content")); + + + // Fetch the top level post again, replies stay because they + // haven't really been deleted... + // TODO Really we should probably delete posts with no attached replies + item = getPost(postName, Status.STATUS_OK); + assertEquals(1, item.getInt("totalReplyCount")); + assertEquals(1, item.getInt("replyCount")); + } + + /** + * Test for the various listings: + * All, New, Hot (Most Active), Mine + */ + public void testListings() throws Exception + { + JSONObject result; + JSONObject item; + + + // Check all of the listings, none should have anything yet + result = getPosts(null, Status.STATUS_OK); + assertEquals(0, result.getInt("total")); + assertEquals(0, result.getInt("itemCount")); + assertEquals(0, result.getJSONArray("items").length()); + + result = getPosts("hot", Status.STATUS_OK); + assertEquals(0, result.getInt("total")); + assertEquals(0, result.getInt("itemCount")); + assertEquals(0, result.getJSONArray("items").length()); + + result = getPosts("mine", Status.STATUS_OK); + assertEquals(0, result.getInt("total")); + assertEquals(0, result.getInt("itemCount")); + assertEquals(0, result.getJSONArray("items").length()); + + result = getPosts("new?numdays=100", Status.STATUS_OK); + assertEquals(0, result.getInt("total")); + assertEquals(0, result.getInt("itemCount")); + assertEquals(0, result.getJSONArray("items").length()); + + + // Check with a noderef too + result = getPosts(FORUM_NODE, null, Status.STATUS_OK); + assertEquals(0, result.getInt("total")); + assertEquals(0, result.getInt("itemCount")); + assertEquals(0, result.getJSONArray("items").length()); + + result = getPosts(FORUM_NODE, "hot", Status.STATUS_OK); + assertEquals(0, result.getInt("total")); + assertEquals(0, result.getInt("itemCount")); + assertEquals(0, result.getJSONArray("items").length()); + + result = getPosts(FORUM_NODE, "mine", Status.STATUS_OK); + assertEquals(0, result.getInt("total")); + assertEquals(0, result.getInt("itemCount")); + assertEquals(0, result.getJSONArray("items").length()); + + result = getPosts(FORUM_NODE, "new?numdays=100", Status.STATUS_OK); + assertEquals(0, result.getInt("total")); + assertEquals(0, result.getInt("itemCount")); + assertEquals(0, result.getJSONArray("items").length()); + + + // Now add a few topics with replies + // Some of these will be created as different users + item = createSitePost("SiteTitle1", "Content", Status.STATUS_OK); + NodeRef siteTopic1 = new NodeRef(item.getString("nodeRef")); + this.authenticationComponent.setCurrentUser(USER_TWO); + item = createSitePost("SiteTitle2", "Content", Status.STATUS_OK); + NodeRef siteTopic2 = new NodeRef(item.getString("nodeRef")); + + item = createNodePost(FORUM_NODE, "NodeTitle1", "Content", Status.STATUS_OK); + NodeRef nodeTopic1 = new NodeRef(item.getString("nodeRef")); + this.authenticationComponent.setCurrentUser(USER_ONE); + item = createNodePost(FORUM_NODE, "NodeTitle2", "Content", Status.STATUS_OK); + NodeRef nodeTopic2 = new NodeRef(item.getString("nodeRef")); + item = createNodePost(FORUM_NODE, "NodeTitle3", "Content", Status.STATUS_OK); + NodeRef nodeTopic3 = new NodeRef(item.getString("nodeRef")); + + item = createReply(siteTopic1, "Reply1a", "Content", Status.STATUS_OK); + NodeRef siteReply1A = new NodeRef(item.getString("nodeRef")); + item = createReply(siteTopic1, "Reply1b", "Content", Status.STATUS_OK); + NodeRef siteReply1B = new NodeRef(item.getString("nodeRef")); + + this.authenticationComponent.setCurrentUser(USER_TWO); + item = createReply(siteTopic2, "Reply2a", "Content", Status.STATUS_OK); + NodeRef siteReply2A = new NodeRef(item.getString("nodeRef")); + item = createReply(siteTopic2, "Reply2b", "Content", Status.STATUS_OK); + NodeRef siteReply2B = new NodeRef(item.getString("nodeRef")); + item = createReply(siteTopic2, "Reply2c", "Content", Status.STATUS_OK); + NodeRef siteReply2C = new NodeRef(item.getString("nodeRef")); + + item = createReply(siteReply2A, "Reply2aa", "Content", Status.STATUS_OK); + NodeRef siteReply2AA = new NodeRef(item.getString("nodeRef")); + item = createReply(siteReply2A, "Reply2ab", "Content", Status.STATUS_OK); + NodeRef siteReply2AB = new NodeRef(item.getString("nodeRef")); + this.authenticationComponent.setCurrentUser(USER_ONE); + item = createReply(siteReply2AA, "Reply2aaa", "Content", Status.STATUS_OK); + NodeRef siteReply2AAA = new NodeRef(item.getString("nodeRef")); + + item = createReply(nodeTopic1, "ReplyN1a", "Content", Status.STATUS_OK); + NodeRef nodeReply1A = new NodeRef(item.getString("nodeRef")); + item = createReply(nodeReply1A, "ReplyN1aa", "Content", Status.STATUS_OK); + NodeRef nodeReply1AA = new NodeRef(item.getString("nodeRef")); + item = createReply(nodeReply1AA, "ReplyN1aaa", "Content", Status.STATUS_OK); + NodeRef nodeReply1AAA = new NodeRef(item.getString("nodeRef")); + + + // Check for totals + // We should get all the topics + result = getPosts(null, Status.STATUS_OK); + assertEquals(2, result.getInt("total")); + assertEquals(2, result.getInt("itemCount")); + assertEquals(2, result.getJSONArray("items").length()); + assertEquals("SiteTitle1", result.getJSONArray("items").getJSONObject(1).getString("title")); + assertEquals("SiteTitle2", result.getJSONArray("items").getJSONObject(0).getString("title")); + assertEquals(2, result.getJSONArray("items").getJSONObject(1).getInt("replyCount")); + assertEquals(3, result.getJSONArray("items").getJSONObject(0).getInt("replyCount")); + + result = getPosts(FORUM_NODE, null, Status.STATUS_OK); + assertEquals(3, result.getInt("total")); + assertEquals(3, result.getInt("itemCount")); + assertEquals(3, result.getJSONArray("items").length()); + assertEquals("NodeTitle1", result.getJSONArray("items").getJSONObject(2).getString("title")); + assertEquals("NodeTitle2", result.getJSONArray("items").getJSONObject(1).getString("title")); + assertEquals("NodeTitle3", result.getJSONArray("items").getJSONObject(0).getString("title")); + assertEquals(1, result.getJSONArray("items").getJSONObject(2).getInt("replyCount")); + assertEquals(0, result.getJSONArray("items").getJSONObject(1).getInt("replyCount")); + assertEquals(0, result.getJSONArray("items").getJSONObject(0).getInt("replyCount")); + + + // Check for "mine" + // User 1 has Site 1, and Nodes 2 + 3 + result = getPosts("mine", Status.STATUS_OK); + assertEquals(1, result.getInt("total")); + assertEquals(1, result.getInt("itemCount")); + assertEquals(1, result.getJSONArray("items").length()); + assertEquals("SiteTitle1", result.getJSONArray("items").getJSONObject(0).getString("title")); + assertEquals(2, result.getJSONArray("items").getJSONObject(0).getInt("replyCount")); + + result = getPosts(FORUM_NODE, "mine", Status.STATUS_OK); + assertEquals(2, result.getInt("total")); + assertEquals(2, result.getInt("itemCount")); + assertEquals(2, result.getJSONArray("items").length()); + assertEquals("NodeTitle2", result.getJSONArray("items").getJSONObject(0).getString("title")); + assertEquals("NodeTitle3", result.getJSONArray("items").getJSONObject(1).getString("title")); + assertEquals(0, result.getJSONArray("items").getJSONObject(0).getInt("replyCount")); + assertEquals(0, result.getJSONArray("items").getJSONObject(1).getInt("replyCount")); + + + // Check for recent (new) + // We should get all the topics, with the newest one first (rather than last as with others) + result = getPosts("new?numdays=2", Status.STATUS_OK); + assertEquals(2, result.getInt("total")); + assertEquals(2, result.getInt("itemCount")); + assertEquals(2, result.getJSONArray("items").length()); + assertEquals("SiteTitle2", result.getJSONArray("items").getJSONObject(0).getString("title")); + assertEquals("SiteTitle1", result.getJSONArray("items").getJSONObject(1).getString("title")); + assertEquals(3, result.getJSONArray("items").getJSONObject(0).getInt("replyCount")); + assertEquals(2, result.getJSONArray("items").getJSONObject(1).getInt("replyCount")); + + result = getPosts(FORUM_NODE, "new?numdays=2", Status.STATUS_OK); + assertEquals(3, result.getInt("total")); + assertEquals(3, result.getInt("itemCount")); + assertEquals(3, result.getJSONArray("items").length()); + assertEquals("NodeTitle3", result.getJSONArray("items").getJSONObject(0).getString("title")); + assertEquals("NodeTitle2", result.getJSONArray("items").getJSONObject(1).getString("title")); + assertEquals("NodeTitle1", result.getJSONArray("items").getJSONObject(2).getString("title")); + assertEquals(0, result.getJSONArray("items").getJSONObject(0).getInt("replyCount")); + assertEquals(0, result.getJSONArray("items").getJSONObject(1).getInt("replyCount")); + assertEquals(1, result.getJSONArray("items").getJSONObject(2).getInt("replyCount")); + + + // Check for hot + // Will only show topics with replies. Sorting is by replies, not date + result = getPosts("hot", Status.STATUS_OK); + assertEquals(2, result.getInt("total")); + assertEquals(2, result.getInt("itemCount")); + assertEquals(2, result.getJSONArray("items").length()); + assertEquals("SiteTitle2", result.getJSONArray("items").getJSONObject(0).getString("title")); + assertEquals("SiteTitle1", result.getJSONArray("items").getJSONObject(1).getString("title")); + assertEquals(3, result.getJSONArray("items").getJSONObject(0).getInt("replyCount")); + assertEquals(2, result.getJSONArray("items").getJSONObject(1).getInt("replyCount")); + + result = getPosts(FORUM_NODE, "hot", Status.STATUS_OK); + assertEquals(1, result.getInt("total")); + assertEquals(1, result.getInt("itemCount")); + assertEquals(1, result.getJSONArray("items").length()); + assertEquals("NodeTitle1", result.getJSONArray("items").getJSONObject(0).getString("title")); + assertEquals(1, result.getJSONArray("items").getJSONObject(0).getInt("replyCount")); + + + // Shift some of the posts into the past + // (Update the created and published dates) + pushCreatedDateBack(siteTopic1, 10); + pushCreatedDateBack(siteReply1B, -2); // Make it newer + + pushCreatedDateBack(nodeTopic2, 10); + pushCreatedDateBack(nodeTopic3, 4); + pushCreatedDateBack(nodeReply1AAA, -1); // Make it newer + + + // Re-check totals, only ordering changes + result = getPosts(null, Status.STATUS_OK); + assertEquals(2, result.getInt("total")); + assertEquals(2, result.getInt("itemCount")); + assertEquals(2, result.getJSONArray("items").length()); + assertEquals("SiteTitle1", result.getJSONArray("items").getJSONObject(1).getString("title")); + assertEquals("SiteTitle2", result.getJSONArray("items").getJSONObject(0).getString("title")); + assertEquals(2, result.getJSONArray("items").getJSONObject(1).getInt("replyCount")); + assertEquals(3, result.getJSONArray("items").getJSONObject(0).getInt("replyCount")); + + result = getPosts(FORUM_NODE, null, Status.STATUS_OK); + assertEquals(3, result.getInt("total")); + assertEquals(3, result.getInt("itemCount")); + assertEquals(3, result.getJSONArray("items").length()); + assertEquals("NodeTitle2", result.getJSONArray("items").getJSONObject(2).getString("title")); + assertEquals("NodeTitle3", result.getJSONArray("items").getJSONObject(1).getString("title")); + assertEquals("NodeTitle1", result.getJSONArray("items").getJSONObject(0).getString("title")); + assertEquals(0, result.getJSONArray("items").getJSONObject(2).getInt("replyCount")); + assertEquals(0, result.getJSONArray("items").getJSONObject(1).getInt("replyCount")); + assertEquals(1, result.getJSONArray("items").getJSONObject(0).getInt("replyCount")); + + + // Re-check recent, old ones vanish + result = getPosts("new?numdays=2", Status.STATUS_OK); + assertEquals(1, result.getInt("total")); + assertEquals(1, result.getInt("itemCount")); + assertEquals(1, result.getJSONArray("items").length()); + assertEquals("SiteTitle2", result.getJSONArray("items").getJSONObject(0).getString("title")); + assertEquals(3, result.getJSONArray("items").getJSONObject(0).getInt("replyCount")); + + result = getPosts(FORUM_NODE, "new?numdays=6", Status.STATUS_OK); + assertEquals(2, result.getInt("total")); + assertEquals(2, result.getInt("itemCount")); + assertEquals(2, result.getJSONArray("items").length()); + assertEquals("NodeTitle1", result.getJSONArray("items").getJSONObject(0).getString("title")); + assertEquals("NodeTitle3", result.getJSONArray("items").getJSONObject(1).getString("title")); + assertEquals(1, result.getJSONArray("items").getJSONObject(0).getInt("replyCount")); + assertEquals(0, result.getJSONArray("items").getJSONObject(1).getInt("replyCount")); + + result = getPosts(FORUM_NODE, "new?numdays=2", Status.STATUS_OK); + assertEquals(1, result.getInt("total")); + assertEquals(1, result.getInt("itemCount")); + assertEquals(1, result.getJSONArray("items").length()); + assertEquals("NodeTitle1", result.getJSONArray("items").getJSONObject(0).getString("title")); + assertEquals(1, result.getJSONArray("items").getJSONObject(0).getInt("replyCount")); + + + // Re-check "mine", no change except ordering + result = getPosts("mine", Status.STATUS_OK); + assertEquals(1, result.getInt("total")); + assertEquals(1, result.getInt("itemCount")); + assertEquals(1, result.getJSONArray("items").length()); + assertEquals("SiteTitle1", result.getJSONArray("items").getJSONObject(0).getString("title")); + assertEquals(2, result.getJSONArray("items").getJSONObject(0).getInt("replyCount")); + + result = getPosts(FORUM_NODE, "mine", Status.STATUS_OK); + assertEquals(2, result.getInt("total")); + assertEquals(2, result.getInt("itemCount")); + assertEquals(2, result.getJSONArray("items").length()); + assertEquals("NodeTitle2", result.getJSONArray("items").getJSONObject(0).getString("title")); + assertEquals("NodeTitle3", result.getJSONArray("items").getJSONObject(1).getString("title")); + assertEquals(0, result.getJSONArray("items").getJSONObject(0).getInt("replyCount")); + assertEquals(0, result.getJSONArray("items").getJSONObject(1).getInt("replyCount")); + + + // Re-check hot, some old ones vanish + result = getPosts("hot", Status.STATUS_OK); + assertEquals(2, result.getInt("total")); + assertEquals(2, result.getInt("itemCount")); + assertEquals(2, result.getJSONArray("items").length()); + assertEquals("SiteTitle2", result.getJSONArray("items").getJSONObject(0).getString("title")); + assertEquals("SiteTitle1", result.getJSONArray("items").getJSONObject(1).getString("title")); + assertEquals(3, result.getJSONArray("items").getJSONObject(0).getInt("replyCount")); + assertEquals(2, result.getJSONArray("items").getJSONObject(1).getInt("replyCount")); + + result = getPosts(FORUM_NODE, "hot", Status.STATUS_OK); + assertEquals(1, result.getInt("total")); + assertEquals(1, result.getInt("itemCount")); + assertEquals(1, result.getJSONArray("items").length()); + assertEquals("NodeTitle1", result.getJSONArray("items").getJSONObject(0).getString("title")); + assertEquals(1, result.getJSONArray("items").getJSONObject(0).getInt("replyCount")); + + + // Check paging + result = getPosts("limit", Status.STATUS_OK); + assertEquals(2, result.getInt("total")); + assertEquals(1, result.getInt("itemCount")); + assertEquals(1, result.getJSONArray("items").length()); + assertEquals("SiteTitle2", result.getJSONArray("items").getJSONObject(0).getString("title")); + assertEquals(3, result.getJSONArray("items").getJSONObject(0).getInt("replyCount")); + + result = getPosts(FORUM_NODE, "limit", Status.STATUS_OK); + assertEquals(3, result.getInt("total")); + assertEquals(1, result.getInt("itemCount")); + assertEquals(1, result.getJSONArray("items").length()); + assertEquals("NodeTitle1", result.getJSONArray("items").getJSONObject(0).getString("title")); + assertEquals(1, result.getJSONArray("items").getJSONObject(0).getInt("replyCount")); + } + + /** + * https://issues.alfresco.com/jira/browse/ALF-17443 reports that site contributors are unable + * to edit replies that they have made. + */ + public void testContributorCanEditReply() throws Exception + { + authenticationComponent.setCurrentUser(USER_ONE); + JSONObject post = createSitePost("Can contributors edit replies?", "The title says it all", Status.STATUS_OK); + NodeRef postNodeRef = new NodeRef(post.getString("nodeRef")); + + authenticationComponent.setCurrentUser(USER_TWO); + JSONObject reply = createReply(postNodeRef, "", "Let's see.", Status.STATUS_OK); + NodeRef replyNodeRef = new NodeRef(reply.getString("nodeRef")); + updateComment(replyNodeRef, "", "Yes I can", Status.STATUS_OK); + + authenticationComponent.setCurrentUser(USER_ONE); + + post = getPost(postNodeRef, Status.STATUS_OK); + assertEquals("Can contributors edit replies?", post.getString("title")); + assertEquals("The title says it all", post.getString("content")); + assertEquals(1, post.getInt("replyCount")); + + JSONObject replies = getReplies(postNodeRef, Status.STATUS_OK); + JSONArray items = replies.getJSONArray("items"); + assertEquals(1, items.length()); + + reply = items.getJSONObject(0); + assertEquals("Yes I can", reply.getString("content")); + + } + + /** + * Test for MNT-11964 + * @throws Exception + */ + public void testCreateForumPermission() throws Exception + { + this.authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName()); + + String siteName = SITE_SHORT_NAME_DISCUSSION + GUID.generate(); + this.siteService.createSite("ForumSitePreset", siteName, "SiteTitle", "SiteDescription", SiteVisibility.PUBLIC); + + String userName = USER_ONE + GUID.generate(); + createUser(userName, SiteModel.SITE_COLLABORATOR, siteName); + + // Check permissions for admin + checkForumPermissions(siteName); + + // Check permissions for user + this.authenticationComponent.setCurrentUser(userName); + checkForumPermissions(siteName); + + // Cleanup + this.authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName()); + this.siteService.deleteSite(siteName); + + // Create a new site as user + this.authenticationComponent.setCurrentUser(userName); + siteName = SITE_SHORT_NAME_DISCUSSION + GUID.generate(); + this.siteService.createSite("BlogSitePreset", siteName, "SiteTitle", "SiteDescription", SiteVisibility.PUBLIC); + + // Check permissions for user + checkForumPermissions(siteName); + + // Check permissions for admin + this.authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName()); + checkForumPermissions(siteName); + + // Cleanup + this.siteService.deleteSite(siteName); + this.personService.deletePerson(userName); + } + + private void checkForumPermissions(String siteName) throws Exception + { + String url = "/api/forum/site/" + siteName + "/" + COMPONENT_DISCUSSION + "/posts"; + Response response = sendRequest(new GetRequest(url), 200); + JSONObject result = new JSONObject(response.getContentAsString()); + + assertTrue("The user sould have permission to create a new discussion.", result.getJSONObject("forumPermissions").getBoolean("create")); + } +}