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;
022
023import org.apache.avalon.framework.parameters.Parameters;
024import org.apache.avalon.framework.service.ServiceException;
025import org.apache.avalon.framework.service.ServiceManager;
026import org.apache.cocoon.acting.ServiceableAction;
027import org.apache.cocoon.environment.ObjectModelHelper;
028import org.apache.cocoon.environment.Redirector;
029import org.apache.cocoon.environment.Request;
030import org.apache.cocoon.environment.SourceResolver;
031import org.apache.commons.lang.BooleanUtils;
032import org.apache.commons.lang3.StringUtils;
033
034import org.ametys.cms.repository.Content;
035import org.ametys.core.cocoon.JSonReader;
036import org.ametys.core.right.RightManager;
037import org.ametys.core.right.RightManager.RightResult;
038import org.ametys.core.user.CurrentUserProvider;
039import org.ametys.core.util.I18nUtils;
040import org.ametys.plugins.repository.AmetysObjectIterable;
041import org.ametys.plugins.repository.AmetysObjectResolver;
042import org.ametys.plugins.repository.query.QueryHelper;
043import org.ametys.plugins.repository.query.SortCriteria;
044import org.ametys.plugins.repository.query.expression.AndExpression;
045import org.ametys.plugins.repository.query.expression.Expression;
046import org.ametys.web.filter.SharedContentsHelper;
047
048/**
049 * This action is used by the edition.select-site widget for searching sites from a query string
050 */
051public class SearchSitesAction extends ServiceableAction
052{
053    /** The site manager */
054    protected SiteManager _siteManager;
055    
056    /** The site types EP */
057    protected SiteTypesExtensionPoint _siteTypesEP;
058    
059    /** I18N Utils */
060    protected I18nUtils _i18nUtils;
061    
062    /** Ametys resolver */
063    protected AmetysObjectResolver _resolver;
064    
065    /** Ametys resolver */
066    protected RightManager _rightManager;
067    
068    /** User provider */
069    protected CurrentUserProvider _userProvider;
070    
071    @Override
072    public void service(ServiceManager smanager) throws ServiceException
073    {
074        super.service(smanager);
075        _siteManager = (SiteManager) smanager.lookup(SiteManager.ROLE);
076        _siteTypesEP = (SiteTypesExtensionPoint) smanager.lookup(SiteTypesExtensionPoint.ROLE);
077        _i18nUtils = (I18nUtils) smanager.lookup(I18nUtils.ROLE);
078        _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE);
079        _rightManager = (RightManager) smanager.lookup(RightManager.ROLE);
080        _userProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE);
081    }
082    
083    public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception
084    {
085        Iterable<Site> sites;
086        
087        Request request = ObjectModelHelper.getRequest(objectModel);
088        String currentSiteName = (String) request.getAttribute("siteName");
089        
090        @SuppressWarnings("unchecked")
091        Map<String, Object> jsParameters = (Map<String, Object>) objectModel.get(ObjectModelHelper.PARENT_CONTEXT);
092        
093        boolean readAccessOnly = BooleanUtils.isTrue((Boolean) jsParameters.get("readAccessOnly"));
094        boolean sharedSitesOnly = BooleanUtils.isTrue((Boolean) jsParameters.get("sharedSitesOnly"));
095        
096        @SuppressWarnings("unchecked")
097        List<String> siteNames = (List<String>) jsParameters.get("names");
098        if (siteNames != null)
099        {
100            List<Site> siteList = new ArrayList<>();
101            for (String siteName : siteNames)
102            {
103                Site site = _siteManager.getSite(siteName);
104                siteList.add(site);
105            }
106            
107            sites = siteList;
108        }
109        else
110        {
111            String query = (String) jsParameters.get("query");
112            
113            Expression siteExpression = null;
114            if (StringUtils.isNotEmpty(query))
115            {
116                String[] wildcardValues = StringUtils.isEmpty(query) ? new String[0] : query.split("\\s");
117                
118                List<Expression> expressions = new ArrayList<>();
119                for (String wildcardValue : wildcardValues)
120                {
121                    expressions.add(new SiteTitleExpression(wildcardValue));
122                }
123                
124                siteExpression = new AndExpression(expressions.toArray(new Expression[expressions.size()]));
125            }
126            
127            SortCriteria sortCriteria = new SortCriteria();
128            sortCriteria.addCriterion("title", true, true);
129            
130            String siteXPathQuery = QueryHelper.getXPathQuery(null, "ametys:site", siteExpression, sortCriteria);
131            sites = _resolver.query(siteXPathQuery);
132        }
133        
134        // Site matching
135        List<Map<String, Object>> matchingSites = new ArrayList<>();
136        for (Site site : sites)
137        {
138            // Calculate shared contents only if necessary
139            long sharedContentsCount = sharedSitesOnly ? _sharedContentsSize(site, currentSiteName) : 0;
140            
141            boolean readAccessCondition = !readAccessOnly
142                                          || readAccessOnly && canRead(site, request);
143            boolean sharedSiteCondition = !sharedSitesOnly
144                                          || sharedSitesOnly && sharedContentsCount > 0;
145            
146            if (readAccessCondition && sharedSiteCondition)
147            {
148                matchingSites.add(site2json(request, site, currentSiteName, readAccessOnly, sharedSitesOnly));
149            }
150        }
151        Map<String, Object> result = new HashMap<>();
152        result.put("sites", matchingSites);
153        
154        request.setAttribute(JSonReader.OBJECT_TO_READ, result);
155        
156        return EMPTY_MAP;
157    }
158    
159    /**
160     * Convert page to JSON object
161     * @param request the request
162     * @param site The site to convert
163     * @param currentSiteName the current site name
164     * @param readAccessOnly true to get site on read access only
165     * @param sharedSitesOnly  true to get shared site only
166     * @return The site as JSON object
167     */
168    protected Map<String, Object> site2json(Request request, Site site, String currentSiteName, boolean readAccessOnly, boolean sharedSitesOnly)
169    {
170        Map<String, Object> object = new HashMap<>();
171        
172        object.put("id", site.getId());
173        object.put("name", site.getName());
174        object.put("title", site.getTitle());
175        object.put("description", site.getDescription());
176        object.put("path", site.getPath());
177        object.put("url", site.getUrl());
178        
179        SiteType siteType = _siteTypesEP.getExtension(site.getType());
180        object.put("type", _i18nUtils.translate(siteType.getLabel()));
181        
182        return object;
183    }
184    
185    private long _sharedContentsSize (Site site, String currentSiteName)
186    {
187        if (currentSiteName == null)
188        {
189            return 0;
190        }
191        
192        Site currentSite = _siteManager.getSite(currentSiteName);
193        Expression expr = SharedContentsHelper.getSharedContentsExpression(currentSite, site);
194        
195        SortCriteria sortCriteria = new SortCriteria();
196        sortCriteria.addCriterion("title", true, true);
197        String xPathQuery = QueryHelper.getXPathQuery(null, "ametys:content", expr, sortCriteria);
198        
199        AmetysObjectIterable<Content> contents = _resolver.query(xPathQuery);
200        return contents.getSize();
201    }
202    /**
203     * Determines if the current user has a read access on site
204     * @param site the site
205     * @param request the request
206     * @return true if current user has read access
207     */
208    protected boolean canRead(Site site, Request request)
209    {
210        String currentSiteName = (String) request.getAttribute("siteName");
211        
212        try
213        {
214            request.setAttribute("siteName", site.getName());
215            return _rightManager.hasRight(_userProvider.getUser(), "Web_Right_Site_See_Contents", "/cms") == RightResult.RIGHT_ALLOW;
216        }
217        finally
218        {
219            request.setAttribute("siteName", currentSiteName);
220        }
221    }
222
223    /**
224     * Site title expression
225     */
226    protected class SiteTitleExpression implements Expression
227    {
228        private String _value;
229
230        /**
231         * Creates the comparison Expression.
232         * @param value the value to test
233         */
234        public SiteTitleExpression(String value)
235        {
236            _value = value;
237        }
238        
239        public String build()
240        {
241            return "jcr:like(fn:lower-case(ametys:title), '%" + _escapeValue(_value.toLowerCase()) + "%')";
242        }
243
244        private String _escapeValue(String value)
245        {
246            // First escape ' - " \ _ and %, Then escape ' into '' for XQuery compliance
247            return value.replaceAll("(['\\-_\"\\\\%])", "\\\\$1").replaceAll("'", "''");
248        }
249    }
250}