001/*
002 *  Copyright 2020 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.theme;
017
018import java.io.InputStream;
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024
025import org.apache.avalon.framework.configuration.Configuration;
026import org.apache.avalon.framework.configuration.ConfigurationException;
027import org.apache.avalon.framework.service.ServiceException;
028import org.apache.avalon.framework.service.ServiceManager;
029import org.apache.avalon.framework.service.Serviceable;
030import org.apache.excalibur.source.Source;
031import org.apache.excalibur.source.SourceResolver;
032
033import org.ametys.cms.tag.AbstractTagProvider;
034import org.ametys.cms.tag.DefaultTag;
035import org.ametys.cms.tag.Tag;
036import org.ametys.runtime.i18n.I18nizableText;
037import org.ametys.web.repository.site.Site;
038import org.ametys.web.repository.site.SiteManager;
039import org.ametys.web.skin.Skin;
040import org.ametys.web.skin.SkinConfigurationHelper;
041import org.ametys.web.skin.SkinsManager;
042
043/**
044 * This class represents the themes provided by the skin
045 */
046public class SkinThemeProvider extends AbstractTagProvider<DefaultTag> implements Serviceable
047{
048    /** The source resolver */
049    protected SourceResolver _resolver;
050    
051    /** The tags */
052    protected Map<String, List<String>> _skinLocalIds;
053    /** The tags */
054    protected Map<String, Map<String, DefaultTag>> _skinTags;
055    
056    private SiteManager _siteManager;
057    private SkinsManager _skinsManager;
058
059    private SkinConfigurationHelper _skinConfigurationHelper;
060    
061    @Override
062    public void service(ServiceManager smanager) throws ServiceException
063    {
064        _resolver = (SourceResolver) smanager.lookup(SourceResolver.ROLE);
065        _siteManager = (SiteManager) smanager.lookup(SiteManager.ROLE);
066        _skinsManager = (SkinsManager) smanager.lookup(SkinsManager.ROLE);
067        _skinConfigurationHelper = (SkinConfigurationHelper) smanager.lookup(SkinConfigurationHelper.ROLE);
068    }
069    
070    @Override
071    public void configure(Configuration configuration) throws ConfigurationException
072    {
073        _skinLocalIds = new HashMap<>();
074        
075        _id = configuration.getAttribute("id");
076        _label = configureLabel(configuration, "plugin." + _pluginName);
077        _description = configureDescription(configuration, "plugin." + _pluginName);
078        
079        if (_skinTags == null)
080        {
081            _skinTags = new HashMap<>();
082        }
083        
084        for (String skinName : _skinsManager.getSkins())
085        {
086            try
087            {
088                initializeTags(skinName);
089            }
090            catch (Exception e)
091            {
092                throw new ConfigurationException("Unable to load tags configuration values for skin " + skinName, e);
093            }
094        }
095    }
096    
097    @Override
098    public boolean hasTag(String tagID, Map<String, Object> contextualParameters)
099    {
100        return getTag(tagID, contextualParameters) != null;
101    }
102    
103    @Override
104    public DefaultTag getTag(String tagID, Map<String, Object> contextualParameters)
105    {
106        Map<String, DefaultTag> tags = getTags(contextualParameters);
107        return tags != null ? _recursiveSearchTags(tags, tagID) : null;
108    }
109
110    @Override
111    public Collection<DefaultTag> getTags(String tagID, Map<String, Object> contextualParameters)
112    {
113        DefaultTag tag = getTag(tagID, contextualParameters);
114        return tag != null ? tag.getTags().values() : null;
115    }
116    
117    private DefaultTag _recursiveSearchTags(Map<String, DefaultTag> tags, String tagID)
118    {
119        if (tags.containsKey(tagID))
120        {
121            return tags.get(tagID);
122        }
123        
124        for (DefaultTag child : tags.values())
125        {
126            DefaultTag tag = _recursiveSearchTags(child.getTags(), tagID);
127            if (tag != null)
128            {
129                return tag;
130            }
131        }
132        
133        return null;
134    }
135    
136    @Override
137    public Map<String, DefaultTag> getTags(Map<String, Object> contextualParameters)
138    {
139        if (contextualParameters.get("siteName") == null)
140        {
141            // contextualParameters#containsKey not sufficient here because the in some case the siteName key can be set to null
142            return null;
143        }
144        
145        Site site = _siteManager.getSite((String) contextualParameters.get("siteName"));
146        
147        if (site == null)
148        {
149            String errorMessage = "Unable to load tags configuration values for site " + (String) contextualParameters.get("siteName");
150            getLogger().error(errorMessage);
151            return null;
152        }
153        
154        String skin = site.getSkinId();
155        
156        if (!_skinTags.containsKey(skin))
157        {
158            try
159            {
160                initializeTags(skin);
161            }
162            catch (Exception e)
163            {
164                String errorMessage = "Unable to load tags configuration values for skin " + skin;
165                getLogger().error(errorMessage, e);
166            }
167        }
168        
169        return _skinTags.get(skin);
170    }
171    
172    /**
173     * Initialize a skin's themes from the themes file.
174     * @param skinName the name of the skin to initialize themes.
175     * @throws Exception if an error occurs.
176     */
177    protected void initializeTags(String skinName) throws Exception
178    {
179        // Obsolete location
180        Source otherSrc = null;
181        try
182        {
183            otherSrc = _resolver.resolveURI("skin:" + skinName + "://conf/link-themes.xml");
184            if (otherSrc.exists())
185            {
186                getLogger().error("In skin '" + skinName + "' (or one of its parent skin) the 'conf/link-themes.xml' file location is obsolete AND NOT SUPPORTED ANYMORE. Move the file conf/link-themes.xml to conf/link-directory.xml");
187            }
188        }
189        finally 
190        {
191            _resolver.release(otherSrc);
192        }
193        
194        
195        // Right inheritable location
196        Skin skin = _skinsManager.getSkin(skinName);
197        try (InputStream xslIs = getClass().getResourceAsStream("link-directory-merge.xsl"))
198        {
199            Configuration configuration = _skinConfigurationHelper.getInheritanceMergedConfiguration(skin, "conf/link-directory.xml", xslIs);
200            
201            if (!_skinLocalIds.containsKey(skinName))
202            {
203                _skinLocalIds.put(skinName, new ArrayList<String>());
204            }
205            
206            Map<String, DefaultTag> tags = configureTags(configuration, skinName, null, "skin." + skinName);
207            _skinTags.put(skinName, tags);
208        }
209    }
210    
211    /**
212     * Configure themes from the passed configuration
213     * @param configuration The configuration
214     * @param skinName the skin name
215     * @param parent The parent theme if any (as recursion is disabled, should be null)
216     * @param defaultCatalogue The default catalog for i18n
217     * @return a Set of {@link DefaultTag}
218     * @throws ConfigurationException if configuration is invalid
219     */
220    protected Map<String, DefaultTag> configureTags (Configuration configuration, String skinName, DefaultTag parent, String defaultCatalogue)  throws ConfigurationException
221    {
222        Map<String, DefaultTag> themes = new HashMap<>();
223        
224        Configuration[] tagsConfiguration = configuration.getChild("definitions").getChildren("theme");
225        for (Configuration tagConfiguration : tagsConfiguration)
226        {
227            String id = tagConfiguration.getAttribute("id", null);
228            if (id == null)
229            {
230                getLogger().error("Missing attributed named \"id\" for theme configuration at " + tagConfiguration.getLocation() + ". Theme is ignored.");
231            }
232            else if (!Tag.NAME_PATTERN.matcher(id).matches())
233            {
234                getLogger().error("Invalid tag ID \"" + id + "\" for theme configuration at " + tagConfiguration.getLocation() + ". It must match the pattern " + Tag.NAME_PATTERN.toString() + ". Theme is ignored.");
235            }
236            else if (_skinLocalIds.get(skinName).contains(id))
237            {
238                getLogger().error("A tag with the ID \"" + id + "\" for theme configuration at " + tagConfiguration.getLocation() + " already exists. Theme is ignored.");
239            }
240            else
241            {
242                _skinLocalIds.get(skinName).add(id);
243                
244                I18nizableText label = configureLabel (tagConfiguration, defaultCatalogue);
245                I18nizableText description = configureDescription (tagConfiguration, defaultCatalogue);
246                DefaultTag theme = new DefaultTag(id, id, parent, label, description);
247                
248                // No recursivity for themes, if it become a thing, uncomment this :
249//                Map<String, DefaultTag> childTags = configureTags(tagConfiguration, skinName, theme, defaultCatalogue);
250//                theme.setTags(childTags);
251                themes.put(id, theme);
252            }
253            
254        }
255        
256        return themes;
257    }
258}