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}