001/*
002 *  Copyright 2012 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.web.clientsideelement;
017
018import java.util.HashMap;
019import java.util.Map;
020import java.util.Set;
021
022import org.apache.avalon.framework.configuration.Configuration;
023import org.apache.avalon.framework.configuration.ConfigurationException;
024import org.apache.avalon.framework.configuration.DefaultConfiguration;
025import org.apache.avalon.framework.service.ServiceException;
026import org.apache.avalon.framework.service.ServiceManager;
027import org.apache.commons.lang.StringUtils;
028
029import org.ametys.cms.contenttype.ContentType;
030import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
031import org.ametys.cms.contenttype.ContentTypesHelper;
032import org.ametys.cms.repository.Content;
033import org.ametys.core.DevMode;
034import org.ametys.core.DevMode.DEVMODE;
035import org.ametys.core.observation.Event;
036import org.ametys.core.observation.ObservationManager;
037import org.ametys.core.ui.Callable;
038import org.ametys.core.ui.SimpleMenu;
039import org.ametys.core.ui.StaticClientSideElement;
040import org.ametys.core.util.I18nUtils;
041import org.ametys.plugins.repository.AmetysObject;
042import org.ametys.plugins.repository.AmetysObjectResolver;
043import org.ametys.runtime.i18n.I18nizableText;
044import org.ametys.runtime.model.View;
045import org.ametys.web.ObservationConstants;
046import org.ametys.web.contenttype.SkinContentViewHelper;
047import org.ametys.web.contenttype.SkinContentViewHelper.SkinContentView;
048import org.ametys.web.repository.page.ContentTypesAssignmentHandler;
049import org.ametys.web.repository.page.ModifiableZoneItem;
050import org.ametys.web.repository.page.SitemapElement;
051import org.ametys.web.repository.page.ZoneItem.ZoneType;
052import org.ametys.web.skin.SkinsManager;
053
054/**
055 * Menu giving the possibility to change the view rendering the content in a given ZoneItem.
056 */
057public class SetContentViewMenu extends SimpleMenu
058{
059    /** The content type extension point. */
060    protected ContentTypeExtensionPoint _contentTypeExtensionPoint;
061    
062    /** The content types assignment handler */
063    protected ContentTypesAssignmentHandler _cTypeHandler;
064    
065    /** The content types helper */
066    protected ContentTypesHelper _contentTypesHelper;
067    
068    /** Repository content */
069    protected AmetysObjectResolver _resolver;
070    
071    /** Helper for skin views */
072    protected SkinContentViewHelper _skinContentViewHelper;
073    
074    /** The observation manager */
075    protected ObservationManager _observationManager;
076
077    /** The skins manager */
078    protected SkinsManager _skinsManager;
079    
080    /** The i18n utils */
081    protected I18nUtils _i18nUtils;
082    
083    private boolean _galleryInitialized;
084
085    
086    @Override
087    public void service(ServiceManager serviceManager) throws ServiceException
088    {
089        super.service(serviceManager);
090        _contentTypeExtensionPoint = (ContentTypeExtensionPoint) serviceManager.lookup(ContentTypeExtensionPoint.ROLE);
091        _cTypeHandler = (ContentTypesAssignmentHandler) serviceManager.lookup(ContentTypesAssignmentHandler.ROLE);
092        _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE);
093        _i18nUtils = (I18nUtils) serviceManager.lookup(I18nUtils.ROLE);
094        _contentTypesHelper = (ContentTypesHelper) serviceManager.lookup(ContentTypesHelper.ROLE);
095        _observationManager = (ObservationManager) serviceManager.lookup(ObservationManager.ROLE);
096        _skinsManager = (SkinsManager) serviceManager.lookup(SkinsManager.ROLE);
097        _skinContentViewHelper = (SkinContentViewHelper) serviceManager.lookup(SkinContentViewHelper.ROLE);
098    }
099    
100    @Override
101    protected void _getGalleryItems(Map<String, Object> parameters, Map<String, Object> contextualParameters)
102    {
103        try
104        {
105            _lazyInitializeContentViewGallery();
106        }
107        catch (Exception e)
108        {
109            throw new IllegalStateException("Unable to lookup client side element local components", e);
110        }
111        
112        super._getGalleryItems(parameters, contextualParameters);
113    }
114    
115    private synchronized void _lazyInitializeContentViewGallery() throws ConfigurationException
116    {
117        DEVMODE devMode = DevMode.getDeveloperMode(null);
118        
119        if (!_galleryInitialized || devMode == DEVMODE.DEVELOPMENT || devMode == DEVMODE.SUPER_DEVELOPPMENT)
120        {
121            if (_galleryInitialized)
122            {
123                // reinitialize the gallery items
124                _galleryItems.clear();
125                _initializeGalleryItemManager();
126            }
127            
128            Set<String> contentTypesIds = _contentTypeExtensionPoint.getExtensionsIds();
129            
130            if (contentTypesIds.size() > 0)
131            {
132                GalleryItem galleryItem = new GalleryItem();
133                
134                for (String contentTypeId : contentTypesIds)
135                {
136                    ContentType contentType = _contentTypeExtensionPoint.getExtension(contentTypeId);
137                    
138                    GalleryGroup galleryGroup = new GalleryGroup(contentType.getLabel());
139                    galleryItem.addGroup(galleryGroup);
140                    
141                    for (String viewName : contentType.getViewNames(false))
142                    {
143                        View view = contentType.getView(viewName);
144                        
145                        String id = this.getId() + "." + contentTypeId + "." + viewName;
146                        
147                        Configuration conf = _getViewItemConfiguration(id, view, contentTypeId);
148                        _getGalleryItemManager().addComponent(_pluginName, null, id, StaticClientSideElement.class, conf);
149                        galleryGroup.addItem(new UnresolvedItem(id, true));
150                    }
151                    
152                    // Get other views from all skins
153                    for (String skinName : _skinsManager.getSkins())
154                    {
155                        Set<SkinContentView> skinViews = _skinContentViewHelper.getContentViewsFromSkin(skinName, contentType);
156                        for (SkinContentView skinView : skinViews)
157                        {
158                            String id = this.getId() + "." + skinName + "." + contentTypeId + "." + skinView.name();
159                            Configuration conf = _getViewItemConfiguration(id, skinView, contentTypeId);
160                            _getGalleryItemManager().addComponent(_pluginName, null, id, StaticClientSideElement.class, conf);
161                            galleryGroup.addItem(new UnresolvedItem(id, true));
162                        }
163                    }
164                }
165                
166                _galleryItems.add(galleryItem);
167            }
168            
169            if (_galleryItems.size() > 0)
170            {
171                try
172                {
173                    _getGalleryItemManager().initialize();
174                }
175                catch (Exception e)
176                {
177                    throw new ConfigurationException("Unable to lookup parameter local components", e);
178                }
179            }
180        }
181        
182        _galleryInitialized = true;
183    }
184    
185    /**
186     * Get the configuration for a view item
187     * @param itemId The item id
188     * @param view The view
189     * @param cTypeId The content type id
190     * @return The configuration
191     */
192    protected Configuration _getViewItemConfiguration(String itemId, View view, String cTypeId)
193    {
194        return new ViewItemConfigurationBuilder(itemId, this._script, _i18nUtils)
195                    .withName(view.getName())
196                    .withLabel(view.getLabel())
197                    .withDescription(view.getDescription())
198                    .withContentType(cTypeId)
199                    .withIconGlyph(view.getIconGlyph(), view.getIconDecorator())
200                    .withIcon(view.getSmallIcon(), view.getMediumIcon(), view.getLargeIcon())
201                    .build();
202    }
203    
204    /**
205     * Get the configuration for a skin view item
206     * @param itemId The item id
207     * @param view The skin view
208     * @param cTypeId The content type id
209     * @return The configuration
210     */
211    protected Configuration _getViewItemConfiguration(String itemId, SkinContentView view, String cTypeId)
212    {
213        return new ViewItemConfigurationBuilder(itemId, this._script, _i18nUtils)
214                .withName(view.name())
215                .withSkin(view.skinName())
216                .withLabel(view.label())
217                .withDescription(view.description())
218                .withContentType(cTypeId)
219                .withIconGlyph(view.iconGlyph(), view.iconDecorator())
220                .withIcon(view.iconSmall(), view.iconMedium(), view.iconLarge())
221                .build();
222    }
223    
224    
225    /**
226     * Builder to get a view item configuration
227     *
228     */
229    public static class ViewItemConfigurationBuilder
230    {
231        private DefaultConfiguration _classConf;
232        private I18nUtils _i18nUtils;
233        private String _itemId;
234        private Script _script;
235
236        ViewItemConfigurationBuilder(String itemId, Script script, I18nUtils i18nUtils)
237        {
238            _itemId = itemId;
239            _script = script;
240            _i18nUtils = i18nUtils;
241            _classConf = new DefaultConfiguration("class");
242            _classConf.setAttribute("name", "Ametys.ribbon.element.ui.ButtonController");
243        }
244        
245        /**
246         * Set the view name. This parameter is mandatory
247         * @param viewName the view name
248         * @return this view item configuration
249         */
250        public ViewItemConfigurationBuilder withName(String viewName)
251        {
252            DefaultConfiguration nameConf = new DefaultConfiguration("name");
253            nameConf.setValue(viewName);
254            _classConf.addChild(nameConf);
255            return this;
256        }
257        
258        /**
259         * Set the skin name. May be null for a view common to all skin
260         * @param skinName the skin name
261         * @return this view item configuration
262         */
263        public ViewItemConfigurationBuilder withSkin(String skinName)
264        {
265            DefaultConfiguration nameConf = new DefaultConfiguration("skinName");
266            nameConf.setValue(skinName);
267            _classConf.addChild(nameConf);
268            return this;
269        }
270        /**
271         * Set the item's label. This parameter is mandatory
272         * @param label the label
273         * @return this view item configuration
274         */
275        public ViewItemConfigurationBuilder withLabel(I18nizableText label)
276        {
277            DefaultConfiguration labelConf = new DefaultConfiguration("label");
278            labelConf.setValue(_i18nUtils.translate(label));
279            _classConf.addChild(labelConf);
280            return this;
281        }
282        
283        /**
284         * Set the item's description. This parameter is mandatory
285         * @param description the description
286         * @return this view item configuration
287         */
288        public ViewItemConfigurationBuilder withDescription(I18nizableText description)
289        {
290            DefaultConfiguration descConf = new DefaultConfiguration("description");
291            descConf.setValue(_i18nUtils.translate(description));
292            _classConf.addChild(descConf);
293            return this;
294        }
295        
296        /**
297         * Set the content type. This parameter is mandatory
298         * @param cTypeId the content type id.
299         * @return this view item configuration
300         */
301        public ViewItemConfigurationBuilder withContentType(String cTypeId)
302        {
303            DefaultConfiguration cTypeConf = new DefaultConfiguration("contentType");
304            cTypeConf.setValue(cTypeId);
305            _classConf.addChild(cTypeConf);
306            return this;
307        }
308        
309        /**
310         * Set the icon glyph and decorator. May be null.
311         * @param iconGlyph the icon glyph. May be null.
312         * @param iconDecorator the icon decorator. May be null.
313         * @return this view item configuration
314         */
315        public ViewItemConfigurationBuilder withIconGlyph(String iconGlyph, String iconDecorator)
316        {
317            if (iconGlyph != null)
318            {
319                DefaultConfiguration iconGlyphConf = new DefaultConfiguration("icon-glyph");
320                iconGlyphConf.setValue(iconGlyph);
321                _classConf.addChild(iconGlyphConf);
322                
323                if (iconDecorator != null)
324                {
325                    DefaultConfiguration iconDecoratorConf = new DefaultConfiguration("icon-decorator");
326                    iconDecoratorConf.setValue(iconDecorator);
327                    _classConf.addChild(iconDecoratorConf);
328                }
329            }
330            
331            return this;
332        }
333        
334        /**
335         * Set the icon. May be null.
336         * @param iconSmall the small icon (16x16 pixels)
337         * @param iconMedium the medium icon (32x32 pixels)
338         * @param iconLarge the large icon (48x48 pixels)
339         * @return this view item configuration
340         */
341        public ViewItemConfigurationBuilder withIcon(String iconSmall, String iconMedium, String iconLarge)
342        {
343            if (iconMedium != null)
344            {
345                DefaultConfiguration iconSmallConf = new DefaultConfiguration("icon-small");
346                iconSmallConf.setValue(iconSmall);
347                _classConf.addChild(iconSmallConf);
348                DefaultConfiguration iconMediumConf = new DefaultConfiguration("icon-medium");
349                iconMediumConf.setValue(iconMedium);
350                _classConf.addChild(iconMediumConf);
351                DefaultConfiguration iconLargeConf = new DefaultConfiguration("icon-large");
352                iconLargeConf.setValue(iconLarge);
353                _classConf.addChild(iconLargeConf);
354            }
355            return this;
356        }
357        
358        /**
359         * Build the configuration
360         * @return the configuration
361         */
362        public Configuration build()
363        {
364            DefaultConfiguration conf = new DefaultConfiguration("extension");
365            conf.setAttribute("id", _itemId);
366            
367            // Common configuration
368            @SuppressWarnings("unchecked")
369            Map<String, Object> commonConfig = (Map<String, Object>) _script.getParameters().get("items-config");
370            for (String tagName : commonConfig.keySet())
371            {
372                DefaultConfiguration c = new DefaultConfiguration(tagName);
373                c.setValue(String.valueOf(commonConfig.get(tagName)));
374                _classConf.addChild(c);
375            }
376            
377            conf.addChild(_classConf);
378            return conf;
379        }
380    }
381    
382    /**
383     * Set the view of a content
384     * @param zoneItemId the id of the zone item
385     * @param viewName the name of the view to use
386      */
387    @Callable
388    public void setContentView(String zoneItemId, String viewName)
389    {
390        AmetysObject object = _resolver.resolveById(zoneItemId);
391        if (!(object instanceof ModifiableZoneItem))
392        {
393            throw new IllegalArgumentException("The provided ID '" + zoneItemId + "' does not reference a modifiable zone item.");
394        }
395        
396        ModifiableZoneItem zoneItem = (ModifiableZoneItem) object;
397        
398        if (!zoneItem.getType().equals(ZoneType.CONTENT))
399        {
400            throw new IllegalArgumentException("The zone item '" + zoneItemId + "' does not contain a content.");
401        }
402        
403        Content content = zoneItem.getContent();
404        
405        String modelViewName = viewName;
406        SkinContentView skinContentView = _skinContentViewHelper.getContentViewFromSkin(viewName, content);
407        if (skinContentView != null)
408        {
409            // the requested view is a skin view, get the associated model view to check model
410            modelViewName = skinContentView.modelViewName();
411        }
412        
413        View view = _contentTypesHelper.getView(modelViewName, content.getTypes(), content.getMixinTypes());
414        if (view == null)
415        {
416            throw new IllegalArgumentException("The view '" + viewName + "' can't be assigned on the content '" + StringUtils.join(content.getTypes(), ",")  + "'.");
417        }
418        
419        if (getLogger().isDebugEnabled())
420        {
421            getLogger().debug("Setting view '" + viewName + "' in the content zone item '" + zoneItemId + "'.");
422        }
423        
424        // Set the view name.
425        zoneItem.setViewName(viewName);
426        
427        zoneItem.saveChanges();
428        
429        SitemapElement sitemapElement = zoneItem.getZone().getSitemapElement();
430        Map<String, Object> eventParams = new HashMap<>();
431        eventParams.put(ObservationConstants.ARGS_SITEMAP_ELEMENT, sitemapElement);
432        eventParams.put(ObservationConstants.ARGS_ZONE_ITEM_ID, zoneItemId);
433        eventParams.put(ObservationConstants.ARGS_ZONE_TYPE, ZoneType.CONTENT);
434        _observationManager.notify(new Event(ObservationConstants.EVENT_ZONEITEM_MOVED, _currentUserProvider.getUser(), eventParams));
435    }
436}