001/*
002 *  Copyright 2015 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.web.repository.site;
017
018import java.util.ArrayList;
019import java.util.HashMap;
020import java.util.List;
021import java.util.Map;
022import java.util.Objects;
023import java.util.stream.Collectors;
024
025import org.apache.avalon.framework.parameters.Parameters;
026import org.apache.avalon.framework.service.ServiceException;
027import org.apache.avalon.framework.service.ServiceManager;
028import org.apache.cocoon.acting.ServiceableAction;
029import org.apache.cocoon.environment.ObjectModelHelper;
030import org.apache.cocoon.environment.Redirector;
031import org.apache.cocoon.environment.Request;
032import org.apache.cocoon.environment.SourceResolver;
033import org.apache.commons.lang.BooleanUtils;
034import org.apache.commons.lang.StringUtils;
035
036import org.ametys.cms.repository.Content;
037import org.ametys.core.cocoon.JSonReader;
038import org.ametys.core.group.GroupDirectoryContextHelper;
039import org.ametys.core.group.GroupDirectoryDAO;
040import org.ametys.core.right.RightManager;
041import org.ametys.core.right.RightManager.RightResult;
042import org.ametys.core.user.CurrentUserProvider;
043import org.ametys.core.user.UserIdentity;
044import org.ametys.core.user.population.PopulationContextHelper;
045import org.ametys.core.user.population.UserPopulationDAO;
046import org.ametys.plugins.repository.AmetysObject;
047import org.ametys.plugins.repository.AmetysObjectIterable;
048import org.ametys.plugins.repository.AmetysObjectResolver;
049import org.ametys.plugins.repository.query.SortCriteria;
050import org.ametys.plugins.repository.query.expression.Expression;
051import org.ametys.web.filter.SharedContentsHelper;
052import org.ametys.web.site.SiteColorsComponent;
053import org.ametys.web.site.SiteConfigurationManager;
054import org.ametys.web.skin.Skin;
055import org.ametys.web.skin.SkinsManager;
056
057/**
058 * Action for getting the list of existing {@link Site}.
059 */
060public class GetSitesAction extends ServiceableAction
061{
062    /** The site manager */
063    protected SiteManager _siteManager;
064    /** The rights manager */
065    protected RightManager _rightManager;
066    /** The current user provider */
067    protected CurrentUserProvider _userProvider;
068    /** The population context helper */
069    protected PopulationContextHelper _populationContextHelper;
070    /** The group directory context helper */
071    protected GroupDirectoryContextHelper _groupDirectoryContextHelper;
072    /** The user population DAO */
073    protected UserPopulationDAO _userPopulationDAO;
074    /** The group directory DAO */
075    protected GroupDirectoryDAO _groupDirectoryDAO;
076    /** Site type EP */
077    protected SiteTypesExtensionPoint _siteTypeExtensionPoint;
078    /** Site configuration */
079    protected SiteConfigurationManager _siteConfigurationManager;
080    /** Ametys resolver */
081    protected AmetysObjectResolver _ametysResolver;
082    /** Handle site colors */
083    protected SiteColorsComponent _siteColors;
084    /** The skins manager */ 
085    protected SkinsManager _skinsManager;
086    
087    @Override
088    public void service(ServiceManager smanager) throws ServiceException
089    {
090        super.service(smanager);
091        _siteManager = (SiteManager) smanager.lookup(SiteManager.ROLE);
092        _rightManager = (RightManager) smanager.lookup(RightManager.ROLE);
093        _userProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE);
094        _siteTypeExtensionPoint = (SiteTypesExtensionPoint) smanager.lookup(SiteTypesExtensionPoint.ROLE);
095        _siteConfigurationManager = (SiteConfigurationManager) smanager.lookup(SiteConfigurationManager.ROLE);
096        _ametysResolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE);
097        _populationContextHelper = (PopulationContextHelper) smanager.lookup(PopulationContextHelper.ROLE);
098        _groupDirectoryContextHelper = (GroupDirectoryContextHelper) smanager.lookup(GroupDirectoryContextHelper.ROLE);
099        _userPopulationDAO = (UserPopulationDAO) smanager.lookup(UserPopulationDAO.ROLE);
100        _groupDirectoryDAO = (GroupDirectoryDAO) smanager.lookup(GroupDirectoryDAO.ROLE);
101        _siteColors = (SiteColorsComponent) smanager.lookup(SiteColorsComponent.ROLE);
102        _skinsManager = (SkinsManager) smanager.lookup(SkinsManager.ROLE);
103    }
104    
105    @Override
106    public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception
107    {
108        Request request = ObjectModelHelper.getRequest(objectModel);
109        Map<String, Object> result = new HashMap<>();
110        
111        UserIdentity user = _userProvider.getUser();
112        
113        String id = request.getParameter("id");
114        
115        boolean recursive = parameters.getParameterAsBoolean("recursive", BooleanUtils.toBoolean(request.getParameter("recursive")));
116        boolean readAccessOnly = parameters.getParameterAsBoolean("readAccessOnly", BooleanUtils.toBoolean(request.getParameter("readAccessOnly")));
117        boolean sharedSitesOnly = parameters.getParameterAsBoolean("sharedSitesOnly", BooleanUtils.toBoolean(request.getParameter("sharedSitesOnly")));
118        
119        List<Map<String, Object>> sites = new ArrayList<>();
120        
121        try (AmetysObjectIterable<Site> rootSites = _getRootSites(id);)
122        {
123            String currentSiteName = (String) request.getAttribute("siteName");
124            
125            for (Site site : rootSites)
126            {
127                sites.addAll(_rootSite2json(request, site, currentSiteName, recursive, readAccessOnly, sharedSitesOnly, user));
128            }
129        }
130        
131        result.put("sites", sites);
132        
133        request.setAttribute(JSonReader.OBJECT_TO_READ, result);
134        
135        return EMPTY_MAP;
136    }
137    
138    /**
139     * Get root sites
140     * @param id Optionnal root id
141     * @return Ametys iterable of sites
142     */
143    protected AmetysObjectIterable<Site> _getRootSites(String id)
144    {
145        if (StringUtils.isNotEmpty(id))
146        {
147            AmetysObject ao = _ametysResolver.resolveById(id);
148
149            if (ao instanceof Site)
150            {
151                return ((Site) ao).getChildrenSites();
152            }
153        }
154        return _siteManager.getRootSites();
155    }
156    
157    /**
158     * Retrieves the JSON for a root site
159     * @param request The request
160     * @param site The root site
161     * @param currentSiteName The name of the current site
162     * @param recursive True to handle child sites
163     * @param readAccessOnly Only read access site
164     * @param sharedSitesOnly Only shared sites
165     * @param user The current user
166     * @return The root site info
167     */
168    protected List<Map<String, Object>> _rootSite2json(Request request, Site site, String currentSiteName, boolean recursive, boolean readAccessOnly, boolean sharedSitesOnly, UserIdentity user)
169    {
170        List<Map<String, Object>> sites = new ArrayList<>();
171        
172        if (recursive)
173        {
174            // on recursion, returns all sub sites on the same level. The real level is determined by the "depth" property 
175            sites.addAll(_childSites2json(request, site, currentSiteName, 0, readAccessOnly, sharedSitesOnly));
176        }
177        else
178        {
179            sites.add(_site2json(request, site, currentSiteName, 0, readAccessOnly, sharedSitesOnly));
180        }
181        
182        return sites;
183    }
184    
185    /**
186     * Child site to JSON
187     * @param request The request
188     * @param site The root site
189     * @param currentSiteName The name of the current site
190     * @param depth The depth from the root site
191     * @param readAccessOnly Only read access site
192     * @param sharedSitesOnly Only shared sites
193     * @return The child sites info
194     */
195    protected List<Map<String, Object>> _childSites2json (Request request, Site site, String currentSiteName, int depth, boolean readAccessOnly, boolean sharedSitesOnly)
196    {
197        List<Map<String, Object>> childrenAndItseft = new ArrayList<>();
198        
199        childrenAndItseft.add(_site2json(request, site, currentSiteName, depth, readAccessOnly, sharedSitesOnly));
200        
201        // Get child site recursively
202        for (Site child : site.getChildrenSites())
203        {
204            childrenAndItseft.addAll(_childSites2json(request, child, currentSiteName, depth + 1, readAccessOnly, sharedSitesOnly));
205        }
206        
207        return childrenAndItseft;
208    }
209    
210    /**
211     * Site to JSON
212     * @param request The request
213     * @param site The root site
214     * @param currentSiteName The name of the current site
215     * @param depth The depth from the root site
216     * @param readAccessOnly Only read access site
217     * @param sharedSitesOnly Only shared sites
218     * @return The site info
219     */
220    protected Map<String, Object> _site2json (Request request, Site site, String currentSiteName, int depth, boolean readAccessOnly, boolean sharedSitesOnly)
221    {
222        Map<String, Object> object = new HashMap<>();
223        
224        // Calculate shared contents only if necessary
225        long sharedContentsCount = sharedSitesOnly ? _sharedContentsSize(site, currentSiteName) : 0;
226        
227        boolean readAccessCondition = !readAccessOnly
228                                      || readAccessOnly && canRead(site, request);
229        boolean sharedSiteCondition = !sharedSitesOnly
230                                      || sharedSitesOnly && sharedContentsCount > 0;
231        
232        if (readAccessCondition && sharedSiteCondition)
233        {
234            object = _site2json(site);
235            object.put("depth", depth);
236            object.put("current", site.getName().equals(currentSiteName));
237            object.put("sharedContentsCount", sharedContentsCount);
238            
239            if (site.getChildrenSiteNames().isEmpty())
240            {
241                object.put("sites", new ArrayList<>());
242            }
243        }
244        return object;
245    }
246    
247    /**
248     * Get the JSON representation of a site
249     * @param site The site
250     * @return The site as JSON object
251     */
252    protected Map<String, Object> _site2json (Site site)
253    {
254        Map<String, Object> object = new HashMap<>();
255        
256        object.put("id", site.getId());
257        object.put("name", site.getName());
258        object.put("title", StringUtils.defaultString(site.getTitle()));
259        object.put("description", StringUtils.defaultString(site.getDescription()));
260        object.put("url", StringUtils.defaultString(site.getUrl()));
261        object.put("valid", _siteConfigurationManager.isSiteConfigurationValid(site));
262        
263        object.put("color", site.getColor());
264        String colorIndex = site.getColor();
265        if (!_siteColors.getColors().containsKey(colorIndex))
266        {
267            colorIndex = _siteColors.getDefaultKey();
268        }
269        object.put("colorIndex", colorIndex);
270        
271        SiteType siteType = _siteTypeExtensionPoint.getExtension(site.getType());
272        
273        String iconGlyph = siteType.getIconGlyph();
274        if (iconGlyph != null)
275        {
276            object.put("iconGlyph", iconGlyph);
277        }
278        
279        object.put("iconSmall", siteType.getSmallIcon());
280        object.put("iconLarge", siteType.getLargeIcon());
281        object.put("typeLabel", siteType.getLabel());
282        object.put("type", siteType.getId());
283        object.put("privateType", siteType.isPrivateType());
284        
285        String skinId = site.getSkinId();
286        object.put("skin", skinId);
287            
288        Skin skin = _skinsManager.getSkin(skinId);
289        if (skin != null)
290        {
291            object.put("skinLabel", skin.getLabel());
292        }
293        
294        object.put("populations", _populationContextHelper.getUserPopulationsOnContext("/sites/" + site.getName(), true).stream()
295                                                                                                                        .map(_userPopulationDAO::getUserPopulation)
296                                                                                                                        .filter(Objects::nonNull)
297                                                                                                                        .map(_userPopulationDAO::getUserPopulationAsJson)
298                                                                                                                        .collect(Collectors.toList()));
299        object.put("populationsFO", _populationContextHelper.getUserPopulationsOnContext("/sites-fo/" + site.getName(), true).stream()
300                                                                                                                             .map(_userPopulationDAO::getUserPopulation)
301                                                                                                                             .filter(Objects::nonNull)
302                                                                                                                             .map(_userPopulationDAO::getUserPopulationAsJson)
303                                                                                                                             .collect(Collectors.toList()));
304        
305        object.put("groups", _groupDirectoryContextHelper.getGroupDirectoriesOnContext("/sites/" + site.getName()).stream()
306                                                                                                                  .map(_groupDirectoryDAO::getGroupDirectory)
307                                                                                                                  .filter(Objects::nonNull)
308                                                                                                                  .map(_groupDirectoryDAO::getGroupDirectory2Json)
309                                                                                                                  .collect(Collectors.toList()));
310        object.put("groupsFO", _groupDirectoryContextHelper.getGroupDirectoriesOnContext("/sites-fo/" + site.getName()).stream()
311                                                                                                                       .map(_groupDirectoryDAO::getGroupDirectory)
312                                                                                                                       .filter(Objects::nonNull)
313                                                                                                                       .map(_groupDirectoryDAO::getGroupDirectory2Json)
314                                                                                                                       .collect(Collectors.toList()));
315        
316        return object;
317    }
318    
319    private long _sharedContentsSize (Site site, String currentSiteName)
320    {
321        if (currentSiteName == null)
322        {
323            return 0;
324        }
325        
326        Site currentSite = _siteManager.getSite(currentSiteName);
327        Expression expr = SharedContentsHelper.getSharedContentsExpression(currentSite, site);
328        
329        SortCriteria sortCriteria = new SortCriteria();
330        sortCriteria.addCriterion("title", true, true);
331        String xPathQuery = org.ametys.plugins.repository.query.QueryHelper.getXPathQuery(null, "ametys:content", expr, sortCriteria);
332        
333        AmetysObjectIterable<Content> contents = _ametysResolver.query(xPathQuery);
334        return contents.getSize();
335    }
336    
337    /**
338     * Determines if the current user has a read access on site
339     * @param site the site
340     * @param request the request
341     * @return true if current user has read access
342     */
343    protected boolean canRead (Site site, Request request)
344    {
345        String currentSiteName = (String) request.getAttribute("siteName");
346        
347        try
348        {
349            request.setAttribute("siteName", site.getName());
350            return _rightManager.hasRight(_userProvider.getUser(), "Web_Right_Site_See_Contents", "/cms") == RightResult.RIGHT_ALLOW;
351        }
352        finally
353        {
354            request.setAttribute("siteName", currentSiteName);
355        }
356    }
357    
358}