001/*
002 *  Copyright 2014 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.cms.tag;
017
018import java.io.IOException;
019import java.util.ArrayList;
020import java.util.HashMap;
021import java.util.HashSet;
022import java.util.List;
023import java.util.Map;
024import java.util.Set;
025
026import javax.jcr.RepositoryException;
027
028import org.apache.avalon.framework.parameters.Parameters;
029import org.apache.avalon.framework.service.ServiceException;
030import org.apache.avalon.framework.service.ServiceManager;
031import org.apache.cocoon.ProcessingException;
032import org.apache.cocoon.acting.ServiceableAction;
033import org.apache.cocoon.environment.ObjectModelHelper;
034import org.apache.cocoon.environment.Redirector;
035import org.apache.cocoon.environment.Request;
036import org.apache.cocoon.environment.SourceResolver;
037import org.apache.commons.lang.StringUtils;
038import org.xml.sax.SAXException;
039
040import org.ametys.cms.repository.Content;
041import org.ametys.cms.tag.Tag.TagVisibility;
042import org.ametys.cms.tag.jcr.JCRTagProvider;
043import org.ametys.core.cocoon.JSonReader;
044import org.ametys.core.right.RightManager;
045import org.ametys.core.right.RightManager.RightResult;
046import org.ametys.core.user.CurrentUserProvider;
047import org.ametys.core.user.UserIdentity;
048import org.ametys.plugins.repository.AmetysObject;
049import org.ametys.plugins.repository.AmetysObjectResolver;
050import org.ametys.plugins.repository.AmetysRepositoryException;
051
052/**
053 * SAX events for tags
054 *
055 */
056public class GetTagsAction extends ServiceableAction
057{
058    /** The tag provider extension point */
059    protected TagProviderExtensionPoint _tagProviderExtPt;
060
061    /** The Ametys object resolver */
062    protected AmetysObjectResolver _resolver;
063
064    /** The rights manager. */
065    protected RightManager _rightManager;
066    
067    /** The current user provider. */
068    protected CurrentUserProvider _currentUserProvider;
069
070
071    @Override
072    public void service(ServiceManager serviceManager) throws ServiceException
073    {
074        super.service(serviceManager);
075        _tagProviderExtPt = (TagProviderExtensionPoint) serviceManager.lookup(TagProviderExtensionPoint.ROLE);
076        _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE);
077        _rightManager = (RightManager) serviceManager.lookup(RightManager.ROLE);
078        _currentUserProvider = (CurrentUserProvider) serviceManager.lookup(CurrentUserProvider.ROLE);
079        
080    }
081
082    @Override
083    public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws IOException, SAXException, ProcessingException
084    {
085        @SuppressWarnings("unchecked")
086        Map<String, Object> jsParameters = (Map<String, Object>) objectModel.get(ObjectModelHelper.PARENT_CONTEXT);
087        
088        String parentId = (String) jsParameters.get("node");
089        String nodeName = (String) jsParameters.get("nodeName");
090        boolean onlyCustomTags = (Boolean) jsParameters.get("onlyCustomTags");
091        
092        @SuppressWarnings("unchecked")
093        List<String> objectTargetIds = (List<String>) jsParameters.get("objectTargetIds");
094        
095        String targetType = (String) jsParameters.get("targetType");
096        
097        @SuppressWarnings("unchecked")
098        Map<String, Object> contextualParameters = (Map<String, Object>) jsParameters.get("contextualParameters");
099        
100        List<Map<String, Object>> tags = new ArrayList<>();
101
102        if (StringUtils.isEmpty(parentId) || parentId.equals("tag-root"))
103        {
104            Set<String> extensionsIds = getProvidersIds(onlyCustomTags);
105
106            for (String id : extensionsIds)
107            {
108                TagProvider tagProvider = _tagProviderExtPt.getExtension(id);
109                tags.add(providerToJSON(tagProvider, contextualParameters, targetType));
110            }
111        }
112        else if (nodeName.startsWith("provider_"))
113        {
114            String providerId = nodeName.substring("provider_".length());
115            TagProvider tagProvider = _tagProviderExtPt.getExtension(providerId);
116            
117            if (tagProvider != null)
118            {
119                Map<String, Tag> childs = tagProvider.getFilteredTags(targetType, contextualParameters);
120                if (childs != null)
121                {
122                    for (Tag tag : childs.values())
123                    {
124                        tags.add(tagToJSON(tag, tagProvider, objectTargetIds));
125                    }
126                }                
127            }
128        }
129        else
130        {
131            Set<String> extensionsIds = getProvidersIds(onlyCustomTags);
132            for (String id : extensionsIds)
133            {
134                TagProvider tagProvider = _tagProviderExtPt.getExtension(id);
135                if (tagProvider.hasTag(nodeName, contextualParameters))
136                {
137                    Tag parent = tagProvider.getTag(nodeName, contextualParameters);
138                    Map<String, Tag> childs = parent.getTags();
139                    for (Tag tag : childs.values())
140                    {
141                        if (targetType == null || tag.getTarget().getName().equals(targetType))
142                        {
143                            tags.add(tagToJSON(tag, tagProvider, objectTargetIds));
144                        }
145                    }
146                }
147            }
148        }
149
150        Map<String, Object> result = new HashMap<>();
151        result.put("children", tags);
152
153        Request request = ObjectModelHelper.getRequest(objectModel);
154        request.setAttribute(JSonReader.OBJECT_TO_READ, result);
155
156        return EMPTY_MAP;
157    }
158
159    /**
160     * Get all providers IDs
161     * @param onlyCustomTags If true, return only JCR providers IDs.
162     * @return a set of providers IDs
163     */
164    protected Set<String> getProvidersIds(boolean onlyCustomTags)
165    {
166        if (onlyCustomTags)
167        {
168            Set<String> extensionsIds = new HashSet<>();
169            TagProvider tagProvider = _tagProviderExtPt.getExtension(JCRTagProvider.class.getName());
170            extensionsIds.add(tagProvider.getId());
171            return extensionsIds;
172        }
173        
174        return _tagProviderExtPt.getExtensionsIds();
175    }
176    
177    /**
178     * Returns the tag providers' id
179     * @return The id of tag providers
180     */
181    protected Set<String> getProvidersId()
182    {
183        return getProvidersIds(false);
184    }
185
186    /**
187     * Get tag's properties to JSON format
188     * @param tag The tag
189     * @param tagProvider the tag provider
190     * @param objectTargetIds the list of Ametys object ids. Can be empty
191     * @return The tag properties
192     */
193    protected Map<String, Object> tagToJSON(Tag tag, TagProvider tagProvider, List<String> objectTargetIds)
194    {
195        Map<String, Object> infos = new HashMap<>();
196
197        infos.put("id", tag.getId());
198        infos.put("name", tag.getName());
199        infos.put("title", tag.getTitle());
200        infos.put("description", tag.getDescription());
201        
202        boolean authorized = isUserAuthorized(tag, objectTargetIds);
203        infos.put("authorized", authorized);
204        infos.put("creationAllowed", isCreationAllowed(tagProvider, tag));
205        
206        TagVisibility visibility = tag.getVisibility();
207        infos.put("visibility", visibility.toString());
208        
209        TagTargetType tagTargetType = tag.getTarget();
210        
211        if (tagTargetType != null)
212        {
213            Map<String, Object> target2json = new HashMap<>();
214            target2json.put("name", tagTargetType.getName());
215            target2json.put("label", tagTargetType.getLabel());
216            target2json.put("description", tagTargetType.getDescription());
217            
218            infos.put("target", target2json);
219        }
220        
221        if (TagVisibility.PRIVATE.equals(visibility))
222        {
223            if (authorized)
224            {
225                infos.put("iconSmall", "/plugins/cms/resources/img/tag/tag_private_16.png");
226                infos.put("iconMedium", "/plugins/cms/resources/img/tag/tag_private_32.png");
227                infos.put("iconLarge", "/plugins/cms/resources/img/tag/tag_private_50.png");
228            }
229            else
230            {
231                infos.put("iconSmall", "/plugins/cms/resources/img/tag/tag_unauthorized_16.png");
232                infos.put("iconMedium", "/plugins/cms/resources/img/tag/tag_unauthorized_32.png");
233                infos.put("iconLarge", "/plugins/cms/resources/img/tag/tag_unauthorized_50.png");
234            }    
235        }
236        else
237        {
238            infos.put("iconSmall", "/plugins/cms/resources/img/tag/tag_16.png");
239            infos.put("iconMedium", "/plugins/cms/resources/img/tag/tag_32.png");
240            infos.put("iconLarge", "/plugins/cms/resources/img/tag/tag_50.png");
241        }
242
243        if (tag.getTags().size() == 0)
244        {
245            infos.put("leaf", true);
246        }
247        
248        
249        return infos;
250    }
251
252    /**
253     * Get tag provider's properties to JSON format
254     * @param tagProvider the tag provider
255     * @param contextualParameters The contextual parameters
256     * @param targetType The type of target (such as 'content')
257     * @return The tag providers properties
258     * @throws AmetysRepositoryException If an error occurred
259     */
260    protected Map<String, Object> providerToJSON(TagProvider tagProvider, Map<String, Object> contextualParameters, String targetType)
261    {
262        Map<String, Object> infos = new HashMap<>();
263
264        if (tagProvider instanceof JCRTagProvider)
265        {
266            JCRTagProvider provider = (JCRTagProvider) tagProvider;
267            try
268            {
269                infos.put("id", provider.getRootNode(contextualParameters).getId());
270            }
271            catch (AmetysRepositoryException e)
272            {
273                getLogger().error("Unable to get JCR tag provider root node", e);
274            }
275            catch (RepositoryException e)
276            {
277                getLogger().error("Unable to get JCR tag provider root node", e);
278            }
279        }
280        else
281        {
282            infos.put("id", tagProvider.getId());
283        }
284        
285        infos.put("type", "provider");
286        infos.put("name", "provider_" + tagProvider.getId());
287        infos.put("title", tagProvider.getLabel());
288        infos.put("description", tagProvider.getDescription());
289        infos.put("authorized", true); 
290        infos.put("creationAllowed", isCreationAllowed(tagProvider, null));
291        infos.put("iconSmall", "/plugins/cms/resources/img/tag/tags_16.png");
292        infos.put("iconMedium", "/plugins/cms/resources/img/tag/tags_32.png");
293        infos.put("iconLarge", "/plugins/cms/resources/img/tag/tags_50.png");
294
295        if (tagProvider.getFilteredTags(targetType, contextualParameters).size() == 0)
296        {
297            infos.put("leaf", true);
298        }
299        return infos;
300    }
301    
302    /**
303     * Test if a tag is visible to the current user.
304     * @param tag the Tag object.
305     * @param content the current content (can be null).
306     * @return true if the user has access to the tag, false otherwise.
307     */
308    protected boolean isUserAuthorized(Tag tag, Content content)
309    {
310        if (content != null)
311        {
312            if (isPrivate(tag))
313            {
314                return _rightManager.hasRight(_getCurrentUser(), "CMS_Rights_Content_Private_Tag", content) == RightResult.RIGHT_ALLOW;
315            }
316            else
317            {
318                return _rightManager.hasRight(_getCurrentUser(), "CMS_Rights_Content_Tag", content) == RightResult.RIGHT_ALLOW
319                        || _rightManager.hasRight(_getCurrentUser(), "CMS_Rights_Content_Private_Tag", content) == RightResult.RIGHT_ALLOW;
320            }
321        }
322        
323        return true;
324    }
325    
326    /**
327     * Test if a tag is visible to the current user.
328     * @param tag the Tag object.
329     * @param objectTargetIds the ids of current Ametys objects
330     * @return true if the user has access to the tag, false otherwise.
331     */
332    protected boolean isUserAuthorized(Tag tag, List<String> objectTargetIds)
333    {
334        if (objectTargetIds != null && objectTargetIds.size() > 0)
335        {
336            boolean hasRight = false;
337            for (String objectId : objectTargetIds)
338            {
339                AmetysObject ao = _resolver.resolveById(objectId);
340                
341                if (ao instanceof Content && tag.getTarget().getName().equals("CONTENT"))
342                {
343                    hasRight = isUserAuthorized (tag, (Content) ao) || hasRight;
344                }
345            }
346            return hasRight;
347        }
348        
349        return true;
350    }
351    
352    /**
353     * Determines if the current user can created tag
354     * @param tagProvider The tag provider
355     * @param tag The tag. Can be null.
356     * @return true if creation is allowed
357     */
358    protected boolean isCreationAllowed(TagProvider tagProvider, Tag tag)
359    {
360        if (!(tagProvider instanceof JCRTagProvider))
361        {
362            return false;
363        }
364        return _rightManager.hasRight(_getCurrentUser(), "CMS_Rights_Tags_HandleTag", "/cms") == RightResult.RIGHT_ALLOW;
365    }
366
367    /**
368     * Determines if a tag is private
369     * @param tag the tag to test
370     * @return true if a tag is private
371     */
372    protected boolean isPrivate (Tag tag)
373    {
374        return tag.getVisibility() == TagVisibility.PRIVATE;
375    }
376    
377    /**
378     * Provides the current user.
379     * @return the current user which cannot be <code>null</code>.
380     */
381    protected UserIdentity _getCurrentUser()
382    {
383        return _currentUserProvider.getUser();
384    }
385    
386}