diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts-hot.get.js b/config/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts-hot.get.js deleted file mode 100644 index 5d51526dff..0000000000 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts-hot.get.js +++ /dev/null @@ -1,116 +0,0 @@ - - - - -var MAX_NUM_OF_PROCESSED_POSTS = 20; - -/** - * Fetches the hot topics found in the forum. - * Hot topics are topics with the most replies over the last x days. - * - * The current implementation fetches all posts in the forum ordered by inverse - * creation date. It then analyzes the last x posts and fetches the topics thereof, - * keeping track of the number of posts for each. - * - * Note: We only look at topics with replies, the others will therefore not show up - * in that list. - */ -function getHotTopicPostList(node, index, count) -{ - // get the posts to check - var luceneQuery = " +TYPE:\"{http://www.alfresco.org/model/forum/1.0}post\"" + - " +PATH:\"" + node.qnamePath + "/*/*\"" + - " +ASPECT:\"cm:referencing\""; - var sortAttribute = "@{http://www.alfresco.org/model/content/1.0}published"; - var posts = search.luceneSearch(node.nodeRef.storeRef.toString(), luceneQuery, sortAttribute, false); - - // check how many posts we check in the result set - var max = MAX_NUM_OF_PROCESSED_POSTS; - if (posts.length < max) - { - max = posts.length; - } - - // get for each the topic, keeping track of the number of replies and the first occurance - // of the post. - var idToData = {}; - for (var x = 0; x < max; x++) - { - // get the topic node (which is the direct parent of the post) - var parent = posts[x].parent; - var id = parent.nodeRef.id; - if (idToData[id] != null) - { - idToData[id].count += 1; - } - else - { - idToData[id] = - { - count: 1, - pos: x, - node: parent - }; - } - } - - // copy the elements to an array as we will have to sort it - // afterwards - var dataArr = new Array(); - for (n in idToData) - { - dataArr.push(idToData[n]); - } - - // sort the elements by number of replies, then by the position - var sorter = function(a, b) - { - if (a.count != b.count) - { - // more replies first - return b.count - a.count - } - else - { - // lower pos first - return a.pos - b.pos; - } - } - dataArr.sort(sorter); - - // extract now the nodes - var nodes = Array(); - for (var x = 0; x < dataArr.length; x++) - { - nodes.push(dataArr[x].node); - } - - // get the paginated data - return getPagedResultsData(nodes, index, count, getTopicPostData); -} - -function main() -{ - // get requested node - var node = getRequestNode(); - if (status.getCode() != status.STATUS_OK) - { - return; - } - - // process additional parameters - var index = args["startIndex"] != undefined ? parseInt(args["startIndex"]) : 0; - var count = args["pageSize"] != undefined ? parseInt(args["pageSize"]) : 10; - - // fetch the data and assign it to the model - model.data = getHotTopicPostList(node, index, count); - - // fetch the contentLength param - var contentLength = args["contentLength"] != undefined ? parseInt(args["contentLength"]) : -1; - model.contentLength = isNaN(contentLength) ? -1 : contentLength; - - // also set the forum node - model.forum = node; -} - -main(); diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/discussions/topicpost.lib.js b/config/alfresco/templates/webscripts/org/alfresco/repository/discussions/topicpost.lib.js deleted file mode 100644 index 5dcebf13b6..0000000000 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/discussions/topicpost.lib.js +++ /dev/null @@ -1,143 +0,0 @@ - -/** - * Returns the fm:post node given a fm:topic or fm:post node. - * - * This function makes sure that a post is returned in case the passed node is a topic. - */ -function findPostNode(node) -{ - if (node.type == "{http://www.alfresco.org/model/forum/1.0}post") - { - return node; - } - else if (node.type == "{http://www.alfresco.org/model/forum/1.0}topic") - { - var nodes = getOrderedPosts(node); - if (nodes.length > 0) - { - return nodes[0]; - } - else - { - status.setCode(status.STATUS_INTERNAL_SERVER_ERROR, "First post of topic node" + node.nodeRef + " missing"); - } - } - else - { - status.setCode(STATUS_BAD_REQUEST, "Incompatible node type. Required either fm:topic or fm:post. Received: " + node.type); - return null; - } -} - - -/** Returns the posts of a topic, ordered by creation date. - * We use this for two things: To find the root node and the last reply - */ -function getOrderedPosts(topic) -{ - var query = " +TYPE:\"{http://www.alfresco.org/model/forum/1.0}post\"" + - " +PATH:\"" + topic.qnamePath + "/*\" "; - var sortAttribute = "@{http://www.alfresco.org/model/content/1.0}published"; - //var sortAttribute = "@{http://www.alfresco.org/model/content/1.0}created"; // TODO Switch to this - return search.luceneSearch(topic.nodeRef.storeRef.toString(), query, sortAttribute, true) ; -} - -/* - * Returns a JavaScript object that is used by the freemarker template - * to render a topic post - */ -function getTopicPostData(topicNode) -{ - // fetch the posts - var posts = getOrderedPosts(topicNode); - - return getTopicPostDataFromTopicAndPosts(topicNode, posts); -} - -/* - * Returns a JavaScript object that is used by the freemarker template - * to render a topic post - */ -function getTopicPostDataFromTopicAndPosts(topicNode, posts) -{ - // check the first post (which is the main post) - if (posts.length < 1) - { - status.setCode(status.STATUS_INTERNAL_SERVER_ERROR, "First post of topic node" + topicNode.nodeRef + " missing"); - return; - } - - var item = {}; - - // fetch the data - item.isTopicPost = true; - item.topic = topicNode; - item.post = posts[0]; - item.canEdit = canUserEditPost(item.post); - item.author = people.getPerson(item.post.properties["cm:creator"]); - item.totalReplyCount = posts.length - 1; - // in case of replies, find the last reply - if (posts.length > 1) - { - item.lastReply = posts[posts.length - 1]; - item.lastReplyBy = people.getPerson(item.lastReply.properties["cm:creator"]); - } - - // tags - if (topicNode.tags != undefined) - { - item.tags = topicNode.tags; - } - else - { - item.tags = []; - } - - return item; -} - -/** - * Returns the data object that is used by the freemarker template to render a reply post - */ -function getReplyPostData(post) -{ - var item = {}; - item.isTopicPost = false; - item.post = post; - item.canEdit = canUserEditPost(item.post); - item.author = people.getPerson(item.post.properties["cm:creator"]); - return item; -} - -/** - * Returns true if the current user can edit the post. - * - * Site managers can edit any post, everyone else should only - * be able to edit their own posts. - */ -function canUserEditPost(post) -{ - // see if user has write permission first of all - var canEdit = post.hasPermission("Write"); - - // if current user is not the same user as the author check - // that they are a site manager otherwise they can't edit the post - if (canEdit) - { - // get the site id - var siteId = url.templateArgs.site; - - if (siteId !== null) - { - var username = person.properties["cm:userName"]; - var postAuthor = post.properties["cm:creator"]; - - if (username != postAuthor) - { - canEdit = siteService.isSiteManager(siteId); - } - } - } - - return canEdit; -} diff --git a/config/alfresco/web-scripts-application-context.xml b/config/alfresco/web-scripts-application-context.xml index 1f902e3c21..1d0dd4cc99 100644 --- a/config/alfresco/web-scripts-application-context.xml +++ b/config/alfresco/web-scripts-application-context.xml @@ -1625,6 +1625,12 @@ parent="abstractDiscussionWebScript"> + + + + diff --git a/source/java/org/alfresco/repo/web/scripts/discussion/AbstractDiscussionWebScript.java b/source/java/org/alfresco/repo/web/scripts/discussion/AbstractDiscussionWebScript.java index c4500f1332..16681530f5 100644 --- a/source/java/org/alfresco/repo/web/scripts/discussion/AbstractDiscussionWebScript.java +++ b/source/java/org/alfresco/repo/web/scripts/discussion/AbstractDiscussionWebScript.java @@ -45,6 +45,7 @@ import org.alfresco.service.cmr.site.SiteService; import org.alfresco.util.Pair; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONTokener; @@ -164,6 +165,41 @@ public abstract class AbstractDiscussionWebScript extends DeclarativeWebScript return paging; } + protected List getTags(JSONObject json) throws JSONException + { + List tags = null; + if(json.has("tags")) + { + // Is it "tags":"" or "tags":[...] ? + if(json.get("tags") instanceof String) + { + // This is normally an empty string, skip + String tagsS = json.getString("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 = json.getJSONArray("tags"); + for(int i=0; i 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", topics.getTotalResultCount().getFirst()); + model.put("total", size.getFirst()); model.put("pageSize", paging.getMaxItems()); model.put("startIndex", paging.getSkipCount()); - model.put("itemCount", topics.getPage().size()); + model.put("itemCount", topics.size()); // Data List> items = new ArrayList>(); - for(TopicInfo topic : topics.getPage()) + for(TopicInfo topic : topics) { items.add(renderTopic(topic, site)); } diff --git a/source/java/org/alfresco/repo/web/scripts/discussion/ForumPostPut.java b/source/java/org/alfresco/repo/web/scripts/discussion/ForumPostPut.java index cee65af72b..a4eb441c76 100644 --- a/source/java/org/alfresco/repo/web/scripts/discussion/ForumPostPut.java +++ b/source/java/org/alfresco/repo/web/scripts/discussion/ForumPostPut.java @@ -18,6 +18,7 @@ */ package org.alfresco.repo.web.scripts.discussion; +import java.util.List; import java.util.Map; import org.alfresco.service.cmr.discussion.PostInfo; @@ -114,10 +115,11 @@ public class ForumPostPut extends AbstractDiscussionWebScript if(json.has("tags")) { topic.getTags().clear(); - JSONArray tags = json.getJSONArray("tags"); - for(int i=0; i tags = getTags(json); + if(tags != null) { - topic.getTags().add( tags.getString(i) ); + topic.getTags().addAll(tags); } } } diff --git a/source/java/org/alfresco/repo/web/scripts/discussion/ForumTopicPost.java b/source/java/org/alfresco/repo/web/scripts/discussion/ForumTopicPost.java index b97c0ce9a7..10ae8b5891 100644 --- a/source/java/org/alfresco/repo/web/scripts/discussion/ForumTopicPost.java +++ b/source/java/org/alfresco/repo/web/scripts/discussion/ForumTopicPost.java @@ -69,15 +69,7 @@ public class ForumTopicPost extends AbstractDiscussionWebScript { contents = json.getString("content"); } - if(json.has("tags")) - { - tags = new ArrayList(); - JSONArray jsTags = json.getJSONArray("tags"); - for(int i=0; i. + */ +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.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; + } +}