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}