001/* 002 * Copyright 2018 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.linkdirectory; 017 018import java.util.Collection; 019import java.util.List; 020import java.util.Map; 021import java.util.stream.Collectors; 022 023import org.apache.avalon.framework.configuration.Configurable; 024import org.apache.avalon.framework.configuration.Configuration; 025import org.apache.avalon.framework.configuration.ConfigurationException; 026import org.apache.avalon.framework.context.Context; 027import org.apache.avalon.framework.context.ContextException; 028import org.apache.avalon.framework.context.Contextualizable; 029import org.apache.avalon.framework.logger.AbstractLogEnabled; 030import org.apache.avalon.framework.service.ServiceException; 031import org.apache.avalon.framework.service.ServiceManager; 032import org.apache.avalon.framework.service.Serviceable; 033import org.apache.cocoon.ProcessingException; 034import org.apache.cocoon.components.ContextHelper; 035import org.apache.cocoon.environment.Request; 036import org.apache.cocoon.xml.AttributesImpl; 037import org.apache.cocoon.xml.XMLUtils; 038import org.apache.commons.lang3.StringUtils; 039import org.xml.sax.ContentHandler; 040import org.xml.sax.SAXException; 041 042import org.ametys.core.user.CurrentUserProvider; 043import org.ametys.core.user.UserIdentity; 044import org.ametys.plugins.linkdirectory.LinkDirectoryThemesInputDataHelper.ConfiguredThemesInputData; 045import org.ametys.plugins.linkdirectory.LinkDirectoryThemesInputDataHelper.ThemesInputData; 046import org.ametys.plugins.linkdirectory.repository.DefaultLink; 047import org.ametys.web.inputdata.InputData; 048import org.ametys.web.repository.page.Page; 049import org.ametys.web.repository.site.Site; 050 051/** 052 * Input data for the link directory user preferences in thumbnails mode 053 */ 054public class LinkDirectoryInputData extends AbstractLogEnabled implements Contextualizable, InputData, Serviceable, Configurable 055{ 056 /** The current user provider */ 057 protected CurrentUserProvider _currentUserProvider; 058 059 /** The link directory themes input data helper */ 060 protected LinkDirectoryThemesInputDataHelper _linkDirectoryThemesInputDataHelper; 061 062 /** The Avalon context */ 063 private Context _context; 064 065 private DirectoryHelper _directoryHelper; 066 067 private String _confFilePath; 068 069 @Override 070 public void contextualize(Context context) throws ContextException 071 { 072 _context = context; 073 } 074 075 @Override 076 public void service(ServiceManager manager) throws ServiceException 077 { 078 _linkDirectoryThemesInputDataHelper = (LinkDirectoryThemesInputDataHelper) manager.lookup(LinkDirectoryThemesInputDataHelper.ROLE); 079 _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE); 080 _directoryHelper = (DirectoryHelper) manager.lookup(DirectoryHelper.ROLE); 081 } 082 083 public void configure(Configuration configuration) throws ConfigurationException 084 { 085 _confFilePath = configuration.getChild("config", true).getAttribute("file", DirectoryHelper.DEFAULT_CONF_FILE_PATH); 086 } 087 088 @Override 089 public boolean isCacheable(Site site, Page currentPage) 090 { 091 Request request = ContextHelper.getRequest(_context); 092 093 String template = _getTemplate(request); 094 if (template == null) 095 { 096 return true; 097 } 098 099 try 100 { 101 ConfiguredThemesInputData configuredThemesInputData = _linkDirectoryThemesInputDataHelper.getThemesInputData(site.getSkinId(), _confFilePath); 102 List<ThemesInputData> themesInputData = _getThemesForSkinAndTemplate(configuredThemesInputData, template); 103 if (themesInputData.isEmpty()) 104 { 105 // The current template is not configured for a link directory input data 106 return true; 107 } 108 109 for (ThemesInputData themeInputData : themesInputData) 110 { 111 if (themeInputData.configurable() || themeInputData.displayUserLinks()) 112 { 113 // The applications are configurable 114 return false; 115 } 116 } 117 118 // Find the configured theme ids for this template 119 String language = _directoryHelper.getLanguage(request); 120 List<String> configuredThemesNames = themesInputData.stream() 121 .map(inputData -> _getConfiguredThemes(inputData, language)) 122 .flatMap(Collection::stream) 123 .collect(Collectors.toList()); 124 String siteName = _directoryHelper.getSiteName(request); 125 126 return !_directoryHelper.hasRestrictions(siteName, language, configuredThemesNames) && !_directoryHelper.hasInternalUrl(siteName, language, configuredThemesNames); 127 } 128 catch (Exception e) 129 { 130 getLogger().error("An error occurred while retrieving information from the skin configuration", e); 131 // Configuration file is not readable => toSAX method will not generate any xml 132 return true; 133 } 134 } 135 136 @Override 137 public void toSAX(ContentHandler contentHandler) throws ProcessingException 138 { 139 Request request = ContextHelper.getRequest(_context); 140 141 // Get the current user's login if he is in the front office 142 UserIdentity user = _currentUserProvider.getUser(); 143 144 String template = _getTemplate(request); 145 if (template == null) 146 { 147 getLogger().info("There is no current template"); 148 return; 149 } 150 151 String skinId = _getSkin(request); 152 if (skinId == null) 153 { 154 getLogger().info("There is no current skin"); 155 return; 156 } 157 158 try 159 { 160 contentHandler.startDocument(); 161 162 ConfiguredThemesInputData configuredThemesInputData = _linkDirectoryThemesInputDataHelper.getThemesInputData(skinId, _confFilePath); 163 List<ThemesInputData> themesInputDatas = _getThemesForSkinAndTemplate(configuredThemesInputData, template); 164 165 // Is there an error in the configuration file ? 166 String error = configuredThemesInputData.error(); 167 if (StringUtils.isNotBlank(error)) 168 { 169 AttributesImpl attrs = new AttributesImpl(); 170 attrs.addCDATAAttribute("error", error); 171 XMLUtils.createElement(contentHandler, "linkDirectory", attrs); 172 } 173 else 174 { 175 String language = _directoryHelper.getLanguage(request); 176 String siteName = _directoryHelper.getSiteName(request); 177 for (ThemesInputData themesInputData : themesInputDatas) 178 { 179 if (!themesInputData.excludeAnonymous() || _currentUserProvider.getUser() != null) 180 { 181 AttributesImpl attrs = new AttributesImpl(); 182 attrs.addCDATAAttribute("applicable", Boolean.TRUE.toString()); 183 attrs.addCDATAAttribute("configurable", String.valueOf(themesInputData.configurable())); 184 attrs.addCDATAAttribute("displayUserLinks", String.valueOf(themesInputData.displayUserLinks())); 185 attrs.addCDATAAttribute("id", themesInputData.id()); 186 187 XMLUtils.startElement(contentHandler, "linkDirectory", attrs); 188 189 List<String> configuredThemesNames = _getConfiguredThemes(themesInputData, language); 190 if (configuredThemesNames != null) 191 { 192 Map<String, List<String>> themesMap = _directoryHelper.getThemesMap(configuredThemesNames, siteName, language); 193 List<String> correctThemesIds = themesMap.get("themes"); 194 List<String> unknownThemesNames = themesMap.get("unknown-themes"); 195 196 _saxThemes(contentHandler, correctThemesIds, unknownThemesNames); 197 _saxLinks(contentHandler, user, request, correctThemesIds, themesInputData.displayUserLinks(), themesInputData.configurable(), themesInputData.id()); 198 } 199 XMLUtils.endElement(contentHandler, "linkDirectory"); 200 } 201 } 202 } 203 } 204 catch (Exception e) 205 { 206 getLogger().error("An exception occurred during the processing of the link directory's input data" , e); 207 } 208 } 209 210 private void _saxThemes(ContentHandler contentHandler, List<String> themeIds, List<String> unknownThemesNames) throws SAXException 211 { 212 if (!themeIds.isEmpty()) 213 { 214 XMLUtils.startElement(contentHandler, "themes"); 215 for (String themeId : themeIds) 216 { 217 XMLUtils.createElement(contentHandler, "theme", themeId); 218 } 219 XMLUtils.endElement(contentHandler, "themes"); 220 } 221 222 if (!unknownThemesNames.isEmpty()) 223 { 224 AttributesImpl attr = new AttributesImpl(); 225 attr.addCDATAAttribute("count", Integer.toString(unknownThemesNames.size())); 226 XMLUtils.createElement(contentHandler, "unknown-themes", attr, StringUtils.join(unknownThemesNames, ", ")); 227 } 228 } 229 230 private void _saxLinks(ContentHandler contentHandler, UserIdentity user, Request request, List<String> themeIds, boolean displayUserLinks, boolean configurable, String specificContext) throws ProcessingException 231 { 232 String language = _directoryHelper.getLanguage(request); 233 String siteName = _directoryHelper.getSiteName(request); 234 try 235 { 236 // SAX common links 237 List<DefaultLink> links = _directoryHelper.getLinks(themeIds, siteName, language); 238 239 List<DefaultLink> userLinks = null; 240 if (user != null && displayUserLinks) 241 { 242 userLinks = _directoryHelper.getUserLinks(siteName, language, user).stream().collect(Collectors.toList()); 243 } 244 245 246 // SAX the user own links 247 XMLUtils.startElement(contentHandler, "links"); 248 249 try 250 { 251 String storageContext = siteName + "/" + language; 252 if (StringUtils.isNotEmpty(specificContext)) 253 { 254 storageContext += "/" + specificContext; 255 } 256 _directoryHelper.saxLinks(siteName, contentHandler, links, userLinks, themeIds, configurable, _directoryHelper.getContextVars(request), storageContext, user); 257 } 258 catch (Exception e) 259 { 260 getLogger().error("An exception occurred while saxing the links", e); 261 } 262 263 XMLUtils.endElement(contentHandler, "links"); 264 } 265 catch (Exception e) 266 { 267 throw new ProcessingException("An error occurred while retrieving or saxing the links", e); 268 } 269 } 270 271 /** 272 * Retrieve the configured themes names defined in the skin file link-directory.xml for the current input data and the current language 273 * @param themesInputData Can be an empty {@link String} 274 * @param lang language to filter by. Themes with lang=null will always be returned. 275 * @return the list of configured themes ids, can be empty, cannot be null 276 */ 277 private List<String> _getConfiguredThemes(ThemesInputData themesInputData, String lang) 278 { 279 return themesInputData.themes() 280 .stream() 281 .filter(t -> t.get("lang") == null || t.get("lang").equals(lang)) 282 .map(t -> t.get("id")) 283 .collect(Collectors.toList()); 284 } 285 286 private List<ThemesInputData> _getThemesForSkinAndTemplate(ConfiguredThemesInputData configuredThemesInputData, String template) 287 { 288 List<ThemesInputData> themesInputDatas = configuredThemesInputData.themesInputDatas(); 289 if (themesInputDatas == null) 290 { 291 return List.of(); 292 } 293 294 return themesInputDatas.stream() 295 .filter(t -> _filterByTemplate(t, template)) 296 .collect(Collectors.toList()); 297 } 298 299 private boolean _filterByTemplate(ThemesInputData theme, String template) 300 { 301 List<String> templates = theme.templates(); 302 return templates.contains(template) || templates.contains(LinkDirectoryThemesInputDataHelper.WILDCARD); 303 } 304 305 /** 306 * Get the current template 307 * @param request the request 308 * @return the current template 309 */ 310 private String _getTemplate(Request request) 311 { 312 return (String) request.getAttribute("template"); 313 } 314 315 /** 316 * Get the current skin 317 * @param request the request 318 * @return the current skin 319 */ 320 private String _getSkin(Request request) 321 { 322 return (String) request.getAttribute("skin"); 323 } 324}