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