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