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}