001/* 002 * Copyright 2010 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.cms.clientsideelement; 017 018import java.util.ArrayList; 019import java.util.Comparator; 020import java.util.HashSet; 021import java.util.LinkedHashMap; 022import java.util.List; 023import java.util.Map; 024import java.util.Optional; 025import java.util.Set; 026import java.util.TreeMap; 027import java.util.TreeSet; 028 029import org.apache.avalon.framework.configuration.Configuration; 030import org.apache.avalon.framework.configuration.ConfigurationException; 031import org.apache.avalon.framework.configuration.DefaultConfiguration; 032import org.apache.avalon.framework.service.ServiceException; 033import org.apache.avalon.framework.service.ServiceManager; 034 035import org.ametys.cms.content.RootContentHelper; 036import org.ametys.cms.contenttype.ContentType; 037import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 038import org.ametys.cms.languages.LanguagesManager; 039import org.ametys.core.right.RightManager.RightResult; 040import org.ametys.core.ui.ClientSideElement; 041import org.ametys.core.ui.SimpleMenu; 042import org.ametys.core.ui.StaticClientSideElement; 043import org.ametys.core.user.UserIdentity; 044import org.ametys.core.util.I18nUtils; 045import org.ametys.runtime.i18n.I18nizableText; 046 047/** 048 * This element creates a menu with one gallery item per content type classified by category (default mode). 049 * 050 * This element supports an alternative display, to display content types into sub menu items (root menu items will be the categories), 051 * if 'show-in-menu' parameters is set to 'true'. This mode should be privileged if the number of content types is important (> 30) 052 * 053 * The user rights are checked. 054 */ 055public class ContentTypesGallery extends SimpleMenu 056{ 057 /** The list of content types */ 058 protected ContentTypeExtensionPoint _contentTypeExtensionPoint; 059 /** The language manager */ 060 protected LanguagesManager _languagesManager; 061 062 /** The i18n utils */ 063 protected I18nUtils _i18nUtils; 064 065 /** Helper for root content */ 066 protected RootContentHelper _rootContentHelper; 067 068 private boolean _contentTypesInitialized; 069 070 private Set<String> _addedComponents; 071 072 @Override 073 public void service(ServiceManager smanager) throws ServiceException 074 { 075 super.service(smanager); 076 _contentTypeExtensionPoint = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE); 077 _i18nUtils = (I18nUtils) smanager.lookup(I18nUtils.ROLE); 078 _languagesManager = (LanguagesManager) smanager.lookup(LanguagesManager.ROLE); 079 _rootContentHelper = (RootContentHelper) smanager.lookup(RootContentHelper.ROLE); 080 } 081 082 @Override 083 protected Script _configureScript(Configuration configuration) throws ConfigurationException 084 { 085 Script script = super._configureScript(configuration); 086 087 // Add the CSS brought by content types 088 for (String id: _contentTypeExtensionPoint.getExtensionsIds()) 089 { 090 ContentType contentType = _contentTypeExtensionPoint.getExtension(id); 091 script.getCSSFiles().addAll(contentType.getCSSFiles()); 092 } 093 094 return script; 095 } 096 097 @Override 098 protected void _getGalleryItems(Map<String, Object> parameters, Map<String, Object> contextualParameters) 099 { 100 if (!_showInMenu()) 101 { 102 // In this mode (default mode), the available content types will be displayed into gallery items, classified by category 103 104 try 105 { 106 _lazyInitializeContentTypeGallery(); 107 } 108 catch (Exception e) 109 { 110 throw new IllegalStateException("Unable to lookup client side element local components", e); 111 } 112 113 if (_galleryItems.size() > 0) 114 { 115 parameters.put("gallery-item", new LinkedHashMap<String, Object>()); 116 117 for (GalleryItem galleryItem : _galleryItems) 118 { 119 @SuppressWarnings("unchecked") 120 Map<String, Object> galleryItems = (Map<String, Object>) parameters.get("gallery-item"); 121 galleryItems.put("gallery-groups", new ArrayList<>()); 122 123 for (GalleryGroup galleryGp : galleryItem.getGroups()) 124 { 125 @SuppressWarnings("unchecked") 126 List<Object> galleryGroups = (List<Object>) galleryItems.get("gallery-groups"); 127 128 Map<String, Object> groupParams = new LinkedHashMap<>(); 129 130 I18nizableText label = galleryGp.getLabel(); 131 groupParams.put("label", label); 132 133 // Group items 134 List<String> gpItems = new ArrayList<>(); 135 for (ClientSideElement element : galleryGp.getItems()) 136 { 137 String cTypeId = element.getId().substring(this.getId().length() + 1); 138 ContentType cType = _contentTypeExtensionPoint.getExtension(cTypeId); 139 140 if (hasRight(cType)) 141 { 142 gpItems.add(element.getId()); 143 } 144 } 145 146 if (gpItems.size() > 0) 147 { 148 groupParams.put("items", gpItems); 149 galleryGroups.add(groupParams); 150 } 151 } 152 } 153 } 154 } 155 } 156 157 private boolean _showInMenu() 158 { 159 return _script.getParameters().containsKey("show-in-menu") && _script.getParameters().get("show-in-menu").equals("true"); 160 } 161 162 private void _lazyInitializeContentTypeGallery() throws ConfigurationException 163 { 164 if (!_contentTypesInitialized) 165 { 166 _addedComponents = new HashSet<>(); 167 168 Map<I18nizableText, Set<ContentType>> cTypesByGroup = _getContentTypesByGroup(); 169 170 if (cTypesByGroup.size() > 0) 171 { 172 GalleryItem galleryItem = new GalleryItem(); 173 174 for (I18nizableText groupLabel : cTypesByGroup.keySet()) 175 { 176 GalleryGroup galleryGroup = new GalleryGroup(groupLabel); 177 galleryItem.addGroup(galleryGroup); 178 179 Set<ContentType> cTypes = cTypesByGroup.get(groupLabel); 180 for (ContentType cType : cTypes) 181 { 182 String id = this.getId() + "-" + cType.getId(); 183 184 DefaultConfiguration conf = new DefaultConfiguration("extension"); 185 conf.setAttribute("id", id); 186 _addContentTypeConfiguration (conf, cType); 187 188 if (!_addedComponents.contains(id)) 189 { 190 _galleryItemManager.addComponent(_pluginName, null, id, StaticClientSideElement.class, conf); 191 galleryGroup.addItem(new UnresolvedItem(id, true)); 192 _addedComponents.add(id); 193 } 194 } 195 } 196 197 _galleryItems.add(galleryItem); 198 } 199 200 if (_galleryItems.size() > 0) 201 { 202 try 203 { 204 _galleryItemManager.initialize(); 205 } 206 catch (Exception e) 207 { 208 throw new ConfigurationException("Unable to lookup parameter local components", e); 209 } 210 } 211 } 212 213 _contentTypesInitialized = true; 214 } 215 216 private void _lazyInitializeContentTypeMenu() 217 { 218 if (!_contentTypesInitialized) 219 { 220 _addedComponents = new HashSet<>(); 221 222 Map<I18nizableText, Set<ContentType>> cTypesByGroup = _getContentTypesByGroup(); 223 224 if (cTypesByGroup.size() > 0) 225 { 226 for (I18nizableText groupLabel : cTypesByGroup.keySet()) 227 { 228 String id = this.getId() + "-" + org.ametys.core.util.StringUtils.generateKey(); 229 DefaultConfiguration conf = new DefaultConfiguration("extension"); 230 conf.setAttribute("id", id); 231 232 Set<ContentType> cTypes = cTypesByGroup.get(groupLabel); 233 _addGroupContentTypeConfiguration (conf, groupLabel, cTypes); 234 235 if (!_addedComponents.contains(id)) 236 { 237 _menuItemManager.addComponent(_pluginName, null, id, SimpleMenu.class, conf); 238 _unresolvedMenuItems.add(new UnresolvedItem(id, true)); 239 _addedComponents.add(id); 240 } 241 } 242 } 243 } 244 245 _contentTypesInitialized = true; 246 } 247 248 /** 249 * Get the configuration of the content type item 250 * @param rootConf The root configuration 251 * @param groupLabel The group's label 252 * @param cTypes The content types of the group 253 */ 254 protected void _addGroupContentTypeConfiguration (DefaultConfiguration rootConf, I18nizableText groupLabel, Set<ContentType> cTypes) 255 { 256 DefaultConfiguration classConf = new DefaultConfiguration("class"); 257 classConf.setAttribute("name", "Ametys.plugins.cms.content.controller.ContentTypeMenuItemController"); 258 259 // Label and description 260 DefaultConfiguration labelConf = new DefaultConfiguration("label"); 261 DefaultConfiguration descConf = new DefaultConfiguration("description"); 262 if (groupLabel.isI18n()) 263 { 264 labelConf.setAttribute("i18n", "true"); 265 labelConf.setValue(groupLabel.getCatalogue() + ":" + groupLabel.getKey()); 266 descConf.setAttribute("i18n", "true"); 267 descConf.setValue(groupLabel.getCatalogue() + ":" + groupLabel.getKey()); 268 } 269 else 270 { 271 labelConf.setValue(groupLabel.getLabel()); 272 descConf.setValue(groupLabel.getLabel()); 273 } 274 classConf.addChild(labelConf); 275 classConf.addChild(descConf); 276 277 // Icons or glyph (use the first content type of the list) 278 ContentType firstCtype = cTypes.iterator().next(); 279 if (firstCtype.getIconGlyph() != null) 280 { 281 DefaultConfiguration iconGlyphConf = new DefaultConfiguration("icon-glyph"); 282 iconGlyphConf.setValue(firstCtype.getIconGlyph()); 283 classConf.addChild(iconGlyphConf); 284 } 285 if (firstCtype.getIconDecorator() != null) 286 { 287 DefaultConfiguration iconDecoratorConf = new DefaultConfiguration("icon-decorator"); 288 iconDecoratorConf.setValue(firstCtype.getIconDecorator()); 289 classConf.addChild(iconDecoratorConf); 290 } 291 if (firstCtype.getSmallIcon() != null) 292 { 293 DefaultConfiguration iconSmallConf = new DefaultConfiguration("icon-small"); 294 iconSmallConf.setValue(firstCtype.getSmallIcon()); 295 classConf.addChild(iconSmallConf); 296 DefaultConfiguration iconMediumConf = new DefaultConfiguration("icon-medium"); 297 iconMediumConf.setValue(firstCtype.getMediumIcon()); 298 classConf.addChild(iconMediumConf); 299 DefaultConfiguration iconLargeConf = new DefaultConfiguration("icon-large"); 300 iconLargeConf.setValue(firstCtype.getLargeIcon()); 301 classConf.addChild(iconLargeConf); 302 } 303 304 // Common configuration 305 @SuppressWarnings("unchecked") 306 Map<String, Object> commonConfig = (Map<String, Object>) this._script.getParameters().get("group-items-config"); 307 for (String tagName : commonConfig.keySet()) 308 { 309 DefaultConfiguration c = new DefaultConfiguration(tagName); 310 c.setValue(String.valueOf(commonConfig.get(tagName))); 311 classConf.addChild(c); 312 } 313 314 rootConf.addChild(classConf); 315 316 // Menu items 317 DefaultConfiguration menuItemsConf = new DefaultConfiguration("menu-items"); 318 319 for (ContentType contentType : cTypes) 320 { 321 DefaultConfiguration menuItemConf = new DefaultConfiguration("item"); 322 menuItemConf.setAttribute("id", this.getId() + "-" + contentType.getId()); 323 _addContentTypeConfiguration (menuItemConf, contentType); 324 325 menuItemsConf.addChild(menuItemConf); 326 } 327 328 rootConf.addChild(menuItemsConf); 329 } 330 331 /** 332 * Get the configuration of the content type item 333 * @param rootConf The root configuration 334 * @param cType The content type 335 */ 336 protected void _addContentTypeConfiguration (DefaultConfiguration rootConf, ContentType cType) 337 { 338 DefaultConfiguration classConf = new DefaultConfiguration("class"); 339 classConf.setAttribute("name", "Ametys.ribbon.element.ui.ButtonController"); 340 341 // Parent controller id 342 DefaultConfiguration parentIdConf = new DefaultConfiguration("controllerParentId"); 343 parentIdConf.setValue(this.getId()); 344 classConf.addChild(parentIdConf); 345 346 // Label 347 DefaultConfiguration labelConf = new DefaultConfiguration("label"); 348 I18nizableText label = cType.getLabel(); 349 if (label.isI18n()) 350 { 351 labelConf.setAttribute("i18n", "true"); 352 labelConf.setValue(label.getCatalogue() + ":" + label.getKey()); 353 } 354 else 355 { 356 labelConf.setValue(label.getLabel()); 357 } 358 classConf.addChild(labelConf); 359 360 // Description 361 DefaultConfiguration descConf = new DefaultConfiguration("description"); 362 I18nizableText description = cType.getDescription(); 363 if (description.isI18n()) 364 { 365 descConf.setAttribute("i18n", "true"); 366 descConf.setValue(description.getCatalogue() + ":" + description.getKey()); 367 } 368 else 369 { 370 descConf.setValue(description.getLabel()); 371 } 372 classConf.addChild(descConf); 373 374 // Default content title 375 DefaultConfiguration defaultTitleConf = new DefaultConfiguration("defaultContentTitle"); 376 I18nizableText defaultTitle = cType.getDefaultTitle(); 377 if (defaultTitle.isI18n()) 378 { 379 defaultTitleConf.setAttribute("i18n", "true"); 380 defaultTitleConf.setValue(defaultTitle.getCatalogue() + ":" + defaultTitle.getKey()); 381 } 382 else 383 { 384 defaultTitleConf.setValue(defaultTitle.getLabel()); 385 } 386 classConf.addChild(defaultTitleConf); 387 388 // Content type 389 DefaultConfiguration typeConf = new DefaultConfiguration("contentTypes"); 390 typeConf.setValue(cType.getId()); 391 classConf.addChild(typeConf); 392 393 // Icons or glyph 394 if (cType.getIconGlyph() != null) 395 { 396 DefaultConfiguration iconGlyphConf = new DefaultConfiguration("icon-glyph"); 397 iconGlyphConf.setValue(cType.getIconGlyph()); 398 classConf.addChild(iconGlyphConf); 399 } 400 if (cType.getIconDecorator() != null) 401 { 402 DefaultConfiguration iconDecoratorConf = new DefaultConfiguration("icon-decorator"); 403 iconDecoratorConf.setValue(cType.getIconDecorator()); 404 classConf.addChild(iconDecoratorConf); 405 } 406 if (cType.getSmallIcon() != null) 407 { 408 DefaultConfiguration iconSmallConf = new DefaultConfiguration("icon-small"); 409 iconSmallConf.setValue(cType.getSmallIcon()); 410 classConf.addChild(iconSmallConf); 411 DefaultConfiguration iconMediumConf = new DefaultConfiguration("icon-medium"); 412 iconMediumConf.setValue(cType.getMediumIcon()); 413 classConf.addChild(iconMediumConf); 414 DefaultConfiguration iconLargeConf = new DefaultConfiguration("icon-large"); 415 iconLargeConf.setValue(cType.getLargeIcon()); 416 classConf.addChild(iconLargeConf); 417 } 418 419 // Common configuration 420 @SuppressWarnings("unchecked") 421 Map<String, Object> commonConfig = (Map<String, Object>) this._script.getParameters().get("items-config"); 422 for (String tagName : commonConfig.keySet()) 423 { 424 DefaultConfiguration c = new DefaultConfiguration(tagName); 425 c.setValue(String.valueOf(commonConfig.get(tagName))); 426 classConf.addChild(c); 427 } 428 429 rootConf.addChild(classConf); 430 431 _addRightsOnContentTypeConfiguration(rootConf); 432 } 433 434 /** 435 * Get the 'rights' configuration of the content type item 436 * @param rootConf The root configuration 437 */ 438 protected void _addRightsOnContentTypeConfiguration(DefaultConfiguration rootConf) 439 { 440 DefaultConfiguration rightsConf = new DefaultConfiguration("rights"); 441 rightsConf.setAttribute("mode", _rightsMode); 442 for (String rightId : _rights.keySet()) 443 { 444 DefaultConfiguration rightConf = new DefaultConfiguration("right"); 445 Optional.ofNullable(_rights.get(rightId)).ifPresent(contextPrefix -> rightConf.setAttribute("context-prefix", contextPrefix)); 446 rightConf.setValue(rightId); 447 rightsConf.addChild(rightConf); 448 } 449 rootConf.addChild(rightsConf); 450 } 451 452 /** 453 * Get the list of content types classified by groups 454 * @return The content types 455 */ 456 protected Map<I18nizableText, Set<ContentType>> _getContentTypesByGroup () 457 { 458 Map<I18nizableText, Set<ContentType>> groups = new TreeMap<>(new I18nizableTextComparator()); 459 460 if (this._script.getParameters().get("contentTypes") != null) 461 { 462 String[] contentTypesIds = ((String) this._script.getParameters().get("contentTypes")).split(","); 463 464 boolean allowInherit = "true".equals(this._script.getParameters().get("allowInherit")); 465 466 for (String contentTypeId : contentTypesIds) 467 { 468 ContentType contentType = _contentTypeExtensionPoint.getExtension(contentTypeId); 469 470 if (isValidContentType(contentType)) 471 { 472 addContentType (contentType, groups); 473 } 474 475 if (allowInherit) 476 { 477 for (String subTypeId : _contentTypeExtensionPoint.getSubTypes(contentTypeId)) 478 { 479 ContentType subContentType = _contentTypeExtensionPoint.getExtension(subTypeId); 480 if (isValidContentType(subContentType)) 481 { 482 addContentType (subContentType, groups); 483 } 484 } 485 } 486 } 487 } 488 else 489 { 490 Set<String> contentTypesIds = _contentTypeExtensionPoint.getExtensionsIds(); 491 492 for (String contentTypeId : contentTypesIds) 493 { 494 ContentType contentType = _contentTypeExtensionPoint.getExtension(contentTypeId); 495 496 if (isValidContentType(contentType)) 497 { 498 addContentType (contentType, groups); 499 } 500 } 501 } 502 503 return groups; 504 } 505 506 /** 507 * Add content to groups 508 * @param contentType The content type 509 * @param groups The groups 510 */ 511 protected void addContentType (ContentType contentType, Map<I18nizableText, Set<ContentType>> groups) 512 { 513 I18nizableText group = contentType.getCategory(); 514 if ((group.isI18n() && group.getKey().isEmpty()) || (!group.isI18n() && group.getLabel().isEmpty())) 515 { 516 group = new I18nizableText("plugin.cms", "PLUGINS_CMS_CONTENT_CREATECONTENTMENU_GROUP_10_CONTENT"); 517 } 518 519 if (!groups.containsKey(group)) 520 { 521 groups.put(group, new TreeSet<>(new ContentTypeComparator())); 522 } 523 Set<ContentType> cTypes = groups.get(group); 524 cTypes.add(contentType); 525 } 526 527 @Override 528 public List<Script> getScripts(boolean ignoreRights, Map<String, Object> contextParameters) 529 { 530 if (_showInMenu()) 531 { 532 // In this mode, the available content types will be displayed into menu items, classified by category (sub menu items) 533 try 534 { 535 _lazyInitializeContentTypeMenu(); 536 } 537 catch (Exception e) 538 { 539 throw new IllegalStateException("Unable to lookup client side element local components", e); 540 } 541 } 542 543 for (String id: _contentTypeExtensionPoint.getExtensionsIds()) 544 { 545 ContentType contentType = _contentTypeExtensionPoint.getExtension(id); 546 if (isValidContentType(contentType)) 547 { 548 return super.getScripts(ignoreRights, contextParameters); 549 } 550 } 551 return new ArrayList<>(); 552 } 553 554 /** 555 * Determines if the content type is a valid content type for the gallery 556 * @param contentType The coentent 557 * @return true if it is a valid content type 558 */ 559 protected boolean isValidContentType (ContentType contentType) 560 { 561 return !contentType.isAbstract() && !contentType.isPrivate() && !contentType.isMixin(); 562 } 563 564 /** 565 * Test if the current user has the right needed by the content type to create a content. 566 * @param cType the content type 567 * @return true if the user has the right needed, false otherwise. 568 */ 569 protected boolean hasRight(ContentType cType) 570 { 571 String right = cType.getRight(); 572 573 if (right == null) 574 { 575 return true; 576 } 577 else 578 { 579 UserIdentity user = _currentUserProvider.getUser(); 580 return _rightManager.hasRight(user, right, "/cms") == RightResult.RIGHT_ALLOW || _rightManager.hasRight(user, right, _rootContentHelper.getRootContent()) == RightResult.RIGHT_ALLOW; 581 } 582 } 583 584 class ContentTypeComparator implements Comparator<ContentType> 585 { 586 @Override 587 public int compare(ContentType c1, ContentType c2) 588 { 589 if (c1 == c2) 590 { 591 return 0; 592 } 593 594 I18nizableText t1 = c1.getLabel(); 595 I18nizableText t2 = c2.getLabel(); 596 597 String str1 = t1.isI18n() ? t1.getKey() : t1.getLabel(); 598 String str2 = t2.isI18n() ? t2.getKey() : t2.getLabel(); 599 600 int compareTo = str1.toString().compareTo(str2.toString()); 601 if (compareTo == 0) 602 { 603 // Content types have same keys but there are not equals, so do not return 0 to add it in TreeSet 604 // Indeed, in a TreeSet implementation two elements that are equal by the method compareTo are, from the standpoint of the set, equal 605 return 1; 606 } 607 return compareTo; 608 } 609 } 610 611 class I18nizableTextComparator implements Comparator<I18nizableText> 612 { 613 @Override 614 public int compare(I18nizableText t1, I18nizableText t2) 615 { 616 String str1 = t1.isI18n() ? t1.getKey() : t1.getLabel(); 617 String str2 = t2.isI18n() ? t2.getKey() : t2.getLabel(); 618 619 return str1.toString().compareTo(str2.toString()); 620 } 621 } 622 623}