001/*
002 *  Copyright 2019 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 */
016
017package org.ametys.web.site;
018
019import java.util.ArrayList;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023import java.util.Map.Entry;
024import java.util.Set;
025
026import org.apache.avalon.framework.component.Component;
027import org.apache.avalon.framework.logger.AbstractLogEnabled;
028import org.apache.avalon.framework.service.ServiceException;
029import org.apache.avalon.framework.service.ServiceManager;
030import org.apache.avalon.framework.service.Serviceable;
031import org.apache.commons.lang3.StringUtils;
032
033import org.ametys.cms.tag.CMSTag;
034import org.ametys.cms.tag.CMSTag.TagVisibility;
035import org.ametys.cms.tag.Tag;
036import org.ametys.cms.tag.TagsDAO;
037import org.ametys.core.right.RightManager;
038import org.ametys.core.ui.Callable;
039import org.ametys.core.user.CurrentUserProvider;
040import org.ametys.core.user.UserIdentity;
041import org.ametys.core.util.I18nUtils;
042import org.ametys.plugins.repository.AmetysObject;
043import org.ametys.plugins.repository.AmetysObjectResolver;
044import org.ametys.web.repository.page.Page;
045import org.ametys.web.repository.page.PageDAO;
046import org.ametys.web.repository.sitemap.Sitemap;
047
048/**
049 * Component to help AddPageWizard to get all it's infos without calling {@link PageDAO} or {@link TagsDAO} multiple times
050 */
051public class AddPageWizardHelper extends AbstractLogEnabled implements Serviceable, Component
052{
053    /** Avalon Role */
054    public static final String ROLE = AddPageWizardHelper.class.getName();
055    
056    /** Ametys Object Resolver */
057    protected AmetysObjectResolver _resolver;
058    
059    /** Right Manager */
060    protected RightManager _rightManager;
061    
062    /** Current User Provider */
063    protected CurrentUserProvider _currentUserProvider;
064    
065    /** Tags DAO */
066    protected TagsDAO _tagsDAO;
067    
068    /** The I18n utils */
069    protected I18nUtils _i18nUtils;
070    
071    public void service(ServiceManager manager) throws ServiceException
072    {
073        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
074        _rightManager = (RightManager) manager.lookup(RightManager.ROLE);
075        _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE);
076        _tagsDAO = (TagsDAO) manager.lookup(TagsDAO.ROLE);
077        _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE);
078    }
079
080    /**
081     * Get the page's properties
082     * @param pageId the page ID
083     * @param depth number of parents to fetch to generate the title (0 to display only the page name)
084     * @param separator separator between the parents (e.g. " > " to have "grandParent > parent > page")
085     * @return the properties
086     */
087    @Callable
088    public Map<String, Object> getPageInfos (String pageId, int depth, String separator)
089    {
090        AmetysObject page = _resolver.resolveById(pageId);
091        UserIdentity user = _currentUserProvider.getUser();
092        return getPageDetails(page, user, depth, separator);
093    }
094    
095    /**
096     * Get the page's properties
097     * @param pageIds the page IDs
098     * @param depth number of parents to fetch to generate the title (0 to display only the page name)
099     * @param separator separator between the parents (e.g. " &gt; " to have "grandParent &gt; parent &gt; page")
100     * @return the properties
101     */
102    @Callable
103    public List<Map<String, Object>> getPagesInfos (List<String> pageIds, int depth, String separator)
104    {
105        UserIdentity user = _currentUserProvider.getUser();
106        List<Map<String, Object>> result = new ArrayList<>();
107        for (String pageId : pageIds)
108        {
109            AmetysObject page = _resolver.resolveById(pageId);
110            Map<String, Object> pageInfos = getPageDetails(page, user, depth, separator);
111            result.add(pageInfos);
112        }
113        return result;
114    }
115    
116    /**
117     * get the id, rights and formatted title of a page
118     * @param page page/sitemap to work on
119     * @param user user to check rights
120     * @param depth number of parents to fetch to generate the title (0 to display only the page name)
121     * @param separator separator between the parents (e.g. " &gt; " to have "grandParent &gt; parent &gt; page")
122     * @return a map with id (String), rights (Set&lt;String&gt;) and title (String)
123     */
124    protected Map<String, Object> getPageDetails(AmetysObject page, UserIdentity user, int depth, String separator)
125    {
126        Map<String, Object> result = new HashMap<>();
127        result.put("id", page.getId());
128        Set<String> userRights = _rightManager.getUserRights(user, page);
129        result.put("rights", userRights);
130        
131        String title = "";
132        AmetysObject parent = page;
133        for (int i = 0; i <= depth; i++)
134        {
135            // We stop if we have no parent
136            // OR parent is not a page 
137            //   AND parent is not a Sitemap
138            // OR parent is a Sitemap and i != 0 (we display the sitemap only if it is specifically requested)
139            if (parent == null 
140                    || !(parent instanceof Page || parent instanceof Sitemap)
141                    || (parent instanceof Sitemap && i != 0))
142            {
143                // If we have reached the sitemap, we stop to try to get a parent
144                break;
145            }
146            
147            if (i != 0)
148            {
149                title = separator + title;
150            }
151            
152            if (parent instanceof Page)
153            {
154                title = ((Page) parent).getTitle() + title;
155            }
156            else if (parent instanceof Sitemap)
157            {
158                Sitemap sitemap = (Sitemap) parent;
159                title = sitemap.getSiteName() + " (" + sitemap.getSitemapName() + ")" + title;
160            }
161            parent = parent.getParent();
162        }
163        
164        result.put("title", title);
165        
166        return result;
167    }
168    
169    /**
170     * Get the list of sub-tags for a list of input tags
171     * @param tagNames name of input tags where we want to get the list of contained tags
172     * @param filters list of filters to disable, can contain PAGE_PRIVATE, CONTENT_PRIVATE, PAGE, CONTENT
173     * @param targetType tag target type
174     * @param showCategories true to display tags containing other tags. If false, only the non-parents tags will be listed
175     * @param separator separator between the parents (e.g. " &gt; " to have "grandParent &gt; parent &gt; page")
176     * @param contextualParameters the contextual parameters
177     * @return a list with one item per requested tagName, each one containing name (String), title (String) and tags (List of Map&lt;String, String&gt;, each map containing a name and title)
178     */
179    @Callable
180    public List<Map<String, Object>> getTags(List<String> tagNames, List<String> filters, String targetType, boolean showCategories, String separator, Map<String, Object> contextualParameters)
181    {
182        List<Map<String, Object>> result = new ArrayList<>();
183        for (String tagName : tagNames)
184        {
185            Tag tag = _tagsDAO.getTag(tagName, contextualParameters);
186            if (tag != null)
187            {
188                if (!isTagAvailable(tag, filters, targetType))
189                {
190                    continue;
191                }
192                Map<String, Object> tagMap = parseTag(tag, tag, separator);
193                List<Map<String, Object>> subTags = getSubTags(tag, tag, filters, targetType, showCategories, separator);
194                tagMap.put("tags", subTags);
195                result.add(tagMap);
196            }
197            else
198            {
199                getLogger().warn("The tag '" + tagName + "' can not be found.");
200            }
201        }
202        
203        return result;
204    }
205    
206    /**
207     * Check if we can use this tag
208     * @param tag tag to check
209     * @param filters list of filters to disable, can contain PAGE_PRIVATE, CONTENT_PRIVATE, PAGE, CONTENT
210     * @param targetType tag target type
211     * @return true if this tag is not impacted by the input filters
212     */
213    protected boolean isTagAvailable(Tag tag, List<String> filters, String targetType)
214    {
215        // If we can find the visibility/target of the tag, we filter it
216        if (tag instanceof CMSTag)
217        {
218            CMSTag cmsTag = (CMSTag) tag;
219
220            //PAGE_PRIVATE, CONTENT_PRIVATE, PAGE, CONTENT
221            boolean filterContent = filters.contains("CONTENT");
222            boolean filterPage = filters.contains("PAGE");
223            boolean filterPrivateContent = filters.contains("CONTENT_PRIVATE");
224            boolean filterPrivatePage = filters.contains("PAGE_PRIVATE");
225            
226            boolean isContent = cmsTag.getTarget().getName().equals("CONTENT");
227            boolean isPage = cmsTag.getTarget().getName().equals("PAGE");
228            boolean isPrivate = cmsTag.getVisibility() == TagVisibility.PRIVATE;
229            
230            boolean targetTypeOK = StringUtils.isEmpty(targetType) || cmsTag.getTarget().getName().equals(targetType);
231            boolean typeContentOK = targetTypeOK || (isContent && !(filterContent || filterPrivateContent));
232            boolean typePageOK = targetTypeOK || (isPage && !(filterPage || filterPrivatePage));
233            boolean visibilityOK = !(isPrivate && (filterPrivateContent || filterPrivatePage));
234            
235            return typeContentOK && typePageOK && visibilityOK;
236//            return !((filterContent && isContent)
237//                    || (filterPage && isPage)
238//                    || (filterPrivateContent && isContent && isPrivate)
239//                    || (filterPrivatePage && isPage && isPrivate));
240        }
241        return true;
242    }
243    
244    /**
245     * Iterate over all sub-tags for a tag and list them with their full name
246     * @param tag tag to iterate over
247     * @param rootTag needed to display the hierarchy in the full name
248     * @param filters list of filters to disable, can contain PAGE_PRIVATE, CONTENT_PRIVATE, PAGE, CONTENT
249     * @param targetType tag target type
250     * @param showCategories true to display tags containing other tags. If false, only the non-parents tags will be listed
251     * @param separator separator between the parents (e.g. " &gt; " to have "grandParent &gt; parent &gt; page")
252     * @return a list with one item per requested tagName, each one containing name (String), title (String) and tags (List of Map&lt;String, String&gt;, each map containing a name and title)
253     */
254    private List<Map<String, Object>> getSubTags(Tag tag, Tag rootTag, List<String> filters, String targetType, boolean showCategories, String separator)
255    {
256        List<Map<String, Object>> subTags = new ArrayList<>();
257        Map<String, ? extends Tag> tags = tag.getTags();
258        
259        for (Entry<String, ? extends Tag> entry : tags.entrySet())
260        {
261            Tag subTag = entry.getValue();
262            if (!isTagAvailable(subTag, filters, targetType))
263            {
264                break;
265            }
266            Map<String, Object> subTagMap = parseTag(subTag, rootTag, separator);
267            if (showCategories || subTag.getTags().isEmpty())
268            {
269                subTags.add(subTagMap);
270            }
271            subTags.addAll(getSubTags(subTag, rootTag, filters, targetType, showCategories, separator));
272        }
273        return subTags;
274    }
275    private Map<String, Object> parseTag(Tag tag, Tag rootTag, String separator)
276    {
277        Map<String, Object> result = new HashMap<>();
278        
279        result.put("name", tag.getName());
280        result.put("title", getTagFullName(tag, rootTag, separator));
281        
282        return result;
283    }
284    
285    /**
286     * Get the formatted tag name for a tag, with it's parents names
287     * @param tag tag to find the name
288     * @param rootTag root tag to display full name until this tag
289     * @param separator separator between the parents (e.g. " &gt; " to have "grandParent &gt; parent &gt; child")
290     * @return a formatted name
291     */
292    protected String getTagFullName(Tag tag, Tag rootTag, String separator)
293    {
294        String title = _i18nUtils.translate(tag.getTitle());
295        Tag parent = tag.getParent();
296        String rootTagId = rootTag.getId();
297        
298        while (parent != null && !parent.getId().equals(rootTagId))
299        {
300            String tagTitle = _i18nUtils.translate(parent.getTitle());
301            title = tagTitle + separator + title;
302            parent = parent.getParent();
303        }
304        return title;
305    }
306}