diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts-filtered.get.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts-filtered.get.desc.xml
new file mode 100644
index 0000000000..d7daea840d
--- /dev/null
+++ b/config/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts-filtered.get.desc.xml
@@ -0,0 +1,9 @@
+
+ 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
+
diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts-filtered.get.json.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/discussions/forum/forum-posts-filtered.get.json.ftl
new file mode 100644
index 0000000000..f338b40df2
--- /dev/null
+++ b/config/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
+ #if>
+ },
+<@gen.pagedResults data=data ; item>
+ <@postLib.postJSON postData=item />
+@gen.pagedResults>
+}
\ No newline at end of file
diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/discussions/post.lib.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/discussions/post.lib.ftl
index c477c4ceb5..e39ec2ee71 100644
--- a/config/alfresco/templates/webscripts/org/alfresco/repository/discussions/post.lib.ftl
+++ b/config/alfresco/templates/webscripts/org/alfresco/repository/discussions/post.lib.ftl
@@ -61,6 +61,7 @@
#if>
#if>
"tags": [<#list postData.tags as x>"${x}"<#if x_has_next>, #if>#list>],
+ "site": "${postData.site!""}",
<#else>
"name": "${post.name}",
#if>
diff --git a/config/alfresco/web-scripts-application-context.xml b/config/alfresco/web-scripts-application-context.xml
index 4b08a8b53c..3ca598456c 100644
--- a/config/alfresco/web-scripts-application-context.xml
+++ b/config/alfresco/web-scripts-application-context.xml
@@ -1732,6 +1732,13 @@
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 ec23051254..2fe83357ac 100644
--- a/source/java/org/alfresco/repo/web/scripts/discussion/AbstractDiscussionWebScript.java
+++ b/source/java/org/alfresco/repo/web/scripts/discussion/AbstractDiscussionWebScript.java
@@ -358,6 +358,9 @@ public abstract class AbstractDiscussionWebScript extends DeclarativeWebScript
// 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)
{
diff --git a/source/java/org/alfresco/repo/web/scripts/discussion/ForumTopicsFilteredGet.java b/source/java/org/alfresco/repo/web/scripts/discussion/ForumTopicsFilteredGet.java
new file mode 100644
index 0000000000..5b15169802
--- /dev/null
+++ b/source/java/org/alfresco/repo/web/scripts/discussion/ForumTopicsFilteredGet.java
@@ -0,0 +1,408 @@
+/*
+ * Copyright (C) 2005-2012 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ */
+package org.alfresco.repo.web.scripts.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.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
+ */
+ public void setSearchService(SearchService searchService)
+ {
+ this.searchService = searchService;
+ }
+
+ /**
+ * Overrides AbstractDiscussionWebScript to allow a null site
+ *
+ * @param req
+ * @param status
+ * @param cache
+ *
+ * @return
+ */
+ @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
+ * @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
+ * @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
+ */
+ @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
+ * @param paging
+ * @return
+ */
+ 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
+ * @param author
+ * @param daysAgo
+ * @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:" + 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
+ * @return
+ */
+ 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
+ * @return
+ */
+ @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
+ * @param paging
+ * @return
+ */
+ 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;
+ }
+ };
+ }
+}