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