001/*
002 *  Copyright 2022 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.plugins.calendar.search;
017
018import java.util.Collection;
019import java.util.Collections;
020import java.util.HashMap;
021import java.util.HashSet;
022import java.util.LinkedHashSet;
023import java.util.List;
024import java.util.Map;
025import java.util.Objects;
026import java.util.Set;
027import java.util.stream.Collectors;
028
029import org.apache.avalon.framework.parameters.Parameters;
030import org.apache.avalon.framework.service.ServiceException;
031import org.apache.avalon.framework.service.ServiceManager;
032import org.apache.cocoon.xml.AttributesImpl;
033import org.apache.cocoon.xml.XMLUtils;
034import org.apache.commons.lang3.StringUtils;
035import org.xml.sax.ContentHandler;
036import org.xml.sax.SAXException;
037
038import org.ametys.cms.search.advanced.AbstractTreeNode;
039import org.ametys.cms.search.advanced.TreeLeaf;
040import org.ametys.cms.tag.CMSTag;
041import org.ametys.cms.tag.Tag;
042import org.ametys.cms.tag.TagProviderExtensionPoint;
043import org.ametys.core.user.CurrentUserProvider;
044import org.ametys.core.user.UserIdentity;
045import org.ametys.core.userpref.UserPreferencesException;
046import org.ametys.core.userpref.UserPreferencesManager;
047import org.ametys.core.util.JSONUtils;
048import org.ametys.plugins.calendar.icsreader.IcsEventHelper;
049import org.ametys.web.frontoffice.search.instance.SearchServiceInstance;
050import org.ametys.web.frontoffice.search.instance.model.FOSearchCriterion;
051import org.ametys.web.frontoffice.search.requesttime.SearchComponent;
052import org.ametys.web.frontoffice.search.requesttime.SearchComponentArguments;
053import org.ametys.web.frontoffice.search.requesttime.impl.SaxFormSearchComponent;
054import org.ametys.web.frontoffice.search.requesttime.impl.SearchComponentHelper;
055import org.ametys.web.frontoffice.search.requesttime.input.SearchUserInputs;
056import org.ametys.web.repository.page.ZoneItem;
057
058/**
059 * {@link SearchComponent} for saxing form for calendar search service
060 * Reactivate default {@link SaxFormSearchComponent} (disabled by "disableDefaultSax" parameter)
061 */
062public class CalendarSaxFormSearchComponent extends SaxFormSearchComponent
063{
064    /** Helper to get ICS events */
065    protected IcsEventHelper _icsEventHelper;
066    /** The tags provider */
067    protected TagProviderExtensionPoint _tagProviderEP;
068    /** The JSON utils */
069    protected JSONUtils _jsonUtils;
070    /** The user preferences manager */
071    protected UserPreferencesManager _userPrefsManager;
072    /** The current user provider */
073    protected CurrentUserProvider _currentUserProvider;
074    
075    
076    @Override
077    public void service(ServiceManager smanager) throws ServiceException
078    {
079        super.service(smanager);
080        _icsEventHelper = (IcsEventHelper) smanager.lookup(IcsEventHelper.ROLE);
081        _tagProviderEP = (TagProviderExtensionPoint) smanager.lookup(TagProviderExtensionPoint.ROLE);
082        _currentUserProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE);
083        _userPrefsManager = (UserPreferencesManager) smanager.lookup(UserPreferencesManager.ROLE);
084        _jsonUtils = (JSONUtils) smanager.lookup(JSONUtils.ROLE);
085    }
086    
087    @Override
088    public boolean supports(SearchComponentArguments args)
089    {
090        return CalendarSearchService.isActive(args);
091    }
092    
093    @Override
094    public void execute(SearchComponentArguments args) throws Exception
095    {
096        ContentHandler contentHandler = args.contentHandler();
097        SearchServiceInstance serviceInstance = args.serviceInstance();
098        List<FOSearchCriterion> nonStaticCriteria = serviceInstance.getCriterionTree()
099                .map(AbstractTreeNode::getFlatLeaves)
100                .orElseGet(Collections::emptyList)
101                .stream()
102                .map(TreeLeaf::getValue)
103                .filter(c -> !c.getMode().isStatic())
104                .collect(Collectors.toList());
105        Parameters parameters = args.generatorParameters();
106        
107        XMLUtils.startElement(contentHandler, "form");
108        
109        XMLUtils.startElement(contentHandler, "fields");
110        Map<String, Object> contextualParameters = SearchComponentHelper.getSearchComponentContextualParameters(args);
111        saxFormFields(contentHandler, parameters, nonStaticCriteria, contextualParameters, serviceInstance.computeCriteriaCounts());
112        XMLUtils.endElement(contentHandler, "fields");
113        
114        SearchUserInputs userInputs = args.userInputs();
115        
116        Map<String, Object> userCriteria = userInputs.criteria();
117        XMLUtils.startElement(contentHandler, "values");
118        saxFormValues(contentHandler, parameters, nonStaticCriteria, userCriteria);
119        XMLUtils.endElement(contentHandler, "values");
120        
121        String zoneItemId = args.serviceInstance().getId();
122        ZoneItem zoneItem = (ZoneItem) _resolver.resolveById(zoneItemId);
123        
124        if (CalendarSearchService.hasTagCategories(args.serviceInstance()))
125        {
126            List<String> categoryNames = CalendarSearchService.getTagCategories(serviceInstance);
127            Set<Tag> tagCategories = getTagCategories(categoryNames, args);
128            tagCategories.addAll(_icsEventHelper.getIcsTags(zoneItem, args.currentSite().getName()));
129            saxTagFacet(contentHandler, tagCategories, args);
130        }
131        XMLUtils.endElement(contentHandler, "form");
132    }
133    
134    /**
135     * Get the configured tag categories
136     * @param categoryNames The parent tag category
137     * @param args the search arguments
138     * @return the tags
139     */
140    protected Set<Tag> getTagCategories(List<String> categoryNames, SearchComponentArguments args)
141    {
142        Set<Tag> tags = new HashSet<>();
143        
144        if (categoryNames != null && !categoryNames.isEmpty())
145        {
146            Map<String, Object> params = Map.of("siteName", args.currentSite().getName());
147            
148            Set<CMSTag> tagCategories = categoryNames.stream()
149                    .map(c -> _tagProviderEP.getTag(c, params))
150                    .filter(Objects::nonNull)
151                    .collect(Collectors.toSet());
152            
153            for (CMSTag cmsTag : tagCategories)
154            {
155                tags.addAll(_getChildTags(cmsTag));
156            }
157        }
158        
159        return tags;
160    }
161    
162    private Set<Tag> _getChildTags(Tag tag)
163    {
164        Set<Tag> allTags = new LinkedHashSet<>();
165
166        Map<String, ? extends Tag> childTagsMap = tag.getTags();
167
168        if (childTagsMap != null)
169        {
170            Collection<? extends Tag> childTags = childTagsMap.values();
171            allTags.addAll(childTags);
172            
173            for (Tag child : childTags)
174            {
175                allTags.addAll(_getChildTags(child));
176            }
177        }
178
179        return allTags;
180    }
181    
182    /**
183     * Generate the list of tags
184     * @param handler the content handler
185     * @param tags the list of tags
186     * @param args the search arguments
187     * @throws SAXException if an error occurs while saxing
188     */
189    protected void saxTagFacet(ContentHandler handler, Collection<Tag> tags, SearchComponentArguments args) throws SAXException
190    {
191        Set<String> selectedTags = new HashSet<>(CalendarSearchService.getSelectedTags(args));
192        
193        if (CalendarSearchService.saveUserPrefsEnabled(args) && CalendarSearchService.saveFilterInUserPrefs(args.serviceInstance()))
194        {
195            List<String> userTags = _getCurrentSearchUserTagsFacet(args.serviceInstance().getId());
196            selectedTags.addAll(userTags);
197        }
198        
199        XMLUtils.startElement(handler, "tag-facet");
200        for (Tag tag : tags)
201        {
202            AttributesImpl attrs = new AttributesImpl();
203            attrs.addCDATAAttribute("selected",  String.valueOf(selectedTags.contains(tag.getName())));
204            CalendarSearchService.saxTag(handler, attrs, tag);
205        }
206        XMLUtils.endElement(handler, "tag-facet");
207    }
208    
209    /**
210     * Get the current search user tags for the given service.
211     * @param serviceId The service ID
212     * @return the current user tags if the user is authenticated and have saved filters for this service. Otherwise, it returns <code>null</code>.
213     */
214    protected List<String> _getCurrentSearchUserTagsFacet(String serviceId)
215    {
216        try
217        {
218            UserIdentity user = _currentUserProvider.getUser();
219            if (user != null)
220            {
221                Map<String, String> userPreferences = _userPrefsManager.getUnTypedUserPrefs(user, "calendar-search-" + serviceId, new HashMap<>());
222
223                if (!userPreferences.isEmpty())
224                {
225                    return _jsonUtils.convertJsonToList(userPreferences.get("search.calendar.tags")).stream()
226                        .filter(String.class::isInstance)
227                        .map(String.class::cast)
228                        .filter(StringUtils::isNotBlank)
229                        .collect(Collectors.toList());
230                }
231            }
232            else
233            {
234                getLogger().debug("There is no authenticated user.");
235            }
236        }
237        catch (UserPreferencesException e)
238        {
239            getLogger().error("An error occured while getting the user preferences.", e);
240        }
241        
242        return List.of();
243    }
244    
245}