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 || (readAccessOnly && canRead(site, request));
142            boolean sharedSiteCondition = !sharedSitesOnly || (sharedSitesOnly && sharedContentsCount > 0);
143            
144            if (readAccessCondition && sharedSiteCondition)
145            {
146                matchingSites.add(site2json(request, site, currentSiteName, readAccessOnly, sharedSitesOnly));
147            }
148        }
149        Map<String, Object> result = new HashMap<>();
150        result.put("sites", matchingSites);
151        
152        request.setAttribute(JSonReader.OBJECT_TO_READ, result);
153        
154        return EMPTY_MAP;
155    }
156    
157    /**
158     * Convert page to JSON object
159     * @param request the request
160     * @param site The site to convert
161     * @param currentSiteName the current site name
162     * @param readAccessOnly true to get site on read access only
163     * @param sharedSitesOnly  true to get shared site only
164     * @return The site as JSON object
165     */
166    protected Map<String, Object> site2json(Request request, Site site, String currentSiteName, boolean readAccessOnly, boolean sharedSitesOnly)
167    {
168        Map<String, Object> object = new HashMap<>();
169        
170        object.put("id", site.getId());
171        object.put("name", site.getName());
172        object.put("title", site.getTitle());
173        object.put("description", site.getDescription());
174        object.put("path", site.getPath());
175        object.put("url", site.getUrl());
176        
177        SiteType siteType = _siteTypesEP.getExtension(site.getType());
178        object.put("type", _i18nUtils.translate(siteType.getLabel()));
179        
180        return object;
181    }
182    
183    private long _sharedContentsSize (Site site, String currentSiteName)
184    {
185        if (currentSiteName == null)
186        {
187            return 0;
188        }
189        
190        Site currentSite = _siteManager.getSite(currentSiteName);
191        Expression expr = SharedContentsHelper.getSharedContentsExpression(currentSite, site);
192        
193        SortCriteria sortCriteria = new SortCriteria();
194        sortCriteria.addCriterion("title", true, true);
195        String xPathQuery = QueryHelper.getXPathQuery(null, "ametys:content", expr, sortCriteria);
196        
197        AmetysObjectIterable<Content> contents = _resolver.query(xPathQuery);
198        return contents.getSize();
199    }
200    /**
201     * Determines if the current user has a read access on site
202     * @param site the site
203     * @param request the request
204     * @return true if current user has read access
205     */
206    protected boolean canRead(Site site, Request request)
207    {
208        String currentSiteName = (String) request.getAttribute("siteName");
209        
210        try
211        {
212            request.setAttribute("siteName", site.getName());
213            return _rightManager.hasRight(_userProvider.getUser(), "Web_Right_Site_See_Contents", "/cms") == RightResult.RIGHT_ALLOW;
214        }
215        finally
216        {
217            request.setAttribute("siteName", currentSiteName);
218        }
219    }
220
221    /**
222     * Site title expression
223     */
224    protected class SiteTitleExpression implements Expression
225    {
226        private String _value;
227
228        /**
229         * Creates the comparison Expression.
230         * @param value the value to test
231         */
232        public SiteTitleExpression(String value)
233        {
234            _value = value;
235        }
236        
237        public String build()
238        {
239            return "jcr:like(fn:lower-case(ametys:title), '%" + _escapeValue(_value.toLowerCase()) + "%')";
240        }
241
242        private String _escapeValue(String value)
243        {
244            // First escape ' - " \ _ and %, Then escape ' into '' for XQuery compliance
245            return value.replaceAll("(['\\-_\"\\\\%])", "\\\\$1").replaceAll("'", "''");
246        }
247    }
248}