REPO-4674/ REPO-4675 Reinstate removed deprecated functionality for ACS6.2: Add Discussions (Forums+Blogs) (#357)

* Update alfresco-repository to 7.127

* REPO-4674 Reinstate removed deprecated functionality for ACS6.2: Add Discussions (Forums) (#352)

* REPO-4674 Revert"REPO-3235Removediscussions(#171)"

This reverts commit f299959ac5.

* REPO-4675 Reinstate removed deprecated functionality for ACS6.2: Add Blogs (remote-api) (#355)

* Revert "REPO-3236: Remove blogs (#177)"

This reverts commit f63354466a.

* Apply from 6.1.N REPO-4360 : remove xmlrpc-client dependency (#220)
   Remove external publishing and retrieval V0 APIs

(cherry picked from commit 499c0c3b02)
(cherry picked from commit b500375ee6)
(cherry picked from commit e1a2b245df)
This commit is contained in:
David Edwards
2019-10-01 17:44:49 +01:00
committed by GitHub
parent 1502520eff
commit ed8c248638
120 changed files with 7855 additions and 198 deletions

View File

@@ -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"

View File

@@ -37,7 +37,7 @@
<dir.root>${project.build.directory}/alf_data</dir.root>
<img.exe>convert</img.exe>
<dependency.alfresco-repository.version>7.125</dependency.alfresco-repository.version>
<dependency.alfresco-repository.version>7.127</dependency.alfresco-repository.version>
<dependency.alfresco-data-model.version>8.50.1</dependency.alfresco-data-model.version>
<dependency.alfresco-core.version>7.21</dependency.alfresco-core.version>

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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<String, Object> executeImpl(WebScriptRequest req, Status status, Cache cache)
{
Map<String, String> 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<String, Object> executeImpl(SiteInfo site,
NodeRef nodeRef, BlogPostInfo blog, WebScriptRequest req,
JSONObject json, Status status, Cache cache);
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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<QName, Serializable> getBlogPropertiesArray(JSONObject json)
{
Map<QName, Serializable> arr = new HashMap<QName, Serializable>();
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<QName, Serializable> arr, String jsonKey, QName mapKey)
{
if (json.containsKey(jsonKey))
{
arr.put(mapKey, (Serializable)json.get(jsonKey));
}
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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<String, Object> getBlogPostData(NodeRef node, ServiceRegistry services)
{
Map<String, Object> data = new HashMap<String, Object>();
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;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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<ChildAssociationRef> getComments(NodeRef node, ServiceRegistry services)
{
List<ChildAssociationRef> result = new ArrayList<ChildAssociationRef>();
NodeRef commentsFolder = getCommentsFolder(node, services);
if (commentsFolder != null)
{
List<ChildAssociationRef> 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<ChildAssociationRef> 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<ChildAssociationRef> 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;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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<String, Object> 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<String, Object> model = new HashMap<String, Object>();
model.put(ITEM, containerNodeRef);
return model;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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<String, Object> 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<String, Object> model = new HashMap<String, Object>();
model.put("item", containerNodeRef);
return model;
}
/**
* Creates a post inside the passed forum node.
*/
@SuppressWarnings("deprecation")
private void updateBlog(NodeRef node, JSONObject json)
{
Map<QName, Serializable> arr = BlogLibJs.getBlogPropertiesArray(json);
if (nodeService.hasAspect(node, BlogIntegrationModel.ASPECT_BLOG_DETAILS))
{
Map<QName, Serializable> properties = nodeService.getProperties(node);
properties.putAll(arr);
nodeService.setProperties(node, properties);
}
else
{
nodeService.addAspect(node, BlogIntegrationModel.ASPECT_BLOG_DETAILS, arr);
}
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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<String, Object> 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<String, Object> model = new HashMap<String, Object>();
String message = rb.getString(MSG_BLOG_DELETED);
model.put("message",MessageFormat.format(message, blog.getNodeRef()));
return model;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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<String, Object> 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<String, Object> model = new HashMap<String, Object>();
// TODO Fetch this from the BlogPostInfo object
NodeRef node = blog.getNodeRef();
Map<String, Object> 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;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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<String, Object> executeImpl(SiteInfo site, NodeRef nonSiteContainer,
BlogPostInfo blog, WebScriptRequest req, JSONObject json, Status status, Cache cache)
{
Map<String, Object> model = new HashMap<String, Object>();
// process additional parameters. <index, count>
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<BlogPostInfo> 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<String, Object> model, NodeRef node, PagingRequest pagingReq,
PagingResults<BlogPostInfo> blogPostList)
{
Map<String, Object> blogPostsData = new HashMap<String, Object>();
final Pair<Integer, Integer> 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<Map<String, Object>> blogPostDataSets = new ArrayList<Map<String, Object>>(blogPostList.getPage().size());
for (BlogPostInfo postInfo : blogPostList.getPage())
{
Map<String, Object> 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<String, String> 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<BlogPostInfo> 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<BlogPostInfo> getBlogResultsImpl(
SiteInfo site, NodeRef nonSiteContainer, Date fromDate, Date toDate, PagingRequest pagingReq);
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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<BlogPostInfo> 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<BlogPostInfo>();
}
// 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);
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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<BlogPostInfo> 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);
}
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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<BlogPostInfo> 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);
}
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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<BlogPostInfo> 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);
}
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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<BlogPostInfo> 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<String, Object> model, NodeRef node, PagingRequest pagingReq, PagingResults<BlogPostInfo> 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<String, Object> getMonthDataObject(Date date)
{
Map<String, Object> data = new HashMap<String, Object>();
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<Map<String, Object>> getBlogPostMonths(PagingResults<BlogPostInfo> nodes)
{
// will hold the months information
List<Map<String, Object>> data = new ArrayList<Map<String, Object>>();
// do we have posts?
if (!nodes.getPage().isEmpty())
{
int currYear = -1;
int currMonth = -1;
Map<String, Object> 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<String, Object> emptyData = getMonthDataObject(new Date());
emptyData.put("count", 0);
data.add(emptyData);
}
return data;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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<String, Object> executeImpl(SiteInfo site, NodeRef nodeRef,
BlogPostInfo blog, WebScriptRequest req, JSONObject json, Status status, Cache cache)
{
Map<String, Object> model = new HashMap<String, Object>();
// 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<String, Object> 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<String> tags = new ArrayList<String>();
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<String> tagsParam = new ArrayList<String>();
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<String> nonEmptyTags = new ArrayList<String>();
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<String> 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<String> getTags()
{
return tags;
}
public void setTags(List<String> 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;
}
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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<String> getTags(JSONObject json)
{
List<String> 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<String>();
JSONArray jsTags = (JSONArray)json.get("tags");
for (int i=0; i<jsTags.size(); i++)
{
tags.add( (String)jsTags.get(i) );
}
}
}
return tags;
}
/**
* Generates an activity entry for the discussion item
*
* @param thing Either post or reply
* @param event One of created, updated, deleted
*/
protected void addActivityEntry(String thing, String event, TopicInfo topic,
PostInfo post, SiteInfo site, WebScriptRequest req, JSONObject json)
{
// We can only add activities against a site
if (site == null)
{
logger.info("Unable to add activity entry for " + thing + " " + 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 = "discussions-topicview";
}
// Get the title
String title = topic.getTitle();
if (post != null)
{
String postTitle = post.getTitle();
if (postTitle != null && postTitle.length() > 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<String, Object> renderPost(PostInfo post, SiteInfo site)
{
Map<String, Object> item = new HashMap<String, Object>();
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<String, Object> 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<PostInfo> 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<String, Object> item = new HashMap<String, Object>();
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<String, Object> renderTopics(PagingResults<TopicInfo> 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<String, Object> renderTopics(List<TopicInfo> topics,
Pair<Integer,Integer> size, PagingRequest paging, SiteInfo site)
{
Map<String, Object> model = new HashMap<String, Object>();
// Paging info
model.put("total", size.getFirst());
model.put("pageSize", paging.getMaxItems());
model.put("startIndex", paging.getSkipCount());
model.put("itemCount", topics.size());
// Data
List<Map<String,Object>> items = new ArrayList<Map<String,Object>>();
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<String, Object> buildCommonModel(SiteInfo site, TopicInfo topic,
PostInfo post, WebScriptRequest req)
{
// Build the common model parts
Map<String, Object> model = new HashMap<String, Object>();
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<String, Object> executeImpl(WebScriptRequest req,
Status status, Cache cache)
{
Map<String, String> 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<TopicInfo,PostInfo> 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<String, Object> executeImpl(SiteInfo site,
NodeRef nodeRef, TopicInfo topic, PostInfo post,
WebScriptRequest req, JSONObject json, Status status, Cache cache);
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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<String, Object> 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<String, Object> 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());
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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<String, Object> executeImpl(SiteInfo site, NodeRef nodeRef,
TopicInfo topic, PostInfo post, WebScriptRequest req, JSONObject json,
Status status, Cache cache)
{
// Build the common model parts
Map<String, Object> 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;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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<String, Object> executeImpl(SiteInfo site, NodeRef nodeRef,
TopicInfo topic, PostInfo post, WebScriptRequest req, JSONObject json,
Status status, Cache cache)
{
// Build the common model parts
Map<String, Object> 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<String> 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);
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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<String, Object> 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<String, Object> 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<String, Object> renderReplies(PostWithReplies replies, SiteInfo site)
{
Map<String, Object> reply = renderPost(replies.getPost(), site);
reply.put("childCount", replies.getReplies().size());
List<Map<String,Object>> r = new ArrayList<Map<String,Object>>();
for (PostWithReplies child : replies.getReplies())
{
r.add(renderReplies(child, site));
}
reply.put("children", r);
return reply;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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<String, Object> 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<String, Object> 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;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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<String, Object> 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<String> 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<String, Object> model = buildCommonModel(site, topic, post, req);
// Build the JSON for the whole topic
model.put(KEY_POSTDATA, renderTopic(topic, site));
// All done
return model;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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<String, Object> executeImpl(WebScriptRequest req, Status status, Cache cache)
{
Map<String, String> 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<String, Object> 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<String, String> searchQuery = getSearchQuery(site, author, daysAgo);
// Get the filtered topics
PagingRequest paging = buildPagingRequest(req);
PagingResults<TopicInfo> topics = doSearch(searchQuery, false, paging);
// Build the common model parts
Map<String, Object> 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<TopicInfo> doSearch(Pair<String, String> searchQuery, boolean sortAscending, PagingRequest paging)
{
ResultSet resultSet = null;
PagingResults<TopicInfo> pagedResults = new EmptyPagingResults<TopicInfo>();
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<String, String> 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<String, String> searchQuery = new Pair<String, String>(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<TopicInfo> wrap(final ResultSet finalResults, PagingRequest paging)
{
int maxItems = paging.getMaxItems();
Comparator<TopicInfo> lastPostDesc = new Comparator<TopicInfo>()
{
@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<TopicInfo> topics = new TreeSet<TopicInfo>(lastPostDesc);
for (ResultSetRow row : finalResults)
{
Pair<TopicInfo, PostInfo> 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<TopicInfo>()
{
@Override
public boolean hasMoreItems()
{
try
{
return finalResults.hasMore();
}
catch(UnsupportedOperationException e)
{
// Not all search results support paging
return false;
}
}
@Override
public Pair<Integer, Integer> 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<Integer, Integer>(totalItemsInUnpagedResultSet, totalItemsInUnpagedResultSet);
}
@Override
public List<TopicInfo> getPage()
{
return new ArrayList<TopicInfo>(topics);
}
@Override
public String getQueryExecutionId()
{
return null;
}
};
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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<String, Object> 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<TopicInfo> 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<String, Object> 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;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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<String, Object> 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<Pair<TopicInfo,Integer>> 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<TopicInfo> topics = new ArrayList<TopicInfo>();
for (Pair<TopicInfo,Integer> 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<String, Object> 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;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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<String, Object> 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<TopicInfo> 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<String, Object> 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;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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<String, Object> 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<TopicInfo> 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<String, Object> 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;
}
}

View File

@@ -0,0 +1,71 @@
<?xml version='1.0' encoding='UTF-8'?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="abstractBlogWebScript" parent="webscript" abstract="true">
<property name="repository" ref="repositoryHelper"/>
<property name="blogService" ref="BlogService"/>
<property name="nodeService" ref="NodeService"/>
<property name="siteService" ref="SiteService"/>
<property name="serviceRegistry" ref="ServiceRegistry"/>
<property name="activityService" ref="activityService"/>
</bean>
<!-- repository/blogs/blog -->
<bean id="webscript.org.alfresco.repository.blogs.blog.blog.get"
class="org.alfresco.repo.web.scripts.blogs.blog.BlogGet"
parent="abstractBlogWebScript">
</bean>
<bean id="webscript.org.alfresco.repository.blogs.blog.blog.put"
class="org.alfresco.repo.web.scripts.blogs.blog.BlogPut"
parent="abstractBlogWebScript">
</bean>
<!-- repository/blogs/post -->
<bean id="webscript.org.alfresco.repository.blogs.post.blog-post.delete"
class="org.alfresco.repo.web.scripts.blogs.post.BlogPostDelete"
parent="abstractBlogWebScript">
</bean>
<bean id="webscript.org.alfresco.repository.blogs.post.blog-post.get"
class="org.alfresco.repo.web.scripts.blogs.post.BlogPostGet"
parent="abstractBlogWebScript">
</bean>
<!-- repository/blogs/posts -->
<bean id="webscript.org.alfresco.repository.blogs.posts.blog-posts.get"
class="org.alfresco.repo.web.scripts.blogs.posts.BlogPostsGet"
parent="abstractBlogWebScript">
</bean>
<bean id="webscript.org.alfresco.repository.blogs.posts.blog-posts-mydrafts.get"
class="org.alfresco.repo.web.scripts.blogs.posts.BlogPostsMyDraftsGet"
parent="abstractBlogWebScript">
</bean>
<bean id="webscript.org.alfresco.repository.blogs.posts.blog-posts-mypublished.get"
class="org.alfresco.repo.web.scripts.blogs.posts.BlogPostsMyPublishedGet"
parent="abstractBlogWebScript">
</bean>
<bean id="webscript.org.alfresco.repository.blogs.posts.blog-posts-new.get"
class="org.alfresco.repo.web.scripts.blogs.posts.BlogPostsNewGet"
parent="abstractBlogWebScript">
</bean>
<bean id="webscript.org.alfresco.repository.blogs.posts.blog-posts-per-month.get"
class="org.alfresco.repo.web.scripts.blogs.posts.BlogPostsPerMonthGet"
parent="abstractBlogWebScript">
</bean>
<bean id="webscript.org.alfresco.repository.blogs.posts.blog-posts.post"
class="org.alfresco.repo.web.scripts.blogs.posts.BlogPostsPost"
parent="abstractBlogWebScript">
<property name="taggingService" ref="TaggingService"/>
</bean>
</beans>

View File

@@ -328,7 +328,16 @@
</list>
</property>
</bean>
<bean id="discussionsActivitySummaryProcessor" class="org.alfresco.rest.api.impl.activities.DiscussionsActivitySummaryProcessor">
<property name="registry" ref="activitySummaryParser" />
<property name="eventTypes">
<list>
<value>org.alfresco.discussions.reply-created</value>
</list>
</property>
</bean>
<bean id="subscriptionsActivitySummaryProcessor" class="org.alfresco.rest.api.impl.activities.SubscriptionsActivitySummaryProcessor">
<property name="registry" ref="activitySummaryParser" />
<property name="eventTypes">

View File

@@ -0,0 +1,14 @@
<#include "../slingshot-common.lib.ftl">
<entry xmlns='http://www.w3.org/2005/Atom'>
<title>Blog post created: ${htmlTitle?xml}</title>
<link rel="alternate" type="text/html" href="" />
<id>http://www.alfresco.org/rss/atom/${id}</id>
<updated>${xmldate(date)}</updated>
<summary type="html">
<![CDATA[&quot;${htmlTitle}&quot; blog post created by ${userName?html}.]]>
</summary>
<author>
<name>${userName?xml}</name>
<uri>${userId?xml}</uri>
</author>
</entry>

View File

@@ -0,0 +1,14 @@
<#include "../slingshot-common.lib.ftl">
<entry xmlns='http://www.w3.org/2005/Atom'>
<title>Blog post deleted: ${htmlTitle?xml}</title>
<link rel="alternate" type="text/html" href="" />
<id>http://www.alfresco.org/rss/atom/${id}</id>
<updated>${xmldate(date)}</updated>
<summary type="html">
<![CDATA[&quot;${htmlTitle}&quot; blog post deleted by ${userName?html}.]]>
</summary>
<author>
<name>${userName?xml}</name>
<uri>${userId?xml}</uri>
</author>
</entry>

View File

@@ -0,0 +1,14 @@
<#include "../slingshot-common.lib.ftl">
<entry xmlns='http://www.w3.org/2005/Atom'>
<title>Blog post updated: ${htmlTitle?xml}</title>
<link rel="alternate" type="text/html" href="" />
<id>http://www.alfresco.org/rss/atom/${id}</id>
<updated>${xmldate(date)}</updated>
<summary type="html">
<![CDATA[&quot;${htmlTitle}&quot; blog post updated by ${userName?html}.]]>
</summary>
<author>
<name>${userName?xml}</name>
<uri>${userId?xml}</uri>
</author>
</entry>

View File

@@ -0,0 +1,14 @@
<#include "../slingshot-common.lib.ftl">
<entry xmlns='http://www.w3.org/2005/Atom'>
<title>New discussion: ${htmlTitle?xml}</title>
<link rel="alternate" type="text/html" href="" />
<id>http://www.alfresco.org/rss/atom/${id}</id>
<updated>${xmldate(date)}</updated>
<summary type="html">
<![CDATA[&quot;${htmlTitle}&quot; discussion created by ${userName?html}.]]>
</summary>
<author>
<name>${userName?xml}</name>
<uri>${userId?xml}</uri>
</author>
</entry>

View File

@@ -0,0 +1,14 @@
<#include "../slingshot-common.lib.ftl">
<entry xmlns='http://www.w3.org/2005/Atom'>
<title>Discussion deleted: ${htmlTitle?xml}</title>
<link rel="alternate" type="text/html" href="" />
<id>http://www.alfresco.org/rss/atom/${id}</id>
<updated>${xmldate(date)}</updated>
<summary type="html">
<![CDATA[&quot;${htmlTitle}&quot; discussion deleted by ${userName?html}.]]>
</summary>
<author>
<name>${userName?xml}</name>
<uri>${userId?xml}</uri>
</author>
</entry>

View File

@@ -0,0 +1,14 @@
<#include "../slingshot-common.lib.ftl">
<entry xmlns='http://www.w3.org/2005/Atom'>
<title>Discussion updated: ${htmlTitle?xml}</title>
<link rel="alternate" type="text/html" href="" />
<id>http://www.alfresco.org/rss/atom/${id}</id>
<updated>${xmldate(date)}</updated>
<summary type="html">
<![CDATA[&quot;${htmlTitle}&quot; discussion updated by ${userName?html}.]]>
</summary>
<author>
<name>${userName?xml}</name>
<uri>${userId?xml}</uri>
</author>
</entry>

View File

@@ -0,0 +1,14 @@
<#include "../slingshot-common.lib.ftl">
<entry xmlns='http://www.w3.org/2005/Atom'>
<title>Reply added to ${(htmlTitle!'')?html?xml}</title>
<link rel="alternate" type="text/html" href="" />
<id>http://www.alfresco.org/rss/atom/${id}</id>
<updated>${xmldate(date)}</updated>
<summary type="html">
<![CDATA[&quot;${htmlTitle}&quot; replied to by ${userName?html}.]]>
</summary>
<author>
<name>${userName?xml}</name>
<uri>${userId?xml}</uri>
</author>
</entry>

View File

@@ -0,0 +1,14 @@
<#include "../slingshot-common.lib.ftl">
<entry xmlns='http://www.w3.org/2005/Atom'>
<title>Reply to ${(htmlTitle!'')?html?xml} updated</title>
<link rel="alternate" type="text/html" href="" />
<id>http://www.alfresco.org/rss/atom/${id}</id>
<updated>${xmldate(date)}</updated>
<summary type="html">
<![CDATA[Reply to &quot;${htmlTitle}&quot; updated by ${userName?html}.]]>
</summary>
<author>
<name>${userName?xml}</name>
<uri>${userId?xml}</uri>
</author>
</entry>

View File

@@ -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}
</#if>
}
}
</#escape>
</#macro>

View File

@@ -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;
}

View File

@@ -0,0 +1,11 @@
<webscript>
<shortname>Get blog</shortname>
<description>Get the blog information.</description>
<url>/api/blog/site/{site}/{container}/{path}</url>
<url>/api/blog/site/{site}/{container}</url>
<url>/api/blog/node/{store_type}/{store_id}/{id}</url>
<format default="json">argument</format>
<authentication>user</authentication>
<transaction allow="readonly">required</transaction>
<lifecycle>limited_support</lifecycle>
</webscript>

View File

@@ -0,0 +1,4 @@
<#import "../blog.lib.ftl" as blogLib/>
{
"item" : <@blogLib.blogJSON item=item />
}

View File

@@ -0,0 +1,11 @@
<webscript>
<shortname>Put blog</shortname>
<description>Updates a blog.</description>
<url>/api/blog/site/{site}/{container}/{path}</url>
<url>/api/blog/site/{site}/{container}</url>
<url>/api/blog/node/{store_type}/{store_id}/{id}</url>
<format default="json">argument</format>
<authentication>user</authentication>
<transaction>required</transaction>
<lifecycle>limited_support</lifecycle>
</webscript>

View File

@@ -0,0 +1,4 @@
<#import "../blog.lib.ftl" as blogLib/>
{
"item" : <@blogLib.blogJSON item=item />
}

View File

@@ -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}",
</#if>
"username": "${person.properties["cm:userName"]}",
"firstName": "${person.properties["cm:firstName"]!""}",
"lastName": "${person.properties["cm:lastName"]!""}"
},
</#escape>
</#macro>
<#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}",
</#if>
</#escape>
</#macro>
<#--
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}"
},
</#if>
"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>, </#if></#list>],
<#-- draft vs internal published -->
"isDraft": ${item.isDraft?string},
<#if (! item.isDraft)>
"releasedOn": "${formatDateRFC822(item.releasedDate)}",
</#if>
<#-- true if the post has been updated -->
"isUpdated": ${item.isUpdated?string},
<#if (item.isUpdated)>
"updatedOn": "${formatDateRFC822(item.updatedDate)}",
</#if>
<#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},
</#if>
<#-- external publishing - last to make sure that we correctly end the response without a comma -->
"isPublished": ${(item.node.properties["blg:published"]!'false')?string}
}
</#escape>
</#macro>
<#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 />
</@gen.pagedResults>
}
</#macro>
<#macro renderPost>
{
"metadata":
{
"externalBlogConfig": ${externalBlogConfig?string}
},
"item": <@blogpostJSON item=item />
}
</#macro>
<#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 -->
</#function>

View File

@@ -0,0 +1,130 @@
<import resource="classpath:alfresco/templates/webscripts/org/alfresco/repository/comments/comments.lib.js">
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);
}
}

View File

@@ -0,0 +1,10 @@
<webscript>
<shortname>Delete blog post</shortname>
<description>Deletes a blog post.</description>
<url>/api/blog/post/site/{site}/{container}/{path}</url>
<url>/api/blog/post/node/{store_type}/{store_id}/{id}</url>
<format default="json">argument</format>
<authentication>user</authentication>
<transaction>required</transaction>
<lifecycle>limited_support</lifecycle>
</webscript>

View File

@@ -0,0 +1 @@
blog-post.msg.deleted=Blog {0} deleted

View File

@@ -0,0 +1 @@
blog-post.msg.deleted=Blog {0} gel\u00f6scht

View File

@@ -0,0 +1 @@
blog-post.msg.deleted=Blog {0} eliminado

View File

@@ -0,0 +1 @@
blog-post.msg.deleted=Blog {0} supprim\u00e9

View File

@@ -0,0 +1 @@
blog-post.msg.deleted=Blog {0} eliminato

View File

@@ -0,0 +1 @@
blog-post.msg.deleted=\u30d6\u30ed\u30b0 "{0}" \u304c\u524a\u9664\u3055\u308c\u307e\u3057\u305f

View File

@@ -0,0 +1 @@
blog-post.msg.deleted=Bloggen {0} ble slettet

View File

@@ -0,0 +1 @@
blog-post.msg.deleted=Blog {0} verwijderd

View File

@@ -0,0 +1 @@
blog-post.msg.deleted=Blog {0} exclu\u00eddo

View File

@@ -0,0 +1 @@
blog-post.msg.deleted=\u0411\u043b\u043e\u0433 {0} \u0443\u0434\u0430\u043b\u0435\u043d

View File

@@ -0,0 +1 @@
blog-post.msg.deleted=\u5df2\u5220\u9664\u535a\u5ba2 {0}

View File

@@ -0,0 +1,10 @@
<webscript>
<shortname>Get all blogs</shortname>
<description>Gets all blogs.</description>
<url>/api/blog/post/site/{site}/{container}/{path}</url>
<url>/api/blog/post/node/{store_type}/{store_id}/{id}</url>
<format default="json">argument</format>
<authentication>user</authentication>
<transaction allow="readonly">required</transaction>
<lifecycle>limited_support</lifecycle>
</webscript>

View File

@@ -0,0 +1,2 @@
<#import "../blogpost.lib.ftl" as blogpostLib/>
<@blogpostLib.renderPost />

View File

@@ -0,0 +1,10 @@
<webscript>
<shortname>Update blog post</shortname>
<description>Updates a blog post.</description>
<url>/api/blog/post/site/{site}/{container}/{path}</url>
<url>/api/blog/post/node/{store_type}/{store_id}/{id}</url>
<format default="json">argument</format>
<authentication>user</authentication>
<transaction>required</transaction>
<lifecycle>limited_support</lifecycle>
</webscript>

View File

@@ -0,0 +1,2 @@
<#import "../blogpost.lib.ftl" as blogpostLib/>
<@blogpostLib.renderPost />

View File

@@ -0,0 +1,91 @@
<import resource="classpath:alfresco/templates/webscripts/org/alfresco/repository/requestutils.lib.js">
<import resource="classpath:alfresco/templates/webscripts/org/alfresco/repository/blogs/blogpost.lib.js">
/**
* 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();

View File

@@ -0,0 +1,11 @@
<webscript>
<shortname>Get blog posts (draft)</shortname>
<description>Gets all draft posts for a blog.</description>
<url>/api/blog/site/{site}/{container}/{path}/posts/mydrafts</url>
<url>/api/blog/site/{site}/{container}/posts/mydrafts</url>
<url>/api/blog/node/{store_type}/{store_id}/{id}/posts/mydrafts</url>
<format default="json">argument</format>
<authentication>user</authentication>
<transaction allow="readonly">required</transaction>
<lifecycle>limited_support</lifecycle>
</webscript>

View File

@@ -0,0 +1,2 @@
<#import "../blogpost.lib.ftl" as blogpostLib/>
<@blogpostLib.renderPostList />

View File

@@ -0,0 +1,11 @@
<webscript>
<shortname>Get blog posts (published)</shortname>
<description>Gets all published posts for a blog.</description>
<url>/api/blog/site/{site}/{container}/{path}/posts/mypublished</url>
<url>/api/blog/site/{site}/{container}/posts/mypublished</url>
<url>/api/blog/node/{store_type}/{store_id}/{id}/posts/mypublished</url>
<format default="json">argument</format>
<authentication>user</authentication>
<transaction allow="readonly">required</transaction>
<lifecycle>limited_support</lifecycle>
</webscript>

View File

@@ -0,0 +1,2 @@
<#import "../blogpost.lib.ftl" as blogpostLib/>
<@blogpostLib.renderPostList />

View File

@@ -0,0 +1,11 @@
<webscript>
<shortname>Get blog posts for a number of days</shortname>
<description>Get all posts for a blog that were created within the specified number of days.</description>
<url>/api/blog/site/{site}/{container}/{path}/posts/new?numdays={numdays}</url>
<url>/api/blog/site/{site}/{container}/posts/new?numdays={numdays}</url>
<url>/api/blog/node/{store_type}/{store_id}/{id}/posts/new?numdays={numdays}</url>
<format default="json">argument</format>
<authentication>user</authentication>
<transaction allow="readonly">required</transaction>
<lifecycle>limited_support</lifecycle>
</webscript>

View File

@@ -0,0 +1,2 @@
<#import "../blogpost.lib.ftl" as blogpostLib/>
<@blogpostLib.renderPostList />

View File

@@ -0,0 +1,11 @@
<webscript>
<shortname>Get blog posts per month</shortname>
<description>Gets all months for which there are blog posts plus the number of posts in each.</description>
<url>/api/blog/site/{site}/{container}/{path}/postspermonth</url>
<url>/api/blog/site/{site}/{container}/postspermonth</url>
<url>/api/blog/node/{store_type}/{store_id}/{id}/postspermonth</url>
<format default="json">argument</format>
<authentication>user</authentication>
<transaction allow="readonly">required</transaction>
<lifecycle>limited_support</lifecycle>
</webscript>

View File

@@ -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>,</#if>
</#list>
]
}
</#escape>

View File

@@ -0,0 +1,11 @@
<webscript>
<shortname>Get blog posts</shortname>
<description>Gets all posts for a blog.</description>
<url>/api/blog/site/{site}/{container}/{path}/posts</url>
<url>/api/blog/site/{site}/{container}/posts</url>
<url>/api/blog/node/{store_type}/{store_id}/{id}/posts</url>
<format default="json">argument</format>
<authentication>user</authentication>
<transaction allow="readonly">required</transaction>
<lifecycle>limited_support</lifecycle>
</webscript>

View File

@@ -0,0 +1,2 @@
<#import "../blogpost.lib.ftl" as blogpostLib/>
<@blogpostLib.renderPostList />

View File

@@ -0,0 +1,11 @@
<webscript>
<shortname>Create blog post</shortname>
<description>Creates a new blog post.</description>
<url>/api/blog/site/{site}/{container}/{path}/posts</url>
<url>/api/blog/site/{site}/{container}/posts</url>
<url>/api/blog/node/{store_type}/{store_id}/{id}/posts</url>
<format default="json">argument</format>
<authentication>user</authentication>
<transaction>required</transaction>
<lifecycle>limited_support</lifecycle>
</webscript>

View File

@@ -0,0 +1,2 @@
<#import "../blogpost.lib.ftl" as blogpostLib/>
<@blogpostLib.renderPost />

View File

@@ -0,0 +1,10 @@
<webscript>
<shortname>Get filtered forum posts</shortname>
<description>Get the discussion topics that match the filters.</description>
<url>/api/forum/site/{site}/discussions/posts/filtered</url>
<url>/api/forum/discussions/posts/filtered</url>
<format default="json">argument</format>
<authentication>user</authentication>
<transaction allow="readonly">required</transaction>
<lifecycle>limited_support</lifecycle>
</webscript>

View File

@@ -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>
}

View File

@@ -0,0 +1,11 @@
<webscript>
<shortname>Get hot forum posts</shortname>
<description>Get the hot topics in the forum.</description>
<url>/api/forum/site/{site}/{container}/{path}/posts/hot</url>
<url>/api/forum/site/{site}/{container}/posts/hot</url>
<url>/api/forum/node/{store_type}/{store_id}/{id}/posts/hot</url>
<format default="json">argument</format>
<authentication>user</authentication>
<transaction allow="readonly">required</transaction>
<lifecycle>limited_support</lifecycle>
</webscript>

View File

@@ -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 />
</@gen.pagedResults>
}

View File

@@ -0,0 +1,11 @@
<webscript>
<shortname>Get my forum posts</shortname>
<description>Gets the forum posts created by the current user.</description>
<url>/api/forum/site/{site}/{container}/{path}/posts/myposts</url>
<url>/api/forum/site/{site}/{container}/posts/myposts</url>
<url>/api/forum/node/{store_type}/{store_id}/{id}/posts/myposts</url>
<format default="json">argument</format>
<authentication>user</authentication>
<transaction allow="readonly">required</transaction>
<lifecycle>limited_support</lifecycle>
</webscript>

View File

@@ -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 />
</@gen.pagedResults>
}

View File

@@ -0,0 +1,11 @@
<webscript>
<shortname>Get new forum posts</shortname>
<description>Gets the forum posts for the last specified number of days.</description>
<url>/api/forum/site/{site}/{container}/{path}/posts/new?numdays={numdays}</url>
<url>/api/forum/site/{site}/{container}/posts/new?numdays={numdays}</url>
<url>/api/forum/node/{store_type}/{store_id}/{id}/posts/new?numdays={numdays}</url>
<format default="json">argument</format>
<authentication>user</authentication>
<transaction allow="readonly">required</transaction>
<lifecycle>limited_support</lifecycle>
</webscript>

View File

@@ -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 />
</@gen.pagedResults>
}

View File

@@ -0,0 +1,11 @@
<webscript>
<shortname>Get forum posts</shortname>
<description>Gets the forum posts.</description>
<url>/api/forum/site/{site}/{container}/{path}/posts</url>
<url>/api/forum/site/{site}/{container}/posts</url>
<url>/api/forum/node/{store_type}/{store_id}/{id}/posts</url>
<format default="json">argument</format>
<authentication>user</authentication>
<transaction allow="readonly">required</transaction>
<lifecycle>limited_support</lifecycle>
</webscript>

View File

@@ -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}
</#if>
},
<@gen.pagedResults data=data ; item>
<@postLib.postJSON postData=item />
</@gen.pagedResults>
}

View File

@@ -0,0 +1,11 @@
<webscript>
<shortname>Add forum post</shortname>
<description>Adds a post to a forum.</description>
<url>/api/forum/site/{site}/{container}/{path}/posts</url>
<url>/api/forum/site/{site}/{container}/posts</url>
<url>/api/forum/node/{store_type}/{store_id}/{id}/posts</url>
<format default="json">argument</format>
<authentication>user</authentication>
<transaction>required</transaction>
<lifecycle>limited_support</lifecycle>
</webscript>

View File

@@ -0,0 +1,4 @@
<#import "../post.lib.ftl" as postLib />
{
"item": <@postLib.postJSON postData=postData />
}

View File

@@ -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}",
</#if>
"username": "${person.properties["cm:userName"]}",
"firstName": "${person.properties["cm:firstName"]!""}",
"lastName": "${person.properties["cm:lastName"]!""}"
</#if>
},
</#escape>
</#macro>
<#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}",
</#if>
</#escape>
</#macro>
<#macro postJSON postData>
{
<@postDataJSON postData=postData />
}
</#macro>
<#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 />
</#if>
<#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": ""
},
</#if>
</#if>
"tags": [<#list postData.tags as x>"${x}"<#if x_has_next>, </#if></#list>],
"site": "${postData.site!""}",
<#else>
"name": "${post.name}",
</#if>
<#-- 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>
<#if postData.author?? && postData.author?has_content>
<@renderPerson person=postData.author fieldName="author" />
<#else>
"author":
{
"username": "${post.properties["cm:creator"]}"
},
</#if>
<@addContent post=post />
"replyCount": <#if post.sourceAssocs["cm:references"]??>${post.sourceAssocs["cm:references"]?size?c}<#else>0</#if>,
"permissions":
{
"edit": ${postData.canEdit?string},
"reply": ${post.parent.hasPermission("CreateChildren")?string},
"delete": ${post.hasPermission("Delete")?string}
}
</#escape>
</#macro>
<#-- 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 />
</#if>
}
</#macro>
<#macro repliesRootJSON children>
[
<#list children as child>
<@repliesJSON data=child/>
<#if child_has_next>,</#if>
</#list>
]
</#macro>

View File

@@ -0,0 +1,10 @@
<webscript>
<shortname>Get forum post replies</shortname>
<description>Get the forum post replies.</description>
<url>/api/forum/post/site/{site}/{container}/{path}/replies</url>
<url>/api/forum/post/node/{store_type}/{store_id}/{id}/replies</url>
<format default="json">argument</format>
<authentication>user</authentication>
<transaction allow="readonly">required</transaction>
<lifecycle>limited_support</lifecycle>
</webscript>

View File

@@ -0,0 +1,4 @@
<#import "../post.lib.ftl" as postLib />
{
"items": <@postLib.repliesRootJSON children=data />
}

View File

@@ -0,0 +1,9 @@
<webscript>
<shortname>Add forum post reply</shortname>
<description>Adds a reply to a post.</description>
<url>/api/forum/post/node/{store_type}/{store_id}/{id}/replies</url>
<format default="json">argument</format>
<authentication>user</authentication>
<transaction>required</transaction>
<lifecycle>limited_support</lifecycle>
</webscript>

View File

@@ -0,0 +1,4 @@
<#import "../post.lib.ftl" as postLib />
{
"item": <@postLib.postJSON postData=postData />
}

View File

@@ -0,0 +1,10 @@
<webscript>
<shortname>Delete topic</shortname>
<description>Deletes a topic.</description>
<url>/api/forum/post/site/{site}/{container}/{path}</url>
<url>/api/forum/post/node/{store_type}/{store_id}/{id}</url>
<format default="json">argument</format>
<authentication>user</authentication>
<transaction>required</transaction>
<lifecycle>limited_support</lifecycle>
</webscript>

View File

@@ -0,0 +1,2 @@
forum-post.msg.deleted=Node {0} deleted
forum-post.msg.marked.removed=Node {0} marked as removed

View File

@@ -0,0 +1,2 @@
forum-post.msg.deleted=Node {0} gel\u00f6scht
forum-post.msg.marked.removed=Node {0} als entfernt gekennzeichnet

View File

@@ -0,0 +1,2 @@
forum-post.msg.deleted=Nodo {0} eliminado
forum-post.msg.marked.removed=Nodo {0} marcado como eliminado

View File

@@ -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

Some files were not shown because too many files have changed in this diff Show More