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