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