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