diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/generic-paged-results.lib.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/generic-paged-results.lib.ftl
index bc1f7e2a4a..7cd367b232 100644
--- a/config/alfresco/templates/webscripts/org/alfresco/repository/generic-paged-results.lib.ftl
+++ b/config/alfresco/templates/webscripts/org/alfresco/repository/generic-paged-results.lib.ftl
@@ -48,3 +48,41 @@ Usage:
#if>
#escape>
#macro>
+
+<#--
+ Renders paged results information to conforms to the RESTful APIs standards.
+ The passed in data object should contain following attributes:
+ count: The actual number of elements returned
+ hasMoreItems: True if more items on next page
+ totalItems: The total result count
+ skipCount: The number of elements to skip before retrieving the page
+ maxItems: The number of elements requested to be returned
+
+Usage:
+ <#import "generic-paged-results.lib.ftl" as gen/>
+ {
+ <@gen.standardRestfulPagedResults data=data ; item>
+ output of the individual item, for example by calling another macro:
+ <@yourLib.itemJSON item=item />
+ @gen.standardRestfulPagedResults>
+ }
+-->
+<#macro standardRestfulPagedResults data>
+ "list" : {
+ "pagination" : {
+ "count" : ${data.count?c},
+ "hasMoreItems" : ${data.hasMoreItems?c},
+ "totalItems" : ${data.totalItems?c},
+ "skipCount" :${data.skipCount?c},
+ "maxItems" : ${data.maxItems?c}
+ },
+ "entries" : [{
+ <#list data.items as item>
+ "entry" : {
+ <#nested item>
+ }
+ }<#if item_has_next>, {#if>
+ #list>
+ ]
+ }
+#macro>
diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/site/site-admin-sites.get.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/repository/site/site-admin-sites.get.desc.xml
new file mode 100644
index 0000000000..515ab4c09c
--- /dev/null
+++ b/config/alfresco/templates/webscripts/org/alfresco/repository/site/site-admin-sites.get.desc.xml
@@ -0,0 +1,8 @@
+
+ Get sites
+ Get a collection of the sites in the repository accessible to site administrators. The collection can be filtered by name.
+ /api/admin-sites?nf={namefilter?}&maxItems={maxItems?}&skipCount={skipCount?}&roles={roles?}&
+ argument
+ user
+ required
+
diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/site/site-admin-sites.get.json.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/site/site-admin-sites.get.json.ftl
new file mode 100644
index 0000000000..fa1dba1b9b
--- /dev/null
+++ b/config/alfresco/templates/webscripts/org/alfresco/repository/site/site-admin-sites.get.json.ftl
@@ -0,0 +1,31 @@
+<#import "../generic-paged-results.lib.ftl" as gen/>
+
+<#macro siteJSON item>
+<#escape x as jsonUtils.encodeJSONString(x)>
+ "url" : "${url.serviceContext + "/api/sites/" + item.siteInfo.shortName}",
+ "sitePreset" : "${item.siteInfo.sitePreset}",
+ "shortName" : "${item.siteInfo.shortName}",
+ "title" : "${item.siteInfo.title}",
+ "description" : "${item.siteInfo.description}",
+ "createdDate" : "${xmldate(item.siteInfo.createdDate)}",
+ "lastModifiedDate" : "${xmldate(item.siteInfo.lastModifiedDate)}",
+ "visibility" : "${item.siteInfo.visibility}",
+ "siteManagers" : [{
+ <#list item.members as manager>
+ "entry" : {
+ "userName" : "${manager.userName}",
+ "firstName" : "${manager.firstName}",
+ "lastName" : "${manager.lastName}"
+ }
+ }<#if manager_has_next>, {#if>
+ #list>
+ ]
+#escape>
+#macro>
+
+{
+ <@gen.standardRestfulPagedResults data=data ; item>
+ <@siteJSON item=item />
+ @gen.standardRestfulPagedResults>
+}
+
diff --git a/config/alfresco/web-scripts-application-context.xml b/config/alfresco/web-scripts-application-context.xml
index 4d688d7ef9..53fa81bfb0 100644
--- a/config/alfresco/web-scripts-application-context.xml
+++ b/config/alfresco/web-scripts-application-context.xml
@@ -786,6 +786,13 @@
+
+
+
+
+
+
+
diff --git a/source/java/org/alfresco/repo/web/scripts/site/SiteAdminSitesGet.java b/source/java/org/alfresco/repo/web/scripts/site/SiteAdminSitesGet.java
new file mode 100644
index 0000000000..88022fe9a6
--- /dev/null
+++ b/source/java/org/alfresco/repo/web/scripts/site/SiteAdminSitesGet.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2005-2014 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ */
+
+package org.alfresco.repo.web.scripts.site;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.query.PagingResults;
+import org.alfresco.repo.node.getchildren.FilterProp;
+import org.alfresco.repo.node.getchildren.FilterPropString;
+import org.alfresco.repo.node.getchildren.FilterPropString.FilterTypeString;
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.alfresco.repo.site.SiteModel;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.cmr.security.PersonService;
+import org.alfresco.service.cmr.site.SiteInfo;
+import org.alfresco.service.cmr.site.SiteService;
+import org.alfresco.service.namespace.QName;
+import org.alfresco.util.Pair;
+import org.alfresco.util.ScriptPagingDetails;
+import org.springframework.extensions.webscripts.Cache;
+import org.springframework.extensions.webscripts.DeclarativeWebScript;
+import org.springframework.extensions.webscripts.Status;
+import org.springframework.extensions.webscripts.WebScriptException;
+import org.springframework.extensions.webscripts.WebScriptRequest;
+
+/**
+ * This class is the controller for the site-admin-sites.get web script.
+ *
+ * @author Jamal Kaabi-Mofrad
+ */
+public class SiteAdminSitesGet extends DeclarativeWebScript
+{
+ private static final String NAME_FILTER = "nf";
+ private static final String MAX_ITEMS = "maxItems";
+ private static final String SKIP_COUNT = "skipCount";
+ private static final int DEFAULT_MAX_ITEMS_PER_PAGE = 50;
+
+ private SiteService siteService;
+ private NodeService nodeService;
+ private PersonService personService;
+
+ public void setSiteService(SiteService siteService)
+ {
+ this.siteService = siteService;
+ }
+
+ public void setNodeService(NodeService nodeService)
+ {
+ this.nodeService = nodeService;
+ }
+
+ public void setPersonService(PersonService personService)
+ {
+ this.personService = personService;
+ }
+
+ @Override
+ protected Map executeImpl(WebScriptRequest req, Status status, Cache cache)
+ {
+ // check the current user access rights
+ if (!siteService.isSiteAdmin(AuthenticationUtil.getFullyAuthenticatedUser()))
+ {
+ // Note: security, no message to indicate why
+ throw new WebScriptException(HttpServletResponse.SC_NOT_FOUND, "Resource no found.");
+ }
+
+ // Create paging
+ final ScriptPagingDetails paging = new ScriptPagingDetails(getIntParameter(req, MAX_ITEMS,
+ DEFAULT_MAX_ITEMS_PER_PAGE), getIntParameter(req, SKIP_COUNT, 0));
+
+ // request a total count of found items
+ paging.setRequestTotalCountMax(Integer.MAX_VALUE);
+
+ final List filterProp = getFilterProperties(req.getParameter(NAME_FILTER));
+
+ final List> sortProps = new ArrayList>();
+ sortProps.add(new Pair(ContentModel.PROP_NAME, true));
+
+ PagingResults pagingResults = AuthenticationUtil.runAs(
+ new AuthenticationUtil.RunAsWork>()
+ {
+ public PagingResults doWork() throws Exception
+ {
+ return siteService.listSites(filterProp, sortProps, paging);
+ }
+ }, AuthenticationUtil.getAdminUserName());
+
+ List result = pagingResults.getPage();
+ List sites = new ArrayList(result.size());
+ for (SiteInfo info : result)
+ {
+ sites.add(SiteState.create(info,
+ siteService.listMembers(info.getShortName(), null, SiteModel.SITE_MANAGER, 0), nodeService,
+ personService));
+ }
+
+ Map sitesData = new HashMap(6);
+
+ // Site data
+ sitesData.put("items", sites);
+ // Paging data
+ sitesData.put("count", result.size());
+ sitesData.put("hasMoreItems", pagingResults.hasMoreItems());
+ sitesData.put("totalItems", (pagingResults.getTotalResultCount() == null ? -1 : pagingResults.getTotalResultCount().getFirst()));
+ sitesData.put("skipCount", paging.getSkipCount());
+ sitesData.put("maxItems", paging.getMaxItems());
+
+ // Create the model from the site and pagination data
+ Map model = new HashMap(1);
+ model.put("data", sitesData);
+
+ return model;
+ }
+
+ private int getIntParameter(WebScriptRequest req, String paramName, int defaultValue)
+ {
+ String paramString = req.getParameter(paramName);
+
+ if (paramString != null)
+ {
+ try
+ {
+ int param = Integer.valueOf(paramString);
+
+ if (param >= 0)
+ {
+ return param;
+ }
+ }
+ catch (NumberFormatException e)
+ {
+ throw new WebScriptException(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
+ }
+ }
+
+ return defaultValue;
+ }
+
+ private List getFilterProperties(String filter)
+ {
+ if (filter == null || filter.isEmpty() || filter.equals("*"))
+ {
+ return null;
+ }
+ List filterProps = new ArrayList();
+ filterProps.add(new FilterPropString(ContentModel.PROP_NAME, filter, FilterTypeString.STARTSWITH_IGNORECASE));
+ filterProps.add(new FilterPropString(ContentModel.PROP_TITLE, filter, FilterTypeString.STARTSWITH_IGNORECASE));
+ return filterProps;
+ }
+}
diff --git a/source/java/org/alfresco/repo/web/scripts/site/SiteState.java b/source/java/org/alfresco/repo/web/scripts/site/SiteState.java
new file mode 100644
index 0000000000..59c817a04d
--- /dev/null
+++ b/source/java/org/alfresco/repo/web/scripts/site/SiteState.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2005-2014 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ */
+
+package org.alfresco.repo.web.scripts.site;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.cmr.security.PersonService;
+import org.alfresco.service.cmr.site.SiteInfo;
+
+/**
+ * A simple POJO class for the state of a site. For easier passing to the FTL model.
+ *
+ * @author jkaabimofrad
+ */
+public class SiteState
+{
+
+ private SiteInfo siteInfo;
+ private List members;
+
+ private SiteState()
+ {
+ }
+
+ public static SiteState create(SiteInfo siteInfo, Map members, NodeService nodeService,
+ PersonService personService)
+ {
+ SiteState result = new SiteState();
+ result.members = new ArrayList(members.size());
+
+ result.siteInfo = siteInfo;
+
+ Set siteMembers = members.keySet();
+ for (String userName : siteMembers)
+ {
+ NodeRef person = personService.getPersonOrNull(userName);
+ if (person != null)
+ {
+ String firstName = (String) nodeService.getProperty(person, ContentModel.PROP_FIRSTNAME);
+ String lastName = (String) nodeService.getProperty(person, ContentModel.PROP_LASTNAME);
+ result.members.add(new MemberState(userName, firstName, lastName));
+ }
+ }
+
+ return result;
+ }
+
+ public SiteInfo getSiteInfo()
+ {
+ return this.siteInfo;
+ }
+
+ public List getMembers()
+ {
+ return this.members;
+ }
+
+ public static class MemberState
+ {
+ private String userName;
+ private String firstName;
+ private String lastName;
+
+ public MemberState(String userName, String firstName, String lastName)
+ {
+ this.userName = userName;
+ this.firstName = firstName;
+ this.lastName = lastName;
+ }
+
+ public String getUserName()
+ {
+ return this.userName;
+ }
+
+ public String getFirstName()
+ {
+ return this.firstName;
+ }
+
+ public String getLastName()
+ {
+ return this.lastName;
+ }
+
+ }
+}