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