001/*
002 *  Copyright 2010 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.newsletter.category;
017
018import java.io.IOException;
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.HashMap;
022import java.util.HashSet;
023import java.util.List;
024import java.util.Map;
025import java.util.Set;
026
027import org.apache.avalon.framework.configuration.Configuration;
028import org.apache.avalon.framework.configuration.ConfigurationException;
029import org.apache.avalon.framework.configuration.DefaultConfiguration;
030import org.apache.avalon.framework.context.Context;
031import org.apache.avalon.framework.context.ContextException;
032import org.apache.avalon.framework.context.Contextualizable;
033import org.apache.avalon.framework.service.ServiceException;
034import org.apache.avalon.framework.service.ServiceManager;
035import org.apache.cocoon.components.ContextHelper;
036import org.apache.cocoon.components.LifecycleHelper;
037import org.apache.cocoon.environment.Request;
038import org.apache.cocoon.util.log.SLF4JLoggerAdapter;
039import org.apache.excalibur.source.SourceNotFoundException;
040import org.apache.excalibur.source.SourceResolver;
041import org.apache.excalibur.source.TraversableSource;
042import org.slf4j.LoggerFactory;
043
044import org.ametys.core.ui.Callable;
045import org.ametys.core.ui.ClientSideElement;
046import org.ametys.core.ui.SimpleMenu;
047import org.ametys.core.ui.StaticClientSideElement;
048import org.ametys.core.util.I18nUtils;
049import org.ametys.plugins.repository.AmetysObjectResolver;
050import org.ametys.runtime.i18n.I18nizableText;
051import org.ametys.runtime.plugin.component.ThreadSafeComponentManager;
052import org.ametys.web.repository.site.Site;
053import org.ametys.web.repository.site.SiteManager;
054import org.ametys.web.skin.Skin;
055import org.ametys.web.skin.SkinsManager;
056
057/**
058 * This element finally creates a gallery button with one item per template  
059 */
060public class TemplatesMenu extends SimpleMenu implements Contextualizable
061{
062    /** The plugin in which are the templates resources (messages and icons). */
063    protected static final String _RESOURCES_PLUGIN = "newsletter";
064    
065    private static final String GALLERY_ITEM_MANAGER = TemplatesMenu.class.getName() + "$GalleryItemManager";
066    private static final String GALLERY_ITEMS = TemplatesMenu.class.getName() + "$GalleryItems";
067    
068    /** The skins manager */
069    protected SkinsManager _skinsManager;
070    /** The site manager */
071    protected SiteManager _siteManager;
072    /** The source resolver */
073    protected SourceResolver _sourceResolver;
074    /** The service manager */
075    protected ServiceManager _manager;
076    /** The i18n utils */
077    protected I18nUtils _i18nUtils;
078    /** The Ametys object resolver */
079    protected AmetysObjectResolver _resolver;
080    
081    private Context _context;
082    
083    @Override
084    public void service(ServiceManager smanager) throws ServiceException
085    {
086        super.service(smanager);
087        _manager = smanager;
088        _skinsManager = (SkinsManager) smanager.lookup(SkinsManager.ROLE);
089        _siteManager = (SiteManager) smanager.lookup(SiteManager.ROLE);
090        _sourceResolver = (SourceResolver) smanager.lookup(SourceResolver.ROLE);
091        _i18nUtils = (I18nUtils) smanager.lookup(I18nUtils.ROLE);
092        _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE);
093    }
094    
095    public void contextualize(Context context) throws ContextException
096    {
097        _context = context;
098    }
099    
100    /**
101     * Get the available templates for pages
102     * @param categoryIds The ids of categories
103     * @return the list of templates' name
104     */
105    @Callable
106    public Map<String, Object> getStatus (List<String> categoryIds)
107    {
108        Map<String, Object> result = new HashMap<>();
109        
110        result.put("categories", new ArrayList<Map<String, Object>>());
111        
112        for (String categoryId : categoryIds)
113        {
114            JCRCategory category = _resolver.resolveById(categoryId);
115            
116            // Category parameters
117            Map<String, Object> categoryParams = getCategoryDefaultParameters(category);
118            categoryParams.put("template", category.getTemplate());
119            
120            @SuppressWarnings("unchecked")
121            List<Map<String, Object>> categories = (List<Map<String, Object>>) result.get("categories");
122            categories.add(categoryParams);
123        }
124        
125        return result;
126    }
127    
128    /**
129     * Get the default category's parameters
130     * @param category The category
131     * @return The default parameters
132     */
133    protected Map<String, Object> getCategoryDefaultParameters (JCRCategory category)
134    {
135        Map<String, Object> categoryParams = new HashMap<>();
136        categoryParams.put("id", category.getId());
137        categoryParams.put("title", category.getTitle());
138        
139        return categoryParams;
140    }
141    
142    @Override
143    public List<Script> getScripts(boolean ignoreRights, Map<String, Object> contextParameters)
144    {
145        String siteName = (String) contextParameters.get("siteName");
146        
147        String skinId = _siteManager.getSite(siteName).getSkinId();
148        Skin skin = _skinsManager.getSkin(skinId);
149        
150        if (skin.getTemplates().size() == 0)
151        {
152            // Hide menu if there is no template
153            return new ArrayList<>();
154        }
155            
156        try
157        {
158            _lazyInitializeTemplateGallery(contextParameters);
159            
160            return super.getScripts(ignoreRights, contextParameters);
161        }
162        catch (Exception e)
163        {
164            throw new IllegalStateException("Unable to lookup client side element local components", e);
165        }
166        finally
167        {
168            _getGalleryItemManager().dispose();
169        }
170    }
171
172    /**
173     * Get templates of newletter categories
174     * @param categoryIds the ids of categories
175     * @return The list of used templates
176     */
177    @Callable
178    public List<String> getTemplate (List<String> categoryIds)
179    {
180        List<String> templates = new ArrayList<>();
181        
182        for (String id : categoryIds)
183        {
184            Category category = _resolver.resolveById(id);
185            
186            if (!templates.contains(category.getTemplate()))
187            {
188                templates.add(category.getTemplate());
189            }
190        }
191        return templates;
192    }
193    
194    @SuppressWarnings("unchecked")
195    @Override
196    protected ThreadSafeComponentManager<ClientSideElement> _getGalleryItemManager()
197    {
198        Request request = ContextHelper.getRequest(_context);
199        return (ThreadSafeComponentManager<ClientSideElement>) request.getAttribute(GALLERY_ITEM_MANAGER);
200    }
201    
202    @SuppressWarnings("unchecked")
203    @Override
204    protected List<GalleryItem> _getGalleryItems()
205    {
206        Request request = ContextHelper.getRequest(_context);
207        return (List<GalleryItem>) request.getAttribute(GALLERY_ITEMS);
208    }
209    
210    private void _lazyInitializeTemplateGallery(Map<String, Object> contextualParameters) throws ConfigurationException
211    {
212        String siteName = (String) contextualParameters.get("siteName");
213        
214        Request request = ContextHelper.getRequest(_context);
215        
216        @SuppressWarnings("unchecked")
217        ThreadSafeComponentManager<ClientSideElement> oldGalleryItemManagerInSameRequest = (ThreadSafeComponentManager<ClientSideElement>) request.getAttribute(GALLERY_ITEM_MANAGER);
218        if (oldGalleryItemManagerInSameRequest != null)
219        {
220            oldGalleryItemManagerInSameRequest.dispose();
221        }
222        
223        ThreadSafeComponentManager<ClientSideElement> galleryItemManager = new ThreadSafeComponentManager<>();
224        galleryItemManager.setLogger(LoggerFactory.getLogger("cms.plugin.threadsafecomponent"));
225        galleryItemManager.service(_smanager);
226        request.setAttribute(GALLERY_ITEM_MANAGER, galleryItemManager);
227        
228        List<GalleryItem> galleryItems = new ArrayList<>();
229        request.setAttribute(GALLERY_ITEMS, galleryItems);
230        
231        Site site = _siteManager.getSite(siteName);
232        String skinId = site.getSkinId();
233        
234        Set<String> templatesId = new HashSet<>();
235        TraversableSource dir;
236        try
237        {
238            dir = (TraversableSource) _sourceResolver.resolveURI("skin:" + skinId + "://newsletter");
239            if (dir.exists())
240            {                
241                for (TraversableSource child : (Collection<TraversableSource>) dir.getChildren())
242                {
243                    if (_templateExists(skinId, child.getName()))
244                    {
245                        templatesId.add(child.getName());
246                    }
247                }
248            }
249        }
250        catch (SourceNotFoundException e)
251        {
252            // Silently ignore
253        }
254        catch (Exception e)
255        {
256            throw new ConfigurationException("Cannot find template for the skin " + skinId, e);
257        }
258        
259        if (!templatesId.isEmpty())
260        {
261            GalleryItem galleryItem = new GalleryItem();
262            
263            GalleryGroup galleryGroup = new GalleryGroup(new I18nizableText("plugin." + _RESOURCES_PLUGIN, "PLUGINS_NEWSLETTER_CATEGORY_TEMPLATESMENU_GROUP_LABEL"));
264            galleryItem.addGroup(galleryGroup);
265                
266            for (String templateId : templatesId)
267            {
268                String id = this.getId() + "." + templateId;
269                
270                try
271                {
272                    NewsletterTemplate template = new NewsletterTemplate(skinId, templateId);
273                    LifecycleHelper.setupComponent(template, new SLF4JLoggerAdapter(getLogger()), null, _manager, null, true);
274                    template.refreshValues();
275                    
276                    Configuration conf = _getTemplateItemConfiguration (id, template);
277                    galleryItemManager.addComponent(_pluginName, null, id, StaticClientSideElement.class, conf);
278                    galleryGroup.addItem(new UnresolvedItem(id, true));
279                }
280                catch (Exception e)
281                {
282                    throw new ConfigurationException("Unable to configure local client side element of id " + id, e);
283                }
284            }
285            
286            galleryItems.add(galleryItem);
287        }
288        
289        if (galleryItems.size() > 0)
290        {
291            try
292            {
293                galleryItemManager.initialize();
294            }
295            catch (Exception e)
296            {
297                throw new ConfigurationException("Unable to lookup parameter local components", e);
298            }
299        }
300    }
301    
302    private boolean _templateExists(String skinId, String id) throws IOException
303    {
304        TraversableSource skinsDir = (TraversableSource) _sourceResolver.resolveURI("skin:" + skinId + "://newsletter/" + id + "/stylesheets/template.xsl");
305        return skinsDir.exists();
306    }
307    
308    /**
309     * Get the configuration for a template item
310     * @param itemId The item id
311     * @param template The newsletter template
312     * @return The configuration
313     */
314    protected Configuration _getTemplateItemConfiguration (String itemId, NewsletterTemplate template)
315    {
316        DefaultConfiguration conf = new DefaultConfiguration("extension");
317        conf.setAttribute("id", itemId);
318        
319        DefaultConfiguration classConf = new DefaultConfiguration("class");
320        classConf.setAttribute("name", "Ametys.ribbon.element.ui.ButtonController");
321        
322        // enable toggle
323        DefaultConfiguration enableToggleConf = new DefaultConfiguration("toggle-enabled");
324        enableToggleConf.setValue("true");
325        classConf.addChild(enableToggleConf);
326        
327        // Label
328        DefaultConfiguration labelConf = new DefaultConfiguration("label");
329        labelConf.setValue(_i18nUtils.translate(template.getLabel()));
330        classConf.addChild(labelConf);
331        
332        // Description
333        DefaultConfiguration descConf = new DefaultConfiguration("description");
334        descConf.setValue(_i18nUtils.translate(template.getDescription()));
335        classConf.addChild(descConf);
336        
337        // Name
338        DefaultConfiguration nameConf = new DefaultConfiguration("name");
339        nameConf.setValue(template.getId());
340        classConf.addChild(nameConf);
341        
342        // Icons
343        DefaultConfiguration iconSmallConf = new DefaultConfiguration("icon-small");
344        iconSmallConf.setValue(template.getSmallImage());
345        classConf.addChild(iconSmallConf);
346        DefaultConfiguration iconMediumConf = new DefaultConfiguration("icon-medium");
347        iconMediumConf.setValue(template.getMediumImage());
348        classConf.addChild(iconMediumConf);
349        DefaultConfiguration iconLargeConf = new DefaultConfiguration("icon-large");
350        iconLargeConf.setValue(template.getLargeImage());
351        classConf.addChild(iconLargeConf);
352        
353     // Common configuration
354        @SuppressWarnings("unchecked")
355        Map<String, Object> commonConfig = (Map<String, Object>) this._script.getParameters().get("items-config");
356        for (String tagName : commonConfig.keySet())
357        {
358            DefaultConfiguration c = new DefaultConfiguration(tagName);
359            c.setValue(String.valueOf(commonConfig.get(tagName)));
360            classConf.addChild(c);
361        }
362        
363        conf.addChild(classConf);
364        return conf;
365    }
366}